Julia reimplementation of the ARCHIMED light interception pipeline with a composable, function-first API.
The figure above is generated from the bundled coffee fixture with scripts/generate_home_figure.jl. The script loads a scene, models, options, and meteo rows, adds explicit ground geometry, runs one light step, attaches Ri_PAR_f onto the MTG, and then renders the colored scene with plantviz(..., color=:Ri_PAR_f).
- Scene/model/meteo input pipeline
- Sky + turtle discretization
- First-order interception (CPU raster/z-buffer)
- Iterative scattering (CPU reference)
- Julia-native fixture regression harness (numeric + visual references)
Energy balance, transpiration and photosynthesis are intentionally out of scope for now.
using ArchimedLight
options, scene, meteo, models = read_config("config.yml")
row = first(prepare_meteo(meteo, options).rows)
sky = compute_sky(row, options)
turtle = build_turtle(options, sky)
fluxes = compute_directional_fluxes(row, sky, turtle, options)
first_order = compute_first_order(scene, models, turtle, fluxes, options)
scat = compute_scattering(scene, models, turtle, first_order, options)
budget = integrate_light(scene, models, first_order, scat, options; meteo_row=row)In Julia code, LightBudget is grouped by quantity and waveband:
budget.incident_flux.total.par
budget.incident_energy.total.par
budget.absorbed_flux.total.nir
budget.absorbed_energy.initial.parFile exports and attached MTG attributes keep the ARCHIMED names:
Ri_*: incident lightRa_*: absorbed light*_f: irradiance (W m^-2)*_q: per-component energy per timestep (J)
Band coefficients come from the model definition when present. If a model omits one band in
optical_properties, the runtime falls back to the global option for that band:
PARfallback:LightOptions(scattering_coeff_par=...)NIRfallback:LightOptions(scattering_coeff_nir=...)
With the default options, that means:
- missing model
PARcoefficient falls back to0.15 - missing model
NIRcoefficient falls back to0.30 - the corresponding default absorptances used in the final budget are
1 - coeff
options, scene, meteo, models = read_config("config.yml")
row = first(prepare_meteo(meteo, options).rows)
step = run_light_step(scene, models, row, options)
series = run_light_series(scene, models, meteo, options)
attach_light_step!(scene, step; fields=[:incident_par_flux, :absorbed_par_total_energy])
write_scene("output/scene.opf", scene)
# Optional backend kwargs:
step = run_light_step(
scene,
models,
row,
options;
interception_backend=RasterCPUBackend(),
scattering_backend=RaycastScatteringBackend(),
)- Self-contained files and script are under
example_1/. - Run with:
julia --project=. example_1/full_featured_example.jlread_config("config.yml")is the convenience entrypoint for file-driven workflows and returnsoptions, scene, meteo, models.- You can call each stage independently (
compute_sky,build_turtle,compute_first_order,compute_scattering,integrate_light, ...). - File-based and in-memory workflows share the same runtime path:
read_scene(...)andprepare_scene(...)both produceSceneGeometry, whileread_models(...)andprepare_models(...)both produceLightModels. compute_skyfollows the ARCHIMED clearness/global conversion and DeJong hourly direct/diffuse partitioning.compute_skyuses substep-weighted sun position (radiation_timestep) when sun angles are not provided.- Meteo
#' use: ...consistency checks forclearness/RI_SW_f/RI_PAR_f/RI_NIR_fare enforced like Java. compute_first_order(...; backend=:raster_cpu)is the current reference backend (RasterCPUBackend()also available).compute_scattering(...; mode=:raycast)/compute_scattering(...; mode=:links)expose the two supported scattering modes; backend objects are also available (RaycastScatteringBackend(),LinksScatteringBackend()).pixel_sizeis validated with ARCHIMED-compatible bounds (0 < pixel_size <= 0.5meters).cache_pixel_table=trueenables an on-disk direction projection cache under the interception cache directory.build_turtlefollows the canonical ARCHIMED sector counts1, 6, 16, 46, 136, 406.add_ground!(scene; ...)is an explicit scene-editing step, so inspectable ground is ordinary geometry in the exported MTG.- Virtual sensors are declared on the interception model (
sensor=true, or legacymodel: VirtualSensorin YAML). They receive light but stay transparent and non-absorbing in the simulation.
Run the default fast suite:
julia --project=test test/runtests.jlThis runs the standalone @testitems discovered from the *-test.jl files under test/,
excluding the :release-tagged heavy regression item.
Run only tests with the :fast tag directly:
julia --project=test -e 'using TestItemRunner; TestItemRunner.run_tests("test"; filter=ti -> :fast in ti.tags, verbose=true)'Run one tagged synthetic case directly:
julia --project=test -e 'using TestItemRunner; TestItemRunner.run_tests("test"; filter=ti -> :single_plate_direct in ti.tags, verbose=true)'Run one tagged fast fixture directly:
julia --project=test -e 'using TestItemRunner; TestItemRunner.run_tests("test"; filter=ti -> :simpleplant_16_toric in ti.tags, verbose=true)'Run the opt-in regression matrix against frozen Julia baselines:
julia --project=test test/regression_matrix/runtests.jlUseful regression-matrix controls:
# refresh in-repo baselines intentionally
ARCHIMEDLIGHT_REGRESSION_UPDATE=true julia --project=test test/regression_matrix/runtests.jl
# write reports somewhere else
ARCHIMEDLIGHT_REGRESSION_REPORT_DIR=/tmp/archimedlight-regression \
julia --project=test test/regression_matrix/runtests.jlARCHIMEDLIGHT_REGRESSION_FILTER accepts a comma-separated list of exact case ids from
regression_report.csv.
The regression harness writes a machine-readable CSV report at
test/regression_matrix/reports/latest/regression_report.csv by default and stores frozen
fast-profile baselines under test/regression_matrix/baselines/.
The dedicated synthetic cases are defined in test/synthetic-scenes-test.jl. Current case names include:
single_plate_direct, stacked_scattering, toricity_wraparound,
virtual_sensor_transparency, run_light_step_matches_staged,
cache_radiation_parity, cached_scattering_series_parity, and missing_models.
Fast fixture inputs/references are under test/fast_fixtures/ and are intended to be readable
as usage examples.
Generate or refresh fast fixture references (simple-plant numeric CSV + image):
julia --project=. scripts/generate_fast_fixture_references.jlHeavy full-fixture regression can be kept outside the package repository and run only before releases.
Build a data-only artifact tarball from a heavy fixture dataset checkout (fixtures + references + reference images + manifest):
julia --project=. scripts/build_release_fixture_artifact.jl \
--test-root /path/to/heavy-test-root \
--tarball /tmp/archimedlight-release-fixtures.tar.gzIf you want the current Julia outputs to become the packaged references first, refresh and package in one step:
julia --project=. scripts/build_release_fixture_artifact.jl \
--test-root /path/to/heavy-test-root \
--tarball /tmp/archimedlight-release-fixtures.tar.gz \
--refresh-referencesOptional: bind the artifact in Artifacts.toml by providing the download URL:
julia --project=. scripts/build_release_fixture_artifact.jl \
--test-root /path/to/heavy-test-root \
--tarball /tmp/archimedlight-release-fixtures.tar.gz \
--refresh-references \
--url https://github.com/ARCHIMED-platform/ArchimedLight.jl/releases/download/v0.0.1/archimedlight-release-fixtures-v0.0.1.tar.gzRun release-only heavy regression:
julia --project=test -e 'using TestItemRunner; TestItemRunner.run_tests("test"; filter=ti -> :release in ti.tags, verbose=true)'You can also bypass artifacts and point directly to a local extracted release dataset:
ARCHIMEDLIGHT_RELEASE_FIXTURES_DIR=/path/to/release-fixtures \
julia --project=test -e 'using TestItemRunner; TestItemRunner.run_tests("test"; filter=ti -> :release in ti.tags, verbose=true)'Run one release fixture directly by tag. Fixture ids are exposed as tags with - replaced by _:
ARCHIMEDLIGHT_RELEASE_FIXTURES_DIR=/path/to/release-fixtures \
julia --project=test -e 'using TestItemRunner; TestItemRunner.run_tests("test"; filter=ti -> :test_compare_simpleplant in ti.tags, verbose=true)'Release test scripts are local in this repository (test/release/) and consume only data from the
artifact/dataset. During release runs, per-fixture progress is logged with start/end timestamps.
The regression matrix also has an optional release profile that reuses the same dataset root:
ARCHIMEDLIGHT_REGRESSION_PROFILE=release \
ARCHIMEDLIGHT_RELEASE_FIXTURES_DIR=/path/to/release-fixtures \
julia --project=test test/regression_matrix/runtests.jlThe repository includes a separate benchmark project for AirspeedVelocity.jl under
benchmark/.
If you use ArchimedLight.jl in your work, please cite using the reference given in CITATION.cff.
