Sensitivity Analysis in apsimNGpy
Sensitivity analysis in apsimNGpy implements the same established
methods used in APSIM—namely the Morris Elementary Effects method and
the Sobol variance–based method. The key difference is flexibility:
apsimNGpy allows you to construct a sensitivity analysis directly from
any APSIM template file, including the model you are actively developing.
With only a few lines of Python code, you can specify the sensitivity
method, configure the factors, build the sensitivity model, and execute it.
In contrast to the APSIM GUI, which provides graphical representations and
interactive controls, the Python interface expects the user to understand
the fundamentals of the analysis being performed. apsimNGpy does not
generate graphical summaries automatically. However, interpretation is not
lost—APSIM’s native sensitivity outputs remain accessible, and the
SensitivityManager class provides convenient access points for
visualization and custom analysis, enabling you to create your own plots
using Matplotlib, Seaborn, or other Python libraries.
The workflow for creating a sensitivity analysis is conceptually similar to setting up factorial experiments: define the method, specify the factors, build the sensitivity experiment node, and run the simulations. The example below shows a minimal, practical workflow for constructing a sensitivity analysis directly from Python.
Tip
apsimNGpy is designed to integrate sensitivity analysis seamlessly
into reproducible pipelines. Because everything is defined in Python,
you can version-control the full sensitivity experiment, regenerate it
consistently, and run it across multiple APSIM templates or scenarios.
Workflow Overview
The sensitivity analysis workflow in apsimNGpy follows a structured,
reproducible sequence of steps. The process is designed to mirror APSIM’s
internal sensitivity framework while providing full programmatic control in
Python. The key stages are:
Initialize the manager instance Create a
SensitivityManagerobject using your APSIM template file. This instance will hold the experiment configuration and manage all subsequent steps.Define sensitivity factors Add one or more factors (parameters) to the manager instance. Each factor corresponds to an APSIM model path and a numerical range to be explored. Factors are defined using methods on the instantiated
SensitivityManagerobject.Build the sensitivity simulation model Construct the sensitivity experiment node within the APSIM file. During this step the user:
selects the sensitivity method (Morris or Sobol),
specifies the database table name for storing results,
determines the aggregation column used to summarize outputs,
and, when using the Morris method: - sets the number of jumps, - defines the number of intervals, - and optionally customizes the step size
Δ.
For all sensitivity methods, this step also sets the number of parameter paths, which controls the total number of simulations executed.
Run the sensitivity simulations Execute the sensitivity experiment.
apsimNGpyautomatically handles model execution, path construction, execution, and data storage.
Workflow Diagram
+-----------------+ +----------------------+
|instantiate class |----> | define parameters |
+-----------------+ +----------------------+
|
v
+-----------------+ +----------------------+
| run model |<----- | Build nodes |
+-----------------+ +----------------------+
Step 1.
from apsimNGpy.core import senstivitymanager import SensitivityManager
morris = SensitivityManager("Maize", out_path='sob.apsimx')
Step 2.
morris.add_sens_factor(name='cnr', path='Field.SurfaceOrganicMatter.InitialCNR', lower_bound=10, upper_bound=120)
morris.add_sens_factor(name='cn2bare', path='Field.Soil.SoilWater.CN2Bare', lower_bound=70, upper_bound=100)
Step 3.
morris.build_sense_model(method='Morris', aggregation_column_name='Clock.Today', jumps=10)
We can still use any other methods inherited from ApsimModel as follows
morris.inspect_file()
└── Models.Core.Simulations: .Simulations
├── Models.Storage.DataStore: .Simulations.DataStore
└── Models.Morris: .Simulations.Morris
└── Models.Core.Simulation: .Simulations.Morris.Simulation
├── Models.Clock: .Simulations.Morris.Simulation.Clock
├── Models.Core.Zone: .Simulations.Morris.Simulation.Field
│ ├── Models.Manager: .Simulations.Morris.Simulation.Field.Fertilise at sowing
│ ├── Models.Fertiliser: .Simulations.Morris.Simulation.Field.Fertiliser
│ ├── Models.Manager: .Simulations.Morris.Simulation.Field.Harvest
│ ├── Models.PMF.Plant: .Simulations.Morris.Simulation.Field.Maize
│ ├── Models.Report: .Simulations.Morris.Simulation.Field.Report
│ ├── Models.Soils.Soil: .Simulations.Morris.Simulation.Field.Soil
│ │ ├── Models.Soils.Chemical: .Simulations.Morris.Simulation.Field.Soil.Chemical
│ │ ├── Models.Soils.Solute: .Simulations.Morris.Simulation.Field.Soil.NH4
│ │ ├── Models.Soils.Solute: .Simulations.Morris.Simulation.Field.Soil.NO3
│ │ ├── Models.Soils.Organic: .Simulations.Morris.Simulation.Field.Soil.Organic
│ │ ├── Models.Soils.Physical: .Simulations.Morris.Simulation.Field.Soil.Physical
│ │ │ └── Models.Soils.SoilCrop: .Simulations.Morris.Simulation.Field.Soil.Physical.MaizeSoil
│ │ ├── Models.Soils.Solute: .Simulations.Morris.Simulation.Field.Soil.Urea
│ │ └── Models.Soils.Water: .Simulations.Morris.Simulation.Field.Soil.Water
│ ├── Models.Manager: .Simulations.Morris.Simulation.Field.Sow using a variable rule
│ └── Models.Surface.SurfaceOrganicMatter: .Simulations.Morris.Simulation.Field.SurfaceOrganicMatter
├── Models.Graph: .Simulations.Morris.Simulation.Graph
│ └── Models.Series: .Simulations.Morris.Simulation.Graph.Series
├── Models.MicroClimate: .Simulations.Morris.Simulation.MicroClimate
├── Models.Soils.Arbitrator.SoilArbitrator: .Simulations.Morris.Simulation.SoilArbitrator
├── Models.Summary: .Simulations.Morris.Simulation.Summary
└── Models.Climate.Weather: .Simulations.Morris.Simulation.Weather
Step 4
morris.run()
You can access the statics as follows
morris.statistics
CheckpointID Clock.Today ... ColumnName Indices
0 1 1991-05-28 ... Maize.AboveGround.Wt FirstOrder
1 1 1991-05-28 ... Maize.AboveGround.Wt FirstOrder
2 1 1991-05-28 ... Maize.AboveGround.Wt Total
3 1 1991-05-28 ... Maize.AboveGround.Wt Total
4 1 1991-05-28 ... Maize.AboveGround.N FirstOrder
.. ... ... ... ... ...
355 1 2000-04-05 ... Maize.Grain.N Total
356 1 2000-04-05 ... Maize.Total.Wt FirstOrder
357 1 2000-04-05 ... Maize.Total.Wt FirstOrder
358 1 2000-04-05 ... Maize.Total.Wt Total
359 1 2000-04-05 ... Maize.Total.Wt Total
[360 rows x 10 columns]
We can get a list of available variables as follows
morris.statistics.columns
Index(['CheckpointID', 'Parameter', 'Clock.Today', 'Maize.AboveGround.Wt.Mu',
'Maize.AboveGround.Wt.MuStar', 'Maize.AboveGround.Wt.Sigma',
'Maize.AboveGround.N.Mu', 'Maize.AboveGround.N.MuStar',
'Maize.AboveGround.N.Sigma', 'Yield.Mu', 'Yield.MuStar', 'Yield.Sigma',
'Maize.Grain.Wt.Mu', 'Maize.Grain.Wt.MuStar', 'Maize.Grain.Wt.Sigma',
'Maize.Grain.Size.Mu', 'Maize.Grain.Size.MuStar',
'Maize.Grain.Size.Sigma', 'Maize.Grain.NumberFunction.Mu',
'Maize.Grain.NumberFunction.MuStar', 'Maize.Grain.NumberFunction.Sigma',
'Maize.Grain.Total.Wt.Mu', 'Maize.Grain.Total.Wt.MuStar',
'Maize.Grain.Total.Wt.Sigma', 'Maize.Grain.N.Mu',
'Maize.Grain.N.MuStar', 'Maize.Grain.N.Sigma', 'Maize.Total.Wt.Mu',
'Maize.Total.Wt.MuStar', 'Maize.Total.Wt.Sigma'],
dtype='object')
Simulated results can be accessed as follows:
morris.results
#same as
morris.get_simulated_output('Report')
In order to use Sobol, use `method =sobol’ as follows
sobol = SensitivityManager("Maize", out_path='sob.apsimx')
morris.add_sens_factor(name='cnr', path='Field.SurfaceOrganicMatter.InitialCNR', lower_bound=10, upper_bound=120)
morris.add_sens_factor(name='cn2bare', path='Field.Soil.SoilWater.CN2Bare', lower_bound=70, upper_bound=100)
sobol.build_sense_model(method='Sobol', aggregation_column_name='Clock.Today')
sobol.inspect_file()
sobol.run()
sobol.statistics
sobol.results
# same as
sobol.get_simulated_output('Report')
└── Models.Core.Simulations: .Simulations
├── Models.Storage.DataStore: .Simulations.DataStore
└── Models.Sobol: .Simulations.Sobol
└── Models.Core.Simulation: .Simulations.Sobol.Simulation
├── Models.Clock: .Simulations.Sobol.Simulation.Clock
├── Models.Core.Zone: .Simulations.Sobol.Simulation.Field
│ ├── Models.Manager: .Simulations.Sobol.Simulation.Field.Fertilise at sowing
│ ├── Models.Fertiliser: .Simulations.Sobol.Simulation.Field.Fertiliser
│ ├── Models.Manager: .Simulations.Sobol.Simulation.Field.Harvest
│ ├── Models.PMF.Plant: .Simulations.Sobol.Simulation.Field.Maize
│ ├── Models.Report: .Simulations.Sobol.Simulation.Field.Report
│ ├── Models.Soils.Soil: .Simulations.Sobol.Simulation.Field.Soil
│ │ ├── Models.Soils.Chemical: .Simulations.Sobol.Simulation.Field.Soil.Chemical
│ │ ├── Models.Soils.Solute: .Simulations.Sobol.Simulation.Field.Soil.NH4
│ │ ├── Models.Soils.Solute: .Simulations.Sobol.Simulation.Field.Soil.NO3
│ │ ├── Models.Soils.Organic: .Simulations.Sobol.Simulation.Field.Soil.Organic
│ │ ├── Models.Soils.Physical: .Simulations.Sobol.Simulation.Field.Soil.Physical
│ │ │ └── Models.Soils.SoilCrop: .Simulations.Sobol.Simulation.Field.Soil.Physical.MaizeSoil
│ │ ├── Models.Soils.Solute: .Simulations.Sobol.Simulation.Field.Soil.Urea
│ │ └── Models.Soils.Water: .Simulations.Sobol.Simulation.Field.Soil.Water
│ ├── Models.Manager: .Simulations.Sobol.Simulation.Field.Sow using a variable rule
│ └── Models.Surface.SurfaceOrganicMatter: .Simulations.Sobol.Simulation.Field.SurfaceOrganicMatter
├── Models.Graph: .Simulations.Sobol.Simulation.Graph
│ └── Models.Series: .Simulations.Sobol.Simulation.Graph.Series
├── Models.MicroClimate: .Simulations.Sobol.Simulation.MicroClimate
├── Models.Soils.Arbitrator.SoilArbitrator: .Simulations.Sobol.Simulation.SoilArbitrator
├── Models.Summary: .Simulations.Sobol.Simulation.Summary
└── Models.Climate.Weather: .Simulations.Sobol.Simulation.Weather
The rest of the workflow is the same as above
The API interface is still the same because all methods and attributes are inherited from the ApsimModel. Some examples are given below:
sobol.inspect_model('Models.Manager')
['.Simulations.Sobol.Simulation.Field.Sow using a variable rule',
'.Simulations.Sobol.Simulation.Field.Fertilise at sowing',
'.Simulations.Sobol.Simulation.Field.Harvest']
sobol.inspect_model_parameters_by_path('.Simulations.Sobol.Simulation.Field.Fertilise at sowing')
{'Crop': 'Maize', 'FertiliserType': 'NO3N', 'Amount': '160.0'}
We can still edit the base simulation models as follows
sobol.edit_model_by_path('.Simulations.Sobol.Simulation.Field.Fertilise at sowing', Amount=150)
Same as:
sobol.edit_model(model_type='Models.Manager', model_name='Fertilise at sowing', Amount=150)
Then, you can run the model
sobol.run(verbose = True)
Before I log off, you can check out the documentation of following methods, which have been used in this tutorial
add_sens_factor(),
build_sense_model(),
statistics().
XXXX Thank you XXXX