"""Synthetic fixed-offset 24-hour schedule; interval means, no crop model.""" import math import time from datetime import datetime, timedelta from vpd_kernel import analyze BOUNDS = {'temperature': (0, 50), 'rh_light': (0, 100), 'rh_dark': (0, 100), 'ppfd': (0, 3000), 'light_start': (0, 23.5), 'light_hours': (0, 24)} DEFAULT = dict(temperature=25, rh_light=50, rh_dark=50, ppfd=200, light_start=8, light_hours=16) def schedule(p): if set(p) != set(BOUNDS): raise ValueError('Unknown or missing parameters') for key, (lo, hi) in BOUNDS.items(): value = p[key] if type(value) not in (int, float) or not math.isfinite(value) or not lo <= value <= hi: raise ValueError('Parameter out of range: ' + key) if key in ('light_start', 'light_hours') and abs(value * 2 - round(value * 2)) > 1e-8: raise ValueError('Lighting times must use half-hour steps') # Retain published unequal interval boundaries; add exact lighting transitions. cuts = sorted({0, 5, 8, 9, 15, 24, p['light_start'], (p['light_start'] + p['light_hours']) % 24}) midnight = datetime.fromisoformat('2026-09-11T00:00:00+09:00') rows = [] for a, b in zip(cuts, cuts[1:]): lit = ((a + b) / 2 - p['light_start']) % 24 < p['light_hours'] rows.append(dict(start=(midnight + timedelta(hours=a)).isoformat(), end=(midnight + timedelta(hours=b)).isoformat(), temp_c=p['temperature'], rh_pct=p['rh_light'] if lit else p['rh_dark'], ppfd_umol_m2_s=p['ppfd'] if lit else 0)) return rows def run(name, p): if name != 'vpd': raise ValueError('Unknown experiment') start = time.perf_counter() rows = schedule(p) result = analyze(rows) hours = [0.0] dose = [0.0] step_x, vpd, ppfd = [], [], [] mean = 0.0 for row, interval in zip(rows, result['intervals']): end = hours[-1] + interval['seconds'] / 3600 step_x.extend([hours[-1], end]) vpd.extend([interval['air_vpd_kpa']] * 2) ppfd.extend([row['ppfd_umol_m2_s']] * 2) mean += interval['air_vpd_kpa'] * interval['seconds'] / 86400 hours.append(end) dose.append(interval['cumulative_mol_m2']) def series(ident, x, y, unit): return dict(id=ident, x=x, y=y, x_unit='h', y_unit=unit) return dict(metrics=[dict(id='mean_air_vpd', value=mean, unit='kPa'), dict(id='daily_light', value=result['dli_mol_m2_day'], unit='mol m⁻² day⁻¹'), dict(id='day_coverage', value=hours[-1], unit='h')], series=[series('air_vpd', step_x, vpd, 'kPa'), series('light_dose', hours, dose, 'mol m⁻²'), series('light_schedule', step_x, ppfd, 'μmol m⁻² s⁻¹')], stdout='', provenance=dict(data_type='synthetic', compute_ms=(time.perf_counter()-start)*1000)) if __name__ == '__main__': import argparse, json parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('--parameters', default=json.dumps(DEFAULT)) print(json.dumps(run('vpd', json.loads(parser.parse_args().parameters)), allow_nan=False))