From 94970a9cc6c4058d59ae1721247e2e153683a079 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Mon, 14 Sep 2026 16:32:34 +0200 Subject: [PATCH 1/5] Support underscore-style CIF structure tags (#222) * Fix underscore-style CIF structure imports * Prevent races in shared data index cache --- pixi.lock | 1 + pyproject.toml | 1 + .../analysis/calculators/crysfml.py | 17 ++++---- .../categories/atom_sites/default.py | 21 +++++++--- src/easydiffraction/io/cif/handler.py | 16 ++++++-- src/easydiffraction/utils/utils.py | 30 ++++++++------ .../analysis/calculators/test_crysfml.py | 10 +++++ .../structure/categories/test_atom_sites.py | 8 ++++ .../datablocks/structure/item/test_factory.py | 33 ++++++++++++++++ .../easydiffraction/io/cif/test_handler.py | 12 +++++- .../utils/test_utils_coverage.py | 39 +++++++++++++++++++ 11 files changed, 160 insertions(+), 28 deletions(-) diff --git a/pixi.lock b/pixi.lock index f45289f91..1d520d7da 100644 --- a/pixi.lock +++ b/pixi.lock @@ -9665,6 +9665,7 @@ packages: - diffpy-pdffit2 - diffpy-utils - emcee + - filelock - gemmi - h5py - lmfit diff --git a/pyproject.toml b/pyproject.toml index 50e2a410e..57c7c712f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,6 +26,7 @@ dependencies = [ 'numpy', # Numerical computing library 'asciichartpy', # ASCII charts for terminal output 'pooch', # Data downloader + 'filelock', # Cross-process locking for shared download caches 'typer', # Command-line interface creation 'rich', # Rich text and beautiful formatting in the terminal 'varname', # Variable name introspection diff --git a/src/easydiffraction/analysis/calculators/crysfml.py b/src/easydiffraction/analysis/calculators/crysfml.py index e3fae3c30..6520126b3 100644 --- a/src/easydiffraction/analysis/calculators/crysfml.py +++ b/src/easydiffraction/analysis/calculators/crysfml.py @@ -24,7 +24,7 @@ from __future__ import annotations -import string +import re from typing import TYPE_CHECKING import numpy as np @@ -60,23 +60,26 @@ def _element_symbol(type_symbol: str) -> str: """ - Strip a leading isotope number from an atom type symbol. + Extract the element from an isotope or ionic atom type symbol. CrysFML resolves scattering by element and does not understand - isotope prefixes such as ``11B`` or ``2H`` (cryspy does). Returning - the bare element symbol lets one model drive both engines. + isotope prefixes such as ``11B`` or ionic suffixes such as ``Fe3+`` + (cryspy does). Returning the bare element symbol lets one model + drive both engines. Parameters ---------- type_symbol : str - Atom type symbol, optionally isotope-prefixed (e.g. ``11B``). + Atom type symbol, optionally isotope-prefixed or charged (e.g. + ``11B`` or ``Fe3+``). Returns ------- str - The symbol with any leading digits removed (e.g. ``B``). + The bare element symbol (e.g. ``B`` or ``Fe``). """ - return type_symbol.lstrip(string.digits) + match = re.fullmatch(r'\d*([A-Z][a-z]?)(?:[1-8][+-])?', type_symbol.strip()) + return match.group(1) if match else type_symbol def _cfl_label(name: str) -> str: diff --git a/src/easydiffraction/datablocks/structure/categories/atom_sites/default.py b/src/easydiffraction/datablocks/structure/categories/atom_sites/default.py index 3c6ed869e..0940ac708 100644 --- a/src/easydiffraction/datablocks/structure/categories/atom_sites/default.py +++ b/src/easydiffraction/datablocks/structure/categories/atom_sites/default.py @@ -160,7 +160,10 @@ def __init__(self) -> None: value_spec=AttributeSpec(default=None, allow_none=True), tags=TagSpec( edi_names=['_atom_site.multiplicity'], - cif_names=['_atom_site.site_symmetry_multiplicity'], + cif_names=[ + '_atom_site.site_symmetry_multiplicity', + '_atom_site_symmetry_multiplicity', + ], ), ) self._occupancy = Parameter( @@ -215,14 +218,22 @@ def __init__(self) -> None: @property def _type_symbol_allowed_values(self) -> list[str]: """ - Chemical symbols accepted by *cryspy*. + Chemical and ionic symbols accepted by *cryspy*. Returns ------- list[str] - Unique element/isotope symbols from the database. - """ - return list({key[1] for key in DATABASE['Isotopes']}) + Unique element/isotope symbols from the database, with + common signed oxidation-state suffixes. + """ + symbols = {key[1] for key in DATABASE['Isotopes']} + ions = { + f'{symbol}{charge}{sign}' + for symbol in symbols + for charge in range(1, 9) + for sign in ('+', '-') + } + return list(symbols | ions) def _resolve_structure_space_group(self) -> object | None: """ diff --git a/src/easydiffraction/io/cif/handler.py b/src/easydiffraction/io/cif/handler.py index 3166720f2..0a2a7316d 100644 --- a/src/easydiffraction/io/cif/handler.py +++ b/src/easydiffraction/io/cif/handler.py @@ -79,13 +79,23 @@ def cif_name(self) -> str: @property def cif_read_names(self) -> list[str]: - """Accepted ``.cif`` import names, in lookup order.""" - return list(dict.fromkeys(self.cif_names)) + """ + Accepted ``.cif`` import names, in lookup order. + + CIF dictionaries use both ``_category.item`` and the older + ``_category_item`` spelling. Gemmi preserves the spelling from + the input document, so add the underscore form of every dotted + name as an import-only alias. Explicitly declared names retain + priority over inferred aliases. + """ + names = list(dict.fromkeys(self.cif_names)) + aliases = [name.replace('.', '_', 1) for name in names if '.' in name] + return list(dict.fromkeys([*names, *aliases])) @property def read_names(self) -> list[str]: """Names accepted on read across both formats (union).""" - return list(dict.fromkeys([self.edi_name, *self._edi_names, *self.cif_names])) + return list(dict.fromkeys([*self.edi_read_names, *self.cif_read_names])) @property def category_name(self) -> str: diff --git a/src/easydiffraction/utils/utils.py b/src/easydiffraction/utils/utils.py index d126af118..80168f709 100644 --- a/src/easydiffraction/utils/utils.py +++ b/src/easydiffraction/utils/utils.py @@ -20,6 +20,7 @@ import numpy as np import pandas as pd import pooch +from filelock import FileLock from packaging.version import Version from rich.markup import escape from uncertainties import UFloat @@ -440,21 +441,28 @@ def _fetch_data_index() -> dict: index_url = _build_data_url('index.json') _validate_url(index_url) - cache_dir = pooch.os_cache('easydiffraction') + cache_dir = pathlib.Path(pooch.os_cache('easydiffraction')) + cache_dir.mkdir(parents=True, exist_ok=True) # Cache under a commit-named file so a ref bump downloads a fresh # index instead of reusing a stale one (data-source-pinning ADR). destination_fname = f'data-index-{_data_index_ref()}.json' + lock_path = cache_dir / f'{destination_fname}.lock' + + # Pooch does not lock ``retrieve`` calls. Parallel processes can + # therefore replace the same cache file while another process opens + # it, which raises PermissionError on Windows. Keep retrieval and + # parsing in one lock. + with FileLock(lock_path): + index_path = pooch.retrieve( + url=index_url, + known_hash=None, + fname=destination_fname, + path=cache_dir, + progressbar=False, + ) - index_path = pooch.retrieve( - url=index_url, - known_hash=None, - fname=destination_fname, - path=cache_dir, - progressbar=False, - ) - - with pathlib.Path(index_path).open('r', encoding='utf-8') as f: - return json.load(f) + with pathlib.Path(index_path).open('r', encoding='utf-8') as f: + return json.load(f) def _existing_project_dir(extraction_dir: pathlib.Path) -> pathlib.Path | None: diff --git a/tests/unit/easydiffraction/analysis/calculators/test_crysfml.py b/tests/unit/easydiffraction/analysis/calculators/test_crysfml.py index b7051730e..7fb0d846c 100644 --- a/tests/unit/easydiffraction/analysis/calculators/test_crysfml.py +++ b/tests/unit/easydiffraction/analysis/calculators/test_crysfml.py @@ -90,6 +90,16 @@ def test_module_import(): assert MUT.__name__ == 'easydiffraction.analysis.calculators.crysfml' +@pytest.mark.parametrize( + ('type_symbol', 'expected'), + [('Fe', 'Fe'), ('57Fe', 'Fe'), ('Fe3+', 'Fe'), ('O2-', 'O')], +) +def test_element_symbol_strips_isotope_and_ionic_notation(type_symbol, expected): + from easydiffraction.analysis.calculators.crysfml import _element_symbol + + assert _element_symbol(type_symbol) == expected + + def test_crysfml_calculate_pattern_applies_absorption(monkeypatch): from easydiffraction.analysis.calculators.crysfml import CrysfmlCalculator from easydiffraction.analysis.corrections import absorption diff --git a/tests/unit/easydiffraction/datablocks/structure/categories/test_atom_sites.py b/tests/unit/easydiffraction/datablocks/structure/categories/test_atom_sites.py index 10f59dabc..3da6ce03c 100644 --- a/tests/unit/easydiffraction/datablocks/structure/categories/test_atom_sites.py +++ b/tests/unit/easydiffraction/datablocks/structure/categories/test_atom_sites.py @@ -77,6 +77,14 @@ def test_type_symbol_setter(self): site.type_symbol = 'Fe' assert site.type_symbol.value == 'Fe' + def test_ionic_type_symbol_setter(self): + from easydiffraction.datablocks.structure.categories.atom_sites.default import AtomSite + + site = AtomSite() + site.type_symbol = 'Fe3+' + + assert site.type_symbol.value == 'Fe3+' + def test_coordinate_setters(self): from easydiffraction.datablocks.structure.categories.atom_sites.default import AtomSite diff --git a/tests/unit/easydiffraction/datablocks/structure/item/test_factory.py b/tests/unit/easydiffraction/datablocks/structure/item/test_factory.py index 148e010ad..0378f1339 100644 --- a/tests/unit/easydiffraction/datablocks/structure/item/test_factory.py +++ b/tests/unit/easydiffraction/datablocks/structure/item/test_factory.py @@ -7,3 +7,36 @@ def test_from_scratch(): m = StructureFactory.from_scratch(name='abc') assert m.name == 'abc' + + +def test_from_cif_str_accepts_underscore_style_structure_tags(): + cif = """\ +data_legacy +_cell_length_a 9.15993(5) +_cell_length_b 9.15993(5) +_cell_length_c 9.15993(5) +_cell_angle_alpha 90 +_cell_angle_beta 90 +_cell_angle_gamma 90 +_symmetry_space_group_name_H-M 'P 21 3' + +loop_ +_atom_site_label +_atom_site_type_symbol +_atom_site_symmetry_multiplicity +_atom_site_fract_x +_atom_site_fract_y +_atom_site_fract_z +_atom_site_B_iso_or_equiv +_atom_site_occupancy +Zr1 Zr4+ 4 0.0003(4) 0.0003(4) 0.0003(4) 0.010(1) 1 +W1 W6+ 4 0.3412(3) 0.3412(3) 0.3412(3) 0.012(1) 1 +""" + + structure = StructureFactory.from_cif_str(cif) + + assert structure.cell.length_a.value == 9.15993 + assert structure.space_group.name_h_m.value == 'P 21 3' + assert structure.atom_sites.names == ['Zr1', 'W1'] + assert structure.atom_sites['Zr1'].type_symbol.value == 'Zr4+' + assert structure.atom_sites['Zr1'].multiplicity.value == 4 diff --git a/tests/unit/easydiffraction/io/cif/test_handler.py b/tests/unit/easydiffraction/io/cif/test_handler.py index 1a7a7e6bc..42b08fb67 100644 --- a/tests/unit/easydiffraction/io/cif/test_handler.py +++ b/tests/unit/easydiffraction/io/cif/test_handler.py @@ -58,7 +58,15 @@ def test_cif_read_names_dedup_and_canonical_first(): handler = TagSpec(edi_names=['_a.x'], cif_names=['_b.y', '_b.z', '_b.y']) assert handler.cif_name == '_b.y' - assert handler.cif_read_names == ['_b.y', '_b.z'] + assert handler.cif_read_names == ['_b.y', '_b.z', '_b_y', '_b_z'] + + +def test_cif_read_names_add_underscore_alias_for_dotted_name(): + from easydiffraction.io.cif.handler import TagSpec + + handler = TagSpec(edi_names=['_cell.length_a']) + + assert handler.cif_read_names == ['_cell.length_a', '_cell_length_a'] def test_cif_names_default_to_edi_names(): @@ -76,4 +84,4 @@ def test_read_names_union_orders_edi_before_cif_and_dedupes(): handler = TagSpec(edi_names=['_a.x'], cif_names=['_a.x', '_b.y']) # Edi name first, then CIF-only aliases, with duplicates removed. - assert handler.read_names == ['_a.x', '_b.y'] + assert handler.read_names == ['_a.x', '_b.y', '_a_x', '_b_y'] diff --git a/tests/unit/easydiffraction/utils/test_utils_coverage.py b/tests/unit/easydiffraction/utils/test_utils_coverage.py index e18671532..596af43a6 100644 --- a/tests/unit/easydiffraction/utils/test_utils_coverage.py +++ b/tests/unit/easydiffraction/utils/test_utils_coverage.py @@ -3,6 +3,8 @@ """Supplementary unit tests for easydiffraction.utils.utils — coverage gaps.""" +import concurrent.futures +import threading import urllib.request import numpy as np @@ -642,6 +644,43 @@ def test_fetch_data_index_reads_cached_json(monkeypatch, tmp_path): assert result == {'1': {'path': 'a.xye'}} +def test_fetch_data_index_serializes_shared_cache_access(monkeypatch, tmp_path): + import json + + import easydiffraction.utils.utils as MUT + + index_file = tmp_path / 'data-index.json' + index_file.write_text(json.dumps({'1': {'path': 'a.xye'}}), encoding='utf-8') + first_retrieve_entered = threading.Event() + release_first_retrieve = threading.Event() + state_lock = threading.Lock() + active_retrieves = 0 + max_active_retrieves = 0 + + def fake_retrieve(url, known_hash, fname, path, progressbar): + nonlocal active_retrieves, max_active_retrieves + with state_lock: + active_retrieves += 1 + max_active_retrieves = max(max_active_retrieves, active_retrieves) + first_retrieve_entered.set() + release_first_retrieve.wait(timeout=1) + with state_lock: + active_retrieves -= 1 + return str(index_file) + + monkeypatch.setattr(MUT.pooch, 'os_cache', lambda name: tmp_path) + monkeypatch.setattr(MUT.pooch, 'retrieve', fake_retrieve) + + with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor: + futures = [executor.submit(MUT._fetch_data_index) for _ in range(4)] + assert first_retrieve_entered.wait(timeout=1) + release_first_retrieve.set() + results = [future.result(timeout=1) for future in futures] + + assert results == [{'1': {'path': 'a.xye'}}] * 4 + assert max_active_retrieves == 1 + + # --- _existing_project_dir ---------------------------------------------------- From c4ae7e8ca230ae099d912e9ffef74463b6b90226 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Mon, 14 Sep 2026 17:56:21 +0200 Subject: [PATCH 2/5] Normalize imported CIF datablock names (#223) --- src/easydiffraction/io/cif/parse.py | 13 ++++++++++--- .../datablocks/experiment/item/test_factory.py | 9 +++++++++ .../datablocks/structure/item/test_factory.py | 7 +++++++ tests/unit/easydiffraction/io/cif/test_parse.py | 11 +++++++++++ 4 files changed, 37 insertions(+), 3 deletions(-) diff --git a/src/easydiffraction/io/cif/parse.py b/src/easydiffraction/io/cif/parse.py index 0577f0b47..9cf46118b 100644 --- a/src/easydiffraction/io/cif/parse.py +++ b/src/easydiffraction/io/cif/parse.py @@ -4,11 +4,18 @@ from __future__ import annotations +import re + import gemmi # Minimum raw-string length for CIF surrounding-quote detection _MIN_QUOTED_LEN = 2 +# Model datablock names use a backend-safe subset of CIF block-name +# characters. Hyphens and underscores are retained because they are used +# throughout EasyDiffraction's public naming conventions. +_UNSUPPORTED_DATABLOCK_NAME_CHARS = re.compile(r'[^a-z0-9_-]') + def document_from_path(path: str) -> gemmi.cif.Document: """Read a CIF document from a file path.""" @@ -26,9 +33,9 @@ def pick_sole_block(doc: gemmi.cif.Document) -> gemmi.cif.Block: def name_from_block(block: gemmi.cif.Block) -> str: - """Extract a model name from the CIF block name.""" - # TODO: Need validator or normalization? - return block.name + """Extract and normalize a model name from the CIF block name.""" + lowercase_name = block.name.lower() + return _UNSUPPORTED_DATABLOCK_NAME_CHARS.sub('', lowercase_name) def read_cif_str(block: gemmi.cif.Block, tag: str) -> str | None: diff --git a/tests/unit/easydiffraction/datablocks/experiment/item/test_factory.py b/tests/unit/easydiffraction/datablocks/experiment/item/test_factory.py index f58b117f7..90af7f1a7 100644 --- a/tests/unit/easydiffraction/datablocks/experiment/item/test_factory.py +++ b/tests/unit/easydiffraction/datablocks/experiment/item/test_factory.py @@ -29,6 +29,15 @@ def test_experiment_factory_from_scratch(): assert ex.experiment_type.sample_form.value == SampleFormEnum.POWDER.value +def test_from_cif_str_normalizes_datablock_name(): + from easydiffraction.datablocks.experiment.item.factory import ExperimentFactory + + experiment = ExperimentFactory.from_cif_str('data_83267-ICSD\n') + + assert experiment.name == '83267-icsd' + assert experiment.as_cif.startswith('data_83267-icsd\n') + + def test_from_cif_str_restores_non_default_peak_profile_type(): """ Loading a CIF with a non-default peak profile type must reconstruct diff --git a/tests/unit/easydiffraction/datablocks/structure/item/test_factory.py b/tests/unit/easydiffraction/datablocks/structure/item/test_factory.py index 0378f1339..1f85b891e 100644 --- a/tests/unit/easydiffraction/datablocks/structure/item/test_factory.py +++ b/tests/unit/easydiffraction/datablocks/structure/item/test_factory.py @@ -9,6 +9,13 @@ def test_from_scratch(): assert m.name == 'abc' +def test_from_cif_str_normalizes_datablock_name(): + structure = StructureFactory.from_cif_str('data_83267-ICSD\n') + + assert structure.name == '83267-icsd' + assert structure.as_cif.startswith('data_83267-icsd\n') + + def test_from_cif_str_accepts_underscore_style_structure_tags(): cif = """\ data_legacy diff --git a/tests/unit/easydiffraction/io/cif/test_parse.py b/tests/unit/easydiffraction/io/cif/test_parse.py index f1d37bbe1..c46ef6c58 100644 --- a/tests/unit/easydiffraction/io/cif/test_parse.py +++ b/tests/unit/easydiffraction/io/cif/test_parse.py @@ -31,6 +31,17 @@ def test_name_from_block(self): name = name_from_block(block) assert name == 'silicon' + def test_name_from_block_normalizes_unsupported_name(self): + from easydiffraction.io.cif.parse import document_from_string + from easydiffraction.io.cif.parse import name_from_block + from easydiffraction.io.cif.parse import pick_sole_block + + cif = 'data_My+83267-ICSD.example\n_cell.length_a 5.43\n' + doc = document_from_string(cif) + block = pick_sole_block(doc) + name = name_from_block(block) + assert name == 'my83267-icsdexample' + class TestDocumentFromPath: def test_valid_file(self, tmp_path): From c448b7083413caa2e6193e12870607215186c857 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Mon, 14 Sep 2026 20:19:01 +0200 Subject: [PATCH 3/5] Correct weighted R-factor calculations and document reliability metrics (#224) * Fix weighted R-factor uncertainty handling * Document reliability factor definitions * Add pending ICSD CIF import regression test * Simplify LaM7O3 tutorial sidebar label * Enable ICSD CIF import regression test --- .../user-guide/analysis-workflow/analysis.md | 3 + .../analysis-workflow/reliability-factors.md | 156 ++++++++++++++++++ docs/mkdocs.yml | 3 +- src/easydiffraction/analysis/analysis.py | 8 +- .../analysis/fit_helpers/metrics.py | 10 +- .../fitting/test_icsd_structure_cif_import.py | 151 +++++++++++++++++ .../analysis/fit_helpers/test_metrics.py | 21 ++- .../analysis/test_analysis_coverage.py | 10 +- 8 files changed, 345 insertions(+), 17 deletions(-) create mode 100644 docs/docs/user-guide/analysis-workflow/reliability-factors.md create mode 100644 tests/integration/fitting/test_icsd_structure_cif_import.py diff --git a/docs/docs/user-guide/analysis-workflow/analysis.md b/docs/docs/user-guide/analysis-workflow/analysis.md index 4de074cbe..1eae23d6d 100644 --- a/docs/docs/user-guide/analysis-workflow/analysis.md +++ b/docs/docs/user-guide/analysis-workflow/analysis.md @@ -265,6 +265,9 @@ Now, you can inspect the fitted parameters to see how they have changed during the refinement process, select more parameters to be refined, and perform additional fits as needed. +The equations and interpretation of the reported fit-quality values are +listed on the [Reliability Factors](reliability-factors.md) page. + To plot the measured and calculated data after the fit, you can use the `pattern` method of the `display` object: diff --git a/docs/docs/user-guide/analysis-workflow/reliability-factors.md b/docs/docs/user-guide/analysis-workflow/reliability-factors.md new file mode 100644 index 000000000..8261ef6df --- /dev/null +++ b/docs/docs/user-guide/analysis-workflow/reliability-factors.md @@ -0,0 +1,156 @@ +--- +title: Reliability Factors +icon: material/chart-bell-curve-cumulative +--- + +# :material-chart-bell-curve-cumulative: Reliability Factors + +EasyDiffraction reports several complementary measures of agreement +between observed and calculated diffraction data. They are ratios +internally; values shown with a percent sign are multiplied by 100. + +For the equations below, $y_i^{\mathrm{obs}}$ and $y_i^{\mathrm{calc}}$ +are the observed and calculated intensities, $\sigma_i$ is the standard +uncertainty of the observed intensity, and + +$$ +w_i = \frac{1}{\sigma_i^2} +$$ + +is its inverse-variance weight. The sum runs over the $N$ data points +included in the reported value. + +## R-factor (Rf) + +The unweighted profile R-factor is the absolute difference between +observed and calculated intensities, normalized by the total absolute +observed intensity: + +$$ +R_f = +\frac{\sum_i \left|y_i^{\mathrm{obs}}-y_i^{\mathrm{calc}}\right|} + {\sum_i \left|y_i^{\mathrm{obs}}\right|}. +$$ + +Lower values indicate closer agreement. This factor does not use the +measurement uncertainties, so every data point contributes according to +the magnitude of its absolute residual. + +## Squared-residual R-factor (Rf²) + +The value labelled `Rf²` in the fit summary is the unweighted +root-squared-residual ratio: + +$$ +R_{f^2} = +\left[ +\frac{\sum_i \left(y_i^{\mathrm{obs}}-y_i^{\mathrm{calc}}\right)^2} + {\sum_i \left(y_i^{\mathrm{obs}}\right)^2} +\right]^{1/2}. +$$ + +Despite the compact `Rf²` label, this is not the algebraic square of +$R_f$. The label indicates that squared intensities and residuals are +used before taking the square root. + +## Weighted R-factor (wR) + +The weighted R-factor is the root ratio of weighted squared residuals: + +$$ +wR = +\left[ +\frac{\sum_i w_i + \left(y_i^{\mathrm{obs}}-y_i^{\mathrm{calc}}\right)^2} + {\sum_i w_i \left(y_i^{\mathrm{obs}}\right)^2} +\right]^{1/2}, +\qquad +w_i = \frac{1}{\sigma_i^2}. +$$ + +Consequently, a point with a smaller standard uncertainty has more +influence than a less precise point. EasyDiffraction expects standard +uncertainties as input and converts them to inverse-variance weights; it +does not use $\sigma_i$ itself as the weight. + +## Chi-square and reduced chi-square + +The uncertainty-weighted sum of squared residuals is + +$$ +\chi^2 = +\sum_i \left( +\frac{y_i^{\mathrm{obs}}-y_i^{\mathrm{calc}}}{\sigma_i} +\right)^2 += \sum_i w_i +\left(y_i^{\mathrm{obs}}-y_i^{\mathrm{calc}}\right)^2. +$$ + +If $p$ free parameters were fitted, the number of degrees of freedom is +$\nu=N-p$, and the reported goodness-of-fit is the reduced chi-square: + +$$ +\chi_\nu^2 = \frac{\chi^2}{\nu}. +$$ + +A value near 1 means that the size of the residuals is consistent with +the stated standard uncertainties. A much larger value can indicate a +poor model or underestimated uncertainties; a much smaller value can +indicate overestimated uncertainties or an over-flexible model. + +For a joint fit, EasyDiffraction also multiplies each experiment's +squared normalized residuals by its normalized joint-fit weight. Those +experiment weights are normalized so that their sum equals the number of +experiments. + +## Expected weighted profile R-factor + +For powder fits, the expected weighted profile R-factor is + +$$ +wR_{\mathrm{expected}} = +\left[ +\frac{\nu} + {\sum_i w_i \left(y_i^{\mathrm{obs}}\right)^2} +\right]^{1/2}. +$$ + +It is the weighted profile R-factor expected when $\chi_\nu^2=1$. +Therefore, $wR / wR_{\mathrm{expected}} = \sqrt{\chi_\nu^2}$ when the +same data points and weights are used for both values. + +## Bragg R-factor (BR) + +When observed and calculated structure-factor magnitudes are available, +EasyDiffraction can report the Bragg R-factor: + +$$ +BR = +\frac{\sum_h \left|F_h^{\mathrm{obs}}-F_h^{\mathrm{calc}}\right|} + {\sum_h F_h^{\mathrm{obs}}}. +$$ + +Here $h$ indexes reflections and $F_h$ is a structure-factor magnitude. +Lower values indicate closer agreement between observed and calculated +reflection amplitudes. + +## Names and data subsets + +The fit summary uses the short labels `Rf`, `Rf²`, `wR`, and `BR`. Saved +deterministic fit results also expose IUCr-style names: + +| Saved result | Definition and scope | +| -------------------- | ---------------------------------------------------------------- | +| `R_factor_all` | $R_f$ for all included observations | +| `wR_factor_all` | $wR$ for all included observations | +| `R_factor_gt` | $R_f$ for observations satisfying $y_i^{\mathrm{obs}}>3\sigma_i$ | +| `wR_factor_gt` | $wR$ for observations satisfying $y_i^{\mathrm{obs}}>3\sigma_i$ | +| `prof_R_factor` | $R_f$ for all included powder-profile points | +| `prof_wR_factor` | $wR$ for all included powder-profile points | +| `prof_wR_expected` | $wR_{\mathrm{expected}}$ for all included powder-profile points | +| `reduced_chi_square` | $\chi_\nu^2$ for the fitted residual vector | + +Only finite observations with finite calculated values and positive, +finite standard uncertainties are included in saved deterministic +statistics. A metric is unavailable when its denominator is zero or when +it does not apply to the fitted data. diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index f53fc69fe..3b7ae5379 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -222,6 +222,7 @@ nav: - Structure: user-guide/analysis-workflow/model.md - Experiment: user-guide/analysis-workflow/experiment.md - Analysis: user-guide/analysis-workflow/analysis.md + - Reliability Factors: user-guide/analysis-workflow/reliability-factors.md - Report: user-guide/analysis-workflow/report.md - Tutorials: - Tutorials: tutorials/index.md @@ -235,7 +236,7 @@ nav: - HS pd-neut-cwl: tutorials/refine-hs-hrpt.ipynb - Si pd-neut-tof: tutorials/refine-si-sepd.ipynb - PbSO4 pd-xray-cwl: tutorials/refine-pbso4-xray.ipynb - - LaM(7)O3 P02.1 pd-xray-cwl: tutorials/refine-lam7o3-p021.ipynb + - LaM(7)O3 pd-xray-cwl: tutorials/refine-lam7o3-p021.ipynb - LMO pd-neut-cwl: tutorials/refine-lmo-echidna.ipynb - Without Measured Data: - LBCO pd-neut-cwl: tutorials/simulate-lbco-cwl.ipynb diff --git a/src/easydiffraction/analysis/analysis.py b/src/easydiffraction/analysis/analysis.py index da6f174a6..2334bdd4d 100644 --- a/src/easydiffraction/analysis/analysis.py +++ b/src/easydiffraction/analysis/analysis.py @@ -44,6 +44,7 @@ from easydiffraction.analysis.fit_helpers.bayesian import PosteriorSamples from easydiffraction.analysis.fit_helpers.bayesian import posterior_predictive_cache_key from easydiffraction.analysis.fit_helpers.metrics import calculate_r_factor +from easydiffraction.analysis.fit_helpers.metrics import calculate_weighted_r_factor from easydiffraction.analysis.fit_helpers.reporting import FitResults from easydiffraction.analysis.fitting import Fitter from easydiffraction.analysis.fitting import FitterFitOptions @@ -2035,12 +2036,7 @@ def _weighted_r_factor_or_none( """Return a weighted R factor when inputs are available.""" if observed.size == 0: return None - weights = 1.0 / uncertainties**2 - denominator = float(np.sum(weights * observed**2)) - if denominator <= 0.0: - return None - numerator = float(np.sum(weights * (observed - calculated) ** 2)) - value = np.sqrt(numerator / denominator) + value = calculate_weighted_r_factor(observed, calculated, uncertainties) return float(value) if np.isfinite(value) else None @staticmethod diff --git a/src/easydiffraction/analysis/fit_helpers/metrics.py b/src/easydiffraction/analysis/fit_helpers/metrics.py index af05ba648..c8604cd8e 100644 --- a/src/easydiffraction/analysis/fit_helpers/metrics.py +++ b/src/easydiffraction/analysis/fit_helpers/metrics.py @@ -44,7 +44,7 @@ def calculate_r_factor( def calculate_weighted_r_factor( y_obs: np.ndarray, y_calc: np.ndarray, - weights: np.ndarray, + standard_uncertainties: np.ndarray, ) -> float: """ Calculate weighted R-factor between observed and calculated data. @@ -55,8 +55,9 @@ def calculate_weighted_r_factor( Observed data points. y_calc : np.ndarray Calculated data points. - weights : np.ndarray - Weights for each data point. + standard_uncertainties : np.ndarray + Standard uncertainties for the observed data points. The + inverse-variance weights are calculated as ``1 / sigma**2``. Returns ------- @@ -65,7 +66,8 @@ def calculate_weighted_r_factor( """ y_obs = np.asarray(y_obs) y_calc = np.asarray(y_calc) - weights = np.asarray(weights) + standard_uncertainties = np.asarray(standard_uncertainties) + weights = 1.0 / standard_uncertainties**2 numerator = np.sum(weights * (y_obs - y_calc) ** 2) denominator = np.sum(weights * y_obs**2) return np.sqrt(numerator / denominator) if denominator != 0 else np.nan diff --git a/tests/integration/fitting/test_icsd_structure_cif_import.py b/tests/integration/fitting/test_icsd_structure_cif_import.py new file mode 100644 index 000000000..986a6f2b6 --- /dev/null +++ b/tests/integration/fitting/test_icsd_structure_cif_import.py @@ -0,0 +1,151 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Regression coverage for importing an underscore-style ICSD CIF.""" + +import numpy as np +import pytest + +from easydiffraction.analysis.calculators.cryspy import CryspyCalculator +from easydiffraction.datablocks.structure.item.factory import StructureFactory + +ZRW2O8_CIF = """\ +data_83267-ICSD +_database_code_ICSD 83267 +_audit_creation_date 1998-06-26 +_chemical_name_systematic +'Zirconium bis(tungstate)' +_chemical_formula_structural +'Zr (W O4)2' +_chemical_formula_sum +'O8 W2 Zr1' +_publ_section_title +'O8 W2 Zr1' +loop_ +_citation_id +_citation_journal_abbrev +_citation_year +_citation_journal_volume +_citation_page_first +_citation_page_last +_citation_journal_id_ASTM +primary 'Science' 1996 272 90 92 SCIEAS +loop_ +_publ_author_name +Mary, T.A.;Evans, J.S.O.;Vogt, T.;Sleight, A.W. +_cell_length_a 9.15993(5) +_cell_length_b 9.15993(5) +_cell_length_c 9.15993(5) +_cell_angle_alpha 90. +_cell_angle_beta 90. +_cell_angle_gamma 90. +_cell_volume 768.56 +_cell_formula_units_Z 4 +_symmetry_space_group_name_H-M 'P 21 3' +_symmetry_Int_Tables_number 198 +_refine_ls_R_factor_all 0.024000 +loop_ +_symmetry_equiv_pos_site_id +_symmetry_equiv_pos_as_xyz + 1 '-z+1/2, -x, y+1/2' + 2 '-y+1/2, -z, x+1/2' + 3 '-x+1/2, -y, z+1/2' + 4 '-z, x+1/2, -y+1/2' + 5 '-y, z+1/2, -x+1/2' + 6 '-x, y+1/2, -z+1/2' + 7 'z+1/2, -x+1/2, -y' + 8 'y+1/2, -z+1/2, -x' + 9 'x+1/2, -y+1/2, -z' + 10 'z, x, y' + 11 'y, z, x' + 12 'x, y, z' +loop_ +_atom_type_symbol +_atom_type_oxidation_number +Zr4+ 4 +W6+ 6 +O2- -2 +loop_ +_atom_site_label +_atom_site_type_symbol +_atom_site_symmetry_multiplicity +_atom_site_Wyckoff_symbol +_atom_site_fract_x +_atom_site_fract_y +_atom_site_fract_z +_atom_site_B_iso_or_equiv +_atom_site_occupancy +Zr1 Zr4+ 4 a 0.0003(4) 0.0003(4) 0.0003(4) 0.010(1) 1. +W1 W6+ 4 a 0.3412(3) 0.3412(3) 0.3412(3) 0.012(1) 1. +W2 W6+ 4 a 0.6008(3) 0.6008(3) 0.6008(3) 0.010(1) 1. +O1 O2- 12 b 0.2071(3) 0.4378(4) 0.4470(3) 0.022(1) 1. +O2 O2- 12 b 0.7876(3) 0.5694(4) 0.5565(3) 0.020(1) 1. +O3 O2- 4 a 0.4916(5) 0.4916(5) 0.4916(5) 0.023(1) 1. +O4 O2- 4 a 0.2336(3) 0.2336(3) 0.2336(3) 0.037(1) 1. +""" + + +def test_icsd_cif_import_preserves_structure_and_ionic_symbols_for_cryspy(): + """Import the ICSD structure and preserve ionic atom types through Cryspy.""" + from cryspy.H_functions_global.function_1_cryspy_objects import str_to_globaln + + structure = StructureFactory.from_cif_str(ZRW2O8_CIF) + + assert structure.name == '83267-icsd' + assert structure.as_cif.startswith('data_83267-icsd\n') + assert structure.space_group.name_h_m.value == 'P 21 3' + np.testing.assert_allclose( + [ + structure.cell.length_a.value, + structure.cell.length_b.value, + structure.cell.length_c.value, + structure.cell.angle_alpha.value, + structure.cell.angle_beta.value, + structure.cell.angle_gamma.value, + ], + [9.15993, 9.15993, 9.15993, 90.0, 90.0, 90.0], + ) + np.testing.assert_allclose( + [ + structure.cell.length_a.uncertainty, + structure.cell.length_b.uncertainty, + structure.cell.length_c.uncertainty, + ], + [0.00005, 0.00005, 0.00005], + ) + + expected_sites = { + 'Zr1': ('Zr4+', 4, 'a', (0.0003, 0.0003, 0.0003), (0.0004,) * 3, 0.010), + 'W1': ('W6+', 4, 'a', (0.3412, 0.3412, 0.3412), (0.0003,) * 3, 0.012), + 'W2': ('W6+', 4, 'a', (0.6008, 0.6008, 0.6008), (0.0003,) * 3, 0.010), + 'O1': ('O2-', 12, 'b', (0.2071, 0.4378, 0.4470), (0.0003, 0.0004, 0.0003), 0.022), + 'O2': ('O2-', 12, 'b', (0.7876, 0.5694, 0.5565), (0.0003, 0.0004, 0.0003), 0.020), + 'O3': ('O2-', 4, 'a', (0.4916, 0.4916, 0.4916), (0.0005,) * 3, 0.023), + 'O4': ('O2-', 4, 'a', (0.2336, 0.2336, 0.2336), (0.0003,) * 3, 0.037), + } + + assert structure.atom_sites.names == list(expected_sites) + for label, expected in expected_sites.items(): + type_symbol, multiplicity, wyckoff, coordinates, coordinate_sus, adp_iso = expected + site = structure.atom_sites[label] + assert site.type_symbol.value == type_symbol + assert site.multiplicity.value == multiplicity + assert site.wyckoff_letter.value == wyckoff + assert site.occupancy.value == 1.0 + np.testing.assert_allclose( + [site.fract_x.value, site.fract_y.value, site.fract_z.value], + coordinates, + ) + np.testing.assert_allclose( + [site.fract_x.uncertainty, site.fract_y.uncertainty, site.fract_z.uncertainty], + coordinate_sus, + ) + assert site.adp_iso.value == pytest.approx(adp_iso) + assert site.adp_iso.uncertainty == pytest.approx(0.001) + + cryspy_cif = CryspyCalculator()._convert_structure_to_cryspy_cif(structure) + cryspy_structure = str_to_globaln(cryspy_cif).items[0] + + assert cryspy_structure.data_name == '83267-icsd' + assert [site.type_symbol for site in cryspy_structure.atom_site.items] == [ + expected[0] for expected in expected_sites.values() + ] diff --git a/tests/unit/easydiffraction/analysis/fit_helpers/test_metrics.py b/tests/unit/easydiffraction/analysis/fit_helpers/test_metrics.py index d87f24614..f696b2988 100644 --- a/tests/unit/easydiffraction/analysis/fit_helpers/test_metrics.py +++ b/tests/unit/easydiffraction/analysis/fit_helpers/test_metrics.py @@ -11,12 +11,12 @@ def test_calculate_r_metrics_and_chi_square(): y_obs = np.array([1.0, 2.0, 3.0]) y_calc = np.array([1.1, 1.9, 2.8]) - weights = np.array([1.0, 2.0, 3.0]) + standard_uncertainties = np.array([1.0, 2.0, 3.0]) residuals = y_obs - y_calc r = M.calculate_r_factor(y_obs, y_calc) rb = M.calculate_rb_factor(y_obs, y_calc) - rw = M.calculate_weighted_r_factor(y_obs, y_calc, weights) + rw = M.calculate_weighted_r_factor(y_obs, y_calc, standard_uncertainties) r2 = M.calculate_r_factor_squared(y_obs, y_calc) chi2 = M.calculate_reduced_chi_square(residuals, num_parameters=1) @@ -28,6 +28,23 @@ def test_calculate_r_metrics_and_chi_square(): assert np.isfinite(chi2) +def test_calculate_weighted_r_factor_uses_inverse_variance_weights(): + from easydiffraction.analysis.fit_helpers import metrics as M + + y_obs = np.array([10.0, 20.0]) + y_calc = np.array([9.0, 16.0]) + standard_uncertainties = np.array([1.0, 2.0]) + + result = M.calculate_weighted_r_factor( + y_obs, + y_calc, + standard_uncertainties, + ) + + # sqrt((1 * 1^2 + 1/4 * 4^2) / (1 * 10^2 + 1/4 * 20^2)) + assert np.isclose(result, np.sqrt(5.0 / 200.0)) + + def test_get_reliability_inputs_collects_arrays_with_default_su(): from easydiffraction.analysis.fit_helpers import metrics as M diff --git a/tests/unit/easydiffraction/analysis/test_analysis_coverage.py b/tests/unit/easydiffraction/analysis/test_analysis_coverage.py index 1c05f4aad..34042ba40 100644 --- a/tests/unit/easydiffraction/analysis/test_analysis_coverage.py +++ b/tests/unit/easydiffraction/analysis/test_analysis_coverage.py @@ -576,10 +576,12 @@ def test_weighted_r_factor_or_none_empty_and_zero_denominator(self): def test_weighted_r_factor_or_none_computes_value(self): from easydiffraction.analysis.analysis import Analysis - observed = np.asarray([10.0, 10.0], dtype=float) - calculated = np.asarray([10.0, 10.0], dtype=float) - uncertainties = np.asarray([1.0, 1.0], dtype=float) - assert Analysis._weighted_r_factor_or_none(observed, calculated, uncertainties) == 0.0 + observed = np.asarray([10.0, 20.0], dtype=float) + calculated = np.asarray([9.0, 16.0], dtype=float) + uncertainties = np.asarray([1.0, 2.0], dtype=float) + value = Analysis._weighted_r_factor_or_none(observed, calculated, uncertainties) + assert value is not None + assert np.isclose(value, np.sqrt(5.0 / 200.0)) def test_expected_weighted_r_factor_guards(self): from easydiffraction.analysis.analysis import Analysis From 5e3ea2a3461331aa7613b9fba928f3a89d1942c6 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Tue, 15 Sep 2026 00:45:32 +0200 Subject: [PATCH 4/5] Reorganize reliability factors documentation (#225) * Reorganize reliability factors documentation * Add reliability factors to User Guide index --- docs/docs/user-guide/analysis-workflow/reliability-factors.md | 3 +-- docs/docs/user-guide/index.md | 3 +++ docs/mkdocs.yml | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/docs/user-guide/analysis-workflow/reliability-factors.md b/docs/docs/user-guide/analysis-workflow/reliability-factors.md index 8261ef6df..328764a88 100644 --- a/docs/docs/user-guide/analysis-workflow/reliability-factors.md +++ b/docs/docs/user-guide/analysis-workflow/reliability-factors.md @@ -1,9 +1,8 @@ --- title: Reliability Factors -icon: material/chart-bell-curve-cumulative --- -# :material-chart-bell-curve-cumulative: Reliability Factors +# Reliability Factors EasyDiffraction reports several complementary measures of agreement between observed and calculated diffraction data. They are ratios diff --git a/docs/docs/user-guide/index.md b/docs/docs/user-guide/index.md index f5d1ac5f3..4b41026c0 100644 --- a/docs/docs/user-guide/index.md +++ b/docs/docs/user-guide/index.md @@ -23,3 +23,6 @@ Here is a brief overview of the User Guide sections: EasyDiffraction in Python or Jupyter notebooks. - [Analysis Workflow](analysis-workflow/index.md) – Breaks down the data analysis pipeline into practical, sequential steps. +- [Reliability Factors](analysis-workflow/reliability-factors.md) – + Defines the equations and interpretation of the reported fit-quality + metrics. diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 3b7ae5379..dce43ea3d 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -222,8 +222,8 @@ nav: - Structure: user-guide/analysis-workflow/model.md - Experiment: user-guide/analysis-workflow/experiment.md - Analysis: user-guide/analysis-workflow/analysis.md - - Reliability Factors: user-guide/analysis-workflow/reliability-factors.md - Report: user-guide/analysis-workflow/report.md + - Reliability Factors: user-guide/analysis-workflow/reliability-factors.md - Tutorials: - Tutorials: tutorials/index.md - Getting Started: From 7e415c82de9be4a393b0fd86f1c9db2d759a89eb Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Tue, 22 Sep 2026 18:53:32 +0200 Subject: [PATCH 5/5] Add SPODI refinement tutorial and improve fitting controls (#227) * Clarify unavailable correlation matrix warning * Add n_free_parameters to tutorial baselines * Update data index reference hash * Add YAlO3+Al2O3 SPODI tutorial * Update YAP tutorial fit parameters * Expose method-specific minimizer tolerances * Update YAP tutorial convergence setup * Warn about unsupported ionic atom symbols * Avoid hard-wrapped notebook output * Update PbSO4 tutorial charge example * Use faster convergence tolerance in tutorials * Refine YAP tutorial index description * Update tutorial baselines after convergence changes * Update Si model in LBCO tutorial * Add auto-install check to LBCO tutorial * Update LBCO Si tutorial baseline --- docs/dev/package-structure/full.md | 7 +- .../tutorials/bayesian-dream-lbco-hrpt.ipynb | 66 +- .../tutorials/bayesian-dream-lbco-hrpt.py | 3 + .../tutorials/bayesian-emcee-lbco-hrpt.ipynb | 66 +- .../tutorials/bayesian-emcee-lbco-hrpt.py | 3 + .../tutorials/bayesian-emcee-tbti-heidi.ipynb | 64 +- .../tutorials/bayesian-emcee-tbti-heidi.py | 3 + docs/docs/tutorials/calibrate-beer-ess.ipynb | 36 +- docs/docs/tutorials/calibrate-beer-ess.py | 3 + .../tutorials/exercise-bayesian-si-lbco.ipynb | 224 ++-- .../tutorials/exercise-bayesian-si-lbco.py | 6 + .../tutorials/exercise-refine-si-lbco.ipynb | 284 ++--- .../docs/tutorials/exercise-refine-si-lbco.py | 6 + docs/docs/tutorials/index.json | 8 + docs/docs/tutorials/index.md | 4 + docs/docs/tutorials/joint-si-bragg-pdf.ipynb | 20 +- docs/docs/tutorials/joint-si-bragg-pdf.py | 3 + .../tutorials/load-and-fit-lbco-hrpt.ipynb | 22 +- docs/docs/tutorials/load-and-fit-lbco-hrpt.py | 3 + docs/docs/tutorials/pdf-nacl-xrd.ipynb | 18 +- docs/docs/tutorials/pdf-nacl-xrd.py | 3 + docs/docs/tutorials/pdf-ni-npd.ipynb | 18 +- docs/docs/tutorials/pdf-ni-npd.py | 3 + docs/docs/tutorials/pdf-si-nomad.ipynb | 18 +- docs/docs/tutorials/pdf-si-nomad.py | 3 + .../refine-cosio-d20-tscan-resumed.ipynb | 34 +- .../refine-cosio-d20-tscan-resumed.py | 3 + .../tutorials/refine-cosio-d20-tscan.ipynb | 72 +- docs/docs/tutorials/refine-cosio-d20-tscan.py | 3 + docs/docs/tutorials/refine-cosio-d20.ipynb | 26 +- docs/docs/tutorials/refine-cosio-d20.py | 3 + docs/docs/tutorials/refine-hs-hrpt.ipynb | 86 +- docs/docs/tutorials/refine-hs-hrpt.py | 3 + docs/docs/tutorials/refine-lam7o3-p021.ipynb | 54 +- docs/docs/tutorials/refine-lam7o3-p021.py | 3 + .../tutorials/refine-lbco-hrpt-from-cif.ipynb | 32 +- .../tutorials/refine-lbco-hrpt-from-cif.py | 3 + .../refine-lbco-hrpt-from-data.ipynb | 48 +- .../tutorials/refine-lbco-hrpt-from-data.py | 3 + .../tutorials/refine-lbco-hrpt-report.ipynb | 120 ++- .../docs/tutorials/refine-lbco-hrpt-report.py | 3 + .../tutorials/refine-lbco-si-mcstas.ipynb | 25 +- docs/docs/tutorials/refine-lbco-si-mcstas.py | 10 +- docs/docs/tutorials/refine-lmo-echidna.ipynb | 22 +- docs/docs/tutorials/refine-lmo-echidna.py | 3 + docs/docs/tutorials/refine-ncaf-wish.ipynb | 22 +- docs/docs/tutorials/refine-ncaf-wish.py | 3 + docs/docs/tutorials/refine-pbso4-joint.ipynb | 30 +- docs/docs/tutorials/refine-pbso4-joint.py | 3 + docs/docs/tutorials/refine-pbso4-xray.ipynb | 26 +- docs/docs/tutorials/refine-pbso4-xray.py | 3 + docs/docs/tutorials/refine-si-sepd.ipynb | 84 +- docs/docs/tutorials/refine-si-sepd.py | 3 + .../docs/tutorials/refine-taurine-senju.ipynb | 42 +- docs/docs/tutorials/refine-taurine-senju.py | 3 + docs/docs/tutorials/refine-tbti-heidi.ipynb | 46 +- docs/docs/tutorials/refine-tbti-heidi.py | 3 + docs/docs/tutorials/refine-yap-3k.ipynb | 989 ++++++++++++++++++ docs/docs/tutorials/refine-yap-3k.py | 428 ++++++++ .../user-guide/analysis-workflow/analysis.md | 27 + docs/mkdocs.yml | 1 + src/easydiffraction/_data_index_ref.txt | 2 +- .../analysis/calculators/crysfml.py | 18 +- .../analysis/calculators/cryspy.py | 69 +- .../analysis/categories/minimizer/bumps.py | 8 +- .../categories/minimizer/bumps_amoeba.py | 8 +- .../analysis/categories/minimizer/bumps_de.py | 4 +- .../analysis/categories/minimizer/bumps_lm.py | 8 +- .../analysis/categories/minimizer/dfols.py | 6 +- .../analysis/categories/minimizer/lmfit.py | 8 +- .../minimizer/lmfit_least_squares.py | 4 +- .../categories/minimizer/lmfit_leastsq.py | 8 +- .../analysis/categories/minimizer/lsq_base.py | 204 ++++ .../analysis/minimizers/bumps.py | 18 +- .../analysis/minimizers/bumps_amoeba.py | 6 + .../analysis/minimizers/bumps_de.py | 5 + .../analysis/minimizers/bumps_lm.py | 6 + .../analysis/minimizers/dfols.py | 11 +- .../analysis/minimizers/lmfit.py | 12 + .../minimizers/lmfit_least_squares.py | 9 + .../analysis/minimizers/lmfit_leastsq.py | 9 + .../categories/atom_sites/default.py | 63 ++ src/easydiffraction/display/plotting.py | 4 +- src/easydiffraction/utils/logging.py | 44 +- .../fitting/test_icsd_structure_cif_import.py | 13 +- tests/tutorials/baseline.json | 53 +- tests/tutorials/generate_baseline.py | 3 + tests/tutorials/test_tutorial_outputs.py | 5 + .../analysis/calculators/test_crysfml.py | 20 + .../analysis/calculators/test_cryspy.py | 79 ++ .../categories/minimizer/test_base.py | 7 +- .../categories/minimizer/test_lsq_base.py | 16 +- .../analysis/minimizers/test_bumps.py | 5 +- .../analysis/minimizers/test_dfols.py | 3 +- .../analysis/minimizers/test_lmfit.py | 5 + .../easydiffraction/analysis/test_analysis.py | 54 +- .../structure/categories/test_atom_sites.py | 27 +- .../utils/test_logging_coverage.py | 40 + 98 files changed, 3314 insertions(+), 713 deletions(-) create mode 100644 docs/docs/tutorials/refine-yap-3k.ipynb create mode 100644 docs/docs/tutorials/refine-yap-3k.py diff --git a/docs/dev/package-structure/full.md b/docs/dev/package-structure/full.md index 4cbf7e719..a10d5f663 100644 --- a/docs/dev/package-structure/full.md +++ b/docs/dev/package-structure/full.md @@ -103,7 +103,11 @@ │ │ │ ├── 📄 lmfit_leastsq.py │ │ │ │ └── 🏷️ class LmfitLeastsqMinimizer │ │ │ └── 📄 lsq_base.py -│ │ │ └── 🏷️ class LeastSquaresMinimizerBase +│ │ │ ├── 🏷️ class LeastSquaresMinimizerBase +│ │ │ ├── 🏷️ class ObjectiveParameterToleranceMinimizerBase +│ │ │ ├── 🏷️ class GradientToleranceMinimizerBase +│ │ │ ├── 🏷️ class PopulationToleranceMinimizerBase +│ │ │ └── 🏷️ class TrustRegionToleranceMinimizerBase │ │ ├── 📁 sequential_fit │ │ │ ├── 📄 __init__.py │ │ │ ├── 📄 default.py @@ -744,6 +748,7 @@ │ │ └── 🏷️ class FigureEmbedMode │ ├── 📄 logging.py │ │ ├── 🏷️ class IconifiedRichHandler +│ │ ├── 🏷️ class NotebookAwareConsole │ │ ├── 🏷️ class ConsoleManager │ │ ├── 🏷️ class LoggerConfig │ │ ├── 🏷️ class ExceptionHookManager diff --git a/docs/docs/tutorials/bayesian-dream-lbco-hrpt.ipynb b/docs/docs/tutorials/bayesian-dream-lbco-hrpt.ipynb index eda8e2ebb..c153d76ce 100644 --- a/docs/docs/tutorials/bayesian-dream-lbco-hrpt.ipynb +++ b/docs/docs/tutorials/bayesian-dream-lbco-hrpt.ipynb @@ -467,7 +467,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.analysis.fit()" + "project.analysis.minimizer.chi_square_change_tolerance = 1e-2" ] }, { @@ -476,13 +476,23 @@ "id": "37", "metadata": {}, "outputs": [], + "source": [ + "project.analysis.fit()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "38", + "metadata": {}, + "outputs": [], "source": [ "project.display.fit.results()" ] }, { "cell_type": "markdown", - "id": "38", + "id": "39", "metadata": {}, "source": [ "The correlation plot shows how strongly the fitted parameters move\n", @@ -494,7 +504,7 @@ { "cell_type": "code", "execution_count": null, - "id": "39", + "id": "40", "metadata": {}, "outputs": [], "source": [ @@ -504,7 +514,7 @@ { "cell_type": "code", "execution_count": null, - "id": "40", + "id": "41", "metadata": {}, "outputs": [], "source": [ @@ -513,7 +523,7 @@ }, { "cell_type": "markdown", - "id": "41", + "id": "42", "metadata": {}, "source": [ "## 🎲 Prepare Sampling\n", @@ -535,7 +545,7 @@ { "cell_type": "code", "execution_count": null, - "id": "42", + "id": "43", "metadata": {}, "outputs": [], "source": [ @@ -544,7 +554,7 @@ }, { "cell_type": "markdown", - "id": "43", + "id": "44", "metadata": {}, "source": [ "Set fit bounds for all free parameters using the default multiplier of\n", @@ -556,7 +566,7 @@ { "cell_type": "code", "execution_count": null, - "id": "44", + "id": "45", "metadata": {}, "outputs": [], "source": [ @@ -566,7 +576,7 @@ }, { "cell_type": "markdown", - "id": "45", + "id": "46", "metadata": {}, "source": [ "Displaying the free parameters again is a convenient way to confirm\n", @@ -577,7 +587,7 @@ { "cell_type": "code", "execution_count": null, - "id": "46", + "id": "47", "metadata": {}, "outputs": [], "source": [ @@ -586,7 +596,7 @@ }, { "cell_type": "markdown", - "id": "47", + "id": "48", "metadata": {}, "source": [ "## 🎲 Run Sampling\n", @@ -612,7 +622,7 @@ { "cell_type": "code", "execution_count": null, - "id": "48", + "id": "49", "metadata": {}, "outputs": [], "source": [ @@ -622,7 +632,7 @@ { "cell_type": "code", "execution_count": null, - "id": "49", + "id": "50", "metadata": {}, "outputs": [], "source": [ @@ -632,7 +642,7 @@ { "cell_type": "code", "execution_count": null, - "id": "50", + "id": "51", "metadata": {}, "outputs": [], "source": [ @@ -644,7 +654,7 @@ { "cell_type": "code", "execution_count": null, - "id": "51", + "id": "52", "metadata": {}, "outputs": [], "source": [ @@ -653,7 +663,7 @@ }, { "cell_type": "markdown", - "id": "52", + "id": "53", "metadata": {}, "source": [ "## 📊 Inspect Results\n", @@ -666,7 +676,7 @@ { "cell_type": "code", "execution_count": null, - "id": "53", + "id": "54", "metadata": {}, "outputs": [], "source": [ @@ -675,7 +685,7 @@ }, { "cell_type": "markdown", - "id": "54", + "id": "55", "metadata": {}, "source": [ "The correlation and posterior-pair plots are complementary:\n", @@ -692,7 +702,7 @@ { "cell_type": "code", "execution_count": null, - "id": "55", + "id": "56", "metadata": {}, "outputs": [], "source": [ @@ -702,7 +712,7 @@ { "cell_type": "code", "execution_count": null, - "id": "56", + "id": "57", "metadata": {}, "outputs": [], "source": [ @@ -711,7 +721,7 @@ }, { "cell_type": "markdown", - "id": "57", + "id": "58", "metadata": {}, "source": [ "The one-dimensional posterior distributions below make it easier to\n", @@ -722,7 +732,7 @@ { "cell_type": "code", "execution_count": null, - "id": "58", + "id": "59", "metadata": {}, "outputs": [], "source": [ @@ -731,7 +741,7 @@ }, { "cell_type": "markdown", - "id": "59", + "id": "60", "metadata": {}, "source": [ "Finally, the posterior predictive plot propagates the sampled parameter\n", @@ -743,7 +753,7 @@ { "cell_type": "code", "execution_count": null, - "id": "60", + "id": "61", "metadata": {}, "outputs": [], "source": [ @@ -752,7 +762,7 @@ }, { "cell_type": "markdown", - "id": "61", + "id": "62", "metadata": {}, "source": [ "A final zoomed measured-vs-calculated plot is useful for checking how\n", @@ -763,7 +773,7 @@ { "cell_type": "code", "execution_count": null, - "id": "62", + "id": "63", "metadata": {}, "outputs": [], "source": [ @@ -772,7 +782,7 @@ }, { "cell_type": "markdown", - "id": "63", + "id": "64", "metadata": {}, "source": [ "## 💾 Save Project\n", @@ -783,7 +793,7 @@ { "cell_type": "code", "execution_count": null, - "id": "64", + "id": "65", "metadata": {}, "outputs": [], "source": [ diff --git a/docs/docs/tutorials/bayesian-dream-lbco-hrpt.py b/docs/docs/tutorials/bayesian-dream-lbco-hrpt.py index 10fb7a5c9..9e6056dce 100644 --- a/docs/docs/tutorials/bayesian-dream-lbco-hrpt.py +++ b/docs/docs/tutorials/bayesian-dream-lbco-hrpt.py @@ -222,6 +222,9 @@ # %% project.analysis.minimizer.type = 'bumps (lm)' +# %% +project.analysis.minimizer.chi_square_change_tolerance = 1e-2 + # %% project.analysis.fit() diff --git a/docs/docs/tutorials/bayesian-emcee-lbco-hrpt.ipynb b/docs/docs/tutorials/bayesian-emcee-lbco-hrpt.ipynb index ecaff5c57..750613752 100644 --- a/docs/docs/tutorials/bayesian-emcee-lbco-hrpt.ipynb +++ b/docs/docs/tutorials/bayesian-emcee-lbco-hrpt.ipynb @@ -457,7 +457,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.analysis.fit()" + "project.analysis.minimizer.chi_square_change_tolerance = 1e-2" ] }, { @@ -466,13 +466,23 @@ "id": "36", "metadata": {}, "outputs": [], + "source": [ + "project.analysis.fit()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "37", + "metadata": {}, + "outputs": [], "source": [ "project.display.fit.results()" ] }, { "cell_type": "markdown", - "id": "37", + "id": "38", "metadata": {}, "source": [ "The correlation plot shows how strongly the fitted parameters move\n", @@ -484,7 +494,7 @@ { "cell_type": "code", "execution_count": null, - "id": "38", + "id": "39", "metadata": {}, "outputs": [], "source": [ @@ -494,7 +504,7 @@ { "cell_type": "code", "execution_count": null, - "id": "39", + "id": "40", "metadata": {}, "outputs": [], "source": [ @@ -503,7 +513,7 @@ }, { "cell_type": "markdown", - "id": "40", + "id": "41", "metadata": {}, "source": [ "## 🎲 Prepare Sampling\n", @@ -525,7 +535,7 @@ { "cell_type": "code", "execution_count": null, - "id": "41", + "id": "42", "metadata": {}, "outputs": [], "source": [ @@ -534,7 +544,7 @@ }, { "cell_type": "markdown", - "id": "42", + "id": "43", "metadata": {}, "source": [ "Set fit bounds for all free parameters using the default multiplier of\n", @@ -546,7 +556,7 @@ { "cell_type": "code", "execution_count": null, - "id": "43", + "id": "44", "metadata": {}, "outputs": [], "source": [ @@ -556,7 +566,7 @@ }, { "cell_type": "markdown", - "id": "44", + "id": "45", "metadata": {}, "source": [ "Displaying the free parameters again is a convenient way to confirm\n", @@ -567,7 +577,7 @@ { "cell_type": "code", "execution_count": null, - "id": "45", + "id": "46", "metadata": {}, "outputs": [], "source": [ @@ -576,7 +586,7 @@ }, { "cell_type": "markdown", - "id": "46", + "id": "47", "metadata": {}, "source": [ "## 🎲 Run Sampling\n", @@ -597,7 +607,7 @@ { "cell_type": "code", "execution_count": null, - "id": "47", + "id": "48", "metadata": {}, "outputs": [], "source": [ @@ -607,7 +617,7 @@ { "cell_type": "code", "execution_count": null, - "id": "48", + "id": "49", "metadata": {}, "outputs": [], "source": [ @@ -617,7 +627,7 @@ { "cell_type": "code", "execution_count": null, - "id": "49", + "id": "50", "metadata": {}, "outputs": [], "source": [ @@ -630,7 +640,7 @@ { "cell_type": "code", "execution_count": null, - "id": "50", + "id": "51", "metadata": {}, "outputs": [], "source": [ @@ -639,7 +649,7 @@ }, { "cell_type": "markdown", - "id": "51", + "id": "52", "metadata": {}, "source": [ "## 📊 Inspect Results\n", @@ -652,7 +662,7 @@ { "cell_type": "code", "execution_count": null, - "id": "52", + "id": "53", "metadata": {}, "outputs": [], "source": [ @@ -661,7 +671,7 @@ }, { "cell_type": "markdown", - "id": "53", + "id": "54", "metadata": {}, "source": [ "The correlation and posterior-pair plots are complementary:\n", @@ -678,7 +688,7 @@ { "cell_type": "code", "execution_count": null, - "id": "54", + "id": "55", "metadata": {}, "outputs": [], "source": [ @@ -688,7 +698,7 @@ { "cell_type": "code", "execution_count": null, - "id": "55", + "id": "56", "metadata": {}, "outputs": [], "source": [ @@ -697,7 +707,7 @@ }, { "cell_type": "markdown", - "id": "56", + "id": "57", "metadata": {}, "source": [ "The one-dimensional posterior distributions below make it easier to\n", @@ -708,7 +718,7 @@ { "cell_type": "code", "execution_count": null, - "id": "57", + "id": "58", "metadata": {}, "outputs": [], "source": [ @@ -717,7 +727,7 @@ }, { "cell_type": "markdown", - "id": "58", + "id": "59", "metadata": {}, "source": [ "Finally, the posterior predictive plot propagates the sampled parameter\n", @@ -729,7 +739,7 @@ { "cell_type": "code", "execution_count": null, - "id": "59", + "id": "60", "metadata": {}, "outputs": [], "source": [ @@ -738,7 +748,7 @@ }, { "cell_type": "markdown", - "id": "60", + "id": "61", "metadata": {}, "source": [ "A final zoomed measured-vs-calculated plot is useful for checking how\n", @@ -749,7 +759,7 @@ { "cell_type": "code", "execution_count": null, - "id": "61", + "id": "62", "metadata": {}, "outputs": [], "source": [ @@ -758,7 +768,7 @@ }, { "cell_type": "markdown", - "id": "62", + "id": "63", "metadata": {}, "source": [ "## 💾 Save Project\n", @@ -769,7 +779,7 @@ { "cell_type": "code", "execution_count": null, - "id": "63", + "id": "64", "metadata": {}, "outputs": [], "source": [ diff --git a/docs/docs/tutorials/bayesian-emcee-lbco-hrpt.py b/docs/docs/tutorials/bayesian-emcee-lbco-hrpt.py index c5cde4ff4..26578a979 100644 --- a/docs/docs/tutorials/bayesian-emcee-lbco-hrpt.py +++ b/docs/docs/tutorials/bayesian-emcee-lbco-hrpt.py @@ -219,6 +219,9 @@ # %% project.analysis.minimizer.show_supported() +# %% +project.analysis.minimizer.chi_square_change_tolerance = 1e-2 + # %% project.analysis.fit() diff --git a/docs/docs/tutorials/bayesian-emcee-tbti-heidi.ipynb b/docs/docs/tutorials/bayesian-emcee-tbti-heidi.ipynb index b69083dba..9ad8ec388 100644 --- a/docs/docs/tutorials/bayesian-emcee-tbti-heidi.ipynb +++ b/docs/docs/tutorials/bayesian-emcee-tbti-heidi.ipynb @@ -332,13 +332,23 @@ "id": "27", "metadata": {}, "outputs": [], + "source": [ + "project.analysis.minimizer.chi_square_change_tolerance = 1e-2" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "28", + "metadata": {}, + "outputs": [], "source": [ "project.analysis.fit()" ] }, { "cell_type": "markdown", - "id": "28", + "id": "29", "metadata": {}, "source": [ "The fit-results display summarizes the locally refined values and their\n", @@ -348,7 +358,7 @@ { "cell_type": "code", "execution_count": null, - "id": "29", + "id": "30", "metadata": {}, "outputs": [], "source": [ @@ -357,7 +367,7 @@ }, { "cell_type": "markdown", - "id": "30", + "id": "31", "metadata": {}, "source": [ "The correlation plot shows how strongly the refined parameters move\n", @@ -369,7 +379,7 @@ { "cell_type": "code", "execution_count": null, - "id": "31", + "id": "32", "metadata": {}, "outputs": [], "source": [ @@ -379,7 +389,7 @@ { "cell_type": "code", "execution_count": null, - "id": "32", + "id": "33", "metadata": {}, "outputs": [], "source": [ @@ -388,7 +398,7 @@ }, { "cell_type": "markdown", - "id": "33", + "id": "34", "metadata": {}, "source": [ "## 🎲 Prepare Sampling\n", @@ -412,7 +422,7 @@ { "cell_type": "code", "execution_count": null, - "id": "34", + "id": "35", "metadata": {}, "outputs": [], "source": [ @@ -421,7 +431,7 @@ }, { "cell_type": "markdown", - "id": "35", + "id": "36", "metadata": {}, "source": [ "Set fit bounds for all free parameters using `multiplier=1.5`. In this\n", @@ -433,7 +443,7 @@ { "cell_type": "code", "execution_count": null, - "id": "36", + "id": "37", "metadata": {}, "outputs": [], "source": [ @@ -443,7 +453,7 @@ }, { "cell_type": "markdown", - "id": "37", + "id": "38", "metadata": {}, "source": [ "Displaying the free parameters again is a convenient way to confirm\n", @@ -454,7 +464,7 @@ { "cell_type": "code", "execution_count": null, - "id": "38", + "id": "39", "metadata": {}, "outputs": [], "source": [ @@ -463,7 +473,7 @@ }, { "cell_type": "markdown", - "id": "39", + "id": "40", "metadata": {}, "source": [ "## 🎲 Run Sampling\n", @@ -484,7 +494,7 @@ { "cell_type": "code", "execution_count": null, - "id": "40", + "id": "41", "metadata": {}, "outputs": [], "source": [ @@ -494,7 +504,7 @@ { "cell_type": "code", "execution_count": null, - "id": "41", + "id": "42", "metadata": {}, "outputs": [], "source": [ @@ -504,7 +514,7 @@ { "cell_type": "code", "execution_count": null, - "id": "42", + "id": "43", "metadata": {}, "outputs": [], "source": [ @@ -517,7 +527,7 @@ { "cell_type": "code", "execution_count": null, - "id": "43", + "id": "44", "metadata": {}, "outputs": [], "source": [ @@ -526,7 +536,7 @@ }, { "cell_type": "markdown", - "id": "44", + "id": "45", "metadata": {}, "source": [ "## 📊 Inspect Results\n", @@ -539,7 +549,7 @@ { "cell_type": "code", "execution_count": null, - "id": "45", + "id": "46", "metadata": {}, "outputs": [], "source": [ @@ -548,7 +558,7 @@ }, { "cell_type": "markdown", - "id": "46", + "id": "47", "metadata": {}, "source": [ "The correlation and posterior-pair plots are complementary:\n", @@ -565,7 +575,7 @@ { "cell_type": "code", "execution_count": null, - "id": "47", + "id": "48", "metadata": {}, "outputs": [], "source": [ @@ -575,7 +585,7 @@ { "cell_type": "code", "execution_count": null, - "id": "48", + "id": "49", "metadata": {}, "outputs": [], "source": [ @@ -584,7 +594,7 @@ }, { "cell_type": "markdown", - "id": "49", + "id": "50", "metadata": {}, "source": [ "The one-dimensional posterior distributions below make it easier to\n", @@ -595,7 +605,7 @@ { "cell_type": "code", "execution_count": null, - "id": "50", + "id": "51", "metadata": {}, "outputs": [], "source": [ @@ -604,7 +614,7 @@ }, { "cell_type": "markdown", - "id": "51", + "id": "52", "metadata": {}, "source": [ "Finally, the posterior predictive plot propagates the sampled\n", @@ -615,7 +625,7 @@ { "cell_type": "code", "execution_count": null, - "id": "52", + "id": "53", "metadata": {}, "outputs": [], "source": [ @@ -624,7 +634,7 @@ }, { "cell_type": "markdown", - "id": "53", + "id": "54", "metadata": {}, "source": [ "## 💾 Save Project\n", @@ -635,7 +645,7 @@ { "cell_type": "code", "execution_count": null, - "id": "54", + "id": "55", "metadata": {}, "outputs": [], "source": [ diff --git a/docs/docs/tutorials/bayesian-emcee-tbti-heidi.py b/docs/docs/tutorials/bayesian-emcee-tbti-heidi.py index 30079d6fa..5b29cdd08 100644 --- a/docs/docs/tutorials/bayesian-emcee-tbti-heidi.py +++ b/docs/docs/tutorials/bayesian-emcee-tbti-heidi.py @@ -143,6 +143,9 @@ # %% project.analysis.minimizer.show_supported() +# %% +project.analysis.minimizer.chi_square_change_tolerance = 1e-2 + # %% project.analysis.fit() diff --git a/docs/docs/tutorials/calibrate-beer-ess.ipynb b/docs/docs/tutorials/calibrate-beer-ess.ipynb index be1b4f1a5..5addb7499 100644 --- a/docs/docs/tutorials/calibrate-beer-ess.ipynb +++ b/docs/docs/tutorials/calibrate-beer-ess.ipynb @@ -664,13 +664,23 @@ "id": "55", "metadata": {}, "outputs": [], + "source": [ + "project.analysis.minimizer.chi_square_change_tolerance = 1e-2" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "56", + "metadata": {}, + "outputs": [], "source": [ "project.analysis.fit()" ] }, { "cell_type": "markdown", - "id": "56", + "id": "57", "metadata": {}, "source": [ "Fix background and run fitting again." @@ -679,7 +689,7 @@ { "cell_type": "code", "execution_count": null, - "id": "57", + "id": "58", "metadata": {}, "outputs": [], "source": [ @@ -692,7 +702,7 @@ { "cell_type": "code", "execution_count": null, - "id": "58", + "id": "59", "metadata": {}, "outputs": [], "source": [ @@ -701,7 +711,7 @@ }, { "cell_type": "markdown", - "id": "59", + "id": "60", "metadata": {}, "source": [ "Show fit results and parameter correlations." @@ -710,7 +720,7 @@ { "cell_type": "code", "execution_count": null, - "id": "60", + "id": "61", "metadata": {}, "outputs": [], "source": [ @@ -720,7 +730,7 @@ }, { "cell_type": "markdown", - "id": "61", + "id": "62", "metadata": {}, "source": [ "### Display Pattern\n", @@ -731,7 +741,7 @@ { "cell_type": "code", "execution_count": null, - "id": "62", + "id": "63", "metadata": {}, "outputs": [], "source": [ @@ -741,7 +751,7 @@ { "cell_type": "code", "execution_count": null, - "id": "63", + "id": "64", "metadata": {}, "outputs": [], "source": [ @@ -750,7 +760,7 @@ }, { "cell_type": "markdown", - "id": "64", + "id": "65", "metadata": {}, "source": [ "Show selected peaks in d-spacing." @@ -759,7 +769,7 @@ { "cell_type": "code", "execution_count": null, - "id": "65", + "id": "66", "metadata": {}, "outputs": [], "source": [ @@ -774,7 +784,7 @@ { "cell_type": "code", "execution_count": null, - "id": "66", + "id": "67", "metadata": {}, "outputs": [], "source": [ @@ -788,7 +798,7 @@ }, { "cell_type": "markdown", - "id": "67", + "id": "68", "metadata": {}, "source": [ "## 💾 Save Project\n", @@ -799,7 +809,7 @@ { "cell_type": "code", "execution_count": null, - "id": "68", + "id": "69", "metadata": {}, "outputs": [], "source": [ diff --git a/docs/docs/tutorials/calibrate-beer-ess.py b/docs/docs/tutorials/calibrate-beer-ess.py index e5818ab81..1b3a987ed 100644 --- a/docs/docs/tutorials/calibrate-beer-ess.py +++ b/docs/docs/tutorials/calibrate-beer-ess.py @@ -299,6 +299,9 @@ # # Run full fitting with all free parameters. +# %% +project.analysis.minimizer.chi_square_change_tolerance = 1e-2 + # %% project.analysis.fit() diff --git a/docs/docs/tutorials/exercise-bayesian-si-lbco.ipynb b/docs/docs/tutorials/exercise-bayesian-si-lbco.ipynb index 049e844c6..ec4b66780 100644 --- a/docs/docs/tutorials/exercise-bayesian-si-lbco.ipynb +++ b/docs/docs/tutorials/exercise-bayesian-si-lbco.ipynb @@ -340,6 +340,16 @@ "id": "24", "metadata": {}, "outputs": [], + "source": [ + "project_1.analysis.minimizer.chi_square_change_tolerance = 1e-2" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "25", + "metadata": {}, + "outputs": [], "source": [ "project_1.analysis.fit()\n", "project_1.display.fit.results()" @@ -347,7 +357,7 @@ }, { "cell_type": "markdown", - "id": "25", + "id": "26", "metadata": {}, "source": [ "### 🔗 Understand the Local Correlation Chart\n", @@ -370,7 +380,7 @@ { "cell_type": "code", "execution_count": null, - "id": "26", + "id": "27", "metadata": {}, "outputs": [], "source": [ @@ -379,7 +389,7 @@ }, { "cell_type": "markdown", - "id": "27", + "id": "28", "metadata": {}, "source": [ "The matrix shows only one triangular half because the other half would\n", @@ -404,7 +414,7 @@ }, { "cell_type": "markdown", - "id": "28", + "id": "29", "metadata": {}, "source": [ "### 🎲 Define the Sampling Region\n", @@ -423,7 +433,7 @@ }, { "cell_type": "markdown", - "id": "29", + "id": "30", "metadata": {}, "source": [ "📖 See\n", @@ -434,7 +444,7 @@ { "cell_type": "code", "execution_count": null, - "id": "30", + "id": "31", "metadata": {}, "outputs": [], "source": [ @@ -445,7 +455,7 @@ { "cell_type": "code", "execution_count": null, - "id": "31", + "id": "32", "metadata": {}, "outputs": [], "source": [ @@ -454,7 +464,7 @@ }, { "cell_type": "markdown", - "id": "32", + "id": "33", "metadata": {}, "source": [ "### 🎲 Run DREAM Sampling\n", @@ -471,7 +481,7 @@ }, { "cell_type": "markdown", - "id": "33", + "id": "34", "metadata": {}, "source": [ "📖 See\n", @@ -482,7 +492,7 @@ { "cell_type": "code", "execution_count": null, - "id": "34", + "id": "35", "metadata": {}, "outputs": [], "source": [ @@ -494,7 +504,7 @@ }, { "cell_type": "markdown", - "id": "35", + "id": "36", "metadata": {}, "source": [ "Burn-in samples allow the chains to move away from their initial\n", @@ -505,7 +515,7 @@ { "cell_type": "code", "execution_count": null, - "id": "36", + "id": "37", "metadata": {}, "outputs": [], "source": [ @@ -514,7 +524,7 @@ }, { "cell_type": "markdown", - "id": "37", + "id": "38", "metadata": {}, "source": [ "### 📋 Understand the Bayesian Fit Summary\n", @@ -537,7 +547,7 @@ { "cell_type": "code", "execution_count": null, - "id": "38", + "id": "39", "metadata": {}, "outputs": [], "source": [ @@ -546,7 +556,7 @@ }, { "cell_type": "markdown", - "id": "39", + "id": "40", "metadata": {}, "source": [ "A short teaching run may fail these convergence criteria even when it\n", @@ -557,7 +567,7 @@ }, { "cell_type": "markdown", - "id": "40", + "id": "41", "metadata": {}, "source": [ "### 🔗 Understand Posterior Correlations\n", @@ -571,7 +581,7 @@ { "cell_type": "code", "execution_count": null, - "id": "41", + "id": "42", "metadata": {}, "outputs": [], "source": [ @@ -580,7 +590,7 @@ }, { "cell_type": "markdown", - "id": "42", + "id": "43", "metadata": {}, "source": [ "Compare this chart with the local chart above. Similar coefficients\n", @@ -592,7 +602,7 @@ }, { "cell_type": "markdown", - "id": "43", + "id": "44", "metadata": {}, "source": [ "### 🗺️ Understand the Posterior Pair Plot\n", @@ -618,7 +628,7 @@ { "cell_type": "code", "execution_count": null, - "id": "44", + "id": "45", "metadata": {}, "outputs": [], "source": [ @@ -627,7 +637,7 @@ }, { "cell_type": "markdown", - "id": "45", + "id": "46", "metadata": {}, "source": [ "In Jupyter, the default plotting engine resolves to interactive Plotly.\n", @@ -647,7 +657,7 @@ }, { "cell_type": "markdown", - "id": "46", + "id": "47", "metadata": {}, "source": [ "### 📈 Understand Marginal Posterior Distributions\n", @@ -661,7 +671,7 @@ { "cell_type": "code", "execution_count": null, - "id": "47", + "id": "48", "metadata": {}, "outputs": [], "source": [ @@ -670,7 +680,7 @@ }, { "cell_type": "markdown", - "id": "48", + "id": "49", "metadata": {}, "source": [ "A density pressed against a fit bound warns that the allowed region may\n", @@ -682,7 +692,7 @@ }, { "cell_type": "markdown", - "id": "49", + "id": "50", "metadata": {}, "source": [ "### 📊 Understand the Posterior-Predictive Plot\n", @@ -696,7 +706,7 @@ { "cell_type": "code", "execution_count": null, - "id": "50", + "id": "51", "metadata": {}, "outputs": [], "source": [ @@ -706,7 +716,7 @@ { "cell_type": "code", "execution_count": null, - "id": "51", + "id": "52", "metadata": {}, "outputs": [], "source": [ @@ -719,7 +729,7 @@ }, { "cell_type": "markdown", - "id": "52", + "id": "53", "metadata": {}, "source": [ "Compare the width of the band with the experimental uncertainty and\n", @@ -734,7 +744,7 @@ }, { "cell_type": "markdown", - "id": "53", + "id": "54", "metadata": {}, "source": [ "Save the completed reference Bayesian project. Its MCMC chain and\n", @@ -745,7 +755,7 @@ { "cell_type": "code", "execution_count": null, - "id": "54", + "id": "55", "metadata": {}, "outputs": [], "source": [ @@ -754,7 +764,7 @@ }, { "cell_type": "markdown", - "id": "55", + "id": "56", "metadata": {}, "source": [ "## 💪 Exercise: Fix One Correlated Parameter\n", @@ -776,7 +786,7 @@ }, { "cell_type": "markdown", - "id": "56", + "id": "57", "metadata": {}, "source": [ "### 📂 Exercise 1: Create a Fresh Project\n", @@ -789,7 +799,7 @@ }, { "cell_type": "markdown", - "id": "57", + "id": "58", "metadata": {}, "source": [ "**Hint:**" @@ -797,7 +807,7 @@ }, { "cell_type": "markdown", - "id": "58", + "id": "59", "metadata": {}, "source": [ "Use `edi.Project.load()` with `refinement_project_dir`, which was\n", @@ -806,7 +816,7 @@ }, { "cell_type": "markdown", - "id": "59", + "id": "60", "metadata": {}, "source": [ "**Solution:**" @@ -815,7 +825,7 @@ { "cell_type": "code", "execution_count": null, - "id": "60", + "id": "61", "metadata": {}, "outputs": [], "source": [ @@ -829,7 +839,7 @@ }, { "cell_type": "markdown", - "id": "61", + "id": "62", "metadata": {}, "source": [ "### 🎯 Exercise 2: Reduce the Free-Parameter Set\n", @@ -842,7 +852,7 @@ }, { "cell_type": "markdown", - "id": "62", + "id": "63", "metadata": {}, "source": [ "**Hint:**" @@ -850,7 +860,7 @@ }, { "cell_type": "markdown", - "id": "63", + "id": "64", "metadata": {}, "source": [ "Iterate over `project_2.experiments['sim_lbco'].background` and set\n", @@ -859,7 +869,7 @@ }, { "cell_type": "markdown", - "id": "64", + "id": "65", "metadata": {}, "source": [ "**Solution:**" @@ -868,7 +878,7 @@ { "cell_type": "code", "execution_count": null, - "id": "65", + "id": "66", "metadata": {}, "outputs": [], "source": [ @@ -880,7 +890,7 @@ }, { "cell_type": "markdown", - "id": "66", + "id": "67", "metadata": {}, "source": [ "#### Exercise 2.2: Fix `broad_gauss_sigma_2`\n", @@ -891,7 +901,7 @@ }, { "cell_type": "markdown", - "id": "67", + "id": "68", "metadata": {}, "source": [ "**Hint:**" @@ -899,7 +909,7 @@ }, { "cell_type": "markdown", - "id": "68", + "id": "69", "metadata": {}, "source": [ "Set the parameter's `free` attribute to `False`, then call\n", @@ -908,7 +918,7 @@ }, { "cell_type": "markdown", - "id": "69", + "id": "70", "metadata": {}, "source": [ "**Solution:**" @@ -917,7 +927,7 @@ { "cell_type": "code", "execution_count": null, - "id": "70", + "id": "71", "metadata": {}, "outputs": [], "source": [ @@ -927,7 +937,7 @@ { "cell_type": "code", "execution_count": null, - "id": "71", + "id": "72", "metadata": {}, "outputs": [], "source": [ @@ -936,7 +946,7 @@ }, { "cell_type": "markdown", - "id": "72", + "id": "73", "metadata": {}, "source": [ "Six parameters remain. The background intensities and\n", @@ -946,7 +956,7 @@ }, { "cell_type": "markdown", - "id": "73", + "id": "74", "metadata": {}, "source": [ "### 🚀 Exercise 3: Repeat the Local Refinement\n", @@ -958,7 +968,7 @@ }, { "cell_type": "markdown", - "id": "74", + "id": "75", "metadata": {}, "source": [ "**Hint:**" @@ -966,7 +976,7 @@ }, { "cell_type": "markdown", - "id": "75", + "id": "76", "metadata": {}, "source": [ "Repeat the local-refinement sequence from the introduction with\n", @@ -975,7 +985,7 @@ }, { "cell_type": "markdown", - "id": "76", + "id": "77", "metadata": {}, "source": [ "**Solution:**" @@ -984,7 +994,17 @@ { "cell_type": "code", "execution_count": null, - "id": "77", + "id": "78", + "metadata": {}, + "outputs": [], + "source": [ + "project_2.analysis.minimizer.chi_square_change_tolerance = 1e-2" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "79", "metadata": {}, "outputs": [], "source": [ @@ -996,7 +1016,7 @@ { "cell_type": "code", "execution_count": null, - "id": "78", + "id": "80", "metadata": {}, "outputs": [], "source": [ @@ -1005,7 +1025,7 @@ }, { "cell_type": "markdown", - "id": "79", + "id": "81", "metadata": {}, "source": [ "The `broad_gauss_sigma_1`–`broad_gauss_sigma_2` pair is absent because\n", @@ -1016,7 +1036,7 @@ }, { "cell_type": "markdown", - "id": "80", + "id": "82", "metadata": {}, "source": [ "### 🎲 Exercise 4: Set New Sampling Bounds\n", @@ -1028,7 +1048,7 @@ }, { "cell_type": "markdown", - "id": "81", + "id": "83", "metadata": {}, "source": [ "**Hint:**" @@ -1036,7 +1056,7 @@ }, { "cell_type": "markdown", - "id": "82", + "id": "84", "metadata": {}, "source": [ "Iterate over `project_2.free_parameters` and call\n", @@ -1045,7 +1065,7 @@ }, { "cell_type": "markdown", - "id": "83", + "id": "85", "metadata": {}, "source": [ "**Solution:**" @@ -1054,7 +1074,7 @@ { "cell_type": "code", "execution_count": null, - "id": "84", + "id": "86", "metadata": {}, "outputs": [], "source": [ @@ -1065,7 +1085,7 @@ { "cell_type": "code", "execution_count": null, - "id": "85", + "id": "87", "metadata": {}, "outputs": [], "source": [ @@ -1074,7 +1094,7 @@ }, { "cell_type": "markdown", - "id": "86", + "id": "88", "metadata": {}, "source": [ "Fixing one correlated parameter changes the local covariance matrix and\n", @@ -1084,7 +1104,7 @@ }, { "cell_type": "markdown", - "id": "87", + "id": "89", "metadata": {}, "source": [ "### 🎲 Exercise 5: Repeat DREAM Sampling\n", @@ -1095,7 +1115,7 @@ }, { "cell_type": "markdown", - "id": "88", + "id": "90", "metadata": {}, "source": [ "**Hint:**" @@ -1103,7 +1123,7 @@ }, { "cell_type": "markdown", - "id": "89", + "id": "91", "metadata": {}, "source": [ "Use 300 sampling steps, 60 burn-in steps, and random seed 42 so the two\n", @@ -1112,7 +1132,7 @@ }, { "cell_type": "markdown", - "id": "90", + "id": "92", "metadata": {}, "source": [ "**Solution:**" @@ -1121,7 +1141,7 @@ { "cell_type": "code", "execution_count": null, - "id": "91", + "id": "93", "metadata": {}, "outputs": [], "source": [ @@ -1134,7 +1154,7 @@ { "cell_type": "code", "execution_count": null, - "id": "92", + "id": "94", "metadata": {}, "outputs": [], "source": [ @@ -1143,7 +1163,7 @@ }, { "cell_type": "markdown", - "id": "93", + "id": "95", "metadata": {}, "source": [ "### 📊 Exercise 6: Compare the Posterior Results\n", @@ -1157,7 +1177,7 @@ }, { "cell_type": "markdown", - "id": "94", + "id": "96", "metadata": {}, "source": [ "**Hint:**" @@ -1165,7 +1185,7 @@ }, { "cell_type": "markdown", - "id": "95", + "id": "97", "metadata": {}, "source": [ "Use `project_2.display.fit.results()`, then compare the diagnostics\n", @@ -1174,7 +1194,7 @@ }, { "cell_type": "markdown", - "id": "96", + "id": "98", "metadata": {}, "source": [ "**Solution:**" @@ -1183,7 +1203,7 @@ { "cell_type": "code", "execution_count": null, - "id": "97", + "id": "99", "metadata": {}, "outputs": [], "source": [ @@ -1192,7 +1212,7 @@ }, { "cell_type": "markdown", - "id": "98", + "id": "100", "metadata": {}, "source": [ "Reducing the dimension can make sampling easier, but a 300-step chain\n", @@ -1202,7 +1222,7 @@ }, { "cell_type": "markdown", - "id": "99", + "id": "101", "metadata": {}, "source": [ "#### Exercise 6.2: Inspect Correlations and Pair Relationships\n", @@ -1214,7 +1234,7 @@ }, { "cell_type": "markdown", - "id": "100", + "id": "102", "metadata": {}, "source": [ "**Hint:**" @@ -1222,7 +1242,7 @@ }, { "cell_type": "markdown", - "id": "101", + "id": "103", "metadata": {}, "source": [ "Use the same `display.fit.correlations(max_parameters=5)` and\n", @@ -1233,7 +1253,7 @@ }, { "cell_type": "markdown", - "id": "102", + "id": "104", "metadata": {}, "source": [ "**Solution:**" @@ -1242,7 +1262,7 @@ { "cell_type": "code", "execution_count": null, - "id": "103", + "id": "105", "metadata": {}, "outputs": [], "source": [ @@ -1252,7 +1272,7 @@ { "cell_type": "code", "execution_count": null, - "id": "104", + "id": "106", "metadata": {}, "outputs": [], "source": [ @@ -1261,7 +1281,7 @@ }, { "cell_type": "markdown", - "id": "105", + "id": "107", "metadata": {}, "source": [ "The original pair is gone because only sampled parameters appear in\n", @@ -1273,7 +1293,7 @@ }, { "cell_type": "markdown", - "id": "106", + "id": "108", "metadata": {}, "source": [ "#### Exercise 6.3: Compare `broad_gauss_sigma_1`\n", @@ -1285,7 +1305,7 @@ }, { "cell_type": "markdown", - "id": "107", + "id": "109", "metadata": {}, "source": [ "**Hint:**" @@ -1293,7 +1313,7 @@ }, { "cell_type": "markdown", - "id": "108", + "id": "110", "metadata": {}, "source": [ "Call `display.posterior.distribution()` for each project and pass the\n", @@ -1302,7 +1322,7 @@ }, { "cell_type": "markdown", - "id": "109", + "id": "111", "metadata": {}, "source": [ "**Solution:**" @@ -1311,7 +1331,7 @@ { "cell_type": "code", "execution_count": null, - "id": "110", + "id": "112", "metadata": {}, "outputs": [], "source": [ @@ -1323,7 +1343,7 @@ { "cell_type": "code", "execution_count": null, - "id": "111", + "id": "113", "metadata": {}, "outputs": [], "source": [ @@ -1334,7 +1354,7 @@ }, { "cell_type": "markdown", - "id": "112", + "id": "114", "metadata": {}, "source": [ "The conditional posterior can be narrower because\n", @@ -1346,7 +1366,7 @@ }, { "cell_type": "markdown", - "id": "113", + "id": "115", "metadata": {}, "source": [ "#### Exercise 6.4: Compare Posterior Predictions\n", @@ -1358,7 +1378,7 @@ }, { "cell_type": "markdown", - "id": "114", + "id": "116", "metadata": {}, "source": [ "**Hint:**" @@ -1366,7 +1386,7 @@ }, { "cell_type": "markdown", - "id": "115", + "id": "117", "metadata": {}, "source": [ "Call `display.posterior.predictive()` for both projects with the same\n", @@ -1375,7 +1395,7 @@ }, { "cell_type": "markdown", - "id": "116", + "id": "118", "metadata": {}, "source": [ "**Solution:**" @@ -1384,7 +1404,7 @@ { "cell_type": "code", "execution_count": null, - "id": "117", + "id": "119", "metadata": {}, "outputs": [], "source": [ @@ -1398,7 +1418,7 @@ { "cell_type": "code", "execution_count": null, - "id": "118", + "id": "120", "metadata": {}, "outputs": [], "source": [ @@ -1411,7 +1431,7 @@ }, { "cell_type": "markdown", - "id": "119", + "id": "121", "metadata": {}, "source": [ "Two parameterizations can produce similarly good calculated patterns\n", @@ -1422,7 +1442,7 @@ }, { "cell_type": "markdown", - "id": "120", + "id": "122", "metadata": {}, "source": [ "### 💾 Exercise 7: Save the Project\n", @@ -1432,7 +1452,7 @@ }, { "cell_type": "markdown", - "id": "121", + "id": "123", "metadata": {}, "source": [ "**Hint:**" @@ -1440,7 +1460,7 @@ }, { "cell_type": "markdown", - "id": "122", + "id": "124", "metadata": {}, "source": [ "The project directory was set in Exercise 1, so use `project_2.save()`\n", @@ -1449,7 +1469,7 @@ }, { "cell_type": "markdown", - "id": "123", + "id": "125", "metadata": {}, "source": [ "**Solution:**" @@ -1458,7 +1478,7 @@ { "cell_type": "code", "execution_count": null, - "id": "124", + "id": "126", "metadata": {}, "outputs": [], "source": [ @@ -1467,7 +1487,7 @@ }, { "cell_type": "markdown", - "id": "125", + "id": "127", "metadata": {}, "source": [ "#### Final Remarks\n", @@ -1493,7 +1513,7 @@ }, { "cell_type": "markdown", - "id": "126", + "id": "128", "metadata": {}, "source": [ "## 🎁 Bonus\n", diff --git a/docs/docs/tutorials/exercise-bayesian-si-lbco.py b/docs/docs/tutorials/exercise-bayesian-si-lbco.py index fa1170638..1ad0e4b97 100644 --- a/docs/docs/tutorials/exercise-bayesian-si-lbco.py +++ b/docs/docs/tutorials/exercise-bayesian-si-lbco.py @@ -176,6 +176,9 @@ # %% project_1.analysis.minimizer.type = 'bumps (lm)' +# %% +project_1.analysis.minimizer.chi_square_change_tolerance = 1e-2 + # %% project_1.analysis.fit() project_1.display.fit.results() @@ -524,6 +527,9 @@ # %% [markdown] # **Solution:** +# %% tags=["solution", "hide-input"] +project_2.analysis.minimizer.chi_square_change_tolerance = 1e-2 + # %% tags=["solution", "hide-input"] project_2.analysis.minimizer.type = 'bumps (lm)' project_2.analysis.fit() diff --git a/docs/docs/tutorials/exercise-refine-si-lbco.ipynb b/docs/docs/tutorials/exercise-refine-si-lbco.ipynb index b61b25a17..b0752bf1f 100644 --- a/docs/docs/tutorials/exercise-refine-si-lbco.ipynb +++ b/docs/docs/tutorials/exercise-refine-si-lbco.ipynb @@ -1078,6 +1078,16 @@ "id": "75", "metadata": {}, "outputs": [], + "source": [ + "project_1.analysis.minimizer.chi_square_change_tolerance = 1e-2" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "76", + "metadata": {}, + "outputs": [], "source": [ "project_1.analysis.fit()\n", "project_1.display.fit.results()" @@ -1085,7 +1095,7 @@ }, { "cell_type": "markdown", - "id": "76", + "id": "77", "metadata": {}, "source": [ "#### Display Fit Results\n", @@ -1104,7 +1114,7 @@ }, { "cell_type": "markdown", - "id": "77", + "id": "78", "metadata": {}, "source": [ "#### Display the Fitted Pattern\n", @@ -1118,7 +1128,7 @@ { "cell_type": "code", "execution_count": null, - "id": "78", + "id": "79", "metadata": {}, "outputs": [], "source": [ @@ -1127,7 +1137,7 @@ }, { "cell_type": "markdown", - "id": "79", + "id": "80", "metadata": {}, "source": [ "#### TOF vs d-spacing\n", @@ -1161,7 +1171,7 @@ { "cell_type": "code", "execution_count": null, - "id": "80", + "id": "81", "metadata": {}, "outputs": [], "source": [ @@ -1170,7 +1180,7 @@ }, { "cell_type": "markdown", - "id": "81", + "id": "82", "metadata": {}, "source": [ "As you can see, the calculated diffraction pattern now matches the\n", @@ -1196,7 +1206,7 @@ { "cell_type": "code", "execution_count": null, - "id": "82", + "id": "83", "metadata": {}, "outputs": [], "source": [ @@ -1205,7 +1215,7 @@ }, { "cell_type": "markdown", - "id": "83", + "id": "84", "metadata": {}, "source": [ "## 💪 Exercise: Complex Fit – LBCO\n", @@ -1226,7 +1236,7 @@ }, { "cell_type": "markdown", - "id": "84", + "id": "85", "metadata": {}, "source": [ "**Hint:**" @@ -1234,7 +1244,7 @@ }, { "cell_type": "markdown", - "id": "85", + "id": "86", "metadata": {}, "source": [ "You can use the same approach as in the previous part of the notebook,\n", @@ -1243,7 +1253,7 @@ }, { "cell_type": "markdown", - "id": "86", + "id": "87", "metadata": {}, "source": [ "**Solution:**" @@ -1252,7 +1262,7 @@ { "cell_type": "code", "execution_count": null, - "id": "87", + "id": "88", "metadata": {}, "outputs": [], "source": [ @@ -1265,7 +1275,7 @@ }, { "cell_type": "markdown", - "id": "88", + "id": "89", "metadata": {}, "source": [ "### 🔬 Exercise 2: Define an Experiment\n", @@ -1278,7 +1288,7 @@ }, { "cell_type": "markdown", - "id": "89", + "id": "90", "metadata": {}, "source": [ "**Hint:**" @@ -1286,7 +1296,7 @@ }, { "cell_type": "markdown", - "id": "90", + "id": "91", "metadata": {}, "source": [ "You can use the same approach as in the previous part of the notebook,\n", @@ -1295,7 +1305,7 @@ }, { "cell_type": "markdown", - "id": "91", + "id": "92", "metadata": {}, "source": [ "**Solution:**" @@ -1304,7 +1314,7 @@ { "cell_type": "code", "execution_count": null, - "id": "92", + "id": "93", "metadata": {}, "outputs": [], "source": [ @@ -1327,7 +1337,7 @@ }, { "cell_type": "markdown", - "id": "93", + "id": "94", "metadata": {}, "source": [ "#### Exercise 2.2: Inspect Measured Data\n", @@ -1339,7 +1349,7 @@ }, { "cell_type": "markdown", - "id": "94", + "id": "95", "metadata": {}, "source": [ "**Hint:**" @@ -1347,7 +1357,7 @@ }, { "cell_type": "markdown", - "id": "95", + "id": "96", "metadata": {}, "source": [ "Use the `pattern` method of the project's `display` facade to visualize\n", @@ -1358,7 +1368,7 @@ }, { "cell_type": "markdown", - "id": "96", + "id": "97", "metadata": {}, "source": [ "**Solution:**" @@ -1367,7 +1377,7 @@ { "cell_type": "code", "execution_count": null, - "id": "97", + "id": "98", "metadata": {}, "outputs": [], "source": [ @@ -1381,7 +1391,7 @@ }, { "cell_type": "markdown", - "id": "98", + "id": "99", "metadata": {}, "source": [ "#### Exercise 2.3: Set Instrument\n", @@ -1391,7 +1401,7 @@ }, { "cell_type": "markdown", - "id": "99", + "id": "100", "metadata": {}, "source": [ "**Hint:**" @@ -1399,7 +1409,7 @@ }, { "cell_type": "markdown", - "id": "100", + "id": "101", "metadata": {}, "source": [ "Use the values from the data reduction process for the LBCO and\n", @@ -1408,7 +1418,7 @@ }, { "cell_type": "markdown", - "id": "101", + "id": "102", "metadata": {}, "source": [ "**Solution:**" @@ -1417,7 +1427,7 @@ { "cell_type": "code", "execution_count": null, - "id": "102", + "id": "103", "metadata": {}, "outputs": [], "source": [ @@ -1431,7 +1441,7 @@ }, { "cell_type": "markdown", - "id": "103", + "id": "104", "metadata": {}, "source": [ "#### Exercise 2.4: Set Peak Profile\n", @@ -1441,7 +1451,7 @@ }, { "cell_type": "markdown", - "id": "104", + "id": "105", "metadata": {}, "source": [ "**Hint:**" @@ -1449,7 +1459,7 @@ }, { "cell_type": "markdown", - "id": "105", + "id": "106", "metadata": {}, "source": [ "Use the values from the\n", @@ -1461,7 +1471,7 @@ }, { "cell_type": "markdown", - "id": "106", + "id": "107", "metadata": {}, "source": [ "**Solution:**" @@ -1470,7 +1480,7 @@ { "cell_type": "code", "execution_count": null, - "id": "107", + "id": "108", "metadata": {}, "outputs": [], "source": [ @@ -1489,7 +1499,7 @@ }, { "cell_type": "markdown", - "id": "108", + "id": "109", "metadata": {}, "source": [ "#### Exercise 2.5: Set Background\n", @@ -1500,7 +1510,7 @@ }, { "cell_type": "markdown", - "id": "109", + "id": "110", "metadata": {}, "source": [ "**Hint:**" @@ -1508,7 +1518,7 @@ }, { "cell_type": "markdown", - "id": "110", + "id": "111", "metadata": {}, "source": [ "Use the same approach as in the previous part of the notebook, but\n", @@ -1519,7 +1529,7 @@ }, { "cell_type": "markdown", - "id": "111", + "id": "112", "metadata": {}, "source": [ "**Solution:**" @@ -1528,7 +1538,7 @@ { "cell_type": "code", "execution_count": null, - "id": "112", + "id": "113", "metadata": {}, "outputs": [], "source": [ @@ -1543,7 +1553,7 @@ }, { "cell_type": "markdown", - "id": "113", + "id": "114", "metadata": {}, "source": [ "### 🧩 Exercise 3: Define a Structure – LBCO\n", @@ -1559,7 +1569,7 @@ }, { "cell_type": "markdown", - "id": "114", + "id": "115", "metadata": {}, "source": [ "```\n", @@ -1593,7 +1603,7 @@ }, { "cell_type": "markdown", - "id": "115", + "id": "116", "metadata": {}, "source": [ "Note that the `occupancy` of the La and Ba atoms is 0.5\n", @@ -1623,7 +1633,7 @@ }, { "cell_type": "markdown", - "id": "116", + "id": "117", "metadata": {}, "source": [ "#### Exercise 3.1: Create Structure\n", @@ -1634,7 +1644,7 @@ }, { "cell_type": "markdown", - "id": "117", + "id": "118", "metadata": {}, "source": [ "**Hint:**" @@ -1642,7 +1652,7 @@ }, { "cell_type": "markdown", - "id": "118", + "id": "119", "metadata": {}, "source": [ "You can use the same approach as in the previous part of the notebook,\n", @@ -1652,7 +1662,7 @@ }, { "cell_type": "markdown", - "id": "119", + "id": "120", "metadata": {}, "source": [ "**Solution:**" @@ -1661,7 +1671,7 @@ { "cell_type": "code", "execution_count": null, - "id": "120", + "id": "121", "metadata": {}, "outputs": [], "source": [ @@ -1670,7 +1680,7 @@ }, { "cell_type": "markdown", - "id": "121", + "id": "122", "metadata": {}, "source": [ "#### Exercise 3.2: Set Space Group\n", @@ -1680,7 +1690,7 @@ }, { "cell_type": "markdown", - "id": "122", + "id": "123", "metadata": {}, "source": [ "**Hint:**" @@ -1688,7 +1698,7 @@ }, { "cell_type": "markdown", - "id": "123", + "id": "124", "metadata": {}, "source": [ "Use the space group name and IT coordinate system code from the CIF\n", @@ -1697,7 +1707,7 @@ }, { "cell_type": "markdown", - "id": "124", + "id": "125", "metadata": {}, "source": [ "**Solution:**" @@ -1706,7 +1716,7 @@ { "cell_type": "code", "execution_count": null, - "id": "125", + "id": "126", "metadata": {}, "outputs": [], "source": [ @@ -1716,7 +1726,7 @@ }, { "cell_type": "markdown", - "id": "126", + "id": "127", "metadata": {}, "source": [ "#### Exercise 3.3: Set Unit Cell\n", @@ -1726,7 +1736,7 @@ }, { "cell_type": "markdown", - "id": "127", + "id": "128", "metadata": {}, "source": [ "**Hint:**" @@ -1734,7 +1744,7 @@ }, { "cell_type": "markdown", - "id": "128", + "id": "129", "metadata": {}, "source": [ "Use the lattice parameters from the CIF data." @@ -1742,7 +1752,7 @@ }, { "cell_type": "markdown", - "id": "129", + "id": "130", "metadata": {}, "source": [ "**Solution:**" @@ -1751,7 +1761,7 @@ { "cell_type": "code", "execution_count": null, - "id": "130", + "id": "131", "metadata": {}, "outputs": [], "source": [ @@ -1760,7 +1770,7 @@ }, { "cell_type": "markdown", - "id": "131", + "id": "132", "metadata": {}, "source": [ "#### Exercise 3.4: Set Atom Sites\n", @@ -1770,7 +1780,7 @@ }, { "cell_type": "markdown", - "id": "132", + "id": "133", "metadata": {}, "source": [ "**Hint:**" @@ -1778,7 +1788,7 @@ }, { "cell_type": "markdown", - "id": "133", + "id": "134", "metadata": {}, "source": [ "Use the atom sites from the CIF data. Call the `create` method of the\n", @@ -1787,7 +1797,7 @@ }, { "cell_type": "markdown", - "id": "134", + "id": "135", "metadata": {}, "source": [ "**Solution:**" @@ -1796,7 +1806,7 @@ { "cell_type": "code", "execution_count": null, - "id": "135", + "id": "136", "metadata": {}, "outputs": [], "source": [ @@ -1842,7 +1852,7 @@ }, { "cell_type": "markdown", - "id": "136", + "id": "137", "metadata": {}, "source": [ "#### Display Structure\n", @@ -1854,7 +1864,7 @@ { "cell_type": "code", "execution_count": null, - "id": "137", + "id": "138", "metadata": {}, "outputs": [], "source": [ @@ -1863,7 +1873,7 @@ }, { "cell_type": "markdown", - "id": "138", + "id": "139", "metadata": {}, "source": [ "### 🔗 Exercise 4: Assign Structure to Experiment\n", @@ -1873,7 +1883,7 @@ }, { "cell_type": "markdown", - "id": "139", + "id": "140", "metadata": {}, "source": [ "**Hint:**" @@ -1881,7 +1891,7 @@ }, { "cell_type": "markdown", - "id": "140", + "id": "141", "metadata": {}, "source": [ "Use the `linked_structures` attribute of the experiment to link the\n", @@ -1890,7 +1900,7 @@ }, { "cell_type": "markdown", - "id": "141", + "id": "142", "metadata": {}, "source": [ "**Solution:**" @@ -1899,7 +1909,7 @@ { "cell_type": "code", "execution_count": null, - "id": "142", + "id": "143", "metadata": {}, "outputs": [], "source": [ @@ -1908,7 +1918,7 @@ }, { "cell_type": "markdown", - "id": "143", + "id": "144", "metadata": {}, "source": [ "### 🚀 Exercise 5: Analyze and Fit the Data\n", @@ -1921,7 +1931,7 @@ }, { "cell_type": "markdown", - "id": "144", + "id": "145", "metadata": {}, "source": [ "**Hint:**" @@ -1929,7 +1939,7 @@ }, { "cell_type": "markdown", - "id": "145", + "id": "146", "metadata": {}, "source": [ "You can start with the scale factor and the background points, as in\n", @@ -1938,7 +1948,7 @@ }, { "cell_type": "markdown", - "id": "146", + "id": "147", "metadata": {}, "source": [ "**Solution:**" @@ -1947,7 +1957,7 @@ { "cell_type": "code", "execution_count": null, - "id": "147", + "id": "148", "metadata": {}, "outputs": [], "source": [ @@ -1959,7 +1969,7 @@ }, { "cell_type": "markdown", - "id": "148", + "id": "149", "metadata": {}, "source": [ "#### Exercise 5.2: Run Fitting\n", @@ -1970,7 +1980,7 @@ }, { "cell_type": "markdown", - "id": "149", + "id": "150", "metadata": {}, "source": [ "**Hint:**" @@ -1978,7 +1988,7 @@ }, { "cell_type": "markdown", - "id": "150", + "id": "151", "metadata": {}, "source": [ "Use the `pattern` method of the project's `display` facade to\n", @@ -1989,7 +1999,7 @@ }, { "cell_type": "markdown", - "id": "151", + "id": "152", "metadata": {}, "source": [ "**Solution:**" @@ -1998,7 +2008,17 @@ { "cell_type": "code", "execution_count": null, - "id": "152", + "id": "153", + "metadata": {}, + "outputs": [], + "source": [ + "project_2.analysis.minimizer.chi_square_change_tolerance = 1e-2" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "154", "metadata": {}, "outputs": [], "source": [ @@ -2010,7 +2030,7 @@ }, { "cell_type": "markdown", - "id": "153", + "id": "155", "metadata": {}, "source": [ "#### Exercise 5.3: Find the Misfit in the Fit\n", @@ -2025,7 +2045,7 @@ }, { "cell_type": "markdown", - "id": "154", + "id": "156", "metadata": {}, "source": [ "**Hint:**" @@ -2033,7 +2053,7 @@ }, { "cell_type": "markdown", - "id": "155", + "id": "157", "metadata": {}, "source": [ "Consider the following options:\n", @@ -2045,7 +2065,7 @@ }, { "cell_type": "markdown", - "id": "156", + "id": "158", "metadata": {}, "source": [ "**Solution:**" @@ -2053,7 +2073,7 @@ }, { "cell_type": "markdown", - "id": "157", + "id": "159", "metadata": {}, "source": [ "\n", @@ -2075,7 +2095,7 @@ { "cell_type": "code", "execution_count": null, - "id": "158", + "id": "160", "metadata": {}, "outputs": [], "source": [ @@ -2084,7 +2104,7 @@ }, { "cell_type": "markdown", - "id": "159", + "id": "161", "metadata": {}, "source": [ "#### Exercise 5.4: Refine the LBCO Lattice Parameter\n", @@ -2094,7 +2114,7 @@ }, { "cell_type": "markdown", - "id": "160", + "id": "162", "metadata": {}, "source": [ "**Hint:**" @@ -2102,7 +2122,7 @@ }, { "cell_type": "markdown", - "id": "161", + "id": "163", "metadata": {}, "source": [ "To achieve this, we will set the `free` attribute of the `length_a`\n", @@ -2117,7 +2137,7 @@ }, { "cell_type": "markdown", - "id": "162", + "id": "164", "metadata": {}, "source": [ "**Solution:**" @@ -2126,7 +2146,7 @@ { "cell_type": "code", "execution_count": null, - "id": "163", + "id": "165", "metadata": {}, "outputs": [], "source": [ @@ -2140,7 +2160,7 @@ }, { "cell_type": "markdown", - "id": "164", + "id": "166", "metadata": {}, "source": [ "One of the main goals of this study was to refine the lattice\n", @@ -2153,7 +2173,7 @@ }, { "cell_type": "markdown", - "id": "165", + "id": "167", "metadata": {}, "source": [ "#### Exercise 5.5: Display Fit Results (d-spacing)\n", @@ -2164,7 +2184,7 @@ }, { "cell_type": "markdown", - "id": "166", + "id": "168", "metadata": {}, "source": [ "**Hint:**" @@ -2172,7 +2192,7 @@ }, { "cell_type": "markdown", - "id": "167", + "id": "169", "metadata": {}, "source": [ "Use the `pattern` method of the project's `display` facade and set\n", @@ -2181,7 +2201,7 @@ }, { "cell_type": "markdown", - "id": "168", + "id": "170", "metadata": {}, "source": [ "**Solution:**" @@ -2190,7 +2210,7 @@ { "cell_type": "code", "execution_count": null, - "id": "169", + "id": "171", "metadata": {}, "outputs": [], "source": [ @@ -2199,7 +2219,7 @@ }, { "cell_type": "markdown", - "id": "170", + "id": "172", "metadata": {}, "source": [ "#### Exercise 5.6: Refine the Peak Profile Parameters\n", @@ -2228,7 +2248,7 @@ { "cell_type": "code", "execution_count": null, - "id": "171", + "id": "173", "metadata": {}, "outputs": [], "source": [ @@ -2237,7 +2257,7 @@ }, { "cell_type": "markdown", - "id": "172", + "id": "174", "metadata": {}, "source": [ "The measured pattern does not contain enough information to refine all\n", @@ -2249,7 +2269,7 @@ }, { "cell_type": "markdown", - "id": "173", + "id": "175", "metadata": {}, "source": [ "**Hint:**" @@ -2257,7 +2277,7 @@ }, { "cell_type": "markdown", - "id": "174", + "id": "176", "metadata": {}, "source": [ "You can set the `free` attribute of the peak profile parameters to\n", @@ -2268,7 +2288,7 @@ }, { "cell_type": "markdown", - "id": "175", + "id": "177", "metadata": {}, "source": [ "**Solution:**" @@ -2277,7 +2297,7 @@ { "cell_type": "code", "execution_count": null, - "id": "176", + "id": "178", "metadata": {}, "outputs": [], "source": [ @@ -2294,7 +2314,7 @@ }, { "cell_type": "markdown", - "id": "177", + "id": "179", "metadata": {}, "source": [ "#### Exercise 5.7: Find Undefined Features\n", @@ -2306,7 +2326,7 @@ }, { "cell_type": "markdown", - "id": "178", + "id": "180", "metadata": {}, "source": [ "**Hint:**" @@ -2314,7 +2334,7 @@ }, { "cell_type": "markdown", - "id": "179", + "id": "181", "metadata": {}, "source": [ "While the fit is now significantly better, there are still some\n", @@ -2326,7 +2346,7 @@ }, { "cell_type": "markdown", - "id": "180", + "id": "182", "metadata": {}, "source": [ "**Solution:**" @@ -2335,7 +2355,7 @@ { "cell_type": "code", "execution_count": null, - "id": "181", + "id": "183", "metadata": {}, "outputs": [], "source": [ @@ -2344,7 +2364,7 @@ }, { "cell_type": "markdown", - "id": "182", + "id": "184", "metadata": {}, "source": [ "#### Exercise 5.8: Identify the Cause of the Unexplained Peaks\n", @@ -2357,7 +2377,7 @@ }, { "cell_type": "markdown", - "id": "183", + "id": "185", "metadata": {}, "source": [ "**Hint:**" @@ -2365,7 +2385,7 @@ }, { "cell_type": "markdown", - "id": "184", + "id": "186", "metadata": {}, "source": [ "Consider the following options:\n", @@ -2377,7 +2397,7 @@ }, { "cell_type": "markdown", - "id": "185", + "id": "187", "metadata": {}, "source": [ "**Solution:**" @@ -2385,7 +2405,7 @@ }, { "cell_type": "markdown", - "id": "186", + "id": "188", "metadata": {}, "source": [ "1. ❌ In principle, this could be the case, as sometimes the presence\n", @@ -2405,7 +2425,7 @@ }, { "cell_type": "markdown", - "id": "187", + "id": "189", "metadata": {}, "source": [ "#### Exercise 5.9: Identify the Impurity Phase\n", @@ -2416,7 +2436,7 @@ }, { "cell_type": "markdown", - "id": "188", + "id": "190", "metadata": {}, "source": [ "**Hint:**" @@ -2424,7 +2444,7 @@ }, { "cell_type": "markdown", - "id": "189", + "id": "191", "metadata": {}, "source": [ "Check the positions of the unexplained peaks in the diffraction\n", @@ -2434,7 +2454,7 @@ }, { "cell_type": "markdown", - "id": "190", + "id": "192", "metadata": {}, "source": [ "**Solution:**" @@ -2442,7 +2462,7 @@ }, { "cell_type": "markdown", - "id": "191", + "id": "193", "metadata": {}, "source": [ "The unexplained peaks are likely due to the presence of a small amount\n", @@ -2457,7 +2477,7 @@ { "cell_type": "code", "execution_count": null, - "id": "192", + "id": "194", "metadata": {}, "outputs": [], "source": [ @@ -2467,7 +2487,7 @@ }, { "cell_type": "markdown", - "id": "193", + "id": "195", "metadata": {}, "source": [ "#### Exercise 5.10: Create a Second Structure – Si as an Impurity\n", @@ -2479,7 +2499,7 @@ }, { "cell_type": "markdown", - "id": "194", + "id": "196", "metadata": {}, "source": [ "**Hint:**" @@ -2487,7 +2507,7 @@ }, { "cell_type": "markdown", - "id": "195", + "id": "197", "metadata": {}, "source": [ "You can use the same approach as in the previous part of the notebook,\n", @@ -2497,7 +2517,7 @@ }, { "cell_type": "markdown", - "id": "196", + "id": "198", "metadata": {}, "source": [ "**Solution:**" @@ -2506,7 +2526,7 @@ { "cell_type": "code", "execution_count": null, - "id": "197", + "id": "199", "metadata": {}, "outputs": [], "source": [ @@ -2535,7 +2555,7 @@ }, { "cell_type": "markdown", - "id": "198", + "id": "200", "metadata": {}, "source": [ "#### Exercise 5.11: Refine the Scale of the Si Phase\n", @@ -2548,7 +2568,7 @@ }, { "cell_type": "markdown", - "id": "199", + "id": "201", "metadata": {}, "source": [ "**Hint:**" @@ -2556,7 +2576,7 @@ }, { "cell_type": "markdown", - "id": "200", + "id": "202", "metadata": {}, "source": [ "You can use the `pattern` method of the project's `display` facade to\n", @@ -2567,7 +2587,7 @@ }, { "cell_type": "markdown", - "id": "201", + "id": "203", "metadata": {}, "source": [ "**Solution:**" @@ -2576,7 +2596,7 @@ { "cell_type": "code", "execution_count": null, - "id": "202", + "id": "204", "metadata": {}, "outputs": [], "source": [ @@ -2605,7 +2625,7 @@ }, { "cell_type": "markdown", - "id": "203", + "id": "205", "metadata": {}, "source": [ "All previously unexplained peaks are now accounted for in the pattern,\n", @@ -2628,7 +2648,7 @@ }, { "cell_type": "markdown", - "id": "204", + "id": "206", "metadata": {}, "source": [ "Finally, we save the project to disk to preserve the current state of\n", @@ -2638,7 +2658,7 @@ { "cell_type": "code", "execution_count": null, - "id": "205", + "id": "207", "metadata": {}, "outputs": [], "source": [ @@ -2647,7 +2667,7 @@ }, { "cell_type": "markdown", - "id": "206", + "id": "208", "metadata": {}, "source": [ "#### Final Remarks\n", @@ -2666,7 +2686,7 @@ }, { "cell_type": "markdown", - "id": "207", + "id": "209", "metadata": {}, "source": [ "## 🎁 Bonus\n", diff --git a/docs/docs/tutorials/exercise-refine-si-lbco.py b/docs/docs/tutorials/exercise-refine-si-lbco.py index 7ed52bf4e..1e63b7493 100644 --- a/docs/docs/tutorials/exercise-refine-si-lbco.py +++ b/docs/docs/tutorials/exercise-refine-si-lbco.py @@ -631,6 +631,9 @@ # [documentation](https://docs.easydiffraction.org/lib/latest/user-guide/analysis-workflow/analysis/#perform-fit) # for more details about the fitting process. +# %% +project_1.analysis.minimizer.chi_square_change_tolerance = 1e-2 + # %% project_1.analysis.fit() project_1.display.fit.results() @@ -1135,6 +1138,9 @@ # %% [markdown] # **Solution:** +# %% tags=["solution", "hide-input"] +project_2.analysis.minimizer.chi_square_change_tolerance = 1e-2 + # %% tags=["solution", "hide-input"] project_2.display.pattern(expt_name='sim_lbco') diff --git a/docs/docs/tutorials/index.json b/docs/docs/tutorials/index.json index 162496dd6..50ac09432 100644 --- a/docs/docs/tutorials/index.json +++ b/docs/docs/tutorials/index.json @@ -254,5 +254,13 @@ "title": "Structure Refinement: LMO, ECHIDNA", "description": "Rietveld refinement of an LMO structure with constrained Li/Ni site mixing using constant wavelength neutron powder diffraction data from ECHIDNA at ANSTO", "level": "intermediate" + }, + "refine-yap-3k": { + "order": 33, + "url": "https://easyscience.github.io/diffraction-lib/{version}/tutorials/refine-yap-3k/refine-yap-3k.ipynb", + "original_name": "yap_3k", + "title": "Structure Refinement: YAlO3+Al2O3, SPODI", + "description": "Staged two-phase Rietveld refinement of YAlO3 with an Al2O3 impurity using constant wavelength neutron powder diffraction data measured at 3 K on SPODI at MLZ", + "level": "intermediate" } } diff --git a/docs/docs/tutorials/index.md b/docs/docs/tutorials/index.md index 4ea3a395d..354e18c92 100644 --- a/docs/docs/tutorials/index.md +++ b/docs/docs/tutorials/index.md @@ -66,6 +66,10 @@ The tutorials are organized into the following categories: Rietveld refinement of an LMO structure with constrained Li/Ni site mixing using constant wavelength neutron powder diffraction data from ECHIDNA at ANSTO. +- [YAlO3+Al2O3 `pd-neut-cwl`](refine-yap-3k.ipynb) – Demonstrates a + staged two-phase Rietveld refinement of YAlO3 with an Al2O3 impurity + using constant wavelength neutron powder diffraction data measured at + 3 K on SPODI at MLZ. ## Without Measured Data diff --git a/docs/docs/tutorials/joint-si-bragg-pdf.ipynb b/docs/docs/tutorials/joint-si-bragg-pdf.ipynb index aac5d8c73..dde913849 100644 --- a/docs/docs/tutorials/joint-si-bragg-pdf.ipynb +++ b/docs/docs/tutorials/joint-si-bragg-pdf.ipynb @@ -608,6 +608,16 @@ "id": "55", "metadata": {}, "outputs": [], + "source": [ + "project.analysis.minimizer.chi_square_change_tolerance = 1e-2" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "56", + "metadata": {}, + "outputs": [], "source": [ "project.analysis.fit()\n", "project.display.fit.results()\n", @@ -616,7 +626,7 @@ }, { "cell_type": "markdown", - "id": "56", + "id": "57", "metadata": {}, "source": [ "### Display Pattern (After Fit)" @@ -625,7 +635,7 @@ { "cell_type": "code", "execution_count": null, - "id": "57", + "id": "58", "metadata": {}, "outputs": [], "source": [ @@ -635,7 +645,7 @@ { "cell_type": "code", "execution_count": null, - "id": "58", + "id": "59", "metadata": {}, "outputs": [], "source": [ @@ -644,7 +654,7 @@ }, { "cell_type": "markdown", - "id": "59", + "id": "60", "metadata": {}, "source": [ "## 💾 Save Project" @@ -653,7 +663,7 @@ { "cell_type": "code", "execution_count": null, - "id": "60", + "id": "61", "metadata": {}, "outputs": [], "source": [ diff --git a/docs/docs/tutorials/joint-si-bragg-pdf.py b/docs/docs/tutorials/joint-si-bragg-pdf.py index 7e1830528..52fff621c 100644 --- a/docs/docs/tutorials/joint-si-bragg-pdf.py +++ b/docs/docs/tutorials/joint-si-bragg-pdf.py @@ -257,6 +257,9 @@ # %% project.analysis.minimizer.type = 'bumps (lm)' +# %% +project.analysis.minimizer.chi_square_change_tolerance = 1e-2 + # %% project.analysis.fit() project.display.fit.results() diff --git a/docs/docs/tutorials/load-and-fit-lbco-hrpt.ipynb b/docs/docs/tutorials/load-and-fit-lbco-hrpt.ipynb index 7e65da9ed..58060a294 100644 --- a/docs/docs/tutorials/load-and-fit-lbco-hrpt.ipynb +++ b/docs/docs/tutorials/load-and-fit-lbco-hrpt.ipynb @@ -161,13 +161,23 @@ "id": "15", "metadata": {}, "outputs": [], + "source": [ + "project.analysis.minimizer.chi_square_change_tolerance = 1e-2" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "16", + "metadata": {}, + "outputs": [], "source": [ "project.analysis.fit()" ] }, { "cell_type": "markdown", - "id": "16", + "id": "17", "metadata": {}, "source": [ "### Display Fit Results" @@ -176,7 +186,7 @@ { "cell_type": "code", "execution_count": null, - "id": "17", + "id": "18", "metadata": {}, "outputs": [], "source": [ @@ -186,7 +196,7 @@ { "cell_type": "code", "execution_count": null, - "id": "18", + "id": "19", "metadata": {}, "outputs": [], "source": [ @@ -196,7 +206,7 @@ { "cell_type": "code", "execution_count": null, - "id": "19", + "id": "20", "metadata": {}, "outputs": [], "source": [ @@ -205,7 +215,7 @@ }, { "cell_type": "markdown", - "id": "20", + "id": "21", "metadata": {}, "source": [ "## 💾 Save Project" @@ -214,7 +224,7 @@ { "cell_type": "code", "execution_count": null, - "id": "21", + "id": "22", "metadata": {}, "outputs": [], "source": [ diff --git a/docs/docs/tutorials/load-and-fit-lbco-hrpt.py b/docs/docs/tutorials/load-and-fit-lbco-hrpt.py index 5b6395462..c87e3299b 100644 --- a/docs/docs/tutorials/load-and-fit-lbco-hrpt.py +++ b/docs/docs/tutorials/load-and-fit-lbco-hrpt.py @@ -54,6 +54,9 @@ # %% [markdown] # ### Run Fitting +# %% +project.analysis.minimizer.chi_square_change_tolerance = 1e-2 + # %% project.analysis.fit() diff --git a/docs/docs/tutorials/pdf-nacl-xrd.ipynb b/docs/docs/tutorials/pdf-nacl-xrd.ipynb index 3279652a8..a604c7e16 100644 --- a/docs/docs/tutorials/pdf-nacl-xrd.ipynb +++ b/docs/docs/tutorials/pdf-nacl-xrd.ipynb @@ -308,6 +308,16 @@ "id": "27", "metadata": {}, "outputs": [], + "source": [ + "project.analysis.minimizer.chi_square_change_tolerance = 1e-2" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "28", + "metadata": {}, + "outputs": [], "source": [ "project.analysis.fit()\n", "project.display.fit.results()\n", @@ -316,7 +326,7 @@ }, { "cell_type": "markdown", - "id": "28", + "id": "29", "metadata": {}, "source": [ "### Display Pattern" @@ -325,7 +335,7 @@ { "cell_type": "code", "execution_count": null, - "id": "29", + "id": "30", "metadata": {}, "outputs": [], "source": [ @@ -334,7 +344,7 @@ }, { "cell_type": "markdown", - "id": "30", + "id": "31", "metadata": {}, "source": [ "## 💾 Save Project" @@ -343,7 +353,7 @@ { "cell_type": "code", "execution_count": null, - "id": "31", + "id": "32", "metadata": {}, "outputs": [], "source": [ diff --git a/docs/docs/tutorials/pdf-nacl-xrd.py b/docs/docs/tutorials/pdf-nacl-xrd.py index 8cf71a543..f0236e432 100644 --- a/docs/docs/tutorials/pdf-nacl-xrd.py +++ b/docs/docs/tutorials/pdf-nacl-xrd.py @@ -121,6 +121,9 @@ # %% [markdown] # ### Run Fitting +# %% +project.analysis.minimizer.chi_square_change_tolerance = 1e-2 + # %% project.analysis.fit() project.display.fit.results() diff --git a/docs/docs/tutorials/pdf-ni-npd.ipynb b/docs/docs/tutorials/pdf-ni-npd.ipynb index 65c1392bd..b582d98ce 100644 --- a/docs/docs/tutorials/pdf-ni-npd.ipynb +++ b/docs/docs/tutorials/pdf-ni-npd.ipynb @@ -238,6 +238,16 @@ "id": "21", "metadata": {}, "outputs": [], + "source": [ + "project.analysis.minimizer.chi_square_change_tolerance = 1e-2" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "22", + "metadata": {}, + "outputs": [], "source": [ "project.analysis.fit()\n", "project.display.fit.results()\n", @@ -246,7 +256,7 @@ }, { "cell_type": "markdown", - "id": "22", + "id": "23", "metadata": {}, "source": [ "### Display Pattern" @@ -255,7 +265,7 @@ { "cell_type": "code", "execution_count": null, - "id": "23", + "id": "24", "metadata": {}, "outputs": [], "source": [ @@ -264,7 +274,7 @@ }, { "cell_type": "markdown", - "id": "24", + "id": "25", "metadata": {}, "source": [ "## 💾 Save Project" @@ -273,7 +283,7 @@ { "cell_type": "code", "execution_count": null, - "id": "25", + "id": "26", "metadata": {}, "outputs": [], "source": [ diff --git a/docs/docs/tutorials/pdf-ni-npd.py b/docs/docs/tutorials/pdf-ni-npd.py index 9e3b900d8..b7cb79f3b 100644 --- a/docs/docs/tutorials/pdf-ni-npd.py +++ b/docs/docs/tutorials/pdf-ni-npd.py @@ -91,6 +91,9 @@ # %% [markdown] # ### Run Fitting +# %% +project.analysis.minimizer.chi_square_change_tolerance = 1e-2 + # %% project.analysis.fit() project.display.fit.results() diff --git a/docs/docs/tutorials/pdf-si-nomad.ipynb b/docs/docs/tutorials/pdf-si-nomad.ipynb index a16fd2699..47912d07a 100644 --- a/docs/docs/tutorials/pdf-si-nomad.ipynb +++ b/docs/docs/tutorials/pdf-si-nomad.ipynb @@ -268,6 +268,16 @@ "id": "24", "metadata": {}, "outputs": [], + "source": [ + "project.analysis.minimizer.chi_square_change_tolerance = 1e-2" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "25", + "metadata": {}, + "outputs": [], "source": [ "project.analysis.fit()\n", "project.display.fit.results()\n", @@ -276,7 +286,7 @@ }, { "cell_type": "markdown", - "id": "25", + "id": "26", "metadata": {}, "source": [ "### Display Pattern" @@ -285,7 +295,7 @@ { "cell_type": "code", "execution_count": null, - "id": "26", + "id": "27", "metadata": {}, "outputs": [], "source": [ @@ -294,7 +304,7 @@ }, { "cell_type": "markdown", - "id": "27", + "id": "28", "metadata": {}, "source": [ "## 💾 Save Project" @@ -303,7 +313,7 @@ { "cell_type": "code", "execution_count": null, - "id": "28", + "id": "29", "metadata": {}, "outputs": [], "source": [ diff --git a/docs/docs/tutorials/pdf-si-nomad.py b/docs/docs/tutorials/pdf-si-nomad.py index 5f4cb4be1..575571ac3 100644 --- a/docs/docs/tutorials/pdf-si-nomad.py +++ b/docs/docs/tutorials/pdf-si-nomad.py @@ -102,6 +102,9 @@ # %% [markdown] # ### Run Fitting +# %% +project.analysis.minimizer.chi_square_change_tolerance = 1e-2 + # %% project.analysis.fit() project.display.fit.results() diff --git a/docs/docs/tutorials/refine-cosio-d20-tscan-resumed.ipynb b/docs/docs/tutorials/refine-cosio-d20-tscan-resumed.ipynb index fad38c9c6..47a51cac8 100644 --- a/docs/docs/tutorials/refine-cosio-d20-tscan-resumed.ipynb +++ b/docs/docs/tutorials/refine-cosio-d20-tscan-resumed.ipynb @@ -150,13 +150,23 @@ "id": "13", "metadata": {}, "outputs": [], + "source": [ + "project.analysis.minimizer.chi_square_change_tolerance = 1e-2" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "14", + "metadata": {}, + "outputs": [], "source": [ "project.analysis.fit()" ] }, { "cell_type": "markdown", - "id": "14", + "id": "15", "metadata": {}, "source": [ "### Replay Fitted Datasets\n", @@ -167,7 +177,7 @@ { "cell_type": "code", "execution_count": null, - "id": "15", + "id": "16", "metadata": {}, "outputs": [], "source": [ @@ -177,7 +187,7 @@ }, { "cell_type": "markdown", - "id": "16", + "id": "17", "metadata": {}, "source": [ "\n", @@ -187,7 +197,7 @@ { "cell_type": "code", "execution_count": null, - "id": "17", + "id": "18", "metadata": {}, "outputs": [], "source": [ @@ -197,7 +207,7 @@ }, { "cell_type": "markdown", - "id": "18", + "id": "19", "metadata": {}, "source": [ "### Display Parameter Evolution\n", @@ -209,7 +219,7 @@ { "cell_type": "code", "execution_count": null, - "id": "19", + "id": "20", "metadata": {}, "outputs": [], "source": [ @@ -218,7 +228,7 @@ }, { "cell_type": "markdown", - "id": "20", + "id": "21", "metadata": {}, "source": [ "Plot fit quality metrics vs. temperature." @@ -227,7 +237,7 @@ { "cell_type": "code", "execution_count": null, - "id": "21", + "id": "22", "metadata": {}, "outputs": [], "source": [ @@ -247,7 +257,7 @@ }, { "cell_type": "markdown", - "id": "22", + "id": "23", "metadata": {}, "source": [ "Omitting `param` plots every fitted parameter one after another." @@ -256,7 +266,7 @@ { "cell_type": "code", "execution_count": null, - "id": "23", + "id": "24", "metadata": {}, "outputs": [], "source": [ @@ -265,7 +275,7 @@ }, { "cell_type": "markdown", - "id": "24", + "id": "25", "metadata": {}, "source": [ "## 💾 Save Project" @@ -274,7 +284,7 @@ { "cell_type": "code", "execution_count": null, - "id": "25", + "id": "26", "metadata": {}, "outputs": [], "source": [ diff --git a/docs/docs/tutorials/refine-cosio-d20-tscan-resumed.py b/docs/docs/tutorials/refine-cosio-d20-tscan-resumed.py index 269bce1cc..b8e1ce2b6 100644 --- a/docs/docs/tutorials/refine-cosio-d20-tscan-resumed.py +++ b/docs/docs/tutorials/refine-cosio-d20-tscan-resumed.py @@ -55,6 +55,9 @@ # run. Running the fit again skips datasets already present in the CSV # and continues from the remaining files. +# %% +project.analysis.minimizer.chi_square_change_tolerance = 1e-2 + # %% project.analysis.fit() diff --git a/docs/docs/tutorials/refine-cosio-d20-tscan.ipynb b/docs/docs/tutorials/refine-cosio-d20-tscan.ipynb index d597a9d4b..677cff0a2 100644 --- a/docs/docs/tutorials/refine-cosio-d20-tscan.ipynb +++ b/docs/docs/tutorials/refine-cosio-d20-tscan.ipynb @@ -571,7 +571,7 @@ "metadata": {}, "outputs": [], "source": [ - "analysis.fit()" + "analysis.minimizer.chi_square_change_tolerance = 1e-2" ] }, { @@ -580,13 +580,23 @@ "id": "46", "metadata": {}, "outputs": [], + "source": [ + "analysis.fit()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "47", + "metadata": {}, + "outputs": [], "source": [ "display.fit.results()" ] }, { "cell_type": "markdown", - "id": "47", + "id": "48", "metadata": {}, "source": [ "### Display Correlations" @@ -595,7 +605,7 @@ { "cell_type": "code", "execution_count": null, - "id": "48", + "id": "49", "metadata": {}, "outputs": [], "source": [ @@ -604,7 +614,7 @@ }, { "cell_type": "markdown", - "id": "49", + "id": "50", "metadata": {}, "source": [ "### Display Pattern" @@ -613,7 +623,7 @@ { "cell_type": "code", "execution_count": null, - "id": "50", + "id": "51", "metadata": {}, "outputs": [], "source": [ @@ -622,7 +632,7 @@ }, { "cell_type": "markdown", - "id": "51", + "id": "52", "metadata": {}, "source": [ "### Display Structure" @@ -631,7 +641,7 @@ { "cell_type": "code", "execution_count": null, - "id": "52", + "id": "53", "metadata": {}, "outputs": [], "source": [ @@ -641,7 +651,7 @@ }, { "cell_type": "markdown", - "id": "53", + "id": "54", "metadata": {}, "source": [ "### Run Sequential Fitting\n", @@ -653,7 +663,7 @@ { "cell_type": "code", "execution_count": null, - "id": "54", + "id": "55", "metadata": {}, "outputs": [], "source": [ @@ -662,7 +672,7 @@ }, { "cell_type": "markdown", - "id": "55", + "id": "56", "metadata": { "lines_to_next_cell": 2 }, @@ -675,7 +685,7 @@ { "cell_type": "code", "execution_count": null, - "id": "56", + "id": "57", "metadata": {}, "outputs": [], "source": [ @@ -685,7 +695,7 @@ { "cell_type": "code", "execution_count": null, - "id": "57", + "id": "58", "metadata": {}, "outputs": [], "source": [ @@ -699,7 +709,7 @@ }, { "cell_type": "markdown", - "id": "58", + "id": "59", "metadata": {}, "source": [ "Set the sequential fitting parameters." @@ -708,7 +718,7 @@ { "cell_type": "code", "execution_count": null, - "id": "59", + "id": "60", "metadata": {}, "outputs": [], "source": [ @@ -720,7 +730,7 @@ }, { "cell_type": "markdown", - "id": "60", + "id": "61", "metadata": {}, "source": [ "Run the sequential fit over all data files in the scan directory." @@ -729,7 +739,7 @@ { "cell_type": "code", "execution_count": null, - "id": "61", + "id": "62", "metadata": {}, "outputs": [], "source": [ @@ -738,7 +748,7 @@ }, { "cell_type": "markdown", - "id": "62", + "id": "63", "metadata": {}, "source": [ "### Replay a Dataset\n", @@ -749,7 +759,7 @@ { "cell_type": "code", "execution_count": null, - "id": "63", + "id": "64", "metadata": {}, "outputs": [], "source": [ @@ -759,7 +769,7 @@ }, { "cell_type": "markdown", - "id": "64", + "id": "65", "metadata": {}, "source": [ "\n", @@ -769,7 +779,7 @@ { "cell_type": "code", "execution_count": null, - "id": "65", + "id": "66", "metadata": {}, "outputs": [], "source": [ @@ -779,7 +789,7 @@ }, { "cell_type": "markdown", - "id": "66", + "id": "67", "metadata": {}, "source": [ "### Display Parameter Evolution\n", @@ -789,7 +799,7 @@ }, { "cell_type": "markdown", - "id": "67", + "id": "68", "metadata": {}, "source": [ "Plot fit quality metrics vs. temperature." @@ -798,7 +808,7 @@ { "cell_type": "code", "execution_count": null, - "id": "68", + "id": "69", "metadata": {}, "outputs": [], "source": [ @@ -809,7 +819,7 @@ }, { "cell_type": "markdown", - "id": "69", + "id": "70", "metadata": {}, "source": [ "Plot unit cell parameters vs. temperature." @@ -818,7 +828,7 @@ { "cell_type": "code", "execution_count": null, - "id": "70", + "id": "71", "metadata": {}, "outputs": [], "source": [ @@ -829,7 +839,7 @@ }, { "cell_type": "markdown", - "id": "71", + "id": "72", "metadata": {}, "source": [ "Plot isotropic displacement parameters vs. temperature." @@ -838,7 +848,7 @@ { "cell_type": "code", "execution_count": null, - "id": "72", + "id": "73", "metadata": {}, "outputs": [], "source": [ @@ -851,7 +861,7 @@ }, { "cell_type": "markdown", - "id": "73", + "id": "74", "metadata": {}, "source": [ "Plot selected fractional coordinates vs. temperature." @@ -860,7 +870,7 @@ { "cell_type": "code", "execution_count": null, - "id": "74", + "id": "75", "metadata": {}, "outputs": [], "source": [ @@ -873,7 +883,7 @@ }, { "cell_type": "markdown", - "id": "75", + "id": "76", "metadata": {}, "source": [ "## 💾 Save Project\n", @@ -884,7 +894,7 @@ { "cell_type": "code", "execution_count": null, - "id": "76", + "id": "77", "metadata": {}, "outputs": [], "source": [ diff --git a/docs/docs/tutorials/refine-cosio-d20-tscan.py b/docs/docs/tutorials/refine-cosio-d20-tscan.py index b060aefa0..5e0f07292 100644 --- a/docs/docs/tutorials/refine-cosio-d20-tscan.py +++ b/docs/docs/tutorials/refine-cosio-d20-tscan.py @@ -281,6 +281,9 @@ # help with convergence and speed of the sequential fitting, especially # if the initial parameters are far from optimal. +# %% +analysis.minimizer.chi_square_change_tolerance = 1e-2 + # %% analysis.fit() diff --git a/docs/docs/tutorials/refine-cosio-d20.ipynb b/docs/docs/tutorials/refine-cosio-d20.ipynb index a7721536d..d34e90abc 100644 --- a/docs/docs/tutorials/refine-cosio-d20.ipynb +++ b/docs/docs/tutorials/refine-cosio-d20.ipynb @@ -604,7 +604,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.analysis.fit()" + "project.analysis.minimizer.chi_square_change_tolerance = 1e-2" ] }, { @@ -614,7 +614,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.fit.results()" + "project.analysis.fit()" ] }, { @@ -623,13 +623,23 @@ "id": "52", "metadata": {}, "outputs": [], + "source": [ + "project.display.fit.results()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "53", + "metadata": {}, + "outputs": [], "source": [ "project.display.fit.correlations()" ] }, { "cell_type": "markdown", - "id": "53", + "id": "54", "metadata": {}, "source": [ "### Display Pattern" @@ -638,7 +648,7 @@ { "cell_type": "code", "execution_count": null, - "id": "54", + "id": "55", "metadata": {}, "outputs": [], "source": [ @@ -648,7 +658,7 @@ { "cell_type": "code", "execution_count": null, - "id": "55", + "id": "56", "metadata": {}, "outputs": [], "source": [ @@ -657,7 +667,7 @@ }, { "cell_type": "markdown", - "id": "56", + "id": "57", "metadata": {}, "source": [ "## 📊 Report\n", @@ -668,7 +678,7 @@ }, { "cell_type": "markdown", - "id": "57", + "id": "58", "metadata": {}, "source": [ "## 💾 Save Project\n", @@ -679,7 +689,7 @@ { "cell_type": "code", "execution_count": null, - "id": "58", + "id": "59", "metadata": {}, "outputs": [], "source": [ diff --git a/docs/docs/tutorials/refine-cosio-d20.py b/docs/docs/tutorials/refine-cosio-d20.py index c3fa9dd9b..1f847445a 100644 --- a/docs/docs/tutorials/refine-cosio-d20.py +++ b/docs/docs/tutorials/refine-cosio-d20.py @@ -276,6 +276,9 @@ # %% [markdown] # ### Run Fitting +# %% +project.analysis.minimizer.chi_square_change_tolerance = 1e-2 + # %% project.analysis.fit() diff --git a/docs/docs/tutorials/refine-hs-hrpt.ipynb b/docs/docs/tutorials/refine-hs-hrpt.ipynb index 23f5a98b0..d37ec1b6a 100644 --- a/docs/docs/tutorials/refine-hs-hrpt.ipynb +++ b/docs/docs/tutorials/refine-hs-hrpt.ipynb @@ -487,7 +487,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.analysis.fit()" + "project.analysis.minimizer.chi_square_change_tolerance = 1e-2" ] }, { @@ -496,13 +496,23 @@ "id": "44", "metadata": {}, "outputs": [], + "source": [ + "project.analysis.fit()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "45", + "metadata": {}, + "outputs": [], "source": [ "project.display.fit.results()" ] }, { "cell_type": "markdown", - "id": "45", + "id": "46", "metadata": {}, "source": [ "#### Display Pattern" @@ -511,7 +521,7 @@ { "cell_type": "code", "execution_count": null, - "id": "46", + "id": "47", "metadata": {}, "outputs": [], "source": [ @@ -521,7 +531,7 @@ { "cell_type": "code", "execution_count": null, - "id": "47", + "id": "48", "metadata": {}, "outputs": [], "source": [ @@ -530,7 +540,7 @@ }, { "cell_type": "markdown", - "id": "48", + "id": "49", "metadata": {}, "source": [ "### Perform Fit 2/4\n", @@ -541,7 +551,7 @@ { "cell_type": "code", "execution_count": null, - "id": "49", + "id": "50", "metadata": {}, "outputs": [], "source": [ @@ -556,7 +566,7 @@ }, { "cell_type": "markdown", - "id": "50", + "id": "51", "metadata": {}, "source": [ "Show free parameters after selection." @@ -565,7 +575,7 @@ { "cell_type": "code", "execution_count": null, - "id": "51", + "id": "52", "metadata": {}, "outputs": [], "source": [ @@ -574,7 +584,7 @@ }, { "cell_type": "markdown", - "id": "52", + "id": "53", "metadata": {}, "source": [ "#### Run Fitting" @@ -583,7 +593,7 @@ { "cell_type": "code", "execution_count": null, - "id": "53", + "id": "54", "metadata": {}, "outputs": [], "source": [ @@ -593,7 +603,7 @@ { "cell_type": "code", "execution_count": null, - "id": "54", + "id": "55", "metadata": {}, "outputs": [], "source": [ @@ -602,7 +612,7 @@ }, { "cell_type": "markdown", - "id": "55", + "id": "56", "metadata": {}, "source": [ "#### Display Pattern" @@ -611,7 +621,7 @@ { "cell_type": "code", "execution_count": null, - "id": "56", + "id": "57", "metadata": {}, "outputs": [], "source": [ @@ -621,7 +631,7 @@ { "cell_type": "code", "execution_count": null, - "id": "57", + "id": "58", "metadata": {}, "outputs": [], "source": [ @@ -630,7 +640,7 @@ }, { "cell_type": "markdown", - "id": "58", + "id": "59", "metadata": {}, "source": [ "### Perform Fit 3/4\n", @@ -641,7 +651,7 @@ { "cell_type": "code", "execution_count": null, - "id": "59", + "id": "60", "metadata": {}, "outputs": [], "source": [ @@ -654,7 +664,7 @@ }, { "cell_type": "markdown", - "id": "60", + "id": "61", "metadata": {}, "source": [ "Show free parameters after selection." @@ -663,7 +673,7 @@ { "cell_type": "code", "execution_count": null, - "id": "61", + "id": "62", "metadata": {}, "outputs": [], "source": [ @@ -672,7 +682,7 @@ }, { "cell_type": "markdown", - "id": "62", + "id": "63", "metadata": {}, "source": [ "#### Run Fitting" @@ -681,7 +691,7 @@ { "cell_type": "code", "execution_count": null, - "id": "63", + "id": "64", "metadata": {}, "outputs": [], "source": [ @@ -691,7 +701,7 @@ { "cell_type": "code", "execution_count": null, - "id": "64", + "id": "65", "metadata": {}, "outputs": [], "source": [ @@ -700,7 +710,7 @@ }, { "cell_type": "markdown", - "id": "65", + "id": "66", "metadata": {}, "source": [ "#### Display Pattern" @@ -709,7 +719,7 @@ { "cell_type": "code", "execution_count": null, - "id": "66", + "id": "67", "metadata": {}, "outputs": [], "source": [ @@ -719,7 +729,7 @@ { "cell_type": "code", "execution_count": null, - "id": "67", + "id": "68", "metadata": {}, "outputs": [], "source": [ @@ -728,7 +738,7 @@ }, { "cell_type": "markdown", - "id": "68", + "id": "69", "metadata": {}, "source": [ "### Perform Fit 4/4\n", @@ -739,7 +749,7 @@ { "cell_type": "code", "execution_count": null, - "id": "69", + "id": "70", "metadata": {}, "outputs": [], "source": [ @@ -757,7 +767,7 @@ }, { "cell_type": "markdown", - "id": "70", + "id": "71", "metadata": {}, "source": [ "Show free parameters after selection." @@ -766,7 +776,7 @@ { "cell_type": "code", "execution_count": null, - "id": "71", + "id": "72", "metadata": {}, "outputs": [], "source": [ @@ -775,7 +785,7 @@ }, { "cell_type": "markdown", - "id": "72", + "id": "73", "metadata": {}, "source": [ "#### Run Fitting" @@ -784,7 +794,7 @@ { "cell_type": "code", "execution_count": null, - "id": "73", + "id": "74", "metadata": {}, "outputs": [], "source": [ @@ -794,7 +804,7 @@ { "cell_type": "code", "execution_count": null, - "id": "74", + "id": "75", "metadata": {}, "outputs": [], "source": [ @@ -804,7 +814,7 @@ { "cell_type": "code", "execution_count": null, - "id": "75", + "id": "76", "metadata": {}, "outputs": [], "source": [ @@ -813,7 +823,7 @@ }, { "cell_type": "markdown", - "id": "76", + "id": "77", "metadata": {}, "source": [ "#### Display Pattern" @@ -822,7 +832,7 @@ { "cell_type": "code", "execution_count": null, - "id": "77", + "id": "78", "metadata": {}, "outputs": [], "source": [ @@ -832,7 +842,7 @@ { "cell_type": "code", "execution_count": null, - "id": "78", + "id": "79", "metadata": {}, "outputs": [], "source": [ @@ -841,7 +851,7 @@ }, { "cell_type": "markdown", - "id": "79", + "id": "80", "metadata": {}, "source": [ "## 📊 Report\n", @@ -852,7 +862,7 @@ }, { "cell_type": "markdown", - "id": "80", + "id": "81", "metadata": {}, "source": [ "## 💾 Save Project" @@ -861,7 +871,7 @@ { "cell_type": "code", "execution_count": null, - "id": "81", + "id": "82", "metadata": {}, "outputs": [], "source": [ diff --git a/docs/docs/tutorials/refine-hs-hrpt.py b/docs/docs/tutorials/refine-hs-hrpt.py index 7c52569fb..646fc3cc5 100644 --- a/docs/docs/tutorials/refine-hs-hrpt.py +++ b/docs/docs/tutorials/refine-hs-hrpt.py @@ -204,6 +204,9 @@ # %% [markdown] # #### Run Fitting +# %% +project.analysis.minimizer.chi_square_change_tolerance = 1e-2 + # %% project.analysis.fit() diff --git a/docs/docs/tutorials/refine-lam7o3-p021.ipynb b/docs/docs/tutorials/refine-lam7o3-p021.ipynb index 04f3617cd..91c6673fa 100644 --- a/docs/docs/tutorials/refine-lam7o3-p021.ipynb +++ b/docs/docs/tutorials/refine-lam7o3-p021.ipynb @@ -677,13 +677,23 @@ "id": "53", "metadata": {}, "outputs": [], + "source": [ + "project.analysis.minimizer.chi_square_change_tolerance = 1e-2" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "54", + "metadata": {}, + "outputs": [], "source": [ "project.analysis.fit()" ] }, { "cell_type": "markdown", - "id": "54", + "id": "55", "metadata": {}, "source": [ "Display the initial fit summary and the strongest parameter\n", @@ -693,7 +703,7 @@ { "cell_type": "code", "execution_count": null, - "id": "55", + "id": "56", "metadata": {}, "outputs": [], "source": [ @@ -703,7 +713,7 @@ { "cell_type": "code", "execution_count": null, - "id": "56", + "id": "57", "metadata": {}, "outputs": [], "source": [ @@ -712,7 +722,7 @@ }, { "cell_type": "markdown", - "id": "57", + "id": "58", "metadata": {}, "source": [ "#### Display Pattern (After Initial Fit)\n", @@ -724,7 +734,7 @@ { "cell_type": "code", "execution_count": null, - "id": "58", + "id": "59", "metadata": {}, "outputs": [], "source": [ @@ -734,7 +744,7 @@ { "cell_type": "code", "execution_count": null, - "id": "59", + "id": "60", "metadata": {}, "outputs": [], "source": [ @@ -743,7 +753,7 @@ }, { "cell_type": "markdown", - "id": "60", + "id": "61", "metadata": {}, "source": [ "### Improve Background Estimate\n", @@ -757,7 +767,7 @@ { "cell_type": "code", "execution_count": null, - "id": "61", + "id": "62", "metadata": {}, "outputs": [], "source": [ @@ -767,7 +777,7 @@ { "cell_type": "code", "execution_count": null, - "id": "62", + "id": "63", "metadata": {}, "outputs": [], "source": [ @@ -777,7 +787,7 @@ { "cell_type": "code", "execution_count": null, - "id": "63", + "id": "64", "metadata": {}, "outputs": [], "source": [ @@ -787,7 +797,7 @@ }, { "cell_type": "markdown", - "id": "64", + "id": "65", "metadata": {}, "source": [ "#### Run Fitting" @@ -796,7 +806,7 @@ { "cell_type": "code", "execution_count": null, - "id": "65", + "id": "66", "metadata": {}, "outputs": [], "source": [ @@ -806,7 +816,7 @@ { "cell_type": "code", "execution_count": null, - "id": "66", + "id": "67", "metadata": {}, "outputs": [], "source": [ @@ -816,7 +826,7 @@ { "cell_type": "code", "execution_count": null, - "id": "67", + "id": "68", "metadata": {}, "outputs": [], "source": [ @@ -825,7 +835,7 @@ }, { "cell_type": "markdown", - "id": "68", + "id": "69", "metadata": {}, "source": [ "#### Display Pattern (After Final Fit)" @@ -834,7 +844,7 @@ { "cell_type": "code", "execution_count": null, - "id": "69", + "id": "70", "metadata": {}, "outputs": [], "source": [ @@ -844,7 +854,7 @@ { "cell_type": "code", "execution_count": null, - "id": "70", + "id": "71", "metadata": {}, "outputs": [], "source": [ @@ -853,7 +863,7 @@ }, { "cell_type": "markdown", - "id": "71", + "id": "72", "metadata": {}, "source": [ "## 📊 Report\n", @@ -865,7 +875,7 @@ { "cell_type": "code", "execution_count": null, - "id": "72", + "id": "73", "metadata": {}, "outputs": [], "source": [ @@ -875,7 +885,7 @@ }, { "cell_type": "markdown", - "id": "73", + "id": "74", "metadata": {}, "source": [ "## 💾 Save Project" @@ -884,7 +894,7 @@ { "cell_type": "code", "execution_count": null, - "id": "74", + "id": "75", "metadata": {}, "outputs": [], "source": [ @@ -894,7 +904,7 @@ { "cell_type": "code", "execution_count": null, - "id": "75", + "id": "76", "metadata": {}, "outputs": [], "source": [] diff --git a/docs/docs/tutorials/refine-lam7o3-p021.py b/docs/docs/tutorials/refine-lam7o3-p021.py index bd88f070f..26fec6d65 100644 --- a/docs/docs/tutorials/refine-lam7o3-p021.py +++ b/docs/docs/tutorials/refine-lam7o3-p021.py @@ -334,6 +334,9 @@ # %% [markdown] # #### Run Fitting +# %% +project.analysis.minimizer.chi_square_change_tolerance = 1e-2 + # %% project.analysis.fit() diff --git a/docs/docs/tutorials/refine-lbco-hrpt-from-cif.ipynb b/docs/docs/tutorials/refine-lbco-hrpt-from-cif.ipynb index bd7b3d06a..02c306535 100644 --- a/docs/docs/tutorials/refine-lbco-hrpt-from-cif.ipynb +++ b/docs/docs/tutorials/refine-lbco-hrpt-from-cif.ipynb @@ -172,6 +172,16 @@ "id": "15", "metadata": {}, "outputs": [], + "source": [ + "project.analysis.minimizer.chi_square_change_tolerance = 1e-2" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "16", + "metadata": {}, + "outputs": [], "source": [ "# Start refinement. All parameters, which have standard uncertainties\n", "# in the input CIF files, are refined by default.\n", @@ -181,7 +191,7 @@ { "cell_type": "code", "execution_count": null, - "id": "16", + "id": "17", "metadata": {}, "outputs": [], "source": [ @@ -192,7 +202,7 @@ { "cell_type": "code", "execution_count": null, - "id": "17", + "id": "18", "metadata": {}, "outputs": [], "source": [ @@ -202,7 +212,7 @@ }, { "cell_type": "markdown", - "id": "18", + "id": "19", "metadata": {}, "source": [ "### With Constraints" @@ -211,7 +221,7 @@ { "cell_type": "code", "execution_count": null, - "id": "19", + "id": "20", "metadata": {}, "outputs": [], "source": [ @@ -236,7 +246,7 @@ { "cell_type": "code", "execution_count": null, - "id": "20", + "id": "21", "metadata": {}, "outputs": [], "source": [ @@ -248,7 +258,7 @@ { "cell_type": "code", "execution_count": null, - "id": "21", + "id": "22", "metadata": {}, "outputs": [], "source": [ @@ -259,7 +269,7 @@ { "cell_type": "code", "execution_count": null, - "id": "22", + "id": "23", "metadata": {}, "outputs": [], "source": [ @@ -270,7 +280,7 @@ { "cell_type": "code", "execution_count": null, - "id": "23", + "id": "24", "metadata": {}, "outputs": [], "source": [ @@ -281,7 +291,7 @@ { "cell_type": "code", "execution_count": null, - "id": "24", + "id": "25", "metadata": {}, "outputs": [], "source": [ @@ -291,7 +301,7 @@ }, { "cell_type": "markdown", - "id": "25", + "id": "26", "metadata": {}, "source": [ "## 💾 Save Project" @@ -300,7 +310,7 @@ { "cell_type": "code", "execution_count": null, - "id": "26", + "id": "27", "metadata": {}, "outputs": [], "source": [ diff --git a/docs/docs/tutorials/refine-lbco-hrpt-from-cif.py b/docs/docs/tutorials/refine-lbco-hrpt-from-cif.py index 75b27003d..5b6387c4f 100644 --- a/docs/docs/tutorials/refine-lbco-hrpt-from-cif.py +++ b/docs/docs/tutorials/refine-lbco-hrpt-from-cif.py @@ -61,6 +61,9 @@ # %% [markdown] # ### Without Constraints +# %% +project.analysis.minimizer.chi_square_change_tolerance = 1e-2 + # %% # Start refinement. All parameters, which have standard uncertainties # in the input CIF files, are refined by default. diff --git a/docs/docs/tutorials/refine-lbco-hrpt-from-data.ipynb b/docs/docs/tutorials/refine-lbco-hrpt-from-data.ipynb index 19122a43d..decc8a24d 100644 --- a/docs/docs/tutorials/refine-lbco-hrpt-from-data.ipynb +++ b/docs/docs/tutorials/refine-lbco-hrpt-from-data.ipynb @@ -342,7 +342,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.analysis.fit()" + "project.analysis.minimizer.chi_square_change_tolerance = 1e-2" ] }, { @@ -352,7 +352,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.fit.results()" + "project.analysis.fit()" ] }, { @@ -362,7 +362,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.display.fit.correlations()" + "project.display.fit.results()" ] }, { @@ -371,13 +371,23 @@ "id": "29", "metadata": {}, "outputs": [], + "source": [ + "project.display.fit.correlations()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "30", + "metadata": {}, + "outputs": [], "source": [ "project.display.pattern(expt_name='hrpt')" ] }, { "cell_type": "markdown", - "id": "30", + "id": "31", "metadata": {}, "source": [ "### With Constraints" @@ -386,7 +396,7 @@ { "cell_type": "code", "execution_count": null, - "id": "31", + "id": "32", "metadata": {}, "outputs": [], "source": [ @@ -411,7 +421,7 @@ { "cell_type": "code", "execution_count": null, - "id": "32", + "id": "33", "metadata": {}, "outputs": [], "source": [ @@ -422,7 +432,7 @@ { "cell_type": "code", "execution_count": null, - "id": "33", + "id": "34", "metadata": {}, "outputs": [], "source": [ @@ -432,7 +442,7 @@ { "cell_type": "code", "execution_count": null, - "id": "34", + "id": "35", "metadata": {}, "outputs": [], "source": [ @@ -442,7 +452,7 @@ { "cell_type": "code", "execution_count": null, - "id": "35", + "id": "36", "metadata": {}, "outputs": [], "source": [ @@ -452,7 +462,7 @@ { "cell_type": "code", "execution_count": null, - "id": "36", + "id": "37", "metadata": {}, "outputs": [], "source": [ @@ -461,7 +471,7 @@ }, { "cell_type": "markdown", - "id": "37", + "id": "38", "metadata": {}, "source": [ "### Switch Calculator" @@ -470,7 +480,7 @@ { "cell_type": "code", "execution_count": null, - "id": "38", + "id": "39", "metadata": {}, "outputs": [], "source": [ @@ -480,7 +490,7 @@ { "cell_type": "code", "execution_count": null, - "id": "39", + "id": "40", "metadata": {}, "outputs": [], "source": [ @@ -490,7 +500,7 @@ { "cell_type": "code", "execution_count": null, - "id": "40", + "id": "41", "metadata": {}, "outputs": [], "source": [ @@ -500,7 +510,7 @@ { "cell_type": "code", "execution_count": null, - "id": "41", + "id": "42", "metadata": {}, "outputs": [], "source": [ @@ -510,7 +520,7 @@ { "cell_type": "code", "execution_count": null, - "id": "42", + "id": "43", "metadata": {}, "outputs": [], "source": [ @@ -520,7 +530,7 @@ { "cell_type": "code", "execution_count": null, - "id": "43", + "id": "44", "metadata": {}, "outputs": [], "source": [ @@ -529,7 +539,7 @@ }, { "cell_type": "markdown", - "id": "44", + "id": "45", "metadata": {}, "source": [ "## 💾 Save Project" @@ -538,7 +548,7 @@ { "cell_type": "code", "execution_count": null, - "id": "45", + "id": "46", "metadata": {}, "outputs": [], "source": [ diff --git a/docs/docs/tutorials/refine-lbco-hrpt-from-data.py b/docs/docs/tutorials/refine-lbco-hrpt-from-data.py index 62b324463..deec055fe 100644 --- a/docs/docs/tutorials/refine-lbco-hrpt-from-data.py +++ b/docs/docs/tutorials/refine-lbco-hrpt-from-data.py @@ -153,6 +153,9 @@ experiment.linked_structures['lbco'].scale.free = True +# %% +project.analysis.minimizer.chi_square_change_tolerance = 1e-2 + # %% project.analysis.fit() diff --git a/docs/docs/tutorials/refine-lbco-hrpt-report.ipynb b/docs/docs/tutorials/refine-lbco-hrpt-report.ipynb index 52bc562d8..2ea583330 100644 --- a/docs/docs/tutorials/refine-lbco-hrpt-report.ipynb +++ b/docs/docs/tutorials/refine-lbco-hrpt-report.ipynb @@ -1157,6 +1157,16 @@ "id": "111", "metadata": {}, "outputs": [], + "source": [ + "project.analysis.minimizer.chi_square_change_tolerance = 1e-2" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "112", + "metadata": {}, + "outputs": [], "source": [ "project.analysis.fit()\n", "project.display.fit.results()" @@ -1164,7 +1174,7 @@ }, { "cell_type": "markdown", - "id": "112", + "id": "113", "metadata": {}, "source": [ "#### Display Pattern" @@ -1173,7 +1183,7 @@ { "cell_type": "code", "execution_count": null, - "id": "113", + "id": "114", "metadata": {}, "outputs": [], "source": [ @@ -1183,7 +1193,7 @@ { "cell_type": "code", "execution_count": null, - "id": "114", + "id": "115", "metadata": {}, "outputs": [], "source": [ @@ -1192,7 +1202,7 @@ }, { "cell_type": "markdown", - "id": "115", + "id": "116", "metadata": {}, "source": [ "### Perform Fit 2/5\n", @@ -1203,7 +1213,7 @@ { "cell_type": "code", "execution_count": null, - "id": "116", + "id": "117", "metadata": {}, "outputs": [], "source": [ @@ -1215,7 +1225,7 @@ }, { "cell_type": "markdown", - "id": "117", + "id": "118", "metadata": {}, "source": [ "Show free parameters after selection." @@ -1224,7 +1234,7 @@ { "cell_type": "code", "execution_count": null, - "id": "118", + "id": "119", "metadata": {}, "outputs": [], "source": [ @@ -1233,7 +1243,7 @@ }, { "cell_type": "markdown", - "id": "119", + "id": "120", "metadata": {}, "source": [ "#### Run Fitting" @@ -1242,7 +1252,7 @@ { "cell_type": "code", "execution_count": null, - "id": "120", + "id": "121", "metadata": {}, "outputs": [], "source": [ @@ -1252,7 +1262,7 @@ }, { "cell_type": "markdown", - "id": "121", + "id": "122", "metadata": {}, "source": [ "#### Display Pattern" @@ -1261,7 +1271,7 @@ { "cell_type": "code", "execution_count": null, - "id": "122", + "id": "123", "metadata": {}, "outputs": [], "source": [ @@ -1271,7 +1281,7 @@ { "cell_type": "code", "execution_count": null, - "id": "123", + "id": "124", "metadata": {}, "outputs": [], "source": [ @@ -1280,7 +1290,7 @@ }, { "cell_type": "markdown", - "id": "124", + "id": "125", "metadata": {}, "source": [ "#### Save Project State" @@ -1289,7 +1299,7 @@ { "cell_type": "code", "execution_count": null, - "id": "125", + "id": "126", "metadata": {}, "outputs": [], "source": [ @@ -1298,7 +1308,7 @@ }, { "cell_type": "markdown", - "id": "126", + "id": "127", "metadata": {}, "source": [ "### Perform Fit 3/5\n", @@ -1309,7 +1319,7 @@ { "cell_type": "code", "execution_count": null, - "id": "127", + "id": "128", "metadata": {}, "outputs": [], "source": [ @@ -1321,7 +1331,7 @@ }, { "cell_type": "markdown", - "id": "128", + "id": "129", "metadata": {}, "source": [ "Show free parameters after selection." @@ -1330,7 +1340,7 @@ { "cell_type": "code", "execution_count": null, - "id": "129", + "id": "130", "metadata": {}, "outputs": [], "source": [ @@ -1339,7 +1349,7 @@ }, { "cell_type": "markdown", - "id": "130", + "id": "131", "metadata": {}, "source": [ "#### Run Fitting" @@ -1348,7 +1358,7 @@ { "cell_type": "code", "execution_count": null, - "id": "131", + "id": "132", "metadata": {}, "outputs": [], "source": [ @@ -1358,7 +1368,7 @@ }, { "cell_type": "markdown", - "id": "132", + "id": "133", "metadata": {}, "source": [ "#### Display Pattern" @@ -1367,7 +1377,7 @@ { "cell_type": "code", "execution_count": null, - "id": "133", + "id": "134", "metadata": {}, "outputs": [], "source": [ @@ -1377,7 +1387,7 @@ { "cell_type": "code", "execution_count": null, - "id": "134", + "id": "135", "metadata": {}, "outputs": [], "source": [ @@ -1386,7 +1396,7 @@ }, { "cell_type": "markdown", - "id": "135", + "id": "136", "metadata": {}, "source": [ "### Perform Fit 4/5\n", @@ -1399,7 +1409,7 @@ { "cell_type": "code", "execution_count": null, - "id": "136", + "id": "137", "metadata": {}, "outputs": [], "source": [ @@ -1415,7 +1425,7 @@ }, { "cell_type": "markdown", - "id": "137", + "id": "138", "metadata": {}, "source": [ "Set constraints." @@ -1424,7 +1434,7 @@ { "cell_type": "code", "execution_count": null, - "id": "138", + "id": "139", "metadata": {}, "outputs": [], "source": [ @@ -1433,7 +1443,7 @@ }, { "cell_type": "markdown", - "id": "139", + "id": "140", "metadata": {}, "source": [ "Show defined constraints." @@ -1442,7 +1452,7 @@ { "cell_type": "code", "execution_count": null, - "id": "140", + "id": "141", "metadata": {}, "outputs": [], "source": [ @@ -1451,7 +1461,7 @@ }, { "cell_type": "markdown", - "id": "141", + "id": "142", "metadata": {}, "source": [ "Show free parameters." @@ -1460,7 +1470,7 @@ { "cell_type": "code", "execution_count": null, - "id": "142", + "id": "143", "metadata": {}, "outputs": [], "source": [ @@ -1469,7 +1479,7 @@ }, { "cell_type": "markdown", - "id": "143", + "id": "144", "metadata": {}, "source": [ "#### Run Fitting" @@ -1478,7 +1488,7 @@ { "cell_type": "code", "execution_count": null, - "id": "144", + "id": "145", "metadata": {}, "outputs": [], "source": [ @@ -1488,7 +1498,7 @@ }, { "cell_type": "markdown", - "id": "145", + "id": "146", "metadata": {}, "source": [ "#### Display Pattern" @@ -1497,7 +1507,7 @@ { "cell_type": "code", "execution_count": null, - "id": "146", + "id": "147", "metadata": {}, "outputs": [], "source": [ @@ -1507,7 +1517,7 @@ { "cell_type": "code", "execution_count": null, - "id": "147", + "id": "148", "metadata": {}, "outputs": [], "source": [ @@ -1516,7 +1526,7 @@ }, { "cell_type": "markdown", - "id": "148", + "id": "149", "metadata": {}, "source": [ "### Perform Fit 5/5\n", @@ -1529,7 +1539,7 @@ { "cell_type": "code", "execution_count": null, - "id": "149", + "id": "150", "metadata": {}, "outputs": [], "source": [ @@ -1545,7 +1555,7 @@ }, { "cell_type": "markdown", - "id": "150", + "id": "151", "metadata": {}, "source": [ "Set more constraints." @@ -1554,7 +1564,7 @@ { "cell_type": "code", "execution_count": null, - "id": "151", + "id": "152", "metadata": {}, "outputs": [], "source": [ @@ -1565,7 +1575,7 @@ }, { "cell_type": "markdown", - "id": "152", + "id": "153", "metadata": {}, "source": [ "Show defined constraints." @@ -1574,7 +1584,7 @@ { "cell_type": "code", "execution_count": null, - "id": "153", + "id": "154", "metadata": { "lines_to_next_cell": 2 }, @@ -1585,7 +1595,7 @@ }, { "cell_type": "markdown", - "id": "154", + "id": "155", "metadata": {}, "source": [ "Set structure parameters to be refined." @@ -1594,7 +1604,7 @@ { "cell_type": "code", "execution_count": null, - "id": "155", + "id": "156", "metadata": {}, "outputs": [], "source": [ @@ -1603,7 +1613,7 @@ }, { "cell_type": "markdown", - "id": "156", + "id": "157", "metadata": {}, "source": [ "Show free parameters after selection." @@ -1612,7 +1622,7 @@ { "cell_type": "code", "execution_count": null, - "id": "157", + "id": "158", "metadata": {}, "outputs": [], "source": [ @@ -1621,7 +1631,7 @@ }, { "cell_type": "markdown", - "id": "158", + "id": "159", "metadata": {}, "source": [ "#### Run Fitting" @@ -1630,7 +1640,7 @@ { "cell_type": "code", "execution_count": null, - "id": "159", + "id": "160", "metadata": {}, "outputs": [], "source": [ @@ -1641,7 +1651,7 @@ }, { "cell_type": "markdown", - "id": "160", + "id": "161", "metadata": {}, "source": [ "#### Display Pattern" @@ -1650,7 +1660,7 @@ { "cell_type": "code", "execution_count": null, - "id": "161", + "id": "162", "metadata": {}, "outputs": [], "source": [ @@ -1660,7 +1670,7 @@ { "cell_type": "code", "execution_count": null, - "id": "162", + "id": "163", "metadata": {}, "outputs": [], "source": [ @@ -1669,7 +1679,7 @@ }, { "cell_type": "markdown", - "id": "163", + "id": "164", "metadata": {}, "source": [ "#### Display Structure" @@ -1678,7 +1688,7 @@ { "cell_type": "code", "execution_count": null, - "id": "164", + "id": "165", "metadata": {}, "outputs": [], "source": [ @@ -1687,7 +1697,7 @@ }, { "cell_type": "markdown", - "id": "165", + "id": "166", "metadata": {}, "source": [ "## 📊 Report\n", @@ -1706,7 +1716,7 @@ { "cell_type": "code", "execution_count": null, - "id": "166", + "id": "167", "metadata": {}, "outputs": [], "source": [ diff --git a/docs/docs/tutorials/refine-lbco-hrpt-report.py b/docs/docs/tutorials/refine-lbco-hrpt-report.py index 245045aef..949b5a836 100644 --- a/docs/docs/tutorials/refine-lbco-hrpt-report.py +++ b/docs/docs/tutorials/refine-lbco-hrpt-report.py @@ -472,6 +472,9 @@ # %% [markdown] # #### Run Fitting +# %% +project.analysis.minimizer.chi_square_change_tolerance = 1e-2 + # %% project.analysis.fit() project.display.fit.results() diff --git a/docs/docs/tutorials/refine-lbco-si-mcstas.ipynb b/docs/docs/tutorials/refine-lbco-si-mcstas.ipynb index 000dd0c17..cc3548675 100644 --- a/docs/docs/tutorials/refine-lbco-si-mcstas.ipynb +++ b/docs/docs/tutorials/refine-lbco-si-mcstas.ipynb @@ -197,7 +197,7 @@ "outputs": [], "source": [ "structure_2.space_group.name_h_m = 'F d -3 m'\n", - "structure_2.space_group.coord_system_code = '2'" + "structure_2.space_group.coord_system_code = '1'" ] }, { @@ -215,7 +215,7 @@ "metadata": {}, "outputs": [], "source": [ - "structure_2.cell.length_a = 5.43146" + "structure_2.cell.length_a = 5.43" ] }, { @@ -239,7 +239,8 @@ " fract_x=0.0,\n", " fract_y=0.0,\n", " fract_z=0.0,\n", - " adp_iso=0.0,\n", + " adp_type='Biso',\n", + " adp_iso=0.89,\n", ")" ] }, @@ -672,6 +673,16 @@ "id": "60", "metadata": {}, "outputs": [], + "source": [ + "project.analysis.minimizer.chi_square_change_tolerance = 1e-2" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "61", + "metadata": {}, + "outputs": [], "source": [ "project.analysis.fit()\n", "project.display.fit.results()\n", @@ -680,7 +691,7 @@ }, { "cell_type": "markdown", - "id": "61", + "id": "62", "metadata": {}, "source": [ "### Display Pattern" @@ -689,7 +700,7 @@ { "cell_type": "code", "execution_count": null, - "id": "62", + "id": "63", "metadata": {}, "outputs": [], "source": [ @@ -698,7 +709,7 @@ }, { "cell_type": "markdown", - "id": "63", + "id": "64", "metadata": {}, "source": [ "## 💾 Save Project" @@ -707,7 +718,7 @@ { "cell_type": "code", "execution_count": null, - "id": "64", + "id": "65", "metadata": {}, "outputs": [], "source": [ diff --git a/docs/docs/tutorials/refine-lbco-si-mcstas.py b/docs/docs/tutorials/refine-lbco-si-mcstas.py index 44704828e..b0db3d187 100644 --- a/docs/docs/tutorials/refine-lbco-si-mcstas.py +++ b/docs/docs/tutorials/refine-lbco-si-mcstas.py @@ -88,13 +88,13 @@ # %% structure_2.space_group.name_h_m = 'F d -3 m' -structure_2.space_group.coord_system_code = '2' +structure_2.space_group.coord_system_code = '1' # %% [markdown] # #### Set Unit Cell # %% -structure_2.cell.length_a = 5.43146 +structure_2.cell.length_a = 5.43 # %% [markdown] # #### Set Atom Sites @@ -106,7 +106,8 @@ fract_x=0.0, fract_y=0.0, fract_z=0.0, - adp_iso=0.0, + adp_type='Biso', + adp_iso=0.89, ) # %% [markdown] @@ -294,6 +295,9 @@ # %% [markdown] # ### Run Fitting +# %% +project.analysis.minimizer.chi_square_change_tolerance = 1e-2 + # %% project.analysis.fit() project.display.fit.results() diff --git a/docs/docs/tutorials/refine-lmo-echidna.ipynb b/docs/docs/tutorials/refine-lmo-echidna.ipynb index c024415e9..604b69285 100644 --- a/docs/docs/tutorials/refine-lmo-echidna.ipynb +++ b/docs/docs/tutorials/refine-lmo-echidna.ipynb @@ -766,13 +766,23 @@ "id": "61", "metadata": {}, "outputs": [], + "source": [ + "project.analysis.minimizer.chi_square_change_tolerance = 1e-2" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "62", + "metadata": {}, + "outputs": [], "source": [ "project.analysis.fit()" ] }, { "cell_type": "markdown", - "id": "62", + "id": "63", "metadata": {}, "source": [ "### Inspect Results\n", @@ -784,7 +794,7 @@ { "cell_type": "code", "execution_count": null, - "id": "63", + "id": "64", "metadata": {}, "outputs": [], "source": [ @@ -794,7 +804,7 @@ { "cell_type": "code", "execution_count": null, - "id": "64", + "id": "65", "metadata": {}, "outputs": [], "source": [ @@ -804,7 +814,7 @@ { "cell_type": "code", "execution_count": null, - "id": "65", + "id": "66", "metadata": {}, "outputs": [], "source": [ @@ -813,7 +823,7 @@ }, { "cell_type": "markdown", - "id": "66", + "id": "67", "metadata": {}, "source": [ "## 💾 Save Project\n", @@ -825,7 +835,7 @@ { "cell_type": "code", "execution_count": null, - "id": "67", + "id": "68", "metadata": {}, "outputs": [], "source": [ diff --git a/docs/docs/tutorials/refine-lmo-echidna.py b/docs/docs/tutorials/refine-lmo-echidna.py index 23d9e9840..5d4d77b51 100644 --- a/docs/docs/tutorials/refine-lmo-echidna.py +++ b/docs/docs/tutorials/refine-lmo-echidna.py @@ -371,6 +371,9 @@ # %% [markdown] # ### Fit Model +# %% +project.analysis.minimizer.chi_square_change_tolerance = 1e-2 + # %% project.analysis.fit() diff --git a/docs/docs/tutorials/refine-ncaf-wish.ipynb b/docs/docs/tutorials/refine-ncaf-wish.ipynb index 8afeac2bb..9b71e7a9b 100644 --- a/docs/docs/tutorials/refine-ncaf-wish.ipynb +++ b/docs/docs/tutorials/refine-ncaf-wish.ipynb @@ -671,6 +671,16 @@ "id": "50", "metadata": {}, "outputs": [], + "source": [ + "project.analysis.minimizer.chi_square_change_tolerance = 1e-2" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "51", + "metadata": {}, + "outputs": [], "source": [ "project.analysis.fit()\n", "project.display.fit.results()\n", @@ -679,7 +689,7 @@ }, { "cell_type": "markdown", - "id": "51", + "id": "52", "metadata": {}, "source": [ "### Display Pattern" @@ -688,7 +698,7 @@ { "cell_type": "code", "execution_count": null, - "id": "52", + "id": "53", "metadata": {}, "outputs": [], "source": [ @@ -698,7 +708,7 @@ { "cell_type": "code", "execution_count": null, - "id": "53", + "id": "54", "metadata": {}, "outputs": [], "source": [ @@ -707,7 +717,7 @@ }, { "cell_type": "markdown", - "id": "54", + "id": "55", "metadata": {}, "source": [ "## 📊 Report\n", @@ -718,7 +728,7 @@ }, { "cell_type": "markdown", - "id": "55", + "id": "56", "metadata": {}, "source": [ "## 💾 Save Project" @@ -727,7 +737,7 @@ { "cell_type": "code", "execution_count": null, - "id": "56", + "id": "57", "metadata": {}, "outputs": [], "source": [ diff --git a/docs/docs/tutorials/refine-ncaf-wish.py b/docs/docs/tutorials/refine-ncaf-wish.py index cab0e5a2b..46ee49002 100644 --- a/docs/docs/tutorials/refine-ncaf-wish.py +++ b/docs/docs/tutorials/refine-ncaf-wish.py @@ -343,6 +343,9 @@ # %% [markdown] # ### Run Fitting +# %% +project.analysis.minimizer.chi_square_change_tolerance = 1e-2 + # %% project.analysis.fit() project.display.fit.results() diff --git a/docs/docs/tutorials/refine-pbso4-joint.ipynb b/docs/docs/tutorials/refine-pbso4-joint.ipynb index afe3c08ef..f98c3db85 100644 --- a/docs/docs/tutorials/refine-pbso4-joint.ipynb +++ b/docs/docs/tutorials/refine-pbso4-joint.ipynb @@ -705,7 +705,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.analysis.fit()" + "project.analysis.minimizer.chi_square_change_tolerance = 1e-2" ] }, { @@ -714,13 +714,23 @@ "id": "61", "metadata": {}, "outputs": [], + "source": [ + "project.analysis.fit()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "62", + "metadata": {}, + "outputs": [], "source": [ "project.display.fit.results()" ] }, { "cell_type": "markdown", - "id": "62", + "id": "63", "metadata": {}, "source": [ "#### Display Correlations" @@ -729,7 +739,7 @@ { "cell_type": "code", "execution_count": null, - "id": "63", + "id": "64", "metadata": {}, "outputs": [], "source": [ @@ -738,7 +748,7 @@ }, { "cell_type": "markdown", - "id": "64", + "id": "65", "metadata": {}, "source": [ "### Display Pattern" @@ -747,7 +757,7 @@ { "cell_type": "code", "execution_count": null, - "id": "65", + "id": "66", "metadata": {}, "outputs": [], "source": [ @@ -757,7 +767,7 @@ { "cell_type": "code", "execution_count": null, - "id": "66", + "id": "67", "metadata": {}, "outputs": [], "source": [ @@ -766,7 +776,7 @@ }, { "cell_type": "markdown", - "id": "67", + "id": "68", "metadata": {}, "source": [ "### Display Structure" @@ -775,7 +785,7 @@ { "cell_type": "code", "execution_count": null, - "id": "68", + "id": "69", "metadata": {}, "outputs": [], "source": [ @@ -784,7 +794,7 @@ }, { "cell_type": "markdown", - "id": "69", + "id": "70", "metadata": {}, "source": [ "## 💾 Save Project" @@ -793,7 +803,7 @@ { "cell_type": "code", "execution_count": null, - "id": "70", + "id": "71", "metadata": {}, "outputs": [], "source": [ diff --git a/docs/docs/tutorials/refine-pbso4-joint.py b/docs/docs/tutorials/refine-pbso4-joint.py index 842441168..55c2c6967 100644 --- a/docs/docs/tutorials/refine-pbso4-joint.py +++ b/docs/docs/tutorials/refine-pbso4-joint.py @@ -324,6 +324,9 @@ # %% [markdown] # ### Run Fitting +# %% +project.analysis.minimizer.chi_square_change_tolerance = 1e-2 + # %% project.analysis.fit() diff --git a/docs/docs/tutorials/refine-pbso4-xray.ipynb b/docs/docs/tutorials/refine-pbso4-xray.ipynb index a3fb6cd8f..48468f270 100644 --- a/docs/docs/tutorials/refine-pbso4-xray.ipynb +++ b/docs/docs/tutorials/refine-pbso4-xray.ipynb @@ -518,7 +518,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.analysis.fit()" + "project.analysis.minimizer.chi_square_change_tolerance = 1e-2" ] }, { @@ -527,13 +527,23 @@ "id": "43", "metadata": {}, "outputs": [], + "source": [ + "project.analysis.fit()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "44", + "metadata": {}, + "outputs": [], "source": [ "project.display.fit.results()" ] }, { "cell_type": "markdown", - "id": "44", + "id": "45", "metadata": {}, "source": [ "#### Display Correlations" @@ -542,7 +552,7 @@ { "cell_type": "code", "execution_count": null, - "id": "45", + "id": "46", "metadata": {}, "outputs": [], "source": [ @@ -551,7 +561,7 @@ }, { "cell_type": "markdown", - "id": "46", + "id": "47", "metadata": {}, "source": [ "### Display Pattern" @@ -560,7 +570,7 @@ { "cell_type": "code", "execution_count": null, - "id": "47", + "id": "48", "metadata": {}, "outputs": [], "source": [ @@ -570,7 +580,7 @@ { "cell_type": "code", "execution_count": null, - "id": "48", + "id": "49", "metadata": {}, "outputs": [], "source": [ @@ -579,7 +589,7 @@ }, { "cell_type": "markdown", - "id": "49", + "id": "50", "metadata": {}, "source": [ "## 💾 Save Project" @@ -588,7 +598,7 @@ { "cell_type": "code", "execution_count": null, - "id": "50", + "id": "51", "metadata": {}, "outputs": [], "source": [ diff --git a/docs/docs/tutorials/refine-pbso4-xray.py b/docs/docs/tutorials/refine-pbso4-xray.py index 49e79309c..9cd7518c6 100644 --- a/docs/docs/tutorials/refine-pbso4-xray.py +++ b/docs/docs/tutorials/refine-pbso4-xray.py @@ -246,6 +246,9 @@ # %% [markdown] # ### Run Fitting +# %% +project.analysis.minimizer.chi_square_change_tolerance = 1e-2 + # %% project.analysis.fit() diff --git a/docs/docs/tutorials/refine-si-sepd.ipynb b/docs/docs/tutorials/refine-si-sepd.ipynb index 30d687913..9524aee85 100644 --- a/docs/docs/tutorials/refine-si-sepd.ipynb +++ b/docs/docs/tutorials/refine-si-sepd.ipynb @@ -464,6 +464,16 @@ "id": "43", "metadata": {}, "outputs": [], + "source": [ + "project.analysis.minimizer.chi_square_change_tolerance = 1e-2" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "44", + "metadata": {}, + "outputs": [], "source": [ "project.analysis.fit()\n", "project.display.fit.results()" @@ -471,7 +481,7 @@ }, { "cell_type": "markdown", - "id": "44", + "id": "45", "metadata": {}, "source": [ "#### Display Pattern" @@ -480,7 +490,7 @@ { "cell_type": "code", "execution_count": null, - "id": "45", + "id": "46", "metadata": {}, "outputs": [], "source": [ @@ -490,7 +500,7 @@ { "cell_type": "code", "execution_count": null, - "id": "46", + "id": "47", "metadata": {}, "outputs": [], "source": [ @@ -499,7 +509,7 @@ }, { "cell_type": "markdown", - "id": "47", + "id": "48", "metadata": {}, "source": [ "### Perform Fit 2/4\n", @@ -510,7 +520,7 @@ { "cell_type": "code", "execution_count": null, - "id": "48", + "id": "49", "metadata": {}, "outputs": [], "source": [ @@ -520,7 +530,7 @@ }, { "cell_type": "markdown", - "id": "49", + "id": "50", "metadata": {}, "source": [ "Show free parameters after selection." @@ -529,7 +539,7 @@ { "cell_type": "code", "execution_count": null, - "id": "50", + "id": "51", "metadata": {}, "outputs": [], "source": [ @@ -538,7 +548,7 @@ }, { "cell_type": "markdown", - "id": "51", + "id": "52", "metadata": {}, "source": [ "#### Run Fitting" @@ -547,7 +557,7 @@ { "cell_type": "code", "execution_count": null, - "id": "52", + "id": "53", "metadata": {}, "outputs": [], "source": [ @@ -557,7 +567,7 @@ }, { "cell_type": "markdown", - "id": "53", + "id": "54", "metadata": {}, "source": [ "#### Display Pattern" @@ -566,7 +576,7 @@ { "cell_type": "code", "execution_count": null, - "id": "54", + "id": "55", "metadata": {}, "outputs": [], "source": [ @@ -576,7 +586,7 @@ { "cell_type": "code", "execution_count": null, - "id": "55", + "id": "56", "metadata": {}, "outputs": [], "source": [ @@ -585,7 +595,7 @@ }, { "cell_type": "markdown", - "id": "56", + "id": "57", "metadata": {}, "source": [ "### Perform Fit 3/4\n", @@ -596,7 +606,7 @@ { "cell_type": "code", "execution_count": null, - "id": "57", + "id": "58", "metadata": {}, "outputs": [], "source": [ @@ -606,7 +616,7 @@ }, { "cell_type": "markdown", - "id": "58", + "id": "59", "metadata": {}, "source": [ "Set more parameters to be refined." @@ -615,7 +625,7 @@ { "cell_type": "code", "execution_count": null, - "id": "59", + "id": "60", "metadata": {}, "outputs": [], "source": [ @@ -626,7 +636,7 @@ }, { "cell_type": "markdown", - "id": "60", + "id": "61", "metadata": {}, "source": [ "Show free parameters after selection." @@ -635,7 +645,7 @@ { "cell_type": "code", "execution_count": null, - "id": "61", + "id": "62", "metadata": {}, "outputs": [], "source": [ @@ -644,7 +654,7 @@ }, { "cell_type": "markdown", - "id": "62", + "id": "63", "metadata": {}, "source": [ "#### Run Fitting" @@ -653,7 +663,7 @@ { "cell_type": "code", "execution_count": null, - "id": "63", + "id": "64", "metadata": {}, "outputs": [], "source": [ @@ -663,7 +673,7 @@ }, { "cell_type": "markdown", - "id": "64", + "id": "65", "metadata": {}, "source": [ "#### Display Pattern" @@ -672,7 +682,7 @@ { "cell_type": "code", "execution_count": null, - "id": "65", + "id": "66", "metadata": {}, "outputs": [], "source": [ @@ -682,7 +692,7 @@ { "cell_type": "code", "execution_count": null, - "id": "66", + "id": "67", "metadata": {}, "outputs": [], "source": [ @@ -691,7 +701,7 @@ }, { "cell_type": "markdown", - "id": "67", + "id": "68", "metadata": {}, "source": [ "### Perform Fit 4/4\n", @@ -702,7 +712,7 @@ { "cell_type": "code", "execution_count": null, - "id": "68", + "id": "69", "metadata": {}, "outputs": [], "source": [ @@ -714,7 +724,7 @@ }, { "cell_type": "markdown", - "id": "69", + "id": "70", "metadata": {}, "source": [ "Show free parameters after selection." @@ -723,7 +733,7 @@ { "cell_type": "code", "execution_count": null, - "id": "70", + "id": "71", "metadata": {}, "outputs": [], "source": [ @@ -732,7 +742,7 @@ }, { "cell_type": "markdown", - "id": "71", + "id": "72", "metadata": {}, "source": [ "#### Run Fitting" @@ -741,7 +751,7 @@ { "cell_type": "code", "execution_count": null, - "id": "72", + "id": "73", "metadata": {}, "outputs": [], "source": [ @@ -751,7 +761,7 @@ }, { "cell_type": "markdown", - "id": "73", + "id": "74", "metadata": {}, "source": [ "#### Display Correlations" @@ -760,7 +770,7 @@ { "cell_type": "code", "execution_count": null, - "id": "74", + "id": "75", "metadata": {}, "outputs": [], "source": [ @@ -769,7 +779,7 @@ }, { "cell_type": "markdown", - "id": "75", + "id": "76", "metadata": {}, "source": [ "#### Display Pattern" @@ -778,7 +788,7 @@ { "cell_type": "code", "execution_count": null, - "id": "76", + "id": "77", "metadata": {}, "outputs": [], "source": [ @@ -788,7 +798,7 @@ { "cell_type": "code", "execution_count": null, - "id": "77", + "id": "78", "metadata": {}, "outputs": [], "source": [ @@ -798,7 +808,7 @@ { "cell_type": "code", "execution_count": null, - "id": "78", + "id": "79", "metadata": {}, "outputs": [], "source": [ @@ -807,7 +817,7 @@ }, { "cell_type": "markdown", - "id": "79", + "id": "80", "metadata": {}, "source": [ "## 💾 Save Project" @@ -816,7 +826,7 @@ { "cell_type": "code", "execution_count": null, - "id": "80", + "id": "81", "metadata": {}, "outputs": [], "source": [ diff --git a/docs/docs/tutorials/refine-si-sepd.py b/docs/docs/tutorials/refine-si-sepd.py index afd072b0e..f16f42ce1 100644 --- a/docs/docs/tutorials/refine-si-sepd.py +++ b/docs/docs/tutorials/refine-si-sepd.py @@ -183,6 +183,9 @@ # %% project.analysis.minimizer.type = 'bumps (lm)' +# %% +project.analysis.minimizer.chi_square_change_tolerance = 1e-2 + # %% project.analysis.fit() project.display.fit.results() diff --git a/docs/docs/tutorials/refine-taurine-senju.ipynb b/docs/docs/tutorials/refine-taurine-senju.ipynb index e50fe05e3..242ab9e5d 100644 --- a/docs/docs/tutorials/refine-taurine-senju.ipynb +++ b/docs/docs/tutorials/refine-taurine-senju.ipynb @@ -277,6 +277,16 @@ "id": "26", "metadata": {}, "outputs": [], + "source": [ + "project.analysis.minimizer.chi_square_change_tolerance = 1e-2" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "27", + "metadata": {}, + "outputs": [], "source": [ "# Start refinement. All parameters, which have standard uncertainties\n", "# in the input CIF files, are refined by default.\n", @@ -286,7 +296,7 @@ { "cell_type": "code", "execution_count": null, - "id": "27", + "id": "28", "metadata": {}, "outputs": [], "source": [ @@ -297,7 +307,7 @@ { "cell_type": "code", "execution_count": null, - "id": "28", + "id": "29", "metadata": {}, "outputs": [], "source": [ @@ -307,7 +317,7 @@ { "cell_type": "code", "execution_count": null, - "id": "29", + "id": "30", "metadata": {}, "outputs": [], "source": [ @@ -317,7 +327,7 @@ { "cell_type": "code", "execution_count": null, - "id": "30", + "id": "31", "metadata": {}, "outputs": [], "source": [ @@ -326,7 +336,7 @@ }, { "cell_type": "markdown", - "id": "31", + "id": "32", "metadata": {}, "source": [ "### ADP aniso" @@ -335,7 +345,7 @@ { "cell_type": "code", "execution_count": null, - "id": "32", + "id": "33", "metadata": {}, "outputs": [], "source": [ @@ -346,7 +356,7 @@ { "cell_type": "code", "execution_count": null, - "id": "33", + "id": "34", "metadata": {}, "outputs": [], "source": [ @@ -359,7 +369,7 @@ { "cell_type": "code", "execution_count": null, - "id": "34", + "id": "35", "metadata": {}, "outputs": [], "source": [ @@ -369,7 +379,7 @@ { "cell_type": "code", "execution_count": null, - "id": "35", + "id": "36", "metadata": {}, "outputs": [], "source": [ @@ -379,7 +389,7 @@ { "cell_type": "code", "execution_count": null, - "id": "36", + "id": "37", "metadata": {}, "outputs": [], "source": [ @@ -389,7 +399,7 @@ { "cell_type": "code", "execution_count": null, - "id": "37", + "id": "38", "metadata": {}, "outputs": [], "source": [ @@ -399,7 +409,7 @@ { "cell_type": "code", "execution_count": null, - "id": "38", + "id": "39", "metadata": {}, "outputs": [], "source": [ @@ -409,7 +419,7 @@ { "cell_type": "code", "execution_count": null, - "id": "39", + "id": "40", "metadata": {}, "outputs": [], "source": [ @@ -419,7 +429,7 @@ { "cell_type": "code", "execution_count": null, - "id": "40", + "id": "41", "metadata": {}, "outputs": [], "source": [ @@ -428,7 +438,7 @@ }, { "cell_type": "markdown", - "id": "41", + "id": "42", "metadata": {}, "source": [ "## 💾 Save Project" @@ -437,7 +447,7 @@ { "cell_type": "code", "execution_count": null, - "id": "42", + "id": "43", "metadata": {}, "outputs": [], "source": [ diff --git a/docs/docs/tutorials/refine-taurine-senju.py b/docs/docs/tutorials/refine-taurine-senju.py index 6a331b8a7..ca1e093d6 100644 --- a/docs/docs/tutorials/refine-taurine-senju.py +++ b/docs/docs/tutorials/refine-taurine-senju.py @@ -89,6 +89,9 @@ # Limit number of iterations to prevent long calculation time in this tutorial. project.analysis.minimizer.max_iterations = 500 +# %% +project.analysis.minimizer.chi_square_change_tolerance = 1e-2 + # %% # Start refinement. All parameters, which have standard uncertainties # in the input CIF files, are refined by default. diff --git a/docs/docs/tutorials/refine-tbti-heidi.ipynb b/docs/docs/tutorials/refine-tbti-heidi.ipynb index ddbb1943a..32d45e45a 100644 --- a/docs/docs/tutorials/refine-tbti-heidi.ipynb +++ b/docs/docs/tutorials/refine-tbti-heidi.ipynb @@ -339,6 +339,16 @@ "id": "30", "metadata": {}, "outputs": [], + "source": [ + "project.analysis.minimizer.chi_square_change_tolerance = 1e-2" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "31", + "metadata": {}, + "outputs": [], "source": [ "# Start refinement. All parameters, which have standard uncertainties\n", "# in the input CIF files, are refined by default.\n", @@ -348,7 +358,7 @@ { "cell_type": "code", "execution_count": null, - "id": "31", + "id": "32", "metadata": {}, "outputs": [], "source": [ @@ -359,7 +369,7 @@ { "cell_type": "code", "execution_count": null, - "id": "32", + "id": "33", "metadata": {}, "outputs": [], "source": [ @@ -369,7 +379,7 @@ { "cell_type": "code", "execution_count": null, - "id": "33", + "id": "34", "metadata": {}, "outputs": [], "source": [ @@ -379,7 +389,7 @@ { "cell_type": "code", "execution_count": null, - "id": "34", + "id": "35", "metadata": {}, "outputs": [], "source": [ @@ -389,7 +399,7 @@ { "cell_type": "code", "execution_count": null, - "id": "35", + "id": "36", "metadata": {}, "outputs": [], "source": [ @@ -398,7 +408,7 @@ }, { "cell_type": "markdown", - "id": "36", + "id": "37", "metadata": {}, "source": [ "### ADP aniso" @@ -407,7 +417,7 @@ { "cell_type": "code", "execution_count": null, - "id": "37", + "id": "38", "metadata": {}, "outputs": [], "source": [ @@ -420,7 +430,7 @@ { "cell_type": "code", "execution_count": null, - "id": "38", + "id": "39", "metadata": {}, "outputs": [], "source": [ @@ -430,7 +440,7 @@ { "cell_type": "code", "execution_count": null, - "id": "39", + "id": "40", "metadata": {}, "outputs": [], "source": [ @@ -447,7 +457,7 @@ { "cell_type": "code", "execution_count": null, - "id": "40", + "id": "41", "metadata": {}, "outputs": [], "source": [ @@ -457,7 +467,7 @@ { "cell_type": "code", "execution_count": null, - "id": "41", + "id": "42", "metadata": {}, "outputs": [], "source": [ @@ -467,7 +477,7 @@ { "cell_type": "code", "execution_count": null, - "id": "42", + "id": "43", "metadata": {}, "outputs": [], "source": [ @@ -477,7 +487,7 @@ { "cell_type": "code", "execution_count": null, - "id": "43", + "id": "44", "metadata": {}, "outputs": [], "source": [ @@ -487,7 +497,7 @@ { "cell_type": "code", "execution_count": null, - "id": "44", + "id": "45", "metadata": {}, "outputs": [], "source": [ @@ -496,7 +506,7 @@ }, { "cell_type": "markdown", - "id": "45", + "id": "46", "metadata": {}, "source": [ "### Display Structure (final)\n", @@ -510,7 +520,7 @@ { "cell_type": "code", "execution_count": null, - "id": "46", + "id": "47", "metadata": {}, "outputs": [], "source": [ @@ -519,7 +529,7 @@ }, { "cell_type": "markdown", - "id": "47", + "id": "48", "metadata": {}, "source": [ "## 📊 Report\n", @@ -532,7 +542,7 @@ { "cell_type": "code", "execution_count": null, - "id": "48", + "id": "49", "metadata": {}, "outputs": [], "source": [ diff --git a/docs/docs/tutorials/refine-tbti-heidi.py b/docs/docs/tutorials/refine-tbti-heidi.py index eb2ad0c53..f053dbedc 100644 --- a/docs/docs/tutorials/refine-tbti-heidi.py +++ b/docs/docs/tutorials/refine-tbti-heidi.py @@ -125,6 +125,9 @@ experiment.linked_structure.scale.free = True experiment.extinction.radius.free = True +# %% +project.analysis.minimizer.chi_square_change_tolerance = 1e-2 + # %% # Start refinement. All parameters, which have standard uncertainties # in the input CIF files, are refined by default. diff --git a/docs/docs/tutorials/refine-yap-3k.ipynb b/docs/docs/tutorials/refine-yap-3k.ipynb new file mode 100644 index 000000000..54bb188c9 --- /dev/null +++ b/docs/docs/tutorials/refine-yap-3k.ipynb @@ -0,0 +1,989 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "0", + "metadata": { + "tags": [ + "hide-in-docs" + ] + }, + "outputs": [], + "source": [ + "# Check whether easydiffraction is installed; install it if needed.\n", + "# Required for remote environments such as Google Colab.\n", + "import importlib.util\n", + "\n", + "if importlib.util.find_spec('easydiffraction') is None:\n", + " %pip install easydiffraction" + ] + }, + { + "cell_type": "markdown", + "id": "1", + "metadata": {}, + "source": [ + "# Structure Refinement: YAlO3+Al2O3, SPODI\n", + "\n", + "This example demonstrates a staged two-phase Rietveld refinement of\n", + "yttrium aluminium perovskite YAlO3 (or YAP) with a small Al2O3\n", + "impurity using constant wavelength neutron powder diffraction data\n", + "measured at 3 K on SPODI at MLZ.\n", + "\n", + "The workflow defines both structures, configures the experiment, and\n", + "refines the cell, scale, profile, background, and atom parameters of\n", + "both phases in stages." + ] + }, + { + "cell_type": "markdown", + "id": "2", + "metadata": {}, + "source": [ + "## 🛠️ Import Library" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3", + "metadata": {}, + "outputs": [], + "source": [ + "import easydiffraction as edi" + ] + }, + { + "cell_type": "markdown", + "id": "4", + "metadata": {}, + "source": [ + "## 📦 Define Project\n", + "\n", + "### Create Project" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5", + "metadata": {}, + "outputs": [], + "source": [ + "project = edi.Project(\n", + " name='yap_3k',\n", + " description='Two-phase YAlO3 and Al2O3 refinement using 3 K data from SPODI at MLZ.',\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "6", + "metadata": {}, + "source": [ + "### Save Initial Project" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7", + "metadata": {}, + "outputs": [], + "source": [ + "project.save_as(dir_path='projects/refine-yap-3k')" + ] + }, + { + "cell_type": "markdown", + "id": "8", + "metadata": {}, + "source": [ + "## 🧩 Define Structures\n", + "\n", + "### Create Structure 1: YAlO3\n", + "\n", + "Preserve the orthorhombic Pbnm setting used in FullProf. In\n", + "EasyDiffraction this is represented by the standard space-group\n", + "symbol `P n m a` with coordinate-system code `cab`. The cell axes and\n", + "atom coordinates below therefore stay in the original Pbnm setting.\n", + "\n", + "FullProf's PCR occupancies include the site multiplicity divided by\n", + "the general-position multiplicity. Here each atom site is fully\n", + "occupied: the PCR values 0.5 for Y, Al, and O1, and 1.0 for O2, all\n", + "become an occupancy of 1.0. Displacement parameters are entered as\n", + "Biso, matching the PCR file." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9", + "metadata": {}, + "outputs": [], + "source": [ + "yap_cif = \"\"\"\n", + "data_yap\n", + "\n", + "_cell.length_a 5.18\n", + "_cell.length_b 5.33\n", + "_cell.length_c 7.37\n", + "_cell.angle_alpha 90.\n", + "_cell.angle_beta 90.\n", + "_cell.angle_gamma 90.\n", + "\n", + "_space_group.name_h_m \"P n m a\"\n", + "_space_group.coord_system_code cab\n", + "\n", + "loop_\n", + "_atom_site.id\n", + "_atom_site.type_symbol\n", + "_atom_site.fract_x\n", + "_atom_site.fract_y\n", + "_atom_site.fract_z\n", + "_atom_site.occupancy\n", + "_atom_site.adp_iso\n", + "_atom_site.adp_type\n", + "Y Y 0.0100 0.5500 0.2500 1.0 0.12 Biso\n", + "Al Al 0.0000 0.0000 0.0000 1.0 0.13 Biso\n", + "O1 O -0.0800 -0.0200 0.2500 1.0 0.06 Biso\n", + "O2 O 0.2000 0.2900 0.0400 1.0 0.14 Biso\n", + "\"\"\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "10", + "metadata": {}, + "outputs": [], + "source": [ + "project.structures.add_from_cif_str(yap_cif)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "11", + "metadata": {}, + "outputs": [], + "source": [ + "yap = project.structures['yap']" + ] + }, + { + "cell_type": "markdown", + "id": "12", + "metadata": {}, + "source": [ + "### Display Structure 1: YAlO3" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "13", + "metadata": {}, + "outputs": [], + "source": [ + "yap.show_as_text()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "14", + "metadata": {}, + "outputs": [], + "source": [ + "project.display.structure(struct_name='yap')" + ] + }, + { + "cell_type": "markdown", + "id": "15", + "metadata": {}, + "source": [ + "### Create Structure 2: Al2O3\n", + "\n", + "Define the corundum impurity in the hexagonal setting of R-3c. The\n", + "FullProf PCR occupancies of 2/3 for Al and 1 for O also describe fully\n", + "occupied sites. Refine its two independent cell lengths, Al z and O x\n", + "coordinates, and both Biso values, as specified by the PCR codewords.\n", + "The PCR contains a negative Al Biso. EasyDiffraction requires a\n", + "nonnegative input value, so start this parameter at 0.1 Ų and refine\n", + "it alongside O Biso." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "16", + "metadata": {}, + "outputs": [], + "source": [ + "alumina = edi.StructureFactory.from_scratch(name='alumina')" + ] + }, + { + "cell_type": "markdown", + "id": "17", + "metadata": {}, + "source": [ + "#### Set Space Group" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "18", + "metadata": {}, + "outputs": [], + "source": [ + "alumina.space_group.name_h_m = 'R -3 c'\n", + "alumina.space_group.coord_system_code = 'h'" + ] + }, + { + "cell_type": "markdown", + "id": "19", + "metadata": {}, + "source": [ + "#### Set Unit Cell" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "20", + "metadata": {}, + "outputs": [], + "source": [ + "alumina.cell.length_a = 4.75\n", + "alumina.cell.length_c = 12.95" + ] + }, + { + "cell_type": "markdown", + "id": "21", + "metadata": {}, + "source": [ + "#### Set Atom Sites" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "22", + "metadata": {}, + "outputs": [], + "source": [ + "alumina.atom_sites.create(\n", + " id='Al1',\n", + " type_symbol='Al',\n", + " fract_x=0.0,\n", + " fract_y=0.0,\n", + " fract_z=0.33351,\n", + " occupancy=1.0,\n", + " adp_type='Biso',\n", + " adp_iso=0.1,\n", + ")\n", + "alumina.atom_sites.create(\n", + " id='O1',\n", + " type_symbol='O',\n", + " fract_x=0.3503,\n", + " fract_y=0.0,\n", + " fract_z=0.25,\n", + " occupancy=1.0,\n", + " adp_type='Biso',\n", + " adp_iso=1.22884,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "23", + "metadata": {}, + "outputs": [], + "source": [ + "project.structures.add(alumina)" + ] + }, + { + "cell_type": "markdown", + "id": "24", + "metadata": {}, + "source": [ + "### Display Structure 2: Al2O3" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "25", + "metadata": {}, + "outputs": [], + "source": [ + "alumina.show_as_text()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "26", + "metadata": {}, + "outputs": [], + "source": [ + "project.display.structure(struct_name='alumina')" + ] + }, + { + "cell_type": "markdown", + "id": "27", + "metadata": {}, + "source": [ + "## 🔬 Define Experiment\n", + "\n", + "### Download Measured Data\n", + "\n", + "Download the YAlO3 + Al2O3 pattern from the EasyDiffraction online\n", + "data repository. The three columns contain 2-theta in degrees,\n", + "intensity, and its standard uncertainty. They are copied from the\n", + "original SPODI dataset without changing the measured values." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "28", + "metadata": {}, + "outputs": [], + "source": [ + "data_path = edi.download_data('meas-yap-spodi', destination='data')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "29", + "metadata": {}, + "outputs": [], + "source": [ + "project.experiments.add_from_data_path(\n", + " name='yap_3k',\n", + " data_path=data_path,\n", + " sample_form='powder',\n", + " beam_mode='constant wavelength',\n", + " radiation_probe='neutron',\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "30", + "metadata": {}, + "outputs": [], + "source": [ + "expt = project.experiments['yap_3k']" + ] + }, + { + "cell_type": "markdown", + "id": "31", + "metadata": {}, + "source": [ + "### Set Instrument\n", + "\n", + "Use the neutron wavelength reported for the SPODI dataset and start\n", + "with zero 2-theta offset." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "32", + "metadata": {}, + "outputs": [], + "source": [ + "expt.instrument.setup_wavelength = 1.54816\n", + "expt.instrument.calib_twotheta_offset = 0.0" + ] + }, + { + "cell_type": "markdown", + "id": "33", + "metadata": {}, + "source": [ + "### Set Peak Profile\n", + "\n", + "Use a pseudo-Voigt profile with Bérar-Baldinozzi asymmetry." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "34", + "metadata": {}, + "outputs": [], + "source": [ + "expt.peak.show_supported()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "35", + "metadata": {}, + "outputs": [], + "source": [ + "expt.peak.type = 'pseudo-voigt + berar-baldinozzi asymmetry'" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "36", + "metadata": {}, + "outputs": [], + "source": [ + "expt.peak.broad_gauss_u = 0.04\n", + "expt.peak.broad_gauss_v = -0.05\n", + "expt.peak.broad_gauss_w = 0.10\n", + "expt.peak.broad_lorentz_x = 0.0\n", + "expt.peak.broad_lorentz_y = 0.01" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "37", + "metadata": {}, + "outputs": [], + "source": [ + "expt.peak.asym_beba_a0 = 0.0\n", + "expt.peak.asym_beba_b0 = 0.0\n", + "expt.peak.asym_beba_a1 = 0.0\n", + "expt.peak.asym_beba_b1 = 0.0" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "38", + "metadata": {}, + "outputs": [], + "source": [ + "expt.peak.cutoff_fwhm = 8.0" + ] + }, + { + "cell_type": "markdown", + "id": "39", + "metadata": {}, + "source": [ + "### Set Absorption\n", + "\n", + "Apply the cylindrical-sample Hewat correction with the absorption\n", + "radius product from FullProf." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "40", + "metadata": {}, + "outputs": [], + "source": [ + "expt.absorption.type = 'cylinder-hewat'\n", + "expt.absorption.mu_r = 0.0221" + ] + }, + { + "cell_type": "markdown", + "id": "41", + "metadata": {}, + "source": [ + "### Set Excluded Regions" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "42", + "metadata": {}, + "outputs": [], + "source": [ + "expt.excluded_regions.create(id='1', start=0.0, end=4.0)\n", + "expt.excluded_regions.create(id='2', start=153.95, end=180.0)" + ] + }, + { + "cell_type": "markdown", + "id": "43", + "metadata": {}, + "source": [ + "### Set Background\n", + "\n", + "Estimate the initial line-segment background from the measured pattern.\n", + "This first estimate does not use a calculated structural model." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "44", + "metadata": {}, + "outputs": [], + "source": [ + "expt.background.auto_estimate(use_model=False)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "45", + "metadata": {}, + "outputs": [], + "source": [ + "expt.background.show()" + ] + }, + { + "cell_type": "markdown", + "id": "46", + "metadata": {}, + "source": [ + "### Set Linked Structures\n", + "\n", + "Give each phase an independent scale factor. Scale factors are fitted\n", + "intensity multipliers, rather than phase weight fractions." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "47", + "metadata": {}, + "outputs": [], + "source": [ + "expt.linked_structures.create(structure_id='yap', scale=30)\n", + "expt.linked_structures.create(structure_id='alumina', scale=0.1)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "48", + "metadata": {}, + "outputs": [], + "source": [ + "expt.show_as_text()" + ] + }, + { + "cell_type": "markdown", + "id": "49", + "metadata": {}, + "source": [ + "## 🚀 Perform Analysis\n", + "\n", + "### Display Initial Pattern" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "50", + "metadata": { + "lines_to_next_cell": 2 + }, + "outputs": [], + "source": [ + "project.display.pattern(expt_name='yap_3k')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "51", + "metadata": {}, + "outputs": [], + "source": [ + "project.display.pattern(expt_name='yap_3k', x_min=134, x_max=146)" + ] + }, + { + "cell_type": "markdown", + "id": "52", + "metadata": {}, + "source": [ + "### Select Calculator" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "53", + "metadata": {}, + "outputs": [], + "source": [ + "expt.calculator.show_supported()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "54", + "metadata": {}, + "outputs": [], + "source": [ + "expt.calculator.type = 'cryspy'" + ] + }, + { + "cell_type": "markdown", + "id": "55", + "metadata": {}, + "source": [ + "### Select Minimizer" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "56", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.minimizer.show_supported()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "57", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.minimizer.type = 'bumps (lm)'" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "58", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.minimizer.max_iterations = 500\n", + "project.analysis.minimizer.chi_square_change_tolerance = 1e-2" + ] + }, + { + "cell_type": "markdown", + "id": "59", + "metadata": {}, + "source": [ + "### Perform Fit 1/3: Cell, Scale, and Background\n", + "\n", + "First refine the independent cell lengths of both phases, both phase\n", + "scales, the instrument zero offset, and the automatically estimated\n", + "background intensities. Hexagonal symmetry couples the Al2O3 b length\n", + "to a, leaving only a and c independent." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "60", + "metadata": {}, + "outputs": [], + "source": [ + "yap.cell.length_a.free = True\n", + "yap.cell.length_b.free = True\n", + "yap.cell.length_c.free = True\n", + "\n", + "alumina.cell.length_a.free = True\n", + "alumina.cell.length_c.free = True\n", + "\n", + "expt.linked_structures['yap'].scale.free = True\n", + "expt.linked_structures['alumina'].scale.free = True\n", + "\n", + "expt.instrument.calib_twotheta_offset.free = True\n", + "\n", + "for point in expt.background:\n", + " point.intensity.free = True" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "61", + "metadata": {}, + "outputs": [], + "source": [ + "project.display.parameters.free()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "62", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.fit()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "63", + "metadata": {}, + "outputs": [], + "source": [ + "project.display.fit.results()" + ] + }, + { + "cell_type": "markdown", + "id": "64", + "metadata": {}, + "source": [ + "### Perform Fit 2/3: Peak Profile\n", + "\n", + "Add the Gaussian and Lorentzian broadening and asymmetry parameters\n", + "to the refinement. The background intensities remain free." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "65", + "metadata": {}, + "outputs": [], + "source": [ + "expt.peak.broad_gauss_u.free = True\n", + "expt.peak.broad_gauss_v.free = True\n", + "expt.peak.broad_gauss_w.free = True\n", + "expt.peak.broad_lorentz_y.free = True\n", + "\n", + "expt.peak.asym_beba_a0.free = True\n", + "# expt.peak.asym_beba_b0.free = True\n", + "# expt.peak.asym_beba_a1.free = True\n", + "expt.peak.asym_beba_b1.free = True" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "66", + "metadata": {}, + "outputs": [], + "source": [ + "project.display.parameters.free()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "67", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.fit()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "68", + "metadata": {}, + "outputs": [], + "source": [ + "project.display.fit.results()" + ] + }, + { + "cell_type": "markdown", + "id": "69", + "metadata": {}, + "source": [ + "### Perform Fit 3/3: Model-Guided Background and Atom Parameters\n", + "\n", + "Replace the initial background with a new estimate based on the fitted\n", + "peak model. Automatically generated points are fixed by default, so\n", + "mark their intensities free before fitting them with the atom parameters." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "70", + "metadata": {}, + "outputs": [], + "source": [ + "expt.background.auto_estimate(use_model=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "71", + "metadata": {}, + "outputs": [], + "source": [ + "expt.background.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "72", + "metadata": {}, + "outputs": [], + "source": [ + "for point in expt.background:\n", + " point.intensity.free = True" + ] + }, + { + "cell_type": "markdown", + "id": "73", + "metadata": {}, + "source": [ + "Refine the independent Y and O coordinates in Pbnm and the\n", + "isotropic displacement parameters of both phases. Symmetry keeps Y\n", + "and O1 in YAlO3 at z = 1/4 and Al at the origin. For Al2O3, refine\n", + "Al1 z and O1 x. Occupancies remain fixed\n", + "at 1.0, and all coordinates fixed by symmetry remain fixed." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "74", + "metadata": {}, + "outputs": [], + "source": [ + "yap.atom_sites['Y'].fract_x.free = True\n", + "yap.atom_sites['Y'].fract_y.free = True\n", + "yap.atom_sites['O1'].fract_x.free = True\n", + "yap.atom_sites['O1'].fract_y.free = True\n", + "yap.atom_sites['O2'].fract_x.free = True\n", + "yap.atom_sites['O2'].fract_y.free = True\n", + "yap.atom_sites['O2'].fract_z.free = True\n", + "\n", + "alumina.atom_sites['Al1'].fract_z.free = True\n", + "alumina.atom_sites['O1'].fract_x.free = True\n", + "\n", + "for structure in (yap, alumina):\n", + " for atom in structure.atom_sites:\n", + " atom.adp_iso.free = True" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "75", + "metadata": {}, + "outputs": [], + "source": [ + "project.display.parameters.free()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "76", + "metadata": {}, + "outputs": [], + "source": [ + "project.analysis.fit()" + ] + }, + { + "cell_type": "markdown", + "id": "77", + "metadata": {}, + "source": [ + "### Inspect Results\n", + "\n", + "Review the fit statistics, refined parameters, and correlations.\n", + "Inspect the full pattern and a closer view with impurity reflections." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "78", + "metadata": {}, + "outputs": [], + "source": [ + "project.display.fit.results()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "79", + "metadata": {}, + "outputs": [], + "source": [ + "project.display.fit.correlations()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "80", + "metadata": {}, + "outputs": [], + "source": [ + "project.display.pattern(expt_name='yap_3k')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "81", + "metadata": {}, + "outputs": [], + "source": [ + "project.display.pattern(expt_name='yap_3k', x_min=134, x_max=146)" + ] + }, + { + "cell_type": "markdown", + "id": "82", + "metadata": {}, + "source": [ + "## 💾 Save Project\n", + "\n", + "Save the refined model and analysis results in the project directory." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "83", + "metadata": {}, + "outputs": [], + "source": [ + "project.save()" + ] + } + ], + "metadata": { + "jupytext": { + "cell_metadata_filter": "-all", + "main_language": "python", + "notebook_metadata_filter": "-all" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/docs/tutorials/refine-yap-3k.py b/docs/docs/tutorials/refine-yap-3k.py new file mode 100644 index 000000000..bf411e986 --- /dev/null +++ b/docs/docs/tutorials/refine-yap-3k.py @@ -0,0 +1,428 @@ +# %% [markdown] +# # Structure Refinement: YAlO3+Al2O3, SPODI +# +# This example demonstrates a staged two-phase Rietveld refinement of +# yttrium aluminium perovskite YAlO3 (or YAP) with a small Al2O3 +# impurity using constant wavelength neutron powder diffraction data +# measured at 3 K on SPODI at MLZ. +# +# The workflow defines both structures, configures the experiment, and +# refines the cell, scale, profile, background, and atom parameters of +# both phases in stages. + +# %% [markdown] +# ## 🛠️ Import Library + +# %% +import easydiffraction as edi + +# %% [markdown] +# ## 📦 Define Project +# +# ### Create Project + +# %% +project = edi.Project( + name='yap_3k', + description='Two-phase YAlO3 and Al2O3 refinement using 3 K data from SPODI at MLZ.', +) + +# %% [markdown] +# ### Save Initial Project + +# %% +project.save_as(dir_path='projects/refine-yap-3k') + +# %% [markdown] +# ## 🧩 Define Structures +# +# ### Create Structure 1: YAlO3 +# +# Preserve the orthorhombic Pbnm setting used in FullProf. In +# EasyDiffraction this is represented by the standard space-group +# symbol `P n m a` with coordinate-system code `cab`. The cell axes and +# atom coordinates below therefore stay in the original Pbnm setting. +# +# FullProf's PCR occupancies include the site multiplicity divided by +# the general-position multiplicity. Here each atom site is fully +# occupied: the PCR values 0.5 for Y, Al, and O1, and 1.0 for O2, all +# become an occupancy of 1.0. Displacement parameters are entered as +# Biso, matching the PCR file. + +# %% +yap_cif = """ +data_yap + +_cell.length_a 5.18 +_cell.length_b 5.33 +_cell.length_c 7.37 +_cell.angle_alpha 90. +_cell.angle_beta 90. +_cell.angle_gamma 90. + +_space_group.name_h_m "P n m a" +_space_group.coord_system_code cab + +loop_ +_atom_site.id +_atom_site.type_symbol +_atom_site.fract_x +_atom_site.fract_y +_atom_site.fract_z +_atom_site.occupancy +_atom_site.adp_iso +_atom_site.adp_type +Y Y 0.0100 0.5500 0.2500 1.0 0.12 Biso +Al Al 0.0000 0.0000 0.0000 1.0 0.13 Biso +O1 O -0.0800 -0.0200 0.2500 1.0 0.06 Biso +O2 O 0.2000 0.2900 0.0400 1.0 0.14 Biso +""" + +# %% +project.structures.add_from_cif_str(yap_cif) + +# %% +yap = project.structures['yap'] + +# %% [markdown] +# ### Display Structure 1: YAlO3 + +# %% +yap.show_as_text() + +# %% +project.display.structure(struct_name='yap') + +# %% [markdown] +# ### Create Structure 2: Al2O3 +# +# Define the corundum impurity in the hexagonal setting of R-3c. The +# FullProf PCR occupancies of 2/3 for Al and 1 for O also describe fully +# occupied sites. Refine its two independent cell lengths, Al z and O x +# coordinates, and both Biso values, as specified by the PCR codewords. +# The PCR contains a negative Al Biso. EasyDiffraction requires a +# nonnegative input value, so start this parameter at 0.1 Ų and refine +# it alongside O Biso. + +# %% +alumina = edi.StructureFactory.from_scratch(name='alumina') + +# %% [markdown] +# #### Set Space Group + +# %% +alumina.space_group.name_h_m = 'R -3 c' +alumina.space_group.coord_system_code = 'h' + +# %% [markdown] +# #### Set Unit Cell + +# %% +alumina.cell.length_a = 4.75 +alumina.cell.length_c = 12.95 + +# %% [markdown] +# #### Set Atom Sites + +# %% +alumina.atom_sites.create( + id='Al1', + type_symbol='Al', + fract_x=0.0, + fract_y=0.0, + fract_z=0.33351, + occupancy=1.0, + adp_type='Biso', + adp_iso=0.1, +) +alumina.atom_sites.create( + id='O1', + type_symbol='O', + fract_x=0.3503, + fract_y=0.0, + fract_z=0.25, + occupancy=1.0, + adp_type='Biso', + adp_iso=1.22884, +) + +# %% +project.structures.add(alumina) + +# %% [markdown] +# ### Display Structure 2: Al2O3 + +# %% +alumina.show_as_text() + +# %% +project.display.structure(struct_name='alumina') + +# %% [markdown] +# ## 🔬 Define Experiment +# +# ### Download Measured Data +# +# Download the YAlO3 + Al2O3 pattern from the EasyDiffraction online +# data repository. The three columns contain 2-theta in degrees, +# intensity, and its standard uncertainty. They are copied from the +# original SPODI dataset without changing the measured values. + +# %% +data_path = edi.download_data('meas-yap-spodi', destination='data') + +# %% +project.experiments.add_from_data_path( + name='yap_3k', + data_path=data_path, + sample_form='powder', + beam_mode='constant wavelength', + radiation_probe='neutron', +) + +# %% +expt = project.experiments['yap_3k'] + +# %% [markdown] +# ### Set Instrument +# +# Use the neutron wavelength reported for the SPODI dataset and start +# with zero 2-theta offset. + +# %% +expt.instrument.setup_wavelength = 1.54816 +expt.instrument.calib_twotheta_offset = 0.0 + +# %% [markdown] +# ### Set Peak Profile +# +# Use a pseudo-Voigt profile with Bérar-Baldinozzi asymmetry. + +# %% +expt.peak.show_supported() + +# %% +expt.peak.type = 'pseudo-voigt + berar-baldinozzi asymmetry' + +# %% +expt.peak.broad_gauss_u = 0.04 +expt.peak.broad_gauss_v = -0.05 +expt.peak.broad_gauss_w = 0.10 +expt.peak.broad_lorentz_x = 0.0 +expt.peak.broad_lorentz_y = 0.01 + +# %% +expt.peak.asym_beba_a0 = 0.0 +expt.peak.asym_beba_b0 = 0.0 +expt.peak.asym_beba_a1 = 0.0 +expt.peak.asym_beba_b1 = 0.0 + +# %% +expt.peak.cutoff_fwhm = 8.0 + +# %% [markdown] +# ### Set Absorption +# +# Apply the cylindrical-sample Hewat correction with the absorption +# radius product from FullProf. + +# %% +expt.absorption.type = 'cylinder-hewat' +expt.absorption.mu_r = 0.0221 + +# %% [markdown] +# ### Set Excluded Regions + +# %% +expt.excluded_regions.create(id='1', start=0.0, end=4.0) +expt.excluded_regions.create(id='2', start=153.95, end=180.0) + +# %% [markdown] +# ### Set Background +# +# Estimate the initial line-segment background from the measured pattern. +# This first estimate does not use a calculated structural model. + +# %% +expt.background.auto_estimate(use_model=False) + +# %% +expt.background.show() + +# %% [markdown] +# ### Set Linked Structures +# +# Give each phase an independent scale factor. Scale factors are fitted +# intensity multipliers, rather than phase weight fractions. + +# %% +expt.linked_structures.create(structure_id='yap', scale=30) +expt.linked_structures.create(structure_id='alumina', scale=0.1) + +# %% +expt.show_as_text() + +# %% [markdown] +# ## 🚀 Perform Analysis +# +# ### Display Initial Pattern + +# %% +project.display.pattern(expt_name='yap_3k') + + +# %% +project.display.pattern(expt_name='yap_3k', x_min=134, x_max=146) + +# %% [markdown] +# ### Select Calculator + +# %% +expt.calculator.show_supported() + +# %% +expt.calculator.type = 'cryspy' + +# %% [markdown] +# ### Select Minimizer + +# %% +project.analysis.minimizer.show_supported() + +# %% +project.analysis.minimizer.type = 'bumps (lm)' + +# %% +project.analysis.minimizer.max_iterations = 500 +project.analysis.minimizer.chi_square_change_tolerance = 1e-2 + +# %% [markdown] +# ### Perform Fit 1/3: Cell, Scale, and Background +# +# First refine the independent cell lengths of both phases, both phase +# scales, the instrument zero offset, and the automatically estimated +# background intensities. Hexagonal symmetry couples the Al2O3 b length +# to a, leaving only a and c independent. + +# %% +yap.cell.length_a.free = True +yap.cell.length_b.free = True +yap.cell.length_c.free = True + +alumina.cell.length_a.free = True +alumina.cell.length_c.free = True + +expt.linked_structures['yap'].scale.free = True +expt.linked_structures['alumina'].scale.free = True + +expt.instrument.calib_twotheta_offset.free = True + +for point in expt.background: + point.intensity.free = True + +# %% +project.display.parameters.free() + +# %% +project.analysis.fit() + +# %% +project.display.fit.results() + +# %% [markdown] +# ### Perform Fit 2/3: Peak Profile +# +# Add the Gaussian and Lorentzian broadening and asymmetry parameters +# to the refinement. The background intensities remain free. + +# %% +expt.peak.broad_gauss_u.free = True +expt.peak.broad_gauss_v.free = True +expt.peak.broad_gauss_w.free = True +expt.peak.broad_lorentz_y.free = True + +expt.peak.asym_beba_a0.free = True +# expt.peak.asym_beba_b0.free = True +# expt.peak.asym_beba_a1.free = True +expt.peak.asym_beba_b1.free = True + +# %% +project.display.parameters.free() + +# %% +project.analysis.fit() + +# %% +project.display.fit.results() + +# %% [markdown] +# ### Perform Fit 3/3: Model-Guided Background and Atom Parameters +# +# Replace the initial background with a new estimate based on the fitted +# peak model. Automatically generated points are fixed by default, so +# mark their intensities free before fitting them with the atom parameters. + +# %% +expt.background.auto_estimate(use_model=True) + +# %% +expt.background.show() + +# %% +for point in expt.background: + point.intensity.free = True + +# %% [markdown] +# Refine the independent Y and O coordinates in Pbnm and the +# isotropic displacement parameters of both phases. Symmetry keeps Y +# and O1 in YAlO3 at z = 1/4 and Al at the origin. For Al2O3, refine +# Al1 z and O1 x. Occupancies remain fixed +# at 1.0, and all coordinates fixed by symmetry remain fixed. + +# %% +yap.atom_sites['Y'].fract_x.free = True +yap.atom_sites['Y'].fract_y.free = True +yap.atom_sites['O1'].fract_x.free = True +yap.atom_sites['O1'].fract_y.free = True +yap.atom_sites['O2'].fract_x.free = True +yap.atom_sites['O2'].fract_y.free = True +yap.atom_sites['O2'].fract_z.free = True + +alumina.atom_sites['Al1'].fract_z.free = True +alumina.atom_sites['O1'].fract_x.free = True + +for structure in (yap, alumina): + for atom in structure.atom_sites: + atom.adp_iso.free = True + +# %% +project.display.parameters.free() + +# %% +project.analysis.fit() + +# %% [markdown] +# ### Inspect Results +# +# Review the fit statistics, refined parameters, and correlations. +# Inspect the full pattern and a closer view with impurity reflections. + +# %% +project.display.fit.results() + +# %% +project.display.fit.correlations() + +# %% +project.display.pattern(expt_name='yap_3k') + +# %% +project.display.pattern(expt_name='yap_3k', x_min=134, x_max=146) + +# %% [markdown] +# ## 💾 Save Project +# +# Save the refined model and analysis results in the project directory. + +# %% +project.save() diff --git a/docs/docs/user-guide/analysis-workflow/analysis.md b/docs/docs/user-guide/analysis-workflow/analysis.md index 1eae23d6d..ae8bfed59 100644 --- a/docs/docs/user-guide/analysis-workflow/analysis.md +++ b/docs/docs/user-guide/analysis-workflow/analysis.md @@ -169,6 +169,33 @@ To select the desired minimizer, e.g., 'lmfit': project.analysis.minimizer.type = 'lmfit' ``` +The available convergence settings change with the selected minimizer. +Their names describe the stopping condition rather than the backend's +short option name (`ftol`, `xtol`, or `gtol`). + +| Minimizer | Available convergence settings and defaults | +| -------------------------- | -------------------------------------------------------------------------------------------------------- | +| `lmfit`, `lmfit (leastsq)` | `chi_square_change_tolerance=1e-8`, `parameter_change_tolerance=1e-8`, `gradient_tolerance=0` (disabled) | +| `lmfit (least_squares)` | `chi_square_change_tolerance=1e-8`, `parameter_change_tolerance=1e-8`, `gradient_tolerance=1e-8` | +| `bumps`, `bumps (lm)` | `chi_square_change_tolerance=1e-8`, `parameter_change_tolerance=1e-8` | +| `bumps (amoeba)` | `chi_square_change_tolerance=1e-8`, `parameter_change_tolerance=1e-6` | +| `bumps (de)` | `population_convergence_tolerance=1e-6` | +| `dfols` | `final_trust_region_radius=1e-8` | + +For example, to require a smaller relative change in chi-square before +LMFIT stops: + +```python +project.analysis.minimizer.type = 'lmfit (leastsq)' +project.analysis.minimizer.chi_square_change_tolerance = 1e-10 +``` + +`chi_square_change_tolerance` is the setting that directly tests the +change in chi-square. The other stopping criteria can still affect the +final chi-square because a minimizer stops when any active criterion is +satisfied. Bayesian samplers use sampling controls instead of these +deterministic convergence tolerances. + ### Fit Mode In EasyDiffraction, you can set the **fit mode** to control how the diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index dce43ea3d..b2f1fc9b0 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -238,6 +238,7 @@ nav: - PbSO4 pd-xray-cwl: tutorials/refine-pbso4-xray.ipynb - LaM(7)O3 pd-xray-cwl: tutorials/refine-lam7o3-p021.ipynb - LMO pd-neut-cwl: tutorials/refine-lmo-echidna.ipynb + - YAlO3+Al2O3 pd-neut-cwl: tutorials/refine-yap-3k.ipynb - Without Measured Data: - LBCO pd-neut-cwl: tutorials/simulate-lbco-cwl.ipynb - Si pd-neut-tof: tutorials/simulate-si-tof.ipynb diff --git a/src/easydiffraction/_data_index_ref.txt b/src/easydiffraction/_data_index_ref.txt index 294152605..787ca0029 100644 --- a/src/easydiffraction/_data_index_ref.txt +++ b/src/easydiffraction/_data_index_ref.txt @@ -1 +1 @@ -4ecda04c6e9f083f90f187b02db8c661ad6f2efa +3ec801667904578ea39f1a4f4aa941866543a669 diff --git a/src/easydiffraction/analysis/calculators/crysfml.py b/src/easydiffraction/analysis/calculators/crysfml.py index 6520126b3..44f0d8068 100644 --- a/src/easydiffraction/analysis/calculators/crysfml.py +++ b/src/easydiffraction/analysis/calculators/crysfml.py @@ -142,6 +142,7 @@ def __init__(self) -> None: """Initialize CrysFML calculator state.""" super().__init__() self._cw_doublet_fallback_warned = False + self._unsupported_charge_symbols_warned: set[str] = set() @property def name(self) -> str: @@ -534,12 +535,27 @@ def _atom_line(self, atom: object, structure: Structure) -> str: occupancy = self._normalized_occupancy(atom, structure) return ( f' Atom {_cfl_label(atom.id.value)} ' - f'{_element_symbol(atom.type_symbol.value)} ' + f'{self._cfl_type_symbol(atom.type_symbol.value)} ' f'{_fmt(atom.fract_x.value)} {_fmt(atom.fract_y.value)} ' f'{_fmt(atom.fract_z.value)} {_fmt(atom.adp_iso_as_b)} ' f'{_fmt(occupancy)}' ) + def _cfl_type_symbol(self, type_symbol: str) -> str: + """ + Return CrysFML's element symbol and warn when charge is lost. + """ + element_symbol = _element_symbol(type_symbol) + stripped_type_symbol = type_symbol.strip() + has_charge = bool(re.fullmatch(r'\d*[A-Z][a-z]?[1-8][+-]', stripped_type_symbol)) + if has_charge and stripped_type_symbol not in self._unsupported_charge_symbols_warned: + self._unsupported_charge_symbols_warned.add(stripped_type_symbol) + log.warning( + f"[CrysfmlCalculator] Charged atom type '{stripped_type_symbol}' " + 'is not supported by CrysFML yet; the charge will be ignored.' + ) + return element_symbol + def _normalized_occupancy(self, atom: object, structure: Structure) -> float: """ Return FullProf occupancy for a CFL ``Atom`` line. diff --git a/src/easydiffraction/analysis/calculators/cryspy.py b/src/easydiffraction/analysis/calculators/cryspy.py index 901193550..4854fe894 100644 --- a/src/easydiffraction/analysis/calculators/cryspy.py +++ b/src/easydiffraction/analysis/calculators/cryspy.py @@ -7,6 +7,7 @@ import contextlib import copy import io +import re from typing import TYPE_CHECKING from typing import Any @@ -43,6 +44,9 @@ EXPECTED_HKL_INDEX_ROWS = 3 +_CHARGED_TYPE_SYMBOL_PATTERN = re.compile( + r'(?P\d*(?P[A-Z][a-z]?))(?P[1-8][+-])' +) @CalculatorFactory.register @@ -75,6 +79,7 @@ def __init__(self) -> None: self._cached_pref_orient: dict[str, tuple] = {} self._cached_polarization_settings: dict[str, tuple[float, float] | None] = {} self._last_powder_phase_blocks: dict[str, dict[str, Any] | None] = {} + self._unsupported_charge_symbols_warned: set[str] = set() def _invalidate_stale_cache( self, @@ -957,15 +962,75 @@ def _convert_structure_to_cryspy_cif(self, structure: Structure) -> str: str The Cryspy CIF string representation of the structure. """ - saved = self._temporarily_convert_to_u_notation(structure) + saved_adp = self._temporarily_convert_to_u_notation(structure) + saved_type_symbols: list[tuple[object, str]] = [] try: + saved_type_symbols = self._temporarily_use_supported_type_symbols(structure) cif = structure.as_cif finally: - self._restore_from_u_notation(structure, saved) + self._restore_type_symbols(saved_type_symbols) + self._restore_from_u_notation(structure, saved_adp) return self._relabel_cif_tags_for_cryspy(cif) + def _temporarily_use_supported_type_symbols( + self, + structure: Structure, + ) -> list[tuple[object, str]]: + """ + Replace unsupported CrysPy ions with their neutral symbols. + """ + from cryspy.A_functions_base.database import DATABASE # noqa: PLC0415 + + supported_ions = DATABASE['Scattering amplitude'] + saved: list[tuple[object, str]] = [] + + for atom in structure.atom_sites: + type_symbol = atom.type_symbol.value.strip() + match = _CHARGED_TYPE_SYMBOL_PATTERN.fullmatch(type_symbol) + if match is None: + continue + + ion_symbol = f'{match.group("element")}{match.group("charge")}' + if ion_symbol in supported_ions: + continue + + neutral_symbol = match.group('neutral') + saved.append((atom, atom._type_symbol._value)) + atom._type_symbol._value = neutral_symbol + + warning_key = f'cryspy:{type_symbol}' + atom_warning_keys = getattr(atom, '_type_symbol_charge_warnings', ()) + already_warned = warning_key in atom_warning_keys + if not already_warned and type_symbol not in self._unsupported_charge_symbols_warned: + self._unsupported_charge_symbols_warned.add(type_symbol) + element_symbol = match.group('element') + available_ions = sorted( + symbol + for symbol in supported_ions + if re.fullmatch(rf'{re.escape(element_symbol)}[1-8][+-]', symbol) + ) + ionic_support = ( + f"Supported ionic forms for '{element_symbol}': {', '.join(available_ions)}." + if available_ions + else f"No ionic forms are available for '{element_symbol}'." + ) + log.warning( + f"[CryspyCalculator] Charged atom type '{type_symbol}' is not " + f'available in the CrysPy scattering-factor database. {ionic_support} ' + 'Using the default neutral-atom scattering factors for ' + f"'{neutral_symbol}' (no ionic charge)." + ) + + return saved + + @staticmethod + def _restore_type_symbols(saved: list[tuple[object, str]]) -> None: + """Restore charged type symbols after CrysPy CIF generation.""" + for atom, type_symbol in saved: + atom._type_symbol._value = type_symbol + # Edi persistence renamed several CIF tags away from the legacy # IUCr spellings that cryspy's CIF parser still requires. The # displacement values are already converted to U notation by diff --git a/src/easydiffraction/analysis/categories/minimizer/bumps.py b/src/easydiffraction/analysis/categories/minimizer/bumps.py index 3f6ec06a9..6e97164bd 100644 --- a/src/easydiffraction/analysis/categories/minimizer/bumps.py +++ b/src/easydiffraction/analysis/categories/minimizer/bumps.py @@ -7,19 +7,23 @@ from typing import ClassVar from easydiffraction.analysis.categories.minimizer.factory import MinimizerCategoryFactory -from easydiffraction.analysis.categories.minimizer.lsq_base import LeastSquaresMinimizerBase +from easydiffraction.analysis.categories.minimizer.lsq_base import ( + ObjectiveParameterToleranceMinimizerBase, +) from easydiffraction.analysis.minimizers.enums import MinimizerTypeEnum from easydiffraction.core.metadata import TypeInfo @MinimizerCategoryFactory.register -class BumpsMinimizer(LeastSquaresMinimizerBase): +class BumpsMinimizer(ObjectiveParameterToleranceMinimizerBase): """Persisted settings for the default BUMPS minimizer.""" _engine_metadata: ClassVar[dict[str, str]] = { 'optimizer_name': 'bumps', 'method_name': 'lm', } + _default_chi_square_change_tolerance: ClassVar[float] = 1e-8 + _default_parameter_change_tolerance: ClassVar[float] = 1e-8 url: str = 'https://bumps.readthedocs.io' type_info = TypeInfo( diff --git a/src/easydiffraction/analysis/categories/minimizer/bumps_amoeba.py b/src/easydiffraction/analysis/categories/minimizer/bumps_amoeba.py index cd58fd5f4..5d6932cdf 100644 --- a/src/easydiffraction/analysis/categories/minimizer/bumps_amoeba.py +++ b/src/easydiffraction/analysis/categories/minimizer/bumps_amoeba.py @@ -7,19 +7,23 @@ from typing import ClassVar from easydiffraction.analysis.categories.minimizer.factory import MinimizerCategoryFactory -from easydiffraction.analysis.categories.minimizer.lsq_base import LeastSquaresMinimizerBase +from easydiffraction.analysis.categories.minimizer.lsq_base import ( + ObjectiveParameterToleranceMinimizerBase, +) from easydiffraction.analysis.minimizers.enums import MinimizerTypeEnum from easydiffraction.core.metadata import TypeInfo @MinimizerCategoryFactory.register -class BumpsAmoebaMinimizer(LeastSquaresMinimizerBase): +class BumpsAmoebaMinimizer(ObjectiveParameterToleranceMinimizerBase): """Persisted settings for the BUMPS amoeba minimizer.""" _engine_metadata: ClassVar[dict[str, str]] = { 'optimizer_name': 'bumps (amoeba)', 'method_name': 'amoeba', } + _default_chi_square_change_tolerance: ClassVar[float] = 1e-8 + _default_parameter_change_tolerance: ClassVar[float] = 1e-6 url: str = 'https://bumps.readthedocs.io' type_info = TypeInfo( diff --git a/src/easydiffraction/analysis/categories/minimizer/bumps_de.py b/src/easydiffraction/analysis/categories/minimizer/bumps_de.py index 218f7cd37..8d9396e26 100644 --- a/src/easydiffraction/analysis/categories/minimizer/bumps_de.py +++ b/src/easydiffraction/analysis/categories/minimizer/bumps_de.py @@ -7,13 +7,13 @@ from typing import ClassVar from easydiffraction.analysis.categories.minimizer.factory import MinimizerCategoryFactory -from easydiffraction.analysis.categories.minimizer.lsq_base import LeastSquaresMinimizerBase +from easydiffraction.analysis.categories.minimizer.lsq_base import PopulationToleranceMinimizerBase from easydiffraction.analysis.minimizers.enums import MinimizerTypeEnum from easydiffraction.core.metadata import TypeInfo @MinimizerCategoryFactory.register -class BumpsDeMinimizer(LeastSquaresMinimizerBase): +class BumpsDeMinimizer(PopulationToleranceMinimizerBase): """Persisted settings for the BUMPS de minimizer.""" _engine_metadata: ClassVar[dict[str, str]] = { diff --git a/src/easydiffraction/analysis/categories/minimizer/bumps_lm.py b/src/easydiffraction/analysis/categories/minimizer/bumps_lm.py index bd552f9bc..9f89ea750 100644 --- a/src/easydiffraction/analysis/categories/minimizer/bumps_lm.py +++ b/src/easydiffraction/analysis/categories/minimizer/bumps_lm.py @@ -7,19 +7,23 @@ from typing import ClassVar from easydiffraction.analysis.categories.minimizer.factory import MinimizerCategoryFactory -from easydiffraction.analysis.categories.minimizer.lsq_base import LeastSquaresMinimizerBase +from easydiffraction.analysis.categories.minimizer.lsq_base import ( + ObjectiveParameterToleranceMinimizerBase, +) from easydiffraction.analysis.minimizers.enums import MinimizerTypeEnum from easydiffraction.core.metadata import TypeInfo @MinimizerCategoryFactory.register -class BumpsLmMinimizer(LeastSquaresMinimizerBase): +class BumpsLmMinimizer(ObjectiveParameterToleranceMinimizerBase): """Persisted settings for the BUMPS lm minimizer.""" _engine_metadata: ClassVar[dict[str, str]] = { 'optimizer_name': 'bumps (lm)', 'method_name': 'lm', } + _default_chi_square_change_tolerance: ClassVar[float] = 1e-8 + _default_parameter_change_tolerance: ClassVar[float] = 1e-8 url: str = 'https://bumps.readthedocs.io' type_info = TypeInfo( diff --git a/src/easydiffraction/analysis/categories/minimizer/dfols.py b/src/easydiffraction/analysis/categories/minimizer/dfols.py index d8a60cca1..4f2ef92b8 100644 --- a/src/easydiffraction/analysis/categories/minimizer/dfols.py +++ b/src/easydiffraction/analysis/categories/minimizer/dfols.py @@ -7,13 +7,15 @@ from typing import ClassVar from easydiffraction.analysis.categories.minimizer.factory import MinimizerCategoryFactory -from easydiffraction.analysis.categories.minimizer.lsq_base import LeastSquaresMinimizerBase +from easydiffraction.analysis.categories.minimizer.lsq_base import ( + TrustRegionToleranceMinimizerBase, +) from easydiffraction.analysis.minimizers.enums import MinimizerTypeEnum from easydiffraction.core.metadata import TypeInfo @MinimizerCategoryFactory.register -class DfolsMinimizer(LeastSquaresMinimizerBase): +class DfolsMinimizer(TrustRegionToleranceMinimizerBase): """Persisted settings for the DFO-LS minimizer.""" _engine_metadata: ClassVar[dict[str, str]] = { diff --git a/src/easydiffraction/analysis/categories/minimizer/lmfit.py b/src/easydiffraction/analysis/categories/minimizer/lmfit.py index fc024ec23..616f30d9a 100644 --- a/src/easydiffraction/analysis/categories/minimizer/lmfit.py +++ b/src/easydiffraction/analysis/categories/minimizer/lmfit.py @@ -7,19 +7,23 @@ from typing import ClassVar from easydiffraction.analysis.categories.minimizer.factory import MinimizerCategoryFactory -from easydiffraction.analysis.categories.minimizer.lsq_base import LeastSquaresMinimizerBase +from easydiffraction.analysis.categories.minimizer.lsq_base import GradientToleranceMinimizerBase from easydiffraction.analysis.minimizers.enums import MinimizerTypeEnum from easydiffraction.core.metadata import TypeInfo @MinimizerCategoryFactory.register -class LmfitMinimizer(LeastSquaresMinimizerBase): +class LmfitMinimizer(GradientToleranceMinimizerBase): """Persisted settings for the default LMFIT minimizer.""" _engine_metadata: ClassVar[dict[str, str]] = { 'optimizer_name': 'lmfit', 'method_name': 'leastsq', } + _default_chi_square_change_tolerance: ClassVar[float] = 1e-8 + _default_parameter_change_tolerance: ClassVar[float] = 1e-8 + _default_gradient_tolerance: ClassVar[float] = 0.0 + _gradient_tolerance_allows_zero: ClassVar[bool] = True url: str = 'https://lmfit.github.io/lmfit-py' type_info = TypeInfo( diff --git a/src/easydiffraction/analysis/categories/minimizer/lmfit_least_squares.py b/src/easydiffraction/analysis/categories/minimizer/lmfit_least_squares.py index dab687ee8..8ca4f86d2 100644 --- a/src/easydiffraction/analysis/categories/minimizer/lmfit_least_squares.py +++ b/src/easydiffraction/analysis/categories/minimizer/lmfit_least_squares.py @@ -7,13 +7,13 @@ from typing import ClassVar from easydiffraction.analysis.categories.minimizer.factory import MinimizerCategoryFactory -from easydiffraction.analysis.categories.minimizer.lsq_base import LeastSquaresMinimizerBase +from easydiffraction.analysis.categories.minimizer.lsq_base import GradientToleranceMinimizerBase from easydiffraction.analysis.minimizers.enums import MinimizerTypeEnum from easydiffraction.core.metadata import TypeInfo @MinimizerCategoryFactory.register -class LmfitLeastSquaresMinimizer(LeastSquaresMinimizerBase): +class LmfitLeastSquaresMinimizer(GradientToleranceMinimizerBase): """Persisted settings for the LMFIT least_squares minimizer.""" _engine_metadata: ClassVar[dict[str, str]] = { diff --git a/src/easydiffraction/analysis/categories/minimizer/lmfit_leastsq.py b/src/easydiffraction/analysis/categories/minimizer/lmfit_leastsq.py index e9ba7b732..e5a5c0879 100644 --- a/src/easydiffraction/analysis/categories/minimizer/lmfit_leastsq.py +++ b/src/easydiffraction/analysis/categories/minimizer/lmfit_leastsq.py @@ -7,19 +7,23 @@ from typing import ClassVar from easydiffraction.analysis.categories.minimizer.factory import MinimizerCategoryFactory -from easydiffraction.analysis.categories.minimizer.lsq_base import LeastSquaresMinimizerBase +from easydiffraction.analysis.categories.minimizer.lsq_base import GradientToleranceMinimizerBase from easydiffraction.analysis.minimizers.enums import MinimizerTypeEnum from easydiffraction.core.metadata import TypeInfo @MinimizerCategoryFactory.register -class LmfitLeastsqMinimizer(LeastSquaresMinimizerBase): +class LmfitLeastsqMinimizer(GradientToleranceMinimizerBase): """Persisted settings for the LMFIT leastsq minimizer.""" _engine_metadata: ClassVar[dict[str, str]] = { 'optimizer_name': 'lmfit (leastsq)', 'method_name': 'leastsq', } + _default_chi_square_change_tolerance: ClassVar[float] = 1e-8 + _default_parameter_change_tolerance: ClassVar[float] = 1e-8 + _default_gradient_tolerance: ClassVar[float] = 0.0 + _gradient_tolerance_allows_zero: ClassVar[bool] = True url: str = 'https://lmfit.github.io/lmfit-py' type_info = TypeInfo( diff --git a/src/easydiffraction/analysis/categories/minimizer/lsq_base.py b/src/easydiffraction/analysis/categories/minimizer/lsq_base.py index 4ea81ef97..8dfe10ffd 100644 --- a/src/easydiffraction/analysis/categories/minimizer/lsq_base.py +++ b/src/easydiffraction/analysis/categories/minimizer/lsq_base.py @@ -12,6 +12,7 @@ from easydiffraction.core.validation import AttributeSpec from easydiffraction.core.validation import RangeValidator from easydiffraction.core.variable import IntegerDescriptor +from easydiffraction.core.variable import NumericDescriptor from easydiffraction.io.cif.handler import TagSpec @@ -58,3 +59,206 @@ def max_iterations(self) -> IntegerDescriptor: def max_iterations(self, value: int) -> None: """Set the maximum solver iterations.""" self._max_iterations.value = value + + +class ObjectiveParameterToleranceMinimizerBase(LeastSquaresMinimizerBase): + """Settings with objective and parameter stopping criteria.""" + + _default_chi_square_change_tolerance: ClassVar[float] = 1e-8 + _default_parameter_change_tolerance: ClassVar[float] = 1e-8 + _expected_descriptor_names: ClassVar[tuple[str, ...]] = ( + *LeastSquaresMinimizerBase._expected_descriptor_names, + 'chi_square_change_tolerance', + 'parameter_change_tolerance', + ) + _native_key_map: ClassVar[dict[str, str]] = { + **LeastSquaresMinimizerBase._native_key_map, + 'chi_square_change_tolerance': 'chi_square_change_tolerance', + 'parameter_change_tolerance': 'parameter_change_tolerance', + } + _setting_descriptor_names: ClassVar[tuple[str, ...]] = ( + *LeastSquaresMinimizerBase._setting_descriptor_names, + 'chi_square_change_tolerance', + 'parameter_change_tolerance', + ) + + def __init__(self) -> None: + """Initialize objective and parameter change tolerances.""" + super().__init__() + self._chi_square_change_tolerance = self._tolerance_descriptor( + name='chi_square_change_tolerance', + description='Relative change in the objective (chi-square) used to stop fitting.', + display_name='χ² change tolerance', + default=self._default_chi_square_change_tolerance, + ) + self._parameter_change_tolerance = self._tolerance_descriptor( + name='parameter_change_tolerance', + description='Relative change in fitted parameters used to stop fitting.', + display_name='Parameter change tolerance', + default=self._default_parameter_change_tolerance, + ) + + @staticmethod + def _tolerance_descriptor( + *, + name: str, + description: str, + display_name: str, + default: float, + allow_zero: bool = False, + ) -> NumericDescriptor: + """Create a dimensionless solver-tolerance descriptor.""" + validator = RangeValidator(ge=0.0) if allow_zero else RangeValidator(gt=0.0) + return NumericDescriptor( + name=name, + description=description, + value_spec=AttributeSpec(default=default, validator=validator), + tags=TagSpec( + edi_names=[f'_minimizer.{name}'], + cif_names=[f'_easydiffraction_minimizer.{name}'], + ), + display_handler=DisplayHandler( + display_name=display_name, + latex_name=display_name, + ), + ) + + @property + def chi_square_change_tolerance(self) -> NumericDescriptor: + """Relative chi-square change used for convergence.""" + return self._chi_square_change_tolerance + + @chi_square_change_tolerance.setter + def chi_square_change_tolerance(self, value: float) -> None: + """Set the relative objective-change tolerance.""" + self._chi_square_change_tolerance.value = value + + @property + def parameter_change_tolerance(self) -> NumericDescriptor: + """Relative parameter change used for convergence.""" + return self._parameter_change_tolerance + + @parameter_change_tolerance.setter + def parameter_change_tolerance(self, value: float) -> None: + """Set the relative parameter-change tolerance.""" + self._parameter_change_tolerance.value = value + + +class GradientToleranceMinimizerBase(ObjectiveParameterToleranceMinimizerBase): + """Settings that also expose a gradient stopping criterion.""" + + _default_gradient_tolerance: ClassVar[float] = 1e-8 + _gradient_tolerance_allows_zero: ClassVar[bool] = False + _expected_descriptor_names: ClassVar[tuple[str, ...]] = ( + *ObjectiveParameterToleranceMinimizerBase._expected_descriptor_names, + 'gradient_tolerance', + ) + _native_key_map: ClassVar[dict[str, str]] = { + **ObjectiveParameterToleranceMinimizerBase._native_key_map, + 'gradient_tolerance': 'gradient_tolerance', + } + _setting_descriptor_names: ClassVar[tuple[str, ...]] = ( + *ObjectiveParameterToleranceMinimizerBase._setting_descriptor_names, + 'gradient_tolerance', + ) + + def __init__(self) -> None: + """Initialize the gradient tolerance.""" + super().__init__() + self._gradient_tolerance = self._tolerance_descriptor( + name='gradient_tolerance', + description='Gradient orthogonality used to stop fitting; zero disables it.', + display_name='Gradient tolerance', + default=self._default_gradient_tolerance, + allow_zero=self._gradient_tolerance_allows_zero, + ) + + @property + def gradient_tolerance(self) -> NumericDescriptor: + """Gradient orthogonality used for convergence.""" + return self._gradient_tolerance + + @gradient_tolerance.setter + def gradient_tolerance(self, value: float) -> None: + """Set the gradient tolerance.""" + self._gradient_tolerance.value = value + + +class PopulationToleranceMinimizerBase(LeastSquaresMinimizerBase): + """Settings for population-based minimizers.""" + + _default_population_convergence_tolerance: ClassVar[float] = 1e-6 + _expected_descriptor_names: ClassVar[tuple[str, ...]] = ( + *LeastSquaresMinimizerBase._expected_descriptor_names, + 'population_convergence_tolerance', + ) + _native_key_map: ClassVar[dict[str, str]] = { + **LeastSquaresMinimizerBase._native_key_map, + 'population_convergence_tolerance': 'population_convergence_tolerance', + } + _setting_descriptor_names: ClassVar[tuple[str, ...]] = ( + *LeastSquaresMinimizerBase._setting_descriptor_names, + 'population_convergence_tolerance', + ) + + def __init__(self) -> None: + """Initialize the population-convergence tolerance.""" + super().__init__() + self._population_convergence_tolerance = ( + ObjectiveParameterToleranceMinimizerBase._tolerance_descriptor( + name='population_convergence_tolerance', + description='Population spread used to stop differential evolution.', + display_name='Population convergence tolerance', + default=self._default_population_convergence_tolerance, + ) + ) + + @property + def population_convergence_tolerance(self) -> NumericDescriptor: + """Population spread used for convergence.""" + return self._population_convergence_tolerance + + @population_convergence_tolerance.setter + def population_convergence_tolerance(self, value: float) -> None: + """Set the population-convergence tolerance.""" + self._population_convergence_tolerance.value = value + + +class TrustRegionToleranceMinimizerBase(LeastSquaresMinimizerBase): + """Settings for derivative-free trust-region minimizers.""" + + _default_final_trust_region_radius: ClassVar[float] = 1e-8 + _expected_descriptor_names: ClassVar[tuple[str, ...]] = ( + *LeastSquaresMinimizerBase._expected_descriptor_names, + 'final_trust_region_radius', + ) + _native_key_map: ClassVar[dict[str, str]] = { + **LeastSquaresMinimizerBase._native_key_map, + 'final_trust_region_radius': 'final_trust_region_radius', + } + _setting_descriptor_names: ClassVar[tuple[str, ...]] = ( + *LeastSquaresMinimizerBase._setting_descriptor_names, + 'final_trust_region_radius', + ) + + def __init__(self) -> None: + """Initialize the final trust-region radius.""" + super().__init__() + self._final_trust_region_radius = ( + ObjectiveParameterToleranceMinimizerBase._tolerance_descriptor( + name='final_trust_region_radius', + description='Final trust-region radius used to stop DFO-LS.', + display_name='Final trust-region radius', + default=self._default_final_trust_region_radius, + ) + ) + + @property + def final_trust_region_radius(self) -> NumericDescriptor: + """Final trust-region radius used for convergence.""" + return self._final_trust_region_radius + + @final_trust_region_radius.setter + def final_trust_region_radius(self, value: float) -> None: + """Set the final trust-region radius.""" + self._final_trust_region_radius.value = value diff --git a/src/easydiffraction/analysis/minimizers/bumps.py b/src/easydiffraction/analysis/minimizers/bumps.py index 143715c2a..751f71f6f 100644 --- a/src/easydiffraction/analysis/minimizers/bumps.py +++ b/src/easydiffraction/analysis/minimizers/bumps.py @@ -19,6 +19,8 @@ DEFAULT_METHOD = 'lm' DEFAULT_MAX_ITERATIONS = 1000 +DEFAULT_CHI_SQUARE_CHANGE_TOLERANCE = 1e-8 +DEFAULT_PARAMETER_CHANGE_TOLERANCE = 1e-8 _COVARIANCE_RELATIVE_STEP = 1e-4 @@ -206,6 +208,9 @@ def __init__( name: str = MinimizerTypeEnum.BUMPS, method: str = DEFAULT_METHOD, max_iterations: int = DEFAULT_MAX_ITERATIONS, + chi_square_change_tolerance: float | None = DEFAULT_CHI_SQUARE_CHANGE_TOLERANCE, + parameter_change_tolerance: float | None = DEFAULT_PARAMETER_CHANGE_TOLERANCE, + population_convergence_tolerance: float | None = None, ) -> None: """Initialize the BUMPS minimizer with default settings.""" super().__init__( @@ -213,6 +218,9 @@ def __init__( method=method, max_iterations=max_iterations, ) + self.chi_square_change_tolerance = chi_square_change_tolerance + self.parameter_change_tolerance = parameter_change_tolerance + self.population_convergence_tolerance = population_convergence_tolerance @staticmethod def _tracks_progress_via_solver_monitor() -> bool: @@ -291,11 +299,19 @@ def _run_solver( ) fitclass = next(cls for cls in FITTERS if cls.id == self.method) + driver_options: dict[str, object] = {'steps': self.max_iterations} + if self.chi_square_change_tolerance is not None: + driver_options['ftol'] = self.chi_square_change_tolerance + if self.parameter_change_tolerance is not None: + driver_options['xtol'] = self.parameter_change_tolerance + if self.population_convergence_tolerance is not None: + driver_options['xtol'] = self.population_convergence_tolerance + driver = FitDriver( fitclass=fitclass, problem=problem, monitors=[progress_monitor], - steps=self.max_iterations, + **driver_options, ) driver.clip() try: diff --git a/src/easydiffraction/analysis/minimizers/bumps_amoeba.py b/src/easydiffraction/analysis/minimizers/bumps_amoeba.py index 1c002ba73..b7a611133 100644 --- a/src/easydiffraction/analysis/minimizers/bumps_amoeba.py +++ b/src/easydiffraction/analysis/minimizers/bumps_amoeba.py @@ -11,6 +11,8 @@ DEFAULT_METHOD = 'amoeba' DEFAULT_MAX_ITERATIONS = 1000 +DEFAULT_CHI_SQUARE_CHANGE_TOLERANCE = 1e-8 +DEFAULT_PARAMETER_CHANGE_TOLERANCE = 1e-6 @MinimizerFactory.register @@ -27,10 +29,14 @@ def __init__( name: str = MinimizerTypeEnum.BUMPS_AMOEBA, method: str = DEFAULT_METHOD, max_iterations: int = DEFAULT_MAX_ITERATIONS, + chi_square_change_tolerance: float = DEFAULT_CHI_SQUARE_CHANGE_TOLERANCE, + parameter_change_tolerance: float = DEFAULT_PARAMETER_CHANGE_TOLERANCE, ) -> None: """Initialize the BUMPS Nelder-Mead simplex minimizer.""" super().__init__( name=name, method=method, max_iterations=max_iterations, + chi_square_change_tolerance=chi_square_change_tolerance, + parameter_change_tolerance=parameter_change_tolerance, ) diff --git a/src/easydiffraction/analysis/minimizers/bumps_de.py b/src/easydiffraction/analysis/minimizers/bumps_de.py index 27266a1d6..e998d008a 100644 --- a/src/easydiffraction/analysis/minimizers/bumps_de.py +++ b/src/easydiffraction/analysis/minimizers/bumps_de.py @@ -11,6 +11,7 @@ DEFAULT_METHOD = 'de' DEFAULT_MAX_ITERATIONS = 1000 +DEFAULT_POPULATION_CONVERGENCE_TOLERANCE = 1e-6 @MinimizerFactory.register @@ -27,10 +28,14 @@ def __init__( name: str = MinimizerTypeEnum.BUMPS_DE, method: str = DEFAULT_METHOD, max_iterations: int = DEFAULT_MAX_ITERATIONS, + population_convergence_tolerance: float = DEFAULT_POPULATION_CONVERGENCE_TOLERANCE, ) -> None: """Initialize the BUMPS differential evolution minimizer.""" super().__init__( name=name, method=method, max_iterations=max_iterations, + chi_square_change_tolerance=None, + parameter_change_tolerance=None, + population_convergence_tolerance=population_convergence_tolerance, ) diff --git a/src/easydiffraction/analysis/minimizers/bumps_lm.py b/src/easydiffraction/analysis/minimizers/bumps_lm.py index 8e6624637..60303698e 100644 --- a/src/easydiffraction/analysis/minimizers/bumps_lm.py +++ b/src/easydiffraction/analysis/minimizers/bumps_lm.py @@ -11,6 +11,8 @@ DEFAULT_METHOD = 'lm' DEFAULT_MAX_ITERATIONS = 1000 +DEFAULT_CHI_SQUARE_CHANGE_TOLERANCE = 1e-8 +DEFAULT_PARAMETER_CHANGE_TOLERANCE = 1e-8 @MinimizerFactory.register @@ -29,10 +31,14 @@ def __init__( name: str = MinimizerTypeEnum.BUMPS_LM, method: str = DEFAULT_METHOD, max_iterations: int = DEFAULT_MAX_ITERATIONS, + chi_square_change_tolerance: float = DEFAULT_CHI_SQUARE_CHANGE_TOLERANCE, + parameter_change_tolerance: float = DEFAULT_PARAMETER_CHANGE_TOLERANCE, ) -> None: """Initialize the BUMPS Levenberg-Marquardt minimizer.""" super().__init__( name=name, method=method, max_iterations=max_iterations, + chi_square_change_tolerance=chi_square_change_tolerance, + parameter_change_tolerance=parameter_change_tolerance, ) diff --git a/src/easydiffraction/analysis/minimizers/dfols.py b/src/easydiffraction/analysis/minimizers/dfols.py index e1f25f6cc..7b19def77 100644 --- a/src/easydiffraction/analysis/minimizers/dfols.py +++ b/src/easydiffraction/analysis/minimizers/dfols.py @@ -11,6 +11,7 @@ from easydiffraction.core.metadata import TypeInfo DEFAULT_MAX_ITERATIONS = 1000 +DEFAULT_FINAL_TRUST_REGION_RADIUS = 1e-8 @MinimizerFactory.register @@ -26,10 +27,12 @@ def __init__( self, name: str = MinimizerTypeEnum.DFOLS, max_iterations: int = DEFAULT_MAX_ITERATIONS, + final_trust_region_radius: float = DEFAULT_FINAL_TRUST_REGION_RADIUS, **kwargs: object, ) -> None: """Initialize the DFO-LS minimizer with default settings.""" super().__init__(name=name, method=None, max_iterations=max_iterations) + self.final_trust_region_radius = final_trust_region_radius # Intentionally unused, accepted for API compatibility del kwargs @@ -61,7 +64,13 @@ def _run_solver(self, objective_function: object, **kwargs: object) -> object: """Run the DFO-LS solver on the objective function.""" x0 = kwargs.get('x0') bounds = kwargs.get('bounds') - return solve(objective_function, x0=x0, bounds=bounds, maxfun=self.max_iterations) + return solve( + objective_function, + x0=x0, + bounds=bounds, + maxfun=self.max_iterations, + rhoend=self.final_trust_region_radius, + ) def _sync_result_to_parameters( # noqa: PLR6301 self, diff --git a/src/easydiffraction/analysis/minimizers/lmfit.py b/src/easydiffraction/analysis/minimizers/lmfit.py index e3b7c5ed9..c4e28c421 100644 --- a/src/easydiffraction/analysis/minimizers/lmfit.py +++ b/src/easydiffraction/analysis/minimizers/lmfit.py @@ -11,6 +11,9 @@ DEFAULT_METHOD = 'leastsq' DEFAULT_MAX_ITERATIONS = 1000 +DEFAULT_CHI_SQUARE_CHANGE_TOLERANCE = 1e-8 +DEFAULT_PARAMETER_CHANGE_TOLERANCE = 1e-8 +DEFAULT_GRADIENT_TOLERANCE = 0.0 @MinimizerFactory.register @@ -27,6 +30,9 @@ def __init__( name: str = MinimizerTypeEnum.LMFIT, method: str = DEFAULT_METHOD, max_iterations: int = DEFAULT_MAX_ITERATIONS, + chi_square_change_tolerance: float = DEFAULT_CHI_SQUARE_CHANGE_TOLERANCE, + parameter_change_tolerance: float = DEFAULT_PARAMETER_CHANGE_TOLERANCE, + gradient_tolerance: float = DEFAULT_GRADIENT_TOLERANCE, ) -> None: """Initialize the lmfit minimizer with default settings.""" super().__init__( @@ -34,6 +40,9 @@ def __init__( method=method, max_iterations=max_iterations, ) + self.chi_square_change_tolerance = chi_square_change_tolerance + self.parameter_change_tolerance = parameter_change_tolerance + self.gradient_tolerance = gradient_tolerance def _prepare_solver_args( # noqa: PLR6301 self, @@ -88,6 +97,9 @@ def _run_solver(self, objective_function: object, **kwargs: object) -> object: method=self.method, nan_policy='propagate', max_nfev=self.max_iterations, + ftol=self.chi_square_change_tolerance, + xtol=self.parameter_change_tolerance, + gtol=self.gradient_tolerance, ) def _sync_result_to_parameters( # noqa: PLR6301 diff --git a/src/easydiffraction/analysis/minimizers/lmfit_least_squares.py b/src/easydiffraction/analysis/minimizers/lmfit_least_squares.py index 06089c66c..0a7a0e8d8 100644 --- a/src/easydiffraction/analysis/minimizers/lmfit_least_squares.py +++ b/src/easydiffraction/analysis/minimizers/lmfit_least_squares.py @@ -11,6 +11,9 @@ DEFAULT_METHOD = 'least_squares' DEFAULT_MAX_ITERATIONS = 1000 +DEFAULT_CHI_SQUARE_CHANGE_TOLERANCE = 1e-8 +DEFAULT_PARAMETER_CHANGE_TOLERANCE = 1e-8 +DEFAULT_GRADIENT_TOLERANCE = 1e-8 @MinimizerFactory.register @@ -29,10 +32,16 @@ def __init__( name: str = MinimizerTypeEnum.LMFIT_LEAST_SQUARES, method: str = DEFAULT_METHOD, max_iterations: int = DEFAULT_MAX_ITERATIONS, + chi_square_change_tolerance: float = DEFAULT_CHI_SQUARE_CHANGE_TOLERANCE, + parameter_change_tolerance: float = DEFAULT_PARAMETER_CHANGE_TOLERANCE, + gradient_tolerance: float = DEFAULT_GRADIENT_TOLERANCE, ) -> None: """Initialize the lmfit least_squares minimizer.""" super().__init__( name=name, method=method, max_iterations=max_iterations, + chi_square_change_tolerance=chi_square_change_tolerance, + parameter_change_tolerance=parameter_change_tolerance, + gradient_tolerance=gradient_tolerance, ) diff --git a/src/easydiffraction/analysis/minimizers/lmfit_leastsq.py b/src/easydiffraction/analysis/minimizers/lmfit_leastsq.py index 27626a5bb..358d28b05 100644 --- a/src/easydiffraction/analysis/minimizers/lmfit_leastsq.py +++ b/src/easydiffraction/analysis/minimizers/lmfit_leastsq.py @@ -13,6 +13,9 @@ DEFAULT_METHOD = 'leastsq' DEFAULT_MAX_ITERATIONS = 1000 +DEFAULT_CHI_SQUARE_CHANGE_TOLERANCE = 1e-8 +DEFAULT_PARAMETER_CHANGE_TOLERANCE = 1e-8 +DEFAULT_GRADIENT_TOLERANCE = 0.0 @MinimizerFactory.register @@ -31,10 +34,16 @@ def __init__( name: str = MinimizerTypeEnum.LMFIT_LEASTSQ, method: str = DEFAULT_METHOD, max_iterations: int = DEFAULT_MAX_ITERATIONS, + chi_square_change_tolerance: float = DEFAULT_CHI_SQUARE_CHANGE_TOLERANCE, + parameter_change_tolerance: float = DEFAULT_PARAMETER_CHANGE_TOLERANCE, + gradient_tolerance: float = DEFAULT_GRADIENT_TOLERANCE, ) -> None: """Initialize the lmfit leastsq minimizer.""" super().__init__( name=name, method=method, max_iterations=max_iterations, + chi_square_change_tolerance=chi_square_change_tolerance, + parameter_change_tolerance=parameter_change_tolerance, + gradient_tolerance=gradient_tolerance, ) diff --git a/src/easydiffraction/datablocks/structure/categories/atom_sites/default.py b/src/easydiffraction/datablocks/structure/categories/atom_sites/default.py index 0940ac708..e96b8b99a 100644 --- a/src/easydiffraction/datablocks/structure/categories/atom_sites/default.py +++ b/src/easydiffraction/datablocks/structure/categories/atom_sites/default.py @@ -10,6 +10,7 @@ from __future__ import annotations import math +import re from cryspy.A_functions_base.database import DATABASE @@ -32,6 +33,10 @@ from easydiffraction.io.cif.handler import TagSpec from easydiffraction.utils.logging import log +_CHARGED_TYPE_SYMBOL_PATTERN = re.compile( + r'(?P\d*(?P[A-Z][a-z]?))(?P[1-8][+-])' +) + class AtomSite(CategoryItem): """ @@ -61,6 +66,7 @@ def __init__(self) -> None: # search (only the cheap per-iteration snap runs). None until # first detection; invalidated when detection clears the site. self._wyckoff_template_cache: str | None = None + self._type_symbol_charge_warnings: set[str] = set() self._id = StringDescriptor( name='id', @@ -660,6 +666,63 @@ def type_symbol(self) -> StringDescriptor: @type_symbol.setter def type_symbol(self, value: str) -> None: self._type_symbol.value = value + self._warn_about_default_calculator_charge() + + def _warn_about_default_calculator_charge(self) -> None: + """Warn when the default calculator cannot use this charge.""" + from easydiffraction.analysis.calculators.factory import CalculatorFactory # noqa: PLC0415 + from easydiffraction.datablocks.experiment.item.enums import ( # noqa: PLC0415 + ScatteringTypeEnum, + ) + + type_symbol = self._type_symbol.value.strip() + match = _CHARGED_TYPE_SYMBOL_PATTERN.fullmatch(type_symbol) + if match is None: + return + + calculator = CalculatorFactory.default_tag( + scattering_type=ScatteringTypeEnum.BRAGG, + ) + available = CalculatorFactory.supported_tags() + if available and calculator not in available: + calculator = available[0] + + warning_key = f'{calculator}:{type_symbol}' + if warning_key in self._type_symbol_charge_warnings: + return + + if calculator == 'cryspy': + element_symbol = match.group('element') + ion_symbol = f'{element_symbol}{match.group("charge")}' + scattering_amplitudes = DATABASE['Scattering amplitude'] + if ion_symbol in scattering_amplitudes: + return + neutral_symbol = match.group('neutral') + supported_ions = sorted( + symbol + for symbol in scattering_amplitudes + if re.fullmatch(rf'{re.escape(element_symbol)}[1-8][+-]', symbol) + ) + ionic_support = ( + f"Supported ionic forms for '{element_symbol}': {', '.join(supported_ions)}." + if supported_ions + else f"No ionic forms are available for '{element_symbol}'." + ) + log.warning( + f"Charged atom type '{type_symbol}' is not available in the default " + f'CrysPy scattering-factor database. {ionic_support} The default ' + f"neutral-atom scattering factors for '{neutral_symbol}' (no ionic " + 'charge) will be used.' + ) + elif calculator == 'crysfml': + log.warning( + f"Charged atom type '{type_symbol}' is not supported by the default " + 'CrysFML calculator yet; the charge will be ignored.' + ) + else: + return + + self._type_symbol_charge_warnings.add(warning_key) @property def adp_type(self) -> EnumDescriptor: diff --git a/src/easydiffraction/display/plotting.py b/src/easydiffraction/display/plotting.py index 95945005b..b2f53bdec 100644 --- a/src/easydiffraction/display/plotting.py +++ b/src/easydiffraction/display/plotting.py @@ -1830,7 +1830,9 @@ def _get_param_correlation_dataframe(self) -> pd.DataFrame | None: log.warning( 'Correlation matrix is unavailable for this fit. ' - 'Use a minimizer that returns covariance information or posterior samples.' + 'This can happen if the minimization failed, if some parameters are too ' + 'strongly correlated or poorly determined, or if the selected minimizer ' + 'does not return covariance information.' ) return None diff --git a/src/easydiffraction/utils/logging.py b/src/easydiffraction/utils/logging.py index 1f6e4ddb8..54e558bea 100644 --- a/src/easydiffraction/utils/logging.py +++ b/src/easydiffraction/utils/logging.py @@ -24,6 +24,8 @@ if TYPE_CHECKING: # pragma: no cover from types import TracebackType + from rich.traceback import Traceback as RichTraceback + import html import re import sys @@ -31,6 +33,7 @@ from rich import traceback from rich.console import Console +from rich.console import ConsoleRenderable from rich.console import Group from rich.console import RenderableType from rich.logging import RichHandler @@ -117,17 +120,52 @@ def render_message( return Text(str(message)) return super().render_message(record, message) + def render( + self, + *, + record: logging.LogRecord, + traceback: RichTraceback | None, + message_renderable: ConsoleRenderable, + ) -> ConsoleRenderable: + """Render notebook logs without Rich's fixed-width log grid.""" + if not in_jupyter(): + return super().render( + record=record, + traceback=traceback, + message_renderable=message_renderable, + ) + + message = ( + message_renderable + if isinstance(message_renderable, Text) + else Text(str(message_renderable)) + ) + line = Text.assemble(self.get_level_text(record), ' ', message) + return Group(line, traceback) if traceback is not None else line + # ====================================================================== # CONSOLE MANAGER # ====================================================================== +class NotebookAwareConsole(Console): + """Rich console that leaves line wrapping to the notebook UI.""" + + def print(self, *objects: object, **kwargs: object) -> None: + """ + Print without inserting width-based line breaks in Jupyter. + """ + if in_jupyter(): + kwargs.setdefault('soft_wrap', True) + super().print(*objects, **kwargs) + + class ConsoleManager: """Central provider for shared Rich Console instance.""" _MIN_CONSOLE_WIDTH = 130 - _instance: Console | None = None + _instance: NotebookAwareConsole | None = None @staticmethod def _detect_width() -> int: @@ -148,10 +186,10 @@ def _detect_width() -> int: return max(width, min_width) @classmethod - def get(cls) -> Console: + def get(cls) -> NotebookAwareConsole: """Return a shared Rich Console instance.""" if cls._instance is None: - cls._instance = Console( + cls._instance = NotebookAwareConsole( width=cls._detect_width(), force_jupyter=False, ) diff --git a/tests/integration/fitting/test_icsd_structure_cif_import.py b/tests/integration/fitting/test_icsd_structure_cif_import.py index 986a6f2b6..884d4cc92 100644 --- a/tests/integration/fitting/test_icsd_structure_cif_import.py +++ b/tests/integration/fitting/test_icsd_structure_cif_import.py @@ -84,8 +84,8 @@ """ -def test_icsd_cif_import_preserves_structure_and_ionic_symbols_for_cryspy(): - """Import the ICSD structure and preserve ionic atom types through Cryspy.""" +def test_icsd_cif_import_preserves_ions_and_uses_cryspy_fallbacks(): + """Preserve imported ions while replacing unsupported CrysPy symbols.""" from cryspy.H_functions_global.function_1_cryspy_objects import str_to_globaln structure = StructureFactory.from_cif_str(ZRW2O8_CIF) @@ -147,5 +147,14 @@ def test_icsd_cif_import_preserves_structure_and_ionic_symbols_for_cryspy(): assert cryspy_structure.data_name == '83267-icsd' assert [site.type_symbol for site in cryspy_structure.atom_site.items] == [ + 'Zr4+', + 'W6+', + 'W6+', + 'O', + 'O', + 'O', + 'O', + ] + assert [site.type_symbol.value for site in structure.atom_sites] == [ expected[0] for expected in expected_sites.values() ] diff --git a/tests/tutorials/baseline.json b/tests/tutorials/baseline.json index 551209f06..b1473f44f 100644 --- a/tests/tutorials/baseline.json +++ b/tests/tutorials/baseline.json @@ -209,14 +209,14 @@ "refine-lbco-si-mcstas": { "result_kind": "deterministic", "rtol": 0.02, - "reduced_chi_square": 9.532167, - "r_factor_all": 0.058084, - "wr_factor_all": 0.076338, + "reduced_chi_square": 3.445143, + "r_factor_all": 0.049173, + "wr_factor_all": 0.045893, "parameters": { - "lbco.cell.length_a": 3.89046, - "si.cell.length_a": 5.4358, - "mcstas.linked_structure.lbco.scale": 39.15, - "mcstas.linked_structure.si.scale": 0.0412 + "lbco.cell.length_a": 3.89045, + "si.cell.length_a": 5.43284, + "mcstas.linked_structure.lbco.scale": 39.7, + "mcstas.linked_structure.si.scale": 0.3311 } }, "refine-lmo-echidna": { @@ -259,13 +259,13 @@ "refine-pbso4-xray": { "result_kind": "deterministic", "rtol": 0.02, - "reduced_chi_square": 3.403071, - "r_factor_all": 0.06796, - "wr_factor_all": 0.089973, + "reduced_chi_square": 3.413049, + "r_factor_all": 0.068017, + "wr_factor_all": 0.090105, "parameters": { - "pbso4.cell.length_a": 8.480969, - "pbso4.cell.length_b": 5.399004, - "pbso4.cell.length_c": 6.96054, + "pbso4.cell.length_a": 8.480971, + "pbso4.cell.length_b": 5.399011, + "pbso4.cell.length_c": 6.960543, "xrd.linked_structure.pbso4.scale": 0.000999 } }, @@ -284,12 +284,12 @@ "refine-taurine-senju": { "result_kind": "deterministic", "rtol": 0.02, - "reduced_chi_square": 12.192189, - "r_factor_all": 0.133438, - "wr_factor_all": 0.084877, + "reduced_chi_square": 11.886304, + "r_factor_all": 0.132617, + "wr_factor_all": 0.083805, "parameters": { - "taurine.atom_site.S1.fract_x": 0.203646, - "senju.linked_structure.scale": 1.358181 + "taurine.atom_site.S1.fract_x": 0.203176, + "senju.linked_structure.scale": 1.359678 } }, "refine-tbti-heidi": { @@ -302,5 +302,22 @@ "tbti.atom_site.Ti.occupancy": 0.9661, "heidi.linked_structure.scale": 2.874 } + }, + "refine-yap-3k": { + "result_kind": "deterministic", + "rtol": 0.02, + "reduced_chi_square": 9.248924, + "n_free_parameters": 49, + "r_factor_all": 0.031261, + "wr_factor_all": 0.039551, + "parameters": { + "yap.cell.length_a": 5.172815, + "yap.cell.length_b": 5.327131, + "yap.cell.length_c": 7.361296, + "alumina.cell.length_a": 4.75804, + "alumina.cell.length_c": 12.9748, + "yap_3k.linked_structure.yap.scale": 28.006, + "yap_3k.linked_structure.alumina.scale": 0.185 + } } } diff --git a/tests/tutorials/generate_baseline.py b/tests/tutorials/generate_baseline.py index f658d9055..599d03b34 100644 --- a/tests/tutorials/generate_baseline.py +++ b/tests/tutorials/generate_baseline.py @@ -94,6 +94,9 @@ def build_entry(name: str, cif: AnalysisEdi) -> dict | None: 'rtol': BAYESIAN_RTOL if kind == 'bayesian' else DETERMINISTIC_RTOL, 'reduced_chi_square': round(reduced_chi_square, ROUND_DIGITS), } + n_free_parameters = cif.scalar('n_free_parameters') + if n_free_parameters is not None: + entry['n_free_parameters'] = int(n_free_parameters) if name in PLATFORM_SENSITIVE: entry['platform_sensitive'] = True for scalar_name in OPTIONAL_SCALARS: diff --git a/tests/tutorials/test_tutorial_outputs.py b/tests/tutorials/test_tutorial_outputs.py index c94346d94..509030f0d 100644 --- a/tests/tutorials/test_tutorial_outputs.py +++ b/tests/tutorials/test_tutorial_outputs.py @@ -78,6 +78,11 @@ def test_tutorial_output(name: str) -> None: f"{name}: result_kind '{cif.result_kind}' != expected '{expected['result_kind']}'" ) + if 'n_free_parameters' in expected: + assert cif.scalar('n_free_parameters') == expected['n_free_parameters'], ( + f'{name}: number of free parameters differs from {expected["n_free_parameters"]}' + ) + # Some tutorials (e.g. ed-7 on the compiled crysfml backend) # produce fit metrics that are not reproducible across CPU arch # or BLAS; confirm they ran and saved, but skip the numbers. diff --git a/tests/unit/easydiffraction/analysis/calculators/test_crysfml.py b/tests/unit/easydiffraction/analysis/calculators/test_crysfml.py index 7fb0d846c..de13aedac 100644 --- a/tests/unit/easydiffraction/analysis/calculators/test_crysfml.py +++ b/tests/unit/easydiffraction/analysis/calculators/test_crysfml.py @@ -100,6 +100,26 @@ def test_element_symbol_strips_isotope_and_ionic_notation(type_symbol, expected) assert _element_symbol(type_symbol) == expected +def test_crysfml_warns_once_when_ionic_charge_is_ignored(monkeypatch): + import easydiffraction.analysis.calculators.crysfml as crysfml_mod + from easydiffraction.analysis.calculators.crysfml import CrysfmlCalculator + + warning_messages = [] + monkeypatch.setattr(crysfml_mod.log, 'warning', warning_messages.append) + calculator = CrysfmlCalculator() + + assert calculator._cfl_type_symbol('Fe3+') == 'Fe' + assert calculator._cfl_type_symbol('Fe3+') == 'Fe' + assert calculator._cfl_type_symbol('Fe') == 'Fe' + + assert warning_messages == [ + ( + "[CrysfmlCalculator] Charged atom type 'Fe3+' is not supported by " + 'CrysFML yet; the charge will be ignored.' + ) + ] + + def test_crysfml_calculate_pattern_applies_absorption(monkeypatch): from easydiffraction.analysis.calculators.crysfml import CrysfmlCalculator from easydiffraction.analysis.corrections import absorption diff --git a/tests/unit/easydiffraction/analysis/calculators/test_cryspy.py b/tests/unit/easydiffraction/analysis/calculators/test_cryspy.py index be1ec0d07..ed86febce 100644 --- a/tests/unit/easydiffraction/analysis/calculators/test_cryspy.py +++ b/tests/unit/easydiffraction/analysis/calculators/test_cryspy.py @@ -60,6 +60,85 @@ def as_cif(self): assert calc._convert_structure_to_cryspy_cif(DummySample()) == 'data_x' +def test_cryspy_falls_back_for_unsupported_charge_number_and_sign(monkeypatch): + import easydiffraction.analysis.calculators.cryspy as cryspy_mod + from easydiffraction.analysis.calculators.cryspy import CryspyCalculator + + class Descriptor: + def __init__(self, value): + self._value = value + + @property + def value(self): + return self._value + + def atom(type_symbol): + descriptor = Descriptor(type_symbol) + return SimpleNamespace(type_symbol=descriptor, _type_symbol=descriptor) + + supported = atom('Fe3+') + unsupported_number = atom('Fe1+') + unsupported_sign = atom('Fe3-') + isotope = atom('57Fe1+') + structure = SimpleNamespace( + atom_sites=[supported, unsupported_number, unsupported_sign, isotope] + ) + warning_messages = [] + monkeypatch.setattr(cryspy_mod.log, 'warning', warning_messages.append) + calculator = CryspyCalculator() + + saved = calculator._temporarily_use_supported_type_symbols(structure) + + assert supported.type_symbol.value == 'Fe3+' + assert unsupported_number.type_symbol.value == 'Fe' + assert unsupported_sign.type_symbol.value == 'Fe' + assert isotope.type_symbol.value == '57Fe' + assert len(warning_messages) == 3 + assert "Charged atom type 'Fe1+'" in warning_messages[0] + assert "Supported ionic forms for 'Fe': Fe2+, Fe3+." in warning_messages[0] + assert ( + "default neutral-atom scattering factors for 'Fe' (no ionic charge)" in warning_messages[0] + ) + assert "Charged atom type 'Fe3-'" in warning_messages[1] + assert "Charged atom type '57Fe1+'" in warning_messages[2] + assert "Supported ionic forms for 'Fe': Fe2+, Fe3+." in warning_messages[2] + assert ( + "default neutral-atom scattering factors for '57Fe' (no ionic charge)" + in warning_messages[2] + ) + + calculator._restore_type_symbols(saved) + + assert [atom.type_symbol.value for atom in structure.atom_sites] == [ + 'Fe3+', + 'Fe1+', + 'Fe3-', + '57Fe1+', + ] + + calculator._temporarily_use_supported_type_symbols(structure) + assert len(warning_messages) == 3 + + +def test_cryspy_does_not_repeat_default_calculator_charge_warning(monkeypatch): + import easydiffraction.analysis.calculators.cryspy as cryspy_mod + from easydiffraction.analysis.calculators.cryspy import CryspyCalculator + from easydiffraction.datablocks.structure.categories.atom_sites.default import AtomSite + + warning_messages = [] + monkeypatch.setattr(cryspy_mod.log, 'warning', warning_messages.append) + atom = AtomSite() + atom.type_symbol = 'Pb3+' + calculator = CryspyCalculator() + + saved = calculator._temporarily_use_supported_type_symbols(SimpleNamespace(atom_sites=[atom])) + + assert atom.type_symbol.value == 'Pb' + assert len(warning_messages) == 1 + calculator._restore_type_symbols(saved) + assert atom.type_symbol.value == 'Pb3+' + + def test_tof_pseudo_voigt_cif_section_uses_non_convoluted_peak_shape(): import easydiffraction.analysis.calculators.cryspy as MUT from easydiffraction.datablocks.experiment.categories.peak.tof import TofPseudoVoigt diff --git a/tests/unit/easydiffraction/analysis/categories/minimizer/test_base.py b/tests/unit/easydiffraction/analysis/categories/minimizer/test_base.py index 3cdda2a08..aa19ba18f 100644 --- a/tests/unit/easydiffraction/analysis/categories/minimizer/test_base.py +++ b/tests/unit/easydiffraction/analysis/categories/minimizer/test_base.py @@ -14,4 +14,9 @@ def test_descriptor_values_and_native_kwargs_use_descriptor_values(): assert minimizer._descriptor_values(('max_iterations',)) == { 'max_iterations': 25, } - assert minimizer._native_kwargs() == {'max_iterations': 25} + assert minimizer._native_kwargs() == { + 'max_iterations': 25, + 'chi_square_change_tolerance': 1e-8, + 'parameter_change_tolerance': 1e-8, + 'gradient_tolerance': 0.0, + } diff --git a/tests/unit/easydiffraction/analysis/categories/minimizer/test_lsq_base.py b/tests/unit/easydiffraction/analysis/categories/minimizer/test_lsq_base.py index fa1ca3ddf..c31dccf47 100644 --- a/tests/unit/easydiffraction/analysis/categories/minimizer/test_lsq_base.py +++ b/tests/unit/easydiffraction/analysis/categories/minimizer/test_lsq_base.py @@ -13,7 +13,15 @@ def test_lsq_minimizer_defaults_to_settings_only(): minimizer = LmfitLeastsqMinimizer() assert minimizer.max_iterations.value == 1000 - assert minimizer._setting_descriptor_names == ('max_iterations',) + assert minimizer.chi_square_change_tolerance.value == 1e-8 + assert minimizer.parameter_change_tolerance.value == 1e-8 + assert minimizer.gradient_tolerance.value == 0.0 + assert minimizer._setting_descriptor_names == ( + 'max_iterations', + 'chi_square_change_tolerance', + 'parameter_change_tolerance', + 'gradient_tolerance', + ) assert minimizer._result_descriptor_names == () @@ -23,9 +31,15 @@ def test_lsq_minimizer_reads_cif_settings(): document = gemmi.cif.read_string( """data_minimizer _minimizer.max_iterations 42 +_minimizer.chi_square_change_tolerance 0.000000001 +_minimizer.parameter_change_tolerance 0.000000002 +_minimizer.gradient_tolerance 0.000000003 """ ) minimizer = LmfitLeastsqMinimizer() minimizer.from_cif(document.sole_block()) assert minimizer.max_iterations.value == 42 + assert minimizer.chi_square_change_tolerance.value == 1e-9 + assert minimizer.parameter_change_tolerance.value == 2e-9 + assert minimizer.gradient_tolerance.value == 3e-9 diff --git a/tests/unit/easydiffraction/analysis/minimizers/test_bumps.py b/tests/unit/easydiffraction/analysis/minimizers/test_bumps.py index bf2bbff02..4e285fe9b 100644 --- a/tests/unit/easydiffraction/analysis/minimizers/test_bumps.py +++ b/tests/unit/easydiffraction/analysis/minimizers/test_bumps.py @@ -306,7 +306,10 @@ def test_run_solver_returns_optimize_result(): bumps_params=[bp1, bp2], ) - assert len(mock_driver_cls.call_args.kwargs['monitors']) == 1 + driver_kwargs = mock_driver_cls.call_args.kwargs + assert len(driver_kwargs['monitors']) == 1 + assert driver_kwargs['ftol'] == 1e-8 + assert driver_kwargs['xtol'] == 1e-8 assert isinstance(res, OptimizeResult) assert res.success is True diff --git a/tests/unit/easydiffraction/analysis/minimizers/test_dfols.py b/tests/unit/easydiffraction/analysis/minimizers/test_dfols.py index 68437dd2a..431ff947e 100644 --- a/tests/unit/easydiffraction/analysis/minimizers/test_dfols.py +++ b/tests/unit/easydiffraction/analysis/minimizers/test_dfols.py @@ -41,7 +41,7 @@ def __init__(self): # Patch dfols.solve to return our FakeRes import easydiffraction.analysis.minimizers.dfols as mod - def fake_solve(fun, x0, bounds, maxfun): + def fake_solve(fun, x0, bounds, maxfun, rhoend): # Verify we pass reasonable arguments del fun assert isinstance(x0, np.ndarray) @@ -49,6 +49,7 @@ def fake_solve(fun, x0, bounds, maxfun): assert isinstance(bounds, tuple) assert all(isinstance(b, np.ndarray) for b in bounds) assert maxfun == 10 + assert rhoend == 1e-8 return FakeRes() monkeypatch.setattr(mod, 'solve', fake_solve) diff --git a/tests/unit/easydiffraction/analysis/minimizers/test_lmfit.py b/tests/unit/easydiffraction/analysis/minimizers/test_lmfit.py index 8286e5121..65e687e42 100644 --- a/tests/unit/easydiffraction/analysis/minimizers/test_lmfit.py +++ b/tests/unit/easydiffraction/analysis/minimizers/test_lmfit.py @@ -92,9 +92,13 @@ def fake_minimize( method, nan_policy, max_nfev, + ftol, + xtol, + gtol, ): del objective_function, params, method, nan_policy observed_max_nfev['value'] = max_nfev + observed_max_nfev['tolerances'] = (ftol, xtol, gtol) return types.SimpleNamespace(success=True, params={}) monkeypatch.setattr( @@ -110,4 +114,5 @@ def fake_minimize( assert minimizer.max_iterations == 300 assert observed_max_nfev['value'] == 300 + assert observed_max_nfev['tolerances'] == (1e-8, 1e-8, 0.0) assert not hasattr(minimizer, 'steps') diff --git a/tests/unit/easydiffraction/analysis/test_analysis.py b/tests/unit/easydiffraction/analysis/test_analysis.py index baaa10129..8d8916484 100644 --- a/tests/unit/easydiffraction/analysis/test_analysis.py +++ b/tests/unit/easydiffraction/analysis/test_analysis.py @@ -164,7 +164,11 @@ def test_minimizer_selector_swap_warns_for_different_defaults(monkeypatch): removed_warning = next(w for w in warnings if 'removes these settings' in w) added_warning = next(w for w in warnings if 'adds these settings' in w) assert removed_warning == ( - 'Switching minimizer type removes these settings:\n• max_iterations' + 'Switching minimizer type removes these settings:\n' + '• chi_square_change_tolerance\n' + '• gradient_tolerance\n' + '• max_iterations\n' + '• parameter_change_tolerance' ) assert added_warning.splitlines() == [ 'Switching minimizer type adds these settings with defaults:', @@ -179,6 +183,54 @@ def test_minimizer_selector_swap_warns_for_different_defaults(monkeypatch): assert not any('' in w for w in warnings) +def test_minimizer_switch_changes_available_tolerances(): + from easydiffraction.analysis.analysis import Analysis + + analysis = Analysis(project=_make_project_with_names([])) + + assert analysis.minimizer._setting_descriptor_names == ( + 'max_iterations', + 'chi_square_change_tolerance', + 'parameter_change_tolerance', + 'gradient_tolerance', + ) + + analysis.minimizer.type = 'bumps (amoeba)' + assert analysis.minimizer._setting_descriptor_names == ( + 'max_iterations', + 'chi_square_change_tolerance', + 'parameter_change_tolerance', + ) + + analysis.minimizer.type = 'bumps (de)' + assert analysis.minimizer._setting_descriptor_names == ( + 'max_iterations', + 'population_convergence_tolerance', + ) + + analysis.minimizer.type = 'dfols' + assert analysis.minimizer._setting_descriptor_names == ( + 'max_iterations', + 'final_trust_region_radius', + ) + + +def test_minimizer_tolerances_are_applied_to_engine(): + from easydiffraction.analysis.analysis import Analysis + + analysis = Analysis(project=_make_project_with_names([])) + analysis.minimizer.chi_square_change_tolerance = 2e-9 + analysis.minimizer.parameter_change_tolerance = 3e-9 + analysis.minimizer.gradient_tolerance = 0.0 + + analysis._sync_engine_from_minimizer_category() + + engine = analysis.fitter.minimizer + assert engine.chi_square_change_tolerance == 2e-9 + assert engine.parameter_change_tolerance == 3e-9 + assert engine.gradient_tolerance == 0.0 + + def test_undo_fit_restores_scalars_and_clears_fit_outputs(): from easydiffraction.analysis.analysis import Analysis from easydiffraction.core.posterior import PosteriorParameterSummary diff --git a/tests/unit/easydiffraction/datablocks/structure/categories/test_atom_sites.py b/tests/unit/easydiffraction/datablocks/structure/categories/test_atom_sites.py index 3da6ce03c..41951847a 100644 --- a/tests/unit/easydiffraction/datablocks/structure/categories/test_atom_sites.py +++ b/tests/unit/easydiffraction/datablocks/structure/categories/test_atom_sites.py @@ -77,13 +77,38 @@ def test_type_symbol_setter(self): site.type_symbol = 'Fe' assert site.type_symbol.value == 'Fe' - def test_ionic_type_symbol_setter(self): + def test_ionic_type_symbol_setter(self, monkeypatch): + import easydiffraction.datablocks.structure.categories.atom_sites.default as atom_sites_mod from easydiffraction.datablocks.structure.categories.atom_sites.default import AtomSite + warning_messages = [] + monkeypatch.setattr(atom_sites_mod.log, 'warning', warning_messages.append) site = AtomSite() site.type_symbol = 'Fe3+' assert site.type_symbol.value == 'Fe3+' + assert warning_messages == [] + + def test_unsupported_ionic_type_symbol_warns_for_default_cryspy(self, monkeypatch): + import easydiffraction.datablocks.structure.categories.atom_sites.default as atom_sites_mod + from easydiffraction.datablocks.structure.categories.atom_sites.default import AtomSite + + warning_messages = [] + monkeypatch.setattr(atom_sites_mod.log, 'warning', warning_messages.append) + site = AtomSite() + + site.type_symbol = 'Pb3+' + site.type_symbol = 'Pb3+' + + assert site.type_symbol.value == 'Pb3+' + assert warning_messages == [ + ( + "Charged atom type 'Pb3+' is not available in the default CrysPy " + "scattering-factor database. Supported ionic forms for 'Pb': Pb2+, " + "Pb4+. The default neutral-atom scattering factors for 'Pb' (no " + 'ionic charge) will be used.' + ) + ] def test_coordinate_setters(self): from easydiffraction.datablocks.structure.categories.atom_sites.default import AtomSite diff --git a/tests/unit/easydiffraction/utils/test_logging_coverage.py b/tests/unit/easydiffraction/utils/test_logging_coverage.py index 503261757..bc69c2841 100644 --- a/tests/unit/easydiffraction/utils/test_logging_coverage.py +++ b/tests/unit/easydiffraction/utils/test_logging_coverage.py @@ -4,6 +4,7 @@ import logging import sys +from io import StringIO import pytest @@ -12,6 +13,7 @@ from easydiffraction.utils.logging import ExceptionHookManager from easydiffraction.utils.logging import IconifiedRichHandler from easydiffraction.utils.logging import Logger +from easydiffraction.utils.logging import NotebookAwareConsole from easydiffraction.utils.logging import _rich_markup_to_inline_html @@ -83,6 +85,44 @@ def test_returns_at_least_min_width(self): assert isinstance(width, int) +class TestNotebookAwareConsole: + def test_notebook_output_has_no_hard_width_wrap(self, monkeypatch): + monkeypatch.setattr('easydiffraction.utils.logging.in_jupyter', lambda: True) + stream = StringIO() + console = NotebookAwareConsole( + file=stream, + width=20, + force_terminal=False, + color_system=None, + ) + + console.print('This message is deliberately wider than twenty characters.', end='') + + assert stream.getvalue() == 'This message is deliberately wider than twenty characters.' + + def test_notebook_log_has_no_hard_width_wrap(self, monkeypatch): + monkeypatch.setattr('easydiffraction.utils.logging.in_jupyter', lambda: True) + stream = StringIO() + console = NotebookAwareConsole( + file=stream, + width=20, + force_terminal=False, + color_system=None, + ) + handler = IconifiedRichHandler( + console=console, + mode='compact', + show_time=False, + show_path=False, + ) + handler.setFormatter(logging.Formatter('%(message)s')) + message = 'This warning is deliberately wider than twenty characters.' + + handler.emit(_make_record(msg=message)) + + assert stream.getvalue() == f'⚠️ {message}\n' + + class TestGetLevelText: def test_compact_mode_returns_icon(self): import logging