From 1837ceaf2a4bf9425c1c2b28db4ea8ca88ecd484 Mon Sep 17 00:00:00 2001 From: Jacob Wilkins Date: Wed, 30 Oct 2024 14:56:59 +0000 Subject: [PATCH 1/4] Add autoscale enum and add alternative options --- MDMC/control/control.py | 10 ++-- MDMC/refinement/FoM/ChiSquared_experror.py | 37 ++++++++++++ MDMC/refinement/FoM/FoM_abs.py | 67 ++++++++++++++++++++++ MDMC/refinement/FoM/RSquared_noneerror.py | 37 ++++++++++++ MDMC/refinement/FoM/__init__.py | 2 + tests/control/test_control.py | 56 ++++++++++-------- 6 files changed, 180 insertions(+), 29 deletions(-) diff --git a/MDMC/control/control.py b/MDMC/control/control.py index e05577f63..f62c0e1d2 100755 --- a/MDMC/control/control.py +++ b/MDMC/control/control.py @@ -38,7 +38,7 @@ from MDMC.MD.engine_facades.facade import MDEngineError from MDMC.MD.parameters import Parameters from MDMC.MD.simulation import Simulation -from MDMC.refinement.FoM.FoM_abs import ObservablePair +from MDMC.refinement.FoM.FoM_abs import AutoScale, ObservablePair from MDMC.refinement.FoM.FoM_factory import FoMFactory from MDMC.refinement.minimizers.minimizer_factory import MinimizerFactory from MDMC.resolution.resolution_factory import ResolutionFactory @@ -147,10 +147,8 @@ class Control: - ``rescale_factor`` (`float`, optional, defaults to `1.`) applied to the experimental data when calculating the FoM to ensure it is on the same scale as the calculated observable - - ``auto_scale`` (`bool`, optional, defaults to `False`) set the - ``rescale_factor`` automatically to minimise the FoM, if both - ``rescale_factor`` and ``auto_scale`` are provided then a warning - is printed and ``auto_scale`` takes precedence + - ``auto_scale`` (str or :class:`AutoScale`, optional, defaults to `CONSTANT`) + See :class:`AutoScale` documentation for more information. - ``use_FFT`` (`bool`, optional, defaults to `True`) whether to use Fast Fourier Transforms in the calculation of dependent variables. FFT speeds up calculation but places restrictions on spacing in the @@ -261,7 +259,7 @@ class Control: 'reader':'GENERIC_READER', 'weight':0.5, 'resolution':{'gaussian':2.35} - 'auto_scale':True}] + 'auto_scale': 'minimise_fom'}] Attributes ---------- diff --git a/MDMC/refinement/FoM/ChiSquared_experror.py b/MDMC/refinement/FoM/ChiSquared_experror.py index 2e50a7089..11d365de6 100644 --- a/MDMC/refinement/FoM/ChiSquared_experror.py +++ b/MDMC/refinement/FoM/ChiSquared_experror.py @@ -31,6 +31,43 @@ class ChiSquaredExpError(FigureOfMerit): mathematical details. """ + def _compute_unreduced(self, obs_pair: ObservablePair): + """ + Compute the unreduced FoM value for the given observable pair. + + Parameters + ---------- + obs_pair : ObservablePair + An ``ObservablePair`` for which the FoM is calculated. + + Returns + ------- + float + Unreduced FoM value. + """ + return np.sum((obs_pair.calculate_difference() / + obs_pair.calculate_exp_errors()) ** 2) + + def _minimise_factor(self, obs_pair: ObservablePair) -> float: + """ + Minimise the FoM factor for the given FoM type. + + Parameters + ---------- + obs_pair : ObservablePair + An ``ObservablePair`` for which the FoM is calculated + + Returns + ------- + float + Computed auto_scale factor to minimise the FoM. + """ + exp_errors = np.array(*obs_pair.exp_obs.errors.values()) + exp_values = np.array(*obs_pair.exp_obs.dependent_variables.values()) + MD_values = np.array(*obs_pair.MD_obs.dependent_variables.values()) + return (np.sum((MD_values / exp_errors) ** 2) / + np.sum(MD_values * exp_values / exp_errors ** 2)) + def calculate_single_FoM(self, obs_pair: ObservablePair): """ Calculates the chi-squared figure of merit for a single diff --git a/MDMC/refinement/FoM/FoM_abs.py b/MDMC/refinement/FoM/FoM_abs.py index 50f7bd869..b2ac2e126 100644 --- a/MDMC/refinement/FoM/FoM_abs.py +++ b/MDMC/refinement/FoM/FoM_abs.py @@ -17,6 +17,7 @@ """A module for Figure of Merits""" from abc import ABC, abstractmethod +from enum import Enum, auto import numpy as np @@ -468,3 +469,69 @@ def calculate_single_FoM(self, obs_pair: ObservablePair) -> float: """ raise NotImplementedError + + @abstractmethod + def _compute_unreduced(self, obs_pair: ObservablePair) -> float: + """ + Compute the unreduced FoM value for the given observable pair. + + Parameters + ---------- + obs_pair : ObservablePair + An ``ObservablePair`` for which the FoM is calculated. + + Returns + ------- + float + Unreduced FoM value. + """ + + @abstractmethod + def _minimise_factor(self, obs_pair: ObservablePair) -> float: + """ + Minimise the FoM factor for the given FoM type. + + Parameters + ---------- + obs_pair : ObservablePair + An ``ObservablePair`` for which the FoM is calculated + + Returns + ------- + float + Computed auto_scale factor to minimise the FoM. + """ + + def compute_rescale_factor(self, obs_pair: ObservablePair) -> float: + """ + Compute rescale factor for calculated observable to match experimental data. + + Parameters + ---------- + obs_pair : ObservablePair + An ``ObservablePair`` for which the FoM is calculated + + Returns + ------- + float + Computed rescale factor. + """ + dep_vars = np.array(*obs_pair.exp_obs.dependent_variables.values()) + + match obs_pair.auto_scale: + case AutoScale.CONSTANT: + fac = obs_pair.rescale_factor + case AutoScale.MINIMISE_FOM: + fac = self._minimise_factor(obs_pair) + case AutoScale.MATCH_MAXIMUM: + fac = max(obs.max() for obs in dep_vars) + case AutoScale.MATCH_ABS_MAXIMUM: + fac = max(np.abs(obs).max() for obs in dep_vars) + case AutoScale.MATCH_SUM: + fac = sum(obs.sum() for obs in dep_vars) + case AutoScale.MATCH_ABS_SUM: + fac = sum(np.abs(obs.sum()) for obs in dep_vars) + case _: + fac = 1. + + return fac diff --git a/MDMC/refinement/FoM/RSquared_noneerror.py b/MDMC/refinement/FoM/RSquared_noneerror.py index 7ecdf4640..f97d0e762 100644 --- a/MDMC/refinement/FoM/RSquared_noneerror.py +++ b/MDMC/refinement/FoM/RSquared_noneerror.py @@ -49,6 +49,43 @@ class RSquared_noneerror(FigureOfMerit): simple linear scaling. """ + def _compute_unreduced(self, obs_pair: ObservablePair): + """ + Compute the unreduced FoM value for the given observable pair. + + Parameters + ---------- + obs_pair : ObservablePair + An ``ObservablePair`` for which the FoM is calculated. + + Returns + ------- + float + Unreduced FoM value. + """ + return np.sum(obs_pair.calculate_difference() ** 2) + + def _minimise_factor(self, obs_pair: ObservablePair) -> float: + """ + Minimise the FoM factor for the given FoM type. + + Parameters + ---------- + obs_pair : ObservablePair + An ``ObservablePair`` for which the FoM is calculated + + Returns + ------- + float + Computed auto_scale factor to minimise the FoM. + """ + exp_values = np.array( + *obs_pair.exp_obs.dependent_variables.values()) + MD_values = np.array(*obs_pair.MD_obs.dependent_variables.values()) + A = np.sum(MD_values * exp_values) + B = np.sum(exp_values ** 2) + return A / B + def calculate_single_FoM(self, obs_pair: ObservablePair): # ignore line too long linting as it is necessary for LaTeX formatting # pylint: disable=line-too-long diff --git a/MDMC/refinement/FoM/__init__.py b/MDMC/refinement/FoM/__init__.py index aaa08d9a3..404209405 100644 --- a/MDMC/refinement/FoM/__init__.py +++ b/MDMC/refinement/FoM/__init__.py @@ -17,10 +17,12 @@ """A module for Figure of Merit calculation""" from . import ChiSquared_experror, FoM_abs, FoM_factory, RSquared_noneerror +from .FoM_abs import AutoScale __all__ = [ "ChiSquared_experror", "FoM_abs", "FoM_factory", "RSquared_noneerror", + "AutoScale", ] diff --git a/tests/control/test_control.py b/tests/control/test_control.py index c278bae86..41dfed552 100644 --- a/tests/control/test_control.py +++ b/tests/control/test_control.py @@ -1,24 +1,26 @@ -"""Tests the Control class -""" +"""Tests the Control class.""" + +import re +from typing import List +from unittest.mock import Mock import logging from pathlib import Path import numpy as np import pandas as pd import pytest -import re -from typing import List -from unittest.mock import Mock, ANY from MDMC.control import Control from MDMC.trajectory_analysis.compact_trajectory import CompactTrajectory from MDMC.trajectory_analysis.observables.sqw import SQw from MDMC.trajectory_analysis.observables.pdf import PairDistributionFunction +from MDMC.MD.engine_facades.facade import MDEngineError from MDMC.MD.parameters import Parameter, Parameters from MDMC.MD.simulation import Simulation, Universe -from MDMC.MD.engine_facades.facade import MDEngineError +from MDMC.refinement.FoM import AutoScale from MDMC.resolution.from_file import FileResolution -from MDMC.MD import Atom, Dispersion, LennardJones +from MDMC.trajectory_analysis.observables.pdf import PairDistributionFunction +from MDMC.trajectory_analysis.observables.sqw import SQw from tests.test_data import data from MDMC.control import Control from MDMC.MD import Atom, Dispersion, LennardJones, Simulation, Universe @@ -392,7 +394,7 @@ def test_control_refine_stdout_auto_scale(simulation, exp_datasets, MockParameter('A', 1), MockParameter('B', 34743.233E6)]) - datasets = exp_datasets(auto_scale=True, file_name=file_name) + datasets = exp_datasets(auto_scale="MINIMISE_FOM", file_name=file_name) dt = DATASET_INFO['use_FFT'][file_name]['dt'] ctrl = Control(simulation(time_step=dt), datasets, [], reset_config=False) @@ -442,7 +444,7 @@ def test_control_no_scaling(simulation, exp_datasets, file_name): for pair in ctrl.observable_pairs: assert pair.rescale_factor == 1. - assert not pair.auto_scale + assert pair.auto_scale is AutoScale.CONSTANT @pytest.mark.parametrize('file_name', @@ -460,43 +462,51 @@ def test_control_rescale_factor(simulation, exp_datasets, file_name): for pair in ctrl.observable_pairs: assert pair.rescale_factor == 0.5 - assert not pair.auto_scale - - -@pytest.mark.parametrize('file_name', - ['263K05Awat_LAMP', 'Well_s_q_omega_Ar_data.xml']) -def test_control_auto_scale(simulation, exp_datasets, file_name): + assert pair.auto_scale is AutoScale.CONSTANT + +@pytest.mark.parametrize("scale_type, expected", [("NONE", 1.), + ("CONSTANT", 2.), + ("MINIMISE_FOM", 8889991348.84444), + ("MATCH_MAXIMUM", 5.113321478819434), + ("MATCH_ABS_MAXIMUM", 5.113321478819434), + ("MATCH_SUM", 75.04494098975), + ("MATCH_ABS_SUM", 75.04494098975), + ]) +def test_control_auto_scale(simulation, exp_datasets, scale_type, expected): """ Test that ``auto_scale`` is applied. """ - datasets = exp_datasets(auto_scale=True, file_name=file_name) - dt = DATASET_INFO['use_FFT'][file_name]['dt'] + datasets = exp_datasets(auto_scale=scale_type, + rescale_factor=2., + file_name="Well_s_q_omega_Ar_data.xml") + dt = DATASET_INFO["use_FFT"]["Well_s_q_omega_Ar_data.xml"]["dt"] ctrl = Control(simulation(time_step=dt), datasets, [], verbose=-1, reset_config=False) for pair in ctrl.observable_pairs: - assert pair.auto_scale + assert pair.auto_scale is AutoScale[scale_type] + assert pair.rescale_factor == pytest.approx(expected) @pytest.mark.parametrize('file_name', ['263K05Awat_LAMP', 'Well_s_q_omega_Ar_data.xml']) def test_control_scaling_warning(simulation, exp_datasets, file_name, - capsys): + caplog): """ Test that when both ``rescale_factor`` and ``auto_scale`` specified then the latter is used and a warning is printed to explain this. """ datasets = exp_datasets(rescale_factor=0.5, - auto_scale=True, + auto_scale="minimize_fom", file_name=file_name) dt = DATASET_INFO['use_FFT'][file_name]['dt'] ctrl = Control(simulation(time_step=dt), datasets, [], reset_config=False) for pair in ctrl.observable_pairs: - assert pair.auto_scale + assert pair.auto_scale is AutoScale.MINIMISE_FOM stdout = capsys.readouterr().out stdout_message = ('Both `rescale_factor` and `auto_scale` set for file ' @@ -836,8 +846,8 @@ def test_control_resolution_function(simulation, exp_datasets): verbose=-1, reset_config=False) - assert type(ctrl.observable_pairs[0].exp_obs.resolution) == FileResolution - assert type(ctrl.observable_pairs[0].MD_obs.resolution) == FileResolution + assert isinstance(ctrl.observable_pairs[0].exp_obs.resolution, FileResolution) + assert isinstance(ctrl.observable_pairs[0].MD_obs.resolution, FileResolution) @pytest.mark.parametrize('steps', [0, None]) def test_control_equilibrate_auto_check(simulation, exp_datasets, steps, monkeypatch): From 99f3358b98e8ec461a8c0535d71271a0acec11e2 Mon Sep 17 00:00:00 2001 From: Jacob Wilkins Date: Mon, 6 Jan 2025 15:07:36 +0000 Subject: [PATCH 2/4] Add initial docs on auto-scale --- doc/_static/files/linux/mdmc.tar.gz | Bin 1087 -> 1030 bytes doc/_static/files/osx-windows/mdmc.zip | Bin 1017 -> 1017 bytes doc/tutorials/Argon-a-to-z.ipynb | 58 ++++++++-------- doc/tutorials/running-a-refinement.ipynb | 13 +++- .../demo_output_visualisation.ipynb | 57 ++++++++-------- examples/June_23_Workshop/demo_worklow.ipynb | 63 ++++++++---------- examples/water-dlp.py | 4 +- tests/MD/dlpoly/test_simulation_dlpoly_ar.py | 2 +- 8 files changed, 96 insertions(+), 101 deletions(-) diff --git a/doc/_static/files/linux/mdmc.tar.gz b/doc/_static/files/linux/mdmc.tar.gz index d68200d7a52ffddefb10d393c219fda6d99e7b1f..e19fd68f0f38a74f593a72408c93e2d21b19ca6a 100644 GIT binary patch literal 1030 zcmV+h1o`_PiwFqo@Ox(h|7~P#V=i=IascgE-%sl{6z(&B#SufgFfBjZ(t9f*T?7x? z1iEN2X?sFWVj4qYXSS0z(*F1zJ0U5(7xprkHvCnp9ozYQj=ytGn)-nse7+)^A1TSG zoV*Ic4$rk=IGv4sGaiL;6i;9?c>~l+6N%eT-Y4RZP-<4vlV}l!^W$i?_~g&#;o?&~ z-J3Z4@8JipzlTZc#e8nsXfY4B+ac$p**soEVHAZ6lmF52VgmEG%>R3misXfLe>eZ1 z)!)^BSN~o8f4}uV6X^q$UMk8;DC)Om@f!JmJexgN|EJL+-l_l7;~49I_?G$qPV0X| zrDB3F;ltDqKkO+g8!l4#bXHBYv){R#ORFXqpJ| z&@c30|07~)zV_`{BZ|7DTTEw?8m9?xx`qtBO0__11&3Uyj$Gl0G47FE0pS@MOnWx; ze4s1I177FoRd*IFDd>}994+$$qs6Uid3w8MI=}65c4>!$VYB^#`^M1rTkfjxTNIgg zO=O0qj++F!tO6fry_&Pl^6BMBeI_bYJ2$32;K>?B^iM*H{FG}(3bk#xUDX?E`yCTj zn-Y&^Jlr5*JV=Pj!AnPbiIIG}JV8o0`U`YJ(;CGn>q4^%53VOv10mg5$raUVJW@FS z>H6y8^mm}Vfrdy%`a`!0ers~zTQLo3Avm>?R;&WXv2+xLIp8~@OibyVaJa{c@?`!_ ziqhVGNJjIJX=U7NMQ!85^D~zB9~^(vB+RT32cr1P2F)Sn6OafR8@o`n*nj47uR){PL`l;vP@f z_M6a;Z!1;^J+NaaOov$TpIeny;Qey*)x3E+2cxb#3E!24VR|IXc2^El_fG_TnJ@U> zXI0CSuWlZq!}BoCL*IVzf(tK^Vr|NS=b^h|?Mcx)$x5=KOZ!l<+qhqs?)}fb|GD=+ zhkrT$?L+O=|Nnd8{qGz9|3z^ey7xZ^2L}fS2L}fS2L}fShkpux0YQYto Ag#Z8m literal 1087 zcmV-F1i{2x_?ej`UO(`Ik2hrbDp(I}P5wutWHf-# z<^SGgPNiakPvJx2haV0Um2;L+H9Y{R@+vbez^q|qJ_X-i`~>G^DKerd)T*Jm@U&im z7LW<93FB1$0*iuW1uzAY{?#x^b9fLEZdO;Ms%m8xlMJ<}7Vzr)`ugS;N;Z>3uHe{L z0&u;buW;yO11=X-L8BOdgxkBzPhajV0P3HDmJPL6wUAn^?Kaq-YRZAwQESBSO})}o z0vDRjgm`WjdT=;FnVNrxc5E)nrlK227m^yM32?fA9KFi4Kx+kuLZ~&k!VzQK3%CNp zb2ON?Xy|zdSCMDDF3_v>Ua(n7x3;mj%nu9~_iDwn`vud*{W@o-b~xyi9d_7v2DVkX z%fcT~WZDIhIhxul33OElKF)d#XUpmKWeMpOY_*4s-A<(uURGQ!bcpi7!HNJiC^n$@^*EuiWE>C#fJ zs8;=!!sTzbH&^F>0_Ag<6Uj)s>6XFQCOf=GrXec@rje9H6*!b|goYuNHivVNa+$HIpnao^=5b|k*jYw*OYi&t> zvcpPa!Vd{R)#y{pe;=tcifrLl<(`RArU}z+#5w`J;S$3B+{FTwr8Qxx>nO5I66AUQU@CWeDV?37tPGakPEIa zuPBartyNhK-siio=FQVN=y}~q_;p$6rhBk_cV##A zv_;^*;{`watX}ciS2uUj;dvP6p>O~2f(vgZ#ln;W&qH^`!jqzPl2zo9PVGa*s&Q|Z z?)%Su|9x-gf9Wt8`ja$@PA93$(07slufAFa|9AfHod0d-e}DcLr-{k`B#nmO+4-M& zkh%Kr>i>^j|K0CD@2>vakD9&e_>c3ySAPE)$4TPO|C~5+;>3v)CrgM`0WJ?3r0WjLCt_>JUZ^vkio?huIbYmIo1` diff --git a/doc/tutorials/Argon-a-to-z.ipynb b/doc/tutorials/Argon-a-to-z.ipynb index f6d1e1b2e..1f93c9a6c 100644 --- a/doc/tutorials/Argon-a-to-z.ipynb +++ b/doc/tutorials/Argon-a-to-z.ipynb @@ -1,16 +1,16 @@ { "cells": [ { - "attachments": {}, "cell_type": "markdown", + "id": "bdcd3b83", "metadata": {}, "source": [ "# Argon A-to-Z" ] }, { - "attachments": {}, "cell_type": "markdown", + "id": "4bffcd2e", "metadata": {}, "source": [ "This tutorial aims to take the user from no familiarity with MDMC to enough competence that they can create a simple simulation and run a refinement, by walking through a simulation and refinement for the liquid argon data from [van Well et al. (1985)](https://doi.org/10.1103/PhysRevA.31.3391). For more details on specific parts of this, please see the how-to guides!\n", @@ -23,6 +23,7 @@ { "cell_type": "code", "execution_count": null, + "id": "ef884b17", "metadata": {}, "outputs": [], "source": [ @@ -36,8 +37,8 @@ ] }, { - "attachments": {}, "cell_type": "markdown", + "id": "b265543d", "metadata": {}, "source": [ "### Setting up a configuration and simulation\n", @@ -50,6 +51,7 @@ { "cell_type": "code", "execution_count": null, + "id": "c16282ff", "metadata": {}, "outputs": [], "source": [ @@ -60,8 +62,8 @@ ] }, { - "attachments": {}, "cell_type": "markdown", + "id": "51c77e8c", "metadata": {}, "source": [ "Note that at this point, there are no interaction forces between the argon atoms! The simulation doesn't know how these atoms should interact with each other. In the cell below an appropriate (for argon) force-field interaction potential is defined; we use a dispersive interaction with potential energy calculated by the [Lennard-Jones potential](https://en.wikipedia.org/wiki/Lennard-Jones_potential). This interaction has two parameters, epsilon and sigma, which determine the strength of the potential energy between atoms." @@ -70,6 +72,7 @@ { "cell_type": "code", "execution_count": null, + "id": "00c26bbd", "metadata": {}, "outputs": [], "source": [ @@ -80,8 +83,8 @@ ] }, { - "attachments": {}, "cell_type": "markdown", + "id": "133a3bec", "metadata": {}, "source": [ "A `cutoff` distance, past which atoms do not interact, is chosen arbitrarily (see `help(Dispersion)` for more info). A [rule of thumb for Lennard-Jones](https://en.wikipedia.org/wiki/Lennard-Jones_potential#Lennard-Jones_truncated_&_shifted_(LJTS)_potential) is to pick `cutoff=2.5*sigma`. The value for argon is recommended to be between 8 and 12 ang. Ideally, and for any system you want to pick at value of the `cutoff` which is small while not compromising accuracy. For this system, picking a value between 8 and 12 ang is found to give near identical results to the experimental data.\n", @@ -89,7 +92,7 @@ "Next (and before starting the refinement), we set up the Simulation, which contains:\n", "- the `Universe` that the simulation is based on;\n", "- the MD engine used to run the simulations;\n", - "- the time-step in femtoseconds between each simulation frame; \n", + "- the time-step in femtoseconds between each simulation frame;\n", "- the temperature of the Universe (used to calculate atom velocity);\n", "- the trajectory step, which is how many time steps should occur for each 'step' of the refinement." ] @@ -97,6 +100,7 @@ { "cell_type": "code", "execution_count": null, + "id": "de142543", "metadata": {}, "outputs": [], "source": [ @@ -109,8 +113,8 @@ ] }, { - "attachments": {}, "cell_type": "markdown", + "id": "b944b6dc", "metadata": {}, "source": [ "We then minimize and equilibrate the simulation; minimising the simulation avoids it getting 'stuck' in local minima, and equilibration runs the simulation until it reaches a state with a physically feasible temperature/energy distribution. This ensures our simulation isn't affected by the initial arrangement of the atoms." @@ -119,6 +123,7 @@ { "cell_type": "code", "execution_count": null, + "id": "3d5a65c3", "metadata": {}, "outputs": [], "source": [ @@ -128,8 +133,8 @@ ] }, { - "attachments": {}, "cell_type": "markdown", + "id": "f1daca6b", "metadata": {}, "source": [ "### Refining our data\n", @@ -140,6 +145,7 @@ { "cell_type": "code", "execution_count": null, + "id": "10fe3825", "metadata": {}, "outputs": [], "source": [ @@ -151,13 +157,13 @@ " 'type':'SQw',\n", " 'reader':'xml_SQw',\n", " 'weight':1.,\n", - " 'auto_scale':True,\n", + " 'auto_scale':'minimise_fom',\n", " 'resolution':800}]" ] }, { - "attachments": {}, "cell_type": "markdown", + "id": "02e73992", "metadata": {}, "source": [ "We then need to create our parameters to fit against. In this case, we take all the universe parameters (which here are just the sigma and epsilon values that the Lennard-Jones potential depends on). Note that above when we set our initial `LennardJones` function in the `Dispersion` object, the epsilon and sigma were our \"initial guesses\" that the refinement will start from.\n", @@ -168,6 +174,7 @@ { "cell_type": "code", "execution_count": null, + "id": "99ee9584", "metadata": {}, "outputs": [], "source": [ @@ -177,8 +184,8 @@ ] }, { - "attachments": {}, "cell_type": "markdown", + "id": "828ddedb", "metadata": {}, "source": [ "Now we create our `Control` object. This object oversees the refinement; it brings the simulation, dataset, and the fitting parameters together, and then does the following:\n", @@ -194,6 +201,7 @@ { "cell_type": "code", "execution_count": null, + "id": "7e59ae9e", "metadata": {}, "outputs": [], "source": [ @@ -217,6 +225,7 @@ }, { "cell_type": "markdown", + "id": "73f95081", "metadata": {}, "source": [ "Now that the dataset has been specified, and used to configure various processes and parameters, the system can be equilibrated." @@ -225,6 +234,7 @@ { "cell_type": "code", "execution_count": null, + "id": "edff7a1a", "metadata": {}, "outputs": [], "source": [ @@ -234,8 +244,8 @@ ] }, { - "attachments": {}, "cell_type": "markdown", + "id": "6163c9b4", "metadata": {}, "source": [ "The number of `MD_steps` specified must be large enough to allow for statistically reasonable calculation of all observables. This depends the `type` of the dataset provided and the value of the `traj_step` (specified when creating the `Simulation`). If a value for `MD_steps` is not provided, then the minimum number needed will be used automatically.\n", @@ -244,8 +254,8 @@ ] }, { - "attachments": {}, "cell_type": "markdown", + "id": "5abcefeb", "metadata": {}, "source": [ "Finally, start the refinement! `n_steps` has been set to `25` just so you can see what a refinement looks like; it will take many more steps to fully refine a dataset. Bump it up to a higher number when you're ready. Results can also be plotted via the `control.plot_results` method." @@ -254,6 +264,7 @@ { "cell_type": "code", "execution_count": null, + "id": "055f5d10", "metadata": {}, "outputs": [], "source": [ @@ -267,25 +278,8 @@ "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.2" - }, - "vscode": { - "interpreter": { - "hash": "949777d72b0d2535278d3dc13498b2535136f6dfe0678499012e853ee9abcab1" - } } }, "nbformat": 4, - "nbformat_minor": 4 -} + "nbformat_minor": 5 +} \ No newline at end of file diff --git a/doc/tutorials/running-a-refinement.ipynb b/doc/tutorials/running-a-refinement.ipynb index 98b7627bd..992a3e6cc 100644 --- a/doc/tutorials/running-a-refinement.ipynb +++ b/doc/tutorials/running-a-refinement.ipynb @@ -102,7 +102,7 @@ " 'type':'SQw',\n", " 'reader':'LAMPSQw',\n", " 'weight':1.,\n", - " 'auto_scale':True,\n", + " 'auto_scale': 'minimise_fom',\n", " 'use_FFT':False,\n", " 'resolution':{'file': 'data/262p7K0A5van_LAMP'}}" ] @@ -114,7 +114,16 @@ "source": [ "As the dataset we are using has both negative energy values and non-uniform spacing, the default behaviour of `use_FFT=True` would cause the data to be interpolated as part of the refinement process. Setting this to `False` allows us to preserve the original energy values.\n", "\n", - "The default behaviour of the scaling (`rescale_factor=1.`, `auto_scale=False`) assumes that the dataset provided has been properly scaled and normalised for the refinement process. This is the preferred way of using MDMC, and arbitrary or automatic rescaling should be undertaken with care. For example, using `auto_scale` to determine the scaling does not take into account any physical aspects of scaling the data, such as the presence or absence of background events from peaks outside its range.\n", + "The default behaviour of the scaling (`rescale_factor=1.`, `auto_scale='constant'`) assumes that the dataset provided has been properly scaled and normalised for the refinement process. This is the preferred way of using MDMC, and arbitrary or automatic rescaling should be undertaken with care. For example, using `minimise_fom` to determine the scaling does not take into account any physical aspects of scaling the data, such as the presence or absence of background events from peaks outside its range.\n", + "\n", + "Other possible values for the auto_scale are:\n", + "\n", + "- `CONSTANT` applies a constant scaling factor to the MD data to align with the experimental data.\n", + "- `MINIMISE_FOM` uses the algorithm described in :ref:`explanation/figure-of-merit:rescaling` to best minimise the figure of merit.\n", + "- `MATCH_MAXIMUM` rescales the MD data such that the maximum is the same as that of the experimental data.\n", + "- `MATCH_ABS_MAXIMUM` rescales the MD data such that the absolute maximum is the same as that of the experimental data.\n", + "- `MATCH_SUM` is a naïve approximation to rescale the MD data such that the integral area under the curve is the same under the assumption that the samples are evenly spaced.\n", + "- `MATCH_ABS_SUM` is like `MATCH_SUM` except that the data's absolute values are used instead.\n", "\n", "The specific manner in which the `weight` applies to calculating the figure of merit (FoM) depends on the particular FoM which is used for the refinement. By default this is a least-squares difference weighted by the experimental error (`StandardFoMCalculator`):" ] diff --git a/examples/June_23_Workshop/demo_output_visualisation.ipynb b/examples/June_23_Workshop/demo_output_visualisation.ipynb index 63909ae0d..af6750030 100644 --- a/examples/June_23_Workshop/demo_output_visualisation.ipynb +++ b/examples/June_23_Workshop/demo_output_visualisation.ipynb @@ -1,16 +1,16 @@ { "cells": [ { - "attachments": {}, "cell_type": "markdown", + "id": "b57818db", "metadata": {}, "source": [ "# Demonstration of output analysis/visualisation" ] }, { - "attachments": {}, "cell_type": "markdown", + "id": "090721d1", "metadata": {}, "source": [ "In this demonstration we will run a refinement and then focus on interrogating the results. First we need to run the refinement." @@ -19,6 +19,7 @@ { "cell_type": "code", "execution_count": null, + "id": "0fadb556", "metadata": {}, "outputs": [], "source": [ @@ -53,7 +54,7 @@ " 'type':'SQw',\n", " 'reader':'xml_SQw',\n", " 'weight':1.,\n", - " 'auto_scale':True,\n", + " 'auto_scale':'minimise_fom',\n", " 'resolution':800}]\n", "\n", "fit_parameters = universe.parameters\n", @@ -65,14 +66,14 @@ " fit_parameters=fit_parameters,\n", " minimizer_type=\"GPO\",\n", " reset_config=True,\n", - " MD_steps=12000, \n", + " MD_steps=12000,\n", " equilibration_steps=6000)\n", "control.refine(n_steps=22)" ] }, { - "attachments": {}, "cell_type": "markdown", + "id": "fa6df12a", "metadata": {}, "source": [ "Look at the liklihood of the parameter values across the range of possible parameter values using a built-in helper method" @@ -81,6 +82,7 @@ { "cell_type": "code", "execution_count": null, + "id": "3e1bdfc0", "metadata": {}, "outputs": [], "source": [ @@ -88,22 +90,25 @@ ] }, { - "attachments": {}, "cell_type": "markdown", + "id": "2aa4e946", "metadata": {}, "source": [ - "The `S(Q,w)` data is contained in the `Control` object used for the refinement. The `ObservablePair` contains both the experimentally measured `S(Q,w)` as well as the one calculated from the last MD simulation's `Trajectory`. " + "The `S(Q,w)` data is contained in the `Control` object used for the refinement. The `ObservablePair` contains both the experimentally measured `S(Q,w)` as well as the one calculated from the last MD simulation's `Trajectory`." ] }, { "cell_type": "code", "execution_count": null, - "metadata": {}, + "id": "bbb15e9d", + "metadata": { + "lines_to_next_cell": 2 + }, "outputs": [], "source": [ "# Extract the `ObservablePair` with the S(Q,w) data\n", "obs_pair = control.observable_pairs[0]\n", - "# Extract the calculated S(Q,w) and apply the `rescale_factor` that minimises the \n", + "# Extract the calculated S(Q,w) and apply the `rescale_factor` that minimises the\n", "# figure-of-merit (needed as the experimental data is in arbitrary units).\n", "SQw_sim = obs_pair.MD_obs.SQw / obs_pair.rescale_factor\n", "# Extract the measured S(Q,w) and its errors\n", @@ -111,12 +116,12 @@ "SQw_err = obs_pair.exp_obs.SQw_err\n", "# Extract the Q and energy (E) values at which S(Q,w) was measured\n", "Q = obs_pair.exp_obs.Q\n", - "E = obs_pair.exp_obs.E\n" + "E = obs_pair.exp_obs.E" ] }, { - "attachments": {}, "cell_type": "markdown", + "id": "ae0c869b", "metadata": {}, "source": [ "Below is plotting code to look at S(Q,w) as a function of Q from both the experimental data and the final trajectory. Note that depending on the optimisation procedure used the final trajectory might not correspond to the most optimal point in the refinable parameter space." @@ -125,6 +130,7 @@ { "cell_type": "code", "execution_count": null, + "id": "a539d229", "metadata": {}, "outputs": [], "source": [ @@ -139,6 +145,7 @@ { "cell_type": "code", "execution_count": null, + "id": "ec4ed679", "metadata": {}, "outputs": [], "source": [ @@ -168,8 +175,8 @@ ] }, { - "attachments": {}, "cell_type": "markdown", + "id": "f479bdd8", "metadata": {}, "source": [ "This is an example of how one could plot the PairDistributionFunction (`PDF`) if that was used as the type of `Observable` to refine. We can calculate the `PDF` from the last `Trajectory` of the refinement above (might not correspond to the most optimal place in parameter space) or we can also import a previously calculated/measured `PDF`." @@ -178,6 +185,7 @@ { "cell_type": "code", "execution_count": null, + "id": "38b7c47d", "metadata": {}, "outputs": [], "source": [ @@ -194,6 +202,7 @@ { "cell_type": "code", "execution_count": null, + "id": "a4e4e67e", "metadata": {}, "outputs": [], "source": [ @@ -206,16 +215,17 @@ ] }, { - "attachments": {}, "cell_type": "markdown", + "id": "88345914", "metadata": {}, "source": [ - "Below is another example of plotting a `PDF` and partial pairs but this time the data is imported from a previous calculation (using NMOLDYN on a water trajetory to be precise) " + "Below is another example of plotting a `PDF` and partial pairs but this time the data is imported from a previous calculation (using NMOLDYN on a water trajetory to be precise)" ] }, { "cell_type": "code", "execution_count": null, + "id": "51b25f19", "metadata": {}, "outputs": [], "source": [ @@ -237,21 +247,8 @@ "display_name": "Python 3", "language": "python", "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.2" - }, - "orig_nbformat": 4 + } }, "nbformat": 4, - "nbformat_minor": 2 -} + "nbformat_minor": 5 +} \ No newline at end of file diff --git a/examples/June_23_Workshop/demo_worklow.ipynb b/examples/June_23_Workshop/demo_worklow.ipynb index 57433191f..bc1c2cb3c 100644 --- a/examples/June_23_Workshop/demo_worklow.ipynb +++ b/examples/June_23_Workshop/demo_worklow.ipynb @@ -1,16 +1,16 @@ { "cells": [ { - "attachments": {}, "cell_type": "markdown", + "id": "f63f9253", "metadata": {}, "source": [ "# Workflow Demonstration using Argon" ] }, { - "attachments": {}, "cell_type": "markdown", + "id": "95e6f65e", "metadata": {}, "source": [ "Demo of the complete workflow of optimising the Lennard Jones parameters for liquid argon. For details of the different parts of the demo see the mentioned tutorials and wider MDMC documentation. This is adapted from the `Argon a-to-z` tutorial." @@ -19,6 +19,7 @@ { "cell_type": "code", "execution_count": null, + "id": "9cdaca3a", "metadata": {}, "outputs": [], "source": [ @@ -30,8 +31,8 @@ ] }, { - "attachments": {}, "cell_type": "markdown", + "id": "0dbf6401", "metadata": {}, "source": [ "Set up the simulation box and add some Argon atoms. The mass is specified here because the experimental data set used later was for Ar36. Otherwise, the mass would be automatically look up in a reference table.\n", @@ -41,6 +42,7 @@ { "cell_type": "code", "execution_count": null, + "id": "6765d588", "metadata": {}, "outputs": [], "source": [ @@ -57,8 +59,8 @@ ] }, { - "attachments": {}, "cell_type": "markdown", + "id": "8ddaf2c2", "metadata": {}, "source": [ "Add an interatomic interaction between the Argon atoms using a Lennard-Jones potential. See also the `Building a Universe` and `Applying a ForceField` tutorials for more info." @@ -67,6 +69,7 @@ { "cell_type": "code", "execution_count": null, + "id": "66cfebfd", "metadata": {}, "outputs": [], "source": [ @@ -77,8 +80,8 @@ ] }, { - "attachments": {}, "cell_type": "markdown", + "id": "6ed7f70f", "metadata": {}, "source": [ "In this case the interaction potential chosen is the humble Lennard Jones (to get info see doc or type `help(LennardJones)`).\n", @@ -87,8 +90,8 @@ ] }, { - "attachments": {}, "cell_type": "markdown", + "id": "a7329e17", "metadata": {}, "source": [ "\n", @@ -98,6 +101,7 @@ { "cell_type": "code", "execution_count": null, + "id": "9d109d03", "metadata": {}, "outputs": [], "source": [ @@ -112,6 +116,7 @@ { "cell_type": "code", "execution_count": null, + "id": "09527995", "metadata": {}, "outputs": [], "source": [ @@ -121,8 +126,8 @@ ] }, { - "attachments": {}, "cell_type": "markdown", + "id": "e823d1aa", "metadata": {}, "source": [ "Specify details about the experimental data that we want to refine against. See also the `Running a refinement` tutorial." @@ -131,6 +136,7 @@ { "cell_type": "code", "execution_count": null, + "id": "a138c0f3", "metadata": {}, "outputs": [], "source": [ @@ -140,13 +146,13 @@ " 'type':'SQw',\n", " 'reader':'xml_SQw',\n", " 'weight':1.,\n", - " 'auto_scale':True,\n", + " 'auto_scale':'minimise_fom',\n", " 'resolution':800}]" ] }, { - "attachments": {}, "cell_type": "markdown", + "id": "c4dad7d7", "metadata": {}, "source": [ "Select the parameters to be refined and give them bounds. See also the `Selecting fitting parameters` tutorial" @@ -155,6 +161,7 @@ { "cell_type": "code", "execution_count": null, + "id": "3a1b6be5", "metadata": {}, "outputs": [], "source": [ @@ -164,8 +171,8 @@ ] }, { - "attachments": {}, "cell_type": "markdown", + "id": "c45d2605", "metadata": {}, "source": [ "Set up the refinement using the Gaussian-Process-Optimiser procedure and specify the length of the trajectory via `MD_steps`." @@ -174,6 +181,7 @@ { "cell_type": "code", "execution_count": null, + "id": "3ffe0ee0", "metadata": {}, "outputs": [], "source": [ @@ -182,13 +190,13 @@ " fit_parameters=fit_parameters,\n", " minimizer_type=\"GPO\",\n", " reset_config=True,\n", - " MD_steps=12000, \n", + " MD_steps=12000,\n", " equilibration_steps=12000)" ] }, { - "attachments": {}, "cell_type": "markdown", + "id": "f3365a19", "metadata": {}, "source": [ "And finally start the refinement! The parameter `n_steps` specifies the number of refinement steps to be run. It can also be specified during the previous step of creating a `Control` object." @@ -197,16 +205,19 @@ { "cell_type": "code", "execution_count": null, - "metadata": {}, + "id": "e31a672a", + "metadata": { + "lines_to_next_cell": 2 + }, "outputs": [], "source": [ "# Run the refinement, i.e. refine the FF parameters against the data\n", - "control.refine(n_steps=35)\n" + "control.refine(n_steps=35)" ] }, { - "attachments": {}, "cell_type": "markdown", + "id": "e4280a0e", "metadata": {}, "source": [ "We can now plot the output of the refinement in various ways. For example using a corner plot via a little helper function:" @@ -215,6 +226,7 @@ { "cell_type": "code", "execution_count": null, + "id": "873315cf", "metadata": {}, "outputs": [], "source": [ @@ -227,25 +239,8 @@ "display_name": "Python 3.9.6 64-bit", "language": "python", "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.2" - }, - "vscode": { - "interpreter": { - "hash": "949777d72b0d2535278d3dc13498b2535136f6dfe0678499012e853ee9abcab1" - } } }, "nbformat": 4, - "nbformat_minor": 4 -} + "nbformat_minor": 5 +} \ No newline at end of file diff --git a/examples/water-dlp.py b/examples/water-dlp.py index e464f57bc..08477b582 100755 --- a/examples/water-dlp.py +++ b/examples/water-dlp.py @@ -59,12 +59,12 @@ exp_dataset_ILL = [{'file_name':'../doc/tutorials/data/263K05Awat_LAMP', 'type':'SQw', 'reader':'LAMPSQw', - 'auto_scale':True, + 'auto_scale':'minimise_fom', 'weight':1.}] exp_dataset_ISIS = [{'file_name':'../doc/tutorials/data/IRIS_26176_water_data.dat', 'type':'SQw', 'reader':'MantidSQw', - 'auto_scale':True, + 'auto_scale':'minimise_fom', 'weight':1., 'resolution':'../doc/tutorials/data/IRIS_26173_water_data_resolution.dat'}] diff --git a/tests/MD/dlpoly/test_simulation_dlpoly_ar.py b/tests/MD/dlpoly/test_simulation_dlpoly_ar.py index 7d9bf069b..d51a45599 100644 --- a/tests/MD/dlpoly/test_simulation_dlpoly_ar.py +++ b/tests/MD/dlpoly/test_simulation_dlpoly_ar.py @@ -56,7 +56,7 @@ def exp_datasets(): 'type': 'SQw', 'reader': 'xml_SQw', 'weight': 1., - 'auto_scale': True, + 'auto_scale': 'minimise_fom', 'resolution': None}] From b520109251c7e2ecc4c0857b0e6a497037af49fb Mon Sep 17 00:00:00 2001 From: Jacob Wilkins <46597752+oerc0122@users.noreply.github.com> Date: Tue, 25 Mar 2025 11:56:33 +0000 Subject: [PATCH 3/4] Update MDMC/control/control.py Co-authored-by: Alex H. Room <69592136+alexhroom@users.noreply.github.com> --- MDMC/control/control.py | 1 + MDMC/refinement/FoM/FoM_abs.py | 1 + 2 files changed, 2 insertions(+) diff --git a/MDMC/control/control.py b/MDMC/control/control.py index f62c0e1d2..fe3fbcb71 100755 --- a/MDMC/control/control.py +++ b/MDMC/control/control.py @@ -148,6 +148,7 @@ class Control: the experimental data when calculating the FoM to ensure it is on the same scale as the calculated observable - ``auto_scale`` (str or :class:`AutoScale`, optional, defaults to `CONSTANT`) + The method for automatically setting the rescale factor. See :class:`AutoScale` documentation for more information. - ``use_FFT`` (`bool`, optional, defaults to `True`) whether to use Fast Fourier Transforms in the calculation of dependent variables. diff --git a/MDMC/refinement/FoM/FoM_abs.py b/MDMC/refinement/FoM/FoM_abs.py index b2ac2e126..c510ca0b5 100644 --- a/MDMC/refinement/FoM/FoM_abs.py +++ b/MDMC/refinement/FoM/FoM_abs.py @@ -18,6 +18,7 @@ from abc import ABC, abstractmethod from enum import Enum, auto +from typing import Union import numpy as np From b296b374693be1162e8911494ed6afbbdf5e2489 Mon Sep 17 00:00:00 2001 From: Jacob Wilkins Date: Wed, 14 May 2025 14:36:55 +0100 Subject: [PATCH 4/4] Fix issues with members --- MDMC/control/control.py | 2 +- MDMC/refinement/FoM/ChiSquared_experror.py | 8 ++++---- MDMC/refinement/FoM/FoM_abs.py | 5 ++--- MDMC/refinement/FoM/RSquared_noneerror.py | 5 ++--- doc/_static/files/linux/mdmc.tar.gz | Bin 1030 -> 1029 bytes doc/_static/files/osx-windows/mdmc.zip | Bin 1017 -> 1017 bytes 6 files changed, 9 insertions(+), 11 deletions(-) diff --git a/MDMC/control/control.py b/MDMC/control/control.py index fe3fbcb71..360537180 100755 --- a/MDMC/control/control.py +++ b/MDMC/control/control.py @@ -38,7 +38,7 @@ from MDMC.MD.engine_facades.facade import MDEngineError from MDMC.MD.parameters import Parameters from MDMC.MD.simulation import Simulation -from MDMC.refinement.FoM.FoM_abs import AutoScale, ObservablePair +from MDMC.refinement.FoM.FoM_abs import ObservablePair from MDMC.refinement.FoM.FoM_factory import FoMFactory from MDMC.refinement.minimizers.minimizer_factory import MinimizerFactory from MDMC.resolution.resolution_factory import ResolutionFactory diff --git a/MDMC/refinement/FoM/ChiSquared_experror.py b/MDMC/refinement/FoM/ChiSquared_experror.py index 11d365de6..20fc371a4 100644 --- a/MDMC/refinement/FoM/ChiSquared_experror.py +++ b/MDMC/refinement/FoM/ChiSquared_experror.py @@ -45,8 +45,7 @@ def _compute_unreduced(self, obs_pair: ObservablePair): float Unreduced FoM value. """ - return np.sum((obs_pair.calculate_difference() / - obs_pair.calculate_exp_errors()) ** 2) + return np.sum((obs_pair.calculate_difference() / obs_pair.calculate_exp_errors()) ** 2) def _minimise_factor(self, obs_pair: ObservablePair) -> float: """ @@ -65,8 +64,9 @@ def _minimise_factor(self, obs_pair: ObservablePair) -> float: exp_errors = np.array(*obs_pair.exp_obs.errors.values()) exp_values = np.array(*obs_pair.exp_obs.dependent_variables.values()) MD_values = np.array(*obs_pair.MD_obs.dependent_variables.values()) - return (np.sum((MD_values / exp_errors) ** 2) / - np.sum(MD_values * exp_values / exp_errors ** 2)) + return np.sum((MD_values / exp_errors) ** 2) / np.sum( + MD_values * exp_values / exp_errors**2, + ) def calculate_single_FoM(self, obs_pair: ObservablePair): """ diff --git a/MDMC/refinement/FoM/FoM_abs.py b/MDMC/refinement/FoM/FoM_abs.py index c510ca0b5..b36f278f6 100644 --- a/MDMC/refinement/FoM/FoM_abs.py +++ b/MDMC/refinement/FoM/FoM_abs.py @@ -17,12 +17,11 @@ """A module for Figure of Merits""" from abc import ABC, abstractmethod -from enum import Enum, auto -from typing import Union import numpy as np from MDMC.common.decorators import repr_decorator +from MDMC.refinement.FoM import AutoScale from MDMC.trajectory_analysis.observables.obs import Observable @@ -533,6 +532,6 @@ def compute_rescale_factor(self, obs_pair: ObservablePair) -> float: case AutoScale.MATCH_ABS_SUM: fac = sum(np.abs(obs.sum()) for obs in dep_vars) case _: - fac = 1. + fac = 1.0 return fac diff --git a/MDMC/refinement/FoM/RSquared_noneerror.py b/MDMC/refinement/FoM/RSquared_noneerror.py index f97d0e762..61e9d74df 100644 --- a/MDMC/refinement/FoM/RSquared_noneerror.py +++ b/MDMC/refinement/FoM/RSquared_noneerror.py @@ -79,11 +79,10 @@ def _minimise_factor(self, obs_pair: ObservablePair) -> float: float Computed auto_scale factor to minimise the FoM. """ - exp_values = np.array( - *obs_pair.exp_obs.dependent_variables.values()) + exp_values = np.array(*obs_pair.exp_obs.dependent_variables.values()) MD_values = np.array(*obs_pair.MD_obs.dependent_variables.values()) A = np.sum(MD_values * exp_values) - B = np.sum(exp_values ** 2) + B = np.sum(exp_values**2) return A / B def calculate_single_FoM(self, obs_pair: ObservablePair): diff --git a/doc/_static/files/linux/mdmc.tar.gz b/doc/_static/files/linux/mdmc.tar.gz index e19fd68f0f38a74f593a72408c93e2d21b19ca6a..0c3f207f4323561ccdbe1a5730f4c54ba633ea8e 100644 GIT binary patch literal 1029 zcmV+g1p50QiwFoaZ{BDE|7~P#V=i=IascgET~FjT6rE>&#T7+bsG6_Ie6S-SErN%( z0xeotseM9DVkU;fPHksqBK61j+6l=pyU>?H)#5YK*s-1CdwuS`nT#L!!KW*-`I(Z8 z%F(MJZ1G$hhVf+RoAD?dN8=G}MsI*xX(Dm^(fh>s7)s4bdJ@g1^VuW{KYsM1@pwEr zp6rbr{`c^M*Wbe=^?W)tZ8V>T+ijoo(PTV}WBiU{lmF2?9KrN0^Z%ZtB6(rm-_8GL z^>_8()qhw2-*5fTMEXFbmx{6yiuz4iyhi@dCX?ste;m!nJM}-F#aREtx6J={TK`v6 zDkk^>KE!_bVNX%HVkuRNJ%BPRQ_}(@H7l|O`1axlI4ug15>25}HO+*lbpu*JD!3+$ zQ~49DbC%}76iE6<%_Pm>PDr?FE=XBc$}A=+?xH%vi_^=?t7|A&B8hC^&{qO*y`MEW zbhJg6396u0j4$E(=KS;58%u!uFF?zh+N(-Pty;Sc_veaoAa>Lm@oQZ*no8h8(?p1e zZlMSJ$B2db+P7mXQPd^fVmg!5I8A`lHDu^jss&mrIOIaLPcH}RGf|=1xiR$tPu4J^e-cvUr(82qsBOdTs?Jc`Z<(;# zlz245;RXrgK|)jxUOL!IjO5$p2~xt*U!WVB)+k0<7n)Uga2=uQ3F*d4uBcYSk;3^e z*H;&(zXRnfSP{uccj#8Z?@ji6OQs<$1gBQgidDckmX4w@2Yg4Ai7A~E4)<75p3L7# zQQF%N$zUEbEscAPsBL_BUPddOpG1JMZ|;(G#Y|?lA%uF`U?Y;6;A$<&j&@Y3P57Yz zxHbCJ=|4njLQyR|s?0MniW*AmyR41THY<9*$Cv_B5Vpo2vj?*cy+l*dW!-PQ9rtD< zw4ULwN)*zr$Cj=r@5yv7>Sb;WX&@)7*0ahuE^1X6dojQ|r8ARfB}sFXv5&7lX!;Q^ z?Yn<=J$MX-PTrG)uG7yC(he%*T32cj1P2F)Sn6OafR8@m`n*nj47uR){PL`l;vP@f z_E(`lzO7gxbkB~V5cjd*KesBa!25FZ&AfR!2ZOFV3E!55etICwc31XO_fG_TnJ@U! zXH~~l+6N%eT-Y4RZP-<4vlV}l!^W$i?_~g&#;o?&~ z-J3Z4@8JipzlTZc#e8nsXfY4B+ac$p**soEVHAZ6lmF52VgmEG%>R3misXfLe>eZ1 z)!)^BSN~o8f4}uV6X^q$UMk8;DC)Om@f!JmJexgN|EJL+-l_l7;~49I_?G$qPV0X| zrDB3F;ltDqKkO+g8!l4#bXHBYv){R#ORFXqpJ| z&@c30|07~)zV_`{BZ|7DTTEw?8m9?xx`qtBO0__11&3Uyj$Gl0G47FE0pS@MOnWx; ze4s1I177FoRd*IFDd>}994+$$qs6Uid3w8MI=}65c4>!$VYB^#`^M1rTkfjxTNIgg zO=O0qj++F!tO6fry_&Pl^6BMBeI_bYJ2$32;K>?B^iM*H{FG}(3bk#xUDX?E`yCTj zn-Y&^Jlr5*JV=Pj!AnPbiIIG}JV8o0`U`YJ(;CGn>q4^%53VOv10mg5$raUVJW@FS z>H6y8^mm}Vfrdy%`a`!0ers~zTQLo3Avm>?R;&WXv2+xLIp8~@OibyVaJa{c@?`!_ ziqhVGNJjIJX=U7NMQ!85^D~zB9~^(vB+RT32cr1P2F)Sn6OafR8@o`n*nj47uR){PL`l;vP@f z_M6a;Z!1;^J+NaaOov$TpIeny;Qey*)x3E+2cxb#3E!24VR|IXc2^El_fG_TnJ@U> zXI0CSuWlZq!}BoCL*IVzf(tK^Vr|NS=b^h|?Mcx)$x5=KOZ!l<+qhqs?)}fb|GD=+ zhkrT$?L+O=|Nnd8{qGz9|3z^ey7xZ^2L}fS2L}fS2L}fShkpux0YQYto Ag#Z8m diff --git a/doc/_static/files/osx-windows/mdmc.zip b/doc/_static/files/osx-windows/mdmc.zip index 0e5955064d47f8e9a6f28eef6c0437ece77bd27a..77de51d3edf03906afe4f7ff48e09e32c1ae0817 100644 GIT binary patch delta 50 vcmey#{*#?Az?+#xgn@y9gQ2+XO~^(*duA38V{#y~I)qWfYy)BJVYUSTfjbbX delta 50 vcmey#{*#?Az?+#xgn@y9gCR4pG<+kUJu?f4F*%S~9m1$#wt+DAFxvtENr4UB