# GridSpec variant sweep simulation - tree growth affected by external data
# Demonstrates using .josh.j2 model templates with GridSpec variant sweeps
#
# Grid geometry is injected via template_vars from GridSpec.
# The `external soil_quality` expression reads preprocessed .jshd data
# that varies spatially and by scenario (via GridSpec variants).
# Growth ceiling is controlled by `config sweep_config.maxGrowth`.
start simulation {{ simulation_name }}
grid.size = {{ size_m }} m
grid.low = {{ low_lat }} degrees latitude, {{ low_lon }} degrees longitude
grid.high = {{ high_lat }} degrees latitude, {{ high_lon }} degrees longitude
grid.patch = "Default"
steps.low = 0 count
steps.high = {{ steps }} count
exportFiles.patch = "file:///tmp/variant_sweep_{run_hash}_{replicate}.csv"
end simulation
start patch Default
ForeverTree.init = create 10 count of ForeverTree
soil_quality.step = external soil_quality
export.average_height.step = mean(ForeverTree.height)
export.average_age.step = mean(ForeverTree.age)
export.soil_quality.step = soil_quality
end patch
start organism ForeverTree
age.init = 0 year
age.step = prior.age + 1 year
height.init = 0 meters
# Growth rate scales with soil quality from external data, capped by maxGrowth
max_growth.step = map here.soil_quality from [0 percent, 100 percent] to [0 meters, config sweep_config.maxGrowth] linear
height.step = prior.height + sample uniform from 0 meters to max_growth
end organism
start unit year
alias years
alias yr
alias yrs
end unit
Grid geometry (size_m, low_lat, etc.) comes from GridSpec.template_vars at render time. The model reads external soil quality data and caps tree growth at config sweep_config.maxGrowth.
# Auto-generated configuration for tutorial_sweep.josh
# Parameter sweep: maxGrowth={{ maxGrowth }}
# =============================================================================
# STATIC CONFIG VALUES
# These values are the same for all runs in the sweep.
# Use static values for constants that don't need to vary across experiments.
# =============================================================================
# Initial tree count per organism (constant across all sweep runs)
initialTreeCount = 10 count
# =============================================================================
# SWEPT CONFIG VALUES
# These values vary across sweep runs. Each unique combination creates a job.
# Use swept values for parameters you want to explore or optimize.
# =============================================================================
# Maximum growth per timestep (meters) - SWEPT via Jinja template
maxGrowth = {{ maxGrowth }} meters
{ maxGrowth } is filled in per job – either from template_vars (ad-hoc) or from ConfigSweepParameter (sweep).
Running 1 jobs (2 total replicates)
[1/1] Running (local): {}
[OK] Completed successfully
Completed: 1 succeeded, 0 failed
Completed: 1 succeeded, 0 failed
Loading patch results from: /tmp/variant_sweep_{run_hash}_{replicate}.csv
Loaded 34782 rows from variant_sweep_c7038b2fe88b_0.csv
Loaded 34782 rows from variant_sweep_c7038b2fe88b_1.csv
Results:
Jobs in sweep: 1
Jobs with results loaded: 1
Total rows loaded: 69564
69564
Run 3: Re-run gradient with tweaked growth (same label)
What if you want to iterate on the gradient run but keep calling it "gradient_low"? Use on_collision="timestamp" to automatically archive the old label:
The registry stores the full config content and josh source for every run. You can view or diff them with the inspect module – see Inspecting Runs from the Registry for the full workflow.
The get_file_mappings() method recalls which .jshd data files were used:
The model works. Now sweep the full parameter x variant space.
The same templates and GridSpec are reused – the only change is adding a SweepConfig. Parameter values move from template_vars to ConfigSweepParameter, and file_mappings_for() becomes variant_sweep():
Figure 2: Tree height across all three soil quality patterns.
diag.plot_comparison("average_height", group_by="maxGrowth", title="Tree Height by Max Growth Rate",)
Figure 3: Tree height across growth rate values.
Cross-tabulation
Both the variant label (pattern) and config parameter (maxGrowth) are stored in config_parameters, so you can query the full cartesian product:
result = sweep_registry.query(""" SELECT cp.pattern, cp.maxGrowth, AVG(cd.average_height) AS mean_final_height FROM cell_data cd JOIN config_parameters cp ON cd.run_hash = cp.run_hash WHERE cd.step = (SELECT MAX(step) FROM cell_data) GROUP BY cp.pattern, cp.maxGrowth ORDER BY cp.pattern, cp.maxGrowth""")result.df()