diff --git a/IBPSA/Resources/C-Sources/WeeklySchedule.c b/IBPSA/Resources/C-Sources/WeeklySchedule.c index a01217d8f4..ceb0397429 100644 --- a/IBPSA/Resources/C-Sources/WeeklySchedule.c +++ b/IBPSA/Resources/C-Sources/WeeklySchedule.c @@ -3,6 +3,8 @@ This code implements a weekly schedule. Changelog: + March 30, 2024 by Filip Jorissen, Builtwins + Revisions for #1860 to avoid memory leaks when calling ModelicaFormatError. May 25, 2022 by Michael Wetter, LBNL Refactored to comply with C89. March 9, 2022 by Filip Jorissen, KU Leuven @@ -29,13 +31,11 @@ int cmpfun(const void * tuple1, const void * tuple2) { return (time1 - time2); } -void* weeklyScheduleInit(const int tableOnFile, const char* name, const double t_offset, char* stringData) { - FILE* fp; +void* weeklyScheduleInit(const int tableOnFile, const char* name, const double t_offset, const char* stringData) { const int bufLen = 255; WeeklySchedule* scheduleID = NULL; - char* token = NULL; - struct TimeDataTuple **rules; + int i = 0; /* iterator */ int j = 0; /* iterator */ int k = 0; /* iterator */ @@ -48,34 +48,52 @@ void* weeklyScheduleInit(const int tableOnFile, const char* name, const double t int comment = 0; /* we are parsing a comment */ int n_rulesInMem = 0; /* number of rules for which we have allocated memory */ int n_rulesInRow = 0; /* number of rules that exist in the current row */ - int n_rowsUnpacked = 0; /* total number of unpacked rules */ int n_rowsPacked = 0; /* number of rules */ int n_newLines = 0; /* number of newlines */ + int mustHaveNewLine = 0;/* The next character must be a newline */ char c; /* the character that is being parsed in this iteration */ + int parseToken = 0; double timeStamp; - char* buff2; int tokenLen; int offset = 0; - double* lastData = NULL; - scheduleID = (WeeklySchedule*)calloc(1, sizeof(WeeklySchedule)); - if ( scheduleID == NULL) + if ( scheduleID == NULL){ ModelicaFormatError("Failed to allocate memory for scheduleID in WeeklySchedule.c."); + } - token = (char*)calloc(sizeof(char), bufLen); - if ( token == NULL) - ModelicaFormatError("Failed to allocate memory for token in WeeklySchedule.c."); + scheduleID->n_allocatedRules = 0; + scheduleID->n_allocatedRulesData = 0; + scheduleID->lastData = NULL; + scheduleID->token = NULL; + scheduleID->fp = NULL; + scheduleID->buff2 = NULL; + scheduleID->rules = NULL; + + scheduleID->token = (char*)calloc(sizeof(char), bufLen); + if ( scheduleID->token == NULL){ + weeklyScheduleFreeInit(scheduleID); + weeklyScheduleFree(scheduleID); + ModelicaFormatError("Failed to allocate memory for token in WeeklySchedule.c."); + } if (tableOnFile){ - fp = fopen(name, "r"); - if (fp == NULL) { + scheduleID->fp = fopen(name, "r"); + if (scheduleID->fp == NULL) { + weeklyScheduleFreeInit(scheduleID); + weeklyScheduleFree(scheduleID); ModelicaFormatError("Failed to open weekly schedule '%s'.", name); } } + scheduleID->buff2 = (char*)calloc(sizeof(char), bufLen); + if (scheduleID->buff2 == NULL){ + weeklyScheduleFreeInit(scheduleID); + weeklyScheduleFree(scheduleID); + ModelicaFormatError("Failed to allocate memory for buff in WeeklySchedule.c."); + } /* Identify 'tokens' by splitting on (one or more) whitespace characters. */ /* Each token is parsed and special behaviour is created for comments and the header. */ @@ -87,7 +105,7 @@ void* weeklyScheduleInit(const int tableOnFile, const char* name, const double t parseToken = 0; if (tableOnFile){ - c = fgetc( fp ); /* read a character from the file */ + c = fgetc( scheduleID->fp ); /* read a character from the file */ }else{ c = stringData[j]; /* read a character from the string */ j++; @@ -95,27 +113,38 @@ void* weeklyScheduleInit(const int tableOnFile, const char* name, const double t if ( c == EOF || c == '\0') { if (!tableOnFile && c == '\0'){ break; - }else if (tableOnFile && feof(fp)) { + }else if (tableOnFile && feof(scheduleID->fp)) { break; /* exit the while loop */ } else { + weeklyScheduleFreeInit(scheduleID); + weeklyScheduleFree(scheduleID); ModelicaFormatError("Error while reading file '%s'.", name); } }{ if (index >= bufLen - 2) { + weeklyScheduleFreeInit(scheduleID); + weeklyScheduleFree(scheduleID); ModelicaFormatError("Buffer overflow when reading weekly schedule '%s'.", name); } if (c == '\n') { /* Check whether a token ends */ parseToken = 1; + mustHaveNewLine = 0; n_newLines++; + } else if (mustHaveNewLine == 1){ + weeklyScheduleFreeInit(scheduleID); + weeklyScheduleFree(scheduleID); + ModelicaFormatError("Error while reading weekly schedule '%s'. Inconsistent line endings: \\r must be followed by \\n.", name); + } else if (c == '\r') { /* Check whether a token ends */ + mustHaveNewLine = 1; } else if (comment == 1 || c == '#') { comment = 1; continue; /* ignore this character and the next characters until a newline is detected, then parse the token */ } else if ( isHeaderLine == 0 && (c == ' ' || c == '\t' ) && index > 0) { /* parse token when reaching a space or tab, unless buffer is empty */ parseToken = 1; } else if (c != ' ' && c != '\t' ) { /* build up the token by copying a character */ - token[index] = c; + scheduleID->token[index] = c; index++; } @@ -123,19 +152,17 @@ void* weeklyScheduleInit(const int tableOnFile, const char* name, const double t /* Parse a token if needed. */ if (parseToken == 1 && index > 0) { /* shouldn't require an overflow check since token is already checked */ - buff2 = (char*)calloc(sizeof(char), bufLen); - if (buff2 == NULL) - ModelicaFormatError("Failed to allocate memory for buff in WeeklySchedule.c."); + offset = 0; - token[index] = '\0'; + scheduleID->token[index] = '\0'; index++; - tokenLen = strlen(token); + tokenLen = strlen(scheduleID->token); index = 0; - /* ModelicaFormatWarning("Parsing token %s", token);*/ + /* ModelicaFormatWarning("Parsing token %s", scheduleID->token); */ - if (foundHeader == 0 && strcmp("double", token) == 0) { + if (foundHeader == 0 && strcmp("double", scheduleID->token) == 0) { /* we found a header line, we expect a specific format after the whitespace */ isHeaderLine = 1; foundHeader = 1; @@ -144,105 +171,145 @@ void* weeklyScheduleInit(const int tableOnFile, const char* name, const double t int ncharsRow; int ncharsCol; - if (strncmp("tab1(", token, 4) != 0) { + if (strncmp("tab1(", scheduleID->token, 4) != 0) { + weeklyScheduleFreeInit(scheduleID); + weeklyScheduleFree(scheduleID); ModelicaFormatError("Incorrect header when reading weekly schedule '%s'. It should start with 'tab1('.", name); } - source = token + 5; + source = scheduleID->token + 5; ncharsRow = strcspn(source, ","); if (tokenLen == ncharsRow + 5 ) { + weeklyScheduleFreeInit(scheduleID); + weeklyScheduleFree(scheduleID); ModelicaFormatError("Incorrect header when reading weekly schedule '%s'. No comma was found in the header.", name); } - strncpy(buff2, source, ncharsRow); - buff2[ncharsRow] = '\0'; + if ( ncharsRow > bufLen - 2 ) { + weeklyScheduleFreeInit(scheduleID); + weeklyScheduleFree(scheduleID); + ModelicaFormatError("Header length exceeds buffer size."); + } + strncpy(scheduleID->buff2, source, ncharsRow); + scheduleID->buff2[ncharsRow] = '\0'; - if (sscanf(buff2, "%i", &scheduleID->n_rows_in) != 1) { - ModelicaFormatError("Error in intenger conversion in header while parsing %s in weekly schedule '%s'.", buff2, name); + if (sscanf(scheduleID->buff2, "%i", &scheduleID->n_rows_in) != 1) { + weeklyScheduleFreeInit(scheduleID); + weeklyScheduleFree(scheduleID); + ModelicaFormatError("Error in intenger conversion in header while parsing %s in weekly schedule '%s'.", scheduleID->buff2, name); } source = source + ncharsRow + 1; ncharsCol = strcspn(source, ")"); if (tokenLen == ncharsCol + ncharsRow + 5 + 1) { + weeklyScheduleFreeInit(scheduleID); + weeklyScheduleFree(scheduleID); ModelicaFormatError("Incorrect header when reading weekly schedule '%s'. No closing bracket was found in the header.", name); } else if (tokenLen > ncharsCol + ncharsRow + 5 + 1 + 1) { - ModelicaFormatError("Incorrect header when reading weekly schedule '%s'. It has trailing characters: '%s'.", name, token + ncharsRow + ncharsCol + 7); + weeklyScheduleFreeInit(scheduleID); + weeklyScheduleFree(scheduleID); + ModelicaFormatError("Incorrect header when reading weekly schedule '%s'. It has trailing characters: '%s'.", name, scheduleID->token + ncharsRow + ncharsCol + 7); } - strncpy(buff2, source, ncharsCol); - buff2[ncharsCol] = '\0'; - if (sscanf(buff2, "%i", &scheduleID->n_cols_in) != 1) { - ModelicaFormatError("Error in integer conversion in header while parsing %s in weekly schedule '%s'..", buff2, name); + strncpy(scheduleID->buff2, source, ncharsCol); + scheduleID->buff2[ncharsCol] = '\0'; + if (sscanf(scheduleID->buff2, "%i", &scheduleID->n_cols_in) != 1) { + weeklyScheduleFreeInit(scheduleID); + weeklyScheduleFree(scheduleID); + ModelicaFormatError("Error in integer conversion in header while parsing %s in weekly schedule '%s'..", scheduleID->buff2, name); } if (scheduleID->n_cols_in < 2) { + weeklyScheduleFreeInit(scheduleID); + weeklyScheduleFree(scheduleID); ModelicaFormatError("Illegal number of columns '%i' when reading weekly schedule '%s'.", scheduleID->n_cols_in, name); } if (scheduleID->n_rows_in < 1) { + weeklyScheduleFreeInit(scheduleID); + weeklyScheduleFree(scheduleID); ModelicaFormatError("Illegal number of rows '%i' when reading weekly schedule '%s'.", scheduleID->n_rows_in, name); } isHeaderLine = 0; foundHeader = 1; - rules = (TimeDataTuple**)calloc(sizeof(TimeDataTuple *), scheduleID->n_rows_in); - if ( rules == NULL) + scheduleID->rules = (TimeDataTuple**)calloc(sizeof(TimeDataTuple *), scheduleID->n_rows_in); + if ( scheduleID->rules == NULL){ + weeklyScheduleFreeInit(scheduleID); + weeklyScheduleFree(scheduleID); ModelicaFormatError("Failed to allocate memory for rules in WeeklySchedule.c."); + } n_rulesInMem = scheduleID->n_rows_in; } else if (foundHeader == 0) { + weeklyScheduleFreeInit(scheduleID); + weeklyScheduleFree(scheduleID); ModelicaFormatError("Illegal file format, no header was found when reading weekly schedule '%s'.", name); } else if (tokensInLine == 0) { /* 0 tokens have been found on this line, so we're parsing a date/time */ - const int ncharsDays = strcspn(token, ":"); + const int ncharsDays = strcspn(scheduleID->token, ":"); timeStamp = 0; if (tokenLen != ncharsDays) { double val; - const int ncharsHour = strcspn(token + ncharsDays + 1, ":"); - - strncpy(buff2, token + ncharsDays + 1, ncharsHour); - buff2[ncharsHour] = '\0'; - if (sscanf(buff2, "%lf", &val) != 1) { - ModelicaFormatError("Error in float conversion in hours when reading weekly schedule '%s'. Found token %s with length %i", name, buff2, ncharsHour); + const int ncharsHour = strcspn(scheduleID->token + ncharsDays + 1, ":"); + + strncpy(scheduleID->buff2, scheduleID->token + ncharsDays + 1, ncharsHour); + scheduleID->buff2[ncharsHour] = '\0'; + if (sscanf(scheduleID->buff2, "%lf", &val) != 1) { + weeklyScheduleFreeInit(scheduleID); + weeklyScheduleFree(scheduleID); + ModelicaFormatError("Error in float conversion in hours when reading weekly schedule '%s'. Found token %s with length %i", name, scheduleID->buff2, ncharsHour); } if (val > 24 || val < 0) { + weeklyScheduleFreeInit(scheduleID); + weeklyScheduleFree(scheduleID); ModelicaFormatError("Unexpected value for hour: '%f' when reading weekly schedule '%s', should be between 0 and 24.", val, name); } timeStamp += val * 3600; if (tokenLen != ncharsHour + ncharsDays + 1) { - const int ncharsMinutes = strcspn(token + ncharsDays + ncharsHour + 2, ":"); - strncpy(buff2, token + ncharsDays + ncharsHour + 2, ncharsMinutes); - buff2[ncharsMinutes] = '\0'; - if (sscanf(buff2, "%lf", &val) != 1) { + const int ncharsMinutes = strcspn(scheduleID->token + ncharsDays + ncharsHour + 2, ":"); + strncpy(scheduleID->buff2, scheduleID->token + ncharsDays + ncharsHour + 2, ncharsMinutes); + scheduleID->buff2[ncharsMinutes] = '\0'; + if (sscanf(scheduleID->buff2, "%lf", &val) != 1) { + weeklyScheduleFreeInit(scheduleID); + weeklyScheduleFree(scheduleID); ModelicaFormatError("Error in float conversion in minutes when reading weekly schedule '%s'.", name); } if (val > 60 || val < 0) { + weeklyScheduleFreeInit(scheduleID); + weeklyScheduleFree(scheduleID); ModelicaFormatError("Unexpected value for minute: '%f' when reading weekly schedule '%s', should be between 0 and 60.", val, name); } timeStamp += val * 60; if (tokenLen != ncharsMinutes + ncharsHour + ncharsDays + 2) { const int ncharsSeconds = tokenLen - ncharsMinutes - ncharsHour - ncharsDays - 2; - strncpy(buff2, token + ncharsDays + ncharsHour + ncharsMinutes + 3, ncharsSeconds); - buff2[ncharsSeconds] = '\0'; - if (sscanf(buff2, "%lf", &val) != 1) { + strncpy(scheduleID->buff2, scheduleID->token + ncharsDays + ncharsHour + ncharsMinutes + 3, ncharsSeconds); + scheduleID->buff2[ncharsSeconds] = '\0'; + if (sscanf(scheduleID->buff2, "%lf", &val) != 1) { + weeklyScheduleFreeInit(scheduleID); + weeklyScheduleFree(scheduleID); ModelicaFormatError("Error in float conversion in seconds when reading weekly schedule '%s'.", name); } if (val > 60 || val < 0) { + weeklyScheduleFreeInit(scheduleID); + weeklyScheduleFree(scheduleID); ModelicaFormatError("Unexpected value for seconds: '%f' when reading weekly schedule '%s', should be between 0 and 60.", val, name); } timeStamp += val; } } } - strncpy(buff2, token, ncharsDays); - buff2[ncharsDays] = '\0'; + strncpy(scheduleID->buff2, scheduleID->token, ncharsDays); + scheduleID->buff2[ncharsDays] = '\0'; /* loop over all days (comma separated) and for each date, add a rule */ while ( 1 ) { - char * startIndex = buff2 + offset; + char * startIndex = scheduleID->buff2 + offset; double t_day, time_i; int nchars = strcspn(startIndex, ","); if (nchars != 3 ) { + weeklyScheduleFreeInit(scheduleID); + weeklyScheduleFree(scheduleID); ModelicaFormatError("Unexpected day format when reading weekly schedule '%s': %s.", name, startIndex); } @@ -261,30 +328,42 @@ void* weeklyScheduleInit(const int tableOnFile, const char* name, const double t } else if (strncmp("sun", startIndex, 3) == 0) { t_day = 6 * 3600 * 24; } else { + weeklyScheduleFreeInit(scheduleID); + weeklyScheduleFree(scheduleID); ModelicaFormatError("Unexpected day format when parsing weekday '%s': %s.", name, startIndex); } /* expand the memory if the initially assigned memory block does not suffice*/ if (rule_i >= n_rulesInMem) { n_rulesInMem += scheduleID->n_rows_in; - rules = (TimeDataTuple**)realloc(rules, sizeof(TimeDataTuple*) * n_rulesInMem); - if (rules == NULL) { + scheduleID->rules = (TimeDataTuple**)realloc(scheduleID->rules, sizeof(TimeDataTuple*) * n_rulesInMem); + if (scheduleID->rules == NULL) { + weeklyScheduleFreeInit(scheduleID); + weeklyScheduleFree(scheduleID); ModelicaFormatError("Failed to reallocate memory when reading weekly schedule '%s'.", name); } } time_i = timeStamp + t_day; - rules[rule_i] = (TimeDataTuple*)calloc(sizeof(TimeDataTuple), 1); - if ( rules[rule_i] == NULL) + scheduleID->rules[rule_i] = (TimeDataTuple*)calloc(sizeof(TimeDataTuple), 1); + if ( scheduleID->rules[rule_i] == NULL){ + weeklyScheduleFreeInit(scheduleID); + weeklyScheduleFree(scheduleID); ModelicaFormatError("Failed to allocate memory for rules[rule_i] in WeeklySchedule.c."); - rules[rule_i]->time = time_i; - rules[rule_i]->data = (double*)calloc(sizeof(double), (scheduleID->n_cols_in - 1)); - if ( rules[rule_i]->data == NULL) + } + scheduleID->n_allocatedRules++; + + scheduleID->rules[rule_i]->time = time_i; + scheduleID->rules[rule_i]->data = (double*)calloc(sizeof(double), (scheduleID->n_cols_in - 1)); + if ( scheduleID->rules[rule_i]->data == NULL){ + weeklyScheduleFreeInit(scheduleID); + weeklyScheduleFree(scheduleID); ModelicaFormatError("Failed to allocate memory for rules[rule_i]->data in WeeklySchedule.c."); - rule_i++; + } + scheduleID->n_allocatedRulesData++; + rule_i++; n_rulesInRow++; - n_rowsUnpacked++; if (offset == 0) /* only for the first rule in this row*/ n_rowsPacked++; @@ -300,30 +379,37 @@ void* weeklyScheduleInit(const int tableOnFile, const char* name, const double t /* a token has been found on this line before, so we're parsing some numerical data*/ if (tokensInLine >= scheduleID->n_cols_in) { + weeklyScheduleFreeInit(scheduleID); + weeklyScheduleFree(scheduleID); ModelicaFormatError("Too many columns on row %i when reading weekly schedule '%s'.", line, name); } - if (sscanf(token, "%lf", &val) != 1) { - if (token[0] == '-') { + if (sscanf(scheduleID->token, "%lf", &val) != 1) { + if (scheduleID->token[0] == '-') { val = HUGE_VAL; /*convert the wildcard in a double representation*/ } else { - ModelicaFormatError("Invalid format for float %s when reading weekly schedule '%s'.", token, name); + weeklyScheduleFreeInit(scheduleID); + weeklyScheduleFree(scheduleID); + ModelicaFormatError("Invalid format for float %s when reading weekly schedule '%s'.", scheduleID->token, name); } } /* Set the data for all rules that result from this row.*/ for (i = rule_i - n_rulesInRow; i < rule_i; ++i) { - rules[i]->data[tokensInLine - 1] = val; + scheduleID->rules[i]->data[tokensInLine - 1] = val; } tokensInLine++; } else { + weeklyScheduleFreeInit(scheduleID); + weeklyScheduleFree(scheduleID); ModelicaFormatError("Logic error when reading weekly schedule '%s'.", name); /*should not be able to end up here*/ } - free(buff2); } if (c == '\n') { /*reset some internal variables*/ if (tokensInLine > 0 && tokensInLine != scheduleID->n_cols_in) { + weeklyScheduleFreeInit(scheduleID); + weeklyScheduleFree(scheduleID); ModelicaFormatError("Incorrect number of columns on line %i when reading weekly schedule '%s'.", line, name); } line++; @@ -333,29 +419,37 @@ void* weeklyScheduleInit(const int tableOnFile, const char* name, const double t } } } - if (tableOnFile){ - fclose(fp); - } if (n_newLines==0){ + weeklyScheduleFreeInit(scheduleID); + weeklyScheduleFree(scheduleID); ModelicaFormatError("In weekly schedule '%s': The provided %s is incorrectly formatted since it does not contain newline characters.", name, tableOnFile ? "file": "string parameter"); } if (n_rowsPacked != scheduleID->n_rows_in) { + weeklyScheduleFreeInit(scheduleID); + weeklyScheduleFree(scheduleID); ModelicaFormatError("Incorrect number of rows when reading weekly schedule '%s': %i instead of %i.", name, n_rowsPacked, scheduleID->n_rows_in); } - if (scheduleID->n_cols_in < 1) + if (scheduleID->n_cols_in < 1){ + weeklyScheduleFreeInit(scheduleID); + weeklyScheduleFree(scheduleID); ModelicaFormatError("The number of columns in the weekly schedule '%s' is %i, which is too small. ", name, scheduleID->n_cols_in); + } /* sort all data by time stamp*/ - qsort(rules, rule_i, sizeof(TimeDataTuple*), cmpfun); + qsort(scheduleID->rules, rule_i, sizeof(TimeDataTuple*), cmpfun); { /* working vector with zero initial value*/ - lastData = (double*)calloc(sizeof(double), scheduleID->n_cols_in - 1); - if (lastData == NULL) + scheduleID->lastData = (double*)calloc(sizeof(double), scheduleID->n_cols_in - 1); + if (scheduleID->lastData == NULL){ + weeklyScheduleFreeInit(scheduleID); + weeklyScheduleFree(scheduleID); ModelicaFormatError("Failed to allocate memory for lastData in WeeklySchedule.c., scheduleID->n_cols_in -1 = %d", scheduleID->n_cols_in - 1); - memset(lastData, (char)(double)0, scheduleID->n_cols_in - 1); /* set vector to zero initial guess*/ + } + + memset(scheduleID->lastData, (char)(double)0, scheduleID->n_cols_in - 1); /* set vector to zero initial guess*/ /* Loop over all data and fill in wildcards using the last preceeding value.*/ /* This may wrap back to the end of last week, therefore loop the data twice.*/ @@ -364,41 +458,60 @@ void* weeklyScheduleInit(const int tableOnFile, const char* name, const double t for (i = 0; i < 2; ++i) { for (j = 0; j < rule_i; ++j) { for (k = 0; k < scheduleID->n_cols_in - 1; ++k) { - if ( rules[j]->data[k] != HUGE_VAL ) { - lastData[k] = rules[j]->data[k]; + if ( scheduleID->rules[j]->data[k] != HUGE_VAL ) { + scheduleID->lastData[k] = scheduleID->rules[j]->data[k]; } else if (i > 0) { /* only on the second pass, since otherwise the default value is filled in permanently and information from the back of the domain can't be recycled */ - rules[j]->data[k] = lastData[k]; + scheduleID->rules[j]->data[k] = scheduleID->lastData[k]; } } } } - free(lastData); } /* store data for later use */ scheduleID->t_offset = t_offset; - scheduleID->n_rowsUnpacked = n_rowsUnpacked; scheduleID->previousIndex = 0; - scheduleID->schedule = rules; scheduleID->previousTimestamp = HUGE_VAL; - free(token); + weeklyScheduleFreeInit(scheduleID); return (void*) scheduleID; } +void weeklyScheduleFreeInit(void * ID) { + WeeklySchedule* scheduleID = (WeeklySchedule*)ID; + + if (scheduleID->lastData != NULL) + free(scheduleID->lastData); + + if (scheduleID->token != NULL) + free(scheduleID->token); + + if (scheduleID->fp != NULL) + fclose(scheduleID->fp); + + if (scheduleID->buff2 != NULL) + free(scheduleID->buff2); + +} + + void weeklyScheduleFree(void * ID) { WeeklySchedule* scheduleID = (WeeklySchedule*)ID; int i; - for (i = 0; i < scheduleID->n_rowsUnpacked; ++i) { - free(scheduleID->schedule[i]->data); - free(scheduleID->schedule[i]); + for (i = 0; i < scheduleID->n_allocatedRulesData; ++i) { + free(scheduleID->rules[i]->data); + } + for (i = 0; i < scheduleID->n_allocatedRules; ++i) { + free(scheduleID->rules[i]); } - free(scheduleID->schedule); + if (scheduleID->rules != NULL) + free(scheduleID->rules); + free(ID); ID = NULL; } @@ -413,6 +526,8 @@ double getScheduleValue(void * ID, const int column, const double modelicaTime) int i; const int columnIndex = column - 1; /* Since we do not store the time indices in the data table */ + /* Not calling weeklyScheduleFreeInit() or weeklyScheduleFree() since weeklyScheduleFreeInit() has already been called at the end of the + initialization and Modelica will call weeklyScheduleFree() upon a call of ModelicaFormatError) */ if (column < 0 || column > scheduleID->n_cols_in - 1) { ModelicaFormatError("The requested column index '%i' is outside of the table range.", column + 1); } @@ -423,28 +538,28 @@ double getScheduleValue(void * ID, const int column, const double modelicaTime) if (time == scheduleID->previousTimestamp) { i = scheduleID->previousIndex; - } else if (time > scheduleID->schedule[scheduleID->previousIndex]->time) { - for (i = scheduleID->previousIndex; i < scheduleID->n_rowsUnpacked - 1; i ++) { - if (scheduleID->schedule[i + 1]->time > time) { + } else if (time > scheduleID->rules[scheduleID->previousIndex]->time) { + for (i = scheduleID->previousIndex; i < scheduleID->n_allocatedRules - 1; i ++) { + if (scheduleID->rules[i + 1]->time > time) { break; } } } else { for (i = scheduleID->previousIndex; i > 0; i--) { - if (scheduleID->schedule[i - 1]->time < time) { + if (scheduleID->rules[i - 1]->time < time) { i = i - 1; break; } } /* if time is smaller than the first row, wrap back to the end of the week */ - if (i == 0 && scheduleID->schedule[0]->time > time) { - i = scheduleID->n_rowsUnpacked - 1; + if (i == 0 && scheduleID->rules[0]->time > time) { + i = scheduleID->n_allocatedRules - 1; } } scheduleID->previousIndex = i; scheduleID->previousTimestamp = time; - return scheduleID->schedule[i]->data[columnIndex]; + return scheduleID->rules[i]->data[columnIndex]; } #endif diff --git a/IBPSA/Resources/C-Sources/WeeklySchedule.h b/IBPSA/Resources/C-Sources/WeeklySchedule.h index 4b52a6db2e..3a4105e261 100644 --- a/IBPSA/Resources/C-Sources/WeeklySchedule.h +++ b/IBPSA/Resources/C-Sources/WeeklySchedule.h @@ -16,6 +16,7 @@ #ifndef WEEKCAL_h #define WEEKCAL_h + typedef struct TimeDataTuple { double time; /* Time relative to monday midnight. */ double *data; /* Corresponding column data */ @@ -26,20 +27,28 @@ typedef struct WeeklySchedule { double t_offset; /* Time offset for monday, midnight. */ int n_rows_in; /* Number of input rows */ int n_cols_in; /* Number of input columns */ - int n_rowsUnpacked; /* Number of rows: number of rows after unpacking the date */ double previousTimestamp; /* Time where the schedule was called the previous time */ int previousIndex; /* Index where the schedule was called the previous time */ - struct TimeDataTuple ** schedule; + int n_allocatedRules; + int n_allocatedRulesData; + double * lastData; + char * token; + FILE* fp; + char* buff2; + struct TimeDataTuple **rules; + } WeeklySchedule; -void* weeklyScheduleInit(const int tableOnFile, const char* name, const double t_offset, char* stringData); +void* weeklyScheduleInit(const int tableOnFile, const char* name, const double t_offset, const char* stringData); void weeklyScheduleFree(void * ID); +void weeklyScheduleFreeInit(void * ID); + double getScheduleValue(void * ID, const int column, const double time); #endif diff --git a/IBPSA/Resources/Data/scheduleWindows.txt b/IBPSA/Resources/Data/scheduleWindows.txt new file mode 100644 index 0000000000..beabafdb0c --- /dev/null +++ b/IBPSA/Resources/Data/scheduleWindows.txt @@ -0,0 +1,7 @@ +# Comments start with a # +# The user is responsible for making sure that the header dimensions are correct +double tab1(3,5) # Comments can be added at the end of a line for adding local documentation +mon:0:0:10 - 3 1 - +# Comments can be added in the table too +tue,thu:20:30:59 123 - 45 - +wed 12 1 4 - diff --git a/IBPSA/Resources/ReferenceResults/Dymola/IBPSA_Utilities_IO_Files_Validation_WeeklyScheduleWindowsLineEndings.txt b/IBPSA/Resources/ReferenceResults/Dymola/IBPSA_Utilities_IO_Files_Validation_WeeklyScheduleWindowsLineEndings.txt new file mode 100644 index 0000000000..cb6e638234 --- /dev/null +++ b/IBPSA/Resources/ReferenceResults/Dymola/IBPSA_Utilities_IO_Files_Validation_WeeklyScheduleWindowsLineEndings.txt @@ -0,0 +1,12 @@ +last-generated=2024-04-02 +statistics-simulation= +{ + "linear": " ", + "nonlinear": " ", + "numerical Jacobians": "0" +} +time=[-1e+04, 1e+06] +weeSchLin.y[1]=[1.2e+01, 1.2e+01, 1.2e+01, 1.2e+01, 1.2e+01, 1.2e+01, 1.2e+01, 1.2e+01, 1.2e+01, 1.2e+01, 1.2e+01, 1.2e+01, 1.2e+01, 1.2e+01, 1.23e+02, 1.23e+02, 1.23e+02, 1.23e+02, 1.23e+02, 1.23e+02, 1.23e+02, 1.23e+02, 1.23e+02, 1.23e+02, 1.23e+02, 1.23e+02, 1.23e+02, 1.23e+02, 1.23e+02, 1.23e+02, 1.23e+02, 1.23e+02, 1.23e+02, 1.23e+02, 1.23e+02, 1.23e+02, 1.23e+02, 1.23e+02, 1.23e+02, 1.23e+02, 1.23e+02, 1.23e+02, 1.23e+02, 1.23e+02, 1.23e+02, 1.23e+02, 1.23e+02, 1.23e+02, 1.23e+02, 1.23e+02, 1.23e+02, 1.23e+02, 1.23e+02, 1.23e+02, 1.23e+02, 1.23e+02, 1.23e+02, 1.23e+02, 1.2e+01, 1.2e+01, 1.2e+01, 1.2e+01, 1.2e+01, 1.2e+01, 1.2e+01, 1.2e+01, 1.2e+01, 1.2e+01, 1.2e+01, 1.2e+01, 1.2e+01, 1.2e+01, 1.2e+01, 1.2e+01, 1.23e+02, 1.23e+02, 1.23e+02, 1.23e+02, 1.23e+02, 1.23e+02, 1.23e+02, 1.23e+02, 1.23e+02, 1.23e+02, 1.23e+02, 1.23e+02, 1.23e+02, 1.23e+02, 1.23e+02, 1.23e+02, 1.23e+02, 1.23e+02, 1.23e+02, 1.23e+02, 1.23e+02, 1.23e+02, 1.23e+02, 1.23e+02, 1.23e+02, 1.23e+02, 1.23e+02] +weeSchLin.y[2]=[1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 3e+00, 3e+00, 3e+00, 3e+00, 3e+00, 3e+00, 3e+00, 3e+00, 3e+00, 3e+00, 3e+00, 3e+00, 3e+00, 3e+00, 3e+00, 3e+00, 3e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00] +weeSchLin.y[3]=[4e+00, 4e+00, 4e+00, 4e+00, 4e+00, 4e+00, 4e+00, 4e+00, 4e+00, 4e+00, 4e+00, 4e+00, 4e+00, 4e+00, 4.5e+01, 4.5e+01, 4.5e+01, 4.5e+01, 4.5e+01, 4.5e+01, 4.5e+01, 4.5e+01, 4.5e+01, 4.5e+01, 4.5e+01, 4.5e+01, 4.5e+01, 4.5e+01, 4.5e+01, 4.5e+01, 4.5e+01, 4.5e+01, 4.5e+01, 4.5e+01, 4.5e+01, 4.5e+01, 4.5e+01, 4.5e+01, 4.5e+01, 4.5e+01, 4.5e+01, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 1e+00, 4.5e+01, 4.5e+01, 4e+00, 4e+00, 4e+00, 4e+00, 4e+00, 4e+00, 4e+00, 4e+00, 4e+00, 4e+00, 4e+00, 4e+00, 4e+00, 4e+00, 4e+00, 4e+00, 4.5e+01, 4.5e+01, 4.5e+01, 4.5e+01, 4.5e+01, 4.5e+01, 4.5e+01, 4.5e+01, 4.5e+01, 4.5e+01, 4.5e+01, 4.5e+01, 4.5e+01, 4.5e+01, 4.5e+01, 4.5e+01, 4.5e+01, 4.5e+01, 4.5e+01, 4.5e+01, 4.5e+01, 4.5e+01, 4.5e+01, 4.5e+01, 4.5e+01, 4.5e+01, 4.5e+01] +weeSchLin.y[4]=[0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00, 0e+00] diff --git a/IBPSA/Resources/Scripts/Dymola/Utilities/IO/Files/Validation/WeeklyScheduleWindowsLineEndings.mos b/IBPSA/Resources/Scripts/Dymola/Utilities/IO/Files/Validation/WeeklyScheduleWindowsLineEndings.mos new file mode 100644 index 0000000000..d90d3a8c46 --- /dev/null +++ b/IBPSA/Resources/Scripts/Dymola/Utilities/IO/Files/Validation/WeeklyScheduleWindowsLineEndings.mos @@ -0,0 +1,2 @@ +simulateModel("IBPSA.Utilities.IO.Files.Validation.WeeklyScheduleWindowsLineEndings", startTime=-10000, stopTime=1000000, tolerance=1e-6, method="dassl", resultFile="WeeklyScheduleWindowsLineEndings"); +createPlot(id=1, position={15, 15, 592, 364}, y={"weeSchLin.y[1]", "weeSchLin.y[2]", "weeSchLin.y[3]", "weeSchLin.y[4]"}, range={-100000.0, 1000000.0, -20.0, 140.0}, grid=true, colors={{28,108,200}, {238,46,47}, {0,140,72}, {217,67,180}}, timeUnit="s"); diff --git a/IBPSA/Utilities/IO/Files/Validation/WeeklyScheduleWindowsLineEndings.mo b/IBPSA/Utilities/IO/Files/Validation/WeeklyScheduleWindowsLineEndings.mo new file mode 100644 index 0000000000..30ab9564ad --- /dev/null +++ b/IBPSA/Utilities/IO/Files/Validation/WeeklyScheduleWindowsLineEndings.mo @@ -0,0 +1,55 @@ +within IBPSA.Utilities.IO.Files.Validation; +model WeeklyScheduleWindowsLineEndings "Weekly schedule example" + extends Modelica.Icons.Example; + parameter String data = "double tab1(3,5) #test: +mon:0:0:10 - 3 1 - +tue,thu:20:30:59 123 - 45 - +wed 12 1 4 -" "Contents of schedule.txt"; + IBPSA.Utilities.IO.Files.WeeklySchedule weeSchLin( + columns={2,3,4,5}, + tableOnFile=true, + fileName=Modelica.Utilities.Files.loadResource("modelica://IBPSA/Resources/Data/schedule.txt"), + t_offset=1e6) "Weekly schedule example using file data source" + annotation (Placement(transformation(extent={{-10,20},{10,40}}))); + + IBPSA.Utilities.IO.Files.WeeklySchedule weeSchWin( + columns={2,3,4,5}, + tableOnFile=true, + fileName=Modelica.Utilities.Files.loadResource("modelica://IBPSA/Resources/Data/scheduleWindows.txt"), + t_offset=1e6) "Weekly schedule example using parameter data source" + annotation (Placement(transformation(extent={{-10,-32},{10,-12}}))); + Diagnostics.AssertEquality assEqu[4]( + each startTime=-10000, + each threShold=Modelica.Constants.small) + "Trigger an assertion if the outputs differ" + annotation (Placement(transformation(extent={{40,-10},{60,10}}))); +equation + connect(weeSchLin.y, assEqu.u1) + annotation (Line(points={{11,30},{24,30},{24,6},{38,6}}, color={0,0,127})); + connect(weeSchWin.y, assEqu.u2) annotation (Line(points={{11,-22},{24,-22},{24, + -6},{38,-6}}, color={0,0,127})); + annotation ( + Documentation(info=" +
+Example for a weekly schedule that reads the schedule data from a file. +There are two file readers, one reading a file with Windows line endings and the other with Linux line endings. +
+", +revisions=" ++This package contains models that validate the models that access files. +The examples plot various outputs. These model outputs are stored as reference data to +allow continuous validation whenever models in the library change. +
+")); +end Validation; diff --git a/IBPSA/Utilities/IO/Files/Validation/package.order b/IBPSA/Utilities/IO/Files/Validation/package.order new file mode 100644 index 0000000000..a47e6a26b0 --- /dev/null +++ b/IBPSA/Utilities/IO/Files/Validation/package.order @@ -0,0 +1 @@ +WeeklyScheduleWindowsLineEndings diff --git a/IBPSA/Utilities/IO/Files/WeeklySchedule.mo b/IBPSA/Utilities/IO/Files/WeeklySchedule.mo index caa626a0a0..5c50a78cba 100644 --- a/IBPSA/Utilities/IO/Files/WeeklySchedule.mo +++ b/IBPSA/Utilities/IO/Files/WeeklySchedule.mo @@ -54,6 +54,10 @@ protected revisions="columns
is used to specify which columns of the table
The first column is time, hence for the above example, set columns = {2}
.
-See IBPSA/Resources/Data/schedule.txt +See IBPSA/Resources/Data/schedule.txt for an example of the supported file format.
"), diff --git a/IBPSA/Utilities/IO/Files/package.order b/IBPSA/Utilities/IO/Files/package.order index 6cbc46401f..41dcc44817 100644 --- a/IBPSA/Utilities/IO/Files/package.order +++ b/IBPSA/Utilities/IO/Files/package.order @@ -3,4 +3,5 @@ CombiTimeTableWriter JSONWriter WeeklySchedule Examples +Validation BaseClasses