Commit 1ca2c04d authored by Tjerk Vreeken's avatar Tjerk Vreeken

Simulation: opt-in partial workaround for delay()

A workaround is added to support a delay of zero (or make delay expression
behave as such). By default, this workaround is disabled. The user has to
explicitly set "_force_zero_delay" to True to enable it.
parent f413630d
......@@ -32,6 +32,10 @@ class SimulationProblem:
# Folders in which the referenced Modelica libraries are found
modelica_library_folders = []
# Force workaround for delay support by assuming zero delay. This flag
# will be removed when proper delay support is added.
_force_zero_delay = False
def __init__(self, **kwargs):
# Check arguments
assert('model_folder' in kwargs)
......@@ -62,11 +66,8 @@ class SimulationProblem:
self.__mx['constant_inputs'] = []
self.__mx['lookup_tables'] = []
# TODO: implement delayed feedback
delayed_feedback_variables = []
for v in self.__pymoca_model.inputs:
if v.symbol.name() in delayed_feedback_variables:
if v.symbol.name() in self.__pymoca_model.delay_states:
# Delayed feedback variables are local to each ensemble, and
# therefore belong to the collection of algebraic variables,
# rather than to the control inputs.
......@@ -147,8 +148,19 @@ class SimulationProblem:
for index, derivative_state in enumerate(self.__mx['derivatives']):
derivative_approximation_residuals.append(derivative_state - (X[index] - X_prev[index]) / dt)
if self.__pymoca_model.delay_states and not self._force_zero_delay:
raise NotImplementedError("Delayed states are not supported")
# Delayed feedback (assuming zero delay)
# TODO: implement delayed feedback support for delay != 0
delayed_feedback_equations = []
for delay_state, delay_argument in zip(self.__pymoca_model.delay_states,
self.__pymoca_model.delay_arguments):
logger.warning("Assuming zero delay for delay state '{}'".format(delay_state))
delayed_feedback_equations.append(delay_argument.expr - self.__sym_dict[delay_state])
# Append residuals for derivative approximations
dae_residual = ca.vertcat(self.__dae_residual, *derivative_approximation_residuals)
dae_residual = ca.vertcat(self.__dae_residual, *derivative_approximation_residuals, *delayed_feedback_equations)
# TODO: implement lookup_tables
......@@ -328,6 +340,14 @@ class SimulationProblem:
else:
[evaluated_bounds] = bound_evaluator.call([])
# Update with the bounds of delayed states
n_delay = len(self.__pymoca_model.delay_states)
delay_bounds = np.array([-np.inf, np.inf] * n_delay).reshape((n_delay, 2))
offset = len(self.__pymoca_model.states) + len(self.__pymoca_model.alg_states)
evaluated_bounds = np.vstack((evaluated_bounds[:offset, :],
delay_bounds,
evaluated_bounds[offset:, :]))
# Construct arrays of state bounds (used in the initialize() nlp, but not in __do_step rootfinder)
self.__lbx = evaluated_bounds[:, 0]
self.__ubx = evaluated_bounds[:, 1]
......
......@@ -13,8 +13,8 @@ model TestModel
output Real z;
//TODO: Implement delayed variables and tests for them
//input Real x_delayed;
//TODO: Implement properly delayed variables and tests for them
output Real x_delayed;
output Real switched;
......@@ -41,4 +41,5 @@ equation
u_out = u + 1;
x_delayed = delay(3 * x, 0) + 1;
end TestModel;
......@@ -7,7 +7,8 @@ from .data_path import data_path
class SimulationTestProblem(PIMixin, SimulationProblem):
# pi_validate_timeseries = False
_force_zero_delay = True
def __init__(self):
super().__init__(
input_folder=data_path(),
......
......@@ -11,6 +11,7 @@ from .data_path import data_path
class SimulationTestProblem(SimulationProblem):
_force_zero_delay = True
def __init__(self):
super().__init__(
......@@ -54,6 +55,8 @@ class TestSimulation(TestCase):
"x_start",
"y",
"z",
"_pymoca_delay_0[1,1]",
"x_delayed"
},
)
self.assertEqual(set(self.problem.get_parameter_variables()), {"x_start", "k"})
......@@ -62,7 +65,7 @@ class TestSimulation(TestCase):
)
self.assertEqual(
set(self.problem.get_output_variables()),
{"constant_output", "switched", "u_out", "y", "z"},
{"constant_output", "switched", "u_out", "y", "z", "x_delayed"},
)
def test_get_set_var(self):
......@@ -121,6 +124,9 @@ class TestSimulation(TestCase):
self.problem.update(dt)
val = self.problem.get_var("switched")
self.assertEqual(val, expected_values[i])
# Test zero-delayed expression
self.assertAlmostEqual(self.problem.get_var('x_delayed'), self.problem.get_var('x') * 3 + 1, 1e-6)
i += 1
def test_set_input2(self):
......@@ -142,3 +148,26 @@ class TestSimulation(TestCase):
val = self.problem.get_var("switched")
self.assertEqual(val, expected_values[i])
i += 1
class FailingSimulationTestProblem(SimulationProblem):
def __init__(self):
super().__init__(
input_folder=data_path(),
output_folder=data_path(),
model_name="TestModel",
model_folder=data_path(),
)
def compiler_options(self):
compiler_options = super().compiler_options()
compiler_options["cache"] = False
return compiler_options
class TestFailingSimulation(TestCase):
def test_delay_exception(self):
with self.assertRaisesRegex(NotImplementedError, 'Delayed states are not supported'):
self.problem = FailingSimulationTestProblem()
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment