Skip to content

Experiment Framework

GreenFlux keeps the physical simulator organized by greenhouse domain, but the upper workflow follows a problem-centric structure inspired by energy modelling frameworks such as enflow/emflow:

Project -> Problem -> Experiment -> Benchmark

This is intentionally lighter than adopting a full external framework. The goal is to make greenhouse studies reproducible without hiding the physical models. The same workflow is also the basis for high-quality dataset generation: a project defines the physical asset, a problem defines the question, an experiment records one reproducible run and a benchmark compares families of runs.

Project

greenflux.projects defines the physical object:

  • site and weather references
  • greenhouse geometry
  • envelope and equipment
  • crop parameters
  • named simulation cases
  • calibration profiles

A project answers: what greenhouse are we modelling?

For ML and control datasets, projects are also the correct place to store calibration profiles, weather references and project-local result folders. Keeping these definitions attached to the greenhouse avoids undocumented dataset-generation scripts with hidden assumptions.

Problem

greenflux.problems defines the question being asked of a project. A GreenhouseProblem combines:

  • one GreenhouseProject
  • one simulation case
  • an objective
  • default actuation commands
  • initial climate conditions
  • resolved weather forcing

The environment exposes a deterministic sequential interface:

from greenflux.actuation import GreenhouseActuation
from greenflux.problems import GreenhouseProblem

problem = GreenhouseProblem(
    problem_id="winter_climate",
    project=project,
    case_name="winter_day",
    default_action=GreenhouseActuation(heating_power_w_m2=60.0),
)

result = problem.environment(weather=weather).run()

Actions are greenhouse actuation commands such as vent opening, screen closure, heating, lighting, CO2 injection and vapour source/sink terms. Observations are compact records with temperature, RH, vapour pressure, CO2 and harvested dry mass.

For downstream algorithms, a problem is the natural boundary for deciding what is observed, what is controlled and what remains hidden as simulator truth. This distinction is important for anomaly detection, fault diagnosis, observability analysis and sensor-placement studies.

The action contract itself lives in greenflux.actuation, so controllers, management policies and problem environments use the same physical command language without mixing actuator definitions with decision logic.

See docs/actuation_management.md for the detailed boundary between equipment, actuation, controls, management and problem evaluation.

Management policies live in greenflux.management and produce those actuation commands:

from greenflux.actuation import GreenhouseActuation
from greenflux.management import DayNightManagementPolicy

policy = DayNightManagementPolicy(
    day_command=GreenhouseActuation(lighting_power_w_m2=80.0),
    night_command=GreenhouseActuation(screen_closure=1.0, heating_power_w_m2=40.0),
)

result = problem.environment(weather=weather).run(
    action_schedule=lambda time_s, state: policy.action(time_s=time_s, state=state)
)

Experiment

greenflux.experiments packages a problem run into a reproducible unit:

from greenflux.experiments import GreenhouseExperiment

experiment = GreenhouseExperiment(
    experiment_id="winter_climate_baseline",
    problem=problem,
    tags=("baseline", "synthetic-weather"),
)

result = experiment.run(weather=weather)
result.write_summary("outputs/experiments/winter_climate_baseline.json")

The summary stores the experiment id, project id, case name, model, GreenFlux version, record count, objective metrics and final observation. Full time-series export still belongs to greenflux.io and scenario/project runners.

Experiments should also become the preferred unit for generated datasets: normal baselines, perturbed weather runs, fault-injected runs, stochastic control rollouts and sensor-subset studies can all share the same provenance shape.

Benchmark

greenflux.benchmarks runs one or more experiments and checks metric thresholds:

from greenflux.benchmarks import BenchmarkThreshold, GreenhouseBenchmark, GreenhouseBenchmarkCase

benchmark = GreenhouseBenchmark(
    benchmark_id="public_smoke_benchmark",
    cases=(
        GreenhouseBenchmarkCase(
            name="winter_day",
            experiment=experiment,
            thresholds=(BenchmarkThreshold("objective_score", maximum=5.0),),
        ),
    ),
)

benchmark_result = benchmark.run(weather_by_case={"winter_day": weather})

Benchmarks are the right place for public comparison contracts: real-data replay cases, crop calibration cases, smoke tests, anomaly-detection datasets, fault-diagnosis scenarios, controller comparisons and future multi-model comparisons.

Relationship to declarative studies

The Problem → Experiment → Benchmark layer is the programmatic workflow (Python objects, step-able environments, provenance and metric thresholds). There is also a declarative sibling — studies/<id>/study.json, run with greenflux study run — that packages a named analysis pipeline (stochastic planning, anomaly detection) with its parameters and writes a result file, without writing Python. The two express "a reproducible question over a project" at different granularities (a study is a multi-run analysis; an experiment is one problem-run).

They share one run-record shape: greenflux.provenance.RunProvenance. An experiment summary carries a provenance block; a study result is {"provenance": {...}, "result": {...}} with the same block (kind, run_id, project_id, greenflux_version, created_at_utc, metrics). A report or benchmark consumer sees the same provenance envelope from either layer. See studies.md.

Dataset Generation Contract

When an experiment is used to generate ML-ready data, it should keep these streams separate:

  • true simulator state
  • sensor or measurement stream
  • actuation command
  • equipment response
  • weather and other exogenous forcing
  • crop state and biological fluxes
  • fault or perturbation metadata
  • validation metrics and physical residuals

Why Not Spaces Yet?

GreenFlux does not currently need a first-class greenflux.spaces package. The state/action/exogenous/observation concepts are present inside the problem layer, but extracting them too early would add abstraction before we need it.

Add a dedicated spaces layer only when GreenFlux needs formal compatibility with control, MPC, reinforcement-learning or multi-agent APIs. Until then, problem and experiment objects are enough to keep studies modular and readable.