Skip to content

Commit a9a0714

Browse files
committed
Clean up, test FMI 2 container, add nSteps
1 parent abac84f commit a9a0714

File tree

6 files changed

+44
-166
lines changed

6 files changed

+44
-166
lines changed

native/container-fmu/container-fmu/src/fmi3.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -883,7 +883,7 @@ pub extern "C" fn fmi3DoStep(
883883
*eventHandlingNeeded = false;
884884
*terminateSimulation = container.terminated;
885885
*earlyReturn = false;
886-
*lastSuccessfulTime = container.time
886+
*lastSuccessfulTime = container.time();
887887
};
888888

889889
status

native/container-fmu/container-fmu/src/lib.rs

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ pub mod fmi2;
55
pub mod fmi3;
66

77
use crate::conf::*;
8-
use approx::relative_ne;
8+
use approx::{relative_eq, relative_ne};
99
use fmi::fmi2::types::*;
1010
use fmi::fmi2::*;
1111
use fmi::fmi3::types::*;
@@ -33,10 +33,10 @@ enum FMUInstance {
3333
}
3434

3535
struct Container {
36-
time: f64,
3736
tolerance: Option<f64>,
3837
startTime: f64,
3938
stopTime: Option<f64>,
39+
nSteps: u64,
4040
terminated: bool,
4141
instances: Vec<FMUInstance>,
4242
system: System,
@@ -244,10 +244,10 @@ impl Container {
244244
let stringValues = Vec::new();
245245

246246
let container = Container {
247-
time: 0.0,
248247
tolerance: None,
249248
startTime: 0.0,
250249
stopTime: None,
250+
nSteps: 0,
251251
terminated: false,
252252
instances,
253253
system,
@@ -420,6 +420,10 @@ impl Container {
420420
self.call_all(|fmu| fmu.terminate(), |fmu| fmu.terminate())
421421
}
422422

423+
fn time(&self) -> f64 {
424+
self.startTime + self.nSteps as f64 * self.system.fixedStepSize
425+
}
426+
423427
fn getVariable(
424428
&self,
425429
valueReference: fmi3ValueReference,
@@ -464,7 +468,7 @@ impl Container {
464468

465469
for valueReference in valueReferences {
466470
if *valueReference == 0 {
467-
values[0] = self.time;
471+
values[0] = self.time();
468472
values = &mut values[1..];
469473
} else {
470474
let variable = match self.getVariable(*valueReference, VariableType::Float64) {
@@ -1174,7 +1178,7 @@ impl Container {
11741178
// thread safety constraints (FMU callbacks are not Sync).
11751179
// The FMU doStep calls must be executed sequentially.
11761180

1177-
let currentCommunicationPoint = self.time;
1181+
let currentCommunicationPoint = self.time();
11781182
let communicationStepSize = self.system.fixedStepSize;
11791183

11801184
let do_step = |instance: &FMUInstance| match instance {
@@ -1226,7 +1230,7 @@ impl Container {
12261230

12271231
return_on_error!(status, self.updateConnections());
12281232

1229-
self.time += communicationStepSize;
1233+
self.nSteps += 1;
12301234

12311235
status
12321236
}
@@ -1238,18 +1242,29 @@ impl Container {
12381242
) -> fmiStatus {
12391243
let mut status = fmiOK;
12401244

1241-
if relative_ne!(currentCommunicationPoint, self.time) {
1245+
if relative_ne!(currentCommunicationPoint, self.time()) {
12421246
let message = format!(
12431247
"Expected currentCommunicationPoint={} but was {}",
1244-
self.time, currentCommunicationPoint
1248+
self.time(),
1249+
currentCommunicationPoint
12451250
);
12461251
self.logError(&message);
12471252
return fmiError;
12481253
}
12491254

1250-
let next_time = currentCommunicationPoint + communicationStepSize;
1255+
let n_steps_float = communicationStepSize / self.system.fixedStepSize;
1256+
let n_steps = n_steps_float.round() as u64;
1257+
1258+
if n_steps == 0 || !relative_eq!(n_steps as f64, n_steps_float) {
1259+
let message = format!(
1260+
"The communicationStepSize={} must be an even multiple of the fixedStepSize={}.",
1261+
self.system.fixedStepSize, communicationStepSize
1262+
);
1263+
self.logError(&message);
1264+
return fmiError;
1265+
}
12511266

1252-
while self.time < next_time && relative_ne!(self.time, next_time) {
1267+
for _ in 0..n_steps {
12531268
return_on_error!(status, self.doFixedStep());
12541269
}
12551270

native/container-fmu/container-fmu/tests/common.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -201,12 +201,11 @@ pub fn create_fmi2_container() -> FMU2 {
201201
}
202202

203203
let log_message = move |status: &fmi2Status, category: &str, message: &str| {
204-
println!("[{status:?}] [{category}] {message}")
204+
println!(" [{status:?}] [{category}] {message}")
205205
};
206206

207-
let log_fmi_call = move |_status: &fmi2Status, _message: &str| {
208-
// println!("[{_status:?}] {_message}")
209-
};
207+
let log_fmi_call =
208+
move |_status: &fmi2Status, _message: &str| println!(">[{_status:?}] {_message}");
210209

211210
FMU2::new(
212211
&unzipdir,

src/fmpy/container_fmu/__init__.py

Lines changed: 0 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,13 @@
1-
from typing import Sequence
2-
31
import json
4-
from datetime import datetime, timezone
52
from pathlib import Path
63
import numpy as np
7-
8-
import jinja2
94
from fmpy import read_model_description
105
from fmpy.container_fmu.config import Configuration
116
from fmpy.model_description import ModelVariable
127

13-
148
__version__ = "0.1.0"
159

1610

17-
def write_model_description_from_configuration(configuration: Configuration, filename: Path):
18-
template_dir = Path(__file__).parent / "template"
19-
20-
loader = jinja2.FileSystemLoader(searchpath=template_dir)
21-
environment = jinja2.Environment(loader=loader, trim_blocks=True)
22-
template = environment.get_template("FMI3.xml")
23-
24-
def xml_encode(s):
25-
"""Escape non-ASCII characters"""
26-
27-
if s is None:
28-
return s
29-
30-
s = s.replace("&", "&amp;")
31-
s = s.replace("<", "&lt;")
32-
s = s.replace(">", "&gt;")
33-
s = s.replace('"', "&quot;")
34-
35-
for c in s:
36-
if ord(c) > 127:
37-
s = s.replace(c, "&#x" + format(ord(c), "x") + ";")
38-
39-
return s
40-
41-
def to_literal(value):
42-
if isinstance(value, bool):
43-
return "true" if value else "false"
44-
else:
45-
return str(value)
46-
47-
template.globals.update(
48-
{
49-
"xml_encode": xml_encode,
50-
"to_literal": to_literal,
51-
}
52-
)
53-
54-
xml = template.render(
55-
container_fmu_version=__version__,
56-
generationDateAndTime=datetime.now(timezone.utc).isoformat(),
57-
system=configuration,
58-
)
59-
60-
with open(filename, "w") as f:
61-
f.write(xml)
62-
63-
6411
def write_configuration(configuration: Configuration, filename: Path):
6512

6613
data = {

src/fmpy/container_fmu/template/FMI3.xml

Lines changed: 0 additions & 88 deletions
This file was deleted.

tests/container_fmu/test_feedthrough.py

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,30 +9,35 @@
99
printLogMessage,
1010
)
1111
from fmpy.model_description import BaseUnit, DisplayUnit, ModelDescription, SimpleType, Item, DefaultExperiment, \
12-
ModelVariable, Unit, CoSimulation
12+
ModelVariable, Unit, CoSimulation, VariableType
1313

1414

15-
@pytest.mark.skipif(platform_tuple == "aarch64-darwin", reason="FMI 2.0 does not support aarch64-darwin")
16-
def test_feedthrough(work_dir, reference_fmus_dist_dir, resources_dir):
15+
@pytest.mark.parametrize("fmi_version", ["2.0", "3.0"])
16+
def test_feedthrough(work_dir, reference_fmus_dist_dir, resources_dir, fmi_version):
17+
18+
if platform_tuple == "aarch64-darwin":
19+
pytest.skip("FMI 2.0 does not support aarch64-darwin")
20+
21+
variable_type: VariableType = "Real" if fmi_version == "2.0" else "Float64"
1722

1823
container_input = ModelVariable(
19-
type="Real",
24+
type=variable_type,
2025
variability="continuous",
2126
causality="input",
2227
name="Float64_continuous_input",
2328
start="1.1",
2429
)
2530

2631
container_output = ModelVariable(
27-
type="Real",
32+
type=variable_type,
2833
initial="calculated",
2934
variability="continuous",
3035
causality="output",
3136
name="Float64_continuous_output",
3237
)
3338

3439
model_description = ModelDescription(
35-
fmiVersion="2.0",
40+
fmiVersion=fmi_version,
3641
modelName="Feedthrough",
3742
instantiationToken="",
3843
unitDefinitions=[
@@ -43,7 +48,7 @@ def test_feedthrough(work_dir, reference_fmus_dist_dir, resources_dir):
4348
),
4449
],
4550
typeDefinitions=[
46-
SimpleType(name='AngularVelocity', type="Real", quantity='AngularVelocity', unit='rad/s',
51+
SimpleType(name='AngularVelocity', type=variable_type, quantity='AngularVelocity', unit='rad/s',
4752
displayUnit='rpm'),
4853
SimpleType(name='Option', type='Enumeration', items=[
4954
Item(name='Option 1', value="1", description="First option"),
@@ -58,7 +63,7 @@ def test_feedthrough(work_dir, reference_fmus_dist_dir, resources_dir):
5863
),
5964
modelVariables=[
6065
ModelVariable(
61-
type="Real",
66+
type=variable_type,
6267
variability="continuous",
6368
causality="independent",
6469
name="time",
@@ -107,7 +112,7 @@ def test_feedthrough(work_dir, reference_fmus_dist_dir, resources_dir):
107112

108113
unzipdir = work_dir
109114

110-
filename = unzipdir / "Container.fmu"
115+
filename = unzipdir / f"Container_fmi{fmi_version[0]}.fmu"
111116

112117
create_container_fmu(configuration, unzipdir, filename)
113118

@@ -126,7 +131,7 @@ def test_feedthrough(work_dir, reference_fmus_dist_dir, resources_dir):
126131
filename,
127132
debug_logging=True,
128133
fmi_call_logger=print,
129-
logger=printLogMessage,
134+
# logger=printLogMessage,
130135
output_interval=0.5,
131136
stop_time=1,
132137
# start_values={"Float64_continuous_input": 1.5},

0 commit comments

Comments
 (0)