Skip to content

Experiments

Study models through many runs: designs, scenarios, replicates, sensitivity analysis, and calibration.

BaseDesign

Bases: BaseConfig[Bounds, None], ABC

Base of designs, which give rows of values by factory argument or dotted path.

A design stores its settings, such as the number of rows, in __init__. Preparing it on a mapping from name to what it samples sets its rows in rows_, which running it keeps:

  • a sampled design reads a pair (low, high) or a SciPy distribution per name;
  • a grid design reads a sequence of values per name;
  • a rows design reads nothing, because its rows are its setting.

A child implements _prepare(bounds), which sets rows_.

Attributes:

Name Type Description
rows_ list[Row]

One mapping from factory argument or dotted path to value per row, set by prepare.

Examples:

>>> from skcomplex.experiments import BaseDesign
>>> class MidpointDesign(BaseDesign):
...     def _prepare(self, bounds=None):
...         self.rows_ = [{name: sum(bound) / 2 for name, bound in (bounds or {}).items()}]
>>> MidpointDesign().check().check().prepare({'r': (0.0, 1.0)}).run().rows_
[{'r': 0.5}]

BaseExperiment

Bases: BaseConfig[None, None], ABC

Base of experiments, which run a model, given by a factory of systems or a system, many times.

An experiment stores its source and settings in __init__, validates them in check, resolves the source and what it runs in prepare, and runs in run, which keeps its results in the attributes that end in _. A child implements _prepare and _run, and reads the model with the private methods of the base. A model of the library is passed as its factory, or as a system that skcomplex.models.create returns, since experiments do not import the model library.

Examples:

>>> from skcomplex.experiments import BaseExperiment
>>> from skcomplex.models.biology.ecology import logistic_growth
>>> class SystemExperiment(BaseExperiment):
...     def __init__(self, source):
...         self.source = source
...
...     def _check(self):
...         pass
...
...     def _prepare(self, context):
...         self.factory_ = self._check_source(self.source)
...
...     def _run(self, inputs):
...         self.system_ = self.factory_()
>>> len(SystemExperiment(logistic_growth).check().prepare().run().system_.elements)
1

BasinStabilityExperiment(source, bounds, classifier, *, n=500, until=None, dt=None, random_state=None, executor='serial', vectorized=False)

Bases: BaseExperiment

Fractions of random initial states, drawn uniformly within bounds, that end in each attractor.

Every initial state is run to until and its trajectory is labelled by the classifier. With vectorized, all initial states run as the members of one simulation of the replicated system, and the classifier receives every member as a trajectory of the original system.

Parameters:

Name Type Description Default
source Source

A factory of systems, or a system, such as one that skcomplex.models.create returns.

required
bounds Mapping[str, tuple[float, float]]

A mapping from dotted state path to the (low, high) bounds of its initial value.

required
classifier Callable[[Trajectory], object]

A function of a trajectory that returns the label of the attractor it ends in.

required
n int

The number of initial states.

500
until float | None

The simulated time of every run, or None for the default of the simulation.

None
dt float | None

The time between frames, or None for the default of the simulation.

None
random_state int | None

The seed of the initial states and the runs.

None
executor str | Executor

'serial', 'thread', 'process', or a concurrent.futures executor of the separate runs.

'serial'
vectorized bool

Whether to run every initial state as a member of one simulation of the replicated system.

False

Attributes:

Name Type Description
rows_

The initial states, one mapping from dotted path to value per run, set by prepare.

labels_

The attractor label of every initial state.

fractions_

The fraction of initial states per attractor label.

errors_

The standard error of every fraction.

Examples:

>>> from skcomplex.experiments import BasinStabilityExperiment
>>> from skcomplex.models.biology.ecology import logistic_growth
>>> experiment = BasinStabilityExperiment(
...     logistic_growth, {'population.n': (0.1, 10)}, lambda trajectory: 'capacity', n=5, until=5
... )
>>> experiment.check().prepare().run().fractions_
{'capacity': 1.0}
Source code in src/skcomplex/experiments/_basin_stability.py
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
def __init__(
    self,
    source: Source,
    bounds: Mapping[str, tuple[float, float]],
    classifier: Callable[[Trajectory], object],
    *,
    n: int = 500,
    until: float | None = None,
    dt: float | None = None,
    random_state: int | None = None,
    executor: str | Executor = 'serial',
    vectorized: bool = False,
) -> None:
    self.source = source
    self.bounds = bounds
    self.classifier = classifier
    self.n = n
    self.until = until
    self.dt = dt
    self.random_state = random_state
    self.executor = executor
    self.vectorized = vectorized

CalibrationExperiment(source, data, bounds, *, initial=None, loss='squares', replicates=1, dt=None, random_state=None, arguments=None)

Bases: BaseExperiment

Fit of parameters, given by dotted path, so that a model reproduces observed time series, with SciPy.

Every evaluation runs the model to the last data time and interpolates its states or observables at the data times. Stochastic models average replicates runs with fixed streams, so the loss is smooth in the parameters. Least squares use scipy.optimize.least_squares, and the Gaussian likelihood with an unknown variance uses scipy.optimize.minimize.

Parameters:

Name Type Description Default
source Source

A factory of systems, or a system, such as one that skcomplex.models.create returns.

required
data Mapping[str, tuple[ArrayLike, ArrayLike]]

A mapping from state or observable path to its observed (times, values).

required
bounds Mapping[str, tuple[float, float]]

A mapping from dotted parameter or initial-state path to its (low, high) bounds.

required
initial Mapping[str, float] | None

The starting values of the parameters, or None for the midpoints of their bounds.

None
loss str

'squares' for least squares or 'likelihood' for the Gaussian likelihood with an unknown variance.

'squares'
replicates int

The number of runs averaged per evaluation.

1
dt float | None

The time between frames, or None for the default of the simulation.

None
random_state int | None

The seed of the runs.

None
arguments Mapping[str, object] | None

The factory arguments of the model, or None.

None

Attributes:

Name Type Description
estimates_

The fitted value of every parameter.

covariance_

The covariance of the estimates from the Jacobian of the residuals, or None when it is singular.

r2_

The coefficient of determination over all data points.

rmse_

The root mean squared error.

mae_

The mean absolute error.

mape_

The mean absolute percentage error, over data points that are not zero.

simulated_

The simulated values at the data times, by path.

success_

Whether the optimiser converged.

Examples:

>>> import numpy as np
>>> from skcomplex.experiments import CalibrationExperiment
>>> from skcomplex.models.biology.ecology import logistic_growth
>>> times = np.arange(0, 21.0)
>>> values = 100 / (1 + 9 * np.exp(-0.5 * times))
>>> bounds = {'growth.r': (0.1, 2.0), 'growth.K': (50, 200)}
>>> calibration = CalibrationExperiment(logistic_growth, {'population.n': (times, values)}, bounds)
>>> estimates = calibration.check().prepare().run().estimates_
>>> round(estimates['growth.r'], 2), round(estimates['growth.K'])
(0.5, 100)
Source code in src/skcomplex/experiments/_calibration.py
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
def __init__(
    self,
    source: Source,
    data: Mapping[str, tuple[npt.ArrayLike, npt.ArrayLike]],
    bounds: Mapping[str, tuple[float, float]],
    *,
    initial: Mapping[str, float] | None = None,
    loss: str = 'squares',
    replicates: int = 1,
    dt: float | None = None,
    random_state: int | None = None,
    arguments: Mapping[str, object] | None = None,
) -> None:
    self.source = source
    self.data = data
    self.bounds = bounds
    self.initial = initial
    self.loss = loss
    self.replicates = replicates
    self.dt = dt
    self.random_state = random_state
    self.arguments = arguments

Experiment(source, design=None, bounds=None, scenarios=None, replicates=1, *, analyses=None, until=None, dt=None, solver=None, outputs=None, warmup=0.0, random_state=None, executor='serial', observables=None)

Bases: BaseExperiment

Runs of a model over a design, scenarios, and replicates, with one random stream per design row and replicate.

The streams are spawned from random_state in a fixed order, so the results do not depend on the executor, and every scenario of a design row and replicate shares its stream.

Every run clones every analysis, prepares the clone on the run's trajectory, runs it, and keeps the attributes that its run sets as scalar outputs named <prefix>.<attribute>, without the trailing underscore. The prefix is the lowercase class name of the analysis, such as entropyanalysis, numbered -1, -2, ... when several analyses share a class, as scikit-learn names the steps of make_pipeline.

Parameters:

Name Type Description Default
source Source

A factory of systems, or a system, such as one that skcomplex.models.create returns.

required
design BaseDesign | None

The design of factory arguments and dotted paths, or None for one run of the defaults.

None
bounds Bounds | None

What the design prepares on: a pair (low, high) or a SciPy distribution per name for a sampled design, a sequence of values per name for a grid design, or None.

None
scenarios Sequence[ScenarioSpec] | None

The scenarios to compare, or None for the baseline alone.

None
replicates int

The number of runs per design row and scenario.

1
analyses Sequence[BaseEstimator] | None

The unprepared analyses of every run, or None.

None
until float | None

The simulated time of every run, or None for 10.

None
dt float | None

The time between frames, or None for the default of the simulation.

None
solver BaseSolver | None

The solver, or None for the default of the simulation.

None
outputs Sequence[Output] | None

The state and observable paths to keep as series, and functions of a trajectory that return a scalar or a mapping of scalars. None keeps every observable, or every state when the model declares none.

None
warmup float

The simulated time before which kept series are discarded.

0.0
random_state int | None

The seed of the random streams.

None
executor str | Executor

'serial', 'thread', 'process', or a concurrent.futures executor.

'serial'
observables Sequence[ObservableSpec] | None

The observables of every run, or None for none.

None

Attributes:

Name Type Description
tasks_

The runs of the experiment in their fixed order, set by prepare.

result_

The results of every run, set by run.

Examples:

>>> from skcomplex.experiments import Experiment, GridDesign
>>> from skcomplex.models.biology.ecology import logistic_growth
>>> bounds = {'r': [0.5, 1.0]}
>>> experiment = Experiment(logistic_growth, GridDesign(), bounds, until=5, outputs=['population.n'])
>>> len(experiment.check().prepare().run().result_.rows)
2
Source code in src/skcomplex/experiments/_experiment.py
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
def __init__(
    self,
    source: Source,
    design: BaseDesign | None = None,
    bounds: Bounds | None = None,
    scenarios: Sequence[ScenarioSpec] | None = None,
    replicates: int = 1,
    *,
    analyses: Sequence[BaseEstimator] | None = None,
    until: float | None = None,
    dt: float | None = None,
    solver: BaseSolver | None = None,
    outputs: Sequence[Output] | None = None,
    warmup: float = 0.0,
    random_state: int | None = None,
    executor: str | Executor = 'serial',
    observables: Sequence[ObservableSpec] | None = None,
) -> None:
    self.source = source
    self.design = design
    self.bounds = bounds
    self.scenarios = scenarios
    self.replicates = replicates
    self.analyses = analyses
    self.until = until
    self.dt = dt
    self.solver = solver
    self.outputs = outputs
    self.warmup = warmup
    self.random_state = random_state
    self.executor = executor
    self.observables = observables

ExperimentResult(runs)

Results of an experiment: one row per run, the kept series, ensembles, and summaries across replicates.

Parameters:

Name Type Description Default
runs Sequence[_RunResultSpec]

The results of every run, in the experiment's order.

required

Examples:

>>> from skcomplex.experiments import Experiment, GridDesign
>>> from skcomplex.models.biology.epidemiology import sir
>>> experiment = Experiment(sir, GridDesign(), {'gamma': [0.1, 0.2]}, until=10).check().prepare().run()
>>> [row['gamma'] for row in experiment.result_.rows]
[0.1, 0.2]
Source code in src/skcomplex/experiments/_results.py
31
32
def __init__(self, runs: Sequence[_RunResultSpec]) -> None:
    self.runs = list(runs)

rows property

One row per run with its design values, scenario, replicate, seed, and scalar outputs.

column(name)

Return one column of the rows.

Parameters:

Name Type Description Default
name str

The name of a design value, scenario, replicate, seed, or a scalar output.

required

Returns:

Name Type Description
values NDArray[generic]

One value per run.

Source code in src/skcomplex/experiments/_results.py
42
43
44
45
46
47
48
49
50
51
52
53
def column(self, name: str) -> npt.NDArray[np.generic]:
    """Return one column of the rows.

    Args:
        name:
            The name of a design value, `scenario`, `replicate`, `seed`, or a scalar output.

    Returns:
        values:
            One value per run.
    """
    return np.asarray([row[name] for row in self.rows])

ensemble(name)

Return a kept series of every run interpolated on the times of the shortest run, with the seeds of the runs.

Parameters:

Name Type Description Default
name str

The path of the kept series.

required

Returns:

Name Type Description
times NDArray[float64]

The common times.

values NDArray[float64]

One row per run and one column per common time.

seeds NDArray[int64]

The seed of the run of every row.

Examples:

>>> from skcomplex.experiments import Experiment
>>> from skcomplex.models.biology.epidemiology import sir
>>> result = Experiment(sir, replicates=3, until=10, random_state=0).check().prepare().run().result_
>>> times, values, seeds = result.ensemble('I.n')
>>> values.shape == (3, times.size), seeds.tolist()
(True, [0, 1, 2])
Source code in src/skcomplex/experiments/_results.py
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
def ensemble(
    self,
    name: str,
) -> tuple[npt.NDArray[np.float64], npt.NDArray[np.float64], npt.NDArray[np.int64]]:
    """Return a kept series of every run interpolated on the times of the shortest run, with the seeds of the runs.

    Args:
        name:
            The path of the kept series.

    Returns:
        times:
            The common times.

        values:
            One row per run and one column per common time.

        seeds:
            The seed of the run of every row.

    Examples:
        >>> from skcomplex.experiments import Experiment
        >>> from skcomplex.models.biology.epidemiology import sir
        >>> result = Experiment(sir, replicates=3, until=10, random_state=0).check().prepare().run().result_
        >>> times, values, seeds = result.ensemble('I.n')
        >>> values.shape == (3, times.size), seeds.tolist()
        (True, [0, 1, 2])
    """
    times = min((np.asarray(run.times, dtype=float) for run in self.runs), key=len)
    values = np.asarray(
        [
            np.interp(times, np.asarray(run.times, dtype=float), np.asarray(run.series[name], dtype=float))
            for run in self.runs
        ],
    )
    seeds = np.asarray([run.seed for run in self.runs], dtype=np.int64)
    return times, values, seeds

series(name)

Return a kept series of every run, stacked when all runs have the same number of frames.

Parameters:

Name Type Description Default
name str

The path of the kept series.

required

Returns:

Name Type Description
series NDArray[generic]

One row per run, or an object array of per-run series of different lengths.

Source code in src/skcomplex/experiments/_results.py
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
def series(self, name: str) -> npt.NDArray[np.generic]:
    """Return a kept series of every run, stacked when all runs have the same number of frames.

    Args:
        name:
            The path of the kept series.

    Returns:
        series:
            One row per run, or an object array of per-run series of different lengths.
    """
    values = [run.series[name] for run in self.runs]
    if len({value.shape for value in values}) == 1:
        return np.stack(values)
    array = np.empty(len(values), dtype=object)
    array[:] = values
    return array

summary(quantiles=(0.05, 0.5, 0.95))

Return the mean, standard deviation, and quantiles of every numeric scalar output per row and scenario.

Parameters:

Name Type Description Default
quantiles Sequence[float]

The quantiles to report.

(0.05, 0.5, 0.95)

Returns:

Name Type Description
summary list[dict[str, object]]

One row per design row and scenario, with <output>.mean, <output>.std, and <output>.q<quantile> columns and the number of replicates as runs.

Source code in src/skcomplex/experiments/_results.py
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
def summary(self, quantiles: Sequence[float] = (0.05, 0.5, 0.95)) -> list[dict[str, object]]:
    """Return the mean, standard deviation, and quantiles of every numeric scalar output per row and scenario.

    Args:
        quantiles:
            The quantiles to report.

    Returns:
        summary:
            One row per design row and scenario, with `<output>.mean`, `<output>.std`, and `<output>.q<quantile>`
            columns and the number of replicates as `runs`.
    """
    groups: dict[tuple[tuple[tuple[str, object], ...], str], list[_RunResultSpec]] = defaultdict(list)
    for run in self.runs:
        groups[(tuple(run.row.items()), run.scenario)].append(run)
    summary = []
    for (row, scenario), runs in groups.items():
        entry: dict[str, object] = {**dict(row), 'scenario': scenario, 'runs': len(runs)}
        for name in runs[0].scalars:
            values = np.asarray([run.scalars[name] for run in runs])
            if values.ndim != 1 or not np.issubdtype(values.dtype, np.number):
                continue
            values = values.astype(float)
            entry[f'{name}.mean'] = float(values.mean())
            entry[f'{name}.std'] = float(values.std(ddof=1)) if values.size > 1 else 0.0
            for quantile in quantiles:
                entry[f'{name}.q{quantile:g}'] = float(np.quantile(values, quantile))
        summary.append(entry)
    return summary

to_frame()

Return the rows as a pandas data frame.

Returns:

Name Type Description
frame DataFrame

The data frame.

Source code in src/skcomplex/experiments/_results.py
141
142
143
144
145
146
147
148
def to_frame(self) -> pd.DataFrame:
    """Return the rows as a pandas data frame.

    Returns:
        frame:
            The data frame.
    """
    return pd.DataFrame(self.rows)

GridDesign

Bases: BaseDesign

Design of every combination of the values of every name, which prepares on a sequence of values per name.

Attributes:

Name Type Description
rows_ list[Row]

One row per combination of the values, the last name varying fastest, set by prepare.

Examples:

>>> from skcomplex.experiments import GridDesign
>>> GridDesign().check().check().prepare({'r': [0.5, 1.0], 'K': [100, 200]}).run().rows_
[{'r': 0.5, 'K': 100}, {'r': 0.5, 'K': 200}, {'r': 1.0, 'K': 100}, {'r': 1.0, 'K': 200}]

LatinHypercubeDesign(n=64, random_state=None)

Bases: BaseDesign

Design of a Latin hypercube sample, one sample in every stratum of every name.

Parameters:

Name Type Description Default
n int

The number of rows.

64
random_state int | None

The seed of the samples.

None

Attributes:

Name Type Description
rows_ list[Row]

The n sampled rows, set by prepare on a pair (low, high) or a SciPy distribution per name.

Examples:

>>> from skcomplex.experiments import LatinHypercubeDesign
>>> rows = LatinHypercubeDesign(n=4, random_state=0).check().check().prepare({'r': (0.0, 1.0)}).run().rows_
>>> sorted(int(row['r'] * 4) for row in rows)
[0, 1, 2, 3]
Source code in src/skcomplex/experiments/_latin_hypercube.py
33
34
35
def __init__(self, n: int = 64, random_state: int | None = None) -> None:
    self.n = n
    self.random_state = random_state

MorrisDesign(n=10, random_state=None)

Bases: BaseDesign

Design of the Morris trajectories of SALib on four levels, the rows a Morris sensitivity analysis reads.

Every trajectory starts at a random point of the grid of levels and moves one name at a time, n * (names + 1) rows in all.

Parameters:

Name Type Description Default
n int

The number of trajectories.

10
random_state int | None

The seed of the trajectories.

None

Attributes:

Name Type Description
rows_ list[Row]

The n * (names + 1) rows of the trajectories, set by prepare on a pair (low, high) per name.

Examples:

>>> from skcomplex.experiments import MorrisDesign
>>> design = MorrisDesign(n=4, random_state=0).check().check().prepare({'r': (0.0, 1.0), 'K': (50, 150)})
>>> len(design.run().rows_)
12
Source code in src/skcomplex/experiments/_morris.py
36
37
38
def __init__(self, n: int = 10, random_state: int | None = None) -> None:
    self.n = n
    self.random_state = random_state

OneAtATimeDesign

Bases: BaseDesign

Design that moves every name from its lower to its upper bound with the others at their midpoints.

Attributes:

Name Type Description
rows_ list[Row]

Two rows per name, at its lower and then at its upper bound, set by prepare on a pair (low, high) per name.

Examples:

>>> from skcomplex.experiments import OneAtATimeDesign
>>> OneAtATimeDesign().check().check().prepare({'r': (0.0, 1.0), 'K': (50, 150)}).run().rows_
[{'r': 0.0, 'K': 100.0}, {'r': 1.0, 'K': 100.0}, {'r': 0.5, 'K': 50.0}, {'r': 0.5, 'K': 150.0}]

OrbitDiagramExperiment(source, parameter, values, observable, *, transient, keep, dt=None, decimals=6, random_state=None, executor='serial', vectorized=False)

Bases: BaseExperiment

Values an observable visits after a transient, for every value of a parameter.

Every value of the parameter is run to transient + (keep - 1) * dt, and the frames from transient on are kept. With vectorized, all values run as the members of one simulation of the system replicated over them, which needs a dotted path of a state or parameter of an element, and a system of single elements and their interactions.

Parameters:

Name Type Description Default
source Source

A factory of systems, or a system, such as one that skcomplex.models.create returns.

required
parameter str

The dotted path or factory argument to sweep.

required
values Sequence[float]

The values of the parameter.

required
observable str

The state or observable path whose values are kept.

required
transient float

The simulated time discarded before keeping values.

required
keep int

The number of frames kept after the transient.

required
dt float | None

The time between frames, or None for the default of the simulation, 1 for maps.

None
decimals int

The decimals the kept values are rounded to before counting the period of every orbit.

6
random_state int | None

The seed of stochastic runs.

None
executor str | Executor

'serial', 'thread', 'process', or a concurrent.futures executor of the separate runs.

'serial'
vectorized bool

Whether to run every value as a member of one simulation of the replicated system.

False

Attributes:

Name Type Description
points_

The kept values of the observable for every value of the parameter.

periods_

The number of distinct kept values for every value of the parameter, the period of periodic orbits.

Examples:

>>> from skcomplex.experiments import OrbitDiagramExperiment
>>> from skcomplex.models.mathematics.dynamical_systems import logistic_map
>>> diagram = OrbitDiagramExperiment(logistic_map, 'map.r', [2.9, 3.2], 'map.x', transient=1000, keep=20)
>>> diagram.check().prepare().run().periods_.tolist()
[1, 2]
Source code in src/skcomplex/experiments/_orbit_diagram.py
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
def __init__(
    self,
    source: Source,
    parameter: str,
    values: Sequence[float],
    observable: str,
    *,
    transient: float,
    keep: int,
    dt: float | None = None,
    decimals: int = 6,
    random_state: int | None = None,
    executor: str | Executor = 'serial',
    vectorized: bool = False,
) -> None:
    self.source = source
    self.parameter = parameter
    self.values = values
    self.observable = observable
    self.transient = transient
    self.keep = keep
    self.dt = dt
    self.decimals = decimals
    self.random_state = random_state
    self.executor = executor
    self.vectorized = vectorized

RandomDesign(n=64, random_state=None)

Bases: BaseDesign

Design of independent random samples, uniform between a pair of bounds or drawn from a SciPy distribution.

Parameters:

Name Type Description Default
n int

The number of rows.

64
random_state int | None

The seed of the samples.

None

Attributes:

Name Type Description
rows_ list[Row]

The n sampled rows, set by prepare on a pair (low, high) or a SciPy distribution per name.

Examples:

>>> from skcomplex.experiments import RandomDesign
>>> len(RandomDesign(n=5, random_state=0).check().check().prepare({'r': (0.1, 1.0)}).run().rows_)
5
Source code in src/skcomplex/experiments/_random.py
32
33
34
def __init__(self, n: int = 64, random_state: int | None = None) -> None:
    self.n = n
    self.random_state = random_state

RowsDesign(rows)

Bases: BaseDesign

Design given by its rows, which prepares on no bounds.

Parameters:

Name Type Description Default
rows Sequence[Mapping[str, object]]

One mapping from factory argument or dotted path to value per row.

required

Attributes:

Name Type Description
rows_ list[Row]

A copy of the rows, set by prepare.

Examples:

>>> from skcomplex.experiments import RowsDesign
>>> RowsDesign([{'growth.r': 0.5}, {'growth.r': 1.0}]).check().check().prepare().run().rows_
[{'growth.r': 0.5}, {'growth.r': 1.0}]
Source code in src/skcomplex/experiments/_rows.py
30
31
def __init__(self, rows: Sequence[Mapping[str, object]]) -> None:
    self.rows = rows

SaltelliDesign(n=1024, random_state=None)

Bases: BaseDesign

Design of Saltelli's sample of SALib, the rows a Sobol sensitivity analysis reads.

The rows are n base points of a Sobol sequence and, for every name, the same points with that name taken from a second set of points, n * (names + 2) rows in all.

Parameters:

Name Type Description Default
n int

The number of base points, best a power of 2.

1024
random_state int | None

The seed of the scrambling.

None

Attributes:

Name Type Description
rows_ list[Row]

The n * (names + 2) sampled rows, set by prepare on a pair (low, high) per name.

Examples:

>>> from skcomplex.experiments import SaltelliDesign
>>> design = SaltelliDesign(n=8, random_state=0).check().check().prepare({'r': (0.0, 1.0), 'K': (50, 150)})
>>> len(design.run().rows_)
32
Source code in src/skcomplex/experiments/_saltelli.py
36
37
38
def __init__(self, n: int = 1024, random_state: int | None = None) -> None:
    self.n = n
    self.random_state = random_state

SensitivityAnalysis(method='sobol', output=None, random_state=None)

Bases: BaseAnalysis

Sensitivity of a scalar output of an experiment to the values of its design, with SALib.

The analysis prepares on the result of an experiment whose design matches the method, with one run per row:

  • 'oat' reads a OneAtATimeDesign and gives the change of the output from the lower to the upper bound of every input, with the others at their midpoints.
  • 'morris' reads a MorrisDesign and gives the Morris elementary effects.
  • 'sobol' reads a SaltelliDesign and gives the first-order and total Sobol indices.

Parameters:

Name Type Description Default
method str

'oat', 'morris', or 'sobol'.

'sobol'
output str | None

The name of the scalar output, such as the path of a kept series, whose last value it is.

None
random_state int | None

The seed of the bootstrap confidence intervals.

None

Attributes:

Name Type Description
names_

The inputs, the names of the design values.

inputs_

The design values of every run, one column per input.

outputs_

The output of every run.

effects_

The one-at-a-time effect of every input, for 'oat'.

mu_star_, (mu_star_conf_, sigma_)

The mean absolute elementary effect, its confidence interval, and the standard deviation of the elementary effects of every input, for 'morris'.

first_order_, (first_order_conf_, total_order_, total_order_conf_)

The first-order and total Sobol indices of every input and their confidence intervals, for 'sobol'.

ranking_ (first_order_conf_, total_order_, total_order_conf_)

The inputs from the most to the least influential, by effects_, mu_star_, or total_order_.

Examples:

>>> from skcomplex.experiments import Experiment, OneAtATimeDesign, SensitivityAnalysis
>>> from skcomplex.models.biology.ecology import logistic_growth
>>> bounds = {'growth.r': (0.1, 1.0), 'growth.K': (50, 150)}
>>> experiment = Experiment(logistic_growth, OneAtATimeDesign(), bounds, until=20, outputs=['population.n'])
>>> result = experiment.check().prepare().run().result_
>>> SensitivityAnalysis('oat', 'population.n').check().prepare(result).run().ranking_[0]
'growth.K'
Source code in src/skcomplex/experiments/_sensitivity.py
72
73
74
75
def __init__(self, method: str = 'sobol', output: str | None = None, random_state: int | None = None) -> None:
    self.method = method
    self.output = output
    self.random_state = random_state

SobolDesign(n=64, random_state=None)

Bases: BaseDesign

Design of a scrambled Sobol sequence, a low-discrepancy sample, best with n a power of 2.

Parameters:

Name Type Description Default
n int

The number of rows.

64
random_state int | None

The seed of the scrambling.

None

Attributes:

Name Type Description
rows_ list[Row]

The n sampled rows, set by prepare on a pair (low, high) or a SciPy distribution per name.

Examples:

>>> from skcomplex.experiments import SobolDesign
>>> len(SobolDesign(n=8, random_state=0).check().check().prepare({'r': (0.0, 1.0)}).run().rows_)
8
Source code in src/skcomplex/experiments/_sobol.py
32
33
34
def __init__(self, n: int = 64, random_state: int | None = None) -> None:
    self.n = n
    self.random_state = random_state