ArchimedLight.jl

Meteo And Forcing Reference​#

The light solver does not fundamentally need a meteo file. It needs a time-dependent forcing description that tells it:

  • which time interval is being simulated

  • how much shortwave radiation arrives

  • how that radiation is split into PAR, NIR, and any extra bands

  • how much is direct beam and how much is diffuse sky

  • where the sun is, or enough information to reconstruct it

Historically that information often comes from a semicolon-separated meteo CSV. In dynamic workflows, the same semantics can be provided directly through a PlantMeteo.TimeStepTable or even a single SkyState.

What Matters In Meteo For The Light Solver​#

Whatever the source format, the runtime ultimately wants one or more forcing steps that define:

  • a start and end time

  • a usable radiative description

  • enough site information to reconstruct solar geometry when needed

The rest of this page is organized around those semantics rather than around the CSV format.

Minimal Schema​#

For most users, a meteo table should provide these fields:

NeedAccepted columns / metadataUnitsRequired?
Datedate, a DateTime-like date, or dayofyear (+ optional year)date or datetimerequired only for reconstructed sun/clearness paths
Time intervalhour_start + hour_end, or duration/step_durationtime strings, seconds, or Julia Dates.Perioda positive duration is always required; start time is required only for reconstructed sun/clearness paths
Site latitudelatitude or lat as a column or metadatadegreesyes, unless sun position is explicit
PAR irradianceRI_PAR_f, Ri_PAR_f, PAR, or parW m^-2one radiation description is required
NIR irradianceRI_NIR_f, Ri_NIR_f, NIR, or nirW m^-2optional if total shortwave is given
Total shortwaveRI_SW_f, Ri_SW_f, Rg, rg, sw_global, or globalW m^-2alternative to PAR/NIR
Atmospheric clearnessclearness or Ktfractionalternative forcing input
Sun positionsun_azimuth/sun_azimut and sun_elevationdegreesoptional
Direct fractiondirect_fraction, fDIR_SW, or Fdfractionoptional
Direct/diffuse SWRi_SW_f_direct/RI_SW_f_direct and Ri_SW_f_diffuse/RI_SW_f_diffuseW m^-2optional partition or total-SW path

run_light accepts PlantMeteo.TimeStepTable and generic Tables.jl-compatible inputs such as vectors of named tuples or DataFrames. Use summarize_meteo(meteo) to see what ArchimedLight detected, and check_meteo(meteo) to diagnose missing columns before a simulation.

Canonical Names And Compatibility Aliases​#

The canonical in-memory names follow PlantMeteo and use Ri_: Ri_SW_f, Ri_PAR_f, and Ri_NIR_f. ArchimedLight continues to accept the historical file spellings RI_SW_f, RI_PAR_f, and RI_NIR_f, plus the aliases listed in the table above. New Julia code should prefer the canonical Ri_* names.

Aliases are resolved by value rather than by column order. If a PlantMeteo Atmosphere contributes its default Ri_PAR_f=Inf while a legacy RI_PAR_f column contains a finite measurement, the finite measurement is used. missing, blank strings, NaN, and positive or negative Inf are treated as unavailable sentinels. If two finite aliases for the same variable disagree, validation returns an error naming both columns and values.

Historical files may still contain use metadata. PlantMeteo preserves that value as source provenance, but ArchimedLight does not interpret it: it neither selects a radiation path nor suppresses finite inputs. The resolver chooses the usable values actually present and reports conflicting finite values. New files should omit use.

Explicit sun_azimuth/sun_azimut and sun_elevation values are always in degrees. Small degree values are not reinterpreted as radians.

Radiation Derivation Rules​#

The resolver obtains one consistent SW/PAR/NIR forcing using these rules:

  1. Preserve a finite total Ri_SW_f when provided.

  2. Otherwise derive SW from finite PAR + NIR, direct + diffuse SW components, one direct/diffuse component plus direct_fraction, PAR alone, NIR alone, or finally clearness, in that order.

  3. With total SW and one measured band, derive the other band as the residual.

  4. With total SW and no measured bands, use the ARCHIMED 0.48 PAR / 0.52 NIR split.

When nir_interception=false, NIR is not required. If finite PAR and NIR are the best available description, their sum still resolves total SW before the NIR branch is disabled; when finite SW and PAR are already available, an unusable NIR value does not block the PAR-only computation.

Redundant finite inputs must agree within 1% or 1 W m^-2, whichever is larger. Nonfinite values do not conflict: they are ignored when another derivation path is available. An error is returned only when the forcing cannot be derived, the finite inputs conflict, or enabled boundary checks fail.

Row values take precedence over PlantMeteo.TimeStepTable metadata. Metadata is used as a fallback when every row-level alias for a logical variable is absent or nonfinite.

Optional custom bands use Ri_<BAND>_f/RI_<BAND>_f. They follow the same sentinel, alias-agreement, and nonnegative-boundary rules as the built-in light bands. Ri_TIR_f is thermal forcing and is intentionally not sent through the light interception/scattering pipeline.

Boundary Validation​#

Boundary validation is enabled by default through LightOptions(check_meteo_boundaries=true). It checks nonnegative irradiance, fractions and clearness in [0, 1], latitude in [-90, 90], sun azimuth in [0, 360), sun elevation in [-90, 90], and a positive timestep duration. For example:

julia
report = check_meteo(meteo; options=options)

For an intentional out-of-range research input, disable only the physical range checks:

julia
report = check_meteo(meteo; options=options, check_boundaries=false)
prepared = prepare_meteo(meteo, options; check_boundaries=false)
step = run_light(sim, row; check_boundaries=false)

Missing/nonfinite resolution, positive-duration checks, and conflicting-input checks remain active when check_boundaries=false; there is no general unsafe validation bypass.

Date and time values needed by the chosen solar path are parsed strictly. Malformed or blank values are never replaced silently with noon or a default calendar date. They may be ignored only when a complete alternative path is available—for example, finite explicit sun coordinates plus a positive step_duration.

1. Time Intervals​#

The light model is driven by intervals, not isolated timestamps.

From Files​#

A meteo row usually defines an interval through date, hour_start, and hour_end:

text
date;hour_start;hour_end;RI_PAR_f;RI_NIR_f
2016/06/12;12:00:00;12:30:00;350;250

Dynamically In Julia​#

The direct in-memory equivalent inside a PlantMeteo.TimeStepTable row is:

julia
(
    date=Date(2020, 6, 21),
    hour_start="12:00:00",
    hour_end="12:30:00",
    RI_PAR_f=350.0,
    RI_NIR_f=250.0,
)

If you bypass the meteo layer entirely and build a SkyState directly, then the interval is no longer carried by the forcing object itself. You instead provide the step duration explicitly later on, for example in run_light(sim, sky; step_duration_seconds=1800.0).

Start, End, and Centre Timestamps​#

A DateTime-valued date, or a separate hour column, identifies the start of the radiation interval by default. Set radiation_timestamp when a source labels its radiation averages differently:

radiation_timestampInterval for a timestamp t and duration Δt
:start (default)[t, t + Δt]
:end[t - Δt, t]
:center[t - Δt/2, t + Δt/2]

Declare it in a column or in table metadata; a nonmissing column value takes precedence. Strings such as "end" are also accepted for file metadata. An explicit hour_start takes precedence over this timestamp convention. The :end and :center cases require a positive duration or step_duration. Intervals crossing midnight retain their duration and use the appropriate calendar date internally.

For example, Open-Meteo labels its hourly radiation with the end of the averaging interval: radiation timestamped 19:00 represents 18:00–19:00. Its other variables, such as air temperature, can describe the instantaneous 19:00 state. Declare the radiation convention without shifting the original table or its temperature observations. See the Open-Meteo variable definitions.

julia
meteo = PlantMeteo.TimeStepTable(
    [(
        date=DateTime(2025, 5, 15, 19), duration=Hour(1),
        T=21.3, Ri_SW_f=41.0, Ri_SW_f_direct=16.0, Ri_SW_f_diffuse=25.0,
    )],
    (latitude=43.61, longitude=3.878, timezone="UTC", radiation_timestamp=:end),
)

The resolved radiation interval is 18:00–19:00; meteo.date and meteo.T remain unchanged. ArchimedLight does not infer this convention from a filename or an API name. Choose it explicitly when preparing imported radiation.

Snapshots Between Radiation Averages​#

For regularly spaced hourly inputs, interpolate_radiation prepares instantaneous forcing with the same solar-aware interpolation used by the browser demo:

julia
forcing = interpolate_radiation(hourly_sw, hourly_par, hourly_direct_sw, 18.5;
    latitude=43.61, longitude=3.878, date=Date(2025, 5, 15),
    utc_offset_hours=0, first_timestamp=0, interval_hours=1,
    radiation_timestamp=:end)
row = (; date=Date(2025, 5, 15), step_duration=60.0, forcing...)
step = run_light(simulation, row)

The vectors contain horizontal irradiance means in W m⁻². The returned sun coordinates already account for the declared clock and longitude. This smooth snapshot reconstruction omits diffuse twilight and does not conserve each hourly integral exactly; use the original interval means for energy budgets.

2. Site Metadata​#

Some radiative reconstructions need site metadata, especially latitude.

From Files​#

ARCHIMED-style meteo files often begin with metadata comments such as:

text
#' name: Aquiares
#' latitude: 15.0
#' altitude: 100.0

Dynamically In Julia​#

The in-memory equivalent is the metadata part of PlantMeteo.TimeStepTable:

julia
meteo = PlantMeteo.TimeStepTable(
    rows,
    (latitude=15.0, altitude=100.0, file="interactive",),
)

If you build a SkyState directly, you do not need latitude because the sun direction is already prescribed.

Civil Clock Time and Solar Time​#

When ArchimedLight reconstructs the sun, historical inputs without a declared clock retain the Java convention: hours are local mean solar time, and the astronomical calculation applies the equation of time. To use civil clock timestamps, provide longitude and their UTC offset:

FieldMeaning
longitudeDegrees east, in [-180, 180]; west is negative
utc_offset_hoursCivil time minus UTC, in [-14, 14]; fractional offsets are accepted
timezone="UTC", "GMT", or "Z"Shorthand for a zero UTC offset

These fields can be columns or metadata, with nonmissing columns taking precedence. The conversion is mean_solar_hour = clock_hour - utc_offset_hours + longitude / 15. The equation of time is then applied once by the solar model. Dates are normalized when this conversion crosses midnight.

For example, (longitude=3.878, timezone="UTC") describes UTC observations at Montpellier. Local observations in May can instead use (longitude=3.878, timezone="Europe/Paris", utc_offset_hours=2.0). Named timezones require an explicit numeric offset; ArchimedLight does not resolve daylight saving time from a timezone database. For a series spanning an offset change, provide the correct utc_offset_hours in each row. Sequence validation compares the resolved intervals in UTC, so repeated local hours at the end of daylight saving time remain distinct. The original table and meteo_range selections continue to use the supplied civil timestamps.

A declared civil clock requires longitude for astronomical calculations. Longitude alone preserves historical solar-time behavior and produces a warning explaining that it was not applied. A UTC timezone combined with a nonzero offset is an input conflict. These convention checks remain enabled when physical boundary checks are disabled.

With explicit sun coordinates and no usable astronomical interval, no civil clock conversion is needed. Prescribing a SkyState directly is also an option when another solar-position model has already prepared the forcing.

3. Radiative Inputs​#

The current light runtime accepts several ways of defining the incoming shortwave forcing. At least one usable description must be present.

clearness​#

clearness is a compact atmospheric forcing description from which the runtime can reconstruct shortwave forcing and part of the sky partition logic.

From Files​#

text
date;hour_start;hour_end;latitude;clearness
2016/06/12;12:00:00;12:30:00;15.0;0.75

Dynamically In Julia​#

julia
(
    date=Date(2020, 6, 21),
    hour_start="12:00:00",
    hour_end="12:30:00",
    latitude=15.0,
    clearness=0.75,
)

This is useful when measured PAR and NIR are unavailable but the atmospheric state is known well enough to reconstruct a plausible sky.

RI_PAR_f And RI_NIR_f​#

These are the most direct way to prescribe the main ARCHIMED wavebands.

From Files​#

text
date;hour_start;hour_end;RI_PAR_f;RI_NIR_f
2016/06/12;12:00:00;12:30:00;350;250

Dynamically In Julia​#

julia
(
    date=Date(2020, 6, 21),
    hour_start="12:00:00",
    hour_end="12:30:00",
    RI_PAR_f=350.0,
    RI_NIR_f=250.0,
)

This is usually the clearest input style when PAR and NIR measurements are available.

RI_SW_f​#

RI_SW_f is total shortwave irradiance.

From Files​#

text
date;hour_start;hour_end;RI_SW_f
2016/06/12;12:00:00;12:30:00;600

Dynamically In Julia​#

julia
(
    date=Date(2020, 6, 21),
    hour_start="12:00:00",
    hour_end="12:30:00",
    RI_SW_f=600.0,
)

When only total shortwave is available, the runtime partitions it into PAR and NIR using the ARCHIMED assumptions.

Custom Wavebands​#

Additional shortwave bands follow the pattern:

text
RI_<band>_f

For example:

text
RI_red_f
RI_custom_f

The same naming works in dynamic named tuples:

julia
(
    date=Date(2020, 6, 21),
    hour_start="12:00:00",
    hour_end="12:30:00",
    RI_PAR_f=350.0,
    RI_red_f=80.0,
)

Extra bands only become physically meaningful if the scene models also define matching optical properties for them. Band names are matched case-insensitively, so a meteo column such as RI_custom_f uses the custom coefficient from optical_properties during scattering.

RI_TIR_f is reserved for thermal infrared forcing. ArchimedLight does not run the energy-balance calculations that consume it, so this column is explicitly ignored: it is not treated as an extra light band and it is not intercepted or scattered.

4. Direct And Diffuse Partition​#

The light model needs more than total irradiance. It needs a directional interpretation of that forcing.

From Files​#

This partition can be provided explicitly with columns such as direct_fraction, or inferred from the available radiation inputs and the historical ARCHIMED assumptions.

Dynamically In Julia With PlantMeteo.TimeStepTable​#

The same applies to in-memory rows:

julia
(
    date=Date(2020, 6, 21),
    hour_start="12:00:00",
    hour_end="12:30:00",
    RI_PAR_f=350.0,
    RI_NIR_f=250.0,
    direct_fraction=0.8,
)

If you do not provide it, compute_sky reconstructs it from the available forcing.

Dynamically In Julia With SkyState​#

If you already know the direct/diffuse split, you can bypass the reconstruction entirely:

julia
sky = SkyState(
    135.0,
    35.0,
    350.0,
    250.0,
    0.8,
    0.2,
)

This is the most direct route when the forcing is already known in physically resolved form.

5. Solar Geometry​#

The light pipeline needs sun direction.

From Files​#

When not given explicitly, it can be reconstructed from:

  • date

  • hour_start / hour_end

  • latitude

Some files may also carry explicit sun_azimuth and sun_elevation columns.

Dynamically In Julia With PlantMeteo.TimeStepTable​#

The same logic applies to in-memory rows. You can either:

  • let compute_sky reconstruct the sun position from time and latitude

  • provide sun_azimuth and sun_elevation explicitly in the row

Example:

julia
(
    date=Date(2020, 6, 21),
    hour_start="12:00:00",
    hour_end="12:30:00",
    latitude=15.0,
    sun_azimuth=180.0,
    sun_elevation=70.0,
    RI_PAR_f=350.0,
    RI_NIR_f=250.0,
)

Dynamically In Julia With SkyState​#

With SkyState, the sun direction is already explicit:

julia
SkyState(180.0, 89.0, 350.0, 250.0, 0.95, 0.05)

That means "nearly zenith, mostly direct, prescribed PAR and NIR".

6. Internal Radiation Timestep​#

A meteo row can span a long interval, for example 30 minutes, and the model can still evaluate radiation on smaller internal radiative substeps.

This is controlled by LightOptions:

julia
options = LightOptions(radiation_timestep_minutes=15.0)

So the user-facing meteo timeline and the internal radiative integration granularity are related but not identical.

7. File-Based Workflow​#

If your forcing already exists on disk, the usual pattern is:

julia
meteo = read_meteo("meteo.csv")
sim = LightSimulation(scene, models; options=options)
step = run_light(sim, first(meteo))

8. Dynamic Workflow With PlantMeteo.TimeStepTable​#

Use PlantMeteo.TimeStepTable when you still want the normal meteo-driven pipeline, but without writing a CSV:

julia
meteo = PlantMeteo.TimeStepTable(
    [
        (
            date=Date(2020, 6, 21),
            hour_start="12:00:00",
            hour_end="12:30:00",
            latitude=15.0,
            RI_PAR_f=350.0,
            RI_NIR_f=250.0,
        ),
    ],
    (latitude=15.0, file="interactive",),
)

sim = LightSimulation(scene, models; options=options)
step = run_light(sim, first(meteo))

This is the direct in-memory equivalent of the file-based meteo workflow.

9. Dynamic Workflow With SkyState​#

Use SkyState when you already know the forcing for one step and do not need the meteo parsing layer at all:

julia
sky = SkyState(
    135.0,
    35.0,
    350.0,
    250.0,
    0.95,
    0.05,
)

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

This is especially useful for:

  • synthetic scenes

  • controlled light tests

  • interactive simulations where the forcing is already computed elsewhere

10. Other Meteo Variables​#

Historical files may also contain:

  • temperature

  • relativeHumidity

  • VPD

  • wind

  • atmosphereCO2_ppm

  • Re_SW_f

Those variables were important in the original ARCHIMED energy-balance and photosynthesis workflows. In the current light-only package, they are ignored by the light solver. They may remain in legacy files without changing the result, but they are not required.

Practical Advice​#

For a robust light-only workflow:

  • always define a valid time interval

  • provide a reliable latitude whenever sun position must be reconstructed

  • prefer direct RI_PAR_f / RI_NIR_f when those measurements exist

  • use SkyState when you already know the sun direction and direct/diffuse partition

  • add extra wavebands only if the component models define matching optical properties