apsimNGpy Cheat Sheet for Crop Modelers.
Not a Member? Read for FREE here.
Are you working in crop modeling or ecosystem service assessment? Interested in the APSIM process-based model but want something more convenient, extensible, and Python-native?
apsimNGpy is a fast, flexible, and developer-friendly interface to APSIM—designed for simulation, optimization, and data-driven workflows, all in Python.
This cheat sheet walks you through:
- Installing and setting up
apsimNGpy - Loading and editing APSIM models programmatically
- Running simulations and extracting results
- Validating model outputs
- Performing parameter optimization and sensitivity analysis
Whether you’re calibrating a crop model or exploring ecosystem service trade-offs, this guide is your quick-start reference to APSIM smart modeling with Python.
1. Installing apsimNGpy
Installing from PyPI
pip install apsimNGpyInstalling from GitHub
pip install git+https://github.com/MAGALA-RICHARD/apsimNGpy.gitProviding the installed APSIM binaries.
- Use the terminal. When apsimNGpy is installed, it creates a command line module apsim_bin_path
apsim_bin_path -u 'path/to/your/apsim/binary/folder/bin'2. Use the config module
from apsimNGpy.core.config import set_apsim_bin_path
# Set the path to the APSIM binaries:
set_apsim_bin_path(path=r'path/to/your/apsim/binary/folder/bin')Getting the APSIM binary path
- Use the terminal
apsim_bin_path -s- Use the config module
from apsimNGpy.core import config
print(config.get_apsim_bin_path())Viewing apsimNGpy version
import apsimNGpy
apsimNGpy.__version__What about the APSIM version?
from apsimNGpy.core.config import apsim_version
print(apsim_version())
Instantiating apsimNGpy Model Objects
You can either load a built-in template or use your own APSIM file
- Load the factory default model
from apsimNGpy.core import base_data
# Option 1: Load default maize simulation
model = base_data.load_default_simulations(crop='Maize')
# Option 2: Equivalent direct instantiation. Supported by versions 0.35 +
from apsimNGpy.core.apsim import ApsimModel
model = ApsimModel(model='Maize', out_path = './maize.apsimx')
# other crops inlcude: Pinus, Barley, Soybean, EucalyptusRotation, Eucalyptus,
# Sugarcane, Oats, WhiteClover, Sorghum, Potato, Canola, Chickpea, RedClover, Mungbean etc.2. Load the model from a file on the computer disk
from apsimNGpy.core.apsim import ApsimModel
model = ApsimModel(model='./Maize.apsimx', out_path = './maize.apsimx')Running loaded models
from apsimNGpy.core.apsim import ApsimModel
model = ApsimModel('Pinus')
model.run()
Retrieving simulated results
# Retrieve and save the results
df = model.results
df.to_csv('apsim_df_res.csv') # Save the results to a CSV file
print(model.results)
Saving the edited file or model
model.save('./simulated_pinus.apsimx')Inspecting model structure
Most of the time, when modifying model parameters and values, you need the full path to the specified APSIM model. This is where the inspect_model method becomes useful—it allows you to inspect the model without opening the file in the APSIM GUI.
from apsimNGpy.core.apsim import ApsimModel
model = ApsimModel(model= 'Maize')Finding the path to the manager modules
model.inspect_model('Models.Manager', fullpath=True)
# output
[.Simulations.Simulation.Field.Sow using a variable rule', '.Simulations.Simulation.Field.Fertilise at
sowing', '.Simulations.Simulation.Field.Harvest']Names only
model.inspect_model('Models.Manager', fullpath=False)
# output
['Sow using a variable rule', 'Fertilise at sowing', 'Harvest']Getting the names of the simulations in the loaded file
model.inspect_model('Models.Core.Simulation', fullpath=False)
# Output
['Simulation']The models from APSIM Models namespace are abstracted to use strings. but you can still play around with the Models namespace as follows:
from apsimNGpy.core.core import Models
model.inspect_model(Models.Core.Simulation, fullpath=False)
# Output
['Simulation']Whole Model inspection
model.inspect_file()Inspecting model parameters
- Using inspect_model_parameters
from apsimNGpy.core import ApsimModel
model = ApsimModel('Maize')
Inspect the full soil Organic profile:
model.inspect_model_parameters('Organic', simulations='Simulation', model_name='Organic')
# output
CNR Carbon Depth FBiom ... FOM Nitrogen SoilCNRatio Thickness
0 12.0 1.20 0-150 0.04 ... 347.129032 0.100 12.0 150.0
1 12.0 0.96 150-300 0.02 ... 270.344362 0.080 12.0 150.0
2 12.0 0.60 300-600 0.02 ... 163.972144 0.050 12.0 300.0
3 12.0 0.30 600-900 0.02 ... 99.454133 0.025 12.0 300.0
4 12.0 0.18 900-1200 0.01 ... 60.321981 0.015 12.0 300.0
5 12.0 0.12 1200-1500 0.01 ... 36.587131 0.010 12.0 300.0
6 12.0 0.12 1500-1800 0.01 ... 22.191217 0.010 12.0 300.0
[7 rows x 9 columns]Inspect soil Physical profile:
model.inspect_model_parameters('Physical', simulations='Simulation', model_name='Physical')
# output
AirDry BD DUL ... SWmm Thickness ThicknessCumulative
0 0.130250 1.010565 0.521000 ... 78.150033 150.0 150.0
1 0.198689 1.071456 0.496723 ... 74.508522 150.0 300.0
2 0.280000 1.093939 0.488438 ... 146.531282 300.0 600.0
3 0.280000 1.158613 0.480297 ... 144.089091 300.0 900.0
4 0.280000 1.173012 0.471584 ... 141.475079 300.0 1200.0
5 0.280000 1.162873 0.457071 ... 137.121171 300.0 1500.0
6 0.280000 1.187495 0.452332 ... 135.699528 300.0 1800.0
[7 rows x 17 columns]Inspect soil Chemical profile:
model.inspect_model_parameters('Chemical', simulations='Simulation', model_name='Chemical')
# output
Depth PH Thickness
0 0-150 8.0 150.0
1 150-300 8.0 150.0
2 300-600 8.0 300.0
3 600-900 8.0 300.0
4 900-1200 8.0 300.0
5 1200-1500 8.0 300.0
6 1500-1800 8.0 300.0Getting the current weather/met file
model.inspect_model_parameters('Weather', simulations='Simulation',
model_name='Weather')
# output
'%root%/Examples/WeatherFiles/AU_Dalby.met'Inspect Manager script parameters.
model.inspect_model_parameters('Manager',
simulations='Simulation', model_name='Sow using a variable rule')
# output
{'Crop': 'Maize',
'StartDate': '1-nov',
'EndDate': '10-jan',
'MinESW': '100.0',
'MinRain': '25.0',
'RainDays': '7',
'CultivarName': 'Dekalb_XL82',
'SowingDepth': '30.0',
'RowSpacing': '750.0',
'Population': '10'}Specify a few parameters
model.inspect_model_parameters('Manager',
simulations='Simulation', model_name='Sow using a variable rule',
parameters=['Population'])
# output
{'Population': '10'}The primary limitation of inspect_model_parameters is its verbosity — it often requires passing model_type, model_name and simulations or navigating deeply nested structures.
The inspect_model_parameters_by_path method addresses this verbosity problem by allowing users to simply specify the path to the model component and (optionally) the parameters to inspect. This makes the API more concise and user-friendly.
Inspect SurfaceOrganicMatter module parameters
model = ApsimModel('Maize')
model.inspect_model_parameters_by_path('.Simulations.Simulation.Field.SurfaceOrganicMatter')
# output
{'InitialCPR': 0.0,
'InitialCNR': 100.0,
'NH4': 0.0,
'NO3': 0.0,
'Cover': 0.0,
'LabileP': 0.0,
'N': 0.0,
'SurfOM': <System.Collections.Generic.List[SurfOrganicMatterType] object at 0x1ae5c10c0>,
'InitialResidueMass': 500.0,
'LyingWt': 0.0,
'StandingWt': 0.0,
'C': 0.0,
'P': 0.0}Inspect the surface organic matter module parameters by selecting a few parameters
model.inspect_model_parameters_by_path('.Simulations.Simulation.Field.SurfaceOrganicMatter', parameters = 'InitialCNR')
# output
{'InitialCNR': 100.0}If all the above is not enough, view the file in the GUI
model.preview_simulation()Editing the model parameters
Apart from inspecting the above parameters, we can actually change them
Get Richard Magala (Ph.D)’s stories in your inbox
Join Medium for free to get updates from this writer.
editing the model cultivar
model.edit_model(
model_type='Cultivar',
simulations='Simulation',
commands='[Phenology].Juvenile.Target.FixedValue',
values=256,
new_cultivar_name = 'B_110-e',
model_name='B_110',
cultivar_manager='Sow using a variable rule')model_name: 'B_110' is an existing cultivar in the Maize Model, which we want to edit. Please note that editing a cultivar without specifying the new_cultivar_name will throw a ValueError. The name should be different to the the one being edited.
Edit a soil organic module:
model = ApsimModel(model='Maize')
model.edit_model(
model_type='Organic',
simulations='Simulation',
model_name='Organic',
Carbon=1.23)editing only the top and the second soil layer’s soil carbon
model.edit_model(
model_type='Organic',
simulations='Simulation',
model_name='Organic',
Carbon=[1.23, 1.0])Editing a manager script:
model.edit_model(
model_type='Manager',
simulations='Simulation',
model_name='Sow using a variable rule',
population=8.4)If you prefer little boilerplate code, you are covered with edit_model_by_path.
model.edit_model_by_path(path = '.Simulations.Simulation.Field.Sow using a variable rule', Population =12)Running Factorial Experiments
Creating an Experiment
model.create_experiment(permutation=True, verbose=False) # Default is a permutation experimentAdding Factors
- Add nitrogen levels as a continuous factor
model.add_factor(specification="[Fertilise at sowing].Script.Amount = 0 to 200 step 20", factor_name='Nitrogen')2. Add population density as a categorical factor:
model.add_factor(specification="[Sow using a variable rule].Script.Population = 4, 10, 2, 7, 6",
factor_name='Population')Running the Experiment
Running the experiment is the same as running the ordinary model
model.run(report_name='Report')
df = apsim.results
df[['population']] = pd.Categorical(['Population'])
sns.catplot(x='Nitrogen', y='Yield', hue='Population', data=df, kind='box')
plt.show()If the factors are associated with cultivar, then you need to add a crop replacement
model.add_crop_replacements(_crop='Maize')
# Create experiment as above
model.create_experiment(permutation=True, verbose=False)Replacing the weather data
# replace the weather with lonlat specification as follows;
maize_model.get_weather_from_web(lonlat = (-93.885490, 42.060650), start = 1990, end =2001)Using local weather data on the computer disk
maize_model.replace_met_file(weather_file = './pathtotheeatherfile')Single-Objective Optimization with apsimNGpy
from apsimNGpy.optimizer.single import ContinuousVariable, MixedVariable
from apsimNGpy.core.apsim import ApsimModelExplanation
ApsimModel`: used to initialize the apsim model and handles model simulation and editingContinuousVariable: wraps your problem setup for continuous variablesMixedVariable: wraps your problem setup for mixed variables
Load the APSIM model. This is typically a single simulation file you want to calibrate or optimize.
maize_model = ApsimModel("Maize") # replace with the template path
obs = [
7000.0, 5000.505, 1000.047, 3504.000, 7820.075,
7000.517, 3587.101, 4000.152, 8379.435, 4000.301
]- Create your own problem description class
class Problem(ContinuousVariable):
def __init__(self, apsim_model, obs):
super().__init__(apsim_model=apsim_model)
self.obs = obs
def evaluate_objectives(self, **kwargs):
# This function runs APSIM and compares the predicted maize yield results with observed data.
predicted = self.apsim_model.run(verbose=False).results.Yield
# Use root mean square error or another metric.
return self.rmse(self.obs, predicted)
# Initialize the class
problem = Problem(maize_model, obs)2. Approach 2 is to define directly the objectives and supply the objectives while initializing any of ContinuousVariable or MixedVariable classes.
def maximize_yield(df):
# Negate yield to convert to a minimization problem
return -df.Yield.mean()
problem = ContinuousVariable(maize_model, objectives = maximize_yield)Adding control variables/decision variables to the defined problem
problem.add_control(
path='.Simulations.Simulation.Field.Fertilise at sowing',
Amount="?", bounds=[50, 300], v_type='int', start_value=150
)
problem.add_control(
path='.Simulations.Simulation.Field.Sow using a variable rule',
Population="?", v_type='int', bounds=[4, 14], start_value=8
)Amount and Populationwill be filled in by the optimizer because they are marked with ‘?’. It is also possible to supply extra parameters associated with any of the model paths, which comes in handy if you want to change them on the fly, but you don’t want to optimize them. An example is shown below.
problem.add_control(
path='.Simulations.Simulation.Field.Fertilise at sowing', CultivarName= 'B_110',
Amount="?", bounds=[50, 300], v_type='int', start_value=150 )Minimize with any solver
res_local = problem.minimize_with_a_local_solver(
method='Powell',
options={
'maxiter': 100,
'disp': True
}
)Changing to another solver
res_local = problem.minimize_with_a_local_solver(
method='Nelder-Mead',
options={
'maxiter': 100,
'disp': True
}
)For details about these algorithms, see the minimize documentation.
Run a global optimizer using differential evolution
# Run a global optimizer using differential evolution
res_de = problem.minimize_with_de(
popsize=10,
maxiter=100,
polish=False # Set to True if you want to refine with a local solver at the end
)Getting results
print(problem)Multi-Objective Optimization with apsimNGpy
In real-world agricultural systems, most objectives — such as maximizing crop yield while minimizing environmental impact — are inherently conflicting. These trade-offs cannot be effectively addressed using single-objective optimization algorithms, which are limited to optimizing one goal at a time. Fortunately, multi-objective optimization algorithms inspired by evolutionary principles are well-suited to handle such complexity by exploring a range of trade-offs between competing objectives.
from apsimNGpy.optimizer.moo import MultiObjectiveProblem, compute_hyper_volume, NSGA2
from pymoo.optimize import minimize
import matplotlib.pyplot as plt
from apsimNGpy.core.apsim import ApsimModel as RunnerInterpretation
Runner`: handles model simulation and editing. It is an apsimNGpy classMultiObjectiveProblem: wraps your problem into a multi-objective oneNSGA2: a multi-objective genetic algorithmminimize: will be used to minimize the objectives in the finals steps
Initialize the APSIM model runner
runner = Runner("Maize")
runner.add_report_variable('[Soil].Nutrient.NO3.kgha[1] as nitrate', report_name='Report')Defining Objective Functions
Objective functions take APSIM output (as a DataFrame) and return scalar values.
def maximize_yield(df):
return -df['Yield'].mean()
def minimize_nitrate_leaching(df):
return df['nitrate'].sum()Defining decision variables
- use a list of dicts
decision_vars = [
{'path': '.Simulations.Simulation.Field.Fertilise at sowing',
'Amount': "?", 'bounds': [50, 300], 'v_type': 'float'},
{'path': '.Simulations.Simulation.Field.Sow using a variable rule',
'Population': "?", 'bounds': [4, 14], 'v_type': 'float'}
]
# Then, initialise the problem
problem = MultiObjectiveProblem(runner, objectives=[maximize_yield, minimize_nitrate_leaching], decision_vars=decision_vars)Each dictionary defines:
path: the APSIM model path to the component.Amount/ Population: the parameter to be optimized (denoted by ‘?’).bounds: lower and upper bounds for the optimizer.v_type: variable type.
2. Add the decision variables after problem initialization
# Initialise the problem
problem = MultiObjectiveProblem(runner, objectives=[maximize_yield, minimize_nitrate_leaching])
problem.add_control(
path='.Simulations.Simulation.Field.Fertilise at sowing',
Amount='?', bounds=[50, 300], v_type='float')
problem.control(
path='.Simulations.Simulation.Field.Sow using a variable rule',
Population='?', bounds=[4, 14], v_type='float')Run the NSGA-II optimizer
algorithm = NSGA2(pop_size=20)
result = minimize(
problem.get_problem(),
algorithm,
('n_gen', 10),
seed=1,
verbose=True
)Plot the Pareto Front
F = result.F
plt.scatter(F[:, 0]* -1, F[:, 1])
plt.xlabel("Yield")
plt.ylabel("N Leaching")
plt.title("Pareto Front")
plt.show()




