-
Notifications
You must be signed in to change notification settings - Fork 6
/
sipnet.c
2788 lines (2124 loc) · 111 KB
/
sipnet.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/* SiPnET: Simplified PnET
Author: Bill Sacks [email protected]
modified by...
John Zobitz [email protected]
Dave Moore [email protected]
A simple box model of carbon cycling
largely based on PnET
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include "sipnet.h"
#include "runmean.h"
#include "util.h"
#include "spatialParams.h"
#include "outputItems.h"
// begin definitions for choosing different model structures
// (1 -> true, 0 -> false)
#define CSV_OUTPUT 0
//output .out file as a CSV file
//alternative transpiration methods modified by Dave Moore
#define ALTERNATIVE_TRANS 0
// do we want to impliment alternative transpiration?
#define BALL_BERRY 0
//impliment a Ball Berry submodel to calculate gs from RH, CO2 and A
//MUST BE OFF for PENMAN MONTEITH TO RUN
#define PENMAN_MONTEITH_TRANS 0
//impliment a transpiration calculation based on the Penman-Monteith Equation.
//March 1st 2007 PM equation not really working.
//#define G 0
//assume that soil heat flux is zero;
#define GROWTH_RESP 0
// explicitly model growth resp., rather than including with maint. resp.
#define LLOYD_TAYLOR 0
// use Lloyd-Taylor model for soil respiration, in which temperature sensitivity decreases at higher temperatures?
// Lloyd-Taylor model is R = R0 * e^(E0 * (1/(-T0) - 1/(T - T0))) (see Lloyd and Taylor 1994, "On the temperature dependence of soil respiration")
// where R0 is rate at 0 degrees C, T is soil temp., E0 and T0 are parameters
#define SEASONAL_R_SOIL 0 && !LLOYD_TAYLOR
// use different parameters for soil resp. (baseSoilResp and soilRespQ10) when tsoil < (some threshold)?
// if so, use standard parameters for warm soil, separate parameters for cold soil
// if we're using the Lloyd-Taylor model, we won't use different parameters at different temperatures
#define WATER_PSN 1
// does soil moisture affect photosynthesis?
#define WATER_HRESP 1
// does soil moisture affect heterotrophic respiration?
#define DAYCENT_WATER_HRESP 0 && WATER_HRESP
// use DAYCENT soil moisture function?
#define MODEL_WATER 1
// do we model soil water?
// if not, take soil wetness from climate file
#define COMPLEX_WATER 1 && MODEL_WATER
// do we use a more complex water submodel? (model evaporation as well as transpiration)
// when we use a complex water submodel, we always model snow
// if model water is off, then complex water is off: complex water wouldn't do anything
#define LITTER_WATER 0 && (COMPLEX_WATER)
// do we have a separate litter water layer, used for evaporation?
// if complex water is off, then litter water is off: litter water layer wouldn't do anything
#define LITTER_WATER_DRAINAGE 1 && (LITTER_WATER)
// does water from the top layer drain down into bottom layer even if top layer not overflowing?
// if litter water is off, then litter water drainage is off: litter water drainage wouldn't do anything
#define LEAF_WATER 0 && (COMPLEX_WATER)
// calculate leaf pool and evaporate from that pool
// makes immediate evaporation more realistic for smaller timesteps
#define SNOW (1 || (COMPLEX_WATER)) && MODEL_WATER
// keep track of snowpack, rather than assuming all precip. is liquid
// note: when using a complex water submodel, we ALWAYS keep track of snowpack
// if model water is off, then snow is off: snow wouldn't do anything
#define GDD 1
// use GDD to determine leaf growth? (note: mutually exclusive with SOIL_PHENOL)
#define SOIL_PHENOL 0 && !GDD
// use soil temp. to determine leaf growth? (note: mutually exclusive with GDD)
#define LITTER_POOL 0
// have extra litter pool, in addition to soil c pool
#define SOIL_MULTIPOOL 0 && !LITTER_POOL
// do we have a multipool approach to model soils?
// if LITTER_POOL == 1, then SOIL_MULTIPOOL will be 0 because we take care of litter with
// the soil quality submodel.
#define NUMBER_SOIL_CARBON_POOLS 3
// first number: number of pools we want to have.
// IF SOIL_MULTIPOOL=0, then NUMBER_SOIL_CARBON_POOLS = 1
// if SOIL_MULTIPOOL=1, then NUMBER_SOIL_CARBON_POOLS = number given
#define SOIL_QUALITY 0 && SOIL_MULTIPOOL
// do we have a soil quality submodel?
// we only do SOIL_QUALITY if SOIL_MULTIPOOL is turned on
#define MICROBES 0 && !SOIL_MULTIPOOL
// do we utilize microbes. This will only be an option
// if SOIL_MULTIPOOL==0 and MICROBES ==1
#define STOICHIOMETRY 0 && MICROBES
// do we utilize stoichometric considerations for the microbial pool?
#define ROOTS 1
// do we model root dynamics?
// end definitions for choosing different model structures
// begin constant definitions
#define C_WEIGHT 12.0 // molecular weight of carbon
#define TEN_6 1000000.0 // for conversions from micro
#define TEN_9 1000000000.0 // for conversions from nano
#define SEC_PER_DAY 86400.0
// constants for tracking running mean of NPP:
#define MEAN_NPP_DAYS 5 // over how many days do we keep the running mean?
#define MEAN_NPP_MAX_ENTRIES MEAN_NPP_DAYS * 50 // assume that the most pts we can have is two per hour
// constants for tracking running mean of GPP:
#define MEAN_GPP_SOIL_DAYS 5 // over how many days do we keep the running mean?
#define MEAN_GPP_SOIL_MAX_ENTRIES MEAN_GPP_SOIL_DAYS * 50 // assume that the most pts we can have is one per hour
// constants for tracking running mean of fPAR:
#define MEAN_FPAR_DAYS 1 // over how many days do we keep the running mean?
#define MEAN_FPAR_MAX_ENTRIES MEAN_FPAR_DAYS * 24 // assume that the most pts we can have is one per hour
// some constants for water submodel:
#define LAMBDA 2501000. // latent heat of vaporization (J/kg)
#define LAMBDA_S 2835000. // latent heat of sublimation (J/kg)
#define RHO 1.3 // air density (kg/m^3)
#define CP 1005. // specific heat of air (J/(kg K))
#define GAMMA 66. // psychometric constant (Pa/K)
#define E_STAR_SNOW 0.6 /* approximate saturation vapor pressure at 0 degrees C (kPa)
(we assume snow temperature is 0 degrees C or slightly lower) */
#define TINY 0.000001 // to avoid those nasty divide-by-zero errors
// end constant definitions
// linked list of climate variables
// one node for each time step
// these climate data are read in from a file
typedef struct ClimateVars ClimateNode;
struct ClimateVars {
int year; // year of start of this timestep
int day; // day of start of this timestep (1 = Jan 1.)
double time; // time of start of this timestep (hour.fraction - e.g. noon = 12.0, midnight = 0.0)
double length; // length of this timestep (in days) - allow variable-length timesteps
double tair; // avg. air temp for this time step (degrees C)
double tsoil; // avg. soil temp for this time step (degrees C)
double par; /* average par for this time step (Einsteins * m^-2 ground area * day^-1)
NOTE: input is in Einsteins * m^-2 ground area, summed over entire time step */
double precip; /* total precip. for this time step (cm water equiv. - either rain or snow)
NOTE: input is in mm */
double vpd; /* average vapor pressure deficit (kPa)
NOTE: input is in Pa */
double vpdSoil; /* average vapor pressure deficit between soil and air (kPa)
NOTE: input is in Pa
differs from vpd in that saturation vapor pressure calculated using Tsoil rather than Tair */
double vPress; /* average vapor pressure in canopy airspace (kPa)
NOTE: input is in Pa */
double wspd; // avg. wind speed (m/s)
double soilWetness; // fractional soil wetness (fraction of saturation - between 0 and 1)
#if GDD
double gdd; /* growing degree days from Jan. 1 to now
NOTE: Calculated, *not* read from file */
#endif
ClimateNode *nextClim;
};
// model parameters which can change from one run to the next
// these include initializations of state
// initial values are read in from a file, or calculated at start of model
// if any parameters are added here, and these parameters are to be read from file,
// be sure to add them to the readParamData function, below
typedef struct Parameters {
// parameters read in from file:
// initial state values:
double plantWoodInit; // g C * m^-2 ground area in wood (above-ground + roots)
double laiInit; // initial leaf area, m^2 leaves * m^-2 ground area (multiply by leafCSpWt to get initial plant leaf C)
double litterInit; // g C * m^-2 ground area
double soilInit; // g C * m^-2 ground area
double litterWFracInit; // unitless: fraction of litterWHC
double soilWFracInit; // unitless: fraction of soilWHC
double snowInit; // cm water equiv.
// 7 parameters
// parameters:
// photosynthesis:
double aMax; /* max photosynthesis (nmol CO2 * g^-1 leaf * sec^-1)
assuming max. possible par, all intercepted, no temp, water or vpd stress */
double aMaxFrac; // avg. daily aMax as fraction of instantaneous
double baseFolRespFrac; // basal foliage resp. rate, as % of max. net photosynth. rate
double psnTMin, psnTOpt; // min and optimal temps at which net photosynthesis occurs (degrees C)
double dVpdSlope, dVpdExp; // dVpd = 1 - dVpdSlope * vpd^dVpdExp
double halfSatPar; /* par at which photosynthesis occurs at 1/2 theoretical maximum
(Einsteins * m^-2 ground area * day^-1) */
double attenuation; // light attenuation coefficient
// 9 parameters
// phenology-related:
double leafOnDay; // day when leaves appear
double gddLeafOn; // with gdd-based phenology, gdd threshold for leaf appearance
double soilTempLeafOn; // with soil temp-based phenology, soil temp threshold for leaf appearance
double leafOffDay; // day when leaves disappear
double leafGrowth; // add'l leaf growth at start of growing season (g C * m^-2 ground)
double fracLeafFall; // add'l fraction of leaves that fall at end of growing season
double leafAllocation; // fraction of NPP allocated to leaf growth
double leafTurnoverRate; /* average turnover rate of leaves, in fraction per day
NOTE: read in as per-year rate! */
// 8 parameters
// autotrophic respiration:
double baseVegResp; /* vegetation maintenance respiration at 0 degrees C
(g C respired * g^-1 plant C * day^-1)
NOTE: only counts plant wood C - leaves handled elsewhere
(both above and below-ground: assumed for now to have same resp. rate)
NOTE: read in as per-year rate! */
double vegRespQ10; // scalar determining effect of temp on veg. resp.
double growthRespFrac; // growth resp. as fraction of (GPP - woodResp - folResp)
double frozenSoilFolREff; // amount that foliar resp. is shutdown if soil is frozen (0 = full shutdown, 1 = no shutdown)
double frozenSoilThreshold; // soil temperature below which frozenSoilFolREff and frozenSoilEff kick in (degrees C)
// 5 parameters
// soil respiration:
double litterBreakdownRate; /* rate at which litter is converted to soil/respired
at 0 degrees C and max soil moisture
(g C broken down * g^-1 litter C * day^-1)
NOTE: read in as per-year rate */
double fracLitterRespired; /* of the litter broken down, fraction respired
(the rest is transferred to soil pool) */
double baseSoilResp; /* soil respiration at 0 degrees C and max soil moisture
(g C respired * g^-1 soil C * day^-1)
NOTE: read in as per-year rate! */
double baseSoilRespCold; /* soil respiration at 0 degrees C and max soil moisture when tsoil < coldSoilThreshold
(g C respired * g^-1 soil C * day^-1)
NOTE: read in as per-year rate! */
// 6 parameters
double soilRespQ10; // scalar determining effect of temp on soil resp.
double soilRespQ10Cold; // scalar determining effect of temp on soil resp. when tsoil < coldSoilThreshold
double coldSoilThreshold; // temp. at which use baseSoilRespCold and soilRespQ10Cold (if SEASONAL_R_SOIL true) (degrees C)
double E0; // E0 in Lloyd-Taylor soil respiration function
double T0; // T0 in Lloyd-Taylor soil respiration function
double soilRespMoistEffect; // scalar determining effect of moisture on soil resp.
// 8 parameters
// moisture-related:
double waterRemoveFrac; /* fraction of plant available soil water which can be removed in one day
without water stress occurring */
double frozenSoilEff; /* fraction of water that's available if soil is frozen (0 = none available, 1 = all still avail.)
NOTE: if frozenSoilEff = 0, then shut down psn. even if WATER_PSN = 0, if soil is frozen
(if frozenSoilEff > 0, it has no effect if WATER_PSN = 0) */
double wueConst; // water use efficiency constant
double litterWHC; // litter (evaporative layer) water holding capacity (cm)
double soilWHC; // soil (transpiration layer) water holding capacity (cm)
double immedEvapFrac; // fraction of rain that is immediately intercepted & evaporated
double fastFlowFrac; // fraction of water entering soil that goes directly to drainage
double snowMelt; // rate at which snow melts (cm water equiv./degree C/day)
double litWaterDrainRate; // rate at which litter water drains into lower layer when litter layer fully moisture-saturated (cm water/day)
double rdConst; // scalar determining amount of aerodynamic resistance
double rSoilConst1, rSoilConst2; /* soil resistance = e^(rSoilConst1 - rSoilConst2 * W1)
where W1 = (litterWater/litterWHC) */
double m_ballBerry ; // slope for the Ball Berry relationship
double leafCSpWt; // g C * m^-2 leaf area
double cFracLeaf; // g leaf C * g^-1 leaf
double leafPoolDepth; // leaf (evaporative) pool rim thickness in mm
double woodTurnoverRate; /* average turnover rate of woody plant C, in fraction per day
(leaf loss handled separately)
NOTE: read in as per-year rate! */
// calculated parameters:
double psnTMax; // degrees C - assumed symmetrical around psnTOpt
// 16-1 calculated= 15 parameters
// quality model parameters
double qualityLeaf; // value for leaf litter quality
double qualityWood; // value for wood litter quality
double efficiency; // conversion efficiency of ingested carbon
// 4 parameters
double maxIngestionRate; // hr-1 - maximum ingestion rate of the microbe
double halfSatIngestion; // mg C g-1 soil - half saturation ingestion rate of microbe
double totNitrogen; // Percentage nitrogen in soil
double microbeNC; // mg N / mg C - microbe N:C ratio
// 5 parameters
double microbeInit; // mg C / g soil microbe initial carbon amount
// 1 parameters
double fineRootFrac; // fraction of wood carbon allocated to fine roots
double coarseRootFrac; // fraction of wood carbon that is coarse roots
double fineRootAllocation; // fraction of NPP allocated to fine roots
double woodAllocation; // fraction of NPP allocated to the roots
double fineRootExudation; // fraction of GPP exuded to the soil
double coarseRootExudation; // fraction of NPP exuded to the soil
// 6 parameters
double fineRootTurnoverRate; // turnover of fine roots (per year rate)
double coarseRootTurnoverRate; // turnover of coarse roots (per year rate)
double baseFineRootResp; // base respiration rate of fine roots (per year rate)
double baseCoarseRootResp; // base respiration rate of coarse roots (per year rate)
double fineRootQ10; // Q10 of fine roots
double coarseRootQ10; // Q10 of coarse roots
// 6 parameters
double baseMicrobeResp; // base respiration rate of microbes
double microbeQ10; // Q10 of coarse roots
double microbePulseEff; // fraction of exudates that microbes immediately use.
//Total parameters=7+9+8+5+14+15+4+5+1+5+6+3=81
// double soilBreakdownCoeff[NUMBER_SOIL_CARBON_POOLS]; // Rate coefficients if we do not have a multipool quality model
} Params;
#define NUM_PARAMS sizeof(Params)/sizeof(double)
// the state of the environment
typedef struct Environment {
double plantWoodC; // carbon in plant wood (above-ground + roots) (g C * m^-2 ground area)
double plantLeafC; // carbon in leaves (g C * m^-2 ground area)
double litter; // carbon in litter (g C * m^-2 ground area)
#if SOIL_MULTIPOOL
double soil[NUMBER_SOIL_CARBON_POOLS]; // if we have more than one quality pool we use this
#else
double soil;
#endif
double litterWater; // water in litter (evaporative) layer (cm)
double soilWater; // plant available soil water (cm)
double snow; // snow pack (cm water equiv.)
double microbeC; // carbon in microbes g C m-2 ground area
double coarseRootC;
double fineRootC;
} Envi;
// fluxes as per-day rates
typedef struct FluxVars {
double photosynthesis; // GROSS photosynthesis (g C * m^-2 ground area * day^-1)
double leafCreation; // g C * m^-2 ground area * day^-1 transferred from wood to leaves
double leafLitter; // g C * m^-2 ground area * day^-1 leaves falling
double woodLitter; // g C * m^-2 ground area * day^-1
double rVeg; // vegetation respiration (g C * m^-2 ground area * day^-1)
double litterToSoil; // g C * m^-2 ground area * day^-1 litter turned into soil
double rLitter; // g C * m^-2 ground area * day^-1 respired by litter
double rSoil; // g C * m^-2 ground area * day^-1 respired by soil
double rain; // cm water * day^-1 (only liquid precip.)
double snowFall; // cm water equiv. * day^-1
double immedEvap; // rain that's intercepted and immediately evaporated (cm water * day^-1)
double snowMelt; // cm water equiv. * day^-1
double sublimation; // cm water equiv. * day^-1
double fastFlow; // water entering soil that goes directly to drainage (out of system) (cm water * day^-1)
double evaporation; // evaporation from top of soil (cm water * day^-1)
double topDrainage; // drainage from top of soil to lower level (cm water * day^-1)
double bottomDrainage; // drainage from lower level of soil out of system (cm water * day^-1)
double transpiration; // cm water * day^-1
double rWood; // g C m^-2 ground area day^-1 of wood respiration
double rLeaf; // g C m^-2 ground area day^-1 of leaf respiration
#if SOIL_MULTIPOOL
double maintRespiration[NUMBER_SOIL_CARBON_POOLS]; // Microbial maintenance respiration rate g C m-2 ground area day^-1
double microbeIngestion[NUMBER_SOIL_CARBON_POOLS];
#else
double maintRespiration;
double microbeIngestion;
#endif
double fineRootLoss; // Loss rate of fine roots (turnover + exudation)
double coarseRootLoss; // Loss rate of coarse roots (turnover + exudation)
double fineRootCreation; // Creation rate of fine roots
double coarseRootCreation; // Creation rate of coarse roots
double woodCreation; // Creation rate of wood
double rCoarseRoot; // Coarse root respiration
double rFineRoot; // Fine root respiration
double soilPulse; // Exudates into the soil
} Fluxes;
typedef struct TrackerVars { // variables to track various things
double gpp; // g C * m^-2 taken up in this time interval: GROSS photosynthesis
double rtot; // g C * m^-2 respired in this time interval
double ra; // g C * m^-2 autotrophic resp. in this time interval
double rh; // g C * m^-2 heterotrophic resp. in this time interval
double npp; // g C * m^-2 taken up in this time interval
double nee; // g C * m^-2 given off in this time interval
double yearlyGpp; // g C * m^-2 taken up, year to date: GROSS photosynthesis
double yearlyRtot; // g C * m^-2 respired, year to date
double yearlyRa; // g C * m^-2 autotrophic resp., year to date
double yearlyRh; // g C * m^-2 heterotrophic resp., year to date
double yearlyNpp; // g C * m^-2 taken up, year to date
double yearlyNee; // g C * m^-2 given off, year to date
double totGpp; // g C * m^-2 taken up, to date: GROSS photosynthesis
double totRtot; // g C * m^-2 respired, to date
double totRa; // g C * m^-2 autotrophic resp., to date
double totRh; // g C * m^-2 heterotrophic resp., to date
double totNpp; // g C * m^-2 taken up, to date
double totNee; // g C * m^-2 given off, to date
double evapotranspiration; // cm water evaporated/sublimated (sublimed???) or transpired in this time step
double soilWetnessFrac; /* mean fractional soil wetness (soilWater/soilWHC) over this time step
(linear mean: mean of wetness at start of time step and wetness at end of time step) */
double fa; // g C * m^-2 of net photosynthesis (GPP - leaf respiration) in this time interval
double fr; // g C * m^-2 of non foliar respiration (soil + wood respiration) in this time interval
double totSoilC; // total soil carbon across all the pools
double rRoot; // g C m-2 of root respiration
double rSoil; // Soil respiration (microbes+root)
double rAboveground; // Wood and foliar respiration
double fpar; // 8 day mean fractional photosynthetically active radiation (percentage)
double plantWoodC; // carbon in plant wood (above-ground + roots) (g C * m^-2 ground area)
double LAI; //Leaf Area Index - leaf area per ground area / divide PlantLeafC by leafCSpWt
double yearlyLitter; // g C * m^-2 litterfall, year to date: SUM litter
} Trackers;
typedef struct PhenologyTrackersStruct { /* variables to track each year's phenological development
Only used in leafFluxes function, but made global so can be
initialized dynamically, based on day of year of first climate record */
int didLeafGrowth; // have we done leaf growth at start of growing season this year? (0 = no, 1 = yes)
int didLeafFall; // have we done leaf fall at end of growing season this year? (0 = no, 1 = yes)
int lastYear; // year of previous time step, for tracking when we hit a new calendar year
} PhenologyTrackers;
// global variables:
// made global so they can be initialized in a separate function
// so they only have to be initialized once
static ClimateNode **firstClimates; // a vector of pointers to first climates of each point in space
// more global variables:
// these are global to increase efficiency (avoid lots of parameter passing)
static Params params;
static Envi envi; // state variables
static Trackers trackers;
static PhenologyTrackers phenologyTrackers;
static MeanTracker *meanNPP; // running mean of NPP over some fixed time (stored in g C * m^-2 * day^-1)
static MeanTracker *meanGPP; // running mean of GPP over some fixed time for linkages (stored in g C * m^-2 * day^-1)
static MeanTracker *meanFPAR; // running mean of FPAR of some fixed time for MODIS
static ClimateNode *climate; // current climate
static Fluxes fluxes;
static double *outputPtrs[MAX_DATA_TYPES]; // pointers to different possible outputs
/* Read climate file into linked lists,
make firstClimates be a vector where each element is a pointer to the head of a list corresponding to one spatial location
return an array containing the number of time steps in each location (dynamically allocated with malloc)
numLocs = number of spatial locations: there should be one set of entries in climFile for each location,
or a location can be skipped, and we'll use climate at location 0 for that location, too
format of climFile:
each line represents one time step, with the following format:
location year day time intervalLength tair tsoil par precip vpd vpdSoil vPress wspd soilWetness
Note: there should be NO blank lines in file, even between different spatial locations;
all entries for a given location should be contiguous, and locations should be in ascending order
There may be locations for which there is no climate data, in which case we use climate from location 0
*/
int * readClimData(char *climFile, int numLocs) {
FILE *in;
ClimateNode *curr, *next;
int loc, year, day;
int lastYear;
double time, length; // time in hours, length in days (or fraction of day)
double tair, tsoil, par, precip, vpd, vpdSoil, vPress, wspd, soilWetness;
int currLoc;
int count;
int i;
int *steps; // # of time steps in each location
#if GDD
double thisGdd; // growing degree days of this time step
double gdd = 0.0; // growing degree days since the last Jan. 1
#endif
int status; // status of the read
steps = (int *)malloc(numLocs * sizeof(int));
for (i = 0; i < numLocs; i++)
steps[i] = 0; // 0 will denote nothing read
in = openFile(climFile, "r");
status = fscanf(in, "%d %d %d %lf %lf %lf %lf %lf %lf %lf %lf %lf %lf %lf", &loc, &year, &day, &time, &length, &tair, &tsoil, &par, &precip, &vpd, &vpdSoil, &vPress, &wspd, &soilWetness);
if (status == EOF) {
printf("Error: no climate data in %s\n", climFile);
exit(1);
}
if (loc != 0) {
printf("Error reading from climate file: first location must be loc. 0\n");
exit(1);
}
firstClimates = (ClimateNode **)malloc(numLocs * sizeof(ClimateNode *)); // vector of heads
for (currLoc = 0; currLoc < numLocs; currLoc++)
firstClimates[currLoc] = NULL; // default is null
// if firstClimates[i] stays null for some i, that means we didn't read any climate data for location i, so use climate from location 0
currLoc = loc;
firstClimates[currLoc] = (ClimateNode *)malloc(sizeof(ClimateNode)); // allocate space for head
curr = next = firstClimates[currLoc];
count = 0;
while (status != EOF) {
// we have another day's climate
curr = next;
count++; // # of time steps in this location
curr->year = year;
curr->day = day;
curr->time = time;
if (length < 0) // parse as seconds
length = length/-86400.; // convert to days
curr->length = length;
curr->tair = tair;
curr->tsoil = tsoil;
curr->par = par * (1.0/length);
// convert par from Einsteins * m^-2 to Eisteins * m^-2 * day^-1
curr->precip = precip * 0.1; // convert from mm to cm
curr->vpd = vpd * 0.001; // convert from Pa to kPa
if (curr->vpd < TINY)
curr->vpd = TINY; // avoid divide by zero
curr->vpdSoil = vpdSoil * 0.001; // convert from Pa to kPa
curr->vPress = vPress * 0.001; // convert from Pa to kPa
curr->wspd = wspd;
if (curr->wspd < TINY)
curr->wspd = TINY; // avoid divide by zero
curr->soilWetness = soilWetness;
#if GDD
if (year != lastYear) // HAPPY NEW YEAR!
gdd = 0; // reset growing degree days
thisGdd = tair * length;
if (thisGdd < 0) // can't have negative growing degree days
thisGdd = 0;
gdd += thisGdd;
curr->gdd = gdd;
#endif
lastYear = year;
status = fscanf(in, "%d %d %d %lf %lf %lf %lf %lf %lf %lf %lf %lf %lf %lf", &loc, &year, &day, &time, &length, &tair, &tsoil, &par, &precip, &vpd, &vpdSoil, &vPress, &wspd, &soilWetness);
if (status != EOF) { // we have another climate record
// check new location, compare with old location (currLoc), make sure new location is valid, and act accordingly:
if (loc == currLoc) { // still reading climate records from the same place: add a node at end of linked list
next = (ClimateNode *)malloc(sizeof(ClimateNode));
curr->nextClim = next;
// set this down here rather than at top of loop so head treated the same as rest of list
}
else if (loc >= numLocs) { // (loc == numLocs is an error since we use 0-indexing)
printf("Error reading climate file: trying to read location %d, but numLocs = %d\n", loc, numLocs);
exit(1);
}
else if (loc > currLoc) { // we've advanced to the next location (note: possible that we skipped some locations: this is OK)
curr->nextClim = NULL; // terminate last linked list
steps[currLoc] = count; // record the number of time steps for last location
count = 0; // reset count of # of time steps
#if GDD
gdd = 0; // reset growing degree days
#endif
currLoc = loc;
firstClimates[currLoc] = (ClimateNode *)malloc(sizeof(ClimateNode)); // allocate space for head
curr = next = firstClimates[currLoc]; // we'll start writing to next linked list
}
else { // loc < currLoc
printf("Error reading climate file: was reading location %d, trying to read location %d\n", currLoc, loc);
printf("Climate records for a given location should be contiguous, and locations should be in ascending order\n");
exit(1);
}
}
else { // status == EOF - no more records
curr->nextClim = NULL; // terminate this last linked list
steps[currLoc] = count; // record the number of time steps for last location
}
} // end while
fclose(in);
for (i = 0; i < numLocs; i++)
if (steps[i] == 0) // nothing read for this location
steps[i] = steps[0]; // this location will duplicate location 0
return steps;
}
// allocate & initialize spatialParamsPtr (a pointer to a SpatialParams pointer to allow for allocation)
// read in parameter file, put parameters (and other info) in spatialParams
// return numLocs (read from first line of spatialParams file)
// note that the last argument in the "initializeOneSpatialParam" function indicates whether the given parameter is required
// 1 -> must be in param file, 0 -> optional
// parameters that are required for the model to run should be flagged as 1 to allow for error checking
// if a parameter is only required with certain options, this argument can be set to a boolean value that evaluates to true iff these options are enabled
// for maximum convenience, all parameters can be flagged as 0 (i.e. optional), but you are taking your life into your own hands if you do so
int readParamData(SpatialParams **spatialParamsPtr, char *paramFile, char *spatialParamFile) {
FILE *paramF, *spatialParamF;
SpatialParams *spatialParams; // a local variable to prevent lots of unnecessary dereferences
int numLocs;
paramF = openFile(paramFile, "r");
spatialParamF = openFile(spatialParamFile, "r");
fscanf(spatialParamF, "%d", &numLocs);
if (numLocs < 1) {
printf("Error: numLocs must be >= 1: read %d\n", numLocs);
exit(1);
}
*spatialParamsPtr = newSpatialParams(NUM_PARAMS, numLocs);
spatialParams = *spatialParamsPtr; // to prevent lots of unnecessary dereferences
initializeOneSpatialParam(spatialParams, "plantWoodInit", &(params.plantWoodInit), 1);
initializeOneSpatialParam(spatialParams, "laiInit", &(params.laiInit), 1);
initializeOneSpatialParam(spatialParams, "litterInit", &(params.litterInit), 1);
initializeOneSpatialParam(spatialParams, "soilInit", &(params.soilInit), 1);
initializeOneSpatialParam(spatialParams, "litterWFracInit", &(params.litterWFracInit), 1);
initializeOneSpatialParam(spatialParams, "soilWFracInit", &(params.soilWFracInit), 1);
initializeOneSpatialParam(spatialParams, "snowInit", &(params.snowInit), 1);
initializeOneSpatialParam(spatialParams, "aMax", &(params.aMax), 1);
initializeOneSpatialParam(spatialParams, "aMaxFrac", &(params.aMaxFrac), 1);
initializeOneSpatialParam(spatialParams, "baseFolRespFrac", &(params.baseFolRespFrac), 1);
initializeOneSpatialParam(spatialParams, "psnTMin", &(params.psnTMin), 1);
initializeOneSpatialParam(spatialParams, "psnTOpt", &(params.psnTOpt), 1);
initializeOneSpatialParam(spatialParams, "vegRespQ10", &(params.vegRespQ10), 1);
initializeOneSpatialParam(spatialParams, "growthRespFrac", &(params.growthRespFrac), GROWTH_RESP);
initializeOneSpatialParam(spatialParams, "frozenSoilFolREff", &(params.frozenSoilFolREff), 1);
initializeOneSpatialParam(spatialParams, "frozenSoilThreshold", &(params.frozenSoilThreshold), 1);
initializeOneSpatialParam(spatialParams, "dVpdSlope", &(params.dVpdSlope), 1);
initializeOneSpatialParam(spatialParams, "dVpdExp", &(params.dVpdExp), 1);
initializeOneSpatialParam(spatialParams, "halfSatPar", &(params.halfSatPar), 1);
initializeOneSpatialParam(spatialParams, "attenuation", &(params.attenuation), 1);
initializeOneSpatialParam(spatialParams, "leafOnDay", &(params.leafOnDay), !((GDD) || (SOIL_PHENOL)));
initializeOneSpatialParam(spatialParams, "gddLeafOn", &(params.gddLeafOn), GDD);
initializeOneSpatialParam(spatialParams, "soilTempLeafOn", &(params.soilTempLeafOn), SOIL_PHENOL);
initializeOneSpatialParam(spatialParams, "leafOffDay", &(params.leafOffDay), 1);
initializeOneSpatialParam(spatialParams, "leafGrowth", &(params.leafGrowth), 1);
initializeOneSpatialParam(spatialParams, "fracLeafFall", &(params.fracLeafFall), 1);
initializeOneSpatialParam(spatialParams, "leafAllocation", &(params.leafAllocation), 1);
initializeOneSpatialParam(spatialParams, "leafTurnoverRate", &(params.leafTurnoverRate), 1);
initializeOneSpatialParam(spatialParams, "baseVegResp", &(params.baseVegResp), 1);
initializeOneSpatialParam(spatialParams, "litterBreakdownRate", &(params.litterBreakdownRate), LITTER_POOL);
initializeOneSpatialParam(spatialParams, "fracLitterRespired", &(params.fracLitterRespired), LITTER_POOL);
initializeOneSpatialParam(spatialParams, "baseSoilResp", &(params.baseSoilResp), 1);
initializeOneSpatialParam(spatialParams, "baseSoilRespCold", &(params.baseSoilRespCold), SEASONAL_R_SOIL);
initializeOneSpatialParam(spatialParams, "soilRespQ10", &(params.soilRespQ10), 1);
initializeOneSpatialParam(spatialParams, "soilRespQ10Cold", &(params.soilRespQ10Cold), SEASONAL_R_SOIL);
initializeOneSpatialParam(spatialParams, "coldSoilThreshold", &(params.coldSoilThreshold), SEASONAL_R_SOIL);
initializeOneSpatialParam(spatialParams, "E0", &(params.E0), LLOYD_TAYLOR);
initializeOneSpatialParam(spatialParams, "T0", &(params.T0), LLOYD_TAYLOR);
initializeOneSpatialParam(spatialParams, "soilRespMoistEffect", &(params.soilRespMoistEffect), ((WATER_HRESP) && !(DAYCENT_WATER_HRESP)));
initializeOneSpatialParam(spatialParams, "waterRemoveFrac", &(params.waterRemoveFrac), 1);
initializeOneSpatialParam(spatialParams, "frozenSoilEff", &(params.frozenSoilEff), 1);
initializeOneSpatialParam(spatialParams, "wueConst", &(params.wueConst), 1);
initializeOneSpatialParam(spatialParams, "litterWHC", &(params.litterWHC), 1);
initializeOneSpatialParam(spatialParams, "soilWHC", &(params.soilWHC), 1);
initializeOneSpatialParam(spatialParams, "immedEvapFrac", &(params.immedEvapFrac), COMPLEX_WATER);
initializeOneSpatialParam(spatialParams, "fastFlowFrac", &(params.fastFlowFrac), COMPLEX_WATER);
initializeOneSpatialParam(spatialParams, "leafPoolDepth", &(params.leafPoolDepth), LEAF_WATER);
initializeOneSpatialParam(spatialParams, "snowMelt", &(params.snowMelt), SNOW);
initializeOneSpatialParam(spatialParams, "litWaterDrainRate", &(params.litWaterDrainRate), LITTER_WATER_DRAINAGE);
initializeOneSpatialParam(spatialParams, "rdConst", &(params.rdConst), (COMPLEX_WATER) || (PENMAN_MONTEITH_TRANS));
initializeOneSpatialParam(spatialParams, "rSoilConst1", &(params.rSoilConst1), COMPLEX_WATER);
initializeOneSpatialParam(spatialParams, "rSoilConst2", &(params.rSoilConst2), COMPLEX_WATER);
initializeOneSpatialParam(spatialParams, "leafCSpWt", &(params.leafCSpWt), 1);
initializeOneSpatialParam(spatialParams, "cFracLeaf", &(params.cFracLeaf), 1);
initializeOneSpatialParam(spatialParams, "woodTurnoverRate", &(params.woodTurnoverRate), 1);
initializeOneSpatialParam(spatialParams, "qualityLeaf", &(params.qualityLeaf), SOIL_QUALITY);
initializeOneSpatialParam(spatialParams, "qualityWood", &(params.qualityWood), SOIL_QUALITY);
initializeOneSpatialParam(spatialParams, "efficiency", &(params.efficiency), (SOIL_QUALITY) || (MICROBES));
initializeOneSpatialParam(spatialParams, "maxIngestionRate", &(params.maxIngestionRate), (SOIL_QUALITY) || (MICROBES));
initializeOneSpatialParam(spatialParams, "halfSatIngestion", &(params.halfSatIngestion), MICROBES);
initializeOneSpatialParam(spatialParams, "totNitrogen", &(params.totNitrogen), STOICHIOMETRY);
initializeOneSpatialParam(spatialParams, "microbeNC", &(params.microbeNC), STOICHIOMETRY);
initializeOneSpatialParam(spatialParams, "microbeInit", &(params.microbeInit), (SOIL_QUALITY) || (MICROBES));
initializeOneSpatialParam(spatialParams, "fineRootFrac", &(params.fineRootFrac), ROOTS);
initializeOneSpatialParam(spatialParams, "coarseRootFrac", &(params.coarseRootFrac), ROOTS);
initializeOneSpatialParam(spatialParams, "fineRootAllocation", &(params.fineRootAllocation), ROOTS);
initializeOneSpatialParam(spatialParams, "woodAllocation", &(params.woodAllocation), ROOTS);
initializeOneSpatialParam(spatialParams, "fineRootExudation", &(params.fineRootExudation), ROOTS);
initializeOneSpatialParam(spatialParams, "coarseRootExudation", &(params.coarseRootExudation), ROOTS);
initializeOneSpatialParam(spatialParams, "fineRootTurnoverRate", &(params.fineRootTurnoverRate), ROOTS);
initializeOneSpatialParam(spatialParams, "coarseRootTurnoverRate", &(params.coarseRootTurnoverRate), ROOTS);
initializeOneSpatialParam(spatialParams, "baseFineRootResp", &(params.baseFineRootResp), ROOTS);
initializeOneSpatialParam(spatialParams, "baseCoarseRootResp", &(params.baseCoarseRootResp), ROOTS);
initializeOneSpatialParam(spatialParams, "fineRootQ10", &(params.fineRootQ10), ROOTS);
initializeOneSpatialParam(spatialParams, "coarseRootQ10", &(params.coarseRootQ10), ROOTS);
initializeOneSpatialParam(spatialParams, "baseMicrobeResp", &(params.baseMicrobeResp), MICROBES);
initializeOneSpatialParam(spatialParams, "microbeQ10", &(params.microbeQ10), MICROBES);
initializeOneSpatialParam(spatialParams, "microbePulseEff", &(params.microbePulseEff), (ROOTS) && (MICROBES) );
initializeOneSpatialParam(spatialParams, "m_ballBerry", &(params.m_ballBerry), 1);
readSpatialParams(spatialParams, paramF, spatialParamF);
fclose(paramF);
fclose(spatialParamF);
return numLocs;
}
// pre: out is open for writing
// print header line to output file
//I wish someone who write a .header file to automatically output the correct header
// Not only that ...I'd like the options used in the model run to be added to the file;
void outputHeader(FILE *out) {
fprintf(out, "Notes: (PlantWoodC, PlantLeafC, Soil and Litter in g C/m^2; Water and Snow in cm; SoilWetness is fraction of WHC;\n");
fprintf(out, "loc year day time plantWoodC plantLeafC ");
#if SOIL_MULTIPOOL
int counter;
for(counter=0; counter<NUMBER_SOIL_CARBON_POOLS; counter++) {
fprintf(out, "soil(%8.2f) ",envi.soil[counter]);
}
fprintf(out,"totSoilC ");
#else
fprintf(out, "soil ");
#endif
fprintf(out, "microbeC ");
fprintf(out, "coarseRootC fineRootC ");
fprintf(out, "litter litterWater soilWater soilWetnessFrac snow ");
fprintf(out, "npp nee cumNEE gpp rAboveground rSoil rRoot ra rh rtot evapotranspiration fluxestranspiration fPAR\n");
}
// pre: out is open for writing
// print current state to output file
void outputState(FILE *out, int loc, int year, int day, double time) {
fprintf(out,"%8d %4d %3d %5.2f %8.2f %8.2f ",loc,year,day,time,envi.plantWoodC,envi.plantLeafC);
#if SOIL_MULTIPOOL
int counter;
for(counter=0; counter<NUMBER_SOIL_CARBON_POOLS; counter++) {
fprintf(out, "%8.2f ",envi.soil[counter]);
}
fprintf(out,"%8.2f ",trackers.totSoilC);
#else
fprintf(out, "%8.2f ",envi.soil);
#endif
fprintf(out, "%8.2f ", envi.microbeC);
fprintf(out, "%8.2f %8.2f", envi.coarseRootC,envi.fineRootC);
fprintf(out, " %8.2f %8.3f %8.2f %8.3f %8.2f ",
envi.litter, envi.litterWater, envi.soilWater, trackers.soilWetnessFrac, envi.snow);
fprintf(out,"%8.2f %8.2f %8.2f %8.2f %8.3f %8.3f %8.3f %8.3f %8.3f %8.3f %8.8f %8.4f %8.4f\n", trackers.npp, trackers.nee, trackers.totNee, trackers.gpp, trackers.rAboveground,
trackers.rSoil, trackers.rRoot, trackers.ra, trackers.rh, trackers.rtot, trackers.evapotranspiration, fluxes.transpiration, trackers.fpar);
//note without modeling root dynamics
//trackers.fa, trackers.fr, fluxes.rLeaf*climate->length,trackers.evapotranspiration
}
void outputStatecsv(FILE *out, int loc, int year, int day, double time) {
fprintf(out, "%8d , %4d , %3d , %5.2f , %8.2f , %8.2f , ", loc, year, day, time,
envi.plantWoodC, envi.plantLeafC);
#if SOIL_MULTIPOOL
fprintf(out, "%8.2f ,", trackers.totSoilC);
#else
fprintf(out, "%8.2f ,", envi.soil);
#endif
fprintf(out, "%8.2f , %8.3f, %8.2f , %8.3f , %8.2f , %8.2f , %8.2f , %8.2f , %8.2f , %8.3f , %8.3f , %8.3f, %8.3f , %8.3f , %8.3f %8.8f %8.4f %8.4f\n",
envi.litter, envi.litterWater, envi.soilWater, trackers.soilWetnessFrac, envi.snow,
trackers.npp, trackers.nee, trackers.totNee, trackers.gpp, trackers.rAboveground, trackers.rSoil, trackers.rRoot, trackers.ra, trackers.rh, trackers.rtot, trackers.evapotranspiration, fluxes.transpiration, trackers.fpar);
}
// de-allocate space used for climate linked list
void freeClimateList(int numLocs) {
ClimateNode *curr, *prev;
int loc;
for (loc = 0; loc < numLocs; loc++) { // loop through firstClimates, deallocating each linked list
curr = firstClimates[loc];
while (curr != NULL) {
prev = curr;
curr = curr->nextClim;
free(prev);
}
}
// and finally deallocate the vector itself:
free(firstClimates);
}
// !!! functions for calculating auxiliary variables !!!
// rather than returning a value, they have as parameters the variable(s) which they modify
// so a single function can modify multiple variables
// calculate amount of light absorbed
// using light attenuation (as in PnET)
// CURRENTLY UNUSED (see calcLightEff3)
void calcLightEff2 (double *lightEff, double lai, double par) {
static const int NUM_LAYERS = 50;
int layer; // counter
double cumLai = 0.0; // lai from this layer up
double lightIntensity;
double cumLightEff = 0.0;
if (lai > 0 && par > 0) { // must have at least some leaves and some light
for (layer = 1; layer <= NUM_LAYERS; layer++) {
cumLai = lai * ((double)layer / NUM_LAYERS); // lai from this layer up
lightIntensity = par * exp(-1.0 * params.attenuation * cumLai); // between 0 and par
cumLightEff += (1 - pow(2, (-1.0 * lightIntensity/params.halfSatPar))); // add between 0 and 1
// when lightIntensity = halfSatPar, add 1/2
}
*lightEff = cumLightEff/(double)NUM_LAYERS; // find average light effect
}
else // no leaves or no light!
*lightEff = 0;
}
// another method to calculate amount of light absorbed
// using light attenuation (as in PnET)
// difference between this and calcLightEff2 is that here we use Simpson's method to approximate the integral
void calcLightEff3 (double *lightEff, double lai, double par) {
/*
We are essentially integrating over the canopy, from top to bottom,
but it's an ugly integral, so we'll approximate it numerically.
Here we use Simpson's method to approximate the integral, so we need an odd number of points.
This means that NUM_LAYERS must be EVEN because we loop from layer = 0 to layer = NUM_LAYERS
As a reminder, Simpson's rule approximates the integral as:
(h/3) * (y(0) + 4y(1) + 2y(2) + 4y(3) + ... + 2y(n-2) + 4y(n-1) + y(n)),
where h is the distance between each x value (here h = 1/NUM_LAYERS).
We keep track of the running sum in cumLightEff.
*/
// Information on the distribution of LAI with height is available
// as of March 2007 ... contact Dr. Maggie Prater [email protected]
static const int NUM_LAYERS = 6;
// believe it or not, 6 layers gives approximately the same result as 100 layers
int layer; // counter
double cumLai; // lai from this layer up
double lightIntensity;
double currLightEff, cumLightEff;
double currfAPAR,cumfAPAR;
int coeff; // current coefficient in Simpson's rule
if (lai > 0 && par > 0) { // must have at least some leaves and some light
cumLai = 0.0;
cumLightEff = 0.0; // the running sum
layer = 0;
coeff = 1;
int err;
double fAPAR; // Calculation of fAPAR according to 1 - exp(attenuation*LAI)
double APAR; // Absorbed PAR by the canopy
double lightIntensityTop, lightIntensityBottom; // PAR absorbed by the canopy
cumfAPAR = 0.0;
while (layer <= NUM_LAYERS) {
cumLai = lai * ((double)layer / NUM_LAYERS); // lai from this layer up (starting at top)
lightIntensity = par * exp(-1.0 * params.attenuation * cumLai); // between 0 and par
currLightEff = (1 - pow(2, (-1.0 * lightIntensity/params.halfSatPar))); // between 0 and 1
// when lightIntensity = halfSatPar, currLightEff = 1/2
cumLightEff += coeff * currLightEff;
currfAPAR = 1 - (lightIntensity / par);
cumfAPAR += coeff * currfAPAR;
// now move to the next layer:
layer++;
coeff = 2*(1 + layer%2); // coeff. goes 1, 4, 2, 4, ..., 2, 4, 2
}
// last value should have had a coefficient of 1, but actually had a coefficient of 2,
// so subtract 1:
cumLightEff -= currLightEff;
*lightEff = cumLightEff/(3.0*NUM_LAYERS); // multiplying by (h/3) in Simpson's rule
// now calculate fAPAR
cumfAPAR -= currfAPAR;
fAPAR = cumfAPAR/(3.0*NUM_LAYERS); // the average value, multiplying by h/3 in Simpson's rule, and dividing by the number of steps
lightIntensityTop = par; // Energy at the top of the canopy
lightIntensityBottom = par * exp(-1.0 * params.attenuation * lai); // LAI at the bottom of the canopy
// between 0 and par
// this is the amount of incident par
APAR = params.m_ballBerry * (lightIntensityTop - lightIntensityBottom); // APAR at this layer
// is a fraction of the difference between incoming par and transmitted par
//fAPAR = APAR / par; // Take the average across all of the layers
// fAPAR = (1 - exp(-1.0 * params.attenuation * lai)); // 4/28/11: Update from TQuaife
err = addValueToMeanTracker(meanFPAR, fAPAR, 1); // update running mean of FPAR (we don't care about climate length)
if (err != 0) {
printf("******* Error type %d while trying to add value to FPAR mean tracker in sipnet:potPSN() *******\n", err);
printf("FPAR = %f, climate->length = %f\n", fAPAR, climate->length);
printf("Suggestion: try changing MEAN_FPAR_MAX_ENTRIES in sipnet.c\n");
exit(1);
}
}
else // no leaves or no light!
*lightEff = 0;
}
// calculate gross photosynthesis without water effect (g C * m^-2 ground area * day^-1)
// and base foliar respiration without temp, water, etc. (g C * m^-2 ground area * day^-1)