Skip to main content

Framework for creating grid based simulations.

Project description

Abstract

CubeFlow is a Python framework to easily build and analyse grid based simulation such as heat transportation or fluid dynamics. CubeFlow aims to provide an easy implementation without the need of writing code other than the one needed for the actual simulation. CubeFlow is not a super fast simulator but focuses primarily on educational purposes.

Cube grid layout

#.. image:: grid_image.png

Heat flow example

Lets suppose we have a cube that is build of various materials and we want to explore the flow of heat within the structure. We therefore define a quadratic grid that divides the cube in cells of homogeneous size. Each cell is described by its coordinates, temperature (T) and thermal diffusivity (alpha). For the sake of simplicity we assume that each cell is homogeneous regarding the distribution of its mass and density. The experiment at hand can then be expressed in the form

\begin{align*} \frac{\partial T(\vec{x},t)}{\partial t} & = &\alpha(\vec{x}) \triangle T(\vec{x}, t) \\ \vec{x} \in \mathbb{R}^2, t \in \mathbb{R}^+, \alpha(\vec{x}) \in \mathbb{R}^+ \end{align*}

in the continuity. We then use the grid to build a discrete model regarding space and time where c are a cells coordinates and dt the time step of the simulation. We can then calculate the temperature delta dT and the new temperature of the cell of simulation step i by using the discrete equation

\begin{align*} \frac{dT(\vec{c}, t)_i}{dt} & = & \alpha(\vec{c}) \triangle T(\vec{c}, t)_i \\ dT(\vec{c}, t)_i & = & dt \; \alpha(\vec{c}) \triangle T(\vec{c}, t)_i \\ T_{i+1} &=& T_{i} + dT(\vec{c}, t)_i \\ \vec{c} \in \mathbb{N}_0^2, \in \mathbb{Q}^+, \alpha(\vec{c}) \in \mathbb{Q}^+ \end{align*}

Using cell coordinates m,n as positional reference instead of c the equation can be written as

\begin{align*} dT_{m,n}^{(i)} &=& dt \; \alpha_{m,n} \triangle T_{m,n}^{(i)} \\ T_{m,n}^{(i+1)} & = & T_{m,n}^{(i)} + dT_{m,n}^{(i)} \end{align*}

with the time index step written as “exponent”. Note that this is not entirely correct if the alpha values of the cells within differ, but we will ignore that for now and address this problem again in the error handling chapter. We now have to discretise the Laplace operator which is done by discretising the second derivate. We will use a discrete center derivation in this example, which leads to the following equations regarding cell coordinates m,n and cell distances dx and dy:

\begin{align*} f^{''}(x) &=& \frac{f(x+h) -2f(x)+f(x-h)}{h^2} \\ \frac{\partial^2 T_{m,n}}{\partial x^2} & \approx & \frac{T_{m+1,n} - 2 T_{m,n} + T_{m-1,n}}{dx^2} \\ \frac{\partial^2 T_{m,n}}{\partial y^2} & \approx & \frac{T_{m,n+1} - 2 T_{m,n} + T_{m,n-1}}{dy^2} \\ dx = dy &=& dl \Rightarrow \\ \frac{\partial^2 T_{m,n}}{\partial x^2} & \approx & \frac{T_{m+1,n} - 2 T_{m,n} + T_{m-1,n}}{dl^2} \\ \frac{\partial^2 T_{m,n}}{\partial y^2} & \approx & \frac{T_{m,n+1} - 2 T_{m,n} + T_{m,n-1}}{dl^2} \\ \end{align*}

Data types

Now we need a data structure for the cells of our cube in order to simulate the heat flow. CubeFlow has some builtin types to help simplifying the simulation process.

from typing import Dict, Sequence
from cubeflow.cell import MetaCell, Scalar
from cubeflow.report import Report
from cubeflow.cube import CubeGrid

class HeatCell(metaclass=MetaCell):
    temperature = Scalar()
    diffusivity = Scalar()
    flow = 0.0

By using MetaCell as metaclass we let CubeFlow take care of generating constructors and accessors. The type Scalar used for temperature and diffusivity tell MetaCell that those fields are core values of the simulation while flow is just used for storing a temporary value. We could define flow as Scalar, too if we want to track its value over the time of the simulation. Scalar types behave like floats and are output of the various reporting mechanisms of CubeFlow.

Simulation

In almost every grid based simulation the border cells of the simulation domain have to be handled in a special way. Since we want to explore the flow of heat within the cube we assume that the border cells will always have the same temperature as the adjacent inner cell in order to prevent energy transport from or into the system. We thus have two separate parts in our simulation, one regarding the borders and one regarding the inner cells.

Border handling

As each border cell should have at most one adjacent inner cell in CubeFlow, its following state is determined by the mention adjacent cell alone. Depending of the type of the border, the calculation is more or less complex. In our example we have the following equations that satisfy our conditions:

\begin{align*} T_{0,y} &= &T_{1, y} \\ T_{x_max+1,y} &= &T_{x_max, y} \\ T_{x,0} &=& T_{x, 1} \\ T_{x,y_max+1} &=& T_{x, y_max} \end{align*}

Inner cell handling

The first part of our handling of the inner cell takes care of calculating the heat flow of each cell by applying the Laplace operator regarding temperature to it while taking the thermal diffusivity into account. We will call this step the preparation step in CubeFlow. In the second part of the simulation we do the actual simulation, which means adding the calculated flow of each cell to its temperature. This step is called simulation step within CubeFlow.

We will start by generating a data type that takes care of the simulation.

from cubeflow.simulator import BaseSimulator, add_derivations, border_handler

@add_derivations
class HeatSimulator(BaseSimulator[HeatCell]):
    Type = HeatCell

The add_derivations decorator adds methods for calculating the first and second derivation as well as the gradient and laplace operator of each HeatCell member variable that is of type Scalar. We will now define a border handler method for handling all borders of type 1, which means our mentioned now-flow borders.

@border_handler(1)
def no_flow_border(self, cells: Dict[Sequence[int], HeatCell]) -> HeatCell:
    cell = cells[(0, 0)]
    adjacent = self._get_adjacent_inner(cells)
if adjacent:
    # cell is not a corner
    cell.temperature = self._get_only_cell(adjacent)[1].temperature
    return cell
else:
    return self._handle_corner(cells)

The Dict[Sequence[int], HeatCell] type represents a mapping window around the current cell which maps vectors to neighbour cells. cells[(0, 0)] represents the inner cell, while cell[(1, 0)] represents the cell right to cell. If such a cell does not exist, which is only possible for border cells, the corresponding value will be None. Almost all functions dealing with cell will take Dict[Sequence[int], HeatCell] as argument. _get_adjacent_inner returns all neighbour cells which are well defined (not None). If the second argument to get_adjacent_inner is omitted or True, only cells along the direction of the grid axis are returned. _get_only_cell is a helper function that returns the coordinates and cell of the only cell present in its argument. The border_handler(1) call tells CubeFlow that this method takes care of every border of type 1 and will be called if a border of type 1 is encountered during the handling of the border. We may define as much border handlers as we wish for every positive integer, type 0 is reserved for inner cells, though. handle_corner is used for corner cells that normally take no part of the simulation at all. For this reason, their values except their cell type are set to any of the surrounding border cells, although ignoring them is fine, too. The next function takes care of the preparation step in which we will calculate the heat flow of every cell. We will do this by overwriting the _prepare method within our simulator class.

def _prepare(self, cells: Dict[Sequence[int], HeatCell]) -> HeatCell:
    cell = cells[(0, 0)]
    cell.flow = -cell.diffusivity * self.laplace_temperature(cells)
    return cell

The _laplace_temperature method has been added to our simulator class by the application of the add_derivations decorator. A similar method _laplace_diffusivity exists although it is not used in our little example. _laplace_temperature as well as all other methods regarding differentiation take additional parameters that modify the way the summands of the denominator in the derivation equations are weighted. The default weights for the _laplace_diffusivity methods represent the center differentiation method we chose early on. The final method we will look at takes care of the actual simulation, the calculation of the cell’s temperature.

def _simulate(self, cells: Dict[Sequence[int], HeatCell]) -> HeatCell:
    cell = cells[(0, 0)]
    cell.temperature += self.dt*cell.flow
    return cell

The dt member is the current time-step of our simulation, which is 0.1 time units per default and constant throughout our simulation. A t member also exists, which represents the current time of our simulation. The last thing we have to do is defining a small entry function for our program that loads a grid from file and performs the actual simulation.

if __name__ == '__main__':
    from sys import argv
    from cubeflow.report.csv import CSVReport
    from cubeflow.predicates.counter import Counter
    simulator = HeatSimulator(CubeGrid.from_file(argv[1], HeatCell), [CSVReport('heat')])
    simulator.simulate_while(Counter(1000))

This will load a grid from command line and perform 1000 simulation steps, where each of them will be output to file heat.csv.i where i ist the current step of the simulation. simulate_while takes a predicate that may evaluate the grid and decide if another simulation step should take place. Counter of course does not do much evaluation. The second argument of the HeatSimulator constructor takes a list of Report instances that will report every simulation step. CSVReport for instance saves every step to a csv file for later analysis (e.g. by the usage of ParaView). Now we run out simulator with the sample grid provided in the Appendix of this document:

python3 heat.py sample_heat_grid.json

Stability conditions

Time-Step calculation

Creating grids

Appendix

Sample heat grid

#.. include:: sample_heat_grid.json # :code: json

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distribution

CubeFlow-0.1.5-py3-none-any.whl (21.4 kB view hashes)

Uploaded Python 3

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page