Run experiments
Sweep parameters, repeat runs, and collect their results in one table.
Experiments
Run a system many times, over a design of parameters, under scenarios, and with replicates, and collect the results in
one table. Complex systems are studied through many runs, and Experiment organises them.
from skcomplex.core.components.specs import ScenarioSpec
from skcomplex.core.experiments import Experiment, GridDesign
from skcomplex.systems import describe
bounds = {'stochastic': [True], 'gamma': [0.1, 0.2]}
sir = describe('sir').factory
ensemble = Experiment(sir, GridDesign(), bounds, replicates=10, until=60, random_state=0).prepare().run()
round(float(ensemble.result_.summary()[0]['I.n.mean']), 1)
policies = [ScenarioSpec('baseline'), ScenarioSpec('quota', schedule=[(50.0, 'harvest.effort', 0.025)])]
harvesting = describe('harvesting').factory
stocks = Experiment(harvesting, scenarios=policies, until=80, outputs=['land/stock.n']).prepare().run()
[round(float(row['land/stock.n'])) for row in stocks.result_.rows]
46.7
[500, 750]
Experiment runs a system, or a factory of systems, over three things:
- a design of factory arguments and dotted paths, such as
gammaabove, - scenarios declared as
ScenarioSpec, such as a harvest quota that starts at time 50, - replicates.
Every design row and replicate gets its own random stream, spawned from random_state in a fixed order. So results are
the same serially, on threads, or on processes. Every scenario of a row and replicate shares its stream. Overrides of
parameters and initial states reuse one compiled problem, so sweeps of thousands of runs compile once.
An experiment speaks the two verbs of every object. prepare validates its settings and resolves the system and the
runs. run executes them, and keeps the results in result_.
The parts of an experiment are these:
- Designs. They are
GridDesign,RandomDesign,LatinHypercubeDesign,SobolDesign,RowsDesign, and the designs of sensitivity analysisOneAtATimeDesign,MorrisDesign, andSaltelliDesign. A design stores its settings, such asSobolDesign(n=64), and prepares on theboundsof the experiment. The bounds are a pair or a SciPy distribution per name for a sampled design, a sequence of values per name for a grid, and nothing for a rows design.preparesets the rows of a design inrows_. - Outputs.
outputskeeps states and observables as series, afterwarmup, and functions of a trajectory as scalars. - Analyses.
analysesare unprepared analyses. Every run clones each one, prepares it on its trajectory, runs it, and keeps what itsrunsets as columns, such asentropyanalysis.entropy. - Results. They give one row per run,
series,ensemblewith one row per run on common times and the seeds of the runs,summarywith means, standard deviations, and quantiles across replicates, andto_framefor pandas.
Calibration and sensitivity
Fit the parameters of a system to observed data, and rank which inputs matter most.
import numpy as np
from skcomplex.core.experiments import CalibrationExperiment, Experiment, OneAtATimeDesign, SensitivityAnalysis
from skcomplex.systems import describe
logistic_growth = describe('logistic_growth').factory
times = np.arange(0.0, 21.0)
observed = 500 / (1 + 49 * np.exp(-0.8 * times))
bounds = {'growth.r': (0.1, 2.0), 'growth.K': (100, 1000)}
fit = CalibrationExperiment(logistic_growth, {'population.n': (times, observed)}, bounds).prepare().run()
round(fit.estimates_['growth.r'], 2), round(fit.r2_, 4)
sweep = Experiment(logistic_growth, OneAtATimeDesign(), bounds, until=20, outputs=['population.n'])
sweep = sweep.prepare().run()
SensitivityAnalysis('oat', 'population.n').prepare(sweep.result_).run().ranking_
(0.8, 1.0)
['growth.K', 'growth.r']
CalibrationExperimentfits dotted paths to observed series, by least squares or a Gaussian likelihood. It keeps the estimates and their covariance, with R², RMSE, MAE, and MAPE. Above, it recovers the growth rate 0.8 of the data.SensitivityAnalysisprepares on the result of an experiment and ranks its inputs one at a time, by Morris screening, or by Sobol indices with SALib.