component-model


Namecomponent-model JSON
Version 0.1.0 PyPI version JSON
download
home_pageNone
SummaryConstructs a Functional Mockup Interface component model from a python script (fulfilling some requirements).
upload_time2024-11-08 11:42:13
maintainerNone
docs_urlNone
authorNone
requires_python>=3.10
licenseMIT License Copyright (c) 2024 [DNV](https://www.dnv.com) [open source](https://github.com/dnv-opensource) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
keywords fmi osp model simulation
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            Introduction
============
The package extends the `PythonFMU package <https://github.com/NTNU-IHB/PythonFMU>`_.
It includes the necessary modules to construct a component model according to the fmi, OSP and DNV-RP-0513 standards
with focus on the following features:

* seamless translation of a Python model to an FMU package with minimal overhead (definition of FMU interface)
* support of vector variables (numpy)
* support of variable units and display units
* support of range checking of variables

Features which facilitate `Assurance of Simulation Models, DNV-RP-0513 <https://standards.dnv.com/explorer/document/6A4F5922251B496B9216572C23730D33/2>`_
shall have a special focus in this package.

Installation
------------

``pip install component-model``


Getting Started
---------------
A new model can consist of any python code. To turn the python code into an FMU the following is necessary

#. The model code is wrapped into a Python class which inherits from `Model`
#. The exposed interface variables (model parameters, input- and output connectors) are defined as `Variable` objects
#. The `(model).do_step( time, dt)` function of the model class is extended with model internal code,
   i.e. model evolves from `time` to `time+dt`.
#. Calling the method `Model.build()` will then compile the FMU and package it into a suitable FMU file.

See the files `example_models/bouncing_ball.py` and `tests/test_make_bouncingBall.py` supplied with this package
as a simple example of this process. The first file defines the model class
and the second file demonstrates the process of making the FMU and using it within fmpy and OSP.


Usage example
-------------
This is another BouncingBall example, using 3D vectors and units.

.. code-block:: python

   from math import sqrt

   import numpy as np

   from component_model.model import Model
   from component_model.variable import Variable


   class BouncingBall3D(Model):
      """Another Python-based BouncingBall model, using PythonFMU to construct a FMU.

      Special features:

      * The ball has a 3-D vector as position and speed
      * As output variable the model estimates the next bouncing point
      * As input variables, the restitution coefficient `e`, the gravitational acceleration `g`
         and the initial speed can be changed.
      * Internal units are SI (m,s,rad)

      Args:
         pos (np.array)=(0,0,1): The 3-D position in of the ball at time [m]
         speed (np.array)=(1,0,0): The 3-D speed of the ball at time [m/s]
         g (float)=9.81: The gravitational acceleration [m/s^2]
         e (float)=0.9: The coefficient of restitution (dimensionless): |speed after| / |speed before| collision
         min_speed_z (float)=1e-6: The minimum speed in z-direction when bouncing stops [m/s]
      """

      def __init__(
         self,
         name: str = "BouncingBall3D",
         description="Another Python-based BouncingBall model, using Model and Variable to construct a FMU",
         pos: tuple = ("0 m", "0 m", "10 inch"),
         speed: tuple = ("1 m/s", "0 m/s", "0 m/s"),
         g: float = "9.81 m/s^2",
         e: float = 0.9,
         min_speed_z: float = 1e-6,
         **kwargs,
      ):
         super().__init__(name, description, author="DNV, SEACo project", **kwargs)
         self._pos = self._interface("pos", pos)
         self._speed = self._interface("speed", speed)
         self._g = self._interface("g", g)
         self.a = np.array((0, 0, -self.g), float)
         self._e = self._interface("e", e)
         self.min_speed_z = min_speed_z
         self.stopped = False
         self.time = 0.0
         self._p_bounce = self._interface("p_bounce", ("0m", "0m", "0m"))  # Note: 3D, but z always 0
         self.t_bounce, self.p_bounce = (-1.0, self.pos)  # provoke an update at simulation start

      def do_step(self, _, dt):
         """Perform a simulation step from `self.time` to `self.time + dt`.

         With respect to bouncing (self.t_bounce should be initialized to a negative value)
         .t_bounce <= .time: update .t_bounce
         .time < .t_bounce <= .time+dt: bouncing happens within time step
         .t_bounce > .time+dt: no bouncing. Just advance .pos and .speed
         """
         if not super().do_step(self.time, dt):
               return False
         if self.t_bounce < self.time:  # calculate first bounce
               self.t_bounce, self.p_bounce = self.next_bounce()
         while self.t_bounce <= self.time + dt:  # bounce happens within step or at border
               dt1 = self.t_bounce - self.time
               self.pos = self.p_bounce
               self.speed += self.a * dt1  # speed before bouncing
               self.speed[2] = -self.speed[2]  # speed after bouncing if e==1.0
               self.speed *= self.e  # speed reduction due to coefficient of restitution
               if self.speed[2] < self.min_speed_z:
                  self.stopped = True
                  self.a[2] = 0.0
                  self.speed[2] = 0.0
                  self.pos[2] = 0.0
               self.time += dt1  # jump to the exact bounce time
               dt -= dt1
               self.t_bounce, self.p_bounce = self.next_bounce()  # update to the next bounce
         if dt > 0:
               # print(f"pos={self.pos}, speed={self.speed}, a={self.a}, dt={dt}")
               self.pos += self.speed * dt + 0.5 * self.a * dt**2
               self.speed += self.a * dt
               self.time += dt
         if self.pos[2] < 0:
               self.pos[2] = 0
         return True

      def next_bounce(self):
         """Calculate time of next bounce and position where the ground will be hit,
         based on .time, .pos and .speed.
         """
         if self.stopped:  # stopped bouncing
               return (1e300, np.array((1e300, 1e300, 0), float))
         else:
               dt_bounce = (self.speed[2] + sqrt(self.speed[2] ** 2 + 2 * self.g * self.pos[2])) / self.g
               p_bounce = self.pos + self.speed * dt_bounce  # linear. not correct for z-direction!
               p_bounce[2] = 0
               return (self.time + dt_bounce, p_bounce)

      def setup_experiment(self, start: float):
         """Set initial (non-interface) variables."""
         super().setup_experiment(start)
         self.stopped = False
         self.time = start

      def exit_initialization_mode(self):
         """Initialize the model after initial variables are set."""
         super().exit_initialization_mode()
         self.a = np.array((0, 0, -self.g), float)

      def _interface(self, name: str, start: float | tuple):
         """Define a FMU2 interface variable, using the variable interface.

         Args:
               name (str): base name of the variable
               start (str|float|tuple): start value of the variable (optionally with units)

         Returns:
               the variable object. As a side effect the variable value is made available as self.<name>
         """
         if name == "pos":
               return Variable(
                  self,
                  name="pos",
                  description="The 3D position of the ball [m] (height in inch as displayUnit example.",
                  causality="output",
                  variability="continuous",
                  initial="exact",
                  start=start,
                  rng=((0, "100 m"), None, (0, "10 m")),
               )
         elif name == "speed":
               return Variable(
                  self,
                  name="speed",
                  description="The 3D speed of the ball, i.e. d pos / dt [m/s]",
                  causality="output",
                  variability="continuous",
                  initial="exact",
                  start=start,
                  rng=((0, "1 m/s"), None, ("-100 m/s", "100 m/s")),
               )
         elif name == "g":
               return Variable(
                  self,
                  name="g",
                  description="The gravitational acceleration (absolute value).",
                  causality="parameter",
                  variability="fixed",
                  start=start,
                  rng=(),
               )
         elif name == "e":
               return Variable(
                  self,
                  name="e",
                  description="The coefficient of restitution, i.e. |speed after| / |speed before| bounce.",
                  causality="parameter",
                  variability="fixed",
                  start=start,
                  rng=(),
               )
         elif name == "p_bounce":
               return Variable(
                  self,
                  name="p_bounce",
                  description="The expected position of the next bounce as 3D vector",
                  causality="output",
                  variability="continuous",
                  start=start,
                  rng=(),
               )



The following might be noted:

- The interface variables are defined in a separate local method ``_interface_variables``,
  keeping it separate from the model code.
- The ``do_step()`` method contains the essential code, describing how the ball moves through the air.
  It calls the ``super().do_step()`` method, which is essential to link it to ``Model``.
  The `return True` statement is also essential for the working of the emerging FMU.
- The ``next_bounce()`` method is a helper method.
- In addition to the extension of ``do_step()``, here also the ``setup_experiment()`` method is extended.
  Local (non-interface) variables can thus be initialized in a convenient way.

It should be self-evident that thorough testing of any model is necessary **before** translation to a FMU.
The simulation orchestration engine (e.g. OSP) used to run FMUs obfuscates error messages,
such that first stage assurance of a model should aways done using e.g. ``pytest``.
The minimal code to make the FMU file package is


.. code-block:: python

   from component_model.model import Model
   from fmpy.util import fmu_info

   asBuilt = Model.build("../component_model/example_models/bouncing_ball.py")
   info = fmu_info(asBuilt.name)  # not necessary, but it lists essential properties of the FMU


The model can then be run using `fmpy <https://pypi.org/project/FMPy/>`_

.. code-block:: python

   from fmpy import plot_result, simulate_fmu

   result = simulate_fmu(
      "BouncingBall.fmu",
      stop_time=3.0,
      step_size=0.1,
      validate=True,
      solver="Euler",
      debug_logging=True,
      logger=print,
      start_values={"pos[2]": 2}, # optional start value settings
   )
   plot_result(result)


Similarly, the model can be run using `OSP <https://opensimulationplatform.com/>`_
(or rather `libcosimpy <https://pypi.org/project/libcosimpy/>`_ - OSP wrapped into Python):

.. code-block:: Python

   from libcosimpy.CosimEnums import CosimExecutionState
   from libcosimpy.CosimExecution import CosimExecution
   from libcosimpy.CosimSlave import CosimLocalSlave

   sim = CosimExecution.from_step_size(step_size=1e7)  # empty execution object with fixed time step in nanos
   bb = CosimLocalSlave(fmu_path="./BouncingBall.fmu", instance_name="bb")

   print("SLAVE", bb, sim.status())

   ibb = sim.add_local_slave(bb)
   assert ibb == 0, f"local slave number {ibb}"

   reference_dict = {var_ref.name.decode(): var_ref.reference for var_ref in sim.slave_variables(ibb)}

   sim.real_initial_value(ibb, reference_dict["pos[2]"], 2.0)  # Set initial values

   sim_status = sim.status()
   assert sim_status.current_time == 0
   assert CosimExecutionState(sim_status.state) == CosimExecutionState.STOPPED
   infos = sim.slave_infos()
   print("INFOS", infos)

   sim.simulate_until(target_time=3e9)  # Simulate for 1 second


This is admittedly more complex than the ``fmpy`` example,
but it should be emphasised that fmpy is made for single component model simulation (testing),
while OSP is made for multi-component systems.


Development Setup
-----------------

1. Install uv
^^^^^^^^^^^^^
This project uses `uv` as package manager.

If you haven't already, install `uv <https://docs.astral.sh/uv/>`_, preferably using it's `"Standalone installer" <https://docs.astral.sh/uv/getting-started/installation/#__tabbed_1_2/>`_ method:

..on Windows:

``powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"``

..on MacOS and Linux:

``curl -LsSf https://astral.sh/uv/install.sh | sh``

(see `docs.astral.sh/uv <https://docs.astral.sh/uv/getting-started/installation//>`_ for all / alternative installation methods.)

Once installed, you can update `uv` to its latest version, anytime, by running:

``uv self update``

2. Install Python
^^^^^^^^^^^^^^^^^
This project requires Python 3.10 or later.

If you don't already have a compatible version installed on your machine, the probably most comfortable way to install Python is through ``uv``:

``uv python install``

This will install the latest stable version of Python into the uv Python directory, i.e. as a uv-managed version of Python.

Alternatively, and if you want a standalone version of Python on your machine, you can install Python either via ``winget``:

``winget install --id Python.Python``

or you can download and install Python from the `python.org <https://www.python.org/downloads//>`_ website.

3. Clone the repository
^^^^^^^^^^^^^^^^^^^^^^^
Clone the component-model repository into your local development directory:

``git clone https://github.com/dnv-opensource/component-model path/to/your/dev/component-model``

4. Install dependencies
^^^^^^^^^^^^^^^^^^^^^^^
Run ``uv sync`` to create a virtual environment and install all project dependencies into it:

``uv sync``

5. (Optional) Activate the virtual environment
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
When using ``uv``, there is in almost all cases no longer a need to manually activate the virtual environment.

``uv`` will find the ``.venv`` virtual environment in the working directory or any parent directory, and activate it on the fly whenever you run a command via `uv` inside your project folder structure:

``uv run <command>``

However, you still *can* manually activate the virtual environment if needed.
When developing in an IDE, for instance, this can in some cases be necessary depending on your IDE settings.
To manually activate the virtual environment, run one of the "known" legacy commands:

..on Windows:

``.venv\Scripts\activate.bat``

..on Linux:

``source .venv/bin/activate``

6. Install pre-commit hooks
^^^^^^^^^^^^^^^^^^^^^^^^^^^
The ``.pre-commit-config.yaml`` file in the project root directory contains a configuration for pre-commit hooks.
To install the pre-commit hooks defined therein in your local git repository, run:

``uv run pre-commit install``

All pre-commit hooks configured in ``.pre-commit-config.yam`` will now run each time you commit changes.

7. Test that the installation works
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
To test that the installation works, run pytest in the project root folder:

``uv run pytest``


Meta
----
Copyright (c) 2024 `DNV <https://www.dnv.com/>`_ AS. All rights reserved.

Siegfried Eisinger - siegfried.eisinger@dnv.com

Distributed under the MIT license. See `LICENSE <LICENSE.md/>`_ for more information.

`https://github.com/dnv-opensource/component-model <https://github.com/dnv-opensource/component-model/>`_

Contribute
----------
Anybody in the FMU and OSP community is welcome to contribute to this code, to make it better,
and especially including other features from model assurance,
as we firmly believe that trust in our models is needed
if we want to base critical decisions on the support from these models.

To contribute, follow these steps:

1. Fork it `<https://github.com/dnv-opensource/component-model/fork/>`_
2. Create an issue in your GitHub repo
3. Create your branch based on the issue number and type (``git checkout -b issue-name``)
4. Evaluate and stage the changes you want to commit (``git add -i``)
5. Commit your changes (``git commit -am 'place a descriptive commit message here'``)
6. Push to the branch (``git push origin issue-name``)
7. Create a new Pull Request in GitHub

For your contribution, please make sure you follow the `STYLEGUIDE <STYLEGUIDE.md/>`_ before creating the Pull Request.

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "component-model",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.10",
    "maintainer_email": null,
    "keywords": "FMI, OSP, model, simulation",
    "author": null,
    "author_email": "Siegfried Eisinger <Siegfried.Eisinger@dnv.com>",
    "download_url": "https://files.pythonhosted.org/packages/65/86/ff61f1a1d686cdcb658ca9d11c786bd140a2119724a6157c175304cb60c0/component_model-0.1.0.tar.gz",
    "platform": null,
    "description": "Introduction\n============\nThe package extends the `PythonFMU package <https://github.com/NTNU-IHB/PythonFMU>`_.\nIt includes the necessary modules to construct a component model according to the fmi, OSP and DNV-RP-0513 standards\nwith focus on the following features:\n\n* seamless translation of a Python model to an FMU package with minimal overhead (definition of FMU interface)\n* support of vector variables (numpy)\n* support of variable units and display units\n* support of range checking of variables\n\nFeatures which facilitate `Assurance of Simulation Models, DNV-RP-0513 <https://standards.dnv.com/explorer/document/6A4F5922251B496B9216572C23730D33/2>`_\nshall have a special focus in this package.\n\nInstallation\n------------\n\n``pip install component-model``\n\n\nGetting Started\n---------------\nA new model can consist of any python code. To turn the python code into an FMU the following is necessary\n\n#. The model code is wrapped into a Python class which inherits from `Model`\n#. The exposed interface variables (model parameters, input- and output connectors) are defined as `Variable` objects\n#. The `(model).do_step( time, dt)` function of the model class is extended with model internal code,\n   i.e. model evolves from `time` to `time+dt`.\n#. Calling the method `Model.build()` will then compile the FMU and package it into a suitable FMU file.\n\nSee the files `example_models/bouncing_ball.py` and `tests/test_make_bouncingBall.py` supplied with this package\nas a simple example of this process. The first file defines the model class\nand the second file demonstrates the process of making the FMU and using it within fmpy and OSP.\n\n\nUsage example\n-------------\nThis is another BouncingBall example, using 3D vectors and units.\n\n.. code-block:: python\n\n   from math import sqrt\n\n   import numpy as np\n\n   from component_model.model import Model\n   from component_model.variable import Variable\n\n\n   class BouncingBall3D(Model):\n      \"\"\"Another Python-based BouncingBall model, using PythonFMU to construct a FMU.\n\n      Special features:\n\n      * The ball has a 3-D vector as position and speed\n      * As output variable the model estimates the next bouncing point\n      * As input variables, the restitution coefficient `e`, the gravitational acceleration `g`\n         and the initial speed can be changed.\n      * Internal units are SI (m,s,rad)\n\n      Args:\n         pos (np.array)=(0,0,1): The 3-D position in of the ball at time [m]\n         speed (np.array)=(1,0,0): The 3-D speed of the ball at time [m/s]\n         g (float)=9.81: The gravitational acceleration [m/s^2]\n         e (float)=0.9: The coefficient of restitution (dimensionless): |speed after| / |speed before| collision\n         min_speed_z (float)=1e-6: The minimum speed in z-direction when bouncing stops [m/s]\n      \"\"\"\n\n      def __init__(\n         self,\n         name: str = \"BouncingBall3D\",\n         description=\"Another Python-based BouncingBall model, using Model and Variable to construct a FMU\",\n         pos: tuple = (\"0 m\", \"0 m\", \"10 inch\"),\n         speed: tuple = (\"1 m/s\", \"0 m/s\", \"0 m/s\"),\n         g: float = \"9.81 m/s^2\",\n         e: float = 0.9,\n         min_speed_z: float = 1e-6,\n         **kwargs,\n      ):\n         super().__init__(name, description, author=\"DNV, SEACo project\", **kwargs)\n         self._pos = self._interface(\"pos\", pos)\n         self._speed = self._interface(\"speed\", speed)\n         self._g = self._interface(\"g\", g)\n         self.a = np.array((0, 0, -self.g), float)\n         self._e = self._interface(\"e\", e)\n         self.min_speed_z = min_speed_z\n         self.stopped = False\n         self.time = 0.0\n         self._p_bounce = self._interface(\"p_bounce\", (\"0m\", \"0m\", \"0m\"))  # Note: 3D, but z always 0\n         self.t_bounce, self.p_bounce = (-1.0, self.pos)  # provoke an update at simulation start\n\n      def do_step(self, _, dt):\n         \"\"\"Perform a simulation step from `self.time` to `self.time + dt`.\n\n         With respect to bouncing (self.t_bounce should be initialized to a negative value)\n         .t_bounce <= .time: update .t_bounce\n         .time < .t_bounce <= .time+dt: bouncing happens within time step\n         .t_bounce > .time+dt: no bouncing. Just advance .pos and .speed\n         \"\"\"\n         if not super().do_step(self.time, dt):\n               return False\n         if self.t_bounce < self.time:  # calculate first bounce\n               self.t_bounce, self.p_bounce = self.next_bounce()\n         while self.t_bounce <= self.time + dt:  # bounce happens within step or at border\n               dt1 = self.t_bounce - self.time\n               self.pos = self.p_bounce\n               self.speed += self.a * dt1  # speed before bouncing\n               self.speed[2] = -self.speed[2]  # speed after bouncing if e==1.0\n               self.speed *= self.e  # speed reduction due to coefficient of restitution\n               if self.speed[2] < self.min_speed_z:\n                  self.stopped = True\n                  self.a[2] = 0.0\n                  self.speed[2] = 0.0\n                  self.pos[2] = 0.0\n               self.time += dt1  # jump to the exact bounce time\n               dt -= dt1\n               self.t_bounce, self.p_bounce = self.next_bounce()  # update to the next bounce\n         if dt > 0:\n               # print(f\"pos={self.pos}, speed={self.speed}, a={self.a}, dt={dt}\")\n               self.pos += self.speed * dt + 0.5 * self.a * dt**2\n               self.speed += self.a * dt\n               self.time += dt\n         if self.pos[2] < 0:\n               self.pos[2] = 0\n         return True\n\n      def next_bounce(self):\n         \"\"\"Calculate time of next bounce and position where the ground will be hit,\n         based on .time, .pos and .speed.\n         \"\"\"\n         if self.stopped:  # stopped bouncing\n               return (1e300, np.array((1e300, 1e300, 0), float))\n         else:\n               dt_bounce = (self.speed[2] + sqrt(self.speed[2] ** 2 + 2 * self.g * self.pos[2])) / self.g\n               p_bounce = self.pos + self.speed * dt_bounce  # linear. not correct for z-direction!\n               p_bounce[2] = 0\n               return (self.time + dt_bounce, p_bounce)\n\n      def setup_experiment(self, start: float):\n         \"\"\"Set initial (non-interface) variables.\"\"\"\n         super().setup_experiment(start)\n         self.stopped = False\n         self.time = start\n\n      def exit_initialization_mode(self):\n         \"\"\"Initialize the model after initial variables are set.\"\"\"\n         super().exit_initialization_mode()\n         self.a = np.array((0, 0, -self.g), float)\n\n      def _interface(self, name: str, start: float | tuple):\n         \"\"\"Define a FMU2 interface variable, using the variable interface.\n\n         Args:\n               name (str): base name of the variable\n               start (str|float|tuple): start value of the variable (optionally with units)\n\n         Returns:\n               the variable object. As a side effect the variable value is made available as self.<name>\n         \"\"\"\n         if name == \"pos\":\n               return Variable(\n                  self,\n                  name=\"pos\",\n                  description=\"The 3D position of the ball [m] (height in inch as displayUnit example.\",\n                  causality=\"output\",\n                  variability=\"continuous\",\n                  initial=\"exact\",\n                  start=start,\n                  rng=((0, \"100 m\"), None, (0, \"10 m\")),\n               )\n         elif name == \"speed\":\n               return Variable(\n                  self,\n                  name=\"speed\",\n                  description=\"The 3D speed of the ball, i.e. d pos / dt [m/s]\",\n                  causality=\"output\",\n                  variability=\"continuous\",\n                  initial=\"exact\",\n                  start=start,\n                  rng=((0, \"1 m/s\"), None, (\"-100 m/s\", \"100 m/s\")),\n               )\n         elif name == \"g\":\n               return Variable(\n                  self,\n                  name=\"g\",\n                  description=\"The gravitational acceleration (absolute value).\",\n                  causality=\"parameter\",\n                  variability=\"fixed\",\n                  start=start,\n                  rng=(),\n               )\n         elif name == \"e\":\n               return Variable(\n                  self,\n                  name=\"e\",\n                  description=\"The coefficient of restitution, i.e. |speed after| / |speed before| bounce.\",\n                  causality=\"parameter\",\n                  variability=\"fixed\",\n                  start=start,\n                  rng=(),\n               )\n         elif name == \"p_bounce\":\n               return Variable(\n                  self,\n                  name=\"p_bounce\",\n                  description=\"The expected position of the next bounce as 3D vector\",\n                  causality=\"output\",\n                  variability=\"continuous\",\n                  start=start,\n                  rng=(),\n               )\n\n\n\nThe following might be noted:\n\n- The interface variables are defined in a separate local method ``_interface_variables``,\n  keeping it separate from the model code.\n- The ``do_step()`` method contains the essential code, describing how the ball moves through the air.\n  It calls the ``super().do_step()`` method, which is essential to link it to ``Model``.\n  The `return True` statement is also essential for the working of the emerging FMU.\n- The ``next_bounce()`` method is a helper method.\n- In addition to the extension of ``do_step()``, here also the ``setup_experiment()`` method is extended.\n  Local (non-interface) variables can thus be initialized in a convenient way.\n\nIt should be self-evident that thorough testing of any model is necessary **before** translation to a FMU.\nThe simulation orchestration engine (e.g. OSP) used to run FMUs obfuscates error messages,\nsuch that first stage assurance of a model should aways done using e.g. ``pytest``.\nThe minimal code to make the FMU file package is\n\n\n.. code-block:: python\n\n   from component_model.model import Model\n   from fmpy.util import fmu_info\n\n   asBuilt = Model.build(\"../component_model/example_models/bouncing_ball.py\")\n   info = fmu_info(asBuilt.name)  # not necessary, but it lists essential properties of the FMU\n\n\nThe model can then be run using `fmpy <https://pypi.org/project/FMPy/>`_\n\n.. code-block:: python\n\n   from fmpy import plot_result, simulate_fmu\n\n   result = simulate_fmu(\n      \"BouncingBall.fmu\",\n      stop_time=3.0,\n      step_size=0.1,\n      validate=True,\n      solver=\"Euler\",\n      debug_logging=True,\n      logger=print,\n      start_values={\"pos[2]\": 2}, # optional start value settings\n   )\n   plot_result(result)\n\n\nSimilarly, the model can be run using `OSP <https://opensimulationplatform.com/>`_\n(or rather `libcosimpy <https://pypi.org/project/libcosimpy/>`_ - OSP wrapped into Python):\n\n.. code-block:: Python\n\n   from libcosimpy.CosimEnums import CosimExecutionState\n   from libcosimpy.CosimExecution import CosimExecution\n   from libcosimpy.CosimSlave import CosimLocalSlave\n\n   sim = CosimExecution.from_step_size(step_size=1e7)  # empty execution object with fixed time step in nanos\n   bb = CosimLocalSlave(fmu_path=\"./BouncingBall.fmu\", instance_name=\"bb\")\n\n   print(\"SLAVE\", bb, sim.status())\n\n   ibb = sim.add_local_slave(bb)\n   assert ibb == 0, f\"local slave number {ibb}\"\n\n   reference_dict = {var_ref.name.decode(): var_ref.reference for var_ref in sim.slave_variables(ibb)}\n\n   sim.real_initial_value(ibb, reference_dict[\"pos[2]\"], 2.0)  # Set initial values\n\n   sim_status = sim.status()\n   assert sim_status.current_time == 0\n   assert CosimExecutionState(sim_status.state) == CosimExecutionState.STOPPED\n   infos = sim.slave_infos()\n   print(\"INFOS\", infos)\n\n   sim.simulate_until(target_time=3e9)  # Simulate for 1 second\n\n\nThis is admittedly more complex than the ``fmpy`` example,\nbut it should be emphasised that fmpy is made for single component model simulation (testing),\nwhile OSP is made for multi-component systems.\n\n\nDevelopment Setup\n-----------------\n\n1. Install uv\n^^^^^^^^^^^^^\nThis project uses `uv` as package manager.\n\nIf you haven't already, install `uv <https://docs.astral.sh/uv/>`_, preferably using it's `\"Standalone installer\" <https://docs.astral.sh/uv/getting-started/installation/#__tabbed_1_2/>`_ method:\n\n..on Windows:\n\n``powershell -ExecutionPolicy ByPass -c \"irm https://astral.sh/uv/install.ps1 | iex\"``\n\n..on MacOS and Linux:\n\n``curl -LsSf https://astral.sh/uv/install.sh | sh``\n\n(see `docs.astral.sh/uv <https://docs.astral.sh/uv/getting-started/installation//>`_ for all / alternative installation methods.)\n\nOnce installed, you can update `uv` to its latest version, anytime, by running:\n\n``uv self update``\n\n2. Install Python\n^^^^^^^^^^^^^^^^^\nThis project requires Python 3.10 or later.\n\nIf you don't already have a compatible version installed on your machine, the probably most comfortable way to install Python is through ``uv``:\n\n``uv python install``\n\nThis will install the latest stable version of Python into the uv Python directory, i.e. as a uv-managed version of Python.\n\nAlternatively, and if you want a standalone version of Python on your machine, you can install Python either via ``winget``:\n\n``winget install --id Python.Python``\n\nor you can download and install Python from the `python.org <https://www.python.org/downloads//>`_ website.\n\n3. Clone the repository\n^^^^^^^^^^^^^^^^^^^^^^^\nClone the component-model repository into your local development directory:\n\n``git clone https://github.com/dnv-opensource/component-model path/to/your/dev/component-model``\n\n4. Install dependencies\n^^^^^^^^^^^^^^^^^^^^^^^\nRun ``uv sync`` to create a virtual environment and install all project dependencies into it:\n\n``uv sync``\n\n5. (Optional) Activate the virtual environment\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\nWhen using ``uv``, there is in almost all cases no longer a need to manually activate the virtual environment.\n\n``uv`` will find the ``.venv`` virtual environment in the working directory or any parent directory, and activate it on the fly whenever you run a command via `uv` inside your project folder structure:\n\n``uv run <command>``\n\nHowever, you still *can* manually activate the virtual environment if needed.\nWhen developing in an IDE, for instance, this can in some cases be necessary depending on your IDE settings.\nTo manually activate the virtual environment, run one of the \"known\" legacy commands:\n\n..on Windows:\n\n``.venv\\Scripts\\activate.bat``\n\n..on Linux:\n\n``source .venv/bin/activate``\n\n6. Install pre-commit hooks\n^^^^^^^^^^^^^^^^^^^^^^^^^^^\nThe ``.pre-commit-config.yaml`` file in the project root directory contains a configuration for pre-commit hooks.\nTo install the pre-commit hooks defined therein in your local git repository, run:\n\n``uv run pre-commit install``\n\nAll pre-commit hooks configured in ``.pre-commit-config.yam`` will now run each time you commit changes.\n\n7. Test that the installation works\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\nTo test that the installation works, run pytest in the project root folder:\n\n``uv run pytest``\n\n\nMeta\n----\nCopyright (c) 2024 `DNV <https://www.dnv.com/>`_ AS. All rights reserved.\n\nSiegfried Eisinger - siegfried.eisinger@dnv.com\n\nDistributed under the MIT license. See `LICENSE <LICENSE.md/>`_ for more information.\n\n`https://github.com/dnv-opensource/component-model <https://github.com/dnv-opensource/component-model/>`_\n\nContribute\n----------\nAnybody in the FMU and OSP community is welcome to contribute to this code, to make it better,\nand especially including other features from model assurance,\nas we firmly believe that trust in our models is needed\nif we want to base critical decisions on the support from these models.\n\nTo contribute, follow these steps:\n\n1. Fork it `<https://github.com/dnv-opensource/component-model/fork/>`_\n2. Create an issue in your GitHub repo\n3. Create your branch based on the issue number and type (``git checkout -b issue-name``)\n4. Evaluate and stage the changes you want to commit (``git add -i``)\n5. Commit your changes (``git commit -am 'place a descriptive commit message here'``)\n6. Push to the branch (``git push origin issue-name``)\n7. Create a new Pull Request in GitHub\n\nFor your contribution, please make sure you follow the `STYLEGUIDE <STYLEGUIDE.md/>`_ before creating the Pull Request.\n",
    "bugtrack_url": null,
    "license": "MIT License  Copyright (c) 2024 [DNV](https://www.dnv.com) [open source](https://github.com/dnv-opensource)  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:  The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.  THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.",
    "summary": "Constructs a Functional Mockup Interface component model from a python script (fulfilling some requirements).",
    "version": "0.1.0",
    "project_urls": {
        "Changelog": "https://github.com/dnv-innersource/component-model/blob/main/CHANGELOG.md",
        "Documentation": "https://dnv-innersource.github.io/component-model/README.html",
        "Homepage": "https://github.com/dnv-innersource/component-model",
        "Issues": "https://github.com/dnv-innersource/component-model/issues",
        "Repository": "https://github.com/dnv-innersource/component-model.git"
    },
    "split_keywords": [
        "fmi",
        " osp",
        " model",
        " simulation"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "b5e499bd295cab04e0ff38dea506073602e3c9076d6367c89a63ff21305d4ed9",
                "md5": "289c4af30ea07f5684a5f522c3a619e0",
                "sha256": "83ce0e5c6a6e0bee5271976761128e01354b3c651036d5ed5fd866d9bd8cff4e"
            },
            "downloads": -1,
            "filename": "component_model-0.1.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "289c4af30ea07f5684a5f522c3a619e0",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.10",
            "size": 43339,
            "upload_time": "2024-11-08T11:42:10",
            "upload_time_iso_8601": "2024-11-08T11:42:10.711352Z",
            "url": "https://files.pythonhosted.org/packages/b5/e4/99bd295cab04e0ff38dea506073602e3c9076d6367c89a63ff21305d4ed9/component_model-0.1.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "6586ff61f1a1d686cdcb658ca9d11c786bd140a2119724a6157c175304cb60c0",
                "md5": "aa0ca80754a20d148d3f5761ebba7932",
                "sha256": "23e80e36afed81eaba5e786fece2b54e0d23401713a02c6ef7b7b63a33fa6308"
            },
            "downloads": -1,
            "filename": "component_model-0.1.0.tar.gz",
            "has_sig": false,
            "md5_digest": "aa0ca80754a20d148d3f5761ebba7932",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.10",
            "size": 842939,
            "upload_time": "2024-11-08T11:42:13",
            "upload_time_iso_8601": "2024-11-08T11:42:13.340329Z",
            "url": "https://files.pythonhosted.org/packages/65/86/ff61f1a1d686cdcb658ca9d11c786bd140a2119724a6157c175304cb60c0/component_model-0.1.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-11-08 11:42:13",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "dnv-innersource",
    "github_project": "component-model",
    "github_not_found": true,
    "lcname": "component-model"
}
        
Elapsed time: 0.37830s