Skip to content

Commit 89eb488

Browse files
committed
Use command line instead of OMPython since OMCSessionZMQ hangs at times.
1 parent 6e0ed4d commit 89eb488

3 files changed

Lines changed: 177 additions & 122 deletions

File tree

pyproject.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ priority = "primary"
1111

1212
[tool.poetry.dependencies]
1313
python = ">=3.11,<3.13"
14-
ompython = "^3.4.0"
1514
numpy = "^1.22.4"
1615
pandas = "^1.5.2"
1716
scipy = "^1.9.0"

wedoco_optimo/helpers.py

Lines changed: 0 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,76 +1,6 @@
11
import pandas as pd
22
import os
33
import numpy as np
4-
import shutil
5-
import numpy as np
6-
7-
def load_modelica_files(omc, modelica_files=[]):
8-
"""
9-
Load required Modelica files and packages.
10-
11-
:param mo_files: The list of paths to the involved Modelica models and libraries.
12-
13-
"""
14-
15-
16-
# Load needed model files and libraries.
17-
for f in modelica_files:
18-
print('Loading {} ...'.format(f))
19-
if not os.path.exists(f):
20-
f = find_file_in_modelicapath(f)
21-
file_loaded = omc.loadFile(f)
22-
if file_loaded.startswith('false'):
23-
raise Exception('Modelica compilation failed: {}'.format(omc.sendExpression('getErrorString()')))
24-
# omc.sendExpression('loadFile(\"{}\")'.format(f))
25-
26-
print('List of defined Modelica class names: {}'.format(omc.sendExpression("getClassNames()")))
27-
28-
def find_file_in_modelicapath(filename):
29-
"""
30-
Find a file in the MODELICAPATH environment variable.
31-
32-
"""
33-
modelicapath = os.environ.get('MODELICAPATH', '')
34-
directories = modelicapath.split(os.pathsep)
35-
36-
for directory in directories:
37-
for root, _, files in os.walk(directory):
38-
if filename in files:
39-
return os.path.abspath(os.path.join(root, filename))
40-
41-
return None
42-
43-
def build_model_fmu(omc, mo_class, commandLineOptions=None, enable_directional_derivatives=True):
44-
"""
45-
Compile an FMU from a Modelica model.
46-
47-
:param mo_class: The Modelica class name to be compiled.
48-
:return: Full path of the generated FMU.
49-
50-
"""
51-
52-
## set commandLineOptions if provided by users
53-
if commandLineOptions is not None:
54-
exp = "".join(["setCommandLineOptions(", "\"", commandLineOptions, "\"", ")"])
55-
print(exp)
56-
# self.omc.sendExpression('setCommandLineOptions(\"+g=Modelica\")')
57-
print(omc.sendExpression(exp))
58-
59-
# Translate model to FMU.
60-
fmu_version = 2.0
61-
# See the following link for options: https://github.com/casadi/fmu_compiler/blob/cross/generate_fmu.py
62-
# The following actually enables directional derivatives
63-
if enable_directional_derivatives:
64-
omc.sendExpression('setDebugFlags("-disableDirectionalDerivatives")')
65-
# Compile the FMU
66-
fmu_path = omc.sendExpression('buildModelFMU({0}, version=\"{1}\")'.format(mo_class, fmu_version))
67-
flag = omc.sendExpression('getErrorString()')
68-
if not fmu_path.endswith('.fmu'): raise Exception(f'FMU generation failed: {flag}')
69-
print(f"translateModelFMU warnings:\n{flag}")
70-
71-
# fmu_path = self.omc.sendExpression('buildModelFMU({0}, version=\"{1}\", fmuType=\"cs\")'.format(mo_class, self.fmu_version))
72-
73-
return fmu_path
744

755
def explore_dae(dae):
766
print(f"\nParameters and initial guesses ({dae.np()}):")

wedoco_optimo/model.py

Lines changed: 177 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,76 +1,202 @@
11
import casadi as ca
22
import os
3-
from OMPython import OMCSessionZMQ
43
from pathlib import Path
5-
import casadi as ca
64
import rockit
7-
import os
85
import numpy as np
6+
import glob
7+
import shutil
8+
import subprocess
9+
import tempfile
10+
import time
911

10-
from wedoco_optimo.helpers import load_modelica_files, build_model_fmu, explore_dae, get_dae_results
12+
from wedoco_optimo.helpers import explore_dae, get_dae_results
1113

1214
class OptimoModel:
1315

1416
def check_model(self, model: str, modelica_files=list[str], pedantic: bool=False):
15-
omc = OMCSessionZMQ()
16-
# Change working directory to the specified path
17-
cwd_result = omc.sendExpression(f'cd("{os.getcwd()}")')
18-
print(f"Working directory: {cwd_result}")
17+
"""
18+
Check a Modelica model using command-line omc (avoids ZMQ hanging issues).
1919
20-
# Enable pedantic checking if requested
21-
if pedantic:
22-
print("Enabling strict checking...")
23-
# Enable various checking flags
24-
omc.sendExpression('setCommandLineOptions("--showErrorMessages")')
25-
omc.sendExpression('setCommandLineOptions("+d=initialization")')
20+
Args:
21+
model: The Modelica class name to check
22+
modelica_files: List of Modelica files to load
23+
pedantic: Enable strict checking
2624
27-
# Load Modelica files from the specified path. Ensure right format.
25+
Returns:
26+
Dictionary with check_result and errors
27+
"""
28+
# Ensure modelica files are in the right format
2829
modelica_files = [str(Path(path)) for path in modelica_files]
29-
load_modelica_files(omc, modelica_files=modelica_files)
30-
31-
# Check the model
32-
print(f"\nChecking model: {model}")
33-
check_result = omc.sendExpression(f'checkModel({model})')
34-
35-
# Get error messages
36-
error_string = omc.sendExpression('getErrorString()')
37-
38-
print("\n" + "="*80)
39-
print(f"Model Check Results for: {model}")
40-
print("="*80)
41-
print("\nCheck Result:")
42-
print(check_result)
43-
44-
if error_string and error_string.strip() not in ['""', '']:
45-
print("\nErrors/Warnings:")
46-
print(error_string)
4730

48-
print("="*80 + "\n")
31+
# Create .mos script for model checking
32+
mos_script = tempfile.NamedTemporaryFile(mode='w', suffix='.mos', delete=False, dir=os.getcwd())
4933

50-
return {
51-
"check_result": check_result,
52-
"errors": error_string
53-
}
54-
print("\n" + "="*80)
55-
print(f"Model Check Results for: {model}")
56-
print("="*80)
57-
print(check_result)
58-
print("="*80 + "\n")
59-
return check_result
34+
try:
35+
# Write checking script
36+
mos_script.write(f'cd("{os.getcwd()}");\n')
37+
mos_script.write(f'print("Working directory: " + cd() + "\\n");\n')
38+
39+
# Load Modelica packages
40+
for mf in modelica_files:
41+
mos_script.write(f'print("Loading {os.path.basename(mf)}...\\n");\n')
42+
mos_script.write(f'loadFile("{mf}");\n')
43+
44+
mos_script.write('print("Loaded: " + String(getClassNames()) + "\\n");\n')
45+
46+
# Enable pedantic checking if requested
47+
if pedantic:
48+
mos_script.write('setCommandLineOptions("--showErrorMessages");\n')
49+
mos_script.write('setCommandLineOptions("+d=initialization");\n')
50+
51+
# Check the model
52+
mos_script.write(f'print("\\nChecking model: {model}\\n");\n')
53+
mos_script.write(f'check_result := checkModel({model});\n')
54+
mos_script.write('print("\\n" + "=" * 80 + "\\n");\n')
55+
mos_script.write('print("Check Result:\\n");\n')
56+
mos_script.write('print(check_result + "\\n");\n')
57+
mos_script.write('print("\\nMessages:\\n");\n')
58+
mos_script.write('print(getErrorString() + "\\n");\n')
59+
mos_script.write('print("=" * 80 + "\\n");\n')
60+
mos_script.close()
61+
62+
print(f"🔍 Checking model: {model}")
63+
64+
# Run omc without timeout (model checking is usually fast)
65+
result = subprocess.run(
66+
['omc', mos_script.name],
67+
cwd=os.getcwd(),
68+
capture_output=True,
69+
text=True,
70+
timeout=None # No timeout for checking
71+
)
72+
73+
# Parse and display output
74+
check_result = ""
75+
error_string = ""
76+
in_check_result = False
77+
in_messages = False
78+
79+
for line in result.stdout.split('\n'):
80+
if line.strip():
81+
print(f" {line}")
82+
83+
if "Check Result:" in line:
84+
in_check_result = True
85+
in_messages = False
86+
elif "Messages:" in line:
87+
in_check_result = False
88+
in_messages = True
89+
elif in_check_result and not line.startswith("="):
90+
check_result += line + "\n"
91+
elif in_messages and not line.startswith("="):
92+
error_string += line + "\n"
93+
94+
return {
95+
"check_result": check_result.strip(),
96+
"errors": error_string.strip()
97+
}
98+
99+
except subprocess.TimeoutExpired:
100+
raise Exception(f'Model check timed out')
101+
finally:
102+
try:
103+
os.unlink(mos_script.name)
104+
except:
105+
pass
60106

61107
def transfer_model(self, model: str, modelica_files=list[str], force_recompile: bool=True, enable_directional_derivatives: bool=True) -> str:
108+
"""
109+
Compile and transfer a Modelica model to FMU using command-line omc.
110+
Avoids OMCSessionZMQ which has hanging issues with buildModelFMU.
111+
112+
Args:
113+
model: The Modelica class name to compile
114+
modelica_files: List of Modelica files to load
115+
force_recompile: Force recompilation even if FMU exists
116+
enable_directional_derivatives: Enable directional derivatives in FMU
117+
118+
Returns:
119+
Path to the compiled FMU file
120+
"""
62121
model_name = model.split(".")[-1]
63122
fmu_file_path = str(Path(os.path.join(os.getcwd(), f"{model_name}.fmu")))
123+
64124
# Compile the FMU if needed
65125
if not os.path.exists(fmu_file_path) or force_recompile:
66-
omc = OMCSessionZMQ()
67-
# Change working directory to the specified path
68-
omc.sendExpression(f'cd("{os.getcwd()}")')
69-
# Load Modelica files from the specified path. Ensure right format.
126+
# CRITICAL: Clean up stale .fmutmp directories that cause buildModelFMU to hang
127+
print("\n🧹 Cleaning up stale build directories...")
128+
for stale_dir in glob.glob("*.fmutmp"):
129+
try:
130+
shutil.rmtree(stale_dir)
131+
print(f" Removed: {stale_dir}")
132+
except Exception as e:
133+
raise Exception(f"Cannot remove {stale_dir}: {e}. Please remove manually.")
134+
135+
# Ensure modelica files are in the right format
70136
modelica_files = [str(Path(path)) for path in modelica_files]
71-
load_modelica_files(omc, modelica_files=modelica_files)
72-
# Build FMU in the specified path
73-
build_model_fmu(omc, model, enable_directional_derivatives=enable_directional_derivatives)
137+
138+
# Build FMU using command-line omc (ZMQ interface hangs with buildModelFMU)
139+
mos_script = tempfile.NamedTemporaryFile(mode='w', suffix='.mos', delete=False, dir=os.getcwd())
140+
try:
141+
# Write compilation script
142+
mos_script.write(f'cd("{os.getcwd()}");\n')
143+
144+
# Load Modelica packages
145+
for mf in modelica_files:
146+
mos_script.write(f'print("Loading {os.path.basename(mf)}...\\n");\n')
147+
mos_script.write(f'loadFile("{mf}");\n')
148+
149+
mos_script.write('print("Loaded: " + String(getClassNames()) + "\\n");\n')
150+
mos_script.write('setCommandLineOptions("--fmuCMakeBuild=false");\n')
151+
152+
if not enable_directional_derivatives:
153+
mos_script.write('setDebugFlags("disableDirectionalDerivatives");\n')
154+
155+
# Build FMU
156+
mos_script.write('print("Building FMU...\\n");\n')
157+
mos_script.write(f'result := buildModelFMU({model}, version="2.0", fmuType="me", platforms={{"static"}});\n')
158+
mos_script.write('print("Result: " + result + "\\n");\n')
159+
mos_script.write('print("Messages: " + getErrorString() + "\\n");\n')
160+
mos_script.close()
161+
162+
print(f"🔨 Compiling FMU (time varies by model complexity)...")
163+
start = time.time()
164+
165+
# Run omc without strict timeout (compilation time varies by model)
166+
result = subprocess.run(
167+
['omc', mos_script.name],
168+
cwd=os.getcwd(),
169+
capture_output=True,
170+
text=True,
171+
timeout=None # No timeout - let compilation finish
172+
)
173+
elapsed = time.time() - start
174+
175+
# Show output
176+
if result.stdout:
177+
for line in result.stdout.split('\n'):
178+
if line.strip() and line.strip() not in ['true', '""', '']:
179+
print(f" {line}")
180+
181+
if result.stderr:
182+
print(" STDERR:")
183+
for line in result.stderr.split('\n'):
184+
if line.strip():
185+
print(f" {line}")
186+
187+
# Verify FMU was created
188+
if not os.path.exists(fmu_file_path):
189+
raise Exception(f'FMU compilation failed - file not found: {fmu_file_path}')
190+
191+
print(f"✅ FMU compiled successfully in {elapsed:.1f}s")
192+
193+
except subprocess.TimeoutExpired:
194+
raise Exception(f'FMU compilation timed out. This should not happen with timeout=None.')
195+
finally:
196+
try:
197+
os.unlink(mos_script.name)
198+
except:
199+
pass
74200

75201
# Parse FMU to dae object
76202
self.dae = ca.DaeBuilder("model", fmu_file_path, {"debug": False})

0 commit comments

Comments
 (0)