This page is the shortest path to your first light simulation. It uses the bundled coffee example from example_2/, which is also the source of the image on the home page. Here is how to plot it:
using CairoMakie, PlantGeom
repo_root = normpath(joinpath(dirname(pathof(ArchimedLight)), ".."))
ops_path = joinpath(repo_root, "example_2", "scene", "coffee.ops")
scene_preview = read_scene(ops_path)
plantviz(scene_preview.mtg, color = Dict("Mesh0" => :gray87, "Mesh1" => "#42A25ABD"))
reading file paths and model options from config.yml, then choosing a tutorial resolution explicitly
loading a scene from .ops and .opf
loading functional-group models from YAML
reading one meteo step
running one light step
attaching Ri_PAR_f to the scene for visualization or export
For this tutorial we use 1 cm pixels, keeping the original coffee geometry, optical models, 46 sky directions, and scattering. The original configuration uses 1 mm pixels, which takes longer to compute. Omit the pixel_size override when you need to run that finer configuration.
using ArchimedLight
repo_root = normpath(joinpath(dirname(pathof(ArchimedLight)), ".."))
config = joinpath(repo_root, "example_2", "config.yml")
sim, meteo = read_simulation(config)
update_options!(sim, LightOptions(sim.options; pixel_size=0.01, include_sky_fraction=true))
scene = sim.scene
models = sim.models
options = sim.options
row = first(meteo)
step = run_light(sim, row)The result is a LightStepResult. The most useful field at first is step.budget, which groups values by:
quantity: incident or absorbed
form: flux (W m^-2) or energy (J per component per step)
stage: initial for first-order only, total for first-order plus scattering
waveband: par, nir, and any extra shortwave bands present in the meteo file
ArchimedLight.jl keeps the simulation results in the LightStepResult by default. If you want an inspectable scene, attach selected outputs back onto the MTG:
attach_light_step!(
scene,
step;
fields=[:area, :incident_par_flux, :incident_par_energy, :absorbed_par_energy, :sky_fraction],
)This way you can inspect the results using the powerful MTG query system, or save the results to disk with the topology and geometry of the original scene:
out_path = joinpath(mktempdir(), "coffee_step.opf")
write_scene(out_path, scene)
out_pathThe attached node attributes use the standard ARCHIMED names:
:incident_par_flux -> Ri_PAR_f
:incident_par_energy -> Ri_PAR_q
:absorbed_par_energy -> Ra_PAR_q
:sky_fraction -> sky_fraction
:area -> area
You can plot the results using PlantGeom.jl + Makie.jl:
using PlantGeom, CairoMakie
fig, ax, p = plantviz(
scene.mtg;
color=:Ri_PAR_f,
colormap=:thermal,
colorrange=(0.0, maximum(values(step.budget.incident_flux.total.par))), # This is automatic by default, but we set it explicitly here to show how to control it.
figure=(size=(980, 700),),
)
PlantGeom.colorbar(fig[1, 2], p, label="Ri_PAR_f (W m^-2)")
fig
If you do not want to reattach values onto the MTG just for visualization, ArchimedLight.jl also provides a helper that plots directly from the LightStepResult (this is more performant):
fig2, ax2, p2 = lightplot(
step;
color=:incident_par_flux,
colormap=:thermal,
)
Colorbar(fig2[1, 2], p2.plots[1], label="Ri_PAR_f (W m^-2)")
fig2
If you want to visualize the same toric result as a repeated tile, build a tiled render geometry from the scene plot bounds and reuse the same light result:
tiled = tile_light_geometry(scene, step; nx=3, ny=3)
fig_inf, ax_inf, p_inf = lightplot(tiled, step; color=:incident_par_flux)
Colorbar(fig_inf[1, 2], p_inf.plots[1], label="Ri_PAR_f (W m^-2)")
fig_inf
For a time series, we can generate the plot once and update timestep to inspect any simulated hour. Each LightStepResult supplies its own stored render geometry, so this also works for a series assembled from scenes that changed between simulation steps.
The scene stays fixed in this example (i.e. no growth or pruning), so we can enable cache_radiation to reuse most of the computations across the ten hourly steps. We will also use toricity to simulate an infinite canopy.
# Generating a series of meteo row to simulate a day:
meteo = PlantMeteo.TimeStepTable(
[
(
date=Date(2020, 6, 21),
hour_start=Time("06:00:00") + Hour(i-1),
duration=Hour(1),
latitude=15.0,
clearness=0.6,
)
for i in 1:10],
(latitude=15.0, file="interactive",),
)
update_options!(sim, LightOptions(sim.options; turtle_sectors=16, cache_radiation=true))
series = run_light(sim, meteo)
fig = Figure(resolution=(980, 700))
ax = Axis3(fig[1, 1], title="Intercepted PAR flux over time (06:00)", azimuth=π/4, elevation = π/6, aspect=:data, perspectiveness= 0.4)
p = lightplot!(ax, series; color=:Ri_PAR_f, colormap=:thermal)
Colorbar(fig[1, 2], p.plots[1], label="Ri_PAR_f (W m^-2)")
p[:timestep][] = length(series)
ax.title[] = "Intercepted PAR flux over time ($(Time("06:00") + Hour(length(series)-1)))"
fig
When colorrange is left automatic on a series plot, lightplot(series) uses one color scale for the whole series so the same value maps to the same color at every timestep.
The toricity parameter is activated here, this is why we see the shade of the coffee plant coming from all corners, because light that goes out of the scene on one side comes back in on the other side. This is done for simulating an infinite canopy, but it can be turned off with toricity=false in the config.
Beginner Workflows shows file-based, interactive, and coupled-model usage.
File-Based Workflow explains how the example directory is organised and how each file contributes to the simulation.
Interactive Workflow shows how to build a scene directly in Julia and run light on it without .ops / .opf input files.
Configuration And Options Reference lists the configuration keys that the current Julia package actually uses.
The coffee example still contains several Java-era keys related to outputs, photosynthesis, and energy balance. ArchimedLight.jl keeps reading the light configuration and the input file paths, but the package documented here is currently the light-only core. For current behavior, prefer the reference pages on this site over older Java screenshots or YAML examples.