diff --git a/README.md b/README.md
index 6e55d6a..d0c437a 100644
--- a/README.md
+++ b/README.md
@@ -2,6 +2,7 @@
[Installation Guide](https://pyautofit.readthedocs.io/en/latest/installation/overview.html) |
[PyAutoFit readthedocs](https://pyautofit.readthedocs.io/en/latest/index.html) |
+[Browse Chapter 1 With Images](markdown/README.md) |
[autofit_workspace](https://github.com/PyAutoLabs/autofit_workspace)
diff --git a/config/build/markdown_examples.yaml b/config/build/markdown_examples.yaml
new file mode 100644
index 0000000..8b4783a
--- /dev/null
+++ b/config/build/markdown_examples.yaml
@@ -0,0 +1,16 @@
+# Curated examples rendered to executed markdown pages (markdown/) with real
+# output images, for GitHub browsing. Built by PyAutoBuild's generate_markdown.py
+# (never TEST_MODE; features/ never rendered; order = execution + index order).
+# Batch 2b: chapter_1_introduction (the intro lecture, in reading order).
+- script: scripts/chapter_1_introduction/start_here.py
+ max_minutes: 30
+- script: scripts/chapter_1_introduction/tutorial_1_models.py
+ max_minutes: 30
+- script: scripts/chapter_1_introduction/tutorial_2_fitting_data.py
+ max_minutes: 30
+- script: scripts/chapter_1_introduction/tutorial_3_non_linear_search.py
+ max_minutes: 90
+- script: scripts/chapter_1_introduction/tutorial_4_why_modeling_is_hard.py
+ max_minutes: 120
+- script: scripts/chapter_1_introduction/tutorial_5_results_and_samples.py
+ max_minutes: 90
diff --git a/markdown/README.md b/markdown/README.md
new file mode 100644
index 0000000..d5a67a2
--- /dev/null
+++ b/markdown/README.md
@@ -0,0 +1,12 @@
+# HowToFit examples, executed — browse with output images
+
+Every page below is the corresponding example script **fully executed**, rendered to markdown with its real output images, so you can read the examples on GitHub exactly as they run. Each page links back to the `.py` script and Jupyter notebook it was generated from.
+
+- [HowToFit Lectures](chapter_1_introduction/start_here.md) — from `scripts/chapter_1_introduction/start_here.py`
+- [Tutorial 1: Models](chapter_1_introduction/tutorial_1_models.md) — from `scripts/chapter_1_introduction/tutorial_1_models.py`
+- [Tutorial 2: Fitting Data](chapter_1_introduction/tutorial_2_fitting_data.md) — from `scripts/chapter_1_introduction/tutorial_2_fitting_data.py`
+- [Tutorial 3: Non Linear Search](chapter_1_introduction/tutorial_3_non_linear_search.md) — from `scripts/chapter_1_introduction/tutorial_3_non_linear_search.py`
+- [Tutorial 4: Why Modeling Is Hard](chapter_1_introduction/tutorial_4_why_modeling_is_hard.md) — from `scripts/chapter_1_introduction/tutorial_4_why_modeling_is_hard.py`
+- [Tutorial 5: Results And Samples](chapter_1_introduction/tutorial_5_results_and_samples.md) — from `scripts/chapter_1_introduction/tutorial_5_results_and_samples.py`
+
+These pages are regenerated manually by PyAutoBuild's `generate_markdown.py` when a curated script changes.
diff --git a/markdown/chapter_1_introduction/start_here.md b/markdown/chapter_1_introduction/start_here.md
new file mode 100644
index 0000000..6a532fa
--- /dev/null
+++ b/markdown/chapter_1_introduction/start_here.md
@@ -0,0 +1,62 @@
+> ✏️ **This page is auto-generated from [`scripts/chapter_1_introduction/start_here.py`](../../scripts/chapter_1_introduction/start_here.py) — do not edit it directly.**
+> It shows the example fully executed, with its real output images.
+> Run it yourself via the [Python script](../../scripts/chapter_1_introduction/start_here.py) or the [Jupyter notebook](../../notebooks/chapter_1_introduction/start_here.ipynb).
+
+HowToFit Lectures
+=================
+
+Welcome to the HowToFit Jupyter Notebook lectures!
+
+At the core of data science is fitting a model to data. This process extracts meaningful patterns, relationships,
+and insights, enabling accurate predictions, decision-making, and understanding of underlying processes.
+
+However, data science can be quite challenging. With a vast array of statistical methods to choose from, it can be
+difficult to determine the right one for your problem. Interpreting large volumes of results is complex, and managing big datasets requires significant computational power and sophisticated statistical methods.
+
+The HowToFit lectures teach you how to perform effective data science analysis. Designed at an undergraduate level,
+these lectures assume no prior knowledge of model-fitting, Bayesian statistics, or scientific analysis. They
+introduce core concepts without formal statistical equations, aiming to provide an understanding of the
+phenomenological methods used in data science. By the end of the lectures, you'll be equipped to perform your own
+data analysis.
+
+The lectures use the probabilistic programming language PyAutoFit, an open-source library for model-fitting,
+scientific analysis, and big data analysis (https://github.com/PyAutoLabs/PyAutoFit).
+
+The HowToFit lectures are composed of 3 chapters:
+
+**Chapter 1: Introduction**: How to fit a model to data, perform statistical inference, and interpret the results
+for scientific analysis.
+
+**Chapter 2: Scientific Workflow**: Scaling model-fitting to big datasets while ensuring detailed scientific analysis
+of the results.
+
+**Chapter 3: Graphical Models**: Simultaneous model fitting of large datasets, scaling up to models with tens of
+thousands of parameters.
+
+After each chapter, it is advised that you apply what you've learned to your own model-fitting analysis based on
+your scientific problem to build confidence in the techniques. Once confident, proceed to the next chapter.
+
+__Chapter 1: Introduction__
+
+The first chapter of the HowToFit lectures covers the basics of model-fitting, statistical inference, and scientific
+interpretation. The chapter includes:
+
+`tutorial_1_models.py`: What probabilistic models are and how to compose them using PyAutoFit.
+
+`tutorial_2_fitting_data.py`: Fitting a model with an input set of parameters to data and quantifying the goodness of fit.
+
+`tutorial_3_non_linear_search.py`: Searching non-linear parameter spaces to find the best-fit model.
+
+`tutorial_4_why_modeling_is_hard.py`: Why modeling becomes difficult and how to over model-fitting problems.
+
+`tutorial_5_results_and_samples.py`: Interpreting model-fit results and using the samples for scientific analysis.
+
+An applied astronomy-focused example (previously tutorial 8) now lives in
+`autofit_workspace_developer/scripts/howtofit/chapter_1_introduction/tutorial_8_astronomy_example.py`,
+alongside the larger `projects/cosmology/` example. These depend on astronomy-specific datasets
+that only live in the developer workspace.
+
+
+```python
+
+```
diff --git a/markdown/chapter_1_introduction/tutorial_1_models.md b/markdown/chapter_1_introduction/tutorial_1_models.md
new file mode 100644
index 0000000..4f5b485
--- /dev/null
+++ b/markdown/chapter_1_introduction/tutorial_1_models.md
@@ -0,0 +1,736 @@
+> ✏️ **This page is auto-generated from [`scripts/chapter_1_introduction/tutorial_1_models.py`](../../scripts/chapter_1_introduction/tutorial_1_models.py) — do not edit it directly.**
+> It shows the example fully executed, with its real output images.
+> Run it yourself via the [Python script](../../scripts/chapter_1_introduction/tutorial_1_models.py) or the [Jupyter notebook](../../notebooks/chapter_1_introduction/tutorial_1_models.ipynb).
+
+Tutorial 1: Models
+==================
+
+At the heart of model-fitting is the model: a set of equations, numerical processes, and assumptions describing a
+physical system of interest. The goal of model-fitting is to better understand this physical system and develop
+predictive models that describe it more accurately.
+
+In astronomy, a model might describe the distribution of stars within a galaxy. In biology, it might represent the
+interaction of proteins within a cell. In finance, it could describe the evolution of stock prices in a market.
+Regardless of the field, the model acts as a mathematical description of the physical system, aiming to enhance
+understanding and enable new predictions.
+
+Whatever your model, its equations are defined by "free parameters." Changing these parameters alters the
+behavior and predictions of the model.
+
+Once the model is defined and parameter values are chosen, the model creates "model data"—a realization of how the
+physical system appears given those parameters. This process, often referred to as "forward modeling," describes the
+physical system from its starting point and predicts the data we observe.
+
+By varying the model parameters, we can generate numerous model datasets. The ultimate goal of model-fitting, which
+you will learn by the end of this chapter, is to determine the model parameters and corresponding dataset that best
+fit the observed data.
+
+__Astronomy Example__
+
+For instance, in astronomy, we might model the distribution of stars, including:
+
+- A parameter describing the brightness of the stars.
+
+- Multiple parameters defining their distribution.
+
+- Several parameters describing their colors.
+
+If our model pertains to the distribution of stars within a galaxy, the forward model will produce an image of what
+that galaxy looks like when observed with a telescope. This forward model might account for physical effects such as
+the blurring of light due to diffraction in the telescope optics.
+
+By altering the parameters describing the stars, we can generate many different model images via this forward model.
+
+At the end of this chapter, we will use a real-world astronomy example to illustrate everything you have learned,
+including fitting a real galaxy observed with the Hubble Space Telescope.
+
+__Overview__
+
+In tutorial 1, we will cover the basics of defining a model, specifically:
+
+- Defining a simple model described by a few simple equations.
+
+- Showing that this model is characterized by three or more free parameters.
+
+- Using the model, with different sets of parameters, to generate model data.
+
+__Contents__
+
+This tutorial is split into the following sections:
+
+- **Paths**: Setting up the working directory path so the tutorial runs correctly on your computer.
+- **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.
+- **Model Mapping**: Mapping an input vector of parameters to the model to create an instance of the model.
+- **Complex Models**: Composing a more complex model with multiple model components and more free parameters.
+- **Tuple Parameters**: Defining a model component with tuple parameters.
+- **Extensibility**: Discussing how PyAutoFit's model composition API is scalable and extensible.
+- **Wrap Up**: Concluding the tutorial and considering how to apply the concepts to your own scientific problem.
+
+This tutorial introduces the PyAutoFit API for model composition, which forms the foundation of all model-fitting
+performed by PyAutoFit.
+
+
+```python
+
+import numpy as np
+import matplotlib.pyplot as plt
+
+import autofit as af
+```
+
+ .../PyAutoConf/autoconf/workspace.py:206: UserWarning: Cannot verify the workspace at HowToFit/scripts/chapter_1_introduction is compatible with the installed library version (2026.7.6.649): no `version.minimum_library_version` or `version.workspace_version` key in config/general.yaml and no version.txt at the workspace root.
+
+ If you cloned the workspace from `main` rather than a release tag, set `version.workspace_version_check: False` in config/general.yaml to silence this warning. The `main` branch updates more frequently than library releases, so version mismatches are expected and not actionable for `main`-branch users.
+
+ You can also set the environment variable PYAUTO_SKIP_WORKSPACE_VERSION_CHECK=1 to disable temporarily.
+ warnings.warn(_missing_version_warning(root, library_version))
+
+
+__Paths__
+
+PyAutoFit assumes the current working directory is /path/to/HowToFit/ on your hard-disk (or in Colab).
+This setup allows PyAutoFit to:
+
+- Load configuration settings from config files in the HowToFit/config folder.
+
+- Load example data from the HowToFit/dataset folder.
+
+- Output the results of model fits to your hard disk in the autofit/output folder.
+
+If you don't have a HowToFit clone, you can download it here:
+
+ https://github.com/PyAutoLabs/autofit_workspace
+
+__Model Parameterization__
+
+A model is a set of equations, numerical processes, and assumptions that describe a physical system and dataset.
+
+In this example, our model is one or more 1-dimensional Gaussians, defined by the following equation:
+
+\begin{equation*}
+g(x, I, \sigma) = \frac{N}{\sigma\sqrt{2\pi}} \exp{(-0.5 (x / \sigma)^2)}
+\end{equation*}
+
+Where:
+
+- `x`: The x-axis coordinate where the Gaussian is evaluated.
+
+- `N`: The overall normalization of the Gaussian.
+
+
+- `\sigma`: The size of the Gaussian (Full Width Half Maximum, $\mathrm{FWHM}$, is $2{\sqrt{2\ln 2}}\;\sigma$).
+
+While a 1D Gaussian might seem like a rudimentary model, it has many real-world applications in signal processing.
+For example, 1D Gaussians are fitted to datasets to measure the size of an observed signal. Thus, this model has
+practical real-world applications.
+
+We now have a model, expressed as a simple 1D Gaussian. The model has three parameters, $(x, N, \sigma)$. Using
+different combinations of these parameters creates different realizations of the model, which we illustrate below.
+
+__Model Composition__
+
+We now define the 1D Gaussian as a "model component" in PyAutoFit. We use the term "model component" because the model
+can be extended to include multiple components, each related to different equations and numerical processes.
+
+We first illustrate a model composed of a single model component, the 1D Gaussian. We then show a model made of
+multiple model components.
+
+To define a "model component" in PyAutoFit, we simply write it as a Python class using the format shown below:
+
+
+```python
+
+
+class Gaussian:
+ def __init__(
+ self,
+ centre: float = 30.0, # <- **PyAutoFit** recognises these constructor arguments
+ normalization: float = 1.0, # <- are the Gaussian`s model parameters.
+ sigma: float = 5.0,
+ ):
+ """
+ Represents a 1D Gaussian profile.
+
+ This is a model-component of example models in the **HowToFit** lectures and is used to perform model-fitting
+ of example datasets.
+
+ Parameters
+ ----------
+ centre
+ The x coordinate of the profile centre.
+ normalization
+ Overall normalization of the profile.
+ sigma
+ The sigma value controlling the size of the Gaussian.
+ """
+ self.centre = centre
+ self.normalization = normalization
+ self.sigma = sigma
+
+ def model_data_from(self, xvalues: np.ndarray) -> np.ndarray:
+ """
+ Returns a 1D Gaussian on an input list of Cartesian x coordinates.
+
+ The input xvalues are translated to a coordinate system centred on the Gaussian, via its `centre`.
+
+ The output is referred to as the `model_data` to signify that it is a representation of the data from the
+ model.
+
+ Parameters
+ ----------
+ xvalues
+ The x coordinates in the original reference frame of the data.
+
+ Returns
+ -------
+ np.array
+ The Gaussian values at the input x coordinates.
+ """
+ transformed_xvalues = np.subtract(xvalues, self.centre)
+ return np.multiply(
+ np.divide(self.normalization, self.sigma * np.sqrt(2.0 * np.pi)),
+ np.exp(-0.5 * np.square(np.divide(transformed_xvalues, self.sigma))),
+ )
+
+```
+
+The format of this Python class defines how PyAutoFit composes the Gaussian as a model component, where:
+
+- The name of the class is the name of the model component, in this case, "Gaussian".
+
+- The input arguments of the constructor (the `__init__` method) are the parameters of the model, in the example
+above `centre`, `normalization`, and `sigma`.
+
+- The default values and typing of the input arguments define whether a parameter is a single-valued float or a
+multi-valued tuple. For the `Gaussian` class above, no input parameters are tuples, but later examples use tuples.
+
+- It includes functions associated with that model component, specifically the model_data function. When we create
+instances of a `Gaussian` below, this function is used to generate a 1D representation of it as a NumPy array.
+
+__Model Creation__
+
+The `Gaussian` class above is a standard Python class. It does not yet act as a model component that can be used
+for model fitting with PyAutoFit.
+
+To transform the Gaussian class into a model component that can be used for model fitting with PyAutoFit, we use
+the `af.Model` object. This tells PyAutoFit to treat the input Python class as a model component.
+
+
+```python
+model = af.Model(Gaussian)
+print("Model `Gaussian` object: \n")
+print(model)
+```
+
+ Model `Gaussian` object:
+
+ Gaussian (centre, UniformPrior [0], lower_limit = 0.0, upper_limit = 100.0), (normalization, LogUniformPrior [1], lower_limit = 1e-06, upper_limit = 1000000.0), (sigma, UniformPrior [2], lower_limit = 0.0, upper_limit = 25.0)
+
+
+In PyAutoFit, a Model object encapsulates a model component that can be used for model fitting. It provides several
+attributes that describe the model component, such as the `total_free_parameters` attribute, which indicates the
+number of free parameters in the model:
+
+
+```python
+print(model.total_free_parameters)
+```
+
+ 3
+
+
+In PyAutoFit, you can retrieve comprehensive information about a model by accessing its `info` attribute.
+
+When you print the model info, it displays detailed information about each parameter in the model, including its name,
+type, and associated prior distribution. Priors define the expected range or distribution of values for each
+parameter during the model fitting process. If you're unfamiliar with priors, they are covered in tutorial 3 of
+this chapter, which explains their role in model fitting.
+
+[The `info` below may not display optimally on your computer screen, for example the whitespace between parameter
+names on the left and parameter priors on the right may lead them to appear across multiple lines. This is a
+common issue in Jupyter notebooks.
+
+The`info_whitespace_length` parameter in the file `config/general.yaml` in the "output" section can be changed to
+increase or decrease the amount of whitespace (The Jupyter notebook kernel will need to be reset for this change to
+appear in a notebook).]
+
+
+```python
+print(model.info)
+```
+
+ Total Free Parameters = 3
+
+ model Gaussian (N=3)
+
+ centre UniformPrior [0], lower_limit = 0.0, upper_limit = 100.0
+ normalization LogUniformPrior [1], lower_limit = 1e-06, upper_limit = 1000000.0
+ sigma UniformPrior [2], lower_limit = 0.0, upper_limit = 25.0
+
+
+__Model Mapping__
+
+In PyAutoFit, instances of model components created via the af.Model object can be instantiated by mapping an input
+vector of parameters to the Python class that the model object represents. The order of parameters in the model is
+crucial for correctly defining the input vector.
+
+To determine the order of parameters in the model, PyAutoFit provides the paths attribute of the model object.
+This attribute contains information about the parameter paths within the model.
+
+Here's how you can access the paths attribute to understand the order of parameters in the model:
+
+
+```python
+print(model.paths)
+```
+
+ [('centre',), ('normalization',), ('sigma',)]
+
+
+To create an instance of the Gaussian model component using PyAutoFit, following the order of parameters defined by
+the paths attribute (`centre`, `normalization`, and `sigma`), you can initialize the instance as follows:
+
+
+```python
+instance = model.instance_from_vector(vector=[30.0, 2.0, 3.0])
+```
+
+This is an instance of the `Gaussian` class.
+
+
+```python
+print("Model Instance: \n")
+print(instance)
+```
+
+ Model Instance:
+
+ <__main__.Gaussian object at 0x7fe0b75dbd40>
+
+
+It has the parameters of the `Gaussian` with the values input above.
+
+
+```python
+print("Instance Parameters \n")
+print("x = ", instance.centre)
+print("normalization = ", instance.normalization)
+print("sigma = ", instance.sigma)
+```
+
+ Instance Parameters
+
+ x = 30.0
+ normalization = 2.0
+ sigma = 3.0
+
+
+We can use all class functions, such as the `model_data_from` function, to generate an instance of the
+1D `Gaussian` and visualize it through plotting.
+
+The code below generates the 1D Gaussian model data, which requires an input list of x values where the Gaussian is
+evaluated. The output is a NumPy array of the Gaussian's y values at the input x coordinates.
+
+Although simple, the code below is essentially the process of forward modeling, where we use the model to generate
+the data we would observe in an experiment for a given set of parameters.
+
+
+```python
+xvalues = np.arange(0.0, 100.0, 1.0)
+
+model_data = instance.model_data_from(xvalues=xvalues)
+
+plt.plot(xvalues, model_data, color="r")
+plt.title("1D Gaussian Model Data.")
+plt.xlabel("x values of profile")
+plt.ylabel("Gaussian Value")
+plt.show()
+plt.clf()
+```
+
+
+
+
+
+
+
+
+
+
+
+__Complex Models__
+
+The code above may seem like a lot of steps just to create an instance of the `Gaussian` class. Couldn't we have
+simply done this instead?
+
+```python
+instance = Gaussian(centre=30.0, normalization=2.0, sigma=3.0)
+```
+
+Yes, we could have.
+
+However, the model composition API used above is designed to simplify the process of composing complex models that
+consist of multiple components with many free parameters. It provides a scalable approach for defining and
+manipulating models.
+
+To demonstrate this capability, let's conclude the tutorial by composing a model composed of a Gaussian
+component and another 1D profile, an `Exponential`, defined by the equation:
+
+\begin{equation*}
+g(x, I, \lambda) = N \lambda \exp{- \lambda x }
+\end{equation*}
+
+where:
+
+- `x`: Represents the x-axis coordinate where the Exponential profile is evaluated.
+
+- `N`: Describes the overall normalization of the Exponential profile.
+
+- $\lambda$: Represents the rate of decay of the exponential.
+
+We'll start by defining the `Exponential` profile using a format similar to the Gaussian definition above.
+
+
+```python
+
+
+class Exponential:
+ def __init__(
+ self,
+ centre: float = 30.0, # <- **PyAutoFit** recognises these constructor arguments
+ normalization: float = 1.0, # <- are the Exponential`s model parameters.
+ rate: float = 0.01,
+ ):
+ """
+ Represents a 1D Exponential profile.
+
+ This is a model-component of example models in the **HowToFit** lectures and is used to fit example datasets
+ via a non-linear search.
+
+ Parameters
+ ----------
+ centre
+ The x coordinate of the profile centre.
+ normalization
+ Overall normalization of the profile.
+ ratw
+ The decay rate controlling has fast the Exponential declines.
+ """
+ self.centre = centre
+ self.normalization = normalization
+ self.rate = rate
+
+ def model_data_from(self, xvalues: np.ndarray):
+ """
+ Returns a 1D Gaussian on an input list of Cartesian x coordinates.
+
+ The input xvalues are translated to a coordinate system centred on the `Exponential`, via its `centre`.
+
+ The output is referred to as the `model_data` to signify that it is a representation of the data from the
+ model.
+
+ Parameters
+ ----------
+ xvalues
+ The x coordinates in the original reference frame of the data.
+ """
+ transformed_xvalues = np.subtract(xvalues, self.centre)
+ return self.normalization * np.multiply(
+ self.rate, np.exp(-1.0 * self.rate * abs(transformed_xvalues))
+ )
+
+```
+
+We can construct a model comprising one `Gaussian` object and one `Exponential` object using the `af.Collection` object:
+
+
+```python
+model = af.Collection(gaussian=af.Model(Gaussian), exponential=af.Model(Exponential))
+```
+
+You can retrieve all the information about the model created via the `af.Collection` by printing its `info` attribute
+in one go:
+
+
+```python
+print(model.info)
+```
+
+ Total Free Parameters = 6
+
+ model Collection (N=6)
+ gaussian Gaussian (N=3)
+ exponential Exponential (N=3)
+
+ gaussian
+ centre UniformPrior [3], lower_limit = 0.0, upper_limit = 100.0
+ normalization LogUniformPrior [4], lower_limit = 1e-06, upper_limit = 1000000.0
+ sigma UniformPrior [5], lower_limit = 0.0, upper_limit = 25.0
+ exponential
+ centre UniformPrior [6], lower_limit = 0.0, upper_limit = 100.0
+ normalization LogUniformPrior [7], lower_limit = 1e-06, upper_limit = 1000000.0
+ rate UniformPrior [8], lower_limit = 0.0, upper_limit = 1.0
+
+
+When `Gaussian` and `Exponential` are added to a `Collection`, they are automatically assigned as `Model` objects.
+
+Therefore, there's no need to use the `af.Model` method when passing classes to a `Collection`, which makes the Python
+code more concise and readable.
+
+
+```python
+model = af.Collection(gaussian=Gaussian, exponential=Exponential)
+```
+
+The `model.info` is identical to the previous example.
+
+
+```python
+print(model.info)
+```
+
+ Total Free Parameters = 6
+
+ model Collection (N=6)
+ gaussian Gaussian (N=3)
+ exponential Exponential (N=3)
+
+ gaussian
+ centre UniformPrior [9], lower_limit = 0.0, upper_limit = 100.0
+ normalization LogUniformPrior [10], lower_limit = 1e-06, upper_limit = 1000000.0
+ sigma UniformPrior [11], lower_limit = 0.0, upper_limit = 25.0
+ exponential
+ centre UniformPrior [12], lower_limit = 0.0, upper_limit = 100.0
+ normalization LogUniformPrior [13], lower_limit = 1e-06, upper_limit = 1000000.0
+ rate UniformPrior [14], lower_limit = 0.0, upper_limit = 1.0
+
+
+A `Collection` functions analogously to a `Model`, but it includes multiple model components.
+
+This can be observed by examining its `paths` attribute, which displays paths to all 6 free parameters across both model components.
+
+The paths contain entries such as `.gaussian.` and `.exponential.`, corresponding to the names we provided when
+defining the `af.Collection` earlier. Modifying the names of the model components supplied to the `Collection`
+would adjust the paths accordingly.
+
+
+```python
+print(model.paths)
+```
+
+ [('gaussian', 'centre'), ('gaussian', 'normalization'), ('gaussian', 'sigma'), ('exponential', 'centre'), ('exponential', 'normalization'), ('exponential', 'rate')]
+
+
+A model instance can again be created by mapping an input `vector`, which now has 6 entries.
+
+
+```python
+instance = model.instance_from_vector(vector=[0.1, 0.2, 0.3, 0.4, 0.5, 0.01])
+```
+
+This `instance` contains each of the model components we defined above.
+
+The argument names input into the `Collection` define the attribute names of the `instance`:
+
+
+```python
+print("Instance Parameters \n")
+print("x (Gaussian) = ", instance.gaussian.centre)
+print("normalization (Gaussian) = ", instance.gaussian.normalization)
+print("sigma (Gaussian) = ", instance.gaussian.sigma)
+print("x (Exponential) = ", instance.exponential.centre)
+print("normalization (Exponential) = ", instance.exponential.normalization)
+print("sigma (Exponential) = ", instance.exponential.rate)
+```
+
+ Instance Parameters
+
+ x (Gaussian) = 0.1
+ normalization (Gaussian) = 0.2
+ sigma (Gaussian) = 0.3
+ x (Exponential) = 0.4
+ normalization (Exponential) = 0.5
+ sigma (Exponential) = 0.01
+
+
+In the context of the model's equations, it is simply the sum of the equations defining the `Gaussian`
+and `Exponential` components.
+
+To generate the `model_data`, we sum the `model_data` of each individual model component, as demonstrated and
+visualized below.
+
+
+```python
+xvalues = np.arange(0.0, 100.0, 1.0)
+
+model_data_0 = instance.gaussian.model_data_from(xvalues=xvalues)
+model_data_1 = instance.exponential.model_data_from(xvalues=xvalues)
+
+model_data = model_data_0 + model_data_1
+
+plt.plot(xvalues, model_data, color="r")
+plt.plot(xvalues, model_data_0, "b", "--")
+plt.plot(xvalues, model_data_1, "k", "--")
+plt.title("1D Gaussian + Exponential Model Data.")
+plt.xlabel("x values of profile")
+plt.ylabel("Value")
+plt.show()
+plt.clf()
+```
+
+
+
+
+
+
+
+
+
+
+
+__Tuple Parameters__
+
+The `Gaussian` and `Exponential` model components above only has parameters that are single-valued floats.
+
+Parameters can also be tuples, which is useful for defining model components where certain parameters are naturally
+grouped together.
+
+For example, we can define a 2D Gaussian with a center that has two coordinates and therefore free parameters, (x, y),
+using a tuple.
+
+
+```python
+from typing import Tuple
+
+
+class Gaussian2D:
+ def __init__(
+ self,
+ centre: Tuple[float, float] = (0.0, 0.0),
+ normalization: float = 0.1,
+ sigma: float = 1.0,
+ ):
+ self.centre = centre
+ self.normalization = normalization
+ self.sigma = sigma
+
+```
+
+The model's `total_free_parameters` attribute now includes 4 free parameters, as the tuple `centre` parameter accounts
+for 2 free parameters.
+
+
+```python
+model = af.Model(Gaussian2D)
+
+print("Total Free Parameters:", model.total_free_parameters)
+```
+
+ Total Free Parameters: 4
+
+
+This information is again displayed in the `info` attribute:
+
+
+```python
+print("\nInfo:")
+print(model.info)
+```
+
+
+ Info:
+ Total Free Parameters = 4
+
+ model Gaussian2D (N=4)
+
+ centre
+ centre_0 UniformPrior [15], lower_limit = 0.0, upper_limit = 100.0
+ centre_1 UniformPrior [16], lower_limit = 0.0, upper_limit = 100.0
+ normalization LogUniformPrior [17], lower_limit = 1e-06, upper_limit = 1000000.0
+ sigma UniformPrior [18], lower_limit = 0.0, upper_limit = 25.0
+
+
+The `paths` attribute provides information on the order of parameters in the model, illustrating how the
+`centre` tuple is split into two parameters.
+
+
+```python
+print("\nPaths:")
+print(model.paths)
+```
+
+
+ Paths:
+ [('centre', 'centre_0'), ('centre', 'centre_1'), ('normalization',), ('sigma',)]
+
+
+This ordering is used to create an instance of the `Gaussian2D` model component:
+
+
+```python
+instance = model.instance_from_vector(vector=[40.0, 60.0, 2.0, 3.0])
+
+print("\nInstance Parameters:")
+print("centre (x) = ", instance.centre[0])
+print("centre (y) = ", instance.centre[1])
+print("normalization = ", instance.normalization)
+print("sigma = ", instance.sigma)
+```
+
+
+ Instance Parameters:
+ centre (x) = 40.0
+ centre (y) = 60.0
+ normalization = 2.0
+ sigma = 3.0
+
+
+__Extensibility__
+
+It should now be clear why we use `Model` and `Collection` objects to construct our model.
+
+These objects facilitate the straightforward extension of our models to include multiple components and parameters.
+For instance, we can add more `Gaussian` and `Exponential` components to the `Collection`, or define new Python
+classes to represent entirely new model components with additional parameters.
+
+These objects serve numerous other essential purposes that we will explore in subsequent tutorials.
+
+**PyAutoFit** offers a comprehensive API for building models, which includes models constructed using NumPy arrays,
+hierarchies of Python classes, and graphical models where parameters are interconnected. These advanced modeling
+techniques are gradually introduced throughout the HowToFit lectures.
+
+For a detailed understanding of PyAutoFit's model composition API and a quick reference guide on how to construct
+models, you may want to take a quick look at the model cookbook in the PyAutoFit documentation. It provides an
+extensive overview and can serve as a helpful resource as you progress:
+
+[PyAutoFit Model Cookbook](https://pyautofit.readthedocs.io/en/latest/cookbooks/model.html)
+
+Don't worry if it seems a bit overwhelming at this stage; the concepts will become clearer as you continue exploring
+and working with PyAutoFit.
+
+__Wrap Up__
+
+In this tutorial, we've learned how to define and compose a model that can generate model data.
+
+Now, think about your specific field of study and the problem you want to address through model-fitting. Consider
+the following questions:
+
+- What type of model would best describe your data?
+
+- Which Python class, following the format introduced here, would you need to compose this model?
+
+- What are the free parameters of your model that need to be determined through fitting?
+
+If you decide to incorporate a new model component into your autofit_workspace tailored to your specific model-fitting
+task, refer to the following script:
+
+autofit_workspace/scripts/overview/new_model_component/new_model_component.py
+
+This script provides guidance on setting up the PyAutoFit configuration files associated with your custom model.
+
+
+```python
+
+```
diff --git a/markdown/chapter_1_introduction/tutorial_1_models_files/tutorial_1_models_19_0.png b/markdown/chapter_1_introduction/tutorial_1_models_files/tutorial_1_models_19_0.png
new file mode 100644
index 0000000..b758e2d
Binary files /dev/null and b/markdown/chapter_1_introduction/tutorial_1_models_files/tutorial_1_models_19_0.png differ
diff --git a/markdown/chapter_1_introduction/tutorial_1_models_files/tutorial_1_models_37_0.png b/markdown/chapter_1_introduction/tutorial_1_models_files/tutorial_1_models_37_0.png
new file mode 100644
index 0000000..35a0227
Binary files /dev/null and b/markdown/chapter_1_introduction/tutorial_1_models_files/tutorial_1_models_37_0.png differ
diff --git a/markdown/chapter_1_introduction/tutorial_2_fitting_data.md b/markdown/chapter_1_introduction/tutorial_2_fitting_data.md
new file mode 100644
index 0000000..71b2a15
--- /dev/null
+++ b/markdown/chapter_1_introduction/tutorial_2_fitting_data.md
@@ -0,0 +1,830 @@
+> ✏️ **This page is auto-generated from [`scripts/chapter_1_introduction/tutorial_2_fitting_data.py`](../../scripts/chapter_1_introduction/tutorial_2_fitting_data.py) — do not edit it directly.**
+> It shows the example fully executed, with its real output images.
+> Run it yourself via the [Python script](../../scripts/chapter_1_introduction/tutorial_2_fitting_data.py) or the [Jupyter notebook](../../notebooks/chapter_1_introduction/tutorial_2_fitting_data.ipynb).
+
+Tutorial 2: Fitting Data
+========================
+
+We've learned that a model consists of equations, numerical processes, and assumptions that describe a physical system.
+Using **PyAutoFit**, we defined simple 1D models like the Gaussian, composed them into models using `Model` and
+`Collection` objects, and generated model data by varying their parameters.
+
+To apply our model to real-world situations, we must fit it to data. Fitting involves assessing how well the model
+matches observed data. A good fit indicates that the model's parameter values accurately describe the physical system.
+Conversely, a poor fit suggests that adjustments are needed to better reflect reality.
+
+Model-fitting is a cyclical process: define the model, fit it to data, and refine the model based on insights gained.
+Iteratively improving the model's complexity enhances its ability to accurately represent the system under study.
+This iterative process lies at the core of model-fitting in scientific analysis.
+
+__Astronomy Example__
+
+In Astronomy, this process has been crucial for understanding the distributions of stars within galaxies. By
+fitting high-quality images of galaxies with increasingly sophisticated models, astronomers have determined that
+stars within galaxies are organized into structures such as disks, bars, and bulges. This approach has also revealed
+that stars appear differently in red and blue images due to variations in their age and composition.
+
+__Overview__
+
+In this tutorial, we will explore how to fit the `model_data` generated by a model to actual data. Specifically, we will:
+
+- Load data representing a 1D Gaussian signal, which serves as our target dataset for fitting.
+
+- Compute quantities such as residuals by subtracting the model data from the observed data.
+
+- Quantitatively assess the goodness-of-fit using a critical measure in model-fitting known as the `log_likelihood`.
+
+All these steps will utilize the **PyAutoFit** API for model composition, introduced in the previous tutorial.
+
+__Contents__
+
+This tutorial is split into the following sections:
+
+- **Data**: Load and plot the 1D Gaussian dataset we will fit.
+- **Model Data**: Generate model data of the `Gaussian` model using a forward model.
+- **Residuals**: Compute and visualize residuals between the model data and observed data.
+- **Normalized Residuals**: Compute and visualize normalized residuals, which account for the noise properties of the data.
+- **Chi Squared**: Compute and visualize the chi-squared map, a measure of the overall goodness-of-fit.
+- **Noise Normalization**: Compute the noise normalization term which describes the noise properties of the data.
+- **Likelihood**: Compute the log likelihood, a key measure of the goodness-of-fit of the model to the data.
+- **Recap**: Summarize the standard metrics for quantifying model fit quality.
+- **Fitting Models**: Fit the `Gaussian` model to the 1D data and compute the log likelihood, by guessing parameters.
+- **Guess 1**: A first parameter guess with an explanation of the resulting log likelihood.
+- **Guess 2**: An improved parameter guess with a better log likelihood.
+- **Guess 3**: The optimal parameter guess providing the best fit to the data.
+- **Extensibility**: Use the `Collection` object for fitting models with multiple components.
+- **Wrap Up**: Summarize the key concepts of this tutorial.
+
+
+```python
+
+from autoconf import setup_notebook; setup_notebook()
+
+from os import path
+import matplotlib.pyplot as plt
+import numpy as np
+
+import autofit as af
+```
+
+ Working Directory has been set to `HowToFit`
+
+
+__Data__
+
+Our dataset consists of noisy 1D data containing a signal, where the underlying signal can be modeled using
+equations such as a 1D Gaussian, a 1D Exponential, or a combination of multiple 1D profiles.
+
+We load this dataset from .json files, where:
+
+- `data` is a 1D NumPy array containing values representing the observed signal.
+
+- `noise_map` is a 1D NumPy array containing values representing the estimated root mean squared (RMS) noise level at
+ each data point.
+
+These datasets are generated using scripts located in `HowToFit/scripts/simulators`. Feel free to explore
+these scripts for more details!
+
+
+```python
+dataset_path = path.join("dataset", "example_1d", "gaussian_x1")
+```
+
+__Dataset Auto-Simulation__
+
+If the dataset does not already exist on your system, it will be created by running the corresponding
+simulator script. This ensures that all example scripts can be run without manually simulating data first.
+
+
+```python
+if not path.exists(dataset_path):
+ import subprocess
+ import sys
+
+ subprocess.run(
+ [sys.executable, "scripts/simulators/simulators.py"],
+ check=True,
+ )
+
+data = af.util.numpy_array_from_json(file_path=path.join(dataset_path, "data.json"))
+
+noise_map = af.util.numpy_array_from_json(
+ file_path=path.join(dataset_path, "noise_map.json")
+)
+```
+
+Next, we visualize the 1D signal using `matplotlib`.
+
+The signal is observed over uniformly spaced `xvalues`, computed using the `arange` function and `data.shape[0]` method.
+
+We will reuse these `xvalues` shortly when generating model data from the model.
+
+
+```python
+xvalues = np.arange(data.shape[0])
+plt.plot(xvalues, data, color="k")
+plt.title("1D Dataset Containing a Gaussian.")
+plt.xlabel("x values of profile")
+plt.ylabel("Signal Value")
+plt.show()
+```
+
+
+
+
+
+
+
+The earlier plot depicted only the signal without indicating the estimated noise at each data point.
+
+To visualize both the signal and its `noise_map`, we can use `matplotlib`'s `errorbar` function.
+
+
+```python
+plt.errorbar(
+ xvalues,
+ data,
+ yerr=noise_map,
+ linestyle="",
+ color="k",
+ ecolor="k",
+ elinewidth=1,
+ capsize=2,
+)
+plt.title("1D Gaussian dataset with errors from the noise-map.")
+plt.xlabel("x values of profile")
+plt.ylabel("Signal Value")
+plt.show()
+
+```
+
+
+
+
+
+
+
+__Model Data__
+
+To fit our `Gaussian` model to this data, we start by generating `model_data` from the 1D `Gaussian` model,
+following the same steps as outlined in the previous tutorial.
+
+We begin by again defining the `Gaussian` class, following the **PyAutoFit** format for model components.
+
+
+```python
+
+
+class Gaussian:
+ def __init__(
+ self,
+ centre: float = 30.0, # <- **PyAutoFit** recognises these constructor arguments
+ normalization: float = 1.0, # <- are the Gaussian`s model parameters.
+ sigma: float = 5.0,
+ ):
+ """
+ Represents a 1D Gaussian profile.
+
+ This is a model-component of example models in the **HowToFit** lectures and is used to perform model-fitting
+ of example datasets.
+
+ Parameters
+ ----------
+ centre
+ The x coordinate of the profile centre.
+ normalization
+ Overall normalization of the profile.
+ sigma
+ The sigma value controlling the size of the Gaussian.
+ """
+ self.centre = centre
+ self.normalization = normalization
+ self.sigma = sigma
+
+ def model_data_from(self, xvalues: np.ndarray) -> np.ndarray:
+ """
+ Returns a 1D Gaussian on an input list of Cartesian x coordinates.
+
+ The input xvalues are translated to a coordinate system centred on the Gaussian, via its `centre`.
+
+ The output is referred to as the `model_data` to signify that it is a representation of the data from the
+ model.
+
+ Parameters
+ ----------
+ xvalues
+ The x coordinates in the original reference frame of the data.
+
+ Returns
+ -------
+ np.array
+ The Gaussian values at the input x coordinates.
+ """
+ transformed_xvalues = np.subtract(xvalues, self.centre)
+ return np.multiply(
+ np.divide(self.normalization, self.sigma * np.sqrt(2.0 * np.pi)),
+ np.exp(-0.5 * np.square(np.divide(transformed_xvalues, self.sigma))),
+ )
+
+```
+
+To create `model_data` for the `Gaussian`, we use the model by providing it with `xvalues` corresponding to the
+observed data, as demonstrated in the previous tutorial.
+
+The following code essentially utilizes a forward model to generate the model data based on a specified set of
+parameters.
+
+
+```python
+model = af.Model(Gaussian)
+
+gaussian = model.instance_from_vector(vector=[60.0, 20.0, 15.0])
+
+model_data = gaussian.model_data_from(xvalues=xvalues)
+
+plt.plot(xvalues, model_data, color="r")
+plt.title("1D Gaussian model.")
+plt.xlabel("x values of profile")
+plt.ylabel("Profile Normalization")
+plt.show()
+plt.clf()
+```
+
+
+
+
+
+
+
+
+
+
+
+For comparison purposes, it is more informative to plot both the `data` and `model_data` on the same plot.
+
+
+```python
+plt.errorbar(
+ x=xvalues,
+ y=data,
+ yerr=noise_map,
+ linestyle="",
+ color="k",
+ ecolor="k",
+ elinewidth=1,
+ capsize=2,
+)
+plt.plot(xvalues, model_data, color="r")
+plt.title("Model-data fit to 1D Gaussian data.")
+plt.xlabel("x values of profile")
+plt.ylabel("Profile normalization")
+plt.show()
+plt.close()
+```
+
+
+
+
+
+
+
+Changing the values of `centre`, `normalization`, and `sigma` alters the appearance of the `Gaussian`.
+
+You can modify the parameters passed into `instance_from_vector()` above. After recomputing the `model_data`, plot
+it again to observe how these changes affect the Gaussian's appearance.
+
+__Residuals__
+
+While it's informative to compare the `data` and `model_data` above, gaining insights from the residuals can be even
+more useful.
+
+Residuals are calculated as `data - model_data` in 1D:
+
+
+```python
+residual_map = data - model_data
+plt.plot(xvalues, residual_map, color="k")
+plt.title("Residuals of model-data fit to 1D Gaussian data.")
+plt.xlabel("x values of profile")
+plt.ylabel("Residuals")
+plt.show()
+plt.clf()
+```
+
+
+
+
+
+
+
+
+
+
+
+Are these residuals indicative of a good fit to the data? Without considering the noise in the data, it's difficult
+to ascertain.
+
+We can plot the residuals with error bars based on the noise map. The plot below reveals that the model is a poor fit,
+as many residuals deviate significantly from zero even after accounting for the noise in each data point.
+
+A blue line through zero is included on the plot, to make it clear where residuals are not constent with zero
+above the noise level.
+
+
+```python
+residual_map = data - model_data
+plt.plot(range(data.shape[0]), np.zeros(data.shape[0]), "--", color="b")
+plt.errorbar(
+ x=xvalues,
+ y=residual_map,
+ yerr=noise_map,
+ color="k",
+ ecolor="k",
+ elinewidth=1,
+ capsize=2,
+ linestyle="",
+)
+plt.title("Residuals of model-data fit to 1D Gaussian data.")
+plt.xlabel("x values of profile")
+plt.ylabel("Residuals")
+plt.show()
+plt.clf()
+```
+
+
+
+
+
+
+
+
+
+
+
+__Normalized Residuals__
+
+Another method to quantify and visualize the quality of the fit is using the normalized residual map, also known as
+standardized residuals.
+
+The normalized residual map is computed as the residual map divided by the noise map:
+
+\[ \text{normalized\_residual} = \frac{\text{residual\_map}}{\text{noise\_map}} = \frac{\text{data} - \text{model\_data}}{\text{noise\_map}} \]
+
+If you're familiar with the concept of standard deviations (sigma) in statistics, the normalized residual map represents
+how many standard deviations the residual is from zero. For instance, a normalized residual of 2.0 (corresponding
+to a 95% confidence interval) means that the probability of the model underestimating the data by that amount is only 5%.
+
+Both the residual map with error bars and the normalized residual map convey the same information. However,
+the normalized residual map is particularly useful for visualization in multidimensional problems, as plotting
+error bars in 2D or higher dimensions is not straightforward.
+
+
+```python
+normalized_residual_map = residual_map / noise_map
+plt.plot(xvalues, normalized_residual_map, color="k")
+plt.title("Normalized residuals of model-data fit to 1D Gaussian data.")
+plt.xlabel("x values of profile")
+plt.ylabel("Normalized Residuals")
+plt.show()
+plt.clf()
+```
+
+
+
+
+
+
+
+
+
+
+
+__Chi Squared__
+
+Next, we define the `chi_squared_map`, which is obtained by squaring the `normalized_residual_map` and serves as a
+measure of goodness of fit.
+
+The chi-squared map is calculated as:
+
+\[ \chi^2 = \left(\frac{\text{data} - \text{model\_data}}{\text{noise\_map}}\right)^2 \]
+
+The purpose of squaring the normalized residual map is to ensure all values are positive. For instance, both a
+normalized residual of -0.2 and 0.2 would square to 0.04, indicating the same level of fit in terms of `chi_squared`.
+
+As seen from the normalized residual map, it's evident that the model does not provide a good fit to the data.
+
+
+```python
+chi_squared_map = (normalized_residual_map) ** 2
+plt.plot(xvalues, chi_squared_map, color="k")
+plt.title("Chi-Squared Map of model-data fit to 1D Gaussian data.")
+plt.xlabel("x values of profile")
+plt.ylabel("Chi-Squareds")
+plt.show()
+plt.clf()
+```
+
+
+
+
+
+
+
+
+
+
+
+Now, we consolidate all the information in our `chi_squared_map` into a single measure of goodness-of-fit
+called `chi_squared`.
+
+It is defined as the sum of all values in the `chi_squared_map` and is computed as:
+
+\[ \chi^2 = \sum \left(\frac{\text{data} - \text{model\_data}}{\text{noise\_map}}\right)^2 \]
+
+This summing process highlights why ensuring all values in the chi-squared map are positive is crucial. If we
+didn't square the values (making them positive), positive and negative residuals would cancel each other out,
+leading to an inaccurate assessment of the model's fit to the data.
+
+
+```python
+chi_squared = np.sum(chi_squared_map)
+print("Chi-squared = ", chi_squared)
+```
+
+ Chi-squared = 3894.173830959268
+
+
+The lower the `chi_squared`, the fewer residuals exist between the model's fit and the data, indicating a better
+overall fit!
+
+__Noise Normalization__
+
+Next, we introduce another quantity that contributes to our final assessment of the goodness-of-fit:
+the `noise_normalization`.
+
+The `noise_normalization` is computed as the logarithm of the sum of squared noise values in our data:
+
+\[
+\text{{noise\_normalization}} = \sum \log(2 \pi \text{{noise\_map}}^2)
+\]
+
+This quantity is fixed because the noise-map remains constant throughout the fitting process. Despite this,
+including the `noise_normalization` is considered good practice due to its statistical significance.
+
+Understanding the exact meaning of `noise_normalization` isn't critical for our primary goal of successfully
+fitting a model to a dataset. Essentially, it provides a measure of how well the noise properties of our data align
+with a Gaussian distribution.
+
+
+```python
+noise_normalization = np.sum(np.log(2 * np.pi * noise_map**2.0))
+```
+
+__Likelihood__
+
+From the `chi_squared` and `noise_normalization`, we can define a final goodness-of-fit measure known as
+the `log_likelihood`.
+
+This measure is calculated by taking the sum of the `chi_squared` and `noise_normalization`, and then multiplying the
+result by -0.5:
+
+\[ \text{log\_likelihood} = -0.5 \times \left( \chi^2 + \text{noise\_normalization} \right) \]
+
+Why multiply by -0.5? The exact rationale behind this factor isn't critical for our current understanding.
+
+
+```python
+log_likelihood = -0.5 * (chi_squared + noise_normalization)
+print("Log Likelihood = ", log_likelihood)
+```
+
+ Log Likelihood = -1717.0931863132812
+
+
+Above, we mentioned that a lower `chi_squared` indicates a better fit of the model to the data.
+
+When calculating the `log_likelihood`, we multiply the `chi_squared` by -0.5. Therefore, a higher log likelihood
+corresponds to a better model fit. This is what we aim for when fitting models to data, we want to maximize the
+log likelihood!
+
+__Recap__
+
+If you're familiar with model-fitting, you've likely encountered terms like 'residuals', 'chi-squared',
+and 'log_likelihood' before.
+
+These metrics are standard ways to quantify the quality of a model fit. They are applicable not only to 1D data but
+also to more complex data structures like 2D images, 3D data cubes, or any other multidimensional datasets.
+
+If these terms are new to you, it's important to understand their meanings as they form the basis of all
+model-fitting operations in **PyAutoFit** (and in statistical inference more broadly).
+
+Let's recap what we've learned so far:
+
+- We can define models, such as a 1D `Gaussian`, using Python classes that follow a specific format.
+
+- Models can be organized using `Collection` and `Model` objects, with parameters mapped to instances of their
+ respective model classes (e.g., `Gaussian`).
+
+- Using these model instances, we can generate model data, compare it to observed data, and quantify the
+ goodness-of-fit using the log likelihood.
+
+__Fitting Models__
+
+Now, armed with this knowledge, we are ready to fit our model to our data!
+
+But how do we find the best-fit model, which maximizes the log likelihood?
+
+The simplest approach is to guess parameters. Starting with initial parameter values that yield a good
+fit (i.e., a higher log likelihood), we iteratively adjust these values to refine our model until we achieve an
+optimal fit.
+
+For a 1D `Gaussian`, this iterative process works effectively. Below, we fit three different `Gaussian` models and
+identify the best-fit model—the one that matches the original dataset most closely.
+
+To streamline this process, I've developed functions that compute the `log_likelihood` of a model fit and visualize
+the data alongside the model predictions, complete with error bars.
+
+
+```python
+
+
+def log_likelihood_from(
+ data: np.ndarray, noise_map: np.ndarray, model_data: np.ndarray
+) -> float:
+ """
+ Compute the log likelihood of a model fit to data given the noise map.
+
+ Parameters
+ ----------
+ data
+ The observed data.
+ noise_map
+ The root mean square noise (or uncertainty) associated with each data point.
+ model_data
+ The model's predicted data for the given data x points.
+
+ Returns
+ -------
+ float
+ The log likelihood of the model fit to the data.
+ """
+ # Calculate residuals and normalized residuals
+ residual_map = data - model_data
+ normalized_residual_map = residual_map / noise_map
+
+ # Compute chi-squared and noise normalization
+ chi_squared_map = normalized_residual_map**2
+ chi_squared = np.sum(chi_squared_map)
+ noise_normalization = np.sum(np.log(2 * np.pi * noise_map**2.0))
+
+ # Compute log likelihood
+ log_likelihood = -0.5 * (chi_squared + noise_normalization)
+
+ return log_likelihood
+
+
+def plot_model_fit(
+ xvalues: np.ndarray,
+ data: np.ndarray,
+ noise_map: np.ndarray,
+ model_data: np.ndarray,
+ color: str = "k",
+):
+ """
+ Plot the observed data, model predictions, and error bars.
+
+ Parameters
+ ----------
+ xvalues
+ The x-axis values where the data is observed and model is predicted.
+ data
+ The observed data points.
+ noise_map
+ The root mean squared noise (or uncertainty) associated with each data point.
+ model_data
+ The model's predicted data for the given data x points.
+ color
+ The color for plotting (default is "k" for black).
+ """
+ plt.errorbar(
+ x=xvalues,
+ y=data,
+ yerr=noise_map,
+ linestyle="",
+ color=color,
+ ecolor="k",
+ elinewidth=1,
+ capsize=2,
+ )
+ plt.plot(xvalues, model_data, color="r")
+ plt.title("Fit of model-data to data")
+ plt.xlabel("x values of profile")
+ plt.ylabel("Profile Value")
+ plt.show()
+ plt.clf() # Clear figure to prevent overlapping plots
+
+```
+
+__Guess 1__
+
+The first guess correctly pinpoints that the Gaussian's peak is at 50.0, but the width and normalization are off.
+
+The `log_likelihood` is computed and printed, however because we don't have a value to compare it to yet, its hard
+to assess if it is a large or small value.
+
+
+```python
+
+gaussian = model.instance_from_vector(vector=[50.0, 10.0, 5.0])
+model_data = gaussian.model_data_from(xvalues=xvalues)
+plot_model_fit(
+ xvalues=xvalues,
+ data=data,
+ noise_map=noise_map,
+ model_data=model_data,
+ color="r",
+)
+
+log_likelihood = log_likelihood_from(
+ data=data, noise_map=noise_map, model_data=model_data
+)
+print(f"Log Likelihood: {log_likelihood}")
+```
+
+
+
+
+
+
+
+ Log Likelihood: -1529.6103506638979
+
+
+
+
+
+
+__Guess 2__
+
+The second guess refines the width and normalization, but the size of the Gaussian is still off.
+
+The `log_likelihood` is computed and printed, and increases a lot compared to the previous guess, indicating that
+the fit is better.
+
+
+```python
+
+gaussian = model.instance_from_vector(vector=[50.0, 25.0, 5.0])
+model_data = gaussian.model_data_from(xvalues=xvalues)
+plot_model_fit(
+ xvalues=xvalues,
+ data=data,
+ noise_map=noise_map,
+ model_data=model_data,
+ color="r",
+)
+
+log_likelihood = log_likelihood_from(
+ data=data, noise_map=noise_map, model_data=model_data
+)
+print(f"Log Likelihood: {log_likelihood}")
+```
+
+
+
+
+
+
+
+ Log Likelihood: -2391.6438609542743
+
+
+
+
+
+
+__Guess 3__
+
+The third guess provides a good fit to the data, with the Gaussian's peak, width, and normalization all accurately
+representing the observed signal.
+
+The `log_likelihood` is computed and printed, and is the highest value yet, indicating that this model provides the
+best fit to the data.
+
+
+```python
+
+gaussian = model.instance_from_vector(vector=[50.0, 25.0, 10.0])
+model_data = gaussian.model_data_from(xvalues=xvalues)
+plot_model_fit(
+ xvalues=xvalues,
+ data=data,
+ noise_map=noise_map,
+ model_data=model_data,
+ color="r",
+)
+
+log_likelihood = log_likelihood_from(
+ data=data, noise_map=noise_map, model_data=model_data
+)
+print(f"Log Likelihood: {log_likelihood}")
+```
+
+
+
+
+
+
+
+ Log Likelihood: 177.645544225071
+
+
+
+
+
+
+__Extensibility__
+
+Fitting models composed of multiple components is straightforward with PyAutoFit. Using the `Collection` object,
+we can define complex models consisting of several components. Once defined, we generate `model_data`
+from this collection and fit it to the observed data to compute the log likelihood.
+
+
+```python
+model = af.Collection(gaussian_0=Gaussian, gaussian_1=Gaussian)
+
+instance = model.instance_from_vector(vector=[40.0, 0.2, 0.3, 60.0, 0.5, 1.0])
+
+model_data_0 = instance.gaussian_0.model_data_from(xvalues=xvalues)
+model_data_1 = instance.gaussian_1.model_data_from(xvalues=xvalues)
+
+model_data = model_data_0 + model_data_1
+```
+
+We plot the data and model data below, showing that we get a bad fit (a low log likelihood) for this model.
+
+We could attempt to improve the model-fit and find a higher log likelihood solution by varying the parameters of
+the two Gaussians. However, with 6 parameters, this would be a challenging and cumbersome task to perform by eye.
+
+
+```python
+plot_model_fit(
+ xvalues=xvalues,
+ data=data,
+ noise_map=noise_map,
+ model_data=model_data,
+ color="r",
+)
+
+log_likelihood = log_likelihood_from(
+ data=data, noise_map=noise_map, model_data=model_data
+)
+print(f"Log Likelihood: {log_likelihood}")
+
+```
+
+
+
+
+
+
+
+ Log Likelihood: -5119.60036353835
+
+
+
+
+
+
+When our model consisted of only 3 parameters, it was manageable to visually guess their values and achieve a good
+fit to the data. However, as we expanded our model to include six parameters, this approach quickly became
+inefficient. Attempting to manually optimize models with even more parameters would effectively become impossible,
+and a more systematic approach is required.
+
+In the next tutorial, we will introduce an automated approach for fitting models to data. This method will enable
+us to systematically determine the optimal values of model parameters that best describe the observed data, without
+relying on manual guesswork.
+
+__Wrap Up__
+
+To conclude, take a moment to reflect on the model you ultimately aim to fit using **PyAutoFit**. What does your
+data look like? Is it one-dimensional, like a spectrum or a time series? Or is it two-dimensional, such as an image
+or a map? Visualize the nature of your data and consider whether you can define a mathematical model that
+accurately generates similar data.
+
+Can you imagine what a residual map would look like if you were to compare your model's predictions against this
+data? A residual map shows the differences between observed data and the model's predictions, often revealing
+patterns or areas where the model fits well or poorly.
+
+Furthermore, can you foresee how you would calculate a log likelihood from this residual map? The log likelihood q
+uantifies how well your model fits the data, incorporating both the residual values and the noise characteristics of
+your observations.
+
+If you find it challenging to visualize these aspects right now, that's perfectly fine. The first step is to
+grasp the fundamentals of fitting a model to data using **PyAutoFit**, which will provide you with the tools
+and understanding needed to address these questions effectively in the future.
+
+
+```python
+
+```
diff --git a/markdown/chapter_1_introduction/tutorial_2_fitting_data_files/tutorial_2_fitting_data_13_0.png b/markdown/chapter_1_introduction/tutorial_2_fitting_data_files/tutorial_2_fitting_data_13_0.png
new file mode 100644
index 0000000..e4cf568
Binary files /dev/null and b/markdown/chapter_1_introduction/tutorial_2_fitting_data_files/tutorial_2_fitting_data_13_0.png differ
diff --git a/markdown/chapter_1_introduction/tutorial_2_fitting_data_files/tutorial_2_fitting_data_15_0.png b/markdown/chapter_1_introduction/tutorial_2_fitting_data_files/tutorial_2_fitting_data_15_0.png
new file mode 100644
index 0000000..41e3572
Binary files /dev/null and b/markdown/chapter_1_introduction/tutorial_2_fitting_data_files/tutorial_2_fitting_data_15_0.png differ
diff --git a/markdown/chapter_1_introduction/tutorial_2_fitting_data_files/tutorial_2_fitting_data_17_0.png b/markdown/chapter_1_introduction/tutorial_2_fitting_data_files/tutorial_2_fitting_data_17_0.png
new file mode 100644
index 0000000..227d10f
Binary files /dev/null and b/markdown/chapter_1_introduction/tutorial_2_fitting_data_files/tutorial_2_fitting_data_17_0.png differ
diff --git a/markdown/chapter_1_introduction/tutorial_2_fitting_data_files/tutorial_2_fitting_data_19_0.png b/markdown/chapter_1_introduction/tutorial_2_fitting_data_files/tutorial_2_fitting_data_19_0.png
new file mode 100644
index 0000000..f570793
Binary files /dev/null and b/markdown/chapter_1_introduction/tutorial_2_fitting_data_files/tutorial_2_fitting_data_19_0.png differ
diff --git a/markdown/chapter_1_introduction/tutorial_2_fitting_data_files/tutorial_2_fitting_data_21_0.png b/markdown/chapter_1_introduction/tutorial_2_fitting_data_files/tutorial_2_fitting_data_21_0.png
new file mode 100644
index 0000000..1472cf9
Binary files /dev/null and b/markdown/chapter_1_introduction/tutorial_2_fitting_data_files/tutorial_2_fitting_data_21_0.png differ
diff --git a/markdown/chapter_1_introduction/tutorial_2_fitting_data_files/tutorial_2_fitting_data_23_0.png b/markdown/chapter_1_introduction/tutorial_2_fitting_data_files/tutorial_2_fitting_data_23_0.png
new file mode 100644
index 0000000..4033854
Binary files /dev/null and b/markdown/chapter_1_introduction/tutorial_2_fitting_data_files/tutorial_2_fitting_data_23_0.png differ
diff --git a/markdown/chapter_1_introduction/tutorial_2_fitting_data_files/tutorial_2_fitting_data_33_0.png b/markdown/chapter_1_introduction/tutorial_2_fitting_data_files/tutorial_2_fitting_data_33_0.png
new file mode 100644
index 0000000..c311bab
Binary files /dev/null and b/markdown/chapter_1_introduction/tutorial_2_fitting_data_files/tutorial_2_fitting_data_33_0.png differ
diff --git a/markdown/chapter_1_introduction/tutorial_2_fitting_data_files/tutorial_2_fitting_data_35_0.png b/markdown/chapter_1_introduction/tutorial_2_fitting_data_files/tutorial_2_fitting_data_35_0.png
new file mode 100644
index 0000000..38844ce
Binary files /dev/null and b/markdown/chapter_1_introduction/tutorial_2_fitting_data_files/tutorial_2_fitting_data_35_0.png differ
diff --git a/markdown/chapter_1_introduction/tutorial_2_fitting_data_files/tutorial_2_fitting_data_37_0.png b/markdown/chapter_1_introduction/tutorial_2_fitting_data_files/tutorial_2_fitting_data_37_0.png
new file mode 100644
index 0000000..fa8a3f3
Binary files /dev/null and b/markdown/chapter_1_introduction/tutorial_2_fitting_data_files/tutorial_2_fitting_data_37_0.png differ
diff --git a/markdown/chapter_1_introduction/tutorial_2_fitting_data_files/tutorial_2_fitting_data_41_0.png b/markdown/chapter_1_introduction/tutorial_2_fitting_data_files/tutorial_2_fitting_data_41_0.png
new file mode 100644
index 0000000..c861af9
Binary files /dev/null and b/markdown/chapter_1_introduction/tutorial_2_fitting_data_files/tutorial_2_fitting_data_41_0.png differ
diff --git a/markdown/chapter_1_introduction/tutorial_2_fitting_data_files/tutorial_2_fitting_data_7_0.png b/markdown/chapter_1_introduction/tutorial_2_fitting_data_files/tutorial_2_fitting_data_7_0.png
new file mode 100644
index 0000000..800b46f
Binary files /dev/null and b/markdown/chapter_1_introduction/tutorial_2_fitting_data_files/tutorial_2_fitting_data_7_0.png differ
diff --git a/markdown/chapter_1_introduction/tutorial_2_fitting_data_files/tutorial_2_fitting_data_9_0.png b/markdown/chapter_1_introduction/tutorial_2_fitting_data_files/tutorial_2_fitting_data_9_0.png
new file mode 100644
index 0000000..8286e11
Binary files /dev/null and b/markdown/chapter_1_introduction/tutorial_2_fitting_data_files/tutorial_2_fitting_data_9_0.png differ
diff --git a/markdown/chapter_1_introduction/tutorial_3_non_linear_search.md b/markdown/chapter_1_introduction/tutorial_3_non_linear_search.md
new file mode 100644
index 0000000..3d158ff
--- /dev/null
+++ b/markdown/chapter_1_introduction/tutorial_3_non_linear_search.md
@@ -0,0 +1,1325 @@
+> ✏️ **This page is auto-generated from [`scripts/chapter_1_introduction/tutorial_3_non_linear_search.py`](../../scripts/chapter_1_introduction/tutorial_3_non_linear_search.py) — do not edit it directly.**
+> It shows the example fully executed, with its real output images.
+> Run it yourself via the [Python script](../../scripts/chapter_1_introduction/tutorial_3_non_linear_search.py) or the [Jupyter notebook](../../notebooks/chapter_1_introduction/tutorial_3_non_linear_search.ipynb).
+
+Tutorial 3: Non Linear Search
+=============================
+
+In the previous tutorials, we laid the groundwork by defining a model and manually fitting it to data using fitting
+functions. We quantified the goodness of fit using the log likelihood and demonstrated that for models with only a few
+free parameters, we could achieve satisfactory fits by manually guessing parameter values. However, as the complexity
+of our models increased, this approach quickly became impractical.
+
+In this tutorial, we will delve into a more systematic approach for fitting models to data. This technique is designed
+to handle models with a larger number of parameters—ranging from tens to hundreds. By adopting this approach, we aim
+to achieve more efficient and reliable model fits, ensuring that our models accurately capture the underlying
+structure of the data.
+
+This approach not only improves the accuracy of our fits but also allows us to explore more complex models that better
+represent the systems we are studying.
+
+__Overview__
+
+In this tutorial, we will use a non-linear search to fit a 1D Gaussian profile to noisy data. Specifically, we will:
+
+- Introduce concept like a "parameter space", "likelihood surface" and "priors", and relate them to how a non-linear
+ search works.
+
+- Introduce the `Analysis` class, which defines the `log_likelihood_function` that quantifies the goodness of fit of a
+ model instance to the data.
+
+- Fit a 1D Gaussian model to 1D data with different non-linear searches, including a maximum likelihood estimator (MLE),
+ Markok Chain Monte Carlo (MCMC) and nested sampling.
+
+All these steps utilize **PyAutoFit**'s API for model-fitting.
+
+__Contents__
+
+This tutorial is split into the following sections:
+
+- **Parameter Space**: Introduce the concept of a "parameter space" and how it relates to model-fitting.
+- **Non-Linear Search**: Introduce the concept of a "non-linear search" and how it fits models to data.
+- **Search Types**: Introduce the maximum likelihood estimator (MLE), Markov Chain Monte Carlo (MCMC) and nested sampling search algorithms used in this tutorial.
+- **Deeper Background**: Provide links to resources that more thoroughly describe the statistical principles that underpin non-linear searches.
+- **Data**: Load and plot the 1D Gaussian dataset we'll fit.
+- **Model**: Introduce the 1D `Gaussian` model we'll fit to the data.
+- **Priors**: Introduce priors and how they are used to define the parameter space and guide the non-linear search.
+- **Analysis**: Introduce the `Analysis` class, which contains the `log_likelihood_function` used to fit the model to the data.
+- **Searches**: An overview of the searches used in this tutorial.
+- **Maximum Likelihood Estimation (MLE)**: Perform a model-fit using the MLE search.
+- **Markov Chain Monte Carlo (MCMC)**: Perform a model-fit using the MCMC search.
+- **Nested Sampling**: Perform a model-fit using the nested sampling search.
+- **What is The Best Search To Use?**: Compare the strengths and weaknesses of each search method.
+- **Wrap Up**: A summary of the concepts introduced in this tutorial.
+
+__Parameter Space__
+
+In mathematics, a function is defined by its parameters, which relate inputs to outputs.
+
+For example, consider a simple function:
+
+\[ f(x) = x^2 \]
+
+Here, \( x \) is the parameter input into the function \( f \), and \( f(x) \) returns \( x^2 \). This
+mapping between \( x \) and \( f(x) \) defines the "parameter space" of the function, which in this case is a parabola.
+
+Functions can have multiple parameters, such as \( x \), \( y \), and \( z \):
+
+\[ f(x, y, z) = x + y^2 - z^3 \]
+
+Here, the mapping between \( x \), \( y \), \( z \), and \( f(x, y, z) \) defines a parameter space with three
+dimensions.
+
+This concept of a parameter space relates closely to how we define and use instances of models in model-fitting.
+For instance, in our previous tutorial, we used instances of a 1D Gaussian profile with
+parameters \( (x, I, \sigma) \) to fit data and compute a log likelihood.
+
+This process can be thought of as complete analogous to a function \( f(x, y, z) \), where the output value is the
+log likelihood. This key function, which maps parameter values to a log likelihood, is called the "likelihood function"
+in statistical inference, albeit we will refer to it hereafter as the `log_likelihood_function` to be explicit
+that it is the log of the likelihood function.
+
+By expressing the likelihood in this manner, we can consider our model as having a parameter space -— a
+multidimensional surface that spans all possible values of the model parameters \( x, I, \sigma \).
+
+This surface is often referred to as the "likelihood surface", and our objective during model-fitting is to find
+its peak.
+
+This parameter space is "non-linear", meaning the relationship between the input parameters and the log likelihood
+does not behave linearly. This non-linearity implies that we cannot predict the log likelihood from a set of model
+parameters without actually performing a fit to the data by performing the forward model calculation.
+
+__Non-Linear Search__
+
+Now that we understand our problem in terms of a non-linear parameter space with a likelihood surface, we can
+introduce the method used to fit the model to the data—the "non-linear search".
+
+Previously, our approach involved manually guessing models until finding one with a good fit and high log likelihood.
+Surprisingly, this random guessing forms the basis of how model-fitting using a non-linear search actually works!
+
+A non-linear search involves systematically guessing many models while tracking their log likelihoods. As the
+algorithm progresses, it tends to favor models with parameter combinations that have previously yielded higher
+log likelihoods. This iterative refinement helps to efficiently explore the vast parameter space.
+
+There are two key differences between guessing random models and using a non-linear search:
+
+- **Computational Efficiency**: The non-linear search can evaluate the log likelihood of a model parameter
+ combinations in milliseconds and therefore many thousands of models in minutes. This computational speed enables
+ it to thoroughly sample potential solutions, which would be impractical for a human.
+
+- **Effective Sampling**: The search algorithm maintains a robust memory of previously guessed models and their log
+ likelihoods. This allows it to sample potential solutions more thoroughly and converge on the highest
+ likelihood solutions more efficiently, which is again impractical for a human.
+
+Think of the non-linear search as systematically exploring parameter space to pinpoint regions with the highest log
+likelihood values. Its primary goal is to identify and converge on the parameter values that best describe the data.
+
+__Search Types__
+
+There are different types of non-linear searches, each of which explores parameter space in a unique way.
+In this example, we will use three types of searches, which broadly represent the various approaches to non-linear
+searches used in statistical inference.
+
+These are:
+
+- **Maximum Likelihood Estimation (MLE)**: This method aims to find the model that maximizes the likelihood function.
+ It does so by testing nearby models and adjusting parameters in the direction that increases the likelihood.
+
+- **Markov Chain Monte Carlo (MCMC)**: This approach uses a group of "walkers" that explore parameter space randomly.
+ The likelihood at each walker's position influences the probability of the walker moving to a new position.
+
+- **Nested Sampling**: This technique samples points from the parameter space iteratively. Lower likelihood points
+ are replaced by higher likelihood ones, gradually concentrating the samples in regions of high likelihood.
+
+We will provide more details on each of these searches below.
+
+__Deeper Background__
+
+**The descriptions of how searches work in this example are simplfied and phoenomenological and do not give a full
+description of how they work at a deep statistical level. The goal is to provide you with an intuition for how to use
+them and when different searches are appropriate for different problems. Later tutorials will provide a more formal
+description of how these searches work.**
+
+If you're interested in learning more about these principles, you can explore resources such as:
+
+- [Markov Chain Monte Carlo (MCMC)](https://en.wikipedia.org/wiki/Markov_chain_Monte_Carlo)
+- [Introduction to MCMC Sampling](https://twiecki.io/blog/2015/11/10/mcmc-sampling/)
+- [Nested Sampling](https://www.imperial.ac.uk/media/imperial-college/research-centres-and-groups/astrophysics/public/icic/data-analysis-workshop/2016/NestedSampling_JRP.pdf)
+- [A Zero-Math Introduction to MCMC Methods](https://towardsdatascience.com/a-zero-math-introduction-to-markov-chain-monte-carlo-methods-dcba889e0c50)
+
+
+```python
+
+import numpy as np
+import matplotlib.pyplot as plt
+from os import path
+
+import autofit as af
+
+from autoconf import setup_notebook; setup_notebook()
+```
+
+ .../PyAutoConf/autoconf/workspace.py:206: UserWarning: Cannot verify the workspace at HowToFit/scripts/chapter_1_introduction is compatible with the installed library version (2026.7.6.649): no `version.minimum_library_version` or `version.workspace_version` key in config/general.yaml and no version.txt at the workspace root.
+
+ If you cloned the workspace from `main` rather than a release tag, set `version.workspace_version_check: False` in config/general.yaml to silence this warning. The `main` branch updates more frequently than library releases, so version mismatches are expected and not actionable for `main`-branch users.
+
+ You can also set the environment variable PYAUTO_SKIP_WORKSPACE_VERSION_CHECK=1 to disable temporarily.
+ warnings.warn(_missing_version_warning(root, library_version))
+
+
+ Working Directory has been set to `HowToFit`
+
+
+__Data__
+
+Load and plot the dataset from the `HowToFit/dataset` folder.
+
+
+```python
+dataset_path = path.join("dataset", "example_1d", "gaussian_x1")
+```
+
+__Dataset Auto-Simulation__
+
+If the dataset does not already exist on your system, it will be created by running the corresponding
+simulator script. This ensures that all example scripts can be run without manually simulating data first.
+
+
+```python
+if not path.exists(dataset_path):
+ import subprocess
+ import sys
+
+ subprocess.run(
+ [sys.executable, "scripts/simulators/simulators.py"],
+ check=True,
+ )
+
+data = af.util.numpy_array_from_json(file_path=path.join(dataset_path, "data.json"))
+noise_map = af.util.numpy_array_from_json(
+ file_path=path.join(dataset_path, "noise_map.json")
+)
+
+xvalues = np.arange(data.shape[0])
+
+plt.errorbar(
+ xvalues,
+ data,
+ yerr=noise_map,
+ linestyle="",
+ color="k",
+ ecolor="k",
+ elinewidth=1,
+ capsize=2,
+)
+plt.title("1D Gaussian dataset.")
+plt.xlabel("x values of profile")
+plt.ylabel("Profile Normalization")
+plt.show()
+plt.clf()
+```
+
+
+
+
+
+
+
+
+
+
+
+__Model__
+
+Create the `Gaussian` class from which we will compose model components using the standard format.
+
+
+```python
+
+
+class Gaussian:
+ def __init__(
+ self,
+ centre: float = 30.0, # <- **PyAutoFit** recognises these constructor arguments
+ normalization: float = 1.0, # <- are the Gaussian`s model parameters.
+ sigma: float = 5.0,
+ ):
+ """
+ Represents a 1D Gaussian profile.
+
+ This is a model-component of example models in the **HowToFit** lectures and is used to perform model-fitting
+ of example datasets.
+
+ Parameters
+ ----------
+ centre
+ The x coordinate of the profile centre.
+ normalization
+ Overall normalization of the profile.
+ sigma
+ The sigma value controlling the size of the Gaussian.
+ """
+ self.centre = centre
+ self.normalization = normalization
+ self.sigma = sigma
+
+ def model_data_from(self, xvalues: np.ndarray) -> np.ndarray:
+ """
+ Returns a 1D Gaussian on an input list of Cartesian x coordinates.
+
+ The input xvalues are translated to a coordinate system centred on the Gaussian, via its `centre`.
+
+ The output is referred to as the `model_data` to signify that it is a representation of the data from the
+ model.
+
+ Parameters
+ ----------
+ xvalues
+ The x coordinates in the original reference frame of the data.
+
+ Returns
+ -------
+ np.array
+ The Gaussian values at the input x coordinates.
+ """
+ transformed_xvalues = np.subtract(xvalues, self.centre)
+ return np.multiply(
+ np.divide(self.normalization, self.sigma * np.sqrt(2.0 * np.pi)),
+ np.exp(-0.5 * np.square(np.divide(transformed_xvalues, self.sigma))),
+ )
+
+```
+
+We now compose our model, a single 1D Gaussian, which we will fit to the data via the non-linear search.
+
+
+```python
+model = af.Model(Gaussian)
+
+print(model.info)
+```
+
+ Total Free Parameters = 3
+
+ model Gaussian (N=3)
+
+ centre UniformPrior [0], lower_limit = 0.0, upper_limit = 100.0
+ normalization LogUniformPrior [1], lower_limit = 1e-06, upper_limit = 1000000.0
+ sigma UniformPrior [2], lower_limit = 0.0, upper_limit = 25.0
+
+
+__Priors__
+
+When we examine the `.info` of our model, we notice that each parameter (like `centre`, `normalization`,
+and `sigma` in our Gaussian model) is associated with priors, such as `UniformPrior`. These priors define the
+range of permissible values that each parameter can assume during the model fitting process.
+
+The priors displayed above use default values defined in the `config/priors` directory. These default values have
+been chosen to be broad, and contain all plausible solutions contained in the simulated 1D Gaussian datasets.
+
+For instance, consider the `centre` parameter of our Gaussian. In theory, it could take on any value from
+negative to positive infinity. However, upon inspecting our dataset, we observe that valid values for `centre`
+fall strictly between 0.0 and 100.0. By using a `UniformPrior` with `lower_limit=0.0` and `upper_limit=100.0`,
+we restrict our parameter space to include only physically plausible values.
+
+Priors serve two primary purposes:
+
+**Defining Valid Parameter Space:** Priors specify the range of parameter values that constitute valid solutions.
+This ensures that our model explores only those solutions that are consistent with our observed data and physical
+constraints.
+
+**Incorporating Prior Knowledge:** Priors also encapsulate our prior beliefs or expectations about the model
+parameters. For instance, if we have previously fitted a similar model to another dataset and obtained certain
+parameter values, we can incorporate this knowledge into our priors for a new dataset. This approach guides the
+model fitting process towards parameter values that are more probable based on our prior understanding.
+
+While we are using `UniformPriors` in this tutorial due to their simplicity, **PyAutoFit** offers various other
+priors like `TruncatedGaussianPrior` and `LogUniformPrior`. These priors are useful for encoding different forms of prior
+information, such as normally distributed values around a mean (`TruncatedGaussianPrior`) or parameters spanning multiple
+orders of magnitude (`LogUniformPrior`).
+
+
+```python
+model.centre = af.UniformPrior(lower_limit=0.0, upper_limit=100.0)
+model.normalization = af.UniformPrior(lower_limit=0.0, upper_limit=10.0)
+model.sigma = af.UniformPrior(lower_limit=0.0, upper_limit=10.0)
+```
+
+__Analysis__
+
+In **PyAutoFit**, the `Analysis` class plays a crucial role in interfacing between the data being fitted and the
+model under consideration. Its primary responsibilities include:
+
+**Receiving Data:** The `Analysis` class is initialized with the data (`data`) and noise map (`noise_map`) that
+ the model aims to fit.
+
+**Defining the Log Likelihood Function:** The `Analysis` class defines the `log_likelihood_function`, which
+ computes the log likelihood of a model instance given the data. It evaluates how well the model, for a given set of
+ parameters, fits the observed data.
+
+**Interface with Non-linear Search:** The `log_likelihood_function` is repeatedly called by the non-linear search
+ algorithm to assess the goodness of fit of different parameter combinations. The search algorithm call this function
+ many times and maps out regions of parameter space that yield high likelihood solutions.
+
+Below is a suitable `Analysis` class for fitting a 1D gaussian to the data loaded above.
+
+
+```python
+
+
+class Analysis(af.Analysis):
+ def __init__(self, data: np.ndarray, noise_map: np.ndarray):
+ """
+ The `Analysis` class acts as an interface between the data and model in **PyAutoFit**.
+
+ Its `log_likelihood_function` defines how the model is fitted to the data and it is called many times by
+ the non-linear search fitting algorithm.
+
+ In this example the `Analysis` `__init__` constructor only contains the `data` and `noise-map`, but it can be
+ easily extended to include other quantities.
+
+ Parameters
+ ----------
+ data
+ A 1D numpy array containing the data (e.g. a noisy 1D signal) fitted in the workspace examples.
+ noise_map
+ A 1D numpy array containing the noise values of the data, used for computing the goodness of fit
+ metric, the log likelihood.
+ """
+ super().__init__()
+
+ self.data = data
+ self.noise_map = noise_map
+
+ def log_likelihood_function(self, instance) -> float:
+ """
+ Returns the log likelihood of a fit of a 1D Gaussian to the dataset.
+
+ The `instance` that comes into this method is an instance of the `Gaussian` model above. The parameter values
+ are chosen by the non-linear search, based on where it thinks the high likelihood regions of parameter
+ space are.
+
+ The lines of Python code are commented out below to prevent excessive print statements when we run the
+ non-linear search, but feel free to uncomment them and run the search to see the parameters of every instance
+ that it fits.
+
+ print("Gaussian Instance:")
+ print("Centre = ", instance.centre)
+ print("Normalization = ", instance.normalization)
+ print("Sigma = ", instance.sigma)
+
+ The data is fitted using an `instance` of the `Gaussian` class where its `model_data_from`
+ is called in order to create a model data representation of the Gaussian that is fitted to the data.
+ """
+ xvalues = np.arange(self.data.shape[0])
+
+ model_data = instance.model_data_from(xvalues=xvalues)
+ residual_map = self.data - model_data
+ chi_squared_map = (residual_map / self.noise_map) ** 2.0
+ chi_squared = sum(chi_squared_map)
+ noise_normalization = np.sum(np.log(2 * np.pi * noise_map**2.0))
+ log_likelihood = -0.5 * (chi_squared + noise_normalization)
+
+ return log_likelihood
+
+```
+
+We create an instance of the `Analysis` class by simply passing it the `data` and `noise_map`:
+
+
+```python
+analysis = Analysis(data=data, noise_map=noise_map)
+```
+
+__Searches__
+
+To perform a non-linear search, we create an instance of a `NonLinearSearch` object. **PyAutoFit** offers many options
+for this. A detailed description of each search method and guidance on when to use them can be found in
+the [search cookbook](https://pyautofit.readthedocs.io/en/latest/cookbooks/search.html).
+
+In this tutorial, we’ll focus on three searches that represent different approaches to model fitting:
+
+1. **Maximum Likelihood Estimation (MLE)** using the `LBFGS` non-linear search.
+2. **Markov Chain Monte Carlo (MCMC)** using the `Emcee` non-linear search.
+3. **Nested Sampling** using the `Dynesty` non-linear search.
+
+In this example, non-linear search results are stored in memory rather and not written to hard disk because the fits
+are fast and can therefore be easily regenerated. The next tutorial will perform fits which write results to the
+hard-disk.
+
+__Maximum Likelihood Estimation (MLE)__
+
+Maximum likelihood estimation (MLE) is the most straightforward type of non-linear search. Here’s a simplified
+overview of how it works:
+
+1. Starts at a point in parameter space with a set of initial values for the model parameters.
+2. Calculates the likelihood of the model at this starting point.
+3. Evaluates the likelihood at nearby points to estimate the gradient, determining the direction in which to move "up" in parameter space.
+4. Moves to a new point where, based on the gradient, the likelihood is higher.
+
+This process repeats until the search finds a point where the likelihood can no longer be improved, indicating that
+the maximum likelihood has been reached.
+
+The `LBFGS` search is an example of an MLE algorithm that follows this iterative procedure. Let’s see how it
+performs on our 1D Gaussian model.
+
+In the example below, we don’t specify a starting point for the MLE, so it begins at the center of the prior
+range for each parameter.
+
+
+```python
+search = af.LBFGS()
+```
+
+To begin the model-fit via the non-linear search, we pass it our model and analysis and begin the fit.
+
+The fit will take a minute or so to run.
+
+
+```python
+print(
+ """
+ The non-linear search has begun running.
+ This Jupyter notebook cell with progress once the search has completed - this could take a few minutes!
+ """
+)
+
+model = af.Model(Gaussian)
+
+result = search.fit(model=model, analysis=analysis)
+
+print("The search has finished run - you may now continue the notebook.")
+```
+
+
+ The non-linear search has begun running.
+ This Jupyter notebook cell with progress once the search has completed - this could take a few minutes!
+
+ 2026-07-11 16:22:50,877 - autofit.non_linear.search.abstract_search - INFO - Starting non-linear search with 1 cores.
+
+
+ 2026-07-11 16:22:50,890 - root - INFO - Output to hard-disk disabled, input a search name to enable.
+
+
+ 2026-07-11 16:22:51,022 - autofit.non_linear.initializer - INFO - Generating initial samples of model using JAX LH Function cores
+
+
+ 2026-07-11 16:22:51,023 - autofit.non_linear.initializer - INFO - Initial samples generated, starting non-linear search
+
+
+ 2026-07-11 16:22:51,024 - root - INFO - Starting new L-BFGS-B non-linear search (no previous samples found).
+
+
+ 2026-07-11 16:22:51,025 - root - INFO - Visualizing Starting Point Model in image_start folder.
+
+
+ 2026-07-11 16:22:51,043 - autofit.non_linear.search.updater - INFO - Creating latent samples by drawing 100 from the PDF.
+
+
+ 2026-07-11 16:22:51,044 - autofit.non_linear.search.updater - INFO - Drawing via PDF not available for this search, using all samples above the samples weight threshold instead.
+
+
+ 2026-07-11 16:22:51,053 - root - INFO - Removing search internal folder.
+
+
+ 2026-07-11 16:22:51,102 - root - INFO - Search complete, returning result
+
+
+ The search has finished run - you may now continue the notebook.
+
+
+Upon completion the non-linear search returns a `Result` object, which contains information about the model-fit.
+
+The `info` attribute shows the result in a readable format.
+
+[Above, we discussed that the `info_whitespace_length` parameter in the config files could b changed to make
+the `model.info` attribute display optimally on your computer. This attribute also controls the whitespace of the
+`result.info` attribute.]
+
+
+```python
+print(result.info)
+```
+
+ Maximum Log Likelihood 178.11809146
+
+ model Gaussian (N=3)
+
+ Maximum Log Likelihood Model:
+
+ centre 50.119
+ normalization 25.114
+ sigma 10.036
+
+
+
+
+The result has a "maximum log likelihood instance", which refers to the specific set of model parameters (e.g.,
+for a `Gaussian`) that yielded the highest log likelihood among all models tested by the non-linear search.
+
+
+```python
+print("Maximum Likelihood Model:\n")
+max_log_likelihood_instance = result.samples.max_log_likelihood()
+print("Centre = ", max_log_likelihood_instance.centre)
+print("Normalization = ", max_log_likelihood_instance.normalization)
+print("Sigma = ", max_log_likelihood_instance.sigma)
+```
+
+ Maximum Likelihood Model:
+
+ Centre = 50.11929554760558
+ Normalization = 25.113855320837537
+ Sigma = 10.036449187081434
+
+
+We can use this to plot the maximum log likelihood fit over the data and determine the quality of fit was inferred:
+
+
+```python
+model_data = result.max_log_likelihood_instance.model_data_from(
+ xvalues=np.arange(data.shape[0])
+)
+plt.errorbar(
+ x=xvalues,
+ y=data,
+ yerr=noise_map,
+ linestyle="",
+ color="k",
+ ecolor="k",
+ elinewidth=1,
+ capsize=2,
+)
+plt.plot(xvalues, model_data, color="r")
+plt.title("Dynesty model fit to 1D Gaussian dataset.")
+plt.xlabel("x values of profile")
+plt.ylabel("Profile normalization")
+plt.show()
+plt.close()
+```
+
+
+
+
+
+
+
+The fit quality was poor, and the MLE failed to identify the correct model.
+
+This happened because the starting point of the search was a poor match to the data, placing it far from the true
+solution in parameter space. As a result, after moving "up" the likelihood gradient several times, the search
+settled into a "local maximum," where it couldn't find a better solution.
+
+To achieve a better fit with MLE, the search needs to begin in a region of parameter space where the log likelihood
+is higher. This process is known as "initialization," and it involves providing the search with an
+appropriate "starting point" in parameter space.
+
+
+```python
+initializer = af.InitializerParamStartPoints(
+ {
+ model.centre: 55.0,
+ model.normalization: 20.0,
+ model.sigma: 8.0,
+ }
+)
+
+search = af.LBFGS(initializer=initializer)
+
+print(
+ """
+ The non-linear search has begun running.
+ This Jupyter notebook cell with progress once the search has completed - this could take a few minutes!
+ """
+)
+
+model = af.Model(Gaussian)
+
+result = search.fit(model=model, analysis=analysis)
+
+print("The search has finished run - you may now continue the notebook.")
+```
+
+
+ The non-linear search has begun running.
+ This Jupyter notebook cell with progress once the search has completed - this could take a few minutes!
+
+ 2026-07-11 16:22:51,221 - autofit.non_linear.search.abstract_search - INFO - Starting non-linear search with 1 cores.
+
+
+ 2026-07-11 16:22:51,231 - root - INFO - Output to hard-disk disabled, input a search name to enable.
+
+
+ 2026-07-11 16:22:51,232 - autofit.non_linear.initializer - INFO - Generating initial samples of model using JAX LH Function cores
+
+
+ 2026-07-11 16:22:51,233 - autofit.non_linear.initializer - WARNING - Range for centre not set in the InitializerParamBounds. Using defaults.
+
+
+ 2026-07-11 16:22:51,546 - autofit.non_linear.initializer - WARNING - Range for normalization not set in the InitializerParamBounds. Using defaults.
+
+
+ 2026-07-11 16:22:51,547 - autofit.non_linear.initializer - WARNING - Range for sigma not set in the InitializerParamBounds. Using defaults.
+
+
+ 2026-07-11 16:22:51,548 - autofit.non_linear.initializer - INFO - Initial samples generated, starting non-linear search
+
+
+ 2026-07-11 16:22:51,549 - root - INFO - Starting new L-BFGS-B non-linear search (no previous samples found).
+
+
+ 2026-07-11 16:22:51,549 - root - INFO - Visualizing Starting Point Model in image_start folder.
+
+
+ 2026-07-11 16:22:51,652 - autofit.non_linear.search.updater - INFO - Creating latent samples by drawing 100 from the PDF.
+
+
+ 2026-07-11 16:22:51,653 - autofit.non_linear.search.updater - INFO - Drawing via PDF not available for this search, using all samples above the samples weight threshold instead.
+
+
+ 2026-07-11 16:22:51,662 - root - INFO - Removing search internal folder.
+
+
+ 2026-07-11 16:22:51,719 - root - INFO - Search complete, returning result
+
+
+ The search has finished run - you may now continue the notebook.
+
+
+By printing `result.info` and looking at the maximum log likelihood model, we can confirm the search provided a
+good model fit with a much higher likelihood than the incorrect model above.
+
+
+```python
+print(result.info)
+
+model_data = result.max_log_likelihood_instance.model_data_from(
+ xvalues=np.arange(data.shape[0])
+)
+plt.errorbar(
+ x=xvalues,
+ y=data,
+ yerr=noise_map,
+ linestyle="",
+ color="k",
+ ecolor="k",
+ elinewidth=1,
+ capsize=2,
+)
+plt.plot(xvalues, model_data, color="r")
+plt.title("Dynesty model fit to 1D Gaussian dataset.")
+plt.xlabel("x values of profile")
+plt.ylabel("Profile normalization")
+plt.show()
+plt.close()
+```
+
+ Maximum Log Likelihood -3328.20371089
+
+ model Gaussian (N=3)
+
+ Maximum Log Likelihood Model:
+
+ centre -17956.265
+ normalization 63480.416
+ sigma 8029.315
+
+
+
+
+
+
+
+
+
+
+MLE is a great starting point for model-fitting because it’s fast, conceptually simple, and often yields
+accurate results. It is especially effective if you can provide a good initialization, allowing it to find the
+best-fit solution quickly.
+
+However, MLE has its limitations. As seen above, it can get "stuck" in a local maximum, particularly if the
+starting point is poorly chosen. In complex model-fitting problems, providing a suitable starting point can be
+challenging. While MLE performed well in the example with just three parameters, it struggles with models that have
+many parameters, as the complexity of the likelihood surface makes simply moving "up" the gradient less effective.
+
+The MLE also does not provide any information on the errors on the parameters, which is a significant limitation.
+The next two types of searches "map out" the likelihood surface, such that they not only infer the maximum likelihood
+solution but also quantify the errors on the parameters.
+
+__Markov Chain Monte Carlo (MCMC)__
+
+Markov Chain Monte Carlo (MCMC) is a more powerful method for model-fitting, though it is also more computationally
+intensive and conceptually complex. Here’s a simplified overview:
+
+1. Place a set of "walkers" in parameter space, each with random parameter values.
+2. Calculate the likelihood of each walker's position.
+3. Move the walkers to new positions, guided by the likelihood of their current positions. Walkers in high-likelihood
+regions encourage those in lower regions to move closer to them.
+
+This process repeats, with the walkers converging on the highest-likelihood regions of parameter space.
+
+Unlike MLE, MCMC thoroughly explores parameter space. While MLE moves a single point up the likelihood gradient,
+MCMC uses many walkers to explore high-likelihood regions, making it more effective at finding the global maximum,
+though slower.
+
+In the example below, we use the `Emcee` MCMC search to fit the 1D Gaussian model. The search starts with walkers
+initialized in a "ball" around the center of the model’s priors, similar to the MLE search that failed earlier.
+
+
+```python
+search = af.Emcee(
+ nwalkers=10, # The number of walkers we'll use to sample parameter space.
+ nsteps=200, # The number of steps each walker takes, after which 10 * 200 = 2000 steps the non-linear search ends.
+)
+
+print(
+ """
+ The non-linear search has begun running.
+ This Jupyter notebook cell with progress once the search has completed - this could take a few minutes!
+ """
+)
+
+model = af.Model(Gaussian)
+
+result = search.fit(model=model, analysis=analysis)
+
+print("The search has finished run - you may now continue the notebook.")
+
+print(result.info)
+
+model_data = result.max_log_likelihood_instance.model_data_from(
+ xvalues=np.arange(data.shape[0])
+)
+plt.errorbar(
+ x=xvalues,
+ y=data,
+ yerr=noise_map,
+ linestyle="",
+ color="k",
+ ecolor="k",
+ elinewidth=1,
+ capsize=2,
+)
+plt.plot(xvalues, model_data, color="r")
+plt.title("Dynesty model fit to 1D Gaussian dataset.")
+plt.xlabel("x values of profile")
+plt.ylabel("Profile normalization")
+plt.show()
+plt.close()
+```
+
+
+ The non-linear search has begun running.
+ This Jupyter notebook cell with progress once the search has completed - this could take a few minutes!
+
+ 2026-07-11 16:22:51,829 - autofit.non_linear.search.abstract_search - INFO - Starting non-linear search with 1 cores.
+
+
+ 2026-07-11 16:22:51,837 - root - INFO - Output to hard-disk disabled, input a search name to enable.
+
+
+ 2026-07-11 16:22:51,863 - autofit.non_linear.initializer - INFO - Generating initial samples of model using JAX LH Function cores
+
+
+ 2026-07-11 16:22:51,866 - autofit.non_linear.initializer - INFO - Initial samples generated, starting non-linear search
+
+
+ 2026-07-11 16:22:51,867 - root - INFO - Visualizing Starting Point Model in image_start folder.
+
+
+ 2026-07-11 16:22:51,868 - root - INFO - Starting new Emcee non-linear search (no previous samples found).
+
+
+ 0%| | 0/200 [00:00, ?it/s]
+
+ .../PyAutoFit/autofit/non_linear/fitness.py:299: RuntimeWarning: invalid value encountered in scalar subtract
+ log_likelihood -= np.sum(log_prior_list)
+ 32%|███▏ | 63/200 [00:00<00:00, 627.34it/s]
+
+ 64%|██████▍ | 129/200 [00:00<00:00, 642.26it/s]
+
+ 98%|█████████▊| 196/200 [00:00<00:00, 654.31it/s]
+
+ 100%|██████████| 200/200 [00:00<00:00, 644.86it/s]
+
+ 2026-07-11 16:22:52,211 - autofit.non_linear.search.updater - INFO - Creating latent samples by drawing 100 from the PDF.
+
+
+
+
+
+ 2026-07-11 16:22:52,278 - root - INFO - Search complete, returning result
+
+
+ The search has finished run - you may now continue the notebook.
+ Maximum Log Likelihood 178.05680096
+
+ model Gaussian (N=3)
+
+ Maximum Log Likelihood Model:
+
+ centre 49.875
+ normalization 25.412
+ sigma 9.967
+
+
+ Summary (3.0 sigma limits):
+
+ centre 50.20 (49.63, 51.33)
+ normalization 25.06 (15.27, 25.98)
+ sigma 9.95 (6.66, 10.49)
+
+
+ Summary (1.0 sigma limits):
+
+ centre 50.20 (50.10, 50.28)
+ normalization 25.06 (24.66, 25.25)
+ sigma 9.95 (9.64, 10.04)
+
+ instances
+
+
+
+
+
+
+
+
+
+
+The MCMC search succeeded, finding the same high-likelihood model that the MLE search with a good starting point
+identified, even without a good initialization. Its use of multiple walkers exploring parameter space allowed it to
+avoid the local maxima that had trapped the MLE search.
+
+A major advantage of MCMC is that it provides estimates of parameter uncertainties by "mapping out" the likelihood
+surface, unlike MLE, which only finds the maximum likelihood solution. These error estimates are accessible in
+the `result.info` string and through the `result.samples` object, with further details in tutorial 5.
+
+While a good starting point wasn't necessary for this simple model, it becomes essential for efficiently mapping the
+likelihood surface in more complex models with many parameters. The code below shows an MCMC fit using a good starting
+point, with two key differences from the MLE initialization:
+
+1. Instead of single starting values, we provide bounds for each parameter. MCMC initializes each walker in a
+small "ball" in parameter space, requiring a defined range for each parameter from which values are randomly drawn.
+
+2. We do not specify a starting point for the sigma parameter, allowing its initial values to be drawn from its
+priors. This illustrates that with MCMC, it’s not necessary to know a good starting point for every parameter.
+
+
+```python
+initializer = af.InitializerParamBounds(
+ {
+ model.centre: (54.0, 56.0),
+ model.normalization: (19.0, 21.0),
+ }
+)
+
+search = af.Emcee(
+ nwalkers=10, # The number of walkers we'll use to sample parameter space.
+ nsteps=200, # The number of steps each walker takes, after which 10 * 200 = 2000 steps the non-linear search ends.
+ initializer=initializer,
+)
+
+print(
+ """
+ The non-linear search has begun running.
+ This Jupyter notebook cell with progress once the search has completed - this could take a few minutes!
+ """
+)
+
+model = af.Model(Gaussian)
+
+result = search.fit(model=model, analysis=analysis)
+
+print("The search has finished run - you may now continue the notebook.")
+
+print(result.info)
+```
+
+
+ The non-linear search has begun running.
+ This Jupyter notebook cell with progress once the search has completed - this could take a few minutes!
+
+ 2026-07-11 16:22:52,386 - autofit.non_linear.search.abstract_search - INFO - Starting non-linear search with 1 cores.
+
+
+ 2026-07-11 16:22:52,395 - root - INFO - Output to hard-disk disabled, input a search name to enable.
+
+
+ 2026-07-11 16:22:52,397 - autofit.non_linear.initializer - INFO - Generating initial samples of model using JAX LH Function cores
+
+
+ 2026-07-11 16:22:52,397 - autofit.non_linear.initializer - WARNING - Range for centre not set in the InitializerParamBounds. Using defaults.
+
+
+ 2026-07-11 16:22:52,398 - autofit.non_linear.initializer - WARNING - Range for normalization not set in the InitializerParamBounds. Using defaults.
+
+
+ 2026-07-11 16:22:52,400 - autofit.non_linear.initializer - WARNING - Range for sigma not set in the InitializerParamBounds. Using defaults.
+
+
+ 2026-07-11 16:22:52,407 - autofit.non_linear.initializer - INFO - Initial samples generated, starting non-linear search
+
+
+ 2026-07-11 16:22:52,407 - root - INFO - Visualizing Starting Point Model in image_start folder.
+
+
+ 2026-07-11 16:22:52,408 - root - INFO - Starting new Emcee non-linear search (no previous samples found).
+
+
+ 0%| | 0/200 [00:00, ?it/s]
+
+ .../PyAutoFit/autofit/non_linear/fitness.py:299: RuntimeWarning: invalid value encountered in scalar subtract
+ log_likelihood -= np.sum(log_prior_list)
+ 28%|██▊ | 56/200 [00:00<00:00, 552.01it/s]
+
+ 57%|█████▋ | 114/200 [00:00<00:00, 565.11it/s]
+
+ 89%|████████▉ | 178/200 [00:00<00:00, 595.09it/s]
+
+ 100%|██████████| 200/200 [00:00<00:00, 586.15it/s]
+
+ 2026-07-11 16:22:52,778 - autofit.non_linear.search.updater - INFO - Creating latent samples by drawing 100 from the PDF.
+
+
+
+
+
+ 2026-07-11 16:22:52,849 - root - INFO - Search complete, returning result
+
+
+ The search has finished run - you may now continue the notebook.
+ Maximum Log Likelihood 179.87090348
+
+ model Gaussian (N=3)
+
+ Maximum Log Likelihood Model:
+
+ centre 14000309534.500
+ normalization 14321679592530.984
+ sigma -1135993457.656
+
+
+ Summary (3.0 sigma limits):
+
+ centre 50.35 (44.70, 13301286926.78)
+ normalization 24.36 (12.65, 13612118261755.57)
+ sigma 7.15 (-1097240948.89, 2268.59)
+
+
+ Summary (1.0 sigma limits):
+
+ centre 50.35 (48.74, 12245.20)
+ normalization 24.36 (22.22, 12379223.37)
+ sigma 7.15 (-540.41, 8.31)
+
+ instances
+
+
+
+
+MCMC is a powerful tool for model-fitting, providing accurate parameter estimates and uncertainties. For simple models
+without a starting point, MCMC can still find the correct solution, and if a good starting point is provided, it can
+efficiently scale to more complex models with more parameters.
+
+The main limitation of MCMC is that one has to supply the number of steps the walkers take (`nsteps`). If this value
+is too low, the walkers may not explore the likelihood surface sufficiently. It can be challenging to know the right
+number of steps, especially if models of different complexity are being fitted or if datasets of varying quality are
+used. One often ends up having to perform "trial and error" to verify a sufficient number of steps are used.
+
+MCMC can perform badly in parameter spaces with certain types of complexity, for example when there are
+are local maxima "peaks" the walkers can become stuck walking around them.
+
+__Nested Sampling__
+
+**Nested Sampling** is an advanced method for model-fitting that excels in handling complex models with intricate
+parameter spaces. Here’s a simplified overview of its process:
+
+1. Start with a set of "live points" in parameter space, each initialized with random parameter values drawn from their respective priors.
+
+2. Compute the log likelihood for each live point.
+
+3. Draw a new point based on the likelihood of the current live points, favoring regions of higher likelihood.
+
+4. If the new point has a higher likelihood than any existing live point, it becomes a live point, and the lowest likelihood live point is discarded.
+
+This iterative process continues, gradually focusing the live points around higher likelihood regions of parameter
+space until they converge on the highest likelihood solution.
+
+Like MCMC, Nested Sampling effectively maps out parameter space, providing accurate estimates of parameters and
+their uncertainties.
+
+
+```python
+search = af.DynestyStatic(
+ sample="rwalk", # This makes dynesty run faster, dont worry about what it means for now!
+)
+```
+
+To begin the model-fit via the non-linear search, we pass it our model and analysis and begin the fit.
+
+The fit will take a minute or so to run.
+
+
+```python
+print(
+ """
+ The non-linear search has begun running.
+ This Jupyter notebook cell with progress once the search has completed - this could take a few minutes!
+ """
+)
+
+model = af.Model(Gaussian)
+
+result = search.fit(model=model, analysis=analysis)
+
+print("The search has finished run - you may now continue the notebook.")
+
+print(result.info)
+
+model_data = result.max_log_likelihood_instance.model_data_from(
+ xvalues=np.arange(data.shape[0])
+)
+plt.errorbar(
+ x=xvalues,
+ y=data,
+ yerr=noise_map,
+ linestyle="",
+ color="k",
+ ecolor="k",
+ elinewidth=1,
+ capsize=2,
+)
+plt.plot(xvalues, model_data, color="r")
+plt.title("Dynesty model fit to 1D Gaussian dataset.")
+plt.xlabel("x values of profile")
+plt.ylabel("Profile normalization")
+plt.show()
+plt.close()
+```
+
+
+ The non-linear search has begun running.
+ This Jupyter notebook cell with progress once the search has completed - this could take a few minutes!
+
+ 2026-07-11 16:22:52,867 - autofit.non_linear.search.abstract_search - INFO - Starting non-linear search with 1 cores.
+
+
+ 2026-07-11 16:22:52,875 - root - INFO - Output to hard-disk disabled, input a search name to enable.
+
+
+ 2026-07-11 16:22:52,876 - root - INFO - Starting new Dynesty non-linear search (no previous samples found).
+
+
+ 2026-07-11 16:22:52,990 - autofit.non_linear.initializer - INFO - Generating initial samples of model using JAX LH Function cores
+
+
+ 2026-07-11 16:22:53,003 - autofit.non_linear.initializer - INFO - Initial samples generated, starting non-linear search
+
+
+ ~/venv/PyAuto/lib/python3.12/site-packages/dynesty/dynesty.py:194: UserWarning: Specifying slice option while using rwalk sampler does not make sense
+ warnings.warn('Specifying slice option while using rwalk sampler'
+
+
+ 0it [00:00, ?it/s]
+
+ 46it [00:00, 440.76it/s, bound: 0 | nc: 4 | ncall: 119 | eff(%): 38.655 | loglstar: -inf < -5362.648 < inf | logz: -5364.513 +/- 0.192 | dlogz: 4645.754 > 0.059]
+
+ 91it [00:00, 260.29it/s, bound: 0 | nc: 2 | ncall: 248 | eff(%): 36.694 | loglstar: -inf < -5358.318 < inf | logz: -5362.807 +/- 0.191 | dlogz: 4643.330 > 0.059]
+
+ 121it [00:00, 167.23it/s, bound: 0 | nc: 18 | ncall: 414 | eff(%): 29.227 | loglstar: -inf < -5307.724 < inf | logz: -5314.539 +/- 0.357 | dlogz: 4596.699 > 0.059]
+
+ 142it [00:01, 103.18it/s, bound: 0 | nc: 13 | ncall: 675 | eff(%): 21.037 | loglstar: -inf < -5189.045 < inf | logz: -5196.460 +/- 0.383 | dlogz: 4482.471 > 0.059]
+
+ 157it [00:01, 81.41it/s, bound: 0 | nc: 5 | ncall: 868 | eff(%): 18.088 | loglstar: -inf < -4990.432 < inf | logz: -4997.101 +/- 0.362 | dlogz: 4277.207 > 0.059]
+
+ 169it [00:01, 60.41it/s, bound: 0 | nc: 62 | ncall: 1115 | eff(%): 15.157 | loglstar: -inf < -4879.085 < inf | logz: -4887.029 +/- 0.396 | dlogz: 4171.314 > 0.059]
+
+ 178it [00:02, 43.80it/s, bound: 0 | nc: 33 | ncall: 1393 | eff(%): 12.778 | loglstar: -inf < -4635.657 < inf | logz: -4643.787 +/- 0.401 | dlogz: 3947.173 > 0.059]
+
+ 185it [00:02, 36.48it/s, bound: 0 | nc: 1 | ncall: 1603 | eff(%): 11.541 | loglstar: -inf < -4446.264 < inf | logz: -4454.532 +/- 0.405 | dlogz: 3777.640 > 0.059]
+
+ 190it [00:02, 33.49it/s, bound: 0 | nc: 30 | ncall: 1746 | eff(%): 10.882 | loglstar: -inf < -4369.135 < inf | logz: -4377.503 +/- 0.407 | dlogz: 3686.870 > 0.059]
+
+ 195it [00:03, 25.26it/s, bound: 0 | nc: 110 | ncall: 1992 | eff(%): 9.789 | loglstar: -inf < -4159.827 < inf | logz: -4168.294 +/- 0.409 | dlogz: 3484.804 > 0.059]
+
+ 227it [00:03, 56.83it/s, bound: 5 | nc: 5 | ncall: 2153 | eff(%): 10.543 | loglstar: -inf < -3197.218 < inf | logz: -3206.320 +/- 0.423 | dlogz: 2547.137 > 0.059]
+
+ 260it [00:03, 93.07it/s, bound: 9 | nc: 5 | ncall: 2318 | eff(%): 11.217 | loglstar: -inf < -2364.645 < inf | logz: -2373.284 +/- 0.408 | dlogz: 2009.988 > 0.059]
+
+ 289it [00:03, 124.41it/s, bound: 12 | nc: 5 | ncall: 2463 | eff(%): 11.734 | loglstar: -inf < -1725.724 < inf | logz: -1734.941 +/- 0.418 | dlogz: 1608.689 > 0.059]
+
+ 323it [00:03, 162.53it/s, bound: 17 | nc: 5 | ncall: 2633 | eff(%): 12.267 | loglstar: -inf < -1175.677 < inf | logz: -1186.693 +/- 0.453 | dlogz: 1089.470 > 0.059]
+
+ 348it [00:03, 180.21it/s, bound: 20 | nc: 5 | ncall: 2758 | eff(%): 12.618 | loglstar: -inf < -913.460 < inf | logz: -923.229 +/- 0.416 | dlogz: 1027.920 > 0.059]
+
+ 377it [00:03, 204.86it/s, bound: 23 | nc: 5 | ncall: 2903 | eff(%): 12.987 | loglstar: -inf < -579.293 < inf | logz: -591.397 +/- 0.469 | dlogz: 707.096 > 0.059]
+
+ 409it [00:03, 232.43it/s, bound: 27 | nc: 5 | ncall: 3063 | eff(%): 13.353 | loglstar: -inf < -307.357 < inf | logz: -320.097 +/- 0.479 | dlogz: 490.797 > 0.059]
+
+ 440it [00:04, 252.43it/s, bound: 31 | nc: 5 | ncall: 3218 | eff(%): 13.673 | loglstar: -inf < -178.602 < inf | logz: -188.971 +/- 0.420 | dlogz: 317.799 > 0.059]
+
+ 471it [00:04, 266.73it/s, bound: 35 | nc: 5 | ncall: 3373 | eff(%): 13.964 | loglstar: -inf < -45.073 < inf | logz: -58.981 +/- 0.485 | dlogz: 213.996 > 0.059]
+
+ 501it [00:04, 268.57it/s, bound: 39 | nc: 5 | ncall: 3523 | eff(%): 14.221 | loglstar: -inf < 40.637 < inf | logz: 26.376 +/- 0.484 | dlogz: 136.735 > 0.059]
+
+ 531it [00:04, 276.83it/s, bound: 43 | nc: 5 | ncall: 3673 | eff(%): 14.457 | loglstar: -inf < 94.520 < inf | logz: 80.831 +/- 0.460 | dlogz: 80.335 > 0.059]
+
+ 563it [00:04, 286.04it/s, bound: 47 | nc: 5 | ncall: 3833 | eff(%): 14.688 | loglstar: -inf < 111.136 < inf | logz: 98.837 +/- 0.443 | dlogz: 61.333 > 0.059]
+
+ 593it [00:04, 274.02it/s, bound: 50 | nc: 5 | ncall: 3983 | eff(%): 14.888 | loglstar: -inf < 123.610 < inf | logz: 107.766 +/- 0.496 | dlogz: 55.390 > 0.059]
+
+ 622it [00:04, 277.21it/s, bound: 54 | nc: 5 | ncall: 4128 | eff(%): 15.068 | loglstar: -inf < 140.977 < inf | logz: 125.969 +/- 0.477 | dlogz: 35.745 > 0.059]
+
+ 653it [00:04, 282.41it/s, bound: 58 | nc: 5 | ncall: 4283 | eff(%): 15.246 | loglstar: -inf < 154.748 < inf | logz: 138.700 +/- 0.498 | dlogz: 22.602 > 0.059]
+
+ 683it [00:04, 284.45it/s, bound: 62 | nc: 5 | ncall: 4433 | eff(%): 15.407 | loglstar: -inf < 161.739 < inf | logz: 146.497 +/- 0.487 | dlogz: 17.739 > 0.059]
+
+ 718it [00:05, 302.50it/s, bound: 66 | nc: 5 | ncall: 4608 | eff(%): 15.582 | loglstar: -inf < 168.350 < inf | logz: 151.956 +/- 0.497 | dlogz: 11.617 > 0.059]
+
+ 752it [00:05, 308.04it/s, bound: 71 | nc: 5 | ncall: 4778 | eff(%): 15.739 | loglstar: -inf < 171.360 < inf | logz: 155.949 +/- 0.493 | dlogz: 6.800 > 0.059]
+
+ 785it [00:05, 312.46it/s, bound: 75 | nc: 5 | ncall: 4943 | eff(%): 15.881 | loglstar: -inf < 173.652 < inf | logz: 157.073 +/- 0.493 | dlogz: 5.008 > 0.059]
+
+ 817it [00:05, 310.19it/s, bound: 79 | nc: 5 | ncall: 5103 | eff(%): 16.010 | loglstar: -inf < 175.084 < inf | logz: 158.020 +/- 0.499 | dlogz: 3.432 > 0.059]
+
+ 849it [00:05, 307.12it/s, bound: 83 | nc: 5 | ncall: 5263 | eff(%): 16.131 | loglstar: -inf < 176.159 < inf | logz: 158.808 +/- 0.506 | dlogz: 2.094 > 0.059]
+
+ 880it [00:05, 296.37it/s, bound: 87 | nc: 5 | ncall: 5418 | eff(%): 16.242 | loglstar: -inf < 177.212 < inf | logz: 159.480 +/- 0.514 | dlogz: 1.173 > 0.059]
+
+ 910it [00:05, 263.21it/s, bound: 90 | nc: 5 | ncall: 5568 | eff(%): 16.343 | loglstar: -inf < 177.585 < inf | logz: 159.872 +/- 0.517 | dlogz: 0.599 > 0.059]
+
+ 940it [00:05, 271.66it/s, bound: 94 | nc: 5 | ncall: 5718 | eff(%): 16.439 | loglstar: -inf < 177.856 < inf | logz: 160.101 +/- 0.518 | dlogz: 0.307 > 0.059]
+
+ 970it [00:05, 279.05it/s, bound: 98 | nc: 5 | ncall: 5868 | eff(%): 16.530 | loglstar: -inf < 177.943 < inf | logz: 160.229 +/- 0.519 | dlogz: 0.157 > 0.059]
+
+ 999it [00:06, 274.78it/s, bound: 101 | nc: 5 | ncall: 6013 | eff(%): 16.614 | loglstar: -inf < 177.970 < inf | logz: 160.295 +/- 0.519 | dlogz: 0.079 > 0.059]
+
+ 1013it [00:06, 165.99it/s, +50 | bound: 103 | nc: 1 | ncall: 6133 | eff(%): 17.475 | loglstar: -inf < 178.098 < inf | logz: 160.366 +/- 0.520 | dlogz: 0.001 > 0.059]
+
+
+
+
+ 2026-07-11 16:22:59,192 - autofit.non_linear.search.updater - INFO - Creating latent samples by drawing 100 from the PDF.
+
+
+ 2026-07-11 16:22:59,295 - root - INFO - Search complete, returning result
+
+
+ The search has finished run - you may now continue the notebook.
+ Bayesian Evidence 160.36570347
+ Maximum Log Likelihood 178.09800271
+
+ model Gaussian (N=3)
+
+ Maximum Log Likelihood Model:
+
+ centre 50.119
+ normalization 25.176
+ sigma 10.052
+
+
+ Summary (3.0 sigma limits):
+
+ centre 50.11 (49.71, 50.54)
+ normalization 25.11 (24.29, 26.00)
+ sigma 10.06 (9.64, 10.42)
+
+
+ Summary (1.0 sigma limits):
+
+ centre 50.11 (50.01, 50.24)
+ normalization 25.11 (24.94, 25.34)
+ sigma 10.06 (9.95, 10.19)
+
+ instances
+
+
+
+
+
+
+
+
+
+
+The **Nested Sampling** search was successful, identifying the same high-likelihood model as the MLE and MCMC searches.
+One of the main benefits of Nested Sampling is its ability to provide accurate parameter estimates and uncertainties,
+similar to MCMC. Additionally, it features a built-in stopping criterion, which eliminates the need for users to
+specify the number of steps the search should take.
+
+This method also excels in handling complex parameter spaces, particularly those with multiple peaks. This is because
+the live points will identify each peak and converge around them, but then begin to be discard from a peak if higher
+likelihood points are found elsewhere in parameter space. In MCMC, the walkers can get stuck indefinitely around a
+peak, causing the method to stall.
+
+Another significant advantage is that Nested Sampling estimates an important statistical quantity
+known as "evidence." This value quantifies how well the model fits the data while considering the model's complexity,
+making it essential for Bayesian model comparison, which will be covered in later tutorials.
+
+Nested sampling cannot use a starting point, as it always samples parameter space from scratch by drawing live points
+from the priors. This is both good and bad, depending on if you have access to a good starting point or not. If you do
+not, your MCMC / MLE fit will likely struggle with initialization compared to Nested Sampling. Conversely, if you do
+possess a robust starting point, it can significantly enhance the performance of MCMC, allowing it to begin closer to
+the highest likelihood regions of parameter space. This proximity can lead to faster convergence and more reliable results.
+
+However, Nested Sampling does have limitations; it often scales poorly with increased model complexity. For example,
+once a model has around 50 or more parameters, Nested Sampling can become very slow, whereas MCMC remains efficient
+even in such complex parameter spaces.
+
+__What is The Best Search To Use?__
+
+The choice of the best search method depends on several factors specific to the problem at hand. Here are key
+considerations that influence which search may be optimal:
+
+Firstly, consider the speed of the fit regardless of the search method. If the fitting process runs efficiently,
+nested sampling could be advantageous for low-dimensional parameter spaces due to its ability to handle complex
+parameter spaces and its built-in stopping criterion. However, in high-dimensional scenarios, MCMC may be more
+suitable, as it scales better with the number of parameters.
+
+Secondly, evaluate whether you have access to a robust starting point for your model fit. A strong initialization can
+make MCMC more appealing, allowing the algorithm to bypass the initial sampling stage and leading to quicker convergence.
+
+Additionally, think about the importance of error estimation in your analysis. If error estimation is not a priority,
+MLE might suffice, but this approach heavily relies on having a solid starting point and may struggle with more complex models.
+
+Ultimately, every model-fitting problem is unique, making it impossible to provide a one-size-fits-all answer regarding
+the best search method. This variability is why **PyAutoFit** offers a diverse array of search options, all
+standardized with a consistent interface. This standardization allows users to experiment with different searches on the
+same model-fitting problem and determine which yields the best results.
+
+Finally, it’s important to note that MLE, MCMC, and nested sampling represent only three categories of non-linear
+searches, each containing various algorithms. Each algorithm has its strengths and weaknesses, so experimenting with
+them can reveal the most effective approach for your specific model-fitting challenge. For further guidance, a detailed
+description of each search method can be found in the [search cookbook](https://pyautofit.readthedocs.io/en/latest/cookbooks/search.html).
+
+__Wrap Up__
+
+This tutorial has laid the foundation with several fundamental concepts in model fitting and statistical inference:
+
+1. **Parameter Space**: This refers to the range of possible values that each parameter in a model can take. It
+defines the dimensions over which the likelihood of different parameter values is evaluated.
+
+2. **Likelihood Surface**: This surface represents how the likelihood of the model varies across the parameter space.
+It helps in identifying the best-fit parameters that maximize the likelihood of the model given the data.
+
+3. **Non-linear Search**: This is an optimization technique used to explore the parameter space and find the
+combination of parameter values that best describe the data. It iteratively adjusts the parameters to maximize the
+likelihood. Many different search algorithms exist, each with their own strengths and weaknesses, and this tutorial
+used the MLE, MCMC, and nested sampling searches.
+
+4. **Priors**: Priors are probabilities assigned to different values of parameters before considering the data.
+They encapsulate our prior knowledge or assumptions about the parameter values. Priors can constrain the parameter
+space, making the search more efficient and realistic.
+
+5. **Model Fitting**: The process of adjusting model parameters to minimize the difference between model predictions
+and observed data, quantified by the likelihood function.
+
+Understanding these concepts is crucial as they form the backbone of model fitting and parameter estimation in
+scientific research and data analysis. In the next tutorials, these concepts will be further expanded upon to
+deepen your understanding and provide more advanced techniques for model fitting and analysis.
+
+
+```python
+
+```
diff --git a/markdown/chapter_1_introduction/tutorial_3_non_linear_search_files/tutorial_3_non_linear_search_25_0.png b/markdown/chapter_1_introduction/tutorial_3_non_linear_search_files/tutorial_3_non_linear_search_25_0.png
new file mode 100644
index 0000000..5513e0e
Binary files /dev/null and b/markdown/chapter_1_introduction/tutorial_3_non_linear_search_files/tutorial_3_non_linear_search_25_0.png differ
diff --git a/markdown/chapter_1_introduction/tutorial_3_non_linear_search_files/tutorial_3_non_linear_search_29_1.png b/markdown/chapter_1_introduction/tutorial_3_non_linear_search_files/tutorial_3_non_linear_search_29_1.png
new file mode 100644
index 0000000..3e09dff
Binary files /dev/null and b/markdown/chapter_1_introduction/tutorial_3_non_linear_search_files/tutorial_3_non_linear_search_29_1.png differ
diff --git a/markdown/chapter_1_introduction/tutorial_3_non_linear_search_files/tutorial_3_non_linear_search_31_15.png b/markdown/chapter_1_introduction/tutorial_3_non_linear_search_files/tutorial_3_non_linear_search_31_15.png
new file mode 100644
index 0000000..188d3e7
Binary files /dev/null and b/markdown/chapter_1_introduction/tutorial_3_non_linear_search_files/tutorial_3_non_linear_search_31_15.png differ
diff --git a/markdown/chapter_1_introduction/tutorial_3_non_linear_search_files/tutorial_3_non_linear_search_37_48.png b/markdown/chapter_1_introduction/tutorial_3_non_linear_search_files/tutorial_3_non_linear_search_37_48.png
new file mode 100644
index 0000000..a537569
Binary files /dev/null and b/markdown/chapter_1_introduction/tutorial_3_non_linear_search_files/tutorial_3_non_linear_search_37_48.png differ
diff --git a/markdown/chapter_1_introduction/tutorial_3_non_linear_search_files/tutorial_3_non_linear_search_5_0.png b/markdown/chapter_1_introduction/tutorial_3_non_linear_search_files/tutorial_3_non_linear_search_5_0.png
new file mode 100644
index 0000000..26750b9
Binary files /dev/null and b/markdown/chapter_1_introduction/tutorial_3_non_linear_search_files/tutorial_3_non_linear_search_5_0.png differ
diff --git a/markdown/chapter_1_introduction/tutorial_4_why_modeling_is_hard.md b/markdown/chapter_1_introduction/tutorial_4_why_modeling_is_hard.md
new file mode 100644
index 0000000..9e6e740
--- /dev/null
+++ b/markdown/chapter_1_introduction/tutorial_4_why_modeling_is_hard.md
@@ -0,0 +1,7757 @@
+> ✏️ **This page is auto-generated from [`scripts/chapter_1_introduction/tutorial_4_why_modeling_is_hard.py`](../../scripts/chapter_1_introduction/tutorial_4_why_modeling_is_hard.py) — do not edit it directly.**
+> It shows the example fully executed, with its real output images.
+> Run it yourself via the [Python script](../../scripts/chapter_1_introduction/tutorial_4_why_modeling_is_hard.py) or the [Jupyter notebook](../../notebooks/chapter_1_introduction/tutorial_4_why_modeling_is_hard.ipynb).
+
+Tutorial 4: Why Modeling Is Hard
+================================
+
+We have successfully fitted a simple 1D Gaussian profile to a dataset using a non-linear search. While achieving an
+accurate model fit has been straightforward, the reality is that model fitting is a challenging problem where many things can go wrong.
+
+This tutorial will illustrate why modeling is challenging, highlight common problems that occur when fitting complex
+models, and show how a good scientific approach can help us overcome these challenges.
+
+We will build on concepts introduced in previous tutorials, such as the non-linear parameter space, likelihood surface,
+and the role of priors.
+
+__Overview__
+
+In this tutorial, we will fit complex models with up to 15 free parameters and consider the following:
+
+- Why more complex models are more difficult to fit and may lead the non-linear search to infer an incorrect solution.
+
+- Strategies for ensuring the non-linear search estimates the correct solution.
+
+- What drives the run-times of a model fit and how to carefully balance run-times with model complexity.
+
+__Contents__
+
+- **Data**: Load and plot the 1D Gaussian dataset we'll fit, which is more complex than the previous tutorial.
+- **Model**: The `Gaussian` model component that we will fit to the data.
+- **Analysis**: The log likelihood function used to fit the model to the data.
+- **Alternative Syntax**: An alternative loop-based approach for creating a summed profile from multiple model components.
+- **Collection**: The `Collection` model used to compose the model-fit.
+- **Search**: Set up the nested sampling search (Dynesty) for the model-fit.
+- **Model Fit**: Perform the model-fit and examine the results.
+- **Result**: Determine if the model-fit was successful and what can be done to ensure a good model-fit.
+- **Why Modeling is Hard**: Introduce the concept of randomness and local maxima and why they make model-fitting challenging.
+- **Prior Tuning**: Adjust the priors of the model to help the non-linear search find the global maxima solution.
+- **Reducing Complexity**: Simplify the model to reduce the dimensionality of the parameter space.
+- **Search More Thoroughly**: Adjust the non-linear search settings to search parameter space more thoroughly.
+- **Summary**: Summarize the three strategies for ensuring successful model-fitting.
+- **Run Times**: Discuss how the likelihood function and complexity of a model impacts the run-time of a model-fit.
+- **Model Mismatch**: Introduce the concept of model mismatches and how it makes inferring the correct model challenging.
+- **Astronomy Example**: How the concepts of this tutorial are applied to real astronomical problems.
+- **Wrap Up**: A summary of the key takeaways of this tutorial.
+
+
+```python
+
+from autoconf import setup_notebook; setup_notebook()
+
+from os import path
+import numpy as np
+import matplotlib.pyplot as plt
+
+import autofit as af
+```
+
+ Working Directory has been set to `HowToFit`
+
+
+__Data__
+
+Load the dataset we fit.
+
+This is a new `dataset` where the underlying signal is a sum of five `Gaussian` profiles.
+
+
+```python
+dataset_path = path.join("dataset", "example_1d", "gaussian_x5")
+```
+
+__Dataset Auto-Simulation__
+
+If the dataset does not already exist on your system, it will be created by running the corresponding
+simulator script. This ensures that all example scripts can be run without manually simulating data first.
+
+
+```python
+if not path.exists(dataset_path):
+ import subprocess
+ import sys
+
+ subprocess.run(
+ [sys.executable, "scripts/simulators/simulators.py"],
+ check=True,
+ )
+
+data = af.util.numpy_array_from_json(file_path=path.join(dataset_path, "data.json"))
+noise_map = af.util.numpy_array_from_json(
+ file_path=path.join(dataset_path, "noise_map.json")
+)
+```
+
+Plotting the data reveals that the signal is more complex than a simple 1D Gaussian, as the wings to the left and
+right are more extended than what a single Gaussian profile can account for.
+
+
+```python
+xvalues = np.arange(data.shape[0])
+plt.errorbar(
+ xvalues,
+ data,
+ yerr=noise_map,
+ linestyle="",
+ color="k",
+ ecolor="k",
+ elinewidth=1,
+ capsize=2,
+)
+plt.title("1D Gaussian dataset with errors from the noise-map.")
+plt.xlabel("x values of profile")
+plt.ylabel("Signal Value")
+plt.show()
+plt.clf()
+plt.close()
+```
+
+
+
+
+
+
+
+__Model__
+
+Create the `Gaussian` class from which we will compose model components using the standard format.
+
+
+```python
+
+
+class Gaussian:
+ def __init__(
+ self,
+ centre: float = 30.0, # <- **PyAutoFit** recognises these constructor arguments
+ normalization: float = 1.0, # <- are the Gaussian`s model parameters.
+ sigma: float = 5.0,
+ ):
+ """
+ Represents a 1D Gaussian profile.
+
+ This is a model-component of example models in the **HowToFit** lectures and is used to perform model-fitting
+ of example datasets.
+
+ Parameters
+ ----------
+ centre
+ The x coordinate of the profile centre.
+ normalization
+ Overall normalization of the profile.
+ sigma
+ The sigma value controlling the size of the Gaussian.
+ """
+ self.centre = centre
+ self.normalization = normalization
+ self.sigma = sigma
+
+ def model_data_from(self, xvalues: np.ndarray) -> np.ndarray:
+ """
+ Returns a 1D Gaussian on an input list of Cartesian x coordinates.
+
+ The input xvalues are translated to a coordinate system centred on the Gaussian, via its `centre`.
+
+ The output is referred to as the `model_data` to signify that it is a representation of the data from the
+ model.
+
+ Parameters
+ ----------
+ xvalues
+ The x coordinates in the original reference frame of the data.
+
+ Returns
+ -------
+ np.array
+ The Gaussian values at the input x coordinates.
+ """
+ transformed_xvalues = np.subtract(xvalues, self.centre)
+ return np.multiply(
+ np.divide(self.normalization, self.sigma * np.sqrt(2.0 * np.pi)),
+ np.exp(-0.5 * np.square(np.divide(transformed_xvalues, self.sigma))),
+ )
+
+```
+
+__Analysis__
+
+To define the Analysis class for this model-fit, we need to ensure that the `log_likelihood_function` can handle an
+instance containing multiple 1D profiles. Below is an expanded explanation and the corresponding class definition:
+
+The log_likelihood_function will now assume that the instance it receives consists of multiple Gaussian profiles.
+For each Gaussian in the instance, it will compute the model_data and then sum these to create the overall `model_data`
+that is compared to the observed data.
+
+
+```python
+
+
+class Analysis(af.Analysis):
+ def __init__(self, data: np.ndarray, noise_map: np.ndarray):
+ """
+ The `Analysis` class acts as an interface between the data and model in **PyAutoFit**.
+
+ Its `log_likelihood_function` defines how the model is fitted to the data and it is called many times by
+ the non-linear search fitting algorithm.
+
+ In this example, the `log_likelihood_function` receives an instance containing multiple instances of
+ the `Gaussian` class and sums the `model_data` of each to create the overall model fit to the data.
+
+ In this example the `Analysis` `__init__` constructor only contains the `data` and `noise-map`, but it can be
+ easily extended to include other quantities.
+
+ Parameters
+ ----------
+ data
+ A 1D numpy array containing the data (e.g. a noisy 1D signal) fitted in the workspace examples.
+ noise_map
+ A 1D numpy array containing the noise values of the data, used for computing the goodness of fit
+ metric, the log likelihood.
+ """
+ super().__init__()
+
+ self.data = data
+ self.noise_map = noise_map
+
+ def log_likelihood_function(self, instance) -> float:
+ """
+ Returns the log likelihood of a fit of a 1D Gaussian to the dataset.
+
+ In the previous tutorial, the instance was a single `Gaussian` profile, however this function now assumes
+ the instance contains multiple `Gaussian` profiles.
+
+ The `model_data` is therefore the summed `model_data` of all individual Gaussians in the model.
+
+ The docstring below describes this in more detail.
+
+ Parameters
+ ----------
+ instance
+ A list of 1D profiles with parameters set via the non-linear search.
+
+ Returns
+ -------
+ float
+ The log likelihood value indicating how well this model fit the `MaskedDataset`.
+ """
+
+ """
+ In the previous tutorial the instance was a single `Gaussian` profile, meaning we could create the model data
+ using the line:
+
+ model_data = instance.gaussian.model_data_from(xvalues=self.data.xvalues)
+
+ In this tutorial our instance is comprised of three 1D Gaussians, because we will use a `Collection` to
+ compose the model:
+
+ model = Collection(gaussian_0=Gaussian, gaussian_1=Gaussian, gaussian_2=Gaussian).
+
+ By using a Collection, this means the instance parameter input into the fit function is a
+ dictionary where individual profiles (and their parameters) can be accessed as followed:
+
+ print(instance.gaussian_0)
+ print(instance.gaussian_1)
+ print(instance.gaussian_2)
+
+ print(instance.gaussian_0.centre)
+ print(instance.gaussian_1.centre)
+ print(instance.gaussian_2.centre)
+
+ The `model_data` is therefore the summed `model_data` of all individual Gaussians in the model.
+
+ The function `model_data_from_instance` performs this summation.
+ """
+ model_data = self.model_data_from_instance(instance=instance)
+
+ residual_map = self.data - model_data
+ chi_squared_map = (residual_map / self.noise_map) ** 2.0
+ chi_squared = sum(chi_squared_map)
+ noise_normalization = np.sum(np.log(2 * np.pi * noise_map**2.0))
+ log_likelihood = -0.5 * (chi_squared + noise_normalization)
+
+ return log_likelihood
+
+ def model_data_from_instance(self, instance):
+ """
+ To create the summed profile of all individual profiles, we use a list comprehension to iterate over
+ all profiles in the instance.
+
+ The `instance` has the properties of a Python `iterator` and therefore can be looped over using the standard
+ Python for syntax (e.g. `for profile in instance`).
+
+ __Alternative Syntax__
+
+ For those not familiar with Python list comprehensions, the code below shows how to use the instance to
+ create the summed profile using a for loop and numpy array:
+
+ model_data = np.zeros(shape=self.data.xvalues.shape[0])
+
+ for profile in instance:
+ model_data += profile.model_data_from(xvalues=self.data.xvalues)
+
+ return model_data
+ """
+ xvalues = np.arange(self.data.shape[0])
+
+ return sum([profile.model_data_from(xvalues=xvalues) for profile in instance])
+
+```
+
+__Collection__
+
+In the previous tutorial, we fitted a single `Gaussian` profile to the dataset by turning it into a model
+component using the `Model` class.
+
+In this tutorial, we will fit a model composed of five `Gaussian` profiles. To do this, we need to combine
+five `Gaussian` model components into a single model.
+
+This can be achieved using a `Collection` object, which was introduced in tutorial 1. The `Collection` object allows
+us to group together multiple model components—in this case, five `Gaussian` profiles—into one model that can be
+passed to the non-linear search.
+
+
+```python
+model = af.Collection(
+ gaussian_0=Gaussian,
+ gaussian_1=Gaussian,
+ gaussian_2=Gaussian,
+ gaussian_3=Gaussian,
+ gaussian_4=Gaussian,
+)
+```
+
+The `model.info` confirms the model is composed of 5 `Gaussian` profiles.
+
+
+```python
+print(model.info)
+```
+
+ Total Free Parameters = 15
+
+ model Collection (N=15)
+ gaussian_0 - gaussian_4 Gaussian (N=3)
+
+ gaussian_0
+ centre UniformPrior [0], lower_limit = 0.0, upper_limit = 100.0
+ normalization LogUniformPrior [1], lower_limit = 1e-06, upper_limit = 1000000.0
+ sigma UniformPrior [2], lower_limit = 0.0, upper_limit = 25.0
+ gaussian_1
+ centre UniformPrior [3], lower_limit = 0.0, upper_limit = 100.0
+ normalization LogUniformPrior [4], lower_limit = 1e-06, upper_limit = 1000000.0
+ sigma UniformPrior [5], lower_limit = 0.0, upper_limit = 25.0
+ gaussian_2
+ centre UniformPrior [6], lower_limit = 0.0, upper_limit = 100.0
+ normalization LogUniformPrior [7], lower_limit = 1e-06, upper_limit = 1000000.0
+ sigma UniformPrior [8], lower_limit = 0.0, upper_limit = 25.0
+ gaussian_3
+ centre UniformPrior [9], lower_limit = 0.0, upper_limit = 100.0
+ normalization LogUniformPrior [10], lower_limit = 1e-06, upper_limit = 1000000.0
+ sigma UniformPrior [11], lower_limit = 0.0, upper_limit = 25.0
+ gaussian_4
+ centre UniformPrior [12], lower_limit = 0.0, upper_limit = 100.0
+ normalization LogUniformPrior [13], lower_limit = 1e-06, upper_limit = 1000000.0
+ sigma UniformPrior [14], lower_limit = 0.0, upper_limit = 25.0
+
+
+__Search__
+
+We again use the nested sampling algorithm Dynesty to fit the model to the data.
+
+
+```python
+search = af.DynestyStatic(
+ sample="rwalk", # This makes dynesty run faster, don't worry about what it means for now!
+)
+```
+
+__Model Fit__
+
+Perform the fit using our five `Gaussian` model, which has 15 free parameters.
+
+This means the non-linear parameter space has a dimensionality of N=15, making it significantly more complex
+than the simpler model we fitted in the previous tutorial.
+
+Consequently, the non-linear search takes slightly longer to run but still completes in under a minute.
+
+
+```python
+analysis = Analysis(data=data, noise_map=noise_map)
+
+print(
+ """
+ The non-linear search has begun running.
+ This Jupyter notebook cell with progress once the search has completed - this could take a few minutes!
+ """
+)
+
+result = search.fit(model=model, analysis=analysis)
+
+print("The search has finished run - you may now continue the notebook.")
+```
+
+
+ The non-linear search has begun running.
+ This Jupyter notebook cell with progress once the search has completed - this could take a few minutes!
+
+ 2026-07-11 16:23:05,069 - autofit.non_linear.search.abstract_search - INFO - Starting non-linear search with 1 cores.
+
+
+ 2026-07-11 16:23:05,079 - root - INFO - Output to hard-disk disabled, input a search name to enable.
+
+
+ 2026-07-11 16:23:05,080 - root - INFO - Starting new Dynesty non-linear search (no previous samples found).
+
+
+ 2026-07-11 16:23:05,289 - autofit.non_linear.initializer - INFO - Generating initial samples of model using JAX LH Function cores
+
+
+ 2026-07-11 16:23:05,324 - autofit.non_linear.initializer - INFO - Initial samples generated, starting non-linear search
+
+
+ ~/venv/PyAuto/lib/python3.12/site-packages/dynesty/dynesty.py:194: UserWarning: Specifying slice option while using rwalk sampler does not make sense
+ warnings.warn('Specifying slice option while using rwalk sampler'
+
+
+ 0it [00:00, ?it/s]
+
+ 36it [00:00, 349.06it/s, bound: 0 | nc: 3 | ncall: 100 | eff(%): 36.000 | loglstar: -inf < -inf < inf | logz: -inf +/- nan | dlogz: inf > 0.059]
+
+ 71it [00:00, 234.58it/s, bound: 0 | nc: 1 | ncall: 204 | eff(%): 34.804 | loglstar: -inf < -inf < inf | logz: -inf +/- 0.347 | dlogz: inf > 0.059]
+
+ 97it [00:00, 155.48it/s, bound: 0 | nc: 7 | ncall: 332 | eff(%): 29.217 | loglstar: -inf < -676962.382 < inf | logz: -676968.908 +/- 0.359 | dlogz: 541835.334 > 0.059]
+
+ 116it [00:01, 86.30it/s, bound: 0 | nc: 48 | ncall: 584 | eff(%): 19.863 | loglstar: -inf < -655809.826 < inf | logz: -655816.728 +/- 0.370 | dlogz: 520222.316 > 0.059]
+
+ 129it [00:01, 76.05it/s, bound: 0 | nc: 1 | ncall: 723 | eff(%): 17.842 | loglstar: -inf < -647634.124 < inf | logz: -647641.283 +/- 0.377 | dlogz: 511816.618 > 0.059]
+
+ 140it [00:01, 68.17it/s, bound: 0 | nc: 23 | ncall: 840 | eff(%): 16.667 | loglstar: -inf < -632562.848 < inf | logz: -632570.225 +/- 0.382 | dlogz: 496673.227 > 0.059]
+
+ 149it [00:01, 58.54it/s, bound: 0 | nc: 11 | ncall: 967 | eff(%): 15.408 | loglstar: -inf < -620370.724 < inf | logz: -620378.279 +/- 0.387 | dlogz: 485513.004 > 0.059]
+
+ 156it [00:02, 46.49it/s, bound: 0 | nc: 2 | ncall: 1111 | eff(%): 14.041 | loglstar: -inf < -606573.503 < inf | logz: -606581.197 +/- 0.390 | dlogz: 471472.051 > 0.059]
+
+ 162it [00:02, 37.74it/s, bound: 0 | nc: 59 | ncall: 1268 | eff(%): 12.776 | loglstar: -inf < -599639.808 < inf | logz: -599647.622 +/- 0.393 | dlogz: 464375.439 > 0.059]
+
+ 167it [00:02, 31.86it/s, bound: 0 | nc: 25 | ncall: 1371 | eff(%): 12.181 | loglstar: -inf < -583199.863 < inf | logz: -583207.776 +/- 0.396 | dlogz: 452150.423 > 0.059]
+
+ 171it [00:02, 31.63it/s, bound: 0 | nc: 5 | ncall: 1447 | eff(%): 11.818 | loglstar: -inf < -569894.638 < inf | logz: -569902.630 +/- 0.398 | dlogz: 436253.538 > 0.059]
+
+ 175it [00:02, 27.67it/s, bound: 0 | nc: 42 | ncall: 1559 | eff(%): 11.225 | loglstar: -inf < -564236.979 < inf | logz: -564245.050 +/- 0.400 | dlogz: 428434.317 > 0.059]
+
+ 178it [00:03, 25.65it/s, bound: 0 | nc: 17 | ncall: 1638 | eff(%): 10.867 | loglstar: -inf < -562490.713 < inf | logz: -562498.843 +/- 0.401 | dlogz: 426853.159 > 0.059]
+
+ 181it [00:03, 16.51it/s, bound: 1 | nc: 6 | ncall: 1845 | eff(%): 9.810 | loglstar: -inf < -557232.231 < inf | logz: -557240.420 +/- 0.403 | dlogz: 425013.706 > 0.059]
+
+ 206it [00:03, 47.74it/s, bound: 4 | nc: 5 | ncall: 1970 | eff(%): 10.457 | loglstar: -inf < -471575.571 < inf | logz: -471584.257 +/- 0.413 | dlogz: 348098.046 > 0.059]
+
+ 232it [00:03, 81.14it/s, bound: 7 | nc: 5 | ncall: 2100 | eff(%): 11.048 | loglstar: -inf < -376179.075 < inf | logz: -376188.281 +/- 0.421 | dlogz: 256926.185 > 0.059]
+
+ 258it [00:03, 114.25it/s, bound: 10 | nc: 5 | ncall: 2230 | eff(%): 11.570 | loglstar: -inf < -261154.544 < inf | logz: -261164.268 +/- 0.430 | dlogz: 137801.135 > 0.059]
+
+ 280it [00:03, 136.17it/s, bound: 13 | nc: 5 | ncall: 2340 | eff(%): 11.966 | loglstar: -inf < -213400.730 < inf | logz: -213410.902 +/- 0.435 | dlogz: 182118.092 > 0.059]
+
+ 302it [00:04, 155.31it/s, bound: 16 | nc: 5 | ncall: 2450 | eff(%): 12.327 | loglstar: -inf < -184912.417 < inf | logz: -184920.530 +/- 0.380 | dlogz: 140640.287 > 0.059]
+
+ 324it [00:04, 168.77it/s, bound: 19 | nc: 5 | ncall: 2560 | eff(%): 12.656 | loglstar: -inf < -175034.588 < inf | logz: -175045.673 +/- 0.445 | dlogz: 130836.599 > 0.059]
+
+ 344it [00:04, 176.54it/s, bound: 21 | nc: 5 | ncall: 2660 | eff(%): 12.932 | loglstar: -inf < -159297.074 < inf | logz: -159306.914 +/- 0.411 | dlogz: 115026.106 > 0.059]
+
+ 364it [00:04, 181.46it/s, bound: 24 | nc: 5 | ncall: 2760 | eff(%): 13.188 | loglstar: -inf < -151485.314 < inf | logz: -151495.568 +/- 0.414 | dlogz: 107214.346 > 0.059]
+
+ 384it [00:04, 184.35it/s, bound: 26 | nc: 5 | ncall: 2860 | eff(%): 13.427 | loglstar: -inf < -136064.884 < inf | logz: -136077.188 +/- 0.457 | dlogz: 91900.187 > 0.059]
+
+ 404it [00:04, 187.96it/s, bound: 29 | nc: 5 | ncall: 2960 | eff(%): 13.649 | loglstar: -inf < -124853.150 < inf | logz: -124864.207 +/- 0.425 | dlogz: 80582.182 > 0.059]
+
+ 426it [00:04, 195.99it/s, bound: 31 | nc: 5 | ncall: 3070 | eff(%): 13.876 | loglstar: -inf < -111504.314 < inf | logz: -111516.346 +/- 0.442 | dlogz: 67234.464 > 0.059]
+
+ 448it [00:04, 202.74it/s, bound: 34 | nc: 5 | ncall: 3180 | eff(%): 14.088 | loglstar: -inf < -98643.791 < inf | logz: -98657.386 +/- 0.472 | dlogz: 57842.330 > 0.059]
+
+ 471it [00:04, 209.73it/s, bound: 37 | nc: 5 | ncall: 3295 | eff(%): 14.294 | loglstar: -inf < -93382.628 < inf | logz: -93395.042 +/- 0.441 | dlogz: 49111.660 > 0.059]
+
+ 494it [00:04, 213.22it/s, bound: 40 | nc: 5 | ncall: 3410 | eff(%): 14.487 | loglstar: -inf < -86782.656 < inf | logz: -86796.065 +/- 0.457 | dlogz: 42512.806 > 0.059]
+
+ 516it [00:05, 213.80it/s, bound: 43 | nc: 5 | ncall: 3520 | eff(%): 14.659 | loglstar: -inf < -76030.319 < inf | logz: -76044.167 +/- 0.464 | dlogz: 33874.773 > 0.059]
+
+ 539it [00:05, 218.21it/s, bound: 45 | nc: 5 | ncall: 3635 | eff(%): 14.828 | loglstar: -inf < -66467.299 < inf | logz: -66481.077 +/- 0.458 | dlogz: 24310.634 > 0.059]
+
+ 561it [00:05, 210.52it/s, bound: 48 | nc: 5 | ncall: 3745 | eff(%): 14.980 | loglstar: -inf < -58401.375 < inf | logz: -58417.241 +/- 0.499 | dlogz: 16653.533 > 0.059]
+
+ 583it [00:05, 202.79it/s, bound: 51 | nc: 5 | ncall: 3855 | eff(%): 15.123 | loglstar: -inf < -54018.533 < inf | logz: -54034.842 +/- 0.504 | dlogz: 12091.627 > 0.059]
+
+ 604it [00:05, 195.08it/s, bound: 54 | nc: 5 | ncall: 3960 | eff(%): 15.253 | loglstar: -inf < -52277.995 < inf | logz: -52293.606 +/- 0.486 | dlogz: 16571.889 > 0.059]
+
+ 626it [00:05, 199.56it/s, bound: 56 | nc: 5 | ncall: 4070 | eff(%): 15.381 | loglstar: -inf < -50131.770 < inf | logz: -50148.941 +/- 0.514 | dlogz: 14503.677 > 0.059]
+
+ 647it [00:05, 202.09it/s, bound: 59 | nc: 5 | ncall: 4175 | eff(%): 15.497 | loglstar: -inf < -46247.663 < inf | logz: -46265.252 +/- 0.520 | dlogz: 10553.178 > 0.059]
+
+ 669it [00:05, 206.46it/s, bound: 62 | nc: 5 | ncall: 4285 | eff(%): 15.613 | loglstar: -inf < -44656.592 < inf | logz: -44674.626 +/- 0.524 | dlogz: 9017.177 > 0.059]
+
+ 690it [00:05, 207.29it/s, bound: 64 | nc: 5 | ncall: 4390 | eff(%): 15.718 | loglstar: -inf < -44274.455 < inf | logz: -44290.637 +/- 0.482 | dlogz: 11785.137 > 0.059]
+
+ 713it [00:06, 211.76it/s, bound: 67 | nc: 5 | ncall: 4505 | eff(%): 15.827 | loglstar: -inf < -42622.954 < inf | logz: -42641.878 +/- 0.533 | dlogz: 10171.492 > 0.059]
+
+ 735it [00:06, 213.43it/s, bound: 70 | nc: 5 | ncall: 4615 | eff(%): 15.926 | loglstar: -inf < -42160.151 < inf | logz: -42179.525 +/- 0.536 | dlogz: 9835.917 > 0.059]
+
+ 760it [00:06, 221.66it/s, bound: 73 | nc: 5 | ncall: 4740 | eff(%): 16.034 | loglstar: -inf < -39924.884 < inf | logz: -39943.638 +/- 0.521 | dlogz: 7437.574 > 0.059]
+
+ 786it [00:06, 231.37it/s, bound: 76 | nc: 5 | ncall: 4870 | eff(%): 16.140 | loglstar: -inf < -39022.002 < inf | logz: -39042.409 +/- 0.547 | dlogz: 6549.020 > 0.059]
+
+ 810it [00:06, 229.17it/s, bound: 79 | nc: 5 | ncall: 4990 | eff(%): 16.232 | loglstar: -inf < -37544.420 < inf | logz: -37565.307 +/- 0.553 | dlogz: 5186.148 > 0.059]
+
+ 838it [00:06, 242.02it/s, bound: 83 | nc: 5 | ncall: 5130 | eff(%): 16.335 | loglstar: -inf < -36800.267 < inf | logz: -36821.710 +/- 0.557 | dlogz: 4679.464 > 0.059]
+
+ 865it [00:06, 248.71it/s, bound: 86 | nc: 5 | ncall: 5265 | eff(%): 16.429 | loglstar: -inf < -35519.025 < inf | logz: -35539.371 +/- 0.534 | dlogz: 4912.010 > 0.059]
+
+ 891it [00:06, 250.69it/s, bound: 90 | nc: 5 | ncall: 5395 | eff(%): 16.515 | loglstar: -inf < -34188.087 < inf | logz: -34210.598 +/- 0.572 | dlogz: 3591.748 > 0.059]
+
+ 917it [00:06, 250.16it/s, bound: 93 | nc: 5 | ncall: 5525 | eff(%): 16.597 | loglstar: -inf < -33434.010 < inf | logz: -33457.042 +/- 0.578 | dlogz: 2864.468 > 0.059]
+
+ 943it [00:06, 249.55it/s, bound: 97 | nc: 5 | ncall: 5655 | eff(%): 16.676 | loglstar: -inf < -32749.017 < inf | logz: -32771.448 +/- 0.565 | dlogz: 5273.469 > 0.059]
+
+ 972it [00:07, 260.68it/s, bound: 101 | nc: 5 | ncall: 5800 | eff(%): 16.759 | loglstar: -inf < -32285.709 < inf | logz: -32308.681 +/- 0.569 | dlogz: 7339.013 > 0.059]
+
+ 1001it [00:07, 267.34it/s, bound: 105 | nc: 5 | ncall: 5945 | eff(%): 16.838 | loglstar: -inf < -31683.093 < inf | logz: -31707.823 +/- 0.595 | dlogz: 11152.980 > 0.059]
+
+ 1028it [00:07, 267.13it/s, bound: 108 | nc: 5 | ncall: 6080 | eff(%): 16.908 | loglstar: -inf < -30068.373 < inf | logz: -30093.644 +/- 0.600 | dlogz: 10372.474 > 0.059]
+
+ 1055it [00:07, 266.03it/s, bound: 113 | nc: 5 | ncall: 6215 | eff(%): 16.975 | loglstar: -inf < -28601.815 < inf | logz: -28627.624 +/- 0.607 | dlogz: 8640.504 > 0.059]
+
+ 1082it [00:07, 239.30it/s, bound: 116 | nc: 5 | ncall: 6350 | eff(%): 17.039 | loglstar: -inf < -27057.195 < inf | logz: -27081.536 +/- 0.579 | dlogz: 7978.674 > 0.059]
+
+ 1107it [00:07, 224.32it/s, bound: 120 | nc: 5 | ncall: 6475 | eff(%): 17.097 | loglstar: -inf < -25983.378 < inf | logz: -26010.239 +/- 0.616 | dlogz: 10992.740 > 0.059]
+
+ 1131it [00:07, 225.97it/s, bound: 123 | nc: 5 | ncall: 6595 | eff(%): 17.149 | loglstar: -inf < -23547.514 < inf | logz: -23574.853 +/- 0.623 | dlogz: 10684.604 > 0.059]
+
+ 1154it [00:07, 226.90it/s, bound: 127 | nc: 5 | ncall: 6710 | eff(%): 17.198 | loglstar: -inf < -21859.080 < inf | logz: -21885.764 +/- 0.609 | dlogz: 8721.452 > 0.059]
+
+ 1178it [00:07, 230.43it/s, bound: 130 | nc: 5 | ncall: 6830 | eff(%): 17.247 | loglstar: -inf < -20281.639 < inf | logz: -20309.921 +/- 0.633 | dlogz: 7203.562 > 0.059]
+
+ 1202it [00:08, 231.94it/s, bound: 134 | nc: 5 | ncall: 6950 | eff(%): 17.295 | loglstar: -inf < -19180.535 < inf | logz: -19208.178 +/- 0.620 | dlogz: 6042.911 > 0.059]
+
+ 1229it [00:08, 241.47it/s, bound: 139 | nc: 5 | ncall: 7085 | eff(%): 17.347 | loglstar: -inf < -18161.991 < inf | logz: -18188.795 +/- 0.601 | dlogz: 8424.477 > 0.059]
+
+ 1255it [00:08, 245.08it/s, bound: 143 | nc: 5 | ncall: 7215 | eff(%): 17.394 | loglstar: -inf < -16677.263 < inf | logz: -16707.097 +/- 0.646 | dlogz: 6995.471 > 0.059]
+
+ 1285it [00:08, 258.34it/s, bound: 146 | nc: 5 | ncall: 7365 | eff(%): 17.447 | loglstar: -inf < -15471.839 < inf | logz: -15502.272 +/- 0.652 | dlogz: 6935.616 > 0.059]
+
+ 1312it [00:08, 260.26it/s, bound: 151 | nc: 5 | ncall: 7500 | eff(%): 17.493 | loglstar: -inf < -14107.515 < inf | logz: -14138.489 +/- 0.657 | dlogz: 5514.862 > 0.059]
+
+ 1341it [00:08, 268.54it/s, bound: 155 | nc: 5 | ncall: 7645 | eff(%): 17.541 | loglstar: -inf < -13124.280 < inf | logz: -13155.826 +/- 0.664 | dlogz: 4525.702 > 0.059]
+
+ 1370it [00:08, 274.65it/s, bound: 159 | nc: 5 | ncall: 7790 | eff(%): 17.587 | loglstar: -inf < -12290.026 < inf | logz: -12320.509 +/- 0.645 | dlogz: 3684.529 > 0.059]
+
+ 1400it [00:08, 278.36it/s, bound: 163 | nc: 5 | ncall: 7940 | eff(%): 17.632 | loglstar: -inf < -11090.478 < inf | logz: -11123.217 +/- 0.676 | dlogz: 2529.903 > 0.059]
+
+ 1428it [00:08, 271.40it/s, bound: 166 | nc: 5 | ncall: 8080 | eff(%): 17.673 | loglstar: -inf < -10385.434 < inf | logz: -10418.733 +/- 0.680 | dlogz: 1786.569 > 0.059]
+
+ 1457it [00:08, 276.67it/s, bound: 170 | nc: 5 | ncall: 8225 | eff(%): 17.714 | loglstar: -inf < -9723.039 < inf | logz: -9755.279 +/- 0.661 | dlogz: 2378.173 > 0.059]
+
+ 1488it [00:09, 285.27it/s, bound: 174 | nc: 5 | ncall: 8380 | eff(%): 17.757 | loglstar: -inf < -9244.603 < inf | logz: -9277.466 +/- 0.667 | dlogz: 2645.872 > 0.059]
+
+ 1517it [00:09, 282.34it/s, bound: 178 | nc: 5 | ncall: 8525 | eff(%): 17.795 | loglstar: -inf < -8750.492 < inf | logz: -8785.585 +/- 0.696 | dlogz: 2199.812 > 0.059]
+
+ 1547it [00:09, 286.39it/s, bound: 183 | nc: 5 | ncall: 8675 | eff(%): 17.833 | loglstar: -inf < -8184.322 < inf | logz: -8220.020 +/- 0.703 | dlogz: 1595.906 > 0.059]
+
+ 1578it [00:09, 292.45it/s, bound: 188 | nc: 5 | ncall: 8830 | eff(%): 17.871 | loglstar: -inf < -7524.929 < inf | logz: -7561.244 +/- 0.709 | dlogz: 1145.003 > 0.059]
+
+ 1608it [00:09, 293.74it/s, bound: 192 | nc: 5 | ncall: 8980 | eff(%): 17.906 | loglstar: -inf < -7296.280 < inf | logz: -7333.222 +/- 0.713 | dlogz: 1105.129 > 0.059]
+
+ 1638it [00:09, 289.38it/s, bound: 196 | nc: 5 | ncall: 9130 | eff(%): 17.941 | loglstar: -inf < -7031.596 < inf | logz: -7067.947 +/- 0.697 | dlogz: 1091.543 > 0.059]
+
+ 1667it [00:09, 280.05it/s, bound: 201 | nc: 5 | ncall: 9275 | eff(%): 17.973 | loglstar: -inf < -6714.885 < inf | logz: -6752.933 +/- 0.719 | dlogz: 778.239 > 0.059]
+
+ 1699it [00:09, 289.84it/s, bound: 205 | nc: 5 | ncall: 9435 | eff(%): 18.007 | loglstar: -inf < -6556.201 < inf | logz: -6593.854 +/- 0.713 | dlogz: 616.372 > 0.059]
+
+ 1729it [00:09, 284.76it/s, bound: 209 | nc: 5 | ncall: 9585 | eff(%): 18.039 | loglstar: -inf < -6405.988 < inf | logz: -6445.038 +/- 0.723 | dlogz: 856.664 > 0.059]
+
+ 1758it [00:10, 280.72it/s, bound: 214 | nc: 5 | ncall: 9730 | eff(%): 18.068 | loglstar: -inf < -6217.292 < inf | logz: -6257.243 +/- 0.740 | dlogz: 771.000 > 0.059]
+
+ 1788it [00:10, 285.50it/s, bound: 218 | nc: 5 | ncall: 9880 | eff(%): 18.097 | loglstar: -inf < -6081.272 < inf | logz: -6121.680 +/- 0.738 | dlogz: 625.590 > 0.059]
+
+ 1818it [00:10, 288.43it/s, bound: 222 | nc: 5 | ncall: 10030 | eff(%): 18.126 | loglstar: -inf < -5980.286 < inf | logz: -6020.269 +/- 0.734 | dlogz: 574.204 > 0.059]
+
+ 1848it [00:10, 290.36it/s, bound: 226 | nc: 5 | ncall: 10180 | eff(%): 18.153 | loglstar: -inf < -5791.723 < inf | logz: -5832.353 +/- 0.743 | dlogz: 385.784 > 0.059]
+
+ 1878it [00:10, 288.56it/s, bound: 230 | nc: 5 | ncall: 10330 | eff(%): 18.180 | loglstar: -inf < -5694.234 < inf | logz: -5735.901 +/- 0.746 | dlogz: 313.606 > 0.059]
+
+ 1907it [00:10, 279.89it/s, bound: 234 | nc: 5 | ncall: 10475 | eff(%): 18.205 | loglstar: -inf < -5555.597 < inf | logz: -5596.907 +/- 0.740 | dlogz: 409.483 > 0.059]
+
+ 1936it [00:10, 281.48it/s, bound: 237 | nc: 5 | ncall: 10620 | eff(%): 18.230 | loglstar: -inf < -5520.492 < inf | logz: -5563.596 +/- 0.762 | dlogz: 376.924 > 0.059]
+
+ 1966it [00:10, 285.15it/s, bound: 242 | nc: 5 | ncall: 10770 | eff(%): 18.254 | loglstar: -inf < -5439.713 < inf | logz: -5483.816 +/- 0.780 | dlogz: 392.344 > 0.059]
+
+ 1997it [00:10, 291.25it/s, bound: 247 | nc: 5 | ncall: 10925 | eff(%): 18.279 | loglstar: -inf < -5371.853 < inf | logz: -5416.531 +/- 0.783 | dlogz: 488.928 > 0.059]
+
+ 2028it [00:10, 292.20it/s, bound: 252 | nc: 5 | ncall: 11080 | eff(%): 18.303 | loglstar: -inf < -5279.399 < inf | logz: -5323.611 +/- 0.766 | dlogz: 549.241 > 0.059]
+
+ 2058it [00:11, 294.38it/s, bound: 255 | nc: 5 | ncall: 11230 | eff(%): 18.326 | loglstar: -inf < -5197.653 < inf | logz: -5243.262 +/- 0.782 | dlogz: 545.679 > 0.059]
+
+ 2088it [00:11, 291.54it/s, bound: 259 | nc: 5 | ncall: 11380 | eff(%): 18.348 | loglstar: -inf < -5085.850 < inf | logz: -5130.117 +/- 0.773 | dlogz: 430.834 > 0.059]
+
+ 2119it [00:11, 294.72it/s, bound: 264 | nc: 5 | ncall: 11535 | eff(%): 18.370 | loglstar: -inf < -4989.770 < inf | logz: -5035.643 +/- 0.780 | dlogz: 436.647 > 0.059]
+
+ 2150it [00:11, 297.19it/s, bound: 268 | nc: 5 | ncall: 11690 | eff(%): 18.392 | loglstar: -inf < -4899.780 < inf | logz: -4946.368 +/- 0.797 | dlogz: 347.254 > 0.059]
+
+ 2180it [00:11, 293.89it/s, bound: 271 | nc: 5 | ncall: 11840 | eff(%): 18.412 | loglstar: -inf < -4800.515 < inf | logz: -4847.357 +/- 0.796 | dlogz: 366.033 > 0.059]
+
+ 2210it [00:11, 283.57it/s, bound: 276 | nc: 5 | ncall: 11990 | eff(%): 18.432 | loglstar: -inf < -4764.792 < inf | logz: -4810.659 +/- 0.785 | dlogz: 328.269 > 0.059]
+
+ 2239it [00:11, 266.87it/s, bound: 279 | nc: 5 | ncall: 12135 | eff(%): 18.451 | loglstar: -inf < -4711.500 < inf | logz: -4761.102 +/- 0.829 | dlogz: 288.766 > 0.059]
+
+ 2268it [00:11, 271.81it/s, bound: 283 | nc: 5 | ncall: 12280 | eff(%): 18.469 | loglstar: -inf < -4657.597 < inf | logz: -4706.908 +/- 0.818 | dlogz: 224.333 > 0.059]
+
+ 2298it [00:11, 279.43it/s, bound: 287 | nc: 5 | ncall: 12430 | eff(%): 18.488 | loglstar: -inf < -4610.278 < inf | logz: -4661.015 +/- 0.838 | dlogz: 265.700 > 0.059]
+
+ 2327it [00:12, 277.68it/s, bound: 292 | nc: 5 | ncall: 12575 | eff(%): 18.505 | loglstar: -inf < -4556.876 < inf | logz: -4606.788 +/- 0.826 | dlogz: 444.007 > 0.059]
+
+ 2356it [00:12, 277.40it/s, bound: 296 | nc: 5 | ncall: 12720 | eff(%): 18.522 | loglstar: -inf < -4505.287 < inf | logz: -4557.231 +/- 0.851 | dlogz: 450.880 > 0.059]
+
+ 2385it [00:12, 280.16it/s, bound: 299 | nc: 5 | ncall: 12865 | eff(%): 18.539 | loglstar: -inf < -4459.479 < inf | logz: -4510.327 +/- 0.834 | dlogz: 712.685 > 0.059]
+
+ 2415it [00:12, 283.98it/s, bound: 303 | nc: 5 | ncall: 13015 | eff(%): 18.556 | loglstar: -inf < -4409.904 < inf | logz: -4461.642 +/- 0.840 | dlogz: 663.608 > 0.059]
+
+ 2445it [00:12, 286.60it/s, bound: 307 | nc: 5 | ncall: 13165 | eff(%): 18.572 | loglstar: -inf < -4316.912 < inf | logz: -4370.394 +/- 0.857 | dlogz: 573.184 > 0.059]
+
+ 2474it [00:12, 284.42it/s, bound: 310 | nc: 5 | ncall: 13310 | eff(%): 18.588 | loglstar: -inf < -4235.231 < inf | logz: -4288.364 +/- 0.856 | dlogz: 502.345 > 0.059]
+
+ 2503it [00:12, 247.76it/s, bound: 314 | nc: 5 | ncall: 13455 | eff(%): 18.603 | loglstar: -inf < -4116.690 < inf | logz: -4169.590 +/- 0.851 | dlogz: 382.330 > 0.059]
+
+ 2529it [00:12, 244.70it/s, bound: 317 | nc: 5 | ncall: 13585 | eff(%): 18.616 | loglstar: -inf < -4058.960 < inf | logz: -4112.301 +/- 0.851 | dlogz: 457.538 > 0.059]
+
+ 2555it [00:12, 240.26it/s, bound: 321 | nc: 5 | ncall: 13715 | eff(%): 18.629 | loglstar: -inf < -3974.989 < inf | logz: -4030.954 +/- 0.882 | dlogz: 386.044 > 0.059]
+
+ 2580it [00:13, 228.71it/s, bound: 324 | nc: 5 | ncall: 13840 | eff(%): 18.642 | loglstar: -inf < -3933.400 < inf | logz: -3988.059 +/- 0.859 | dlogz: 332.297 > 0.059]
+
+ 2604it [00:13, 228.95it/s, bound: 327 | nc: 5 | ncall: 13960 | eff(%): 18.653 | loglstar: -inf < -3903.190 < inf | logz: -3960.114 +/- 0.888 | dlogz: 307.461 > 0.059]
+
+ 2629it [00:13, 230.14it/s, bound: 330 | nc: 5 | ncall: 14085 | eff(%): 18.665 | loglstar: -inf < -3884.056 < inf | logz: -3939.796 +/- 0.874 | dlogz: 288.997 > 0.059]
+
+ 2653it [00:13, 224.40it/s, bound: 333 | nc: 5 | ncall: 14205 | eff(%): 18.677 | loglstar: -inf < -3843.682 < inf | logz: -3899.117 +/- 0.870 | dlogz: 365.637 > 0.059]
+
+ 2677it [00:13, 228.08it/s, bound: 336 | nc: 5 | ncall: 14325 | eff(%): 18.688 | loglstar: -inf < -3798.061 < inf | logz: -3855.180 +/- 0.882 | dlogz: 321.799 > 0.059]
+
+ 2703it [00:13, 234.95it/s, bound: 339 | nc: 5 | ncall: 14455 | eff(%): 18.699 | loglstar: -inf < -3761.573 < inf | logz: -3820.350 +/- 0.898 | dlogz: 287.779 > 0.059]
+
+ 2727it [00:13, 231.11it/s, bound: 342 | nc: 5 | ncall: 14575 | eff(%): 18.710 | loglstar: -inf < -3729.105 < inf | logz: -3788.540 +/- 0.909 | dlogz: 258.064 > 0.059]
+
+ 2751it [00:13, 208.03it/s, bound: 345 | nc: 5 | ncall: 14695 | eff(%): 18.721 | loglstar: -inf < -3691.375 < inf | logz: -3750.877 +/- 0.902 | dlogz: 216.550 > 0.059]
+
+ 2776it [00:13, 216.42it/s, bound: 348 | nc: 5 | ncall: 14820 | eff(%): 18.731 | loglstar: -inf < -3644.681 < inf | logz: -3703.636 +/- 0.902 | dlogz: 311.575 > 0.059]
+
+ 2801it [00:14, 224.84it/s, bound: 352 | nc: 5 | ncall: 14945 | eff(%): 18.742 | loglstar: -inf < -3591.772 < inf | logz: -3651.564 +/- 0.912 | dlogz: 259.446 > 0.059]
+
+ 2827it [00:14, 233.60it/s, bound: 355 | nc: 5 | ncall: 15075 | eff(%): 18.753 | loglstar: -inf < -3548.807 < inf | logz: -3608.553 +/- 0.909 | dlogz: 215.304 > 0.059]
+
+ 2851it [00:14, 233.54it/s, bound: 359 | nc: 5 | ncall: 15195 | eff(%): 18.763 | loglstar: -inf < -3504.303 < inf | logz: -3565.368 +/- 0.921 | dlogz: 259.892 > 0.059]
+
+ 2875it [00:14, 235.29it/s, bound: 362 | nc: 5 | ncall: 15315 | eff(%): 18.772 | loglstar: -inf < -3480.360 < inf | logz: -3540.181 +/- 0.907 | dlogz: 233.170 > 0.059]
+
+ 2899it [00:14, 236.65it/s, bound: 365 | nc: 5 | ncall: 15435 | eff(%): 18.782 | loglstar: -inf < -3440.971 < inf | logz: -3501.378 +/- 0.906 | dlogz: 215.082 > 0.059]
+
+ 2923it [00:14, 224.19it/s, bound: 368 | nc: 5 | ncall: 15555 | eff(%): 18.791 | loglstar: -inf < -3388.419 < inf | logz: -3451.788 +/- 0.943 | dlogz: 169.525 > 0.059]
+
+ 2946it [00:14, 216.71it/s, bound: 372 | nc: 5 | ncall: 15670 | eff(%): 18.800 | loglstar: -inf < -3366.886 < inf | logz: -3429.279 +/- 0.929 | dlogz: 142.514 > 0.059]
+
+ 2969it [00:14, 217.91it/s, bound: 375 | nc: 5 | ncall: 15785 | eff(%): 18.809 | loglstar: -inf < -3340.426 < inf | logz: -3404.417 +/- 0.942 | dlogz: 118.237 > 0.059]
+
+ 2992it [00:14, 220.07it/s, bound: 378 | nc: 5 | ncall: 15900 | eff(%): 18.818 | loglstar: -inf < -3322.582 < inf | logz: -3386.217 +/- 0.944 | dlogz: 98.952 > 0.059]
+
+ 3015it [00:14, 218.97it/s, bound: 381 | nc: 5 | ncall: 16015 | eff(%): 18.826 | loglstar: -inf < -3303.484 < inf | logz: -3367.789 +/- 0.945 | dlogz: 80.005 > 0.059]
+
+ 3037it [00:15, 216.95it/s, bound: 383 | nc: 5 | ncall: 16125 | eff(%): 18.834 | loglstar: -inf < -3291.004 < inf | logz: -3356.147 +/- 0.946 | dlogz: 74.722 > 0.059]
+
+ 3062it [00:15, 223.54it/s, bound: 387 | nc: 5 | ncall: 16250 | eff(%): 18.843 | loglstar: -inf < -3278.511 < inf | logz: -3342.283 +/- 0.942 | dlogz: 61.537 > 0.059]
+
+ 3086it [00:15, 227.30it/s, bound: 390 | nc: 5 | ncall: 16370 | eff(%): 18.852 | loglstar: -inf < -3267.117 < inf | logz: -3333.471 +/- 0.960 | dlogz: 53.455 > 0.059]
+
+ 3111it [00:15, 232.99it/s, bound: 393 | nc: 5 | ncall: 16495 | eff(%): 18.860 | loglstar: -inf < -3252.052 < inf | logz: -3318.751 +/- 0.963 | dlogz: 37.967 > 0.059]
+
+ 3137it [00:15, 238.64it/s, bound: 396 | nc: 5 | ncall: 16625 | eff(%): 18.869 | loglstar: -inf < -3241.167 < inf | logz: -3307.366 +/- 0.954 | dlogz: 60.780 > 0.059]
+
+ 3161it [00:15, 225.76it/s, bound: 399 | nc: 5 | ncall: 16745 | eff(%): 18.877 | loglstar: -inf < -3228.201 < inf | logz: -3293.740 +/- 0.959 | dlogz: 47.071 > 0.059]
+
+ 3185it [00:15, 227.66it/s, bound: 402 | nc: 5 | ncall: 16865 | eff(%): 18.885 | loglstar: -inf < -3221.507 < inf | logz: -3288.603 +/- 0.959 | dlogz: 41.640 > 0.059]
+
+ 3208it [00:15, 225.99it/s, bound: 405 | nc: 5 | ncall: 16980 | eff(%): 18.893 | loglstar: -inf < -3218.473 < inf | logz: -3285.662 +/- 0.958 | dlogz: 38.082 > 0.059]
+
+ 3234it [00:15, 235.10it/s, bound: 408 | nc: 5 | ncall: 17110 | eff(%): 18.901 | loglstar: -inf < -3208.836 < inf | logz: -3276.342 +/- 0.974 | dlogz: 53.717 > 0.059]
+
+ 3258it [00:16, 221.87it/s, bound: 411 | nc: 5 | ncall: 17230 | eff(%): 18.909 | loglstar: -inf < -3198.968 < inf | logz: -3267.567 +/- 0.978 | dlogz: 47.690 > 0.059]
+
+ 3281it [00:16, 216.10it/s, bound: 415 | nc: 5 | ncall: 17345 | eff(%): 18.916 | loglstar: -inf < -3191.602 < inf | logz: -3259.916 +/- 0.982 | dlogz: 43.418 > 0.059]
+
+ 3305it [00:16, 221.80it/s, bound: 418 | nc: 5 | ncall: 17465 | eff(%): 18.924 | loglstar: -inf < -3186.406 < inf | logz: -3254.757 +/- 0.982 | dlogz: 45.126 > 0.059]
+
+ 3328it [00:16, 220.68it/s, bound: 421 | nc: 5 | ncall: 17580 | eff(%): 18.931 | loglstar: -inf < -3180.130 < inf | logz: -3250.980 +/- 0.995 | dlogz: 52.236 > 0.059]
+
+ 3352it [00:16, 222.46it/s, bound: 425 | nc: 5 | ncall: 17700 | eff(%): 18.938 | loglstar: -inf < -3171.766 < inf | logz: -3242.285 +/- 0.995 | dlogz: 42.641 > 0.059]
+
+ 3375it [00:16, 218.59it/s, bound: 428 | nc: 5 | ncall: 17815 | eff(%): 18.945 | loglstar: -inf < -3161.316 < inf | logz: -3232.514 +/- 1.008 | dlogz: 33.032 > 0.059]
+
+ 3398it [00:16, 220.35it/s, bound: 431 | nc: 5 | ncall: 17930 | eff(%): 18.951 | loglstar: -inf < -3155.385 < inf | logz: -3226.563 +/- 1.002 | dlogz: 34.306 > 0.059]
+
+ 3422it [00:16, 225.38it/s, bound: 435 | nc: 5 | ncall: 18050 | eff(%): 18.958 | loglstar: -inf < -3151.227 < inf | logz: -3222.548 +/- 1.001 | dlogz: 29.723 > 0.059]
+
+ 3446it [00:16, 229.25it/s, bound: 438 | nc: 5 | ncall: 18170 | eff(%): 18.965 | loglstar: -inf < -3144.038 < inf | logz: -3216.278 +/- 1.012 | dlogz: 29.213 > 0.059]
+
+ 3469it [00:16, 228.92it/s, bound: 441 | nc: 5 | ncall: 18285 | eff(%): 18.972 | loglstar: -inf < -3140.698 < inf | logz: -3211.973 +/- 1.009 | dlogz: 24.218 > 0.059]
+
+ 3493it [00:17, 230.28it/s, bound: 445 | nc: 5 | ncall: 18405 | eff(%): 18.979 | loglstar: -inf < -3135.273 < inf | logz: -3207.607 +/- 1.018 | dlogz: 20.942 > 0.059]
+
+ 3517it [00:17, 232.18it/s, bound: 448 | nc: 5 | ncall: 18525 | eff(%): 18.985 | loglstar: -inf < -3131.892 < inf | logz: -3203.824 +/- 1.016 | dlogz: 16.593 > 0.059]
+
+ 3542it [00:17, 235.95it/s, bound: 451 | nc: 5 | ncall: 18650 | eff(%): 18.992 | loglstar: -inf < -3125.801 < inf | logz: -3198.731 +/- 1.026 | dlogz: 16.917 > 0.059]
+
+ 3567it [00:17, 237.62it/s, bound: 455 | nc: 5 | ncall: 18775 | eff(%): 18.999 | loglstar: -inf < -3123.639 < inf | logz: -3196.680 +/- 1.023 | dlogz: 14.322 > 0.059]
+
+ 3592it [00:17, 239.88it/s, bound: 458 | nc: 5 | ncall: 18900 | eff(%): 19.005 | loglstar: -inf < -3121.695 < inf | logz: -3194.972 +/- 1.025 | dlogz: 12.083 > 0.059]
+
+ 3616it [00:17, 229.01it/s, bound: 461 | nc: 5 | ncall: 19020 | eff(%): 19.012 | loglstar: -inf < -3119.236 < inf | logz: -3193.290 +/- 1.031 | dlogz: 9.948 > 0.059]
+
+ 3639it [00:17, 224.41it/s, bound: 464 | nc: 5 | ncall: 19135 | eff(%): 19.018 | loglstar: -inf < -3117.744 < inf | logz: -3191.885 +/- 1.033 | dlogz: 8.049 > 0.059]
+
+ 3662it [00:17, 219.18it/s, bound: 467 | nc: 5 | ncall: 19250 | eff(%): 19.023 | loglstar: -inf < -3116.298 < inf | logz: -3190.845 +/- 1.034 | dlogz: 9.367 > 0.059]
+
+ 3684it [00:17, 213.58it/s, bound: 470 | nc: 5 | ncall: 19360 | eff(%): 19.029 | loglstar: -inf < -3115.586 < inf | logz: -3189.995 +/- 1.036 | dlogz: 8.062 > 0.059]
+
+ 3706it [00:18, 212.63it/s, bound: 473 | nc: 5 | ncall: 19470 | eff(%): 19.034 | loglstar: -inf < -3114.786 < inf | logz: -3189.469 +/- 1.037 | dlogz: 7.085 > 0.059]
+
+ 3729it [00:18, 215.48it/s, bound: 476 | nc: 5 | ncall: 19585 | eff(%): 19.040 | loglstar: -inf < -3113.585 < inf | logz: -3188.709 +/- 1.041 | dlogz: 5.863 > 0.059]
+
+ 3752it [00:18, 217.67it/s, bound: 479 | nc: 5 | ncall: 19700 | eff(%): 19.046 | loglstar: -inf < -3112.587 < inf | logz: -3188.192 +/- 1.043 | dlogz: 4.893 > 0.059]
+
+ 3776it [00:18, 222.67it/s, bound: 482 | nc: 5 | ncall: 19820 | eff(%): 19.051 | loglstar: -inf < -3112.001 < inf | logz: -3187.720 +/- 1.045 | dlogz: 3.946 > 0.059]
+
+ 3801it [00:18, 229.20it/s, bound: 486 | nc: 5 | ncall: 19945 | eff(%): 19.057 | loglstar: -inf < -3111.140 < inf | logz: -3187.310 +/- 1.047 | dlogz: 3.850 > 0.059]
+
+ 3826it [00:18, 233.50it/s, bound: 489 | nc: 5 | ncall: 20070 | eff(%): 19.063 | loglstar: -inf < -3110.382 < inf | logz: -3186.971 +/- 1.050 | dlogz: 3.691 > 0.059]
+
+ 3851it [00:18, 236.02it/s, bound: 493 | nc: 5 | ncall: 20195 | eff(%): 19.069 | loglstar: -inf < -3109.558 < inf | logz: -3186.610 +/- 1.053 | dlogz: 137.273 > 0.059]
+
+ 3877it [00:18, 242.74it/s, bound: 496 | nc: 5 | ncall: 20325 | eff(%): 19.075 | loglstar: -inf < -3108.505 < inf | logz: -3186.212 +/- 1.057 | dlogz: 180.588 > 0.059]
+
+ 3902it [00:18, 243.41it/s, bound: 499 | nc: 5 | ncall: 20450 | eff(%): 19.081 | loglstar: -inf < -3107.894 < inf | logz: -3185.828 +/- 1.061 | dlogz: 214.871 > 0.059]
+
+ 3927it [00:18, 240.42it/s, bound: 502 | nc: 5 | ncall: 20575 | eff(%): 19.086 | loglstar: -inf < -3107.409 < inf | logz: -3185.574 +/- 1.063 | dlogz: 262.167 > 0.059]
+
+ 3954it [00:19, 249.04it/s, bound: 506 | nc: 5 | ncall: 20710 | eff(%): 19.092 | loglstar: -inf < -3106.449 < inf | logz: -3185.313 +/- 1.066 | dlogz: 401.968 > 0.059]
+
+ 3979it [00:19, 242.51it/s, bound: 509 | nc: 5 | ncall: 20835 | eff(%): 19.098 | loglstar: -inf < -3089.190 < inf | logz: -3171.230 +/- 1.116 | dlogz: 400.672 > 0.059]
+
+ 4006it [00:19, 249.49it/s, bound: 513 | nc: 5 | ncall: 20970 | eff(%): 19.103 | loglstar: -inf < -3028.774 < inf | logz: -3113.872 +/- 1.142 | dlogz: 511.433 > 0.059]
+
+ 4037it [00:19, 266.87it/s, bound: 518 | nc: 5 | ncall: 21125 | eff(%): 19.110 | loglstar: -inf < -2935.952 < inf | logz: -3021.403 +/- 1.139 | dlogz: 413.664 > 0.059]
+
+ 4066it [00:19, 270.70it/s, bound: 522 | nc: 5 | ncall: 21270 | eff(%): 19.116 | loglstar: -inf < -2804.742 < inf | logz: -2890.069 +/- 1.135 | dlogz: 280.633 > 0.059]
+
+ 4094it [00:19, 259.11it/s, bound: 526 | nc: 5 | ncall: 21410 | eff(%): 19.122 | loglstar: -inf < -2703.924 < inf | logz: -2789.659 +/- 1.144 | dlogz: 179.833 > 0.059]
+
+ 4121it [00:19, 253.93it/s, bound: 530 | nc: 5 | ncall: 21545 | eff(%): 19.127 | loglstar: -inf < -2676.905 < inf | logz: -2763.583 +/- 1.140 | dlogz: 287.628 > 0.059]
+
+ 4147it [00:19, 251.85it/s, bound: 534 | nc: 5 | ncall: 21675 | eff(%): 19.133 | loglstar: -inf < -2654.489 < inf | logz: -2741.413 +/- 1.145 | dlogz: 264.911 > 0.059]
+
+ 4173it [00:19, 247.55it/s, bound: 538 | nc: 5 | ncall: 21805 | eff(%): 19.138 | loglstar: -inf < -2595.726 < inf | logz: -2683.058 +/- 1.153 | dlogz: 206.388 > 0.059]
+
+ 4198it [00:20, 244.69it/s, bound: 541 | nc: 5 | ncall: 21930 | eff(%): 19.143 | loglstar: -inf < -2564.045 < inf | logz: -2652.720 +/- 1.158 | dlogz: 189.504 > 0.059]
+
+ 4223it [00:20, 207.94it/s, bound: 545 | nc: 5 | ncall: 22055 | eff(%): 19.148 | loglstar: -inf < -2528.320 < inf | logz: -2614.630 +/- 1.140 | dlogz: 176.314 > 0.059]
+
+ 4245it [00:20, 187.73it/s, bound: 549 | nc: 5 | ncall: 22165 | eff(%): 19.152 | loglstar: -inf < -2490.456 < inf | logz: -2579.553 +/- 1.161 | dlogz: 189.182 > 0.059]
+
+ 4265it [00:20, 185.33it/s, bound: 551 | nc: 5 | ncall: 22265 | eff(%): 19.156 | loglstar: -inf < -2464.797 < inf | logz: -2554.431 +/- 1.163 | dlogz: 224.042 > 0.059]
+
+ 4285it [00:20, 186.16it/s, bound: 554 | nc: 5 | ncall: 22365 | eff(%): 19.159 | loglstar: -inf < -2446.211 < inf | logz: -2536.298 +/- 1.167 | dlogz: 272.742 > 0.059]
+
+ 4307it [00:20, 192.66it/s, bound: 557 | nc: 5 | ncall: 22475 | eff(%): 19.164 | loglstar: -inf < -2417.344 < inf | logz: -2507.510 +/- 1.169 | dlogz: 243.208 > 0.059]
+
+ 4327it [00:20, 190.78it/s, bound: 560 | nc: 5 | ncall: 22575 | eff(%): 19.167 | loglstar: -inf < -2397.637 < inf | logz: -2488.421 +/- 1.169 | dlogz: 373.970 > 0.059]
+
+ 4347it [00:20, 182.05it/s, bound: 562 | nc: 5 | ncall: 22675 | eff(%): 19.171 | loglstar: -inf < -2358.398 < inf | logz: -2448.698 +/- 1.172 | dlogz: 408.663 > 0.059]
+
+ 4366it [00:21, 174.86it/s, bound: 565 | nc: 5 | ncall: 22770 | eff(%): 19.174 | loglstar: -inf < -2328.265 < inf | logz: -2420.536 +/- 1.186 | dlogz: 382.616 > 0.059]
+
+ 4384it [00:21, 173.72it/s, bound: 567 | nc: 5 | ncall: 22860 | eff(%): 19.178 | loglstar: -inf < -2301.027 < inf | logz: -2392.674 +/- 1.180 | dlogz: 352.430 > 0.059]
+
+ 4404it [00:21, 176.69it/s, bound: 570 | nc: 5 | ncall: 22960 | eff(%): 19.181 | loglstar: -inf < -2245.881 < inf | logz: -2336.707 +/- 1.174 | dlogz: 497.115 > 0.059]
+
+ 4422it [00:21, 177.17it/s, bound: 572 | nc: 5 | ncall: 23050 | eff(%): 19.184 | loglstar: -inf < -2199.901 < inf | logz: -2292.184 +/- 1.185 | dlogz: 452.960 > 0.059]
+
+ 4443it [00:21, 184.65it/s, bound: 575 | nc: 5 | ncall: 23155 | eff(%): 19.188 | loglstar: -inf < -2165.182 < inf | logz: -2257.935 +/- 1.189 | dlogz: 541.031 > 0.059]
+
+ 4462it [00:21, 180.09it/s, bound: 577 | nc: 5 | ncall: 23250 | eff(%): 19.191 | loglstar: -inf < -2084.769 < inf | logz: -2179.031 +/- 1.201 | dlogz: 505.635 > 0.059]
+
+ 4481it [00:21, 182.82it/s, bound: 580 | nc: 5 | ncall: 23345 | eff(%): 19.195 | loglstar: -inf < -2009.045 < inf | logz: -2103.452 +/- 1.196 | dlogz: 456.785 > 0.059]
+
+ 4502it [00:21, 188.70it/s, bound: 583 | nc: 5 | ncall: 23450 | eff(%): 19.198 | loglstar: -inf < -1930.708 < inf | logz: -2024.381 +/- 1.194 | dlogz: 376.102 > 0.059]
+
+ 4521it [00:21, 188.81it/s, bound: 585 | nc: 5 | ncall: 23545 | eff(%): 19.202 | loglstar: -inf < -1862.088 < inf | logz: -1955.832 +/- 1.194 | dlogz: 306.935 > 0.059]
+
+ 4543it [00:21, 195.88it/s, bound: 588 | nc: 5 | ncall: 23655 | eff(%): 19.205 | loglstar: -inf < -1794.314 < inf | logz: -1889.577 +/- 1.200 | dlogz: 240.798 > 0.059]
+
+ 4563it [00:22, 192.88it/s, bound: 591 | nc: 5 | ncall: 23755 | eff(%): 19.209 | loglstar: -inf < -1717.877 < inf | logz: -1814.146 +/- 1.213 | dlogz: 379.909 > 0.059]
+
+ 4584it [00:22, 196.54it/s, bound: 593 | nc: 5 | ncall: 23860 | eff(%): 19.212 | loglstar: -inf < -1669.403 < inf | logz: -1765.933 +/- 1.210 | dlogz: 349.557 > 0.059]
+
+ 4604it [00:22, 196.63it/s, bound: 596 | nc: 5 | ncall: 23960 | eff(%): 19.215 | loglstar: -inf < -1629.887 < inf | logz: -1724.199 +/- 1.192 | dlogz: 407.633 > 0.059]
+
+ 4624it [00:22, 195.67it/s, bound: 599 | nc: 5 | ncall: 24060 | eff(%): 19.219 | loglstar: -inf < -1590.358 < inf | logz: -1686.571 +/- 1.208 | dlogz: 370.273 > 0.059]
+
+ 4644it [00:22, 189.72it/s, bound: 602 | nc: 5 | ncall: 24160 | eff(%): 19.222 | loglstar: -inf < -1553.892 < inf | logz: -1651.558 +/- 1.218 | dlogz: 336.168 > 0.059]
+
+ 4664it [00:22, 192.35it/s, bound: 604 | nc: 5 | ncall: 24260 | eff(%): 19.225 | loglstar: -inf < -1504.288 < inf | logz: -1601.485 +/- 1.217 | dlogz: 284.677 > 0.059]
+
+ 4684it [00:22, 192.45it/s, bound: 607 | nc: 5 | ncall: 24360 | eff(%): 19.228 | loglstar: -inf < -1462.571 < inf | logz: -1560.022 +/- 1.214 | dlogz: 242.562 > 0.059]
+
+ 4705it [00:22, 196.34it/s, bound: 609 | nc: 5 | ncall: 24465 | eff(%): 19.232 | loglstar: -inf < -1427.355 < inf | logz: -1526.389 +/- 1.226 | dlogz: 338.098 > 0.059]
+
+ 4725it [00:22, 186.75it/s, bound: 612 | nc: 5 | ncall: 24565 | eff(%): 19.235 | loglstar: -inf < -1380.576 < inf | logz: -1479.819 +/- 1.226 | dlogz: 290.639 > 0.059]
+
+ 4745it [00:23, 189.26it/s, bound: 614 | nc: 5 | ncall: 24665 | eff(%): 19.238 | loglstar: -inf < -1348.555 < inf | logz: -1447.005 +/- 1.219 | dlogz: 256.020 > 0.059]
+
+ 4765it [00:23, 188.60it/s, bound: 617 | nc: 5 | ncall: 24765 | eff(%): 19.241 | loglstar: -inf < -1321.917 < inf | logz: -1420.266 +/- 1.221 | dlogz: 228.608 > 0.059]
+
+ 4784it [00:23, 187.02it/s, bound: 620 | nc: 5 | ncall: 24860 | eff(%): 19.244 | loglstar: -inf < -1294.259 < inf | logz: -1393.464 +/- 1.226 | dlogz: 247.838 > 0.059]
+
+ 4803it [00:23, 177.15it/s, bound: 622 | nc: 5 | ncall: 24955 | eff(%): 19.247 | loglstar: -inf < -1266.353 < inf | logz: -1366.339 +/- 1.233 | dlogz: 220.836 > 0.059]
+
+ 4821it [00:23, 170.98it/s, bound: 624 | nc: 5 | ncall: 25045 | eff(%): 19.249 | loglstar: -inf < -1234.459 < inf | logz: -1335.287 +/- 1.234 | dlogz: 189.475 > 0.059]
+
+ 4839it [00:23, 172.44it/s, bound: 626 | nc: 5 | ncall: 25135 | eff(%): 19.252 | loglstar: -inf < -1223.327 < inf | logz: -1322.906 +/- 1.220 | dlogz: 175.745 > 0.059]
+
+ 4858it [00:23, 176.89it/s, bound: 629 | nc: 5 | ncall: 25230 | eff(%): 19.255 | loglstar: -inf < -1195.042 < inf | logz: -1295.463 +/- 1.233 | dlogz: 274.986 > 0.059]
+
+ 4879it [00:23, 183.43it/s, bound: 632 | nc: 5 | ncall: 25335 | eff(%): 19.258 | loglstar: -inf < -1158.544 < inf | logz: -1261.169 +/- 1.251 | dlogz: 332.831 > 0.059]
+
+ 4898it [00:23, 185.13it/s, bound: 634 | nc: 5 | ncall: 25430 | eff(%): 19.261 | loglstar: -inf < -1139.580 < inf | logz: -1239.890 +/- 1.231 | dlogz: 306.826 > 0.059]
+
+ 4917it [00:23, 169.07it/s, bound: 637 | nc: 5 | ncall: 25525 | eff(%): 19.263 | loglstar: -inf < -1111.420 < inf | logz: -1214.704 +/- 1.254 | dlogz: 313.410 > 0.059]
+
+ 4935it [00:24, 166.44it/s, bound: 639 | nc: 5 | ncall: 25615 | eff(%): 19.266 | loglstar: -inf < -1087.309 < inf | logz: -1190.842 +/- 1.252 | dlogz: 288.249 > 0.059]
+
+ 4954it [00:24, 170.86it/s, bound: 642 | nc: 5 | ncall: 25710 | eff(%): 19.269 | loglstar: -inf < -1077.323 < inf | logz: -1179.547 +/- 1.243 | dlogz: 275.037 > 0.059]
+
+ 4972it [00:24, 169.31it/s, bound: 644 | nc: 5 | ncall: 25800 | eff(%): 19.271 | loglstar: -inf < -1047.803 < inf | logz: -1151.825 +/- 1.253 | dlogz: 247.711 > 0.059]
+
+ 4992it [00:24, 175.23it/s, bound: 647 | nc: 5 | ncall: 25900 | eff(%): 19.274 | loglstar: -inf < -1015.339 < inf | logz: -1119.214 +/- 1.258 | dlogz: 214.694 > 0.059]
+
+ 5010it [00:24, 175.57it/s, bound: 649 | nc: 5 | ncall: 25990 | eff(%): 19.277 | loglstar: -inf < -982.561 < inf | logz: -1087.229 +/- 1.260 | dlogz: 182.691 > 0.059]
+
+ 5029it [00:24, 177.84it/s, bound: 653 | nc: 5 | ncall: 26085 | eff(%): 19.279 | loglstar: -inf < -942.331 < inf | logz: -1047.945 +/- 1.269 | dlogz: 240.876 > 0.059]
+
+ 5051it [00:24, 188.59it/s, bound: 655 | nc: 5 | ncall: 26195 | eff(%): 19.282 | loglstar: -inf < -922.753 < inf | logz: -1027.475 +/- 1.257 | dlogz: 216.980 > 0.059]
+
+ 5074it [00:24, 199.89it/s, bound: 658 | nc: 5 | ncall: 26310 | eff(%): 19.285 | loglstar: -inf < -890.187 < inf | logz: -996.023 +/- 1.265 | dlogz: 185.567 > 0.059]
+
+ 5095it [00:24, 198.38it/s, bound: 661 | nc: 5 | ncall: 26415 | eff(%): 19.288 | loglstar: -inf < -860.389 < inf | logz: -964.609 +/- 1.255 | dlogz: 152.874 > 0.059]
+
+ 5119it [00:25, 208.93it/s, bound: 664 | nc: 5 | ncall: 26535 | eff(%): 19.292 | loglstar: -inf < -834.812 < inf | logz: -940.638 +/- 1.263 | dlogz: 128.693 > 0.059]
+
+ 5141it [00:25, 210.00it/s, bound: 667 | nc: 5 | ncall: 26645 | eff(%): 19.294 | loglstar: -inf < -816.368 < inf | logz: -922.785 +/- 1.272 | dlogz: 117.135 > 0.059]
+
+ 5163it [00:25, 210.22it/s, bound: 670 | nc: 5 | ncall: 26755 | eff(%): 19.297 | loglstar: -inf < -793.441 < inf | logz: -901.470 +/- 1.280 | dlogz: 152.407 > 0.059]
+
+ 5185it [00:25, 205.98it/s, bound: 673 | nc: 5 | ncall: 26865 | eff(%): 19.300 | loglstar: -inf < -778.968 < inf | logz: -886.112 +/- 1.273 | dlogz: 151.699 > 0.059]
+
+ 5206it [00:25, 207.08it/s, bound: 677 | nc: 5 | ncall: 26970 | eff(%): 19.303 | loglstar: -inf < -757.569 < inf | logz: -866.255 +/- 1.285 | dlogz: 132.610 > 0.059]
+
+ 5227it [00:25, 204.77it/s, bound: 680 | nc: 5 | ncall: 27075 | eff(%): 19.306 | loglstar: -inf < -735.428 < inf | logz: -842.552 +/- 1.275 | dlogz: 192.294 > 0.059]
+
+ 5248it [00:25, 200.99it/s, bound: 682 | nc: 5 | ncall: 27180 | eff(%): 19.308 | loglstar: -inf < -709.536 < inf | logz: -819.026 +/- 1.288 | dlogz: 169.466 > 0.059]
+
+ 5269it [00:25, 200.85it/s, bound: 685 | nc: 5 | ncall: 27285 | eff(%): 19.311 | loglstar: -inf < -688.247 < inf | logz: -797.605 +/- 1.292 | dlogz: 147.363 > 0.059]
+
+ 5290it [00:25, 198.89it/s, bound: 688 | nc: 5 | ncall: 27390 | eff(%): 19.314 | loglstar: -inf < -671.449 < inf | logz: -781.591 +/- 1.288 | dlogz: 130.684 > 0.059]
+
+ 5311it [00:25, 199.61it/s, bound: 690 | nc: 5 | ncall: 27495 | eff(%): 19.316 | loglstar: -inf < -656.615 < inf | logz: -766.339 +/- 1.288 | dlogz: 114.740 > 0.059]
+
+ 5331it [00:26, 192.01it/s, bound: 693 | nc: 5 | ncall: 27595 | eff(%): 19.319 | loglstar: -inf < -642.213 < inf | logz: -752.912 +/- 1.295 | dlogz: 101.231 > 0.059]
+
+ 5352it [00:26, 194.77it/s, bound: 696 | nc: 5 | ncall: 27700 | eff(%): 19.321 | loglstar: -inf < -627.427 < inf | logz: -738.732 +/- 1.295 | dlogz: 133.584 > 0.059]
+
+ 5372it [00:26, 189.59it/s, bound: 698 | nc: 5 | ncall: 27800 | eff(%): 19.324 | loglstar: -inf < -609.015 < inf | logz: -720.284 +/- 1.302 | dlogz: 114.804 > 0.059]
+
+ 5392it [00:26, 190.97it/s, bound: 701 | nc: 5 | ncall: 27900 | eff(%): 19.326 | loglstar: -inf < -601.285 < inf | logz: -712.229 +/- 1.292 | dlogz: 151.299 > 0.059]
+
+ 5412it [00:26, 188.95it/s, bound: 704 | nc: 5 | ncall: 28000 | eff(%): 19.329 | loglstar: -inf < -593.134 < inf | logz: -704.499 +/- 1.302 | dlogz: 143.205 > 0.059]
+
+ 5433it [00:26, 193.67it/s, bound: 707 | nc: 5 | ncall: 28105 | eff(%): 19.331 | loglstar: -inf < -582.559 < inf | logz: -693.887 +/- 1.302 | dlogz: 134.669 > 0.059]
+
+ 5455it [00:26, 199.87it/s, bound: 710 | nc: 5 | ncall: 28215 | eff(%): 19.334 | loglstar: -inf < -574.027 < inf | logz: -685.884 +/- 1.306 | dlogz: 126.871 > 0.059]
+
+ 5477it [00:26, 203.79it/s, bound: 713 | nc: 5 | ncall: 28325 | eff(%): 19.336 | loglstar: -inf < -560.920 < inf | logz: -673.836 +/- 1.309 | dlogz: 159.789 > 0.059]
+
+ 5498it [00:26, 201.14it/s, bound: 717 | nc: 5 | ncall: 28430 | eff(%): 19.339 | loglstar: -inf < -532.500 < inf | logz: -645.145 +/- 1.312 | dlogz: 130.443 > 0.059]
+
+ 5519it [00:27, 202.86it/s, bound: 719 | nc: 5 | ncall: 28535 | eff(%): 19.341 | loglstar: -inf < -511.752 < inf | logz: -625.603 +/- 1.321 | dlogz: 157.373 > 0.059]
+
+ 5541it [00:27, 205.72it/s, bound: 722 | nc: 5 | ncall: 28645 | eff(%): 19.344 | loglstar: -inf < -491.757 < inf | logz: -606.488 +/- 1.324 | dlogz: 147.342 > 0.059]
+
+ 5562it [00:27, 199.37it/s, bound: 725 | nc: 5 | ncall: 28750 | eff(%): 19.346 | loglstar: -inf < -469.098 < inf | logz: -584.013 +/- 1.327 | dlogz: 147.191 > 0.059]
+
+ 5582it [00:27, 195.81it/s, bound: 727 | nc: 5 | ncall: 28850 | eff(%): 19.348 | loglstar: -inf < -450.258 < inf | logz: -566.810 +/- 1.336 | dlogz: 155.611 > 0.059]
+
+ 5602it [00:27, 193.33it/s, bound: 730 | nc: 5 | ncall: 28950 | eff(%): 19.351 | loglstar: -inf < -434.797 < inf | logz: -551.605 +/- 1.336 | dlogz: 139.631 > 0.059]
+
+ 5622it [00:27, 193.89it/s, bound: 732 | nc: 5 | ncall: 29050 | eff(%): 19.353 | loglstar: -inf < -419.318 < inf | logz: -536.363 +/- 1.338 | dlogz: 132.572 > 0.059]
+
+ 5645it [00:27, 204.10it/s, bound: 735 | nc: 5 | ncall: 29165 | eff(%): 19.355 | loglstar: -inf < -397.858 < inf | logz: -514.214 +/- 1.336 | dlogz: 108.934 > 0.059]
+
+ 5667it [00:27, 208.54it/s, bound: 738 | nc: 5 | ncall: 29275 | eff(%): 19.358 | loglstar: -inf < -382.623 < inf | logz: -500.059 +/- 1.337 | dlogz: 137.147 > 0.059]
+
+ 5688it [00:27, 207.18it/s, bound: 741 | nc: 5 | ncall: 29380 | eff(%): 19.360 | loglstar: -inf < -364.064 < inf | logz: -482.779 +/- 1.350 | dlogz: 129.827 > 0.059]
+
+ 5709it [00:28, 188.87it/s, bound: 744 | nc: 5 | ncall: 29485 | eff(%): 19.362 | loglstar: -inf < -345.731 < inf | logz: -463.445 +/- 1.343 | dlogz: 108.256 > 0.059]
+
+ 5729it [00:28, 186.35it/s, bound: 746 | nc: 5 | ncall: 29585 | eff(%): 19.365 | loglstar: -inf < -326.340 < inf | logz: -444.575 +/- 1.347 | dlogz: 89.175 > 0.059]
+
+ 5749it [00:28, 188.66it/s, bound: 749 | nc: 5 | ncall: 29685 | eff(%): 19.367 | loglstar: -inf < -316.235 < inf | logz: -434.091 +/- 1.342 | dlogz: 107.706 > 0.059]
+
+ 5769it [00:28, 185.96it/s, bound: 752 | nc: 5 | ncall: 29785 | eff(%): 19.369 | loglstar: -inf < -296.988 < inf | logz: -416.301 +/- 1.351 | dlogz: 133.948 > 0.059]
+
+ 5790it [00:28, 191.54it/s, bound: 754 | nc: 5 | ncall: 29890 | eff(%): 19.371 | loglstar: -inf < -280.015 < inf | logz: -398.863 +/- 1.348 | dlogz: 115.741 > 0.059]
+
+ 5810it [00:28, 191.80it/s, bound: 757 | nc: 5 | ncall: 29990 | eff(%): 19.373 | loglstar: -inf < -254.450 < inf | logz: -375.261 +/- 1.359 | dlogz: 113.409 > 0.059]
+
+ 5831it [00:28, 195.95it/s, bound: 759 | nc: 5 | ncall: 30095 | eff(%): 19.375 | loglstar: -inf < -240.922 < inf | logz: -360.935 +/- 1.356 | dlogz: 97.927 > 0.059]
+
+ 5851it [00:28, 195.62it/s, bound: 762 | nc: 5 | ncall: 30195 | eff(%): 19.377 | loglstar: -inf < -226.959 < inf | logz: -347.459 +/- 1.360 | dlogz: 115.360 > 0.059]
+
+ 5872it [00:28, 197.87it/s, bound: 765 | nc: 5 | ncall: 30300 | eff(%): 19.380 | loglstar: -inf < -211.126 < inf | logz: -331.402 +/- 1.360 | dlogz: 98.639 > 0.059]
+
+ 5892it [00:28, 191.35it/s, bound: 767 | nc: 5 | ncall: 30400 | eff(%): 19.382 | loglstar: -inf < -208.113 < inf | logz: -328.995 +/- 1.353 | dlogz: 95.850 > 0.059]
+
+ 5913it [00:29, 195.36it/s, bound: 770 | nc: 5 | ncall: 30505 | eff(%): 19.384 | loglstar: -inf < -188.645 < inf | logz: -310.444 +/- 1.366 | dlogz: 83.967 > 0.059]
+
+ 5936it [00:29, 202.71it/s, bound: 773 | nc: 5 | ncall: 30620 | eff(%): 19.386 | loglstar: -inf < -181.932 < inf | logz: -303.491 +/- 1.364 | dlogz: 76.442 > 0.059]
+
+ 5957it [00:29, 198.91it/s, bound: 776 | nc: 5 | ncall: 30725 | eff(%): 19.388 | loglstar: -inf < -168.216 < inf | logz: -291.719 +/- 1.374 | dlogz: 65.578 > 0.059]
+
+ 5979it [00:29, 204.09it/s, bound: 779 | nc: 5 | ncall: 30835 | eff(%): 19.390 | loglstar: -inf < -157.946 < inf | logz: -282.255 +/- 1.380 | dlogz: 98.802 > 0.059]
+
+ 6001it [00:29, 206.32it/s, bound: 782 | nc: 5 | ncall: 30945 | eff(%): 19.392 | loglstar: -inf < -149.472 < inf | logz: -272.369 +/- 1.374 | dlogz: 87.494 > 0.059]
+
+ 6022it [00:29, 202.16it/s, bound: 785 | nc: 5 | ncall: 31050 | eff(%): 19.395 | loglstar: -inf < -142.910 < inf | logz: -266.626 +/- 1.375 | dlogz: 107.377 > 0.059]
+
+ 6044it [00:29, 206.25it/s, bound: 788 | nc: 5 | ncall: 31160 | eff(%): 19.397 | loglstar: -inf < -134.279 < inf | logz: -257.319 +/- 1.375 | dlogz: 97.464 > 0.059]
+
+ 6065it [00:29, 205.61it/s, bound: 791 | nc: 5 | ncall: 31265 | eff(%): 19.399 | loglstar: -inf < -120.829 < inf | logz: -246.435 +/- 1.391 | dlogz: 87.072 > 0.059]
+
+ 6086it [00:29, 204.88it/s, bound: 794 | nc: 5 | ncall: 31370 | eff(%): 19.401 | loglstar: -inf < -104.454 < inf | logz: -230.058 +/- 1.388 | dlogz: 69.687 > 0.059]
+
+ 6107it [00:30, 203.61it/s, bound: 796 | nc: 5 | ncall: 31475 | eff(%): 19.403 | loglstar: -inf < -95.193 < inf | logz: -220.601 +/- 1.389 | dlogz: 59.713 > 0.059]
+
+ 6128it [00:30, 200.83it/s, bound: 800 | nc: 5 | ncall: 31580 | eff(%): 19.405 | loglstar: -inf < -87.232 < inf | logz: -212.385 +/- 1.390 | dlogz: 91.878 > 0.059]
+
+ 6149it [00:30, 190.13it/s, bound: 803 | nc: 5 | ncall: 31685 | eff(%): 19.407 | loglstar: -inf < -75.430 < inf | logz: -203.226 +/- 1.404 | dlogz: 83.560 > 0.059]
+
+ 6169it [00:30, 188.15it/s, bound: 805 | nc: 5 | ncall: 31785 | eff(%): 19.409 | loglstar: -inf < -66.947 < inf | logz: -193.211 +/- 1.395 | dlogz: 71.932 > 0.059]
+
+ 6189it [00:30, 190.21it/s, bound: 808 | nc: 5 | ncall: 31885 | eff(%): 19.410 | loglstar: -inf < -59.118 < inf | logz: -186.989 +/- 1.400 | dlogz: 73.215 > 0.059]
+
+ 6209it [00:30, 176.71it/s, bound: 812 | nc: 5 | ncall: 31985 | eff(%): 19.412 | loglstar: -inf < -51.009 < inf | logz: -177.749 +/- 1.399 | dlogz: 63.242 > 0.059]
+
+ 6231it [00:30, 186.39it/s, bound: 815 | nc: 5 | ncall: 32095 | eff(%): 19.414 | loglstar: -inf < -42.764 < inf | logz: -169.966 +/- 1.404 | dlogz: 66.641 > 0.059]
+
+ 6253it [00:30, 194.78it/s, bound: 817 | nc: 5 | ncall: 32205 | eff(%): 19.416 | loglstar: -inf < -32.884 < inf | logz: -161.935 +/- 1.412 | dlogz: 58.689 > 0.059]
+
+ 6274it [00:30, 197.98it/s, bound: 820 | nc: 5 | ncall: 32310 | eff(%): 19.418 | loglstar: -inf < -23.739 < inf | logz: -153.127 +/- 1.413 | dlogz: 49.270 > 0.059]
+
+ 6296it [00:31, 203.59it/s, bound: 823 | nc: 5 | ncall: 32420 | eff(%): 19.420 | loglstar: -inf < -17.428 < inf | logz: -147.226 +/- 1.412 | dlogz: 42.878 > 0.059]
+
+ 6317it [00:31, 203.09it/s, bound: 826 | nc: 5 | ncall: 32525 | eff(%): 19.422 | loglstar: -inf < -7.229 < inf | logz: -136.509 +/- 1.417 | dlogz: 32.571 > 0.059]
+
+ 6338it [00:31, 201.78it/s, bound: 828 | nc: 5 | ncall: 32630 | eff(%): 19.424 | loglstar: -inf < -2.005 < inf | logz: -131.988 +/- 1.417 | dlogz: 29.397 > 0.059]
+
+ 6359it [00:31, 201.75it/s, bound: 831 | nc: 5 | ncall: 32735 | eff(%): 19.426 | loglstar: -inf < 1.861 < inf | logz: -128.496 +/- 1.418 | dlogz: 35.712 > 0.059]
+
+ 6380it [00:31, 203.51it/s, bound: 833 | nc: 5 | ncall: 32840 | eff(%): 19.428 | loglstar: -inf < 8.200 < inf | logz: -122.959 +/- 1.424 | dlogz: 31.874 > 0.059]
+
+ 6402it [00:31, 205.76it/s, bound: 836 | nc: 5 | ncall: 32950 | eff(%): 19.429 | loglstar: -inf < 11.893 < inf | logz: -119.006 +/- 1.419 | dlogz: 37.513 > 0.059]
+
+ 6423it [00:31, 199.62it/s, bound: 839 | nc: 5 | ncall: 33055 | eff(%): 19.431 | loglstar: -inf < 15.607 < inf | logz: -115.545 +/- 1.424 | dlogz: 43.839 > 0.059]
+
+ 6444it [00:31, 201.83it/s, bound: 842 | nc: 5 | ncall: 33160 | eff(%): 19.433 | loglstar: -inf < 21.194 < inf | logz: -111.096 +/- 1.431 | dlogz: 39.186 > 0.059]
+
+ 6466it [00:31, 206.94it/s, bound: 844 | nc: 5 | ncall: 33270 | eff(%): 19.435 | loglstar: -inf < 26.036 < inf | logz: -105.937 +/- 1.431 | dlogz: 33.380 > 0.059]
+
+ 6487it [00:31, 206.96it/s, bound: 847 | nc: 5 | ncall: 33375 | eff(%): 19.437 | loglstar: -inf < 33.854 < inf | logz: -99.073 +/- 1.438 | dlogz: 34.651 > 0.059]
+
+ 6509it [00:32, 209.51it/s, bound: 850 | nc: 5 | ncall: 33485 | eff(%): 19.439 | loglstar: -inf < 39.978 < inf | logz: -92.981 +/- 1.440 | dlogz: 35.337 > 0.059]
+
+ 6532it [00:32, 215.23it/s, bound: 853 | nc: 5 | ncall: 33600 | eff(%): 19.440 | loglstar: -inf < 45.423 < inf | logz: -88.198 +/- 1.443 | dlogz: 31.148 > 0.059]
+
+ 6554it [00:32, 208.95it/s, bound: 856 | nc: 5 | ncall: 33710 | eff(%): 19.442 | loglstar: -inf < 48.905 < inf | logz: -84.626 +/- 1.440 | dlogz: 29.968 > 0.059]
+
+ 6577it [00:32, 214.90it/s, bound: 858 | nc: 5 | ncall: 33825 | eff(%): 19.444 | loglstar: -inf < 54.140 < inf | logz: -80.586 +/- 1.448 | dlogz: 35.359 > 0.059]
+
+ 6599it [00:32, 211.08it/s, bound: 861 | nc: 5 | ncall: 33935 | eff(%): 19.446 | loglstar: -inf < 58.880 < inf | logz: -75.889 +/- 1.450 | dlogz: 30.152 > 0.059]
+
+ 6621it [00:32, 209.28it/s, bound: 864 | nc: 5 | ncall: 34045 | eff(%): 19.448 | loglstar: -inf < 62.936 < inf | logz: -72.279 +/- 1.452 | dlogz: 26.090 > 0.059]
+
+ 6642it [00:32, 202.11it/s, bound: 867 | nc: 5 | ncall: 34150 | eff(%): 19.449 | loglstar: -inf < 67.089 < inf | logz: -68.622 +/- 1.455 | dlogz: 31.986 > 0.059]
+
+ 6664it [00:32, 205.87it/s, bound: 870 | nc: 5 | ncall: 34260 | eff(%): 19.451 | loglstar: -inf < 72.067 < inf | logz: -64.768 +/- 1.459 | dlogz: 27.857 > 0.059]
+
+ 6686it [00:32, 207.65it/s, bound: 873 | nc: 5 | ncall: 34370 | eff(%): 19.453 | loglstar: -inf < 74.062 < inf | logz: -61.687 +/- 1.458 | dlogz: 24.074 > 0.059]
+
+ 6707it [00:32, 206.02it/s, bound: 876 | nc: 5 | ncall: 34475 | eff(%): 19.455 | loglstar: -inf < 77.417 < inf | logz: -59.009 +/- 1.461 | dlogz: 21.008 > 0.059]
+
+ 6728it [00:33, 190.66it/s, bound: 879 | nc: 5 | ncall: 34580 | eff(%): 19.456 | loglstar: -inf < 80.386 < inf | logz: -56.140 +/- 1.463 | dlogz: 17.682 > 0.059]
+
+ 6748it [00:33, 191.22it/s, bound: 881 | nc: 5 | ncall: 34680 | eff(%): 19.458 | loglstar: -inf < 82.976 < inf | logz: -54.602 +/- 1.464 | dlogz: 20.369 > 0.059]
+
+ 6768it [00:33, 190.37it/s, bound: 885 | nc: 5 | ncall: 34780 | eff(%): 19.459 | loglstar: -inf < 87.917 < inf | logz: -50.536 +/- 1.473 | dlogz: 20.620 > 0.059]
+
+ 6790it [00:33, 197.11it/s, bound: 888 | nc: 5 | ncall: 34890 | eff(%): 19.461 | loglstar: -inf < 90.094 < inf | logz: -47.914 +/- 1.471 | dlogz: 20.682 > 0.059]
+
+ 6812it [00:33, 201.59it/s, bound: 891 | nc: 5 | ncall: 35000 | eff(%): 19.463 | loglstar: -inf < 93.103 < inf | logz: -45.626 +/- 1.475 | dlogz: 17.999 > 0.059]
+
+ 6833it [00:33, 197.58it/s, bound: 894 | nc: 5 | ncall: 35105 | eff(%): 19.464 | loglstar: -inf < 96.306 < inf | logz: -42.407 +/- 1.479 | dlogz: 14.301 > 0.059]
+
+ 6854it [00:33, 198.90it/s, bound: 897 | nc: 5 | ncall: 35210 | eff(%): 19.466 | loglstar: -inf < 101.004 < inf | logz: -39.057 +/- 1.484 | dlogz: 14.613 > 0.059]
+
+ 6874it [00:33, 198.72it/s, bound: 899 | nc: 5 | ncall: 35310 | eff(%): 19.468 | loglstar: -inf < 102.224 < inf | logz: -36.830 +/- 1.482 | dlogz: 13.025 > 0.059]
+
+ 6895it [00:33, 196.53it/s, bound: 902 | nc: 5 | ncall: 35415 | eff(%): 19.469 | loglstar: -inf < 105.375 < inf | logz: -34.822 +/- 1.484 | dlogz: 10.648 > 0.059]
+
+ 6915it [00:34, 190.06it/s, bound: 905 | nc: 5 | ncall: 35515 | eff(%): 19.471 | loglstar: -inf < 106.720 < inf | logz: -33.407 +/- 1.484 | dlogz: 10.684 > 0.059]
+
+ 6935it [00:34, 175.97it/s, bound: 907 | nc: 5 | ncall: 35615 | eff(%): 19.472 | loglstar: -inf < 108.175 < inf | logz: -32.152 +/- 1.486 | dlogz: 9.009 > 0.059]
+
+ 6954it [00:34, 177.83it/s, bound: 909 | nc: 5 | ncall: 35710 | eff(%): 19.474 | loglstar: -inf < 108.880 < inf | logz: -31.331 +/- 1.486 | dlogz: 10.241 > 0.059]
+
+ 6974it [00:34, 182.37it/s, bound: 912 | nc: 5 | ncall: 35810 | eff(%): 19.475 | loglstar: -inf < 110.361 < inf | logz: -30.617 +/- 1.487 | dlogz: 9.145 > 0.059]
+
+ 6994it [00:34, 185.79it/s, bound: 915 | nc: 5 | ncall: 35910 | eff(%): 19.476 | loglstar: -inf < 112.202 < inf | logz: -29.377 +/- 1.493 | dlogz: 7.514 > 0.059]
+
+ 7015it [00:34, 191.58it/s, bound: 917 | nc: 5 | ncall: 36015 | eff(%): 19.478 | loglstar: -inf < 113.573 < inf | logz: -28.276 +/- 1.495 | dlogz: 5.987 > 0.059]
+
+ 7036it [00:34, 195.20it/s, bound: 920 | nc: 5 | ncall: 36120 | eff(%): 19.480 | loglstar: -inf < 114.236 < inf | logz: -27.523 +/- 1.495 | dlogz: 5.312 > 0.059]
+
+ 7056it [00:34, 195.37it/s, bound: 923 | nc: 5 | ncall: 36220 | eff(%): 19.481 | loglstar: -inf < 114.818 < inf | logz: -27.045 +/- 1.496 | dlogz: 4.750 > 0.059]
+
+ 7078it [00:34, 202.54it/s, bound: 925 | nc: 5 | ncall: 36330 | eff(%): 19.483 | loglstar: -inf < 115.740 < inf | logz: -26.594 +/- 1.497 | dlogz: 5.688 > 0.059]
+
+ 7099it [00:35, 204.72it/s, bound: 928 | nc: 5 | ncall: 36435 | eff(%): 19.484 | loglstar: -inf < 116.330 < inf | logz: -26.170 +/- 1.498 | dlogz: 5.619 > 0.059]
+
+ 7120it [00:35, 204.46it/s, bound: 931 | nc: 5 | ncall: 36540 | eff(%): 19.485 | loglstar: -inf < 117.045 < inf | logz: -25.799 +/- 1.500 | dlogz: 4.824 > 0.059]
+
+ 7142it [00:35, 206.87it/s, bound: 933 | nc: 5 | ncall: 36650 | eff(%): 19.487 | loglstar: -inf < 117.506 < inf | logz: -25.487 +/- 1.501 | dlogz: 4.117 > 0.059]
+
+ 7163it [00:35, 204.60it/s, bound: 936 | nc: 5 | ncall: 36755 | eff(%): 19.489 | loglstar: -inf < 118.094 < inf | logz: -25.230 +/- 1.502 | dlogz: 3.455 > 0.059]
+
+ 7185it [00:35, 207.08it/s, bound: 939 | nc: 5 | ncall: 36865 | eff(%): 19.490 | loglstar: -inf < 118.597 < inf | logz: -25.010 +/- 1.503 | dlogz: 3.167 > 0.059]
+
+ 7206it [00:35, 207.52it/s, bound: 942 | nc: 5 | ncall: 36970 | eff(%): 19.491 | loglstar: -inf < 119.511 < inf | logz: -24.777 +/- 1.505 | dlogz: 2.555 > 0.059]
+
+ 7227it [00:35, 203.84it/s, bound: 944 | nc: 5 | ncall: 37075 | eff(%): 19.493 | loglstar: -inf < 120.003 < inf | logz: -24.550 +/- 1.507 | dlogz: 2.373 > 0.059]
+
+ 7249it [00:35, 207.05it/s, bound: 947 | nc: 5 | ncall: 37185 | eff(%): 19.494 | loglstar: -inf < 120.543 < inf | logz: -24.336 +/- 1.508 | dlogz: 3.048 > 0.059]
+
+ 7270it [00:35, 203.42it/s, bound: 950 | nc: 5 | ncall: 37290 | eff(%): 19.496 | loglstar: -inf < 121.164 < inf | logz: -24.127 +/- 1.510 | dlogz: 2.462 > 0.059]
+
+ 7292it [00:35, 207.87it/s, bound: 952 | nc: 5 | ncall: 37400 | eff(%): 19.497 | loglstar: -inf < 121.489 < inf | logz: -23.951 +/- 1.512 | dlogz: 2.408 > 0.059]
+
+ 7315it [00:36, 213.78it/s, bound: 955 | nc: 5 | ncall: 37515 | eff(%): 19.499 | loglstar: -inf < 121.775 < inf | logz: -23.810 +/- 1.513 | dlogz: 1.873 > 0.059]
+
+ 7338it [00:36, 216.61it/s, bound: 958 | nc: 5 | ncall: 37630 | eff(%): 19.500 | loglstar: -inf < 122.137 < inf | logz: -23.706 +/- 1.514 | dlogz: 1.951 > 0.059]
+
+ 7360it [00:36, 211.50it/s, bound: 961 | nc: 5 | ncall: 37740 | eff(%): 19.502 | loglstar: -inf < 122.558 < inf | logz: -23.619 +/- 1.515 | dlogz: 5.117 > 0.059]
+
+ 7382it [00:36, 186.66it/s, bound: 964 | nc: 5 | ncall: 37850 | eff(%): 19.503 | loglstar: -inf < 122.884 < inf | logz: -23.540 +/- 1.516 | dlogz: 7.240 > 0.059]
+
+ 7402it [00:36, 187.55it/s, bound: 967 | nc: 5 | ncall: 37950 | eff(%): 19.505 | loglstar: -inf < 123.581 < inf | logz: -23.473 +/- 1.517 | dlogz: 9.735 > 0.059]
+
+ 7422it [00:36, 190.37it/s, bound: 970 | nc: 5 | ncall: 38050 | eff(%): 19.506 | loglstar: -inf < 124.263 < inf | logz: -23.388 +/- 1.518 | dlogz: 9.252 > 0.059]
+
+ 7446it [00:36, 202.35it/s, bound: 973 | nc: 5 | ncall: 38170 | eff(%): 19.507 | loglstar: -inf < 125.337 < inf | logz: -23.249 +/- 1.521 | dlogz: 16.149 > 0.059]
+
+ 7467it [00:36, 198.60it/s, bound: 976 | nc: 5 | ncall: 38275 | eff(%): 19.509 | loglstar: -inf < 127.708 < inf | logz: -22.804 +/- 1.532 | dlogz: 15.310 > 0.059]
+
+ 7488it [00:36, 200.45it/s, bound: 980 | nc: 5 | ncall: 38380 | eff(%): 19.510 | loglstar: -inf < 130.188 < inf | logz: -21.826 +/- 1.548 | dlogz: 13.967 > 0.059]
+
+ 7509it [00:37, 201.13it/s, bound: 984 | nc: 5 | ncall: 38485 | eff(%): 19.511 | loglstar: -inf < 132.042 < inf | logz: -20.064 +/- 1.562 | dlogz: 11.773 > 0.059]
+
+ 7530it [00:37, 203.12it/s, bound: 987 | nc: 5 | ncall: 38590 | eff(%): 19.513 | loglstar: -inf < 133.550 < inf | logz: -18.624 +/- 1.565 | dlogz: 12.998 > 0.059]
+
+ 7552it [00:37, 205.13it/s, bound: 989 | nc: 5 | ncall: 38700 | eff(%): 19.514 | loglstar: -inf < 135.517 < inf | logz: -17.458 +/- 1.566 | dlogz: 11.397 > 0.059]
+
+ 7575it [00:37, 211.89it/s, bound: 992 | nc: 5 | ncall: 38815 | eff(%): 19.516 | loglstar: -inf < 137.749 < inf | logz: -15.521 +/- 1.571 | dlogz: 10.384 > 0.059]
+
+ 7598it [00:37, 214.86it/s, bound: 995 | nc: 5 | ncall: 38930 | eff(%): 19.517 | loglstar: -inf < 139.500 < inf | logz: -14.102 +/- 1.573 | dlogz: 8.499 > 0.059]
+
+ 7623it [00:37, 223.02it/s, bound: 998 | nc: 5 | ncall: 39055 | eff(%): 19.519 | loglstar: -inf < 141.088 < inf | logz: -12.828 +/- 1.574 | dlogz: 9.913 > 0.059]
+
+ 7646it [00:37, 222.73it/s, bound: 1001 | nc: 5 | ncall: 39170 | eff(%): 19.520 | loglstar: -inf < 142.941 < inf | logz: -11.730 +/- 1.577 | dlogz: 8.372 > 0.059]
+
+ 7671it [00:37, 228.74it/s, bound: 1004 | nc: 5 | ncall: 39295 | eff(%): 19.522 | loglstar: -inf < 143.947 < inf | logz: -10.633 +/- 1.579 | dlogz: 6.752 > 0.059]
+
+ 7695it [00:37, 231.26it/s, bound: 1008 | nc: 5 | ncall: 39415 | eff(%): 19.523 | loglstar: -inf < 145.572 < inf | logz: -9.609 +/- 1.582 | dlogz: 5.261 > 0.059]
+
+ 7719it [00:37, 227.73it/s, bound: 1011 | nc: 5 | ncall: 39535 | eff(%): 19.524 | loglstar: -inf < 146.408 < inf | logz: -8.914 +/- 1.583 | dlogz: 5.130 > 0.059]
+
+ 7742it [00:38, 225.64it/s, bound: 1014 | nc: 5 | ncall: 39650 | eff(%): 19.526 | loglstar: -inf < 146.968 < inf | logz: -8.398 +/- 1.584 | dlogz: 4.155 > 0.059]
+
+ 7765it [00:38, 210.86it/s, bound: 1017 | nc: 5 | ncall: 39765 | eff(%): 19.527 | loglstar: -inf < 147.337 < inf | logz: -8.060 +/- 1.584 | dlogz: 3.365 > 0.059]
+
+ 7787it [00:38, 208.79it/s, bound: 1020 | nc: 5 | ncall: 39875 | eff(%): 19.529 | loglstar: -inf < 148.322 < inf | logz: -7.788 +/- 1.585 | dlogz: 3.161 > 0.059]
+
+ 7809it [00:38, 210.96it/s, bound: 1023 | nc: 5 | ncall: 39985 | eff(%): 19.530 | loglstar: -inf < 148.824 < inf | logz: -7.478 +/- 1.586 | dlogz: 5.166 > 0.059]
+
+ 7831it [00:38, 210.81it/s, bound: 1026 | nc: 5 | ncall: 40095 | eff(%): 19.531 | loglstar: -inf < 149.500 < inf | logz: -7.219 +/- 1.588 | dlogz: 4.476 > 0.059]
+
+ 7853it [00:38, 212.26it/s, bound: 1029 | nc: 5 | ncall: 40205 | eff(%): 19.532 | loglstar: -inf < 150.158 < inf | logz: -6.977 +/- 1.589 | dlogz: 3.899 > 0.059]
+
+ 7875it [00:38, 210.53it/s, bound: 1032 | nc: 5 | ncall: 40315 | eff(%): 19.534 | loglstar: -inf < 150.906 < inf | logz: -6.736 +/- 1.591 | dlogz: 3.239 > 0.059]
+
+ 7897it [00:38, 206.26it/s, bound: 1035 | nc: 5 | ncall: 40425 | eff(%): 19.535 | loglstar: -inf < 151.216 < inf | logz: -6.523 +/- 1.593 | dlogz: 3.947 > 0.059]
+
+ 7920it [00:38, 211.02it/s, bound: 1039 | nc: 5 | ncall: 40540 | eff(%): 19.536 | loglstar: -inf < 151.732 < inf | logz: -6.350 +/- 1.594 | dlogz: 3.487 > 0.059]
+
+ 7943it [00:39, 215.26it/s, bound: 1042 | nc: 5 | ncall: 40655 | eff(%): 19.538 | loglstar: -inf < 152.567 < inf | logz: -6.165 +/- 1.596 | dlogz: 2.869 > 0.059]
+
+ 7965it [00:39, 214.83it/s, bound: 1045 | nc: 5 | ncall: 40765 | eff(%): 19.539 | loglstar: -inf < 153.643 < inf | logz: -5.921 +/- 1.599 | dlogz: 2.245 > 0.059]
+
+ 7987it [00:39, 209.51it/s, bound: 1047 | nc: 5 | ncall: 40875 | eff(%): 19.540 | loglstar: -inf < 154.536 < inf | logz: -5.603 +/- 1.603 | dlogz: 5.520 > 0.059]
+
+ 8009it [00:39, 211.50it/s, bound: 1050 | nc: 5 | ncall: 40985 | eff(%): 19.541 | loglstar: -inf < 155.198 < inf | logz: -5.300 +/- 1.606 | dlogz: 5.040 > 0.059]
+
+ 8031it [00:39, 202.57it/s, bound: 1053 | nc: 5 | ncall: 41095 | eff(%): 19.543 | loglstar: -inf < 155.547 < inf | logz: -5.024 +/- 1.609 | dlogz: 4.322 > 0.059]
+
+ 8052it [00:39, 200.41it/s, bound: 1057 | nc: 5 | ncall: 41200 | eff(%): 19.544 | loglstar: -inf < 156.025 < inf | logz: -4.841 +/- 1.610 | dlogz: 4.209 > 0.059]
+
+ 8073it [00:39, 201.59it/s, bound: 1061 | nc: 5 | ncall: 41305 | eff(%): 19.545 | loglstar: -inf < 156.992 < inf | logz: -4.638 +/- 1.612 | dlogz: 4.322 > 0.059]
+
+ 8094it [00:39, 199.78it/s, bound: 1063 | nc: 5 | ncall: 41410 | eff(%): 19.546 | loglstar: -inf < 157.935 < inf | logz: -4.339 +/- 1.616 | dlogz: 4.227 > 0.059]
+
+ 8117it [00:39, 208.30it/s, bound: 1066 | nc: 5 | ncall: 41525 | eff(%): 19.547 | loglstar: -inf < 159.200 < inf | logz: -3.880 +/- 1.622 | dlogz: 5.612 > 0.059]
+
+ 8140it [00:40, 214.47it/s, bound: 1069 | nc: 5 | ncall: 41640 | eff(%): 19.549 | loglstar: -inf < 159.763 < inf | logz: -3.464 +/- 1.626 | dlogz: 4.736 > 0.059]
+
+ 8164it [00:40, 221.50it/s, bound: 1073 | nc: 5 | ncall: 41760 | eff(%): 19.550 | loglstar: -inf < 160.551 < inf | logz: -3.143 +/- 1.628 | dlogz: 3.940 > 0.059]
+
+ 8187it [00:40, 222.55it/s, bound: 1076 | nc: 5 | ncall: 41875 | eff(%): 19.551 | loglstar: -inf < 161.081 < inf | logz: -2.836 +/- 1.631 | dlogz: 6.148 > 0.059]
+
+ 8210it [00:40, 216.62it/s, bound: 1079 | nc: 5 | ncall: 41990 | eff(%): 19.552 | loglstar: -inf < 161.622 < inf | logz: -2.598 +/- 1.632 | dlogz: 5.439 > 0.059]
+
+ 8233it [00:40, 217.85it/s, bound: 1082 | nc: 5 | ncall: 42105 | eff(%): 19.553 | loglstar: -inf < 162.034 < inf | logz: -2.404 +/- 1.634 | dlogz: 4.785 > 0.059]
+
+ 8255it [00:40, 216.83it/s, bound: 1085 | nc: 5 | ncall: 42215 | eff(%): 19.555 | loglstar: -inf < 162.941 < inf | logz: -2.222 +/- 1.635 | dlogz: 4.171 > 0.059]
+
+ 8277it [00:40, 216.18it/s, bound: 1088 | nc: 5 | ncall: 42325 | eff(%): 19.556 | loglstar: -inf < 163.790 < inf | logz: -1.972 +/- 1.638 | dlogz: 5.531 > 0.059]
+
+ 8300it [00:40, 218.86it/s, bound: 1091 | nc: 5 | ncall: 42440 | eff(%): 19.557 | loglstar: -inf < 165.113 < inf | logz: -1.530 +/- 1.644 | dlogz: 5.437 > 0.059]
+
+ 8323it [00:40, 221.25it/s, bound: 1094 | nc: 5 | ncall: 42555 | eff(%): 19.558 | loglstar: -inf < 165.776 < inf | logz: -1.150 +/- 1.647 | dlogz: 4.600 > 0.059]
+
+ 8346it [00:40, 223.23it/s, bound: 1097 | nc: 5 | ncall: 42670 | eff(%): 19.559 | loglstar: -inf < 166.286 < inf | logz: -0.851 +/- 1.650 | dlogz: 3.848 > 0.059]
+
+ 8369it [00:41, 220.13it/s, bound: 1100 | nc: 5 | ncall: 42785 | eff(%): 19.561 | loglstar: -inf < 166.891 < inf | logz: -0.590 +/- 1.652 | dlogz: 3.323 > 0.059]
+
+ 8392it [00:41, 220.00it/s, bound: 1103 | nc: 5 | ncall: 42900 | eff(%): 19.562 | loglstar: -inf < 167.684 < inf | logz: -0.318 +/- 1.654 | dlogz: 3.061 > 0.059]
+
+ 8415it [00:41, 218.89it/s, bound: 1106 | nc: 5 | ncall: 43015 | eff(%): 19.563 | loglstar: -inf < 167.931 < inf | logz: -0.107 +/- 1.656 | dlogz: 5.397 > 0.059]
+
+ 8437it [00:41, 215.25it/s, bound: 1108 | nc: 5 | ncall: 43125 | eff(%): 19.564 | loglstar: -inf < 168.492 < inf | logz: 0.064 +/- 1.657 | dlogz: 4.790 > 0.059]
+
+ 8459it [00:41, 210.09it/s, bound: 1111 | nc: 5 | ncall: 43235 | eff(%): 19.565 | loglstar: -inf < 169.331 < inf | logz: 0.230 +/- 1.659 | dlogz: 4.252 > 0.059]
+
+ 8481it [00:41, 212.42it/s, bound: 1114 | nc: 5 | ncall: 43345 | eff(%): 19.566 | loglstar: -inf < 170.287 < inf | logz: 0.452 +/- 1.661 | dlogz: 3.606 > 0.059]
+
+ 8503it [00:41, 197.05it/s, bound: 1117 | nc: 5 | ncall: 43455 | eff(%): 19.567 | loglstar: -inf < 171.000 < inf | logz: 0.712 +/- 1.664 | dlogz: 2.936 > 0.059]
+
+ 8523it [00:41, 176.36it/s, bound: 1121 | nc: 5 | ncall: 43555 | eff(%): 19.568 | loglstar: -inf < 171.223 < inf | logz: 0.911 +/- 1.667 | dlogz: 2.550 > 0.059]
+
+ 8542it [00:41, 168.25it/s, bound: 1123 | nc: 5 | ncall: 43650 | eff(%): 19.569 | loglstar: -inf < 171.765 < inf | logz: 1.060 +/- 1.668 | dlogz: 2.075 > 0.059]
+
+ 8560it [00:42, 160.79it/s, bound: 1125 | nc: 5 | ncall: 43740 | eff(%): 19.570 | loglstar: -inf < 172.030 < inf | logz: 1.191 +/- 1.669 | dlogz: 1.654 > 0.059]
+
+ 8577it [00:42, 152.19it/s, bound: 1127 | nc: 5 | ncall: 43825 | eff(%): 19.571 | loglstar: -inf < 172.623 < inf | logz: 1.310 +/- 1.671 | dlogz: 1.537 > 0.059]
+
+ 8593it [00:42, 138.09it/s, bound: 1129 | nc: 5 | ncall: 43905 | eff(%): 19.572 | loglstar: -inf < 173.279 < inf | logz: 1.454 +/- 1.673 | dlogz: 2.248 > 0.059]
+
+ 8608it [00:42, 135.57it/s, bound: 1131 | nc: 5 | ncall: 43980 | eff(%): 19.573 | loglstar: -inf < 173.624 < inf | logz: 1.584 +/- 1.674 | dlogz: 1.873 > 0.059]
+
+ 8624it [00:42, 141.06it/s, bound: 1134 | nc: 5 | ncall: 44060 | eff(%): 19.573 | loglstar: -inf < 173.829 < inf | logz: 1.704 +/- 1.676 | dlogz: 1.512 > 0.059]
+
+ 8639it [00:42, 139.40it/s, bound: 1135 | nc: 5 | ncall: 44135 | eff(%): 19.574 | loglstar: -inf < 174.123 < inf | logz: 1.796 +/- 1.677 | dlogz: 1.222 > 0.059]
+
+ 8655it [00:42, 144.65it/s, bound: 1138 | nc: 5 | ncall: 44215 | eff(%): 19.575 | loglstar: -inf < 174.300 < inf | logz: 1.885 +/- 1.678 | dlogz: 1.735 > 0.059]
+
+ 8673it [00:42, 154.16it/s, bound: 1140 | nc: 5 | ncall: 44305 | eff(%): 19.576 | loglstar: -inf < 174.391 < inf | logz: 1.958 +/- 1.679 | dlogz: 1.389 > 0.059]
+
+ 8689it [00:43, 155.15it/s, bound: 1142 | nc: 5 | ncall: 44385 | eff(%): 19.576 | loglstar: -inf < 174.587 < inf | logz: 2.006 +/- 1.679 | dlogz: 1.188 > 0.059]
+
+ 8708it [00:43, 163.38it/s, bound: 1144 | nc: 5 | ncall: 44480 | eff(%): 19.577 | loglstar: -inf < 174.810 < inf | logz: 2.056 +/- 1.680 | dlogz: 0.906 > 0.059]
+
+ 8728it [00:43, 172.50it/s, bound: 1147 | nc: 5 | ncall: 44580 | eff(%): 19.578 | loglstar: -inf < 175.026 < inf | logz: 2.096 +/- 1.680 | dlogz: 1.178 > 0.059]
+
+ 8746it [00:43, 170.07it/s, bound: 1149 | nc: 5 | ncall: 44670 | eff(%): 19.579 | loglstar: -inf < 175.322 < inf | logz: 2.128 +/- 1.681 | dlogz: 0.924 > 0.059]
+
+ 8766it [00:43, 178.00it/s, bound: 1151 | nc: 5 | ncall: 44770 | eff(%): 19.580 | loglstar: -inf < 175.493 < inf | logz: 2.157 +/- 1.681 | dlogz: 0.804 > 0.059]
+
+ 8790it [00:43, 194.74it/s, bound: 1154 | nc: 5 | ncall: 44890 | eff(%): 19.581 | loglstar: -inf < 175.901 < inf | logz: 2.187 +/- 1.681 | dlogz: 0.697 > 0.059]
+
+ 8813it [00:43, 203.91it/s, bound: 1157 | nc: 5 | ncall: 45005 | eff(%): 19.582 | loglstar: -inf < 176.377 < inf | logz: 2.214 +/- 1.682 | dlogz: 0.482 > 0.059]
+
+ 8834it [00:43, 200.92it/s, bound: 1160 | nc: 5 | ncall: 45110 | eff(%): 19.583 | loglstar: -inf < 176.705 < inf | logz: 2.238 +/- 1.682 | dlogz: 0.335 > 0.059]
+
+ 8857it [00:43, 208.42it/s, bound: 1163 | nc: 5 | ncall: 45225 | eff(%): 19.584 | loglstar: -inf < 177.199 < inf | logz: 2.262 +/- 1.683 | dlogz: 0.400 > 0.059]
+
+ 8880it [00:43, 211.64it/s, bound: 1167 | nc: 5 | ncall: 45340 | eff(%): 19.585 | loglstar: -inf < 177.430 < inf | logz: 2.283 +/- 1.683 | dlogz: 0.264 > 0.059]
+
+ 8902it [00:44, 208.82it/s, bound: 1170 | nc: 5 | ncall: 45450 | eff(%): 19.586 | loglstar: -inf < 177.766 < inf | logz: 2.299 +/- 1.684 | dlogz: 0.175 > 0.059]
+
+ 8926it [00:44, 215.99it/s, bound: 1173 | nc: 5 | ncall: 45570 | eff(%): 19.587 | loglstar: -inf < 178.046 < inf | logz: 2.314 +/- 1.684 | dlogz: 0.134 > 0.059]
+
+ 8948it [00:44, 190.86it/s, bound: 1176 | nc: 5 | ncall: 45680 | eff(%): 19.588 | loglstar: -inf < 178.219 < inf | logz: 2.325 +/- 1.684 | dlogz: 0.175 > 0.059]
+
+ 8968it [00:44, 177.14it/s, bound: 1179 | nc: 5 | ncall: 45780 | eff(%): 19.589 | loglstar: -inf < 178.551 < inf | logz: 2.333 +/- 1.685 | dlogz: 0.140 > 0.059]
+
+ 8987it [00:44, 177.14it/s, bound: 1181 | nc: 5 | ncall: 45875 | eff(%): 19.590 | loglstar: -inf < 178.927 < inf | logz: 2.341 +/- 1.685 | dlogz: 0.097 > 0.059]
+
+ 9008it [00:44, 184.19it/s, bound: 1184 | nc: 5 | ncall: 45980 | eff(%): 19.591 | loglstar: -inf < 179.131 < inf | logz: 2.348 +/- 1.685 | dlogz: 0.110 > 0.059]
+
+ 9028it [00:44, 188.16it/s, bound: 1187 | nc: 5 | ncall: 46080 | eff(%): 19.592 | loglstar: -inf < 179.333 < inf | logz: 2.354 +/- 1.685 | dlogz: 0.074 > 0.059]
+
+ 9039it [00:44, 201.70it/s, +50 | bound: 1188 | nc: 1 | ncall: 46185 | eff(%): 19.701 | loglstar: -inf < 181.115 < inf | logz: 2.376 +/- 1.690 | dlogz: 0.001 > 0.059]
+
+
+
+
+ 2026-07-11 16:23:51,837 - autofit.non_linear.search.updater - INFO - Creating latent samples by drawing 100 from the PDF.
+
+
+ 2026-07-11 16:23:52,602 - root - INFO - Removing search internal folder.
+
+
+ 2026-07-11 16:23:52,655 - root - INFO - Search complete, returning result
+
+
+ The search has finished run - you may now continue the notebook.
+
+
+__Result__
+
+The `info` attribute shows the result in a readable format, which contains information on the full collection
+of all 5 model components.
+
+
+```python
+print(result.info)
+```
+
+ Bayesian Evidence 2.37555245
+ Maximum Log Likelihood 181.11496469
+
+ model Collection (N=15)
+ gaussian_0 - gaussian_4 Gaussian (N=3)
+
+ Maximum Log Likelihood Model:
+
+ gaussian_0
+ centre 49.879
+ ... [51 lines of output truncated] ...
+ centre 51.71 (41.37, 60.03)
+ normalization 0.00 (0.00, 0.00)
+ sigma 18.81 (16.40, 21.77)
+ gaussian_2
+ centre 50.00 (49.99, 50.00)
+ normalization 20.50 (20.32, 20.65)
+ sigma 1.01 (1.01, 1.02)
+ gaussian_3
+ centre 50.21 (50.11, 50.29)
+ normalization 125.71 (121.73, 129.59)
+ sigma 13.44 (13.28, 13.61)
+ gaussian_4
+ centre 49.96 (49.93, 50.00)
+ normalization 55.27 (54.46, 56.10)
+ sigma 5.58 (5.53, 5.62)
+
+ instances
+
+
+
+
+From the result info, it is hard to assess if the model fit was good or not.
+
+A good way to evaluate the fit is through a visual inspection of the model data plotted over the actual data.
+
+If the model data (red line) consistently aligns with the data points (black error bars), the fit is good.
+However, if the model misses certain features of the data, such as peaks or regions of high intensity,
+the fit was not successful.
+
+
+```python
+instance = result.max_log_likelihood_instance
+
+model_data_0 = instance.gaussian_0.model_data_from(xvalues=np.arange(data.shape[0]))
+model_data_1 = instance.gaussian_1.model_data_from(xvalues=np.arange(data.shape[0]))
+model_data_2 = instance.gaussian_2.model_data_from(xvalues=np.arange(data.shape[0]))
+model_data_3 = instance.gaussian_3.model_data_from(xvalues=np.arange(data.shape[0]))
+model_data_4 = instance.gaussian_4.model_data_from(xvalues=np.arange(data.shape[0]))
+
+model_data_list = [model_data_0, model_data_1, model_data_2, model_data_3, model_data_4]
+
+model_data = sum(model_data_list)
+
+plt.errorbar(
+ x=xvalues,
+ y=data,
+ yerr=noise_map,
+ linestyle="",
+ color="k",
+ ecolor="k",
+ elinewidth=1,
+ capsize=2,
+)
+plt.plot(range(data.shape[0]), model_data, color="r")
+for model_data_1d_individual in model_data_list:
+ plt.plot(range(data.shape[0]), model_data_1d_individual, "--")
+plt.title(f"Fit (log likelihood = {result.log_likelihood})")
+plt.xlabel("x values of profile")
+plt.ylabel("Profile normalization")
+plt.show()
+plt.close()
+```
+
+
+
+
+
+
+
+It's challenging to determine from the plot whether the data and model data perfectly overlap across the entire dataset.
+
+To clarify this, the residual map introduced in tutorial 2 is useful. It provides a clear representation of where
+the differences between the model and data exceed the noise level.
+
+Regions where the black error bars do not align with the zero line in the residual map indicate areas where the model
+did not fit the data well and is inconsistent with the data above the noise level. Furthermore, regions where
+larger values of residuals are next to one another indicate that the model failed to accurate fit that
+region of the data.
+
+
+```python
+residual_map = data - model_data
+plt.plot(range(data.shape[0]), np.zeros(data.shape[0]), "--", color="b")
+plt.errorbar(
+ x=xvalues,
+ y=residual_map,
+ yerr=noise_map,
+ color="k",
+ ecolor="k",
+ elinewidth=1,
+ capsize=2,
+ linestyle="",
+)
+plt.title(f"Residuals (log likelihood = {result.log_likelihood})")
+plt.xlabel("x values of profile")
+plt.ylabel("Residuals")
+plt.show()
+plt.clf()
+plt.close()
+```
+
+
+
+
+
+
+
+The normalized residual map, as discussed in tutorial 2, provides an alternative visualization of the fit quality.
+
+Normalized residuals indicate the standard deviation (σ) level at which the residuals could have been drawn from the
+noise. For instance, a normalized residual of 2.0 suggests that a residual value is 2.0σ away from the noise,
+implying there is a 5% chance such a residual would occur due to noise.
+
+Values of normalized residuals above 3.0 are particularly improbable (occurring only 0.3% of the time), which is
+generally considered a threshold where issues with the model-fit are likely the cause of the residual as opposed
+to it being a noise fluctuation.
+
+
+```python
+residual_map = data - model_data
+normalized_residual_map = residual_map / noise_map
+plt.plot(xvalues, normalized_residual_map, color="k")
+plt.title(f"Normalized Residuals (log likelihood = {result.log_likelihood})")
+plt.xlabel("x values of profile")
+plt.ylabel("Normalized Residuals ($\sigma$)")
+plt.show()
+plt.clf()
+plt.close()
+```
+
+ <>:6: SyntaxWarning: invalid escape sequence '\s'
+ <>:6: SyntaxWarning: invalid escape sequence '\s'
+ /tmp/ipykernel_20726/582017931.py:6: SyntaxWarning: invalid escape sequence '\s'
+ plt.ylabel("Normalized Residuals ($\sigma$)")
+
+
+
+
+
+
+
+
+So, did you achieve a good fit? Maybe a bad one? Or just an okay one?
+
+The truth is, I don't know, and I can't tell you for sure. Modeling is inherently random. It's not uncommon to
+fit the same model to the same dataset using the same non-linear search and get a different result each time.
+
+When I ran the model fit above, that's exactly what happened. It produced a range of fits: some bad, some okay, and
+some good, as shown in the images below:
+
+
+
+
+
+
+
+Distinguishing between the good and okay fit is difficult, however the normalized residuals make this easier. They show
+that for the okay fit there are residuals above 3.0 sigma, indicating that the model did not perfectly fit the data.
+
+