Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ cmake_minimum_required(VERSION 3.14)
project(OMSimulator)

# Options to enable/disable specific components
option(OMS_ENABLE_OMSimulator "Enable OMSimulator component" ON)
option(OMS_ENABLE_OMSimulatorGui "Enable OMSimulator GUI component" OFF)
option(OMS_ENABLE_PIP "Enable pip component" OFF)
option(OMS_ENABLE_LOCAL_PIP "Prepare pip package using local OMSimulator build" OFF)
Expand Down Expand Up @@ -78,10 +77,6 @@ add_subdirectory(src/OMSimulatorServer)
add_subdirectory(doc)
add_subdirectory(schema)

if(OMS_ENABLE_OMSimulator)
add_subdirectory(src/OMSimulator)
endif()

if(OMS_ENABLE_OMSimulatorGui)
add_subdirectory(src/OMSimulatorGui)
endif()
Expand Down
2 changes: 1 addition & 1 deletion Jenkinsfile
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@ IF NOT ["%ERRORLEVEL%"]==["0"] GOTO fail
cmake --build build/ --config Release --parallel %NUMBER_OF_PROCESSORS% --target install -v
IF NOT ["%ERRORLEVEL%"]==["0"] GOTO fail

call install\\bin\\OMSimulator.exe --version
call install\\bin\\OMSimulator.bat --version
IF NOT ["%ERRORLEVEL%"]==["0"] GOTO fail

C:\\OMDevUCRT\\tools\\msys\\usr\\bin\\sh --login -i '${env.WORKSPACE}/buildZip.sh'
Expand Down
19 changes: 15 additions & 4 deletions doc/UsersGuide/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,24 @@
# if it is changed to link to a shared OMSimulatorLib, then it will not run from the build tree
# because it will nto find the shared lib. In that case we need to first install the project and then
# use the installed OMSimulator here.
add_custom_target(doc-generate-cmd-help-file
COMMAND $<TARGET_FILE:OMSimulator> --help > ${CMAKE_CURRENT_SOURCE_DIR}/source/omsimulator-help.inc

BYPRODUCTS ${CMAKE_CURRENT_SOURCE_DIR}/source/omsimulator-help.inc
COMMENT "Generating omsimulator-help.inc.")
if(WIN32)
set(OMSIMULATOR_COMMAND
"${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}/OMSimulator.bat")
else()
set(OMSIMULATOR_COMMAND
"${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}/OMSimulator")
endif()

add_custom_target(doc-generate-cmd-help-file
COMMAND ${OMSIMULATOR_COMMAND} --help
> "${CMAKE_CURRENT_SOURCE_DIR}/source/omsimulator-help.inc"

BYPRODUCTS
"${CMAKE_CURRENT_SOURCE_DIR}/source/omsimulator-help.inc"

COMMENT "Generating omsimulator-help.inc."
)

####################
## PDF
Expand Down
7 changes: 0 additions & 7 deletions src/OMSimulator/CMakeLists.txt

This file was deleted.

53 changes: 0 additions & 53 deletions src/OMSimulator/main.cpp

This file was deleted.

8 changes: 4 additions & 4 deletions src/OMSimulatorPython/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ endif()

configure_file("${CMAKE_CURRENT_SOURCE_DIR}/__init__.py" "${CMAKE_CURRENT_BINARY_DIR}/__init__.py" @ONLY)
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/capi.py" "${CMAKE_CURRENT_BINARY_DIR}/capi.py" @ONLY)
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/OMSimulatorPython3.in" "${CMAKE_CURRENT_BINARY_DIR}/OMSimulatorPython3" @ONLY)
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/OMSimulator.in" "${CMAKE_CURRENT_BINARY_DIR}/OMSimulator" @ONLY)

install(FILES "${CMAKE_CURRENT_BINARY_DIR}/__init__.py"
"${CMAKE_CURRENT_SOURCE_DIR}/__main__.py"
Expand Down Expand Up @@ -53,14 +53,14 @@ install(FILES "${CMAKE_CURRENT_BINARY_DIR}/__init__.py"
DESTINATION ${CMAKE_INSTALL_LIBDIR}/OMSimulator)

if(MSVC)
install(FILES OMSimulatorPython3.bat TYPE BIN
install(FILES OMSimulator.bat TYPE BIN
PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ)
elseif(MINGW)
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/OMSimulatorPython3" OMSimulatorPython3.bat
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/OMSimulator" OMSimulator.bat
TYPE BIN
PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ)
else()
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/OMSimulatorPython3"
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/OMSimulator"
TYPE BIN
PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ)
endif()
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ set PATH=%~dp0;%~dp0\..\bin;%PATH%
rem A ".py" argument is run as a driver script, as before. Anything else
rem (e.g. a .ssp or .fmu model file) is handed to the OMSimulator CLI module.
if /I "%~x1"==".py" (
python3 %*
python %*
) else (
python3 -m OMSimulator %*
python -m OMSimulator %*
)
128 changes: 109 additions & 19 deletions src/OMSimulatorPython/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,10 @@ def _runSSP(path: Path, args: argparse.Namespace) -> None:
ssp = SSP(str(path))
model = ssp.instantiate()

if args.stop_time is not None:
model.setStopTime(args.stop_time)
if args.result_file is not None:
model.setResultFile(args.result_file)
if args.stopTime is not None:
model.setStopTime(args.stopTime)
if args.resultFile is not None:
model.setResultFile(args.resultFile)

model.initialize()
model.simulate()
Expand All @@ -106,15 +106,17 @@ def _runFMU(path: Path, args: argparse.Namespace) -> None:
# start/stop/tolerance/stepSize must be set before instantiate(): they need to reach
# the FMU's fmi2SetupExperiment call, not just the solver's own bookkeeping, otherwise
# the FMU's first recorded sample won't reflect the override (only later steps would).
fmu.startTime = args.start_time
fmu.stopTime = args.stop_time
fmu.startTime = args.startTime
fmu.stopTime = args.stopTime
fmu.tolerance = args.tolerance
fmu.stepSize = args.step_size
fmu.stepSize = args.stepSize
fmu.instantiate() # applies the settings above (falling back to the FMU's DefaultExperiment)

if args.solver is not None:
if args.solver is not None and args.mode == 'me':
fmu.setSolver(args.solver)
fmu.setResultFile(args.result_file or f"{path.stem}_res.mat")
elif args.mode == 'cs':
fmu.setSolver('ma')
fmu.setResultFile(args.resultFile or f"{path.stem}_res.mat")

exp = fmu.appliedExperiment
kind_str = _FMU_KIND_STR.get(fmu.mode, fmu.mode)
Expand All @@ -138,26 +140,101 @@ def _runFMU(path: Path, args: argparse.Namespace) -> None:
'.fmu': _runFMU,
}


def _has_option(argv, option):
return any(
arg == option or arg.startswith(option + '=')
for arg in argv
)

# Boolean CLI flags that map 1:1 to a global native command line option (applies
# process-wide, regardless of .fmu/.ssp). args attribute name -> native option string.
_GLOBAL_FLAGS = {
'stripRoot': '--stripRoot=true',
'skipCSVHeader': '--skipCSVHeader=true',
}

_GLOBAL_OPTIONS = {
'addParametersToCSV': '--addParametersToCSV',
'algLoopSolver': '--algLoopSolver',
'clearAllOptions': '--clearAllOptions',
'CVODEMaxErrTestFails': '--CVODEMaxErrTestFails',
'CVODEMaxNLSFailures': '--CVODEMaxNLSFailures',
'CVODEMaxNLSIterations': '--CVODEMaxNLSIterations',
'CVODEMaxSteps': '--CVODEMaxSteps',
'deleteTempFiles': '--deleteTempFiles',
'directionalDerivatives': '--directionalDerivatives',
'dumpAlgLoops': '--dumpAlgLoops',
'emitEvents': '--emitEvents',
'ignoreInitialUnknowns': '--ignoreInitialUnknowns',
'initialStepSize': '--initialStepSize',
'inputExtrapolation': '--inputExtrapolation',
'intervals': '--intervals',
'logFile': '--logFile',
'logLevel': '--logLevel',
'master': '--master',
'maxEventIteration': '--maxEventIteration',
'maxLoopIteration': '--maxLoopIteration',
'minimumStepSize': '--minimumStepSize',
'numProcs': '--numProcs',
'progressBar': '--progressBar',
'realTime': '--realTime',
'solverStats': '--solverStats',
'suppressPath': '--suppressPath',
'tempDir': '--tempDir',
'timeout': '--timeout',
'wallTime': '--wallTime',
'workingDir': '--workingDir',
'zeroNominal': '--zeroNominal',
}
def main(argv=None) -> int:
parser = argparse.ArgumentParser(prog='OMSimulatorPython3', description=__doc__)
if argv is None:
argv = sys.argv[1:]

parser = argparse.ArgumentParser(prog='OMSimulatorPython3', description=__doc__, formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument('model', type=Path, help='Path to a .ssp or .fmu file to simulate')
parser.add_argument('--result-file', help='Override the result file name')
parser.add_argument('--start-time', type=float, help='Override the simulation start time')
parser.add_argument('--stop-time', type=float, help='Override the simulation stop time')
parser.add_argument('--tolerance', type=float, help='Override the solver tolerance (.fmu only)')
parser.add_argument('--step-size', type=float, help='Override the (maximum) simulation step size (.fmu only)')
parser.add_argument('--mode', choices=['cs', 'me'], help="Force 'cs' (co-simulation) or 'me' (model exchange) for FMUs that export both " "kinds (.fmu only)")
parser.add_argument('--solver', choices=['euler', 'cvode'], help='Set the ODE solver for model-exchange FMUs (.fmu, mode=me only)')
parser.add_argument('--stripRoot', action='store_true', help='Remove the root system prefix from exported signal names')
parser.add_argument('--skipCSVHeader', action='store_true', help='Skip the CSV delimiter row in the header of .csv result files (already the default)')
parser.add_argument('--resultFile', default='default_res.mat', help='Override the result file name')
parser.add_argument('--startTime', type=float, default=0.0, help='Override the simulation start time')
parser.add_argument('--stopTime', type=float, default=1.0, help='Override the simulation stop time')
parser.add_argument('--tolerance', type=float, default=1e-4, help='Override the solver tolerance (.fmu only)')
parser.add_argument('--stepSize', type=float, default=1e-3, help='Override the (maximum) simulation step size (.fmu only)')
parser.add_argument('--mode', choices=['cs', 'me'], default='me', help="Force 'cs' (co-simulation) or 'me' (model exchange) for FMUs that export both " "kinds (.fmu only)")
parser.add_argument('--solver', choices=['euler', 'cvode'], default='cvode', help='Set the ODE solver for model-exchange FMUs (.fmu, mode=me only)')
parser.add_argument('--stripRoot', action='store_true', default=True, help='Remove the root system prefix from exported signal names')
parser.add_argument('--skipCSVHeader', action='store_true', default=True, help='Skip the CSV delimiter row in the header of .csv result files (already the default)')
parser.add_argument('--validate', action='store_true', help='Only validate the file against its schema; do not simulate')
parser.add_argument('--addParametersToCSV', action='store_true', help='Export parameters to a .csv file')
parser.add_argument('--algLoopSolver', choices=['fixedpoint', 'kinsol'], default='kinsol', help='Specifies the loop solver method (fixedpoint, kinsol) used for algebraic loops spanning multiple components')
parser.add_argument('--clearAllOptions', action='store_true', help='Reset all flags to their default values')
parser.add_argument('--CVODEMaxErrTestFails', type=int, default=100, help='Maximum number of error test failures for CVODE')
parser.add_argument('--CVODEMaxNLSFailures', type=int, default=100, help='Maximum number of nonlinear convergence failures for CVODE')
parser.add_argument('--CVODEMaxNLSIterations', type=int, default=5, help='Maximum number of nonlinear solver iterations for CVODE')
parser.add_argument('--CVODEMaxSteps', type=int, default=1000, help='Maximum number of steps for CVODE')
parser.add_argument('--deleteTempFiles', action=argparse.BooleanOptionalAction, default=True, help='Delete temporary files as soon as they are no longer needed')
parser.add_argument('--directionalDerivatives', action=argparse.BooleanOptionalAction, default=True, help='Use directional derivatives to calculate the Jacobian for algebraic loops')
parser.add_argument('--dumpAlgLoops', action='store_true', help='Dump information for algebraic loops')
parser.add_argument('--emitEvents', action=argparse.BooleanOptionalAction, default=True, help='Emit events during simulation')
parser.add_argument('--ignoreInitialUnknowns', action='store_true', help='Ignore initial unknowns from the modelDescription.xml')
parser.add_argument('--initialStepSize', type=float, default=1e-6, help='Specify the initial step size')
parser.add_argument('--inputExtrapolation', action='store_true', help='Enable input extrapolation using derivative information')
parser.add_argument('--intervals', type=int, default=500, help='Specify the number of communication points (arg > 1)')
parser.add_argument('--logFile', help='Specify the log file (stdout is used if no log file is specified)')
parser.add_argument('--logLevel', type=int, default=0, help='Set the log level (0: default, 1: debug, 2: debug+trace)')
parser.add_argument('--master', default='ma', help='Specify the master algorithm (ma)')
parser.add_argument('--maxEventIteration', type=int, default=100, help='Specify the maximum number of iterations for handling a single event')
parser.add_argument('--maxLoopIteration', type=int, default=10, help='Specify the maximum number of iterations for solving algebraic loops between system-level components. Internal algebraic loops of components are not affected.')
parser.add_argument('--minimumStepSize', type=float, default=1e-12, help='Specify the minimum step size')
parser.add_argument('--numProcs', type=int, default=1, help='Specify the maximum number of processors to use (0=auto, 1=default)')
parser.add_argument('--progressBar', action='store_true', help='Show a progress bar for the simulation progress in the terminal')
parser.add_argument('--realTime', action='store_true', help='Enable experimental feature for (soft) real-time co-simulation')
parser.add_argument('--solverStats', action='store_true', help='Add solver stats to the result file, e.g., step size; not supported for all solvers')
parser.add_argument('--suppressPath', action='store_true', help='Suppress path information in info messages; especially useful for testing')
parser.add_argument('--tempDir', default='.', help='Specify the temporary directory')
parser.add_argument('--timeout', type=int, default=0, help='Specify the maximum allowed time in seconds for running a simulation (0 disables)')
parser.add_argument('--wallTime', action='store_true',help='Add wall time information to the result file')
parser.add_argument('--workingDir', default='.', help='Specify the working directory')
parser.add_argument('--zeroNominal', action='store_true', help='Accept FMUs with invalid nominal values and replace the invalid nominal values with 1.0')
parser.add_argument('--version', action='version', version=f'{Capi.getVersion()}')
args = parser.parse_args(argv)

handler = _HANDLERS.get(args.model.suffix.lower())
Expand All @@ -175,6 +252,19 @@ def main(argv=None) -> int:
if status != Status.ok:
raise RuntimeError(f"Failed to set {option}: {status}")

for attr, option in _GLOBAL_OPTIONS.items():
if _has_option(argv, option):
value = getattr(args, attr)
if value == "":
value = "''" # empty string needs to be quoted for the native command line parser

if isinstance(value, bool):
value = str(value).lower()

status = Capi.setCommandLineOption(f'{option}={value}')
if status != Status.ok:
raise RuntimeError(f"Failed to set {option}: {status}")

handler(args.model, args)
return 0

Expand Down
4 changes: 1 addition & 3 deletions src/OMSimulatorPython/fmu.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
_FMU_SOLVER = {
'euler': 2,
'cvode': 3,
'ma':6
}

class FMU:
Expand Down Expand Up @@ -631,9 +632,6 @@ def setSolver(self, method: str):
raise RuntimeError("FMU must be instantiated before setting the solver")
if method not in _FMU_SOLVER:
raise ValueError(f"Invalid solver '{method}': expected one of {sorted(_FMU_SOLVER)}")
if self.mode != 'me':
raise ValueError(f"Cannot set solver '{method}': '{self.instanceName}' is not a model-exchange FMU "
f"({_FMU_KIND_STR.get(self.mode, self.mode)})")
status = Capi.setSolver(f"{self.instanceName}.root", _FMU_SOLVER[method])
if status != Status.ok:
raise RuntimeError(f"Failed to set solver: {status}")
Expand Down
9 changes: 3 additions & 6 deletions testsuite/runtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ def absolute(path: Path) -> Path:
private work directory of a test never does -- that proof depends on the two
lookups failing with the same error code, so the prefix survives every now and
then. A `\\\\?\\` directory then breaks the test: every .py test is started
through OMSimulatorPython3.bat, i.e. through cmd.exe, which rejects such a
through OMSimulator.bat, i.e. through cmd.exe, which rejects such a
working directory ("UNC paths are not supported"), falls back to C:\\Windows
and runs the test where none of its files are.
"""
Expand Down Expand Up @@ -117,10 +117,7 @@ def simulator_executable(test_file: Path, bin_dir: Path) -> Path:
# (subprocess uses CreateProcess, not a POSIX exec), which cannot run the
# shebang-based shell script directly, so both need the .bat/.exe wrapper.
windows = PLATFORM in ("win", "ucrt64")
if test_file.suffix == ".py":
name = "OMSimulatorPython3.bat" if windows else "OMSimulatorPython3"
else:
name = "OMSimulator.exe" if windows else "OMSimulator"
name = "OMSimulator.bat" if windows else "OMSimulator"
return bin_dir / name


Expand Down Expand Up @@ -342,7 +339,7 @@ def main() -> int:
parser.add_argument("--work-dir", required=True, type=Path,
help="private working directory for this test; it is wiped on every run")
parser.add_argument("--bin-dir", required=True, type=Path,
help="directory holding OMSimulator / OMSimulatorPython3")
help="directory holding OMSimulator / OMSimulator")
parser.add_argument("--omc-diff", required=True, type=Path,
help="the omc-diff executable used to compare results")
parser.add_argument("--epsilon", default=DEFAULT_EPSILON,
Expand Down