Skip to content

Commit

Permalink
#2 move some of the material from day 12 to 13, and add cython soluti…
Browse files Browse the repository at this point in the history
…on for day 12
  • Loading branch information
martinjrobins committed Sep 18, 2019
1 parent 0111caf commit 1d16da2
Show file tree
Hide file tree
Showing 36 changed files with 826 additions and 222 deletions.
1 change: 0 additions & 1 deletion 12_c_plus_plus_and_python/practicals/cell_model/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# Instructions

```bash
git clone https://github.com/pybind/pybind11.git
python3 -m venv env
source env/bin/activate
pip install -e .
Expand Down
77 changes: 1 addition & 76 deletions 12_c_plus_plus_and_python/practicals/cell_model/setup.py
Original file line number Diff line number Diff line change
@@ -1,88 +1,13 @@
import os
import re
import sys
import platform
import subprocess
from setuptools import setup, find_packages

from setuptools import setup, find_packages, Extension
from setuptools.command.build_ext import build_ext
from setuptools import find_packages
from distutils.version import LooseVersion


class CMakeExtension(Extension):

def __init__(self, name, sourcedir=''):
Extension.__init__(self, name, sources=[])
self.sourcedir = os.path.abspath(sourcedir)


class CMakeBuild(build_ext):

def run(self):
try:
out = subprocess.check_output(['cmake', '--version'])
except OSError:
raise RuntimeError("CMake must be installed to build the following extensions: " +
", ".join(e.name for e in self.extensions))

if platform.system() == "Windows":
cmake_version = LooseVersion(
re.search(r'version\s*([\d.]+)', out.decode()).group(1))
if cmake_version < '3.1.0':
raise RuntimeError("CMake >= 3.1.0 is required on Windows")

for ext in self.extensions:
self.build_extension(ext)

def build_extension(self, ext):
extdir = os.path.abspath(
os.path.dirname(self.get_ext_fullpath(ext.name)))
cmake_args = ['-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=' + extdir,
'-DPYTHON_EXECUTABLE=' + sys.executable]

cfg = 'Debug' if self.debug else 'Release'
build_args = ['--config', cfg]

if platform.system() == "Windows":
cmake_args += [
'-DCMAKE_LIBRARY_OUTPUT_DIRECTORY_{}={}'.format(cfg.upper(), extdir)]
if sys.maxsize > 2**32:
cmake_args += ['-A', 'x64']
build_args += ['--', '/m']
else:
cmake_args += ['-DCMAKE_BUILD_TYPE=' + cfg]
build_args += ['--', '-j2']

env = os.environ.copy()
env['CXXFLAGS'] = '{} -DVERSION_INFO=\\"{}\\"'.format(env.get('CXXFLAGS', ''),
self.distribution.get_version())
if not os.path.exists(self.build_temp):
os.makedirs(self.build_temp)
subprocess.check_call(
['cmake', ext.sourcedir] + cmake_args, cwd=self.build_temp, env=env)
subprocess.check_call(
['cmake', '--build', '.'] + build_args, cwd=self.build_temp)


# Load text for description and license
with open('README.md') as f:
readme = f.read()

# Go!
setup(
name='cell_model',
version='0.0.1',
description='cell diffusion and excluded volume model',
long_description=readme,
license='BSD 3-clause license',
maintainer='Martin Robinson',
maintainer_email='[email protected]',
# Packages to include
packages=find_packages(include=('cell_model')),
ext_modules=[CMakeExtension('cell_model_cpp',sourcedir='.')],
cmdclass=dict(build_ext=CMakeBuild),
# List of dependencies
install_requires=[
'numpy',
'matplotlib',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
sim = cell_model.Simulation(x, y, size, max_dt)

# Set to True to include excluded volume interactions
sim.calculate_interactions = False
sim.calculate_interactions = True

# profile a single call to integrate
cProfile.run('sim.integrate(integrate_time)', sort='cumulative')
Expand Down
15 changes: 9 additions & 6 deletions 12_c_plus_plus_and_python/practicals/practical.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@ link-citations: true

# Cell model

This practical uses a model of cell diffusion and excluded volume interaction. It is not
required that you understand the details of the model, as it is implemented for you in
This practical uses a model of cell diffusion and excluded volume interaction. A brief
description of the model is given below, although it is not required that you understand
the details of the model, as it is implemented for you in
the python module contain in the `cell_model` directory. The purpose of this practical
is to profile and increase the efficiency of the model using numpy vectorisation and
C++. The details of the model are provided below.
C++.

The model consists of a set of $N$ cells in a periodic unit square domain. Let
$\bfX_i(t)$ denote the position of the $i$th particle in $\Omega \subset \mathbb R^2$.
Expand All @@ -34,8 +35,8 @@ gradient with respect to $\bfX_i$. The interaction potential $u$ may be a soft p
incorporating effects such as size exclusion by cells and cell-cell adhesion. In this
case we use the soft exponential potential.

The standard way to numerically integrate the SDE is to use a fixed time-step $\Delta t$
and a Euler--Maruyama discretisation, resulting in the time-stepping scheme:
The simple method to numerically integrate the SDE is to use a fixed time-step $\Delta
t$ and a Euler--Maruyama discretisation, resulting in the time-stepping scheme:

$$
\bfX_i(t+ \Delta t) = \bfX_i(t) + \sqrt{2D_\alpha \Delta t} \xi_i - \sum_{j\ne i} \nabla_i u(\| \bfX_i(t)
Expand All @@ -45,7 +46,6 @@ $$
where $\xi_i$ is a two-dimensional normally distributed random variable with zero mean
and unit variance.


# Numpy Vectorisation


Expand All @@ -67,6 +67,9 @@ and unit variance.
the memory cost and how does it scale with the number of cells $N$? How
many particles can you simulate before your computer runs out of RAM?

# Cython



# Wrapping C++

Expand Down
2 changes: 2 additions & 0 deletions 12_c_plus_plus_and_python/practicals/solution/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ pybind11
*.egg-info
*.so
cell_model/__pycache__
cell_model/*.c
cell_model/*.html
1 change: 0 additions & 1 deletion 12_c_plus_plus_and_python/practicals/solution/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# Instructions

```bash
git clone https://github.com/pybind/pybind11.git
python3 -m venv env
source env/bin/activate
pip install -e .
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import numpy as np

from libc.math cimport exp, sqrt
cimport cython

@cython.boundscheck(False) # Deactivate bounds checking
@cython.wraparound(False) # Deactivate negative indexing
@cython.cdivision(True) # Deactivate normal python division checking
@cython.initializedcheck(False) # Deactivate memoryview init checking
cdef class SimulationCython:

cdef public double[:] x
cdef public double[:] y
cdef double[:] xn
cdef double[:] yn
cdef double max_dt
cdef double size
cdef public int calculate_interactions

def __init__(self, double[:] x, double[:] y, size, max_dt):
"""
Creates a new simulation objects that implements the cell model with diffusion
and excluded volume interactions. Cells are defined on a unit square domain and
periodic boundary condtions are implemented
Parameters
----------
x: np.ndarray
array of x positions of the cells
y: np.ndarray
array of y positions of the cells. Must be same length as x
size: float
size of cells
max_dt: float
maximum timestep for the simulation
"""
self.x = x
self.y = y
self.max_dt = max_dt

self.xn = np.copy(x)
self.yn = np.copy(y)

self.size = size

self.calculate_interactions = False

def boundaries(self, double dt):
"""
Any cells that are over the boundary of the domain are translated to the
opposite side of the domain
Updates self.xn and self.yn with the new position of the cells
"""
for i in range(len(self.x)):
if self.xn[i] < 0.0:
self.xn[i] = 0.0 - self.xn[i]
elif self.xn[i] > 1.0:
self.xn[i] = self.xn[i] - 1.0
if self.yn[i] < 0.0:
self.yn[i] = 0.0 - self.yn[i]
elif self.yn[i] > 1.0:
self.yn[i] = self.yn[i] - 1.0

def diffusion(self, double dt):
"""
Perform a diffusion step for all cells
Updates self.xn and self.yn with the new position of the cells
"""
for i in range(len(self.x)):
self.xn[i] += np.sqrt(2.0 * dt) * np.random.randn()
self.yn[i] += np.sqrt(2.0 * dt) * np.random.randn()

def interactions(self, double dt):
"""
Calculates the pairwise interactions between cells, using a soft exponential
repulsive force
Uses self.x and self.y as the current positions of the cells
Updates self.xn and self.yn with the new position of the cells
"""
cdef double dx
cdef double dy
cdef double r
cdef double dp
cdef int n = len(self.x)

for i in range(n):
for j in range(n):
dx = self.x[i] - self.x[j]
dy = self.y[i] - self.y[j]
r = sqrt(dx**2 + dy**2)
if r > 0.0:
dp = (dt/self.size) * exp(-r/self.size) / r
self.xn[i] += dp*dx
self.yn[i] += dp*dy

def step(self, dt):
"""
Perform a single time step for the simulation
First the current positions of the cells are written to self.xn and self.yn,
which will now represent the "next" position of the cells after the current
time-step
The self.interactions, self.diffusion and self.boundaries functions update the
"next" position of the cells according to the cell-cell excluded volume
interactions, the diffusion step and the boundaries respectivly
Finally, the current position of the cells is set to the calculated "next"
position, and the simulation is ready for a new time-step.
"""

if self.calculate_interactions:
self.interactions(dt)
self.diffusion(dt)
self.boundaries(dt)

self.x[:] = self.xn
self.y[:] = self.yn

def integrate(self, period):
"""
integrate over a time period given by period (float).
"""

n = int(np.floor(period / self.max_dt))
print('integrating for {} steps'.format(n+1))
for i in range(n):
self.step(self.max_dt)
final_dt = period - self.max_dt*n
if final_dt > 0:
self.step(final_dt)



Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
from .Simulation import Simulation
from .SimulationCython import SimulationCython
Loading

0 comments on commit 1d16da2

Please sign in to comment.