ArchimedLight.jl

API Reference​#

This page is a compact guide to the public entry points of ArchimedLight.jl. For normal use, create a triangle LightSimulation or import a VoxelScene, then call run_light.

API Stability​#

Version 0.1.3 is the compatibility baseline for ArchimedLight's first release in Julia's General registry. Earlier 0.1.x tags were development snapshots and are not covered by this stability promise. Starting with 0.1.3, the supported public API is the set of names exported by ArchimedLight. This includes the high-level simulation workflow, input readers, model helpers, validation helpers, user-facing result containers, attachment/output helpers, visualization helpers, and concrete backend selectors. Patch releases after 0.1.3 are expected to preserve those names; incompatible changes will use a new minor version and include migration notes.

Some lower-level stage functions are available as qualified calls such as ArchimedLight.compute_sky(...). These are intended for debugging, research, and parity work. They are not exported, and their exact arguments, return containers, and cache internals may be refined before 1.0.

LightStepResult and LightBudget are public result containers. It is fine to read their fields in analysis code, but user code should normally construct results through run_light rather than calling result constructors directly.

LightSimulation owns internal preparation and cache state. Use update_scene!, update_models!, update_options!, and cache_summary rather than relying on the cache field or cache object layout.

Main Workflow​#

File-based run:

The checked example uses 1 cm pixels for a quick demonstration. Omit the update_options! call to use the bundled coffee configuration's 1 mm pixels.

julia
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))
step = run_light(sim, first(meteo))
series = run_light(sim, meteo);
2-element Vector{LightStepResult}: LightStepResult(PAR=3.669 MJ, NIR=8.563 MJ, sky=463.139 W m^-2 PAR / 501.734 W m^-2 NIR, sectors=46 [sky=46, sun=0], scattering=12 iters, converged) LightStepResult(PAR=3.598 MJ, NIR=8.641 MJ, sky=448.777 W m^-2 PAR / 486.175 W m^-2 NIR, sectors=46 [sky=46, sun=0], scattering=12 iters, converged)

Interactive run. This is a schematic API shape because it assumes that the host application provides plant.opf and meteo_row; see the checked Interactive Workflow page for a fully executable in-memory scene.

julia
using PlantGeom

scene = make_scene(domain=(-1.0, -1.0, 1.0, 1.0)) do s
    add_plant!(s, "plant.opf"; group="coffee", id=1)
    add_ground!(s; group="soil", type="ground")
end

models = models_for(
    "coffee" => ("Leaf" => translucent(par=0.15, nir=0.90),),
    "soil" => ("ground" => opaque(par=0.10, nir=0.40),),
)

sim = LightSimulation(scene, models; options=LightOptions())
step = run_light(sim, meteo_row)

For host-model coupling, this schematic loop assumes your host model provides the meteo rows and a later new_scene:

julia
for row in rows
    light = run_light(sim, row)
end

update_scene!(sim, new_scene)

Input Loading​#

Use these when your workflow starts from files or existing tables. The names in this block are schematic placeholders for your actual paths and tables.

julia
read_simulation(path)
read_scene(path)
read_models(path_or_paths)
read_options(path)
read_meteo(path_or_table)

Scene And Model Helpers​#

Use these when inputs are built in Julia. This block lists call signatures and is intentionally schematic.

julia
PlantGeom.make_scene(f; domain, source_path="interactive.scene", kwargs...)
PlantGeom.add_plant!(builder, mtg_or_path; group, id, at=(0, 0, 0), scale=1.0, rotate=(0, 0, 0))
PlantGeom.add_object!(builder, mtg_mesh_or_path; group, id, type="object", at=(0, 0, 0), scale=1.0, rotate=(0, 0, 0))
PlantGeom.add_ground!(builder; z=0.0, nx=9, ny=9, group="pavement", type="Cobblestone")
PlantGeom.prepare_scene(mtg; source_path="interactive.opf", scene_xy_bounds=nothing, relabel_ids=false)
models_for(group => (type => model, ...), ...)
translucent(; par, nir, transparency=0.0)
opaque(; par, nir)
virtual_sensor()
emitter(; radiance, par=0.48, nir=0.52)

PlantGeom.add_plant! is the plant-named wrapper. PlantGeom.add_object! is the general placement helper for MTGs, GeometryBasics meshes, .opf, and .gwa files. For mesh files such as .obj or .ply, use MeshIO/FileIO to load the mesh first. This snippet is schematic because it depends on your local mesh file and builder:

julia
using FileIO, MeshIO
mesh = load("sensor.obj")
PlantGeom.add_object!(builder, mesh; group="sensor", type="panel", id=10)

Both placement helpers accept at, scale, rotate, deg, and OPS-style rotation, inclination_azimut, and inclination_angle placement keywords.

Tuple rotations use fixed X, then Y, then Z order. This is a schematic call shape:

julia
PlantGeom.add_object!(builder, mesh; group="sensor", type="panel", id=10, rotate=(10, 20, 30), deg=true)

Named-tuple rotations preserve the field order, which is useful when you need a specific Euler sequence. This is a schematic call shape:

julia
PlantGeom.add_object!(builder, mesh; group="sensor", type="panel", id=10, rotate=(y=20, z=30, x=10), deg=true)

PlantGeom.add_ground! and write_scene also work on prepared scenes. This snippet is schematic because it assumes an existing scene and output path:

julia
PlantGeom.add_ground!(scene; z=0.0, nx=9, ny=9, xy_bounds=nothing, group="pavement", type="Cobblestone")
write_scene(path, scene)

Voxel Inputs And Individual/Organ Mixtures​#

The Belinga example provides a downloadable voxel dataset of a natural forest in Cameroon. example_scene_path(:belinga) returns its input filename in Julia's artifact cache. The first call downloads the approximately 17 MB, checksummed Zenodo archive; subsequent calls reuse it. Importing ArchimedLight does not download the dataset. Missing-PAD handling remains an explicit choice when reading:

julia
scene = read_scene(example_scene_path(:belinga);
    missing_pad=:zero_with_diagnostics)

Use the dedicated Voxel Modelling section for workflows and model assumptions. Read observations first, then configure materials separately from geometry and numerical options:

julia
scene = read_scene(path; missing_pad=:error)
add_ground!(scene) # optional flat geometry, with the voxel domain extent
leaf = VoxelMaterial(; g=0.5, par_reflectance=0.08, par_transmittance=0.07,
                     nir_reflectance=0.45, nir_transmittance=0.40)
models = models_for(;
    mode=:point_proxy, leaf,
    wood=VoxelMaterial(), unknown=VoxelMaterial(),
    ground=opaque(par=0.12, nir=0.32),
    individuals=Dict(12 => (leaf=(nir_reflectance=0.40,),)),
)
result = run_light(scene, meteo_row; models, options, backend=VoxelCPUBackend())
series = run_light(scene, meteo; models, options, backend=VoxelCPUBackend())
values = light_metric_values(scene, result, :incident_par_flux)
totals = component_values(result; level=:individual)

This is a call-shape reference: path, forcing and options must be defined, and individual 12 must occur in the JSONL input. Global leaf, wood, unknown and ground defaults apply unless overridden. A partial named tuple changes only its supplied parameters, inheriting the rest from the matching default. A complete VoxelMaterial instead replaces that individual's whole organ material. Global leaf may be omitted when every relevant individual has a complete leaf material; a partial override requires its global default. Unknown IDs, missing material coverage and invalid coefficients are rejected.

A plain .vox scene uses the global leaf material for all PAD, with no mode or individual overrides. JSONL inputs require explicit mode=:point_proxy to allocate PAD from point proportions. Wood transmittance must be zero. Unassigned vegetation retains its own unknown default and its individual_id=nothing in aggregated outputs.

update_models!(scene, models) retains the configuration for later calls without a models keyword; supplying models directly does not mutate the scene. Use check_models(scene, models) or check_simulation(scene, meteo; models, options, backend) to validate it. VoxelScene(grid; terrain) and VoxelScene(input; terrain) wrap an existing PAD grid or imported VoxelPointInput without assigning materials. missing_pad=:zero_with_diagnostics is the opt-in alternative for unknown densities.

VoxelVegetationStepResult keeps the native result in .light, sparse VoxelComponentLight records in .components, and the original .missing_pad mask. Energies are joules per step; component plant_area is proxy area in square metres.

See One Tree, Two Input Files for runnable defaults, individual-only models and soil-region overrides, and Input Files And Individuals for coverage assumptions.

Voxel Terrain API​#

add_ground!(scene) creates a horizontal plane covering the voxel domain at its minimum Z. Optional elevation, cells=(nx, ny) and material_ids control geometry and soil region identities. LightOptions.toricity controls horizontal wrapping for the canopy and ground together. Assign soil optics through models_for(; ground=soil, ...) at simulation setup. Use ground=opaque(par=..., nir=...) for soil: par/nir are reflectances, transmission is zero and absorption is 1 - reflectance. ground_materials=Dict(id => override) accepts complete TypeModel values from opaque, or partial (par=..., nir=...) named tuples inheriting the global ground coefficients. Without a global ground default, complete soil models must cover every used material ID. Tree IDs and soil material IDs are independent. The validated band coefficients are copied when the voxel configuration is built. See the runnable Ground Materials example.

ground_materials is a voxel-only keyword. Triangle scenes assign the same soil TypeModel through their ordinary group/type mapping, for example models_for("soil" => ("ground" => soil,)). Both ground workflows reflect into the outward hemisphere. Triangle Opaque supports terrain made of nonvertical facets, orienting normals upward independently of input winding. Group/type names are arbitrary; the geometry must represent terrain, not an arbitrary closed or vertical opaque object. Both PAR/NIR coefficients must be explicit. translucent(..., transparency=0) still has diffuse transmission and is not an opaque ground model.

Use NoVoxelTerrain() for an open bottom, or attach imported geometry:

julia
terrain = read_ply_terrain(ply_path; transform=dtm_to_voxel)
add_ground!(scene, terrain)

The transform defaults to identity. No CRS conversion, translation, or domain extension is inferred; triangulated PLY terrain requires toricity=false.

All terrain constructors and readers create geometry and material IDs. Soil optics are supplied only through the voxel model configuration. AAIGrid and AMAPVox import/alignment workflows use:

julia
aai = read_aai_grid(dtm_path; crs="EPSG:2154")
terrain = height_field_terrain(aai; transform=dtm_to_voxel)
input = VoxelTerrainSceneInput(voxel_path, dtm_path; dtm_to_voxel=dtm_to_voxel)
imported = read_voxel_terrain_scene(input)
scene = VoxelScene(imported.grid; terrain=imported.terrain)

See Voxel Terrain And Soil for coordinate, coverage, periodicity, ground_distance, cache, and energy-balance contracts.

Validation​#

Use these to diagnose inputs before running. This block is schematic and uses placeholder inputs already introduced above:

julia
check_scene(scene)
check_models(scene, models)
check_meteo(meteo; options=LightOptions())
check_simulation(sim)
check_simulation(scene, meteo; models, options=LightOptions())
summarize_scene(scene; models=nothing)
summarize_meteo(meteo; options=LightOptions())

Each function returns a ValidationReport with errors, warnings, and infos. Meteo-aware checks and execution methods accept check_boundaries=false as a one-call override; derivability, duration, and finite-input consistency checks remain enabled.

The summarize_* helpers return structured summaries and print compact diagnostics for humans. Use them when you are not sure what ArchimedLight sees. This schematic snippet assumes scene, models, meteo, and options exist:

julia
summarize_scene(scene; models=models)
summarize_meteo(meteo; options=options)

Simulation Cache​#

LightSimulation owns preparation and cache state. These helpers update inputs and invalidate cached data. This block is schematic and assumes existing replacement inputs:

julia
update_scene!(sim, scene)
update_models!(sim, models)
update_options!(sim, options)
cache_summary(sim)

update_scene! immediately releases old scene-dependent prepared data and cache entries. The cache object itself is an implementation detail; use cache_summary(sim) when you need to inspect cache behavior. Its node_metadata_count and node_metadata_bytes fields report the size of the current lightweight metadata snapshot independently of the radiation-response cache budget.

Backend Selection​#

The default backend is the CPU raster path:

julia
step = run_light_step(scene, models, row, options; interception_backend=:raster_cpu)

The native KernelAbstractions raster backend keeps the same public pipeline while running projection, visibility-stack, area-reduction, and scattering-topology kernels on the selected device:

julia
using CUDA
using KernelAbstractions

dev = KernelAbstractions.get_backend(CUDA.CuArray(zeros(Float32, 1)))
ib = RasterGPUBackend(
    backend=dev,
    max_hits_per_pixel=128,
    tile_size=1,
    tile_face_capacity=128,
    edge_accumulation=:auto,
)
sb = RasterGPUScatteringBackend(ib)

step = run_light_step(
    scene,
    models,
    row,
    options;
    interception_backend=ib,
    scattering_backend=sb,
)

GPU packages are intentionally not hard dependencies of ArchimedLight. Load Metal, CUDA, oneAPI, or AMDGPU in the calling environment and pass the corresponding KernelAbstractions backend. The symbol :raster_gpu selects the same implementation with the KernelAbstractions CPU backend and is useful for portable validation.

Full-stack projections keep up to max_hits_per_pixel=32 hits per projected pixel by default. Dense scenes can raise that value explicitly, with proportionally larger device buffers. edge_accumulation=:auto selects dense atomics when the backend supports them and the dense edge matrix fits; otherwise it uses counted sparse keys with host reduction.

Optional Metal validation​#

Metal.jl remains outside the main package dependencies. The dedicated GPU test environment requires Metal 1.10.3 or newer, the first registered release with the atomic support needed by the RasterGPU kernels:

sh
julia --project=test/gpu -e 'using Pkg; Pkg.instantiate()'
ARCHIMEDLIGHT_TEST_METAL=required julia --project=test/gpu test/gpu/runtests.jl

The RasterGPU atomic kernels use :monotonic ordering. They need working Metal/KernelAbstractions atomics, but do not require acquire/release semantics. Metal.jl reports that capability only when the active toolchain provides MSL 4.1 or newer; installing Metal.jl 1.10.3 does not by itself upgrade the system toolchain. The focused suite verifies atomic availability and compares first-order and scattering results against the CPU raster reference.

Use ARCHIMEDLIGHT_TEST_METAL=1 to skip gracefully when Metal is unavailable, or required to make missing hardware, drivers, or packages fail the run.

Warning

RasterGPUBackend is experimental. Validate new devices and large scenes against RasterCPUBackend, and size max_hits_per_pixel, tile_size, and tile_face_capacity for the target geometry.

Advanced Light Pipeline​#

The explicit stage API is available as qualified, advanced API for debugging, research, and parity workflows. These calls may return stage containers such as ArchimedLight.TurtleGrid or ArchimedLight.FirstOrderResult, which are not exported in 0.1.x. This block is schematic because it assumes all previous stage inputs have already been prepared:

julia
ArchimedLight.compute_sky(row, options)
ArchimedLight.build_turtle(options, sky)
ArchimedLight.compute_directional_fluxes(row, sky, turtle, options)
ArchimedLight.compute_first_order(scene, models, turtle, fluxes, options)
ArchimedLight.compute_scattering(scene, models, turtle, first, options)
ArchimedLight.integrate_light(scene, models, first, scattering, options; meteo_row=row)

These functions and stage containers are intentionally not exported in 0.1.x. Prefer run_light for application code unless you need to inspect or replace individual pipeline stages.

For interactive synthetic scenes, run_light also accepts a prebuilt sky state. This block is schematic because it assumes an existing scene, models, options, and sky:

julia
sim = LightSimulation(scene, models; options=options)
step = run_light(sim, sky; step_duration_seconds=1800.0)

A vector of sky states runs as a series. Supply either one duration shared by all states or one duration per state:

julia
series = run_light(sim, skies; step_duration_seconds=1800.0)
series = run_light(sim, skies; step_duration_seconds=[900.0, 1800.0, 900.0])

The result order matches skies, and the prepared scene and radiation cache are reused throughout the series.

The low-level cache functions are still available for advanced work, but their cache object layout is internal. This block is schematic and uses placeholders:

julia
cache = ArchimedLight.prepare_light_cache(scene, models, options; ...)
ArchimedLight.run_light_step(cache, meteo_row)
ArchimedLight.run_light_series(cache, meteo)

prepare_light_cache uses a tiered policy internally:

  • :full keeps all seen turtle responses in memory

  • :partial keeps a bounded LRU cache for large moving-sun series

  • :topology_fallback reuses prepared geometry/topology only when a full-response cache would be too large or unsupported

Scene Attachment Helpers​#

These functions attach computed values back onto the MTG using ARCHIMED attribute names. This block lists schematic call shapes:

julia
attach_node_values!(scene, attr, values; fill_value=nothing)
attach_light_step!(scene, step; fields=[:incident_par_flux], names=Dict(), fill_value=nothing)
attach_light_series!(scene, steps; fields=[:incident_par_flux], names=Dict(), fill_value=NaN)

attach_light_step! attaches scalar values for one step. attach_light_series! attaches Vector{Float64} values, ordered like steps, for each selected field. The supported fields selectors are:

SelectorDefault MTG attribute
:areaarea
:incident_par_initial_fluxRi_PAR_0_f
:incident_nir_initial_fluxRi_NIR_0_f
:incident_par_fluxRi_PAR_f
:incident_nir_fluxRi_NIR_f
:incident_par_initial_energyRi_PAR_0_q
:incident_nir_initial_energyRi_NIR_0_q
:incident_par_energyRi_PAR_q
:incident_nir_energyRi_NIR_q
:absorbed_par_initial_fluxRa_PAR_0_f
:absorbed_nir_initial_fluxRa_NIR_0_f
:absorbed_par_fluxRa_PAR_f
:absorbed_nir_fluxRa_NIR_f
:absorbed_shortwave_fluxRa_SW_f (Ra_PAR_f + Ra_NIR_f)
:absorbed_par_initial_energyRa_PAR_0_q
:absorbed_nir_initial_energyRa_NIR_0_q
:absorbed_par_energyRa_PAR_q
:absorbed_nir_energyRa_NIR_q
:sky_fractionsky_fraction

For :area, attach_light_step! attaches one scalar surface area per node, while attach_light_series! repeats that area once per step so the attached attribute has the same vector shape as the light fields.

Use names=Dict(selector => attr) to override a default attribute name. Ra_SW_f always contains absorbed PAR plus absorbed NIR. The historical Dict(:absorbed_nir_flux => :Ra_SW_f) mapping remains accepted and now produces that canonical sum, with a deprecation warning; request :absorbed_shortwave_flux in new code.

Scene-Aware Light Queries​#

Use these functions to select geometric nodes and query a step or complete series without first attaching values to the MTG:

julia
light_node_ids(
    scene_or_sim;
    node_ids=nothing,
    source_topology_id=nothing,
    group=nothing,
    species=nothing,
    object_id=nothing,
    symbol=nothing,
    scale=nothing,
    type=nothing,
    attributes=NamedTuple(),
    inherit_attributes=false,
    where=nothing,
)

light_metric_values(
    scene_or_sim,
    step_or_series,
    selector;
    # the same node filters
    reduce=nothing,
    by=nothing,
    sink=nothing,
)

The default result is a Tables.jl-compatible long-form column table. A reducer without by returns one scalar for a step or one value per timestep for a series. A reducer with by returns a grouped table. Native selectors and ARCHIMED names are interchangeable, for example :absorbed_par_energy and :Ra_PAR_q.

group/species and object_id inherit metadata from scene object roots. Arbitrary attributes are node-local unless inherit_attributes=true. Explicit node_ids are runtime identifiers in one prepared scene. source_topology_id identifies the source topology component; combine it with object_id when the same source plant is instantiated more than once.

Results retain a lightweight metadata snapshot by default. Consequently, light_metric_values(sim, dynamic_series, ...) applies semantic filters to each step's own scene version after update_scene!, even when runtime node ids changed. light_node_ids(step; ...) queries one retained snapshot directly.

Configure retention with:

julia
LightOptions(
    store_node_metadata=true,
    node_metadata_attributes=(:organ_id, :cohort),
)

Extra attributes must be lightweight scalar values. Set store_node_metadata=false to minimize retained results; those results must be queried with their original live scene. A custom where predicate also needs a live MTG, so dynamic-series queries should capture its scalar inputs through node_metadata_attributes and use attributes=(...) instead.

Voxel Metric Queries​#

julia
light_metric_values(
    scene_or_grid, result_or_series, selector;
    domain=:vegetation,
    individual_id=nothing, organ=nothing,
    voxel_i=nothing, voxel_j=nothing, voxel_k=nothing,
    patch_id=nothing, material_id=nothing,
    where=nothing, reduce=nothing, by=nothing, sink=nothing,
)

The default vegetation table contains step_number, voxel_i, voxel_j, voxel_k and value. Individual/organ filters, or by containing :individual_id, :organ or :plant_area, select attributed component rows with those extra identity and proxy-area columns. This requires a mixed JSONL result. Positive integer individual IDs remain Int; nonempty text IDs remain String. Each input and scene uses one concrete ID type, and model override keys and query filters must match that type. Mixed integer/text IDs are rejected. IDs are labels rather than array indices; text "001" is preserved. individual_id=(nothing,) selects unattributed vegetation. Organ values are :leaf, :wood and :unknown.

domain=:ground returns step_number, patch_id, material_id, surface_area and value. Ground queries accept patch/material filters; vegetation queries accept voxel/individual/organ filters. Combining filters from these two domains is an error. where receives a row of the selected table: use an individual/organ filter or grouping before accessing those component columns in a predicate. The reducer, grouping and sink rules above also apply to these tables.

Ground flux is normalized by stored patch surface area (the full facet area, not its horizontal projection or its clipped overlap with the voxel domain) and step duration. Vegetation flux uses plant area. The default voxel query therefore excludes ground: obtain both energy totals explicitly for a whole-scene budget.

Visualization Helpers​#

These helpers expose direct mesh coloring and the Makie package extension. This block lists schematic call shapes:

julia
light_render_geometry(scene, models, options)
light_render_geometry(step)
light_render_geometry(steps)
tile_light_geometry(scene, geometry; nx=1, ny=1, centered=true, xperiod=nothing, yperiod=nothing)
tile_light_geometry(scene, step; nx=1, ny=1, centered=true, xperiod=nothing, yperiod=nothing)
tile_light_geometry(scene, steps; nx=1, ny=1, centered=true, xperiod=nothing, yperiod=nothing)
tile_light_geometry(scene, models, options; nx=1, ny=1, centered=true, xperiod=nothing, yperiod=nothing)
light_metric_values(step, selector)
light_metric_values(steps, selector; timestep=1)
light_face_values(data; color=:incident_par_flux, timestep=1, fill_value=NaN)
light_vertex_values(data; color=:incident_par_flux, timestep=1, fill_value=NaN)
light_face_values(scene, models, options, data; color=:incident_par_flux, timestep=1, fill_value=NaN)
light_vertex_values(scene, models, options, data; color=:incident_par_flux, timestep=1, fill_value=NaN)
lightplot(geometry, data; color=:incident_par_flux, timestep=1, interpolate=false, ...)
lightplot!(axis, geometry, data; color=:incident_par_flux, timestep=1, interpolate=false, ...)
lightplot(step; color=:incident_par_flux, timestep=1, interpolate=false, ...)
lightplot(steps; color=:incident_par_flux, timestep=1, interpolate=false, ...)
lightplot!(axis, step; color=:incident_par_flux, timestep=1, interpolate=false, ...)
lightplot!(axis, steps; color=:incident_par_flux, timestep=1, interpolate=false, ...)
lightplot(scene, models, options, data; color=:incident_par_flux, timestep=1, interpolate=false, ...)
lightplot!(axis, scene, models, options, data; color=:incident_par_flux, timestep=1, interpolate=false, ...)
lightplot(voxel_scene_or_grid_or_vegetation; ...)
lightplot(voxel_scene_or_grid_or_vegetation, result; color=:incident_par_flux, timestep=1, ...)
lightplot!(axis, voxel_scene_or_grid_or_vegetation, result; color=:incident_par_flux, timestep=1, ...)
lightplot(grid, terrain; rays=(), quadrature=nothing, cutaway=:none, ...)
lightplot!(axis, grid, terrain; rays=(), quadrature=nothing, cutaway=:none, ...)
light_metric_values(voxel_scene_or_grid_or_vegetation, result_or_series, selector)

For lightplot(steps; colorrange=automatic), the Makie extension keeps a single color range across the whole series so animated timesteps remain comparable.

lightplot without results renders nonzero PAD for a plain grid or organ composition for a point-input/vegetation scene. Supplying a voxel light result uses the same selector vocabulary as triangle plots, with fixed series colour ranges. Absorption selectors require an output that retains absorption; first-order scalar-grid results alone cannot reconstruct material absorption. Use the original PAD grid and terrain geometry for voxel queries and plots; results retain their resolved materials, but do not snapshot edits to geometry arrays. Voxel values remain constant within each cell, so the triangle interpolate option does not apply.

For geometric diagnostics, lightplot(grid, terrain; ...) renders the terrain mesh and optional ray paths computed by trace_voxel_ray. Supplying a VoxelScatteringQuadrature adds the fixed-direction Lambertian redistribution at each terrain hit. Use cutaway=:front to inspect the interior without changing the source VoxelGrid. Pass the simulation's options to use the same LightOptions.toricity for displayed ray paths. Plotting defaults to LightOptions(toricity=false).

Backend Types​#

The current public backend selectors are:

This block lists schematic constructor calls:

julia
RasterCPUBackend()
RaycastScatteringBackend()
VoxelCPUBackend(; rays_per_voxel=1000, g=0.5, traversal=:dda)

They can be passed explicitly to the pipeline helpers when you want to control the implementation used for interception or scattering. Only the concrete selectors are exported in 0.1.x; the backend supertypes are internal, and defining new backend subtypes is not yet a supported extension interface.

  • read Getting Started for the shortest runnable workflow

  • read Composable Stages if you want the stage-by-stage pattern

  • read Outputs for the mapping between LightBudget fields and ARCHIMED attribute names

API List​#

ArchimedLight.VOXEL_NIR_WAVELENGTH_NMConstant

Operational voxel NIR wavelength interval in nanometres.

source
ArchimedLight.VOXEL_PAR_WAVELENGTH_NMConstant

Operational voxel PAR wavelength interval in nanometres.

source
ArchimedLight.AAIGridType

Parsed Arc/Info ASCII Grid with cell-centre elevations in increasing x/y order.

source
ArchimedLight.AbstractVoxelTerrainType

Abstract interface for a surface that bounds voxel transport from below.

source
ArchimedLight.CompiledComponentAggregationType
julia
CompiledComponentAggregation(metadata)

Precompute the stable component-to-owner mapping used to aggregate one LightStepResult from radiative scene components to botanical PlantGeom.SourceOwnerKeys.

Component energy quantities (*_q) are summed. Component flux densities (*_f) are averaged with radiative_area as weight. The mapping is valid only for results sharing the exact LightComponentMetadata snapshot used at construction; rebuild it after update_scene!.

source
ArchimedLight.EmitterModelType
julia
EmitterModel(; model="LambertianEmitter", radiance=0.0, gamma=OpticalProperties(0.48, 0.52), extras=...)

Emission model for artificial or diagnostic light sources.

radiance is the Lambertian radiance L emitted by each matching surface, in power per emitting area per steradian (for example W m^-2 sr^-1). gamma contains independent dimensionless spectral coefficients which are applied exactly as configured; they are not normalized to sum to one. PAR and NIR use the named fields, while additional coefficients in gamma.extras retain their uppercased band names. For a component with emitting surface area A, the hemispherical power in band b is pi * A * L * gamma[b]. The current model is one-sided and assumes a horizontal surface emitting into the downward hemisphere.

Most canopy simulations do not need explicit emitters and rely only on sky and sun forcing, but emitters are useful for artificial lighting setups, synthetic tests, or debugging scenes.

source
ArchimedLight.GroundDistanceAlignmentType

Alignment diagnostics between a terrain and AMAPVox ground_distance.

source
ArchimedLight.GroupModelType
julia
GroupModel(group; types=OrderedDict(), extras=...)

Model definition for one functional group, keyed by component type.

group should match the functional-group name carried by the prepared scene geometry, for example "coffee" or "pavement".

types maps scene type names such as "Leaf", "Metamer", or "Cobblestone" to TypeModel values.

Wildcard support:

  • an exact type key such as "Leaf" applies only to that type

  • the wildcard key "*" acts as a fallback for any type in the group

This allows compact interactive setups such as:

julia
GroupModel(
    "coffee";
    types=OrderedDict(
        "*" => TypeModel(
            interception=InterceptionModel(
                model="Translucent",
                optical_properties=OpticalProperties(0.15, 0.90),
            ),
        ),
        "Stem" => TypeModel(
            interception=InterceptionModel(
                model="Translucent",
                transparency=0.2,
                optical_properties=OpticalProperties(0.10, 0.50),
            ),
        ),
    ),
)

In that example, all coffee components use the wildcard model except "Stem", which is overridden explicitly.

source
ArchimedLight.HeightFieldTerrainType
julia
HeightFieldTerrain(x, y, elevation; diagonal=:southwest_northeast)

Triangulated height samples at increasing vertex coordinates x and y. Every cell uses the fixed southwest-to-northeast diagonal. A BVH is built once and reused. A simulation with LightOptions(toricity=true) tiles the height field horizontally and requires matching elevations on opposite edges. Soil models are assigned at simulation setup.

source
ArchimedLight.InterceptionModelType
julia
InterceptionModel(; model="Translucent", sensor=false, transparency=0.0, optical_properties=nothing, use=nothing, variants=..., extras=...)

Radiative behavior attached to one scene component type.

This stores the interception model name, transparency, optical coefficients, and optional named variants from historical ARCHIMED model files.

Main fields:

  • model: interception model name. "Translucent" uses the historical equal reflection/transmission approximation; "Opaque" reflects toward the exterior of an oriented ground surface.

  • sensor: marks the type as a virtual sensor. Virtual sensors receive light diagnostics but remain transparent in the transfer logic.

  • transparency: first-order transmitted fraction. Typical values are in [0, 1], with 0.0 for fully intercepting surfaces and larger values for partially transmitting components.

  • optical_properties: waveband-dependent scattering coefficients used by the scattering stage.

  • use and variants: preserved support for historical YAML files that define several named parameter sets under one Interception block.

Typical pattern for ordinary canopy elements:

InterceptionModel(model="Translucent", transparency=0.0, optical_properties=OpticalProperties(0.15, 0.90))

source
ArchimedLight.LightBudgetType
julia
LightBudget

Per-node light budget for one simulation step, storing incident and absorbed fluxes and energies for PAR, NIR, and optional extra wavebands. Escaped artificial-emitter energy is stored by waveband and emitting source node.

source
ArchimedLight.LightComponentMetadataType
julia
LightComponentMetadata

Compact identity snapshot for the geometric components used by one prepared light scene.

node_id, source_owner, and radiative_area are aligned columns. The owner is the durable PlantGeom.SourceOwnerKey assigned during scene assembly. radiative_area is the filtered mesh area used by ArchimedLight to normalize component fluxes; it is deliberately distinct from a generic botanical or projected area.

When a scene exposes complete PlantGeom source ownership, every result produced from one light cache shares the same snapshot. Results therefore remain attributable after update_scene! replaces the live scene. Historical scenes without source ownership remain simulatable, but cannot be exported with component_values.

source
ArchimedLight.LightModelsType
julia
LightModels(groups)

Top-level collection of all functional-group models used by one simulation.

This is the object passed to run_light_step, run_light_series, and the lower level interception/scattering functions.

groups maps functional-group names to GroupModel values. The special group key "*" is also supported as a global fallback.

When the solver resolves a scene node (group, type), it matches models with the following precedence:

  1. exact group and exact type

  2. exact group and wildcard type "*"

  3. wildcard group "*" and exact type

  4. wildcard group "*" and wildcard type "*"

This makes LightModels convenient for:

  • precise canopy parameterizations with several plant groups

  • synthetic scenes where one default model should cover many node types

  • gradual interactive workflows where you start with fallback models and then add explicit overrides

Examples:

  • LightModels([GroupModel("coffee"; ...), GroupModel("pavement"; ...)])

  • LightModels([GroupModel("*"; types=OrderedDict("*" => default_type_model))])

source
ArchimedLight.LightNodeMetadataType
julia
LightNodeMetadata

Lightweight columnar scene-node metadata captured for one prepared scene. Every LightStepResult computed from the same simulation cache shares the same snapshot, so semantic queries remain valid after update_scene!.

attributes stores requested node-local scalar attributes, while inherited_attributes stores their nearest inherited values.

source
ArchimedLight.LightOptionsType
julia
LightOptions

Runtime controls for interception, scattering, and caching.

Fields:

  • all_in_turtle: if false, keep the direct beam as a separate sun sector; if true, redistribute it into the turtle sectors.

  • turtle_sectors: number of diffuse sky sectors. Common values are 16, 46, or denser grids for smoother angular resolution.

  • pixel_size: requested ground resolution in meters, with either toricity setting. Periodic scenes fit horizontal raster cells to the plot dimensions. Finite scenes use a perpendicular projection plane whose pixels back-project to squares of this side length on horizontal ground, rotated with light azimuth. Smaller values increase sampling density, runtime, and memory use. ARCHIMED YAML files express this value in centimetres.

  • area_ratio: enable the ARCHIMED projected-area correction used for parity with the historical implementation.

  • scattering: enable multiple scattering after first-order interception.

  • scattering_max_iter: maximum number of scattering iterations.

  • scattering_stop_ratio: stop scattering when the current scattered energy is below this fraction of the initial intercepted energy.

  • scattering_coeff_par: fallback PAR scattering coefficient when the model has no PAR OpticalProperties.

  • scattering_coeff_nir: fallback NIR scattering coefficient when the model has no NIR OpticalProperties.

  • cache_radiation: reuse directional responses across series steps when possible.

  • include_sky_fraction: store the per-node sky_fraction map in each LightStepResult. Leave false unless downstream code needs it. ArchimedLightModel enables this automatically for its organ outputs. When options are read from a config file, this is enabled by requesting sky_fraction in component_variables or opf_variables.

  • store_node_metadata: retain a lightweight per-scene node metadata snapshot shared by all results computed from that scene. This keeps queries valid after update_scene!.

  • node_metadata_attributes: optional scalar MTG attribute names to add to the standard node metadata snapshot. Standard identity and group/type fields are always included when store_node_metadata=true.

  • cache_pixel_table: cache raster pixel tables for repeated projections.

  • pixel_hit_stack_mode: typed PixelHitStackPolicy used for per-pixel hit stacks. The default is AutoPixelHitStack.

  • toricity: enable horizontal periodic wrapping of the simulated plot, including voxel terrain. Periodic height fields must have matching elevations on opposite edges; generic triangle terrain supports open boundaries only. For triangle scenes, true projects onto the horizontal plot tile; with false, each light direction uses a fitted perpendicular canvas covering the complete scene geometry, so elevated objects are not clipped by the plot footprint. This changes neither the physical ground geometry nor the units of pixel_size. For voxel grids, true wraps rays horizontally and false terminates them at the first lateral grid boundary.

  • radiation_timestep_minutes: internal radiative substep used when a meteo row covers a coarser interval.

  • radiation_input_semantics: interpretation of supplied irradiances. :interval_mean preserves the supplied full-timestep mean, while :sunlit_intensity treats it as an intensity that applies only while the sun is above the horizon (the historical Java behavior).

  • scene_rotation_deg: clockwise rotation, in degrees, from geographic north to the scene's local coordinates. The geographic sun is transformed into the fixed local turtle basis before directional weights are computed.

  • check_meteo_boundaries: validate physical ranges for meteorological inputs before preparation or execution. Derivability and conflicting-input checks remain enabled even when this is false.

  • allow_overlapping_meteo_steps: keep overlapping meteo intervals instead of rejecting the series during meteo preparation.

  • nir_interception: include NIR in directional fluxes and first-order interception.

  • nir_scattering: include NIR in the multiple-scattering stage. This has no effect if nir_interception=false.

  • meteo_range: optional historical range selector applied during meteo preparation, for example "2, 5" or a datetime range.

Typical starting point for simple runs:

LightOptions(turtle_sectors=46, pixel_size=0.0025, scattering=true)

To force a raster storage policy from Julia, use for example LightOptions(pixel_hit_stack_mode=SmallPixelHitStack). Configuration-file strings are parsed by read_options before runtime.

Constructor keywords are the field names above. LightOptions(old; kwargs...) copies an existing options value and overrides only the supplied keywords.

source
ArchimedLight.LightRenderGeometryType
julia
LightRenderGeometry

Render-ready geometry used to visualize one simulated light result.

This stores the exact face subset and face-to-node mapping used by the light solver after model-dependent filtering has been applied.

source
ArchimedLight.LightSimulationMethod
julia
LightSimulation(scene, models; options=LightOptions(), kwargs...)

Create a reusable light simulation. Expensive geometry preparation and radiation caches are built lazily by run_light.

Arguments:

  • scene: prepared PlantGeom.SceneGeometry used by the solver.

  • models: model specification accepted by prepare_models.

Keywords:

  • options: LightOptions controlling interception, scattering, and caching.

  • interception_backend: interception backend selector or backend instance. The default is :raster_cpu.

  • scattering_mode: scattering algorithm selector. The default is :raycast.

  • scattering_backend: optional scattering backend instance.

  • memory_limit_bytes: optional limit for resident directional-response cache data. nothing uses the package default.

source
ArchimedLight.LightStepResultType
julia
LightStepResult

Complete result of one light simulation step, including the sky state, turtle, directional fluxes, first-order interception, optional scattering, and the integrated LightBudget. When requested, the result can also store a per-node sky_fraction map. Results returned by run_light_step and run_light_series also carry the render geometry needed by lightplot. When the scene exposes complete source ownership, they retain a shared LightComponentMetadata snapshot for stable source-owner attribution. Historical unattributed scenes keep this field as nothing. By default results also retain the broader LightNodeMetadata snapshot used by scene-aware queries across dynamic scene updates.

source
ArchimedLight.MeteoSummaryType
julia
MeteoSummary

Structured meteo overview returned by summarize_meteo.

source
ArchimedLight.NoVoxelTerrainType

Open lower boundary: rays may leave the voxel grid through :bottom.

source
ArchimedLight.OpticalPropertiesType
julia
OpticalProperties(par=0.0, nir=0.0)

Per-waveband scattering coefficients for a component or emitter.

par and nir are the built-in ARCHIMED bands. Additional coefficients can be stored in extras.

For plant components, these coefficients are typically used as the scattered fraction in each waveband. Expected values are usually in [0, 1].

Typical values:

  • leaves in PAR: often around 0.05-0.25

  • leaves in NIR: often around 0.4-0.9

  • emitters: gamma often uses PAR=0.48, NIR=0.52 as independent spectral coefficients for the built-in bands; the values are not normalized

Examples:

  • OpticalProperties(0.15, 0.90) for a strongly NIR-scattering leaf

  • OpticalProperties(0.48, 0.52) for PAR/NIR emitter coefficients

source
ArchimedLight.PixelHitStackPolicyType
julia
PixelHitStackPolicy

Typed execution policy for the rasterizer's per-pixel hit-stack storage.

  • AutoPixelHitStack lets the rasterizer select the validated storage for the current projection size.

  • SmallPixelHitStack forces compact inline storage.

  • VectorPixelHitStack forces the historical vector storage.

ARCHIMED configuration files keep the portable text values auto, small, and vector; read_options converts those values to this type before the simulation reaches the raster runtime.

source
ArchimedLight.PlanarTerrainType
julia
PlanarTerrain(grid; elevation=grid.minimum[3], cells=size(grid)[1:2], kwargs...)
PlanarTerrain(elevation, x_bounds, y_bounds; cells=(1, 1), kwargs...)

Flat ground split into rectangular soil patches, without a triangle mesh. The voxel-source constructors use the full horizontal scene extent and its bottom elevation by default, with one patch per horizontal voxel. Override elevation or cells to choose a different flat surface or output resolution. Optical properties are supplied through models_for at simulation setup. The plane is an assumed ground surface, not an estimate of the terrain beneath the vegetation. Material identifiers remain part of the geometry in either case.

The source is unchanged. Attach the returned terrain with add_ground!(scene, ground) and supply its soil model through models_for to run_light. LightOptions.toricity controls whether the ground repeats horizontally during a simulation.

source
ArchimedLight.RasterCPUBackendType
julia
RasterCPUBackend()

Reference interception backend based on CPU raster projection.

source
ArchimedLight.RasterGPUBackendType
julia
RasterGPUBackend(; kwargs...)
RasterGPUBackend(config::RasterGPUBackendConfig)

Native GPU-oriented raster interception backend. It keeps projection, visibility stacks, visible-area reductions, and scattering-topology stack generation in KernelAbstractions kernels.

source
ArchimedLight.RasterGPUBackendConfigType
julia
RasterGPUBackendConfig(; backend=KernelAbstractions.CPU(), workgroupsize=nothing,
    max_hits_per_pixel=32, tile_size=1, tile_face_capacity=32,
    top_hit_tile_size=1, top_hit_tile_face_capacity=nothing,
    edge_accumulation=:auto, dense_edge_limit_bytes=512 * 1024^2,
    validate=false)

Configuration for the native KernelAbstractions raster backend. The backend field is a KernelAbstractions backend; GPU packages are loaded by callers. The same device kernels are used for CPU-backed validation and GPU execution.

source
ArchimedLight.RasterGPUScatteringBackendType
julia
RasterGPUScatteringBackend(interception_backend::RasterGPUBackend; kwargs...)
RasterGPUScatteringBackend(; kwargs...)

Scattering backend that builds transfer topology from native raster GPU stacks.

source
ArchimedLight.RaycastScatteringBackendType
julia
RaycastScatteringBackend()

Scattering backend that reconstructs transfer topology from directional ray-visibility stacks.

source
ArchimedLight.SceneSummaryType
julia
SceneSummary

Structured scene overview returned by summarize_scene.

source
ArchimedLight.SkyStateType
julia
SkyState(sun_azimuth_deg, sun_elevation_deg, ri_par_f, ri_nir_f, direct_fraction, diffuse_fraction)

Radiative forcing state used to build the turtle and directional fluxes for one light step. When produced by compute_sky, its irradiance fields are effective means over the complete meteo interval, after applying radiation_input_semantics.

Arguments:

  • sun_azimuth_deg: sun azimuth in degrees on the horizontal plane. The package uses 0° along +y, 90° along +x, 180° along -y, and 270° along -x. If your scene uses the common x=east, y=north convention, this means 0°=north, 90°=east, 180°=south, 270°=west.

  • sun_elevation_deg: sun elevation in degrees above the horizon. Typical daylight values are between 5 and 80. Use 90 for a sun at zenith; in simple examples, 89 is often a good practical choice when you want a nearly vertical beam.

  • ri_par_f: incident PAR irradiance in W m^-2 on a horizontal plane. Typical daylight values are often in the 100-600 range. Clear midday conditions are often around 300-500.

  • ri_nir_f: incident NIR irradiance in W m^-2 on a horizontal plane. Typical daylight values are often in the 100-700 range, commonly of the same order as or slightly larger than PAR.

  • direct_fraction: fraction of the shortwave forcing treated as direct beam. Expected range is [0, 1]. Typical values are near 0.8-1.0 for clear-sky conditions, around 0.3-0.7 for mixed conditions, and 0.0 for a fully diffuse sky.

  • diffuse_fraction: fraction of the shortwave forcing treated as diffuse sky radiation. Expected range is [0, 1]. In most use cases, it should satisfy direct_fraction + diffuse_fraction == 1.

This six-argument constructor stores ri_sw_f = ri_par_f + ri_nir_f automatically.

Examples:

  • Nearly zenith, mostly direct forcing: SkyState(180.0, 89.0, 350.0, 250.0, 0.95, 0.05)

  • Lower sun with mixed direct and diffuse light: SkyState(135.0, 35.0, 200.0, 180.0, 0.5, 0.5)

  • Fully diffuse overcast step: SkyState(180.0, 45.0, 120.0, 130.0, 0.0, 1.0)

source
ArchimedLight.TerrainCoordinateTransformType

Explicit 4×4 affine transform from DTM coordinates to voxel coordinates.

source
ArchimedLight.TerrainHitType

The nearest positive terrain intersection along a normalized ray.

source
ArchimedLight.TriangulatedTerrainType

General non-periodic triangle terrain; geometry and its cached BVH are read-only. Assign soil models at simulation setup.

source
ArchimedLight.TypeModelType
julia
TypeModel(; interception=nothing, light_emitter=nothing, extras=...)

Model definition for one component type inside a functional group.

This is the level that corresponds to one entry under the Type: block in a historical YAML model file.

Typical contents:

  • interception: how this component intercepts, transmits, and scatters light

  • light_emitter: optional emission model for artificial sources

  • extras: preserved non-light metadata from input files

Examples:

  • a leaf type with only interception behavior

  • a lamp type with only light_emitter

  • a diagnostic sensor type with interception=InterceptionModel(sensor=true, ...)

source
ArchimedLight.ValidationReportType
julia
ValidationReport(errors, warnings, infos)

Structured validation result returned by check_scene, check_models, check_meteo, and check_simulation.

source
ArchimedLight.VoxelBandScatteringResultType

Complete iterative scattering result for one spectral band. The terrain_*_energy vectors sum processed per-patch interactions over all foliage orders and terrain bounces, including the first-order terrain input. Incident and reflected energy are internal transfers; absorbed energy is the terminal soil contribution to the external energy budget.

source
ArchimedLight.VoxelCPUBackendType
julia
VoxelCPUBackend(; rays_per_voxel=1000, g=0.5,
                  traversal=:dda, lidf=:spherical)

CPU settings for first-order voxel interception. Horizontal wrapping is controlled by LightOptions.toricity, as for mesh interception. traversal accepts :dda or :reference. Only spherical LIDF is currently supported.

source
ArchimedLight.VoxelComponentLightType

One voxel × individual × organ light diagnostic; energies are joules per step. plant_area is equivalent proxy area in m², not a measured botanical surface.

source
ArchimedLight.VoxelDirectionResponseType

Unit-irradiance response of a voxel grid for one propagation direction.

source
ArchimedLight.VoxelFirstOrderResultType

First-order spectral interception values for one voxel light step.

source
ArchimedLight.VoxelGridType
julia
VoxelGrid(minimum, maximum, pad)

A regular Cartesian voxel grid. minimum and maximum are three-coordinate corners in metres. pad[i, j, k] is plant area density in m² plant m⁻³. Julia indices are 1-based; historical .vox files are converted explicitly at the I/O boundary.

source
ArchimedLight.VoxelGroundOpticsType
julia
VoxelGroundOptics(grid; par_reflectance, nir_reflectance)

Optional Lambertian ground reflectance. Coefficients can be scalars or arrays aligned with the horizontal (x, y) grid. Energy not reflected is absorbed by the ground.

source
ArchimedLight.VoxelLightStepResultType

Complete voxel result for one meteorological row.

source
ArchimedLight.VoxelMaterialType
julia
VoxelMaterial(; g=0.5, par_reflectance=0, par_transmittance=0,
              nir_reflectance=0, nir_transmittance=0)
VoxelMaterial(base::VoxelMaterial; kwargs...)

Constant projection coefficient and PAR/NIR optics of one voxel component. The default is a black absorber, a modelling simplification rather than a calibrated leaf or bark material. g must lie in (0, 1]. Use the copy constructor to override selected coefficients while retaining the other values, for example VoxelMaterial(leaf; par_reflectance=0.1).

source
ArchimedLight.VoxelModelsType
julia
VoxelModels(; leaf=nothing, wood=VoxelMaterial(), unknown=VoxelMaterial(),
              ground=nothing, individuals=Dict(), ground_materials=Dict(),
              mode=nothing, identified_area_fraction=nothing)
models_for(; leaf, wood, unknown, ground, individuals, ground_materials, kwargs...)

Material configuration supplied as models to a voxel run. Component defaults apply throughout the scene. An individual may supply complete VoxelMaterial values or named tuples of parameters inherited from an organ's default. Individual override keys must all be Int or all be String, matching the scene's IDs. Empty overrides can be used with either scene type. Without a global default, supply a complete material for each participating individual/organ. Ground uses the same opaque models as triangle scenes. Its PAR/NIR reflected fractions are distributed above the opaque terrain; the remainder is absorbed. ground_materials maps positive terrain material IDs to complete models or partial (par=..., nir=...) overrides of ground. Resolved coefficients are copied at configuration time.

Merged JSONL inputs require mode=:point_proxy explicitly. This records the assumption that point proportions allocate exported PAD; unassigned vegetation retains the unknown model. The input density and terrain geometry are unchanged.

source
ArchimedLight.VoxelOpticalPropertiesType
julia
VoxelOpticalProperties(grid; par_reflectance, par_transmittance,
                       nir_reflectance, nir_transmittance)

Leaf optical properties for voxel multiple scattering. Each coefficient can be a scalar or a three-dimensional array aligned with grid.pad. The single-scattering albedo in one band is reflectance + transmittance; geometric gaps remain represented by PAD and Beer-Lambert extinction.

For compatibility with historical ARCHIMED material files, pass scattering_par and scattering_nir instead. Their total scattering factors are split equally between reflection and transmission, matching the historical bilambertian approximation.

source
ArchimedLight.VoxelPointComponentType

One observed individual/organ point count, not a measured surface area.

source
ArchimedLight.VoxelPointInputType
julia
VoxelPointInput

The existing PointCloud2Model JSONL export, normalized to a VoxelGrid. missing_pad preserves null or omitted PAD; any stored zero at those locations is an explicit computational imputation, not an observation of empty space.

Point observations use compressed sparse rows: for Julia's column-major linear voxel index v, its components are components[offsets[v]:offsets[v+1]-1]. point_count is the number of filtered, identified points in each voxel. Individuals absent from that filtered population remain unassigned vegetation; their area must not be inferred from zero point counts.

One input uses either positive Int IDs or nonempty String IDs throughout; integer IDs are retained as integers. Mixed ID types are rejected.

source
ArchimedLight.VoxelRayPathType

An ordered ray path through a voxel grid. exit_reason is :terrain, :top, :bottom, or :side. A terrain path carries its nearest terrain_hit; no-terrain paths retain nothing. Public first-order rays point downward; internal scattering rays may leave through either vertical boundary.

source
ArchimedLight.VoxelRaySegmentType

One ordered ray segment inside a voxel.

source
ArchimedLight.VoxelResponseCacheType

Reusable directional responses aligned with a TurtleGrid.

source
ArchimedLight.VoxelScatteringQuadratureType

Normalized full-sphere angular quadrature for isotropic voxel scattering.

source
ArchimedLight.VoxelScatteringResultType

PAR and NIR voxel multiple-scattering results.

source
ArchimedLight.VoxelScatteringTransportCacheType

Geometry-dependent internal-ray paths reused by the matrix-free transport operator.

source
ArchimedLight.VoxelSceneType
julia
VoxelScene(grid_or_input; terrain=NoVoxelTerrain())

A mutable voxel scene owning a density grid, optional individual/organ inputs and materials, and a terrain. Constructors retain the supplied data without copying PAD. read_scene imports .vox and merged .jsonl[.gz] files into this representation. The scene owns no persistent transport cache: run_light shares directional responses within each series call.

scene.grid is the original PAD field; scene.input retains point attribution when available. scene.models optionally retains a VoxelModels configuration set with update_models!; a models keyword supplied to run_light overrides it for that call without mutating the scene. Use add_ground! for ground geometry; supply its soil optics through the model configuration.

source
ArchimedLight.VoxelTerrainSceneInputType

Authoritative AMAPVox voxel + DTM input contract.

source
ArchimedLight.VoxelTransportResultType

One conservative application of foliage and optional soil transport. The terrain_*_energy vectors contain cumulative processed interactions in J per terrain patch, excluding any pending unresolved transport. They are empty for legacy flat ground; the aggregate ground_* fields include either soil representation.

source
ArchimedLight.VoxelVegetationType
julia
VoxelVegetation(input; mode, leaf=nothing, wood=VoxelMaterial(), unknown=VoxelMaterial(),
                individuals=Dict(), identified_area_fraction=nothing)

Resolve a VoxelPointInput into sparse individual/organ components. Pass mode=:point_proxy explicitly: filtered point fractions are assumed to represent fractions of the exported PAD, not measured leaf/wood surfaces. By default all PAD is assigned to identified individuals where points exist; otherwise it remains (nothing, :unknown). An explicit scalar or grid-aligned identified_area_fraction retains the remaining PAD as unattributed vegetation.

individuals maps IDs of the input's type (Int or String) to named tuples containing optional leaf, wood, and unknown overrides. Complete VoxelMaterial values replace that organ's material; parameter named tuples inherit the organ default. A missing default is allowed when every participating individual/organ supplies a full material. Wood must have zero transmittance. Unattributed vegetation uses the default unknown material. For scene workflows, declare these defaults with models_for and pass the returned configuration to run_light.

source
ArchimedLight.VoxelVegetationStepResultType

Mixed vegetation result with native voxel results, sparse component diagnostics, and the original missing-PAD mask (including explicitly imputed cells). The resolved vegetation model is retained so later model changes do not alter material-dependent diagnostics of this result.

source
ArchimedLight.ArchimedLightModelFunction
julia
ArchimedLightModel(simulation::LightSimulation; kwargs...)

Build the PlantSimEngine kernel that runs one ArchimedLight simulation at scene scale and publishes identity-keyed results to declared organ targets.

This method is provided when PlantSimEngine is loaded. The returned value is a model kernel: the application target, distributed-output selector, cadence, and ordering remain explicit in the caller's PlantSimEngine.ModelSpec. The adapter enables include_sky_fraction on the simulation when preparing the coupling so every destination receives the simulated sky visibility.

source
ArchimedLight.apply_voxel_scattering_transportFunction
julia
apply_voxel_scattering_transport(grid, emitted, quadrature, backend;
                                 band=:par, ground=nothing,
                                 initial_ground_energy=nothing, cache=nothing)

Apply one deterministic, matrix-free scattering transport step. Foliage sources emit from voxel centres, or an exposed point in terrain-cut cells, over the full sphere. A supplied ground is Lambertian: radiation reaching it is split into absorbed and reflected energy, and reflected energy is propagated upward immediately.

source
ArchimedLight.archimed_light_outputsFunction
julia
archimed_light_outputs([schema=:coupling])

Return the Distributed(Default(value)) declarations used by PlantSimEngine.outputs_ for an ArchimedLightModel output schema. :coupling contains the variables normally consumed by organ-scale physiology. :full additionally publishes all initial/total PAR/NIR flux and energy metrics.

Both schemas publish area in m²: the sum of the geometric mesh areas mapped to each destination organ. This is the area used to normalize the published radiation fluxes; the raster projection correction does not change it. aPPFD and Ra_SW_f use the shared :surface_area contract basis, so they connect directly to physiology using this same reference area. The separate component_values table API names this quantity radiative_mesh_area.

Both schemas also publish dimensionless sky_fraction, the mesh-area-weighted mean of LightStepResult.sky_fraction over each organ's components. This preserves the solver's existing mean-visible-projected-area convention without rescaling it. It remains available under zero incident radiation.

The model owns these declarations. Bind its destination objects with outputs_to=(OutputTo(selector),), or use a tuple of variable names in OutputTo(...; vars=...) when declaring several destinations.

This method is provided when PlantSimEngine is loaded.

source
ArchimedLight.attach_light_series!Method
julia
attach_light_series!(scene, steps; fields=[:incident_par_flux], names=Dict(), fill_value=NaN)

Attach a time series of LightStepResult values to the scene MTG.

Arguments:

  • scene: MTG-backed PlantGeom.SceneGeometry to mutate.

  • steps: ordered vector of LightStepResult values to attach.

Keywords:

  • fields: budget or metadata selectors to attach.

  • names: optional mapping from selectors in fields to custom MTG attribute names.

  • fill_value: value stored for geometry nodes absent from a selected step dictionary.

For each selected field, every geometry node receives a vector ordered like steps, which is convenient for downstream plotting or coupled simulations. For example, with the default field, each geometry node gets node[:Ri_PAR_f]::Vector{Float64} with one value per light step.

fields accepts the same selectors as attach_light_step!:

  • :area => area

  • :incident_par_initial_flux => Ri_PAR_0_f

  • :incident_nir_initial_flux => Ri_NIR_0_f

  • :incident_par_flux => Ri_PAR_f

  • :incident_nir_flux => Ri_NIR_f

  • :incident_par_initial_energy => Ri_PAR_0_q

  • :incident_nir_initial_energy => Ri_NIR_0_q

  • :incident_par_energy => Ri_PAR_q

  • :incident_nir_energy => Ri_NIR_q

  • :absorbed_par_initial_flux => Ra_PAR_0_f

  • :absorbed_nir_initial_flux => Ra_NIR_0_f

  • :absorbed_par_flux => Ra_PAR_f

  • :absorbed_nir_flux => Ra_NIR_f

  • :absorbed_shortwave_flux => Ra_SW_f (Ra_PAR_f + Ra_NIR_f)

  • :absorbed_par_initial_energy => Ra_PAR_0_q

  • :absorbed_nir_initial_energy => Ra_NIR_0_q

  • :absorbed_par_energy => Ra_PAR_q

  • :absorbed_nir_energy => Ra_NIR_q

  • :sky_fraction => sky_fraction

For :area, series attachment stores the same area value repeated once per step so the result has the same vector shape as the light fields.

Use names to override attached attribute names for downstream packages that expect different names. The canonical Ra_SW_f value is always the absorbed PAR+NIR sum. The historical names=Dict(:absorbed_nir_flux => :Ra_SW_f) call remains accepted and is interpreted as that sum, with a deprecation warning; prefer :absorbed_shortwave_flux in new code. For example:

julia
attach_light_series!(
    scene,
    steps;
    fields=[
        :area,
        :absorbed_par_flux,
        :absorbed_nir_flux,
        :absorbed_shortwave_flux,
        :sky_fraction,
    ],
)

fill_value is used for geometry nodes that are missing from a given step dictionary. The default is NaN, so missing values remain visible in the attached vectors.

sky_fraction is only available when each step was produced with LightOptions(include_sky_fraction=true), or from a config that requests sky_fraction: true in component_variables or opf_variables.

source
ArchimedLight.attach_light_step!Method
julia
attach_light_step!(scene, step; fields=[:incident_par_flux], names=Dict(), fill_value=nothing)

Attach one LightStepResult back onto the scene MTG using ARCHIMED-style attribute names such as Ri_PAR_f and Ra_PAR_q.

Arguments:

  • scene: MTG-backed PlantGeom.SceneGeometry to mutate.

  • step: one LightStepResult whose node-level values will be attached.

Keywords:

  • fields: budget or metadata selectors to attach.

  • names: optional mapping from selectors in fields to custom MTG attribute names.

  • fill_value: value written for geometry nodes absent from a selected step dictionary.

fields selects which budget components to export. Each selected field is attached as one scalar MTG attribute per geometry node. Supported selectors are:

  • :incident_par_initial_flux => Ri_PAR_0_f

  • :incident_nir_initial_flux => Ri_NIR_0_f

  • :incident_par_flux => Ri_PAR_f

  • :incident_nir_flux => Ri_NIR_f

  • :incident_par_initial_energy => Ri_PAR_0_q

  • :incident_nir_initial_energy => Ri_NIR_0_q

  • :incident_par_energy => Ri_PAR_q

  • :incident_nir_energy => Ri_NIR_q

  • :absorbed_par_initial_flux => Ra_PAR_0_f

  • :absorbed_nir_initial_flux => Ra_NIR_0_f

  • :absorbed_par_flux => Ra_PAR_f

  • :absorbed_nir_flux => Ra_NIR_f

  • :absorbed_shortwave_flux => Ra_SW_f (Ra_PAR_f + Ra_NIR_f)

  • :absorbed_par_initial_energy => Ra_PAR_0_q

  • :absorbed_nir_initial_energy => Ra_NIR_0_q

  • :absorbed_par_energy => Ra_PAR_q

  • :absorbed_nir_energy => Ra_NIR_q

  • :sky_fraction => sky_fraction

  • :area => area

Selector naming follows the budget hierarchy:

  • incident means intercepted radiation, corresponding to historical Ri

  • absorbed means absorbed radiation, corresponding to historical Ra

  • initial means first-order only, corresponding to historical _0_

  • no initial means first-order plus scattering

  • flux means W m^-2, corresponding to historical _f

  • energy means J per component and per step, corresponding to historical _q

  • area means the prepared object surface area in m^2

names is an optional dictionary that remaps those exported fields to custom MTG attribute names. For example:

julia
attach_light_step!(
    scene,
    step;
    fields=[:incident_par_flux, :absorbed_par_energy],
    names=Dict(
        :incident_par_flux => :my_par_flux,
        :absorbed_par_energy => :my_par_energy,
    ),
)

With that override, the values are attached on :my_par_flux and :my_par_energy instead of the default ARCHIMED names.

The canonical Ra_SW_f name always contains absorbed PAR plus absorbed NIR. The historical names=Dict(:absorbed_nir_flux => :Ra_SW_f) call remains accepted and is interpreted as that sum, with a deprecation warning. Prefer :absorbed_shortwave_flux in new code.

To expose the sky-view fraction or PlantBiophysics-specific names, you can mix selectors and overrides:

julia
attach_light_step!(
    scene,
    step;
    fields=[
        :area,
        :absorbed_par_flux,
        :absorbed_nir_flux,
        :absorbed_shortwave_flux,
        :sky_fraction,
    ],
)

sky_fraction is only available when the step was produced with LightOptions(include_sky_fraction=true), or from a config that requests sky_fraction: true in component_variables or opf_variables.

source
ArchimedLight.attach_node_values!Method
julia
attach_node_values!(scene, attr, values; fill_value=nothing)

Attach a dictionary of per-node values to the MTG stored in scene.

Only geometry nodes present in the prepared scene are updated. Missing node ids receive fill_value.

Arguments:

  • scene: MTG-backed PlantGeom.SceneGeometry to mutate.

  • attr: MTG attribute name to write on each geometry node.

  • values: dictionary keyed by node id, containing values to attach.

Keywords:

  • fill_value: value written for geometry nodes absent from values.

source
ArchimedLight.cache_summaryMethod
julia
cache_summary(sim)

Return a named tuple summarizing the current radiation cache state for a LightSimulation.

Arguments:

  • sim: simulation whose prepared cache should be summarized. If no cache has been prepared yet, the summary reports mode=:unprepared.

source
ArchimedLight.check_meteoMethod
julia
check_meteo(meteo; options=LightOptions(), check_boundaries=options.check_meteo_boundaries)::ValidationReport

Validate meteo rows for required solar geometry, radiation inputs, and timestep duration data.

Arguments:

  • meteo: a meteo row, PlantMeteo.TimeStepTable, or Tables.jl-compatible table.

Keywords:

  • options: LightOptions used for checks that depend on runtime meteo handling.

  • check_boundaries: check physical ranges for used meteo values. Missing, nonfinite, derivability, and conflicting-input checks are always performed.

source
ArchimedLight.check_modelsMethod
julia
check_models(scene, models)::ValidationReport

Validate that models cover the geometric group/type pairs present in scene.

Arguments:

  • scene: PlantGeom.SceneGeometry whose geometric nodes define the required group/type pairs.

  • models: model specification accepted by prepare_models.

source
ArchimedLight.check_sceneMethod
julia
check_scene(scene)::ValidationReport

Validate basic scene readiness for light interception.

Arguments:

  • scene: PlantGeom.SceneGeometry to check for geometry nodes, faces, and a valid xy domain.

source
ArchimedLight.check_simulationMethod
julia
check_simulation(sim)::ValidationReport
check_simulation(scene, meteo; models, options=LightOptions(), check_boundaries=options.check_meteo_boundaries)::ValidationReport

Validate a reusable simulation or the separate inputs needed to build and run one.

Arguments:

  • sim: LightSimulation to validate.

  • scene: PlantGeom.SceneGeometry to validate when checking separate inputs.

  • meteo: meteo row or table to validate when checking separate inputs.

Keywords:

  • models: required model specification when checking separate inputs.

  • options: LightOptions used for meteo validation.

  • check_boundaries: check physical ranges for used meteo values.

source
ArchimedLight.component_values!Method
julia
component_values!(table, step, aggregation; par_energy_to_photon=4.57)

Refill a preallocated source-owner table returned by component_values. The identity and result columns are reused in place, so scene-level coupling code can compile the owner mapping once and avoid allocating a result table at every light step.

table.source_owner and table.radiative_mesh_area must be the exact columns owned by aggregation; this prevents a buffer from another scene generation from being reused accidentally.

source
ArchimedLight.component_valuesMethod
julia
component_values(step; level=:component, par_energy_to_photon=4.57)
component_values(step, aggregation; par_energy_to_photon=4.57)

Return a typed Tables.jl-compatible column table for one light step.

At level=:component, rows are the filtered geometric components and carry node_id, source_owner, radiative_mesh_area, all initial and total PAR/NIR incident and absorbed flux/energy variables, Ra_SW_f, and aPPFD.

At level=:source_owner, components sharing a botanical owner are coalesced. Energy quantities (*_q) are summed and flux densities (*_f) are weighted by radiative area. Pass a reusable CompiledComponentAggregation as the second argument to avoid recompiling this identity mapping.

par_energy_to_photon converts absorbed PAR energy flux (W m⁻²) to absorbed PPFD (μmol m⁻² s⁻¹). The default 4.57 is the conventional broadband PAR factor; supply a spectrum-specific value when available.

source
ArchimedLight.component_valuesMethod
julia
component_values(step::VoxelVegetationStepResult; level=:component,
                 par_energy_to_photon=4.57)

Return a Tables.jl-compatible table of voxel × individual × organ diagnostics. plant_area is equivalent proxy area (m²), not measured botanical area. The shared Ri_*/Ra_* columns use J per step (*_q) and W m⁻² plant (*_f); aPPFD uses μmol m⁻² s⁻¹. Unattributed vegetation keeps individual_id=nothing. The initial absorbed PAR/NIR energy and flux columns (Ra_PAR_0_q, Ra_NIR_0_q, Ra_PAR_0_f, Ra_NIR_0_f) use the retained materials of this result, independently of later model changes. level=:individual combines each individual/organ across voxels, summing energies and area-weighting fluxes.

source
ArchimedLight.compute_voxel_first_orderFunction
julia
compute_voxel_first_order(grid, turtle, fluxes, backend=VoxelCPUBackend();
                          duration_seconds, cache=nothing, options=LightOptions())

Apply directional PAR/NIR irradiances to precomputed unit responses. Returned energy arrays are in joules per voxel and per step; flux arrays are in W m⁻² plant.

source
ArchimedLight.compute_voxel_scatteringFunction
julia
compute_voxel_scattering(grid, turtle, first, optics, options, backend;
                         duration_seconds, ground=nothing, cache=nothing)

Compute deterministic isotropic multiple scattering independently for PAR and NIR. The returned absorbed energy is distinct from intercepted energy, and the unresolved residual is retained explicitly when the stopping criterion or iteration limit terminates the series.

source
ArchimedLight.emitterMethod
julia
emitter(; radiance, par=0.48, nir=0.52)

Build a TypeModel for an emitting component.

Keywords:

  • radiance: Lambertian radiance per emitting area per steradian.

  • par: unnormalized PAR spectral coefficient. The default is 0.48.

  • nir: unnormalized NIR spectral coefficient. The default is 0.52.

source
ArchimedLight.example_scene_pathMethod
julia
example_scene_path(name::Symbol)

Return the local input path for a downloadable example scene. The first call downloads and verifies the scene's lazy Julia artifact; later calls reuse the artifact cache. Installing or importing ArchimedLight does not download it.

The available scene is :belinga: the attributed voxel canopy by Olivier Martin and Pol Kennel (CIRAD–AMAP), released under CC0 1.0 at https://doi.org/10.5281/zenodo.23695138. The archive is approximately 17 MB.

julia
path = example_scene_path(:belinga)
scene = read_scene(path; missing_pad=:zero_with_diagnostics)

The path refers to the gzip-compressed JSONL input. Reading it and choosing a missing-PAD policy remain explicit. Treat files in the artifact cache as read-only.

source
ArchimedLight.height_field_terrainMethod
julia
height_field_terrain(aai; transform=TerrainCoordinateTransform(), ...)

Convert AAIGrid cell-centre elevations to a continuous triangulated surface. Every centre remains an exact vertex; raster edges use constant nearest-cell extension over the outer half-cell. The same southwest-to-northeast diagonal is then used in every cell. A raster-sized material_ids matrix applies to the original pixel footprints; heterogeneous IDs subdivide this same surface at pixel edges. Expanded surface-cell matrices and facet vectors retain their direct assignment semantics. Axis-aligned affine transforms retain a HeightFieldTerrain; rotations/shears produce an explicit TriangulatedTerrain, which requires LightOptions(toricity=false) when run. Boundary behavior is selected by the simulation options rather than the reader.

source
ArchimedLight.interpolate_radiationMethod
julia
interpolate_radiation(sw, par, direct_sw, hour; latitude, longitude,
    date, utc_offset_hours=0, first_timestamp=0, interval_hours=1,
    radiation_timestamp=:end)

Estimate an instantaneous solar forcing from regularly spaced interval means. sw, par, and direct_sw are horizontal irradiances in W m⁻². hour and first_timestamp use the same civil clock, whose offset from UTC is given in hours. date is the civil date at hour zero; hours can extend across its calendar boundaries. Longitude is positive east. Timestamps mark :start, :center, or :end of each averaging interval; :end matches Open-Meteo radiation.

Interpolate each component relative to its interval's extraterrestrial irradiance, then restore the instantaneous solar geometry. This avoids a spurious direct-normal irradiance peak at the horizon. Values outside the sample centres use the nearest component ratios. This smooth reconstruction does not exactly conserve each hourly integral and omits diffuse twilight.

The returned named tuple contains canonical RI_SW_f, RI_PAR_f, RI_NIR_f, direct_fraction, sun_azimuth, and sun_elevation. Add a date and positive step_duration to use it with run_light; the explicit sun is already corrected for longitude and the UTC offset. NIR is the remainder SW - PAR.

source
ArchimedLight.intersect_terrainFunction
julia
intersect_terrain(terrain, origin, direction, maximum_distance=Inf; toricity=false)

Find the nearest terrain intersection along a normalized ray. Standalone queries use finite terrain by default; toricity=true repeats planar or height-field terrain horizontally. A periodic height field must have matching opposite-edge elevations and a finite maximum_distance. Simulation transport takes this boundary mode from LightOptions.toricity.

source
ArchimedLight.light_face_valuesMethod
julia
light_face_values(scene, models, options, data; color=:incident_par_flux, timestep=1, fill_value=NaN)
light_face_values(data; color=:incident_par_flux, timestep=1, fill_value=NaN)

Return one scalar color value per rendered face for direct Makie mesh coloring.

Arguments:

  • geometry: LightRenderGeometry whose faces receive values.

  • scene: prepared PlantGeom.SceneGeometry used when deriving geometry from simulation inputs.

  • models: LightModels used when deriving geometry from simulation inputs.

  • options: LightOptions used when deriving geometry from simulation inputs.

  • data: one LightStepResult, a vector of results, a node-value dictionary, a vector of dictionaries, or explicit numeric color values.

Keywords:

  • color: metric selector, node-value dictionary, or explicit numeric color vector.

  • timestep: one-based index used for series data.

  • fill_value: value used for nodes/faces without finite data.

When data is a LightStepResult or a series of steps returned by run_light_step / run_light_series, the stored render geometry is used and no scene or model inputs are needed.

source
ArchimedLight.light_metric_valuesMethod
julia
light_metric_values(scene_or_sim, step_or_series, metric; filters..., reduce=nothing, by=nothing, sink=nothing)

Return a filtered, Tables.jl-compatible column table for one light metric. metric accepts package selectors such as :absorbed_par_energy and historical ARCHIMED names such as :Ra_PAR_q.

The node filters are the same as light_node_ids. Without reduce, the result contains step_number, scene and output node identifiers, group/type and MTG metadata, requested attribute columns, and value. A series is returned in long form with one-based step numbers.

With reduce=sum, a single step returns a scalar and a series returns one value per timestep. Pass by to obtain a grouped table instead; series grouping always retains step_number. sink optionally materializes table results with a Tables.jl-compatible sink such as DataFrames.DataFrame.

Summing component energy metrics is physically meaningful. Component fluxes are area-normalized, so summing them generally is not a scene-scale energy total.

source
ArchimedLight.light_metric_valuesMethod
julia
light_metric_values(step, selector)
light_metric_values(steps, selector; timestep=1)

Return the per-node metric map selected by selector.

Arguments:

  • step: one LightStepResult to read.

  • steps: vector of LightStepResult values to index with timestep.

  • selector: metric selector symbol, either package-native or ARCHIMED-style.

Keywords:

  • timestep: one-based index used when steps is a series.

selector may be either a runtime budget field such as :incident_par_flux or the corresponding ARCHIMED attribute name such as :Ri_PAR_f.

source
ArchimedLight.light_metric_valuesMethod
julia
light_metric_values(scene_or_grid, voxel_result_or_series, selector;
                    domain=:vegetation, individual_id=nothing, organ=nothing,
                    voxel_i=nothing, voxel_j=nothing, voxel_k=nothing,
                    patch_id=nothing, material_id=nothing,
                    where=nothing, reduce=nothing, by=nothing, sink=nothing)

Return a Tables.jl-compatible column table. The default domain=:vegetation returns step_number, one-based voxel_i, voxel_j, voxel_k, and value, including empty cells. Use the original source PAD and cell volume.

Filtering by individual_id or organ, or grouping by either, selects voxel × individual × organ contributions from an identified JSONL result. These rows also carry individual_id, organ, and proxy plant_area (m²). IDs retain the scene's Int or String type; filters must match that type. individual_id=(nothing,) selects unattributed vegetation. A shared voxel contributes only the selected individual's share of radiation.

domain=:ground returns step_number, one-based patch_id, material_id, geometric surface_area (m²), and value, using terrain retained in the result. Patch and material filters apply only to ground; voxel/individual/organ filters apply only to vegetation. Ground flux uses actual stored patch surface area, while vegetation flux uses proxy plant area. Ground and vegetation are queried separately; the default is not a combined scene total. where receives the selected row schema; individual/organ columns require an individual/organ filter or grouping.

The reducer, grouping, and sink conventions match triangle queries. Energy can be summed; summing area-normalized flux is not an energy total. Series grouping retains step_number. component_values provides all variables and area-weighted individual/organ summaries.

source
ArchimedLight.light_metric_valuesMethod
julia
light_metric_values(step::VoxelLightStepResult, selector)
light_metric_values(step::VoxelVegetationStepResult, selector)

Read a grid-aligned light metric, accepting the same selector names and ARCHIMED aliases as triangle results. Energy is J per voxel per step; flux is W m⁻² of equivalent plant area. For raw results, total incident flux with scattering requires the original source via the three-argument query. Raw results retain absorption when models or explicit optics were supplied. A mixed-vegetation result also retains component areas and its resolved materials, so subsequent model changes do not alter earlier diagnostics.

source
ArchimedLight.light_node_idsMethod
julia
light_node_ids(scene_or_sim; filters...)

Return the sorted runtime node ids of geometric scene components matching all requested filters. Pass the returned ids back through node_ids= to reuse a selection across light metrics or timesteps.

Standard filters are node_ids, source_topology_id, group (or its alias species), object_id, symbol, scale, and type. Scalar filters use equality; collections use membership. attributes accepts a named tuple or dictionary of exact attribute matches. Arbitrary attributes are node-local unless inherit_attributes=true. The optional where predicate receives the corresponding MTG node.

group/species and object_id use inherited scene metadata, matching the way OPS object roots identify their descendant geometry.

source
ArchimedLight.light_render_geometryMethod
julia
light_render_geometry(scene, models, options)
light_render_geometry(step)
light_render_geometry(steps)

Return the render-ready geometry associated with a light simulation.

Arguments:

  • scene: prepared PlantGeom.SceneGeometry used when deriving geometry from simulation inputs.

  • models: LightModels used to apply solver-visible filtering.

  • options: LightOptions used to match the solver geometry.

  • step: one LightStepResult whose stored render geometry is returned.

  • steps: non-empty vector of LightStepResult values. The first step supplies the geometry.

For a scene, this applies the same model-dependent filtering as the solver. For results returned by run_light_step or run_light_series, it returns the stored render geometry directly.

source
ArchimedLight.light_vertex_valuesMethod
julia
light_vertex_values(scene, models, options, data; color=:incident_par_flux, timestep=1, fill_value=NaN)
light_vertex_values(data; color=:incident_par_flux, timestep=1, fill_value=NaN)

Return one scalar color value per rendered vertex, obtained by averaging the values of adjacent faces.

Arguments:

  • geometry: LightRenderGeometry whose vertices receive values.

  • scene: prepared PlantGeom.SceneGeometry used when deriving geometry from simulation inputs.

  • models: LightModels used when deriving geometry from simulation inputs.

  • options: LightOptions used when deriving geometry from simulation inputs.

  • data: one LightStepResult, a vector of results, a node-value dictionary, a vector of dictionaries, or explicit numeric color values.

Keywords:

  • color: metric selector, node-value dictionary, or explicit numeric color vector.

  • timestep: one-based index used for series data.

  • fill_value: value used for nodes/vertices without finite data.

When data is a LightStepResult or a series of steps returned by run_light_step / run_light_series, the stored render geometry is used and no scene or model inputs are needed.

source
ArchimedLight.lightplotFunction
julia
lightplot

Makie plotting entry point provided by the Makie package extension. Load Makie or CairoMakie before calling it. The preferred API is lightplot(step; ...) or lightplot(steps; ...).

For voxels use lightplot(scene[, results]; ...), where scene can be a VoxelScene, VoxelGrid, VoxelPointInput, or VoxelVegetation. Without results, a raw grid displays PAD; point input or mixed vegetation displays the dominant organ in each occupied voxel (leaf: green, wood: brown, unknown: grey). This is a classification of each existing voxel, not a reconstructed leaf/wood geometry. Select color=:pad, :leaf_fraction, :wood_fraction, or :unknown_fraction to inspect numerical PAD or component fractions. Fractions use point counts for unresolved input and component densities for a resolved vegetation model.

With results, the default is color=:incident_par_flux in W m⁻². Common light selectors and their ARCHIMED aliases also work. timestep selects a series element; the automatic colour range covers the full series and the terrain. Voxel fluxes use PAD × cell volume as their reference area. Terrain fluxes use actual patch area. Unavailable metrics raise an error rather than inferring missing optical properties. colormap and colorrange control both vegetation and terrain light results.

An imported voxel scene supplies its terrain automatically; for a raw grid or vegetation pass terrain=.... Pass the original source and terrain that produced the results; results do not retain a snapshot of later scene edits. Voxel plots use Axis3 and a local coordinate frame with Float64 translation to preserve small features at GIS offsets. cutaway=:front hides the lower-y half of the cells without changing the grid. Diagnostic attributes include pad_threshold, show_lattice, show_terrain, show_rays, and show_normals. rays accepts origin/direction named tuples; directions must point downward and use the supplied options.toricity. An optional quadrature displays terrain Lambertian redistribution directions.

Arguments:

  • data: one LightStepResult or a vector of results with stored render geometry.

  • geometry: optional LightRenderGeometry when plotting explicit geometry/data pairs.

  • scene: prepared PlantGeom.SceneGeometry used when deriving geometry from simulation inputs.

  • models: LightModels used when deriving geometry from simulation inputs.

  • options: LightOptions used when deriving geometry from simulation inputs.

Keywords:

  • kwargs...: Makie plot attributes, including ArchimedLight-specific color, timestep, interpolate, and fill_value.

source
ArchimedLight.lightplot!Function
julia
lightplot!

In-place Makie plotting entry point provided by the Makie package extension. Load Makie or CairoMakie before calling it. The preferred API is lightplot!(axis, step; ...) or lightplot!(axis, steps; ...). For voxel scenes use lightplot!(axis, scene[, results]; ...), with the same attributes and defaults as lightplot. Prefer Axis3 for georeferenced coordinates; WebGL LScene camera conversion cannot retain small features at large offsets.

Arguments:

  • axis: Makie axis or scene-like parent to plot into.

  • data: one LightStepResult or a vector of results with stored render geometry.

  • geometry: optional LightRenderGeometry when plotting explicit geometry/data pairs.

  • scene: prepared PlantGeom.SceneGeometry used when deriving geometry from simulation inputs.

  • models: LightModels used when deriving geometry from simulation inputs.

  • options: LightOptions used when deriving geometry from simulation inputs.

Keywords:

  • kwargs...: Makie plot attributes, including ArchimedLight-specific color, timestep, interpolate, and fill_value.

source
ArchimedLight.models_forMethod
julia
models_for(group_specs...)::LightModels
models_for(; leaf=nothing, wood=VoxelMaterial(), unknown=VoxelMaterial(),
             ground=nothing, individuals=Dict(), ground_materials=Dict(),
             mode=nothing, identified_area_fraction=nothing)::VoxelModels

For triangle scenes, create LightModels from compact (group => (type => model, ...)) pairs matched against geometric scene nodes.

For voxel scenes, keyword defaults create VoxelModels. Individual organ and soil-material overrides may be complete material objects or partial parameter named tuples inheriting from the corresponding default. Without a default, supply complete materials for every participating individual/organ or soil material ID. JSONL inputs require mode=:point_proxy explicitly. Pass the configuration to run_light(scene, meteo; models, options, backend); ground geometry is added separately with add_ground!. Voxel ground accepts an opaque(par=..., nir=...) model; ground_materials assigns complete models or partial (par=..., nir=...) overrides by terrain material ID. Triangle soils use the usual group/type pairs.

An empty call retains the triangle meaning LightModels(). Positional group/type pairs and voxel keywords cannot be combined.

Arguments:

  • group_specs...: one or more pairs where the left side is a scene group name and the right side is an iterable of type => TypeModel pairs.

Example:

julia
julia> models = models_for(
           "coffee" => (
               "Leaf" => translucent(par=0.15, nir=0.90),
               "Stem" => translucent(par=0.20, nir=0.50),
           ),
           "soil" => (
               "ground" => opaque(par=0.10, nir=0.40),
           ),
       );

julia> (collect(keys(models)), models["coffee"].types["Leaf"].interception.model)
(["coffee", "soil"], "Translucent")
source
ArchimedLight.opaqueMethod
julia
opaque(; par, nir)

Build a TypeModel for opaque Lambertian ground in triangle or voxel scenes. par and nir are reflected fractions in [0, 1]; transmission is zero and the absorbed fraction is 1 - reflectance. Reflected radiation is distributed over the exterior hemisphere of the local ground facet normal.

Triangle ground must consist of nonvertical terrain facets; normals are oriented upward independently of input winding. This is not a general opaque material for closed solids or vertical plant meshes. Both PAR and NIR reflectances are required. translucent retains the equal diffuse reflection/transmission approximation for ordinary plant components.

julia
julia> model = opaque(par=0.20, nir=0.40);

julia> (model.interception.model, model.interception.transparency, model.interception.optical_properties.par)
("Opaque", 0.0, 0.2)
source
ArchimedLight.prepare_meteoMethod
julia
prepare_meteo(meteo, options; check_boundaries=options.check_meteo_boundaries)::PlantMeteo.TimeStepTable

Return the effective meteo table after Java-like meteo controls are applied: sequence validation, optional meteo_range, and optional active filtering.

Arguments:

  • meteo: a PlantMeteo.TimeStepTable or Tables.jl-compatible table of meteo rows.

  • options: LightOptions controlling overlap validation, meteo_range, and active-row filtering.

Keywords:

  • check_boundaries: check physical ranges for used meteo values. Missing, nonfinite, derivability, and conflicting-input checks always remain enabled.

source
ArchimedLight.prepare_modelsMethod
julia
prepare_models(models)

Normalize in-memory model definitions into a LightModels object.

Arguments:

  • models: an in-memory model specification.

Accepted inputs are an existing LightModels, a single GroupModel, a vector of groups, or an OrderedDict{String,GroupModel}.

source
ArchimedLight.prepare_voxel_responsesFunction
julia
prepare_voxel_responses(grid, turtle, backend=VoxelCPUBackend(); options=LightOptions())

Precompute the band-independent voxel response for every upward source direction in turtle. Directions are negated before ray propagation. options.toricity selects periodic or open boundaries.

source
ArchimedLight.prepare_voxel_scattering_quadratureMethod
julia
prepare_voxel_scattering_quadrature(turtle)

Build a normalized full-sphere quadrature from the fixed diffuse-sky sectors of a turtle. Explicit sun sectors are excluded. Every diffuse direction is paired with its opposite direction, so isotropic foliage scattering sends equal total energy into the upper and lower hemispheres.

source
ArchimedLight.prepare_voxel_scattering_transportFunction
julia
prepare_voxel_scattering_transport(grid, quadrature, backend;
                                  options=LightOptions(), compact=false)

Cache internal source-to-boundary DDA paths for occupied voxels and upward ground rays. Terrain-cut voxels use a source point in their exposed part. With compact=false, paths retain their geometry for inspection. With compact=true, only occupied segments and their Beer-Lambert fractions are stored. Both modes depend on PAD and the backend extinction coefficient; leaf optical properties and soil reflectance may change without rebuilding.

source
ArchimedLight.read_aai_gridMethod
julia
read_aai_grid(path; crs=nothing)

Read an Arc/Info ASCII Grid DTM. xllcenter/yllcenter headers are converted explicitly to lower-left corner coordinates. File rows run north-to-south and are reversed so elevation[i,j] increases with x and y. Any NODATA or non-finite sample is rejected rather than silently creating open terrain.

source
ArchimedLight.read_meteoMethod
julia
read_meteo(path)::PlantMeteo.TimeStepTable
read_meteo(data)::PlantMeteo.TimeStepTable

Read a meteorological forcing table from path and return it as a PlantMeteo.TimeStepTable.

The resulting table keeps available metadata such as latitude, longitude, altitude, and source file path.

Legacy radiation-only files that omit one or more mandatory Atmosphere columns (T, Wind, or Rh) remain readable as generic timestep tables. PlantMeteo date parsing, unit normalization, and scientific validation errors otherwise propagate unchanged.

Arguments:

  • path: path to a meteorological forcing file readable by PlantMeteo.

  • data: alternatively, a Tables.jl-compatible table or an existing PlantMeteo.TimeStepTable.

source
ArchimedLight.read_modelsMethod
julia
read_models(path_or_paths)::LightModels

Read one or more ARCHIMED model YAML files and return them as a LightModels collection.

Arguments:

  • path_or_paths: a model source to read.

path_or_paths can be:

  • the path to a single model YAML file containing a Group

  • the path to a config YAML file containing a models: list

  • a vector of explicit model-file paths

source
ArchimedLight.read_optionsMethod
julia
read_options(path)::LightOptions

Read runtime light options from a config YAML file.

This parses the ARCHIMED configuration keys such as sky_sectors, pixel_size, toricity, scattering controls, and meteo-range options into a LightOptions instance.

Arguments:

  • path: path to an ARCHIMED-style configuration YAML file.

source
ArchimedLight.read_ply_terrainMethod
julia
read_ply_terrain(path;
                 transform=TerrainCoordinateTransform(), kwargs...)

Read a triangular PLY DTM into the existing TriangulatedTerrain representation. The vertex element must contain scalar x, y, and z properties, and the face element must contain a list property named vertex_indices (or vertex_index) with exactly three zero-based integer indices per face.

ASCII and native-endian binary PLY 1.0 are read by PlyIO, preserving the declared coordinate precision. Other binary endianness is rejected by PlyIO. Colours, file normals, and other attributes do not define soil optics; normals are recomputed from the transformed geometry. Polygonal faces are rejected rather than silently triangulated.

transform explicitly maps file coordinates to the voxel reference frame; the default is identity. No translation, rescaling, CRS conversion, or terrain alignment is inferred. Additional keywords are passed to TriangulatedTerrain, including material IDs and BVH leaf_size. Generic triangular terrain requires LightOptions(toricity=false) when run. The reader imports geometry and material identities. Configure soil models separately at simulation setup.

source
ArchimedLight.read_sceneMethod
julia
read_scene(path; kwargs...)

Read a triangle scene (.ops, .opf, .gwa) as PlantGeom.SceneGeometry, or voxel input (.vox, .jsonl, .jsonl.gz) as VoxelScene.

The scene is relabelled into a dense node-id space and immediately converted to the merged-mesh representation expected by the interception pipeline.

Arguments:

  • path: path to an .ops, .opf, or .gwa scene file.

Keywords:

  • plantgeom_backend: reserved PlantGeom backend selector. The current default is :auto.

  • Voxel inputs accept missing_pad, and .vox accepts pad_column. Configure optical properties separately with models_for, then pass the models to run_light.

source
ArchimedLight.read_simulationMethod
julia
read_simulation(path; plot_paving_override=nothing, check_boundaries=nothing, kwargs...)

Read a complete file-based light simulation and return (sim, meteo), where sim is a LightSimulation.

Arguments:

  • path: path to the ARCHIMED-style configuration YAML file.

Keywords:

  • plot_paving_override: optional replacement paving count used when materializing model-declared ground geometry.

  • check_boundaries: optional boundary-validation policy applied while reading meteo and stored in the returned simulation. Derivability and conflict checks are always performed.

  • kwargs...: keyword arguments forwarded to LightSimulation, such as interception_backend, scattering_mode, scattering_backend, and memory_limit_bytes.

This smoke example uses coarse, non-scattering options so it remains quick:

julia
julia> repo_root = normpath(joinpath(dirname(pathof(ArchimedLight)), ".."));

julia> config = joinpath(repo_root, "example_2", "config.yml");

julia> sim, meteo = read_simulation(config; plot_paving_override=0);

julia> update_options!(
           sim,
           LightOptions(
               sim.options;
               turtle_sectors=1,
               pixel_size=0.1,
               area_ratio=false,
               scattering=false,
               toricity=false,
           ),
       );

julia> step = run_light(sim, first(meteo));

julia> println(step)
LightStepResult(PAR=1.266 MJ, NIR=1.372 MJ, sky=463.139 W m^-2 PAR / 501.734 W m^-2 NIR, sectors=1 [sky=1, sun=0], scattering=off)
source
ArchimedLight.read_voxel_columnMethod
julia
read_voxel_column(path, name; required=true)

Read one numeric column from a complete historical/AMAPVox voxel table while preserving its 0-based i j k indexing convention at the file boundary. Column matching is case-insensitive. With required=false, return nothing when the column is absent.

source
ArchimedLight.read_voxel_gridMethod
julia
read_voxel_grid(path; pad_column=:auto, missing_pad=:error,
                return_diagnostics=false)

Read a complete historical ARCHIMED or AMAPVox VOXEL SPACE file, including AMAPVox 2.4.2 parenthesized coordinate headers. Rows use 0-based (i, j, k) indices. Automatic PAD selection recognizes PAD, PadBVTotal, and pad_attenuation_PPL_MLE, case-insensitively. If several match, select a column explicitly with pad_column="name".

The default return is a VoxelGrid. With return_diagnostics=true, return a named tuple with grid, a Boolean missing_pad mask, and metadata retaining header values and the selected pad_column.

NaN PAD means unknown density and is rejected by default. Explicit missing_pad=:zero_with_diagnostics imputes zero and requires return_diagnostics=true so the unknown-cell mask cannot be discarded. Negative or infinite PAD remains invalid.

source
ArchimedLight.read_voxel_point_inputMethod
julia
read_voxel_point_input(path; missing_pad=:error)::VoxelPointInput

Stream the existing PointCloud2Model schema_version="1.0" .jsonl or .jsonl.gz export (metadata followed by voxel records). The PAD estimator is amapvox.pad_attenuation_PPL_MLE. The exported 0-based Cartesian indices are converted explicitly to Julia indices; record order is unrestricted.

Tree and organ ratios describe filtered lidar point counts, not measured area fractions. Positive integer tree_id values remain Int; nonempty string IDs remain String. Every tree record in one file must use the same ID type, including records in zero-PAD voxels. An input without identities uses Int. Positive PAD without identified points is retained. Null or omitted PAD is rejected by default. With missing_pad=:zero_with_diagnostics, it is explicitly imputed as zero in grid.pad, with every such cell retained in the returned missing_pad mask. This does not establish empty vegetation.

source
ArchimedLight.run_lightMethod
julia
run_light(sim, skies::AbstractVector{<:SkyState}; step_duration_seconds)

Run a series of already computed sky states. The step duration is required because sky states do not contain timing metadata.

step_duration_seconds may be either one positive duration shared by every state or a vector of positive durations with the same length as skies. The results are returned as a Vector{LightStepResult} in input order, while the simulation's prepared scene and radiation cache are reused across the series.

Examples

julia
series = run_light(sim, skies; step_duration_seconds=1800.0)
series = run_light(sim, skies; step_duration_seconds=[900.0, 1800.0, 900.0])
source
ArchimedLight.run_lightMethod
julia
run_light(sim, meteo_or_row; check_boundaries=sim.options.check_meteo_boundaries)

Run one light step for a meteo row, or a full series for a meteo table.

Arguments:

  • sim: LightSimulation containing the scene, models, options, and lazy cache.

  • meteo_or_row: either a single meteo row for one step, or a PlantMeteo.TimeStepTable/Tables.jl-compatible table for a series.

Keywords:

  • check_boundaries: check physical ranges for used meteo values. Derivability and conflicting-input checks are always performed.

source
ArchimedLight.run_lightMethod
julia
run_light(sim, sky::SkyState; step_duration_seconds)

Run one light step from an already computed sky state. The step duration is required because there is no meteo row from which to infer it.

Arguments:

  • sim: LightSimulation containing the scene, models, options, and lazy cache.

  • sky: precomputed SkyState used for one light step.

Keywords:

  • step_duration_seconds: duration of the step in seconds. This is required for energy integration.

source
ArchimedLight.run_lightMethod
julia
run_light(scene::VoxelScene, row_or_table; models=scene.models, options=LightOptions(), backend=VoxelCPUBackend())
run_light(grid_or_vegetation, row_or_table; options=LightOptions(), backend=VoxelCPUBackend(), terrain=NoVoxelTerrain(), kwargs...)

Run one voxel timestep or a meteorological series through the shared Julia solver. Series reuse caches within that call, independently of the triangle cache_radiation option. A new call prepares responses for the current PAD. Supply VoxelModels through models for component defaults, individual overrides, and soil models. Geometry and optical parameters are resolved once before the existing series solver. Raw low-level grids still accept explicit optics; configured VoxelVegetation supplies component materials.

source
ArchimedLight.run_voxel_light_seriesMethod
julia
run_voxel_light_series(grid, meteo, options; backend=VoxelCPUBackend(),
                       optics=nothing, ground=nothing)

Run a meteorological series while reusing first-order directional responses and geometry-only internal scattering paths. Explicit voxel optics are required when scattering is enabled.

source
ArchimedLight.run_voxel_light_seriesMethod

Run a mixed-vegetation series, sharing the native directional and scattering caches.

source
ArchimedLight.run_voxel_light_stepMethod
julia
run_voxel_light_step(grid, meteo_row, options; backend=VoxelCPUBackend(),
                     optics=nothing, ground=nothing)

Run voxel interception for one meteorological row. When options.scattering is true, explicit VoxelOpticalProperties must be supplied through optics. An optional VoxelGroundOptics adds Lambertian ground reflection.

source
ArchimedLight.run_voxel_light_stepMethod
julia
run_voxel_light_step(model::VoxelVegetation, meteo_row, options; backend, terrain)

Run the native voxel transport with constant-G individual/organ mixtures. The original PAD defines flux normalization; backend.g is only the numerical reference of the equivalent grid and must be positive. Component materials define physical extinction. options.toricity remains the boundary setting.

source
ArchimedLight.soil_absorbed_energyMethod

Soil absorption diagnostic (the stored ground_* field name is legacy-compatible).

source
ArchimedLight.soil_opticsMethod
julia
soil_optics(terrain, material_id)
soil_optics(terrain, hit)

Inspect the resolved opaque ground reflectances as an immutable named tuple with par_reflectance and nir_reflectance fields. Configure terrain optics through models_for before inspecting or simulating the terrain.

source
ArchimedLight.soil_reflected_energyMethod

Soil reflection diagnostic (an internal transfer, not an external source).

source
ArchimedLight.summarize_meteoMethod
julia
summarize_meteo(meteo; options=LightOptions(), check_boundaries=options.check_meteo_boundaries)

Return a MeteoSummary describing row count, columns, timestep duration, radiation inputs, and the detected solar-geometry path for a meteo table or row.

Arguments:

  • meteo: a meteo row, PlantMeteo.TimeStepTable, or Tables.jl-compatible table.

Keywords:

  • options: LightOptions used for checks that depend on runtime meteo handling.

  • check_boundaries: check physical ranges while resolving the summary.

source
ArchimedLight.summarize_sceneMethod
julia
summarize_scene(scene; models=nothing)

Return a SceneSummary describing the prepared scene domain, geometric nodes, faces, group/type pairs, object ids, and missing model pairs.

Pass models to include a model coverage check in the summary.

Arguments:

  • scene: PlantGeom.SceneGeometry to summarize.

Keywords:

  • models: optional model specification used to report missing group/type coverage.

source
ArchimedLight.tile_light_geometryMethod
julia
tile_light_geometry(scene, geometry; nx=1, ny=1, centered=true, xperiod=nothing, yperiod=nothing)
tile_light_geometry(scene, step; nx=1, ny=1, centered=true, xperiod=nothing, yperiod=nothing)
tile_light_geometry(scene, steps; nx=1, ny=1, centered=true, xperiod=nothing, yperiod=nothing)
tile_light_geometry(scene, models, options; nx=1, ny=1, centered=true, xperiod=nothing, yperiod=nothing)

Repeat a render geometry nx times along x and ny times along y for visualizing toric or repeated scenes.

Arguments:

  • scene: prepared PlantGeom.SceneGeometry used to infer tile spacing when periods are not provided.

  • geometry: LightRenderGeometry to repeat.

  • step: one LightStepResult whose stored geometry is repeated.

  • steps: non-empty vector of LightStepResult values whose first step supplies the geometry.

  • models: LightModels used when deriving geometry from simulation inputs.

  • options: LightOptions used when deriving geometry from simulation inputs.

Keywords:

  • nx: number of repetitions along x.

  • ny: number of repetitions along y.

  • centered: center tile offsets around the original geometry when true.

  • xperiod: optional explicit x tile period.

  • yperiod: optional explicit y tile period.

By default, the tile period comes from the scene xy bounds, so the visual repetition matches the simulation plot box. Pass xperiod and yperiod explicitly to override that spacing.

source
ArchimedLight.trace_voxel_rayMethod
julia
trace_voxel_ray(grid, origin, direction;
                options=LightOptions(), algorithm=:dda,
                terrain=NoVoxelTerrain())

Trace a normalized propagation ray through grid. direction is normalized internally and must point downward. options.toricity controls horizontal wrapping; otherwise the ray terminates at the first horizontal exit. algorithm=:reference uses a sorted-plane implementation, while :dda uses production streaming DDA. This geometric primitive is also used by voxel interception and ray visualization.

source
ArchimedLight.translucentMethod
julia
translucent(; par, nir, transparency=0.0)

Build a TypeModel for an ordinary translucent component.

Keywords:

  • par: PAR scattering fraction for the component.

  • nir: NIR scattering fraction for the component.

  • transparency: intercepted-light transparency fraction. The default 0.0 makes the component opaque to interception.

par and nir are scattering fractions for the built-in ARCHIMED wavebands. The absorbed fraction is therefore 1 - scattering_fraction.

julia
julia> model = translucent(par=0.15, nir=0.90, transparency=0.1);

julia> (model.interception.model, model.interception.transparency, model.interception.optical_properties.par)
("Translucent", 0.1, 0.15)
source
ArchimedLight.update_models!Method
julia
update_models!(sim, new_models)

Replace the model specification in sim and release all prepared cache data. The next run_light call prepares the new models lazily.

Arguments:

source
ArchimedLight.update_models!Method
julia
update_models!(scene::VoxelScene, models::VoxelModels)

Set the model configuration used by subsequent runs. Geometry and observed PAD are unchanged; the models are resolved against the current scene at run time.

source
ArchimedLight.update_options!Method
julia
update_options!(sim, new_options)

Replace the runtime options in sim and release all prepared cache data. The next run_light call prepares data using the new options.

Arguments:

source
ArchimedLight.update_scene!Method
julia
update_scene!(sim, new_scene)

Replace the scene and immediately release all prepared data tied to the old scene. The next run_light call prepares the new scene lazily.

Arguments:

  • sim: LightSimulation to update in place.

  • new_scene: replacement PlantGeom.SceneGeometry.

source
ArchimedLight.virtual_sensorMethod
julia
virtual_sensor()

Build a TypeModel for virtual sensors. Virtual sensors receive light diagnostics while remaining transparent in interception and scattering logic.

Arguments: none.

source
ArchimedLight.voxel_absorptanceMethod

Return foliage absorptance 1 - reflectance - transmittance.

source
ArchimedLight.voxel_component_valuesMethod
julia
voxel_component_values(result::VoxelVegetationStepResult)

Sum sparse light diagnostics by (individual_id, organ) across voxels. Unattributed vegetation retains individual_id=nothing. Only organ=:leaf absorbed PAR represents leaf APAR. Returned energies are J per step.

source
ArchimedLight.voxel_generic_green_leaf_opticsMethod

Documented generic-green-leaf example optics: total scattering 0.17 PAR and 0.85 NIR.

source
ArchimedLight.voxel_single_scattering_albedoMethod

Return the dimensionless single-scattering albedo reflectance + transmittance.

source
ArchimedLight.write_component_valuesMethod
julia
write_component_values(path::AbstractString, sim::LightSimulation, series; step_index_base::Integer=1)

Write an ARCHIMED-style component_values.csv file from already-computed LightStepResult values.

series may be a single LightStepResult or a collection of them. The function serializes the supplied results only; it does not run the simulation. Step numbers are 1-based by default. Use step_index_base=0 only for historical harness compatibility.

Identity columns reuse the result's retained node metadata when available. sim must supply the scene, models, and options used to compute these results: area and barycentre still come from that scene, and sky fraction is reconstructed from it when absent from the result. To export after update_scene!, keep the original scene context (for example, in another LightSimulation).

Arguments:

  • path: destination CSV file path.

  • sim: LightSimulation that supplies scene, model, and option context for component rows.

  • series: one LightStepResult or a collection of results to serialize.

Keywords:

  • step_index_base: first step number written to the CSV. The default is 1.

source
ArchimedLight.write_sceneMethod
julia
write_scene(path, scene)

Write an MTG-backed PlantGeom.SceneGeometry to path.

Supported output formats are .ops, .opf, and .gwa. The function refreshes the reference-mesh registry and normalizes topology ids before export.

Arguments:

  • path: output path ending in .ops, .opf, or .gwa.

  • scene: MTG-backed PlantGeom.SceneGeometry to write.

source
ArchimedLight.write_voxel_gridFunction
julia
write_voxel_grid(path, grid, result=nothing)

Write a regular grid in historical .vox layout. When result is supplied, the file also contains first-order PAR/NIR energy (J voxel⁻¹ step⁻¹) and plant-area-normalized flux (W m⁻² plant). No leaf-angle distribution metadata is inferred from PAD or the simulation projection coefficient: VoxelGrid does not store that input provenance.

source
PlantGeom.add_ground!Method
julia
add_ground!(scene::VoxelScene; elevation=scene.grid.minimum[3], kwargs...)
add_ground!(scene::VoxelScene, terrain)

Attach flat ground using the scene's horizontal extent and bottom elevation, without assigning optical properties. Supply ground models to run_light. cells=(nx, ny) controls soil patch resolution. options.toricity selects horizontal wrapping for both the canopy and ground when running the simulation. Pass an existing terrain as the second argument to attach a PLY/MNT instead. Returns the same scene and leaves PAD unchanged.

source