Skip to content

Commit

Permalink
#2 add cpp solution
Browse files Browse the repository at this point in the history
  • Loading branch information
martinjrobins committed Aug 31, 2019
1 parent 30e6347 commit 7b76191
Show file tree
Hide file tree
Showing 17 changed files with 609 additions and 49 deletions.
Original file line number Diff line number Diff line change
@@ -1 +1 @@
from cell_model import Simulation
from .Simulation import Simulation
15 changes: 12 additions & 3 deletions 12_c_plus_plus_and_python/practicals/cell_model/src/Functions.cpp
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
#include "Functions.hpp"

void diffusion(std::vector<double> &xn, std::vector<double> &yn,
const double dt) {}
const double dt) {

// TODO: fill this out
}
void boundaries(std::vector<double> &xn, std::vector<double> &yn,
const double dt) {}
const double dt) {

// TODO: fill this out
}
void interactions(std::vector<double> &xn, std::vector<double> &yn,
const std::vector<double> &x, const std::vector<double> &y,
const double dt, const double size) {}
const double dt, const double size) {

// TODO: fill this out
}
40 changes: 21 additions & 19 deletions 12_c_plus_plus_and_python/practicals/cell_model/src/Simulation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
PointHash::PointHash(const double size) {
m_cutoff = 3 * size;
m_sqrt_n_buckets = static_cast<int>(std::floor(1.0 / m_cutoff));
m_cutoff = 1.0 / m_sqrt_n_buckets;
}

int PointHash::total_number_of_buckets() const {
Expand All @@ -20,21 +21,23 @@ int PointHash::bucket_coordinate_to_index(const Coord &c) const {
PointHash::Coord PointHash::point_to_bucket_coordinate(const Point &p) const {
return std::make_pair(p.x / m_cutoff, p.y / m_cutoff);
}
PointHash::Coord PointHash::add_offset_periodic(const Coord &c,
const Coord &offset) const {
Coord ret = std::make_pair(c.first + offset.first, c.second + offset.second);
if (ret.first < 0) {
ret.first += number_of_buckets_along_side();
} else if (ret.first > number_of_buckets_along_side()) {
ret.first -= number_of_buckets_along_side();

PointHash::Coord PointHash::add_offset(const Coord &c,
const Coord &offset) const {
return std::make_pair(c.first + offset.first, c.second + offset.second);
}

bool PointHash::in_domain(const Coord &c) const {
bool in_domain = true;
if (c.first < 0 || c.first >= number_of_buckets_along_side()) {
in_domain = false;
}
if (ret.second < 0) {
ret.second += number_of_buckets_along_side();
} else if (ret.second > number_of_buckets_along_side()) {
ret.second -= number_of_buckets_along_side();
if (c.second < 0 || c.second >= number_of_buckets_along_side()) {
in_domain = false;
}
return ret;
return in_domain;
}

int PointHash::operator()(const Point &p) const {
const Coord &coord = point_to_bucket_coordinate(p);
const int bucket = bucket_coordinate_to_index(coord);
Expand Down Expand Up @@ -63,21 +66,17 @@ Simulation::Simulation(const std::vector<double> &x,
}
}

const std::vector<Point> &Simulation::get_positions() {
return m_next_positions;
}

void Simulation::boundaries(const double dt) {
std::transform(m_next_positions.begin(), m_next_positions.end(),
m_next_positions.begin(), [](const Point &i) {
Point ret = i;
if (ret.x < 0.0) {
ret.x = 0.0 - ret.x;
ret.x = 1.0 + ret.x;
} else if (ret.x > 1.0) {
ret.x = ret.x - 1.0;
}
if (ret.y < 0.0) {
ret.y = 0.0 - ret.y;
ret.y = 1.0 + ret.y;
} else if (ret.y > 1.0) {
ret.y = ret.y - 1.0;
}
Expand All @@ -102,7 +101,10 @@ void Simulation::interactions(const double dt) {
m_bucket_offsets.begin(), m_bucket_offsets.end(), i,
[&](const Point &sum, const auto &offset) {
const auto other_bucket_coords =
m_hash.add_offset_periodic(bucket_coords, offset);
m_hash.add_offset(bucket_coords, offset);
if (!m_hash.in_domain(other_bucket_coords)) {
return sum;
}
const int other_bucket =
m_hash.bucket_coordinate_to_index(other_bucket_coords);
return std::accumulate(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ struct PointHash {
int number_of_buckets_along_side() const;
int bucket_coordinate_to_index(const Coord &c) const;
Coord point_to_bucket_coordinate(const Point &p) const;
Coord add_offset_periodic(const Coord &c, const Coord &offset) const;
Coord add_offset(const Coord &c, const Coord &offset) const;
bool in_domain(const Coord &c) const;
int operator()(const Point &p) const;

private:
Expand All @@ -43,7 +44,6 @@ class Simulation {
const double size, const double max_dt);

void integrate(const double period);
const std::vector<Point>& get_positions();

private:
void boundaries(const double dt);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,4 @@
#include "Functions.hpp"
#include "Simulation.hpp"
#include <pybind11/pybind11.h>
#include <pybind11/stl_bind.h>

namespace py = pybind11;

PYBIND11_MAKE_OPAQUE(std::vector<double>);
PYBIND11_MAKE_OPAQUE(std::vector<Point>);

PYBIND11_MODULE(cell_model_cpp, m) {

py::bind_vector<std::vector<double>>(m, "VectorDouble");
py::bind_vector<std::vector<Point>>(m, "VectorPoint");

py::class_<Point>(m, "Point")
.def(py::init<>())
.def(py::init<const double, const double>())
.def_readwrite("x", &Point::x)
.def_readwrite("y", &Point::y);

py::class_<Simulation>(m, "Simulation")
.def(py::init<const std::vector<double> &, const std::vector<double> &,
const double, const double>())
.def("integrate", &Simulation::integrate)
.def("get_positions", &Simulation::get_positions);
}
// write your python wrapper code here
33 changes: 33 additions & 0 deletions 12_c_plus_plus_and_python/practicals/solution/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
cmake_minimum_required(VERSION 3.1)

project(cell_model_cpp)

set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS 1 )

# OpenMP
find_package(OpenMP REQUIRED)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${OpenMP_EXE_LINKER_FLAGS}")

# Pybind11
add_subdirectory(pybind11 ${CMAKE_BINARY_DIR}/pybind11)

set(source_dir "src")

set(source_files
${source_dir}/Simulation.cpp
${source_dir}/Functions.cpp
${source_dir}/python_wrapper.cpp
)

set(header_files
${source_dir}/Simulation.hpp
${source_dir}/Functions.hpp
)


pybind11_add_module(cell_model_cpp ${source_files} ${header_files})
target_link_libraries(cell_model_cpp PRIVATE ${libs})
target_include_directories(cell_model_cpp PRIVATE ${source_dir})
9 changes: 9 additions & 0 deletions 12_c_plus_plus_and_python/practicals/solution/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Instructions

```bash
git clone https://github.com/pybind/pybind11.git
python3 -m venv env
source env/bin/activate
pip install -e .
python simulate.py
```
113 changes: 113 additions & 0 deletions 12_c_plus_plus_and_python/practicals/solution/cell_model/Simulation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import numpy as np

class Simulation:
def __init__(self, x, 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.empty_like(x)
self.yn = np.empty_like(y)

self.size = size

self.calculate_interactions = False

def boundaries(self, 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
"""
self.xn = np.where(self.xn < 0.0, - self.xn, self.xn)
self.xn = np.where(self.xn > 1.0, self.xn - 1.0, self.xn)
self.yn = np.where(self.yn < 0.0, - self.yn, self.yn)
self.yn = np.where(self.yn > 1.0, self.yn - 1.0, self.yn)

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

def interactions(self, 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
"""
n = len(self.x)
dx = self.x.reshape((1, n)) - self.x.reshape((n, 1))
dy = self.y.reshape((1, n)) - self.y.reshape((n, 1))
r = np.sqrt(dx**2 + dy**2)
self.xn += np.nansum((dt/self.size) * np.exp(-r/self.size) * dx / r, axis=1)
self.yn += np.nansum((dt/self.size) * np.exp(-r/self.size) * dy / r, axis=1)

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.
"""
self.xn[:] = self.x
self.yn[:] = self.y

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
@@ -0,0 +1 @@
from .Simulation import Simulation
Loading

0 comments on commit 7b76191

Please sign in to comment.