diff --git a/notebooks/chapter_1_introduction/tutorial_1_models.ipynb b/notebooks/chapter_1_introduction/tutorial_1_models.ipynb index 25e2c3b..ae1b571 100644 --- a/notebooks/chapter_1_introduction/tutorial_1_models.ipynb +++ b/notebooks/chapter_1_introduction/tutorial_1_models.ipynb @@ -1,865 +1,775 @@ { - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Tutorial 1: Models\n", - "==================\n", - "\n", - "At the heart of model-fitting is the model: a set of equations, numerical processes, and assumptions describing a\n", - "physical system of interest. The goal of model-fitting is to better understand this physical system and develop\n", - "predictive models that describe it more accurately.\n", - "\n", - "In astronomy, a model might describe the distribution of stars within a galaxy. In biology, it might represent the\n", - "interaction of proteins within a cell. In finance, it could describe the evolution of stock prices in a market.\n", - "Regardless of the field, the model acts as a mathematical description of the physical system, aiming to enhance\n", - "understanding and enable new predictions.\n", - "\n", - "Whatever your model, its equations are defined by \"free parameters.\" Changing these parameters alters the\n", - "behavior and predictions of the model.\n", - "\n", - "Once the model is defined and parameter values are chosen, the model creates \"model data\"\u2014a realization of how the\n", - "physical system appears given those parameters. This process, often referred to as \"forward modeling,\" describes the\n", - "physical system from its starting point and predicts the data we observe.\n", - "\n", - "By varying the model parameters, we can generate numerous model datasets. The ultimate goal of model-fitting, which\n", - "you will learn by the end of this chapter, is to determine the model parameters and corresponding dataset that best\n", - "fit the observed data.\n", - "\n", - "__Astronomy Example__\n", - "\n", - "For instance, in astronomy, we might model the distribution of stars, including:\n", - "\n", - "- A parameter describing the brightness of the stars.\n", - "\n", - "- Multiple parameters defining their distribution.\n", - "\n", - "- Several parameters describing their colors.\n", - "\n", - "If our model pertains to the distribution of stars within a galaxy, the forward model will produce an image of what\n", - "that galaxy looks like when observed with a telescope. This forward model might account for physical effects such as\n", - "the blurring of light due to diffraction in the telescope optics.\n", - "\n", - "By altering the parameters describing the stars, we can generate many different model images via this forward model.\n", - "\n", - "At the end of this chapter, we will use a real-world astronomy example to illustrate everything you have learned,\n", - "including fitting a real galaxy observed with the Hubble Space Telescope.\n", - "\n", - "__Overview__\n", - "\n", - "In tutorial 1, we will cover the basics of defining a model, specifically:\n", - "\n", - "- Defining a simple model described by a few simple equations.\n", - "\n", - "- Showing that this model is characterized by three or more free parameters.\n", - "\n", - "- Using the model, with different sets of parameters, to generate model data.\n", - "\n", - "__Contents__\n", - "\n", - "This tutorial is split into the following sections:\n", - "\n", - "- **Paths**: Setting up the working directory path so the tutorial runs correctly on your computer.\n", - "- **PyProjRoot**: A brief introduction to the PyProjRoot package, which sets the working directory for Jupiter notebooks.\n", - "- **Model Parameterization**: An example of how a model is parameterized and is made up of free parameters.\n", - "- **Model Composition**: Composing a model using PyAutoFit's model composition API.\n", - "- **Model Creation**: Creating an instance of the model using PyAutoFit's `Model` python object.\n", - "- **Model Mapping**: Mapping an input vector of parameters to the model to create an instance of the model.\n", - "- **Complex Models**: Composing a more complex model with multiple model components and more free parameters.\n", - "- **Tuple Parameters**: Defining a model component with tuple parameters.\n", - "- **Extensibility**: Discussing how PyAutoFit's model composition API is scalable and extensible.\n", - "- **Wrap Up**: Concluding the tutorial and considering how to apply the concepts to your own scientific problem.\n", - "\n", - "This tutorial introduces the PyAutoFit API for model composition, which forms the foundation of all model-fitting\n", - "performed by PyAutoFit." - ] - }, - { - "cell_type": "code", - "metadata": {}, - "source": [ - "\n", - "import numpy as np\n", - "import matplotlib.pyplot as plt\n", - "\n", - "import autofit as af" - ], - "outputs": [], - "execution_count": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "__Paths__\n", - "\n", - "PyAutoFit assumes the current working directory is /path/to/HowToFit/ on your hard-disk (or in Colab). \n", - "This setup allows PyAutoFit to:\n", - "\n", - "- Load configuration settings from config files in the HowToFit/config folder.\n", - "\n", - "- Load example data from the HowToFit/dataset folder.\n", - "\n", - "- Output the results of model fits to your hard disk in the autofit/output folder.\n", - "\n", - "If you don't have an autofit_workspace, you can download it here:\n", - " \n", - " https://github.com/PyAutoLabs/autofit_workspace\n", - "\n", - "__PyProjRoot__\n", - "\n", - "At the top of every tutorial notebook, you will see the following cell. This cell uses the project pyprojroot to \n", - "locate the path to the workspace on your computer and set it as the working directory of the notebook." - ] - }, - { - "cell_type": "code", - "metadata": {}, - "source": [ - "from autoconf import setup_notebook; setup_notebook()" - ], - "outputs": [], - "execution_count": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "__Model Parameterization__\n", - "\n", - "A model is a set of equations, numerical processes, and assumptions that describe a physical system and dataset.\n", - "\n", - "In this example, our model is one or more 1-dimensional Gaussians, defined by the following equation:\n", - "\n", - "\\begin{equation*}\n", - "g(x, I, \\sigma) = \\frac{N}{\\sigma\\sqrt{2\\pi}} \\exp{(-0.5 (x / \\sigma)^2)}\n", - "\\end{equation*}\n", - "\n", - "Where:\n", - "\n", - "- `x`: The x-axis coordinate where the Gaussian is evaluated.\n", - "\n", - "- `N`: The overall normalization of the Gaussian.\n", - "\n", - "\n", - "- `\\sigma`: The size of the Gaussian (Full Width Half Maximum, $\\mathrm{FWHM}$, is $2{\\sqrt{2\\ln 2}}\\;\\sigma$).\n", - "\n", - "While a 1D Gaussian might seem like a rudimentary model, it has many real-world applications in signal processing. \n", - "For example, 1D Gaussians are fitted to datasets to measure the size of an observed signal. Thus, this model has \n", - "practical real-world applications.\n", - "\n", - "We now have a model, expressed as a simple 1D Gaussian. The model has three parameters, $(x, N, \\sigma)$. Using \n", - "different combinations of these parameters creates different realizations of the model, which we illustrate below.\n", - "\n", - "__Model Composition__\n", - "\n", - "We now define the 1D Gaussian as a \"model component\" in PyAutoFit. We use the term \"model component\" because the model \n", - "can be extended to include multiple components, each related to different equations and numerical processes. \n", - "\n", - "We first illustrate a model composed of a single model component, the 1D Gaussian. We then show a model made of\n", - "multiple model components.\n", - "\n", - "To define a \"model component\" in PyAutoFit, we simply write it as a Python class using the format shown below:" - ] - }, - { - "cell_type": "code", - "metadata": {}, - "source": [ - "\n", - "\n", - "class Gaussian:\n", - " def __init__(\n", - " self,\n", - " centre: float = 30.0, # <- **PyAutoFit** recognises these constructor arguments\n", - " normalization: float = 1.0, # <- are the Gaussian`s model parameters.\n", - " sigma: float = 5.0,\n", - " ):\n", - " \"\"\"\n", - " Represents a 1D Gaussian profile.\n", - "\n", - " This is a model-component of example models in the **HowToFit** lectures and is used to perform model-fitting\n", - " of example datasets.\n", - "\n", - " Parameters\n", - " ----------\n", - " centre\n", - " The x coordinate of the profile centre.\n", - " normalization\n", - " Overall normalization of the profile.\n", - " sigma\n", - " The sigma value controlling the size of the Gaussian.\n", - " \"\"\"\n", - " self.centre = centre\n", - " self.normalization = normalization\n", - " self.sigma = sigma\n", - "\n", - " def model_data_from(self, xvalues: np.ndarray) -> np.ndarray:\n", - " \"\"\"\n", - " Returns a 1D Gaussian on an input list of Cartesian x coordinates.\n", - "\n", - " The input xvalues are translated to a coordinate system centred on the Gaussian, via its `centre`.\n", - "\n", - " The output is referred to as the `model_data` to signify that it is a representation of the data from the\n", - " model.\n", - "\n", - " Parameters\n", - " ----------\n", - " xvalues\n", - " The x coordinates in the original reference frame of the data.\n", - "\n", - " Returns\n", - " -------\n", - " np.array\n", - " The Gaussian values at the input x coordinates.\n", - " \"\"\"\n", - " transformed_xvalues = np.subtract(xvalues, self.centre)\n", - " return np.multiply(\n", - " np.divide(self.normalization, self.sigma * np.sqrt(2.0 * np.pi)),\n", - " np.exp(-0.5 * np.square(np.divide(transformed_xvalues, self.sigma))),\n", - " )\n" - ], - "outputs": [], - "execution_count": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The format of this Python class defines how PyAutoFit composes the Gaussian as a model component, where:\n", - "\n", - "- The name of the class is the name of the model component, in this case, \"Gaussian\".\n", - "\n", - "- The input arguments of the constructor (the `__init__` method) are the parameters of the model, in the example \n", - "above `centre`, `normalization`, and `sigma`.\n", - "\n", - "- The default values and typing of the input arguments define whether a parameter is a single-valued float or a \n", - "multi-valued tuple. For the `Gaussian` class above, no input parameters are tuples, but later examples use tuples.\n", - "\n", - "- It includes functions associated with that model component, specifically the model_data function. When we create \n", - "instances of a `Gaussian` below, this function is used to generate a 1D representation of it as a NumPy array.\n", - "\n", - "__Model Creation__\n", - "\n", - "The `Gaussian` class above is a standard Python class. It does not yet act as a model component that can be used\n", - "for model fitting with PyAutoFit.\n", - "\n", - "To transform the Gaussian class into a model component that can be used for model fitting with PyAutoFit, we use \n", - "the `af.Model` object. This tells PyAutoFit to treat the input Python class as a model component." - ] - }, - { - "cell_type": "code", - "metadata": {}, - "source": [ - "model = af.Model(Gaussian)\n", - "print(\"Model `Gaussian` object: \\n\")\n", - "print(model)" - ], - "outputs": [], - "execution_count": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In PyAutoFit, a Model object encapsulates a model component that can be used for model fitting. It provides several \n", - "attributes that describe the model component, such as the `total_free_parameters` attribute, which indicates the \n", - "number of free parameters in the model:" - ] - }, - { - "cell_type": "code", - "metadata": {}, - "source": [ - "print(model.total_free_parameters)" - ], - "outputs": [], - "execution_count": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In PyAutoFit, you can retrieve comprehensive information about a model by accessing its `info` attribute.\n", - "\n", - "When you print the model info, it displays detailed information about each parameter in the model, including its name, \n", - "type, and associated prior distribution. Priors define the expected range or distribution of values for each \n", - "parameter during the model fitting process. If you're unfamiliar with priors, they are covered in tutorial 3 of \n", - "this chapter, which explains their role in model fitting.\n", - "\n", - "[The `info` below may not display optimally on your computer screen, for example the whitespace between parameter\n", - "names on the left and parameter priors on the right may lead them to appear across multiple lines. This is a\n", - "common issue in Jupyter notebooks.\n", - "\n", - "The`info_whitespace_length` parameter in the file `config/general.yaml` in the \"output\" section can be changed to \n", - "increase or decrease the amount of whitespace (The Jupyter notebook kernel will need to be reset for this change to \n", - "appear in a notebook).]" - ] - }, - { - "cell_type": "code", - "metadata": {}, - "source": [ - "print(model.info)" - ], - "outputs": [], - "execution_count": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "__Model Mapping__\n", - "\n", - "In PyAutoFit, instances of model components created via the af.Model object can be instantiated by mapping an input \n", - "vector of parameters to the Python class that the model object represents. The order of parameters in the model is \n", - "crucial for correctly defining the input vector.\n", - "\n", - "To determine the order of parameters in the model, PyAutoFit provides the paths attribute of the model object. \n", - "This attribute contains information about the parameter paths within the model.\n", - "\n", - "Here's how you can access the paths attribute to understand the order of parameters in the model:" - ] - }, - { - "cell_type": "code", - "metadata": {}, - "source": [ - "print(model.paths)" - ], - "outputs": [], - "execution_count": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "To create an instance of the Gaussian model component using PyAutoFit, following the order of parameters defined by \n", - "the paths attribute (`centre`, `normalization`, and `sigma`), you can initialize the instance as follows:" - ] - }, - { - "cell_type": "code", - "metadata": {}, - "source": [ - "instance = model.instance_from_vector(vector=[30.0, 2.0, 3.0])" - ], - "outputs": [], - "execution_count": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This is an instance of the `Gaussian` class." - ] - }, - { - "cell_type": "code", - "metadata": {}, - "source": [ - "print(\"Model Instance: \\n\")\n", - "print(instance)" - ], - "outputs": [], - "execution_count": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "It has the parameters of the `Gaussian` with the values input above." - ] - }, - { - "cell_type": "code", - "metadata": {}, - "source": [ - "print(\"Instance Parameters \\n\")\n", - "print(\"x = \", instance.centre)\n", - "print(\"normalization = \", instance.normalization)\n", - "print(\"sigma = \", instance.sigma)" - ], - "outputs": [], - "execution_count": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can use all class functions, such as the `model_data_from` function, to generate an instance of the \n", - "1D `Gaussian` and visualize it through plotting.\n", - "\n", - "The code below generates the 1D Gaussian model data, which requires an input list of x values where the Gaussian is\n", - "evaluated. The output is a NumPy array of the Gaussian's y values at the input x coordinates.\n", - "\n", - "Although simple, the code below is essentially the process of forward modeling, where we use the model to generate\n", - "the data we would observe in an experiment for a given set of parameters." - ] - }, - { - "cell_type": "code", - "metadata": {}, - "source": [ - "xvalues = np.arange(0.0, 100.0, 1.0)\n", - "\n", - "model_data = instance.model_data_from(xvalues=xvalues)\n", - "\n", - "plt.plot(xvalues, model_data, color=\"r\")\n", - "plt.title(\"1D Gaussian Model Data.\")\n", - "plt.xlabel(\"x values of profile\")\n", - "plt.ylabel(\"Gaussian Value\")\n", - "plt.show()\n", - "plt.clf()" - ], - "outputs": [], - "execution_count": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "__Complex Models__\n", - "\n", - "The code above may seem like a lot of steps just to create an instance of the `Gaussian` class. Couldn't we have \n", - "simply done this instead?\n", - "\n", - "```python\n", - "instance = Gaussian(centre=30.0, normalization=2.0, sigma=3.0)\n", - "```\n", - "\n", - "Yes, we could have.\n", - "\n", - "However, the model composition API used above is designed to simplify the process of composing complex models that \n", - "consist of multiple components with many free parameters. It provides a scalable approach for defining and \n", - "manipulating models.\n", - "\n", - "To demonstrate this capability, let's conclude the tutorial by composing a model composed of a Gaussian \n", - "component and another 1D profile, an `Exponential`, defined by the equation:\n", - "\n", - "\\begin{equation*}\n", - "g(x, I, \\lambda) = N \\lambda \\exp{- \\lambda x }\n", - "\\end{equation*}\n", - "\n", - "where:\n", - "\n", - "- `x`: Represents the x-axis coordinate where the Exponential profile is evaluated.\n", - "\n", - "- `N`: Describes the overall normalization of the Exponential profile.\n", - "\n", - "- $\\lambda$: Represents the rate of decay of the exponential.\n", - "\n", - "We'll start by defining the `Exponential` profile using a format similar to the Gaussian definition above." - ] - }, - { - "cell_type": "code", - "metadata": {}, - "source": [ - "\n", - "\n", - "class Exponential:\n", - " def __init__(\n", - " self,\n", - " centre: float = 30.0, # <- **PyAutoFit** recognises these constructor arguments\n", - " normalization: float = 1.0, # <- are the Exponential`s model parameters.\n", - " rate: float = 0.01,\n", - " ):\n", - " \"\"\"\n", - " Represents a 1D Exponential profile.\n", - "\n", - " This is a model-component of example models in the **HowToFit** lectures and is used to fit example datasets\n", - " via a non-linear search.\n", - "\n", - " Parameters\n", - " ----------\n", - " centre\n", - " The x coordinate of the profile centre.\n", - " normalization\n", - " Overall normalization of the profile.\n", - " ratw\n", - " The decay rate controlling has fast the Exponential declines.\n", - " \"\"\"\n", - " self.centre = centre\n", - " self.normalization = normalization\n", - " self.rate = rate\n", - "\n", - " def model_data_from(self, xvalues: np.ndarray):\n", - " \"\"\"\n", - " Returns a 1D Gaussian on an input list of Cartesian x coordinates.\n", - "\n", - " The input xvalues are translated to a coordinate system centred on the `Exponential`, via its `centre`.\n", - "\n", - " The output is referred to as the `model_data` to signify that it is a representation of the data from the\n", - " model.\n", - "\n", - " Parameters\n", - " ----------\n", - " xvalues\n", - " The x coordinates in the original reference frame of the data.\n", - " \"\"\"\n", - " transformed_xvalues = np.subtract(xvalues, self.centre)\n", - " return self.normalization * np.multiply(\n", - " self.rate, np.exp(-1.0 * self.rate * abs(transformed_xvalues))\n", - " )\n" - ], - "outputs": [], - "execution_count": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can construct a model comprising one `Gaussian` object and one `Exponential` object using the `af.Collection` object:" - ] - }, - { - "cell_type": "code", - "metadata": {}, - "source": [ - "model = af.Collection(gaussian=af.Model(Gaussian), exponential=af.Model(Exponential))" - ], - "outputs": [], - "execution_count": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "You can retrieve all the information about the model created via the `af.Collection` by printing its `info` attribute \n", - "in one go:" - ] - }, - { - "cell_type": "code", - "metadata": {}, - "source": [ - "print(model.info)" - ], - "outputs": [], - "execution_count": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "When `Gaussian` and `Exponential` are added to a `Collection`, they are automatically assigned as `Model` objects.\n", - "\n", - "Therefore, there's no need to use the `af.Model` method when passing classes to a `Collection`, which makes the Python \n", - "code more concise and readable." - ] - }, - { - "cell_type": "code", - "metadata": {}, - "source": [ - "model = af.Collection(gaussian=Gaussian, exponential=Exponential)" - ], - "outputs": [], - "execution_count": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The `model.info` is identical to the previous example." - ] - }, - { - "cell_type": "code", - "metadata": {}, - "source": [ - "print(model.info)" - ], - "outputs": [], - "execution_count": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "A `Collection` functions analogously to a `Model`, but it includes multiple model components.\n", - "\n", - "This can be observed by examining its `paths` attribute, which displays paths to all 6 free parameters across both model components.\n", - "\n", - "The paths contain entries such as `.gaussian.` and `.exponential.`, corresponding to the names we provided when \n", - "defining the `af.Collection` earlier. Modifying the names of the model components supplied to the `Collection` \n", - "would adjust the paths accordingly." - ] - }, - { - "cell_type": "code", - "metadata": {}, - "source": [ - "print(model.paths)" - ], - "outputs": [], - "execution_count": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "A model instance can again be created by mapping an input `vector`, which now has 6 entries." - ] - }, - { - "cell_type": "code", - "metadata": {}, - "source": [ - "instance = model.instance_from_vector(vector=[0.1, 0.2, 0.3, 0.4, 0.5, 0.01])" - ], - "outputs": [], - "execution_count": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This `instance` contains each of the model components we defined above. \n", - "\n", - "The argument names input into the `Collection` define the attribute names of the `instance`:" - ] - }, - { - "cell_type": "code", - "metadata": {}, - "source": [ - "print(\"Instance Parameters \\n\")\n", - "print(\"x (Gaussian) = \", instance.gaussian.centre)\n", - "print(\"normalization (Gaussian) = \", instance.gaussian.normalization)\n", - "print(\"sigma (Gaussian) = \", instance.gaussian.sigma)\n", - "print(\"x (Exponential) = \", instance.exponential.centre)\n", - "print(\"normalization (Exponential) = \", instance.exponential.normalization)\n", - "print(\"sigma (Exponential) = \", instance.exponential.rate)" - ], - "outputs": [], - "execution_count": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In the context of the model's equations, it is simply the sum of the equations defining the `Gaussian` \n", - "and `Exponential` components.\n", - "\n", - "To generate the `model_data`, we sum the `model_data` of each individual model component, as demonstrated and \n", - "visualized below." - ] - }, - { - "cell_type": "code", - "metadata": {}, - "source": [ - "xvalues = np.arange(0.0, 100.0, 1.0)\n", - "\n", - "model_data_0 = instance.gaussian.model_data_from(xvalues=xvalues)\n", - "model_data_1 = instance.exponential.model_data_from(xvalues=xvalues)\n", - "\n", - "model_data = model_data_0 + model_data_1\n", - "\n", - "plt.plot(xvalues, model_data, color=\"r\")\n", - "plt.plot(xvalues, model_data_0, \"b\", \"--\")\n", - "plt.plot(xvalues, model_data_1, \"k\", \"--\")\n", - "plt.title(\"1D Gaussian + Exponential Model Data.\")\n", - "plt.xlabel(\"x values of profile\")\n", - "plt.ylabel(\"Value\")\n", - "plt.show()\n", - "plt.clf()" - ], - "outputs": [], - "execution_count": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "__Tuple Parameters__\n", - "\n", - "The `Gaussian` and `Exponential` model components above only has parameters that are single-valued floats. \n", - "\n", - "Parameters can also be tuples, which is useful for defining model components where certain parameters are naturally\n", - "grouped together.\n", - "\n", - "For example, we can define a 2D Gaussian with a center that has two coordinates and therefore free parameters, (x, y), \n", - "using a tuple." - ] - }, - { - "cell_type": "code", - "metadata": {}, - "source": [ - "from typing import Tuple\n", - "\n", - "\n", - "class Gaussian2D:\n", - " def __init__(\n", - " self,\n", - " centre: Tuple[float, float] = (0.0, 0.0),\n", - " normalization: float = 0.1,\n", - " sigma: float = 1.0,\n", - " ):\n", - " self.centre = centre\n", - " self.normalization = normalization\n", - " self.sigma = sigma\n" - ], - "outputs": [], - "execution_count": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The model's `total_free_parameters` attribute now includes 4 free parameters, as the tuple `centre` parameter accounts\n", - "for 2 free parameters." - ] - }, - { - "cell_type": "code", - "metadata": {}, - "source": [ - "model = af.Model(Gaussian2D)\n", - "\n", - "print(\"Total Free Parameters:\", model.total_free_parameters)" - ], - "outputs": [], - "execution_count": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This information is again displayed in the `info` attribute:" - ] - }, - { - "cell_type": "code", - "metadata": {}, - "source": [ - "print(\"\\nInfo:\")\n", - "print(model.info)" - ], - "outputs": [], - "execution_count": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The `paths` attribute provides information on the order of parameters in the model, illustrating how the\n", - "`centre` tuple is split into two parameters." - ] - }, - { - "cell_type": "code", - "metadata": {}, - "source": [ - "print(\"\\nPaths:\")\n", - "print(model.paths)" - ], - "outputs": [], - "execution_count": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This ordering is used to create an instance of the `Gaussian2D` model component:" - ] - }, - { - "cell_type": "code", - "metadata": {}, - "source": [ - "instance = model.instance_from_vector(vector=[40.0, 60.0, 2.0, 3.0])\n", - "\n", - "print(\"\\nInstance Parameters:\")\n", - "print(\"centre (x) = \", instance.centre[0])\n", - "print(\"centre (y) = \", instance.centre[1])\n", - "print(\"normalization = \", instance.normalization)\n", - "print(\"sigma = \", instance.sigma)" - ], - "outputs": [], - "execution_count": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "__Extensibility__\n", - "\n", - "It should now be clear why we use `Model` and `Collection` objects to construct our model.\n", - "\n", - "These objects facilitate the straightforward extension of our models to include multiple components and parameters. \n", - "For instance, we can add more `Gaussian` and `Exponential` components to the `Collection`, or define new Python \n", - "classes to represent entirely new model components with additional parameters.\n", - "\n", - "These objects serve numerous other essential purposes that we will explore in subsequent tutorials.\n", - "\n", - "**PyAutoFit** offers a comprehensive API for building models, which includes models constructed using NumPy arrays, \n", - "hierarchies of Python classes, and graphical models where parameters are interconnected. These advanced modeling \n", - "techniques are gradually introduced throughout the HowToFit lectures.\n", - "\n", - "For a detailed understanding of PyAutoFit's model composition API and a quick reference guide on how to construct \n", - "models, you may want to take a quick look at the model cookbook in the PyAutoFit documentation. It provides an \n", - "extensive overview and can serve as a helpful resource as you progress:\n", - "\n", - "[PyAutoFit Model Cookbook](https://pyautofit.readthedocs.io/en/latest/cookbooks/model.html)\n", - "\n", - "Don't worry if it seems a bit overwhelming at this stage; the concepts will become clearer as you continue exploring \n", - "and working with PyAutoFit.\n", - "\n", - "__Wrap Up__\n", - "\n", - "In this tutorial, we've learned how to define and compose a model that can generate model data.\n", - "\n", - "Now, think about your specific field of study and the problem you want to address through model-fitting. Consider \n", - "the following questions:\n", - "\n", - "- What type of model would best describe your data?\n", - "\n", - "- Which Python class, following the format introduced here, would you need to compose this model?\n", - "\n", - "- What are the free parameters of your model that need to be determined through fitting?\n", - "\n", - "If you decide to incorporate a new model component into your autofit_workspace tailored to your specific model-fitting \n", - "task, refer to the following script:\n", - "\n", - "autofit_workspace/*/overview/new_model_component/new_model_component.ipynb\n", - "\n", - "This script provides guidance on setting up the PyAutoFit configuration files associated with your custom model." - ] - }, - { - "cell_type": "code", - "metadata": {}, - "source": [], - "outputs": [], - "execution_count": null - } - ], - "metadata": { - "anaconda-cloud": {}, - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.1" - } - }, - "nbformat": 4, - "nbformat_minor": 4 + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": "Tutorial 1: Models\n==================\n\nAt the heart of model-fitting is the model: a set of equations, numerical processes, and assumptions describing a\nphysical system of interest. The goal of model-fitting is to better understand this physical system and develop\npredictive models that describe it more accurately.\n\nIn astronomy, a model might describe the distribution of stars within a galaxy. In biology, it might represent the\ninteraction of proteins within a cell. In finance, it could describe the evolution of stock prices in a market.\nRegardless of the field, the model acts as a mathematical description of the physical system, aiming to enhance\nunderstanding and enable new predictions.\n\nWhatever your model, its equations are defined by \"free parameters.\" Changing these parameters alters the\nbehavior and predictions of the model.\n\nOnce the model is defined and parameter values are chosen, the model creates \"model data\"—a realization of how the\nphysical system appears given those parameters. This process, often referred to as \"forward modeling,\" describes the\nphysical system from its starting point and predicts the data we observe.\n\nBy varying the model parameters, we can generate numerous model datasets. The ultimate goal of model-fitting, which\nyou will learn by the end of this chapter, is to determine the model parameters and corresponding dataset that best\nfit the observed data.\n\n__Astronomy Example__\n\nFor instance, in astronomy, we might model the distribution of stars, including:\n\n- A parameter describing the brightness of the stars.\n\n- Multiple parameters defining their distribution.\n\n- Several parameters describing their colors.\n\nIf our model pertains to the distribution of stars within a galaxy, the forward model will produce an image of what\nthat galaxy looks like when observed with a telescope. This forward model might account for physical effects such as\nthe blurring of light due to diffraction in the telescope optics.\n\nBy altering the parameters describing the stars, we can generate many different model images via this forward model.\n\nAt the end of this chapter, we will use a real-world astronomy example to illustrate everything you have learned,\nincluding fitting a real galaxy observed with the Hubble Space Telescope.\n\n__Overview__\n\nIn tutorial 1, we will cover the basics of defining a model, specifically:\n\n- Defining a simple model described by a few simple equations.\n\n- Showing that this model is characterized by three or more free parameters.\n\n- Using the model, with different sets of parameters, to generate model data.\n\n__Contents__\n\nThis tutorial is split into the following sections:\n\n- **Paths**: Setting up the working directory path so the tutorial runs correctly on your computer.\n- **Model Parameterization**: An example of how a model is parameterized and is made up of free parameters.\n- **Model Composition**: Composing a model using PyAutoFit's model composition API.\n- **Model Creation**: Creating an instance of the model using PyAutoFit's `Model` python object.\n- **Model Mapping**: Mapping an input vector of parameters to the model to create an instance of the model.\n- **Complex Models**: Composing a more complex model with multiple model components and more free parameters.\n- **Tuple Parameters**: Defining a model component with tuple parameters.\n- **Extensibility**: Discussing how PyAutoFit's model composition API is scalable and extensible.\n- **Wrap Up**: Concluding the tutorial and considering how to apply the concepts to your own scientific problem.\n\nThis tutorial introduces the PyAutoFit API for model composition, which forms the foundation of all model-fitting\nperformed by PyAutoFit." + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "\n", + "import autofit as af" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": "__Paths__\n\nPyAutoFit assumes the current working directory is /path/to/HowToFit/ on your hard-disk (or in Colab). \nThis setup allows PyAutoFit to:\n\n- Load configuration settings from config files in the HowToFit/config folder.\n\n- Load example data from the HowToFit/dataset folder.\n\n- Output the results of model fits to your hard disk in the autofit/output folder.\n\nIf you don't have an autofit_workspace, you can download it here:\n \n https://github.com/PyAutoLabs/autofit_workspace\n\nThe cell below uses `autoconf.setup_notebook` to locate the workspace path on your computer and set it as\nthe working directory of the notebook." + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "from autoconf import setup_notebook; setup_notebook()" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "__Model Parameterization__\n", + "\n", + "A model is a set of equations, numerical processes, and assumptions that describe a physical system and dataset.\n", + "\n", + "In this example, our model is one or more 1-dimensional Gaussians, defined by the following equation:\n", + "\n", + "\\begin{equation*}\n", + "g(x, I, \\sigma) = \\frac{N}{\\sigma\\sqrt{2\\pi}} \\exp{(-0.5 (x / \\sigma)^2)}\n", + "\\end{equation*}\n", + "\n", + "Where:\n", + "\n", + "- `x`: The x-axis coordinate where the Gaussian is evaluated.\n", + "\n", + "- `N`: The overall normalization of the Gaussian.\n", + "\n", + "\n", + "- `\\sigma`: The size of the Gaussian (Full Width Half Maximum, $\\mathrm{FWHM}$, is $2{\\sqrt{2\\ln 2}}\\;\\sigma$).\n", + "\n", + "While a 1D Gaussian might seem like a rudimentary model, it has many real-world applications in signal processing. \n", + "For example, 1D Gaussians are fitted to datasets to measure the size of an observed signal. Thus, this model has \n", + "practical real-world applications.\n", + "\n", + "We now have a model, expressed as a simple 1D Gaussian. The model has three parameters, $(x, N, \\sigma)$. Using \n", + "different combinations of these parameters creates different realizations of the model, which we illustrate below.\n", + "\n", + "__Model Composition__\n", + "\n", + "We now define the 1D Gaussian as a \"model component\" in PyAutoFit. We use the term \"model component\" because the model \n", + "can be extended to include multiple components, each related to different equations and numerical processes. \n", + "\n", + "We first illustrate a model composed of a single model component, the 1D Gaussian. We then show a model made of\n", + "multiple model components.\n", + "\n", + "To define a \"model component\" in PyAutoFit, we simply write it as a Python class using the format shown below:" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "\n", + "\n", + "class Gaussian:\n", + " def __init__(\n", + " self,\n", + " centre: float = 30.0, # <- **PyAutoFit** recognises these constructor arguments\n", + " normalization: float = 1.0, # <- are the Gaussian`s model parameters.\n", + " sigma: float = 5.0,\n", + " ):\n", + " \"\"\"\n", + " Represents a 1D Gaussian profile.\n", + "\n", + " This is a model-component of example models in the **HowToFit** lectures and is used to perform model-fitting\n", + " of example datasets.\n", + "\n", + " Parameters\n", + " ----------\n", + " centre\n", + " The x coordinate of the profile centre.\n", + " normalization\n", + " Overall normalization of the profile.\n", + " sigma\n", + " The sigma value controlling the size of the Gaussian.\n", + " \"\"\"\n", + " self.centre = centre\n", + " self.normalization = normalization\n", + " self.sigma = sigma\n", + "\n", + " def model_data_from(self, xvalues: np.ndarray) -> np.ndarray:\n", + " \"\"\"\n", + " Returns a 1D Gaussian on an input list of Cartesian x coordinates.\n", + "\n", + " The input xvalues are translated to a coordinate system centred on the Gaussian, via its `centre`.\n", + "\n", + " The output is referred to as the `model_data` to signify that it is a representation of the data from the\n", + " model.\n", + "\n", + " Parameters\n", + " ----------\n", + " xvalues\n", + " The x coordinates in the original reference frame of the data.\n", + "\n", + " Returns\n", + " -------\n", + " np.array\n", + " The Gaussian values at the input x coordinates.\n", + " \"\"\"\n", + " transformed_xvalues = np.subtract(xvalues, self.centre)\n", + " return np.multiply(\n", + " np.divide(self.normalization, self.sigma * np.sqrt(2.0 * np.pi)),\n", + " np.exp(-0.5 * np.square(np.divide(transformed_xvalues, self.sigma))),\n", + " )\n" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The format of this Python class defines how PyAutoFit composes the Gaussian as a model component, where:\n", + "\n", + "- The name of the class is the name of the model component, in this case, \"Gaussian\".\n", + "\n", + "- The input arguments of the constructor (the `__init__` method) are the parameters of the model, in the example \n", + "above `centre`, `normalization`, and `sigma`.\n", + "\n", + "- The default values and typing of the input arguments define whether a parameter is a single-valued float or a \n", + "multi-valued tuple. For the `Gaussian` class above, no input parameters are tuples, but later examples use tuples.\n", + "\n", + "- It includes functions associated with that model component, specifically the model_data function. When we create \n", + "instances of a `Gaussian` below, this function is used to generate a 1D representation of it as a NumPy array.\n", + "\n", + "__Model Creation__\n", + "\n", + "The `Gaussian` class above is a standard Python class. It does not yet act as a model component that can be used\n", + "for model fitting with PyAutoFit.\n", + "\n", + "To transform the Gaussian class into a model component that can be used for model fitting with PyAutoFit, we use \n", + "the `af.Model` object. This tells PyAutoFit to treat the input Python class as a model component." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "model = af.Model(Gaussian)\n", + "print(\"Model `Gaussian` object: \\n\")\n", + "print(model)" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In PyAutoFit, a Model object encapsulates a model component that can be used for model fitting. It provides several \n", + "attributes that describe the model component, such as the `total_free_parameters` attribute, which indicates the \n", + "number of free parameters in the model:" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "print(model.total_free_parameters)" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In PyAutoFit, you can retrieve comprehensive information about a model by accessing its `info` attribute.\n", + "\n", + "When you print the model info, it displays detailed information about each parameter in the model, including its name, \n", + "type, and associated prior distribution. Priors define the expected range or distribution of values for each \n", + "parameter during the model fitting process. If you're unfamiliar with priors, they are covered in tutorial 3 of \n", + "this chapter, which explains their role in model fitting.\n", + "\n", + "[The `info` below may not display optimally on your computer screen, for example the whitespace between parameter\n", + "names on the left and parameter priors on the right may lead them to appear across multiple lines. This is a\n", + "common issue in Jupyter notebooks.\n", + "\n", + "The`info_whitespace_length` parameter in the file `config/general.yaml` in the \"output\" section can be changed to \n", + "increase or decrease the amount of whitespace (The Jupyter notebook kernel will need to be reset for this change to \n", + "appear in a notebook).]" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "print(model.info)" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "__Model Mapping__\n", + "\n", + "In PyAutoFit, instances of model components created via the af.Model object can be instantiated by mapping an input \n", + "vector of parameters to the Python class that the model object represents. The order of parameters in the model is \n", + "crucial for correctly defining the input vector.\n", + "\n", + "To determine the order of parameters in the model, PyAutoFit provides the paths attribute of the model object. \n", + "This attribute contains information about the parameter paths within the model.\n", + "\n", + "Here's how you can access the paths attribute to understand the order of parameters in the model:" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "print(model.paths)" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To create an instance of the Gaussian model component using PyAutoFit, following the order of parameters defined by \n", + "the paths attribute (`centre`, `normalization`, and `sigma`), you can initialize the instance as follows:" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "instance = model.instance_from_vector(vector=[30.0, 2.0, 3.0])" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This is an instance of the `Gaussian` class." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "print(\"Model Instance: \\n\")\n", + "print(instance)" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "It has the parameters of the `Gaussian` with the values input above." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "print(\"Instance Parameters \\n\")\n", + "print(\"x = \", instance.centre)\n", + "print(\"normalization = \", instance.normalization)\n", + "print(\"sigma = \", instance.sigma)" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can use all class functions, such as the `model_data_from` function, to generate an instance of the \n", + "1D `Gaussian` and visualize it through plotting.\n", + "\n", + "The code below generates the 1D Gaussian model data, which requires an input list of x values where the Gaussian is\n", + "evaluated. The output is a NumPy array of the Gaussian's y values at the input x coordinates.\n", + "\n", + "Although simple, the code below is essentially the process of forward modeling, where we use the model to generate\n", + "the data we would observe in an experiment for a given set of parameters." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "xvalues = np.arange(0.0, 100.0, 1.0)\n", + "\n", + "model_data = instance.model_data_from(xvalues=xvalues)\n", + "\n", + "plt.plot(xvalues, model_data, color=\"r\")\n", + "plt.title(\"1D Gaussian Model Data.\")\n", + "plt.xlabel(\"x values of profile\")\n", + "plt.ylabel(\"Gaussian Value\")\n", + "plt.show()\n", + "plt.clf()" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "__Complex Models__\n", + "\n", + "The code above may seem like a lot of steps just to create an instance of the `Gaussian` class. Couldn't we have \n", + "simply done this instead?\n", + "\n", + "```python\n", + "instance = Gaussian(centre=30.0, normalization=2.0, sigma=3.0)\n", + "```\n", + "\n", + "Yes, we could have.\n", + "\n", + "However, the model composition API used above is designed to simplify the process of composing complex models that \n", + "consist of multiple components with many free parameters. It provides a scalable approach for defining and \n", + "manipulating models.\n", + "\n", + "To demonstrate this capability, let's conclude the tutorial by composing a model composed of a Gaussian \n", + "component and another 1D profile, an `Exponential`, defined by the equation:\n", + "\n", + "\\begin{equation*}\n", + "g(x, I, \\lambda) = N \\lambda \\exp{- \\lambda x }\n", + "\\end{equation*}\n", + "\n", + "where:\n", + "\n", + "- `x`: Represents the x-axis coordinate where the Exponential profile is evaluated.\n", + "\n", + "- `N`: Describes the overall normalization of the Exponential profile.\n", + "\n", + "- $\\lambda$: Represents the rate of decay of the exponential.\n", + "\n", + "We'll start by defining the `Exponential` profile using a format similar to the Gaussian definition above." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "\n", + "\n", + "class Exponential:\n", + " def __init__(\n", + " self,\n", + " centre: float = 30.0, # <- **PyAutoFit** recognises these constructor arguments\n", + " normalization: float = 1.0, # <- are the Exponential`s model parameters.\n", + " rate: float = 0.01,\n", + " ):\n", + " \"\"\"\n", + " Represents a 1D Exponential profile.\n", + "\n", + " This is a model-component of example models in the **HowToFit** lectures and is used to fit example datasets\n", + " via a non-linear search.\n", + "\n", + " Parameters\n", + " ----------\n", + " centre\n", + " The x coordinate of the profile centre.\n", + " normalization\n", + " Overall normalization of the profile.\n", + " ratw\n", + " The decay rate controlling has fast the Exponential declines.\n", + " \"\"\"\n", + " self.centre = centre\n", + " self.normalization = normalization\n", + " self.rate = rate\n", + "\n", + " def model_data_from(self, xvalues: np.ndarray):\n", + " \"\"\"\n", + " Returns a 1D Gaussian on an input list of Cartesian x coordinates.\n", + "\n", + " The input xvalues are translated to a coordinate system centred on the `Exponential`, via its `centre`.\n", + "\n", + " The output is referred to as the `model_data` to signify that it is a representation of the data from the\n", + " model.\n", + "\n", + " Parameters\n", + " ----------\n", + " xvalues\n", + " The x coordinates in the original reference frame of the data.\n", + " \"\"\"\n", + " transformed_xvalues = np.subtract(xvalues, self.centre)\n", + " return self.normalization * np.multiply(\n", + " self.rate, np.exp(-1.0 * self.rate * abs(transformed_xvalues))\n", + " )\n" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can construct a model comprising one `Gaussian` object and one `Exponential` object using the `af.Collection` object:" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "model = af.Collection(gaussian=af.Model(Gaussian), exponential=af.Model(Exponential))" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can retrieve all the information about the model created via the `af.Collection` by printing its `info` attribute \n", + "in one go:" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "print(model.info)" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "When `Gaussian` and `Exponential` are added to a `Collection`, they are automatically assigned as `Model` objects.\n", + "\n", + "Therefore, there's no need to use the `af.Model` method when passing classes to a `Collection`, which makes the Python \n", + "code more concise and readable." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "model = af.Collection(gaussian=Gaussian, exponential=Exponential)" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The `model.info` is identical to the previous example." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "print(model.info)" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "A `Collection` functions analogously to a `Model`, but it includes multiple model components.\n", + "\n", + "This can be observed by examining its `paths` attribute, which displays paths to all 6 free parameters across both model components.\n", + "\n", + "The paths contain entries such as `.gaussian.` and `.exponential.`, corresponding to the names we provided when \n", + "defining the `af.Collection` earlier. Modifying the names of the model components supplied to the `Collection` \n", + "would adjust the paths accordingly." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "print(model.paths)" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "A model instance can again be created by mapping an input `vector`, which now has 6 entries." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "instance = model.instance_from_vector(vector=[0.1, 0.2, 0.3, 0.4, 0.5, 0.01])" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This `instance` contains each of the model components we defined above. \n", + "\n", + "The argument names input into the `Collection` define the attribute names of the `instance`:" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "print(\"Instance Parameters \\n\")\n", + "print(\"x (Gaussian) = \", instance.gaussian.centre)\n", + "print(\"normalization (Gaussian) = \", instance.gaussian.normalization)\n", + "print(\"sigma (Gaussian) = \", instance.gaussian.sigma)\n", + "print(\"x (Exponential) = \", instance.exponential.centre)\n", + "print(\"normalization (Exponential) = \", instance.exponential.normalization)\n", + "print(\"sigma (Exponential) = \", instance.exponential.rate)" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In the context of the model's equations, it is simply the sum of the equations defining the `Gaussian` \n", + "and `Exponential` components.\n", + "\n", + "To generate the `model_data`, we sum the `model_data` of each individual model component, as demonstrated and \n", + "visualized below." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "xvalues = np.arange(0.0, 100.0, 1.0)\n", + "\n", + "model_data_0 = instance.gaussian.model_data_from(xvalues=xvalues)\n", + "model_data_1 = instance.exponential.model_data_from(xvalues=xvalues)\n", + "\n", + "model_data = model_data_0 + model_data_1\n", + "\n", + "plt.plot(xvalues, model_data, color=\"r\")\n", + "plt.plot(xvalues, model_data_0, \"b\", \"--\")\n", + "plt.plot(xvalues, model_data_1, \"k\", \"--\")\n", + "plt.title(\"1D Gaussian + Exponential Model Data.\")\n", + "plt.xlabel(\"x values of profile\")\n", + "plt.ylabel(\"Value\")\n", + "plt.show()\n", + "plt.clf()" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "__Tuple Parameters__\n", + "\n", + "The `Gaussian` and `Exponential` model components above only has parameters that are single-valued floats. \n", + "\n", + "Parameters can also be tuples, which is useful for defining model components where certain parameters are naturally\n", + "grouped together.\n", + "\n", + "For example, we can define a 2D Gaussian with a center that has two coordinates and therefore free parameters, (x, y), \n", + "using a tuple." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "from typing import Tuple\n", + "\n", + "\n", + "class Gaussian2D:\n", + " def __init__(\n", + " self,\n", + " centre: Tuple[float, float] = (0.0, 0.0),\n", + " normalization: float = 0.1,\n", + " sigma: float = 1.0,\n", + " ):\n", + " self.centre = centre\n", + " self.normalization = normalization\n", + " self.sigma = sigma\n" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The model's `total_free_parameters` attribute now includes 4 free parameters, as the tuple `centre` parameter accounts\n", + "for 2 free parameters." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "model = af.Model(Gaussian2D)\n", + "\n", + "print(\"Total Free Parameters:\", model.total_free_parameters)" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This information is again displayed in the `info` attribute:" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "print(\"\\nInfo:\")\n", + "print(model.info)" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The `paths` attribute provides information on the order of parameters in the model, illustrating how the\n", + "`centre` tuple is split into two parameters." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "print(\"\\nPaths:\")\n", + "print(model.paths)" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This ordering is used to create an instance of the `Gaussian2D` model component:" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "instance = model.instance_from_vector(vector=[40.0, 60.0, 2.0, 3.0])\n", + "\n", + "print(\"\\nInstance Parameters:\")\n", + "print(\"centre (x) = \", instance.centre[0])\n", + "print(\"centre (y) = \", instance.centre[1])\n", + "print(\"normalization = \", instance.normalization)\n", + "print(\"sigma = \", instance.sigma)" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "__Extensibility__\n", + "\n", + "It should now be clear why we use `Model` and `Collection` objects to construct our model.\n", + "\n", + "These objects facilitate the straightforward extension of our models to include multiple components and parameters. \n", + "For instance, we can add more `Gaussian` and `Exponential` components to the `Collection`, or define new Python \n", + "classes to represent entirely new model components with additional parameters.\n", + "\n", + "These objects serve numerous other essential purposes that we will explore in subsequent tutorials.\n", + "\n", + "**PyAutoFit** offers a comprehensive API for building models, which includes models constructed using NumPy arrays, \n", + "hierarchies of Python classes, and graphical models where parameters are interconnected. These advanced modeling \n", + "techniques are gradually introduced throughout the HowToFit lectures.\n", + "\n", + "For a detailed understanding of PyAutoFit's model composition API and a quick reference guide on how to construct \n", + "models, you may want to take a quick look at the model cookbook in the PyAutoFit documentation. It provides an \n", + "extensive overview and can serve as a helpful resource as you progress:\n", + "\n", + "[PyAutoFit Model Cookbook](https://pyautofit.readthedocs.io/en/latest/cookbooks/model.html)\n", + "\n", + "Don't worry if it seems a bit overwhelming at this stage; the concepts will become clearer as you continue exploring \n", + "and working with PyAutoFit.\n", + "\n", + "__Wrap Up__\n", + "\n", + "In this tutorial, we've learned how to define and compose a model that can generate model data.\n", + "\n", + "Now, think about your specific field of study and the problem you want to address through model-fitting. Consider \n", + "the following questions:\n", + "\n", + "- What type of model would best describe your data?\n", + "\n", + "- Which Python class, following the format introduced here, would you need to compose this model?\n", + "\n", + "- What are the free parameters of your model that need to be determined through fitting?\n", + "\n", + "If you decide to incorporate a new model component into your autofit_workspace tailored to your specific model-fitting \n", + "task, refer to the following script:\n", + "\n", + "autofit_workspace/*/overview/new_model_component/new_model_component.ipynb\n", + "\n", + "This script provides guidance on setting up the PyAutoFit configuration files associated with your custom model." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [], + "outputs": [], + "execution_count": null + } + ], + "metadata": { + "anaconda-cloud": {}, + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.1" + } + }, + "nbformat": 4, + "nbformat_minor": 4 } \ No newline at end of file diff --git a/scripts/chapter_1_introduction/tutorial_1_models.py b/scripts/chapter_1_introduction/tutorial_1_models.py index d0f9e2a..85a8453 100644 --- a/scripts/chapter_1_introduction/tutorial_1_models.py +++ b/scripts/chapter_1_introduction/tutorial_1_models.py @@ -56,7 +56,6 @@ This tutorial is split into the following sections: - **Paths**: Setting up the working directory path so the tutorial runs correctly on your computer. -- **PyProjRoot**: A brief introduction to the PyProjRoot package, which sets the working directory for Jupiter notebooks. - **Model Parameterization**: An example of how a model is parameterized and is made up of free parameters. - **Model Composition**: Composing a model using PyAutoFit's model composition API. - **Model Creation**: Creating an instance of the model using PyAutoFit's `Model` python object. @@ -91,14 +90,6 @@ https://github.com/PyAutoLabs/autofit_workspace -__PyProjRoot__ - -At the top of every tutorial notebook, you will see the following cell. This cell uses the project pyprojroot to -locate the path to the workspace on your computer and set it as the working directory of the notebook. -""" -# from autoconf import setup_notebook; setup_notebook() - -""" __Model Parameterization__ A model is a set of equations, numerical processes, and assumptions that describe a physical system and dataset.