From 5ae2652609a15aa52fa5fb38c3773a719951f18c Mon Sep 17 00:00:00 2001 From: Alomir Date: Wed, 18 Dec 2024 18:05:39 -0500 Subject: [PATCH 01/34] Adds event handler infrastructure with simple passing test --- Makefile | 40 +++- events.c | 227 +++++++++++++++++++++ events.h | 67 ++++++ frontend.c | 11 + modelStructures.h | 17 ++ sipnet.c | 57 ++++-- sipnet.h | 4 + tests/sipnet/test_events/Makefile | 27 +++ tests/sipnet/test_events/infra_events.in | 5 + tests/sipnet/test_events/modelStructures.h | 17 ++ tests/sipnet/test_events/testEventInfra.c | 152 ++++++++++++++ tests/sipnet/utils.h | 37 ++++ tests/test.template | 37 ++++ 13 files changed, 676 insertions(+), 22 deletions(-) create mode 100644 events.c create mode 100644 events.h create mode 100644 modelStructures.h create mode 100644 tests/sipnet/test_events/Makefile create mode 100644 tests/sipnet/test_events/infra_events.in create mode 100644 tests/sipnet/test_events/modelStructures.h create mode 100644 tests/sipnet/test_events/testEventInfra.c create mode 100644 tests/sipnet/utils.h create mode 100644 tests/test.template diff --git a/Makefile b/Makefile index 200df75d..9b2a37c3 100755 --- a/Makefile +++ b/Makefile @@ -1,15 +1,16 @@ CC=gcc LD=gcc +AR=ar CFLAGS=-Wall LIBLINKS=-lm -ESTIMATE_CFILES=sipnet.c ml-metro5.c ml-metrorun.c paramchange.c runmean.c util.c spatialParams.c namelistInput.c outputItems.c +ESTIMATE_CFILES=sipnet.c ml-metro5.c ml-metrorun.c paramchange.c runmean.c util.c spatialParams.c namelistInput.c outputItems.c events.c ESTIMATE_OFILES=$(ESTIMATE_CFILES:.c=.o) -SENSTEST_CFILES=sipnet.c sensTest.c paramchange.c runmean.c util.c spatialParams.c namelistInput.c outputItems.c +SENSTEST_CFILES=sipnet.c sensTest.c paramchange.c runmean.c util.c spatialParams.c namelistInput.c outputItems.c events.c SENSTEST_OFILES=$(SENSTEST_CFILES:.c=.o) -SIPNET_CFILES=sipnet.c frontend.c runmean.c util.c spatialParams.c namelistInput.c outputItems.c +SIPNET_CFILES=sipnet.c frontend.c runmean.c util.c spatialParams.c namelistInput.c outputItems.c events.c SIPNET_OFILES=$(SIPNET_CFILES:.c=.o) TRANSPOSE_CFILES=transpose.c util.c @@ -42,10 +43,41 @@ clean: #clean: # rm -f $(ESTIMATE_OFILES) $(SENSTEST_OFILES) $(SIPNET_OFILES) $(TRANSPOSE_OFILES) $(SUBSET_DATA_OFILES) estimate sensTest sipnet transpose subsetData +# +# UNIT TESTS +SIPNET_TEST_DIRS:=$(shell find tests/sipnet -type d -mindepth 1 -maxdepth 1) +SIPNET_TEST_DIRS_CLEAN:= $(addsuffix .clean, $(SIPNET_TEST_DIRS)) +SIPNET_LIB=libsipnet.a + +$(SIPNET_LIB): $(SIPNET_LIB)($(SIPNET_OFILES)) + ranlib $(SIPNET_LIB) + +test: pretest $(SIPNET_TEST_DIRS) posttest $(SIPNET_LIB) + +pretest: + cp modelStructures.h modelStructures.orig.h + +# The dash in the build command tells make to continue if there are errors, allowing cleanup +$(SIPNET_TEST_DIRS): pretest $(SIPNET_LIB) + cp $@/modelStructures.h modelStructures.h + -$(MAKE) -C $@ + +# This is far from infallible, as model_structures.h will be in a bad place if a test +# build step fails in a non-catchable way +posttest: $(SIPNET_TEST_DIRS) + mv modelStructures.orig.h modelStructures.h + +testclean: $(SIPNET_TEST_DIRS_CLEAN) + rm -f $(SIPNET_LIB) + +$(SIPNET_TEST_DIRS_CLEAN): + $(MAKE) -C $(basename $@) clean + +.PHONY: all test $(SIPNET_TEST_DIRS) pretest posttest $(SIPNET_LIB) testclean $(SIPNET_TEST_DIRS_CLEAN) + #This target automatically builds dependencies. depend:: makedepend $(CFILES) # DO NOT DELETE THIS LINE -- make depend depends on it. - diff --git a/events.c b/events.c new file mode 100644 index 00000000..b8c48cc7 --- /dev/null +++ b/events.c @@ -0,0 +1,227 @@ +// +// events.c +// + +#include "events.h" +#include +#include +#include +#include "util.h" + +void printEvent(EventNode* event); + +EventNode* createEventNode( + int loc, int year, int day, int eventType, char* eventParamsStr +) { + EventNode *event = (EventNode*)malloc(sizeof(EventNode)); + event->loc = loc; + event->year = year; + event->day = day; + event->type = eventType; + + switch (eventType) { + case HARVEST: + { + double fracRA, fracRB, fracTA, fracTB; + HarvestParams *params = (HarvestParams*)malloc(sizeof(HarvestParams)); + sscanf(eventParamsStr, "%lf %lf %lf %lf", &fracRA, &fracRB, &fracTA, &fracTB); + params->fractionRemovedAbove = fracRA; + params->fractionRemovedBelow = fracRB; + params->fractionTransferredAbove = fracTA; + params->fractionTransferredBelow = fracTB; + event->eventParams = params; + } + break; + case IRRIGATION: + { + double amountAdded; + int location; + IrrigationParams *params = (IrrigationParams*)malloc(sizeof(IrrigationParams)); + sscanf(eventParamsStr, "%lf %d", &amountAdded, &location); + params->amountAdded = amountAdded; + params->location = location; + event->eventParams = params; + } + break; + case FERTILIZATION: + { + // If/when we try two N pools, enable the additional nh4_no3_frac param (likely via + // compiler switch) + double orgN, orgC, minN; + // double nh4_no3_frac; + FertilizationParams *params = (FertilizationParams*)malloc(sizeof(FertilizationParams)); + sscanf(eventParamsStr, "%lf %lf %lf", &orgN, &orgC, &minN); + // scanf(eventParamsStr, "%lf %lf %lf %lf", &org_N, &org_C, &min_N, &nh4_no3_frac); + params->orgN = orgN; + params->orgC = orgC; + params->minN = minN; + // params->nh4_no3_frac = nh4_nos_frac; + event->eventParams = params; + } + break; + case PLANTING: + { + int emergenceLag; + double addedC, addedN; + PlantingParams *params = (PlantingParams*)malloc(sizeof(PlantingParams)); + sscanf(eventParamsStr, "%d %lf %lf", &emergenceLag, &addedC, &addedN); + params->emergenceLag = emergenceLag; + params->addedC = addedC; + params->addedN = addedN; + event->eventParams = params; + } + break; + case TILLAGE: + { + double fracLT, somDM, litterDM; + TillageParams *params = (TillageParams*)malloc(sizeof(TillageParams)); + sscanf(eventParamsStr, "%lf %lf %lf", &fracLT, &somDM, &litterDM); + params->fractionLitterTransferred = fracLT; + params->somDecompModifier = somDM; + params->litterDecompModifier = litterDM; + event->eventParams = params; + } + break; + default: + // Unknown type, error and exit + printf("Error reading from event file: unknown event type %d\n", eventType); + exit(1); + } + + event->nextEvent = NULL; + return event; +} + +enum EventType getEventType(char *eventTypeStr) { + if (strcmp(eventTypeStr, "irrig") == 0) { + return IRRIGATION; + } else if (strcmp(eventTypeStr, "fert") == 0) { + return FERTILIZATION; + } else if (strcmp(eventTypeStr, "plant") == 0) { + return PLANTING; + } else if (strcmp(eventTypeStr, "till") == 0) { + return TILLAGE; + } else if (strcmp(eventTypeStr, "harv") == 0) { + return HARVEST; + } + return UNKNOWN_EVENT; +} + +EventNode** readEventData(char *eventFile, int numLocs) { + int loc, year, day, eventType; + int currLoc, currYear, currDay; + int EVENT_LINE_SIZE = 1024; + int numBytes; + char *eventParamsStr; + char eventTypeStr[20]; + char line[EVENT_LINE_SIZE]; + EventNode *curr, *next; + + printf("Begin reading event data from file %s\n", eventFile); + + EventNode** events = (EventNode**)calloc(sizeof(EventNode*), numLocs * sizeof(EventNode*)); + // status of the read + FILE *in = openFile(eventFile, "r"); + + if (fgets(line, EVENT_LINE_SIZE, in) == NULL) { + printf("Error: no event data in %s\n", eventFile); + exit(1); + } + sscanf(line, "%d %d %d %s %n", &loc, &year, &day, eventTypeStr, &numBytes); + eventParamsStr = line + numBytes; + + eventType = getEventType(eventTypeStr); + if (eventType == UNKNOWN_EVENT) { + printf("Error: unknown event type %s\n", eventTypeStr); + exit(1); + } + printf("Found event type %d (%s)\n", eventType, eventTypeStr); + + next = createEventNode(loc, year, day, eventType, eventParamsStr); + events[loc] = next; + currLoc = loc; + currYear = year; + currDay = day; + + printf("Found events:\n"); + printEvent(next); + + while (fgets(line, EVENT_LINE_SIZE, in) != NULL) { + // We have another event + curr = next; + sscanf(line, "%d %d %d %s %n", &loc, &year, &day, eventTypeStr, &numBytes); + eventParamsStr = line + numBytes; + + eventType = getEventType(eventTypeStr); + if (eventType == UNKNOWN_EVENT) { + printf("Error: unknown event type %s\n", eventTypeStr); + exit(1); + } + + // make sure location and time are non-decreasing + if (loc < currLoc) { + printf("Error reading event file: was reading location %d, trying to read location %d\n", currLoc, loc); + printf("Event records for a given location should be contiguous, and locations should be in ascending order\n"); + exit(1); + } + if ((loc == currLoc) && ((year < currYear) || (day < currDay))) { + printf("Error reading event file: for location %d, last event was at (%d, %d) ", currLoc, currYear, currDay); + printf("next event is at (%d, %d)\n", year, day); + printf("Event records for a given location should be in time-ascending order\n"); + exit(1); + } + + next = createEventNode(loc, year, day, eventType, eventParamsStr); + printEvent(next); + if (currLoc == loc) { + // Same location, add the new event to this location's list + curr->nextEvent = next; + } + else { + // New location, update location and start a new list + currLoc = loc; + events[currLoc] = next; + } + } + + fclose(in); + return events; +} + +void printEvent(EventNode *event) { + if (event == NULL) { + return; + } + int loc = event->loc; + int year = event->year; + int day = event->day; + switch (event->type) { + case IRRIGATION: + printf("IRRIGATION at loc %d on %d %d, ", loc, year, day); + IrrigationParams *const iParams = (IrrigationParams*)event->eventParams; + printf("with params: amount added %4.2f\n", iParams->amountAdded); + break; + case FERTILIZATION: + printf("FERTILIZATION at loc %d on %d %d, ", loc, year, day); + FertilizationParams *const fParams = (FertilizationParams*)event->eventParams; + printf("with params: org N %4.2f, org C %4.2f, min N %4.2f\n", fParams->orgN, fParams->orgC, fParams->minN); + break; + case PLANTING: + printf("PLANTING at loc %d on %d %d, ", loc, year, day); + PlantingParams *const pParams = (PlantingParams*)event->eventParams; + printf("with params: emergence lag %d, added C %4.2f, added N %4.2f\n", pParams->emergenceLag, pParams->addedC, pParams->addedN); + break; + case TILLAGE: + printf("TILLAGE at loc %d on %d %d, ", loc, year, day); + TillageParams *const tParams = (TillageParams*)event->eventParams; + printf("with params: frac litter transferred %4.2f, som decomp modifier %4.2f, litter decomp modifier %4.2f\n", tParams->fractionLitterTransferred, tParams->somDecompModifier, tParams->litterDecompModifier); + break; + case HARVEST: + printf("HARVEST at loc %d on %d %d, ", loc, year, day); + HarvestParams *const hParams = (HarvestParams*)event->eventParams; + printf("with params: frac removed above %4.2f, frac removed below %4.2f, frac transferred above %4.2f, frac transferred below %4.2f\n", hParams->fractionRemovedAbove, hParams->fractionRemovedBelow, hParams->fractionTransferredAbove, hParams->fractionTransferredBelow); + break; + default: + printf("Error printing event: unknown type %d\n", event->type); + } +} diff --git a/events.h b/events.h new file mode 100644 index 00000000..de027a64 --- /dev/null +++ b/events.h @@ -0,0 +1,67 @@ +// +// Created by Michael J Longfritz on 11/8/24. +// + +#ifndef EVENTS_H +#define EVENTS_H + +#include "modelStructures.h" + +enum EventType { + FERTILIZATION, + HARVEST, + IRRIGATION, + PLANTING, + TILLAGE, + UNKNOWN_EVENT +}; + +typedef struct HarvestParams { + double fractionRemovedAbove; + double fractionRemovedBelow; + double fractionTransferredAbove; // to surface litter pool + double fractionTransferredBelow; // to soil litter pool +} HarvestParams; + +typedef struct IrrigationParams { + double amountAdded; + int location; // 0=canopy, 1=soil +} IrrigationParams; + +typedef struct FertilizationParams { + double orgN; + double orgC; + double minN; + //double nh4_no3_frac; for two-pool version +} FertilizationParams; + +typedef struct PlantingParams { + int emergenceLag; + double addedC; + double addedN; +} PlantingParams; + +typedef struct TillageParams { + double fractionLitterTransferred; + double somDecompModifier; + double litterDecompModifier; +} TillageParams; + +typedef struct EventNode EventNode; +struct EventNode { + enum EventType type; + int loc, year, day; + void *eventParams; + EventNode *nextEvent; +}; + +/* Read event data from .event + * + * Format: returned data is structured as an array of EventNode pointers, indexed by + * location. Each element of the array is the first event for that location (or null + * if there are no events). It is assumed that the events are in chrono order. + */ +EventNode** readEventData(char *eventFile, int numLocs); + + +#endif //EVENTS_H diff --git a/frontend.c b/frontend.c index 018039d7..c79633fe 100755 --- a/frontend.c +++ b/frontend.c @@ -13,6 +13,7 @@ #include "spatialParams.h" #include "namelistInput.h" #include "outputItems.h" +#include "modelStructures.h" // important constants - default values: @@ -81,6 +82,10 @@ int main(int argc, char *argv[]) { double **standarddevs; int dataTypeIndices[MAX_DATA_TYPES]; +#ifdef EVENT_HANDLER + // Extra filename if we are handing events + char eventFile[FILE_MAXNAME+24]; +#endif // get command-line arguments: while ((option = getopt(argc, argv, "hi:")) != -1) { @@ -151,6 +156,12 @@ int main(int argc, char *argv[]) { strcat(climFile, ".clim"); numLocs = initModel(&spatialParams, &steps, paramFile, climFile); +#ifdef EVENT_HANDLER + strcpy(eventFile, fileName); + strcat(eventFile, ".event"); + initEvents(eventFile, numLocs); +#endif + if (doSingleOutputs) { outputItems = newOutputItems(fileName, ' '); setupOutputItems(outputItems); diff --git a/modelStructures.h b/modelStructures.h new file mode 100644 index 00000000..5a591bb0 --- /dev/null +++ b/modelStructures.h @@ -0,0 +1,17 @@ +// +// This file is inteneded to hold settings for different model structure options, +// implemented as compile-time flags. The options are here (with nothing else) to +// improve testability. +// + +#ifndef MODEL_STRUCTURES_H +#define MODEL_STRUCTURES_H + +// Begin definitions for choosing different model structures +// See also sipnet.c for other options (that should be moved here if/when testing is added) + +#define EVENT_HANDLER 1 +// Read in and process agronomic events. Expects a file named .event to exist. + + +#endif //MODEL_STRUCTURES_H diff --git a/sipnet.c b/sipnet.c index 08751453..ee6f717a 100644 --- a/sipnet.c +++ b/sipnet.c @@ -18,6 +18,8 @@ #include "util.h" #include "spatialParams.h" #include "outputItems.h" +#include "modelStructures.h" +#include "events.h" // begin definitions for choosing different model structures // (1 -> true, 0 -> false) @@ -26,14 +28,14 @@ //alternative transpiration methods modified by Dave Moore #define ALTERNATIVE_TRANS 0 -// do we want to impliment alternative transpiration? +// do we want to implement alternative transpiration? #define BALL_BERRY 0 -//impliment a Ball Berry submodel to calculate gs from RH, CO2 and A +//implement 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. +//implement a transpiration calculation based on the Penman-Monteith Equation. //March 1st 2007 PM equation not really working. //#define G 0 @@ -487,7 +489,9 @@ static ClimateNode *climate; // current climate static Fluxes fluxes; static double *outputPtrs[MAX_DATA_TYPES]; // pointers to different possible outputs - +#ifdef EVENT_HANDLER +static EventNode **events; // MJL: event structs +#endif /* 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 @@ -934,8 +938,8 @@ void calcLightEff3 (double *lightEff, double lai, double par) { 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 + //double APAR; // Absorbed PAR by the canopy + //double lightIntensityTop, lightIntensityBottom; // PAR absorbed by the canopy cumfAPAR = 0.0; @@ -966,11 +970,11 @@ void calcLightEff3 (double *lightEff, double lai, double par) { 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 + //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 + //APAR = params.m_ballBerry * (lightIntensityTop - lightIntensityBottom); // APAR at this layer // is a fraction of the difference between incoming par and transmitted par @@ -1932,8 +1936,8 @@ void calculateFluxes() { double potGrossPsn; // potential photosynthesis, without water stress double dWater; double lai; // m^2 leaf/m^2 ground (calculated from plantLeafC) - double litterBreakdown; /* total litter breakdown (i.e. litterToSoil + rLitter) - (g C/m^2 ground/day) */ + //double litterBreakdown; /* total litter breakdown (i.e. litterToSoil + rLitter) + // (g C/m^2 ground/day) */ double folResp, woodResp; // maintenance respiration terms, g C * m^-2 ground area * day^-1 double litterWater, soilWater; /* amount of water in litter and soil (cm) taken from either environment or climate drivers, depending on value of MODEL_WATER */ @@ -2005,7 +2009,7 @@ void calculateFluxes() { fluxes.litterToSoil = litterBreakdown * (1.0 - params.fracLitterRespired); // NOTE: right now, we don't have capability to use separate cold soil params for litter #else - litterBreakdown = 0; + // litterBreakdown = 0; fluxes.rLitter = 0; fluxes.litterToSoil = 0; #endif @@ -2479,6 +2483,12 @@ void setupModel(SpatialParams *spatialParams, int loc) { } +// Setup events at given location +void setupEvents(int currLoc) { + +} + + /* Do one run of the model using parameter values in spatialParams If out != NULL, output results to out If printHeader = 1, print a header for the output file, if 0 don't @@ -2504,6 +2514,9 @@ void runModelOutput(FILE *out, OutputItems *outputItems, int printHeader, Spatia for (currLoc = firstLoc; currLoc <= lastLoc; currLoc++) { setupModel(spatialParams, currLoc); +#ifdef EVENT_HANDLER + setupEvents(currLoc); +#endif if ((loc == -1) && (outputItems != NULL)) { // print the current location at the start of the line sprintf(label, "%d", currLoc); writeOutputItemLabels(outputItems, label); @@ -2511,10 +2524,12 @@ void runModelOutput(FILE *out, OutputItems *outputItems, int printHeader, Spatia while (climate != NULL) { updateState(); - if (out != NULL) - outputState(out, currLoc, climate->year, climate->day, climate->time); - if (outputItems != NULL) - writeOutputItemValues(outputItems); + if (out != NULL) { + outputState(out, currLoc, climate->year, climate->day, climate->time); + } + if (outputItems != NULL) { + writeOutputItemValues(outputItems); + } climate = climate->nextClim; } if (outputItems != NULL) @@ -2775,8 +2790,14 @@ int initModel(SpatialParams **spatialParams, int **steps, char *paramFile, char return numLocs; } - - +/* Do initialization of event data if event handling is turned on. + * Populates static event structs + */ +void initEvents(char *eventFile, int numLocs) { +#ifdef EVENT_HANDLER + events = readEventData(eventFile, numLocs); +#endif +} // call this when done running model: // de-allocates space for climate linked list // (needs to know number of locations) diff --git a/sipnet.h b/sipnet.h index 3378d40d..6c8278b8 100755 --- a/sipnet.h +++ b/sipnet.h @@ -51,6 +51,10 @@ char **getDataTypeNames(); */ int initModel(SpatialParams **spatialParams, int **steps, char *paramFile, char *climFile); +/* + * TBD + */ +void initEvents(char *eventFile, int numLocs); // call this when done running model: // de-allocates space for climate linked list diff --git a/tests/sipnet/test_events/Makefile b/tests/sipnet/test_events/Makefile new file mode 100644 index 00000000..261cb8a6 --- /dev/null +++ b/tests/sipnet/test_events/Makefile @@ -0,0 +1,27 @@ +CC=gcc +LD=gcc +CFLAGS=-Wall -g +LDFLAGS=-L../../.. +LDLIBS=-lsipnet +#INCLUDE=../../../. + +# List test files in this directory here +TEST_CFILES=testEventInfra.c +TEST_OBJ_FILES=$(TEST_CFILES:%.c=%.o) +TEST_EXECUTABLES:=$(basename $(TEST_CFILES)) + +all: tests + +tests: $(TEST_EXECUTABLES) + +$(TEST_EXECUTABLES): %: %.o + $(CC) $(LDFLAGS) $< $(LDLIBS) -o $@ + +$(TEST_OBJ_FILES): ../utils.h ../../../libsipnet.a +$(TEST_OBJ_FILES): %.o: %.c + $(CC) $(CFLAGS) -c -o $@ $< + +clean: + rm -f $(TEST_OBJ_FILES) $(TEST_EXECUTABLES) events.in + +.PHONY: all tests clean diff --git a/tests/sipnet/test_events/infra_events.in b/tests/sipnet/test_events/infra_events.in new file mode 100644 index 00000000..ef0169f8 --- /dev/null +++ b/tests/sipnet/test_events/infra_events.in @@ -0,0 +1,5 @@ +0 2022 40 irrig 5 0 # 5cm canopy irrigation on day 40 +0 2022 40 fert 15 5 10 # fertilized with 15 g/m2 organic N, 5 g/m2 organic C, and 10 g/m2 mineral N on day 40 +0 2022 45 till 0.1 0.2 0.3 # tilled on day 45, 10% of surface litter transferred to soil pool, soil organic matter pool decomposition rate increases by 20% and soil litter pool decomposition rate increases by 30% +0 2022 46 plant 10 10 1 # planting - planting occurs on day 40, after 10 days the plants have emerged with a size of 10 g C/m2 and 1 g N / m2 +0 2022 250 harv 0.4 0.1 0.2 0.3 # harvest 10% of aboveground plant biomass on day 250 diff --git a/tests/sipnet/test_events/modelStructures.h b/tests/sipnet/test_events/modelStructures.h new file mode 100644 index 00000000..5a591bb0 --- /dev/null +++ b/tests/sipnet/test_events/modelStructures.h @@ -0,0 +1,17 @@ +// +// This file is inteneded to hold settings for different model structure options, +// implemented as compile-time flags. The options are here (with nothing else) to +// improve testability. +// + +#ifndef MODEL_STRUCTURES_H +#define MODEL_STRUCTURES_H + +// Begin definitions for choosing different model structures +// See also sipnet.c for other options (that should be moved here if/when testing is added) + +#define EVENT_HANDLER 1 +// Read in and process agronomic events. Expects a file named .event to exist. + + +#endif //MODEL_STRUCTURES_H diff --git a/tests/sipnet/test_events/testEventInfra.c b/tests/sipnet/test_events/testEventInfra.c new file mode 100644 index 00000000..8081baed --- /dev/null +++ b/tests/sipnet/test_events/testEventInfra.c @@ -0,0 +1,152 @@ + +#include +#include +#include + +#include "../utils.h" +#include "../../../events.c" + +int checkEvent(EventNode* event, int loc, int year, int day, enum EventType type) { + int success = + event->loc == loc && + event->year == year && + event->day == day && + event->type == type; + if (!success) { + printf("Error checking event\n"); + printEvent(event); + return 1; + } + return 0; +} + +int checkHarvestParams( + HarvestParams *params, double fracRemAbove, + double fracRemBelow, + double fracTransAbove, + double fracTransBelow +) { + return !( + compareDoubles(params->fractionRemovedAbove, fracRemAbove) && + compareDoubles(params->fractionRemovedBelow, fracRemBelow) && + compareDoubles(params->fractionTransferredAbove, fracTransAbove) && + compareDoubles(params->fractionTransferredBelow, fracTransBelow) + ); +} + +int checkIrrigationParams( + IrrigationParams *params, + double amountAdded, + int location + ) { + return !( + compareDoubles(params->amountAdded, amountAdded) && + params->location == location + ); +} + +int checkFertilizationParams( + FertilizationParams *params, + double orgN, + double orgC, + double minN +) { + return !( + compareDoubles(params->orgN, orgN) && + compareDoubles(params->orgC, orgC) && + compareDoubles(params->minN, minN) + ); +} + +int checkPlantingParams( + PlantingParams *params, int emergenceLag, double addedC, double addedN + ) { + return !( + params->emergenceLag == emergenceLag && + compareDoubles(params->addedC, addedC) && + compareDoubles(params->addedN, addedN) + ); +} + +int checkTillageParams( + TillageParams *params, + double fracLitterTransferred, + double somDecompModifier, + double litterDecompModifier + ) { + return !( + compareDoubles(params->fractionLitterTransferred, fracLitterTransferred) && + compareDoubles(params->somDecompModifier, somDecompModifier) && + compareDoubles(params->litterDecompModifier, litterDecompModifier) + ); +} + +int init() { + // char * filename = "infra_events.in"; + // if (access(filename, F_OK) == -1) { + // return 1; + // } + // + // return copyFile(filename, "events.in"); + return 0; +} + +int run() { + int numLocs = 1; + EventNode** output = readEventData("infra_events.in", numLocs); + + if (!output) { + return 1; + } + + // check output is correct + int status = 0; + EventNode *event = output[0]; + status |= checkEvent(event, 0, 2022, 40, IRRIGATION); + status |= checkIrrigationParams((IrrigationParams*)event->eventParams, 5, 0); + event = event->nextEvent; + status |= checkEvent(event, 0, 2022, 40, FERTILIZATION); + status |= checkFertilizationParams((FertilizationParams*)event->eventParams, 15, 5,10); + event = event->nextEvent; + status |= checkEvent(event, 0, 2022, 45, TILLAGE); + status |= checkTillageParams((TillageParams*)event->eventParams, 0.1, 0.2, 0.3); + event = event->nextEvent; + status |= checkEvent(event, 0, 2022, 46, PLANTING); + status |= checkPlantingParams((PlantingParams*)event->eventParams, 10, 10, 1); + event = event->nextEvent; + status |= checkEvent(event, 0, 2022, 250, HARVEST); + status |= checkHarvestParams((HarvestParams*)event->eventParams, 0.4, 0.1, 0.2, 0.3); + + if (status) { + return 1; + } + + return 0; +} + +void cleanup() { + // Perform any cleanup as needed + // None needed here, we can leave the copied file +} + +int main() { + int status; + status = init(); + if (status) { + printf("Test initialization failed with status %d\n", status); + exit(status); + } else { + printf("Test initialized\n"); + } + + printf("Starting run()\n"); + status = run(); + if (status) { + printf("Test run failed with status %d\n", status); + exit(status); + } + + printf("testEventInfra PASSED\n"); + + cleanup(); +} \ No newline at end of file diff --git a/tests/sipnet/utils.h b/tests/sipnet/utils.h new file mode 100644 index 00000000..54f3429c --- /dev/null +++ b/tests/sipnet/utils.h @@ -0,0 +1,37 @@ +#ifndef UTILS_H +#define UTILS_H + +#include +#include + +extern inline int copyFile(char* src, char* dest) { + FILE *source = fopen(src, "rb"); // Binary mode for compatibility + if (source == NULL) { + printf("Error opening source file %s", src); + return 1; + } + + FILE *destination = fopen(dest, "wb"); + if (destination == NULL) { + printf("Error opening destination file %s", dest); + fclose(source); + return 1; + } + + char buffer[4096]; // 4KB buffer + size_t bytesRead; + + while ((bytesRead = fread(buffer, 1, sizeof(buffer), source)) > 0) { + fwrite(buffer, 1, bytesRead, destination); + } + + fclose(source); + fclose(destination); + return 0; +} + +extern inline int compareDoubles(double a, double b) { + return fabs(a-b) < 1e-6; +} + +#endif //UTILS_H diff --git a/tests/test.template b/tests/test.template new file mode 100644 index 00000000..4ec9aec2 --- /dev/null +++ b/tests/test.template @@ -0,0 +1,37 @@ +// Unit test template file +// +// Copy and modify as needed (and remove these comments!) + +#include +#include + +int init() { + // Initialization steps for the test; eg copying files, allocating mem, etc + + // Return a status code, 0 = success +} + +int run() { + // Run the tests, of course + + // Return a status code, 0 = success +} + +void cleanup() { + // Perform any cleanup as needed +} + +int main() { + int status; + status = init(); + if (status) { + printf("Test initialization failed with status %d\b", status); + } + + status = run(); + if (status) { + printf("Test run failed with status %d\b", status); + } + + cleanup(); +} \ No newline at end of file From 7418af62aba94ab6407f4fa58ddae40be795b5f7 Mon Sep 17 00:00:00 2001 From: Alomir Date: Thu, 19 Dec 2024 12:15:25 -0500 Subject: [PATCH 02/34] Sets event handler off by default --- modelStructures.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modelStructures.h b/modelStructures.h index 5a591bb0..e0c8014d 100644 --- a/modelStructures.h +++ b/modelStructures.h @@ -10,7 +10,7 @@ // Begin definitions for choosing different model structures // See also sipnet.c for other options (that should be moved here if/when testing is added) -#define EVENT_HANDLER 1 +#define EVENT_HANDLER 0 // Read in and process agronomic events. Expects a file named .event to exist. From 1e9b7e62246327115f7328914ff5caaeeb890e83 Mon Sep 17 00:00:00 2001 From: Alomir Date: Thu, 19 Dec 2024 12:19:48 -0500 Subject: [PATCH 03/34] Sets event handler off by default for realz, I hope --- frontend.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend.c b/frontend.c index c79633fe..96333f76 100755 --- a/frontend.c +++ b/frontend.c @@ -156,9 +156,9 @@ int main(int argc, char *argv[]) { strcat(climFile, ".clim"); numLocs = initModel(&spatialParams, &steps, paramFile, climFile); -#ifdef EVENT_HANDLER strcpy(eventFile, fileName); strcat(eventFile, ".event"); +#if EVENT_HANDLER initEvents(eventFile, numLocs); #endif From 86175afc7cfe5d3f48ba2b1fdbde3a343ae5edb3 Mon Sep 17 00:00:00 2001 From: Alomir Date: Thu, 19 Dec 2024 12:32:49 -0500 Subject: [PATCH 04/34] Adds ci steps to run unit tests --- .github/workflows/ci.yml | 24 ++++++++++++++++++++++++ Makefile | 8 +++++++- tests/sipnet/test_events/Makefile | 9 +++++++-- 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0c2880ac..1da462f8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,6 +9,30 @@ on: jobs: + # Build and run unit tests + test: + strategy: + fail-fast: false + matrix: + OS: + - macos-latest + - ubuntu-latest + - ubuntu-20.04 + + runs-on: ${{ matrix.OS }} + + steps: + # checkout source code + - uses: actions/checkout@v2 + + # compile tests + - name: compile tests + run: make test + + # run tests + - name: run tests + run: make testrun + # try to build SIPNET on Ubuntu & MacOS build: strategy: diff --git a/Makefile b/Makefile index 9b2a37c3..6adbfe3b 100755 --- a/Makefile +++ b/Makefile @@ -46,6 +46,7 @@ clean: # # UNIT TESTS SIPNET_TEST_DIRS:=$(shell find tests/sipnet -type d -mindepth 1 -maxdepth 1) +SIPNET_TEST_DIRS_RUN:= $(addsuffix .run, $(SIPNET_TEST_DIRS)) SIPNET_TEST_DIRS_CLEAN:= $(addsuffix .clean, $(SIPNET_TEST_DIRS)) SIPNET_LIB=libsipnet.a @@ -67,13 +68,18 @@ $(SIPNET_TEST_DIRS): pretest $(SIPNET_LIB) posttest: $(SIPNET_TEST_DIRS) mv modelStructures.orig.h modelStructures.h +testrun: $(SIPNET_TEST_DIRS_RUN) + +$(SIPNET_TEST_DIRS_RUN): + $(MAKE) -C $(basename $@) run + testclean: $(SIPNET_TEST_DIRS_CLEAN) rm -f $(SIPNET_LIB) $(SIPNET_TEST_DIRS_CLEAN): $(MAKE) -C $(basename $@) clean -.PHONY: all test $(SIPNET_TEST_DIRS) pretest posttest $(SIPNET_LIB) testclean $(SIPNET_TEST_DIRS_CLEAN) +.PHONY: all test $(SIPNET_TEST_DIRS) pretest posttest $(SIPNET_LIB) testrun $(SIPNET_TEST_DIRS_RUN) testclean $(SIPNET_TEST_DIRS_CLEAN) #This target automatically builds dependencies. diff --git a/tests/sipnet/test_events/Makefile b/tests/sipnet/test_events/Makefile index 261cb8a6..dbf1d024 100644 --- a/tests/sipnet/test_events/Makefile +++ b/tests/sipnet/test_events/Makefile @@ -3,12 +3,12 @@ LD=gcc CFLAGS=-Wall -g LDFLAGS=-L../../.. LDLIBS=-lsipnet -#INCLUDE=../../../. # List test files in this directory here TEST_CFILES=testEventInfra.c TEST_OBJ_FILES=$(TEST_CFILES:%.c=%.o) TEST_EXECUTABLES:=$(basename $(TEST_CFILES)) +RUN_EXECUTABLES:= $(addsuffix .run, $(TEST_EXECUTABLES)) all: tests @@ -21,7 +21,12 @@ $(TEST_OBJ_FILES): ../utils.h ../../../libsipnet.a $(TEST_OBJ_FILES): %.o: %.c $(CC) $(CFLAGS) -c -o $@ $< +run: $(RUN_EXECUTABLES) + +$(RUN_EXECUTABLES): + ./$(basename $@) + clean: rm -f $(TEST_OBJ_FILES) $(TEST_EXECUTABLES) events.in -.PHONY: all tests clean +.PHONY: all tests clean run $(RUN_EXECUTABLES) From f9690e3a6be3b6b4633cc4c774cdae4248df5649 Mon Sep 17 00:00:00 2001 From: Alomir Date: Thu, 19 Dec 2024 13:57:25 -0500 Subject: [PATCH 05/34] Adds math lib to linux build linking --- tests/sipnet/test_events/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/sipnet/test_events/Makefile b/tests/sipnet/test_events/Makefile index dbf1d024..0da516a2 100644 --- a/tests/sipnet/test_events/Makefile +++ b/tests/sipnet/test_events/Makefile @@ -2,7 +2,7 @@ CC=gcc LD=gcc CFLAGS=-Wall -g LDFLAGS=-L../../.. -LDLIBS=-lsipnet +LDLIBS=-lsipnet -lm # List test files in this directory here TEST_CFILES=testEventInfra.c From a10988b28bfeca9c2f764f9c0aac9906d5198695 Mon Sep 17 00:00:00 2001 From: Alomir Date: Thu, 19 Dec 2024 17:04:06 -0500 Subject: [PATCH 06/34] Adds negative tests and an exit function handler --- events.c | 7 +- tests/sipnet/exitHandler.c | 47 ++++++++++ tests/sipnet/test_events/Makefile | 6 +- .../test_events/infra_events_date_ooo.in | 2 + .../test_events/infra_events_loc_ooo.in | 3 + .../sipnet/test_events/infra_events_multi.in | 15 ++++ ...infra_events.in => infra_events_simple.in} | 2 +- .../test_events/infra_events_unknown.in | 1 + tests/sipnet/test_events/testEventInfra.c | 86 ++++++++++++++++++- tests/sipnet/test_events/testEventInfraNeg.c | 68 +++++++++++++++ 10 files changed, 226 insertions(+), 11 deletions(-) create mode 100644 tests/sipnet/exitHandler.c create mode 100644 tests/sipnet/test_events/infra_events_date_ooo.in create mode 100644 tests/sipnet/test_events/infra_events_loc_ooo.in create mode 100644 tests/sipnet/test_events/infra_events_multi.in rename tests/sipnet/test_events/{infra_events.in => infra_events_simple.in} (90%) create mode 100644 tests/sipnet/test_events/infra_events_unknown.in create mode 100644 tests/sipnet/test_events/testEventInfraNeg.c diff --git a/events.c b/events.c index b8c48cc7..a1526736 100644 --- a/events.c +++ b/events.c @@ -135,7 +135,6 @@ EventNode** readEventData(char *eventFile, int numLocs) { printf("Error: unknown event type %s\n", eventTypeStr); exit(1); } - printf("Found event type %d (%s)\n", eventType, eventTypeStr); next = createEventNode(loc, year, day, eventType, eventParamsStr); events[loc] = next; @@ -143,8 +142,8 @@ EventNode** readEventData(char *eventFile, int numLocs) { currYear = year; currDay = day; - printf("Found events:\n"); - printEvent(next); + //printf("Found events:\n"); + //printEvent(next); while (fgets(line, EVENT_LINE_SIZE, in) != NULL) { // We have another event @@ -172,7 +171,7 @@ EventNode** readEventData(char *eventFile, int numLocs) { } next = createEventNode(loc, year, day, eventType, eventParamsStr); - printEvent(next); + //printEvent(next); if (currLoc == loc) { // Same location, add the new event to this location's list curr->nextEvent = next; diff --git a/tests/sipnet/exitHandler.c b/tests/sipnet/exitHandler.c new file mode 100644 index 00000000..acda85e6 --- /dev/null +++ b/tests/sipnet/exitHandler.c @@ -0,0 +1,47 @@ +#include +#include + +static int expected_code = 1; // the expected value a tested function passes to exit +static int should_exit = 1; // 1 if exit should have been called +static int really_exit = 0; // set to 1 to prevent stubbing behavior and actually exit + +static jmp_buf jump_env; + +static int exit_result = 1; +#define test_assert(x) (exit_result = exit_result && (x)) + +// set should_exit=0 when code SHOULDN'T exit(), e.g.: +// +// should_exit = 0; +// if (!(jmp_rval=setjmp(jump_env))) +// { +// call_to_non_exiting_code(); +// } +// test_assert(jmp_rval==0); +// +// set should_exit=1 when exit(0 is expected: +// +// should_exit = 1; +// expected_code = 1; // or whatever the exit code should be +// if (!(jmp_rval=setjmp(jump_env))) +// { +// call_to_exiting_code(); +// } +// +// test_assert(jmp_rval==1); + +// stub function +void exit(int code) +{ + if (!really_exit) + { + printf("Mocking the exit call\n"); + test_assert(should_exit==1); + test_assert(expected_code==code); + longjmp(jump_env, 1); + } + else + { + _exit(code); + } +} diff --git a/tests/sipnet/test_events/Makefile b/tests/sipnet/test_events/Makefile index 0da516a2..d224b95c 100644 --- a/tests/sipnet/test_events/Makefile +++ b/tests/sipnet/test_events/Makefile @@ -5,7 +5,9 @@ LDFLAGS=-L../../.. LDLIBS=-lsipnet -lm # List test files in this directory here -TEST_CFILES=testEventInfra.c +TEST_CFILES=testEventInfra.c testEventInfraNeg.c + +# The rest is boilerplate, likely copyable as is to a new test directory TEST_OBJ_FILES=$(TEST_CFILES:%.c=%.o) TEST_EXECUTABLES:=$(basename $(TEST_CFILES)) RUN_EXECUTABLES:= $(addsuffix .run, $(TEST_EXECUTABLES)) @@ -17,7 +19,7 @@ tests: $(TEST_EXECUTABLES) $(TEST_EXECUTABLES): %: %.o $(CC) $(LDFLAGS) $< $(LDLIBS) -o $@ -$(TEST_OBJ_FILES): ../utils.h ../../../libsipnet.a +$(TEST_OBJ_FILES): ../utils.h ../../../libsipnet.a ../exitHandler.c $(TEST_OBJ_FILES): %.o: %.c $(CC) $(CFLAGS) -c -o $@ $< diff --git a/tests/sipnet/test_events/infra_events_date_ooo.in b/tests/sipnet/test_events/infra_events_date_ooo.in new file mode 100644 index 00000000..a39aa789 --- /dev/null +++ b/tests/sipnet/test_events/infra_events_date_ooo.in @@ -0,0 +1,2 @@ +0 2024 60 till 0.1 0.2 0.3 +0 2024 55 plant 15 20 5 diff --git a/tests/sipnet/test_events/infra_events_loc_ooo.in b/tests/sipnet/test_events/infra_events_loc_ooo.in new file mode 100644 index 00000000..26786210 --- /dev/null +++ b/tests/sipnet/test_events/infra_events_loc_ooo.in @@ -0,0 +1,3 @@ +0 2024 60 till 0.1 0.2 0.3 +1 2024 65 plant 15 20 5 +0 2024 70 irrig 5 1 diff --git a/tests/sipnet/test_events/infra_events_multi.in b/tests/sipnet/test_events/infra_events_multi.in new file mode 100644 index 00000000..9013e358 --- /dev/null +++ b/tests/sipnet/test_events/infra_events_multi.in @@ -0,0 +1,15 @@ +0 2024 60 till 0.1 0.2 0.3 +0 2024 65 plant 15 20 5 +0 2024 70 irrig 5 1 +0 2024 75 fert 15 10 5 +0 2024 250 harv 0.1 0.2 0.3 0.4 +1 2024 59 till 0.2 0.3 0.1 +1 2024 64 plant 15 20 5 +1 2024 69 irrig 5 0 +1 2024 74 fert 5 15 10 +1 2024 249 harv 0.2 0.3 0.4 0.1 +2 2024 58 till 0.3 0.1 0.2 +2 2024 63 plant 15 20 5 +2 2024 68 irrig 6 1 +2 2024 73 fert 5 10 15 +2 2024 248 harv 0.3 0.4 0.1 0.2 diff --git a/tests/sipnet/test_events/infra_events.in b/tests/sipnet/test_events/infra_events_simple.in similarity index 90% rename from tests/sipnet/test_events/infra_events.in rename to tests/sipnet/test_events/infra_events_simple.in index ef0169f8..506cf141 100644 --- a/tests/sipnet/test_events/infra_events.in +++ b/tests/sipnet/test_events/infra_events_simple.in @@ -1,4 +1,4 @@ -0 2022 40 irrig 5 0 # 5cm canopy irrigation on day 40 +0 2022 40 irrig 5 0 # 5cm canopy irrigation on day 40 0 2022 40 fert 15 5 10 # fertilized with 15 g/m2 organic N, 5 g/m2 organic C, and 10 g/m2 mineral N on day 40 0 2022 45 till 0.1 0.2 0.3 # tilled on day 45, 10% of surface litter transferred to soil pool, soil organic matter pool decomposition rate increases by 20% and soil litter pool decomposition rate increases by 30% 0 2022 46 plant 10 10 1 # planting - planting occurs on day 40, after 10 days the plants have emerged with a size of 10 g C/m2 and 1 g N / m2 diff --git a/tests/sipnet/test_events/infra_events_unknown.in b/tests/sipnet/test_events/infra_events_unknown.in new file mode 100644 index 00000000..2e7b0971 --- /dev/null +++ b/tests/sipnet/test_events/infra_events_unknown.in @@ -0,0 +1 @@ +0 2022 40 blarg 5 0 diff --git a/tests/sipnet/test_events/testEventInfra.c b/tests/sipnet/test_events/testEventInfra.c index 8081baed..6538b491 100644 --- a/tests/sipnet/test_events/testEventInfra.c +++ b/tests/sipnet/test_events/testEventInfra.c @@ -1,7 +1,6 @@ #include #include -#include #include "../utils.h" #include "../../../events.c" @@ -82,7 +81,7 @@ int checkTillageParams( } int init() { - // char * filename = "infra_events.in"; + // char * filename = "infra_events_simple.in"; // if (access(filename, F_OK) == -1) { // return 1; // } @@ -91,9 +90,10 @@ int init() { return 0; } -int run() { +int runTestSimple() { + // Simple test with one loc, one event per type int numLocs = 1; - EventNode** output = readEventData("infra_events.in", numLocs); + EventNode** output = readEventData("infra_events_simple.in", numLocs); if (!output) { return 1; @@ -117,6 +117,84 @@ int run() { status |= checkEvent(event, 0, 2022, 250, HARVEST); status |= checkHarvestParams((HarvestParams*)event->eventParams, 0.4, 0.1, 0.2, 0.3); + return status; +} + +int runTestMulti() { + // More complex test; multiple locations + int numLocs = 1; + EventNode** output = readEventData("infra_events_multi.in", numLocs); + + if (!output) { + return 1; + } + + // check output is correct + int status = 0; + int loc = 0; + EventNode *event = output[loc]; + status |= checkEvent(event, loc, 2024, 60, TILLAGE); + status |= checkTillageParams((TillageParams*)event->eventParams, 0.1, 0.2, 0.3); + event = event->nextEvent; + status |= checkEvent(event, loc, 2024, 65, PLANTING); + status |= checkPlantingParams((PlantingParams*)event->eventParams, 15, 20, 5); + event = event->nextEvent; + status |= checkEvent(event, loc, 2024, 70, IRRIGATION); + status |= checkIrrigationParams((IrrigationParams*)event->eventParams, 5, 1); + event = event->nextEvent; + status |= checkEvent(event, loc, 2024, 75, FERTILIZATION); + status |= checkFertilizationParams((FertilizationParams*)event->eventParams, 15, 10,5); + event = event->nextEvent; + status |= checkEvent(event, loc, 2024, 250, HARVEST); + status |= checkHarvestParams((HarvestParams*)event->eventParams, 0.1, 0.2, 0.3, 0.4); + + loc++; + event = output[loc]; + status |= checkEvent(event, loc, 2024, 59, TILLAGE); + status |= checkTillageParams((TillageParams*)event->eventParams, 0.2, 0.3, 0.1); + event = event->nextEvent; + status |= checkEvent(event, loc, 2024, 64, PLANTING); + status |= checkPlantingParams((PlantingParams*)event->eventParams, 15, 20, 5); + event = event->nextEvent; + status |= checkEvent(event, loc, 2024, 69, IRRIGATION); + status |= checkIrrigationParams((IrrigationParams*)event->eventParams, 5, 0); + event = event->nextEvent; + status |= checkEvent(event, loc, 2024, 74, FERTILIZATION); + status |= checkFertilizationParams((FertilizationParams*)event->eventParams, 5, 15,10); + event = event->nextEvent; + status |= checkEvent(event, loc, 2024, 249, HARVEST); + status |= checkHarvestParams((HarvestParams*)event->eventParams, 0.2, 0.3, 0.4, 0.1); + + loc++; + event = output[loc]; + status |= checkEvent(event, loc, 2024, 58, TILLAGE); + status |= checkTillageParams((TillageParams*)event->eventParams, 0.3, 0.1, 0.2); + event = event->nextEvent; + status |= checkEvent(event, loc, 2024, 63, PLANTING); + status |= checkPlantingParams((PlantingParams*)event->eventParams, 15, 20, 5); + event = event->nextEvent; + status |= checkEvent(event, loc, 2024, 68, IRRIGATION); + status |= checkIrrigationParams((IrrigationParams*)event->eventParams, 6, 1); + event = event->nextEvent; + status |= checkEvent(event, loc, 2024, 73, FERTILIZATION); + status |= checkFertilizationParams((FertilizationParams*)event->eventParams, 5, 10,15); + event = event->nextEvent; + status |= checkEvent(event, loc, 2024, 248, HARVEST); + status |= checkHarvestParams((HarvestParams*)event->eventParams, 0.3, 0.4, 0.1, 0.2); + + return status; +} + +int run() { + + int status; + + status = runTestSimple(); + if (status) { + return 1; + } + + status = runTestMulti(); if (status) { return 1; } diff --git a/tests/sipnet/test_events/testEventInfraNeg.c b/tests/sipnet/test_events/testEventInfraNeg.c new file mode 100644 index 00000000..eb7d06a1 --- /dev/null +++ b/tests/sipnet/test_events/testEventInfraNeg.c @@ -0,0 +1,68 @@ + +#include +#include + +#include "../../../events.c" +#include "../exitHandler.c" + +int run() { + int status = 0; + + // exit() handling params + int jmp_rval; + expected_code = 1; + exit_result = 1; + + // Step 0: make sure that we don't have a false positive on good data + should_exit = 0; + jmp_rval = setjmp(jump_env); + if (!jmp_rval) { + readEventData("infra_events_simple.in", 1); + } + test_assert(jmp_rval==0); + status |= !exit_result; + + // First test + should_exit = 1; + jmp_rval = setjmp(jump_env); + if (!jmp_rval) { + readEventData("infra_events_unknown.in", 1); + } + printf("status %d exit_res %d jmp_rval %d\n", status, exit_result, jmp_rval); + test_assert(jmp_rval==1); + status |= !exit_result; + + // Second test + jmp_rval = setjmp(jump_env); + if (!jmp_rval) { + readEventData("infra_events_loc_ooo.in", 1); + } + test_assert(jmp_rval==1); + status |= !exit_result; + + // Third test + jmp_rval = setjmp(jump_env); + if (!jmp_rval) { + readEventData("infra_events_date_ooo.in", 1); + } + test_assert(jmp_rval==1); + status |= !exit_result; + + // Allow a real exit, not that this is really needed + really_exit = 1; + + return status; +} + +int main() { + int status; + + printf("Starting testEventInfraNeg:run()\n"); + status = run(); + if (status) { + printf("Test run failed with status %d\n", status); + exit(status); + } + + printf("testEventInfra PASSED\n"); +} \ No newline at end of file From 682e25c7f3c65843e99aa5d95bb94986d068a636 Mon Sep 17 00:00:00 2001 From: David LeBauer Date: Thu, 19 Dec 2024 23:52:56 -0700 Subject: [PATCH 07/34] Update CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f15c9c4d..7806eda2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,7 +3,7 @@ ## Compiling -SIPNET uses `make` build the model and documentation. There are also miscellaneous targets for running analysis workflows: +SIPNET uses `make` to build the model and documentation. There are also miscellaneous targets for running analysis workflows: ```sh # build SIPNET executable From 8108736968eb9ca6ae7edcca702711103058b305 Mon Sep 17 00:00:00 2001 From: David LeBauer Date: Thu, 19 Dec 2024 23:53:03 -0700 Subject: [PATCH 08/34] Update Makefile --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index caeeaea1..e47457e2 100755 --- a/Makefile +++ b/Makefile @@ -68,7 +68,7 @@ help: @echo " all - Builds all components." @echo " document - Generate documentation." @echo " sipnet - Builds the 'sipnet' executable." - @echo " clean - Removes compiled files and executables." + @echo " clean - Removes compiled files, executables, and documentation." @echo " depend - Automatically generates dependency information for source files." @echo " === additional tools ===" @echo " estimate - Builds 'estimate' executable to estimate parameters using MCMC." From 8e89d94c029209ca67bd7eb4eb2f26889f31957c Mon Sep 17 00:00:00 2001 From: David LeBauer Date: Thu, 19 Dec 2024 23:54:30 -0700 Subject: [PATCH 09/34] Update .gitignore Remove duplicate lines --- .gitignore | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/.gitignore b/.gitignore index 5c662284..757dc24a 100644 --- a/.gitignore +++ b/.gitignore @@ -14,20 +14,6 @@ docs/latex .Rproj.user .Rhistory - -# build artifacts and executables -*.o -estimate -sipnet -subsetData -transpose -.doxygen.stamp - -# documentation -docs/html/ -docs/latex/ - - # System-specific files .DS_Store .Rproj.user From e9ed8b0f51ad5d90cc9c3ca666f1510072573196 Mon Sep 17 00:00:00 2001 From: Alomir Date: Tue, 7 Jan 2025 16:21:27 -0500 Subject: [PATCH 10/34] Code cleanup 1 for PR --- events.c | 4 ---- events.h | 5 +++-- frontend.c | 2 +- modelStructures.h | 3 ++- sipnet.c | 23 ++++++++++++----------- sipnet.h | 9 ++++++++- tests/sipnet/exitHandler.c | 14 +++++++------- tests/test.template | 2 +- 8 files changed, 34 insertions(+), 28 deletions(-) diff --git a/events.c b/events.c index a1526736..b4f30279 100644 --- a/events.c +++ b/events.c @@ -142,9 +142,6 @@ EventNode** readEventData(char *eventFile, int numLocs) { currYear = year; currDay = day; - //printf("Found events:\n"); - //printEvent(next); - while (fgets(line, EVENT_LINE_SIZE, in) != NULL) { // We have another event curr = next; @@ -171,7 +168,6 @@ EventNode** readEventData(char *eventFile, int numLocs) { } next = createEventNode(loc, year, day, eventType, eventParamsStr); - //printEvent(next); if (currLoc == loc) { // Same location, add the new event to this location's list curr->nextEvent = next; diff --git a/events.h b/events.h index de027a64..f96f27a8 100644 --- a/events.h +++ b/events.h @@ -55,11 +55,12 @@ struct EventNode { EventNode *nextEvent; }; -/* Read event data from .event +/* Read event data from input filename (canonically events.in) * * Format: returned data is structured as an array of EventNode pointers, indexed by * location. Each element of the array is the first event for that location (or null - * if there are no events). It is assumed that the events are in chrono order. + * if there are no events). It is assumed that the events are ordered first by location + * and then by year and day. */ EventNode** readEventData(char *eventFile, int numLocs); diff --git a/frontend.c b/frontend.c index 96333f76..302929c8 100755 --- a/frontend.c +++ b/frontend.c @@ -82,7 +82,7 @@ int main(int argc, char *argv[]) { double **standarddevs; int dataTypeIndices[MAX_DATA_TYPES]; -#ifdef EVENT_HANDLER +#if EVENT_HANDLER // Extra filename if we are handing events char eventFile[FILE_MAXNAME+24]; #endif diff --git a/modelStructures.h b/modelStructures.h index e0c8014d..207287a6 100644 --- a/modelStructures.h +++ b/modelStructures.h @@ -11,7 +11,8 @@ // See also sipnet.c for other options (that should be moved here if/when testing is added) #define EVENT_HANDLER 0 -// Read in and process agronomic events. Expects a file named .event to exist. +// Read in and process agronomic events. SIPNET expects a file named events.in to exist, though +// unit tests may use other names. #endif //MODEL_STRUCTURES_H diff --git a/sipnet.c b/sipnet.c index ee6f717a..ddcdd0ff 100644 --- a/sipnet.c +++ b/sipnet.c @@ -489,8 +489,8 @@ static ClimateNode *climate; // current climate static Fluxes fluxes; static double *outputPtrs[MAX_DATA_TYPES]; // pointers to different possible outputs -#ifdef EVENT_HANDLER -static EventNode **events; // MJL: event structs +#if EVENT_HANDLER +static EventNode **events; #endif /* Read climate file into linked lists, @@ -1936,8 +1936,8 @@ void calculateFluxes() { double potGrossPsn; // potential photosynthesis, without water stress double dWater; double lai; // m^2 leaf/m^2 ground (calculated from plantLeafC) - //double litterBreakdown; /* total litter breakdown (i.e. litterToSoil + rLitter) - // (g C/m^2 ground/day) */ + //double litterBreakdown; /* total litter breakdown (i.e. litterToSoil + rLitter) + //(g C/m^2 ground/day) */ double folResp, woodResp; // maintenance respiration terms, g C * m^-2 ground area * day^-1 double litterWater, soilWater; /* amount of water in litter and soil (cm) taken from either environment or climate drivers, depending on value of MODEL_WATER */ @@ -2485,7 +2485,7 @@ void setupModel(SpatialParams *spatialParams, int loc) { // Setup events at given location void setupEvents(int currLoc) { - + // Implementation TBD } @@ -2514,8 +2514,8 @@ void runModelOutput(FILE *out, OutputItems *outputItems, int printHeader, Spatia for (currLoc = firstLoc; currLoc <= lastLoc; currLoc++) { setupModel(spatialParams, currLoc); -#ifdef EVENT_HANDLER - setupEvents(currLoc); +#if EVENT_HANDLER + setupEvents(currLoc); #endif if ((loc == -1) && (outputItems != NULL)) { // print the current location at the start of the line sprintf(label, "%d", currLoc); @@ -2525,15 +2525,16 @@ void runModelOutput(FILE *out, OutputItems *outputItems, int printHeader, Spatia while (climate != NULL) { updateState(); if (out != NULL) { - outputState(out, currLoc, climate->year, climate->day, climate->time); + outputState(out, currLoc, climate->year, climate->day, climate->time); } if (outputItems != NULL) { - writeOutputItemValues(outputItems); + writeOutputItemValues(outputItems); } climate = climate->nextClim; } - if (outputItems != NULL) + if (outputItems != NULL) { terminateOutputItemLines(outputItems); + } } } @@ -2794,7 +2795,7 @@ int initModel(SpatialParams **spatialParams, int **steps, char *paramFile, char * Populates static event structs */ void initEvents(char *eventFile, int numLocs) { -#ifdef EVENT_HANDLER +#if EVENT_HANDLER events = readEventData(eventFile, numLocs); #endif } diff --git a/sipnet.h b/sipnet.h index 6c8278b8..5ce33499 100755 --- a/sipnet.h +++ b/sipnet.h @@ -52,7 +52,14 @@ char **getDataTypeNames(); int initModel(SpatialParams **spatialParams, int **steps, char *paramFile, char *climFile); /* - * TBD + * Read in event data for all the model runs + * + * Read in event data from a file with the following specification: + * - one line per event + * - all events are ordered first by location (ascending) and then by year/day (ascending) + * + * @param eventFile Name of file containing event data + * @param numLocs Number of locations in the event file. */ void initEvents(char *eventFile, int numLocs); diff --git a/tests/sipnet/exitHandler.c b/tests/sipnet/exitHandler.c index acda85e6..8fc56a77 100644 --- a/tests/sipnet/exitHandler.c +++ b/tests/sipnet/exitHandler.c @@ -1,9 +1,11 @@ #include #include +// + static int expected_code = 1; // the expected value a tested function passes to exit -static int should_exit = 1; // 1 if exit should have been called -static int really_exit = 0; // set to 1 to prevent stubbing behavior and actually exit +static int should_exit = 1; // set in test code; 1 if exit should have been called +static int really_exit = 0; // set to 1 to prevent stubbing behavior and actually exit static jmp_buf jump_env; @@ -19,7 +21,7 @@ static int exit_result = 1; // } // test_assert(jmp_rval==0); // -// set should_exit=1 when exit(0 is expected: +// set should_exit=1 when exit is expected: // // should_exit = 1; // expected_code = 1; // or whatever the exit code should be @@ -40,8 +42,6 @@ void exit(int code) test_assert(expected_code==code); longjmp(jump_env, 1); } - else - { - _exit(code); - } + + _exit(code); } diff --git a/tests/test.template b/tests/test.template index 4ec9aec2..53aac2d1 100644 --- a/tests/test.template +++ b/tests/test.template @@ -34,4 +34,4 @@ int main() { } cleanup(); -} \ No newline at end of file +} From 0d8d16c3624ecc258f4f68b9d5cd4fedb2df77fb Mon Sep 17 00:00:00 2001 From: Alomir Date: Thu, 9 Jan 2025 11:44:47 -0500 Subject: [PATCH 11/34] Adds fix for frontend.c and test Makefile template --- frontend.c | 2 +- tests/Makefile.template | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 tests/Makefile.template diff --git a/frontend.c b/frontend.c index 302929c8..9fc48e7c 100755 --- a/frontend.c +++ b/frontend.c @@ -156,9 +156,9 @@ int main(int argc, char *argv[]) { strcat(climFile, ".clim"); numLocs = initModel(&spatialParams, &steps, paramFile, climFile); +#if EVENT_HANDLER strcpy(eventFile, fileName); strcat(eventFile, ".event"); -#if EVENT_HANDLER initEvents(eventFile, numLocs); #endif diff --git a/tests/Makefile.template b/tests/Makefile.template new file mode 100644 index 00000000..e26a64d5 --- /dev/null +++ b/tests/Makefile.template @@ -0,0 +1,34 @@ +CC=gcc +LD=gcc +CFLAGS=-Wall -g +LDFLAGS=-L../../.. +LDLIBS=-lsipnet -lm + +# List test files in this directory here +TEST_CFILES= <<== CHANGE THIS + +# The rest is boilerplate, likely copyable as is to a new test directory +TEST_OBJ_FILES=$(TEST_CFILES:%.c=%.o) +TEST_EXECUTABLES:=$(basename $(TEST_CFILES)) +RUN_EXECUTABLES:= $(addsuffix .run, $(TEST_EXECUTABLES)) + +all: tests + +tests: $(TEST_EXECUTABLES) + +$(TEST_EXECUTABLES): %: %.o + $(CC) $(LDFLAGS) $< $(LDLIBS) -o $@ + +$(TEST_OBJ_FILES): ../utils.h ../../../libsipnet.a ../exitHandler.c +$(TEST_OBJ_FILES): %.o: %.c + $(CC) $(CFLAGS) -c -o $@ $< + +run: $(RUN_EXECUTABLES) + +$(RUN_EXECUTABLES): + ./$(basename $@) + +clean: + rm -f $(TEST_OBJ_FILES) $(TEST_EXECUTABLES) events.in + +.PHONY: all tests clean run $(RUN_EXECUTABLES) From 72e92de6d08ca0e1641809b4e2ed62995b82db69 Mon Sep 17 00:00:00 2001 From: David LeBauer Date: Thu, 9 Jan 2025 14:39:24 -0700 Subject: [PATCH 12/34] Add Doxygen install to CI.yml --- .github/workflows/ci.yml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0c2880ac..8798b2ba 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,7 +25,16 @@ jobs: # checkout source code - uses: actions/checkout@v2 - # compile sipnet + # install doxygen + - name: Install Doxygen + run: | + if [[ "$RUNNER_OS" == "Linux" ]]; then + sudo apt-get install doxygen -y + elif [[ "$RUNNER_OS" == "macOS" ]]; then + brew install doxygen + fi + + # compile SIPNET - name: compile sipnet run: make From 573a76c7397b704c3b5cd606ef756903633c480f Mon Sep 17 00:00:00 2001 From: David LeBauer Date: Thu, 9 Jan 2025 14:42:04 -0700 Subject: [PATCH 13/34] fix brew install doxygen warning --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8798b2ba..20106891 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,7 +31,7 @@ jobs: if [[ "$RUNNER_OS" == "Linux" ]]; then sudo apt-get install doxygen -y elif [[ "$RUNNER_OS" == "macOS" ]]; then - brew install doxygen + brew install --formula doxygen fi # compile SIPNET From 94730e57d0c0971a8a82078a782a8da897540c42 Mon Sep 17 00:00:00 2001 From: David LeBauer Date: Thu, 9 Jan 2025 15:13:24 -0700 Subject: [PATCH 14/34] replace spaces with tab --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 77a9925f..fa78dd38 100755 --- a/Makefile +++ b/Makefile @@ -92,8 +92,8 @@ $(SIPNET_TEST_DIRS_CLEAN): $(MAKE) -C $(basename $@) clean .PHONY: all clean document estimate sipnet transpose subsetData doxygen - test $(SIPNET_TEST_DIRS) pretest posttest $(SIPNET_LIB) testrun - $(SIPNET_TEST_DIRS_RUN) testclean $(SIPNET_TEST_DIRS_CLEAN) + test $(SIPNET_TEST_DIRS) pretest posttest $(SIPNET_LIB) testrun + $(SIPNET_TEST_DIRS_RUN) testclean $(SIPNET_TEST_DIRS_CLEAN) help: @echo "Available targets:" From f379cb9a95f5790a465aa15bac7d7ce26170c190 Mon Sep 17 00:00:00 2001 From: David LeBauer Date: Thu, 9 Jan 2025 15:36:05 -0700 Subject: [PATCH 15/34] Update ci.yml change order of build and unit tests update names and error messages for clarity --- .github/workflows/ci.yml | 104 +++++++++++++++++++-------------------- 1 file changed, 51 insertions(+), 53 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 131f49e6..25efadfc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,85 +4,83 @@ on: push: branches: - master - pull_request: jobs: - - # Build and run unit tests - test: + # Build and Integration Test + build_integration_test: strategy: fail-fast: false matrix: - OS: + OS: - macos-latest - ubuntu-latest - ubuntu-20.04 - runs-on: ${{ matrix.OS }} steps: # checkout source code - uses: actions/checkout@v2 - # compile tests - - name: compile tests - run: make test + # install doxygen + - name: Install Doxygen + run: | + if [[ "$RUNNER_OS" == "Linux" ]]; then + sudo apt-get install doxygen -y + elif [[ "$RUNNER_OS" == "macOS" ]]; then + brew install --formula doxygen + fi + + # compile SIPNET + - name: compile sipnet + run: make - # run tests - - name: run tests - run: make testrun + # remove existing test output file + - name: Remove Niwout Output File + run: rm Sites/Niwot/niwot.out + + # run single sipnet run + - name: Run SIPNET on Sites/Niwot/niwot + run: ./sipnet + + # check for correct output of Niwot run + - name: Check if niwot.out is generated + if: ${{ !files.exists('Sites/Niwot/niwot.out') }} + run: | + echo "::error title={No Output}::Test run for Niwot site failed to produce output" + exit 1 + # Check if niwot.out has changed + - name: Check whether niwot.out has changed + shell: bash + run: | + if git diff --exit-code Sites/Niwot/niwot.out; then + echo "Success: Niwot.out created and has not changed" + else + echo "::error title={Output Changed}::The test file niwot.out has changed. This is expected to fail with some changes to SIPNET. When this happens, assess correctness and then update the reference niwot.out." + exit 1 + fi - # try to build SIPNET on Ubuntu & MacOS - build: + # Run Unit Tests + test: + needs: build_integration_test strategy: fail-fast: false matrix: - OS: + OS: - macos-latest - ubuntu-latest - ubuntu-20.04 - runs-on: ${{ matrix.OS }} steps: - # checkout source code - - uses: actions/checkout@v2 - - # install doxygen - - name: Install Doxygen - run: | - if [[ "$RUNNER_OS" == "Linux" ]]; then - sudo apt-get install doxygen -y - elif [[ "$RUNNER_OS" == "macOS" ]]; then - brew install --formula doxygen - fi - - # compile SIPNET - - name: compile sipnet - run: make + # checkout source code + - uses: actions/checkout@v2 - # remove existing test output file - - name: Remove Niwout Output File - run: rm Sites/Niwot/niwot.out + # compile unit tests + - name: compile tests + run: make test - # run single sipnet run - - name: sipnet on Sites/Niwot/niwot - run: ./sipnet + # run tests + - name: Run Unit Tests + run: make testrun - # check output of test - - name: fail if no niwot output exists - if: ${{ hashFiles('Sites/Niwot/niwot.out') == '' }} - run: | - echo "::error title={No Output}::Test run for Niwot site failed to produce output" - exit 1 - # check whether niwot.out has changed - - name: Check whether niwot.out has changed - shell: bash - run: | - if git diff --exit-code Sites/Niwot/niwot.out; then - echo "Success: Niwot.out created and has not changed" - else - echo "::error title={Output Changed}::The test file niwot.out has changed" - exit 1 - fi From 1b5123a5b5c295abde74ac82b82535b72acd72bf Mon Sep 17 00:00:00 2001 From: David LeBauer Date: Thu, 9 Jan 2025 15:46:31 -0700 Subject: [PATCH 16/34] Update ci.yml --- .github/workflows/ci.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 25efadfc..de333916 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -45,10 +45,13 @@ jobs: # check for correct output of Niwot run - name: Check if niwot.out is generated - if: ${{ !files.exists('Sites/Niwot/niwot.out') }} + shell: bash run: | - echo "::error title={No Output}::Test run for Niwot site failed to produce output" - exit 1 + if: ${{ !files.exists('Sites/Niwot/niwot.out') }} + echo "::error title={No Output}::Test run for Niwot site failed to produce output" + exit 1 + fi + # Check if niwot.out has changed - name: Check whether niwot.out has changed shell: bash From ad10b3dcc360db03dc616fe93d7a92a1734b2074 Mon Sep 17 00:00:00 2001 From: David LeBauer Date: Thu, 9 Jan 2025 15:48:24 -0700 Subject: [PATCH 17/34] Update ci.yml --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index de333916..8238260d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,7 +47,7 @@ jobs: - name: Check if niwot.out is generated shell: bash run: | - if: ${{ !files.exists('Sites/Niwot/niwot.out') }} + if [ ! -f Sites/Niwot/niwot.out ]; then echo "::error title={No Output}::Test run for Niwot site failed to produce output" exit 1 fi From 6fc965f6af5b8d5c7121fa24d26636f94149a89a Mon Sep 17 00:00:00 2001 From: David LeBauer Date: Thu, 9 Jan 2025 15:49:13 -0700 Subject: [PATCH 18/34] Update ci.yml --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8238260d..4708e41c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,7 +8,7 @@ on: jobs: # Build and Integration Test - build_integration_test: + build: strategy: fail-fast: false matrix: From 93adfd1328e7c924351f07fb4a137549e5bba85a Mon Sep 17 00:00:00 2001 From: David LeBauer Date: Thu, 9 Jan 2025 15:49:55 -0700 Subject: [PATCH 19/34] Update ci.yml --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4708e41c..fe0c452e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -65,7 +65,7 @@ jobs: # Run Unit Tests test: - needs: build_integration_test + needs: build strategy: fail-fast: false matrix: From e5774f3784f0b5bd1c2d078de2f7f7f438caf92a Mon Sep 17 00:00:00 2001 From: Mike Longfritz Date: Fri, 10 Jan 2025 12:22:28 -0500 Subject: [PATCH 20/34] Updates events.c to add doc comments at top Co-authored-by: David LeBauer --- events.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/events.c b/events.c index b4f30279..15259afb 100644 --- a/events.c +++ b/events.c @@ -1,5 +1,27 @@ // // events.c +/** + * @file events.c + * @brief Handles reading, parsing, and storing agronomic events for SIPNET simulations. + * + * The `events.in` file specifies agronomic events with the following columns: + * | col | parameter | description | units | + * |-----|-------------|--------------------------------------------|------------------| + * | 1 | loc | spatial location index | | + * | 2 | year | year of start of this timestep | | + * | 3 | day | day of start of this timestep | Day of year | + * | 4 | event_type | type of event | (e.g., "irrig") | + * | 5...n| event_param| parameters specific to the event type | (varies) | + * + * + * Event types include: + * - "irrig": Parameters = [amount added (cm/d), type (0=canopy, 1=soil, 2=flood)] + * - "fert": Parameters = [Org-N (g/m²), Org-C (g/ha), Min-N (g/m²), Min-N2 (g/m²)] + * - "till": Parameters = [SOM decomposition modifier, litter decomposition modifier] + * - "plant": Parameters = [emergence lag (days), C (g/m²), N (g/m²)]] + * - "harv": Parameters = [fraction aboveground removed, fraction belowground removed, ...] + * See test examples in `tests/sipnet/test_events/`. + */ // #include "events.h" From 394c4ab8d3ce5d76e2bc782e9964482cc06085e5 Mon Sep 17 00:00:00 2001 From: Alomir Date: Fri, 10 Jan 2025 12:32:43 -0500 Subject: [PATCH 21/34] Adds docs for building and running unit tests to CONTRIBUTING.md --- CONTRIBUTING.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7806eda2..2d1d609d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -15,3 +15,14 @@ make clean # list all make commands make help ``` +## Testing + +SIPNET also uses `make` to build and run its unit tests. This can be done with the following commands: +```shell +# Compile tests +make test +# Run tests +make testrun +# Clean after tests are run +make testclean +``` From e94682a6fbca211a7d664d746f0c75fc439389f6 Mon Sep 17 00:00:00 2001 From: Alomir Date: Wed, 15 Jan 2025 13:24:34 -0500 Subject: [PATCH 22/34] Moves two switches to modelStructures.h, adds test update script --- modelStructures.h | 9 ++- sipnet.c | 4 +- tests/sipnet/test_events/modelStructures.h | 15 ++++- tests/update_model_structures.sh | 70 ++++++++++++++++++++++ 4 files changed, 93 insertions(+), 5 deletions(-) create mode 100755 tests/update_model_structures.sh diff --git a/modelStructures.h b/modelStructures.h index 207287a6..73783143 100644 --- a/modelStructures.h +++ b/modelStructures.h @@ -1,8 +1,10 @@ // -// This file is inteneded to hold settings for different model structure options, +// This file is intended to hold settings for different model structure options, // implemented as compile-time flags. The options are here (with nothing else) to // improve testability. // +// If this file is changed, consider running tests/update_model_structures.sh to update +// the corresponding unit test versions of this file. #ifndef MODEL_STRUCTURES_H #define MODEL_STRUCTURES_H @@ -14,5 +16,10 @@ // Read in and process agronomic events. SIPNET expects a file named events.in to exist, though // unit tests may use other names. +// have extra litter pool, in addition to soil c pool +#define LITTER_POOL 0 + +// Whether we model root dynamics +#define ROOTS 1 #endif //MODEL_STRUCTURES_H diff --git a/sipnet.c b/sipnet.c index ddcdd0ff..7e0e056e 100644 --- a/sipnet.c +++ b/sipnet.c @@ -96,7 +96,7 @@ #define SOIL_PHENOL 0 && !GDD // use soil temp. to determine leaf growth? (note: mutually exclusive with GDD) -#define LITTER_POOL 0 +// LITTER_POOL moved to modelStructures.h // have extra litter pool, in addition to soil c pool #define SOIL_MULTIPOOL 0 && !LITTER_POOL @@ -120,7 +120,7 @@ #define STOICHIOMETRY 0 && MICROBES // do we utilize stoichometric considerations for the microbial pool? -#define ROOTS 1 +// ROOTS moved to modelStructures.h // do we model root dynamics? diff --git a/tests/sipnet/test_events/modelStructures.h b/tests/sipnet/test_events/modelStructures.h index 5a591bb0..d0307d36 100644 --- a/tests/sipnet/test_events/modelStructures.h +++ b/tests/sipnet/test_events/modelStructures.h @@ -1,8 +1,13 @@ // -// This file is inteneded to hold settings for different model structure options, +// DO NOT ADD CUSTOM CODE TO THIS FILE (other than #define overrides for unit tests) +// IT MAY BE OVERWRITTEN BY update_model_structures.sh +// +// This file is intended to hold settings for different model structure options, // implemented as compile-time flags. The options are here (with nothing else) to // improve testability. // +// If this file is changed, consider running tests/update_model_structures.sh to update +// the corresponding unit test versions of this file. #ifndef MODEL_STRUCTURES_H #define MODEL_STRUCTURES_H @@ -11,7 +16,13 @@ // See also sipnet.c for other options (that should be moved here if/when testing is added) #define EVENT_HANDLER 1 -// Read in and process agronomic events. Expects a file named .event to exist. +// Read in and process agronomic events. SIPNET expects a file named events.in to exist, though +// unit tests may use other names. + +// have extra litter pool, in addition to soil c pool +#define LITTER_POOL 0 +// Whether we model root dynamics +#define ROOTS 1 #endif //MODEL_STRUCTURES_H diff --git a/tests/update_model_structures.sh b/tests/update_model_structures.sh new file mode 100755 index 00000000..0656b64c --- /dev/null +++ b/tests/update_model_structures.sh @@ -0,0 +1,70 @@ +#!/bin/bash +# Run this script from the root SIPNET directory with the command: +# > tests/update_model_structures.sh + +# Name of the file containing #define compiler switches +STRUCTURES_FILE="modelStructures.h" + +# Get the path to the current directory's STRUCTURES_FILE - that is, +# the production version +PROD_MODEL_STRUCTURES="./${STRUCTURES_FILE}" + +# Check if the current directory has the base model_structures.h +if [[ ! -f $PROD_MODEL_STRUCTURES ]]; then + echo "Error: ${STRUCTURES_FILE} not found in the current directory." + exit 1 +fi + +# Define the awk script to update #define values; this will be called on each test +# version of STRUCTURES_FILE +read -r -d '' AWK_SCRIPT << 'EOF' +BEGIN { + FS = "[ \t]+"; # Set field separator to handle spaces or tabs + define_regex = "^#define"; + skip_regex = "MODEL_STRUCTURES_H"; +} + +# Process the test structures file and store the #define values in an associative array +FNR == NR { + if ($1 ~ define_regex && $2 !~ skip_regex) { + name = $2; # The second field is the name of the #define + value = $3; # The third field is the value of the #define + defines[name] = value; + } + next; +} + +# Update the new file with the old #define values +{ + if ($1 ~ define_regex && $2 !~ skip_regex) { + name = $2; + if (name in defines) { + printf "#define %s %s\n", name, defines[name]; + next; + } + } + # Print the line as-is if no overwrite is needed + print $0; +} +EOF + +# Find and process all target files in subdirectories +find . -type f -name "$STRUCTURES_FILE" -mindepth 2 | while read -r file; do + echo "Processing: $file" + + # Create a backup of the original file + cp "$file" "${file}.bak" + + # Add a warning at the top, then update defines + { + echo "//" + echo "// DO NOT ADD CUSTOM CODE TO THIS FILE (other than #define overrides for unit tests)" + echo "// IT MAY BE OVERWRITTEN BY update_model_structures.sh" + cat "$PROD_MODEL_STRUCTURES" + } | awk "$AWK_SCRIPT" "$file" - > "${file}.tmp" + + # Overwrite the target file with the updated content + mv "${file}.tmp" "$file" + + echo "Updated: $file" +done From dadb7e99ce7dfcedd05bd44bcd2d8e96a5dce85c Mon Sep 17 00:00:00 2001 From: Alomir Date: Wed, 15 Jan 2025 13:37:40 -0500 Subject: [PATCH 23/34] Updates CONTRIBUTING.md with test update script command --- CONTRIBUTING.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2d1d609d..fa5d2e1a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -26,3 +26,9 @@ make testrun # Clean after tests are run make testclean ``` + +If changes are made to the `modelStructure.h` file and unit tests are failing, try running the update script as shown below. Consider running this script even if unit tests _are not_ failing. +```shell +# Run this command from the root directory to update unit test versions of modelStructures.h +tests/update_model_structures.sh +``` From 84fdae8de9f045d04b741d12b200721e5562adc2 Mon Sep 17 00:00:00 2001 From: Alomir Date: Fri, 31 Jan 2025 11:31:03 -0500 Subject: [PATCH 24/34] Adds event fetch and stubbed processing in sipnet.c --- sipnet.c | 92 +++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 77 insertions(+), 15 deletions(-) diff --git a/sipnet.c b/sipnet.c index ddcdd0ff..59eaebac 100644 --- a/sipnet.c +++ b/sipnet.c @@ -489,8 +489,12 @@ static ClimateNode *climate; // current climate static Fluxes fluxes; static double *outputPtrs[MAX_DATA_TYPES]; // pointers to different possible outputs +// MJL REMOVE THIS BEFORE CHECKIN +#define EVENT_HANDLER 1 + #if EVENT_HANDLER static EventNode **events; +static EventNode *locEvent; // current location event list #endif /* Read climate file into linked lists, @@ -2275,33 +2279,76 @@ void updateTrackers(double oldSoilWater) { // mean of soil wetness at start of time step at soil wetness at end of time step - assume linear } +// This should be in events.h/c, but with all the global state defined in this file, +// let's leave it here for now. Maybe someday we will factor that out. +// +// Process events for current location/year/day +void processEvents() { +#if EVENT_HANDLER + // If locEvent starts off NULL, this function will just fall through, as it should. + const int year = climate->year; + const int day = climate->day; + + // The events file has been tested on read, so we know this event list should be in chrono + // order. However, we need to check to make sure the current event is not in the past, as + // that would indicate an event that did not have a corresponding climate file record. + while (locEvent != NULL && locEvent->year <= year && locEvent->day <= day) { + if (locEvent->year < year || locEvent->day < day) { + printf("Agronomic event found for loc: %d year: %d day: %d that does not have a corresponding record in the climate file\n", locEvent->year, locEvent->year, locEvent->day); + exit(1); + } + switch (locEvent->type) { + // Implementation TBD, as we enable the various event types + case IRRIGATION: + // TBD + printf("Irrigation events not yet implemented\n"); + break; + case PLANTING: + // TBD + printf("Planting events not yet implemented\n"); + break; + case HARVEST: + // TBD + printf("Harvest events not yet implemented\n"); + break; + case TILLAGE: + // TBD + printf("Tillage events not yet implemented\n"); + break; + case FERTILIZATION: + // TBD + printf("Fertilization events not yet implemented\n"); + break; + default: + printf("Unknown event type (%d) in processEvents()\n", locEvent->type); + exit(1); + } -// !!! main runner function !!! + locEvent = locEvent->nextEvent; + } +#endif +} +// !!! main runner function !!! // calculate all fluxes and update state for this time step // we calculate all fluxes before updating state in case flux calculations depend on the old state void updateState() { double npp; // net primary productivity, g C * m^-2 ground area * day^-1 - double oldSoilWater; // how much soil water was there before we updated it? Used in trackers - int err; - oldSoilWater = envi.soilWater; - - calculateFluxes(); - - // update the stocks, with fluxes adjusted for length of time step: - envi.plantWoodC += (fluxes.photosynthesis + fluxes.woodCreation - fluxes.leafCreation - fluxes.woodLitter - - fluxes.rVeg-fluxes.coarseRootCreation-fluxes.fineRootCreation)* climate->length; - envi.plantLeafC += (fluxes.leafCreation - fluxes.leafLitter) * climate->length; - + double oldSoilWater; // how much soil water was there before we updated it? Used in trackers + int err; + oldSoilWater = envi.soilWater; + calculateFluxes(); + // update the stocks, with fluxes adjusted for length of time step: + envi.plantWoodC += (fluxes.photosynthesis + fluxes.woodCreation - fluxes.leafCreation - fluxes.woodLitter + - fluxes.rVeg-fluxes.coarseRootCreation-fluxes.fineRootCreation)* climate->length; + envi.plantLeafC += (fluxes.leafCreation - fluxes.leafLitter) * climate->length; soilDegradation(); // This updates all the soil functions - - #if MODEL_WATER // water pool updating happens here: #if LITTER_WATER // (2 soil water layers; litter water will only be on if complex water is also on) @@ -2323,6 +2370,13 @@ void updateState() { ensureNonNegativeStocks(); +#if EVENT_HANDLER + // Process events for this location/year/day, AFTER updates are made to fluxes and state + // variables above. Events are (currently, Jan 25) handled as instantaneous deltas to + // relevant state (envi and fluxes fields), + processEvents(); +#endif + npp = fluxes.photosynthesis - fluxes.rVeg-fluxes.rCoarseRoot-fluxes.rFineRoot; @@ -2485,7 +2539,9 @@ void setupModel(SpatialParams *spatialParams, int loc) { // Setup events at given location void setupEvents(int currLoc) { - // Implementation TBD +#if EVENT_HANDLER + locEvent = events[currLoc]; +#endif } @@ -2554,6 +2610,12 @@ void runModelNoOut(double **outArray, int numDataTypes, int dataTypeIndices[], S setupModel(spatialParams, loc); +#if EVENT_HANDLER + // Implementation TBD + printf("Event handler not yet implemented for running model with no output\n"); + exit(1); +#endif + // loop through every step of the model: while (climate != NULL) { updateState(); From 8b03f77bfdc5b0f2e57dc30e95369a7b4723af9c Mon Sep 17 00:00:00 2001 From: Alomir Date: Fri, 31 Jan 2025 11:34:49 -0500 Subject: [PATCH 25/34] Removes EVENT_HANDLER override used in dev --- sipnet.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/sipnet.c b/sipnet.c index 27559343..83790e1e 100644 --- a/sipnet.c +++ b/sipnet.c @@ -489,9 +489,6 @@ static ClimateNode *climate; // current climate static Fluxes fluxes; static double *outputPtrs[MAX_DATA_TYPES]; // pointers to different possible outputs -// MJL REMOVE THIS BEFORE CHECKIN -#define EVENT_HANDLER 1 - #if EVENT_HANDLER static EventNode **events; static EventNode *locEvent; // current location event list From a0a57a6fb481f51b2fa2faa0832be3ce668ae5c0 Mon Sep 17 00:00:00 2001 From: Alomir Date: Fri, 31 Jan 2025 12:42:43 -0500 Subject: [PATCH 26/34] Fixes (some) formatting issues --- sipnet.c | 124 +++++++++++++++++++++++++++---------------------------- 1 file changed, 61 insertions(+), 63 deletions(-) diff --git a/sipnet.c b/sipnet.c index 83790e1e..0e2433c7 100644 --- a/sipnet.c +++ b/sipnet.c @@ -2282,47 +2282,47 @@ void updateTrackers(double oldSoilWater) { // Process events for current location/year/day void processEvents() { #if EVENT_HANDLER - // If locEvent starts off NULL, this function will just fall through, as it should. - const int year = climate->year; - const int day = climate->day; - - // The events file has been tested on read, so we know this event list should be in chrono - // order. However, we need to check to make sure the current event is not in the past, as - // that would indicate an event that did not have a corresponding climate file record. - while (locEvent != NULL && locEvent->year <= year && locEvent->day <= day) { - if (locEvent->year < year || locEvent->day < day) { - printf("Agronomic event found for loc: %d year: %d day: %d that does not have a corresponding record in the climate file\n", locEvent->year, locEvent->year, locEvent->day); - exit(1); - } - switch (locEvent->type) { - // Implementation TBD, as we enable the various event types - case IRRIGATION: - // TBD - printf("Irrigation events not yet implemented\n"); - break; - case PLANTING: - // TBD - printf("Planting events not yet implemented\n"); - break; - case HARVEST: - // TBD - printf("Harvest events not yet implemented\n"); - break; - case TILLAGE: - // TBD - printf("Tillage events not yet implemented\n"); - break; - case FERTILIZATION: - // TBD - printf("Fertilization events not yet implemented\n"); - break; - default: - printf("Unknown event type (%d) in processEvents()\n", locEvent->type); - exit(1); - } + // If locEvent starts off NULL, this function will just fall through, as it should. + const int year = climate->year; + const int day = climate->day; + + // The events file has been tested on read, so we know this event list should be in chrono + // order. However, we need to check to make sure the current event is not in the past, as + // that would indicate an event that did not have a corresponding climate file record. + while (locEvent != NULL && locEvent->year <= year && locEvent->day <= day) { + if (locEvent->year < year || locEvent->day < day) { + printf("Agronomic event found for loc: %d year: %d day: %d that does not have a corresponding record in the climate file\n", locEvent->year, locEvent->year, locEvent->day); + exit(1); + } + switch (locEvent->type) { + // Implementation TBD, as we enable the various event types + case IRRIGATION: + // TBD + printf("Irrigation events not yet implemented\n"); + break; + case PLANTING: + // TBD + printf("Planting events not yet implemented\n"); + break; + case HARVEST: + // TBD + printf("Harvest events not yet implemented\n"); + break; + case TILLAGE: + // TBD + printf("Tillage events not yet implemented\n"); + break; + case FERTILIZATION: + // TBD + printf("Fertilization events not yet implemented\n"); + break; + default: + printf("Unknown event type (%d) in processEvents()\n", locEvent->type); + exit(1); + } - locEvent = locEvent->nextEvent; - } + locEvent = locEvent->nextEvent; + } #endif } @@ -2332,7 +2332,7 @@ void processEvents() { // calculate all fluxes and update state for this time step // we calculate all fluxes before updating state in case flux calculations depend on the old state void updateState() { - double npp; // net primary productivity, g C * m^-2 ground area * day^-1 + double npp; // net primary productivity, g C * m^-2 ground area * day^-1 double oldSoilWater; // how much soil water was there before we updated it? Used in trackers int err; oldSoilWater = envi.soilWater; @@ -2341,40 +2341,39 @@ void updateState() { // update the stocks, with fluxes adjusted for length of time step: envi.plantWoodC += (fluxes.photosynthesis + fluxes.woodCreation - fluxes.leafCreation - fluxes.woodLitter - - fluxes.rVeg-fluxes.coarseRootCreation-fluxes.fineRootCreation)* climate->length; + - fluxes.rVeg-fluxes.coarseRootCreation-fluxes.fineRootCreation)* climate->length; envi.plantLeafC += (fluxes.leafCreation - fluxes.leafLitter) * climate->length; - soilDegradation(); // This updates all the soil functions + soilDegradation(); // This updates all the soil functions - #if MODEL_WATER // water pool updating happens here: + #if MODEL_WATER // water pool updating happens here: - #if LITTER_WATER // (2 soil water layers; litter water will only be on if complex water is also on) - envi.litterWater += (fluxes.rain + fluxes.snowMelt - fluxes.immedEvap - fluxes.fastFlow - - fluxes.evaporation - fluxes.topDrainage) * climate->length; - envi.soilWater += (fluxes.topDrainage - fluxes.transpiration - fluxes.bottomDrainage) - * climate->length; + #if LITTER_WATER // (2 soil water layers; litter water will only be on if complex water is also on) + envi.litterWater += (fluxes.rain + fluxes.snowMelt - fluxes.immedEvap - fluxes.fastFlow + - fluxes.evaporation - fluxes.topDrainage) * climate->length; + envi.soilWater += (fluxes.topDrainage - fluxes.transpiration - fluxes.bottomDrainage) + * climate->length; - #else // LITTER_WATER = 0 (only one soil water layer) - // note: some of these fluxes will always be 0 if complex water is off - envi.soilWater += (fluxes.rain + fluxes.snowMelt - fluxes.immedEvap - fluxes.fastFlow - - fluxes.evaporation - fluxes.transpiration - fluxes.bottomDrainage) * climate->length; - #endif // LITTER_WATER + #else // LITTER_WATER = 0 (only one soil water layer) + // note: some of these fluxes will always be 0 if complex water is off + envi.soilWater += (fluxes.rain + fluxes.snowMelt - fluxes.immedEvap - fluxes.fastFlow + - fluxes.evaporation - fluxes.transpiration - fluxes.bottomDrainage) * climate->length; + #endif // LITTER_WATER - // if COMPLEX_WATER = 0 or SNOW = 0, some or all of these fluxes will always be 0 - envi.snow += (fluxes.snowFall - fluxes.snowMelt - fluxes.sublimation) * climate->length; + // if COMPLEX_WATER = 0 or SNOW = 0, some or all of these fluxes will always be 0 + envi.snow += (fluxes.snowFall - fluxes.snowMelt - fluxes.sublimation) * climate->length; - #endif // MODEL_WATER + #endif // MODEL_WATER ensureNonNegativeStocks(); #if EVENT_HANDLER - // Process events for this location/year/day, AFTER updates are made to fluxes and state - // variables above. Events are (currently, Jan 25) handled as instantaneous deltas to - // relevant state (envi and fluxes fields), - processEvents(); + // Process events for this location/year/day, AFTER updates are made to fluxes and state + // variables above. Events are (currently, Jan 25) handled as instantaneous deltas to + // relevant state (envi and fluxes fields), + processEvents(); #endif - npp = fluxes.photosynthesis - fluxes.rVeg-fluxes.rCoarseRoot-fluxes.rFineRoot; err = addValueToMeanTracker(meanNPP, npp, climate->length); // update running mean of NPP @@ -2394,7 +2393,6 @@ void updateState() { } updateTrackers(oldSoilWater); - } From 7b7bab15b59b6b8e482b5becc0fbe7f5fa95a849 Mon Sep 17 00:00:00 2001 From: David LeBauer Date: Fri, 31 Jan 2025 17:36:45 -0700 Subject: [PATCH 27/34] clarify comment --- sipnet.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sipnet.c b/sipnet.c index 0e2433c7..369e0288 100644 --- a/sipnet.c +++ b/sipnet.c @@ -2369,7 +2369,7 @@ void updateState() { #if EVENT_HANDLER // Process events for this location/year/day, AFTER updates are made to fluxes and state - // variables above. Events are (currently, Jan 25) handled as instantaneous deltas to + // variables above. Events are (currently, Jan 25, 2025) handled as instantaneous deltas to // relevant state (envi and fluxes fields), processEvents(); #endif From 88aa5a95f4b6845cb1da9423e5175ff3d5141497 Mon Sep 17 00:00:00 2001 From: Alomir Date: Mon, 3 Feb 2025 14:22:18 -0500 Subject: [PATCH 28/34] Fixes some indentation issues --- sipnet.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sipnet.c b/sipnet.c index 369e0288..0cef7af3 100644 --- a/sipnet.c +++ b/sipnet.c @@ -2535,7 +2535,7 @@ void setupModel(SpatialParams *spatialParams, int loc) { // Setup events at given location void setupEvents(int currLoc) { #if EVENT_HANDLER - locEvent = events[currLoc]; + locEvent = events[currLoc]; #endif } @@ -2606,9 +2606,9 @@ void runModelNoOut(double **outArray, int numDataTypes, int dataTypeIndices[], S setupModel(spatialParams, loc); #if EVENT_HANDLER - // Implementation TBD - printf("Event handler not yet implemented for running model with no output\n"); - exit(1); + // Implementation TBD + printf("Event handler not yet implemented for running model with no output\n"); + exit(1); #endif // loop through every step of the model: From 4207e80038d4042f736d4f88adcb60b22fb5193b Mon Sep 17 00:00:00 2001 From: Alomir Date: Mon, 3 Feb 2025 15:05:15 -0500 Subject: [PATCH 29/34] Adds exit code defs and minor fixes from PR feedback --- exitCodes.h | 12 ++++++++++++ sipnet.c | 7 ++++--- 2 files changed, 16 insertions(+), 3 deletions(-) create mode 100644 exitCodes.h diff --git a/exitCodes.h b/exitCodes.h new file mode 100644 index 00000000..58b6982a --- /dev/null +++ b/exitCodes.h @@ -0,0 +1,12 @@ +#ifndef EXITCODES_H +#define EXITCODES_H + +// Exit codes for SIPNET; most of the current uses of exit(1) should +// get better codes, which will appear here +typedef enum { + EXIT_SUCCESS = 0, + EXIT_FAILURE = 1, + EXIT_UNKNOWN_EVENT = 2 +} ExitCode; + +#endif diff --git a/sipnet.c b/sipnet.c index 0cef7af3..5e7d7a7d 100644 --- a/sipnet.c +++ b/sipnet.c @@ -20,6 +20,7 @@ #include "outputItems.h" #include "modelStructures.h" #include "events.h" +#include "exitCodes.h" // begin definitions for choosing different model structures // (1 -> true, 0 -> false) @@ -2280,8 +2281,8 @@ void updateTrackers(double oldSoilWater) { // let's leave it here for now. Maybe someday we will factor that out. // // Process events for current location/year/day -void processEvents() { #if EVENT_HANDLER +void processEvents() { // If locEvent starts off NULL, this function will just fall through, as it should. const int year = climate->year; const int day = climate->day; @@ -2318,13 +2319,13 @@ void processEvents() { break; default: printf("Unknown event type (%d) in processEvents()\n", locEvent->type); - exit(1); + exit(EXIT_UNKNOWN_EVENT); } locEvent = locEvent->nextEvent; } -#endif } +#endif // !!! main runner function !!! From f22f0cdb5f3319324f00d48eded430dd01f53d47 Mon Sep 17 00:00:00 2001 From: Alomir Date: Tue, 4 Feb 2025 13:31:24 -0500 Subject: [PATCH 30/34] Fixes exit code success and failure naming --- exitCodes.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/exitCodes.h b/exitCodes.h index 58b6982a..d7a760ab 100644 --- a/exitCodes.h +++ b/exitCodes.h @@ -4,9 +4,9 @@ // Exit codes for SIPNET; most of the current uses of exit(1) should // get better codes, which will appear here typedef enum { - EXIT_SUCCESS = 0, - EXIT_FAILURE = 1, - EXIT_UNKNOWN_EVENT = 2 -} ExitCode; + EXIT_CODE_SUCCESS = 0, + EXIT_CODE_FAILURE = 1, + EXIT_CODE_UNKNOWN_EVENT = 2 +} exit_code_t; #endif From 55a77e4a99e4160e84978a94dcd3726b9a9b97c0 Mon Sep 17 00:00:00 2001 From: Alomir Date: Tue, 4 Feb 2025 13:50:05 -0500 Subject: [PATCH 31/34] Updates exit code defs --- exitCodes.h | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/exitCodes.h b/exitCodes.h index d7a760ab..5d2308c1 100644 --- a/exitCodes.h +++ b/exitCodes.h @@ -5,8 +5,14 @@ // get better codes, which will appear here typedef enum { EXIT_CODE_SUCCESS = 0, - EXIT_CODE_FAILURE = 1, - EXIT_CODE_UNKNOWN_EVENT = 2 + EXIT_CODE_FAILURE = 1, // generic failure + // code 2 typically has a special meaning, see below + EXIT_CODE_UNKNOWN_EVENT = 3 } exit_code_t; +// Note for future: these codes typically have other meanings and should not be +// used here (except for 0 and 1, for which our meaning is the special meaning) +// 0-2, 126-165, and 255 +// See, e.g., https://tldp.org/LDP/abs/html/exitcodes.html + #endif From 364615629d85bc24cb6981f2cbf69c601f5e2b82 Mon Sep 17 00:00:00 2001 From: Alomir Date: Thu, 6 Feb 2025 11:57:56 -0500 Subject: [PATCH 32/34] Minor updates for PR feedback --- exitCodes.h | 15 +++++++++------ sipnet.c | 4 ++-- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/exitCodes.h b/exitCodes.h index 5d2308c1..63ec780f 100644 --- a/exitCodes.h +++ b/exitCodes.h @@ -2,7 +2,15 @@ #define EXITCODES_H // Exit codes for SIPNET; most of the current uses of exit(1) should -// get better codes, which will appear here +// get better codes, which will appear here. +// +// Note for future: some codes typically have other meanings and should not be +// used here (except for 0 and 1, for which our meaning is the usual meaning). +// Some somewhat-standard codes in use: +// - 0-2, 126-165, and 255; see, e.g., https://tldp.org/LDP/abs/html/exitcodes.html +// - 64-78, see https://man7.org/linux/man-pages/man3/sysexits.h.3head.html +// +// The upshot here is that we should be a little sparing in our list of error codes. typedef enum { EXIT_CODE_SUCCESS = 0, EXIT_CODE_FAILURE = 1, // generic failure @@ -10,9 +18,4 @@ typedef enum { EXIT_CODE_UNKNOWN_EVENT = 3 } exit_code_t; -// Note for future: these codes typically have other meanings and should not be -// used here (except for 0 and 1, for which our meaning is the special meaning) -// 0-2, 126-165, and 255 -// See, e.g., https://tldp.org/LDP/abs/html/exitcodes.html - #endif diff --git a/sipnet.c b/sipnet.c index 5e7d7a7d..169160b6 100644 --- a/sipnet.c +++ b/sipnet.c @@ -2534,11 +2534,11 @@ void setupModel(SpatialParams *spatialParams, int loc) { // Setup events at given location -void setupEvents(int currLoc) { #if EVENT_HANDLER +void setupEvents(int currLoc) { locEvent = events[currLoc]; -#endif } +#endif /* Do one run of the model using parameter values in spatialParams From e1d0aa610b9488f8e93466264ae5bc310da52497 Mon Sep 17 00:00:00 2001 From: Alomir Date: Thu, 6 Feb 2025 12:05:18 -0500 Subject: [PATCH 33/34] Fixes comment typo --- exitCodes.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exitCodes.h b/exitCodes.h index 63ec780f..25eb79d8 100644 --- a/exitCodes.h +++ b/exitCodes.h @@ -14,7 +14,7 @@ typedef enum { EXIT_CODE_SUCCESS = 0, EXIT_CODE_FAILURE = 1, // generic failure - // code 2 typically has a special meaning, see below + // code 2 typically has a special meaning, see above EXIT_CODE_UNKNOWN_EVENT = 3 } exit_code_t; From 20cf9b117b0ed5617dd3a3a2827357d28f3ea5ab Mon Sep 17 00:00:00 2001 From: Alomir Date: Thu, 6 Feb 2025 12:15:24 -0500 Subject: [PATCH 34/34] Fixes exit code typo in processEvents --- sipnet.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sipnet.c b/sipnet.c index 169160b6..0c517acb 100644 --- a/sipnet.c +++ b/sipnet.c @@ -2319,7 +2319,7 @@ void processEvents() { break; default: printf("Unknown event type (%d) in processEvents()\n", locEvent->type); - exit(EXIT_UNKNOWN_EVENT); + exit(EXIT_CODE_UNKNOWN_EVENT); } locEvent = locEvent->nextEvent;