boxsimu is a simple modelling framework based on the principle of a system of discrete boxes interacting with each other. boxsimu offers a simple interface to define a system, its components and properties. The system definition is done by instantiating several classes of the boxsimu package like Box, Fluid, Flow, Flux, Process, Reaction, or Box. The temporal evolution of a system defined in boxsimu can then easily be simulated and the output of the simulation can be visualized or further investigated.
After a short primer on what mathematical modelling is and how boxsimu fits in there, this tutorial will lead you through the installation of boxsimu. After the installion, it will briefly explain the basic structure of a system-definition in boxsimu. After that, you should have a basic understanding of the most important classes of boxsimu and you're ready to dive into a first example!
In order to understand the internal dynamics of a system, it is often useful to create a (mathematical) model thereof. A system can be almost anything, e.g. a small mountain lake, the entire ocean, the atmosphere, a machine, a company, or even the entire Earth System. A (mathematical) model represents such a system while neglecting a big amount of complexity. Thus a (mathematical) model tries to explain the fundamental system behaviour as simply as possible.
Schwarzenbach et al. (2003) uses the following defintion of a system:
''A model is an imitation of reality that stresses those aspects that are assumed to be important and omits all properties considered to be nonessential''.
Mathematical modelling of a system is a challenging and complex activity. Depending on the system an analytical solution can even be nonexistent. As a consequence one is forced to simulate/solve such systems numerically. This is where boxsimu comes into play:
boxsimu allows the user to define simple to intermediate complex system and to simulate their temporal evolution. All that can be done on a high level of abstraction, meaning the user doesn't have to bother about the representation of the system with computer code.
At the current status of development of boxsimu, the numerical solver is not very efficient. As a consequence large and complexe systems with a lot of variables, boxes, and processes/reactions will be solved slowly.
Python: boxsimu was only tested with Python 3.4 or newer. Therefore boxsimu may not work as expected with older versions of Python.
boxsimu is a numerical box-modelling framework that builds on diverse other python packages. The following python packages are required by boxsimu:
Scientifc packages:
Other packages:
All of the above listed python package are available in the official python repository and thus can be installed using pip. Thus, if a package is missing on your system just use:
\$ pip install <package name>
Additionally an up-to-date C compiler like gcc is needed, since some computational critical parts of boxsimu are written in Cython, which has to be compiled into binary code on your computer.
boxsimu can easily be installed using pip. On your console type in
$ pip install boxsimu
this should automatically compile all Cython files and copy all source files into the right directory. If the installation fails check if all of the above mentioned dependencies are met.
Alternatively to the installation via pip, boxsimu can also be installed from source. For this, download the most recent source code from github {{URL}} or clone the repository. Afterwards open a system console and change into the directory where you downloaded boxsimu. Then execute the following commands:
\$ cd boxsimu <br> \$ python setup.py
A system is defined by instantiating the class BoxModelSystem and passing instances of the classes Box, Flow, and Flux to it. The instance of the class BoxModelSystem contains all information on the whole system while its components (Boxes, Flows, and Fluxes) store information on the distribution of Fluids and Variables in the system and how the different compartements (boxes) exchange these quantities. The basic structure of a BoxModelSystem instance is graphically shown below:
In this diagram an instance of BoxModelSystem is shown that contains two boxes: 'Box 1' and 'Box 2'. Both boxes contain the same fluid ('Fluid 1') and two instances of the class Variable ('A' and 'B'). Additionally both boxes can contain an arbitrary number of independet processes and reactions of these variables. Finally, the boxes exchange mass of the variables and the fluid via fluxes and flows (the difference between a flow and a flux will be explained further down).
A BoxModelSystem can contain an arbitrary number of boxes, however, the more boxes there are the slower the system's simulation will progress. Similarly the number of fluxes, flows, and variables, processes, reactions within boxes is only limited in regard of computational power. In contrast, every box can only contain zero or one instance of the class Fluid.
The most important class that a user of BoxSimu is interacting with are:
A system is most easily defined following these steps:
Given this first fundamental understanding of boxsimu classes and their sequence of appearence within a system-definition, we jump directly into a first example!
Our first system consists of a freshwater lake that only has one inflow and one outflow. We want to simulate how the concentration of phosphate in this lake evolves over time. In order to do that we assume the inflow to have a constant concentration of phosphate (PO4) while the outflow has the same concentration of PO4 as the (well-mixed) lake itself. The volume/mass of lake-water is constant over time.
In the following we have a simple depiction of the lake system and important parameters thereof:
Before we use boxsimu to simulate this system we can solve the govering equations of this system analytically in order to validate the output of boxsimu.
So lets start with the defintion of all needed variables and their physical units:
Variables (symbols are consistent with the figure above):
$t$ = Time [d]
$k_w$ = $\frac{Q}{V}$ = Specific flow rate [1/d]
Assumptions:
Based on the definition of our system given above we can set up the following differential equation:
$\frac{dm}{dt} = \sum sources - \sum sinks = Q \cdot C_{in} - Q \cdot C$.
The rate of change of the phosphate concentration in the lake is given by the sum of all source terms minus the sum of all sink terms. In this case the only source term is the inflow of phosphate from the river upstream, while the only sink term is the outflow of phosphate through the river downstream.
Where $Q \cdot C_{in}$ and $Q \cdot C$ represents the phosphate-gain and phosphate-loss of the lake per time, respectively.
Next, we devide both sides by the volume of the lake ($V$) and use the specific flow rate $k_w$ on the right hand side (r.h.s.):
$\frac{1}{V}\frac{dm}{dt} = k_w \cdot C_{in} - k_w \cdot C = k_w (C_{in} - C)$
Now, since the volume of the box is constant, we can incorporate the volume into the time-derivative and end up with:
$\frac{1}{V}\frac{dm}{dt} = \frac{d(m/V)}{dt} = \frac{dC}{dt} = k_w (C_{in} - C)$
The solution of this linear, inhomogene, ordinary differential equation is:
$C(t) = (C_0 - C_{in}) e^{-k_wt} + C_{in}$
where we also used the initial condition $C(t=0) = C_0$.
The solution is plotted below for a system with the following parameters:
We define a function that calculates and returns the concentration of phosphate at a time $t$. We also vectorize this function using numpy in order to be able to apply it on arrays. The resulting array is then plotted as a function of time:
In [1]:
import matplotlib.pyplot as plt
import numpy as np
@np.vectorize
def C(t):
V = 1e7
Q = 1e5
C0 = 1e-2
Cin = 3e-1
kw = Q/V
return (C0-Cin)*np.exp(-kw*t) + Cin
t = np.linspace(0, 8e2, 1000)
c_phosphate = C(t)
plt.plot(t, c_phosphate)
plt.xlabel('Time [days]')
plt.ylabel('PO4 concentration [kg/m^3]')
Out[1]:
We can see that the system reaches a steady-state after about $400$ days.
Now we want to use boxsimu to simulate this system...
Since boxsimu accepts some quantities only in certain units (dimensionalities) we first have to calculate the system parameters in the right dimensionalities/units:
Now we define these parameters as python variables. But before we can do that we have to import boxsimu (if not already happened) and the instance of pint.UnitRegistry that is used by boxsimu (boxsimu.ur).
If you use boxsimu you have to use the UnitRegistry from boxsimu, since different UnitRegistry instances are incompatible. Thus use
from boxsimu import urinstead of importing the UnitRegistry directly from the pint libary.
In [3]:
import boxsimu
from boxsimu import ur
Now you are able to specify the units of a python variable by simply multiplying by pint units:
In [4]:
length = 3 # Just a number
print(length)
# Now multiply length by the pint-unit meter:
length = 3 * ur.meter
print(length)
Pint units can easily be transformed to other units of the same physical dimensionality:
In [5]:
print(length.to(ur.km)) # transforms length to kilometer
print(length.to(ur.mm)) # transforms length to millimeter
Now lets define the system parameters calculated above:
In [6]:
m_water = 1e10 * ur.kg
m_0 = 1e5 * ur.kg
flow_rate = 1e8 * ur.kg / ur.day
Next we define the boxsimu system! In the following you see the complete model defintion in boxsimu:
In [7]:
# FLUIDS
freshwater = boxsimu.Fluid('freshwater', rho=1000*ur.kg/ur.meter**3)
# VARIABLES
po4 = boxsimu.Variable('po4')
# PROCESSES
# No processes in this system
# REACTIONS
# No reactions in this system
# BOXES
lake = boxsimu.Box(
name='lake',
description='Little Lake',
fluid=freshwater.q(m_water),
variables=[po4.q(m_0)],
)
# FLOWS
inflow = boxsimu.Flow(
name='Inflow',
source_box=None,
target_box=lake,
rate=flow_rate,
tracer_transport=True,
concentrations={po4: 3e-1 * ur.gram / ur.kg},
)
outflow = boxsimu.Flow(
name='Outflow',
source_box=lake,
target_box=None,
rate=flow_rate,
tracer_transport=True,
)
# FLUXES
# No fluxes in this system
# BOXMODELSYSTEM
system = boxsimu.BoxModelSystem(
name='lake_system',
description='Simple Lake Box Model',
boxes=[lake,],
flows=[inflow, outflow,],
)
We will go through this code line by line. But before we dive into the details of the code, have a quick look at the sequence of definitions of boxsimu classes:
In a first step, instances of the classes Fluid and Variable are instantiated. A fluids represents the solvent of a box in which variables can be dissolved (e.g. for a lake the fluid is water, in which a myrade of chemicals can be dissolved). A box can, but doesn't have to be filled with a fluid. This allows to also simulate quantites that are not typically dissolved (e.g. to simulate a population of animals). Variables are quantities of the system that are of interest to the user and that are simulated. A variable defined for one box, flow, flux, process, or reaction will be present in all boxes. At the time of instantiation (creation of the instance) instances of the classes Fluid and Variable are not quantified, that means the instances contain no information on the mass of fluid or variable they represent. However, before they are passed to an instance of the class Box they need to be quantified. This is done by calling the method ''q(mass)'' for a fluid or variable.
In our simple lake system the fluids and variables are:
Next, if needed, the classes Process and Reaction are instantiated (created). However, in this simple lake system there are no processes and no reactions.
The created instances of Fluid, Variable, Process, and Reaction are then assigned to instances of the class Box that represent a compartement in the system (see lines 14-19 in the code).
In a next step we have to define how the boxes in our system interact with each other and with the environement (outside the system). That means how the boxes are exchaning mass of fluids and variables. Therefore we instantiate the classes Flow and Flux (see lines 22-40 in the code). The difference between a flow and a flux is that flows represent a transport of fluid that again can (but doesn't have to) passively transport dissolved variables, whereas fluxes represent a transport of variables that is not associated with any fluid transport.
Finally we create an instance of the class BoxModelSystem that glues together all elements of the system (see lines 43-46 in the following code).
Lets go through this code line by line:
Line 2:
freshwater = boxsimu.Fluid('freshwater', rho=1000*ur.kg/ur.meter**3)
An instance of the class Fluid
, representing the freshwater of the lake, is created.
The class Fluid
has the following signature:
Fluid(name, rho, description=None)
For further information about the class Fluid
confront the documentation: DOCUMENTATION_URL
Line 5:
po4 = boxsimu.Variable('po4')
An instance of the class Variable
is instantiated (created). This variable represents the substance we are interested in: phosphate. Every system needs to define at least one variable since its the temporal evolution of these variables that is simulated with the boxsimu-solver.
The class Variable
has the following signature:
Variable(name, molar_mass=None, mobility=True, description=None)
In [ ]:
Line xx-xx:
lake = boxsimu.Box(
name='lake',
name_long='Little Lake',
fluid=freshwater.q(m_water),
variables=[po4.q(m_0)],
)
Line xx-xx:
inflow = boxsimu.Flow(
name='Inflow',
source_box=None,
target_box=lake,
rate=flow_rate,
tracer_transport=True,
concentrations={po4: 3e-4},
)
Line xx-xx:
outflow = boxsimu.Flow(
name='Outflow',
source_box=lake,
target_box=None,
rate=flow_rate,
tracer_transport=True,
)
The system defined in boxsimu can now easily be solved using the solve method on the BoxModelSystem instance:
In [14]:
sol = system.solve(800*ur.days, 5*ur.days)
In [10]:
sol.plot_variable_concentration_in_boxes(po4, figsize=(6,4), units=ur.kg/ur.meter**3)
In [ ]:
In [ ]:
In [29]:
from IPython.core.display import HTML
#import urllib2
#HTML( urllib2.urlopen('http://bit.ly/1Bf5Hft').read() )
HTML(open('costum_jupyter_look.css', 'r').read())
Out[29]: