Skip to content

Commit

Permalink
Merge branch 'release/v0.2.5'
Browse files Browse the repository at this point in the history
  • Loading branch information
t-sommer committed Jun 17, 2018
2 parents 10f2b2f + 0dfa7e8 commit 259c5a8
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 32 deletions.
8 changes: 8 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
## v0.2.5 (2018-06-17)

Improved handling of input signals

- input is now applied before initialization for co-simulation
- "time" column for input signals can now have an arbitrary name


## v0.2.4 (2018-05-22)

- `NEW` Remaining FMI functions added
Expand Down
2 changes: 1 addition & 1 deletion fmpy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from ctypes import *
import _ctypes

__version__ = '0.2.4'
__version__ = '0.2.5'


# determine the platform
Expand Down
88 changes: 62 additions & 26 deletions fmpy/simulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,14 +108,24 @@ def __init__(self, fmu, modelDescription, signals):
fmu the FMU instance
modelDescription the model description instance
signals a structured numpy array that contains the input
Example:
Create a Real signal 'step' and a Boolean signal 'switch' with a discrete step at t=0.5
>>> import numpy as np
>>> dtype = [('time', np.double), ('step', np.double), ('switch', np.bool_)]
>>> signals = np.array([(0.0, 0.0, False), (0.5, 0.0, False), (0.5, 0.1, True), (1.0, 1.0, True)], dtype=dtype)
"""

self.fmu = fmu
self.signals = signals

if signals is None:
self.t = None
return

self.t = signals[signals.dtype.names[0]]

is_fmi1 = isinstance(fmu, _FMU1)

# get the setters
Expand All @@ -138,7 +148,7 @@ def __init__(self, fmu, modelDescription, signals):
continue

if sv.causality == 'input':
if sv.name not in self.signals.dtype.names:
if sv.name not in signals.dtype.names:
print("Warning: missing input for " + sv.name)
continue
self.values['Integer' if sv.type == 'Enumeration' else sv.type].append((sv.valueReference, sv.name))
Expand All @@ -147,56 +157,78 @@ def __init__(self, fmu, modelDescription, signals):
real_vrs, self.real_names = zip(*self.values['Real'])
self.real_vrs = (c_uint32 * len(real_vrs))(*real_vrs)
self.real_values = (c_double * len(real_vrs))()
self.real_table = np.stack(map(lambda n: self.signals[n], self.real_names))
self.real_table = np.stack(map(lambda n: signals[n], self.real_names))
else:
self.real_vrs = []

if len(self.values['Integer']) > 0:
integer_vrs, self.integer_names = zip(*self.values['Integer'])
self.integer_vrs = (c_uint32 * len(integer_vrs))(*integer_vrs)
self.integer_values = (c_int32 * len(integer_vrs))()
self.integer_table = np.asarray(np.stack(map(lambda n: self.signals[n], self.integer_names)), dtype=np.int32)
self.integer_table = np.asarray(np.stack(map(lambda n: signals[n], self.integer_names)), dtype=np.int32)
else:
self.integer_vrs = []

if len(self.values['Boolean']) > 0:
boolean_vrs, self.boolean_names = zip(*self.values['Boolean'])
self.boolean_vrs = (c_uint32 * len(boolean_vrs))(*boolean_vrs)
self.boolean_values = ((c_int8 if is_fmi1 else c_int32) * len(boolean_vrs))()
self.boolean_table = np.asarray(np.stack(map(lambda n: self.signals[n], self.boolean_names)), dtype=np.int32)
self.boolean_table = np.asarray(np.stack(map(lambda n: signals[n], self.boolean_names)), dtype=np.int32)
else:
self.boolean_vrs = []

def apply(self, time, continuous=True, discrete=True, after_event=False):
""" Apply the input
if self.signals is None:
return sys.float_info.max
Parameters:
continuous apply continuous inputs
discrete apply discrete inputs
after_event apply right hand side inputs at discontinuities
t = self.signals['time']
Returns:
the next event time or sys.float_info.max if no more events exist
"""

if self.t is None:
return sys.float_info.max

# TODO: check for event

if len(self.real_vrs) > 0 and continuous:
self.real_values[:] = self.interpolate(time=time, t=t, table=self.real_table, after_event=after_event)
self.real_values[:] = self.interpolate(time=time, t=self.t, table=self.real_table, after_event=after_event)
self._setReal(self.fmu.component, self.real_vrs, len(self.real_vrs), self.real_values)

# TODO: discrete apply Reals

if len(self.integer_vrs) > 0 and discrete:
self.integer_values[:] = self.interpolate(time=time, t=t, table=self.integer_table, discrete=True, after_event=after_event)
self.integer_values[:] = self.interpolate(time=time, t=self.t, table=self.integer_table, discrete=True, after_event=after_event)
self._setInteger(self.fmu.component, self.integer_vrs, len(self.integer_vrs), self.integer_values)

if len(self.boolean_vrs) > 0 and discrete:
self.boolean_values[:] = self.interpolate(time=time, t=t, table=self.boolean_table, discrete=True, after_event=after_event)
self.boolean_values[:] = self.interpolate(time=time, t=self.t, table=self.boolean_table, discrete=True, after_event=after_event)
self._setBoolean(self.fmu.component, self.boolean_vrs, len(self.boolean_vrs),
cast(self.boolean_values, POINTER(self._bool_type)))

return Input.nextEvent(time, t)
return Input.nextEvent(time, self.t)

@staticmethod
def nextEvent(time, t):
""" Find the next event in t after time
Parameters:
time time after which to search for events
t the time grid to search for events
Returns:
the next event time or sys.float_info.max if no more events are detected after time
"""

i_events = np.argwhere(np.diff(t) == 0)

if len(i_events) < 1:
# no events detected
return sys.float_info.max

t_events = t[i_events]

i = np.argmax(t_events > time)
Expand Down Expand Up @@ -378,7 +410,7 @@ def simulate_fmu(filename,
use_source_code compile the shared library (requires C sources)
start_values dictionary of variable name -> value pairs
apply_default_start_values apply the start values from the model description
input a structured numpy array that contains the input
input a structured numpy array that contains the input (see :class:`Input`)
output list of variables to record (None: record outputs)
timeout timeout for the simulation
debug_logging enable the FMU's debug logging
Expand Down Expand Up @@ -523,10 +555,10 @@ def simulateME(model_description, fmu_kwargs, start_time, stop_time, solver_name
fmu.instantiate(callbacks=callbacks, loggingOn=debug_logging)
fmu.setupExperiment(startTime=start_time)

# set the start values
apply_start_values(fmu, model_description, start_values, apply_default_start_values)

input = Input(fmu, model_description, input_signals)

# apply input and start values
apply_start_values(fmu, model_description, start_values, apply_default_start_values)
input.apply(time)

if is_fmi1:
Expand Down Expand Up @@ -684,28 +716,32 @@ def simulateCS(model_description, fmu_kwargs, start_time, stop_time, start_value

sim_start = current_time()

# instantiate the model
if model_description.fmiVersion == '1.0':
fmu = FMU1Slave(**fmu_kwargs)
fmu.instantiate(functions=callbacks, loggingOn=debug_logging)
apply_start_values(fmu, model_description, start_values, apply_default_start_values)
fmu.initialize()
else:
fmu = FMU2Slave(**fmu_kwargs)
fmu.instantiate(callbacks=callbacks, loggingOn=debug_logging)
fmu.setupExperiment(tolerance=None, startTime=start_time)
apply_start_values(fmu, model_description, start_values, apply_default_start_values)
fmu.enterInitializationMode()
fmu.exitInitializationMode()

input = Input(fmu=fmu, modelDescription=model_description, signals=input_signals)

recorder = Recorder(fmu=fmu,
modelDescription=model_description,
variableNames=output,
interval=output_interval)

time = start_time

# apply input and start values
apply_start_values(fmu, model_description, start_values, apply_default_start_values)
input.apply(time)

# initialize the model
if model_description.fmiVersion == '1.0':
fmu.initialize()
else:
fmu.enterInitializationMode()
fmu.exitInitializationMode()

recorder = Recorder(fmu=fmu, modelDescription=model_description, variableNames=output, interval=output_interval)

# simulation loop
while time < stop_time:

Expand Down
6 changes: 3 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
- supports FMI 1.0 and 2.0 for Co-Simulation and Model Exchange
- runs on Windows, Linux and macOS
- has a graphical user interface ``NEW``
- compiles source code FMUs ``NEW``
- has a graphical user interface
- compiles source code FMUs
"""

packages = ['fmpy', 'fmpy.cross_check', 'fmpy.examples', 'fmpy.gui', 'fmpy.gui.generated', 'fmpy.ssp',
Expand Down Expand Up @@ -46,7 +46,7 @@
extras_require['complete'] = sorted(set(sum(extras_require.values(), [])))

setup(name='FMPy',
version='0.2.4',
version='0.2.5',
description="Simulate Functional Mock-up Units (FMUs) in Python",
long_description=long_description,
author="Torsten Sommer",
Expand Down
8 changes: 6 additions & 2 deletions tests/test_input.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,15 @@ def test_input_continuous(self):

def test_event_detection(self):

fmx = sys.float_info.max

# no event
t = np.array([0.0, 1.0])
self.assertEqual(fmx, Input.nextEvent(0.8, t), "Expecting no events")

# time grid with events at 0.5 and 0.8
t = np.array([0.1, 0.2, 0.3, 0.4, 0.5, 0.5, 0.6, 0.7, 0.8, 0.8, 0.8, 0.9, 1.0])

fmx = sys.float_info.max

self.assertEqual(0.5, Input.nextEvent(0.0, t), "Expecting first event before first sample")
self.assertEqual(0.5, Input.nextEvent(0.2, t), "Expecting first event before first event")
self.assertEqual(0.8, Input.nextEvent(0.5, t), "Expecting second event at first event")
Expand Down

0 comments on commit 259c5a8

Please sign in to comment.