Repository: fossilfree/numerous (language: python)

Issue:
Refactor
Refactored 3 methods in a functional way; 2 in scope.py, 1 in base_equation.py

Added a Makefile with rules to run the test suite; 
Corrected the test files with the appropriate paths for the modules

Added a .gitignore that ignores the .pyc files

All tests pass, however there are 15 warnings in the tests (that are related to pytest); not sure if they are related with the project.
Readme update

Fix event accuracy with external updates and correct external updates
The numerous solver has been refactored into _solver method.

Event detection accuracy has been increased and updating with chunks have been fixed. 
Tests have been added in test_external_data.py to increase code coverage.
Refactor
Refactored 3 methods in a functional way; 2 in scope.py, 1 in base_equation.py

Added a Makefile with rules to run the test suite; 
Corrected the test files with the appropriate paths for the modules

Added a .gitignore that ignores the .pyc files

All tests pass, however there are 15 warnings in the tests (that are related to pytest); not sure if they are related with the project.


Discussion / hints:

Fix patch:
diff --git a/.circleci/config.yml b/.circleci/config.yml
index cc20b003..e30afd78 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -2,16 +2,30 @@ version: 2.1
 jobs:
   build:
     docker:
-        - image: cimg/python:3.10
-
+     - image: cimg/base:current-22.04
+    parallelism: 8
     steps:
       - checkout
 
+
       - run:
           name: Install packages
           command: |
-             pip3 install -r requirements.txt
-             pip3 install -r linters/linter_requirements.txt
+            sudo apt-get update
+            sudo apt-get -y install python3-pip
+            pip3 install -r requirements.txt
+            pip3 install -r linters/linter_requirements.txt
+
+      - run:
+          name: Tests
+          parallel: true
+          command: |
+            TESTFILES=$(circleci tests glob tests/{test*.py,*/test*.py} | circleci tests split --split-by=timings)
+            mkdir test-results
+            python3 -m pytest $TESTFILES --junitxml=test-results/junit.xml
+            
+      - store_test_results:
+          path: test-results
 
       - run:
           name: Linters
@@ -19,10 +33,6 @@ jobs:
             branch=$(python3 linters/get_base_branch.py)
             git diff -U0 origin/${branch} | lint-diffs
 
-      - run:
-          name: Tests
-          command: |
-            python3 -m pytest
       - run:
           command: cat /sys/fs/cgroup/memory/memory.max_usage_in_bytes
           when: always
diff --git a/.flake8 b/.flake8
index e027e037..b4685919 100644
--- a/.flake8
+++ b/.flake8
@@ -11,5 +11,11 @@ ignore =
     ANN002,
     ANN003,
     R504,
+    F401,
+    F403,
+    W605,
+    E127,
+    E122,
+
 show-source = true
 suppress-none-returning=true
diff --git a/Dockerfile b/Dockerfile
index eb782d59..4d504224 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,8 +1,5 @@
 FROM ubuntu:latest
 
-RUN echo "deb http://archive.ubuntu.com/ubuntu bionic main universe" >> /etc/apt/sources.list &&\
-	echo "deb http://archive.ubuntu.com/ubuntu bionic-security main universe" >> /etc/apt/sources.list &&\
-	echo "deb http://archive.ubuntu.com/ubuntu bionic-updates main universe" >> /etc/apt/sources.list
 RUN apt update
 
 RUN apt-get install python3 python3-pip -y
diff --git a/README.md b/README.md
index 6b93d3d0..7d5cf5ae 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-<img src="https://numerous.com/resources/numerous-logo.svg" width="400" height="100">
+<img src="https://framerusercontent.com/modules/0NBlkpwGRAx5GVr0Qowa/WoxIE6GzMmPgU6UV3heH/assets/JlHMAnfxSjyEebKRUvDVAubjBdE.jpg" width="400" height="200">
 
 [![CircleCI](https://circleci.com/gh/fossilfree/numerous.svg?style=shield)](https://circleci.com/gh/fossilfree/numerous)
 ---------------------------------------------------------------------------------------------------
diff --git a/benchmark/tst.py b/benchmark/tst.py
index b34d6afc..18004b1d 100644
--- a/benchmark/tst.py
+++ b/benchmark/tst.py
@@ -159,11 +159,9 @@ def timeit(s):
         m = Model(ThermalCapacitancesSeries("tcs", num_nodes=i, Tinit=Tinit, T0=T0, k=k))
 
         # Define simulation
-        s_ns = Simulation(m, t_start=0, t_stop=1000, num=1000, num_inner=1, solver_type=SolverType.NUMEROUS,
-                       method=method_ns)
+        s_ns = Simulation(m, t_start=0, t_stop=1000, num=1000, num_inner=1, method=method_ns)
 
-        s_scipy = Simulation(m, t_start=0, t_stop=1000, num=1000, num_inner=1, solver_type=SolverType.SOLVER_IVP,
-                       method=method_scipy)
+        s_scipy = Simulation(m, t_start=0, t_stop=1000, num=1000, num_inner=1, method=method_scipy)
         #
         # solve simulation
         dt_ns = timeit(s_ns)
diff --git a/examples/FMU_usage/example_FMU.py b/examples/FMU_usage/example_FMU.py
new file mode 100644
index 00000000..4369b832
--- /dev/null
+++ b/examples/FMU_usage/example_FMU.py
@@ -0,0 +1,39 @@
+import numpy as np
+from matplotlib import pyplot as plt
+
+from numerous.engine.model import Model
+from numerous.engine.simulation import Simulation
+from numerous.engine.system import Subsystem
+from numerous.engine.system.fmu_subsystem import FMU_Subsystem
+
+
+class S3(Subsystem):
+    def __init__(self, tag):
+        super().__init__(tag)
+
+        fmu_filename = '_01_modex_Linux.fmu'
+        fmu_subsystem = FMU_Subsystem(fmu_filename, "PCU_RHU", debug_output=True)
+        # fmu_subsystem.t1.variables["h"].value = 19
+        self.register_items([fmu_subsystem])
+
+
+subsystem1 = S3('q1')
+
+m1 = Model(subsystem1, use_llvm=True)
+s = Simulation(
+    m1, t_start=0, t_stop=0.2, num=100, num_inner=1, max_step=.1)
+
+s.solve()
+
+fig, ax = plt.subplots()
+
+y1 = np.array(m1.historian_df['q1.PCU_RHU.t1.P_out'])
+t = np.array(m1.historian_df["time"])
+ax.plot(t, y1)
+
+ax.set(xlabel='time (s)', ylabel='outputs', title='Something')
+ax.grid()
+
+plt.show()
+
+print("execution finished")
diff --git a/examples/Static_data.py b/examples/Static_data.py
deleted file mode 100644
index e69de29b..00000000
diff --git a/examples/Two_Tanks_System/TwoTanks_System.ipynb b/examples/Two_Tanks_System/TwoTanks_System.ipynb
index 5409925a..d592e594 100644
--- a/examples/Two_Tanks_System/TwoTanks_System.ipynb
+++ b/examples/Two_Tanks_System/TwoTanks_System.ipynb
@@ -2,14 +2,22 @@
  "cells": [
   {
    "cell_type": "markdown",
-   "metadata": {},
+   "metadata": {
+    "pycharm": {
+     "name": "#%% md\n"
+    }
+   },
    "source": [
     "# Tutorial 2 - Two tanks system"
    ]
   },
   {
    "cell_type": "markdown",
-   "metadata": {},
+   "metadata": {
+    "pycharm": {
+     "name": "#%% md\n"
+    }
+   },
    "source": [
     "## Background information\n",
     "This tutorial shows how to use _numerous_ to create a system of multiple components.\n",
@@ -24,7 +32,11 @@
   },
   {
    "cell_type": "markdown",
-   "metadata": {},
+   "metadata": {
+    "pycharm": {
+     "name": "#%% md\n"
+    }
+   },
    "source": [
     "## System governing equations\n",
     "\n",
@@ -72,7 +84,11 @@
   },
   {
    "cell_type": "markdown",
-   "metadata": {},
+   "metadata": {
+    "pycharm": {
+     "name": "#%% md\n"
+    }
+   },
    "source": [
     "## Analytical solution\n",
     "The system of differential equations above can be quite complex to solve analytically given the time dependence of $\\mathrm{C_v}$. Therefore, a numerical solver can be used to solve the system, and we will show in this tutorial how to create a model and to solve it using _numerous_.\n",
@@ -108,7 +124,11 @@
   },
   {
    "cell_type": "markdown",
-   "metadata": {},
+   "metadata": {
+    "pycharm": {
+     "name": "#%% md\n"
+    }
+   },
    "source": [
     "## Input data\n",
     "The input data used for the analytical solution and the simulation are reported below:\n",
@@ -138,8 +158,12 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 29,
-   "metadata": {},
+   "execution_count": null,
+   "metadata": {
+    "pycharm": {
+     "name": "#%%\n"
+    }
+   },
    "outputs": [],
    "source": [
     "import numpy as np\n",
@@ -165,7 +189,11 @@
   },
   {
    "cell_type": "markdown",
-   "metadata": {},
+   "metadata": {
+    "pycharm": {
+     "name": "#%% md\n"
+    }
+   },
    "source": [
     "### Analytical soltion: Python code\n",
     "First of all, we define a function which is able to return the analytical solution for a given time span, given the input parameters. The implementation below is based on the analytical solution derived above."
@@ -173,8 +201,12 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 30,
-   "metadata": {},
+   "execution_count": null,
+   "metadata": {
+    "pycharm": {
+     "name": "#%%\n"
+    }
+   },
    "outputs": [],
    "source": [
     "import pandas as pd\n",
@@ -217,157 +249,24 @@
   },
   {
    "cell_type": "markdown",
-   "metadata": {},
+   "metadata": {
+    "pycharm": {
+     "name": "#%% md\n"
+    }
+   },
    "source": [
     "We can run the equation to get the analytical soluton for the input data defined above, and we can create a table (using pandas data frame) containing the analytical solution at each evaluated time. The code is shown below."
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 31,
-   "metadata": {},
-   "outputs": [
-    {
-     "data": {
-      "text/html": [
-       "<div>\n",
-       "<style scoped>\n",
-       "    .dataframe tbody tr th:only-of-type {\n",
-       "        vertical-align: middle;\n",
-       "    }\n",
-       "\n",
-       "    .dataframe tbody tr th {\n",
-       "        vertical-align: top;\n",
-       "    }\n",
-       "\n",
-       "    .dataframe thead th {\n",
-       "        text-align: right;\n",
-       "    }\n",
-       "</style>\n",
-       "<table border=\"1\" class=\"dataframe\">\n",
-       "  <thead>\n",
-       "    <tr style=\"text-align: right;\">\n",
-       "      <th></th>\n",
-       "      <th>Time, s</th>\n",
-       "      <th>H1, m</th>\n",
-       "      <th>H2, m</th>\n",
-       "      <th>M1, kg</th>\n",
-       "      <th>M2, kg</th>\n",
-       "    </tr>\n",
-       "  </thead>\n",
-       "  <tbody>\n",
-       "    <tr>\n",
-       "      <th>0</th>\n",
-       "      <td>0.0</td>\n",
-       "      <td>0.500000</td>\n",
-       "      <td>0.000000</td>\n",
-       "      <td>25.000000</td>\n",
-       "      <td>0.000000</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>1</th>\n",
-       "      <td>1.0</td>\n",
-       "      <td>0.369739</td>\n",
-       "      <td>0.065131</td>\n",
-       "      <td>18.486929</td>\n",
-       "      <td>6.513071</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>2</th>\n",
-       "      <td>2.0</td>\n",
-       "      <td>0.259097</td>\n",
-       "      <td>0.120451</td>\n",
-       "      <td>12.954859</td>\n",
-       "      <td>12.045141</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>3</th>\n",
-       "      <td>3.0</td>\n",
-       "      <td>0.168076</td>\n",
-       "      <td>0.165962</td>\n",
-       "      <td>8.403788</td>\n",
-       "      <td>16.596212</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>4</th>\n",
-       "      <td>4.0</td>\n",
-       "      <td>0.096674</td>\n",
-       "      <td>0.201663</td>\n",
-       "      <td>4.833718</td>\n",
-       "      <td>20.166282</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>5</th>\n",
-       "      <td>5.0</td>\n",
-       "      <td>0.044893</td>\n",
-       "      <td>0.227554</td>\n",
-       "      <td>2.244647</td>\n",
-       "      <td>22.755353</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>6</th>\n",
-       "      <td>6.0</td>\n",
-       "      <td>0.012732</td>\n",
-       "      <td>0.243634</td>\n",
-       "      <td>0.636577</td>\n",
-       "      <td>24.363423</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>7</th>\n",
-       "      <td>7.0</td>\n",
-       "      <td>0.000190</td>\n",
-       "      <td>0.249905</td>\n",
-       "      <td>0.009506</td>\n",
-       "      <td>24.990494</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>8</th>\n",
-       "      <td>8.0</td>\n",
-       "      <td>0.000000</td>\n",
-       "      <td>0.250000</td>\n",
-       "      <td>0.000000</td>\n",
-       "      <td>25.000000</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>9</th>\n",
-       "      <td>9.0</td>\n",
-       "      <td>0.000000</td>\n",
-       "      <td>0.250000</td>\n",
-       "      <td>0.000000</td>\n",
-       "      <td>25.000000</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>10</th>\n",
-       "      <td>10.0</td>\n",
-       "      <td>0.000000</td>\n",
-       "      <td>0.250000</td>\n",
-       "      <td>0.000000</td>\n",
-       "      <td>25.000000</td>\n",
-       "    </tr>\n",
-       "  </tbody>\n",
-       "</table>\n",
-       "</div>"
-      ],
-      "text/plain": [
-       "    Time, s     H1, m     H2, m     M1, kg     M2, kg\n",
-       "0       0.0  0.500000  0.000000  25.000000   0.000000\n",
-       "1       1.0  0.369739  0.065131  18.486929   6.513071\n",
-       "2       2.0  0.259097  0.120451  12.954859  12.045141\n",
-       "3       3.0  0.168076  0.165962   8.403788  16.596212\n",
-       "4       4.0  0.096674  0.201663   4.833718  20.166282\n",
-       "5       5.0  0.044893  0.227554   2.244647  22.755353\n",
-       "6       6.0  0.012732  0.243634   0.636577  24.363423\n",
-       "7       7.0  0.000190  0.249905   0.009506  24.990494\n",
-       "8       8.0  0.000000  0.250000   0.000000  25.000000\n",
-       "9       9.0  0.000000  0.250000   0.000000  25.000000\n",
-       "10     10.0  0.000000  0.250000   0.000000  25.000000"
-      ]
-     },
-     "execution_count": 31,
-     "metadata": {},
-     "output_type": "execute_result"
-    }
-   ],
+   "execution_count": null,
+   "metadata": {
+    "pycharm": {
+     "name": "#%%\n"
+    }
+   },
+   "outputs": [],
    "source": [
     "result_analytical = analytical_solution(g=g, A0=A0, M0=M0, rho_water = rho_water, N_t=N_t, time_range=time_range, Cv_0=Cv_0)\n",
     "\n",
@@ -378,7 +277,11 @@
   },
   {
    "cell_type": "markdown",
-   "metadata": {},
+   "metadata": {
+    "pycharm": {
+     "name": "#%% md\n"
+    }
+   },
    "source": [
     "## Implementation using _numerous_\n",
     "\n",
@@ -397,9 +300,10 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 33,
+   "execution_count": null,
    "metadata": {
     "pycharm": {
+     "is_executing": true,
      "name": "#%%\n"
     }
    },
@@ -419,7 +323,11 @@
   },
   {
    "cell_type": "markdown",
-   "metadata": {},
+   "metadata": {
+    "pycharm": {
+     "name": "#%% md\n"
+    }
+   },
    "source": [
     "### Define Tank Equation\n",
     "The first item that we will model is the tank. Before creating the tank item, we need to define the equation to apply, and we thus create a Tank_Equation item using Equation class. We need to define all the parameters and constants of the equation, by using the method ` Equation.add_parameter ` .  _g_ (gravitational accelleration) is the only variable defined as a constant, as its value cannot be modified.\n",
@@ -435,8 +343,13 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 34,
-   "metadata": {},
+   "execution_count": null,
+   "metadata": {
+    "pycharm": {
+     "is_executing": true,
+     "name": "#%%\n"
+    }
+   },
    "outputs": [],
    "source": [
     "class Tank_Equation(EquationBase):\n",
@@ -457,7 +370,11 @@
   },
   {
    "cell_type": "markdown",
-   "metadata": {},
+   "metadata": {
+    "pycharm": {
+     "name": "#%% md\n"
+    }
+   },
    "source": [
     "### Define Valve Equation\n",
     "The second item that we will model is the valve. Before creating the valve item, we need to define the equation to apply, and we thus create a `Valve_Equation` item using `EquationBase` class.\n",
@@ -469,9 +386,10 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 35,
+   "execution_count": null,
    "metadata": {
     "pycharm": {
+     "is_executing": true,
      "name": "#%% Define the equation for the valve \n"
     }
    },
@@ -489,24 +407,30 @@
     "        self.add_parameter('H1', 0)          # [m]         Liquid height in the tank 1 connected to the valve (top tank)\n",
     "        self.add_parameter('H2', 0)          # [m]         Liquid height in the tank 2 connected to the valve (bottom tank)\n",
     "        self.add_parameter('rho', rho)       # [kg/m3]     Liquid density\n",
-    "        self.add_constant('g', g)            # [m/s2]      Gravitational acceleration\n",
+    "        self.add_constant('_g', g)            # [m/s2]      Gravitational acceleration\n",
     "        self.add_parameter('Cv', Cv_0)       # [(kg/s)/Pa] Valve characteristic parameter\n",
+    "        self.add_state('time', 0)\n",
     "        \n",
     "    @Equation()\n",
-    "    def eval(self,scope,global_variables):\n",
-    "        scope.Cv      = scope.Cv_0 * (np.sin(scope.omega * global_variables.time)+1)           #[(kg/s)/Pa]\n",
-    "        deltaP        = scope.rho * scope.g * (scope.H1)                                  #[Pa]\n",
+    "    def eval(self,scope):\n",
+    "        scope.Cv      = scope.Cv_0 * (np.sin(scope.omega * scope.time)+1)           #[(kg/s)/Pa]\n",
+    "        deltaP        = scope.rho * scope._g * (scope.H1)                                  #[Pa]\n",
     "        mdot          = np.sign(deltaP) * np.sqrt(np.absolute(deltaP)) * scope.Cv         #[kg/s]\n",
     "        # The valve will be associated with two mass flow rates (one leaving and one entering the component), \n",
     "        #which - for conservation of mass - have the same magnitude and opposite sign\n",
     "        scope.mdot1   = -mdot                                                             #[kg/s]\n",
     "        scope.mdot2   = mdot                                                              #[kg/s]\n",
+    "        scope.time_dot = 1\n",
     "        "
    ]
   },
   {
    "cell_type": "markdown",
-   "metadata": {},
+   "metadata": {
+    "pycharm": {
+     "name": "#%% md\n"
+    }
+   },
    "source": [
     "### Define Tank as Item\n",
     "We define the `Tank` class as an `Item` class. We then create a namespace 'v1' to contain the variables for the `Tank_Equation`.\n",
@@ -515,8 +439,13 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 36,
-   "metadata": {},
+   "execution_count": null,
+   "metadata": {
+    "pycharm": {
+     "is_executing": true,
+     "name": "#%%\n"
+    }
+   },
    "outputs": [],
    "source": [
     "class Tank(Item):\n",
@@ -529,7 +458,11 @@
   },
   {
    "cell_type": "markdown",
-   "metadata": {},
+   "metadata": {
+    "pycharm": {
+     "name": "#%% md\n"
+    }
+   },
    "source": [
     "### Define Valve as ConnectorTwoWay\n",
     "Once that we have defined the equation describing the mass flow rate flowing through the valve, we need to create the Valve as a class `ConnectorTwoWay` and to assign an equation to it.\n",
@@ -551,9 +484,10 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 37,
+   "execution_count": null,
    "metadata": {
     "pycharm": {
+     "is_executing": true,
      "name": "#%% \n"
     }
    },
@@ -588,7 +522,11 @@
   },
   {
    "cell_type": "markdown",
-   "metadata": {},
+   "metadata": {
+    "pycharm": {
+     "name": "#%% md\n"
+    }
+   },
    "source": [
     "###  Create the sub-system of components \n",
     "After defining all the classes for the items that will consitute the system, we are ready for the system assembly.\n",
@@ -607,9 +545,10 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 38,
+   "execution_count": null,
    "metadata": {
     "pycharm": {
+     "is_executing": true,
      "name": "#%%\n"
     }
    },
@@ -639,7 +578,11 @@
   },
   {
    "cell_type": "markdown",
-   "metadata": {},
+   "metadata": {
+    "pycharm": {
+     "name": "#%% md\n"
+    }
+   },
    "source": [
     "###  Create the system model and simulation\n",
     "Finally we are ready to define the model and the simulation of the implemented system, and we do it by creating a function named `t_1_item_model`. The inputs to the function are given by:\n",
@@ -655,9 +598,10 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 39,
+   "execution_count": null,
    "metadata": {
     "pycharm": {
+     "is_executing": true,
      "name": "#%%\n"
     }
    },
@@ -676,7 +620,11 @@
   },
   {
    "cell_type": "markdown",
-   "metadata": {},
+   "metadata": {
+    "pycharm": {
+     "name": "#%% md\n"
+    }
+   },
    "source": [
     "## Running the simulations - Case 1: Constant Cv \n",
     "We impose a Cv to be constant in time by setting the angular frequency $\\omega$ to be equal to 0 (i.e. the cosine of 0 will be equal to 1 at each time step).\n",
@@ -686,90 +634,14 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 40,
+   "execution_count": null,
    "metadata": {
     "pycharm": {
+     "is_executing": true,
      "name": "#%%\n"
     }
    },
-   "outputs": [
-    {
-     "name": "stderr",
-     "output_type": "stream",
-     "text": [
-      "2021-08-03 16:49:52 INFO     Assembling numerous Model\n",
-      "2021-08-03 16:49:52 INFO     parsing equations starting\n",
-      "2021-08-03 16:49:52 INFO     parsing equations completed\n",
-      "2021-08-03 16:49:52 INFO     process mappings\n",
-      "2021-08-03 16:49:52 INFO     clone eq graph\n",
-      "2021-08-03 16:49:52 INFO     Cleaning eq graph\n",
-      "2021-08-03 16:49:52 INFO     remove dependencies\n",
-      "2021-08-03 16:49:52 INFO     cleaning\n",
-      "2021-08-03 16:49:52 INFO     done cleaning\n",
-      "2021-08-03 16:49:52 INFO     Mappings processed\n",
-      "2021-08-03 16:49:52 INFO     variables sorted\n",
-      "3it [00:00, 9265.77it/s]\n",
-      "2021-08-03 16:49:52 INFO     lowering model\n",
-      "2021-08-03 16:49:52 INFO     Starting topological sort\n",
-      "2021-08-03 16:49:55 INFO     Cleaning eq graph\n",
-      "2021-08-03 16:49:55 INFO     make equations for compilation\n",
-      "2021-08-03 16:49:55 INFO     Starting topological sort\n",
-      "2021-08-03 16:49:55 INFO     Starting topological sort\n"
-     ]
-    },
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "target data:  e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128   \n",
-      " None\n",
-      "target triple:  x86_64-unknown-linux-gnu   \n",
-      " None\n"
-     ]
-    },
-    {
-     "ename": "TypingError",
-     "evalue": "Failed in nopython mode pipeline (step: nopython frontend)\nNameError: name 'global_variables' is not defined",
-     "output_type": "error",
-     "traceback": [
-      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
-      "\u001b[0;31mTypingError\u001b[0m                               Traceback (most recent call last)",
-      "\u001b[0;32m/tmp/ipykernel_30998/879279998.py\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[1;32m      2\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m      3\u001b[0m \u001b[0;31m# Collect the historical data at each time step\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 4\u001b[0;31m \u001b[0ms1\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msol\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mt_1_item_model\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mH0\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mCv_0\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0momega\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mt_start_sim\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mt_stop_sim\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
-      "\u001b[0;32m/tmp/ipykernel_30998/459329669.py\u001b[0m in \u001b[0;36mt_1_item_model\u001b[0;34m(H0, Cv_0, omega, t_start_sim, t_stop_sim)\u001b[0m\n\u001b[1;32m      2\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m      3\u001b[0m     \u001b[0;31m# 1. Instantiate the model\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 4\u001b[0;31m     \u001b[0mm1\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mModel\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mTwo_Tanks\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'subsystem'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mH0\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mCv_0\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0momega\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m      5\u001b[0m     \u001b[0;31m# 2. Setting up the simulation:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m      6\u001b[0m     \u001b[0ms1\u001b[0m  \u001b[0;34m=\u001b[0m \u001b[0mSimulation\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mm1\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mt_start\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mt_start_sim\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mt_stop\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mt_stop_sim\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mnum\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m5000\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
-      "\u001b[0;32m~/Source/numerous_github/numerousv2/numerous/numerous/engine/model/model.py\u001b[0m in \u001b[0;36m__init__\u001b[0;34m(self, system, logger_level, historian_filter, assemble, validate, external_mappings, data_loader, imports, historian, use_llvm, generate_graph_pdf)\u001b[0m\n\u001b[1;32m    167\u001b[0m         \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0minfo\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m{\u001b[0m\u001b[0;34m}\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    168\u001b[0m         \u001b[0;32mif\u001b[0m \u001b[0massemble\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 169\u001b[0;31m             \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0massemble\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m    170\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    171\u001b[0m         \u001b[0;32mif\u001b[0m \u001b[0mvalidate\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
-      "\u001b[0;32m~/Source/numerous_github/numerousv2/numerous/numerous/engine/model/model.py\u001b[0m in \u001b[0;36massemble\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m    315\u001b[0m         \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mgenerate_graph_pdf\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    316\u001b[0m             \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmappings_graph\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mas_graphviz\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msystem\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtag\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mforce\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mTrue\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 317\u001b[0;31m         \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mlower_model_codegen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mtmp_vars\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m    318\u001b[0m         \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mlogged_aliases\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m{\u001b[0m\u001b[0;34m}\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    319\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n",
-      "\u001b[0;32m~/Source/numerous_github/numerousv2/numerous/numerous/engine/model/model.py\u001b[0m in \u001b[0;36mlower_model_codegen\u001b[0;34m(self, tmp_vars)\u001b[0m\n\u001b[1;32m    379\u001b[0m         \u001b[0mlogging\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0minfo\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'lowering model'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    380\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 381\u001b[0;31m         eq_gen = EquationGenerator(equations=self.equations_parsed, filename=\"kernel.py\",\n\u001b[0m\u001b[1;32m    382\u001b[0m                                    \u001b[0mequation_graph\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmappings_graph\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    383\u001b[0m                                    \u001b[0mscope_variables\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mvariables\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mscoped_equations\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mscoped_equations\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
-      "\u001b[0;32m~/Source/numerous_github/numerousv2/numerous/numerous/engine/model/lowering/equations_generator.py\u001b[0m in \u001b[0;36m__init__\u001b[0;34m(self, filename, equation_graph, scope_variables, equations, scoped_equations, temporary_variables, system_tag, use_llvm, imports)\u001b[0m\n\u001b[1;32m     93\u001b[0m         \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0meq_vardefs\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m{\u001b[0m\u001b[0;34m}\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m     94\u001b[0m         \u001b[0;31m# Loop over equation functions and generate code for each equation.\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 95\u001b[0;31m         \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_parse_equations\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mequations\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m     96\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m     97\u001b[0m         \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mall_targeted\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
-      "\u001b[0;32m~/Source/numerous_github/numerousv2/numerous/numerous/engine/model/lowering/equations_generator.py\u001b[0m in \u001b[0;36m_parse_equations\u001b[0;34m(self, equations)\u001b[0m\n\u001b[1;32m    142\u001b[0m                     \u001b[0mcompiled_function\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mTrue\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    143\u001b[0m                 )\n\u001b[0;32m--> 144\u001b[0;31m                 \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mgenerated_program\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0madd_external_function\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfunc_llvm\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msignature\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mlen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mtarget_ids\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m    145\u001b[0m             \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    146\u001b[0m                 func, args, target_ids = function_from_graph_generic(eq[2],\n",
-      "\u001b[0;32m~/Source/numerous_github/numerousv2/numerous/numerous/engine/model/lowering/llvm_builder.py\u001b[0m in \u001b[0;36madd_external_function\u001b[0;34m(self, function, signature, number_of_args, target_ids)\u001b[0m\n\u001b[1;32m     98\u001b[0m         \"\"\"\n\u001b[1;32m     99\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 100\u001b[0;31m         \u001b[0mf_c\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mcfunc\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msig\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0msignature\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfunction\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m    101\u001b[0m         \u001b[0mname\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mfunction\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m__qualname__\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    102\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n",
-      "\u001b[0;32m~/numerous/lib/python3.9/site-packages/numba/core/decorators.py\u001b[0m in \u001b[0;36mwrapper\u001b[0;34m(func)\u001b[0m\n\u001b[1;32m    282\u001b[0m         \u001b[0;32mif\u001b[0m \u001b[0mcache\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    283\u001b[0m             \u001b[0mres\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0menable_caching\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 284\u001b[0;31m         \u001b[0mres\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcompile\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m    285\u001b[0m         \u001b[0;32mreturn\u001b[0m \u001b[0mres\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    286\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n",
-      "\u001b[0;32m~/numerous/lib/python3.9/site-packages/numba/core/compiler_lock.py\u001b[0m in \u001b[0;36m_acquire_compile_lock\u001b[0;34m(*args, **kwargs)\u001b[0m\n\u001b[1;32m     33\u001b[0m         \u001b[0;32mdef\u001b[0m \u001b[0m_acquire_compile_lock\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m     34\u001b[0m             \u001b[0;32mwith\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 35\u001b[0;31m                 \u001b[0;32mreturn\u001b[0m \u001b[0mfunc\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m     36\u001b[0m         \u001b[0;32mreturn\u001b[0m \u001b[0m_acquire_compile_lock\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m     37\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n",
-      "\u001b[0;32m~/numerous/lib/python3.9/site-packages/numba/core/ccallback.py\u001b[0m in \u001b[0;36mcompile\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m     65\u001b[0m                                          self._targetdescr.target_context)\n\u001b[1;32m     66\u001b[0m         \u001b[0;32mif\u001b[0m \u001b[0mcres\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 67\u001b[0;31m             \u001b[0mcres\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_compile_uncached\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m     68\u001b[0m             \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_cache\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msave_overload\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_sig\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mcres\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m     69\u001b[0m         \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
-      "\u001b[0;32m~/numerous/lib/python3.9/site-packages/numba/core/ccallback.py\u001b[0m in \u001b[0;36m_compile_uncached\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m     79\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m     80\u001b[0m         \u001b[0;31m# Compile native function as well as cfunc wrapper\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 81\u001b[0;31m         \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_compiler\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcompile\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msig\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msig\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mreturn_type\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m     82\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m     83\u001b[0m     \u001b[0;34m@\u001b[0m\u001b[0mproperty\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
-      "\u001b[0;32m~/numerous/lib/python3.9/site-packages/numba/core/dispatcher.py\u001b[0m in \u001b[0;36mcompile\u001b[0;34m(self, args, return_type)\u001b[0m\n\u001b[1;32m     81\u001b[0m             \u001b[0;32mreturn\u001b[0m \u001b[0mretval\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m     82\u001b[0m         \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 83\u001b[0;31m             \u001b[0;32mraise\u001b[0m \u001b[0mretval\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m     84\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m     85\u001b[0m     \u001b[0;32mdef\u001b[0m \u001b[0m_compile_cached\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mreturn_type\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
-      "\u001b[0;32m~/numerous/lib/python3.9/site-packages/numba/core/dispatcher.py\u001b[0m in \u001b[0;36m_compile_cached\u001b[0;34m(self, args, return_type)\u001b[0m\n\u001b[1;32m     91\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m     92\u001b[0m         \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 93\u001b[0;31m             \u001b[0mretval\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_compile_core\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mreturn_type\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m     94\u001b[0m         \u001b[0;32mexcept\u001b[0m \u001b[0merrors\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mTypingError\u001b[0m \u001b[0;32mas\u001b[0m \u001b[0me\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m     95\u001b[0m             \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_failed_cache\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mkey\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0me\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
-      "\u001b[0;32m~/numerous/lib/python3.9/site-packages/numba/core/dispatcher.py\u001b[0m in \u001b[0;36m_compile_core\u001b[0;34m(self, args, return_type)\u001b[0m\n\u001b[1;32m    104\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    105\u001b[0m         \u001b[0mimpl\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_get_implementation\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m{\u001b[0m\u001b[0;34m}\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 106\u001b[0;31m         cres = compiler.compile_extra(self.targetdescr.typing_context,\n\u001b[0m\u001b[1;32m    107\u001b[0m                                       \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtargetdescr\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtarget_context\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    108\u001b[0m                                       \u001b[0mimpl\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
-      "\u001b[0;32m~/numerous/lib/python3.9/site-packages/numba/core/compiler.py\u001b[0m in \u001b[0;36mcompile_extra\u001b[0;34m(typingctx, targetctx, func, args, return_type, flags, locals, library, pipeline_class)\u001b[0m\n\u001b[1;32m    604\u001b[0m     pipeline = pipeline_class(typingctx, targetctx, library,\n\u001b[1;32m    605\u001b[0m                               args, return_type, flags, locals)\n\u001b[0;32m--> 606\u001b[0;31m     \u001b[0;32mreturn\u001b[0m \u001b[0mpipeline\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcompile_extra\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfunc\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m    607\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    608\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n",
-      "\u001b[0;32m~/numerous/lib/python3.9/site-packages/numba/core/compiler.py\u001b[0m in \u001b[0;36mcompile_extra\u001b[0;34m(self, func)\u001b[0m\n\u001b[1;32m    351\u001b[0m         \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mstate\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mlifted\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    352\u001b[0m         \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mstate\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mlifted_from\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 353\u001b[0;31m         \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_compile_bytecode\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m    354\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    355\u001b[0m     \u001b[0;32mdef\u001b[0m \u001b[0mcompile_ir\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mfunc_ir\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mlifted\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mlifted_from\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mNone\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
-      "\u001b[0;32m~/numerous/lib/python3.9/site-packages/numba/core/compiler.py\u001b[0m in \u001b[0;36m_compile_bytecode\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m    413\u001b[0m         \"\"\"\n\u001b[1;32m    414\u001b[0m         \u001b[0;32massert\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mstate\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfunc_ir\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 415\u001b[0;31m         \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_compile_core\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m    416\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    417\u001b[0m     \u001b[0;32mdef\u001b[0m \u001b[0m_compile_ir\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
-      "\u001b[0;32m~/numerous/lib/python3.9/site-packages/numba/core/compiler.py\u001b[0m in \u001b[0;36m_compile_core\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m    393\u001b[0m                 \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mstate\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mstatus\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfail_reason\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0me\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    394\u001b[0m                 \u001b[0;32mif\u001b[0m \u001b[0mis_final_pipeline\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 395\u001b[0;31m                     \u001b[0;32mraise\u001b[0m \u001b[0me\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m    396\u001b[0m         \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    397\u001b[0m             \u001b[0;32mraise\u001b[0m \u001b[0mCompilerError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"All available pipelines exhausted\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
-      "\u001b[0;32m~/numerous/lib/python3.9/site-packages/numba/core/compiler.py\u001b[0m in \u001b[0;36m_compile_core\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m    384\u001b[0m             \u001b[0mres\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    385\u001b[0m             \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 386\u001b[0;31m                 \u001b[0mpm\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrun\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mstate\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m    387\u001b[0m                 \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mstate\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcr\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    388\u001b[0m                     \u001b[0;32mbreak\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
-      "\u001b[0;32m~/numerous/lib/python3.9/site-packages/numba/core/compiler_machinery.py\u001b[0m in \u001b[0;36mrun\u001b[0;34m(self, state)\u001b[0m\n\u001b[1;32m    337\u001b[0m                     \u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpipeline_name\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mpass_desc\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    338\u001b[0m                 \u001b[0mpatched_exception\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_patch_error\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmsg\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0me\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 339\u001b[0;31m                 \u001b[0;32mraise\u001b[0m \u001b[0mpatched_exception\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m    340\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    341\u001b[0m     \u001b[0;32mdef\u001b[0m \u001b[0mdependency_analysis\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
-      "\u001b[0;32m~/numerous/lib/python3.9/site-packages/numba/core/compiler_machinery.py\u001b[0m in \u001b[0;36mrun\u001b[0;34m(self, state)\u001b[0m\n\u001b[1;32m    328\u001b[0m                 \u001b[0mpass_inst\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0m_pass_registry\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mget\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mpss\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpass_inst\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    329\u001b[0m                 \u001b[0;32mif\u001b[0m \u001b[0misinstance\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mpass_inst\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mCompilerPass\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 330\u001b[0;31m                     \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_runPass\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0midx\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mpass_inst\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mstate\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m    331\u001b[0m                 \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    332\u001b[0m                     \u001b[0;32mraise\u001b[0m \u001b[0mBaseException\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"Legacy pass in use\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
-      "\u001b[0;32m~/numerous/lib/python3.9/site-packages/numba/core/compiler_lock.py\u001b[0m in \u001b[0;36m_acquire_compile_lock\u001b[0;34m(*args, **kwargs)\u001b[0m\n\u001b[1;32m     33\u001b[0m         \u001b[0;32mdef\u001b[0m \u001b[0m_acquire_compile_lock\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m     34\u001b[0m             \u001b[0;32mwith\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 35\u001b[0;31m                 \u001b[0;32mreturn\u001b[0m \u001b[0mfunc\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m     36\u001b[0m         \u001b[0;32mreturn\u001b[0m \u001b[0m_acquire_compile_lock\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m     37\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n",
-      "\u001b[0;32m~/numerous/lib/python3.9/site-packages/numba/core/compiler_machinery.py\u001b[0m in \u001b[0;36m_runPass\u001b[0;34m(self, index, pss, internal_state)\u001b[0m\n\u001b[1;32m    287\u001b[0m             \u001b[0mmutated\u001b[0m \u001b[0;34m|=\u001b[0m \u001b[0mcheck\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mpss\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrun_initialization\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0minternal_state\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    288\u001b[0m         \u001b[0;32mwith\u001b[0m \u001b[0mSimpleTimer\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mas\u001b[0m \u001b[0mpass_time\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 289\u001b[0;31m             \u001b[0mmutated\u001b[0m \u001b[0;34m|=\u001b[0m \u001b[0mcheck\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mpss\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrun_pass\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0minternal_state\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m    290\u001b[0m         \u001b[0;32mwith\u001b[0m \u001b[0mSimpleTimer\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mas\u001b[0m \u001b[0mfinalize_time\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    291\u001b[0m             \u001b[0mmutated\u001b[0m \u001b[0;34m|=\u001b[0m \u001b[0mcheck\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mpss\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrun_finalizer\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0minternal_state\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
-      "\u001b[0;32m~/numerous/lib/python3.9/site-packages/numba/core/compiler_machinery.py\u001b[0m in \u001b[0;36mcheck\u001b[0;34m(func, compiler_state)\u001b[0m\n\u001b[1;32m    260\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    261\u001b[0m         \u001b[0;32mdef\u001b[0m \u001b[0mcheck\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfunc\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mcompiler_state\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 262\u001b[0;31m             \u001b[0mmangled\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mfunc\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mcompiler_state\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m    263\u001b[0m             \u001b[0;32mif\u001b[0m \u001b[0mmangled\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0;32min\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0;32mTrue\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;32mFalse\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    264\u001b[0m                 msg = (\"CompilerPass implementations should return True/False. \"\n",
-      "\u001b[0;32m~/numerous/lib/python3.9/site-packages/numba/core/typed_passes.py\u001b[0m in \u001b[0;36mrun_pass\u001b[0;34m(self, state)\u001b[0m\n\u001b[1;32m    102\u001b[0m                               % (state.func_id.func_name,)):\n\u001b[1;32m    103\u001b[0m             \u001b[0;31m# Type inference\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 104\u001b[0;31m             typemap, return_type, calltypes, errs = type_inference_stage(\n\u001b[0m\u001b[1;32m    105\u001b[0m                 \u001b[0mstate\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtypingctx\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    106\u001b[0m                 \u001b[0mstate\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfunc_ir\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
-      "\u001b[0;32m~/numerous/lib/python3.9/site-packages/numba/core/typed_passes.py\u001b[0m in \u001b[0;36mtype_inference_stage\u001b[0;34m(typingctx, interp, args, return_type, locals, raise_errors)\u001b[0m\n\u001b[1;32m     78\u001b[0m             \u001b[0minfer\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mseed_type\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mk\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mv\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m     79\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 80\u001b[0;31m         \u001b[0minfer\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mbuild_constraint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m     81\u001b[0m         \u001b[0;31m# return errors in case of partial typing\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m     82\u001b[0m         \u001b[0merrs\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0minfer\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpropagate\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mraise_errors\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mraise_errors\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
-      "\u001b[0;32m~/numerous/lib/python3.9/site-packages/numba/core/typeinfer.py\u001b[0m in \u001b[0;36mbuild_constraint\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m   1022\u001b[0m         \u001b[0;32mfor\u001b[0m \u001b[0mblk\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mblocks\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mvalues\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m   1023\u001b[0m             \u001b[0;32mfor\u001b[0m \u001b[0minst\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mblk\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mbody\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 1024\u001b[0;31m                 \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mconstrain_statement\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0minst\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m   1025\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m   1026\u001b[0m     \u001b[0;32mdef\u001b[0m \u001b[0mreturn_types_from_partial\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
-      "\u001b[0;32m~/numerous/lib/python3.9/site-packages/numba/core/typeinfer.py\u001b[0m in \u001b[0;36mconstrain_statement\u001b[0;34m(self, inst)\u001b[0m\n\u001b[1;32m   1369\u001b[0m     \u001b[0;32mdef\u001b[0m \u001b[0mconstrain_statement\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0minst\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m   1370\u001b[0m         \u001b[0;32mif\u001b[0m \u001b[0misinstance\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0minst\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mir\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mAssign\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 1371\u001b[0;31m             \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtypeof_assign\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0minst\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m   1372\u001b[0m         \u001b[0;32melif\u001b[0m \u001b[0misinstance\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0minst\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mir\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mSetItem\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m   1373\u001b[0m             \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtypeof_setitem\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0minst\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
-      "\u001b[0;32m~/numerous/lib/python3.9/site-packages/numba/core/typeinfer.py\u001b[0m in \u001b[0;36mtypeof_assign\u001b[0;34m(self, inst)\u001b[0m\n\u001b[1;32m   1440\u001b[0m                                               src=value.name, loc=inst.loc))\n\u001b[1;32m   1441\u001b[0m         \u001b[0;32melif\u001b[0m \u001b[0misinstance\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mvalue\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0mir\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mGlobal\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mir\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mFreeVar\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 1442\u001b[0;31m             \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtypeof_global\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0minst\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0minst\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtarget\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mvalue\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m   1443\u001b[0m         \u001b[0;32melif\u001b[0m \u001b[0misinstance\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mvalue\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mir\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mArg\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m   1444\u001b[0m             \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtypeof_arg\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0minst\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0minst\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtarget\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mvalue\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
-      "\u001b[0;32m~/numerous/lib/python3.9/site-packages/numba/core/typeinfer.py\u001b[0m in \u001b[0;36mtypeof_global\u001b[0;34m(self, inst, target, gvar)\u001b[0m\n\u001b[1;32m   1540\u001b[0m     \u001b[0;32mdef\u001b[0m \u001b[0mtypeof_global\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0minst\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mtarget\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mgvar\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m   1541\u001b[0m         \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 1542\u001b[0;31m             \u001b[0mtyp\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mresolve_value_type\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0minst\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mgvar\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mvalue\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m   1543\u001b[0m         \u001b[0;32mexcept\u001b[0m \u001b[0mTypingError\u001b[0m \u001b[0;32mas\u001b[0m \u001b[0me\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m   1544\u001b[0m             if (gvar.name == self.func_id.func_name\n",
-      "\u001b[0;32m~/numerous/lib/python3.9/site-packages/numba/core/typeinfer.py\u001b[0m in \u001b[0;36mresolve_value_type\u001b[0;34m(self, inst, val)\u001b[0m\n\u001b[1;32m   1461\u001b[0m         \u001b[0;32mexcept\u001b[0m \u001b[0mValueError\u001b[0m \u001b[0;32mas\u001b[0m \u001b[0me\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m   1462\u001b[0m             \u001b[0mmsg\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mstr\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0me\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 1463\u001b[0;31m         \u001b[0;32mraise\u001b[0m \u001b[0mTypingError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmsg\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mloc\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0minst\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mloc\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m   1464\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m   1465\u001b[0m     \u001b[0;32mdef\u001b[0m \u001b[0mtypeof_arg\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0minst\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mtarget\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0marg\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
-      "\u001b[0;31mTypingError\u001b[0m: Failed in nopython mode pipeline (step: nopython frontend)\nNameError: name 'global_variables' is not defined"
-     ]
-    }
-   ],
+   "outputs": [],
    "source": [
     "omega = 0\n",
     "\n",
@@ -779,7 +651,11 @@
   },
   {
    "cell_type": "markdown",
-   "metadata": {},
+   "metadata": {
+    "pycharm": {
+     "name": "#%% md\n"
+    }
+   },
    "source": [
     "### Plotting: comparison vs. analytical solution\n",
     "To plot the solution, we add the following code which uses `plotly`. We shall not go into detail on the code, but simply include it here as it's used for the purpose illustrating the results of this tutorial only.\n",
@@ -790,15 +666,24 @@
   {
    "cell_type": "code",
    "execution_count": null,
-   "metadata": {},
+   "metadata": {
+    "pycharm": {
+     "is_executing": true,
+     "name": "#%%\n"
+    }
+   },
    "outputs": [],
-   "source": []
+   "source": [
+    "hdf = s1.model.historian_df\n",
+    "x_value =  [x / 500 for x in list(hdf.index)]"
+   ]
   },
   {
    "cell_type": "code",
    "execution_count": null,
    "metadata": {
     "pycharm": {
+     "is_executing": true,
      "name": "#%%\n"
     }
    },
@@ -812,13 +697,13 @@
     "\n",
     "fig = go.Figure()\n",
     "fig.add_trace(go.Scatter(\n",
-    "            x=hdf.df.index.total_seconds(), y=hdf.df['subsystem.tank_1.v1.H'],\n",
+    "            x=x_value, y=hdf['subsystem.tank_1.v1.H'],\n",
     "            name='Tank 1 - numerical', mode='lines',line_color='rgba(102,51,255,1)'))\n",
     "fig.add_trace(go.Scatter(\n",
     "            x=result_analytical['t'], y=result_analytical['H_1'],\n",
     "            name='Tank 1 - analytical', mode='markers', marker_color='rgba(102,51,255,1)'))\n",
     "fig.add_trace(go.Scatter(\n",
-    "            x=hdf.df.index.total_seconds(), y=hdf.df['subsystem.tank_2.v1.H'],\n",
+    "            x=x_value, y=hdf['subsystem.tank_2.v1.H'],\n",
     "            name='Tank 2 - numerical', mode='lines',line_color='rgba(152, 0, 0, .8)'))\n",
     "fig.add_trace(go.Scatter(\n",
     "            x=result_analytical['t'], y=result_analytical['H_2'],\n",
@@ -835,6 +720,7 @@
    "execution_count": null,
    "metadata": {
     "pycharm": {
+     "is_executing": true,
      "name": "#%%\n"
     }
    },
@@ -842,13 +728,13 @@
    "source": [
     "fig = go.Figure()\n",
     "fig.add_trace(go.Scatter(\n",
-    "            x=hdf.df.index.total_seconds(), y= hdf.df['subsystem.tank_1.v1.H']*rho_water*A0[0],\n",
+    "            x=x_value, y= hdf['subsystem.tank_1.v1.H']*rho_water*A0[0],\n",
     "            name='Tank 1 - numerical', mode='lines',line_color='rgba(102,51,255,1)'))\n",
     "fig.add_trace(go.Scatter(\n",
     "            x=result_analytical['t'], y=result_analytical['M_1'],\n",
     "            name='Tank 1 - analytical', mode='markers', marker_color='rgba(102,51,255,1)'))\n",
     "fig.add_trace(go.Scatter(\n",
-    "            x=hdf.df.index.total_seconds(), y=hdf.df['subsystem.tank_2.v1.H']*rho_water*A0[1],\n",
+    "            x=x_value, y=hdf['subsystem.tank_2.v1.H']*rho_water*A0[1],\n",
     "            name='Tank 2 - numerical', mode='lines',line_color='rgba(152, 0, 0, .8)'))\n",
     "fig.add_trace(go.Scatter(\n",
     "            x=result_analytical['t'], y=result_analytical['M_2'],\n",
@@ -862,7 +748,11 @@
   },
   {
    "cell_type": "markdown",
-   "metadata": {},
+   "metadata": {
+    "pycharm": {
+     "name": "#%% md\n"
+    }
+   },
    "source": [
     "## Running the simulations - Case 2: Time dependent Cv\n",
     "We assign now a value to the parameter $\\omega$, so that the valve has a characteristic parameter that is dependent from time, with a sinusoidal behaviour.\n",
@@ -875,6 +765,7 @@
    "execution_count": null,
    "metadata": {
     "pycharm": {
+     "is_executing": true,
      "name": "#%%\n"
     }
    },
@@ -883,13 +774,16 @@
     "omega_2 = 1.5 # rad/s\n",
     "    \n",
     "# Solve the model and collect hystorical data\n",
-    "hdf_2     = SimpleHistoryDataFrame()\n",
-    "s2, sol_2 = t_1_item_model(H0, Cv_0,omega_2, hdf_2, t_start_sim, t_stop_sim)"
+    "s2, sol_2 = t_1_item_model(H0, Cv_0,omega_2, t_start_sim, t_stop_sim)"
    ]
   },
   {
    "cell_type": "markdown",
-   "metadata": {},
+   "metadata": {
+    "pycharm": {
+     "name": "#%% md\n"
+    }
+   },
    "source": [
     "### Plotting the results"
    ]
@@ -899,6 +793,22 @@
    "execution_count": null,
    "metadata": {
     "pycharm": {
+     "is_executing": true,
+     "name": "#%%\n"
+    }
+   },
+   "outputs": [],
+   "source": [
+    "hdf_2  =  s2.model.historian_df\n",
+    "x_value_2 =  [x / 500 for x in list(hdf_2.index)]"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "pycharm": {
+     "is_executing": true,
      "name": "#%%\n"
     }
    },
@@ -912,10 +822,10 @@
     "\n",
     "fig = go.Figure()\n",
     "fig.add_trace(go.Scatter(\n",
-    "            x=hdf_2.df.index.total_seconds(), y=hdf_2.df['subsystem.tank_1.v1.H'],\n",
+    "            x=x_value_2, y=hdf_2['subsystem.tank_1.v1.H'],\n",
     "            name='Tank 1', mode='lines',line_color='rgba(102,51,255,1)'))\n",
     "fig.add_trace(go.Scatter(\n",
-    "            x=hdf_2.df.index.total_seconds(), y=hdf_2.df['subsystem.tank_2.v1.H'],\n",
+    "            x=x_value_2, y=hdf_2['subsystem.tank_2.v1.H'],\n",
     "            name='Tank 2', mode='lines',line_color='rgba(152, 0, 0, .8)'))\n",
     "              \n",
     "fig.update_layout(title='Liquid height in tanks',\n",
@@ -929,6 +839,7 @@
    "execution_count": null,
    "metadata": {
     "pycharm": {
+     "is_executing": true,
      "name": "#%%\n"
     }
    },
@@ -936,10 +847,10 @@
    "source": [
     "fig = go.Figure()\n",
     "fig.add_trace(go.Scatter(\n",
-    "            x=hdf_2.df.index.total_seconds(), y= hdf_2.df['subsystem.tank_1.v1.H']*rho_water*A0[0],\n",
+    "            x=x_value_2, y= hdf_2['subsystem.tank_1.v1.H']*rho_water*A0[0],\n",
     "            name='Tank 1', mode='lines',line_color='rgba(102,51,255,1)'))\n",
     "fig.add_trace(go.Scatter(\n",
-    "            x=hdf_2.df.index.total_seconds(), y=hdf_2.df['subsystem.tank_2.v1.H']*rho_water*A0[1],\n",
+    "            x=x_value_2, y=hdf_2['subsystem.tank_2.v1.H']*rho_water*A0[1],\n",
     "            name='Tank 2', mode='lines',line_color='rgba(152, 0, 0, .8)'))\n",
     "              \n",
     "fig.update_layout(title='Liquid mass in tanks',\n",
@@ -953,6 +864,7 @@
    "execution_count": null,
    "metadata": {
     "pycharm": {
+     "is_executing": true,
      "name": "#%%\n"
     }
    },
@@ -960,7 +872,7 @@
    "source": [
     "fig = go.Figure()\n",
     "fig.add_trace(go.Scatter(\n",
-    "            x=hdf_2.df.index.total_seconds(), y= hdf_2.df['subsystem.valve_1.v1.Cv'],\n",
+    "            x=x_value_2, y= hdf_2['subsystem.valve_1.v1.Cv'],\n",
     "            name='Cv', mode='lines',line_color='rgba(41, 241, 195, 1)'))           \n",
     "fig.update_layout(title='Valve characteristc parameter',\n",
     "                  yaxis_zeroline=False, xaxis_zeroline=False,xaxis_title=\"time, sec\",\n",
@@ -985,7 +897,7 @@
    "name": "python",
    "nbconvert_exporter": "python",
    "pygments_lexer": "ipython3",
-   "version": "3.9.5"
+   "version": "3.10.4"
   },
   "pycharm": {
    "stem_cell": {
@@ -1002,4 +914,4 @@
  },
  "nbformat": 4,
  "nbformat_minor": 1
-}
+}
\ No newline at end of file
diff --git a/examples/dampened_oscillator/dampened_oscillator.py b/examples/dampened_oscillator/dampened_oscillator.py
index a760ee7d..580dc7d0 100644
--- a/examples/dampened_oscillator/dampened_oscillator.py
+++ b/examples/dampened_oscillator/dampened_oscillator.py
@@ -175,10 +175,8 @@ def __init__(self, tag, c=1, k=1, x0=[10, 8], a=1, n=2):
 
     subsystem = OscillatorSystem('system', k=0.01, c=0.001, a=0, n=2, x0=[1, 2, 3])
     # Define simulation
-    s = simulation.Simulation(
-        model.Model(subsystem, use_llvm=False),
-        t_start=0, t_stop=500.0, num=1000, num_inner=100, max_step=1
-    )
+    s = simulation.Simulation(model.Model(subsystem, use_llvm=False), t_start=0, t_stop=500.0, num=1000, num_inner=100,
+                              max_step=1)
     # Solve and plot
     tic = time()
     s.solve()
diff --git a/examples/example_model.py b/examples/example_model.py
index 957a5e63..e70773a4 100644
--- a/examples/example_model.py
+++ b/examples/example_model.py
@@ -73,7 +73,7 @@ def __init__(self, tag):
 def run_model(ms, solver, use_llvm):
     # print(type(ms2))
     m1 = Model(system=ms, use_llvm=use_llvm)
-    s1 = Simulation(m1, t_start=0, t_stop=1000, num=10, solver_type=solver)
+    s1 = Simulation(m1, t_start=0, t_stop=1000, num=10)
     s1.solve()
     return s1
 
diff --git a/examples/export_example.py b/examples/export_example.py
index 881c2697..ec921410 100644
--- a/examples/export_example.py
+++ b/examples/export_example.py
@@ -75,6 +75,7 @@ def __init__(self, tag, n):
 
 model_filename = "./export_model/S2.numerous"
 import sys
+
 sys.setrecursionlimit(100000)
 if not os.path.isfile(model_filename):
     Ms_3 = S2N("S2", 1000)
@@ -87,16 +88,14 @@ def __init__(self, tag, n):
     # 4.
     print("model compilation time ", end - start)
 
-
-    s1 = Simulation(m1, t_start=0, t_stop=1000, num=100, solver_type=SolverType.NUMEROUS)
+    s1 = Simulation(m1, t_start=0, t_stop=1000, num=100)
     s1.solve()
     print(list(m1.states_as_vector[::-1]))
 else:
     start = time.time()
-    # 1.7976946830749512
     m1 = Model.from_file(model_filename)
     end = time.time()
-    print("model from file  ",end - start)
-    s1 = Simulation(m1, t_start=0, t_stop=1000, num=100, solver_type=SolverType.NUMEROUS)
+    print("model from file  ", end - start)
+    s1 = Simulation(m1, t_start=0, t_stop=1000, num=100)
     s1.solve()
     print(list(m1.states_as_vector[::-1]))
diff --git a/examples/external_data_mapping/example_external_data_mapping.py b/examples/external_data_mapping/example_external_data_mapping.py
new file mode 100644
index 00000000..772dbedb
--- /dev/null
+++ b/examples/external_data_mapping/example_external_data_mapping.py
@@ -0,0 +1,64 @@
+from numerous.multiphysics.equation_base import EquationBase
+from numerous.engine.system.item import Item
+from numerous.multiphysics.equation_decorators import Equation
+from numerous.utils.data_loader import InMemoryDataLoader
+from numerous.engine.system.subsystem import Subsystem
+from numerous.engine import model, simulation
+from numerous.engine.simulation.solvers.base_solver import SolverType
+from numerous.engine.system.external_mappings import ExternalMappingElement, InterpolationType
+
+import pandas as pd
+
+
+class Time(Item, EquationBase):
+    def __init__(self,
+                 tag='time'):
+
+        super(Time, self).__init__(tag)
+
+        dn = self.create_namespace('default')
+
+        self.add_state('t', init_val=0)
+        self.add_parameters({'p1': 0})
+
+        dn.add_equations([self])
+
+    @Equation()
+    def diff(self, scope):
+        scope.t_dot = 1
+
+
+class TestSystem(Subsystem):
+
+    def __init__(self, tag='sys', data_loader=None, external_mappings=None):
+        super().__init__(tag, data_loader=data_loader, external_mappings=external_mappings)
+
+        time_ = Time()
+
+        self.register_items([time_], tag="simples")
+
+
+if __name__ == '__main__':
+    n_hours = 10
+    len_data = n_hours + 1
+    df = pd.DataFrame({'p1': range(len_data), 't': range(len_data)})
+
+    data_loader = InMemoryDataLoader(df)
+
+    external_mappings = [
+        ExternalMappingElement(0, 't', 0, 3600, {'sys.time.default.p1': ('p1', InterpolationType.LINEAR)}),
+    ]
+
+    sys = TestSystem(external_mappings=external_mappings, data_loader=data_loader)
+    m = model.Model(sys, use_llvm=True)
+
+    # Define simulation
+    s = simulation.Simulation(
+        m,
+        t_start=0, t_stop=n_hours * 3600, num=n_hours, num_inner=1, max_step=3600,
+        solver_type=SolverType.NUMEROUS, method="RK45"
+    )
+
+    s.solve()
+
+    assert (s.model.historian_df['sys.time.default.p1'] == df['p1']).all(), "All elements should be equal value!"
diff --git a/examples/historyDataFrameCallbackExample.py b/examples/historyDataFrameCallbackExample.py
deleted file mode 100644
index c474b017..00000000
--- a/examples/historyDataFrameCallbackExample.py
+++ /dev/null
@@ -1,45 +0,0 @@
-import pandas as pd
-from datetime import timedelta
-import numpy as np
-from numba import int64, float64
-
-from numerous.utils.callback_decorators import CallbackMethodType, NumbaCallback
-from numerous.utils.numba_callback import NumbaCallbackBase
-
-
-class HistoryDataFrameCallback(NumbaCallbackBase):
-
-    def __init__(self):
-        super(HistoryDataFrameCallback, self).__init__()
-        self.register_numba_variable('data', float64[:, :])
-        self.register_numba_variable('ix', int64)
-
-    @NumbaCallback(method_type=CallbackMethodType.INITIALIZE)
-    def initialize(self, var_count, number_of_timesteps):
-        self.ix = 0
-        ##* simulation.num_inner
-        self.data = np.empty((var_count + 1, number_of_timesteps), dtype=np.float64)
-        self.data.fill(np.nan)
-
-    @NumbaCallback(method_type=CallbackMethodType.UPDATE, run_after_init=True)
-    def update(self, time, variables):
-        varix = 1
-        ix = self.ix
-        self.data[0][ix] = time
-        for var in variables:
-            self.data[varix][ix] = variables[var]
-            varix += 1
-        self.ix += 1
-
-    def finalize(self,var_list):
-        time = self.data[0]
-        data = {'time': time}
-
-        for i, var in enumerate(var_list):
-            data.update({var: self.data[i + 1]})
-
-        self.df = pd.DataFrame(data)
-        self.df = self.df.dropna(subset=['time'])
-        self.df = self.df.set_index('time')
-        self.df.index = pd.to_timedelta(self.df.index, unit='s')
-
diff --git a/examples/step_solution.py b/examples/step_solution.py
index 71582921..41d62b4b 100644
--- a/examples/step_solution.py
+++ b/examples/step_solution.py
@@ -4,9 +4,9 @@
 from numerous.engine.system import Subsystem
 from numerous.engine.model import Model
 from numerous.engine.simulation import Simulation
-from numerous.engine.simulation.solvers import SolverType
 import numpy as np
 
+
 class SuperSimpleStepTest(Item, EquationBase):
     def __init__(self, tag='simplestepping'):
         super(SuperSimpleStepTest, self).__init__(tag)
@@ -18,27 +18,28 @@ def __init__(self, tag='simplestepping'):
     def eval(self, scope):
         scope.x_dot = 0.1
 
+
 class System(Subsystem):
     def __init__(self, tag='sys'):
         super().__init__(tag)
         i = SuperSimpleStepTest()
         self.register_item(i)
 
+
 sys = System()
 model = Model(sys)
 t_stop = 100
-sim=Simulation(model, t_start=0, t_stop=t_stop, num=50, max_step=np.inf, method='RK45',
-           solver_type=SolverType.NUMEROUS)
+sim = Simulation(model, t_start=0, t_stop=t_stop, num=50, max_step=np.inf, method='RK45')
 
 finished = False
 dt = 2
 while not finished:
-    t, info = sim.step_solve(dt)
+    t, info = sim.step_solve(dt, 1)
     print(t)
-    if t>=t_stop:
-        finished=True
+    if t >= t_stop:
+        finished = True
 
 sim.model.create_historian_df()
 
-df=sim.model.historian_df
+df = sim.model.historian_df
 print(df['time'].values, df['sys.simplestepping.t1.x'].values)
diff --git a/numerous/engine/model/aliased_dataframe.py b/numerous/engine/model/aliased_dataframe.py
new file mode 100644
index 00000000..e481e694
--- /dev/null
+++ b/numerous/engine/model/aliased_dataframe.py
@@ -0,0 +1,41 @@
+from copy import copy
+from typing import Sequence
+
+import pandas as pd
+
+
+class AliasedDataFrame:
+    def __init__(self, data, aliases={}, rename_columns=True):
+        self.aliases = aliases
+        self.rename_columns = rename_columns
+        if self.rename_columns:
+            tmp = copy(list(data.keys()))
+            for key in tmp:
+                if key in self.aliases.keys():
+                    data[self.aliases[key]] = data.pop(key)
+        self.df = pd.DataFrame(data)
+
+    def __getitem__(self, names: str | Sequence[str]):
+        if self.rename_columns:
+            if isinstance(names, str):
+                return self.df[self.aliases[names] if names in self.aliases else names]
+            if isinstance(names, Sequence):
+                df = self.df[[self.aliases[name] if name in self.aliases else name for name in names]]
+                df.columns = list(names)
+                return df
+        else:
+            return self.df[names]
+
+    @property
+    def index(self):
+        return self.df.index
+
+    def get(self, names: str | Sequence[str]):
+        if self.rename_columns:
+            if isinstance(names, str):
+                return self.df.get(self.aliases[names] if names in self.aliases else names)
+            if isinstance(names, Sequence):
+                df = self.df.get([self.aliases[name] if name in self.aliases else name for name in names])
+                return df
+        else:
+            return self.df.get(names)
diff --git a/numerous/engine/model/ast_parser/ast_visitor.py b/numerous/engine/model/ast_parser/ast_visitor.py
index 481cf156..7b38c286 100644
--- a/numerous/engine/model/ast_parser/ast_visitor.py
+++ b/numerous/engine/model/ast_parser/ast_visitor.py
@@ -79,6 +79,23 @@ def _process_named_node(self, node: ast.expr, ast_type: type, node_type: NodeTyp
         self.mapped_stack.append([is_mapped])
         self.node_number_stack.append([en])
 
+    def visit_AugAssign(self, node: ast.AugAssign) -> Any:
+        operation = node.op
+        target = node.target
+        value = node.value
+
+        new_value = ast.BinOp()
+
+        new_value.left = target
+        new_value.right = value
+        new_value.op = operation
+
+        new_assign = ast.Assign()
+        new_assign.targets = [target]
+        new_assign.value = new_value
+
+        self.visit_Assign(new_assign)
+
     def visit_Assign(self, node: ast.Assign):
         for target in node.targets:
             if self._is_assign_target_supported(target):
@@ -131,7 +148,7 @@ def _select_scope_var(self, source_id: str):
     def visit_Constant(self, node: ast.Constant):
         source_id = self.CONSTANT_LABEL + str(node.value)
         en = self.graph.add_node(Node(ao=node, file=self.eq_file, name=self.eq_key, ln=self.eq_lineno,
-                                      label=source_id, ast_type=ast.Num, value=node.value,
+                                      label=source_id, ast_type=ast.Constant, value=node.value,
                                       node_type=NodeTypes.VAR))
         self.mapped_stack.append([None])
         self.node_number_stack.append([en])
@@ -146,6 +163,31 @@ def visit_Tuple(self, node: ast.Tuple):
         self.mapped_stack.append(list(chain.from_iterable(mapped)))
         self.node_number_stack.append(list(chain.from_iterable(en)))
 
+    def visit_Subscript(self, node: ast.Subscript):
+        sl = node.slice
+        if isinstance(sl, ast.Slice):
+            raise Exception('Slices are not supported right now')
+        if isinstance(sl, ast.Tuple):
+            for el in sl.elts:
+                if isinstance(el, ast.Slice):
+                    raise Exception('Slices are not supported right now')
+        en = self.graph.add_node(Node(ao=Node, file=self.eq_file,
+                                      name=self.eq_key, ln=self.eq_lineno,
+                                      label="subscript", ctx=node.ctx, node_type=NodeTypes.SUBSCRIPT,
+                                      ast_type=ast.Subscript))
+        self.traverse(node.value)
+        self.mapped_stack.pop()
+        start = self.node_number_stack.pop()
+        self.graph.add_edge(Edge(start=start[0], end=en,
+                                 e_type=str_to_edgetype("subscript_value"), branches=self.branches.copy()))
+        self.traverse(node.slice)
+        mapped = self.mapped_stack.pop()
+        start = self.node_number_stack.pop()
+        self.graph.add_edge(Edge(start=start[0], end=en,
+                                 e_type=str_to_edgetype("slice"), branches=self.branches.copy()))
+        self.node_number_stack.append([en])
+        self.mapped_stack.append(mapped)
+
     def visit_BinOp(self, node: ast.BinOp):
         op_sym = get_op_sym(node.op)
         en = self.graph.add_node(Node(ao=node, file=self.eq_file, name=self.eq_key,
@@ -248,7 +290,7 @@ def connect_equation_node(equation_graph, mappings_graph, node, is_set, include_
                         pass
 
             elif include_local and equation_graph.get(n, attr='key') and \
-                    not equation_graph.get(n, attr='ast_type') == ast.Num:
+                    not equation_graph.get(n, attr='ast_type') == ast.Constant:
                 var_key = equation_graph.get(n, attr='key')
                 neq = mappings_graph.add_node(Node(key=var_key, node_type=NodeTypes.VAR,
                                                    is_set_var=is_set, ast_type=equation_graph.get(n, attr='ast_type'),
diff --git a/numerous/engine/model/ast_parser/equation_form_graph.py b/numerous/engine/model/ast_parser/equation_form_graph.py
index e3cf58c2..f8218d50 100644
--- a/numerous/engine/model/ast_parser/equation_form_graph.py
+++ b/numerous/engine/model/ast_parser/equation_form_graph.py
@@ -1,4 +1,5 @@
 import ast
+import re
 
 from numerous.engine.model.graph_representation import MappingsGraph, Graph
 from numerous.engine.model.graph_representation.utils import EdgeType
@@ -20,11 +21,8 @@ def node_to_ast(n: int, g: MappingsGraph, var_def, ctxread=False, read=True):
         elif na == ast.Name:
             return var_def(nk, ctxread, read)
 
-        elif na == ast.Num:
-            return ast.Call(args=[ast.Num(value=g.get(n, 'value'),
-                                          lineno=0, col_offset=0)], func=ast.Name(id='float64', lineno=0, col_offset=0,
-                                                                                  ctx=ast.Load()),
-                            keywords=[], lineno=0, col_offset=0)
+        elif na == ast.Constant:
+            return ast.Constant(value=g.get(n, 'value'))
 
         elif na == ast.BinOp:
 
@@ -88,6 +86,18 @@ def node_to_ast(n: int, g: MappingsGraph, var_def, ctxread=False, read=True):
             ast_Comp = ast.Compare(left=left_ast, comparators=comp_ast, ops=g.get(n, 'ops'), lineno=0, col_offset=0)
 
             return ast_Comp
+        elif na == ast.Subscript:
+            val_node = g.get_edges_for_node_filter(end_node=n, attr='e_type', val=EdgeType.SUBSCRIPT_VALUE)[1][0][0]
+
+            val_ast = node_to_ast(val_node, g, var_def, ctxread=ctxread)
+
+            slice_node = g.get_edges_for_node_filter(end_node=n, attr='e_type', val=EdgeType.SLICE)[1][0][0]
+
+            slice_ast = node_to_ast(slice_node, g, var_def, ctxread=ctxread)
+
+            ast_subscript = ast.Subscript(value=val_ast, slice=slice_ast, ctx=g.get(n, 'ctx'), lineno=0, col_offset=0)
+            return ast_subscript
+
         raise TypeError(f'Cannot convert {n},{na}')
     except:
         print(n)
@@ -187,16 +197,30 @@ def function_body_from_graph(g, var_def_, lineno_count=1, level=0):
             body.append(process_if_node(func_body, func_test))
     return body
 
+# TODO need to Refactor later, quick fix, very bad code
+
+
+def tokenize_string(f):
+    return re.split("([\w\.]+)", f)
+
 
 def replace_closure_function(func, replacements, eq_prefix):
     f1 = ast.unparse(func)
     if replacements:
-        keys = list(replacements.keys())
-        for key in keys:
+        tokenized = tokenize_string(f1)
+        for i in range(len(tokenized)):
+            key = None
+            token = tokenized[i]
+            if token in replacements:
+                key = token
+            if token.startswith('s_') and token[2:] == token:
+                key = token[2:]
+            if key is None:
+                continue
             eq_key = (eq_prefix + key).replace('.', '_')
-            f1 = f1.replace(key, eq_key)
+            tokenized[i] = token.replace(key, eq_key)
             replacements[eq_key] = replacements[key]
-            replacements.pop(key)
+            f1 = ''.join(tokenized)
     return f1
 
 
diff --git a/numerous/engine/model/compiled_model.py b/numerous/engine/model/compiled_model.py
index f4838849..71e934a2 100644
--- a/numerous/engine/model/compiled_model.py
+++ b/numerous/engine/model/compiled_model.py
@@ -2,6 +2,12 @@
 import numpy as np
 import numpy.typing as npt
 
+try:
+    FEPS = np.finfo(1.0).eps
+except AttributeError:
+    FEPS = 2.220446049250313e-16
+
+
 # key and value types
 kv_ty = (types.unicode_type, float64)
 
@@ -24,14 +30,21 @@
     ('external_df_idx', int64[:, :]),
     ('approximation_type', boolean[:]),
     ('is_external_data', boolean),
-    ('max_external_t', int64),
+    ('max_external_t', float64),
+    ('min_external_t', float64),
     ('external_idx', int64[:]),
+    ('previous_external_mappings', float64[:])
 ]
 
 
+@njit
+def closest_time_idx(t, time_array):
+    return np.searchsorted(time_array, t + 100 * FEPS, side='right') - 1
+
+
 @njit
 def step_approximation(t, time_array, data_array):
-    idx = np.searchsorted(time_array, t, side='right') - 1
+    idx = closest_time_idx(t, time_array)
     return data_array[idx]
 
 
@@ -40,7 +53,7 @@ def __init__(self, init_vars, deriv_idx, state_idx,
                  global_vars, number_of_timesteps, start_time, historian_max_size,
                  external_mappings_time, number_of_external_mappings,
                  external_mappings_numpy, external_df_idx, interpolation_info,
-                 is_external_data, t_max, external_idx):
+                 is_external_data, t_max, t_min, external_idx):
         self.external_idx = external_idx
         self.external_mappings_time = external_mappings_time
         self.number_of_external_mappings = number_of_external_mappings
@@ -50,6 +63,7 @@ def __init__(self, init_vars, deriv_idx, state_idx,
         self.approximation_type = interpolation_info
         self.is_external_data = is_external_data
         self.max_external_t = t_max
+        self.min_external_t = t_min
         self.global_vars = global_vars
         self.deriv_idx = deriv_idx
         self.state_idx = state_idx
@@ -63,8 +77,9 @@ def __init__(self, init_vars, deriv_idx, state_idx,
         self.historian_data = np.empty(
             (len(init_vars) + 1, self.historian_max_size), dtype=np.float64)
         self.historian_data.fill(np.nan)
+        self.previous_external_mappings = np.zeros(self.number_of_external_mappings)
 
-    def vectorizedfulljacobian(self, t, y, dt):
+    def vectorized_full_jacobian(self, t, y, dt):
         h = 1e-8
         y_perm = y + h * np.diag(np.ones(len(y)))
 
@@ -80,7 +95,13 @@ def vectorizedfulljacobian(self, t, y, dt):
         return np.ascontiguousarray(jac)
 
     def map_external_data(self, t):
+
+        if not self.is_external_data:
+            return
+
+        next_values = np.zeros(self.number_of_external_mappings)
         for i in range(self.number_of_external_mappings):
+
             df_indx = self.external_df_idx[i][0]
             var_idx = self.external_df_idx[i][1]
             value = np.interp(t, self.external_mappings_time[df_indx],
@@ -88,14 +109,26 @@ def map_external_data(self, t):
                 self.approximation_type[i] else \
                 step_approximation(t, self.external_mappings_time[df_indx],
                                    self.external_mappings_numpy[df_indx, :, var_idx])
+            next_values[i] = value
+
+        if np.array_equal(next_values, self.previous_external_mappings):  # No need to update if no values were changed
+            return
+
+        self.previous_external_mappings = next_values
+
+        for i, value in enumerate(next_values):
             self.write_variables(value, self.external_idx[i])
 
-    def update_external_data(self, external_mappings_numpy, external_mappings_time):
+        self.func(t, self.get_states())
+
+    def update_external_data(self, external_mappings_numpy, external_mappings_time, max_external_t, min_external_t):
         self.external_mappings_time = external_mappings_time
         self.external_mappings_numpy = external_mappings_numpy
+        self.max_external_t = max_external_t
+        self.min_external_t = min_external_t
 
     def is_external_data_update_needed(self, t):
-        if self.is_external_data and t > self.max_external_t:
+        if self.is_external_data and (t > self.max_external_t or t < self.min_external_t):
             return True
         return False
 
diff --git a/numerous/engine/model/events.py b/numerous/engine/model/events.py
index 1bbff856..4b48e2dd 100644
--- a/numerous/engine/model/events.py
+++ b/numerous/engine/model/events.py
@@ -15,8 +15,10 @@ def generate_event_condition_ast(event_functions: list[NumerousEvent],
     directions_array = []
     body = [ast.Assign(targets=[ast.Name(id=array_label)], lineno=0,
                        value=ast.List(elts=[], ctx=ast.Load()))]
-
+    compiled_functions = {}
     for event in event_functions:
+        if event.compiled_functions:
+            compiled_functions.update(event.compiled_functions)
         directions_array.append(event.direction)
         body.append(event.condition)
         body.append(ast.Expr(value=ast.Call(
@@ -37,27 +39,48 @@ def generate_event_condition_ast(event_functions: list[NumerousEvent],
                                                 kwonlyargs=[], kw_defaults=[], defaults=[]),
                              body=body, decorator_list=[], lineno=0)
 
-    return [njit_and_compile_function(body_r, from_imports)], np.array(directions_array)
+    return [njit_and_compile_function(body_r, from_imports, compiled_functions=compiled_functions)], np.array(
+        directions_array, dtype=np.float)
+
+
+class VariablesVisitor(ast.NodeVisitor):
+    def __init__(self, path_to_root_str, model, idx_type):
+        self.path_to_root_str = path_to_root_str
+        self.model = model
+        self.idx_type = idx_type
+
+    def generic_visit(self, node):
+        if isinstance(node, ast.Subscript) and isinstance(node.slice, ast.Constant):
+            if isinstance(node.slice.value, str):
+                for (var_path, var) in self.model.path_to_variable.items():
+                    if var_path.startswith(self.path_to_root_str):
+                        var_path = var_path[len(self.path_to_root_str):]
+                    if var_path == node.slice.value:
+                        node.slice.value = self.model._get_var_idx(var, self.idx_type)[0]
+            if isinstance(node.slice.value, str):
+                raise KeyError(f'No such variable: {node.slice.value}')
+
+        ast.NodeVisitor.generic_visit(self, node)
 
 
 def _replace_path_strings(model, function, idx_type, path_to_root=[]):
-    lines = inspect.getsource(function)
+    if hasattr(function, 'lines'):
+        lines = function.lines
+    else:
+        lines = inspect.getsource(function)
     path_to_root_str = ".".join(path_to_root) + "."
-    path_to_root_str_len = len(path_to_root_str)
-    for (var_path, var) in model.path_to_variable.items():
-        if var_path.startswith(path_to_root_str):
-            var_path = var_path[path_to_root_str_len:]
-        if var_path in lines:
-            lines = lines.replace('[\'' + var_path + '\']', str(model._get_var_idx(var, idx_type)))
     func = ast.parse(lines.strip()).body[0]
+    VariablesVisitor(path_to_root_str, model, idx_type).generic_visit(func)
     return func
 
 
 def generate_event_action_ast(event_functions: list[NumerousEvent],
                               from_imports: list[tuple[str, str]]) -> CPUDispatcher:
     body = []
-
+    compiled_functions = {}
     for idx, event in enumerate(event_functions):
+        if event.compiled_functions:
+            compiled_functions.update(event.compiled_functions)
         body.append(event.action)
         body.append(ast.If(test=ast.Compare(left=ast.Name(id='a_idx', ctx=ast.Load()), ops=[ast.Eq()],
                                             comparators=[ast.Constant(value=idx)]),
@@ -74,4 +97,4 @@ def generate_event_action_ast(event_functions: list[NumerousEvent],
                                                 kwonlyargs=[], kw_defaults=[], defaults=[]),
                              body=body, decorator_list=[], lineno=0)
 
-    return njit_and_compile_function(body_r, from_imports)
+    return njit_and_compile_function(body_r, from_imports, compiled_functions=compiled_functions)
diff --git a/numerous/engine/model/external_mappings/__init__.py b/numerous/engine/model/external_mappings/__init__.py
deleted file mode 100644
index f88fe97f..00000000
--- a/numerous/engine/model/external_mappings/__init__.py
+++ /dev/null
@@ -1,4 +0,0 @@
-from .external_mappings import ExternalMapping, ExternalMappingElement, EmptyMapping
-from .interpolation_type import InterpolationType
-
-__all__ = ['ExternalMapping', 'ExternalMappingElement', 'EmptyMapping', 'InterpolationType']
diff --git a/numerous/engine/model/external_mappings/external_mappings.py b/numerous/engine/model/external_mappings/external_mappings.py
deleted file mode 100644
index 82b08bb1..00000000
--- a/numerous/engine/model/external_mappings/external_mappings.py
+++ /dev/null
@@ -1,117 +0,0 @@
-import numpy as np
-
-from numerous.engine.model.external_mappings.interpolation_type import InterpolationType
-
-
-class ExternalMapping:
-    def __init__(self, external_mappings, data_loader):
-        self.external_mappings = external_mappings
-        self.data_loader = data_loader
-        self.external_mappings_numpy = []
-        self.external_mappings_time = []
-        self.external_columns = []
-        self.interpoaltion_type = []
-        self.t_max = 0
-        # loading initial dataframes
-        for element in self.external_mappings:
-            df = self.data_loader.load(element.data_frame_id, element.index_to_timestep_mapping_start)
-            element.add_df(df)
-        # mapping data
-        for element in self.external_mappings:
-            self.external_columns.append(list(element.dataframe_aliases.keys()))
-            self.interpoaltion_type.append([a_tuple[1] for a_tuple in list(element.dataframe_aliases.values())])
-            ext_mapping_numpy = element.df[[a_tuple[0] for a_tuple in list(element.dataframe_aliases.values())]]
-            self.external_mappings_numpy.append(
-                ext_mapping_numpy.to_numpy(dtype=np.float64)[element.index_to_timestep_mapping_start:]
-            )
-            self.external_mappings_time.append(
-                element.time_multiplier * element.df[element.index_to_timestep_mapping].to_numpy(dtype=np.float64)[
-                                          element.index_to_timestep_mapping_start:])
-            # TODO extend for multiple dataframes
-            self.t_max = np.max(self.external_mappings_time[0])
-        self.external_mappings_numpy = np.array(self.external_mappings_numpy, dtype=np.float64)
-        self.external_mappings_time = np.array(self.external_mappings_time, dtype=np.float64)
-        self.interpoaltion_type = [item for sublist in self.interpoaltion_type for item in sublist]
-        self.external_df_idx = []
-        self.interpolation_info = []
-
-    def load_new_external_data_batch(self, t):
-        self.external_mappings_numpy = []
-        self.external_mappings_time = []
-
-        for element in self.external_mappings:
-            # TODO division round bugs? we can skip a row here
-            df = self.data_loader.load(element.data_frame_id, int(t / element.time_multiplier) - 1)
-
-            if df.empty:
-                return False
-
-            element.add_df(df)
-        for element in self.external_mappings:
-            self.external_mappings_numpy.append(
-                element.df[
-                    [a_tuple[0] for a_tuple in list(element.dataframe_aliases.values())]
-                ].to_numpy(dtype=np.float64)[element.index_to_timestep_mapping_start:])
-            self.external_mappings_time.append(
-                element.time_multiplier * element.df[element.index_to_timestep_mapping].to_numpy(dtype=np.float64)[
-                                          element.index_to_timestep_mapping_start:])
-        self.external_mappings_numpy = np.array(self.external_mappings_numpy, dtype=np.float64)
-        self.external_mappings_time = np.array(self.external_mappings_time, dtype=np.float64)
-        # TODO extend for multiple dataframes
-        self.t_max = np.max(self.external_mappings_time[0])
-        return True
-
-    def store_mappings(self):
-        self.external_df_idx = np.array(self.external_df_idx, dtype=np.int64)
-        self.interpolation_info = np.array(self.interpolation_info, dtype=np.bool)
-
-    def add_df_idx(self, variables, var_id, system_id):
-        for i, external_column in enumerate(self.external_columns):
-            for path in variables[var_id].path.path[system_id]:
-                if path in external_column:
-                    i1 = external_column.index(path)
-                    self.external_df_idx.append((i, i1))
-                    self.interpolation_info.append(
-                        self.interpoaltion_type[i].value == InterpolationType.LINEAR.value)
-
-    def is_mapped_var(self, variables, var_id, system_id):
-        for path in variables[var_id].path.path[system_id]:
-            for columns in self.external_columns:
-                if path in columns:
-                    return True
-
-
-class ExternalMappingElement:
-
-    def __init__(self, data_frame_id, index_to_timestep_mapping, index_to_timestep_mapping_start, time_multiplier,
-                 dataframe_aliases):
-        self.data_frame_id = data_frame_id
-        self.index_to_timestep_mapping = index_to_timestep_mapping
-        self.index_to_timestep_mapping_start = index_to_timestep_mapping_start
-        self.time_multiplier = time_multiplier
-        self.dataframe_aliases = dataframe_aliases
-        self.df = None
-
-    # TODO check that e_m is correct format (have all the mappings) after loading
-    def add_df(self, df):
-        if self.df is not None:
-            columns = self.df.columns
-            self.df = df
-            self.df.columns = columns
-        else:
-            self.df = df
-
-
-class EmptyMapping:
-    def __init__(self):
-        self.external_mappings_numpy = np.empty([0, 0, 0], dtype=np.float64)
-        self.external_mappings_time = np.empty([0, 0], dtype=np.float64)
-        self.external_df_idx = np.empty([0, 0], dtype=np.int64)
-        self.t_max = 0
-        self.interpolation_info = np.empty([0], dtype=bool)
-
-    def store_mappings(self):
-        pass
-
-    def is_mapped_var(self, variables, var_id, system_id):
-        pass
diff --git a/numerous/engine/model/graph_representation/graph.py b/numerous/engine/model/graph_representation/graph.py
index 1d356385..ba0bf7db 100644
--- a/numerous/engine/model/graph_representation/graph.py
+++ b/numerous/engine/model/graph_representation/graph.py
@@ -14,7 +14,7 @@ def __init__(self, key=None, ao=None, file=None, name=None, ln=None,
                  label=None, ast_type=None, vectorized=None, item_id=None, node_type=None, ops=None, func=None,
                  id=None, local_id=None, scope_var=None,
                  ast_op=None, value=None, is_set_var=None, set_var_ix=None, subgraph_test=None,
-                 subgraph_body=None):
+                 subgraph_body=None, ctx=None):
         self.key = key
         self.ao = ao
         self.file = file
@@ -38,6 +38,7 @@ def __init__(self, key=None, ao=None, file=None, name=None, ln=None,
         self.subgraph_test = subgraph_test
         self.subgraph_body = subgraph_body
         self.node_n = -1
+        self.ctx = ctx
 
 
 class Edge:
@@ -199,7 +200,7 @@ def get_edges_for_node_filter(self, attr, start_node: int = None, end_node: int
         def filter_function(edge):
             if edge.deleted:
                 return False
-            if getattr(edge, attr) == val :
+            if getattr(edge, attr) == val:
                 return True
             else:
                 return False
diff --git a/numerous/engine/model/graph_representation/utils.py b/numerous/engine/model/graph_representation/utils.py
index c7e4dc61..b43823a1 100644
--- a/numerous/engine/model/graph_representation/utils.py
+++ b/numerous/engine/model/graph_representation/utils.py
@@ -20,7 +20,7 @@ def var_def(self, var, read=True):
             self.vars_inds_map.append(var)
         ix = self.vars_inds_map.index(var)
 
-        return ast.Subscript(slice=ast.Index(value=ast.Num(n=ix)), value=ast.Name(id='l'))
+        return ast.Subscript(slice=ast.Index(value=ast.Constant(value=ix)), value=ast.Name(id='l'))
 
 
 @unique
@@ -39,6 +39,8 @@ class EdgeType(IntEnum):
     TEST = 11
     COMP = 12
     DEP = 13
+    SLICE = 14
+    SUBSCRIPT_VALUE = 15
 
 
 def str_to_edgetype(a):
@@ -52,3 +54,7 @@ def str_to_edgetype(a):
         return EdgeType.ORELSE
     if a == 'test':
         return EdgeType.TEST
+    if a == 'subscript_value':
+        return EdgeType.SUBSCRIPT_VALUE
+    if a == 'slice':
+        return EdgeType.SLICE
diff --git a/numerous/engine/model/lowering/ast_builder.py b/numerous/engine/model/lowering/ast_builder.py
index c017cf7d..a56b7e44 100644
--- a/numerous/engine/model/lowering/ast_builder.py
+++ b/numerous/engine/model/lowering/ast_builder.py
@@ -120,6 +120,7 @@ def var_write(value, idx):
 
             return global_kernel, var_func, var_write
         else:
+
             exec(code, kernel_module.__dict__)
 
             def var_func():
@@ -128,6 +129,7 @@ def var_func():
             def var_write(value, idx):
                 np.put(kernel_module.kernel_variables, [idx], value)
 
+
             return kernel_module.global_kernel, var_func, var_write,
 
         return kernel_module.global_kernel, var_func, var_write
@@ -246,7 +248,6 @@ def add_mapping(self, args, targets):
             for arg in args:
                 arg_idxs.append(self.variable_names[arg])
             target_idx = self.variable_names[target[0]]
-            print(target_idx)
             if len(args) == 1:
                 temp=(ast.Assign(targets=[ast.Subscript(value=GLOBAL_ARRAY,
                                                                    slice=ast.Index(
diff --git a/numerous/engine/model/lowering/equations_generator.py b/numerous/engine/model/lowering/equations_generator.py
index 2fa15be4..e0e3397b 100644
--- a/numerous/engine/model/lowering/equations_generator.py
+++ b/numerous/engine/model/lowering/equations_generator.py
@@ -401,7 +401,7 @@ def _process_sum_node(self, n):
             for k, v in mapping_dict.items():
                 self.generated_program.add_mapping(v, [k])
 
-    def generate_equations(self, export_model=False):
+    def generate_equations(self, export_model=False, clonable=False):
         logging.info('Generate kernel')
         # Generate the ast for the python kernel
         for n in self.topo_sorted_nodes:
@@ -419,6 +419,7 @@ def generate_equations(self, export_model=False):
 
         deriv_idx = []
         state_idx = []
+        save_to_file = export_model or clonable
         for k, v in self.values_order.items():
             if k in self.deriv:
                 deriv_idx.append(v)
@@ -428,7 +429,7 @@ def generate_equations(self, export_model=False):
             logging.info('Generating llvm')
             diff, var_func, var_write = self.generated_program.generate(imports=self.imports,
                                                                         system_tag=self.system_tag,
-                                                                        save_to_file=export_model)
+                                                                        save_to_file=save_to_file)
 
             return diff, var_func, var_write, self.values_order, self.scope_variables, np.array(state_idx,
                                                                                                 dtype=np.int64), \
@@ -438,6 +439,6 @@ def generate_equations(self, export_model=False):
 
             global_kernel, var_func, var_write = self.generated_program.generate(self.imports,
                                                                                  system_tag=self.system_tag,
-                                                                                 save_to_file=export_model)
+                                                                                 save_to_file=save_to_file)
             return global_kernel, var_func, var_write, self.values_order, self.scope_variables, \
                    np.array(state_idx, dtype=np.int64), np.array(deriv_idx, dtype=np.int64)
\ No newline at end of file
diff --git a/numerous/engine/model/lowering/utils.py b/numerous/engine/model/lowering/utils.py
index 989800e8..3cfcead1 100644
--- a/numerous/engine/model/lowering/utils.py
+++ b/numerous/engine/model/lowering/utils.py
@@ -20,7 +20,7 @@ def wrap_module(body):
         if not hasattr(i, "_fields"):
             setattr(i, "_fields", [])
     mod.body = body
-    mod.type_ignores=[]
+    mod.type_ignores = []
     setattr(mod, "_fields", [])
     return mod
 
@@ -82,18 +82,22 @@ def format(self, var, read=True):
             _ctx = ast.Store()
         return ast.Name(id=var.replace('scope.', 's_'), lineno=0, col_offset=0, ctx=_ctx)
 
-    def format_target(self, var,read):
+    def format_target(self, var, read):
         if read:
             _ctx = ast.Load()
         else:
             _ctx = ast.Store()
         if self.llvm:
-            return ast.Subscript(slice=ast.Index(value=ast.Num(n=0, lineno=0,col_offset=0), lineno=0,col_offset=0), value=ast.Call(
-                args=[ast.Name(id=var.replace('scope.', 's_'), lineno=0,col_offset=0, ctx=ast.Load()), ast.Tuple(ctx=ast.Load(),elts=[ast.Num(n=1,lineno=0,col_offset=0)], lineno=0,col_offset=0)],
-                func=ast.Name(id='carray', lineno=0,col_offset=0, ctx=ast.Load()),
-                keywords=[], lineno=0,col_offset=0), lineno=0,col_offset=0, ctx=_ctx)
+            return ast.Subscript(
+                slice=ast.Index(value=ast.Constant(value=0, lineno=0, col_offset=0), lineno=0, col_offset=0),
+                value=ast.Call(
+                    args=[ast.Name(id=var.replace('scope.', 's_'), lineno=0, col_offset=0, ctx=ast.Load()),
+                          ast.Tuple(ctx=ast.Load(), elts=[ast.Constant(value=1, lineno=0, col_offset=0)], lineno=0,
+                                    col_offset=0)],
+                    func=ast.Name(id='carray', lineno=0, col_offset=0, ctx=ast.Load()),
+                    keywords=[], lineno=0, col_offset=0), lineno=0, col_offset=0, ctx=_ctx)
         else:
-            return ast.Name(id=var.replace('scope.', 's_'), lineno=0,col_offset=0, ctx=_ctx)
+            return ast.Name(id=var.replace('scope.', 's_'), lineno=0, col_offset=0, ctx=_ctx)
 
     def order_variables(self, order_data):
         for (var, var_id, used) in order_data:
@@ -107,7 +111,7 @@ def order_variables(self, order_data):
             if tmp_v in self.targets:
                 self.trgs_order.append(tmp_v)
 
-    def var_def(self, var,ctxread, read=True):
+    def var_def(self, var, ctxread, read=True):
         if not var in self.vars_inds_map:
             self.vars_inds_map.append(var)
         if read and 'scope.' in var:
@@ -125,28 +129,28 @@ def var_def(self, var,ctxread, read=True):
 
     def get_order_args(self, form=True):
         if form:
-            result = [self.format(a,False) for a in self.args_order]
+            result = [self.format(a, False) for a in self.args_order]
         else:
             result = self.args
         result_2 = []
         for name in result:
-            result_2.append(ast.arg(arg=name.id,  lineno=0, col_offset=0))
+            result_2.append(ast.arg(arg=name.id, lineno=0, col_offset=0))
         return result_2
 
     def get_order_trgs(self, form=True):
         if form:
-            return [self.format(a,False) for a in self.trgs_order]
+            return [self.format(a, False) for a in self.trgs_order]
         else:
             return self.args
 
     def get_args(self, form=True):
         if form:
-            return [self.format(a,False) for a in self.args]
+            return [self.format(a, False) for a in self.args]
         else:
             return self.args
 
     def get_targets(self, form=True):
         if form:
-            return [self.format(a,False) for a in self.targets]
+            return [self.format(a, False) for a in self.targets]
         else:
             return self.targets
diff --git a/numerous/engine/model/model.py b/numerous/engine/model/model.py
index ddd18878..0d95f971 100644
--- a/numerous/engine/model/model.py
+++ b/numerous/engine/model/model.py
@@ -2,7 +2,8 @@
 import os
 import pickle
 import sys
-from copy import copy
+
+from copy import copy, deepcopy
 from ctypes import CFUNCTYPE, c_int64, POINTER, c_double, c_void_p
 
 import numpy as np
@@ -15,11 +16,12 @@
 
 from numba.experimental import jitclass
 import pandas as pd
+from numerous.engine.model.aliased_dataframe import AliasedDataFrame
 
 from numerous.engine.model.events import generate_event_action_ast, generate_event_condition_ast, _replace_path_strings
-from numerous.engine.model.utils import Imports, njit_and_compile_function
-from numerous.engine.model.external_mappings import ExternalMapping, EmptyMapping
+from numerous.engine.model.utils import Imports
 from numerous.engine.numerous_event import NumerousEvent, TimestampEvent
+from numerous.engine.system.external_mappings import ExternalMapping, EmptyMapping, ExternalMappingUnpacked
 
 from numerous.utils.logger_levels import LoggerLevel
 
@@ -78,9 +80,16 @@ def ordered_variables(self):
             for v in vs:
                 variables___.append(v)
             variables__.append(variables___)
+
         variables__ = [list(x) for x in zip(*variables__)]
         variables__ = list(itertools.chain(*variables__))
-        return variables__
+        variables_ordered = [None] * len(variables__)
+        if self.is_set:
+            for i, v in enumerate(variables__):
+                v.detailed_description.variable_idx = i
+        for variable in variables__:
+            variables_ordered[variable.detailed_description.variable_idx] = variable
+        return variables_ordered
 
 
 class ModelAssembler:
@@ -116,8 +125,8 @@ class Model:
     """
 
     def __init__(self, system=None, logger_level=None, historian_filter=None, assemble=True, validate=False,
-                 external_mappings=None, data_loader=None, imports=None, historian=InMemoryHistorian(),
-                 use_llvm=True, save_to_file=False, generate_graph_pdf=False, export_model=False):
+                 imports=None, historian=InMemoryHistorian(),
+                 use_llvm=True, save_to_file=False, generate_graph_pdf=False, export_model=False, clonable=False):
 
         self.path_to_variable = {}
         self.generate_graph_pdf = generate_graph_pdf
@@ -126,12 +135,15 @@ def __init__(self, system=None, logger_level=None, historian_filter=None, assemb
             self.logger_level = LoggerLevel.ALL
         else:
             self.logger_level = logger_level
+        external_mappings_unpacked = system.get_external_mappings()
+        self.system_external_mappings = external_mappings_unpacked
+        self.is_external_data = True if len(external_mappings_unpacked) else False
+        self.external_mappings = ExternalMapping(external_mappings_unpacked) if len(
+            external_mappings_unpacked) else EmptyMapping()
 
-        self.is_external_data = True if external_mappings else False
-        self.external_mappings = ExternalMapping(external_mappings,
-                                                 data_loader) if external_mappings else EmptyMapping()
         self.use_llvm = use_llvm
         self.save_to_file = save_to_file
+        self.clonable = clonable
         self.imports = Imports()
         self.imports.add_as_import("numpy", "np")
         self.imports.add_from_import("numba", "njit")
@@ -191,21 +203,21 @@ def __init__(self, system=None, logger_level=None, historian_filter=None, assemb
             self.validate()
 
     def __add_item(self, item):
-        model_namespaces = []
+        model_namespaces = {}
         if item.id in self.model_items:
             return model_namespaces
 
         self.model_items.update({item.id: item})
-        model_namespaces.append((item.id, self.create_model_namespaces(item)))
+        model_namespaces[item.id] = self.create_model_namespaces(item)
         if isinstance(item, ItemSet):
             return model_namespaces
         if isinstance(item, Connector):
             for binded_item in item.get_binded_items():
                 if not binded_item.part_of_set:
-                    model_namespaces.extend(self.__add_item(binded_item))
+                    model_namespaces.update(self.__add_item(binded_item))
         if isinstance(item, Subsystem):
             for registered_item in item.registered_items.values():
-                model_namespaces.extend(self.__add_item(registered_item))
+                model_namespaces.update(self.__add_item(registered_item))
         return model_namespaces
 
     def __get_mapping__variable(self, variable, depth):
@@ -225,7 +237,6 @@ def assemble(self):
         - _pos as counterpart to _from
         -  _flat
         -  _3d
-
         """
 
         def __get_mapping__idx(variable):
@@ -238,14 +249,13 @@ def __get_mapping__idx(variable):
         assemble_start = time.time()
 
         # 1. Create list of model namespaces
-        model_namespaces = [_ns
-                            for item in self.system.registered_items.values() if not item.part_of_set
-                            for _ns in self.__add_item(item)]
+        model_namespaces = {item_id: _ns
+                            for item_id, _ns in self.__add_item(self.system).items()}
 
         # 2. Compute dictionaries
         # equation_dict <scope_id, [Callable]>
         # scope_variables <variable_id, Variable>
-        for variables, equation_dict in map(ModelAssembler.namespace_parser, model_namespaces):
+        for variables, equation_dict in map(ModelAssembler.namespace_parser, model_namespaces.items()):
             self.equation_dict.update(equation_dict)
             self.variables.update(variables)
 
@@ -276,16 +286,15 @@ def __get_mapping__idx(variable):
             v.top_item = self.system.id
 
         eq_used = []
-        for ns in model_namespaces:
-            # will be false for empty namespaces. Ones without equations and variables.
-            if ns[1]:
+        for item_id, namespaces in model_namespaces.items():
+            for ns in namespaces:
                 # Key : scope.tag Value: Variable or VariableSet
-                if ns[1][0].is_set:
-                    tag_vars = ns[1][0].set_variables
+                if ns.is_set:
+                    tag_vars = ns.set_variables
                 else:
-                    tag_vars = {v.tag: v for k, v in ns[1][0].variables.items()}
+                    tag_vars = {v.tag: v for k, v in ns.variables.items()}
 
-                parse_eq(model_namespace=ns[1][0], item_id=ns[0], mappings_graph=self.mappings_graph,
+                parse_eq(model_namespace=ns, item_id=item_id, mappings_graph=self.mappings_graph,
                          scope_variables=tag_vars, parsed_eq_branches=self.equations_parsed,
                          scoped_equations=self.scoped_equations, parsed_eq=self.equations_top, eq_used=eq_used)
         self.eq_used = eq_used
@@ -334,6 +343,7 @@ def __get_mapping__idx(variable):
             self.mappings_graph.as_graphviz(self.system.tag, force=True)
         self.lower_model_codegen(tmp_vars)
         self.assembly_tail()
+        self._initial_variables_dict = {k: v.value for k, v in self.variables.items()}
 
     def assembly_tail(self):
         self.logged_aliases = {}
@@ -380,9 +390,9 @@ def assembly_tail(self):
                 external_idx.append(self.vars_ordered_values[var.id])
                 number_of_external_mappings += 1
                 self.external_mappings.add_df_idx(self.variables, var.id, self.system.id)
-
         self.number_of_external_mappings = number_of_external_mappings
         self.external_mappings.store_mappings()
+
         self.external_idx = np.array(external_idx, dtype=np.int64)
         self.generate_path_to_varaible()
 
@@ -390,8 +400,12 @@ def assembly_tail(self):
         for item in self.model_items.values():
             if item.events:
                 for event in item.events:
-                    event.condition = _replace_path_strings(self, event.condition, "state", item.path)
-                    event.action = _replace_path_strings(self, event.action, "var", item.path)
+                    if event.compiled:
+                        pass
+                    else:
+                        event.condition = _replace_path_strings(self, event.condition, "var", item.path)
+                        event.action = _replace_path_strings(self, event.action, "var", item.path)
+
                     self.events.append(event)
             if item.timestamp_events:
                 for event in item.timestamp_events:
@@ -404,6 +418,20 @@ def assembly_tail(self):
         self.info.update({"Number of equations": len(self.compiled_eq)})
         self.info.update({"Solver": {}})
 
+    def _reset(self):
+        for k, v in self._initial_variables_dict.items():
+            self.variables[k].value = v
+            self.var_write(v, self.vars_ordered_values[k])
+
+    def set_variables(self, variables: dict):
+        for k, v in variables.items():
+            var_id = self.aliases[k]
+            self.variables[var_id].value = v
+            self.var_write(v, self.vars_ordered_values[var_id])
+
+    def get_variables_initial_values(self):
+        return {self.inverse_aliases[k]: v for k, v in self._initial_variables_dict.items()}
+
     def lower_model_codegen(self, tmp_vars):
 
         logging.info('Lowering model')
@@ -416,7 +444,7 @@ def lower_model_codegen(self, tmp_vars):
 
         compiled_compute, var_func, var_write, self.vars_ordered_values, self.variables, \
         self.state_idx, self.derivatives_idx = \
-            eq_gen.generate_equations(export_model=self.export_model)
+            eq_gen.generate_equations(export_model=self.export_model, clonable=self.clonable)
 
         for varname, ix in self.vars_ordered_values.items():
             var = self.variables[varname]
@@ -466,13 +494,18 @@ def c5():
             os.makedirs(os.path.dirname(filename), exist_ok=True)
             with open(filename, 'wb') as handle:
                 sys.setrecursionlimit(100000)
-                pickle.dump((self.system, self.logger_level, self.external_mappings,
+                pickle.dump((self.system, self.logger_level,
                              self.imports, self.use_llvm, self.vars_ordered_values, self.variables,
                              self.state_idx, self.derivatives_idx, self.init_values, self.aliases,
                              eq_gen.generated_program.equations_llvm_opt, eq_gen.generated_program.max_var,
                              eq_gen.generated_program.n_deriv), handle, protocol=pickle.HIGHEST_PROTOCOL)
             logging.info('Model successfully exported to ' + filename)
 
+        if self.clonable:
+            self.equations_llvm_opt = eq_gen.generated_program.equations_llvm_opt
+            self.max_var = eq_gen.generated_program.max_var
+            self.n_deriv = eq_gen.generated_program.n_deriv
+
         # set after export (otherwise we get pickling error for var.write_variable=var_write)
         for varname, ix in self.vars_ordered_values.items():
             var = self.variables[varname]
@@ -543,7 +576,7 @@ def search_items(self, item_tag):
         ----------
         items : list of :class:`numerous.engine.system.Item`
             set of items with given tag
-               """
+        """
         return [item for item in self.model_items.values() if item.tag == item_tag]
 
     @property
@@ -570,8 +603,7 @@ def get_variable_path(self, id, item):
         return ""
 
     def add_event(self, key, condition, action, terminal=True, direction=-1, compiled=False):
-        condition = _replace_path_strings(self, condition, "state")
-
+        condition = _replace_path_strings(self, condition, "var")
         action = _replace_path_strings(self, action, "var")
         event = NumerousEvent(key, condition, action, compiled, terminal, direction)
         self.events.append(event)
@@ -593,36 +625,16 @@ def action(t, v):
     def generate_mock_timestamp_event(self):
         def action(t, v):
             i = 1
+
         self.add_timestamp_event("mock", action, [-1])
 
-    def generate_event_condition_ast(self, is_numerous_solver: bool) -> tuple[list[CPUDispatcher], npt.ArrayLike]:
-        if len(self.events) == 0 and is_numerous_solver:
+    def generate_event_condition_ast(self) -> tuple[list[CPUDispatcher], npt.ArrayLike]:
+        if len(self.events) == 0:
             self.generate_mock_event()
-        if is_numerous_solver:
-            return generate_event_condition_ast(self.events, self.imports.from_imports)
-        else:
-            result = []
-            directions = []
-            for event in self.events:
-                if event.compiled:
-                    compiled_event = event
-                else:
-                    compiled_event = njit_and_compile_function(event.condition, self.imports.from_imports)
-                compiled_event.terminal = event.terminal
-                compiled_event.direction = event.direction
-                directions.append(event.direction)
-                result.append(compiled_event)
-            return result, np.array(directions)
-
-    def generate_event_action_ast(self, events, is_numerous_solver: bool) -> list[CPUDispatcher]:
-        if is_numerous_solver:
-            return [generate_event_action_ast(events, self.imports.from_imports)]
-        else:
-            result = []
-            for event in events:
-                compiled_event = njit_and_compile_function(event.action, self.imports.from_imports)
-                result.append(compiled_event)
-            return result
+        return generate_event_condition_ast(self.events, self.imports.from_imports)
+
+    def generate_event_action_ast(self, events) -> list[CPUDispatcher]:
+        return [generate_event_action_ast(events, self.imports.from_imports)]
 
     def _get_var_idx(self, var, idx_type):
         if idx_type == "state":
@@ -658,6 +670,7 @@ def create_model_namespaces(self, item):
             for eq in namespace.associated_equations.values():
                 equations = []
                 ids = []
+
                 for equation in eq.equations:
                     equations.append(equation)
                 for vardesc in eq.variables_descriptions:
@@ -692,8 +705,12 @@ class CompiledModel_instance(tmp):
             class CompiledModel_instance(tmp):
                 pass
 
-        NM_instance = CompiledModel_instance(self.init_values, self.derivatives_idx, self.state_idx,
-                                             self.global_vars, number_of_timesteps, start_time,
+        NM_instance = CompiledModel_instance(self.init_values,
+                                             self.derivatives_idx,
+                                             self.state_idx,
+                                             self.global_vars,
+                                             number_of_timesteps,
+                                             start_time,
                                              self.historian.get_historian_max_size(number_of_timesteps,
                                                                                    len(self.events)),
                                              self.external_mappings.external_mappings_time,
@@ -701,7 +718,9 @@ class CompiledModel_instance(tmp):
                                              self.external_mappings.external_mappings_numpy,
                                              self.external_mappings.external_df_idx,
                                              self.external_mappings.interpolation_info,
-                                             self.is_external_data, self.external_mappings.t_max,
+                                             self.is_external_data,
+                                             self.external_mappings.t_max,
+                                             self.external_mappings.t_min,
                                              self.external_idx
                                              )
 
@@ -715,7 +734,55 @@ class CompiledModel_instance(tmp):
         self.numba_model = NM_instance
         return self.numba_model
 
-    def set_functions(self, compiled_compute, var_func, var_write):
+    def set_functions(self, equations_llvm_opt, n_deriv, max_var):
+        for equation in equations_llvm_opt:
+            llmod2 = llvm.parse_assembly(equation)
+            ee.add_module(llmod2)
+        ee.finalize_object()
+        cfptr = ee.get_function_address("kernel")
+
+        cfptr_var_r = ee.get_function_address("vars_r")
+        cfptr_var_w = ee.get_function_address("vars_w")
+
+        c_float_type = type(np.ctypeslib.as_ctypes(np.float64()))
+
+        diff_ = CFUNCTYPE(POINTER(c_float_type), POINTER(c_float_type))(cfptr)
+
+        vars_r = CFUNCTYPE(POINTER(c_float_type), c_int64)(cfptr_var_r)
+
+        vars_w = CFUNCTYPE(c_void_p, c_double, c_int64)(cfptr_var_w)
+
+        n_deriv = n_deriv
+        max_var = max_var
+
+        @njit('float64[:](float64[:])')
+        def compiled_compute(y):
+            deriv_pointer = diff_(y.ctypes)
+            return carray(deriv_pointer, (n_deriv,)).copy()
+
+        @njit('float64[:]()')
+        def var_func():
+            variables_pointer = vars_r(0)
+            variables_array = carray(variables_pointer, (max_var,))
+
+            return variables_array.copy()
+
+        @njit('void(float64,int64)')
+        def var_write(var, idx):
+            vars_w(var, idx)
+
+        def c1(self, array_):
+            return compiled_compute(array_)
+
+        def c2(self):
+            return var_func()
+
+        def c3(self, value, idx):
+            return var_write(value, idx)
+
+        setattr(CompiledModel, "compiled_compute", c1)
+        setattr(CompiledModel, "read_variables", c2)
+        setattr(CompiledModel, "write_variables", c3)
 
         for varname, ix in self.vars_ordered_values.items():
             var = self.variables[varname]
@@ -752,10 +819,10 @@ def generate_not_nan_history_array(self):
     def create_historian_df(self):
         if self.historian_df is not None:
             import pandas as pd
-            self.historian_df = AliasedDataFrame(pd.concat([self.historian_df,
+            self.historian_df = AliasedDataFrame(pd.concat([self.historian_df.df,
                                                             self._generate_history_df(
                                                                 self.generate_not_nan_history_array(),
-                                                                rename_columns=False)],
+                                                                rename_columns=False).df],
                                                            axis=0, sort=False, ignore_index=True),
                                                  aliases=self.aliases, rename_columns=True)
 
@@ -768,13 +835,50 @@ def _generate_history_df(self, historian_data, rename_columns=True):
         data = self.create_historian_dict(historian_data)
         return AliasedDataFrame(data, aliases=self.aliases, rename_columns=True)
 
+    def set_external_mappings(self, external_mappings, data_loader):
+        external_mappings_unpacked = [ExternalMappingUnpacked(external_mappings, data_loader)]
+        external_mappings_unpacked = self.system_external_mappings + external_mappings_unpacked
+        self.is_external_data = True if len(external_mappings_unpacked) else False
+        self.external_mappings = ExternalMapping(external_mappings_unpacked) if len(
+            external_mappings_unpacked) else EmptyMapping()
+
+        number_of_external_mappings = 0
+        external_idx = []
+
+        for var in self.variables.values():
+            if self.external_mappings.is_mapped_var(self.variables, var.id, self.system.id):
+                external_idx.append(self.vars_ordered_values[var.id])
+                number_of_external_mappings += 1
+                self.external_mappings.add_df_idx(self.variables, var.id, self.system.id)
+        self.number_of_external_mappings = number_of_external_mappings
+        self.external_mappings.store_mappings()
+
+        self.external_idx = np.array(external_idx, dtype=np.int64)
+
+        self.numba_model.external_idx = self.external_idx
+        self.numba_model.external_mappings_time = self.external_mappings.external_mappings_time
+        self.numba_model.number_of_external_mappings = self.number_of_external_mappings
+        self.numba_model.external_mappings_numpy = self.external_mappings.external_mappings_numpy
+        self.numba_model.external_df_idx = self.external_mappings.external_df_idx
+        self.numba_model.approximation_type = self.external_mappings.interpolation_info
+        self.numba_model.is_external_data = self.is_external_data
+        self.numba_model.max_external_t = self.external_mappings.t_max
+        self.numba_model.min_external_t = self.external_mappings.t_min
+
+        self.numba_model.map_external_data(0)
+
+        # if self.numba_model.is_external_data_update_needed(0):
+        self.numba_model.is_external_data = self.external_mappings.load_new_external_data_batch(0)
+        if self.numba_model.is_external_data:
+            self.numba_model.update_external_data(self.external_mappings.external_mappings_numpy,
+                                                  self.external_mappings.external_mappings_time,
+                                                  self.external_mappings.t_max, self.external_mappings.t_min)
+
     @classmethod
     def from_file(cls, param):
-        system_, logger_level, external_mappings, imports, use_llvm, vars_ordered_values, variables, state_idx, \
+        system_, logger_level, imports, use_llvm, vars_ordered_values, variables, state_idx, \
         derivatives_idx, init_values, aliases, equations_llvm_opt, max_var, n_deriv = pickle.load(open(param, "rb"))
-        if isinstance(external_mappings, EmptyMapping):
-            external_mappings = None
-        model = Model(system=system_, assemble=False, logger_level=logger_level, external_mappings=external_mappings,
+        model = Model(system=system_, assemble=False, logger_level=logger_level,
                       use_llvm=use_llvm)
         model.variables = variables
         model.imports = imports
@@ -783,78 +887,32 @@ def from_file(cls, param):
         model.derivatives_idx = derivatives_idx
         model.init_values = init_values
         model.aliases = aliases
-        for equation in equations_llvm_opt:
-            llmod2 = llvm.parse_assembly(equation)
-            ee.add_module(llmod2)
-        ee.finalize_object()
-        cfptr = ee.get_function_address("kernel")
-
-        cfptr_var_r = ee.get_function_address("vars_r")
-        cfptr_var_w = ee.get_function_address("vars_w")
-
-        c_float_type = type(np.ctypeslib.as_ctypes(np.float64()))
-
-        diff_ = CFUNCTYPE(POINTER(c_float_type), POINTER(c_float_type))(cfptr)
-
-        vars_r = CFUNCTYPE(POINTER(c_float_type), c_int64)(cfptr_var_r)
-
-        vars_w = CFUNCTYPE(c_void_p, c_double, c_int64)(cfptr_var_w)
-
-        @njit('float64[:](float64[:])')
-        def compiled_compute(y):
-            deriv_pointer = diff_(y.ctypes)
-            return carray(deriv_pointer, (n_deriv,)).copy()
-
-        @njit('float64[:]()')
-        def var_func():
-            variables_pointer = vars_r(0)
-            variables_array = carray(variables_pointer, (max_var,))
-
-            return variables_array.copy()
 
-        @njit('void(float64,int64)')
-        def var_write(var, idx):
-            vars_w(var, idx)
-
-        def c1(self, array_):
-            return compiled_compute(array_)
-
-        def c2(self):
-            return var_func()
-
-        def c3(self, value, idx):
-            return var_write(value, idx)
-
-        setattr(CompiledModel, "compiled_compute", c1)
-        setattr(CompiledModel, "read_variables", c2)
-        setattr(CompiledModel, "write_variables", c3)
-
-        model.set_functions(compiled_compute, var_func, var_write)
+        model.set_functions(equations_llvm_opt, n_deriv, max_var)
         return model
 
+    def clone(self, clonable=False):
+        if not self.clonable:
+            raise Exception("Model isn't clonable")
+        system_ = self.system
+        logger_level = self.logger_level
+        use_llvm = self.use_llvm
+        model = Model(system=system_, assemble=False, logger_level=logger_level,
+                      use_llvm=use_llvm)
+        model.variables = self.variables
+        model.imports = self.imports
+        model.vars_ordered_values = self.vars_ordered_values
+        model.state_idx = self.state_idx
+        model.derivatives_idx = self.derivatives_idx
+        model.init_values = self.init_values
+        model.aliases = self.aliases
+
+        model.set_functions(self.equations_llvm_opt, self.n_deriv, self.max_var)
+
+        if clonable:
+            model.clonable = True
+            model.equations_llvm_opt = self.equations_llvm_opt
+            model.n_deriv = self.n_deriv
+            model.max_var = self.max_var
 
-class AliasedDataFrame(pd.DataFrame):
-    _metadata = ['aliases']
-
-    def __init__(self, data, aliases={}, rename_columns=True):
-        super().__init__(data)
-        self.aliases = aliases
-        self.rename_columns = rename_columns
-        if self.rename_columns:
-            tmp = copy(list(data.keys()))
-            for key in tmp:
-                if key in self.aliases.keys():
-                    data[self.aliases[key]] = data.pop(key)
-            super().__init__(data)
-
-    def __getitem__(self, item):
-        if self.rename_columns:
-            if not isinstance(item, list):
-                col = self.aliases[item] if item in self.aliases else item
-                return super().__getitem__(col)
-
-            cols = [self.aliases[i] if i in self.aliases else i for i in item]
-
-            return super().__getitem__(cols)
-        else:
-            return super().__getitem__(item)
+        return model
diff --git a/numerous/engine/model/utils.py b/numerous/engine/model/utils.py
index 04ec1466..3fc9fc58 100644
--- a/numerous/engine/model/utils.py
+++ b/numerous/engine/model/utils.py
@@ -33,6 +33,8 @@ class NodeTypes(IntEnum):
     TMP = 5
     ASSIGNTUPLE = 6
     IF = 7
+    FMU_EQUATION = 8
+    SUBSCRIPT = 9
 
 
 def recurse_Attribute(attr, sep='.'):
@@ -44,7 +46,8 @@ def recurse_Attribute(attr, sep='.'):
         return recurse_Attribute(attr.value) + sep + attr.attr
 
 
-def njit_and_compile_function(func: ast.FunctionDef, from_imports: list[(str, str)]) -> CPUDispatcher:
+def njit_and_compile_function(func: ast.FunctionDef, from_imports: list[(str, str)],
+                              compiled_functions: list[CPUDispatcher] = None) -> CPUDispatcher:
     fname = func.name
     njit_decorator = ast.Name(id='njit', ctx=ast.Load())
     func.decorator_list = [njit_decorator]
@@ -57,15 +60,18 @@ def njit_and_compile_function(func: ast.FunctionDef, from_imports: list[(str, st
     body.append(func)
     body.append(
         ast.Return(value=ast.Name(id=fname, ctx=ast.Load(), lineno=0, col_offset=0), lineno=0, col_offset=0))
-
+    wrapper_name = fname + '1'
     func = wrap_function(fname + '1', body, decorators=[],
                          args=ast.arguments(posonlyargs=[], args=[], vararg=None, defaults=[],
                                             kwonlyargs=[], kw_defaults=[], lineno=0, kwarg=None))
     module_func = ast.Module(body=[func], type_ignores=[])
     code = compile(ast.parse(ast.unparse(module_func)), filename='event_storage', mode='exec')
-    namespace = {}
+    if compiled_functions:
+        namespace = compiled_functions
+    else:
+        namespace = {}
     exec(code, namespace)
-    compiled_func = list(namespace.values())[1]()
+    compiled_func = namespace[wrapper_name]()
     return compiled_func
 
 
diff --git a/numerous/engine/numerous_event.py b/numerous/engine/numerous_event.py
index 9c130589..44301838 100644
--- a/numerous/engine/numerous_event.py
+++ b/numerous/engine/numerous_event.py
@@ -1,15 +1,18 @@
 class NumerousEvent:
-    def __init__(self, key, condition, action, compiled, terminal, direction):
+
+    def __init__(self, key, condition, action, compiled, terminal, direction, compiled_functions=None):
         self.key = key
         self.condition = condition
         self.action = action
         self.compiled = compiled
+        self.compiled_functions = compiled_functions
         self.direction = direction
         self.terminal = terminal
 
 
 class TimestampEvent:
-    def __init__(self, key, action, timestamps):
+    def __init__(self, key, action, timestamps, compiled_functions=None):
         self.key = key
         self.action = action
+        self.compiled_functions = compiled_functions
         self.timestamps = timestamps
diff --git a/numerous/engine/simulation/simulation.py b/numerous/engine/simulation/simulation.py
index d48e08c6..7cc5603c 100644
--- a/numerous/engine/simulation/simulation.py
+++ b/numerous/engine/simulation/simulation.py
@@ -2,9 +2,8 @@
 from datetime import datetime
 import time
 import numpy as np
-from numerous.engine.simulation.solvers.base_solver import SolverType
-from numerous.engine.simulation.solvers.ivp_solver.ivp_solver import IVP_solver
 from numerous.engine.simulation.solvers.numerous_solver.numerous_solver import Numerous_solver
+from numerous.engine.model import Model
 
 
 class Simulation:
@@ -25,10 +24,9 @@ class Simulation:
                Not unique tag that will be used in reports or printed output.
     """
 
-    def __init__(self, model, solver_type=SolverType.SOLVER_IVP, t_start=0, t_stop=20000, num=1000, num_inner=1,
-                 max_event_steps=100,
-
-                 start_datetime=datetime.now(), **kwargs):
+    def __init__(self, model: Model, t_start: float = 0, t_stop: float = 20000, num: int = 1000,
+                 max_event_steps: int = 100,
+                 start_datetime: datetime = datetime.now(), **kwargs):
         """
             Creating a namespace.
 
@@ -42,76 +40,34 @@ def __init__(self, model, solver_type=SolverType.SOLVER_IVP, t_start=0, t_stop=2
             new_namespace : `VariableNamespace`
                 Empty namespace with given name
         """
+        if model.numba_model:
+            model._reset()
 
         time_, delta_t = np.linspace(t_start, t_stop, num + 1, retstep=True)
-        self.callbacks = []
         self.time = time_
         self.model = model
 
-        def __end_step(solver, y, t, events_action, event_id=None):
-            """
-
-            """
-            solver.y0 = y.flatten()
-
-            if event_id is not None:
-                self.model.update_local_variables()
-                ## slow code
-                list_var = [v.value for v in self.model.path_to_variable.values()]
-                events_action[event_id](t, list_var)
-                for i, var in enumerate(self.model.path_to_variable.values()):
-                    var.value = list_var[i]
-                self.model.update_all_variables()
-                solver.y0 = self.model.states_as_vector
-
-            else:
-                solver.numba_model.historian_update(t)
-                solver.numba_model.map_external_data(t)
-
-                if solver.numba_model.is_store_required():
-                    self.model.store_history(numba_model.historian_data)
-                    solver.numba_model.historian_reinit()
-                #
-                if solver.numba_model.is_external_data_update_needed(t):
-                    solver.numba_model.is_external_data = self.model.external_mappings.load_new_external_data_batch(t)
-                    if solver.numba_model.is_external_data:
-                        solver.numba_model.update_external_data(self.model.external_mappings.external_mappings_numpy,
-                                                                self.model.external_mappings.external_mappings_time)
-
-        self.end_step = __end_step
         logging.info("Generating Numba Model")
         generation_start = time.time()
         logging.info(f'Number of steps: {len(self.time)}')
-        numba_model = model.generate_compiled_model(t_start, len(self.time))
+        self.numba_model = model.generate_compiled_model(t_start, len(self.time))
 
         generation_finish = time.time()
         logging.info(f"Numba model generation finished, generation time: {generation_finish - generation_start}")
-        if solver_type.value == SolverType.SOLVER_IVP.value:
-            event_function, _ = model.generate_event_condition_ast(False)
-            action_function = model.generate_event_action_ast(model.events, False)
-            timestamp_action_function = model.generate_event_action_ast(model.timestamp_events, False)
-            timestamps = np.array([np.array(event.timestamps) for event in model.timestamp_events])
-            self.solver = IVP_solver(time_, delta_t, model, numba_model,
-                                     num_inner, max_event_steps, self.model.states_as_vector,
-                                     events=(event_function, action_function),
-                                     timestamp_events=(timestamp_action_function, timestamps),
-                                     **kwargs)
-
-        if solver_type.value == SolverType.NUMEROUS.value:
-            event_function, event_directions = model.generate_event_condition_ast(True)
-            action_function = model.generate_event_action_ast(model.events, True)
-            if len(model.timestamp_events) == 0:
-                model.generate_mock_timestamp_event()
-            timestamp_action_function = model.generate_event_action_ast(model.timestamp_events, True)
-            timestamps = np.array([np.array(event.timestamps) for event in model.timestamp_events])
-            self.solver = Numerous_solver(time_, delta_t, model, numba_model,
-                                          num_inner, max_event_steps, self.model.states_as_vector,
-                                          numba_compiled_solver=model.use_llvm,
-                                          events=(event_function, action_function), event_directions=event_directions,
-                                          timestamp_events=(timestamp_action_function, timestamps),
-                                          **kwargs)
-
-        self.solver.register_endstep(__end_step)
+
+        event_function, event_directions = model.generate_event_condition_ast()
+        event_function_full, _ = model.generate_event_condition_ast()
+        action_function = model.generate_event_action_ast(model.events)
+        if len(model.timestamp_events) == 0:
+            model.generate_mock_timestamp_event()
+        timestamp_action_function = model.generate_event_action_ast(model.timestamp_events)
+        timestamps = np.array([np.array(event.timestamps) for event in model.timestamp_events])
+        self.solver = Numerous_solver(time_, delta_t, model, self.numba_model,
+                                      max_event_steps, self.model.states_as_vector,
+                                      numba_compiled_solver=model.use_llvm,
+                                      events=(event_function, action_function), event_directions=event_directions,
+                                      timestamp_events=(timestamp_action_function, timestamps),
+                                      **kwargs)
 
         self.start_datetime = start_datetime
         self.info = model.info["Solver"]
@@ -119,17 +75,20 @@ def __end_step(solver, y, t, events_action, event_id=None):
 
         logging.info("Compiling Numba equations and initializing historian")
         compilation_start = time.time()
-        numba_model.func(t_start, numba_model.get_states())
-        numba_model.historian_update(t_start)
+        self.numba_model.map_external_data(t_start)
+        self.numba_model.func(t_start, self.numba_model.get_states())
+        self.numba_model.historian_update(t_start)
         compilation_finished = time.time()
         logging.info(
             f"Numba equations compiled, historian initizalized, compilation time: {compilation_finished - compilation_start}")
 
-        self.compiled_model = numba_model
+        self.compiled_model = self.numba_model
 
-    def solve(self):
         self.reset()
 
+    def solve(self):
+
+
         sol, self.result_status = self.solver.solve()
 
         self.info.update({"Solving status": self.result_status})
@@ -137,18 +96,24 @@ def solve(self):
         return sol
 
     def reset(self):
+        #  TODO: currently, this method assumes solve start time to be 0
+
         self.__init_step()
-        self.model.numba_model.historian_reinit()
 
-        self.end_step(self.solver, self.model.numba_model.get_states(), 0, [])
+        self.model.historian_df = None
+        self.model.numba_model.historian_reinit()
 
-    def step(self, dt):
-        try:
-            stop = self.solver.solver_step(dt)
-        except Exception as e:
-            raise e
+        self.numba_model.map_external_data(0)
+        self.numba_model.func(0, self.numba_model.get_states())
+        self.numba_model.historian_update(0)
 
-        return stop
+        if self.numba_model.is_external_data_update_needed(0):
+            self.numba_model.is_external_data = self.model.external_mappings.load_new_external_data_batch(0)
+        if self.numba_model.is_external_data:
+            self.numba_model.update_external_data(self.model.external_mappings.external_mappings_numpy,
+                                                  self.model.external_mappings.external_mappings_time,
+                                                  self.model.external_mappings.t_max,
+                                                  self.model.external_mappings.t_min)
 
     def complete(self):
 
@@ -156,9 +121,9 @@ def complete(self):
                                                           self.model.path_variables), self.model.callbacks))
         self.model.create_historian_df()
 
-    def step_solve(self, t, step_size):
+    def step_solve(self, t_start, step_size):
         try:
-            t, results_status = self.solver.solver_step(t, step_size)
+            t, results_status = self.solver.solver_step(t_start, step_size)
 
             return t, results_status
         except Exception as e:
diff --git a/numerous/engine/simulation/solvers/base_solver.py b/numerous/engine/simulation/solvers/base_solver.py
index 02e82ee9..44d21b23 100644
--- a/numerous/engine/simulation/solvers/base_solver.py
+++ b/numerous/engine/simulation/solvers/base_solver.py
@@ -6,7 +6,7 @@ class SolverType(Enum):
     NUMEROUS = 1
 
 
-solver_types = [SolverType.NUMEROUS, SolverType.SOLVER_IVP]
+solver_types = [SolverType.NUMEROUS]
 
 
 class BaseSolver:
diff --git a/numerous/engine/simulation/solvers/ivp_solver/__init__.py b/numerous/engine/simulation/solvers/ivp_solver/__init__.py
deleted file mode 100644
index e69de29b..00000000
diff --git a/numerous/engine/simulation/solvers/ivp_solver/ivp_solver.py b/numerous/engine/simulation/solvers/ivp_solver/ivp_solver.py
deleted file mode 100644
index b927ead8..00000000
--- a/numerous/engine/simulation/solvers/ivp_solver/ivp_solver.py
+++ /dev/null
@@ -1,124 +0,0 @@
-import time
-
-from scipy.integrate import solve_ivp
-from tqdm import tqdm
-import numpy as np
-
-from numerous.engine.simulation.solvers.base_solver import BaseSolver
-
-"""
-Wrapper for scipy ivp solver.
-"""
-
-
-class IVP_solver(BaseSolver):
-
-    def __init__(self, time, delta_t, model, numba_model, num_inner, max_event_steps, y0, events, timestamp_events, **kwargs):
-        super().__init__()
-        self.model = model
-        self.time = time
-        self.eventf = []
-        self.event_a = []
-        for event_cond in events[0]:
-            self.eventf.append(event_cond)
-        for event_action in events[1]:
-            self.event_a.append(event_action)
-
-        self.timestamps_actions = timestamp_events[0]
-        self.timestamps = timestamp_events[1]
-        self.y0 = y0
-        self.num_inner = num_inner
-        self.delta_t = delta_t
-        self.numba_model = numba_model
-        self.diff_function = numba_model.func
-        self.max_event_steps = max_event_steps
-        self.options = kwargs
-        self.t = 0
-
-    def solve(self):
-        """
-        solve the model.
-        Returns
-        -------
-        Solution : 'OdeSolution'
-                returns the most recent OdeSolution from scipy
-        """
-        self.result_status = "Success"
-        self.sol = None
-
-        try:
-            for t in tqdm(self.time[0:-1]):
-                if self.solver_step(t):
-                    break
-        except Exception as e:
-            print(e)
-            raise e
-
-        return self.sol, self.result_status
-
-    def solver_step(self, t, delta_t=None):
-        step_not_finished = True
-        current_timestamp = t
-        event_steps = 0
-
-        step_solver_mode = False
-        if delta_t is None:
-            delta_t = self.delta_t
-        else:
-            step_solver_mode = True
-
-        stop_condition = False
-        t_end = current_timestamp + delta_t
-
-        while step_not_finished:
-            t_eval = np.linspace(current_timestamp, t_end, self.num_inner + 1)
-
-            self.sol = solve_ivp(self.diff_function, (current_timestamp, t + delta_t), y0=self.y0, t_eval=t_eval,
-                                 dense_output=False, events=self.eventf,
-                                 **self.options)
-            step_not_finished = False
-            event_step = self.sol.status == 1
-
-            if self.sol.status == 0:
-                current_timestamp = t_end
-                if step_solver_mode:  # added this
-                    self.model.numba_model.historian_update(current_timestamp)
-                    self.y0 = self.sol.y[:, -1]
-                    return current_timestamp, self.sol.t[-1]
-                for id, timestamps in enumerate(self.timestamps):
-                    for timestamp in timestamps:
-                        if abs(timestamp - current_timestamp) < 1e-6:
-                            self.__end_step(self, self.sol.y, current_timestamp, self.timestamps_actions, event_id=id)
-            if event_step:
-                event_id = np.nonzero([x.size > 0 for x in self.sol.t_events])[0][0]
-                # solution stuck
-                stop_condition = False
-                if (abs(self.sol.t_events[event_id][0] - current_timestamp) < 1e-6):
-                    event_steps += 1
-                else:
-                    event_steps = 0
-
-                if event_steps > self.max_event_steps:
-                    stop_condition = True
-                current_timestamp = self.sol.t_events[event_id][0]
-                step_not_finished = True
-                self.__end_step(self, self.sol.y, current_timestamp,self.event_a, event_id=event_id)
-            else:
-                if self.sol.success:
-                    self.__end_step(self, self.sol.y[:, -1], current_timestamp,self.event_a)
-                else:
-                    self.result_status = self.sol.message
-            if stop_condition:
-                break
-
-        self.t = t_end
-        if stop_condition:
-            self.result_status = "Stopping condition reached"
-            return True
-        return False
-
-    def set_state_vector(self, states_as_vector):
-        self.y0 = states_as_vector
-
-    def register_endstep(self, __end_step):
-        self.__end_step = __end_step
diff --git a/numerous/engine/simulation/solvers/numerous_solver/numerous_solver.py b/numerous/engine/simulation/solvers/numerous_solver/numerous_solver.py
index fd743a5b..3b1d1aec 100644
--- a/numerous/engine/simulation/solvers/numerous_solver/numerous_solver.py
+++ b/numerous/engine/simulation/solvers/numerous_solver/numerous_solver.py
@@ -1,15 +1,25 @@
 import logging
 import time
+import math
 from collections import namedtuple
 from enum import IntEnum, unique
 
 import numpy as np
 from numba import njit
+from copy import deepcopy
 
 from numerous.engine.simulation.solvers.base_solver import BaseSolver
-from .solver_methods import BaseMethod, RK45
+from .solver_methods import BaseMethod, RK45, Euler
 
-Info = namedtuple('Info', ['status', 'event_id', 'step_info', 'dt', 't', 'y', 'order_', 'roller', 'solve_state'])
+solver_methods = {'RK45': RK45, 'Euler': Euler}
+
+Info = namedtuple('Info', ['status', 'event_id', 'step_info', 'initial_step', 'dt', 't', 'y', 'order_', 'roller',
+                           'solve_state', 'ix_eval', 'g'])
+
+try:
+    FEPS = np.finfo(1.0).eps
+except AttributeError:
+    FEPS = 2.220446049250313e-16
 
 
 @unique
@@ -23,29 +33,36 @@ class SolveEvent(IntEnum):
     NoneEvent = 0
     Historian = 1
     ExternalDataUpdate = 2
+    HistorianAndExternalUpdate = 3
 
 
 class Numerous_solver(BaseSolver):
 
-    def __init__(self, time_, delta_t, model, numba_model, num_inner, max_event_steps, y0, numba_compiled_solver,
+    def __init__(self, time_, delta_t, model, numba_model, max_event_steps, y0, numba_compiled_solver,
                  events,
                  event_directions,
                  timestamp_events,
                  **kwargs):
         super().__init__()
 
+        def get_variables_modified(y_):
+            old_states = numba_model.get_states()
+            numba_model.set_states(y_)
+
+            vars = numba_model.read_variables().copy()
+            numba_model.set_states(old_states)
+            return vars
+
         self.events = events[0][0]
         self.event_directions = event_directions
         self.actions = events[1][0]
         self.timestamps = timestamp_events[1]
-
         self.timestamps_actions = timestamp_events[0][0]
         # events value
-        self.g = self.events(time_[0], y0)
+        self.g = self.events(time_[0], get_variables_modified(y0))
 
         self.time = time_
         self.model = model
-        self.num_inner = num_inner
         self.delta_t = delta_t
         self.numba_model = numba_model
         self.diff_function = numba_model.func
@@ -57,18 +74,19 @@ def __init__(self, time_, delta_t, model, numba_model, num_inner, max_event_step
         self.options = kwargs
         self.info = None
 
-        eps = np.finfo(1.0).eps
         odesolver_options = {'longer': kwargs.get('longer', 1.2), 'shorter': kwargs.get('shorter', 0.8),
-                             'min_step': kwargs.get('min_step', 10 * eps), 'strict_eval': True,
+                             'min_step': kwargs.get('min_step', 10 * FEPS), 'strict_eval': True,
                              'max_step': kwargs.get('max_step', np.inf), 'first_step': kwargs.get('first_step', None),
                              'atol': kwargs.get('atol', 1e-6), 'rtol': kwargs.get('rtol', 1e-3),
-                             'outer_itermax': kwargs.get('outer_itermax', 20),
                              'submethod': kwargs.get('submethod', None),
                              }
 
         self.method_options = odesolver_options
         try:
-            self.method = eval(kwargs.get('method', 'RK45'))
+            try:
+                self.method = solver_methods[kwargs.get('method', 'RK45')]
+            except KeyError:
+                raise ValueError(f"Unknown method {kwargs.get('method', 'RK45')}, allowed methods: {list(solver_methods.keys())}")
             self._method = self.method(self, **self.method_options)
             assert issubclass(self.method, BaseMethod), f"{self.method} is not a BaseMethod"
         except Exception as e:
@@ -76,36 +94,86 @@ def __init__(self, time_, delta_t, model, numba_model, num_inner, max_event_step
 
         self.y0 = y0
 
+        def run_event_action(actions, time_, numba_model, action_id):
+            modified_variables = actions(time_, numba_model.read_variables(), action_id)
+            modified_mask = (modified_variables != numba_model.read_variables())
+            for idx in np.argwhere(modified_mask):
+                numba_model.write_variables(modified_variables[idx[0]], idx[0])
+
+        def get_solver_event_id(t, numba_model):
+            if numba_model.is_store_required() and not numba_model.is_external_data_update_needed(t):
+                return SolveEvent.Historian
+            elif not numba_model.is_store_required() and numba_model.is_external_data_update_needed(t):
+                return SolveEvent.ExternalDataUpdate
+            elif numba_model.is_store_required() and numba_model.is_external_data_update_needed(t):
+                return SolveEvent.HistorianAndExternalUpdate
+            else:
+                return SolveEvent.NoneEvent
+
         # Generate the solver
         if numba_compiled_solver:
+            self.run_event_action = njit(run_event_action)
+            self.get_solver_event_id = njit(get_solver_event_id)
             self._non_compiled_solve = njit(self.generate_solver())
             self._solve = self.compile_solver()
+
         else:
+            self.run_event_action = run_event_action
+            self.get_solver_event_id = get_solver_event_id
             self._solve = self.generate_solver()
 
+        self.run_event_action(self.actions, 0, numba_model, 0)
+
     def generate_solver(self):
-        def _solve(numba_model, _solve_state, initial_step, order, order_, roller, strict_eval, outer_itermax,
+        def _solve(numba_model, _solve_state, initial_step, dt_0, order, order_, roller, strict_eval,
                    min_step, max_step, step_integrate_, events, actions, g, number_of_events, event_directions,
-                   timestamps, timestamp_actions,
-                   t0=0.0, t_end=1000.0, t_eval=np.linspace(0.0, 1000.0, 100)):
+                   run_event_action, get_solver_event_id, timestamps, timestamp_actions,
+                   t0=0.0, t_end=1000.0, t_eval=np.linspace(0.0, 1000.0, 100), ix_eval=1, event_tolerance=1e-6):
+
             # Init t to t0
             imax = 100
             step_info = 0
             t = t0
-            dt = initial_step
+            t_start = t0
+            dt = dt_0
+            numba_model.map_external_data(t)
             y = numba_model.get_states()
-            if y.shape[0] == 0:
-                for t in t_eval[1:]:
-                    numba_model.func(t, y)
-                    numba_model.historian_update(t)
-                    numba_model.map_external_data(t)
-                return Info(status=SolveStatus.Finished, event_id=SolveEvent.NoneEvent, step_info=step_info,
-                            dt=dt, t=t, y=y, order_=order_, roller=roller, solve_state=_solve_state)
-            t_start = t
+
             t_previous = t0
             y_previous = np.copy(y)
 
-            len_y = numba_model.get_states().shape[0]
+            # Define event derivatives, values and event guess times
+            te_array = np.zeros(2)
+
+            def is_internal_historian_update_needed(t_next_eval, t):
+                if abs(t_next_eval - t) < 100 * FEPS:
+                    return True
+                return False
+
+            def handle_converged(t, dt, ix_eval, t_next_eval):
+
+                if is_internal_historian_update_needed(t_next_eval, t):
+                    numba_model.historian_update(t)
+                    if strict_eval:
+                        te_array[1] = t_next_eval = t_eval[ix_eval + 1] if ix_eval + 1 < len(t_eval) else t_eval[-1]
+                    else:
+                        t_next_eval = t_eval[ix_eval + 1] if ix_eval + 1 < len(t_eval) else t_eval[-1]
+                    ix_eval += 1
+                    dt = initial_step
+                    te_array[0] = t + dt
+
+                t_start = t
+                t_new_test = np.min(te_array)
+
+                return ix_eval, t_start, t_next_eval, t_new_test, dt
+
+            def get_variables_modified(y_):
+                old_states = numba_model.get_states()
+                numba_model.set_states(y_)
+
+                vars = numba_model.read_variables().copy()
+                numba_model.set_states(old_states)
+                return vars
 
             def add_ring_buffer(t_, y_, rb, o):
 
@@ -125,40 +193,30 @@ def get_order_y(rb, order):
                 y = rb[2][0:order, :]
                 return y
 
-            # Define event derivatives, values and event guess times
-            # edt = np.ones(max(1, n_events)) * tol + 2.0
-            # et = np.zeros(n_events) if n_events > 0 else np.ones(1)
-            te_array = np.zeros(2)
             # 0 index is used to keep next time step defined by solver
             te_array[0] = t
             # 1 index is used to keep next time to eval/save the solution
-            ix_eval = 1
+
             te_array[1] = t_eval[ix_eval] + dt if strict_eval else np.inf
             t_next_eval = t_eval[ix_eval]
 
-            outer_iter = outer_itermax
-
             terminate = False
-            step_converged = True
+            step_converged = False
             event_trigger = False
             event_ix = -1
             t_event = t
             y_event = y
-            dt_last = -1
-            j_i = 0
-            progress_c = t_eval.shape[0]
+            t_event_previous = -1
+            solve_status = SolveStatus.Running
             while not terminate:
                 # updated events time estimates
                 # # time acceleration
                 if not step_converged:
-
                     if min_step > dt:
                         raise ValueError('dt shortened below min_dt')
-                    decrease = True
                     te_array[0] = t_previous + dt
                 elif step_converged and not event_trigger:
                     dt = min(max_step, dt)
-                    decrease = False
 
                     te_array[0] = t + dt
                 else:  # event
@@ -179,34 +237,9 @@ def get_order_y(rb, order):
                         # t_new_test = t_rollback
                         # TODO: make more specific error raising here!
                         raise ValueError('Cannot go back longer than rollback point!')
-                else:
-                    # Since we didnt roll back we can update t_start and rollback
-                    # Check if we should update history at t eval
-                    if t_next_eval <= (t + 10 * np.finfo(1.0).eps):
-                        j_i += 1
-                        p_size = 100
-                        x = int(p_size * j_i / progress_c)
-                        if not step_converged:
-                            print("step not converged, but historian updated")
-                        numba_model.historian_update(t)
-                        if strict_eval:
-                            te_array[1] = t_next_eval = t_eval[ix_eval + 1] if ix_eval + 1 < len(t_eval) else t_eval[-1]
-                        else:
-                            t_next_eval = t_eval[ix_eval + 1] if ix_eval + 1 < len(t_eval) else t_eval[-1]
-                        ix_eval += 1
-                        dt = initial_step
-                        te_array[0] = t + dt
-
-                    t_start = t
-                    t_new_test = np.min(te_array)
-                    if t >= t_end:
-                        break
-
-                    order_ = add_ring_buffer(t, y, roller, order_)
 
                 dt_ = min([t_next_eval - t_start, t_new_test - t_start])
                 # solve from start to new test by calling the step function
-
                 t, y, step_converged, step_info, _solve_state, factor = step_integrate_(numba_model,
                                                                                         t_start,
                                                                                         dt_, y,
@@ -228,13 +261,13 @@ def sol(t, t_r, y_r):
                         yi[i] = np.interp(t, tv, yvi)
                     return yi
 
-                def check_event(event_fun, ix, t_previous, y_previous, t, y, t_next_eval):
+                def check_event(event_fun, ix, t_previous, y_previous, t, y):
                     t_l = t_previous
                     y_l = y_previous
-                    e_l = event_fun(t_l, y_l)[ix]
+                    e_l = event_fun(t_l, get_variables_modified(y_l))[ix]
                     t_r = t
                     y_r = y
-                    e_r = event_fun(t_r, y_r)[ix]
+                    e_r = event_fun(t_r, get_variables_modified(y_r))[ix]
                     status = 0
                     if np.sign(e_l) == np.sign(e_r):
                         return status, t, y
@@ -243,30 +276,32 @@ def check_event(event_fun, ix, t_previous, y_previous, t, y, t_next_eval):
                     y_m = sol(t_m, t, y)
 
                     while status == 0:  # bisection method
-                        e_m = event_fun(t_m, y_m)[ix]
+                        e_m = event_fun(t_m, get_variables_modified(y_m))[ix]
                         if np.sign(e_l) != np.sign(e_m):
                             t_r = t_m
                         elif np.sign(e_r) != np.sign(e_m):
                             t_l = t_m
-                        if abs(e_m) < 1e-6:
+                        if abs(e_m) < 1e-6 or abs(t_l - t_r) < 1e-6:
                             status = 1
                         if i > imax:
                             status = -1
                         t_m = (t_l + t_r) / 2
                         y_m = sol(t_m, t, y)
 
-                    return status, t_m, y_m
+                    return status, t_r, sol(t_r, t, y)
 
                 if step_converged:
-                    g_new = events(t, y)
+                    g_new = events(t, get_variables_modified(y))
+
                     up = (g <= 0) & (g_new >= 0) & (event_directions == 1)
                     down = (g >= 0) & (g_new <= 0) & (event_directions == -1)
                     g = g_new
 
                     for ix in np.concatenate((np.argwhere(up), np.argwhere(down))):
+                        eps = 1.e-6  # for case to t_event = t
                         status, t_event, y_event = check_event(events, ix[0],
-                                                               t_previous, y_previous, t, y, t_next_eval)
-                        t_events[ix[0]] = t_event
+                                                               t_previous, y_previous, t, y)
+                        t_events[ix[0]] = t_event - eps
                         y_events[:, ix[0]] = y_event
 
                 if min(t_events) < t:
@@ -274,7 +309,7 @@ def check_event(event_fun, ix, t_previous, y_previous, t, y, t_next_eval):
                     event_ix = np.argmin(t_events)
                     t_event = t_events[event_ix]
                     y_event = y_events[:, event_ix]
-                    g = events(t_event, y_event)
+                    g = events(t_event, get_variables_modified(y_event))
 
                 if not event_trigger and step_converged:
                     y_previous = y
@@ -290,28 +325,55 @@ def check_event(event_fun, ix, t_previous, y_previous, t, y, t_next_eval):
                                 y_previous = numba_model.get_states()
 
                 if event_trigger:
-                    numba_model.set_states(y_event)
-                    modified_variables = actions(t_event, numba_model.read_variables(), event_ix)
-                    modified_mask = (modified_variables != numba_model.read_variables())
-                    for idx in np.argwhere(modified_mask):
-                        numba_model.write_variables(modified_variables[idx[0]], idx[0])
-                    y_previous = numba_model.get_states()
-                    t_previous = t_event
-                    numba_model.historian_update(t_event)
+                    # Improve detection of event
+
+                    if abs(t_event - t_event_previous) > event_tolerance:
+                        t_event_previous = t_event
+                        step_converged = False  # roll back and refine search
+                        dt = initial_step
+                        g = events(t, get_variables_modified(y_previous))
+                    else:
+                        numba_model.set_states(y_event)
+
+                        run_event_action(actions, t_event, numba_model, event_ix)
+
+                        y_previous = numba_model.get_states()
+                        t_previous = t_event
+
+                        # TODO: Update solve in solver_methods with new solve state after changing states due to events
+
+                        numba_model.historian_update(t_event)
+                        # Immediate rollback in case of exit
+                        t = t_previous
+                        y = y_previous
+
+                        solve_event_id = get_solver_event_id(t, numba_model)
+                        if solve_event_id == SolveEvent.Historian:
+                            break
 
                 if step_converged:
+
+                    solve_event_id = get_solver_event_id(t, numba_model)
+                    if solve_event_id == SolveEvent.ExternalDataUpdate:
+                        break
+
                     numba_model.map_external_data(t)
-                    if numba_model.is_store_required():
-                        return Info(status=SolveStatus.Running, event_id=SolveEvent.Historian, step_info=step_info,
-                                    dt=dt, t=t, y=np.ascontiguousarray(y), order_=order_, roller=roller,
-                                    solve_state=_solve_state)
-                    if numba_model.is_external_data_update_needed(t):
-                        return Info(status=SolveStatus.Running, event_id=SolveEvent.ExternalDataUpdate,
-                                    step_info=step_info, dt=dt, t=t, y=np.ascontiguousarray(y), order_=order_,
-                                    roller=roller, solve_state=_solve_state)
-
-            return Info(status=SolveStatus.Finished, event_id=SolveEvent.NoneEvent, step_info=step_info,
-                        dt=dt, t=t, y=np.ascontiguousarray(y), order_=order_, roller=roller, solve_state=_solve_state)
+                    ix_eval, t_start, t_next_eval, t_new_test, dt = \
+                        handle_converged(t, dt, ix_eval, t_next_eval)
+
+                    if abs(t - t_end) < 100 * FEPS:
+                        solve_status = SolveStatus.Finished
+                        break
+
+                    solve_event_id = get_solver_event_id(t, numba_model)
+                    if solve_event_id == SolveEvent.Historian:
+                        break
+
+                    order_ = add_ring_buffer(t, y, roller, order_)
+
+            return Info(status=solve_status, event_id=get_solver_event_id(t, numba_model), step_info=step_info,
+                        dt=dt, t=t, y=np.ascontiguousarray(y), order_=order_, roller=roller, solve_state=_solve_state,
+                        ix_eval=ix_eval, g=g, initial_step=initial_step)
 
         return _solve
 
@@ -326,7 +388,6 @@ def compile_solver(self):
         min_step = self.method_options.get('min_step')
 
         strict_eval = self.method_options.get('strict_eval', True)
-        outer_itermax = self.method_options.get('outer_itermax', 20)
 
         order = self._method.order
         initial_step = min_step
@@ -336,19 +397,24 @@ def compile_solver(self):
         order_ = 0
 
         args = (self.numba_model,
-                self._method.get_solver_state(len(self.y0)), initial_step,
-                order, order_, roller, strict_eval, outer_itermax, min_step,
+                self._method.get_solver_state(len(self.y0)), initial_step, initial_step,
+                order, order_, roller, strict_eval, min_step,
                 max_step, step_integrate_,
                 self.events,
                 self.actions,
                 self.g,
                 self.number_of_events,
                 self.event_directions,
+                self.run_event_action,
+                self.get_solver_event_id,
                 self.timestamps,
                 self.timestamps_actions,
                 self.time[0],
                 self.time[-1],
-                self.time)
+                self.time,
+                1,
+                self.method_options.get('atol')
+                )
         for a in args:
             argtypes.append(self._non_compiled_solve.typeof_pyval(a))
         # Return the solver function
@@ -360,9 +426,6 @@ def compile_solver(self):
 
         return _solve
 
-    def set_state_vector(self, states_as_vector):
-        self.y0 = states_as_vector
-
     def select_initial_step(self, nm, t0, y0, direction, order, rtol, atol):
         """Taken from scipy select initial step part of the ode solver package. Slightly modified
 
@@ -433,134 +496,173 @@ def _init_roller(self, order):
         roller = (n, np.zeros(n), rb0)
         return roller
 
-    def solve(self):
-        """
-        solve the model.
-
-        Returns
-        -------
-        Solution : 'OdeSolution'
-                returns the most recent OdeSolution from scipy
-
-        """
-        self.result_status = "Success"
-        self.sol = None
-        logging.info('Solve started')
-        # try:
-        t_start = self.time[0]
-        t_end = self.time[-1]
-
-        # Call the solver
-        from copy import deepcopy
-        y0 = deepcopy(self.y0)
+    def load_external_data(self, t):
+        is_external_data = self.model.external_mappings.load_new_external_data_batch(t)
+        external_mappings_numpy = self.model.external_mappings.external_mappings_numpy
+        external_mappings_time = self.model.external_mappings.external_mappings_time
+        max_external_t = self.model.external_mappings.t_max
+        min_external_t = self.model.external_mappings.t_min
+
+        if t > max_external_t:
+            raise ValueError(f"No more external data at t={t} (t_max={max_external_t}")
+        self.numba_model.is_external_data = is_external_data
+        self.numba_model.update_external_data(external_mappings_numpy, external_mappings_time, max_external_t,
+                                              min_external_t)
+
+    def use_no_state_solver(self):
+        states = self.numba_model.get_states()
+        if states.shape[0] == 0:
+            return True
+
+    def _no_state_solver_step(self, t, dt):
+        '''
+        This method calculates the model variables using mappings, which are pushed through using the .func method of
+        the numba_model class. It assumes that the first step has already been saved in the historian.
+        '''
+        solve_event_id = self.get_solver_event_id(t, self.numba_model)
+        if solve_event_id == SolveEvent.Historian:
+            self.model.create_historian_df()
+            self.numba_model.historian_reinit()
+        elif solve_event_id == SolveEvent.ExternalDataUpdate:
+            self.load_external_data(t)
+        elif solve_event_id == SolveEvent.HistorianAndExternalUpdate:
+            self.model.create_historian_df()
+            self.numba_model.historian_reinit()
+            self.load_external_data(t)
 
-        state = deepcopy(self.y0)
+        states = self.numba_model.get_states()
+        t += dt
+        if t > self.time[-1]:
+            return
+        self.numba_model.map_external_data(t)
+        self.numba_model.func(t, states)  # update mappings
+        self.numba_model.historian_update(t)
 
-        # Set options
+    def _init_solve(self, info=None):
 
         max_step = self.method_options.get('max_step')
         min_step = self.method_options.get('min_step')
         rtol = self.method_options.get('rtol')
         atol = self.method_options.get('atol')
-
+        strict_eval = self.method_options.get('strict_eval')
+        step_integrate_ = self._method.step_func
         order = self._method.order
 
-        initial_step = self.select_initial_step(self.numba_model, t_start, y0, 1, order - 1, rtol,
-                                                atol)  # np.min([100000000*min_step, max_step])
+        if not info:
+            t_start = self.time[0]
 
-        strict_eval = self.method_options.get('strict_eval')
-        outer_itermax = self.method_options.get('outer_itermax')
+            # Call the solver
 
-        step_integrate_ = self._method.step_func
+            y0 = deepcopy(self.y0)
 
-        # figure out solve_state init
-        solve_state = self._method.get_solver_state(len(y0))
+            # Set options
 
-        roller = self._init_roller(order)
-        order_ = 0
+            initial_step = self.select_initial_step(self.numba_model, t_start, y0, 1, order - 1, rtol,
+                                                    atol)  # np.min([100000000*min_step, max_step])
+            dt = initial_step
 
-        info = self._solve(self.numba_model,
-                           solve_state, initial_step, order, order_, roller, strict_eval, outer_itermax, min_step,
-                           max_step, step_integrate_, self.events, self.actions, self.g,
-                           self.number_of_events, self.event_directions, self.timestamps,
-                           self.timestamps_actions, t_start, t_end, self.time)
+            # figure out solve_state init
+            solve_state = self._method.get_solver_state(len(y0))
 
-        while info.status == SolveStatus.Running:
-            if info.event_id == 1:
-                self.model.create_historian_df()
-                self.numba_model.historian_reinit()
-            if info.event_id == 2:
-                is_external_data = self.model.external_mappings.load_new_external_data_batch(info.t)
-                external_mappings_numpy = self.model.external_mappings.external_mappings_numpy
-                external_mappings_time = self.model.external_mappings.external_mappings_time
-                self.numba_model.is_external_data = is_external_data
-                self.numba_model.update_external_data(external_mappings_numpy, external_mappings_time)
-            time_idx =np.argmax((self.time - info.t)>0)
-            info = self._solve(self.numba_model,
-                               solve_state, info.dt, order, strict_eval, outer_itermax, min_step,
-                               max_step, step_integrate_, self.events, self.actions, self.g,
-                               self.number_of_events, self.event_directions, self.timestamps, self.timestamps_actions,
-                               info.t, t_end, self.time[time_idx:])
+            roller = self._init_roller(order)
+            order_ = 0
+            g = self.g
+        else:
+
+            dt = self.info.dt  # internal solver step size
+            order_ = self.info.order_
+            roller = self.info.roller
+            solve_state = self.info.solve_state
+            g = self.info.g
+            initial_step = self.info.initial_step
+
+        return dt, strict_eval, step_integrate_, solve_state, roller, order_, order, initial_step, dt, min_step, \
+               max_step, atol, g
+
+    def solve(self):
+        """
+        solve the model.
+
+        Returns
+        -------
+        Solution : 'OdeSolution'
+                returns the most recent OdeSolution from scipy
+
+        """
+        self.result_status = "Success"
+        self.sol = None
+        logging.info('Solve started')
+
+        if self.use_no_state_solver():
+            dt = self.time[1] - self.time[0]
+            for t in self.time[0:-1]:
+                self._no_state_solver_step(t, dt)
+            return self.sol, self.result_status
+
+        self._solver(self.time)
 
         logging.info("Solve finished")
         return self.sol, self.result_status
 
     def solver_step(self, t, delta_t=None):
 
-        solve_state = self._method.get_solver_state(len(self.y0))
-        t_start = t
+        if self.use_no_state_solver():
+            self._no_state_solver_step(t, delta_t)
+            return t + delta_t, t + delta_t
 
         if delta_t is None:
             delta_t = self.delta_t
 
-        strict_eval = self.method_options.get('strict_eval')
-        outer_itermax = self.method_options.get('outer_itermax')
+        n = math.floor(t / delta_t + FEPS * 100) + 1
+        t_eval = np.linspace(t, n * delta_t, 2)
+        t_end = t_eval[-1]
 
-        max_step = self.method_options.get('max_step')
-        min_step = self.method_options.get('min_step')
-        rtol = self.method_options.get('rtol')
-        atol = self.method_options.get('atol')
-        order = self._method.order
-        if self.info is not None:
-            dt = self.info.dt  # internal solver step size
-            order_ = self.info.order_
-            roller = self.info.roller
-            solve_state = self.info.solve_state
+        info = self._solver(t_eval, info=self.info)
+        self.info = info
 
-            assert self.info.t == t_start, f"solver time {self.info.t} does not match external time " \
-                                           f"{t_start}"
-        else:
-            roller = self._init_roller(order)
-            order_ = 0
+        return t_end, self.info.t
 
-            dt = self.select_initial_step(self.numba_model, t_start, self.y0, 1, order - 1, rtol,
-                                          atol)
+    def _handle_solve_event(self, event_id: SolveEvent, t: float):
+        if event_id == SolveEvent.Historian:
+            self.model.create_historian_df()
+            self.numba_model.historian_reinit()
+        elif event_id == SolveEvent.ExternalDataUpdate:
+            self.load_external_data(t)
+        elif event_id == SolveEvent.HistorianAndExternalUpdate:
+            self.model.create_historian_df()
+            self.numba_model.historian_reinit()
+            self.load_external_data(t)
 
-        t_end = t_start + delta_t
+    def _solver(self, t_eval, info=None):
 
-        time_span = np.linspace(t_start, t_end, 2)
+        dt, strict_eval, step_integrate_, solve_state, roller, order_, order, initial_step, dt, min_step, max_step, \
+            atol, g = self._init_solve(info)
 
-        step_integrate_ = self._method.step_func
+        t_start = t_eval[0]
+        t_end = t_eval[-1]
 
         info = self._solve(self.numba_model,
-                           solve_state, dt, order, order_, roller, strict_eval, outer_itermax, min_step,
-                           max_step, step_integrate_, self.events, self.actions, self.g,
-                           self.number_of_events, self.event_directions, self.timestamps, self.timestamps_actions,
-                           t_start, t_end, time_span)
-
-        if info.event_id == 1:
-            self.model.store_history(self.numba_model.historian_data)
-            self.numba_model.historian_reinit()
-        if info.event_id == 2:
-            is_external_data = self.model.external_mappings.load_new_external_data_batch(info.t)
-            external_mappings_numpy = self.model.external_mappings.external_mappings_numpy
-            external_mappings_time = self.model.external_mappings.external_mappings_time
-            self.numba_model.is_external_data = is_external_data
-            self.numba_model.update_external_data(external_mappings_numpy, external_mappings_time)
+                           solve_state, initial_step, dt, order, order_, roller, strict_eval, min_step,
+                           max_step, step_integrate_, self.events, self.actions, g,
+                           self.number_of_events, self.event_directions, self.run_event_action,
+                           self.get_solver_event_id, self.timestamps,
+                           self.timestamps_actions, t_start, t_end,
+                           t_eval, 1, atol)
 
-        self.info = info
+        while info.status == SolveStatus.Running:
+            self._handle_solve_event(info.event_id, info.t)
 
-        return t_end, self.info.t
+            info = self._solve(self.numba_model,
+                               info.solve_state, initial_step, info.dt, order, info.order_, info.roller, strict_eval,
+                               min_step, max_step, step_integrate_, self.events, self.actions, info.g,
+                               self.number_of_events, self.event_directions, self.run_event_action,
+                               self.get_solver_event_id,
+                               self.timestamps,
+                               self.timestamps_actions, info.t, t_end,
+                               t_eval, info.ix_eval, atol)
+
+        self._handle_solve_event(info.event_id, info.t)
+        return info
 
     def register_endstep(self, __end_step):
         self.__end_step = __end_step
diff --git a/numerous/engine/simulation/solvers/numerous_solver/solver_methods.py b/numerous/engine/simulation/solvers/numerous_solver/solver_methods.py
index 9e780aab..4092692e 100644
--- a/numerous/engine/simulation/solvers/numerous_solver/solver_methods.py
+++ b/numerous/engine/simulation/solvers/numerous_solver/solver_methods.py
@@ -176,8 +176,6 @@ def get_solver_state(self, *args, **kwargs):
                 self.error_exponent, True)
 
 
-
-
 class LevenbergMarquardt(BaseMethod):
     def __init__(self, numerous_solver, **options):
         self.order = 5
@@ -436,3 +434,44 @@ def get_solver_state(self, n):
                  )
 
         return state
+
+
+class Euler(BaseMethod):
+    def __init__(self, numerous_solver, **options):
+
+        self.order = 1
+        self.max_factor = options.get('max_factor', 10)
+        self.atol = options.get('atol', 1e-3)
+        self.rtol = options.get('rtol', 1e-3)
+
+        profile = numerous_solver.numba_compiled_solver
+
+        def comp(fun):
+            if profile:
+                return njit(fun)
+            else:
+                # return options['lp'](fun)
+                return fun
+
+        @comp
+        def euler(nm, t, dt, y, _not_used1, _not_used2, _solve_state):
+
+            step_info = 1
+
+            tnew = t + dt
+
+            if len(y) == 0:
+                return tnew, y, True, step_info, _solve_state, 1e20
+
+            fnew = nm.func(t, y)
+
+            ynew = y + fnew * dt
+            # TODO figure out if this call can be avoided
+            nm.func(tnew, ynew)
+            return tnew, ynew, True, step_info, _solve_state, 1e20
+
+        self.step_func = euler
+
+    def get_solver_state(self, *args, **kwargs):
+        return (0.0, 0.0, 0.0, self.max_factor, self.atol, self.rtol, 0.0, 1, self.order,
+                1, True)
diff --git a/numerous/engine/system/external_mappings/__init__.py b/numerous/engine/system/external_mappings/__init__.py
new file mode 100644
index 00000000..e5990bae
--- /dev/null
+++ b/numerous/engine/system/external_mappings/__init__.py
@@ -0,0 +1,2 @@
+from .external_mappings import *
+from .interpolation_type import *
diff --git a/numerous/engine/system/external_mappings/external_mappings.py b/numerous/engine/system/external_mappings/external_mappings.py
new file mode 100644
index 00000000..e2fe8c1a
--- /dev/null
+++ b/numerous/engine/system/external_mappings/external_mappings.py
@@ -0,0 +1,129 @@
+import numpy as np
+
+from numerous.engine.system.external_mappings.interpolation_type import InterpolationType
+
+
+class ExternalMapping:
+    def __init__(self, external_mappings):
+        self.external_mappings = external_mappings
+        self.external_mappings_numpy = []
+        self.external_mappings_time = []
+        self.external_mappings_time = []
+        self.external_columns = []
+        self.interpolation_type = []
+        self.t_max = 0
+        for external_mapping in self.external_mappings:
+            for element in external_mapping.external_mappings:
+                df = external_mapping.data_loader.load(element.data_frame_id, element.index_to_timestep_mapping_start)
+                element.add_df(df)
+        for external_mapping in self.external_mappings:
+            for element in external_mapping.external_mappings:
+                self.external_columns.append(list(element.dataframe_aliases.keys()))
+                self.interpolation_type.append([a_tuple[1] for a_tuple in list(element.dataframe_aliases.values())])
+                self.external_mappings_numpy.append(
+                    element.df[[a_tuple[0] for a_tuple in list(element.dataframe_aliases.values())]]
+                    .to_numpy(dtype=np.float64)[element.index_to_timestep_mapping_start:])
+                self.external_mappings_time.append(
+                    element.time_multiplier * element.df[element.index_to_timestep_mapping]
+                    .to_numpy(dtype=np.float64)[element.index_to_timestep_mapping_start:])
+                # TODO extend for multiple dataframes
+                self.t_max = np.max(self.external_mappings_time[0])
+                self.t_min = np.min(self.external_mappings_time[0])
+
+        self.external_mappings_numpy = np.array(self.external_mappings_numpy, dtype=np.float64)
+        self.external_mappings_time = np.array(self.external_mappings_time, dtype=np.float64)
+        self.interpolation_type = [item for sublist in self.interpolation_type for item in sublist]
+        self.external_df_idx = []
+        self.interpolation_info = []
+
+    def load_new_external_data_batch(self, t):
+        self.external_mappings_numpy = []
+        self.external_mappings_time = []
+
+        for external_mapping in self.external_mappings:
+            for element in external_mapping.external_mappings:
+                # TODO division round bugs? we can skip a row here
+                df = external_mapping.data_loader.load(element.data_frame_id, t)
+
+                if df.empty:
+                    return False
+
+                element.add_df(df)
+        for external_mapping in self.external_mappings:
+            for element in external_mapping.external_mappings:
+                self.external_mappings_numpy.append(
+                    element.df[[a_tuple[0] for a_tuple in list(element.dataframe_aliases.values())]]
+                    .to_numpy(dtype=np.float64)[element.index_to_timestep_mapping_start:])
+                self.external_mappings_time.append(
+                    element.time_multiplier * element.df[element.index_to_timestep_mapping]
+                    .to_numpy(dtype=np.float64)[element.index_to_timestep_mapping_start:])
+
+        self.external_mappings_numpy = np.array(self.external_mappings_numpy, dtype=np.float64)
+        self.external_mappings_time = np.array(self.external_mappings_time, dtype=np.float64)
+        # TODO extend for multiple dataframes
+        self.t_max = np.max(self.external_mappings_time[0])
+        self.t_min = np.min(self.external_mappings_time[0])
+        return True
+
+    def store_mappings(self):
+        self.external_df_idx = np.array(self.external_df_idx, dtype=np.int64)
+        self.interpolation_info = np.array(self.interpolation_info, dtype=np.bool)
+
+    def add_df_idx(self, variables, var_id, system_id):
+        for i, external_column in enumerate(self.external_columns):
+            for path in variables[var_id].path.path[system_id]:
+                for index, column in enumerate(external_column):
+                    if column in path:
+                        self.external_df_idx.append((i, index))
+                        self.interpolation_info.append(
+                            self.interpolation_type[index].value == InterpolationType.LINEAR.value)
+
+    def is_mapped_var(self, variables, var_id, system_id):
+        for path in variables[var_id].path.path[system_id]:
+            for columns in self.external_columns:
+                for column in columns:
+                    if column == path:
+                        return True
+
+
+class ExternalMappingElement:
+
+    def __init__(self, data_frame_id, index_to_timestep_mapping, index_to_timestep_mapping_start, time_multiplier,
+                 dataframe_aliases):
+        self.data_frame_id = data_frame_id
+        self.index_to_timestep_mapping = index_to_timestep_mapping
+        self.index_to_timestep_mapping_start = index_to_timestep_mapping_start
+        self.time_multiplier = time_multiplier
+        self.dataframe_aliases = dataframe_aliases
+        self.df = None
+
+    # TODO check that e_m is correct format (have all the mappings) after loading
+    def add_df(self, df):
+        if self.df is not None:
+            columns = self.df.columns
+            self.df = df
+            self.df.columns = columns
+        else:
+            self.df = df
+
+
+class ExternalMappingUnpacked:
+    def __init__(self, external_mappings=None, data_loader=None):
+        self.data_loader = data_loader
+        self.external_mappings = external_mappings
+
+
+class EmptyMapping:
+    def __init__(self):
+        self.external_mappings_numpy = np.empty([0, 0, 0], dtype=np.float64)
+        self.external_mappings_time = np.empty([0, 0], dtype=np.float64)
+        self.external_df_idx = np.empty([0, 0], dtype=np.int64)
+        self.t_max = 0
+        self.t_min = 0
+        self.interpolation_info = np.empty([0], dtype=bool)
+
+    def store_mappings(self):
+        pass
+
+    def is_mapped_var(self, variables, var_id, system_id):
+        pass
diff --git a/numerous/engine/model/external_mappings/interpolation_type.py b/numerous/engine/system/external_mappings/interpolation_type.py
similarity index 98%
rename from numerous/engine/model/external_mappings/interpolation_type.py
rename to numerous/engine/system/external_mappings/interpolation_type.py
index 306d8484..2eb513e3 100644
--- a/numerous/engine/model/external_mappings/interpolation_type.py
+++ b/numerous/engine/system/external_mappings/interpolation_type.py
@@ -1,5 +1,6 @@
 from enum import Enum
 
+
 class InterpolationType(Enum):
     PIESEWISE = 0
     LINEAR = 1
diff --git a/numerous/engine/system/fmu_subsystem.py b/numerous/engine/system/fmu_subsystem.py
new file mode 100644
index 00000000..f9912490
--- /dev/null
+++ b/numerous/engine/system/fmu_subsystem.py
@@ -0,0 +1,460 @@
+import types as ptypes
+import ctypes
+import ast
+
+import pandas
+from fmpy import read_model_description, extract
+
+from fmpy.fmi1 import printLogMessage
+from fmpy.fmi2 import FMU2Model, fmi2CallbackFunctions, fmi2CallbackLoggerTYPE, fmi2CallbackAllocateMemoryTYPE, \
+    fmi2CallbackFreeMemoryTYPE, allocateMemory, freeMemory, fmi2EventInfo
+from fmpy.simulation import apply_start_values, Input
+from fmpy.util import auto_interval
+
+from numerous.engine.system.external_mappings import InterpolationType, ExternalMappingElement
+from numerous.multiphysics import EquationBase, Equation, NumerousFunction
+
+from numerous.engine.system import Subsystem, ExternalMappingUnpacked
+from numba import cfunc, carray, types, njit
+import numpy as np
+
+from numerous.engine.system.fmu_system_generator.fmu_ast_generator import generate_fmu_eval, generate_eval_llvm, \
+    generate_eval_event, generate_njit_event_cond, generate_action_event, generate_event_action, generate_eq_call
+from numerous.engine.system.fmu_system_generator.utils import address_as_void_pointer
+from numerous.utils.data_loader import InMemoryDataLoader
+
+
+def _replace_name_str(str_v):
+    return str_v.replace(".", "_").replace("[", "_").replace("]", "_").replace(",", "_")
+
+
+class FMU_Subsystem(Subsystem, EquationBase):
+    """
+    """
+
+    def __init__(self, fmu_filename: str, tag: str, fmu_in: str = None, debug_output=False, import_all=False):
+        super().__init__(tag)
+        self.model_description = None
+        self.import_all = import_all
+        input = None
+        fmi_call_logger = None
+        start_values = {}
+        validate = False
+        step_size = None
+        output_interval = None
+        start_values = start_values
+        apply_default_start_values = False
+        input = input
+        namespace_ = "t1"
+        debug_logging = False
+        visible = False
+        self.fmu_input = pandas.read_csv(fmu_in) if fmu_in is not None else None
+        self.dataframe_aliases = {}
+        model_description = read_model_description(fmu_filename, validate=validate)
+        self.model_description = model_description
+        self.value_ref_used = self.set_variables(self.model_description)
+        required_paths = ['resources', 'binaries/']
+        tempdir = extract(fmu_filename, include=lambda n: n.startswith(tuple(required_paths)))
+        unzipdir = tempdir
+        fmi_type = "ModelExchange"
+        experiment = model_description.defaultExperiment
+        start_time = 0.0
+        start_time = float(start_time)
+
+        stop_time = start_time + 0.1
+
+        stop_time = float(stop_time)
+
+        if step_size is None:
+            total_time = stop_time - start_time
+            step_size = 10 ** (np.round(np.log10(total_time)) - 3)
+
+        if output_interval is None and fmi_type == 'CoSimulation' and experiment is not None and experiment.stepSize is not None:
+            output_interval = experiment.stepSize
+            while (stop_time - start_time) / output_interval > 1000:
+                output_interval *= 2
+
+        fmu_args = {
+            'guid': model_description.guid,
+            'unzipDirectory': unzipdir,
+            'instanceName': None,
+            'fmiCallLogger': fmi_call_logger
+        }
+        logger = printLogMessage
+        callbacks = fmi2CallbackFunctions()
+        callbacks.logger = fmi2CallbackLoggerTYPE(logger)
+        callbacks.allocateMemory = fmi2CallbackAllocateMemoryTYPE(allocateMemory)
+        callbacks.freeMemory = fmi2CallbackFreeMemoryTYPE(freeMemory)
+
+        fmu_args['modelIdentifier'] = model_description.modelExchange.modelIdentifier
+
+        fmu = FMU2Model(**fmu_args)
+        self.fmu = fmu
+        fmu.instantiate(visible=visible, callbacks=callbacks, loggingOn=debug_logging)
+
+        if output_interval is None:
+            if step_size is None:
+                output_interval = auto_interval(stop_time - start_time)
+            else:
+                output_interval = step_size
+                while (stop_time - start_time) / output_interval > 1000:
+                    output_interval *= 2
+
+        if step_size is None:
+            step_size = output_interval
+            max_step = (stop_time - start_time) / 1000
+            while step_size > max_step:
+                step_size /= 2
+
+        fmu.setupExperiment(startTime=start_time, stopTime=stop_time)
+
+        input = Input(fmu, model_description, input)
+
+        apply_start_values(fmu, model_description, start_values, apply_default_start_values)
+
+        fmu.enterInitializationMode()
+        input.apply(start_time)
+        fmu.exitInitializationMode()
+
+        getreal = getattr(fmu.dll, "fmi2GetReal")
+        component = fmu.component
+
+        getreal.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p]
+        getreal.restype = ctypes.c_uint
+
+        set_time = getattr(fmu.dll, "fmi2SetTime")
+        set_time.argtypes = [ctypes.c_void_p, ctypes.c_double]
+        set_time.restype = ctypes.c_int
+
+        fmi2SetC = getattr(fmu.dll, "fmi2SetContinuousStates")
+        fmi2SetC.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p]
+        fmi2SetC.restype = ctypes.c_uint
+
+        fmi2SetReal = getattr(fmu.dll, "fmi2SetReal")
+        fmi2SetReal.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p]
+        fmi2SetReal.restype = ctypes.c_uint
+
+        completedIntegratorStep = getattr(fmu.dll, "fmi2CompletedIntegratorStep")
+        completedIntegratorStep.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p,
+                                            ctypes.c_void_p]
+        completedIntegratorStep.restype = ctypes.c_uint
+
+        get_event_indicators = getattr(fmu.dll, "fmi2GetEventIndicators")
+
+        get_event_indicators.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p]
+        get_event_indicators.restype = ctypes.c_uint
+
+        enter_event_mode = getattr(fmu.dll, "fmi2EnterEventMode")
+
+        enter_event_mode.argtypes = [ctypes.c_void_p]
+        enter_event_mode.restype = ctypes.c_uint
+
+        enter_cont_mode = getattr(fmu.dll, "fmi2EnterContinuousTimeMode")
+        enter_cont_mode.argtypes = [ctypes.c_void_p]
+        enter_cont_mode.restype = ctypes.c_uint
+
+        newDiscreteStates = getattr(fmu.dll, "fmi2NewDiscreteStates")
+        newDiscreteStates.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
+        newDiscreteStates.restype = ctypes.c_uint
+
+        len_q = len(self.value_ref_used)
+        var_order = [x.valueReference for x in model_description.modelVariables
+                     if x.valueReference in self.value_ref_used and (x.type != "String" and x.type != "Boolean")]
+
+        term_1 = np.array([0], dtype=np.int32)
+        term_1_ptr = term_1.ctypes.data
+        event_1 = np.array([0], dtype=np.int32)
+        event_1_ptr = event_1.ctypes.data
+        var_array = []
+        ptr_var_array = []
+        idx_tuple_array = []
+        ptr_tuple_array = []
+        for i in range(len_q):
+            a = np.array([0], dtype=np.float64)
+            var_array.append(a)
+            ptr_var_array.append(a.ctypes.data)
+            idx_tuple_array.append(("a_i_" + str(i), 'a' + str(i)))
+            ptr_tuple_array.append(("a" + str(i) + "_ptr", 'a' + str(i)))
+        deriv_idx = []
+        states_idx = []
+        var_names_ordered = []
+        var_states_ordered = []
+        var_names_ordered_ns = []
+        deriv_names_ordered = []
+        input_idx = []
+        input_var_names_ordered = []
+        output_idx = []
+        output_var_names_ordered = []
+        for idx, variable in enumerate(model_description.modelVariables):
+            if variable.valueReference not in self.value_ref_used:
+                continue
+            if variable.type == 'String':
+                continue
+            if variable.type == 'Boolean':
+                continue
+
+            if variable.derivative:
+                deriv_idx.append(self.value_ref_used.index(variable.valueReference))
+                states_idx.append(self.value_ref_used.index(
+                    [x.valueReference for index, x in enumerate(model_description.modelVariables) if
+                     x.name == variable.derivative.name][0]))
+                var_names_ordered_ns.append(namespace_ + "." + _replace_name_str(variable.derivative.name) + "_dot")
+                deriv_names_ordered.append(_replace_name_str(variable.derivative.name) + "_dot")
+                var_states_ordered.append(namespace_ + "." + _replace_name_str(variable.derivative.name))
+            else:
+                var_names_ordered_ns.append(namespace_ + "." + _replace_name_str(variable.name))
+                var_names_ordered.append(_replace_name_str(variable.name))
+            if variable.valueReference in self.input_ref:
+                input_idx.append(self.value_ref_used.index(variable.valueReference))
+                input_var_names_ordered.append(_replace_name_str(variable.name))
+            if variable.valueReference in self.output_ref:
+                output_idx.append(self.value_ref_used.index(variable.valueReference))
+                output_var_names_ordered.append(_replace_name_str(variable.name))
+
+        states_names_ordered = [x.split(".")[1] for x in var_states_ordered]
+
+        fmu.enterContinuousTimeMode()
+
+        q1, equation_call_wrapper = generate_eval_llvm(idx_tuple_array, [idx_tuple_array[i] for i in deriv_idx],
+                                                       states_idx, var_order, output_idx)
+        module_func = ast.Module(body=[q1, equation_call_wrapper], type_ignores=[])
+        if debug_output:
+            print(ast.unparse(module_func))
+        code = compile(ast.parse(ast.unparse(module_func)), filename='fmu_eval', mode='exec')
+        namespace = {"carray": carray, "cfunc": cfunc, "types": types, "np": np, "len_q": len_q, "getreal": getreal,
+                     "component": component, "fmi2SetReal": fmi2SetReal, "set_time": set_time, "fmi2SetC": fmi2SetC,
+                     "completedIntegratorStep": completedIntegratorStep}
+        exec(code, namespace)
+        equation_call = namespace["equation_call"]
+
+        q = generate_fmu_eval(var_names_ordered, ptr_tuple_array,
+                              [ptr_tuple_array[i] for i in deriv_idx],
+                              output_idx, output_var_names_ordered)
+        module_func = ast.Module(body=[q], type_ignores=[])
+        if debug_output:
+            print(ast.unparse(module_func))
+        code = compile(ast.parse(ast.unparse(module_func)), filename='fmu_eval', mode='exec')
+        namespace = {"NumerousFunction": NumerousFunction, "carray": carray,
+                     "address_as_void_pointer": address_as_void_pointer,
+                     "equation_call": equation_call,
+                     "event_1_ptr": event_1_ptr,
+                     "term_1_ptr": term_1_ptr
+                     }
+
+        for i in range(len_q):
+            namespace.update({"a" + str(i): var_array[i]})
+            namespace.update({"a" + str(i) + "_ptr": ptr_var_array[i]})
+        exec(code, namespace)
+
+        event_n = model_description.numberOfEventIndicators
+        self.fmu_eval = namespace["fmu_eval"]
+        event_cond = []
+        event_cond_wrapped = []
+        self._c_ptrs_a = []
+        for _ in range(event_n):
+            self._c_ptrs_a.append(np.zeros(shape=event_n))
+
+        for i in range(event_n):
+            q, wrapper = generate_eval_event(range(len_q), len_q, var_order, event_id=i)
+            module_func = ast.Module(body=[q, wrapper], type_ignores=[])
+            if debug_output:
+                print(ast.unparse(module_func))
+            code = compile(ast.parse(ast.unparse(module_func)), filename='fmu_eval', mode='exec')
+            namespace = {"carray": carray, "event_n": event_n, "cfunc": cfunc, "types": types, "np": np, "len_q": len_q,
+                         "getreal": getreal,
+                         "component": component, "fmi2SetReal": fmi2SetReal, "set_time": set_time,
+                         "get_event_indicators": get_event_indicators,
+                         "completedIntegratorStep": completedIntegratorStep}
+            exec(code, namespace)
+
+            f1, f2 = generate_njit_event_cond(var_states_ordered, i, var_names_ordered_ns)
+            module_func = ast.Module(body=[f1, f2], type_ignores=[])
+            if debug_output:
+                print(ast.unparse(module_func))
+            code = compile(ast.parse(ast.unparse(module_func)), filename='fmu_eval_2', mode='exec')
+            namespace = {"carray": carray, "event_n": event_n, "cfunc": cfunc, "types": types, "np": np,
+                         "event_ind_call_" + str(i): namespace["event_ind_call_" + str(i)],
+                         "c_ptr": self._c_ptrs_a[i].ctypes.data,
+                         "component": component, "fmi2SetReal": fmi2SetReal, "set_time": set_time,
+                         "njit": njit, "address_as_void_pointer": address_as_void_pointer,
+                         "completedIntegratorStep": completedIntegratorStep}
+            exec(code, namespace)
+            event_cond.append(namespace["event_cond_inner_" + str(i)])
+            event_cond_2 = namespace["event_cond_" + str(i)]
+            event_cond_2.lines = ast.unparse(ast.Module(body=[f2], type_ignores=[]))
+            event_cond_wrapped.append(event_cond_2)
+
+        class EventInfo(ctypes.Structure):
+
+            _fields_ = [('newDiscreteStatesNeeded', ctypes.c_int8),
+                        ('terminateSimulation', ctypes.c_int8),
+                        ('nominalsOfContinuousStatesChanged', ctypes.c_int8),
+                        ('valuesOfContinuousStatesChanged', ctypes.c_int8),
+                        ('nextEventTimeDefined', ctypes.c_int8),
+                        ('nextEventTime', ctypes.c_float)]
+
+        event_info = EventInfo()
+        q_ptr = ctypes.addressof(event_info)
+
+        a, b = generate_action_event(len_q, var_order)
+        module_func = ast.Module(body=[a, b], type_ignores=[])
+        if debug_output:
+            print(ast.unparse(module_func))
+        code = compile(ast.parse(ast.unparse(module_func)), filename='fmu_eval', mode='exec')
+        namespace = {"carray": carray, "event_n": event_n, "cfunc": cfunc, "types": types, "np": np, "len_q": len_q,
+                     "getreal": getreal,
+                     "q_a": q_ptr,
+                     "component": component, "enter_event_mode": enter_event_mode, "set_time": set_time,
+                     "get_event_indicators": get_event_indicators, "newDiscreteStates": newDiscreteStates,
+                     "enter_cont_mode": enter_cont_mode, "fmi2SetReal": fmi2SetReal,
+                     "completedIntegratorStep": completedIntegratorStep}
+        exec(code, namespace)
+        event_ind_call = namespace["event_ind_call"]
+
+        ae_vars = []
+        ae_ptrs = []
+
+        for i in range(len_q):
+            a_e = np.array([0], dtype=np.float64)
+            ae_vars.append(a_e)
+            ae_ptrs.append(a_e.ctypes.data)
+
+        a1, b1 = generate_event_action(len_q, var_names_ordered_ns)
+
+        module_func = ast.Module(body=[a1, b1], type_ignores=[])
+        if debug_output:
+            print(ast.unparse(module_func))
+        code = compile(ast.parse(ast.unparse(module_func)), filename='fmu_eval', mode='exec')
+        namespace = {"carray": carray, "event_n": event_n, "cfunc": cfunc, "types": types, "np": np, "len_q": len_q,
+
+                     "component": component, "enter_event_mode": enter_event_mode, "set_time": set_time,
+                     "get_event_indicators": get_event_indicators, "event_ind_call": event_ind_call,
+                     "njit": njit,
+                     "address_as_void_pointer": address_as_void_pointer,
+                     "completedIntegratorStep": completedIntegratorStep}
+
+        for i in range(len_q):
+            namespace.update({"a_e_" + str(i): ae_vars[i]})
+            namespace.update({"a_e_ptr_" + str(i): ae_ptrs[i]})
+
+        exec(code, namespace)
+        event_action = namespace["event_action"]
+        event_action_2 = namespace["event_action_2"]
+        event_action_2.lines = ast.unparse(ast.Module(body=[b1], type_ignores=[]))
+        if len(deriv_names_ordered) > 0:
+            gec = generate_eq_call(deriv_names_ordered, var_names_ordered, input_var_names_ordered,
+                                   output_var_names_ordered,
+                                   [x for x in var_names_ordered if x not in states_names_ordered])
+            if debug_output:
+                print(ast.unparse(gec))
+            code = compile(ast.parse(ast.unparse(gec)), filename='fmu_eval', mode='exec')
+            namespace = {"Equation": Equation}
+            exec(code, namespace)
+            result = namespace["eval"]
+            result.lines = ast.unparse(gec)
+            result.lineno = ast.parse(ast.unparse(gec)).body[0].lineno
+            self.eval = ptypes.MethodType(result, self)
+            self.equations.append(self.eval)
+        self.t1 = self.create_namespace(namespace_)
+        self.t1.add_equations([self])
+        for i in range(event_n):
+            self.add_event("event_u_" + str(i), event_cond_wrapped[i], event_action_2,
+                           compiled_functions={"event_cond_inner_" + str(i): event_cond[i],
+                                               "event_action": event_action}, direction=-1)
+            self.add_event("event_d_" + str(i), event_cond_wrapped[i], event_action_2,
+                           compiled_functions={"event_cond_inner_" + str(i): event_cond[i],
+                                               "event_action": event_action}, direction=1)
+
+    def set_variables(self, model_description):
+        derivatives = []
+        value_ref_used = []
+        derivatives_names = []
+        states = []
+        input_ref = []
+        output_ref = []
+
+        for variable in model_description.modelVariables:
+            if variable.derivative:
+                derivatives.append(variable)
+                derivatives_names.append(_replace_name_str(variable.derivative.name))
+
+                states.append(variable.derivative)
+        for variable in model_description.modelVariables:
+            if variable.type == 'String':
+                continue
+            if variable.type == 'Boolean':
+                continue
+            if variable in states:
+                if variable.start:
+                    start = variable.start
+                else:
+                    start = 0
+                value_ref_used.append(variable.valueReference)
+                self.add_state(_replace_name_str(variable.name), float(start), create_derivative=False)
+                continue
+
+            if variable in derivatives:
+                value_ref_used.append(variable.valueReference)
+                self.add_derivative(derivatives_names[derivatives.index(variable)])
+                continue
+            if variable.initial == 'exact':
+                if variable.variability == 'fixed':
+                    if variable.start != "DISABLED":
+                        if isinstance(variable.start, bool):
+                            start = int(variable.start is True)
+                        else:
+                            start = float(variable.start)
+                        if self.import_all or variable.causality == 'input' or variable.causality == 'output':
+                            value_ref_used.append(variable.valueReference)
+                            self.add_constant(_replace_name_str(variable.name), start)
+                    else:
+                        if self.import_all or variable.causality == 'input' or variable.causality == 'output':
+                            value_ref_used.append(variable.valueReference)
+                            self.add_constant(_replace_name_str(variable.name), 0.0)
+                if variable.variability == 'discrete':
+                    if variable.start == "false":
+                        if self.import_all or variable.causality == 'input' or variable.causality == 'output':
+                            value_ref_used.append(variable.valueReference)
+                            self.add_parameter(_replace_name_str(variable.name), 0.0)
+                    if variable.start == "true":
+                        if self.import_all or variable.causality == 'input' or variable.causality == 'output':
+                            value_ref_used.append(variable.valueReference)
+                            self.add_parameter(_replace_name_str(variable.name), 1.0)
+                if variable.variability == 'tunable':
+                    if self.import_all or variable.causality == 'input' or variable.causality == 'output':
+                        value_ref_used.append(variable.valueReference)
+                        self.add_parameter(_replace_name_str(variable.name), float(variable.start))
+            else:
+                if not variable.derivative:
+                    if self.import_all or variable.causality == 'input' or variable.causality == 'output':
+                        value_ref_used.append(variable.valueReference)
+                        if variable.causality == 'input':
+                            input_ref.append(variable.valueReference)
+                        if variable.causality == 'output':
+                            output_ref.append(variable.valueReference)
+                        self.add_parameter(_replace_name_str(variable.name), 0.0)
+
+        # TODO another types and tunable
+        for variable in model_description.modelVariables:
+            if variable.causality == 'input':
+                if variable.variability == 'discrete':
+                    self.dataframe_aliases[variable.name] = (variable.name, InterpolationType.PIESEWISE)
+                else:
+                    self.dataframe_aliases[variable.name] = (variable.name, InterpolationType.LINEAR)
+
+        if self.fmu_input is not None:
+            data_loader = InMemoryDataLoader(self.fmu_input)
+            index_to_timestep_mapping = 'time'
+            index_to_timestep_mapping_start = 0
+            external_mappings = [(ExternalMappingElement("inmemory",
+                                                         index_to_timestep_mapping,
+                                                         index_to_timestep_mapping_start,
+                                                         1,
+                                                         self.dataframe_aliases,
+                                                         ))]
+            self.external_mappings = ExternalMappingUnpacked(external_mappings, data_loader)
+        else:
+            self.external_mappings = None
+        self.input_ref = input_ref
+        self.output_ref = output_ref
+        return value_ref_used
diff --git a/numerous/engine/system/fmu_system_generator/fmu_ast_generator.py b/numerous/engine/system/fmu_system_generator/fmu_ast_generator.py
new file mode 100644
index 00000000..22035cb5
--- /dev/null
+++ b/numerous/engine/system/fmu_system_generator/fmu_ast_generator.py
@@ -0,0 +1,610 @@
+import ast
+
+CARRAY = 'carray'
+DTYPE = 'dtype'
+SHAPE = 'shape'
+ADDRESS_FUNC = 'address_as_void_pointer'
+EQ_CALL = 'equation_call'
+EVENT_1_PTR = "event_1_ptr"
+TERM_1_PTR = "term_1_ptr"
+NUMEROUS_FUNCTION = 'NumerousFunction'
+
+
+def generate_fmu_eval(input_args, zero_assign_ptrs, output_ptrs, parameters_return_idx, fmu_output_args):
+    args_lst = []
+    for arg_id in input_args:
+        if arg_id not in fmu_output_args:
+            args_lst.append(ast.arg(arg=arg_id))
+    args = ast.arguments(posonlyargs=[], args=args_lst, kwonlyargs=[],
+                         kw_defaults=[], defaults=[])
+
+    zero_assigns = []
+    for zero_assign_ptr, arr_id in zero_assign_ptrs:
+        shape = ast.Attribute(value=ast.Name(id=arr_id, ctx=ast.Load()), attr=SHAPE, ctx=ast.Load())
+        dtype_kw = ast.Attribute(value=ast.Name(id=arr_id, ctx=ast.Load()), attr=DTYPE, ctx=ast.Load())
+        _keywords = ast.keyword(arg=DTYPE, value=dtype_kw)
+        zero_assigns.append(ast.Assign(targets=[carray_call(add_address_as_void_pointer(zero_assign_ptr),
+                                                            shape, _keywords)],
+                                       value=ast.Constant(value=0), lineno=0))
+    eq_call_args = [add_address_as_void_pointer(EVENT_1_PTR), add_address_as_void_pointer(TERM_1_PTR)]
+
+    for zero_assign_ptr, _ in zero_assign_ptrs:
+        eq_call_args.append(add_address_as_void_pointer(zero_assign_ptr))
+
+    for arg_id in input_args:
+        if arg_id not in fmu_output_args:
+            eq_call_args.append(ast.Name(id=arg_id, ctx=ast.Load()))
+
+    eq_call_args.append(ast.Constant(value=0.1))
+    eq_expr = [ast.Expr(value=ast.Call(func=ast.Name(id=EQ_CALL), args=eq_call_args, keywords=[], ctx=ast.Load()),
+                        keywords=[])]
+    return_elts = []
+    return_exp = []
+    for zero_assign_ptr, arr_id in output_ptrs:
+        shape = ast.Attribute(value=ast.Name(id=arr_id, ctx=ast.Load()), attr=SHAPE, ctx=ast.Load())
+        dtype_kw = ast.Attribute(value=ast.Name(id=arr_id, ctx=ast.Load()), attr=DTYPE, ctx=ast.Load())
+        _keywords = ast.keyword(arg=DTYPE, value=dtype_kw)
+        return_elts.append(carray_call(add_address_as_void_pointer(zero_assign_ptr), shape, _keywords))
+    for ix in parameters_return_idx:
+        zero_assign_ptr, arr_id = zero_assign_ptrs[ix]
+        shape = ast.Attribute(value=ast.Name(id=arr_id, ctx=ast.Load()), attr=SHAPE, ctx=ast.Load())
+        dtype_kw = ast.Attribute(value=ast.Name(id=arr_id, ctx=ast.Load()), attr=DTYPE, ctx=ast.Load())
+        _keywords = ast.keyword(arg=DTYPE, value=dtype_kw)
+        return_elts.append(carray_call(add_address_as_void_pointer(zero_assign_ptr), shape, _keywords))
+    if len(return_elts) > 1:
+        return_exp = [ast.Return(value=ast.Tuple(elts=return_elts), ctx=ast.Load())]
+    if len(return_elts) == 1:
+        return_exp = [ast.Return(value=return_elts[0], ctx=ast.Load())]
+
+    decorator_list = [ast.Call(func=ast.Name(id=NUMEROUS_FUNCTION, ctx=ast.Load()), args=[], keywords=[])]
+    return ast.FunctionDef(name='fmu_eval', args=args, body=zero_assigns + eq_expr + return_exp,
+                           decorator_list=decorator_list, lineno=0)
+
+
+def dtype(id: str):
+    return ast.keyword(arg=DTYPE,
+                       value=ast.Attribute(value=ast.Name(id=id, ctx=ast.Load()), attr=DTYPE, ctx=ast.Load()))
+
+
+def carray_call(ptr_arg: ast.expr, shape: ast.Attribute, dtype_kw: ast.keyword):
+    return ast.Subscript(value=ast.Call(func=ast.Name(id=CARRAY, ctx=ast.Load()), args=[ptr_arg, shape],
+                                        keywords=[dtype_kw]), slice=ast.Constant(value=0), ctx=ast.Store())
+
+
+def add_address_as_void_pointer(var_id):
+    return ast.Call(func=ast.Name(id=ADDRESS_FUNC, ctx=ast.Load()), keywords=[], args=[ast.Name(id=var_id,
+                                                                                                ctx=ast.Load())])
+
+
+def _generate_fet_real_expr():
+    return ast.Expr(value=ast.Call(func=ast.Name(id='getreal', ctx=ast.Load()),
+                                   args=[ast.Name(id='component', ctx=ast.Load()),
+                                         ast.Attribute(value=ast.Name(id='vr', ctx=ast.Load()), attr='ctypes',
+                                                       ctx=ast.Load()),
+                                         ast.Name(id='len_q', ctx=ast.Load()),
+                                         ast.Attribute(value=ast.Name(id='value', ctx=ast.Load()), attr='ctypes',
+                                                       ctx=ast.Load())],
+                                   keywords=[]),
+                    keywords=[])
+
+
+def _generate_pointer(attr):
+    return ast.Attribute(value=ast.Name(id='types', ctx=ast.Load()), attr=attr, ctx=ast.Load())
+
+
+def list_from_var_order(var_order: list):
+    elts_ = []
+    for i_d in var_order:
+        elts_.append(ast.Constant(value=i_d))
+    return ast.List(elts_, ctx=ast.Load())
+
+
+def generate_eval_llvm(assign_ptrs, output_args, states_idx, var_order: list, output_idx):
+    args_lst = [ast.arg(arg="event"), ast.arg(arg="term")]
+    output_args_as_list = [item for t in output_args for item in t]
+    for idx_o in output_idx:
+        for x in [item for item in assign_ptrs[idx_o]]:
+            output_args_as_list.append(x)
+    for assign_ptr, arr_id in assign_ptrs:
+        args_lst.append(ast.arg(arg=arr_id))
+    for assign_ptr, arr_id in assign_ptrs:
+        if assign_ptr in output_args_as_list:
+            continue
+        args_lst.append(ast.arg(arg=assign_ptr))
+    args_lst.append(ast.arg(arg="t"))
+    args = ast.arguments(posonlyargs=[], args=args_lst, kwonlyargs=[],
+                         kw_defaults=[], defaults=[])
+    body = []
+
+    body.append(ast.Assign(targets=[ast.Name(id='vr', ctx=ast.Store())],
+                           value=ast.Call(func=ast.Attribute(value=ast.Name(id='np', ctx=ast.Load()), attr='array',
+                                                             ctx=ast.Load()),
+                                          args=[list_from_var_order(var_order)],
+                                          keywords=[ast.keyword(arg='dtype',
+                                                                value=ast.Attribute(value=ast.Name(id='np',
+                                                                                                   ctx=ast.Load()),
+                                                                                    attr='uint32', ctx=ast.Load()))]),
+                           lineno=0))
+    body.append(ast.Assign(targets=[ast.Name(id='value', ctx=ast.Store())],
+                           value=ast.Call(func=ast.Attribute(value=ast.Name(id='np', ctx=ast.Load()), attr='zeros',
+                                                             ctx=ast.Load()),
+                                          args=[ast.Name(id='len_q', ctx=ast.Load())],
+                                          keywords=[ast.keyword(arg='dtype', value=ast.Attribute(
+                                              value=ast.Name(id='np', ctx=ast.Load()), attr='float64',
+                                              ctx=ast.Load()))]), lineno=0))
+
+    body.append(_generate_fet_real_expr())
+    arg_elts = []
+    for idx, (assign_ptr, arr_id) in enumerate(assign_ptrs):
+        if assign_ptr in output_args_as_list:
+            arg_elts.append(ast.Subscript(value=ast.Name(id='value', ctx=ast.Load()),
+                                          slice=ast.Constant(value=idx), ctx=ast.Load()))
+        else:
+            arg_elts.append(ast.Name(id=assign_ptr, ctx=ast.Load()))
+
+    body.append(generate_set_fmi_update(arg_elts))
+    args_ = []
+    for state_idx in states_idx:
+        args_.append(ast.Name(id=assign_ptrs[state_idx][0], ctx=ast.Load()))
+    if len(args_) > 0:
+        body.append(ast.Assign(targets=[ast.Name(id='value3', ctx=ast.Store())],
+                               value=ast.Call(func=ast.Attribute(value=ast.Name(id='np', ctx=ast.Load()), attr='array',
+                                                                 ctx=ast.Load()),
+                                              args=[ast.List(elts=args_, ctx=ast.Load())],
+                                              keywords=[ast.keyword(arg='dtype', value=ast.Attribute(
+                                                  value=ast.Name(id='np', ctx=ast.Load()), attr='float64',
+                                                  ctx=ast.Load()))]), lineno=0))
+
+        body.append(ast.Expr(value=ast.Call(func=ast.Name(id='fmi2SetC', ctx=ast.Load()),
+                                            args=[ast.Name(id='component', ctx=ast.Load()),
+                                                  ast.Attribute(value=ast.Name(id='value3', ctx=ast.Load()),
+                                                                attr='ctypes', ctx=ast.Load()),
+                                                  ast.Constant(value=len(states_idx))], keywords=[])))
+
+    body.append(ast.Expr(value=ast.Call(func=ast.Name(id='set_time', ctx=ast.Load()),
+                                        args=[ast.Name(id='component', ctx=ast.Load()),
+                                              ast.Name(id='t', ctx=ast.Load())], keywords=[])))
+
+    body.append(ast.Expr(value=ast.Call(func=ast.Name(id='completedIntegratorStep', ctx=ast.Load()),
+                                        args=[ast.Name(id='component', ctx=ast.Load()), ast.Constant(value=1),
+                                              ast.Name(id='event', ctx=ast.Load()),
+                                              ast.Name(id='term', ctx=ast.Load())], keywords=[]),
+                         keywords=[]))
+    body.append(_generate_fet_real_expr())
+    return_elts = []
+    for idx, (assign_ptr, arr_id) in enumerate(assign_ptrs):
+        arr_id_n = ast.Name(id=arr_id, ctx=ast.Load())
+        shape = ast.Tuple(elts=[ast.Constant(value=1)], ctx=ast.Load())
+        dtype_kw = ast.Attribute(value=ast.Name(id='np', ctx=ast.Load()), attr='float64', ctx=ast.Load())
+        _keywords = ast.keyword(arg=DTYPE, value=dtype_kw)
+        value = ast.Subscript(value=ast.Name(id='value', ctx=ast.Load()), slice=ast.Constant(value=idx), ctx=ast.Load())
+        return_elts.append(ast.Assign(targets=[carray_call(arr_id_n,
+                                                           shape, _keywords)],
+                                      value=value, lineno=0))
+
+    wrapper_args = [_generate_pointer('voidptr'), _generate_pointer('voidptr')]
+    for _, arr_id in assign_ptrs:
+        wrapper_args.append(_generate_pointer('voidptr'))
+
+    for assign_ptr, arr_id in assign_ptrs:
+        if assign_ptr in output_args_as_list:
+            continue
+        wrapper_args.append(_generate_pointer('float64'))
+
+    wrapper_args.append(_generate_pointer('float64'))
+
+    wrapper = ast.Assign(targets=[ast.Name(id=EQ_CALL, ctx=ast.Store())],
+                         value=ast.Call(func=ast.Call(func=ast.Name(id='cfunc', ctx=ast.Load()),
+                                                      args=[ast.Call(
+                                                          func=ast.Attribute(value=ast.Name(id='types', ctx=ast.Load()),
+                                                                             attr='void', ctx=ast.Load()),
+                                                          args=wrapper_args,
+                                                          keywords=[])], keywords=[]),
+                                        args=[ast.Name(id='eval_llvm', ctx=ast.Load())],
+                                        keywords=[]), lineno=0)
+
+    return ast.FunctionDef(name='eval_llvm', args=args, body=body + return_elts, decorator_list=[], lineno=0), wrapper
+
+
+def generate_eval_event(state_idx, len_q, var_order: list, event_id):
+    args_lst = [ast.arg(arg="event_indicators"), ast.arg(arg="t")]
+    for state_id in state_idx:
+        args_lst.append(ast.arg(arg="y" + str(state_id)))
+
+    args = ast.arguments(posonlyargs=[], args=args_lst, kwonlyargs=[],
+                         kw_defaults=[], defaults=[])
+    body = [ast.Assign(targets=[ast.Name(id='value_event', ctx=ast.Store())],
+                       value=ast.Call(
+                           func=ast.Attribute(value=ast.Name(id='np', ctx=ast.Load()), attr='zeros', ctx=ast.Load()),
+                           args=[ast.Name(id='event_n', ctx=ast.Load())],
+                           keywords=[ast.keyword(arg='dtype',
+                                                 value=ast.Attribute(value=ast.Name(id='np', ctx=ast.Load()),
+                                                                     attr='float64', ctx=ast.Load()))]), lineno=0),
+            ast.Assign(targets=[ast.Name(id='vr', ctx=ast.Store())],
+                       value=ast.Call(func=ast.Attribute(value=ast.Name(id='np', ctx=ast.Load()), attr='array',
+                                                         ctx=ast.Load()),
+                                      args=[list_from_var_order(var_order)],
+                                      keywords=[ast.keyword(arg='dtype',
+                                                            value=ast.Attribute(value=ast.Name(id='np', ctx=ast.Load()),
+                                                                                attr='uint32', ctx=ast.Load()))]),
+                       lineno=0), ast.Assign(targets=[ast.Name(id='value', ctx=ast.Store())],
+                                             value=ast.Call(func=ast.Attribute(value=ast.Name(id='np', ctx=ast.Load()),
+                                                                               attr='zeros',
+                                                                               ctx=ast.Load()),
+                                                            args=[ast.Name(id='len_q', ctx=ast.Load())],
+                                                            keywords=[ast.keyword(arg='dtype', value=ast.Attribute(
+                                                                value=ast.Name(id='np', ctx=ast.Load()), attr='float64',
+                                                                ctx=ast.Load()))]), lineno=0),
+            _generate_fet_real_expr()]
+
+    arg_elts = []
+    for idx in range(len_q):
+        arg_elts.append(ast.Name(id="y" + str(idx), ctx=ast.Load()))
+
+    body.append(generate_set_fmi_update(arg_elts))
+    body.append(ast.Expr(value=ast.Call(func=ast.Name(id='set_time', ctx=ast.Load()),
+                                        args=[ast.Name(id='component', ctx=ast.Load()),
+                                              ast.Name(id='t', ctx=ast.Load())], keywords=[])))
+
+    body.append(ast.Expr(value=ast.Call(func=ast.Name(id='get_event_indicators', ctx=ast.Load()),
+                                        args=[ast.Name(id='component', ctx=ast.Load()), ast.Attribute(
+                                            value=ast.Name(id='value_event', ctx=ast.Load()), attr='ctypes',
+                                            ctx=ast.Load()), ast.Name(id='event_n', ctx=ast.Load())],
+                                        keywords=[])))
+    lst_arg = []
+    for i in range(len_q):
+        lst_arg.append(ast.Subscript(value=ast.Name(id='value', ctx=ast.Load()),
+                                     slice=ast.Constant(value=i), ctx=ast.Load()))
+
+    body.append(ast.Assign(targets=[ast.Name(id='value2', ctx=ast.Store())],
+                           value=ast.Call(func=ast.Attribute(value=ast.Name(id='np', ctx=ast.Load()), attr='array',
+                                                             ctx=ast.Load()),
+                                          args=[ast.List(elts=lst_arg, ctx=ast.Load())],
+                                          keywords=[ast.keyword(arg='dtype', value=ast.Attribute(
+                                              value=ast.Name(id='np', ctx=ast.Load()), attr='float64',
+                                              ctx=ast.Load()))]), lineno=0))
+
+    body.append(ast.Expr(value=ast.Call(func=ast.Name(id='fmi2SetReal', ctx=ast.Load()),
+                                        args=[ast.Name(id='component', ctx=ast.Load()),
+                                              ast.Attribute(value=ast.Name(id='vr', ctx=ast.Load()),
+                                                            attr='ctypes', ctx=ast.Load()),
+                                              ast.Name(id='len_q', ctx=ast.Load()),
+                                              ast.Attribute(value=ast.Name(id='value2', ctx=ast.Load()),
+                                                            attr='ctypes', ctx=ast.Load())], keywords=[])),
+                )
+
+    body.append(ast.Assign(targets=[ast.Subscript(
+        value=ast.Call(func=ast.Name(id='carray', ctx=ast.Load()),
+                       args=[ast.Name(id='event_indicators', ctx=ast.Load()),
+                             ast.Tuple(elts=[ast.Constant(value=1)],
+                                       ctx=ast.Load())],
+                       keywords=[ast.keyword(arg='dtype',
+                                             value=ast.Attribute(
+                                                 value=ast.Name(id='np',
+                                                                ctx=ast.Load()),
+                                                 attr='float64',
+                                                 ctx=ast.Load()))]),
+        slice=ast.Constant(value=0), ctx=ast.Store())],
+        value=ast.Subscript(value=ast.Name(id='value_event', ctx=ast.Load()),
+                            slice=ast.Constant(value=event_id), ctx=ast.Load()), lineno=0))
+
+    wrapper_args = [_generate_pointer('voidptr'), _generate_pointer('float64')]
+
+    for _ in range(len_q):
+        wrapper_args.append(_generate_pointer('float64'))
+
+    wrapper = ast.Assign(targets=[ast.Name(id="event_ind_call_" + str(event_id), ctx=ast.Store())],
+                         value=ast.Call(func=ast.Call(func=ast.Name(id='cfunc', ctx=ast.Load()),
+                                                      args=[ast.Call(
+                                                          func=ast.Attribute(value=ast.Name(id='types', ctx=ast.Load()),
+                                                                             attr='void', ctx=ast.Load()),
+                                                          args=wrapper_args,
+                                                          keywords=[])], keywords=[]),
+                                        args=[ast.Name(id='eval_event', ctx=ast.Load())],
+                                        keywords=[]), lineno=0)
+
+    return ast.FunctionDef(name='eval_event', args=args, body=body, decorator_list=[], lineno=0), wrapper
+
+
+def generate_njit_event_cond(states, id_, variables):
+    body = [ast.Assign(targets=[ast.Name(id='temp_addr', ctx=ast.Store())],
+                       value=ast.Call(func=ast.Name(id='address_as_void_pointer', ctx=ast.Load()),
+                                      args=[ast.Name(id='c_ptr', ctx=ast.Load())],
+                                      keywords=[]), lineno=0),
+            ast.Assign(targets=[ast.Subscript(value=ast.Call(func=ast.Name(id='carray', ctx=ast.Load()),
+                                                             args=[ast.Name(id='temp_addr', ctx=ast.Load()),
+                                                                   ast.Tuple(elts=[ast.Constant(value=1)],
+                                                                             ctx=ast.Load())],
+                                                             keywords=[ast.keyword(arg='dtype',
+                                                                                   value=ast.Attribute(
+                                                                                       value=ast.Name(id='np',
+                                                                                                      ctx=ast.Load()),
+                                                                                       attr='float64',
+                                                                                       ctx=ast.Load()))]),
+                                              slice=ast.Constant(value=0), ctx=ast.Store())],
+                       value=ast.Constant(value=0), lineno=0)]
+
+    args = [ast.Name(id='temp_addr', ctx=ast.Load()), ast.Name(id='t', ctx=ast.Load())]
+
+    for idx, _ in enumerate(variables):
+        args.append(
+            ast.Subscript(value=ast.Name(id='y', ctx=ast.Load()), slice=ast.Constant(value=idx), ctx=ast.Load()))
+
+    body.append(ast.Expr(
+        value=ast.Call(func=ast.Name(id='event_ind_call_' + str(id_), ctx=ast.Load()),
+                       args=args,
+                       keywords=[])))
+
+    body.append(ast.Assign(targets=[ast.Name(id='result', ctx=ast.Store())], value=ast.Subscript(
+        value=ast.Call(func=ast.Name(id='carray', ctx=ast.Load()),
+                       args=[ast.Name(id='temp_addr', ctx=ast.Load()),
+                             ast.Tuple(elts=[ast.Constant(value=1)], ctx=ast.Load())],
+                       keywords=[ast.keyword(arg='dtype',
+                                             value=ast.Attribute(value=ast.Name(id='np', ctx=ast.Load()),
+                                                                 attr='float64',
+                                                                 ctx=ast.Load()))]), slice=ast.Constant(value=0),
+        ctx=ast.Load()), lineno=0))
+
+    body.append(ast.Return(value=ast.Name(id='result', ctx=ast.Load())))
+
+    event_cond = ast.FunctionDef(name='event_cond_inner_' + str(id_),
+                                 args=ast.arguments(posonlyargs=[], args=[ast.arg(arg='t'), ast.arg(arg='y')],
+                                                    kwonlyargs=[],
+                                                    kw_defaults=[], defaults=[]), body=body,
+                                 decorator_list=[ast.Name(id='njit', ctx=ast.Load())], lineno=0)
+
+    body = []
+
+    elts = []
+
+    for var in variables:
+        elts.append(ast.Subscript(value=ast.Name(id='variables', ctx=ast.Load()),
+                                  slice=ast.Constant(value=var), ctx=ast.Load()))
+
+    body.append(ast.Assign(targets=[ast.Name(id='q', ctx=ast.Store())],
+                           value=ast.Call(func=ast.Attribute(value=ast.Name(id='np', ctx=ast.Load()), attr='array',
+                                                             ctx=ast.Load()),
+                                          args=[ast.List(elts=elts,
+                                                         ctx=ast.Load())], keywords=[]), lineno=0))
+
+    body.append(ast.Return(
+        value=ast.Call(func=ast.Name(id='event_cond_inner_' + str(id_), ctx=ast.Load()),
+                       args=[ast.Name(id='t', ctx=ast.Load()), ast.Name(id='q', ctx=ast.Load())], keywords=[])))
+
+    event_cond_2 = ast.FunctionDef(name='event_cond_' + str(id_),
+                                   args=ast.arguments(posonlyargs=[], args=[ast.arg(arg='t'), ast.arg(arg='variables')],
+                                                      kwonlyargs=[],
+                                                      kw_defaults=[], defaults=[]), body=body,
+                                   decorator_list=[], lineno=0)
+
+    return event_cond, event_cond_2
+
+
+def generate_set_fmi_update(arg_elts):
+    body = []
+    body.append(ast.Assign(targets=[ast.Name(id='value1', ctx=ast.Store())],
+                           value=ast.Call(func=ast.Attribute(value=ast.Name(id='np', ctx=ast.Load()), attr='array',
+                                                             ctx=ast.Load()),
+                                          args=[ast.List(elts=arg_elts, ctx=ast.Load())],
+                                          keywords=[ast.keyword(arg='dtype', value=ast.Attribute(
+                                              value=ast.Name(id='np', ctx=ast.Load()),
+                                              attr='float64',
+                                              ctx=ast.Load()))]), lineno=0))
+
+    body.append(ast.Expr(value=ast.Call(func=ast.Name(id='fmi2SetReal', ctx=ast.Load()),
+                                        args=[ast.Name(id='component', ctx=ast.Load()),
+                                              ast.Attribute(value=ast.Name(id='vr', ctx=ast.Load()),
+                                                            attr='ctypes', ctx=ast.Load()),
+                                              ast.Name(id='len_q', ctx=ast.Load()),
+                                              ast.Attribute(value=ast.Name(id='value1', ctx=ast.Load()),
+                                                            attr='ctypes', ctx=ast.Load())], keywords=[])))
+    return body
+
+
+def generate_action_event(len_q: int, var_order: list):
+    args_lst = [ast.arg(arg="t")]
+    for state_id in range(len_q):
+        args_lst.append(ast.arg(arg="y" + str(state_id)))
+    for id_ in range(len_q):
+        args_lst.append(ast.arg(arg="a_" + str(id_)))
+
+    args = ast.arguments(posonlyargs=[], args=args_lst, kwonlyargs=[],
+                         kw_defaults=[], defaults=[])
+
+    body = []
+    arg_elts = []
+
+    for idx in range(len_q):
+        arg_elts.append(ast.Name(id="y" + str(idx), ctx=ast.Load()))
+
+    body.append(ast.Assign(targets=[ast.Name(id='vr', ctx=ast.Store())],
+                           value=ast.Call(func=ast.Attribute(value=ast.Name(id='np', ctx=ast.Load()), attr='array',
+                                                             ctx=ast.Load()),
+                                          args=[list_from_var_order(var_order)],
+                                          keywords=[ast.keyword(arg='dtype',
+                                                                value=ast.Attribute(
+                                                                    value=ast.Name(id='np', ctx=ast.Load()),
+                                                                    attr='uint32', ctx=ast.Load()))]),
+                           lineno=0))
+    body.append(generate_set_fmi_update(arg_elts))
+
+    body.append(ast.Expr(value=ast.Call(func=ast.Name(id='enter_event_mode', ctx=ast.Load()),
+                                        args=[ast.Name(id='component', ctx=ast.Load())], keywords=[])))
+    body.append(ast.Expr(value=ast.Call(func=ast.Name(id='newDiscreteStates', ctx=ast.Load()),
+                                        args=[ast.Name(id='component', ctx=ast.Load()),
+                                              ast.Name(id='q_a', ctx=ast.Load())], keywords=[])))
+    body.append(ast.Expr(value=ast.Call(func=ast.Name(id='enter_cont_mode', ctx=ast.Load()),
+                                        args=[ast.Name(id='component', ctx=ast.Load())], keywords=[])))
+    body.append(ast.Assign(targets=[ast.Name(id='vr', ctx=ast.Store())],
+                           value=ast.Call(func=ast.Attribute(value=ast.Name(id='np', ctx=ast.Load()),
+                                                             attr='array', ctx=ast.Load()),
+                                          args=[list_from_var_order(var_order)], keywords=[ast.keyword(arg='dtype',
+                                                                                                       value=ast.Attribute(
+                                                                                                           value=ast.Name(
+                                                                                                               id='np',
+                                                                                                               ctx=ast.Load()),
+                                                                                                           attr='uint32',
+                                                                                                           ctx=ast.Load()))]),
+                           lineno=0))
+    body.append(ast.Assign(targets=[ast.Name(id='value', ctx=ast.Store())],
+                           value=ast.Call(func=ast.Attribute(value=ast.Name(id='np', ctx=ast.Load()), attr='zeros',
+                                                             ctx=ast.Load()),
+                                          args=[ast.Name(id='len_q', ctx=ast.Load())],
+                                          keywords=[ast.keyword(arg='dtype',
+                                                                value=ast.Attribute(
+                                                                    value=ast.Name(id='np',
+                                                                                   ctx=ast.Load()),
+                                                                    attr='float64',
+                                                                    ctx=ast.Load()))]), lineno=0))
+    body.append(ast.Expr(
+        value=ast.Call(func=ast.Name(id='getreal', ctx=ast.Load()), args=[ast.Name(id='component', ctx=ast.Load()),
+                                                                          ast.Attribute(
+                                                                              value=ast.Name(id='vr', ctx=ast.Load()),
+                                                                              attr='ctypes', ctx=ast.Load()),
+                                                                          ast.Name(id='len_q', ctx=ast.Load()),
+                                                                          ast.Attribute(value=ast.Name(id='value',
+                                                                                                       ctx=ast.Load()),
+                                                                                        attr='ctypes', ctx=ast.Load())],
+                       keywords=[])))
+
+    for i in range(len_q):
+        body.append(ast.Assign(targets=[ast.Subscript(
+            value=ast.Call(func=ast.Name(id='carray', ctx=ast.Load()), args=[ast.Name(id='a_' + str(i), ctx=ast.Load()),
+                                                                             ast.Tuple(elts=[ast.Constant(value=1)],
+                                                                                       ctx=ast.Load())], keywords=[
+                ast.keyword(arg='dtype', value=ast.Attribute(value=ast.Name(id='np', ctx=ast.Load()), attr='float64',
+                                                             ctx=ast.Load()))]),
+            slice=ast.Constant(value=0), ctx=ast.Store())],
+            value=ast.Subscript(value=ast.Name(id='value', ctx=ast.Load()),
+                                slice=ast.Constant(value=i), ctx=ast.Load()), lineno=0))
+
+    wrapper_args = [_generate_pointer('float64')]
+
+    for _ in range(len_q):
+        wrapper_args.append(_generate_pointer('float64'))
+
+    for _ in range(len_q):
+        wrapper_args.append(_generate_pointer('voidptr'))
+
+    wrapper = ast.Assign(targets=[ast.Name(id="event_ind_call", ctx=ast.Store())],
+                         value=ast.Call(func=ast.Call(func=ast.Name(id='cfunc', ctx=ast.Load()),
+                                                      args=[ast.Call(
+                                                          func=ast.Attribute(value=ast.Name(id='types', ctx=ast.Load()),
+                                                                             attr='void', ctx=ast.Load()),
+                                                          args=wrapper_args,
+                                                          keywords=[])], keywords=[]),
+                                        args=[ast.Name(id='event_callback_fun', ctx=ast.Load())],
+                                        keywords=[]), lineno=0)
+
+    return ast.FunctionDef(name='event_callback_fun', args=args, body=body, decorator_list=[], lineno=0), wrapper
+
+
+def generate_event_action(len_q, variables):
+    body = []
+    for i in range(len_q):
+        body.append(ast.Assign(targets=[ast.Subscript(
+            value=ast.Call(func=ast.Name(id='carray', ctx=ast.Load()),
+                           args=[ast.Call(func=ast.Name(id='address_as_void_pointer', ctx=ast.Load()),
+                                          args=[ast.Name(id='a_e_ptr_' + str(i), ctx=ast.Load())],
+                                          keywords=[]),
+                                 ast.Attribute(value=ast.Name(id='a_e_' + str(i), ctx=ast.Load()), attr='shape',
+                                               ctx=ast.Load())],
+                           keywords=[ast.keyword(arg='dtype',
+                                                 value=ast.Attribute(value=ast.Name(id='np', ctx=ast.Load()),
+                                                                     attr='float64',
+                                                                     ctx=ast.Load()))]),
+            slice=ast.Constant(value=0), ctx=ast.Store())], value=ast.Constant(value=0), lineno=0))
+
+    args = [ast.Name(id='t', ctx=ast.Load())]
+
+    for idx, _ in enumerate(variables):
+        args.append(
+            ast.Subscript(value=ast.Name(id='y', ctx=ast.Load()), slice=ast.Constant(value=idx), ctx=ast.Load()))
+
+    for i in range(len_q):
+        args.append(ast.Call(func=ast.Name(id='address_as_void_pointer', ctx=ast.Load()),
+                             args=[ast.Name(id='a_e_ptr_' + str(i), ctx=ast.Load())], keywords=[]))
+
+    body.append(ast.Expr(value=ast.Call(func=ast.Name(id='event_ind_call', ctx=ast.Load()), args=args, keywords=[])))
+
+    elts = []
+    for i in range(len_q):
+        elts.append(ast.Subscript(
+            value=ast.Call(func=ast.Name(id='carray', ctx=ast.Load()), args=[
+                ast.Call(func=ast.Name(id='address_as_void_pointer', ctx=ast.Load()),
+                         args=[ast.Name(id='a_e_ptr_' + str(i), ctx=ast.Load())],
+                         keywords=[]), ast.Tuple(elts=[ast.Constant(value=1)], ctx=ast.Load())], keywords=[
+                ast.keyword(arg='dtype', value=ast.Attribute(value=ast.Name(id='np', ctx=ast.Load()), attr='float64',
+                                                             ctx=ast.Load()))]),
+            slice=ast.Constant(value=0), ctx=ast.Load()))
+
+    body.append(ast.Return(value=ast.List(elts=elts, ctx=ast.Load())))
+
+    event_action_fun = ast.FunctionDef(name='event_action', args=ast.arguments(posonlyargs=[], args=[ast.arg(arg='t'),
+                                                                                                     ast.arg(arg='y')],
+                                                                               kwonlyargs=[], kw_defaults=[],
+                                                                               defaults=[]),
+                                       body=body, decorator_list=[ast.Name(id='njit', ctx=ast.Load())], lineno=0)
+
+    elts2 = []
+
+    for state in variables:
+        elts2.append(ast.Subscript(value=ast.Name(id='variables', ctx=ast.Load()),
+                                   slice=ast.Constant(value=state), ctx=ast.Load()))
+    body2 = [ast.Assign(targets=[ast.Name(id='q', ctx=ast.Store())],
+                        value=ast.Call(func=ast.Attribute(value=ast.Name(id='np', ctx=ast.Load()),
+                                                          attr='array', ctx=ast.Load()),
+                                       args=[ast.List(elts=elts2, ctx=ast.Load())], keywords=[]), lineno=0),
+             ast.Assign(targets=[ast.Name(id='vars', ctx=ast.Store())],
+                        value=ast.Call(func=ast.Name(id='event_action', ctx=ast.Load()),
+                                       args=[ast.Name(id='t', ctx=ast.Load()), ast.Name(id='q', ctx=ast.Load())],
+                                       keywords=[]), lineno=0)]
+
+    for idx, var_id in enumerate(variables):
+        body2.append(ast.Assign(targets=[
+            ast.Subscript(value=ast.Name(id='variables', ctx=ast.Load()), slice=ast.Constant(value=var_id),
+                          ctx=ast.Store())],
+            value=ast.Subscript(value=ast.Name(id='vars', ctx=ast.Load()),
+                                slice=ast.Constant(value=idx), ctx=ast.Load()), lineno=0))
+    event_action_fun_2 = ast.FunctionDef(name='event_action_2', args=ast.arguments(posonlyargs=[],
+                                                                                   args=[ast.arg(arg='t'),
+                                                                                         ast.arg(arg='variables')],
+                                                                                   kwonlyargs=[],
+                                                                                   kw_defaults=[],
+                                                                                   defaults=[]),
+                                         body=body2,
+                                         decorator_list=[], lineno=0)
+    return event_action_fun, event_action_fun_2
+
+
+def generate_eq_call(deriv_names, var_names, input_var_names_ordered, output_var_names_ordered, params_names):
+    elts = []
+    for d_name in deriv_names:
+        elts.append(ast.Attribute(value=ast.Name(id='scope', ctx=ast.Load()), attr=d_name, ctx=ast.Store()))
+    for d_name in params_names:
+        if d_name not in input_var_names_ordered:
+            elts.append(ast.Attribute(value=ast.Name(id='scope', ctx=ast.Load()), attr=d_name, ctx=ast.Store()))
+
+    if len(deriv_names) > 1:
+        trg = [ast.Tuple(elts=elts, ctx=ast.Store())]
+    else:
+        trg = [elts[0]]
+
+    args = []
+    for v_name in var_names:
+        if v_name not in output_var_names_ordered:
+            args.append(ast.Attribute(value=ast.Name(id='scope', ctx=ast.Load()), attr=v_name, ctx=ast.Load()))
+
+    return ast.Module(body=[ast.FunctionDef(name='eval',
+                                            args=ast.arguments(posonlyargs=[],
+                                                               args=[ast.arg(arg='self'), ast.arg(arg='scope')],
+                                                               kwonlyargs=[],
+                                                               kw_defaults=[], defaults=[]),
+                                            body=[ast.Assign(targets=trg, value=ast.Call(
+                                                func=ast.Attribute(value=ast.Name(id='self', ctx=ast.Load()),
+                                                                   attr='fmu_eval', ctx=ast.Load()),
+                                                args=args, keywords=[]), lineno=0)],
+                                            decorator_list=[
+                                                ast.Call(func=ast.Name(id='Equation', ctx=ast.Load()), args=[],
+                                                         keywords=[])], lineno=0)],
+                      type_ignores=[])
diff --git a/numerous/engine/system/fmu_system_generator/utils.py b/numerous/engine/system/fmu_system_generator/utils.py
new file mode 100644
index 00000000..83f1d849
--- /dev/null
+++ b/numerous/engine/system/fmu_system_generator/utils.py
@@ -0,0 +1,13 @@
+from numba.core.extending import intrinsic
+
+
+@intrinsic
+def address_as_void_pointer(typingctx, src):
+    """ returns a void pointer from a given memory address """
+    from numba.core import types, cgutils
+    sig = types.voidptr(src)
+
+    def codegen(cgctx, builder, sig, args):
+        return builder.inttoptr(args[0], cgutils.voidptr_t)
+
+    return sig, codegen
diff --git a/numerous/engine/system/item.py b/numerous/engine/system/item.py
index cc4c6917..c38015d5 100644
--- a/numerous/engine/system/item.py
+++ b/numerous/engine/system/item.py
@@ -62,6 +62,7 @@ def get_default_namespace(self):
         """
         return VariableNamespace(self, DEFAULT_NAMESPACE, is_connector=isinstance(self, Connector))
 
+
     def create_namespace(self, tag):
         """
         Creating a namespace.
@@ -101,7 +102,7 @@ def register_namespace(self, namespace):
                                  .format(namespace.tag, self.tag))
             else:
                 self.registered_namespaces.update({namespace.tag: namespace})
-            namespace.registered=True
+            namespace.registered = True
         else:
             raise ValueError(f'Cannot register namespace {namespace.tag} more than once!')
 
@@ -121,10 +122,12 @@ def get_variables(self):
                 variables_result.append((variable, vn))
         return variables_result
 
-    def add_event(self, key, condition, action, terminal=True, direction=-1,compiled=False):
+    def add_event(self, key, condition, action, compiled_functions=None, terminal=True, direction=-1, compiled=False):
         condition = condition
         action = action
-        event = NumerousEvent(key, condition, action, compiled, terminal, direction)
+        event = NumerousEvent(key, condition, action, compiled, terminal, direction,
+                              compiled_functions=compiled_functions)
+
         self.events.append(event)
 
     def add_timestamp_event(self, key, action, timestamps):
@@ -133,7 +136,7 @@ def add_timestamp_event(self, key, action, timestamps):
         self.timestamp_events.append(event)
 
     def _increase_level(self):
-        self.level = self.level+1
+        self.level = self.level + 1
 
     def get_item(self, item_path):
         """
diff --git a/numerous/engine/system/namespace.py b/numerous/engine/system/namespace.py
index 01303958..0a4403a3 100644
--- a/numerous/engine/system/namespace.py
+++ b/numerous/engine/system/namespace.py
@@ -1,5 +1,6 @@
 import logging
 import uuid
+import deprecation
 from numerous.utils.dict_wrapper import _DictWrapper
 from numerous.engine.variables import Variable, VariableDescription, _VariableFactory, SetOfVariables
 
@@ -46,7 +47,9 @@ def __setattr__(self, name, value):
         else:
             object.__setattr__(self, name, value)
 
-    def create_variable(self, name):
+    @deprecation.deprecated(deprecated_in="0.3", removed_in="0.4",
+                            details='use add_constant, add_parameter, or add_state')
+    def create_variable(self, name, value=0):
         """
         Creates a variable in the namespaces with given name.
 
@@ -56,7 +59,7 @@ def create_variable(self, name):
             Name of a 'Variable'
 
         """
-        self.create_variable_from_desc(VariableDescription(tag=name))
+        self.create_variable_from_desc(VariableDescription(tag=name, initial_value=value))
 
     def create_variable_from_desc(self, variable_description):
         """
diff --git a/numerous/engine/system/subsystem.py b/numerous/engine/system/subsystem.py
index d765cee6..855e9879 100644
--- a/numerous/engine/system/subsystem.py
+++ b/numerous/engine/system/subsystem.py
@@ -1,11 +1,13 @@
 import copy
 from enum import Enum
 
-import networkx as nx
+from numerous.engine.system.external_mappings import ExternalMappingUnpacked
+
 
 from numerous.multiphysics import EquationBase
 from numerous.utils.dict_wrapper import _DictWrapper
 from numerous.engine.system.item import Item
+import networkx as nx
 from numerous.engine.system.connector_item import ConnectorItem
 from numerous.engine.system.namespace import SetNamespace
 
@@ -23,9 +25,10 @@ class Subsystem(ConnectorItem):
 
     """
 
-    def __init__(self, tag):
+    def __init__(self, tag, external_mappings=None, data_loader=None):
         self.ports = _DictWrapper({}, Item)
         self.registered_items = {}
+        self.external_mappings = ExternalMappingUnpacked(external_mappings, data_loader) if external_mappings else None
         super().__init__(tag)
 
     def add_port(self, port_tag, item):
@@ -110,7 +113,6 @@ def get_graph_visualisation(self, DG=None, parent=None):
                 DG.add_edge(self.tag, item.tag)
         return DG
 
-
     def update_variables_path(self, item, tail=[]):
         """
         Create variable path hierarchy.
@@ -151,6 +153,47 @@ def register_item(self, item):
         self.update_variables_path(item)
         self.registered_items.update({item.id: item})
 
+    def _find_variable(self, item, varname):
+        variables = item.get_variables()
+        for variable in variables:
+            for path in variable[0].path.path.values():
+                if path[0] == varname:
+                    return True
+        return False
+
+    def find_variable(self, varname):
+        if self._find_variable(self, varname):
+            return True
+
+        for item in self.registered_items.values():
+            if isinstance(item, Subsystem):
+                return item.find_variable(varname)
+            if self._find_variable(item, varname):
+                return True
+        return False
+
+    def _get_external_mappings(self):
+        external_mappings = []
+        if self.external_mappings is not None:
+            external_mappings.append(self.external_mappings)
+        for item in self.registered_items.values():
+            if isinstance(item, Subsystem):
+                external_mappings.extend(item._get_external_mappings())
+
+        return external_mappings
+
+    def get_external_mappings(self):
+        external_mappings = self._get_external_mappings()
+
+        for external_mapping in external_mappings:
+            for mapping in external_mapping.external_mappings:
+                for alias in mapping.dataframe_aliases:
+                    if not self.find_variable(alias):
+                        raise ValueError(f"No variable named '{alias}' in system '{self.tag}' "
+                                         f"(could not map external data")
+
+        return external_mappings
+
 
 class ItemSet(Subsystem, EquationBase):
 
diff --git a/numerous/engine/variables.py b/numerous/engine/variables.py
index 62e5e45f..cbf53473 100644
--- a/numerous/engine/variables.py
+++ b/numerous/engine/variables.py
@@ -31,8 +31,9 @@ class OverloadAction(Enum):
 class VariableDescription:
     tag: str
     type: VariableType = VariableType.PARAMETER
-    initial_value: Any = None
+    initial_value: [float, int] = None
     id: str = None
+    variable_idx: int = 0
     logger_level: LoggerLevel = LoggerLevel.ALL
     alias: str = None
     update: bool = False
@@ -50,7 +51,7 @@ class DetailedVariableDescription(VariableDescription):
 
 class MappedValue(object):
     def __init__(self, id):
-        self.id = str(id).replace("-","_")
+        self.id = str(id).replace("-", "_")
         self.mapping = None
         self.sum_mapping = []
         self.special_mapping = False
@@ -181,10 +182,9 @@ def __init__(self, detailed_variable_description, base_variable=None):
         self.size = 0
         self.temporary_variable = False
         if base_variable:
-
-            self._value = base_variable.value
+            self.value = base_variable.value
         else:
-            self._value = detailed_variable_description.initial_value
+            self.value = detailed_variable_description.initial_value
         self.item = detailed_variable_description.item
         self.metadata = detailed_variable_description.metadata
         self.mapping = detailed_variable_description.mapping
@@ -210,8 +210,9 @@ def value(self):
     def value(self, value):
         try:
             float(value)
-        except ValueError:
-            print(value)
+        except (ValueError, TypeError):
+            raise ValueError(f"Only numeric values allowed in variables (attempted to set value='{value}' in "
+                             f"{self.id})")
         self._value = value
         if self.llvm_idx is not None:
             self.write_variable(value, self.llvm_idx)
@@ -231,7 +232,7 @@ def update_set_var(self, set_var, set_namespace):
     @staticmethod
     def create(namespace, v_id, tag,
                v_type, value, item, metadata,
-               mapping, update_counter, allow_update, logger_level, alias):
+               mapping, update_counter, allow_update, logger_level, variable_idx, alias):
         return Variable(DetailedVariableDescription(tag=tag,
                                                     id=v_id,
                                                     type=v_type,
@@ -243,6 +244,7 @@ def create(namespace, v_id, tag,
                                                     update_counter=update_counter,
                                                     allow_update=allow_update,
                                                     logger_level=logger_level,
+                                                    variable_idx=variable_idx,
                                                     alias=alias))
 
 
@@ -262,6 +264,7 @@ def _create_from_variable_desc(namespace, item, var_desc):
                                update_counter=0,
                                allow_update=(var_desc.type != VariableType.CONSTANT),
                                logger_level=var_desc.logger_level,
+                               variable_idx=var_desc.variable_idx,
                                alias=var_desc.alias,
                                )
 
@@ -279,6 +282,7 @@ def _create_from_variable_desc_unbound(initial_value, variable_description):
                              allow_update=(variable_description.type != VariableType.CONSTANT),
                              logger_level=variable_description.logger_level,
                              alias=variable_description.alias,
+                             variable_idx=0
                              )
 
         return v1
diff --git a/numerous/multiphysics/equation_base.py b/numerous/multiphysics/equation_base.py
index 29a12be6..f7620dad 100644
--- a/numerous/multiphysics/equation_base.py
+++ b/numerous/multiphysics/equation_base.py
@@ -28,9 +28,8 @@ def register_variable_description(self, variable_description):
     # refactored in a more functional way
     def __iter__(self):
         # used filter and extend
-        self.variables_descriptions_deque.extend(list(filter((lambda v:\
-            isinstance(v, VariableDescription)),\
-            self.variables_descriptions.values())))
+        self.variables_descriptions_deque.extend(list(filter((lambda v: isinstance(v, VariableDescription)),
+                                                             self.variables_descriptions.values())))
         return self
 
     def __next__(self):
@@ -39,6 +38,7 @@ def __next__(self):
         else:
             raise StopIteration
 
+
 class Mapping:
     def __init__(self, from_, to_=None, dir='<-'):
         self.from_ = from_
@@ -52,10 +52,12 @@ class EquationBase:
      are defined in classes extending the :class:`numerous.multiphysics.Equation`.
 
     """
+
     def __init__(self, tag=None):
         if tag:
             self.tag = tag
         self.equations = []
+        self.new_variable_idx = 0
         self.variables_descriptions = VariableDescriptionMap(self)
         super(EquationBase, self).__init__()
         method_list = [func for func in dir(self) if callable(getattr(self, func)) and not func.startswith("__")]
@@ -64,7 +66,6 @@ def __init__(self, tag=None):
             if hasattr(method_call, '_equation'):
                 self.equations.append(method_call)
 
-
     def add_parameter(self, tag, init_val=0, logger_level=None, alias=None, integrate=None):
         """
 
@@ -87,16 +88,15 @@ def integrate_{tag}(self, scope):
                 scope.{integrate['tag']}_dot = scope.{tag} * {integrate['scale']}
             """
             ie = InlineEquation()
-            integrate_source_ = ie('integrate_'+tag, integrate_source, {})
+            integrate_source_ = ie('integrate_' + tag, integrate_source, {})
 
-            setattr(self, 'integrate_'+tag, integrate_source_)
+            setattr(self, 'integrate_' + tag, integrate_source_)
             self.equations.append(integrate_source_)
 
-
-    def add_parameters(self, parameters:dict or list):
+    def add_parameters(self, parameters: dict or list):
         if isinstance(parameters, dict):
             for p, v in parameters.items():
-                if isinstance(v,numbers.Number):
+                if isinstance(v, numbers.Number):
                     self.add_parameter(p, init_val=v)
                 else:
                     self.add_parameter(p, **v)
@@ -104,9 +104,9 @@ def add_parameters(self, parameters:dict or list):
             for p in parameters:
                 self.add_parameter(p)
 
-    def add_constants(self, constants:dict):
+    def add_constants(self, constants: dict):
         for p, v in constants.items():
-            if isinstance(v,numbers.Number):
+            if isinstance(v, numbers.Number):
                 self.add_constant(p, value=v)
             else:
                 self.add_constant(p, **v)
@@ -125,13 +125,18 @@ def add_constant(self, tag, value, logger_level=None, alias=None):
         """
         self.add_variable(tag, value, VariableType.CONSTANT, logger_level, alias)
 
-    def add_state(self, tag, init_val=0, logger_level=None, alias=None):
+    def add_derivative(self, tag, logger_level=None, alias=None):
+        self.add_variable(tag + '_dot', 0, VariableType.DERIVATIVE, logger_level,
+                          alias + "_dot" if alias is not None else None)
+
+    def add_state(self, tag, init_val=0, logger_level=None, alias=None, create_derivative=True):
         """
 
         Parameters
         ----------
         tag
         init_val
+        create_derivative
 
         Returns
         -------
@@ -140,8 +145,9 @@ def add_state(self, tag, init_val=0, logger_level=None, alias=None):
         if not isinstance(init_val, float) and not isinstance(init_val, int) and not isinstance(init_val, np.int64):
             raise ValueError("State must be float or integer")
         self.add_variable(tag, init_val, VariableType.STATE, logger_level, alias)
-        self.add_variable(tag + '_dot', 0, VariableType.DERIVATIVE, logger_level,
-                          alias+"_dot" if alias is not None else None)
+        if create_derivative:
+            self.add_variable(tag + '_dot', 0, VariableType.DERIVATIVE, logger_level,
+                              alias + "_dot" if alias is not None else None)
 
     def add_variable(self, tag, init_val, var_type, logger_level, alias):
         """
@@ -157,14 +163,14 @@ def add_variable(self, tag, init_val, var_type, logger_level, alias):
         -------
 
         """
+
         self.variables_descriptions. \
             register_variable_description(VariableDescription(tag=tag, id=str(uuid.uuid1()), initial_value=init_val,
-                                                              type=var_type, logger_level=logger_level, alias=alias))
+                                                              type=var_type, logger_level=logger_level, alias=alias,
+                                                              variable_idx=self.new_variable_idx))
+        self.new_variable_idx += 1
 
     def map_create_parameters(self, item, mappings):
         for m in mappings:
             if not self.variables_descriptions.variable_exists(m.to_):
                 self.add_parameter(m.to_)
-
-
-
diff --git a/numerous/multiphysics/equation_decorators.py b/numerous/multiphysics/equation_decorators.py
index 0cb31606..3e967192 100644
--- a/numerous/multiphysics/equation_decorators.py
+++ b/numerous/multiphysics/equation_decorators.py
@@ -6,9 +6,10 @@
 from textwrap import dedent
 from numba import njit
 
+
 class NumerousFunction(object):
     def __init__(self, signature=None):
-        self.id =str(uuid.uuid4())
+        self.id = str(uuid.uuid4())
         self.signature = signature
 
     def __call__(self, func):
@@ -16,14 +17,10 @@ def __call__(self, func):
         return njited_func
 
 
-
-
-
-
 class Equation(object):
 
     def __init__(self):
-        self.id =str(uuid.uuid4())
+        self.id = str(uuid.uuid4())
 
     def __call__(self, func):
         @wraps(func)
@@ -34,10 +31,13 @@ def wrapper(self, scope):
             wrapper.__self__ = func.__self__
 
         wrapper._equation = True
-        wrapper.lines = inspect.getsource(func)
+
         wrapper.id = self.id
-        #a = inspect.getsourcelines(func)
-        wrapper.lineno = inspect.getsourcelines(func)[1]
+        try:
+            wrapper.lines = inspect.getsource(func)
+            wrapper.lineno = inspect.getsourcelines(func)[1]
+        except OSError:
+            pass
         wrapper.file = inspect.getfile(func)
         wrapper.name = func.__name__
         self.name = func.__name__
@@ -45,7 +45,6 @@ def wrapper(self, scope):
 
 
 def add_equation(host, func):
-
     eq = Equation()
     eq_func = eq(func)
 
@@ -66,9 +65,10 @@ def dedent_code(code):
                 print(dsource)
                 raise
 
+
 class InlineEquation(Equation):
 
-    def __call__(self, func_name, func_source, namespace = {}):
+    def __call__(self, func_name, func_source, namespace={}):
         self.name = func_name
         tries = 0
         while tries < 10:
@@ -84,14 +84,16 @@ def __call__(self, func_name, func_source, namespace = {}):
                     raise
 
         func = namespace[func_name]
+
         @wraps(func)
-        def wrapper(self,scope):
+        def wrapper(self, scope):
             func(self, scope)
+
         wrapper.name = func_name
         wrapper._equation = True
         wrapper.lines = func_source
         wrapper.id = self.id
-        #a = inspect.getsourcelines(func)
+        # a = inspect.getsourcelines(func)
         wrapper.lineno = 0
         wrapper.file = 'dynamic.py'
         # wrapper.i = self.i
diff --git a/numerous/utils/data_loader.py b/numerous/utils/data_loader.py
index 40555070..bbee08c5 100644
--- a/numerous/utils/data_loader.py
+++ b/numerous/utils/data_loader.py
@@ -2,6 +2,7 @@
 from typing import Tuple
 
 import pandas as pd
+import numpy as np
 from pandas import DataFrame
 from pandas.errors import EmptyDataError
 
@@ -11,30 +12,56 @@ class DataLoader(ABC):
     def __init__(self):
         self.chunksize = 1000
 
-    def load(self, df_id: str, t: int):
+    def load(self, df_id: str, t: float):
         pass
 
 
-class LocalDataLoader(DataLoader):
+class CSVDataLoader(DataLoader):
 
     def __init__(self, chunksize=1000):
+        super(CSVDataLoader, self).__init__()
         self.chunksize = chunksize
         self.is_chunks = False
-        if self.chunksize is not None:
+        self._skiprows = 0
+        self.df = pd.DataFrame()
+        self.t_ix = None
+        if self.chunksize:
             self.is_chunks = True
 
-    def load(self, df_id: str, t: int) -> DataFrame:
+    def _valid(self, chunks, t):
+        for i, chunk in enumerate(chunks):
+            if not self.t_ix:
+                self.t_ix = np.where(chunk.columns == 'time')[0][0]
+            mask = chunk.iloc[:, self.t_ix] <= t
+
+            if mask.all():
+                yield chunk
+            else:
+                self._skiprows += i * self.chunksize
+                yield chunk
+                break
+
+    def load(self, df_id: str, t: float) -> DataFrame:
+        if not self.df.empty and t < min(self.df.iloc[:, self.t_ix]):
+            self._skiprows = 0
+
         if self.is_chunks:
-            return pd.read_csv(df_id, header=0, skiprows=t, chunksize=self.chunksize).get_chunk()
+            chunks = pd.read_csv(df_id, header=0, skiprows=self._skiprows, chunksize=self.chunksize)
+            df = pd.concat(self._valid(chunks, t))
         else:
-            return pd.read_csv(df_id, header=0, skiprows=t, chunksize=self.chunksize)
+            df = pd.read_csv(df_id, header=0)
+
+        self.df = df
+        return df
+
 
 
 class InMemoryDataLoader(DataLoader):
 
     def __init__(self, df):
+        super(InMemoryDataLoader, self).__init__()
         self.df = df
 
-    def load(self, df_id: str, t: int) -> DataFrame:
+    def load(self, df_id: str, t: float) -> DataFrame:
         return self.df
 
diff --git a/requirements.txt b/requirements.txt
index 9c125595..37d07d39 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -2,7 +2,7 @@ attrs>=	19.3.0
 graphviz
 importlib-metadata>=	1.3.0
 more-itertools>=	8.0.2
-numpy ==	1.21.5
+numpy == 1.22.0
 networkx>=2.4
 numba>=0.55
 tqdm>=4.40.2
@@ -16,4 +16,6 @@ pytz>=	2019.3
 scipy>=	1.7.3
 six>=	1.13.0
 wcwidth>=	0.1.7
-zipp >=	0.6.0
\ No newline at end of file
+zipp >=	0.6.0
+fmpy == 0.3.7
+deprecation == 2.1.0
\ No newline at end of file
diff --git a/setup.py b/setup.py
index 040777b9..bbe3f84c 100644
--- a/setup.py
+++ b/setup.py
@@ -9,7 +9,7 @@
 
 setuptools.setup(
     name="numerous-engine",
-    version="0.2.3",
+    version="0.3.0",
     author='Artem Chupryna, EnergyMachines ApS',
     author_email='artem.chupryna@energymachines.com',
     description="Numerous  - an object-oriented modelling and simulation engine.",


Test patch:
diff --git a/examples/simple_sys_test/simple.py b/examples/simple_sys_test/simple.py
index 4422a668..97de84b5 100644
--- a/examples/simple_sys_test/simple.py
+++ b/examples/simple_sys_test/simple.py
@@ -1,14 +1,10 @@
 from numerous.multiphysics.equation_decorators import Equation
 from numerous.multiphysics.equation_base import EquationBase
 from numerous.engine.system.item import Item
-from numerous.engine.system import Subsystem, ConnectorTwoWay, ItemsStructure
-
-import numpy as np
+from numerous.engine.system import Subsystem, ItemsStructure
 
 if __name__ == "__main__":
     from numerous.engine import model, simulation
-    from time import time
-    from matplotlib import pyplot as plt
 
 
 class Simple(EquationBase, Item):
@@ -24,7 +20,6 @@ def __init__(self, tag="simple", x0=1, k=1):
 
         self.add_state('x', x0)
 
-
         # define namespace and add equation
         mechanics = self.create_namespace('mechanics')
         mechanics.add_equations([self])
@@ -32,7 +27,7 @@ def __init__(self, tag="simple", x0=1, k=1):
     @Equation()
     def eval(self, scope):
 
-        scope.x_dot = scope.k + 0*scope.x
+        scope.x_dot = scope.k + 0 * scope.x
 
 
 class SimpleSystem(Subsystem):
@@ -41,21 +36,16 @@ def __init__(self, tag, k=.1, n=1, x0=[0]):
         simples = []
         for i in range(n):
             # Create oscillator
-            simple = Simple('simple' + str(i), k=k*(i+1), x0=x0[i])
+            simple = Simple('simple' + str(i), k=k * (i + 1), x0=x0[i])
             simples.append(simple)
 
         self.register_items(simples, tag="simples", structure=ItemsStructure.LIST)
 
 
-
 if __name__ == "__main__":
     from numerous.engine import model, simulation
-    from time import time
-    from matplotlib import pyplot as plt
 
     subsystem = SimpleSystem('system', k=.1, n=2, x0=[0, 0])
     # Define simulation
-    s = simulation.Simulation(
-        model.Model(subsystem),
-        t_start=0, t_stop=1, num=100, num_inner=100, max_step=1,use_llvm=True, save_to_file=True
-    )
+    s = simulation.Simulation(model.Model(subsystem), t_start=0, t_stop=1, num=100, num_inner=100, max_step=1,
+                              use_llvm=True, save_to_file=True)
diff --git a/tests/basic_test/test_assign.py b/tests/basic_test/test_assign.py
index 6e4cf7a2..f25cfdb9 100644
--- a/tests/basic_test/test_assign.py
+++ b/tests/basic_test/test_assign.py
@@ -3,7 +3,7 @@
 
 from numerous.engine import simulation
 from numerous.engine.model import Model
-from numerous.engine.simulation.solvers.base_solver import solver_types
+
 from numerous.engine.system import Item, Subsystem
 from numerous.multiphysics.equation_base import EquationBase
 from numerous.multiphysics.equation_decorators import Equation
@@ -98,80 +98,73 @@ def __init__(self, tag='system', item=None):
         self.register_items([item])
 
 
-@pytest.mark.parametrize("solver", solver_types)
 @pytest.mark.parametrize("use_llvm", [True, False])
-def test_single_assign(solver, use_llvm):
+def test_single_assign(use_llvm):
     m = System(tag='system1', item=SingleAssign(tag='item1'))
     model = Model(m, use_llvm=use_llvm, generate_graph_pdf=False)
-    s = simulation.Simulation(model, solver_type=solver, t_start=0, t_stop=2.0, num=2, num_inner=2)
+    s = simulation.Simulation(model, t_start=0, t_stop=2.0, num=2, num_inner=2)
     s.solve()
     historian_df = s.model.historian_df
     assert approx(historian_df['system1.item1.t1.x'][2]) == 2
 
 
-@pytest.mark.parametrize("solver", solver_types)
 @pytest.mark.parametrize("use_llvm", [True, False])
-def test_tuple_assign(solver, use_llvm):
+def test_tuple_assign(use_llvm):
     m = System(tag='system2', item=TupleToTupleAssign(tag='item2'))
     model = Model(m, use_llvm=use_llvm, generate_graph_pdf=False)
-    s = simulation.Simulation(model, solver_type=solver, t_start=0, t_stop=2.0, num=2, num_inner=2)
+    s = simulation.Simulation(model, t_start=0, t_stop=2.0, num=2, num_inner=2)
     s.solve()
     historian_df = s.model.historian_df
     assert approx(historian_df['system2.item2.t1.x'][2]) == 2
 
 
-@pytest.mark.parametrize("solver", solver_types)
 @pytest.mark.skip(reason="Functionality not implemented in current version")
 @pytest.mark.parametrize("use_llvm", [True, False])
-def test_tuple_to_var_assign(solver, use_llvm):
+def test_tuple_to_var_assign(use_llvm):
     m = System(tag='system3', item=TupleToVarAssign(tag='item3'))
     model = Model(m, use_llvm=use_llvm, generate_graph_pdf=False)
-    s = simulation.Simulation(model, solver_type=solver, t_start=0, t_stop=2.0, num=2, num_inner=2)
+    s = simulation.Simulation(model, t_start=0, t_stop=2.0, num=2, num_inner=2)
     s.solve()
     historian_df = s.model.historian_df
     assert approx(historian_df['system3.item3.t1.x'][2]) == 2
 
 
-@pytest.mark.parametrize("solver", solver_types)
 @pytest.mark.parametrize("use_llvm", [True, False])
-def test_tuple_to_func_assign(solver, use_llvm):
+def test_tuple_to_func_assign(use_llvm):
     m = System(tag='system4', item=TupleToFuncAssign(tag='item4'))
     model = Model(m, use_llvm=use_llvm, generate_graph_pdf=False, imports=[("external_functions", "if_replacement")])
-    s = simulation.Simulation(model, solver_type=solver, t_start=0, t_stop=2.0, num=2, num_inner=2)
+    s = simulation.Simulation(model, t_start=0, t_stop=2.0, num=2, num_inner=2)
     s.solve()
     historian_df = s.model.historian_df
     assert approx(historian_df['system4.item4.t1.x'][2]) == 2
 
 
-@pytest.mark.parametrize("solver", solver_types)
 @pytest.mark.parametrize("use_llvm", [True, False])
-def test_single_binary_operator(solver, use_llvm):
+def test_single_binary_operator(use_llvm):
     m = System(tag='system5', item=SingleBinaryOperator(tag='item5'))
     model = Model(m, use_llvm=use_llvm, generate_graph_pdf=False)
-    s = simulation.Simulation(model, solver_type=solver, t_start=0, t_stop=2.0, num=2, num_inner=2)
+    s = simulation.Simulation(model, t_start=0, t_stop=2.0, num=2, num_inner=2)
     s.solve()
     historian_df = s.model.historian_df
     assert approx(historian_df['system5.item5.t1.x'][2]) == 2
 
 
-@pytest.mark.parametrize("solver", solver_types)
 @pytest.mark.parametrize("use_llvm", [True, False])
-def test_single_unary_operator(solver, use_llvm):
+def test_single_unary_operator(use_llvm):
     m = System(tag='system6', item=SingleUnaryOperator(tag='item6'))
     model = Model(m, use_llvm=use_llvm, generate_graph_pdf=False)
-    s = simulation.Simulation(model, solver_type=solver, t_start=0, t_stop=2.0, num=2, num_inner=2)
+    s = simulation.Simulation(model, t_start=0, t_stop=2.0, num=2, num_inner=2)
     s.solve()
     historian_df = s.model.historian_df
     assert approx(historian_df['system6.item6.t1.x'][2]) == 2
 
 
-@pytest.mark.parametrize("solver", solver_types)
 @pytest.mark.parametrize("use_llvm", [True, False])
-def test_single_unary_operator_local_variables_are_not_shared(solver, use_llvm):
+def test_single_unary_operator_local_variables_are_not_shared(use_llvm):
     m = System(tag='system7', item=SingleUnaryOperator(tag='item7', t=1))
     m.register_item(SingleUnaryOperator(tag='item8', t=-3))
     model = Model(m, use_llvm=use_llvm, generate_graph_pdf=False)
-    s = simulation.Simulation(model, solver_type=solver, t_start=0, t_stop=2.0, num=2, num_inner=2)
+    s = simulation.Simulation(model, t_start=0, t_stop=2.0, num=2, num_inner=2)
     s.solve()
     historian_df = s.model.historian_df
     assert approx(historian_df['system7.item7.t1.x'][2]) == 4
diff --git a/tests/basic_test/test_eq_using_closure.py b/tests/basic_test/test_eq_using_closure.py
index fa23efe1..58ff262b 100644
--- a/tests/basic_test/test_eq_using_closure.py
+++ b/tests/basic_test/test_eq_using_closure.py
@@ -6,7 +6,7 @@
 from numerous.engine.system.item import Item
 from numerous.engine.system import Subsystem
 from numerous.engine import model, simulation
-from numerous.engine.simulation.solvers.base_solver import solver_types
+
 
 
 
@@ -84,38 +84,35 @@ def __init__(self, tag, *items):
         self.register_items(items)
 
 
-@pytest.mark.parametrize("solver", solver_types)
 @pytest.mark.parametrize("use_llvm", [False, True])
-def test_external_closure_0(solver, use_llvm):
+def test_external_closure_0(use_llvm):
     model_ = model.Model(
         IfSystem('m_system', SelfTest('tm1', 1), SelfTest('tm11', 2), ClosureFuncTest('tm2'), ClosureVarTest('tm3')),
         use_llvm=use_llvm)
-    s = simulation.Simulation(model_, solver_type=solver, t_start=0, t_stop=3, num=1, num_inner=1)
+    s = simulation.Simulation(model_, t_start=0, t_stop=3, num=1, num_inner=1)
     s.solve()
     expected = 4.0
     assert s.model.historian_df['m_system.tm1.test_nm.x'][1] == expected
 
 
 
-@pytest.mark.parametrize("solver", solver_types)
 @pytest.mark.parametrize("use_llvm", [False, True])
-def test_external_closure_2(solver, use_llvm):
+def test_external_closure_2(use_llvm):
     model_ = model.Model(
         IfSystem('m_system', SelfTest('tm1', 1), SelfTest('tm11', 2), ClosureFuncTest('tm2'), ClosureVarTest('tm3')),
         use_llvm=use_llvm)
-    s = simulation.Simulation(model_, solver_type=solver, t_start=0, t_stop=3, num=1, num_inner=1)
+    s = simulation.Simulation(model_, t_start=0, t_stop=3, num=1, num_inner=1)
     s.solve()
     expected = 5.0
     assert s.model.historian_df['m_system.tm11.test_nm.x'][1] == expected
 
 
-@pytest.mark.parametrize("solver", solver_types)
 @pytest.mark.parametrize("use_llvm", [False, True])
-def test_external_closure_3(solver, use_llvm):
+def test_external_closure_3(use_llvm):
     model_ = model.Model(
         IfSystem('m_system', SelfTest('tm1', 1), SelfTest('tm11', 2), ClosureFuncTest('tm2'), ClosureVarTest('tm3')),
         use_llvm=use_llvm)
-    s = simulation.Simulation(model_, solver_type=solver, t_start=0, t_stop=3, num=1, num_inner=1)
+    s = simulation.Simulation(model_, t_start=0, t_stop=3, num=1, num_inner=1)
     s.solve()
 
     expected = 25
@@ -125,14 +122,12 @@ def test_external_closure_3(solver, use_llvm):
     assert s.model.historian_df['m_system.tm3.test_nm.x'][1] == expected
 
 
-
-@pytest.mark.parametrize("solver", solver_types)
 @pytest.mark.parametrize("use_llvm", [False, True])
-def test_external_closure_4(solver, use_llvm):
+def test_external_closure_4(use_llvm):
     model_ = model.Model(
         IfSystem('m_system', SelfTest('tm1', 1), SelfTest('tm11', 2), ClosureFuncTest('tm2'), ClosureVarTest('tm3')),
         use_llvm=use_llvm)
-    s = simulation.Simulation(model_, solver_type=solver, t_start=0, t_stop=3, num=1, num_inner=1)
+    s = simulation.Simulation(model_, t_start=0, t_stop=3, num=1, num_inner=1)
     s.solve()
 
     expected = 44
diff --git a/tests/basic_test/test_equation_generation.py b/tests/basic_test/test_equation_generation.py
index 44862d23..0051b053 100644
--- a/tests/basic_test/test_equation_generation.py
+++ b/tests/basic_test/test_equation_generation.py
@@ -5,7 +5,7 @@
 from numerous.engine.system.item import Item
 from numerous.engine.system import Subsystem
 from numerous.engine import model, simulation
-from numerous.engine.simulation.solvers.base_solver import solver_types
+
 
 
 
@@ -31,11 +31,10 @@ def __init__(self, tag, item1,item2,item3):
         self.register_items([item1,item2,item3])
 
 
-@pytest.mark.parametrize("solver", solver_types)
 @pytest.mark.parametrize("use_llvm", [False,True])
-def test_external_if_statement(solver, use_llvm):
+def test_external_if_statement(use_llvm):
     model_ = model.Model(IfSystem('m_system', EqTest('tm1'),EqTest('tm2'),EqTest('tm3')), use_llvm=use_llvm)
-    s = simulation.Simulation(model_, solver_type=solver, t_start=0, t_stop=1.0, num=1, num_inner=1)
+    s = simulation.Simulation(model_, t_start=0, t_stop=1.0, num=1, num_inner=1)
     s.solve()
     expected = 7
     assert s.model.historian_df['m_system.tm1.test_nm.Y'][1] == expected
diff --git a/tests/basic_test/test_mapping_order.py b/tests/basic_test/test_mapping_order.py
index 1b334092..9390f5a1 100644
--- a/tests/basic_test/test_mapping_order.py
+++ b/tests/basic_test/test_mapping_order.py
@@ -5,7 +5,7 @@
 from numerous.multiphysics.equation_base import EquationBase
 from numerous.engine.system import Item, Subsystem
 from numerous.engine.model import Model
-from numerous.engine.simulation.solvers.base_solver import solver_types
+
 
 
 
@@ -59,13 +59,12 @@ def __init__(self, tag='main'):
         self.register_item(system)
 
 
-@pytest.mark.parametrize("solver", solver_types)
 @pytest.mark.parametrize("use_llvm", [False,True])
-def test_deriv_order(solver, use_llvm):
+def test_deriv_order(use_llvm):
     m = Main()
     model = Model(m, use_llvm=use_llvm)
     import numpy as np
-    expected = np.array([2.5, 3., 1., 1.])
+    expected = np.array([3, 2.5, 1., 1.])
     assert approx(model.compiled_compute(np.array([0., 0., 0., 0.]))) == expected
-    expected_2 = [3.3, 3.6, 1.6, 1.4]
+    expected_2 = [3.4, 3.7, 1.3, 1.8]
     assert approx(model.compiled_compute(np.array([1., 2., 3., 4.]))) == expected_2
diff --git a/tests/basic_test/test_plus_equals.py b/tests/basic_test/test_plus_equals.py
new file mode 100644
index 00000000..fbec03f7
--- /dev/null
+++ b/tests/basic_test/test_plus_equals.py
@@ -0,0 +1,50 @@
+from numerous.multiphysics.equation_decorators import Equation
+from numerous.multiphysics.equation_base import EquationBase
+from numerous.engine.system.item import Item
+from numerous.engine.system import Subsystem
+from numerous.engine.model import Model
+from numerous.engine.simulation import Simulation
+import pytest
+
+
+class TestEQ(EquationBase, Item):
+    """
+        Equation and item modelling a spring and dampener
+    """
+
+    def __init__(self, tag="test_eq", x0=1, k=1):
+        super(TestEQ, self).__init__(tag)
+        # define variables
+        self.add_constant('k', k)
+        self.add_parameter('p', k)
+        self.add_state('x', x0)
+        # define namespace and add equation
+        mechanics = self.create_namespace('mechanics')
+        mechanics.add_equations([self])
+
+    @Equation()
+    def eval(self, scope):
+        p = 1
+        p += scope.k
+        scope.p = p
+
+
+class TestSystem(Subsystem):
+    def __init__(self, k, x, tag="test_sys"):
+        super().__init__(tag)
+        self.register_item(TestEQ(k=k, x0=x))
+
+
+@pytest.mark.parametrize("use_llvm", [True, False])
+def test_augmented_assign(use_llvm):
+    k = 1
+    x = 1
+    test_system = TestSystem(k=k, x=x)
+    test_model = Model(test_system, use_llvm=use_llvm, save_to_file=True)
+
+    test_simulation = Simulation(test_model, t_start=0, t_stop=100, num=10,
+                                 num_inner=1, max_step=1)
+    test_simulation.solve()
+
+    result = test_simulation.model.historian_df["test_sys.test_eq.mechanics.p"]
+    assert list(result)[0] == 2.0
diff --git a/tests/set_variables_test/test_simple_set_model.py b/tests/set_variables_test/test_simple_set_model.py
index 2881bd76..99925614 100644
--- a/tests/set_variables_test/test_simple_set_model.py
+++ b/tests/set_variables_test/test_simple_set_model.py
@@ -6,7 +6,7 @@
 from numerous.engine.system import Subsystem, ItemsStructure
 from pytest import approx
 from numerous.engine import model, simulation
-from numerous.engine.simulation.solvers.base_solver import solver_types
+
 
 
 class Simple(EquationBase, Item):
@@ -41,18 +41,13 @@ def __init__(self, tag, k=.1, n=1, x0=[0]):
         self.register_items(simples, tag="simples", structure=ItemsStructure.SET)
 
 
-
-@pytest.mark.parametrize("solver", solver_types)
 @pytest.mark.parametrize("use_llvm", [True, False])
-def test_simple_set_model(solver, use_llvm):
+def test_simple_set_model(use_llvm):
     n = 100
     subsystem = SimpleSystem('system', k=.1, n=n, x0=[0]*n)
 
-    s = simulation.Simulation(
-        model.Model(subsystem,use_llvm=use_llvm),
-        t_start=0, t_stop=1, num=100, num_inner=100, max_step=1,
-        solver_type = solver
-    )
+    s = simulation.Simulation(model.Model(subsystem, use_llvm=use_llvm), t_start=0, t_stop=1, num=100, num_inner=100,
+                              max_step=1)
     # Solve and plot
     s.solve()
     for i in range(n):
diff --git a/tests/test_binding.py b/tests/test_binding.py
index 06c08eff..d8ade5d2 100644
--- a/tests/test_binding.py
+++ b/tests/test_binding.py
@@ -45,12 +45,12 @@ class TestConductor(ConnectorTwoWay):
         def __init__(self, tag):
             super(TestConductor, self).__init__(tag)
             self.create_namespace('test_namespace')
-            self.side1.test_namespace.create_variable(name='T')
+            self.side1.test_namespace.create_variable(name='T', value=-1)
             self.side1.test_namespace.create_variable(name='P')
             var_desc = VariableDescription(tag='P1', initial_value=11,
                                            type=VariableType.PARAMETER)
             self.test_namespace.create_variable_from_desc(var_desc)
-            self.side2.test_namespace.create_variable(name='T')
+            self.side2.test_namespace.create_variable(name='T', value=-1)
 
     return TestConductor('test_connector')
 
@@ -62,7 +62,7 @@ def test_binding_multiple_add(two_way_connector_item, simple_item_1):
 
 
 def test_binding_1(two_way_connector_item, simple_item_1):
-    assert two_way_connector_item.side1.test_namespace.T.value is None
+    assert two_way_connector_item.side1.test_namespace.T.value == -1
     two_way_connector_item.bind(side1=simple_item_1)
 
     assert two_way_connector_item.side1.test_namespace.T.value == 0
@@ -76,7 +76,7 @@ def test_binding_1(two_way_connector_item, simple_item_1):
 
 
 def test_binding_2(two_way_connector_item, simple_item_1):
-    assert two_way_connector_item.side1.test_namespace.T.value is None
+    assert two_way_connector_item.side1.test_namespace.T.value == -1
     two_way_connector_item.side1.test_namespace.P = two_way_connector_item.test_namespace.P1
     assert simple_item_1.test_namespace.P.value == 0
     two_way_connector_item.bind(side1=simple_item_1)
@@ -87,8 +87,8 @@ def test_binding_2(two_way_connector_item, simple_item_1):
 
 
 def test_binding_3(two_way_connector_item, simple_item_1, simple_item_2):
-    assert two_way_connector_item.side1.test_namespace.T.value is None
-    assert two_way_connector_item.side2.test_namespace.T.value is None
+    assert two_way_connector_item.side1.test_namespace.T.value == -1
+    assert two_way_connector_item.side2.test_namespace.T.value == -1
     two_way_connector_item.bind(side1=simple_item_1, side2=simple_item_2)
 
     assert two_way_connector_item.side1.test_namespace.T.value == 0
diff --git a/tests/test_caching_model.py b/tests/test_caching_model.py
index 8cc90160..b470940a 100644
--- a/tests/test_caching_model.py
+++ b/tests/test_caching_model.py
@@ -1,8 +1,4 @@
 import os.path
-from enum import Enum
-
-import numpy as np
-import pytest
 
 from numerous.engine.simulation import Simulation
 from numerous.engine.system import Subsystem, Item
@@ -10,14 +6,6 @@
 from tests.test_equations import TestEq_input, Test_Eq, TestEq_ground
 
 
-class SolverType(Enum):
-    SOLVER_IVP = 0
-    NUMEROUS = 1
-
-
-solver_types = [SolverType.NUMEROUS, SolverType.SOLVER_IVP]
-
-
 class I(Item):
     def __init__(self, tag, P, T, R):
         super().__init__(tag)
@@ -73,18 +61,18 @@ def __init__(self, tag, n):
         self.register_items(r_items)
 
 
-def test_cashing_model(monkeypatch, tmpdir):
+def test_caching_model(monkeypatch, tmpdir):
     system = S2N("S2", 2)
     tmpdir_path = str(tmpdir)
     monkeypatch.setenv("EXPORT_MODEL_PATH", tmpdir_path)
 
     model = Model(system, export_model=True)
-    s1 = Simulation(model, t_start=0, t_stop=2, num=2, solver_type=SolverType.NUMEROUS)
+    s1 = Simulation(model, t_start=0, t_stop=2, num=2)
     s1.solve()
     assert os.path.exists(os.path.join(tmpdir, "S2.numerous"))
     cached_model = Model.from_file(os.path.join(os.environ.get("EXPORT_MODEL_PATH"), "S2.numerous"))
 
-    s2 = Simulation(cached_model, t_start=0, t_stop=2, num=2, solver_type=SolverType.NUMEROUS)
+    s2 = Simulation(cached_model, t_start=0, t_stop=2, num=2)
     s2.solve()
     assert all(s1.model.states_as_vector == s2.model.states_as_vector)
 
diff --git a/tests/test_clone_model.py b/tests/test_clone_model.py
new file mode 100644
index 00000000..2e1cc1c2
--- /dev/null
+++ b/tests/test_clone_model.py
@@ -0,0 +1,88 @@
+import pytest
+
+from numerous.engine.model import Model
+from numerous.engine.simulation import Simulation
+from numerous.engine.system import Item, Subsystem
+from tests.test_equations import TestEq_input, Test_Eq, TestEq_ground
+
+
+class TestItem(Item):
+    def __init__(self, tag, P, T, R):
+        super().__init__(tag)
+
+        t1 = self.create_namespace('t1')
+        t1.add_equations([TestEq_input(P=P, T=T, R=R)])
+
+
+class T(Item):
+    def __init__(self, tag, T, R):
+        super().__init__(tag)
+
+        t1 = self.create_namespace('t1')
+        t1.add_equations([Test_Eq(T=T, R=R)])
+
+
+class G(Item):
+    def __init__(self, tag, TG, RG):
+        super().__init__(tag)
+
+        t1 = self.create_namespace('t1')
+        t1.add_equations([TestEq_ground(TG=TG, RG=RG)])
+
+
+class S2N(Subsystem):
+    def __init__(self, tag, n):
+        super().__init__(tag)
+        items = []
+        input = TestItem('1', P=100, T=0, R=10)
+        for i in range(n):
+            items.append(T(str(i + 2), T=1, R=5))
+        ground = G(str(n + 2), TG=10, RG=2)
+
+        input.t1.T_o.add_mapping(items[0].t1.T)
+
+        for item in range(n):
+            if item == 0:
+                items[item].t1.R_i.add_mapping(input.t1.R)
+                items[item].t1.T_o.add_mapping(items[item + 1].t1.T)
+            elif item == n - 1:
+                items[item].t1.R_i.add_mapping(items[item - 1].t1.R)
+                items[item].t1.T_i.add_mapping(items[item - 1].t1.T)
+                items[item].t1.T_o.add_mapping(ground.t1.T)
+            else:
+                items[item].t1.R_i.add_mapping(items[item - 1].t1.R)
+                items[item].t1.T_i.add_mapping(items[item - 1].t1.T)
+                items[item].t1.T_o.add_mapping(items[item + 1].t1.T)
+
+        r_items = [input]
+        for i in items:
+            r_items.append(i)
+        r_items.append(ground)
+        self.register_items(r_items)
+
+
+def test_clone_model():
+    system = S2N("S2", 2)
+    model = Model(system, clonable=True)
+    s1 = Simulation(model, t_start=0, t_stop=2, num=2)
+    s1.solve()
+    s2 = Simulation(model.clone(), t_start=0, t_stop=2, num=2)
+    s2.solve()
+    assert all(s1.model.states_as_vector == s2.model.states_as_vector)
+
+
+def test_clone_model_multiple():
+    system = S2N("S2", 2)
+    model = Model(system, clonable=True)
+    s1 = Simulation(model, t_start=0, t_stop=2, num=2)
+    s1.solve()
+    s2 = Simulation(model.clone(clonable=True).clone(), t_start=0, t_stop=2, num=2)
+    s2.solve()
+    assert all(s1.model.states_as_vector == s2.model.states_as_vector)
+
+
+def test_clone_exception():
+    with pytest.raises(Exception, match="Model isn't clonable"):
+        system = S2N("S2", 2)
+        model = Model(system)
+        model.clone()
diff --git a/tests/test_deeplinked_subsystems.py b/tests/test_deeplinked_subsystems.py
index 5ef7c651..032bc7f7 100644
--- a/tests/test_deeplinked_subsystems.py
+++ b/tests/test_deeplinked_subsystems.py
@@ -10,10 +10,6 @@
 from numerous.engine.simulation import Simulation
 import numpy as np
 
-from numerous.engine.simulation.solvers.base_solver import solver_types
-
-
-
 
 class InitialValue(Item, EquationBase):
     def __init__(self, tag='initialvalue', x0=1):
@@ -116,39 +112,34 @@ def expected(length, N, k):
     return (k ** N) * np.ones(length)
 
 
-@pytest.mark.parametrize("solver", solver_types)
 @pytest.mark.parametrize("use_llvm", [True, False])
-def test_system_link_Success1(solver, use_llvm):
+def test_system_link_Success1(use_llvm):
     N_inner = 5
     N_outer = 2
     system = Success1(N_outer=N_outer, N_inner=N_inner)
     model = Model(system, use_llvm=use_llvm)
 
-    sim = Simulation(model, t_start=0, t_stop=100, num=200, solver_type=solver)
+    sim = Simulation(model, t_start=0, t_stop=100, num=200)
 
     sim.solve()
     df = sim.model.historian_df
 
     assert approx(np.array(
         df['works_deeplink.linkersubsystem_deeplink_2.item_5.item_4.item_3.item_2.item_1.boundary_deeplink.t1.x'])[1:],
-                  rel=1) == \
-           expected(len(df.index[:-1]), (N_outer - 1) * N_inner, 0.9)
+        rel=1) == expected(len(df.df.index[:-1]), (N_outer - 1) * N_inner, 0.9)
 
 
-@pytest.mark.parametrize("solver", solver_types)
 @pytest.mark.parametrize("use_llvm", [True, False])
-def test_system_link_Success2(solver, use_llvm):
+def test_system_link_Success2(use_llvm):
     N_inner = 5
     N_outer = 2
     system = Success2(N_outer=N_outer, N_inner=N_inner)
     model = Model(system, use_llvm=use_llvm)
 
-    sim = Simulation(model, t_start=0, t_stop=100, num=200, solver_type=solver)
+    sim = Simulation(model, t_start=0, t_stop=100, num=200)
 
     sim.solve()
     df = sim.model.historian_df
-
     assert approx(
         np.array(df['doesnotwork_deeplink.inlet_2.item_5.item_4.item_3.item_2.item_1.boundary_deeplink.t1.x'])[1:],
-        rel=1) == \
-           expected(len(df.index[:-1]), (N_outer - 1) * N_inner, 0.9)
+        rel=1) == expected(len(df.df.index[:-1]), (N_outer - 1) * N_inner, 0.9)
diff --git a/tests/test_equation_statments/test_if_expression.py b/tests/test_equation_statments/test_if_expression.py
index e28e6304..87272080 100644
--- a/tests/test_equation_statments/test_if_expression.py
+++ b/tests/test_equation_statments/test_if_expression.py
@@ -5,7 +5,7 @@
 from numerous.engine.system.item import Item
 from numerous.engine.system import Subsystem
 from numerous.engine import model, simulation
-from numerous.engine.simulation.solvers.base_solver import solver_types
+
 
 
 class IfTest2(EquationBase, Item):
@@ -72,29 +72,28 @@ def __init__(self, tag, item):
         self.register_items([item])
 
 
-@pytest.mark.parametrize("solver", solver_types)
 @pytest.mark.parametrize("use_llvm", [True, False])
-def test_external_if_statement_if_body_executed(solver, use_llvm):
+def test_external_if_statement_if_body_executed(use_llvm):
     model_ = model.Model(IfSystem('if_system1', IfTest2('tm1')), use_llvm=use_llvm)
-    s = simulation.Simulation(model_, solver_type=solver, t_start=0, t_stop=10.0, num=10, num_inner=10)
+    s = simulation.Simulation(model_, t_start=0, t_stop=10.0, num=10, num_inner=10)
     s.solve()
     expected_ti4 = -95
     assert s.model.historian_df['if_system1.tm1.test_nm.T_i4'][1] == expected_ti4
 
-@pytest.mark.parametrize("solver", solver_types)
+
 @pytest.mark.parametrize("use_llvm", [True, False])
-def test_external_if_statement_if_else_executed(solver, use_llvm):
+def test_external_if_statement_if_else_executed(use_llvm):
     model_ = model.Model(IfSystem('if_system2', IfTest3('tm1')), use_llvm=use_llvm)
-    s = simulation.Simulation(model_, solver_type=solver, t_start=0, t_stop=10.0, num=10, num_inner=10)
+    s = simulation.Simulation(model_, t_start=0, t_stop=10.0, num=10, num_inner=10)
     s.solve()
     expected_ti4 = 1
     assert s.model.historian_df['if_system2.tm1.test_nm.T_i4'][1] == expected_ti4
 
-@pytest.mark.parametrize("solver", solver_types)
+
 @pytest.mark.parametrize("use_llvm", [True, False])
-def test_external_if_statement_if_else_executed_additional_var(solver, use_llvm):
+def test_external_if_statement_if_else_executed_additional_var(use_llvm):
     model_ = model.Model(IfSystem('if_system3', IfTest4('tm1')), use_llvm=use_llvm)
-    s = simulation.Simulation(model_, solver_type=solver, t_start=0, t_stop=10.0, num=10, num_inner=10)
+    s = simulation.Simulation(model_, t_start=0, t_stop=10.0, num=10, num_inner=10)
     s.solve()
     expected_ti4 = 1
     assert s.model.historian_df['if_system3.tm1.test_nm.T_i4'][1] == expected_ti4
diff --git a/tests/test_equation_statments/test_if_statement.py b/tests/test_equation_statments/test_if_statement.py
index b32b9878..d63b9175 100644
--- a/tests/test_equation_statments/test_if_statement.py
+++ b/tests/test_equation_statments/test_if_statement.py
@@ -5,7 +5,7 @@
 from numerous.engine.system.item import Item
 from numerous.engine.system import Subsystem
 from numerous.engine import model, simulation
-from numerous.engine.simulation.solvers.base_solver import solver_types
+
 
 
 class IfTest(EquationBase, Item):
@@ -73,12 +73,11 @@ def __init__(self, tag, item):
         self.register_items([item])
 
 
-@pytest.mark.parametrize("solver", solver_types)
 @pytest.mark.parametrize("use_llvm", [True, False])
-def test_external_if_statement(solver, use_llvm):
+def test_external_if_statement(use_llvm):
     model_ = model.Model(IfSystem('if_system', IfTest('tm1')), use_llvm=use_llvm,
                          imports=[("external_import_else_if", "h_test")])
-    s = simulation.Simulation(model_, solver_type=solver, t_start=0, t_stop=10.0, num=10, num_inner=10)
+    s = simulation.Simulation(model_, t_start=0, t_stop=10.0, num=10, num_inner=10)
     s.solve()
     expected_ti1 = 106
     expected_ti4 = 1
@@ -86,21 +85,19 @@ def test_external_if_statement(solver, use_llvm):
     assert s.model.historian_df['if_system.tm1.test_nm.T_i4'][1] == expected_ti4
 
 
-@pytest.mark.parametrize("solver", solver_types)
 @pytest.mark.parametrize("use_llvm", [True, False])
-def test_external_if_statement_if_body_executed(solver, use_llvm):
+def test_external_if_statement_if_body_executed(use_llvm):
     model_ = model.Model(IfSystem('if_system1', IfTest2('tm1')), use_llvm=use_llvm)
-    s = simulation.Simulation(model_, solver_type=solver, t_start=0, t_stop=10.0, num=10, num_inner=10)
+    s = simulation.Simulation(model_, t_start=0, t_stop=10.0, num=10, num_inner=10)
     s.solve()
     expected_ti4 = -95
     assert s.model.historian_df['if_system1.tm1.test_nm.T_i4'][1] == expected_ti4
 
 
-@pytest.mark.parametrize("solver", solver_types)
 @pytest.mark.parametrize("use_llvm", [True, False])
-def test_external_if_statement_if_body_skipped(solver, use_llvm):
+def test_external_if_statement_if_body_skipped(use_llvm):
     model_ = model.Model(IfSystem('if_system2', IfTest3('tm1')), use_llvm=use_llvm)
-    s = simulation.Simulation(model_, solver_type=solver, t_start=0, t_stop=10.0, num=10, num_inner=10)
+    s = simulation.Simulation(model_, t_start=0, t_stop=10.0, num=10, num_inner=10)
     s.solve()
     expected_ti4 = 0
     assert s.model.historian_df['if_system2.tm1.test_nm.T_i4'][1] == expected_ti4
diff --git a/tests/test_equation_statments/test_subscript.py b/tests/test_equation_statments/test_subscript.py
new file mode 100644
index 00000000..a2f76919
--- /dev/null
+++ b/tests/test_equation_statments/test_subscript.py
@@ -0,0 +1,37 @@
+import numpy as np
+import pytest
+
+from numerous.multiphysics.equation_decorators import Equation
+from numerous.multiphysics.equation_base import EquationBase
+from numerous.engine.system.item import Item
+from numerous.engine.system import Subsystem
+from numerous.engine import model, simulation
+
+
+class SubsciptTest(EquationBase, Item):
+    def __init__(self, tag="tm"):
+        Item.__init__(self, tag)
+        EquationBase.__init__(self, tag)
+        self.add_parameter('x', 0)
+        mechanics = self.create_namespace('test_nm')
+        mechanics.add_equations([self])
+
+    @Equation()
+    def eval(self, scope):
+        y = np.arange(44, 333)
+        scope.x = y[0]
+
+
+class SubscriptSystem(Subsystem):
+    def __init__(self, tag, *items):
+        super().__init__(tag)
+        self.register_items(items)
+
+
+@pytest.mark.parametrize("use_llvm", [True, False])
+def test_subscript(use_llvm):
+    model_ = model.Model(SubscriptSystem('m_system', SubsciptTest('tm3')), use_llvm=True)
+    s = simulation.Simulation(model_, t_start=0, t_stop=3, num=1, num_inner=1)
+    s.solve()
+    expected = 44
+    assert s.model.historian_df['m_system.tm3.test_nm.x'][1] == expected
diff --git a/tests/test_equations.py b/tests/test_equations.py
index 7cf4dfaa..74796ba2 100644
--- a/tests/test_equations.py
+++ b/tests/test_equations.py
@@ -1,3 +1,4 @@
+import numpy as np
 from numerous.multiphysics import EquationBase, Equation
 
 
@@ -35,10 +36,11 @@ def eval(self, scope):
 class TestEq_ground(EquationBase):
     __test__ = False
 
-    def __init__(self, TG=10, RG=2):
+    def __init__(self, TG=10, RG=2, tol=1e-6):
         super().__init__(tag='ground_eq')
         self.add_constant('T', TG)
         self.add_constant('R', RG)
+        self.add_constant('tol', tol)
 
 
 class TestEq_dictState(EquationBase):
diff --git a/tests/test_external_data.py b/tests/test_external_data.py
new file mode 100644
index 00000000..7f0c7543
--- /dev/null
+++ b/tests/test_external_data.py
@@ -0,0 +1,442 @@
+import pytest
+import math
+import shutil
+import os.path
+import pandas as pd
+import numpy as np
+
+from numerous.engine.system.external_mappings import ExternalMappingElement
+from numerous.utils.data_loader import InMemoryDataLoader, CSVDataLoader
+from numerous.utils.historian import InMemoryHistorian
+from numerous.engine.system.external_mappings.interpolation_type import InterpolationType
+from numerous.engine.model import Model
+from numerous.engine.simulation import Simulation
+from numerous.engine.system import Subsystem, Item, LoggerLevel
+from numerous.multiphysics import EquationBase, Equation
+
+try:
+    FEPS = np.finfo(1.0).eps
+except AttributeError:
+    FEPS = 2.220446049250313e-16
+
+
+def analytical_solution(tmax, g=9.81, f=0.05, x0=1):
+    t_hits = []
+    summation = 0
+    i = 0
+    while True:
+        summation += (2 * (1 - f) ** (i))
+        t_hit = np.sqrt(2 * x0 / g) * (summation - 1)
+        if t_hit > tmax:
+            break
+        t_hits.append(t_hit)
+        i += 1
+
+    t_hits = np.array(t_hits)
+    return t_hits
+
+
+class StaticDataTest(EquationBase, Item):
+    def __init__(self, tag="tm"):
+        super(StaticDataTest, self).__init__(tag)
+
+        # will map to variable with the same path in external dataframe/datasource
+        self.add_parameter('T1', 0)
+        self.add_parameter('T2', 0)
+        self.add_parameter('T_i1', 0)
+        self.add_parameter('T_i2', 0)
+        mechanics = self.create_namespace('test_nm')
+        mechanics.add_equations([self])
+
+    @Equation()
+    def eval(self, scope):
+        scope.T_i1 = scope.T1
+        scope.T_i2 = scope.T2
+
+
+class SubSubSystem(Subsystem):
+    def __init__(self, tag='system', n=3, external_mappings=None, data_loader=None):
+        super(SubSubSystem, self).__init__(tag=tag, external_mappings=external_mappings, data_loader=data_loader)
+        o_s = []
+        for i in range(n):
+            o = StaticDataSystem('sub' + str(i))
+            o_s.append(o)
+
+        for i in reversed(range(n - 1)):
+            o = o_s[i]
+            o_sub = o_s[i + 1]
+            o.register_item(o_sub)
+
+        self.register_item(o_s[0])
+
+
+class StaticDataSystem(Subsystem):
+    def __init__(self, tag, n=1, external_mappings=None, data_loader=None):
+        super().__init__(tag, external_mappings, data_loader)
+        o_s = []
+        for i in range(n):
+            o = StaticDataTest('tm' + str(i))
+            o_s.append(o)
+        # Register the items to the subsystem to make it recognize them.
+        self.register_items(o_s)
+
+
+class OuterSystem(Subsystem):
+    def __init__(self, tag, system, n=1, external_mappings=None, data_loader=None):
+        super().__init__(tag, external_mappings, data_loader)
+        o_s = []
+        for i in range(n):
+            o = StaticDataTest('tm' + str(i))
+            o_s.append(o)
+        o_s.append(system)
+        # Register the items to the subsystem to make it recognize them.
+        self.register_items(o_s)
+
+
+class Bouncing(EquationBase):
+    def __init__(self, g=9.81, f_loss=0.05, x=1, v=0):
+        super().__init__(tag='bouncing_eq')
+        self.add_constant('g', g)
+        self.add_constant('f_loss', f_loss)
+        self.add_parameter('t_hit', 0)
+        self.add_state('x', x)
+        self.add_state('v', v)
+
+    @Equation()
+    def eval(self, scope):
+        scope.x_dot = scope.v  # Position
+        scope.v_dot = -scope.g  # Velocity
+
+
+class Ball(Item):
+    def __init__(self, tag="ball", g=9.81, f_loss=0.05, x0=1, v0=0):
+        super().__init__(tag)
+
+        t1 = self.create_namespace('t1')
+        t1.add_equations([Bouncing(g=g, f_loss=f_loss, x=x0, v=v0)])
+
+        def hitground_event_fun(t, states):
+            return states['t1.x']
+
+            # change direction of movement upon event detection and reduce velocity
+
+        def hitground_event_callback_fun(t, variables):
+            velocity = variables['t1.v']
+            velocity = -velocity * (1 - variables['t1.f_loss'])
+            variables['t1.v'] = velocity
+            variables['t1.t_hit'] = t
+
+        self.add_event("hitground_event", hitground_event_fun, hitground_event_callback_fun)
+
+
+class StaticDataSystemWithBall(Subsystem):
+    def __init__(self, tag, n=1, external_mappings=None, data_loader=None):
+        super().__init__(tag, external_mappings, data_loader)
+        o_s = []
+        for i in range(n):
+            o = StaticDataTest('tm' + str(i))
+            o_s.append(o)
+        o_s.append(Ball())
+        # Register the items to the subsystem to make it recognize them.
+        self.register_items(o_s)
+
+
+def step_solver(sim, t0: float, tmax: float, dt: float):
+    t_ = t0
+    while t_ < tmax:
+        t_new, t_ = sim.step_solve(t_, dt)
+
+    sim.model.create_historian_df()
+    df = sim.model.historian_df
+    return df
+
+
+def normal_solver(sim, t0: float, tmax: float, dt: float):
+    sim.solve()
+
+    df = sim.model.historian_df
+    return df
+
+
+def inmemorydataloader(df=None, **kwargs):
+    return InMemoryDataLoader(df=df)
+
+
+def csvdataloader(chunksize=1, **kwargs):
+    return CSVDataLoader(chunksize=chunksize)
+
+
+@pytest.fixture
+def tmpdir():
+    os.mkdir('tmp')
+    yield 'tmp'
+    shutil.rmtree('tmp')
+
+
+@pytest.fixture
+def external_data():
+    def fn(t0=0, tmax=5, dt_data=0.5):
+        data = {'time': np.arange(t0, tmax + dt_data, dt_data),
+                'Dew Point Temperature {C}': np.arange(t0, tmax + dt_data, dt_data) + 1,
+                }
+        return data
+    yield fn
+
+
+@pytest.fixture
+def simulation(tmpdir: tmpdir):
+    def fn(data, dt_eval=0.1, chunksize=1, historian_max_size=None, t0=0, tmax=5, max_step=0.1, dataloader=csvdataloader,
+           system=StaticDataSystemWithBall, mapped_name="Dew Point Temperature {C}",
+           variable_name='system_external.tm0.test_nm.T1'):
+        external_mappings = []
+
+        df = pd.DataFrame(data, columns=['time', 'Dew Point Temperature {C}'])
+        index_to_timestep_mapping = 'time'
+        index_to_timestep_mapping_start = 0
+
+        dataframe_aliases = {
+            variable_name: (mapped_name, InterpolationType.PIESEWISE),
+        }
+        path = os.path.join(tmpdir, "x.csv")
+        df.to_csv(path)
+        external_mappings.append(ExternalMappingElement
+                                 (path, index_to_timestep_mapping, index_to_timestep_mapping_start, 1,
+                                  dataframe_aliases))
+        data_loader = dataloader(df=df, chunksize=chunksize)
+
+        historian = InMemoryHistorian()
+        historian.max_size = historian_max_size
+        s = Simulation(
+            Model(system
+                  ('system_external', external_mappings=external_mappings, data_loader=data_loader),
+                  historian=historian),
+            t_start=t0, t_stop=tmax, num=len(np.arange(0, tmax, dt_eval)), max_step=max_step)
+        return s
+    yield fn
+
+
+@pytest.mark.parametrize("system", [StaticDataSystem, StaticDataSystemWithBall])
+@pytest.mark.parametrize("use_llvm", [True, False])
+def test_external_data_multiple(use_llvm, system, external_data):
+    external_mappings = []
+    external_mappings_outer = []
+
+    t0 = 0
+    tmax = 5
+    dt_data = 0.1
+    dt_eval = 0.1
+    data = external_data(t0, tmax, dt_data)
+    data.update({'Dry Bulb Temperature {C}': data['Dew Point Temperature {C}'] + 1})
+
+    df = pd.DataFrame(data, columns=['time', 'Dew Point Temperature {C}', 'Dry Bulb Temperature {C}'])
+    index_to_timestep_mapping = 'time'
+    index_to_timestep_mapping_start = 0
+    dataframe_aliases = {
+        'system_outer.system_external.tm0.test_nm.T1': ("Dew Point Temperature {C}", InterpolationType.PIESEWISE),
+        'system_outer.system_external.tm0.test_nm.T2': ('Dry Bulb Temperature {C}', InterpolationType.PIESEWISE),
+    }
+    dataframe_aliases_outer = {
+        'system_outer.tm0.test_nm.T1': ("Dew Point Temperature {C}", InterpolationType.PIESEWISE),
+        'system_outer.tm0.test_nm.T2': ('Dry Bulb Temperature {C}', InterpolationType.PIESEWISE),
+    }
+    external_mappings.append(ExternalMappingElement
+                             ("inmemory", index_to_timestep_mapping, index_to_timestep_mapping_start, 1,
+                              dataframe_aliases))
+    external_mappings_outer.append((ExternalMappingElement
+                                    ("inmemory", index_to_timestep_mapping, index_to_timestep_mapping_start, 1,
+                                     dataframe_aliases_outer)))
+    data_loader = InMemoryDataLoader(df)
+    system_outer = OuterSystem('system_outer',
+                               system('system_external', n=1, external_mappings=external_mappings,
+                                                data_loader=data_loader),
+                               external_mappings=external_mappings_outer,
+                               data_loader=data_loader)
+    s = Simulation(
+        Model(system_outer, use_llvm=use_llvm),
+        t_start=0, t_stop=tmax, num=len(np.arange(0, tmax, dt_eval)), max_step=0.1)
+    s.solve()
+    df = s.model.historian_df
+
+    for ix in df.index:
+        t = df['time'][ix]
+        ix_data = math.floor(t / dt_data + FEPS * 100)
+        v0 = data['Dew Point Temperature {C}'][ix_data]
+        z0 = data['Dry Bulb Temperature {C}'][ix_data]
+        v1 = df['system_outer.tm0.test_nm.T1'][ix]
+        v2 = df['system_outer.tm0.test_nm.T_i1'][ix]
+        v3 = df['system_outer.system_external.tm0.test_nm.T1'][ix]
+        v4 = df['system_outer.system_external.tm0.test_nm.T_i1'][ix]
+        z1 = df['system_outer.tm0.test_nm.T2'][ix]
+        z2 = df['system_outer.tm0.test_nm.T_i2'][ix]
+        z3 = df['system_outer.system_external.tm0.test_nm.T2'][ix]
+        z4 = df['system_outer.system_external.tm0.test_nm.T_i2'][ix]
+
+        assert v1 == pytest.approx(v0), f"expected {v0} but got {v1} at {t}"
+        assert v2 == pytest.approx(v0), f"expected {v0} but got {v2} at {t}"
+        assert v3 == pytest.approx(v0), f"expected {v0} but got {v3} at {t}"
+        assert v4 == pytest.approx(v0), f"expected {v0} but got {v4} at {t}"
+        assert v3 == pytest.approx(v4), f"expected {v3} but got {v4} at {t}"
+
+        assert z1 == pytest.approx(z0), f"expected {z0} but got {z1} at {t}"
+        assert z2 == pytest.approx(z0), f"expected {z0} but got {z2} at {t}"
+        assert z3 == pytest.approx(z0), f"expected {z0} but got {z3} at {t}"
+        assert z4 == pytest.approx(z0), f"expected {z0} but got {z4} at {t}"
+        assert z3 == pytest.approx(z4), f"expected {z3} but got {z4} at {t}"
+
+
+@pytest.mark.parametrize("chunksize", [1, 10000])
+@pytest.mark.parametrize("historian_max_size", [1, 10000])
+@pytest.mark.parametrize("solver", [step_solver, normal_solver])
+@pytest.mark.parametrize("dataloader", [inmemorydataloader, csvdataloader])
+@pytest.mark.parametrize("system", [StaticDataSystem, StaticDataSystemWithBall])
+def test_external_data_chunks_and_historian_update(external_data: external_data, simulation: simulation,
+                                                   solver, chunksize, historian_max_size, dataloader, system):
+
+    t0 = 0
+    tmax = 5
+    dt = 0.1
+    dt_data = 0.5
+    data = external_data(t0, tmax, dt_data)
+    s = simulation(data=data, chunksize=chunksize, historian_max_size=historian_max_size, t0=t0, max_step=dt,
+                   dt_eval=dt, tmax=tmax,
+                   dataloader=dataloader, system=system)
+    df = solver(s, t0, tmax, dt)
+
+    for ix in df.index:
+        t = df['time'][ix]
+        ix_data = math.floor(t / dt_data + FEPS * 100)
+        v0 = data['Dew Point Temperature {C}'][ix_data]
+        v = df['system_external.tm0.test_nm.T1'][ix]
+        v1 = df['system_external.tm0.test_nm.T_i1'][ix]
+
+        assert v == pytest.approx(v0), f"expected {v0} but got {v}"
+        assert v1 == pytest.approx(v), f"expected {v} but got {v1}"
+
+    t_hit_analytical = []
+    if system == StaticDataSystemWithBall:
+        t_hit_analytical = analytical_solution(tmax)
+        t_hit_model = np.unique(df['system_external.ball.t1.t_hit'])[1:]
+        assert len(t_hit_model) == len(t_hit_analytical), "not all events were detected"
+        assert t_hit_model == pytest.approx(t_hit_analytical, 1e-3), "event detection inaccurate"
+
+    assert len(df['system_external.tm0.test_nm.T1']) - len(t_hit_analytical) == len(np.arange(0, tmax + dt, dt)), \
+        "output length not as expected"
+
+
+def test_csvdataloader_reset(external_data: external_data, simulation: simulation):
+    t0 = 0
+    tmax = 5
+    dt = 0.1
+    dt_data = 0.5
+    data = external_data(t0, tmax, dt_data)
+    chunksize = 1
+    historian_max_size = 10000
+    s = simulation(data=data, chunksize=chunksize, historian_max_size=historian_max_size, t0=t0, tmax=tmax, max_step=dt,
+                   dt_eval=dt, dataloader=csvdataloader, system=StaticDataSystemWithBall)
+    s.solve()
+    s.solve()
+
+
+def test_wrong_mapping(external_data: external_data, simulation: simulation):
+    t0 = 0
+    tmax = 5
+    dt = 0.1
+    dt_data = 0.5
+    data = external_data(t0, tmax, dt_data)
+    chunksize = 10000
+    historian_max_size = 10000
+    with pytest.raises(ValueError, match=r"No variable named*"):
+        simulation(data=data, chunksize=chunksize, historian_max_size=historian_max_size, t0=t0, tmax=tmax,
+                       max_step=dt, dt_eval=dt, dataloader=csvdataloader, system=StaticDataSystem,
+                       variable_name='does.not.exist')
+
+    simulation(data=data, chunksize=chunksize, historian_max_size=historian_max_size, t0=t0, tmax=tmax,
+                       max_step=dt, dt_eval=dt, dataloader=csvdataloader, system=SubSubSystem,
+                       variable_name='system_external.sub0.sub1.sub2.tm0.test_nm.T1')
+
+
+class SimpleInt(Subsystem, EquationBase):
+    def __init__(self, tag='integrate', external_mappings=None, data_loader=None):
+        super().__init__(tag, external_mappings, data_loader)
+        self.t1 = self.create_namespace('t1')
+        self.add_state('x', 0, logger_level=LoggerLevel.INFO)
+        self.add_parameter('to_map', 0)
+        self.add_parameter('not_to_map', 100)
+        self.add_parameter('to_map_think_not', 200)
+
+        self.t1.add_equations([self])
+
+    @Equation()
+    def eval(self, scope):
+        scope.x_dot = 1
+
+
+@pytest.mark.parametrize("use_llvm", [True, False])
+def test_external_data_model_check_not_mapped(use_llvm):
+    external_mappings = []
+
+    data = {'time': np.arange(101),
+            'to_map': np.arange(101),
+            }
+
+    df = pd.DataFrame(data)
+    index_to_timestep_mapping = 'time'
+    index_to_timestep_mapping_start = 0
+    dataframe_aliases = {
+        'system_external.t1.to_map': ("to_map", InterpolationType.PIESEWISE),
+    }
+    external_mappings.append(ExternalMappingElement
+                             ("inmemory", index_to_timestep_mapping, index_to_timestep_mapping_start, 1,
+                              dataframe_aliases))
+    data_loader = InMemoryDataLoader(df)
+
+    m = Model(SimpleInt('system_external'), use_llvm=use_llvm)
+
+    s = Simulation(
+        m,
+        t_start=0, t_stop=100.0, num=100, num_inner=1, max_step=1)
+
+    m.set_external_mappings(external_mappings, data_loader=data_loader)
+
+    s.solve()
+    assert pytest.approx(np.array(s.model.historian_df['system_external.t1.to_map'])) == np.arange(101), \
+        'This variable should have the values as being mapped'
+    assert pytest.approx(np.array(s.model.historian_df['system_external.t1.not_to_map'])) == 100, \
+        'This should not be mapped and thus not changed'
+    assert pytest.approx(np.array(s.model.historian_df['system_external.t1.to_map_think_not'])) == 200, \
+        'This should not be mapped and thus not changed'
+
+
+@pytest.mark.parametrize("use_llvm", [True, False])
+def test_external_data_system_check_not_mapped(use_llvm):
+    external_mappings = []
+
+    data = {'time': np.arange(101),
+            'to_map': np.arange(101),
+            }
+
+    df = pd.DataFrame(data)
+    index_to_timestep_mapping = 'time'
+    index_to_timestep_mapping_start = 0
+    dataframe_aliases = {
+        'system_external.t1.to_map': ("to_map", InterpolationType.PIESEWISE),
+    }
+    external_mappings.append(ExternalMappingElement
+                             ("inmemory", index_to_timestep_mapping, index_to_timestep_mapping_start, 1,
+                              dataframe_aliases))
+    data_loader = InMemoryDataLoader(df)
+
+    m = Model(SimpleInt('system_external', data_loader=data_loader, external_mappings=external_mappings),
+              use_llvm=use_llvm)
+
+    s = Simulation(
+        m,
+        t_start=0, t_stop=100.0, num=100, num_inner=1, max_step=1)
+
+    s.solve()
+    assert pytest.approx(np.array(s.model.historian_df['system_external.t1.to_map'])) == np.arange(101), \
+        'This variable should have the values as being mapped'
+    assert pytest.approx(np.array(s.model.historian_df['system_external.t1.not_to_map'])) == 100, \
+        'This should not be mapped and thus not changed'
+    assert pytest.approx(np.array(s.model.historian_df['system_external.t1.to_map_think_not'])) == 200, \
+        'This should REALLY not be mapped and thus not changed'
diff --git a/tests/test_fmu/2_way_bouncing_ball/BouncingBall_2_way.fmu b/tests/test_fmu/2_way_bouncing_ball/BouncingBall_2_way.fmu
new file mode 100644
index 00000000..aa27b111
Binary files /dev/null and b/tests/test_fmu/2_way_bouncing_ball/BouncingBall_2_way.fmu differ
diff --git a/tests/test_fmu/2_way_bouncing_ball/test_bb_2_way.py b/tests/test_fmu/2_way_bouncing_ball/test_bb_2_way.py
new file mode 100644
index 00000000..9a9759ba
--- /dev/null
+++ b/tests/test_fmu/2_way_bouncing_ball/test_bb_2_way.py
@@ -0,0 +1,45 @@
+import os
+
+import numpy as np
+import pytest
+from numerous.engine.model import Model
+from numerous.engine.simulation import Simulation
+from numerous.engine.system import Subsystem
+from numerous.engine.system.fmu_subsystem import FMU_Subsystem
+from pytest import approx
+
+
+class S3(Subsystem):
+    def __init__(self, tag):
+        super().__init__(tag)
+        fmu_filename = os.path.join(os.path.dirname(__file__), 'BouncingBall_2_way.fmu')
+        fmu_subsystem = FMU_Subsystem(fmu_filename, "BouncingBall_2_way", debug_output=True)
+        self.register_items([fmu_subsystem])
+
+
+@pytest.mark.parametrize("use_llvm", [True, False])
+def test_bounsing_ball_2_way_pos_hits(use_llvm):
+    subsystem1 = S3('q1')
+    m1 = Model(subsystem1, use_llvm=use_llvm)
+    s = Simulation(m1, t_start=0, t_stop=1, num=10, num_inner=1, max_step=.1)
+    s.solve()
+    pos = np.array(m1.historian_df["q1.BouncingBall_2_way.t1.h"])
+    vel = m1.historian_df["q1.BouncingBall_2_way.t1.v"]
+    asign = np.sign(np.array(vel))
+    signchange = ((np.roll(asign, 1) - asign) != 0).astype(int)
+    args = np.argwhere(signchange > 0)[2:].flatten()
+    assert all(np.isclose(pos[args], np.array([0, 1, 0]), rtol=1e-03, atol=1e-01))
+
+
+@pytest.mark.parametrize("use_llvm", [True, False])
+def test_bounsing_ball_2_way_t_hits(use_llvm):
+    subsystem1 = S3('q1')
+    m1 = Model(subsystem1, use_llvm=use_llvm)
+    s = Simulation(m1, t_start=0, t_stop=1, num=10, num_inner=1, max_step=.1)
+    s.solve()
+    vel = m1.historian_df["q1.BouncingBall_2_way.t1.v"]
+    asign = np.sign(np.array(vel))
+    signchange = ((np.roll(asign, 1) - asign) != 0).astype(int)
+    args = np.argwhere(signchange > 0)[2:].flatten()
+    t_hits = [0.44928425, 0.69429896, 0.91407356]
+    assert approx(np.array(m1.historian_df["time"][args]), rel=0.01) == t_hits
diff --git a/tests/test_fmu/dahlquist/Dahlquist.fmu b/tests/test_fmu/dahlquist/Dahlquist.fmu
new file mode 100644
index 00000000..a29b0e82
Binary files /dev/null and b/tests/test_fmu/dahlquist/Dahlquist.fmu differ
diff --git a/tests/test_fmu/dahlquist/Dahlquist_ref.csv b/tests/test_fmu/dahlquist/Dahlquist_ref.csv
new file mode 100644
index 00000000..dd7717ee
--- /dev/null
+++ b/tests/test_fmu/dahlquist/Dahlquist_ref.csv
@@ -0,0 +1,52 @@
+"time","x"
+0,1
+0.2,0.818697
+0.4,0.670268
+0.6,0.548776
+0.8,0.449307
+1,0.367857
+1.2,0.301165
+1.4,0.246563
+1.6,0.201863
+1.8,0.165267
+2,0.135306
+2.2,0.110777
+2.4,0.0906989
+2.6,0.0742622
+2.8,0.0608022
+3,0.0497789
+3.2,0.0407543
+3.4,0.0333678
+3.6,0.0273202
+3.8,0.022367
+4,0.0183111
+4.2,0.0149921
+4.4,0.0122755
+4.6,0.0100503
+4.8,0.00822774
+5,0.00673565
+5.2,0.00551445
+5.4,0.0045148
+5.6,0.00369631
+5.8,0.00302613
+6,0.00247752
+6.2,0.00202847
+6.4,0.00166084
+6.6,0.00135981
+6.8,0.00111331
+7,0.000911508
+7.2,0.0007463
+7.4,0.000611038
+7.6,0.000500284
+7.8,0.000409599
+8,0.000335355
+8.2,0.000274572
+8.4,0.000224806
+8.6,0.000184058
+8.8,0.000150696
+9,0.000123381
+9.2,0.000101018
+9.4,8.27082e-05
+9.6,6.77167e-05
+9.8,5.54425e-05
+10,4.53932e-05
diff --git a/tests/test_fmu/dahlquist/test_dahlquist.py b/tests/test_fmu/dahlquist/test_dahlquist.py
new file mode 100644
index 00000000..c8b613b5
--- /dev/null
+++ b/tests/test_fmu/dahlquist/test_dahlquist.py
@@ -0,0 +1,32 @@
+import numpy as np
+import pandas as pd
+import pytest
+
+from numerous.engine.model import Model
+from numerous.engine.simulation import Simulation
+from numerous.engine.simulation.solvers import solver_types
+from numerous.engine.system import Subsystem
+from numerous.engine.system.fmu_subsystem import FMU_Subsystem
+
+
+class S3(Subsystem):
+    def __init__(self, tag):
+        super().__init__(tag)
+
+        fmu_filename = 'Dahlquist.fmu'
+        fmu_subsystem = FMU_Subsystem(fmu_filename, "Dahlquist", debug_output=True)
+        self.register_items([fmu_subsystem])
+
+
+@pytest.mark.xfail
+@pytest.mark.parametrize("solver", solver_types)
+@pytest.mark.parametrize("use_llvm", [True, False])
+def test_dahlquist(solver, use_llvm):
+    subsystem1 = S3('q1')
+    m1 = Model(subsystem1, use_llvm=use_llvm)
+    s = Simulation(
+        m1, t_start=0, t_stop=10, num=50, num_inner=1000, max_step=.1, solver_type=solver)
+    s.solve()
+    x = np.array(m1.historian_df["q1.Dahlquist.t1.x"])
+    df = pd.read_csv("Dahlquist_ref.csv")
+    assert np.allclose(x, df['x'], rtol=1e-1)
diff --git a/tests/test_fmu/test_bouncing_ball/BouncingBall.fmu b/tests/test_fmu/test_bouncing_ball/BouncingBall.fmu
new file mode 100644
index 00000000..e351fc6c
Binary files /dev/null and b/tests/test_fmu/test_bouncing_ball/BouncingBall.fmu differ
diff --git a/tests/test_fmu/test_bouncing_ball/BouncingBall_ref.csv b/tests/test_fmu/test_bouncing_ball/BouncingBall_ref.csv
new file mode 100644
index 00000000..e01ca970
--- /dev/null
+++ b/tests/test_fmu/test_bouncing_ball/BouncingBall_ref.csv
@@ -0,0 +1,325 @@
+"time","h","v"
+0,1,0
+0.01,0.999507,-0.0981
+0.02,0.998034,-0.1962
+0.03,0.995581,-0.2943
+0.04,0.992147,-0.3924
+0.05,0.987732,-0.4905
+0.06,0.982337,-0.5886
+0.07,0.97596,-0.6867
+0.08,0.968602,-0.7848
+0.09,0.960263,-0.8829
+0.1,0.950944,-0.981
+0.11,0.940643,-1.0791
+0.12,0.929361,-1.1772
+0.13,0.917099,-1.2753
+0.14,0.903855,-1.3734
+0.15,0.889631,-1.4715
+0.16,0.874425,-1.5696
+0.17,0.858239,-1.6677
+0.18,0.841071,-1.7658
+0.19,0.822922,-1.8639
+0.2,0.803793,-1.962
+0.21,0.783682,-2.0601
+0.22,0.762591,-2.1582
+0.23,0.740518,-2.2563
+0.24,0.717465,-2.3544
+0.25,0.69343,-2.4525
+0.26,0.668415,-2.5506
+0.27,0.642418,-2.6487
+0.28,0.615441,-2.7468
+0.29,0.587482,-2.8449
+0.3,0.558543,-2.943
+0.31,0.528622,-3.0411
+0.32,0.497721,-3.1392
+0.33,0.465838,-3.2373
+0.34,0.432975,-3.3354
+0.35,0.39913,-3.4335
+0.36,0.364305,-3.5316
+0.37,0.328498,-3.6297
+0.38,0.291711,-3.7278
+0.39,0.253942,-3.8259
+0.4,0.215193,-3.924
+0.41,0.175462,-4.0221
+0.42,0.134751,-4.1202
+0.43,0.0930583,-4.2183
+0.44,0.0503848,-4.3164
+0.45,0.00673031,-4.4145
+0.451522,-2.77001e-14,-4.42943
+0.451522,0,3.1006
+0.46,0.02593,3.01743
+0.47,0.0556132,2.91933
+0.48,0.0843156,2.82123
+0.49,0.112037,2.72313
+0.5,0.138778,2.62503
+0.51,0.164538,2.52693
+0.52,0.189316,2.42883
+0.53,0.213114,2.33073
+0.54,0.235931,2.23263
+0.55,0.257766,2.13453
+0.56,0.278621,2.03643
+0.57,0.298495,1.93833
+0.58,0.317388,1.84023
+0.59,0.335299,1.74213
+0.6,0.35223,1.64403
+0.61,0.36818,1.54593
+0.62,0.383149,1.44783
+0.63,0.397137,1.34973
+0.64,0.410143,1.25163
+0.65,0.422169,1.15353
+0.66,0.433214,1.05543
+0.67,0.443278,0.957333
+0.68,0.452361,0.859233
+0.69,0.460462,0.761133
+0.7,0.467583,0.663033
+0.71,0.473723,0.564933
+0.72,0.478882,0.466833
+0.73,0.48306,0.368733
+0.74,0.486257,0.270633
+0.75,0.488472,0.172533
+0.76,0.489707,0.0744327
+0.77,0.489961,-0.0236673
+0.78,0.489234,-0.121767
+0.79,0.487526,-0.219867
+0.8,0.484837,-0.317967
+0.81,0.481166,-0.416067
+0.82,0.476515,-0.514167
+0.83,0.470883,-0.612267
+0.84,0.46427,-0.710367
+0.85,0.456676,-0.808467
+0.86,0.4481,-0.906567
+0.87,0.438544,-1.00467
+0.88,0.428007,-1.10277
+0.89,0.416489,-1.20087
+0.9,0.40399,-1.29897
+0.91,0.39051,-1.39707
+0.92,0.376048,-1.49517
+0.93,0.360606,-1.59327
+0.94,0.344183,-1.69137
+0.95,0.326779,-1.78947
+0.96,0.308394,-1.88757
+0.97,0.289028,-1.98567
+0.98,0.26868,-2.08377
+0.99,0.247352,-2.18187
+1,0.225043,-2.27997
+1.01,0.201753,-2.37807
+1.02,0.177482,-2.47617
+1.03,0.15223,-2.57427
+1.04,0.125996,-2.67237
+1.05,0.0987822,-2.77047
+1.06,0.070587,-2.86857
+1.07,0.0414109,-2.96667
+1.08,0.0112537,-3.06477
+1.08365,-3.98709e-14,-3.10058
+1.08365,0,2.17041
+1.09,0.0135736,2.10812
+1.1,0.0341631,2.01002
+1.11,0.0537723,1.91192
+1.12,0.0724007,1.81382
+1.13,0.0900481,1.71572
+1.14,0.106715,1.61762
+1.15,0.1224,1.51952
+1.16,0.137105,1.42142
+1.17,0.150828,1.32332
+1.18,0.163571,1.22522
+1.19,0.175332,1.12712
+1.2,0.186113,1.02902
+1.21,0.195913,0.930919
+1.22,0.204731,0.832819
+1.23,0.212569,0.734719
+1.24,0.219426,0.636619
+1.25,0.225301,0.538519
+1.26,0.230196,0.440419
+1.27,0.23411,0.342319
+1.28,0.237042,0.244219
+1.29,0.238994,0.146119
+1.3,0.239965,0.0480186
+1.31,0.239954,-0.0500814
+1.32,0.238963,-0.148181
+1.33,0.236991,-0.246281
+1.34,0.234037,-0.344381
+1.35,0.230103,-0.442481
+1.36,0.225188,-0.540581
+1.37,0.219291,-0.638681
+1.38,0.212414,-0.736781
+1.39,0.204556,-0.834881
+1.4,0.195716,-0.932981
+1.41,0.185896,-1.03108
+1.42,0.175095,-1.12918
+1.43,0.163312,-1.22728
+1.44,0.150549,-1.32538
+1.45,0.136805,-1.42348
+1.46,0.122079,-1.52158
+1.47,0.106373,-1.61968
+1.48,0.0896859,-1.71778
+1.49,0.0720175,-1.81588
+1.5,0.0533682,-1.91398
+1.51,0.0337379,-2.01208
+1.52,0.0131266,-2.11018
+1.52613,-5.55112e-17,-2.17035
+1.52613,0,1.51924
+1.53,0.00578613,1.48131
+1.54,0.0201043,1.38321
+1.55,0.0334453,1.28511
+1.56,0.0458055,1.18701
+1.57,0.0571848,1.08891
+1.58,0.0675832,0.99081
+1.59,0.0770007,0.89271
+1.6,0.0854371,0.79461
+1.61,0.0928925,0.69651
+1.62,0.099367,0.59841
+1.63,0.104861,0.50031
+1.64,0.109373,0.40221
+1.65,0.112905,0.30411
+1.66,0.115455,0.20601
+1.67,0.117025,0.10791
+1.68,0.117613,0.00980987
+1.69,0.117221,-0.0882901
+1.7,0.115847,-0.18639
+1.71,0.113493,-0.28449
+1.72,0.110158,-0.38259
+1.73,0.105841,-0.48069
+1.74,0.100544,-0.57879
+1.75,0.0942653,-0.67689
+1.76,0.0870059,-0.77499
+1.77,0.0787655,-0.87309
+1.78,0.0695441,-0.97119
+1.79,0.0593417,-1.06929
+1.8,0.0481583,-1.16739
+1.81,0.0359939,-1.26549
+1.82,0.0228485,-1.36359
+1.83,0.00872209,-1.46169
+1.83585,-3.26544e-14,-1.5191
+1.83585,0,1.06337
+1.84,0.00431854,1.02268
+1.85,0.0140533,0.92458
+1.86,0.0228081,0.82648
+1.87,0.030582,0.72838
+1.88,0.037375,0.63028
+1.89,0.0431871,0.53218
+1.9,0.0480184,0.43408
+1.91,0.0518685,0.33598
+1.92,0.0547376,0.23788
+1.93,0.0566258,0.13978
+1.94,0.0575331,0.0416802
+1.95,0.0574593,-0.0564198
+1.96,0.0564046,-0.15452
+1.97,0.0543688,-0.25262
+1.98,0.051352,-0.35072
+1.99,0.0473543,-0.44882
+2,0.0423756,-0.54692
+2.01,0.0364159,-0.64502
+2.02,0.0294752,-0.74312
+2.03,0.0215534,-0.84122
+2.04,0.0126507,-0.93932
+2.05,0.00276702,-1.03742
+2.05263,-2.8191e-14,-1.06326
+2.05263,0,0.744284
+2.06,0.00520533,0.672028
+2.07,0.0114341,0.573928
+2.08,0.0166824,0.475828
+2.09,0.0209499,0.377728
+2.1,0.0242364,0.279628
+2.11,0.026542,0.181528
+2.12,0.0278666,0.0834277
+2.13,0.0282102,-0.0146723
+2.14,0.0275729,-0.112772
+2.15,0.0259546,-0.210872
+2.16,0.0233553,-0.308972
+2.17,0.019775,-0.407072
+2.18,0.0152137,-0.505172
+2.19,0.00967144,-0.603272
+2.2,0.00314817,-0.701372
+2.20436,-1.36471e-14,-0.744103
+2.20436,0,0.520872
+2.21,0.00277078,0.465504
+2.22,0.00693402,0.367404
+2.23,0.0101171,0.269304
+2.24,0.0123193,0.171204
+2.25,0.0135406,0.0731036
+2.26,0.0137809,-0.0249964
+2.27,0.0130404,-0.123096
+2.28,0.0113187,-0.221196
+2.29,0.00861613,-0.319296
+2.3,0.00493259,-0.417396
+2.31,0.000268093,-0.515496
+2.31052,-1.03597e-14,-0.520573
+2.31052,0,0.364401
+2.32,0.00300073,0.271378
+2.33,0.00522321,0.173278
+2.34,0.00646508,0.0751782
+2.35,0.00672614,-0.0229218
+2.36,0.00600616,-0.121022
+2.37,0.00430532,-0.219122
+2.38,0.00162345,-0.317222
+2.38477,-1.00198e-14,-0.363978
+2.38477,0,0.254785
+2.39,0.00118645,0.203441
+2.4,0.00272884,0.105341
+2.41,0.00329125,0.00724129
+2.42,0.00287287,-0.0908587
+2.43,0.00147351,-0.188959
+2.43665,-9.1073e-18,-0.254191
+2.43665,0,0.177933
+2.44,0.000530079,0.145065
+2.45,0.00148718,0.0469654
+2.46,0.00146579,-0.0511346
+2.47,0.000463562,-0.149235
+2.47284,-4.82164e-15,-0.177103
+2.47284,0,0.123972
+2.48,0.000622747,0.0537409
+2.49,0.000668661,-0.0443591
+2.498,-1.47451e-17,-0.122801
+2.498,0,0
+2.5,0,0
+2.51,0,0
+2.52,0,0
+2.53,0,0
+2.54,0,0
+2.55,0,0
+2.56,0,0
+2.57,0,0
+2.58,0,0
+2.59,0,0
+2.6,0,0
+2.61,0,0
+2.62,0,0
+2.63,0,0
+2.64,0,0
+2.65,0,0
+2.66,0,0
+2.67,0,0
+2.68,0,0
+2.69,0,0
+2.7,0,0
+2.71,0,0
+2.72,0,0
+2.73,0,0
+2.74,0,0
+2.75,0,0
+2.76,0,0
+2.77,0,0
+2.78,0,0
+2.79,0,0
+2.8,0,0
+2.81,0,0
+2.82,0,0
+2.83,0,0
+2.84,0,0
+2.85,0,0
+2.86,0,0
+2.87,0,0
+2.88,0,0
+2.89,0,0
+2.9,0,0
+2.91,0,0
+2.92,0,0
+2.93,0,0
+2.94,0,0
+2.95,0,0
+2.96,0,0
+2.97,0,0
+2.98,0,0
+2.99,0,0
+3,0,0
+3.01,0,0
diff --git a/tests/test_fmu/test_bouncing_ball/test_bouncing_ball.py b/tests/test_fmu/test_bouncing_ball/test_bouncing_ball.py
new file mode 100644
index 00000000..be3e3026
--- /dev/null
+++ b/tests/test_fmu/test_bouncing_ball/test_bouncing_ball.py
@@ -0,0 +1,34 @@
+import numpy as np
+import pandas as pd
+import pytest
+
+from numerous.engine.model import Model
+from numerous.engine.simulation import Simulation
+from numerous.engine.simulation.solvers import solver_types
+from numerous.engine.system import Subsystem
+from numerous.engine.system.fmu_subsystem import FMU_Subsystem
+
+
+class S3(Subsystem):
+    def __init__(self, tag):
+        super().__init__(tag)
+
+        fmu_filename = 'BouncingBall.fmu'
+        fmu_subsystem = FMU_Subsystem(fmu_filename, "BouncingBall", debug_output=True)
+        self.register_items([fmu_subsystem])
+
+
+@pytest.mark.xfail
+@pytest.mark.parametrize("solver", solver_types)
+@pytest.mark.parametrize("use_llvm", [True, False])
+def test_bouncing_ball(solver, use_llvm):
+    subsystem1 = S3('q1')
+    m1 = Model(subsystem1, use_llvm=use_llvm)
+    s = Simulation(
+        m1, t_start=0, t_stop=20, num=200, num_inner=1000, max_step=.1, solver_type=solver)
+    s.solve()
+    h = np.array(m1.historian_df["q1.BouncingBall.t1.h"])
+    v = np.array(m1.historian_df["q1.BouncingBall.t1.v"])
+    df = pd.read_csv("BouncingBall_ref.csv")
+    assert np.allclose(h, df['h'], rtol=1e-1)
+    assert np.allclose(v, df['v'], rtol=1e-1)
diff --git a/tests/test_fmu/test_feedthrough/Feedthrough.fmu b/tests/test_fmu/test_feedthrough/Feedthrough.fmu
new file mode 100644
index 00000000..248d2f42
Binary files /dev/null and b/tests/test_fmu/test_feedthrough/Feedthrough.fmu differ
diff --git a/tests/test_fmu/test_feedthrough/Feedthrough_in.csv b/tests/test_fmu/test_feedthrough/Feedthrough_in.csv
new file mode 100644
index 00000000..79315e29
--- /dev/null
+++ b/tests/test_fmu/test_feedthrough/Feedthrough_in.csv
@@ -0,0 +1,6 @@
+time,real_continuous_in,real_discrete_in,int_in,bool_in
+0.0,0.0,0,0,0
+0.5,0.0,0,0,0
+1.0,1.0,1,1,1
+1.5,1.0,1,1,1
+2.0,1.0,1,1,1
diff --git a/tests/test_fmu/test_feedthrough/Feedthrough_ref.csv b/tests/test_fmu/test_feedthrough/Feedthrough_ref.csv
new file mode 100644
index 00000000..f15d33a2
--- /dev/null
+++ b/tests/test_fmu/test_feedthrough/Feedthrough_ref.csv
@@ -0,0 +1,17 @@
+"time","real_continuous_out","real_discrete_out","int_out","bool_out"
+0,1,0,0,0
+0.2,1,0,0,0
+0.4,1,0,0,0
+0.5,1,0,0,0
+0.5,3,0,0,0
+0.6,2.8,0,0,0
+0.8,2.4,0,0,0
+1,2,0,0,0
+1,2,1,1,1
+1.2,2,1,1,1
+1.4,2,1,1,1
+1.5,2,1,1,1
+1.5,1,1,1,1
+1.6,1,1,1,1
+1.8,1,1,1,1
+2,1,1,1,1
diff --git a/tests/test_fmu/test_feedthrough/Feedthrough_ref.opt b/tests/test_fmu/test_feedthrough/Feedthrough_ref.opt
new file mode 100644
index 00000000..d5ed5eee
--- /dev/null
+++ b/tests/test_fmu/test_feedthrough/Feedthrough_ref.opt
@@ -0,0 +1,4 @@
+StartTime, 0
+StopTime, 2
+StepSize, 0
+RelTol, 1e-5
diff --git a/tests/test_fmu/test_feedthrough/test_feedthrough.py b/tests/test_fmu/test_feedthrough/test_feedthrough.py
new file mode 100644
index 00000000..63838862
--- /dev/null
+++ b/tests/test_fmu/test_feedthrough/test_feedthrough.py
@@ -0,0 +1,34 @@
+import os
+
+import numpy as np
+import pandas as pd
+import pytest
+
+from numerous.engine.model import Model
+from numerous.engine.simulation import Simulation
+from numerous.engine.system import Subsystem
+from numerous.engine.system.fmu_subsystem import FMU_Subsystem
+
+
+class S3(Subsystem):
+    def __init__(self, tag):
+        super().__init__(tag)
+
+        fmu_filename = os.path.join(os.path.dirname(__file__), 'Feedthrough.fmu')
+        fmu_in = os.path.join(os.path.dirname(__file__), 'Feedthrough_in.csv')
+        fmu_subsystem = FMU_Subsystem(fmu_filename, "Feedthrough", fmu_in=fmu_in, debug_output=False)
+        self.register_items([fmu_subsystem])
+
+
+@pytest.mark.xfail
+@pytest.mark.parametrize("use_llvm", [True, False])
+def test_feedthrough(use_llvm):
+    subsystem = S3('q1')
+
+    m1 = Model(subsystem, use_llvm=use_llvm)
+    s = Simulation(
+        m1, t_start=0, t_stop=2, num=10, num_inner=1000, max_event_steps=.1)
+    s.solve()
+    df = s.model.historian_df
+    assert np.allclose(df['q1.Feedthrough.t1.real_discrete_in'], np.array([0.0] * 5 + [1.0] * 6), rtol=1e-4)
+    assert np.allclose(df['q1.Feedthrough.t1.real_continuous_in'], np.array([0.0] * 3 + [0.2] + [0.6] + [1.0] * 6), rtol=1e-4)
diff --git a/tests/test_fmu/test_resource/Resource.fmu b/tests/test_fmu/test_resource/Resource.fmu
new file mode 100644
index 00000000..3f8a2379
Binary files /dev/null and b/tests/test_fmu/test_resource/Resource.fmu differ
diff --git a/tests/test_fmu/test_resource/Resource_ref.csv b/tests/test_fmu/test_resource/Resource_ref.csv
new file mode 100644
index 00000000..1ee31cdb
--- /dev/null
+++ b/tests/test_fmu/test_resource/Resource_ref.csv
@@ -0,0 +1,7 @@
+"time","y"
+0,97
+0.2,97
+0.4,97
+0.6,97
+0.8,97
+1,97
diff --git a/tests/test_fmu/test_resource/test_resource.py b/tests/test_fmu/test_resource/test_resource.py
new file mode 100644
index 00000000..1302fec7
--- /dev/null
+++ b/tests/test_fmu/test_resource/test_resource.py
@@ -0,0 +1,32 @@
+import numpy as np
+import pandas as pd
+import pytest
+
+from numerous.engine.model import Model
+from numerous.engine.simulation import Simulation
+from numerous.engine.simulation.solvers import solver_types
+from numerous.engine.system import Subsystem
+from numerous.engine.system.fmu_subsystem import FMU_Subsystem
+
+
+class S3(Subsystem):
+    def __init__(self, tag):
+        super().__init__(tag)
+
+        fmu_filename = 'Resource.fmu'
+        fmu_subsystem = FMU_Subsystem(fmu_filename, "Resource", debug_output=True)
+        self.register_items([fmu_subsystem])
+
+
+@pytest.mark.xfail
+@pytest.mark.parametrize("solver", solver_types)
+@pytest.mark.parametrize("use_llvm", [True, False])
+def test_resource(solver, use_llvm):
+    subsystem1 = S3('q1')
+    m1 = Model(subsystem1, use_llvm=use_llvm)
+    s = Simulation(
+        m1, t_start=0, t_stop=1, num=6, num_inner=1000, max_step=.1, solver_type=solver)
+    s.solve()
+    h = np.array(m1.historian_df["q1.Resource.t1.y"])
+    df = pd.read_csv("Resource_ref.csv")
+    assert np.allclose(h, df['h'], rtol=1e-1)
diff --git a/tests/test_fmu/test_vanderpol/VanDerPol.fmu b/tests/test_fmu/test_vanderpol/VanDerPol.fmu
new file mode 100644
index 00000000..f01a141d
Binary files /dev/null and b/tests/test_fmu/test_vanderpol/VanDerPol.fmu differ
diff --git a/tests/test_fmu/test_vanderpol/VanDerPol_ref.csv b/tests/test_fmu/test_vanderpol/VanDerPol_ref.csv
new file mode 100644
index 00000000..ff7f7e58
--- /dev/null
+++ b/tests/test_fmu/test_vanderpol/VanDerPol_ref.csv
@@ -0,0 +1,202 @@
+"time","x0","x1"
+0,2,0
+0.1,1.99095,-0.17272
+0.2,1.96695,-0.300738
+0.3,1.93182,-0.397361
+0.4,1.88816,-0.472789
+0.5,1.8377,-0.534452
+0.6,1.78153,-0.587677
+0.7,1.7203,-0.636305
+0.8,1.65432,-0.683178
+0.9,1.58364,-0.730513
+1,1.50813,-0.78017
+1.1,1.42748,-0.833837
+1.2,1.34118,-0.893187
+1.3,1.24859,-0.960008
+1.4,1.14886,-1.0363
+1.5,1.04092,-1.12433
+1.6,0.923491,-1.22665
+1.7,0.794997,-1.34607
+1.8,0.653594,-1.48549
+1.9,0.497145,-1.64747
+2,0.323332,-1.83278
+2.1,0.129925,-2.03845
+2.2,-0.084689,-2.25462
+2.3,-0.320607,-2.45981
+2.4,-0.575053,-2.61731
+2.5,-0.840844,-2.67766
+2.6,-1.10575,-2.59267
+2.7,-1.35382,-2.34103
+2.8,-1.56925,-1.94895
+2.9,-1.74115,-1.48353
+3,-1.86608,-1.02119
+3.1,-1.94732,-0.616063
+3.2,-1.99196,-0.290304
+3.3,-2.00798,-0.0420389
+3.4,-2.00251,0.142124
+3.5,-1.98116,0.278079
+3.6,-1.94803,0.379879
+3.7,-1.90596,0.458493
+3.8,-1.85686,0.521962
+3.9,-1.80191,0.576002
+4,-1.74185,0.624755
+4.1,-1.67705,0.6712
+4.2,-1.60762,0.717633
+4.3,-1.53347,0.765958
+4.4,-1.45431,0.817867
+4.5,-1.36972,0.875007
+4.6,-1.27908,0.939097
+4.7,-1.18161,1.01204
+4.8,-1.0763,1.096
+4.9,-0.961954,1.19344
+5,-0.83707,1.30715
+5.1,-0.699882,1.44005
+5.2,-0.548334,1.59477
+5.3,-0.380156,1.77284
+5.4,-0.19304,1.97286
+5.5,0.0148975,2.18746
+5.6,0.244348,2.39927
+5.7,0.493582,2.5766
+5.8,0.75695,2.67315
+5.9,1.02379,2.63788
+6,1.27901,2.43813
+6.1,1.50624,2.08412
+6.2,1.69259,1.6328
+6.3,1.83217,1.16183
+6.4,1.92644,0.734745
+6.5,1.98166,0.383436
+6.6,2.00579,0.112064
+6.7,2.00637,-0.090404
+6.8,1.98948,-0.239785
+6.9,1.95968,-0.35094
+7,1.92018,-0.435819
+7.1,1.87313,-0.503293
+7.2,1.81991,-0.559766
+7.3,1.7614,-0.609778
+7.4,1.69806,-0.656633
+7.5,1.6301,-0.702823
+7.6,1.55746,-0.750346
+7.7,1.47993,-0.800937
+7.8,1.39711,-0.856242
+7.9,1.30846,-0.917946
+8,1.21325,-0.987872
+8.1,1.11055,-1.06809
+8.2,0.999208,-1.16097
+8.3,0.877838,-1.26922
+8.4,0.744757,-1.39573
+8.5,0.597994,-1.54331
+8.6,0.435334,-1.71396
+8.7,0.254458,-1.90744
+8.8,0.0532638,-2.11876
+8.9,-0.169431,-2.33415
+9,-0.412812,-2.5268
+9.1,-0.672635,-2.6546
+9.2,-0.939849,-2.66596
+9.3,-1.20054,-2.51948
+9.4,-1.43827,-2.20995
+9.5,-1.63852,-1.78122
+9.6,-1.79303,-1.30849
+9.7,-1.90113,-0.862718
+9.8,-1.9679,-0.48598
+9.9,-2.00104,-0.189975
+10,-2.00835,0.0327633
+10.1,-1.99643,0.197292
+10.2,-1.97032,0.319131
+10.3,-1.9336,0.411224
+10.4,-1.88873,0.483397
+10.5,-1.83734,0.542787
+10.6,-1.78042,0.594437
+10.7,-1.71858,0.641968
+10.8,-1.65207,0.688101
+10.9,-1.58093,0.734988
+11,-1.50499,0.784428
+11.1,-1.4239,0.838063
+11.2,-1.33718,0.897537
+11.3,-1.24413,0.964622
+11.4,-1.14391,1.04132
+11.5,-1.03546,1.12988
+11.6,-0.917445,1.23291
+11.7,-0.788293,1.35327
+11.8,-0.646127,1.49381
+11.9,-0.488787,1.657
+12,-0.313958,1.84348
+12.1,-0.119434,2.05004
+12.2,0.0963495,2.26624
+12.3,0.33337,2.46992
+12.4,0.588661,2.62368
+12.5,0.854792,2.67764
+12.6,1.11929,2.58431
+12.7,1.36609,2.32415
+12.8,1.5795,1.92611
+12.9,1.74899,1.45891
+13,1.87153,0.998367
+13.1,1.95067,0.597036
+13.2,1.99362,0.275481
+13.3,2.00835,0.0309306
+13.4,2.00192,-0.150329
+13.5,1.97986,-0.284169
+13.6,1.9462,-0.384501
+13.7,1.90372,-0.46213
+13.8,1.85429,-0.524963
+13.9,1.79906,-0.578616
+14,1.73875,-0.62717
+14.1,1.67371,-0.673552
+14.2,1.60404,-0.720034
+14.3,1.52964,-0.768496
+14.4,1.45023,-0.820624
+14.5,1.36534,-0.878077
+14.6,1.27437,-0.942581
+14.7,1.17652,-1.01604
+14.8,1.07079,-1.10062
+14.9,0.955942,-1.1988
+15,0.830481,-1.31338
+15.1,0.692624,-1.44728
+15.2,0.540298,-1.60316
+15.3,0.371221,-1.78241
+15.4,0.183107,-1.98334
+15.5,-0.0258924,-2.19829
+15.6,-0.256386,-2.40926
+15.7,-0.506499,-2.58385
+15.8,-0.770365,-2.67495
+15.9,-1.03702,-2.63197
+16,-1.29123,-2.42373
+16.1,-1.51666,-2.06298
+16.2,-1.7007,-1.60896
+16.3,-1.83793,-1.13907
+16.4,-1.93009,-0.715353
+16.5,-1.98358,-0.368069
+16.6,-2.00638,-0.100418
+16.7,-2.00594,0.0990613
+16.8,-1.98831,0.246208
+16.9,-1.95795,0.355781
+17,-1.91801,0.439582
+17.1,-1.87061,0.506375
+17.2,-1.81712,0.562424
+17.3,-1.75836,0.612193
+17.4,-1.6948,0.658976
+17.5,-1.6266,0.70521
+17.6,-1.55372,0.752861
+17.7,-1.47592,0.803654
+17.8,-1.39283,0.859235
+17.9,-1.30386,0.921296
+18,-1.20829,0.991683
+18.1,-1.10517,1.07248
+18.2,-0.993359,1.16609
+18.3,-0.871432,1.27517
+18.4,-0.737704,1.40265
+18.5,-0.590197,1.55132
+18.6,-0.426675,1.72316
+18.7,-0.244815,1.91773
+18.8,-0.0425616,2.12963
+18.9,0.181195,2.34463
+19,0.425527,2.53522
+19.1,0.685983,2.6584
+19.2,0.953244,2.66265
+19.3,1.21319,2.50771
+19.4,1.44935,2.19071
+19.5,1.64745,1.75798
+19.6,1.7996,1.28509
+19.7,1.90548,0.841929
+19.8,1.97037,0.469058
+19.9,2.00201,0.177006
+20,2.00821,-0.0424046
diff --git a/tests/test_fmu/test_vanderpol/test_vanderpol.py b/tests/test_fmu/test_vanderpol/test_vanderpol.py
new file mode 100644
index 00000000..f7cb5977
--- /dev/null
+++ b/tests/test_fmu/test_vanderpol/test_vanderpol.py
@@ -0,0 +1,34 @@
+import numpy as np
+import pandas as pd
+import pytest
+
+from numerous.engine.model import Model
+from numerous.engine.simulation import Simulation
+from numerous.engine.simulation.solvers import solver_types
+from numerous.engine.system import Subsystem
+from numerous.engine.system.fmu_subsystem import FMU_Subsystem
+
+
+class S3(Subsystem):
+    def __init__(self, tag):
+        super().__init__(tag)
+
+        fmu_filename = 'VanDerPol.fmu'
+        fmu_subsystem = FMU_Subsystem(fmu_filename, "VanDerPol", debug_output=True)
+        self.register_items([fmu_subsystem])
+
+
+@pytest.mark.xfail
+@pytest.mark.parametrize("solver", solver_types)
+@pytest.mark.parametrize("use_llvm", [True, False])
+def test_vanderpol(solver, use_llvm):
+    subsystem1 = S3('q1')
+    m1 = Model(subsystem1, use_llvm=use_llvm)
+    s = Simulation(
+        m1, t_start=0, t_stop=20, num=200, num_inner=1000, max_step=.1, solver_type=solver)
+    s.solve()
+    x0 = np.array(m1.historian_df["q1.VanDerPol.t1.x0"])
+    x1 = np.array(m1.historian_df["q1.VanDerPol.t1.x1"])
+    df = pd.read_csv("VanDerPol_ref.csv")
+    assert np.allclose(x0, df['x0'], rtol=1e-1)
+    assert np.allclose(x1, df['x1'], rtol=1e-1)
diff --git a/tests/test_imports/test_external_mapping_with_function/external_data_mapping.py b/tests/test_imports/test_external_mapping_with_function/external_data_mapping.py
index d2183537..54be2852 100644
--- a/tests/test_imports/test_external_mapping_with_function/external_data_mapping.py
+++ b/tests/test_imports/test_external_mapping_with_function/external_data_mapping.py
@@ -27,12 +27,11 @@ def __init__(self, tag):
         self.register_items([DynamicDataTest('tm1')])
 
 
-@pytest.mark.parametrize("solver", solver_types)
 @pytest.mark.parametrize("use_llvm", [True, False])
-def test_external_mapping(solver, use_llvm):
+def test_external_mapping(use_llvm):
     model_ = model.Model(DynamicDataSystem('system'), imports=[("external_data_functions", "h_test")],
                          use_llvm=use_llvm)
-    s = simulation.Simulation(model_, solver_type=solver, t_start=0, t_stop=10.0, num=10, num_inner=10)
+    s = simulation.Simulation(model_, t_start=0, t_stop=10.0, num=10, num_inner=10)
     s.solve()
     expected = 106.0
     assert s.model.historian_df['system.tm1.test_nm.T_i1'][1] == expected
diff --git a/tests/test_inheritence_with_equations.py b/tests/test_inheritence_with_equations.py
index ff05597e..5bfeb7a1 100644
--- a/tests/test_inheritence_with_equations.py
+++ b/tests/test_inheritence_with_equations.py
@@ -68,26 +68,25 @@ def __init__(self, tag):
     return S2('S3_inh')
 
 
-
-@pytest.mark.parametrize("solver", solver_types)
 @pytest.mark.parametrize("use_llvm", [True, False])
-def test_single_equation_multiple_variables(ms1, solver,use_llvm):
+def test_single_equation_multiple_variables(ms1, use_llvm):
     m1 = Model(ms1, use_llvm=use_llvm)
-    s1 = Simulation(m1, t_start=0, t_stop=1000, num=100, solver_type=solver)
+    s1 = Simulation(m1, t_start=0, t_stop=1000, num=100)
     s1.solve()
     assert m1.historian_df["S1_inh.test1.t1.P"][100] == 5
     assert m1.historian_df["S1_inh.test1.t1.T"][100] == 50
 
-@pytest.mark.parametrize("solver", solver_types)
+
 @pytest.mark.skip(reason="Functionality not implemented in current version")
-def test_equation_inheritence_2(ms2, solver):
+def test_equation_inheritance_2(ms2):
     m1 = Model(ms2)
-    s1 = Simulation(m1, t_start=0, t_stop=1000, num=100, solver_type=solver)
+    s1 = Simulation(m1, t_start=0, t_stop=1000, num=100)
     s1.solve()
     assert m1.historian_df["S2_inh.test1.t1.P"][100] == 5
     assert m1.historian_df["S2_inh.test1.t1.L"][100] == 7
 
+
 @pytest.mark.skip(reason="Functionality not implemented in current version")
-def test_equation_inheritence_3(ms3):
+def test_equation_inheritance_3(ms3):
     m1 = Model(ms3)
     assert len(m1.compiled_eq) == 1
\ No newline at end of file
diff --git a/tests/test_logger_levels/test_logger_levels_all.py b/tests/test_logger_levels/test_logger_levels_all.py
index 2dd9130f..1495f345 100644
--- a/tests/test_logger_levels/test_logger_levels_all.py
+++ b/tests/test_logger_levels/test_logger_levels_all.py
@@ -6,7 +6,6 @@
 from numerous.multiphysics.equation_decorators import Equation
 from numerous.engine.system.item import Item
 from numerous.engine.system.subsystem import Subsystem
-from numerous.engine.simulation.solvers.base_solver import solver_types
 import numpy as np
 
 INFO = LoggerLevel.INFO
@@ -49,17 +48,15 @@ def sigmoidlike(t):
     return 1 / (1 + np.exp(2 * t))
 
 
-@pytest.mark.parametrize("solver", solver_types)
 @pytest.mark.parametrize("use_llvm", [True, False])
-def test_logger_levels(solver, use_llvm):
+def test_logger_levels(use_llvm):
     num = 100
     t_stop = 100
     t_start = 0
     sys = TestLogSubsystem1()
     model = Model(sys, logger_level=ALL, use_llvm=use_llvm)
     tvec = np.linspace(t_start, t_stop, num + 1, dtype=np.float64)
-    sim = Simulation(model, t_start=t_start, t_stop=t_stop, num=num, num_inner=1, solver_type=solver,
-                     rtol=1e-8, atol=1e-8)
+    sim = Simulation(model, t_start=t_start, t_stop=t_stop, num=num, num_inner=1, rtol=1e-8, atol=1e-8)
     sim.solve()
 
     df = sim.model.historian_df
diff --git a/tests/test_logger_levels/test_logger_levels_debug.py b/tests/test_logger_levels/test_logger_levels_debug.py
index ff47938c..47930265 100644
--- a/tests/test_logger_levels/test_logger_levels_debug.py
+++ b/tests/test_logger_levels/test_logger_levels_debug.py
@@ -6,7 +6,6 @@
 from numerous.multiphysics.equation_decorators import Equation
 from numerous.engine.system.item import Item
 from numerous.engine.system.subsystem import Subsystem
-from numerous.engine.simulation.solvers.base_solver import solver_types
 import numpy as np
 import copy
 
@@ -50,17 +49,15 @@ def sigmoidlike(t):
     return 1 / (1 + np.exp(2 * t))
 
 
-@pytest.mark.parametrize("solver", solver_types)
 @pytest.mark.parametrize("use_llvm", [True, False])
-def test_logger_levels(solver, use_llvm):
+def test_logger_levels(use_llvm):
     num = 100
     t_stop = 100
     t_start = 0
     sys = TestLogSubsystem1()
     model = Model(sys, logger_level=DEBUG, use_llvm=use_llvm)
     tvec = np.linspace(t_start, t_stop, num + 1, dtype=np.float64)
-    sim = Simulation(model, t_start=t_start, t_stop=t_stop, num=num, num_inner=1, solver_type=solver,
-                     rtol=1e-8, atol=1e-8)
+    sim = Simulation(model, t_start=t_start, t_stop=t_stop, num=num, num_inner=1, rtol=1e-8, atol=1e-8)
     sim.solve()
 
     df = sim.model.historian_df
diff --git a/tests/test_logger_levels/test_logger_levels_info.py b/tests/test_logger_levels/test_logger_levels_info.py
index d63d1880..0ac4f4e7 100644
--- a/tests/test_logger_levels/test_logger_levels_info.py
+++ b/tests/test_logger_levels/test_logger_levels_info.py
@@ -6,7 +6,6 @@
 from numerous.multiphysics.equation_decorators import Equation
 from numerous.engine.system.item import Item
 from numerous.engine.system.subsystem import Subsystem
-from numerous.engine.simulation.solvers.base_solver import solver_types
 import numpy as np
 
 INFO = LoggerLevel.INFO
@@ -49,17 +48,15 @@ def sigmoidlike(t):
     return 1 / (1 + np.exp(2 * t))
 
 
-@pytest.mark.parametrize("solver", solver_types)
 @pytest.mark.parametrize("use_llvm", [True, False])
-def test_logger_levels(solver, use_llvm):
+def test_logger_levels(use_llvm):
     num = 100
     t_stop = 100
     t_start = 0
     sys = TestLogSubsystem1()
     model = Model(sys, logger_level=INFO, use_llvm=use_llvm)
     tvec = np.linspace(t_start, t_stop, num + 1, dtype=np.float64)
-    sim = Simulation(model, t_start=t_start, t_stop=t_stop, num=num, num_inner=1, solver_type=solver,
-                     rtol=1e-6, atol=1e-6)
+    sim = Simulation(model, t_start=t_start, t_stop=t_stop, num=num, num_inner=1, rtol=1e-6, atol=1e-6)
     sim.solve()
 
     df = sim.model.historian_df
diff --git a/tests/test_model.py b/tests/test_model.py
index e036d28e..ba66f759 100644
--- a/tests/test_model.py
+++ b/tests/test_model.py
@@ -1,21 +1,16 @@
 import pytest
-from numerous.engine.model.external_mappings import ExternalMappingElement
+import numpy as np
 
-from numerous.utils.data_loader import InMemoryDataLoader
 from pytest import approx
 
-from numerous.engine.model.external_mappings.interpolation_type import InterpolationType
 from numerous.engine.model import Model
 from numerous.engine.simulation import Simulation
 
-from numerous.engine.system import Subsystem, ConnectorItem, Item, ConnectorTwoWay
-from numerous.engine.simulation.solvers.base_solver import solver_types
+from numerous.engine.system import Subsystem, ConnectorItem, Item, ConnectorTwoWay, LoggerLevel
 from numerous.multiphysics import EquationBase, Equation
 from tests.test_equations import TestEq_ground, Test_Eq, TestEq_input
 
-
-
-
+TOL = 1e-6
 @pytest.fixture
 def test_eq1():
     class TestEq1(EquationBase):
@@ -133,6 +128,7 @@ def __init__(self, tag, P, T, R):
 
             t1 = self.create_namespace('t1')
 
+
             t1.add_equations([TestEq_input(P=P, T=T, R=R)])
             ##this line has to be after t1.add_equations since t1 inside output is created there
             self.output.t1.create_variable(name='T')
@@ -156,7 +152,7 @@ def __init__(self, tag, TG, RG):
             super().__init__(tag)
 
             t1 = self.create_namespace('t1')
-            t1.add_equations([TestEq_ground(TG=TG, RG=RG)])
+            t1.add_equations([TestEq_ground(TG=TG, RG=RG, tol=TOL)])
 
             # ##since we asked for variable T in binding we have to create variable T and map it to TG
             # t1.create_variable('T')
@@ -185,28 +181,25 @@ def __init__(self, tag):
     return S3('S3')
 
 
-@pytest.mark.parametrize("solver", solver_types)
 @pytest.mark.parametrize("use_llvm", [True, False])
-def test_model_var_referencing(ms1, solver, use_llvm):
+def test_model_var_referencing(ms1, use_llvm):
     m1 = Model(ms1, use_llvm=use_llvm)
-    s1 = Simulation(m1, t_start=0, t_stop=1000, num=10, solver_type=solver)
+    s1 = Simulation(m1, t_start=0, t_stop=1000, num=10)
     s1.solve()
-    assert approx(list(m1.states_as_vector[::-1]), rel=0.01) == [2010, 1010, 510, 210]
+    assert approx(list(m1.states_as_vector), rel=0.01) == [2010, 1010, 510, 210]
 
 
-@pytest.mark.parametrize("solver", solver_types)
 @pytest.mark.skip(reason="Functionality not implemented in current version")
-def test_model_save_only_aliases(ms3, solver):
+def test_model_save_only_aliases(ms3):
     of = OutputFilter(only_aliases=True)
     m1 = Model(ms3, historian_filter=of)
-    s1 = Simulation(m1, t_start=0, t_stop=1000, num=10, solver_type=solver)
+    s1 = Simulation(m1, t_start=0, t_stop=1000, num=10)
     s1.solve()
     assert m1.historian_df.empty
 
 
-@pytest.mark.parametrize("solver", solver_types)
 @pytest.mark.skip(reason="Functionality not implemented in current version")
-def test_model_save_only_aliases2(ms3, solver):
+def test_model_save_only_aliases2(ms3):
     of = OutputFilter(only_aliases=True)
     m1 = Model(ms3, historian_filter=of)
     item = m1.search_items('2')[0]
@@ -215,7 +208,7 @@ def test_model_save_only_aliases2(ms3, solver):
         var[0].alias = str(i)
         columns_number += 1
 
-    s1 = Simulation(m1, t_start=0, t_stop=1000, num=10, solver_type=solver)
+    s1 = Simulation(m1, t_start=0, t_stop=1000, num=10)
     s1.solve()
     assert m1.historian_df.columns.size == columns_number
 
@@ -226,28 +219,27 @@ def test_1_item_model(ms1):
     assert item.t1.P.value == 100
 
 
-@pytest.mark.parametrize("solver", solver_types)
 @pytest.mark.parametrize("use_llvm", [True, False])
-def test_callback_step_item_model(ms3, solver, use_llvm):
+def test_callback_step_item_model(ms3, use_llvm):
     def action(time, variables):
-        if int(time) == 119:
+        if abs(time - 119) < variables['S3.5.t1.tol']:
             raise ValueError("Overflow of state. time:119")
 
     def condition(time, states):
-        return 500 - states['S3.3.t1.T']
+        return 119 - time
 
     def action2(time, variables):
-        if int(time) == 118:
+        if abs(time - 118) < variables['S3.5.t1.tol']:
             raise ValueError("Overflow of state. time:119")
 
     def condition2(time, states):
-        return 500 - states['S3.3.t1.T']
+        return 118 - time
 
-    m1 = Model(ms3, use_llvm=use_llvm)
+    m1 = Model(ms3)
     m1.add_event("simple", condition, action)
     m1.add_event("simple2", condition2, action2)
-    s1 = Simulation(m1, t_start=0, t_stop=1000, num=100, solver_type=solver)
-    with pytest.raises(ValueError, match=r".*time:119.*"):
+    s1 = Simulation(m1, t_start=0, t_stop=1000, num=100, use_llvm=use_llvm, atol=TOL)
+    with pytest.raises(ValueError, match=r".*time:119*"):
         s1.solve()
 
 
@@ -260,29 +252,27 @@ def __init__(self, tag):
         ms2.register_items([Item_('1')])
 
 
-@pytest.mark.parametrize("solver", solver_types)
 @pytest.mark.parametrize("use_llvm", [True, False])
-def test_chain_item_model(ms2, solver, use_llvm):
+def test_chain_item_model(ms2, use_llvm):
     m1 = Model(ms2, use_llvm=use_llvm)
-    s1 = Simulation(m1, t_start=0, t_stop=1000, num=10, solver_type=solver)
+    s1 = Simulation(m1, t_start=0, t_stop=1000, num=10)
     s1.solve()
     assert approx(m1.states_as_vector, rel=0.01) == [2010, 1010, 510, 210]
 
 
-@pytest.mark.parametrize("solver", solver_types)
 @pytest.mark.parametrize("use_llvm", [True, False])
-def test_chain_item_binding_model_nested(ms3, solver, use_llvm):
+def test_chain_item_binding_model_nested(ms3, use_llvm):
     ms4 = Subsystem('new_s')
     ms4.register_item(ms3)
     m1 = Model(ms4, use_llvm=use_llvm)
-    s1 = Simulation(m1, t_start=0, t_stop=1000, num=10, solver_type=solver)
+    s1 = Simulation(m1, t_start=0, t_stop=1000, num=10)
     s1.solve()
     assert approx(m1.states_as_vector, rel=0.01) == [2010, 1010, 510, 210]
 
 
-@pytest.mark.parametrize("solver", solver_types)
 @pytest.mark.parametrize("use_llvm", [True, False])
-def test_chain_item_binding_model_nested2(ms3, solver, use_llvm):
+def test_chain_item_binding_model_nested2(ms3, use_llvm):
+
     ms4 = Subsystem('new_s4')
     ms4.register_item(ms3)
     ms5 = Subsystem('new_s5')
@@ -293,18 +283,17 @@ def test_chain_item_binding_model_nested2(ms3, solver, use_llvm):
     ms7 = Subsystem('new_s7')
     ms7.register_item(ms6)
     m1 = Model(ms7, use_llvm=use_llvm)
-    s1 = Simulation(m1, t_start=0, t_stop=1000, num=100, solver_type=solver)
+    s1 = Simulation(m1, t_start=0, t_stop=1000, num=100)
     s1.solve()
-    assert len(m1.path_variables) == 50
-    assert len(m1.variables) == 25
+    assert len(m1.path_variables) == 52
+    assert len(m1.variables) == 26
     assert approx(m1.states_as_vector, rel=0.01) == [2010, 1010, 510, 210]
 
 
-@pytest.mark.parametrize("solver", solver_types)
 @pytest.mark.parametrize("use_llvm", [True, False])
-def test_chain_item_binding_model(ms3, solver, use_llvm):
+def test_chain_item_binding_model(ms3, use_llvm):
     m1 = Model(ms3, use_llvm=use_llvm)
-    s1 = Simulation(m1, t_start=0, t_stop=1000, num=100, solver_type=solver)
+    s1 = Simulation(m1, t_start=0, t_stop=1000, num=100)
     s1.solve()
     assert approx(m1.states_as_vector, rel=0.01) == [2010, 1010, 510, 210]
 
@@ -328,8 +317,8 @@ def eval(self, scope):
 
 
 class StaticDataSystem(Subsystem):
-    def __init__(self, tag, n=1):
-        super().__init__(tag)
+    def __init__(self, tag, n=1, external_mappings=None, data_loader=None):
+        super().__init__(tag, external_mappings, data_loader)
         o_s = []
         for i in range(n):
             o = StaticDataTest('tm' + str(i))
@@ -338,48 +327,85 @@ def __init__(self, tag, n=1):
         self.register_items(o_s)
 
 
-@pytest.mark.parametrize("solver", solver_types)
-@pytest.mark.parametrize("use_llvm", [True, False])
-def test_external_data(solver, use_llvm):
-    external_mappings = []
-
-    import pandas as pd
-    import numpy as np
-
-    data = {'time': np.arange(100),
-            'Dew Point Temperature {C}': np.arange(100) + 1,
-            'Dry Bulb Temperature {C}': np.arange(100) + 2,
-            }
-
-    df = pd.DataFrame(data, columns=['time', 'Dew Point Temperature {C}', 'Dry Bulb Temperature {C}'])
-    index_to_timestep_mapping = 'time'
-    index_to_timestep_mapping_start = 0
-    dataframe_aliases = {
-        'system_external.tm0.test_nm.T1': ("Dew Point Temperature {C}", InterpolationType.PIESEWISE),
-        'system_external.tm0.test_nm.T2': ('Dry Bulb Temperature {C}', InterpolationType.PIESEWISE)
-    }
-    external_mappings.append(ExternalMappingElement
-                             ("inmemory", index_to_timestep_mapping, index_to_timestep_mapping_start, 1,
-                              dataframe_aliases))
-    data_loader = InMemoryDataLoader(df)
-    s = Simulation(
-        Model(StaticDataSystem('system_external', n=1), use_llvm=use_llvm, external_mappings=external_mappings,
-              data_loader=data_loader),
-        t_start=0, t_stop=100.0, num=100, num_inner=100, max_step=.1, solver_type=solver
-    )
-    s.solve()
-    assert approx(np.array(s.model.historian_df['system_external.tm0.test_nm.T_i1'])[1:]) == np.arange(101)[1:]
-    assert approx(np.array(s.model.historian_df['system_external.tm0.test_nm.T_i2'])[1:]) == np.arange(101)[1:] + 1
+class OuterSystem(Subsystem):
+    def __init__(self, tag, system, n=1, external_mappings=None, data_loader=None):
+        super().__init__(tag, external_mappings, data_loader)
+        o_s = []
+        for i in range(n):
+            o = StaticDataTest('tm' + str(i))
+            o_s.append(o)
+        o_s.append(system)
+        # Register the items to the subsystem to make it recognize them.
+        self.register_items(o_s)
+
+
+class ExponentialDecay(Subsystem, EquationBase):
+    def __init__(self, tag='exp', alpha=0.1):
+        super(ExponentialDecay, self).__init__(tag)
+        self.t1 = self.create_namespace('t1')
+        self.add_state('x', 1, logger_level=LoggerLevel.INFO)
+        self.add_constant('alpha', alpha)
+        self.t1.add_equations([self])
+
+    @Equation()
+    def eval(self, scope):
+        scope.x_dot = -scope.x * scope.alpha
+
+
+p1_val = 25
+
+
+class SetVar(Subsystem, EquationBase):
+    def __init__(self, tag='setvar'):
+        super(SetVar, self).__init__(tag)
+        self.t1 = self.create_namespace('t1')
+        self.add_state('x', 1, logger_level=LoggerLevel.INFO)
+        self.add_parameter('p1', p1_val)
+        self.t1.add_equations([self])
+
+    @Equation()
+    def eval(self, scope):
+        scope.x_dot = 1
 
 
-@pytest.mark.parametrize("solver", solver_types)
 @pytest.mark.parametrize("use_llvm", [True, False])
-def test_static_system(solver, use_llvm):
-    import numpy as np
-    s = Simulation(
-        Model(StaticDataSystem('system_static', n=1), use_llvm=use_llvm),
-        t_start=0, t_stop=100.0, num=100, num_inner=100, max_step=.1, solver_type=solver
-    )
+def test_static_system(use_llvm):
+    s = Simulation(Model(StaticDataSystem('system_static', n=1), use_llvm=use_llvm), t_start=0, t_stop=100.0, num=100,
+                   num_inner=100, max_step=.1)
     s.solve()
-    assert approx(np.array(s.model.historian_df['system_static.tm0.test_nm.T_i1'])[1:]) == np.repeat(0, (100))
-    assert approx(np.array(s.model.historian_df['system_static.tm0.test_nm.T_i2'])[1:]) == np.repeat(0, (100))
+    assert approx(np.array(s.model.historian_df['system_static.tm0.test_nm.T_i1'])[1:]) == np.repeat(0, 100)
+    assert approx(np.array(s.model.historian_df['system_static.tm0.test_nm.T_i2'])[1:]) == np.repeat(0, 100)
+
+
+def test_reset_model():
+    model = Model(ExponentialDecay(tag='system'))
+    sim = Simulation(model=model, t_start=0, t_stop=100, num=100)
+    sim.solve()
+
+    df_1 = sim.model.historian_df
+
+    sim2 = Simulation(model=model, t_start=0, t_stop=100, num=100)
+    sim2.solve()
+
+    df_2 = sim2.model.historian_df
+
+    assert approx(df_1['system.t1.x'].values) == df_2['system.t1.x'].values
+
+
+def test_set_variables():
+    model = Model(SetVar(tag='system'))
+    p1 = 1
+
+    model.set_variables({'system.t1.p1': p1})
+    sim = Simulation(model=model, t_start=0, t_stop=1, num=3)
+    sim.solve()
+
+    df_1 = sim.model.historian_df
+
+    assert (df_1['system.t1.p1'].values == p1).all()
+
+
+def test_get_init_variables():
+    model = Model(SetVar(tag='system'))
+
+    assert model.get_variables_initial_values()['system.t1.p1'] == p1_val
diff --git a/tests/test_not_used_variable.py b/tests/test_not_used_variable.py
index b435117a..655ff12c 100644
--- a/tests/test_not_used_variable.py
+++ b/tests/test_not_used_variable.py
@@ -3,7 +3,6 @@
 
 from numerous.engine.model import Model
 from numerous.engine.simulation import Simulation
-from numerous.engine.simulation.solvers.base_solver import solver_types
 from numerous.engine.system import Subsystem, Item
 from numerous.multiphysics import EquationBase, Equation
 import numpy as np
@@ -37,7 +36,6 @@ def __init__(self, tag):
     return T1('test_item')
 
 
-
 @pytest.fixture
 def ms4(simple_item):
     class S1(Subsystem):
@@ -48,11 +46,10 @@ def __init__(self, tag):
     return S1('S1_var')
 
 
-@pytest.mark.parametrize("solver", solver_types)
 @pytest.mark.parametrize("use_llvm", [True, False])
-def test_var_not_used(ms4, solver, use_llvm):
-    m1 = Model(ms4,use_llvm=use_llvm)
-    s1 = Simulation(m1, t_start=0, t_stop=100, num=10, solver_type=solver)
+def test_var_not_used(ms4, use_llvm):
+    m1 = Model(ms4, use_llvm=use_llvm)
+    s1 = Simulation(m1, t_start=0, t_stop=100, num=10)
     s1.solve()
     df = s1.model.historian_df
     assert approx(np.array(df['S1_var.test_item.t1.P2'])) == np.repeat(11, (11))
diff --git a/tests/test_numerical_highlevel/test_bouncing_ball_event.py b/tests/test_numerical_highlevel/test_bouncing_ball_event.py
index 99c140f6..56655fab 100644
--- a/tests/test_numerical_highlevel/test_bouncing_ball_event.py
+++ b/tests/test_numerical_highlevel/test_bouncing_ball_event.py
@@ -5,7 +5,6 @@
 from numerous.engine.system import Subsystem
 from numerous.engine.simulation import Simulation
 from numerous.multiphysics import EquationBase, Equation
-from numerous.engine.simulation.solvers.base_solver import solver_types
 from pytest import approx
 
 tmax = 5
@@ -59,8 +58,12 @@ def __init__(self, tag):
 
 
 # returns position to find zero crossing using root finding algorithm of scipy solver
-def hitground_event_fun(t, states):
-    return states['S1.ball.t1.x']
+def hitground_event_fun(t, variables):
+    return variables['S1.ball.t1.x']
+
+
+def hitground_event_fun_g(t, variables):
+    return variables['S1.ball.t1.x'] * (variables['S1.ball.t1.g'] / variables['S1.ball.t1.g'])
 
 
 # change direction of movement upon event detection and reduce velocity
@@ -75,15 +78,30 @@ def timestamp_callback(t, variables):
     print(t)
 
 
-@pytest.mark.parametrize("solver", solver_types)
 @pytest.mark.parametrize("use_llvm", [True, False])
-def test_bouncing_ball(solver, use_llvm):
+def test_bouncing_ball(use_llvm):
     model_system_2 = ms1(Ball(tag="ball", g=9.81, f_loss=0.05))
     m1 = Model(model_system_2, use_llvm=use_llvm)
 
     m1.add_event("hitground_event", hitground_event_fun, hitground_event_callback_fun)
 
-    sim = Simulation(m1, t_start=0, t_stop=tmax, num=num, solver_type=solver)
+    sim = Simulation(m1, t_start=0, t_stop=tmax, num=num)
+
+    sim.solve()
+    asign = np.sign(np.array(m1.historian_df['S1.ball.t1.v']))
+    signchange = ((np.roll(asign, 1) - asign) != 0).astype(int)
+    args = np.argwhere(signchange > 0)[2:].flatten()
+    assert approx(m1.historian_df['time'][args[0::2][:5]], rel=0.01) == t_hits[:5]
+
+
+@pytest.mark.parametrize("use_llvm", [True, False])
+def test_with_full_condition(use_llvm):
+    model_system_2 = ms1(Ball(tag="ball", g=9.81, f_loss=0.05))
+    m1 = Model(model_system_2, use_llvm=use_llvm)
+
+    m1.add_event("hitground_event", hitground_event_fun_g, hitground_event_callback_fun)
+
+    sim = Simulation(m1, t_start=0, t_stop=tmax, num=num)
 
     sim.solve()
     asign = np.sign(np.array(m1.historian_df['S1.ball.t1.v']))
@@ -92,17 +110,36 @@ def test_bouncing_ball(solver, use_llvm):
     assert approx(m1.historian_df['time'][args[0::2][:5]], rel=0.01) == t_hits[:5]
 
 
-@pytest.mark.parametrize("solver", solver_types)
 @pytest.mark.parametrize("use_llvm", [True, False])
-def test_timestamp_events(solver, use_llvm, capsys):
+def test_timestamp_events(use_llvm, capsys):
     model_system_2 = ms1(Ball(tag="ball", g=9.81, f_loss=0.05))
     m1 = Model(model_system_2, use_llvm=use_llvm)
 
     m1.add_timestamp_event("timestamp_event", timestamp_callback, timestamps=[0.11, 0.33])
 
-    sim = Simulation(m1, t_start=0, t_stop=tmax, num=num, solver_type=solver)
+    sim = Simulation(m1, t_start=0, t_stop=tmax, num=num)
 
     sim.solve()
     captured = capsys.readouterr()
 
     assert captured.out == "0.11\n0.33\n"
+
+
+def hitground_event_callback_fun_with_wrong_path(t, variables):
+    velocity = variables['S1.ball.t1.']
+    velocity = -velocity * (1 - variables['S1.ball.t1.f_loss'])
+    variables['S1.ball.t1.v'] = velocity
+    variables['S1.ball.t1.t_hit'] = t
+
+
+@pytest.mark.parametrize("use_llvm", [True, False])
+def test_variable_error(use_llvm):
+    with pytest.raises(KeyError):
+        model_system_2 = ms1(Ball(tag="ball", g=9.81, f_loss=0.05))
+        m1 = Model(model_system_2, use_llvm=use_llvm)
+
+        m1.add_event("hitground_event", hitground_event_fun, hitground_event_callback_fun_with_wrong_path)
+
+        sim = Simulation(m1, t_start=0, t_stop=tmax, num=num)
+
+        sim.solve()
diff --git a/tests/test_numerical_highlevel/test_bouncing_ball_event_item_level.py b/tests/test_numerical_highlevel/test_bouncing_ball_event_item_level.py
index eb2cb7f9..1cee07f3 100644
--- a/tests/test_numerical_highlevel/test_bouncing_ball_event_item_level.py
+++ b/tests/test_numerical_highlevel/test_bouncing_ball_event_item_level.py
@@ -5,7 +5,6 @@
 from numerous.engine.system import Subsystem
 from numerous.engine.simulation import Simulation
 from numerous.multiphysics import EquationBase, Equation
-from numerous.engine.simulation.solvers.base_solver import solver_types
 from pytest import approx
 
 tmax = 5
@@ -84,13 +83,12 @@ def __init__(self, tag):
     return S1('S1')
 
 
-@pytest.mark.parametrize("solver", solver_types)
 @pytest.mark.parametrize("use_llvm", [True, False])
-def test_bouncing_ball(solver, use_llvm):
+def test_bouncing_ball(use_llvm):
     model_system_2 = ms1(Ball(tag="ball", g=9.81, f_loss=0.05))
     m1 = Model(model_system_2, use_llvm=use_llvm)
 
-    sim = Simulation(m1, t_start=0, t_stop=tmax, num=num, solver_type=solver)
+    sim = Simulation(m1, t_start=0, t_stop=tmax, num=num)
 
     sim.solve()
     asign = np.sign(np.array(m1.historian_df['S1.ball.t1.v']))
@@ -99,13 +97,31 @@ def test_bouncing_ball(solver, use_llvm):
     assert approx(m1.historian_df['time'][args[0::2][:5]], rel=0.01) == t_hits[:5]
 
 
-@pytest.mark.parametrize("solver", solver_types)
+@pytest.mark.parametrize("num", [5, 10, 100])
+def test_bouncing_ball_event_detection(num):
+    model_system_2 = ms1(Ball(tag="ball", g=9.81, f_loss=0.05))
+    m1 = Model(model_system_2)
+
+    sim = Simulation(m1, t_start=0, t_stop=tmax, num=5)
+
+    sim.solve()
+
+    df = sim.model.historian_df
+
+    expected_number_of_hits = len(t_hits[t_hits <= tmax])
+
+    t_hits_model = np.unique(df['S1.ball.t1.t_hit'])[1:]
+
+    assert t_hits[:len(t_hits_model)] == approx(t_hits_model, rel=1e-3)
+    assert expected_number_of_hits == len(t_hits_model)
+
+
 @pytest.mark.parametrize("use_llvm", [True, False])
-def test_timetamp_item_event(solver, use_llvm, capsys):
+def test_timetamp_item_event(use_llvm, capsys):
     model_system_2 = ms1(Ball2(tag="ball", g=9.81, f_loss=0.05))
     m1 = Model(model_system_2, use_llvm=use_llvm)
 
-    sim = Simulation(m1, t_start=0, t_stop=tmax, num=num, solver_type=solver)
+    sim = Simulation(m1, t_start=0, t_stop=tmax, num=num)
 
     sim.solve()
     captured = capsys.readouterr()
diff --git a/tests/test_numerical_highlevel/test_sets_linking_sets.py b/tests/test_numerical_highlevel/test_sets_linking_sets.py
index e283721d..892e0c71 100644
--- a/tests/test_numerical_highlevel/test_sets_linking_sets.py
+++ b/tests/test_numerical_highlevel/test_sets_linking_sets.py
@@ -121,10 +121,7 @@ def __init__(self, tag, T0=[50, 150], n=1):
     T0=[150, 50]
     subsystem = ThermalSystem('system', n=n, T0=T0)
     # Define simulation
-    s = simulation.Simulation(
-        model.Model(subsystem),
-        t_start=0, t_stop=500.0, num=100, num_inner=10, max_step=10
-    )
+    s = simulation.Simulation(model.Model(subsystem), t_start=0, t_stop=500.0, num=100, num_inner=10, max_step=10)
     # Solve and plot
     tic = time()
     s.solve()
diff --git a/tests/test_numerical_highlevel/test_step_solver.py b/tests/test_numerical_highlevel/test_step_solver.py
index cd17dc35..41a6eb08 100644
--- a/tests/test_numerical_highlevel/test_step_solver.py
+++ b/tests/test_numerical_highlevel/test_step_solver.py
@@ -122,10 +122,7 @@ def __init__(self, tag, T0=[50, 150], n=1):
     subsystem = ThermalSystem('system', n=n, T0=T0)
     # Define simulation
     n=100
-    s = simulation.Simulation(
-        model.Model(subsystem),
-        t_start=0, t_stop=500.0, num=n, num_inner=1, max_step=1000
-    )
+    s = simulation.Simulation(model.Model(subsystem), t_start=0, t_stop=500.0, num=n, num_inner=1, max_step=1000)
     # Solve and plot
 
     time_ = np.logspace(0,4,n)
diff --git a/tests/test_numerous_solver.py b/tests/test_numerous_solver.py
index af7c63a4..09a26dbc 100644
--- a/tests/test_numerous_solver.py
+++ b/tests/test_numerous_solver.py
@@ -8,8 +8,9 @@
 import pytest
 from pytest import approx
 
+
 class AbsTestSystem(Subsystem):
-    def __init__(self, tag='abstestsys', item: Item=None):
+    def __init__(self, tag='abstestsys', item: Item = None):
         super(AbsTestSystem, self).__init__(tag)
         self.register_item(item)
 
@@ -28,7 +29,7 @@ def __init__(self, tag='abstest'):
     def eval(self, scope):
 
         x_dot = 0
-        y_dot = 0.001*(scope.x-scope.y)
+        y_dot = 0.001 * (scope.x - scope.y)
 
         if scope.y < 0:
             if scope.x <= scope.x_max:
@@ -37,38 +38,44 @@ def eval(self, scope):
             if scope.x >= -scope.x_max:
                 x_dot = - scope.k * scope.y
 
-
         scope.x_dot = x_dot
         scope.y_dot = y_dot
 
+
 @pytest.fixture
 def model():
-    sys = AbsTestSystem(item=AbsTestItem())
-    historian=InMemoryHistorian()
-    historian.max_size = 2000
-    model = Model(system=sys, logger_level=LoggerLevel.INFO, use_llvm=True, historian=historian)
-    yield model
+    def i_model(historian_max_size=2000, historian=InMemoryHistorian()):
+        sys = AbsTestSystem(item=AbsTestItem())
+        historian.max_size = historian_max_size
+        model = Model(system=sys, logger_level=LoggerLevel.INFO, use_llvm=True, historian=historian)
+        return model
+
+    yield i_model
 
-@pytest.fixture
-def variables(model: Model):
-    variables = model.get_variables()
-    yield variables
 
 @pytest.fixture
-def simulation(model: Model, variables: variables):
-    def fn(solver: SolverType, method: str):
+def variables(model: model):
+    variables = model().get_variables()
+    return variables
 
-        model.update_variables(variables)
-        sim = Simulation(model, t_start=0, t_stop=1000, num=1000, solver_type=solver, method=method)
+
+@pytest.fixture
+def simulation(model: model, variables: variables):
+    def fn(solver: SolverType, method: str, historian_max_size=2000, historian=None):
+        model_o = model(historian_max_size=historian_max_size, historian=historian)
+        model_o.update_variables(variables)
+        sim = Simulation(model_o, t_start=0, t_stop=1000, num=1000, solver_type=solver, method=method)
         sim.reset()
         sim.model.historian_df = None
         return sim
-    yield fn
+
+    return fn
+
 
 @pytest.fixture
 def step_solver(simulation: simulation):
-    def fn(solver: SolverType, method: str):
-        sim = simulation(solver=solver, method=method)
+    def fn(solver: SolverType, method: str, historian_max_size=2000, historian=None):
+        sim = simulation(solver=solver, method=method, historian_max_size=historian_max_size, historian=historian)
         t = 0
         dt = 1
         while True:
@@ -79,21 +86,47 @@ def fn(solver: SolverType, method: str):
         sim.model.create_historian_df()
         df = sim.model.historian_df
         return df
+
     yield fn
 
+
 @pytest.fixture()
 def normal_solver(simulation: simulation):
-    def fn(solver: SolverType, method: str):
-        sim = simulation(solver=solver, method=method)
+    def fn(solver: SolverType, method: str, historian_max_size=2000, historian=None):
+        sim = simulation(solver=solver, method=method, historian_max_size=historian_max_size,
+                         historian=historian)
         sim.solve()
         df = sim.model.historian_df
         return df
+
     yield fn
 
+
 def test_numerous_solver(normal_solver: normal_solver, step_solver: step_solver):
     rel = 1e-6
-    df_step = step_solver(solver=SolverType.NUMEROUS, method='RK45')
-    df_normal_solver = normal_solver(solver=SolverType.NUMEROUS, method='RK45')
+    df_step = step_solver(solver=SolverType.NUMEROUS, method='RK45', historian=InMemoryHistorian(),
+                          historian_max_size=2000)
+    df_normal_solver = normal_solver(solver=SolverType.NUMEROUS, method='RK45', historian=InMemoryHistorian(),
+                                     historian_max_size=2000)
+
+    assert df_step['abstestsys.abstest.t1.x'].values == approx(df_normal_solver['abstestsys.abstest.t1.x'].values,
+                                                               rel=rel), \
+        f"results do not match within relative tolerance {rel}"
+
+
+def test_store_historian(normal_solver: normal_solver, step_solver: step_solver):
+    results = {}
+    for solver, name in zip([normal_solver, step_solver], ["normal_solver", "step_solver"]):
+
+        df_split = solver(solver=SolverType.NUMEROUS, method='RK45', historian=InMemoryHistorian(),
+                          historian_max_size=10)
+
+        df_single = solver(solver=SolverType.NUMEROUS, method='RK45', historian=InMemoryHistorian(),
+                           historian_max_size=2000)
+
+        results.update({name: [df_split, df_single]})
 
-    assert df_step['abstestsys.abstest.t1.x'].values == approx(df_normal_solver['abstestsys.abstest.t1.x'].values, rel=rel), \
-        f"results do not match within relative tolerance {rel}"
\ No newline at end of file
+        assert approx(df_split["abstestsys.abstest.t1.x"]) == df_single["abstestsys.abstest.t1.x"], \
+            f"failed for {name}"
+        assert approx(df_split["abstestsys.abstest.t1.y"]) == df_single["abstestsys.abstest.t1.y"], \
+            "failed for {name}"
diff --git a/tests/test_overloadaction_sum.py b/tests/test_overloadaction_sum.py
index 93ff5c36..8b22dc2b 100644
--- a/tests/test_overloadaction_sum.py
+++ b/tests/test_overloadaction_sum.py
@@ -6,10 +6,6 @@
 import pytest
 from pytest import approx
 
-from numerous.engine.simulation.solvers.base_solver import solver_types
-
-
-
 
 class Item1(Item, EquationBase):
     def __init__(self, tag='item1'):
@@ -27,7 +23,6 @@ def eval(self, scope):
         scope.x_dot = -1 * np.exp(-1 * scope.t)
 
 
-
 class Subsystem1(Subsystem, EquationBase):
     def __init__(self, tag='subsystem1', item1=object):
         super(Subsystem1, self).__init__(tag)
@@ -78,6 +73,7 @@ def eval(self, scope):
         scope.x_dot_mod = 0
         scope.x_2_dot_mod = -1
 
+
 class Subsystem3(Subsystem, EquationBase):
     def __init__(self, tag='subsystem1', item1=object):
         super(Subsystem3, self).__init__(tag)
@@ -90,55 +86,60 @@ def __init__(self, tag='subsystem1', item1=object):
 
         item1.t1.x_dot += self.t1.x_dot_mod
         self.t1.x_dot_mod = item2.t1.tmp
+
     @Equation()
     def eval(self, scope):
         scope.x_2_dot_mod = -1
 
+
 class System(Subsystem, EquationBase):
     def __init__(self, tag='system_overload', subsystem=object):
         super(System, self).__init__(tag)
 
         self.register_items([subsystem])
 
+
 @pytest.fixture
 def system1():
-    return System(tag='system1_overload',subsystem=Subsystem1(item1=Item1()))
+    return System(tag='system1_overload', subsystem=Subsystem1(item1=Item1()))
+
 
 @pytest.fixture
 def system2():
-    return System(tag='system2_overload',subsystem=Subsystem2(item1=Item2()))
+    return System(tag='system2_overload', subsystem=Subsystem2(item1=Item2()))
+
 
 @pytest.fixture
 def system3():
-    return System(tag='system3_overload',subsystem=Subsystem3(item1=Item1()))
+    return System(tag='system3_overload', subsystem=Subsystem3(item1=Item1()))
+
 
 def expected_sol(t):
-    return -1*(t*np.exp(t) -1)*np.exp(-t)
+    return -1 * (t * np.exp(t) - 1) * np.exp(-t)
+
 
-@pytest.mark.parametrize("solver", solver_types)
-@pytest.mark.parametrize("use_llvm", [True,False])
-def test_overloadaction_sum(system1, solver, use_llvm):
-    model = Model(system1,use_llvm=use_llvm)
-    sim = Simulation(model, t_start=0, t_stop=10, num=100, solver_type = solver)
+@pytest.mark.parametrize("use_llvm", [True, False])
+def test_overloadaction_sum(system1, use_llvm):
+    model = Model(system1, use_llvm=use_llvm)
+    sim = Simulation(model, t_start=0, t_stop=10, num=100)
     sim.solve()
     df = sim.model.historian_df
     assert approx(np.array(df['system1_overload.subsystem1.item1.t1.x'])) == expected_sol(np.linspace(0, 10, 101))
 
 
-@pytest.mark.parametrize("solver", solver_types)
-@pytest.mark.parametrize("use_llvm", [True,False])
-def test_overloadaction_sum_chain_mapping(system3, solver, use_llvm):
-    model = Model(system3,use_llvm=use_llvm)
-    sim = Simulation(model, t_start=0, t_stop=10, num=100, solver_type = solver)
+@pytest.mark.parametrize("use_llvm", [True, False])
+def test_overloadaction_sum_chain_mapping(system3, use_llvm):
+    model = Model(system3, use_llvm=use_llvm)
+    sim = Simulation(model, t_start=0, t_stop=10, num=100)
     sim.solve()
     df = sim.model.historian_df
     assert approx(np.array(df['system3_overload.subsystem1.item1.t1.x'])) == expected_sol(np.linspace(0, 10, 101))
 
-@pytest.mark.parametrize("solver", solver_types)
+
 @pytest.mark.parametrize("use_llvm", [True, False])
-def test_overloadaction_sum_multiple(system2, solver, use_llvm):
+def test_overloadaction_sum_multiple(system2, use_llvm):
     model = Model(system2, use_llvm=use_llvm)
-    sim = Simulation(model, t_start=0, t_stop=10, num=100, solver_type = solver)
+    sim = Simulation(model, t_start=0, t_stop=10, num=100)
     sim.solve()
     df = sim.model.historian_df
-    assert approx(np.array(df['system2_overload.subsystem1.item1.t1.x'])) == expected_sol(np.linspace(0, 10, 101))
\ No newline at end of file
+    assert approx(np.array(df['system2_overload.subsystem1.item1.t1.x'])) == expected_sol(np.linspace(0, 10, 101))
diff --git a/tests/test_race_condition.py b/tests/test_race_condition.py
index 726937b0..1439c250 100644
--- a/tests/test_race_condition.py
+++ b/tests/test_race_condition.py
@@ -3,13 +3,10 @@
 
 from numerous.engine.model import Model
 from numerous.engine.simulation import Simulation
-from numerous.engine.simulation.solvers.base_solver import solver_types
 from numerous.engine.system import Subsystem, Item
 from numerous.multiphysics import EquationBase, Equation
 
 
-
-
 class Item1(Item, EquationBase):
     def __init__(self, tag='item1', omega=0.5, amplitude=1):
         super(Item1, self).__init__(tag)
@@ -75,27 +72,25 @@ def analytical(tvec, o, a, dt):
     return s, s_delayed
 
 
-@pytest.mark.parametrize("solver", solver_types)
-@pytest.mark.parametrize("use_llvm", [True,False])
-def test_race_condition_1(solver,use_llvm):
+@pytest.mark.parametrize("use_llvm", [True, False])
+def test_race_condition_1(use_llvm):
     omega0 = 0.01
     dt = 10
-    s1 = System(item=Link(item1=Item1(omega=omega0)),tag='system_race_1')
-    s2 = System(item=Item2(omega=omega0),tag='system_race_2')
-
-    m1 = Model(s1,use_llvm=use_llvm)
-    sim1 = Simulation(m1, max_step=dt, num=500, solver_type=solver)
+    s1 = System(item=Link(item1=Item1(omega=omega0)), tag='system_race_1')
+    s2 = System(item=Item2(omega=omega0), tag='system_race_2')
 
+    m1 = Model(s1, use_llvm=use_llvm)
+    sim1 = Simulation(m1, num=500, max_step=dt)
 
     sim1.solve()
 
     df1 = sim1.model.historian_df
 
     m2 = Model(s2, use_llvm=use_llvm)
-    sim2 = Simulation(m2, max_step=dt, num=500, solver_type=solver)
+    sim2 = Simulation(m2, num=500, max_step=dt)
     sim2.solve()
     df2 = sim2.model.historian_df
 
     assert np.all(
-        np.isclose(np.array(df1['system_race_1.link.t1.S'])[2:], np.array(df2['system_race_2.item2.t1.S'][2:]), rtol=1e-02, atol=1e-04))
-
+        np.isclose(np.array(df1['system_race_1.link.t1.S'])[2:], np.array(df2['system_race_2.item2.t1.S'][2:]),
+                   rtol=1e-02, atol=1e-04))
diff --git a/tests/test_solvers.py b/tests/test_solvers.py
new file mode 100644
index 00000000..9ca84ec5
--- /dev/null
+++ b/tests/test_solvers.py
@@ -0,0 +1,44 @@
+from pytest import approx
+
+from numerous.engine.model import Model
+from numerous.engine.simulation import Simulation
+
+from numerous.engine.system import Subsystem, LoggerLevel
+from numerous.multiphysics import EquationBase, Equation
+from numerous.engine.simulation.solvers.base_solver import SolverType
+
+
+class SimpleInt(Subsystem, EquationBase):
+    def __init__(self, tag='integrate'):
+        super(SimpleInt, self).__init__(tag)
+        self.t1 = self.create_namespace('t1')
+        self.add_state('x', 0, logger_level=LoggerLevel.INFO)
+        self.t1.add_equations([self])
+
+    @Equation()
+    def eval(self, scope):
+        scope.x_dot = 1
+
+
+def test_euler():
+    model = Model(SimpleInt(tag='system'))
+
+    n = 10
+    sim = Simulation(model=model, t_start=0, t_stop=10, num=n, solver=SolverType.NUMEROUS, method='Euler')
+    sim.solve()
+
+    df_1 = sim.model.historian_df
+
+    assert approx(df_1['system.t1.x'], 0.01, 0.01) == df_1['time']
+
+
+def test_RK45():
+    model = Model(SimpleInt(tag='system'))
+
+    n = 10
+    sim = Simulation(model=model, t_start=0, t_stop=10, num=n, solver=SolverType.NUMEROUS, method='RK45')
+    sim.solve()
+
+    df_1 = sim.model.historian_df
+
+    assert approx(df_1['system.t1.x'], 0.01, 0.01) == df_1['time']
diff --git a/tests/test_system_link.py b/tests/test_system_link.py
index 63e7e67d..37d65a06 100644
--- a/tests/test_system_link.py
+++ b/tests/test_system_link.py
@@ -7,9 +7,6 @@
 from numerous.engine.simulation import Simulation
 import numpy as np
 
-from numerous.engine.simulation.solvers.base_solver import solver_types
-
-
 
 class InitialValue(Item, EquationBase):
     def __init__(self, tag='initialvalue', x0=1):
@@ -76,12 +73,12 @@ def system15():
 def system51():
     return Root(N_outer=1, N_inner=5)
 
-@pytest.mark.parametrize("solver", solver_types)
+
 @pytest.mark.parametrize("use_llvm", [True, False])
-def test_system_link_1_5(system15, solver,use_llvm):
-    model = Model(system15,use_llvm=use_llvm)
+def test_system_link_1_5(system15, use_llvm):
+    model = Model(system15, use_llvm=use_llvm)
 
-    sim = Simulation(model, t_start=0, t_stop=100, num=200, solver_type=solver)
+    sim = Simulation(model, t_start=0, t_stop=100, num=200)
 
     sim.solve()
     df = sim.model.historian_df
@@ -89,12 +86,12 @@ def test_system_link_1_5(system15, solver,use_llvm):
     assert approx(np.array(df['root_linktest.linkersubsystem_4.item_0.t1.x'])[1:]) == \
            expected(len(df.index[:-1]), 5, 0.9)
 
-@pytest.mark.parametrize("solver", solver_types)
+
 @pytest.mark.parametrize("use_llvm", [True, False])
-def test_system_link_5_1(system51, solver,use_llvm):
-    model = Model(system51,use_llvm=use_llvm)
+def test_system_link_5_1(system51, use_llvm):
+    model = Model(system51, use_llvm=use_llvm)
 
-    sim = Simulation(model, t_start=0, t_stop=100, num=200, solver_type=solver)
+    sim = Simulation(model, t_start=0, t_stop=100, num=200)
 
     sim.solve()
     df = sim.model.historian_df
diff --git a/tests/test_variables.py b/tests/test_variables.py
new file mode 100644
index 00000000..cd0a9c29
--- /dev/null
+++ b/tests/test_variables.py
@@ -0,0 +1,30 @@
+import pytest
+from numerous.engine.model import Model
+from numerous.engine.system import Subsystem
+from numerous.multiphysics import Equation, EquationBase
+
+
+class StringVariables(Subsystem, EquationBase):
+    def __init__(self, tag='system'):
+        super(StringVariables, self).__init__(tag)
+        self.t1 = self.create_namespace('t1')
+        self.add_parameter('var1', 'not implemented')
+        self.t1.add_equations([self])
+
+
+class FloatVariables(Subsystem, EquationBase):
+    def __init__(self, tag='system'):
+        super(FloatVariables, self).__init__(tag)
+        self.t1 = self.create_namespace('t1')
+        self.add_parameter('var1', 1.0)
+        self.t1.add_equations([self])
+
+
+def test_throw_error():
+
+    with pytest.raises(ValueError, match=r"Only numeric values allowed in variables*"):
+        StringVariables()
+
+    with pytest.raises(ValueError, match=r"Only numeric values allowed in variables*"):
+        sys = FloatVariables()
+        sys.t1.var1.value = "test"