-
Notifications
You must be signed in to change notification settings - Fork 29
SIP256 Leaf on/off events #326
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
29baebf
95fc40b
50102a1
95b5276
72a66f8
73a7611
4ed698e
cfabbee
506181e
5fc0ed3
66f53b4
b5419ba
90e4e4c
34a2387
be248c4
9461232
b9d97c2
942bd0f
96e75c6
49ff24f
2896cf8
50d4281
090dc09
d7201e6
f322288
c2bbae9
d9866d0
a512961
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -136,6 +136,32 @@ EventNode *createEventNode(int year, int day, int eventType, | |
| tParams->tillageEffect = tillEffect; | ||
| newEvent->eventParams = tParams; | ||
| } break; | ||
| case LEAFON: { | ||
| double dummy; | ||
| LeafOnParams *lParams = (LeafOnParams *)malloc(sizeof(LeafOnParams)); | ||
| // Check for extraneous data: leafon takes no parameters, so error if any | ||
| // numbers are found. sscanf returns 0 or EOF (-1) for empty/whitespace. | ||
| int numRead = sscanf(eventParamsStr, // NOLINT | ||
| "%lf", &dummy); | ||
| if (numRead > NUM_LEAFON_PARAMS) { | ||
| logError("parsing LeafOn params for year %d day %d\n", year, day); | ||
| exit(EXIT_CODE_INPUT_FILE_ERROR); | ||
| } | ||
| newEvent->eventParams = lParams; | ||
| } break; | ||
| case LEAFOFF: { | ||
| double dummy; | ||
| LeafOffParams *lParams = (LeafOffParams *)malloc(sizeof(LeafOffParams)); | ||
| // Check for extraneous data: leafoff takes no parameters, so error if | ||
| // any numbers are found. sscanf returns 0 or EOF (-1) for empty input. | ||
| int numRead = sscanf(eventParamsStr, // NOLINT | ||
| "%lf", &dummy); | ||
| if (numRead > NUM_LEAFOFF_PARAMS) { | ||
|
Alomir marked this conversation as resolved.
|
||
| logError("parsing LeafOff params for year %d day %d\n", year, day); | ||
| exit(EXIT_CODE_INPUT_FILE_ERROR); | ||
| } | ||
| newEvent->eventParams = lParams; | ||
| } break; | ||
| default: | ||
| // Unknown type, error and exit | ||
| logError("found unknown event type %d while reading event file\n", | ||
|
|
@@ -159,6 +185,10 @@ const char *eventTypeToString(event_type_t type) { | |
| return "fert"; | ||
| case TILLAGE: | ||
| return "till"; | ||
| case LEAFON: | ||
| return "leafon"; | ||
| case LEAFOFF: | ||
| return "leafoff"; | ||
| default: | ||
| logError("unknown event type in eventTypeToString (%d)", type); | ||
| exit(EXIT_CODE_UNKNOWN_EVENT_TYPE_OR_PARAM); | ||
|
|
@@ -168,15 +198,26 @@ const char *eventTypeToString(event_type_t type) { | |
| event_type_t eventStringToType(const char *eventTypeStr) { | ||
| if (strcmp(eventTypeStr, "irrig") == 0) { | ||
| return IRRIGATION; | ||
| } else if (strcmp(eventTypeStr, "fert") == 0) { | ||
| } | ||
| if (strcmp(eventTypeStr, "fert") == 0) { | ||
| return FERTILIZATION; | ||
| } else if (strcmp(eventTypeStr, "plant") == 0) { | ||
| } | ||
| if (strcmp(eventTypeStr, "plant") == 0) { | ||
| return PLANTING; | ||
| } else if (strcmp(eventTypeStr, "till") == 0) { | ||
| } | ||
| if (strcmp(eventTypeStr, "till") == 0) { | ||
| return TILLAGE; | ||
| } else if (strcmp(eventTypeStr, "harv") == 0) { | ||
| } | ||
| if (strcmp(eventTypeStr, "harv") == 0) { | ||
| return HARVEST; | ||
| } | ||
| if (strcmp(eventTypeStr, "leafon") == 0) { | ||
| return LEAFON; | ||
| } | ||
| if (strcmp(eventTypeStr, "leafoff") == 0) { | ||
| return LEAFOFF; | ||
| } | ||
|
|
||
| return UNKNOWN_EVENT; | ||
| } | ||
|
|
||
|
|
@@ -193,6 +234,18 @@ static void checkEventLineTruncation(const char *line, size_t len) { | |
| } | ||
| } | ||
|
|
||
| void checkForCalculatedLeafEvents(void) { | ||
| // We have a leaf event in events.in, so make sure we are not also | ||
| // calculating leaf events | ||
| if (ctx.gdd || ctx.soilPhenol || params.leafOnDay > 0 || | ||
| params.leafOffDay > 0) { | ||
| logError("calculated leaf events (via leafOnDay/leafOffDay params or " | ||
| "gdd/soil-phenol command-line options) are not compatible " | ||
| "with user-specified leaf events in event file\n"); | ||
| exit(EXIT_CODE_BAD_PARAMETER_VALUE); | ||
| } | ||
| } | ||
|
|
||
| EventNode *readEventData(const char *eventFile) { | ||
| int year, day, eventType; | ||
| int currYear, currDay; | ||
|
|
@@ -202,6 +255,7 @@ EventNode *readEventData(const char *eventFile) { | |
| char line[EVENT_LINE_SIZE]; | ||
| EventNode *curr, *next; | ||
| EventNode *newEvents = NULL; | ||
| int hasCheckedLeafEvents = 0; | ||
|
|
||
| // Check for a non-empty file | ||
| if (access(eventFile, F_OK) != 0) { | ||
|
|
@@ -238,6 +292,13 @@ EventNode *readEventData(const char *eventFile) { | |
| exit(EXIT_CODE_UNKNOWN_EVENT_TYPE_OR_PARAM); | ||
| } | ||
|
|
||
| if (eventType == LEAFOFF || eventType == LEAFON) { | ||
| // If we have a leaf event, make sure we aren't also looking for | ||
| // calculated leaf events. | ||
| checkForCalculatedLeafEvents(); | ||
| hasCheckedLeafEvents = 1; | ||
| } | ||
|
|
||
| newEvents = createEventNode(year, day, eventType, eventParamsStr); | ||
| next = newEvents; | ||
| currYear = year; | ||
|
|
@@ -264,12 +325,20 @@ EventNode *readEventData(const char *eventFile) { | |
| exit(EXIT_CODE_UNKNOWN_EVENT_TYPE_OR_PARAM); | ||
| } | ||
|
|
||
| if (eventType == LEAFOFF || eventType == LEAFON) { | ||
| // If we have a leaf event, make sure we aren't also looking for | ||
| // calculated leaf events. | ||
| if (!hasCheckedLeafEvents) { | ||
| checkForCalculatedLeafEvents(); | ||
| hasCheckedLeafEvents = 1; | ||
| } | ||
| } | ||
|
|
||
| if ((year < currYear) || ((year == currYear) && (day < currDay))) { | ||
| // clang-format off | ||
| logError("reading event file: last event was at (%d, %d), next event is " | ||
| "at (%d, %d)\n", currYear, currDay, year, day); | ||
| "at (%d, %d)\n", | ||
| currYear, currDay, year, day); | ||
| logError("event records must be in time-ascending order\n"); | ||
| // clang-format on | ||
| exit(EXIT_CODE_INPUT_FILE_ERROR); | ||
| } | ||
|
|
||
|
|
@@ -363,30 +432,9 @@ int isFirstEventBefore(int year, int day) { | |
| return firstEvent->day < day; | ||
| } | ||
|
|
||
| void resetEventFluxes(void) { | ||
| fluxes.eventLeafC = 0.0; | ||
| fluxes.eventWoodC = 0.0; | ||
| fluxes.eventFineRootC = 0.0; | ||
| fluxes.eventCoarseRootC = 0.0; | ||
| fluxes.eventEvap = 0.0; | ||
| fluxes.eventSoilWater = 0.0; | ||
| fluxes.eventSoilC = 0.0; | ||
| fluxes.eventLitterC = 0.0; | ||
| fluxes.eventMinN = 0.0; | ||
| fluxes.eventSoilOrgN = 0.0; | ||
| fluxes.eventLitterN = 0.0; | ||
|
|
||
| // mass balance | ||
| fluxes.eventInputC = 0.0; | ||
| fluxes.eventOutputC = 0.0; | ||
| fluxes.eventInputN = 0.0; | ||
| fluxes.eventOutputN = 0.0; | ||
| } | ||
|
|
||
| void processEvents(void) { | ||
| // Set all event fluxes to zero, as these have no memory from one step to | ||
| // the next | ||
| resetEventFluxes(); | ||
| // Event fluxes have all been reset to zero at the start of the time step, | ||
| // so we can just add to them as needed | ||
|
|
||
| // If event starts off NULL, this function will just fall through, as it | ||
| // should. | ||
|
|
@@ -537,15 +585,20 @@ void processEvents(void) { | |
| fracRB); | ||
| fluxes.eventOutputN += outputN / climLen; | ||
| } | ||
| // clang-format off | ||
| writeEventOut( | ||
| gEvent, 10, "fluxes.eventSoilC", soilAdd / climLen, | ||
| "fluxes.eventLitterC", litterAdd / climLen, "fluxes.eventLeafC", | ||
| leafDelta / climLen, "fluxes.eventWoodC", woodDelta / climLen, | ||
| gEvent, 10, | ||
| "fluxes.eventSoilC", soilAdd / climLen, | ||
| "fluxes.eventLitterC", litterAdd / climLen, | ||
| "fluxes.eventLeafC", leafDelta / climLen, | ||
| "fluxes.eventWoodC", woodDelta / climLen, | ||
| "fluxes.eventFineRootC", fineDelta / climLen, | ||
| "fluxes.eventCoarseRootC", coarseDelta / climLen, | ||
| "fluxes.eventSoilOrgN", soilNAdd / climLen, "fluxes.eventLitterN", | ||
| litterNAdd / climLen, "fluxes.eventOutputC", outputC / climLen, | ||
| "fluxes.eventSoilOrgN", soilNAdd / climLen, | ||
| "fluxes.eventLitterN", litterNAdd / climLen, | ||
| "fluxes.eventOutputC", outputC / climLen, | ||
| "fluxes.eventOutputN", outputN / climLen); | ||
| // clang-format on | ||
| } break; | ||
| case TILLAGE: { | ||
| // BIG NOTE: this is the one event type that is NOT modeled as a flux; | ||
|
|
@@ -561,8 +614,12 @@ void processEvents(void) { | |
| case FERTILIZATION: { | ||
| const FertilizationParams *fertParams = gEvent->eventParams; | ||
| const double orgC = fertParams->orgC; | ||
| double orgN = fertParams->orgN; | ||
| double minN = fertParams->minN; | ||
| double orgN = 0.0; | ||
| double minN = 0.0; | ||
| if (ctx.nitrogenCycle) { | ||
| orgN = fertParams->orgN; | ||
| minN = fertParams->minN; | ||
| } | ||
| if (ctx.litterPool) { | ||
| fluxes.eventLitterC += orgC / climLen; | ||
| } else { | ||
|
|
@@ -583,10 +640,47 @@ void processEvents(void) { | |
| fluxes.eventInputN += (orgN + minN) / climLen; | ||
| } | ||
|
|
||
| writeEventOut(gEvent, 5, "fluxes.eventOrgN", orgN / climLen, | ||
| "fluxes.eventLitterC", orgC / climLen, "fluxes.eventMinN", | ||
| minN / climLen, "fluxes.eventInputC", orgC / climLen, | ||
| "fluxes.eventInputN", (orgN + minN) / climLen); | ||
| // clang-format off | ||
| writeEventOut(gEvent, 6, | ||
| "fluxes.eventLitterC", ctx.litterPool ? orgC / climLen : 0.0, | ||
| "fluxes.eventSoilC", ctx.litterPool ? 0.0 : orgC / climLen, | ||
| "fluxes.eventMinN", minN / climLen, | ||
| "fluxes.eventLitterN", orgN / climLen, | ||
| "fluxes.eventInputC", orgC / climLen, | ||
| "fluxes.eventInputN", (orgN + minN) / climLen); | ||
| // clang-format on | ||
| } break; | ||
| case LEAFON: { | ||
| double leafOn = params.leafGrowth; | ||
| fluxes.eventLeafOnCreation += leafOn / climLen; | ||
|
|
||
| // Nitrogen is handled implicitly by relative CN ratios. Missing N | ||
| // from low-N wood to higher-N leaves is accounted for in | ||
| // calcNFixationAndUptakeFluxes() via calcPlantNDemand() | ||
|
|
||
| // Unlike planting, this is NOT a system input, so no adjustments to | ||
| // eventInputC or eventInputN | ||
|
|
||
| // ALSO, unlike all other events, we don't write the event here, as N | ||
| // limitation may change the amount | ||
| } break; | ||
| case LEAFOFF: { | ||
| double leafOff = envi.plantLeafC * params.fracLeafFall; | ||
| fluxes.eventLeafOffLitter += leafOff / climLen; | ||
|
|
||
| double litterNAdd = 0.0; | ||
| if (ctx.nitrogenCycle) { | ||
| // Nitrogen - need to account for leaf N moving to litter, as with | ||
| // harvests | ||
| litterNAdd = leafOff / params.leafCN; | ||
| fluxes.eventLitterN += litterNAdd / climLen; | ||
| } | ||
|
|
||
| // clang-format off | ||
| writeEventOut(gEvent, 2, | ||
| "fluxes.eventLeafOffLitter", leafOff / climLen, | ||
| "fluxes.eventLitterN", litterNAdd / climLen); | ||
| // clang-format on | ||
| } break; | ||
| default: | ||
| logError("Unknown event type (%d) in processEvents()\n", gEvent->type); | ||
|
|
@@ -609,6 +703,16 @@ void updatePoolsForEvents(void) { | |
| envi.litterC += fluxes.eventLitterC * climate->length; | ||
| } | ||
|
|
||
| // Leaf on and off events | ||
| envi.plantWoodC -= fluxes.eventLeafOnCreation * climate->length; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what happens if eventLeafOnCreation > plantWoodC?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The same thing that happens if leafOnCreation (the non-event one) is bigger than plantWoodC - that is, nothing good. I didn't attempt to address that here, as I think that's a higher-level question (that needs answering, of course!). This PR is scoped to "replicate leaf on/off as events".
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Even constraining it to plantWoodC is going to leave plantWoodC at zero - is that tenable? (See ongoing plant death discussions)
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What processes are allowed to draw from the storage delta currently? If I'm thinking about it right as "a highly mobile pool of presumably-recent photosynthate" then it seems reasonable to me for leaf growth to draw from it before any other pool. |
||
| envi.plantLeafC += (fluxes.eventLeafOnCreation - fluxes.eventLeafOffLitter) * | ||
| climate->length; | ||
| if (ctx.litterPool) { | ||
| envi.litterC += fluxes.eventLeafOffLitter * climate->length; | ||
| } else { | ||
| envi.soilC += fluxes.eventLeafOffLitter * climate->length; | ||
| } | ||
|
|
||
| // Harvest and planting events | ||
| envi.coarseRootC += fluxes.eventCoarseRootC * climate->length; | ||
| envi.fineRootC += fluxes.eventFineRootC * climate->length; | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.