Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions docs/docs/tutorials/advancedfitting/bayesian_bumps.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -133,21 +133,25 @@
"\n",
"# ---- Make key parameters free with realistic bounds (essential for MCMC) -----\n",
"film_layer.thickness.fixed = False\n",
"film_layer.thickness.bounds = (100, 400)\n",
"film_layer.thickness.min = 100\n",
"film_layer.thickness.max = 400\n",
"\n",
"film.sld.fixed = False\n",
"film.sld.bounds = (0.5, 4.0)\n",
"film.sld.min = 0.5\n",
"film.sld.max = 4.0\n",
"\n",
"model.scale.fixed = False\n",
"model.scale.bounds = (0.8, 1.2)\n",
"model.scale.min = 0.8\n",
"model.scale.max = 1.2\n",
"\n",
"model.background.fixed = False\n",
"model.background.bounds = (1e-7, 1e-5)\n",
"model.background.min = 1e-7\n",
"model.background.max = 1e-5\n",
"\n",
"print('Model created with the following free parameters:')\n",
"for p in model.get_parameters():\n",
" if not p.fixed:\n",
" print(f' {p.name}: value={p.value}, bounds={p.bounds}')"
" print(f' {p.name}: value={p.value}, min={p.min}, max={p.max}')"
]
},
{
Expand Down
825 changes: 825 additions & 0 deletions docs/docs/tutorials/advancedfitting/constraints.ipynb

Large diffs are not rendered by default.

86 changes: 72 additions & 14 deletions docs/docs/tutorials/advancedfitting/multi_contrast.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,14 @@
"id": "694b4e5e-2d1a-402e-aa3f-a26cc82f7774",
"metadata": {},
"outputs": [],
"source": "file_path = pooch.retrieve(\n # Fetch test data from the easyscience/reflectometry data repository\n url='https://raw.githubusercontent.com/easyscience/reflectometry/master/data/multiple.ort',\n known_hash='241bcb819cdae47fbbb310a99c2456c7332312719496b936a153dc7dee83e62c',\n)\ndata = load(file_path)"
"source": [
"file_path = pooch.retrieve(\n",
" # Fetch test data from the easyscience/reflectometry data repository\n",
" url='https://raw.githubusercontent.com/easyscience/reflectometry/master/data/multiple.ort',\n",
" known_hash='241bcb819cdae47fbbb310a99c2456c7332312719496b936a153dc7dee83e62c',\n",
")\n",
"data = load(file_path)"
]
},
{
"cell_type": "markdown",
Expand Down Expand Up @@ -383,11 +390,52 @@
"cell_type": "markdown",
"id": "01abb1a2-1c77-4c59-b8ae-58fc29c00857",
"metadata": {},
"source": "Even through only as single value (that for the d13-DSPC head thickness) was changed, all three values changed. "
},
{
"cell_type": "markdown",
"id": "d375c89c",
"source": "### Custom constraints\n\nThe assembly methods used above cover the common chemical relationships, but arbitrary constraints between any two parameters are also possible with the `constrain`, `constrain_equal` and `unconstrain` helpers (see *Constraining Parameters* in the [model tutorial](../basic/model.md)). As a demonstration, we tie the head thickness of the base contrast to always be half the tail thickness. Since the other contrasts are chained to `d13d2o`, the constraint propagates to all of them. ",
"metadata": {}
},
{
"cell_type": "code",
"id": "041f4e4c",
"source": [
"Even through only as single value (that for the d13-DSPC head thickness) was changed, all three values changed. \n",
"from easyreflectometry import constrain # noqa: E402\n",
"from easyreflectometry import unconstrain # noqa: E402\n",
"\n",
"Having constructed each of the surfactant layer object and implemented the constraints, we can now build Samples and models. "
]
"constrain(d13d2o.head_layer.thickness, '0.5 * t', t=d13d2o.tail_layer.thickness)\n",
"d13d2o.tail_layer.thickness.value = 22\n",
"d13d2o.head_layer.thickness.value, d70d2o.head_layer.thickness.value, d83acmw.head_layer.thickness.value"
],
"metadata": {},
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"id": "d62ee7bd",
"source": "Changing the tail thickness now also updates every head thickness. Note that constraining overwrites the dependent parameter's value and bounds, and `unconstrain` does not restore them. We will not use this constraint in the fit below, so we remove it again and restore the original values before building the models. ",
"metadata": {}
},
{
"cell_type": "code",
"id": "1465c350",
"source": [
"unconstrain(d13d2o.head_layer.thickness)\n",
"d13d2o.tail_layer.thickness.value = 20\n",
"d13d2o.head_layer.thickness.value = 10"
],
"metadata": {},
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"id": "b7e83a2d",
"source": "Having constructed each of the surfactant layer object and implemented the constraints, we can now build Samples and models. ",
"metadata": {}
},
{
"cell_type": "code",
Expand Down Expand Up @@ -440,17 +488,27 @@
"metadata": {},
"outputs": [],
"source": [
"d13d2o_model.scale.bounds = (0.05, 1.5)\n",
"d13d2o_model.background.bounds = (4e-8, 1e-5)\n",
"d70d2o_model.scale.bounds = (0.05, 1.5)\n",
"d70d2o_model.background.bounds = (4e-8, 1e-5)\n",
"d83acmw_model.scale.bounds = (0.05, 1.5)\n",
"d83acmw_model.background.bounds = (4e-8, 1e-5)\n",
"d13d2o_model.scale.min = 0.05\n",
"d13d2o_model.scale.max = 1.5\n",
"d13d2o_model.background.min = 4e-8\n",
"d13d2o_model.background.max = 1e-5\n",
"d70d2o_model.scale.min = 0.05\n",
"d70d2o_model.scale.max = 1.5\n",
"d70d2o_model.background.min = 4e-8\n",
"d70d2o_model.background.max = 1e-5\n",
"d83acmw_model.scale.min = 0.05\n",
"d83acmw_model.scale.max = 1.5\n",
"d83acmw_model.background.min = 4e-8\n",
"d83acmw_model.background.max = 1e-5\n",
"\n",
"d13d2o.tail_layer.area_per_molecule_parameter.bounds = (40, 50)\n",
"d13d2o.head_layer.solvent_fraction_parameter.bounds = (0.2, 0.6)\n",
"d13d2o.tail_layer.thickness.bounds = (18, 24)\n",
"d13d2o.head_layer.thickness.bounds = (8, 12)"
"d13d2o.tail_layer.area_per_molecule_parameter.min = 40\n",
"d13d2o.tail_layer.area_per_molecule_parameter.max = 50\n",
"d13d2o.head_layer.solvent_fraction_parameter.min = 0.2\n",
"d13d2o.head_layer.solvent_fraction_parameter.max = 0.6\n",
"d13d2o.tail_layer.thickness.min = 18\n",
"d13d2o.tail_layer.thickness.max = 24\n",
"d13d2o.head_layer.thickness.min = 8\n",
"d13d2o.head_layer.thickness.max = 12"
]
},
{
Expand Down
9 changes: 6 additions & 3 deletions docs/docs/tutorials/advancedfitting/polarized_fitting.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,8 @@
"fit_model_2ch, fit_film_2ch = build_magnetic_model(rho_m=1.0, theta_m=THETA_M_TRUE, name='Fit: two channels (rho_m only)')\n",
"\n",
"fit_film_2ch.magnetism.rho_m.fixed = False\n",
"fit_film_2ch.magnetism.rho_m.bounds = (0.0, 5.0)\n",
"fit_film_2ch.magnetism.rho_m.min = 0.0\n",
"fit_film_2ch.magnetism.rho_m.max = 5.0\n",
"fit_film_2ch.magnetism.theta_m.fixed = True # moment direction assumed known\n",
"\n",
"initial_channels_2ch = fit_model_2ch.interface.polarized_reflectivity_profiles(Q_PLOT, fit_model_2ch.unique_name)\n",
Expand Down Expand Up @@ -469,9 +470,11 @@
"fit_model_4ch, fit_film_4ch = build_magnetic_model(rho_m=1.5, theta_m=60.0, name='Fit: four channels (rho_m and theta_m)')\n",
"\n",
"fit_film_4ch.magnetism.rho_m.fixed = False\n",
"fit_film_4ch.magnetism.rho_m.bounds = (0.0, 5.0)\n",
"fit_film_4ch.magnetism.rho_m.min = 0.0\n",
"fit_film_4ch.magnetism.rho_m.max = 5.0\n",
"fit_film_4ch.magnetism.theta_m.fixed = False\n",
"fit_film_4ch.magnetism.theta_m.bounds = (0.0, 90.0)\n",
"fit_film_4ch.magnetism.theta_m.min = 0.0\n",
"fit_film_4ch.magnetism.theta_m.max = 90.0\n",
"\n",
"fit_data_4ch = PolarizedDataSet(\n",
" name='Fe film (pp, pm, mp, mm)',\n",
Expand Down
4 changes: 3 additions & 1 deletion docs/docs/tutorials/basic/assemblies_library.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ analysis by making chemical and physical constraints available with
limited code. In this page, we will document the assemblies that are
available with simple examples of the constructors that exist. Full API
documentation is also available for the
`easyreflectometry.sample.assemblies` module.
`easyreflectometry.sample.assemblies` module. For custom constraints
between arbitrary parameters, see _Constraining Parameters_ in the
[Model tutorial](model.md).

## Multilayer

Expand Down
91 changes: 91 additions & 0 deletions docs/docs/tutorials/basic/model.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,94 @@ This will create a `Model` instance where the resolution function
defining the FWHM is determined from a linear interpolation. In the
present case the provided data Q-points are (`[0.01, 0.2, 0.31]`) and
the corresponding FWHM function values are (`[0.001, 0.043, 0.026]`).

## Constraining Parameters

It is often physically motivated to reduce the number of free parameters
in a model by tying parameters together. For example, two layers that
were deposited in the same process step can be expected to have the same
thickness, or the roughness of every interface in a stack can be assumed
to be conformal. Assemblies such as `SurfactantLayer` and `Bilayer`
provide ready-made constraints for their specific chemistry
(`constrain_area_per_molecule`, `conformal_roughness`,
`constrain_multiple_contrast`), but any parameter in a model can be
constrained directly.

### Tying two parameters together

The most common constraint is a simple equality. Here the thickness of
`layer_2` is tied to the thickness of `layer_1`.

```python
from easyreflectometry import constrain_equal

constrain_equal(layer_2.thickness, to=layer_1.thickness)
```

After this call `layer_2.thickness` is no longer an independent
parameter: it immediately takes the value of `layer_1.thickness`,
follows it whenever it changes (including during fitting), and is
removed from the free fit parameters. Only `layer_1.thickness` is varied
by the minimizer.

### Functional constraints

Constraints are not limited to equality. An arbitrary mathematical
expression of one or more parameters can be used, where each placeholder
in the expression is supplied as a keyword argument.

```python
from easyreflectometry import constrain

# layer_2 is always twice as thick as layer_1
constrain(layer_2.thickness, '2 * t', t=layer_1.thickness)

# an SLD that is a fraction-weighted average of two materials
constrain(
mixed.sld,
'frac * a + (1 - frac) * b',
frac=fraction,
a=solvent.sld,
b=film.sld,
)
```

Note that constraining a parameter **overwrites its current value, unit
and bounds** with the evaluated expression, and clears its `fixed` flag.
While constrained, the parameter's value and bounds cannot be set
directly.

### Removing a constraint

```python
from easyreflectometry import unconstrain

unconstrain(layer_2.thickness)
```

The parameter keeps its last evaluated value and becomes an independent,
fittable parameter again. Calling `unconstrain` on a parameter that is
not constrained does nothing. The parameter's original bounds and
`fixed` state are **not** restored. They remain whatever the constraint
left behind, so review and reset the bounds before fitting.

### Things to be aware of

- Constraints are directional: the dependent parameter follows the
independent one, never the other way around. When chaining constraints
across several objects (as in the multiple-contrast tutorials), make
sure the chain has a single independent parameter at its root.
- Placeholder names in expressions must be valid Python identifiers and
not Python keywords. An unmapped name that happens to match a
mathematical builtin (`e`, `pi`, `sin`, ...) evaluates silently
instead of raising an error.
- If the model is already attached to a calculator, regenerate the
bindings after changing constraints so the calculator picks up the new
dependency graph:

```python
model.generate_bindings()
```

- Custom constraints are not yet preserved when a project is saved and
reloaded. Re-apply them after loading a project.
33 changes: 24 additions & 9 deletions docs/docs/tutorials/fitting/material_solvated.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,14 @@
"id": "a95a39dd-d0eb-4029-9dc8-41e6e7918f66",
"metadata": {},
"outputs": [],
"source": "file_path = pooch.retrieve(\n # Fetch test data from the easyscience/reflectometry data repository\n url='https://raw.githubusercontent.com/easyscience/reflectometry/master/data/example.ort',\n known_hash='82d0c95c069092279a799a8131ad3710335f601d9f1080754b387f42e407dfab',\n)\ndata = load(file_path)"
"source": [
"file_path = pooch.retrieve(\n",
" # Fetch test data from the easyscience/reflectometry data repository\n",
" url='https://raw.githubusercontent.com/easyscience/reflectometry/master/data/example.ort',\n",
" known_hash='82d0c95c069092279a799a8131ad3710335f601d9f1080754b387f42e407dfab',\n",
")\n",
"data = load(file_path)"
]
},
{
"cell_type": "markdown",
Expand Down Expand Up @@ -262,18 +269,26 @@
"outputs": [],
"source": [
"# Thicknesses\n",
"sio2_layer.thickness.bounds = (15, 50)\n",
"solvated_film.thickness.bounds = (200, 300)\n",
"sio2_layer.thickness.min = 15\n",
"sio2_layer.thickness.max = 50\n",
"solvated_film.thickness.min = 200\n",
"solvated_film.thickness.max = 300\n",
"# Roughnesses\n",
"sio2_layer.roughness.bounds = (1, 15)\n",
"solvated_film.roughness.bounds = (1, 15)\n",
"subphase.roughness.bounds = (1, 15)\n",
"sio2_layer.roughness.min = 1\n",
"sio2_layer.roughness.max = 15\n",
"solvated_film.roughness.min = 1\n",
"solvated_film.roughness.max = 15\n",
"subphase.roughness.min = 1\n",
"subphase.roughness.max = 15\n",
"# Scattering length density\n",
"film.sld.bounds = (0.1, 3)\n",
"film.sld.min = 0.1\n",
"film.sld.max = 3\n",
"# Background\n",
"model.background.bounds = (1e-8, 1e-5)\n",
"model.background.min = 1e-8\n",
"model.background.max = 1e-5\n",
"# Scale\n",
"model.scale.bounds = (0.5, 1.5)"
"model.scale.min = 0.5\n",
"model.scale.max = 1.5"
]
},
{
Expand Down
22 changes: 17 additions & 5 deletions docs/docs/tutorials/fitting/monolayer.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,15 @@
"id": "e392660e-6f02-4f0b-be86-4c8ea78883e0",
"metadata": {},
"outputs": [],
"source": "file_path = pooch.retrieve(\n # Fetch test data from the easyscience/reflectometry data repository\n url='https://raw.githubusercontent.com/easyscience/reflectometry/master/data/d70d2o.ort',\n known_hash='3e4750536621be8eec493fa21a287e408d384f29cacb113b71d02690d99f0998',\n)\ndata = load(file_path)\nplot(data)"
"source": [
"file_path = pooch.retrieve(\n",
" # Fetch test data from the easyscience/reflectometry data repository\n",
" url='https://raw.githubusercontent.com/easyscience/reflectometry/master/data/d70d2o.ort',\n",
" known_hash='3e4750536621be8eec493fa21a287e408d384f29cacb113b71d02690d99f0998',\n",
")\n",
"data = load(file_path)\n",
"plot(data)"
]
},
{
"cell_type": "markdown",
Expand Down Expand Up @@ -346,11 +354,15 @@
"metadata": {},
"outputs": [],
"source": [
"model.scale.bounds = (0.05, 1.5)\n",
"model.background.bounds = (4e-7, 1e-6)\n",
"model.scale.min = 0.05\n",
"model.scale.max = 1.5\n",
"model.background.min = 4e-7\n",
"model.background.max = 1e-6\n",
"\n",
"dspc.tail_layer.area_per_molecule_parameter.bounds = (30, 60)\n",
"dspc.head_layer.solvent_fraction_parameter.bounds = (0.4, 0.7)"
"dspc.tail_layer.area_per_molecule_parameter.min = 30\n",
"dspc.tail_layer.area_per_molecule_parameter.max = 60\n",
"dspc.head_layer.solvent_fraction_parameter.min = 0.4\n",
"dspc.head_layer.solvent_fraction_parameter.max = 0.7"
]
},
{
Expand Down
12 changes: 10 additions & 2 deletions docs/docs/tutorials/fitting/repeating.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,14 @@
"id": "7121c7e9",
"metadata": {},
"outputs": [],
"source": "file_path = pooch.retrieve(\n # Fetch test data from the easyscience/reflectometry data repository\n url='https://raw.githubusercontent.com/easyscience/reflectometry/master/data/repeating_layers.ort',\n known_hash='a5ffca9fd24f1d362266251723aec7ce9f34f123e39a38dfc4d829c758e6bf90',\n)\ndata = load(file_path)"
"source": [
"file_path = pooch.retrieve(\n",
" # Fetch test data from the easyscience/reflectometry data repository\n",
" url='https://raw.githubusercontent.com/easyscience/reflectometry/master/data/repeating_layers.ort',\n",
" known_hash='a5ffca9fd24f1d362266251723aec7ce9f34f123e39a38dfc4d829c758e6bf90',\n",
")\n",
"data = load(file_path)"
]
},
{
"cell_type": "markdown",
Expand Down Expand Up @@ -220,7 +227,8 @@
"metadata": {},
"outputs": [],
"source": [
"ti_layer.thickness.bounds = (10, 60)"
"ti_layer.thickness.min = 10\n",
"ti_layer.thickness.max = 60"
]
},
{
Expand Down
Loading
Loading