From d9fa18e04ea39ebe7de8b5fad23fcac8f9651585 Mon Sep 17 00:00:00 2001 From: James Nightingale Date: Sun, 14 Nov 2021 17:46:05 +0000 Subject: [PATCH 1/4] made Drawer class --- autofit/non_linear/optimize/drawer.py | 217 ++++++++++++++++++++++++++ autofit/plot/__init__.py | 3 +- 2 files changed, 219 insertions(+), 1 deletion(-) create mode 100644 autofit/non_linear/optimize/drawer.py diff --git a/autofit/non_linear/optimize/drawer.py b/autofit/non_linear/optimize/drawer.py new file mode 100644 index 000000000..df86a9b75 --- /dev/null +++ b/autofit/non_linear/optimize/drawer.py @@ -0,0 +1,217 @@ +from os import path +from typing import List, Optional + +import numpy as np +from sqlalchemy.orm import Session + +from autoconf import conf +from autofit import exc +from autofit.mapper.prior_model.abstract import AbstractPriorModel +from autofit.non_linear.optimize.abstract_optimize import AbstractOptimizer +from autofit.non_linear.samples import OptimizerSamples, Sample +from autofit.non_linear.abstract_search import PriorPasser +from autofit.non_linear.initializer import Initializer +from autofit.plot import DrawerPlotter +from autofit.plot.mat_wrap.wrap.wrap_base import Output + + +class Drawer(AbstractOptimizer): + + __identifier_fields__ = ( + "n_particles", + "cognitive", + "social", + "inertia", + ) + + def __init__( + self, + name: Optional[str] = None, + path_prefix: Optional[str] = None, + unique_tag: Optional[str] = None, + prior_passer: Optional[PriorPasser] = None, + initializer: Optional[Initializer] = None, + iterations_per_update: int = None, + number_of_cores: int = None, + session: Optional[Session] = None, + **kwargs + ): + """ + A Drawer Particle Swarm Optimizer global non-linear search. + + For a full description of Drawer, checkout its Github and readthedocs webpages: + + https://github.com/ljvmiranda921/drawer + https://drawer.readthedocs.io/en/latest/index.html + + Parameters + ---------- + name + The name of the search, controlling the last folder results are output. + path_prefix + The path of folders prefixing the name folder where results are output. + unique_tag + The name of a unique tag for this model-fit, which will be given a unique entry in the sqlite database + and also acts as the folder after the path prefix and before the search name. + prior_passer + Controls how priors are passed from the results of this `NonLinearSearch` to a subsequent non-linear search. + initializer + Generates the initialize samples of non-linear parameter space (see autofit.non_linear.initializer). + number_of_cores : int + The number of cores Emcee sampling is performed using a Python multiprocessing Pool instance. If 1, a + pool instance is not created and the job runs in serial. + session + An SQLalchemy session instance so the results of the model-fit are written to an SQLite database. + """ + + super().__init__( + name=name, + path_prefix=path_prefix, + unique_tag=unique_tag, + prior_passer=prior_passer, + initializer=initializer, + iterations_per_update=iterations_per_update, + session=session, + **kwargs + ) + + self.number_of_cores = ( + self._config("parallel", "number_of_cores") + if number_of_cores is None + else number_of_cores + ) + + self.logger.debug("Creating Drawer Search") + + class Fitness(AbstractOptimizer.Fitness): + + def figure_of_merit_from(self, parameter_list): + """ + The figure of merit is the value that the `NonLinearSearch` uses to sample parameter space. + + The `Drawer` search can use either the log posterior values or log likelihood values. + """ + return -2.0 * self.log_posterior_from(parameter_list=parameter_list) + + def _fit(self, model: AbstractPriorModel, analysis, log_likelihood_cap=None): + """ + Fit a model using Drawer and the Analysis class which contains the data and returns the log likelihood from + instances of the model, which the `NonLinearSearch` seeks to maximize. + + Parameters + ---------- + model : ModelMapper + The model which generates instances for different points in parameter space. + analysis : Analysis + Contains the data and the log likelihood function which fits an instance of the model to the data, returning + the log likelihood the `NonLinearSearch` maximizes. + + Returns + ------- + A result object comprising the Samples object that inclues the maximum log likelihood instance and full + chains used by the fit. + """ + + fitness_function = self.fitness_function_from_model_and_analysis( + model=model, analysis=analysis + ) + + total_draws = self.config_dict_search["total_draws"] + + self.logger.info(f"Performing DrawerSearch for a total of {total_draws} points.") + + unit_parameter_lists, parameter_lists, log_posterior_list = self.initializer.samples_from_model( + total_points=self.config_dict_search["total_draws"], + model=model, + fitness_function=fitness_function, + ) + + self.paths.save_object( + "parameter_lists", + parameter_lists + ) + self.paths.save_object( + "log_posterior_list", + log_posterior_list + ) + + self.perform_update( + model=model, analysis=analysis, during_analysis=False + ) + + self.logger.info("Drawer complete") + + def fitness_function_from_model_and_analysis(self, model, analysis, log_likelihood_cap=None): + + return Drawer.Fitness( + paths=self.paths, + model=model, + analysis=analysis, + samples_from_model=self.samples_from, + log_likelihood_cap=log_likelihood_cap, + ) + + def samples_from(self, model): + + parameter_lists = self.paths.load_object("parameter_lists") + + return DrawerSamples( + model=model, + parameter_lists=parameter_lists, + log_posterior_list=self.paths.load_object("log_posterior_list"), + time=self.timer.time + ) + + def plot_results(self, samples): + + def should_plot(name): + return conf.instance["visualize"]["plots_search"]["drawer"][name] + + plotter = DrawerPlotter( + samples=samples, + output=Output(path=path.join(self.paths.image_path, "search"), format="png") + ) + + +class DrawerSamples(OptimizerSamples): + + def __init__( + self, + model: AbstractPriorModel, + parameter_lists: List[List[float]], + log_posterior_list: List[float], + time: Optional[float] = None, + ): + """ + Create an *OptimizerSamples* object from this non-linear search's output files on the hard-disk and model. + + For Drawer, all quantities are extracted via pickled states of the particle and cost histories. + + Parameters + ---------- + model + The model which generates instances for different points in parameter space. This maps the points from unit + cube values to physical values via the priors. + """ + + self._log_posterior_list = log_posterior_list + + log_prior_list = [ + sum(model.log_prior_list_from_vector(vector=vector)) for vector in parameter_lists + ] + log_likelihood_list = [lp - prior for lp, prior in zip(self._log_posterior_list, log_prior_list)] + weight_list = len(log_likelihood_list) * [1.0] + + sample_list = Sample.from_lists( + model=model, + parameter_lists=parameter_lists, + log_likelihood_list=log_likelihood_list, + log_prior_list=log_prior_list, + weight_list=weight_list + ) + + super().__init__( + model=model, + sample_list=sample_list, + time=time, + ) diff --git a/autofit/plot/__init__.py b/autofit/plot/__init__.py index f175d58f2..6bd41f461 100644 --- a/autofit/plot/__init__.py +++ b/autofit/plot/__init__.py @@ -24,4 +24,5 @@ from autofit.plot.ultranest_plotter import UltraNestPlotter from autofit.plot.emcee_plotter import EmceePlotter from autofit.plot.zeus_plotter import ZeusPlotter -from autofit.plot.pyswarms_plotter import PySwarmsPlotter \ No newline at end of file +from autofit.plot.pyswarms_plotter import PySwarmsPlotter +from autofit.plot.pyswarms_plotter import PySwarmsPlotter as DrawerPlotter \ No newline at end of file From a2ff3f865e81e7b6a10b0ab85a06d6bd08601bc5 Mon Sep 17 00:00:00 2001 From: James Nightingale Date: Sun, 14 Nov 2021 18:46:13 +0000 Subject: [PATCH 2/4] docstring --- .../non_linear/optimize/drawer/__init__.py | 0 .../optimize/{ => drawer}/drawer.py | 85 ++++++------------- autofit/non_linear/optimize/drawer/plotter.py | 5 ++ autofit/non_linear/optimize/drawer/samples.py | 59 +++++++++++++ .../non_linear/optimize/pyswarms/plotter.py | 4 +- 5 files changed, 93 insertions(+), 60 deletions(-) create mode 100644 autofit/non_linear/optimize/drawer/__init__.py rename autofit/non_linear/optimize/{ => drawer}/drawer.py (70%) create mode 100644 autofit/non_linear/optimize/drawer/plotter.py create mode 100644 autofit/non_linear/optimize/drawer/samples.py diff --git a/autofit/non_linear/optimize/drawer/__init__.py b/autofit/non_linear/optimize/drawer/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/autofit/non_linear/optimize/drawer.py b/autofit/non_linear/optimize/drawer/drawer.py similarity index 70% rename from autofit/non_linear/optimize/drawer.py rename to autofit/non_linear/optimize/drawer/drawer.py index df86a9b75..21a98e47d 100644 --- a/autofit/non_linear/optimize/drawer.py +++ b/autofit/non_linear/optimize/drawer/drawer.py @@ -1,27 +1,22 @@ from os import path -from typing import List, Optional +from typing import Optional -import numpy as np from sqlalchemy.orm import Session from autoconf import conf -from autofit import exc from autofit.mapper.prior_model.abstract import AbstractPriorModel from autofit.non_linear.optimize.abstract_optimize import AbstractOptimizer -from autofit.non_linear.samples import OptimizerSamples, Sample from autofit.non_linear.abstract_search import PriorPasser from autofit.non_linear.initializer import Initializer -from autofit.plot import DrawerPlotter -from autofit.plot.mat_wrap.wrap.wrap_base import Output +from autofit.non_linear.optimize.drawer.samples import DrawerSamples +from autofit.non_linear.optimize.drawer.plotter import DrawerPlotter +from autofit.plot.output import Output class Drawer(AbstractOptimizer): __identifier_fields__ = ( - "n_particles", - "cognitive", - "social", - "inertia", + "total_draws", ) def __init__( @@ -37,12 +32,29 @@ def __init__( **kwargs ): """ - A Drawer Particle Swarm Optimizer global non-linear search. + A Drawer non-linear search, which simply draws a fixed number of samples from the model uniformly from the + priors. - For a full description of Drawer, checkout its Github and readthedocs webpages: + Therefore, it does not seek to determine model parameters which maximize the likelihood or map out the + posterior of the overall parameter space. - https://github.com/ljvmiranda921/drawer - https://drawer.readthedocs.io/en/latest/index.html + Whilst this is not the typical use case of a non-linear search, it has certain niche applications, for example: + + - Given a model one can determine how much variation there is in the log likelihood / log posterior values. + By visualizing this as a histogram one can therefore quantify the behaviour of that + model's `log_likelihood_function`. + + - If the `log_likelihood_function` of a model is stochastic (e.g. different values of likelihood may be + computed for an identical model due to randomness in the likelihood evaluation) this search can quantify + the behaviour of that stochasticity. + + - For advanced modeling tools, for example sensitivity mapping performed via the `Sensitivity` object, + the `Drawer` search may be sufficient to perform the overall modeling task, without the need of performing + an actual parameter space search. + + The drawer search itself is performed by simply reusing the functionality of the `Initializer` object. + Whereas this is normally used to initialize a non-linear search, for the drawer it performed all log + likelihood evluations. Parameters ---------- @@ -91,7 +103,7 @@ def figure_of_merit_from(self, parameter_list): The `Drawer` search can use either the log posterior values or log likelihood values. """ - return -2.0 * self.log_posterior_from(parameter_list=parameter_list) + return self.log_posterior_from(parameter_list=parameter_list) def _fit(self, model: AbstractPriorModel, analysis, log_likelihood_cap=None): """ @@ -172,46 +184,3 @@ def should_plot(name): output=Output(path=path.join(self.paths.image_path, "search"), format="png") ) - -class DrawerSamples(OptimizerSamples): - - def __init__( - self, - model: AbstractPriorModel, - parameter_lists: List[List[float]], - log_posterior_list: List[float], - time: Optional[float] = None, - ): - """ - Create an *OptimizerSamples* object from this non-linear search's output files on the hard-disk and model. - - For Drawer, all quantities are extracted via pickled states of the particle and cost histories. - - Parameters - ---------- - model - The model which generates instances for different points in parameter space. This maps the points from unit - cube values to physical values via the priors. - """ - - self._log_posterior_list = log_posterior_list - - log_prior_list = [ - sum(model.log_prior_list_from_vector(vector=vector)) for vector in parameter_lists - ] - log_likelihood_list = [lp - prior for lp, prior in zip(self._log_posterior_list, log_prior_list)] - weight_list = len(log_likelihood_list) * [1.0] - - sample_list = Sample.from_lists( - model=model, - parameter_lists=parameter_lists, - log_likelihood_list=log_likelihood_list, - log_prior_list=log_prior_list, - weight_list=weight_list - ) - - super().__init__( - model=model, - sample_list=sample_list, - time=time, - ) diff --git a/autofit/non_linear/optimize/drawer/plotter.py b/autofit/non_linear/optimize/drawer/plotter.py new file mode 100644 index 000000000..0e0730943 --- /dev/null +++ b/autofit/non_linear/optimize/drawer/plotter.py @@ -0,0 +1,5 @@ +from autofit.plot.samples_plotters import MCMCPlotter + +class DrawerPlotter(MCMCPlotter): + + pass \ No newline at end of file diff --git a/autofit/non_linear/optimize/drawer/samples.py b/autofit/non_linear/optimize/drawer/samples.py new file mode 100644 index 000000000..504f2c0b0 --- /dev/null +++ b/autofit/non_linear/optimize/drawer/samples.py @@ -0,0 +1,59 @@ +from os import path +from typing import List, Optional + +import numpy as np +from sqlalchemy.orm import Session + +from autoconf import conf +from autofit import exc +from autofit.mapper.prior_model.abstract import AbstractPriorModel +from autofit.non_linear.optimize.abstract_optimize import AbstractOptimizer +from autofit.non_linear.samples import OptimizerSamples, Sample +from autofit.non_linear.abstract_search import PriorPasser +from autofit.non_linear.initializer import Initializer +from autofit.plot import DrawerPlotter +from autofit.plot.mat_wrap.wrap.wrap_base import Output + + +class DrawerSamples(OptimizerSamples): + + def __init__( + self, + model: AbstractPriorModel, + parameter_lists: List[List[float]], + log_posterior_list: List[float], + time: Optional[float] = None, + ): + """ + Create an *OptimizerSamples* object from this non-linear search's output files on the hard-disk and model. + + For Drawer, all quantities are extracted via pickled states of the particle and cost histories. + + Parameters + ---------- + model + The model which generates instances for different points in parameter space. This maps the points from unit + cube values to physical values via the priors. + """ + + self._log_posterior_list = log_posterior_list + + log_prior_list = [ + sum(model.log_prior_list_from_vector(vector=vector)) for vector in parameter_lists + ] + log_likelihood_list = [lp - prior for lp, prior in zip(self._log_posterior_list, log_prior_list)] + weight_list = len(log_likelihood_list) * [1.0] + + sample_list = Sample.from_lists( + model=model, + parameter_lists=parameter_lists, + log_likelihood_list=log_likelihood_list, + log_prior_list=log_prior_list, + weight_list=weight_list + ) + + super().__init__( + model=model, + sample_list=sample_list, + time=time, + ) diff --git a/autofit/non_linear/optimize/pyswarms/plotter.py b/autofit/non_linear/optimize/pyswarms/plotter.py index 6013a32d8..a25583114 100644 --- a/autofit/non_linear/optimize/pyswarms/plotter.py +++ b/autofit/non_linear/optimize/pyswarms/plotter.py @@ -1,9 +1,9 @@ -from autofit.plot.samples_plotters import MCMCPlotter from pyswarms.utils import plotters - import matplotlib.pyplot as plt import numpy as np +from autofit.plot.samples_plotters import MCMCPlotter + class PySwarmsPlotter(MCMCPlotter): def contour(self, **kwargs): From ab199d96612b5ece67defe5c115a4cf38e63d783 Mon Sep 17 00:00:00 2001 From: James Nightingale Date: Sun, 14 Nov 2021 18:48:39 +0000 Subject: [PATCH 3/4] config --- autofit/config/non_linear/optimize/Drawer.ini | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 autofit/config/non_linear/optimize/Drawer.ini diff --git a/autofit/config/non_linear/optimize/Drawer.ini b/autofit/config/non_linear/optimize/Drawer.ini new file mode 100644 index 000000000..27c7cbdc9 --- /dev/null +++ b/autofit/config/non_linear/optimize/Drawer.ini @@ -0,0 +1,22 @@ +[search] +total_draws=50 + +[initialize] +method=ball +ball_lower_limit=0.49 +ball_upper_limit=0.51 + +[updates] +iterations_per_update=500 +visualize_every_update=1 +model_results_every_update=1 +log_every_update=1 +remove_state_files_at_end=True + +[printing] +silence=False + +[prior_passer] +sigma=3.0 +use_errors=True +use_widths=True \ No newline at end of file From 6952298a7aba701c561b754174aabc469e5478ef Mon Sep 17 00:00:00 2001 From: James Nightingale Date: Sun, 14 Nov 2021 19:02:10 +0000 Subject: [PATCH 4/4] unit tests on Drawer --- autofit/__init__.py | 1 + autofit/non_linear/optimize/drawer/drawer.py | 10 +-- autofit/non_linear/optimize/drawer/samples.py | 11 --- .../config/non_linear/optimize/Drawer.ini | 22 ++++++ .../non_linear/optimize/test_drawer.py | 64 ++++++++++++++++++ .../tag/pickles/log_posterior_list.pickle | Bin 0 -> 219 bytes .../drawer/tag/pickles/parameter_lists.pickle | Bin 0 -> 345 bytes 7 files changed, 88 insertions(+), 20 deletions(-) create mode 100644 test_autofit/config/non_linear/optimize/Drawer.ini create mode 100644 test_autofit/non_linear/optimize/test_drawer.py create mode 100644 test_autofit/output/non_linear/drawer/tag/pickles/log_posterior_list.pickle create mode 100644 test_autofit/output/non_linear/drawer/tag/pickles/parameter_lists.pickle diff --git a/autofit/__init__.py b/autofit/__init__.py index 54f982b47..1cd4ab323 100644 --- a/autofit/__init__.py +++ b/autofit/__init__.py @@ -63,6 +63,7 @@ from .non_linear.nest.dynesty import DynestyStatic from .non_linear.nest.multinest.multinest import MultiNest from .non_linear.nest.ultranest.ultranest import UltraNest +from .non_linear.optimize.drawer.drawer import Drawer from .non_linear.optimize.lbfgs.lbfgs import LBFGS from .non_linear.optimize.pyswarms.globe import PySwarmsGlobal from .non_linear.optimize.pyswarms.local import PySwarmsLocal diff --git a/autofit/non_linear/optimize/drawer/drawer.py b/autofit/non_linear/optimize/drawer/drawer.py index 21a98e47d..30388660b 100644 --- a/autofit/non_linear/optimize/drawer/drawer.py +++ b/autofit/non_linear/optimize/drawer/drawer.py @@ -27,7 +27,6 @@ def __init__( prior_passer: Optional[PriorPasser] = None, initializer: Optional[Initializer] = None, iterations_per_update: int = None, - number_of_cores: int = None, session: Optional[Session] = None, **kwargs ): @@ -69,9 +68,6 @@ def __init__( Controls how priors are passed from the results of this `NonLinearSearch` to a subsequent non-linear search. initializer Generates the initialize samples of non-linear parameter space (see autofit.non_linear.initializer). - number_of_cores : int - The number of cores Emcee sampling is performed using a Python multiprocessing Pool instance. If 1, a - pool instance is not created and the job runs in serial. session An SQLalchemy session instance so the results of the model-fit are written to an SQLite database. """ @@ -87,11 +83,7 @@ def __init__( **kwargs ) - self.number_of_cores = ( - self._config("parallel", "number_of_cores") - if number_of_cores is None - else number_of_cores - ) + self.number_of_cores = 1 self.logger.debug("Creating Drawer Search") diff --git a/autofit/non_linear/optimize/drawer/samples.py b/autofit/non_linear/optimize/drawer/samples.py index 504f2c0b0..056b5d802 100644 --- a/autofit/non_linear/optimize/drawer/samples.py +++ b/autofit/non_linear/optimize/drawer/samples.py @@ -1,18 +1,7 @@ -from os import path from typing import List, Optional -import numpy as np -from sqlalchemy.orm import Session - -from autoconf import conf -from autofit import exc from autofit.mapper.prior_model.abstract import AbstractPriorModel -from autofit.non_linear.optimize.abstract_optimize import AbstractOptimizer from autofit.non_linear.samples import OptimizerSamples, Sample -from autofit.non_linear.abstract_search import PriorPasser -from autofit.non_linear.initializer import Initializer -from autofit.plot import DrawerPlotter -from autofit.plot.mat_wrap.wrap.wrap_base import Output class DrawerSamples(OptimizerSamples): diff --git a/test_autofit/config/non_linear/optimize/Drawer.ini b/test_autofit/config/non_linear/optimize/Drawer.ini new file mode 100644 index 000000000..c43cb4f3e --- /dev/null +++ b/test_autofit/config/non_linear/optimize/Drawer.ini @@ -0,0 +1,22 @@ +[search] +total_draws=10 + +[initialize] +method=prior +ball_lower_limit=0.49 +ball_upper_limit=0.51 + +[updates] +iterations_per_update=500 +visualize_every_update=1 +model_results_every_update=1 +log_every_update=1 +remove_state_files_at_end=True + +[printing] +silence=False + +[prior_passer] +sigma=3.0 +use_errors=True +use_widths=True \ No newline at end of file diff --git a/test_autofit/non_linear/optimize/test_drawer.py b/test_autofit/non_linear/optimize/test_drawer.py new file mode 100644 index 000000000..3e4452e6a --- /dev/null +++ b/test_autofit/non_linear/optimize/test_drawer.py @@ -0,0 +1,64 @@ +from os import path + +import pytest + +import autofit as af +from autofit.mock import mock + +pytestmark = pytest.mark.filterwarnings("ignore::FutureWarning") + + +class TestDrawerConfig: + def test__loads_from_config_file_correct(self): + drawer = af.Drawer( + total_draws=5, + prior_passer=af.PriorPasser(sigma=2.0, use_errors=False, use_widths=False), + initializer=af.InitializerBall(lower_limit=0.2, upper_limit=0.8), + ) + + assert drawer.prior_passer.sigma == 2.0 + assert drawer.prior_passer.use_errors is False + assert drawer.prior_passer.use_widths is False + assert drawer.config_dict_search["total_draws"] == 5 + assert isinstance(drawer.initializer, af.InitializerBall) + assert drawer.initializer.lower_limit == 0.2 + assert drawer.initializer.upper_limit == 0.8 + assert drawer.number_of_cores == 1 + + drawer = af.Drawer() + + assert drawer.prior_passer.sigma == 3.0 + assert drawer.prior_passer.use_errors is True + assert drawer.prior_passer.use_widths is True + assert drawer.config_dict_search["total_draws"] == 10 + assert isinstance(drawer.initializer, af.InitializerPrior) + + def test__samples_from_model(self): + + drawer = af.Drawer() + drawer.paths = af.DirectoryPaths(path_prefix=path.join("non_linear", "drawer")) + drawer.paths._identifier = "tag" + + model = af.ModelMapper(mock_class=mock.MockClassx3) + model.mock_class.one = af.LogUniformPrior(lower_limit=1e-8, upper_limit=100.0) + model.mock_class.two = af.LogUniformPrior(lower_limit=1e-8, upper_limit=100.0) + model.mock_class.three = af.LogUniformPrior(lower_limit=1e-8, upper_limit=100.0) + + samples = drawer.samples_from(model=model) + + assert isinstance(samples.parameter_lists, list) + assert isinstance(samples.parameter_lists[0], list) + assert isinstance(samples.log_likelihood_list, list) + assert isinstance(samples.log_prior_list, list) + assert isinstance(samples.log_posterior_list, list) + + assert samples.parameter_lists[0] == pytest.approx( + [49.507679, 49.177471, 14.76753], 1.0e-4 + ) + + assert samples.log_likelihood_list[0] == pytest.approx(-2763.925766, 1.0e-4) + assert samples.log_posterior_list[0] == pytest.approx(-2763.817517, 1.0e-4) + assert samples.weight_list[0] == 1.0 + + assert len(samples.parameter_lists) == 3 + assert len(samples.log_likelihood_list) == 3 diff --git a/test_autofit/output/non_linear/drawer/tag/pickles/log_posterior_list.pickle b/test_autofit/output/non_linear/drawer/tag/pickles/log_posterior_list.pickle new file mode 100644 index 0000000000000000000000000000000000000000..d781aec4c4fd16cb276fe9da07e0630d9e74f2f0 GIT binary patch literal 219 zcmZo*nRLuqFrRv4!mgbaXCKeSXR>qg+CYEGOX`K=@rH8FJIWZ@(Xi6K1 z!wOapq*F>N3&18YrCChr>}Ur{YIrmEFxpJ<^Yioi4+LPso1tV%k~4?tt?3gNO<#Hd fY)A%ohM+TtbaP6+(EjDfLP3?;uPqNig;MnZd_P=` literal 0 HcmV?d00001 diff --git a/test_autofit/output/non_linear/drawer/tag/pickles/parameter_lists.pickle b/test_autofit/output/non_linear/drawer/tag/pickles/parameter_lists.pickle new file mode 100644 index 0000000000000000000000000000000000000000..92026a98ec658aa896d24bbe8d7abdf0e94112a3 GIT binary patch literal 345 zcmZo*nd-;L00yyBG=QjwDPAX8aM<%Z{x1drlIlm}XFFv<4rzA76s3@^AzBD(nBx6eJl%Od+Y{kimIf+G6 z+CUsuuzDb!Qc_s}Hi0S4VoGO6J5W-?o4JS4W{RJmpVxmN02AH}B~y}|IkKl+S@QdU zhXdG<44w=jXO0z)O3B-&p$KU)uG;joLl>ed73dv^%A-cQ6YkwbQOQ2_HP6FnPh|7h oTBJ-?Ox8o!JmtU{X-?Gx0M(?3kN^Mx literal 0 HcmV?d00001