From f7f44faece5f2b27c4bdd56914fe15b23bdf954c Mon Sep 17 00:00:00 2001 From: Torsten Sommer Date: Tue, 15 Oct 2024 11:04:20 +0200 Subject: [PATCH] Add --set-stop-time option to fmusim (#614) fixes #612 --- fmusim/FMI1CSSimulation.c | 21 +++++++----- fmusim/FMI1MESimulation.c | 4 +++ fmusim/FMI2CSSimulation.c | 65 ++++++++++++++++++++++++++---------- fmusim/FMI2MESimulation.c | 6 +++- fmusim/FMI3CSSimulation.c | 10 +++++- fmusim/FMI3MESimulation.c | 6 +++- fmusim/FMIModelDescription.c | 5 ++- fmusim/FMIModelDescription.h | 1 + fmusim/FMISimulation.h | 1 + fmusim/fmusim.c | 31 ++++++++--------- tests/test_fmusim.py | 17 ++++++++++ 11 files changed, 121 insertions(+), 46 deletions(-) diff --git a/fmusim/FMI1CSSimulation.c b/fmusim/FMI1CSSimulation.c index e170f5ad..a1fa94e6 100644 --- a/fmusim/FMI1CSSimulation.c +++ b/fmusim/FMI1CSSimulation.c @@ -10,6 +10,8 @@ FMIStatus FMI1CSSimulate(const FMISimulationSettings* s) { FMIStatus status = FMIOK; + fmi1Real time = s->startTime; + FMIInstance* S = s->S; char fmuLocation[FMI_PATH_MAX] = ""; @@ -29,20 +31,20 @@ FMIStatus FMI1CSSimulate(const FMISimulationSettings* s) { // set start values CALL(FMIApplyStartValues(S, s)); - CALL(FMIApplyInput(S, s->input, s->startTime, true, true, true)); + CALL(FMIApplyInput(S, s->input, time, true, true, true)); // initialize - CALL(FMI1InitializeSlave(S, s->startTime, fmi1False, 0)); + CALL(FMI1InitializeSlave(S, time, s->setStopTime, s->stopTime)); - CALL(FMISample(S, s->startTime, s->initialRecorder)); + CALL(FMISample(S, time, s->initialRecorder)); for (unsigned long step = 0;; step++) { - const fmi1Real time = s->startTime + step * s->outputInterval; - CALL(FMISample(S, time, s->recorder)); - if (time > s->stopTime || FMIIsClose(time, s->stopTime)) { + const fmi1Real nextCommunicationPoint = s->startTime + (step + 1) * s->outputInterval; + + if (nextCommunicationPoint > s->stopTime && !FMIIsClose(nextCommunicationPoint, s->stopTime)) { break; } @@ -58,9 +60,7 @@ FMIStatus FMI1CSSimulate(const FMISimulationSettings* s) { if (terminated) { - fmi1Real lastSuccessfulTime; - - CALL(FMI1GetRealStatus(S, fmi1LastSuccessfulTime, &lastSuccessfulTime)); + CALL(FMI1GetRealStatus(S, fmi1LastSuccessfulTime, &time)); CALL(FMISample(S, time, s->recorder)); @@ -68,7 +68,10 @@ FMIStatus FMI1CSSimulate(const FMISimulationSettings* s) { } } else { + CALL(doStepStatus); + + time = nextCommunicationPoint; } if (s->stepFinished && !s->stepFinished(s, time)) { diff --git a/fmusim/FMI1MESimulation.c b/fmusim/FMI1MESimulation.c index 4f35eb95..cc744be5 100644 --- a/fmusim/FMI1MESimulation.c +++ b/fmusim/FMI1MESimulation.c @@ -104,6 +104,10 @@ FMIStatus FMI1MESimulate(const FMISimulationSettings* s) { nextCommunicationPoint = eventInfo.nextEventTime; } + if (nextCommunicationPoint > s->stopTime && !FMIIsClose(nextCommunicationPoint, s->stopTime)) { + nextCommunicationPoint = s->stopTime; + } + inputEvent = FMIIsClose(nextCommunicationPoint, nextInputEventTime); timeEvent = eventInfo.upcomingTimeEvent && FMIIsClose(nextCommunicationPoint, eventInfo.nextEventTime); diff --git a/fmusim/FMI2CSSimulation.c b/fmusim/FMI2CSSimulation.c index 55de334e..c9319f54 100644 --- a/fmusim/FMI2CSSimulation.c +++ b/fmusim/FMI2CSSimulation.c @@ -10,6 +10,13 @@ FMIStatus FMI2CSSimulate(const FMISimulationSettings* s) { FMIStatus status = FMIOK; + fmi2Boolean terminateSimulation = fmi2False; + fmi2Real time = s->startTime; + fmi2Real nextRegularPoint = 0.0; + fmi2Real nextCommunicationPoint = 0.0; + fmi2Real nextInputEventTime = 0.0; + fmi2Real stepSize = 0.0; + char resourcePath[FMI_PATH_MAX] = ""; char resourceURI[FMI_PATH_MAX] = ""; @@ -38,46 +45,68 @@ FMIStatus FMI2CSSimulate(const FMISimulationSettings* s) { CALL(FMIApplyStartValues(S, s)); if (!s->initialFMUStateFile) { - CALL(FMI2SetupExperiment(S, s->tolerance > 0, s->tolerance, s->startTime, fmi2False, 0)); + CALL(FMI2SetupExperiment(S, s->tolerance > 0, s->tolerance, time, s->setStopTime, s->stopTime)); CALL(FMI2EnterInitializationMode(S)); CALL(FMIApplyInput(S, s->input, s->startTime, true, true, true)); CALL(FMI2ExitInitializationMode(S)); } - CALL(FMISample(S, s->startTime, s->initialRecorder)); - - for (unsigned long step = 0;; step++) { - - const fmi2Real time = s->startTime + step * s->outputInterval; + CALL(FMISample(S, time, s->initialRecorder)); + CALL(FMISample(S, time, s->recorder)); - CALL(FMISample(S, time, s->recorder)); + size_t nSteps = 0; + for (;;) { + if (time > s->stopTime || FMIIsClose(time, s->stopTime)) { break; } - CALL(FMIApplyInput(S, s->input, time, true, true, true)); + nextRegularPoint = s->startTime + (nSteps + 1) * s->outputInterval; - const FMIStatus doStepStatus = FMI2DoStep(S, time, s->outputInterval, fmi2True); + nextCommunicationPoint = nextRegularPoint; - if (doStepStatus == fmi2Discard) { + nextInputEventTime = FMINextInputEvent(s->input, time); + + if (nextCommunicationPoint > nextInputEventTime && !FMIIsClose(nextCommunicationPoint, nextInputEventTime)) { + nextCommunicationPoint = nextInputEventTime; + } + + if (nextCommunicationPoint > s->stopTime && !FMIIsClose(nextCommunicationPoint, s->stopTime)) { + if (s->modelDescription->coSimulation->canHandleVariableCommunicationStepSize) { + nextCommunicationPoint = s->stopTime; + } else { + break; + } + } - fmi2Boolean terminated; - CALL(FMI2GetBooleanStatus(S, fmi2Terminated, &terminated)); + stepSize = nextCommunicationPoint - time; - if (terminated) { + CALL(FMIApplyInput(S, s->input, time, true, true, true)); - fmi2Real lastSuccessfulTime; + const FMIStatus doStepStatus = FMI2DoStep(S, time, stepSize, fmi2True); - CALL(FMI2GetRealStatus(S, fmi2LastSuccessfulTime, &lastSuccessfulTime)); + if (doStepStatus == fmi2Discard) { - CALL(FMISample(S, lastSuccessfulTime, s->recorder)); + CALL(FMI2GetRealStatus(S, fmi2LastSuccessfulTime, &time)); - break; - } + CALL(FMI2GetBooleanStatus(S, fmi2Terminated, &terminateSimulation)); } else { + CALL(doStepStatus); + + time = nextCommunicationPoint; + } + + if (FMIIsClose(time, nextRegularPoint)) { + nSteps++; + } + + CALL(FMISample(S, time, s->recorder)); + + if (terminateSimulation) { + goto TERMINATE; } if (s->stepFinished && !s->stepFinished(s, time)) { diff --git a/fmusim/FMI2MESimulation.c b/fmusim/FMI2MESimulation.c index 89478e26..b3eeaad0 100644 --- a/fmusim/FMI2MESimulation.c +++ b/fmusim/FMI2MESimulation.c @@ -72,7 +72,7 @@ FMIStatus FMI2MESimulate(const FMISimulationSettings* s) { if (!s->initialFMUStateFile) { - CALL(FMI2SetupExperiment(S, s->tolerance > 0, s->tolerance, time, fmi2False, 0)); + CALL(FMI2SetupExperiment(S, s->tolerance > 0, s->tolerance, time, s->setStopTime, s->stopTime)); CALL(FMI2EnterInitializationMode(S)); CALL(FMIApplyInput(S, s->input, time, true, // discrete @@ -144,6 +144,10 @@ FMIStatus FMI2MESimulate(const FMISimulationSettings* s) { nextCommunicationPoint = eventInfo.nextEventTime; } + if (nextCommunicationPoint > s->stopTime && !FMIIsClose(nextCommunicationPoint, s->stopTime)) { + nextCommunicationPoint = s->stopTime; + } + inputEvent = FMIIsClose(nextCommunicationPoint, nextInputEventTime); timeEvent = eventInfo.nextEventTimeDefined && FMIIsClose(nextCommunicationPoint, eventInfo.nextEventTime); diff --git a/fmusim/FMI3CSSimulation.c b/fmusim/FMI3CSSimulation.c index 676c0ff5..572e6666 100644 --- a/fmusim/FMI3CSSimulation.c +++ b/fmusim/FMI3CSSimulation.c @@ -93,7 +93,7 @@ FMIStatus FMI3CSSimulate(const FMISimulationSettings* s) { if (!s->initialFMUStateFile) { - CALL(FMI3EnterInitializationMode(S, s->tolerance > 0, s->tolerance, s->startTime, fmi3False, 0)); + CALL(FMI3EnterInitializationMode(S, s->tolerance > 0, s->tolerance, s->startTime, s->setStopTime, s->stopTime)); CALL(FMIApplyInput(S, s->input, s->startTime, true, true, true)); @@ -145,6 +145,14 @@ FMIStatus FMI3CSSimulate(const FMISimulationSettings* s) { nextCommunicationPoint = nextInputEventTime; } + if (nextCommunicationPoint > s->stopTime && !FMIIsClose(nextCommunicationPoint, s->stopTime)) { + if (s->modelDescription->coSimulation->canHandleVariableCommunicationStepSize) { + nextCommunicationPoint = s->stopTime; + } else { + break; + } + } + inputEvent = FMIIsClose(nextCommunicationPoint, nextInputEventTime); stepSize = nextCommunicationPoint - time; diff --git a/fmusim/FMI3MESimulation.c b/fmusim/FMI3MESimulation.c index 340b266d..716489eb 100644 --- a/fmusim/FMI3MESimulation.c +++ b/fmusim/FMI3MESimulation.c @@ -68,7 +68,7 @@ FMIStatus FMI3MESimulate(const FMISimulationSettings* s) { if (!s->initialFMUStateFile) { // initialize - CALL(FMI3EnterInitializationMode(S, s->tolerance > 0, s->tolerance, time, fmi3False, 0)); + CALL(FMI3EnterInitializationMode(S, s->tolerance > 0, s->tolerance, time, s->setStopTime, s->stopTime)); CALL(FMIApplyInput(S, s->input, time, true, // discrete @@ -152,6 +152,10 @@ FMIStatus FMI3MESimulate(const FMISimulationSettings* s) { nextCommunicationPoint = nextEventTime; } + if (nextCommunicationPoint > s->stopTime && !FMIIsClose(nextCommunicationPoint, s->stopTime)) { + nextCommunicationPoint = s->stopTime; + } + inputEvent = FMIIsClose(nextCommunicationPoint, nextInputEventTime); timeEvent = nextEventTimeDefined && FMIIsClose(nextCommunicationPoint, nextEventTime); diff --git a/fmusim/FMIModelDescription.c b/fmusim/FMIModelDescription.c index 61ae3e1e..03fb47c5 100644 --- a/fmusim/FMIModelDescription.c +++ b/fmusim/FMIModelDescription.c @@ -455,7 +455,9 @@ static FMIModelDescription* readModelDescriptionFMI2(xmlNodePtr root) { xpathObj = xmlXPathEvalExpression((xmlChar*)"/fmiModelDescription/CoSimulation", xpathCtx); if (xpathObj->nodesetval->nodeNr == 1) { CALL(FMICalloc((void**)&modelDescription->coSimulation, 1, sizeof(FMICoSimulationInterface))); - modelDescription->coSimulation->modelIdentifier = getStringAttribute(xpathObj->nodesetval->nodeTab[0], "modelIdentifier"); + const xmlNodePtr node = xpathObj->nodesetval->nodeTab[0]; + modelDescription->coSimulation->modelIdentifier = getStringAttribute(node, "modelIdentifier"); + modelDescription->coSimulation->canHandleVariableCommunicationStepSize = getBooleanAttribute(node, "canHandleVariableCommunicationStepSize"); CALL(readSourceFiles(xpathCtx, "/fmiModelDescription/CoSimulation/SourceFiles/File", &modelDescription->coSimulation->nSourceFiles, &modelDescription->coSimulation->sourceFiles)); } xmlXPathFreeObject(xpathObj); @@ -764,6 +766,7 @@ static FMIModelDescription* readModelDescriptionFMI3(xmlNodePtr root) { CALL(FMICalloc((void**)&modelDescription->coSimulation, 1, sizeof(FMICoSimulationInterface))); const xmlNodePtr node = xpathObj->nodesetval->nodeTab[0]; modelDescription->coSimulation->modelIdentifier = getStringAttribute(node, "modelIdentifier"); + modelDescription->coSimulation->canHandleVariableCommunicationStepSize = getBooleanAttribute(node, "canHandleVariableCommunicationStepSize"); modelDescription->coSimulation->hasEventMode = getBooleanAttribute(node, "hasEventMode"); } xmlXPathFreeObject(xpathObj); diff --git a/fmusim/FMIModelDescription.h b/fmusim/FMIModelDescription.h index 59a7b37a..4d8f408e 100644 --- a/fmusim/FMIModelDescription.h +++ b/fmusim/FMIModelDescription.h @@ -139,6 +139,7 @@ typedef struct { typedef struct { const char* modelIdentifier; + bool canHandleVariableCommunicationStepSize; bool hasEventMode; size_t nSourceFiles; const char** sourceFiles; diff --git a/fmusim/FMISimulation.h b/fmusim/FMISimulation.h index 99181f4a..50c9a3fe 100644 --- a/fmusim/FMISimulation.h +++ b/fmusim/FMISimulation.h @@ -30,6 +30,7 @@ typedef struct FMISimulationSettings { const char* finalFMUStateFile; bool visible; bool loggingOn; + bool setStopTime; // Co-Simulation bool earlyReturnAllowed; diff --git a/fmusim/fmusim.c b/fmusim/fmusim.c index 294602e1..57316483 100644 --- a/fmusim/fmusim.c +++ b/fmusim/fmusim.c @@ -112,6 +112,7 @@ void printUsage() { " --tolerance [TOLERANCE] relative tolerance\n" " --start-time [VALUE] start time\n" " --stop-time [VALUE] stop time\n" + " --set-stop-time set stop time explicitly\n" " --output-interval [VALUE] set the output interval\n" " --start-value [name] [value] set a start value\n" " --output-variable [name] record a specific variable\n" @@ -151,6 +152,7 @@ int main(int argc, const char* argv[]) { const char* startTimeLiteral = NULL; const char* stopTimeLiteral = NULL; + bool setStopTime = false; double outputInterval = 0; @@ -251,6 +253,8 @@ int main(int argc, const char* argv[]) { startTimeLiteral = argv[++i]; } else if (!strcmp(v, "--stop-time")) { stopTimeLiteral = argv[++i]; + } else if (!strcmp(v, "--set-stop-time")) { + setStopTime = true; } else if (!strcmp(v, "--output-interval")) { char* error; outputInterval = strtod(argv[++i], &error); @@ -442,26 +446,22 @@ int main(int argc, const char* argv[]) { } } - if (!startTimeLiteral) { - if (modelDescription->defaultExperiment && modelDescription->defaultExperiment->startTime) { - startTimeLiteral = modelDescription->defaultExperiment->startTime; - } else { - startTimeLiteral = "0"; - } + double startTime = 0; + + if (startTimeLiteral) { + startTime = strtod(startTimeLiteral, NULL); + } else if (modelDescription->defaultExperiment && modelDescription->defaultExperiment->startTime) { + startTime = strtod(modelDescription->defaultExperiment->startTime, NULL); } - const double startTime = strtod(startTimeLiteral, NULL); + double stopTime = startTime + 1; - if (!stopTimeLiteral) { - if (modelDescription->defaultExperiment && modelDescription->defaultExperiment->stopTime) { - stopTimeLiteral = modelDescription->defaultExperiment->stopTime; - } else { - stopTimeLiteral = "1"; - } + if (stopTimeLiteral) { + stopTime = strtod(stopTimeLiteral, NULL); + } else if (modelDescription->defaultExperiment && modelDescription->defaultExperiment->stopTime) { + stopTime = strtod(modelDescription->defaultExperiment->stopTime, NULL); } - const double stopTime = strtod(stopTimeLiteral, NULL); - if (outputInterval == 0) { if (modelDescription->defaultExperiment && modelDescription->defaultExperiment->stepSize) { outputInterval = strtod(modelDescription->defaultExperiment->stepSize, NULL); @@ -480,6 +480,7 @@ int main(int argc, const char* argv[]) { settings.startTime = startTime; settings.outputInterval = outputInterval; settings.stopTime = stopTime; + settings.setStopTime = setStopTime; settings.earlyReturnAllowed = earlyReturnAllowed; settings.eventModeUsed = eventModeUsed; settings.recordIntermediateValues = recordIntermediateValues; diff --git a/tests/test_fmusim.py b/tests/test_fmusim.py index ba50373e..a4778936 100644 --- a/tests/test_fmusim.py +++ b/tests/test_fmusim.py @@ -63,6 +63,23 @@ def test_stop_time(fmi_version, interface_type, arch, platform): assert result['time'][-1] == pytest.approx(1.5) +@pytest.mark.parametrize('fmi_version, interface_type', product([1, 2, 3], ['cs', 'me'])) +def test_set_stop_time(fmi_version, interface_type, arch, platform): + + if fmi_version in {1, 2} and arch not in {'x86', 'x86_64'}: + pytest.skip(f"FMI version {fmi_version} is not supported on {arch}.") + + result = call_fmusim(platform, fmi_version, interface_type, 'test_set_stop_time', + ['--stop-time', '3', '--set-stop-time', '--output-interval', '0.7']) + + last = result['time'][-1] + + if fmi_version == 1 and interface_type == 'cs': + assert last == pytest.approx(2.8) + else: + assert last == pytest.approx(3.0) + + @pytest.mark.parametrize('fmi_version, interface_type', product([1, 2, 3], ['cs', 'me'])) def test_start_value_types(fmi_version, interface_type, arch, platform):