[ Web Proxy ]
URL:
Viewing: https://raw.githubusercontent.com/j-emils/OMPython/master/tests/test_ModelicaSystem.py [Back]  [Original]

import OMPython
import os
import pathlib
import pytest
import sys
import tempfile
import numpy as np

skip_on_windows = pytest.mark.skipif(
    sys.platform.startswith("win"),
    reason="OpenModelica Docker image is Linux-only; skipping on Windows.",
)

skip_python_older_312 = pytest.mark.skipif(
    sys.version_info < (3, 12),
    reason="OMCPath(non-local) only working for Python >= 3.12.",
)


@pytest.fixture
def model_firstorder_content():
    return ("""model M
  Real x(start = 1, fixed = true);
  parameter Real a = -1;
equation
  der(x) = x*a;
end M;
""")


@pytest.fixture
def model_firstorder(tmp_path, model_firstorder_content):
    mod = tmp_path / "M.mo"
    mod.write_text(model_firstorder_content)
    return mod


def test_ModelicaSystem_loop(model_firstorder):
    def worker():
        filePath = model_firstorder.as_posix()
        mod = OMPython.ModelicaSystem()
        mod.model(
            file=filePath,
            name="M",
        )
        mod.simulate()
        mod.convertMo2Fmu(fmuType="me")
    for _ in range(10):
        worker()


def test_setParameters():
    omc = OMPython.OMCSessionZMQ()
    model_path_str = omc.sendExpression("getInstallationDirectoryPath()") + "/share/doc/omc/testmodels"
    model_path = omc.omcpath(model_path_str)
    mod = OMPython.ModelicaSystem()
    mod.model(
        file=model_path / "BouncingBall.mo",
        name="BouncingBall",
    )

    # method 1 (test depreciated variants)
    mod.setParameters("e=1.234")
    mod.setParameters(["g=321.0"])
    assert mod.getParameters("e") == ["1.234"]
    assert mod.getParameters("g") == ["321.0"]
    assert mod.getParameters() == {
        "e": "1.234",
        "g": "321.0",
    }
    with pytest.raises(KeyError):
        mod.getParameters("thisParameterDoesNotExist")

    # method 2 (new style)
    pvals = {"e": 21.3, "g": 0.12}
    mod.setParameters(**pvals)
    assert mod.getParameters() == {
        "e": "21.3",
        "g": "0.12",
    }
    assert mod.getParameters(["e", "g"]) == ["21.3", "0.12"]
    assert mod.getParameters(["g", "e"]) == ["0.12", "21.3"]
    with pytest.raises(KeyError):
        mod.getParameters(["g", "thisParameterDoesNotExist"])


def test_setSimulationOptions():
    omc = OMPython.OMCSessionZMQ()
    model_path_str = omc.sendExpression("getInstallationDirectoryPath()") + "/share/doc/omc/testmodels"
    model_path = omc.omcpath(model_path_str)
    mod = OMPython.ModelicaSystem()
    mod.model(
        file=model_path / "BouncingBall.mo",
        name="BouncingBall",
    )

    # method 1
    mod.setSimulationOptions(stopTime=1.234)
    mod.setSimulationOptions(tolerance=1.1e-08)
    assert mod.getSimulationOptions("stopTime") == ["1.234"]
    assert mod.getSimulationOptions("tolerance") == ["1.1e-08"]
    assert mod.getSimulationOptions(["tolerance", "stopTime"]) == ["1.1e-08", "1.234"]
    d = mod.getSimulationOptions()
    assert isinstance(d, dict)
    assert d["stopTime"] == "1.234"
    assert d["tolerance"] == "1.1e-08"
    with pytest.raises(KeyError):
        mod.getSimulationOptions("thisOptionDoesNotExist")

    # method 2
    mod.setSimulationOptions(stopTime=2.1, tolerance=1.2e-08)
    d = mod.getSimulationOptions()
    assert d["stopTime"] == "2.1"
    assert d["tolerance"] == "1.2e-08"


def test_relative_path(model_firstorder):
    cwd = pathlib.Path.cwd()
    (fd, name) = tempfile.mkstemp(prefix='tmpOMPython.tests', dir=cwd, text=True)
    try:
        with os.fdopen(fd, 'w') as f:
            f.write(model_firstorder.read_text())

        model_file = pathlib.Path(name).relative_to(cwd)
        model_relative = str(model_file)
        assert "/" not in model_relative

        mod = OMPython.ModelicaSystem()
        mod.model(
            file=model_relative,
            name="M",
        )
        assert float(mod.getParameters("a")[0]) == -1
    finally:
        model_file.unlink()  # clean up the temporary file


def test_customBuildDirectory(tmp_path, model_firstorder):
    filePath = model_firstorder.as_posix()
    tmpdir = tmp_path / "tmpdir1"
    tmpdir.mkdir()
    mod = OMPython.ModelicaSystem(customBuildDirectory=tmpdir)
    mod.model(
        file=filePath,
        name="M",
    )
    assert pathlib.Path(mod.getWorkDirectory()).resolve() == tmpdir.resolve()
    result_file = tmpdir / "a.mat"
    assert not result_file.exists()
    mod.simulate(resultfile="a.mat")
    assert result_file.is_file()


@skip_on_windows
@skip_python_older_312
def test_getSolutions_docker(model_firstorder):
    omcp = OMPython.OMCProcessDocker(docker="openmodelica/openmodelica:v1.25.0-minimal")
    omc = OMPython.OMCSessionZMQ(omc_process=omcp)

    mod = OMPython.ModelicaSystem(
        omc_process=omc.omc_process,
    )
    mod.model(
        name="M",
        file=model_firstorder.as_posix(),
    )

    _run_getSolutions(mod)


def test_getSolutions(model_firstorder):
    mod = OMPython.ModelicaSystem()
    mod.model(
        file=model_firstorder.as_posix(),
        name="M",
    )

    _run_getSolutions(mod)


def _run_getSolutions(mod):
    x0 = 1
    a = -1
    tau = -1 / a
    stopTime = 5*tau

    simOptions = {"stopTime": stopTime, "stepSize": 0.1, "tolerance": 1e-8}
    mod.setSimulationOptions(**simOptions)
    mod.simulate()

    x = mod.getSolutions("x")
    t, x2 = mod.getSolutions(["time", "x"])
    assert (x2 == x).all()
    sol_names = mod.getSolutions()
    assert isinstance(sol_names, tuple)
    assert "time" in sol_names
    assert "x" in sol_names
    assert "der(x)" in sol_names
    with pytest.raises(OMPython.ModelicaSystemError):
        mod.getSolutions("thisVariableDoesNotExist")
    assert np.isclose(t[0], 0), "time does not start at 0"
    assert np.isclose(t[-1], stopTime), "time does not end at stopTime"
    x_analytical = x0 * np.exp(a*t)
    assert np.isclose(x, x_analytical, rtol=1e-4).all()


def test_getters(tmp_path):
    model_file = tmp_path / "M_getters.mo"
    model_file.write_text("""
model M_getters
Real x(start = 1, fixed = true);
output Real y "the derivative";
parameter Real a = -0.5;
parameter Real b = 0.1;
equation
der(x) = x*a + b;
y = der(x);
end M_getters;
""")
    mod = OMPython.ModelicaSystem()
    mod.model(
        file=model_file.as_posix(),
        name="M_getters",
    )

    q = mod.getQuantities()
    assert isinstance(q, list)
    assert sorted(q, key=lambda d: d["name"]) == sorted([
        {
            'alias': 'noAlias',
            'aliasvariable': None,
            'causality': 'local',
            'changeable': 'true',
            'description': None,
            'max': None,
            'min': None,
            'name': 'x',
            'start': '1.0',
            'unit': None,
            'variability': 'continuous',
        },
        {
            'alias': 'noAlias',
            'aliasvariable': None,
            'causality': 'local',
            'changeable': 'false',
            'description': None,
            'max': None,
            'min': None,
            'name': 'der(x)',
            'start': None,
            'unit': None,
            'variability': 'continuous',
        },
        {
            'alias': 'noAlias',
            'aliasvariable': None,
            'causality': 'output',
            'changeable': 'false',
            'description': 'the derivative',
            'max': None,
            'min': None,
            'name': 'y',
            'start': '-0.4',
            'unit': None,
            'variability': 'continuous',
        },
        {
            'alias': 'noAlias',
            'aliasvariable': None,
            'causality': 'parameter',
            'changeable': 'true',
            'description': None,
            'max': None,
            'min': None,
            'name': 'a',
            'start': '-0.5',
            'unit': None,
            'variability': 'parameter',
        },
        {
            'alias': 'noAlias',
            'aliasvariable': None,
            'causality': 'parameter',
            'changeable': 'true',
            'description': None,
            'max': None,
            'min': None,
            'name': 'b',
            'start': '0.1',
            'unit': None,
            'variability': 'parameter',
        }
    ], key=lambda d: d["name"])

    assert mod.getQuantities("y") == [
        {
            'alias': 'noAlias',
            'aliasvariable': None,
            'causality': 'output',
            'changeable': 'false',
            'description': 'the derivative',
            'max': None,
            'min': None,
            'name': 'y',
            'start': '-0.4',
            'unit': None,
            'variability': 'continuous',
        }
    ]

    assert mod.getQuantities(["y", "x"]) == [
        {
            'alias': 'noAlias',
            'aliasvariable': None,
            'causality': 'output',
            'changeable': 'false',
            'description': 'the derivative',
            'max': None,
            'min': None,
            'name': 'y',
            'start': '-0.4',
            'unit': None,
            'variability': 'continuous',
        },
        {
            'alias': 'noAlias',
            'aliasvariable': None,
            'causality': 'local',
            'changeable': 'true',
            'description': None,
            'max': None,
            'min': None,
            'name': 'x',
            'start': '1.0',
            'unit': None,
            'variability': 'continuous',
        },
    ]

    with pytest.raises(KeyError):
        mod.getQuantities("thisQuantityDoesNotExist")

    assert mod.getInputs() == {}
    with pytest.raises(KeyError):
        mod.getInputs("thisInputDoesNotExist")
    # getOutputs before simulate()
    assert mod.getOutputs() == {'y': '-0.4'}
    assert mod.getOutputs("y") == ["-0.4"]
    assert mod.getOutputs(["y", "y"]) == ["-0.4", "-0.4"]
    with pytest.raises(KeyError):
        mod.getOutputs("thisOutputDoesNotExist")

    # getContinuous before simulate():
    assert mod.getContinuous() == {
        'x': '1.0',
        'der(x)': None,
        'y': '-0.4'
    }
    assert mod.getContinuous("y") == ['-0.4']
    assert mod.getContinuous(["y", "x"]) == ['-0.4', '1.0']
    with pytest.raises(KeyError):
        mod.getContinuous("a")  # a is a parameter

    stopTime = 1.0
    a = -0.5
    b = 0.1
    x0 = 1.0
    x_analytical = -b/a + (x0 + b/a) * np.exp(a * stopTime)
    dx_analytical = (x0 + b/a) * a * np.exp(a * stopTime)
    mod.setSimulationOptions(stopTime=stopTime)
    mod.simulate()

    # getOutputs after simulate()
    d = mod.getOutputs()
    assert d.keys() == {"y"}
    assert np.isclose(d["y"], dx_analytical, 1e-4)
    assert mod.getOutputs("y") == [d["y"]]
    assert mod.getOutputs(["y", "y"]) == [d["y"], d["y"]]
    with pytest.raises(KeyError):
        mod.getOutputs("thisOutputDoesNotExist")

    # getContinuous after simulate() should return values at end of simulation:
    with pytest.raises(OMPython.ModelicaSystemError):
        mod.getContinuous("a")  # a is a parameter
    with pytest.raises(OMPython.ModelicaSystemError):
        mod.getContinuous(["x", "a", "y"])  # a is a parameter
    d = mod.getContinuous()
    assert d.keys() == {"x", "der(x)", "y"}
    assert np.isclose(d["x"], x_analytical, 1e-4)
    assert np.isclose(d["der(x)"], dx_analytical, 1e-4)
    assert np.isclose(d["y"], dx_analytical, 1e-4)
    assert mod.getContinuous("x") == [d["x"]]
    assert mod.getContinuous(["y", "x"]) == [d["y"], d["x"]]

    with pytest.raises(OMPython.ModelicaSystemError):
        mod.getContinuous("a")  # a is a parameter

    with pytest.raises(OMPython.ModelicaSystemError):
        mod.setSimulationOptions(thisOptionDoesNotExist=3)


def test_simulate_inputs(tmp_path):
    model_file = tmp_path / "M_input.mo"
    model_file.write_text("""
model M_input
Real x(start=0, fixed=true);
input Real u1;
input Real u2;
output Real y;
equation
der(x) = u1 + u2;
y = x;
end M_input;
""")
    mod = OMPython.ModelicaSystem()
    mod.model(
        file=model_file.as_posix(),
        name="M_input",
    )

    simOptions = {"stopTime": 1.0}
    mod.setSimulationOptions(**simOptions)

    # integrate zero (no setInputs call) - it should default to None -> 0
    assert mod.getInputs() == {
        "u1": None,
        "u2": None,
    }
    mod.simulate()
    y = mod.getSolutions("y")[0]
    assert np.isclose(y[-1], 0.0)

    # integrate a constant
    mod.setInputs(u1=2.5)
    assert mod.getInputs() == {
        "u1": [
            (0.0, 2.5),
            (1.0, 2.5),
        ],
        # u2 is set due to the call to simulate() above
        "u2": [
            (0.0, 0.0),
            (1.0, 0.0),
        ],
    }
    mod.simulate()
    y = mod.getSolutions("y")[0]
    assert np.isclose(y[-1], 2.5)

    # now let's integrate the sum of two ramps
    inputs = {"u1": [(0.0, 0.0), (0.5, 2), (1.0, 0)]}
    mod.setInputs(**inputs)
    assert mod.getInputs("u1") == [[
        (0.0, 0.0),
        (0.5, 2.0),
        (1.0, 0.0),
    ]]
    mod.simulate()
    y = mod.getSolutions("y")[0]
    assert np.isclose(y[-1], 1.0)

    # let's try some edge cases
    # unmatched startTime
    with pytest.raises(OMPython.ModelicaSystemError):
        mod.setInputs(u1=[(-0.5, 0.0), (1.0, 1)])
        mod.simulate()
    # unmatched stopTime
    with pytest.raises(OMPython.ModelicaSystemError):
        mod.setInputs(u1=[(0.0, 0.0), (0.5, 1)])
        mod.simulate()

    # Let's use both inputs, but each one with different number of
    # samples. This has an effect when generating the csv file.
    inputs = {
        "u1": [(0.0, 0), (1.0, 1)],
        "u2": [(0.0, 0), (0.25, 0.5), (0.5, 1.0), (1.0, 0)],
    }
    mod.setInputs(**inputs)
    csv_file = mod._createCSVData()
    assert pathlib.Path(csv_file).read_text() == """time,u1,u2,end
0.0,0.0,0.0,0
0.25,0.25,0.5,0
0.5,0.5,1.0,0
1.0,1.0,0.0,0
"""

    mod.simulate()
    y = mod.getSolutions("y")[0]
    assert np.isclose(y[-1], 1.0)

Web Proxy Viewer  |  New URL  |  Original Page