1.3.82
 
Loading...
Searching...
No Matches
PhotosynthesisModel.h
Go to the documentation of this file.
1
16#ifndef PHOTOSYNTHESIS_MODEL
17#define PHOTOSYNTHESIS_MODEL
18
19#include "Context.h"
20
21#include <limits>
22
24private:
25 static void validateOptimalTemperature(float optimum_temperature_in_C) {
26 if (optimum_temperature_in_C < 0.f) {
27 helios::helios_runtime_error("ERROR (PhotosyntheticTemperatureResponseParameters): Optimal temperature cannot be negative. Received Topt = " + std::to_string(optimum_temperature_in_C) +
28 " C. Please check that temperature is provided in units of Celsius, not Kelvin.");
29 }
30 if (optimum_temperature_in_C > 100.f) {
31 helios::helios_runtime_error("ERROR (PhotosyntheticTemperatureResponseParameters): Optimal temperature cannot exceed 100 C. Received Topt = " + std::to_string(optimum_temperature_in_C) +
32 " C. This value is biologically unrealistic and likely indicates temperature was provided in Kelvin instead of Celsius. Please convert to Celsius (subtract 273.15 from Kelvin value).");
33 }
34 }
35
37
40 static void validateDeactivationEnergy(float rate_of_increase_dHa, float rate_of_decrease_dHd) {
41 if (rate_of_increase_dHa <= 0.f) {
42 return; // No Arrhenius term is applied, so dHd is unused.
43 }
44 if (rate_of_decrease_dHd <= rate_of_increase_dHa) {
45 helios::helios_runtime_error("ERROR (PhotosyntheticTemperatureResponseParameters): Deactivation energy dHd must be strictly greater than activation energy dHa for a peaked temperature response. Received dHa = " +
46 std::to_string(rate_of_increase_dHa) + " kJ/mol and dHd = " + std::to_string(rate_of_decrease_dHd) +
47 " kJ/mol. The peaked Arrhenius form evaluates ln(dHd/dHa - 1), which is undefined when dHd <= dHa. Increase dHd (a typical value is 10*dHa, or 200-600 kJ/mol) or omit dHd to use the default.");
48 }
49 }
50
51public:
53 value_at_25C = 100.0f;
54 dHa = 60.0f;
55 dHd = 600.0f;
56 Topt = 10000.f;
57 }
58
60 this->value_at_25C = value_at_25C;
61 this->dHa = 0.f;
62 this->dHd = 600.f;
63 this->Topt = 10000.f;
64 }
65
66 PhotosyntheticTemperatureResponseParameters(float value_at_25C, float rate_of_increase_dHa) {
67 this->value_at_25C = value_at_25C;
68 this->dHa = rate_of_increase_dHa;
69 if (rate_of_increase_dHa > 0.f) {
70 this->dHd = 10.f * rate_of_increase_dHa;
71 } else {
72 this->dHd = 600.f;
73 }
74 this->Topt = 10000.f;
75 }
76
77 PhotosyntheticTemperatureResponseParameters(float value_at_25C, float rate_of_increase_dHa, float optimum_temperature_in_C) {
78 validateOptimalTemperature(optimum_temperature_in_C);
79 this->value_at_25C = value_at_25C;
80 this->dHa = rate_of_increase_dHa;
81 if (rate_of_increase_dHa > 0.f) {
82 this->dHd = 10.f * rate_of_increase_dHa;
83 } else {
84 this->dHd = 600.f;
85 }
86 this->Topt = 273.15f + optimum_temperature_in_C;
87 }
88
89 PhotosyntheticTemperatureResponseParameters(float value_at_25C, float rate_of_increase_dHa, float optimum_temperature_in_C, float rate_of_decrease_dHd) {
90 validateOptimalTemperature(optimum_temperature_in_C);
91 validateDeactivationEnergy(rate_of_increase_dHa, rate_of_decrease_dHd);
92 this->value_at_25C = value_at_25C;
93 this->dHa = rate_of_increase_dHa;
94 this->dHd = rate_of_decrease_dHd;
95 this->Topt = 273.15f + optimum_temperature_in_C;
96 }
97
98 float value_at_25C;
99 float dHa;
100 float dHd;
101 float Topt;
102};
103
104
106
108 Tref = 298; // K
109 Ci_ref = 290; // umol CO2/mol air
110 Asat = 18.18; // umol/m^2-s
111 theta = 62.03; // umol/m^2-s (PPFD; i_PAR is converted from W/m^2 before use)
112 Tmin = 290; // K
113 Topt = 303; // K
114 q = 0.344; // unitless
115 R = 1.663e5; // umol-K^0.5/m^2-s
116 ER = 3740; // 1/K
117 kC = 0.791; // unitless
118 }
119
120 // reference values
121 float Tref;
122 float Ci_ref;
123
124 // light response coefficients
125 float Asat;
126 float theta;
127
128 // assimilation temperature response coefficients
129 float Tmin;
130 float Topt;
131 float q;
132
133 // respiration temperature response coefficients
134 float R;
135 float ER;
136
137 // CO2 response coefficients
138 float kC;
139};
140
142public:
144
145 // parameters (at TL = 25C)
146 Vcmax = -1.f; // umol/m^2/s
147 Jmax = -1.f; // umol/m^2/s
148 alpha = -1.f; // unitless
149 Rd = -1.f; // umol/m^2/s
150
151 O = 213.5; // ambient oxygen concentration (mmol/mol)
152
153 // temperature parameters
154 c_Rd = 18.72;
155 c_Vcmax = 26.35;
156 c_Jmax = 18.86;
157 c_Gamma = 19.02;
158 c_Kc = 38.05;
159 c_Ko = 20.30;
160
161 dH_Rd = 46.39f;
162 dH_Vcmax = 65.33f;
163 dH_Jmax = 46.36f;
164 dH_Gamma = 37.83;
165 dH_Kc = 79.43;
166 dH_Ko = 36.38;
167
168 TPU_flag = 0;
169 }
170
171 // options
172 int TPU_flag; // run model with TPU limitation
173
174 // Deprecated legacy parameters. These are retained for backwards compatibility with code that
175 // assigns them directly; a value > 0 selects a simple (non-peaked) Arrhenius response using the
176 // corresponding c_*/dH_* coefficients below. Calling the matching setter (e.g. setVcmax())
177 // resets the field to the -1 sentinel so that the setter is always authoritative — otherwise the
178 // two representations could silently disagree about which value the model actually uses.
179 // Prefer the setter API in new code.
180 float Vcmax;
181 float Jmax;
182 float Rd;
183 float alpha;
184 float O;
185
186 // temperature parameters
187 float c_Rd;
188 float c_Vcmax;
189 float c_Jmax;
190 float c_Gamma;
191 float c_Kc;
192 float c_Ko;
193
194 float dH_Rd;
195 float dH_Vcmax;
196 float dH_Jmax;
197 float dH_Gamma;
198 float dH_Kc;
199 float dH_Ko;
200
202
205 void setVcmax(float Vcmax) {
206 this->VcmaxTempResponse = PhotosyntheticTemperatureResponseParameters(Vcmax);
207 this->Vcmax = -1.f; // legacy field: setter is authoritative
208 }
209
211
215 void setVcmax(float Vcmax_at_25_C, float rate_of_increase_dHa) {
216 this->VcmaxTempResponse = PhotosyntheticTemperatureResponseParameters(Vcmax_at_25_C, rate_of_increase_dHa);
217 this->Vcmax = -1.f; // legacy field: setter is authoritative
218 }
219
221
227 void setVcmax(float Vcmax_at_25_C, float rate_of_increase_dHa, float optimum_temperature_in_C) {
228 float rate_of_decrease_dHd = 10.f * rate_of_increase_dHa;
229 this->VcmaxTempResponse = PhotosyntheticTemperatureResponseParameters(Vcmax_at_25_C, rate_of_increase_dHa, optimum_temperature_in_C, rate_of_decrease_dHd);
230 this->Vcmax = -1.f; // legacy field: setter is authoritative
231 }
232
234
240 void setVcmax(float Vcmax_at_25_C, float rate_of_increase_dHa, float optimum_temperature_in_C, float rate_of_decrease_dHd) {
241 this->VcmaxTempResponse = PhotosyntheticTemperatureResponseParameters(Vcmax_at_25_C, rate_of_increase_dHa, optimum_temperature_in_C, rate_of_decrease_dHd);
242 this->Vcmax = -1.f; // legacy field: setter is authoritative
243 }
244
246
249 void setJmax(float Jmax) {
250 this->JmaxTempResponse = PhotosyntheticTemperatureResponseParameters(Jmax);
251 this->Jmax = -1.f; // legacy field: setter is authoritative
252 }
253
255
259 void setJmax(float Jmax_at_25_C, float rate_of_increase_dHa) {
260 this->JmaxTempResponse = PhotosyntheticTemperatureResponseParameters(Jmax_at_25_C, rate_of_increase_dHa);
261 this->Jmax = -1.f; // legacy field: setter is authoritative
262 }
263
265
270 void setJmax(float Jmax_at_25_C, float rate_of_increase_dHa, float optimum_temperature_in_C) {
271 float rate_of_decrease_dHd = 10.f * rate_of_increase_dHa;
272 this->JmaxTempResponse = PhotosyntheticTemperatureResponseParameters(Jmax_at_25_C, rate_of_increase_dHa, optimum_temperature_in_C, rate_of_decrease_dHd);
273 this->Jmax = -1.f; // legacy field: setter is authoritative
274 }
275
277
283 void setJmax(float Jmax_at_25_C, float rate_of_increase_dHa, float optimum_temperature_in_C, float rate_of_decrease_dHd) {
284 this->JmaxTempResponse = PhotosyntheticTemperatureResponseParameters(Jmax_at_25_C, rate_of_increase_dHa, optimum_temperature_in_C, rate_of_decrease_dHd);
285 this->Jmax = -1.f; // legacy field: setter is authoritative
286 }
287
289
292 void setTPU(float TPU) {
293 this->TPUTempResponse = PhotosyntheticTemperatureResponseParameters(TPU);
294 this->TPU_flag = 1;
295 }
296
298
303 void setTPU(float TPU_at_25_C, float rate_of_increase_dHa) {
304 this->TPUTempResponse = PhotosyntheticTemperatureResponseParameters(TPU_at_25_C, rate_of_increase_dHa);
305 this->TPU_flag = 1;
306 }
307
309
315 void setTPU(float TPU_at_25_C, float rate_of_increase_dHa, float optimum_temperature_in_C) {
316 float rate_of_decrease_dHd = 10.f * rate_of_increase_dHa;
317 this->TPUTempResponse = PhotosyntheticTemperatureResponseParameters(TPU_at_25_C, rate_of_increase_dHa, optimum_temperature_in_C, rate_of_decrease_dHd);
318 this->TPU_flag = 1;
319 }
320
322
328 void setTPU(float TPU_at_25_C, float rate_of_increase_dHa, float optimum_temperature_in_C, float rate_of_decrease_dHd) {
329 this->TPUTempResponse = PhotosyntheticTemperatureResponseParameters(TPU_at_25_C, rate_of_increase_dHa, optimum_temperature_in_C, rate_of_decrease_dHd);
330 this->TPU_flag = 1;
331 }
332
334
337 void setRd(float Rd) {
338 this->RdTempResponse = PhotosyntheticTemperatureResponseParameters(Rd);
339 this->Rd = -1.f; // legacy field: setter is authoritative
340 }
341
343
347 void setRd(float Rd_at_25_C, float rate_of_increase_dHa) {
348 this->RdTempResponse = PhotosyntheticTemperatureResponseParameters(Rd_at_25_C, rate_of_increase_dHa);
349 this->Rd = -1.f; // legacy field: setter is authoritative
350 }
351
353
359 void setRd(float Rd_at_25_C, float rate_of_increase_dHa, float optimum_temperature_in_C) {
360 float rate_of_decrease_dHd = 10.f * rate_of_increase_dHa;
361 this->RdTempResponse = PhotosyntheticTemperatureResponseParameters(Rd_at_25_C, rate_of_increase_dHa, optimum_temperature_in_C, rate_of_decrease_dHd);
362 this->Rd = -1.f; // legacy field: setter is authoritative
363 }
364
366
372 void setRd(float Rd_at_25_C, float rate_of_increase_dHa, float optimum_temperature_in_C, float rate_of_decrease_dHd) {
373 this->RdTempResponse = PhotosyntheticTemperatureResponseParameters(Rd_at_25_C, rate_of_increase_dHa, optimum_temperature_in_C, rate_of_decrease_dHd);
374 this->Rd = -1.f; // legacy field: setter is authoritative
375 }
376
378
381 void setQuantumEfficiency_alpha(float alpha) {
382 this->alphaTempResponse = PhotosyntheticTemperatureResponseParameters(alpha);
383 this->alpha = -1.f; // legacy field: setter is authoritative
384 }
385
387
391 void setQuantumEfficiency_alpha(float alpha_at_25_C, float rate_of_increase_dHa) {
392 this->alphaTempResponse = PhotosyntheticTemperatureResponseParameters(alpha_at_25_C, rate_of_increase_dHa);
393 this->alpha = -1.f; // legacy field: setter is authoritative
394 }
395
397
403 void setQuantumEfficiency_alpha(float alpha_at_25_C, float rate_of_increase_dHa, float optimum_temperature_in_C) {
404 float rate_of_decrease_dHd = 10.f * rate_of_increase_dHa;
405 this->alphaTempResponse = PhotosyntheticTemperatureResponseParameters(alpha_at_25_C, rate_of_increase_dHa, optimum_temperature_in_C, rate_of_decrease_dHd);
406 this->alpha = -1.f; // legacy field: setter is authoritative
407 }
408
410
416 void setQuantumEfficiency_alpha(float alpha_at_25_C, float rate_of_increase_dHa, float optimum_temperature_in_C, float rate_of_decrease_dHd) {
417 this->alphaTempResponse = PhotosyntheticTemperatureResponseParameters(alpha_at_25_C, rate_of_increase_dHa, optimum_temperature_in_C, rate_of_decrease_dHd);
418 this->alpha = -1.f; // legacy field: setter is authoritative
419 }
420
422
426 this->thetaTempResponse = PhotosyntheticTemperatureResponseParameters(theta);
427 }
428
430
434 void setLightResponseCurvature_theta(float theta_at_25_C, float rate_of_increase_dHa) {
435 this->thetaTempResponse = PhotosyntheticTemperatureResponseParameters(theta_at_25_C, rate_of_increase_dHa);
436 }
437
439
445 void setLightResponseCurvature_theta(float theta_at_25_C, float rate_of_increase_dHa, float optimum_temperature_in_C) {
446 float rate_of_decrease_dHd = 10.f * rate_of_increase_dHa;
447 this->thetaTempResponse = PhotosyntheticTemperatureResponseParameters(theta_at_25_C, rate_of_increase_dHa, optimum_temperature_in_C, rate_of_decrease_dHd);
448 }
449
451
457 void setLightResponseCurvature_theta(float theta_at_25_C, float rate_of_increase_dHa, float optimum_temperature_in_C, float rate_of_decrease_dHd) {
458 this->thetaTempResponse = PhotosyntheticTemperatureResponseParameters(theta_at_25_C, rate_of_increase_dHa, optimum_temperature_in_C, rate_of_decrease_dHd);
459 }
460
462
467 this->gmTempResponse = PhotosyntheticTemperatureResponseParameters(gm);
468 }
469
471
475 void setMesophyllConductance_gm(float gm_at_25_C, float rate_of_increase_dHa) {
476 this->gmTempResponse = PhotosyntheticTemperatureResponseParameters(gm_at_25_C, rate_of_increase_dHa);
477 }
478
480
485 void setMesophyllConductance_gm(float gm_at_25_C, float rate_of_increase_dHa, float optimum_temperature_in_C) {
486 this->gmTempResponse = PhotosyntheticTemperatureResponseParameters(gm_at_25_C, rate_of_increase_dHa, optimum_temperature_in_C);
487 }
488
490
496 void setMesophyllConductance_gm(float gm_at_25_C, float rate_of_increase_dHa, float optimum_temperature_in_C, float rate_of_decrease_dHd) {
497 this->gmTempResponse = PhotosyntheticTemperatureResponseParameters(gm_at_25_C, rate_of_increase_dHa, optimum_temperature_in_C, rate_of_decrease_dHd);
498 }
499
500 PhotosyntheticTemperatureResponseParameters getVcmaxTempResponse() {
501 return this->VcmaxTempResponse;
502 }
503
504 PhotosyntheticTemperatureResponseParameters getJmaxTempResponse() {
505 return this->JmaxTempResponse;
506 }
507
509 return this->TPUTempResponse;
510 }
511
513 return this->RdTempResponse;
514 }
515
516 PhotosyntheticTemperatureResponseParameters getQuantumEfficiencyTempResponse() {
517 return this->alphaTempResponse;
518 }
519
520 PhotosyntheticTemperatureResponseParameters getLightResponseCurvatureTempResponse() {
521 return this->thetaTempResponse;
522 }
523
524 PhotosyntheticTemperatureResponseParameters getMesophyllConductance_gmTempResponse() const {
525 return this->gmTempResponse;
526 }
527
528
529private:
537 PhotosyntheticTemperatureResponseParameters gmTempResponse = PhotosyntheticTemperatureResponseParameters(std::numeric_limits<float>::infinity());
538
539
540 friend class PhotosynthesisModel;
541};
542
543
545
555public:
557 // Flexible temperature responses — Setaria viridis defaults from Table 1 (Boyd et al. 2015 for Vpmax/Vcmax/Rd,
558 // Ubierna et al. 2017 refit Arrhenius for gm). Jmax is re-fit from the paper's Gaussian form
559 // Jmax(T) = 400·exp(−((T−43)/26)²) to the peaked-Arrhenius form used here — Jmax(25°C) = 247.69 μmol/m²/s
560 // matches the spreadsheet at the 25°C reference; dHa / Topt / dHd chosen to approximate the Gaussian.
561 VpmaxTempResponse = PhotosyntheticTemperatureResponseParameters(200.f, 50.1f);
562 VcmaxTempResponse = PhotosyntheticTemperatureResponseParameters(40.f, 78.0f);
563 JmaxTempResponse = PhotosyntheticTemperatureResponseParameters(247.69f, 77.9f, 43.0f, 260.f);
564 RdTempResponse = PhotosyntheticTemperatureResponseParameters(1.f, 66.4f);
565 gmTempResponse = PhotosyntheticTemperatureResponseParameters(1.f, 49.8f);
566 }
567
568 // === (1) Flexible temperature-responsive parameters: setter overloads mirror C3 API ===
569
571 void setVpmax(float Vpmax) {
572 this->VpmaxTempResponse = PhotosyntheticTemperatureResponseParameters(Vpmax);
573 }
575 void setVpmax(float Vpmax_at_25_C, float rate_of_increase_dHa) {
576 this->VpmaxTempResponse = PhotosyntheticTemperatureResponseParameters(Vpmax_at_25_C, rate_of_increase_dHa);
577 }
579 void setVpmax(float Vpmax_at_25_C, float rate_of_increase_dHa, float optimum_temperature_in_C) {
580 this->VpmaxTempResponse = PhotosyntheticTemperatureResponseParameters(Vpmax_at_25_C, rate_of_increase_dHa, optimum_temperature_in_C);
581 }
583 void setVpmax(float Vpmax_at_25_C, float rate_of_increase_dHa, float optimum_temperature_in_C, float rate_of_decrease_dHd) {
584 this->VpmaxTempResponse = PhotosyntheticTemperatureResponseParameters(Vpmax_at_25_C, rate_of_increase_dHa, optimum_temperature_in_C, rate_of_decrease_dHd);
585 }
586
588 void setVcmax(float Vcmax) {
589 this->VcmaxTempResponse = PhotosyntheticTemperatureResponseParameters(Vcmax);
590 }
592 void setVcmax(float Vcmax_at_25_C, float rate_of_increase_dHa) {
593 this->VcmaxTempResponse = PhotosyntheticTemperatureResponseParameters(Vcmax_at_25_C, rate_of_increase_dHa);
594 }
596 void setVcmax(float Vcmax_at_25_C, float rate_of_increase_dHa, float optimum_temperature_in_C) {
597 this->VcmaxTempResponse = PhotosyntheticTemperatureResponseParameters(Vcmax_at_25_C, rate_of_increase_dHa, optimum_temperature_in_C);
598 }
600 void setVcmax(float Vcmax_at_25_C, float rate_of_increase_dHa, float optimum_temperature_in_C, float rate_of_decrease_dHd) {
601 this->VcmaxTempResponse = PhotosyntheticTemperatureResponseParameters(Vcmax_at_25_C, rate_of_increase_dHa, optimum_temperature_in_C, rate_of_decrease_dHd);
602 }
603
605 void setJmax(float Jmax) {
606 this->JmaxTempResponse = PhotosyntheticTemperatureResponseParameters(Jmax);
607 }
609 void setJmax(float Jmax_at_25_C, float rate_of_increase_dHa) {
610 this->JmaxTempResponse = PhotosyntheticTemperatureResponseParameters(Jmax_at_25_C, rate_of_increase_dHa);
611 }
613 void setJmax(float Jmax_at_25_C, float rate_of_increase_dHa, float optimum_temperature_in_C) {
614 this->JmaxTempResponse = PhotosyntheticTemperatureResponseParameters(Jmax_at_25_C, rate_of_increase_dHa, optimum_temperature_in_C);
615 }
617 void setJmax(float Jmax_at_25_C, float rate_of_increase_dHa, float optimum_temperature_in_C, float rate_of_decrease_dHd) {
618 this->JmaxTempResponse = PhotosyntheticTemperatureResponseParameters(Jmax_at_25_C, rate_of_increase_dHa, optimum_temperature_in_C, rate_of_decrease_dHd);
619 }
620
622 void setRd(float Rd) {
623 this->RdTempResponse = PhotosyntheticTemperatureResponseParameters(Rd);
624 }
626 void setRd(float Rd_at_25_C, float rate_of_increase_dHa) {
627 this->RdTempResponse = PhotosyntheticTemperatureResponseParameters(Rd_at_25_C, rate_of_increase_dHa);
628 }
630 void setRd(float Rd_at_25_C, float rate_of_increase_dHa, float optimum_temperature_in_C) {
631 this->RdTempResponse = PhotosyntheticTemperatureResponseParameters(Rd_at_25_C, rate_of_increase_dHa, optimum_temperature_in_C);
632 }
634 void setRd(float Rd_at_25_C, float rate_of_increase_dHa, float optimum_temperature_in_C, float rate_of_decrease_dHd) {
635 this->RdTempResponse = PhotosyntheticTemperatureResponseParameters(Rd_at_25_C, rate_of_increase_dHa, optimum_temperature_in_C, rate_of_decrease_dHd);
636 }
637
640 this->gmTempResponse = PhotosyntheticTemperatureResponseParameters(gm);
641 }
643 void setMesophyllConductance_gm(float gm_at_25_C, float rate_of_increase_dHa) {
644 this->gmTempResponse = PhotosyntheticTemperatureResponseParameters(gm_at_25_C, rate_of_increase_dHa);
645 }
647 void setMesophyllConductance_gm(float gm_at_25_C, float rate_of_increase_dHa, float optimum_temperature_in_C) {
648 this->gmTempResponse = PhotosyntheticTemperatureResponseParameters(gm_at_25_C, rate_of_increase_dHa, optimum_temperature_in_C);
649 }
651 void setMesophyllConductance_gm(float gm_at_25_C, float rate_of_increase_dHa, float optimum_temperature_in_C, float rate_of_decrease_dHd) {
652 this->gmTempResponse = PhotosyntheticTemperatureResponseParameters(gm_at_25_C, rate_of_increase_dHa, optimum_temperature_in_C, rate_of_decrease_dHd);
653 }
654
655 // Accessors (read-only) used for material-data serialization
656 PhotosyntheticTemperatureResponseParameters getVpmaxTempResponse() const { return VpmaxTempResponse; }
657 PhotosyntheticTemperatureResponseParameters getVcmaxTempResponse() const { return VcmaxTempResponse; }
658 PhotosyntheticTemperatureResponseParameters getJmaxTempResponse() const { return JmaxTempResponse; }
659 PhotosyntheticTemperatureResponseParameters getRdTempResponse() const { return RdTempResponse; }
660 PhotosyntheticTemperatureResponseParameters getMesophyllConductance_gmTempResponse() const { return gmTempResponse; }
661
662 // === (2) Rubisco + PEPC kinetic constants — hardcoded 25 °C values with simple Arrhenius responses.
663 // Edit directly if different species are needed (paper defaults are for Setaria viridis). Units: μbar
664 // for Cm-like partial pressures, kJ/mol for activation energies.
665
666 float Kc_25 = 1210.f;
667 float Ko_25 = 292000.f;
668 float Kp_25 = 82.f;
669 float gamma_star_25 = 3.81679e-4f;
670 float Om_25 = 210000.f;
671
672 float dH_Kc = 64.2f;
673 float dH_Ko = 10.5f;
674 float dH_Kp = 38.3f;
675 // IMPORTANT — DO NOT change dH_gamma_star back to a negative value.
676 // The von Caemmerer (2021) Setaria spreadsheet (C4__model_setaria__11-06-2021.xlsm, cell C17)
677 // lists -31.1 kJ/mol, but this is a transcription error: Boyd, Gandin & Cousins (2015)
678 // Plant Physiol 169:1850 Table I reports Ea = -31.1 kJ/mol for S_c/o (Rubisco specificity,
679 // which decreases with temperature). Because γ* = 0.5/S_c/o, the activation energy for γ*
680 // has the OPPOSITE sign: Ea(γ*) = -Ea(S_c/o) = +31.1 kJ/mol. The spreadsheet copied Boyd's
681 // S_c/o value into the γ* row without the required sign flip. Woodford, Ermakova, Furbank
682 // & von Caemmerer (2025, bioRxiv 10.1101/2025.06.03.657559) Table 1 silently corrects this
683 // to +31.1 kJ/mol. Positive is also consistent with Bernacchi et al. (2001) for C3
684 // (Ea(Γ*) ≈ +37.8 kJ/mol) and with the physical fact that photorespiration increases with T.
685 float dH_gamma_star = 31.1f;
686 float dH_Om = 0.f;
687
688 // === (3) User-tunable scalar parameters (paper defaults in comments) ===
689
691 float x_etr_partition = 0.4f;
692 float Vpr = 80.f;
693 float Rm_frac = 0.5f;
694 float fcyc = 0.45f;
695 float gbs = 0.003f;
696 float ao = 0.047f;
697 float absorptance = 0.85f;
698 float f_spectral = 0.15f;
699 float theta_etr = 0.7f;
700 float h_protons = 4.f;
701 // Proton stoichiometries for the z factor in the Aj quadratic (ATP / linear-e⁻ ratio).
702 // vC2021 originally collapsed H_J and H_Jcyc to a single "3" in the numerator of the z formula,
703 // which implicitly assumes H_Jcyc = 2 (PGR5-only cyclic flow). Woodford et al. (2025) argue NDH
704 // dominates Setaria cyclic flow (Ermakova 2024 methyl-viologen work), giving H_Jcyc = 3.4, and
705 // derive fcyc = 0.45 consistent with Bjorkman & Demmig 1987 / Yin & Struik 2012 quantum yields.
706 // Together these recover the paper Table 1 value z = 1.45 via the corrected formula in
707 // computeC4RatesFromCm: z = (H_J·(1−fcyc) + H_Jcyc·fcyc) / (h_protons·(1−fcyc)).
708 float H_J = 3.f;
709 float H_Jcyc = 3.4f;
710
711private:
717
718 friend class PhotosynthesisModel;
719};
720
721
723public:
725
728 explicit PhotosynthesisModel(helios::Context *a_context);
729
730 static int selfTest(int argc = 0, char **argv = nullptr);
731
734
737
739 void setModelType_C4();
740
742
746 void setModelCoefficients(const EmpiricalModelCoefficients &modelcoefficients);
747
749
754 void setModelCoefficients(const EmpiricalModelCoefficients &modelcoefficients, const std::vector<uint> &UUIDs);
755
757
761 void setModelCoefficients(const FarquharModelCoefficients &modelcoefficients);
762
764
769 void setModelCoefficients(const FarquharModelCoefficients &modelcoefficients, const std::vector<uint> &UUIDs);
770
772
777 void setModelCoefficients(const std::vector<FarquharModelCoefficients> &modelcoefficients, const std::vector<uint> &UUIDs);
778
780
783 void setFarquharCoefficientsFromLibrary(const std::string &species);
784
786
790 void setFarquharCoefficientsFromLibrary(const std::string &species, const std::vector<uint> &UUIDs);
791
793
798
800
804 void setModelCoefficients(const std::string &material_label, const EmpiricalModelCoefficients &coeffs);
805
807
811 void setModelCoefficients(const std::string &material_label, const FarquharModelCoefficients &coeffs);
812
814
818 void setFarquharCoefficientsFromLibrary(const std::string &species, const std::string &material_label);
819
821
825 void setModelCoefficients(const C4ModelCoefficients &modelcoefficients);
826
828
833 void setModelCoefficients(const C4ModelCoefficients &modelcoefficients, const std::vector<uint> &UUIDs);
834
836
841 void setModelCoefficients(const std::string &material_label, const C4ModelCoefficients &coeffs);
842
844
848 void setC4CoefficientsFromLibrary(const std::string &species);
849
851
855 void setC4CoefficientsFromLibrary(const std::string &species, const std::vector<uint> &UUIDs);
856
858
863 void setC4CoefficientsFromLibrary(const std::string &species, const std::string &material_label);
864
866
871 C4ModelCoefficients getC4CoefficientsFromLibrary(const std::string &species);
872
874 void run();
875
877
880 void run(const std::vector<uint> &lUUIDs);
881
883
888
890
895
897
902
904 void disableMessages();
905
907 void enableMessages();
908
910
914 void optionalOutputPrimitiveData(const char *label);
915
917
924 void setCi(float Ci, const std::vector<uint> &UUIDs);
925
927
933 void setCm(float Cm, const std::vector<uint> &UUIDs);
934
936 void printDefaultValueReport() const;
937
939
942 void printDefaultValueReport(const std::vector<uint> &UUIDs) const;
943
944private:
946 helios::Context *context;
947
948 std::string model;
949 EmpiricalModelCoefficients empiricalmodelcoeffs;
950 FarquharModelCoefficients farquharmodelcoeffs;
951 C4ModelCoefficients c4modelcoeffs;
952
953 std::unordered_map<uint, EmpiricalModelCoefficients> empiricalmodel_coefficients;
954 std::unordered_map<uint, FarquharModelCoefficients> farquharmodel_coefficients;
955 std::unordered_map<uint, C4ModelCoefficients> c4model_coefficients;
956
957 // Cache to avoid repeated Context lookups during run()
958 mutable std::unordered_map<uint, EmpiricalModelCoefficients> material_coefficient_cache_empirical;
959 mutable std::unordered_map<uint, FarquharModelCoefficients> material_coefficient_cache_farquhar;
960 mutable std::unordered_map<uint, C4ModelCoefficients> material_coefficient_cache_c4;
961
962 // Helper to retrieve coefficients with caching
963 EmpiricalModelCoefficients getCoefficientsForPrimitive_Empirical(uint UUID) const;
964 FarquharModelCoefficients getCoefficientsForPrimitive_Farquhar(uint UUID) const;
965 C4ModelCoefficients getCoefficientsForPrimitive_C4(uint UUID) const;
966
968 std::unordered_map<uint, float> previous_Ci;
969
971 std::unordered_map<uint, float> manual_Ci;
972
974 std::unordered_map<uint, float> manual_Cm;
975
976 float evaluateEmpiricalModel(const EmpiricalModelCoefficients &params, float i_PAR, float TL, float CO2, float gM);
977
978 float evaluateFarquharModel(const FarquharModelCoefficients &params, float i_PAR, float TL, float CO2, float gM, float &Ci, float &Gamma, int &limitation_state, int &TPU_flag, float &J_over_Jmax, helios::WarningAggregator &warnings);
979
980 float evaluateC4Model(const C4ModelCoefficients &params, float i_PAR, float TL, float CO2, float gM, float &Ci, float &Cm, float &Vp, int &limitation_state, helios::WarningAggregator &warnings);
981
982 float evaluateCi_Empirical(const EmpiricalModelCoefficients &params, float Ci, float CO2, float fL, float fT, float Rd, float gM) const;
983
984 static float evaluateCi_Farquhar(float Ci, std::vector<float> &variables, const void *parameters);
985
986 static float evaluateCi_C4(float Ci, std::vector<float> &variables, const void *parameters);
987
989 static float evaluateCm_C4(float Cm, std::vector<float> &variables, const C4ModelCoefficients &params);
990
991 static float respondToTemperature(const PhotosyntheticTemperatureResponseParameters *temperatureResponseParameters, float T);
992
993 float i_PAR_default;
994 float TL_default;
995 float CO2_default;
996 float gM_default;
997 float gH_default;
998
999 bool message_flag = true;
1000
1002 std::vector<std::string> output_prim_data;
1003};
1004
1005#endif