From 30e6347caa1c25926b4cd25b52055005da3687b8 Mon Sep 17 00:00:00 2001 From: Martin Robinson Date: Fri, 30 Aug 2019 08:38:56 +0100 Subject: [PATCH] add cpp simulation --- .../practicals/cell_model/.clang-format | 90 +++++++++++ .../practicals/cell_model/CMakeLists.txt | 33 ++++ .../practicals/cell_model/README.md | 5 +- .../Simulation.py} | 64 +------- .../cell_model/cell_model/__init__.py | 1 + .../practicals/cell_model/requirements.txt | 2 - .../practicals/cell_model/setup.py | 91 +++++++++++ .../practicals/cell_model/simulate.py | 61 ++++++++ .../practicals/cell_model/simulate_cpp.py | 57 +++++++ .../practicals/cell_model/src/Functions.cpp | 9 ++ .../practicals/cell_model/src/Functions.hpp | 13 ++ .../practicals/cell_model/src/Simulation.cpp | 147 ++++++++++++++++++ .../practicals/cell_model/src/Simulation.hpp | 64 ++++++++ .../cell_model/src/python_wrapper.cpp | 27 ++++ .../practicals/excluded_volume_diffusion.py | 133 ---------------- .../practicals/practical.md | 2 +- .../practicals/requirements.txt | 2 - 17 files changed, 599 insertions(+), 202 deletions(-) create mode 100644 12_c_plus_plus_and_python/practicals/cell_model/.clang-format create mode 100644 12_c_plus_plus_and_python/practicals/cell_model/CMakeLists.txt rename 12_c_plus_plus_and_python/practicals/cell_model/{cell_model.py => cell_model/Simulation.py} (66%) create mode 100644 12_c_plus_plus_and_python/practicals/cell_model/cell_model/__init__.py delete mode 100644 12_c_plus_plus_and_python/practicals/cell_model/requirements.txt create mode 100644 12_c_plus_plus_and_python/practicals/cell_model/setup.py create mode 100644 12_c_plus_plus_and_python/practicals/cell_model/simulate.py create mode 100644 12_c_plus_plus_and_python/practicals/cell_model/simulate_cpp.py create mode 100644 12_c_plus_plus_and_python/practicals/cell_model/src/Functions.cpp create mode 100644 12_c_plus_plus_and_python/practicals/cell_model/src/Functions.hpp create mode 100644 12_c_plus_plus_and_python/practicals/cell_model/src/Simulation.cpp create mode 100644 12_c_plus_plus_and_python/practicals/cell_model/src/Simulation.hpp create mode 100644 12_c_plus_plus_and_python/practicals/cell_model/src/python_wrapper.cpp delete mode 100644 12_c_plus_plus_and_python/practicals/excluded_volume_diffusion.py delete mode 100644 12_c_plus_plus_and_python/practicals/requirements.txt diff --git a/12_c_plus_plus_and_python/practicals/cell_model/.clang-format b/12_c_plus_plus_and_python/practicals/cell_model/.clang-format new file mode 100644 index 0000000..3a5940e --- /dev/null +++ b/12_c_plus_plus_and_python/practicals/cell_model/.clang-format @@ -0,0 +1,90 @@ +--- +Language: Cpp +# BasedOnStyle: LLVM +AccessModifierOffset: -2 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlinesLeft: false +AlignOperands: true +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: All +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: false +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Attach +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +ColumnLimit: 80 +CommentPragmas: '^ IWYU pragma:' +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] +IncludeCategories: + - Regex: '^"(llvm|llvm-c|clang|clang-c)/' + Priority: 2 + - Regex: '^(<|"(gtest|isl|json)/)' + Priority: 3 + - Regex: '.*' + Priority: 1 +IndentCaseLabels: false +IndentWidth: 2 +IndentWrappedFunctionNames: false +KeepEmptyLinesAtTheStartOfBlocks: true +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBlockIndentWidth: 2 +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 60 +PointerAlignment: Right +ReflowComments: true +SortIncludes: true +SpaceAfterCStyleCast: false +SpaceBeforeAssignmentOperators: true +SpaceBeforeParens: ControlStatements +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Cpp11 +TabWidth: 8 +UseTab: Never +... + diff --git a/12_c_plus_plus_and_python/practicals/cell_model/CMakeLists.txt b/12_c_plus_plus_and_python/practicals/cell_model/CMakeLists.txt new file mode 100644 index 0000000..33f8c41 --- /dev/null +++ b/12_c_plus_plus_and_python/practicals/cell_model/CMakeLists.txt @@ -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}) diff --git a/12_c_plus_plus_and_python/practicals/cell_model/README.md b/12_c_plus_plus_and_python/practicals/cell_model/README.md index f0030cf..dbd811f 100644 --- a/12_c_plus_plus_and_python/practicals/cell_model/README.md +++ b/12_c_plus_plus_and_python/practicals/cell_model/README.md @@ -1,8 +1,9 @@ # Instructions ```bash +git clone https://github.com/pybind/pybind11.git python3 -m venv env source env/bin/activate -pip install -r requirements.txt -python cell_model.py +pip install -e . +python simulate.py ``` diff --git a/12_c_plus_plus_and_python/practicals/cell_model/cell_model.py b/12_c_plus_plus_and_python/practicals/cell_model/cell_model/Simulation.py similarity index 66% rename from 12_c_plus_plus_and_python/practicals/cell_model/cell_model.py rename to 12_c_plus_plus_and_python/practicals/cell_model/cell_model/Simulation.py index ce9af32..87e7fd1 100644 --- a/12_c_plus_plus_and_python/practicals/cell_model/cell_model.py +++ b/12_c_plus_plus_and_python/practicals/cell_model/cell_model/Simulation.py @@ -1,9 +1,4 @@ import numpy as np -import matplotlib.pyplot as plt -import matplotlib -import time -import cProfile - class Simulation: def __init__(self, x, y, size, max_dt): @@ -32,8 +27,8 @@ def __init__(self, x, y, size, max_dt): self.y = y self.max_dt = max_dt - self.xn = np.empty_like(x) - self.yn = np.empty_like(y) + self.xn = np.copy(x) + self.yn = np.copy(y) self.size = size @@ -100,8 +95,6 @@ def step(self, dt): 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) @@ -125,57 +118,4 @@ def integrate(self, period): self.step(final_dt) -if __name__ == "__main__": - - # np.seterr(divide='ignore', invalid='ignore') - - # number of cells - n = 100 - - # mean and standard deviation of the initial positions of the cells - mu, sigma = 0.5, 0.05 - - # set the maximum timestep allowed as fraction of the average diffusion step - timestep_ratio = 0.23 - size = 0.02 - max_dt = (0.23 * size)**2 / 4.0 - - # end time for the simulation - end_time = 0.01 - - # number of output steps - nout = 10 - integrate_time = end_time/nout - - # create simulation - x = np.random.normal(mu, sigma, n) - y = np.random.normal(mu, sigma, n) - sim = Simulation(x, y, size, max_dt) - - # Set to True to include excluded volume interactions - sim.calculate_interactions = False - - # profile a single call to integrate - cProfile.run('sim.integrate(integrate_time)', sort='cumulative') - - # now run the main simulation loop and visualise the positions of the cells at each - # output step - time_for_simulation = 0.0 - f = plt.figure() - for i in range(nout): - # increment simulation - start_time = time.perf_counter() - sim.integrate(integrate_time) - end_time = time.perf_counter() - time_for_simulation += end_time - start_time - - # plot - f.clear() - circles = [plt.Circle((xi,yi), radius=size) for xi,yi in - zip(sim.x,sim.y)] - c = matplotlib.collections.PatchCollection(circles) - f.gca().add_collection(c) - f.savefig('cells_{}.png'.format(i)) - print('finished simulation, time taken (excluding plotting) was {}'.format( - time_for_simulation)) diff --git a/12_c_plus_plus_and_python/practicals/cell_model/cell_model/__init__.py b/12_c_plus_plus_and_python/practicals/cell_model/cell_model/__init__.py new file mode 100644 index 0000000..262d103 --- /dev/null +++ b/12_c_plus_plus_and_python/practicals/cell_model/cell_model/__init__.py @@ -0,0 +1 @@ +from cell_model import Simulation diff --git a/12_c_plus_plus_and_python/practicals/cell_model/requirements.txt b/12_c_plus_plus_and_python/practicals/cell_model/requirements.txt deleted file mode 100644 index aa094d9..0000000 --- a/12_c_plus_plus_and_python/practicals/cell_model/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -numpy -matplotlib diff --git a/12_c_plus_plus_and_python/practicals/cell_model/setup.py b/12_c_plus_plus_and_python/practicals/cell_model/setup.py new file mode 100644 index 0000000..942f15a --- /dev/null +++ b/12_c_plus_plus_and_python/practicals/cell_model/setup.py @@ -0,0 +1,91 @@ +import os +import re +import sys +import platform +import subprocess + +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='martin.robinson@cs.ox.ac.uk', + # 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', + ], +) + diff --git a/12_c_plus_plus_and_python/practicals/cell_model/simulate.py b/12_c_plus_plus_and_python/practicals/cell_model/simulate.py new file mode 100644 index 0000000..a4a7e7c --- /dev/null +++ b/12_c_plus_plus_and_python/practicals/cell_model/simulate.py @@ -0,0 +1,61 @@ +import matplotlib.pyplot as plt +import matplotlib +import numpy as np +import time +import cProfile +import cell_model + +if __name__ == "__main__": + + # np.seterr(divide='ignore', invalid='ignore') + + # number of cells + n = 100 + + # mean and standard deviation of the initial positions of the cells + mu, sigma = 0.5, 0.05 + + # set the maximum timestep allowed as fraction of the average diffusion step + timestep_ratio = 0.23 + size = 0.02 + max_dt = (0.23 * size)**2 / 4.0 + + # end time for the simulation + end_time = 0.01 + + # number of output steps + nout = 10 + integrate_time = end_time/nout + + # create simulation + x = np.random.normal(mu, sigma, n) + y = np.random.normal(mu, sigma, n) + sim = cell_model.Simulation(x, y, size, max_dt) + + # Set to True to include excluded volume interactions + sim.calculate_interactions = False + + # profile a single call to integrate + cProfile.run('sim.integrate(integrate_time)', sort='cumulative') + + # now run the main simulation loop and visualise the positions of the cells at each + # output step + time_for_simulation = 0.0 + f = plt.figure() + for i in range(nout): + # increment simulation + start_time = time.perf_counter() + sim.integrate(integrate_time) + end_time = time.perf_counter() + time_for_simulation += end_time - start_time + + # plot + f.clear() + circles = [plt.Circle((xi,yi), radius=size) for xi,yi in + zip(sim.x,sim.y)] + c = matplotlib.collections.PatchCollection(circles) + f.gca().add_collection(c) + f.savefig('cells_{}.png'.format(i)) + + print('finished simulation, time taken (excluding plotting) was {}'.format( + time_for_simulation)) diff --git a/12_c_plus_plus_and_python/practicals/cell_model/simulate_cpp.py b/12_c_plus_plus_and_python/practicals/cell_model/simulate_cpp.py new file mode 100644 index 0000000..ffce0b5 --- /dev/null +++ b/12_c_plus_plus_and_python/practicals/cell_model/simulate_cpp.py @@ -0,0 +1,57 @@ +import matplotlib.pyplot as plt +import matplotlib +import numpy as np +import time +import cProfile +import cell_model_cpp + +if __name__ == "__main__": + + # np.seterr(divide='ignore', invalid='ignore') + + # number of cells + n = 100 + + # mean and standard deviation of the initial positions of the cells + mu, sigma = 0.5, 0.05 + + # set the maximum timestep allowed as fraction of the average diffusion step + timestep_ratio = 0.23 + size = 0.02 + max_dt = (0.23 * size)**2 / 4.0 + + # end time for the simulation + end_time = 0.01 + + # number of output steps + nout = 10 + integrate_time = end_time/nout + + # create simulation + x = cell_model_cpp.VectorDouble(np.random.normal(mu, sigma, n)) + y = cell_model_cpp.VectorDouble(np.random.normal(mu, sigma, n)) + sim = cell_model_cpp.Simulation(x, y, size, max_dt) + + # profile a single call to integrate + cProfile.run('sim.integrate(integrate_time)', sort='cumulative') + + # now run the main simulation loop and visualise the positions of the cells at each + # output step + time_for_simulation = 0.0 + f = plt.figure() + for i in range(nout): + # increment simulation + start_time = time.perf_counter() + sim.integrate(integrate_time) + end_time = time.perf_counter() + time_for_simulation += end_time - start_time + + # plot + f.clear() + circles = [plt.Circle((p.x,p.y), radius=size) for p in sim.get_positions()] + c = matplotlib.collections.PatchCollection(circles) + f.gca().add_collection(c) + f.savefig('cells_{}.png'.format(i)) + + print('finished simulation, time taken (excluding plotting) was {}'.format( + time_for_simulation)) diff --git a/12_c_plus_plus_and_python/practicals/cell_model/src/Functions.cpp b/12_c_plus_plus_and_python/practicals/cell_model/src/Functions.cpp new file mode 100644 index 0000000..31421d3 --- /dev/null +++ b/12_c_plus_plus_and_python/practicals/cell_model/src/Functions.cpp @@ -0,0 +1,9 @@ +#include "Functions.hpp" + +void diffusion(std::vector &xn, std::vector &yn, + const double dt) {} +void boundaries(std::vector &xn, std::vector &yn, + const double dt) {} +void interactions(std::vector &xn, std::vector &yn, + const std::vector &x, const std::vector &y, + const double dt, const double size) {} diff --git a/12_c_plus_plus_and_python/practicals/cell_model/src/Functions.hpp b/12_c_plus_plus_and_python/practicals/cell_model/src/Functions.hpp new file mode 100644 index 0000000..a1b76ed --- /dev/null +++ b/12_c_plus_plus_and_python/practicals/cell_model/src/Functions.hpp @@ -0,0 +1,13 @@ +#ifndef CELL_MODEL_FUNCTIONS +#define CELL_MODEL_FUNCTIONS + +#include + +void diffusion(std::vector& xn, std::vector& yn, const double dt); +void boundaries(std::vector& xn, std::vector& yn, const double dt); +void interactions(std::vector& xn, std::vector& yn, + const std::vector& x, const std::vector& y, + const double dt, const double size); + + +#endif diff --git a/12_c_plus_plus_and_python/practicals/cell_model/src/Simulation.cpp b/12_c_plus_plus_and_python/practicals/cell_model/src/Simulation.cpp new file mode 100644 index 0000000..4ac1e92 --- /dev/null +++ b/12_c_plus_plus_and_python/practicals/cell_model/src/Simulation.cpp @@ -0,0 +1,147 @@ +#include "Simulation.hpp" +#include +#include +#include +#include + +PointHash::PointHash(const double size) { + m_cutoff = 3 * size; + m_sqrt_n_buckets = static_cast(std::floor(1.0 / m_cutoff)); +} + +int PointHash::total_number_of_buckets() const { + return std::pow(m_sqrt_n_buckets, 2); +} +int PointHash::number_of_buckets_along_side() const { return m_sqrt_n_buckets; } + +int PointHash::bucket_coordinate_to_index(const Coord &c) const { + return c.second * m_sqrt_n_buckets + c.first; +} +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(); + } + 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(); + } + return ret; +} +int PointHash::operator()(const Point &p) const { + const Coord &coord = point_to_bucket_coordinate(p); + const int bucket = bucket_coordinate_to_index(coord); + assert(bucket < std::pow(m_sqrt_n_buckets, 2)); + assert(bucket > 0); + return bucket; +} + +Simulation::Simulation(const std::vector &x, + const std::vector &y, const double size, + const double max_dt) + : m_size(size), m_max_dt(max_dt), m_hash(size), + m_positions(m_hash.total_number_of_buckets(), m_hash) { + + for (int i = 0; i < x.size(); ++i) { + m_positions.insert(Point(x[i], y[i])); + } + + m_next_positions.resize(m_positions.size()); + std::copy(m_positions.begin(), m_positions.end(), m_next_positions.begin()); + + for (int i = -1; i <= 1; i++) { + for (int j = -1; j <= 1; j++) { + m_bucket_offsets.push_back(std::make_pair(i, j)); + } + } +} + +const std::vector &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; + } else if (ret.x > 1.0) { + ret.x = ret.x - 1.0; + } + if (ret.y < 0.0) { + ret.y = 0.0 - ret.y; + } else if (ret.y > 1.0) { + ret.y = ret.y - 1.0; + } + return ret; + }); +} +void Simulation::diffusion(const double dt) { + std::transform(m_next_positions.begin(), m_next_positions.end(), + m_next_positions.begin(), [&](const Point &i) { + const double c = std::sqrt(2.0 * dt); + return Point(i.x + c * m_normal(m_generator), + i.y + c * m_normal(m_generator)); + }); +} + +void Simulation::interactions(const double dt) { + std::transform( + m_next_positions.begin(), m_next_positions.end(), + m_next_positions.begin(), [&](const Point &i) { + const auto bucket_coords = m_hash.point_to_bucket_coordinate(i); + return std::accumulate( + 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); + const int other_bucket = + m_hash.bucket_coordinate_to_index(other_bucket_coords); + return std::accumulate( + m_positions.begin(other_bucket), + m_positions.end(other_bucket), sum, + [&](const Point &sum, const Point &j) { + Point ret = sum; + const double dx_x = i.x - j.x; + const double dx_y = i.y - j.y; + const double r = + std::sqrt(std::pow(dx_x, 2) + std::pow(dx_y, 2)); + if (r > 0.0) { + const double tmp = + (dt / m_size) * std::exp(-r / m_size) / r; + ret.x += tmp * dx_x; + ret.y += tmp * dx_y; + } + return ret; + }); + }); + }); +} + +void Simulation::step(const double dt) { + interactions(dt); + diffusion(dt); + boundaries(dt); + + m_positions.clear(); + m_positions.insert(m_next_positions.begin(), m_next_positions.end()); +} +void Simulation::integrate(const double period) { + const int n = static_cast(std::floor(period / m_max_dt)); + std::cout << "integrating for " << n + 1 << " steps" << std::endl; + for (int i = 0; i < n; ++i) { + step(m_max_dt); + } + const double final_dt = period - m_max_dt * n; + if (final_dt > 0) { + step(final_dt); + } +} diff --git a/12_c_plus_plus_and_python/practicals/cell_model/src/Simulation.hpp b/12_c_plus_plus_and_python/practicals/cell_model/src/Simulation.hpp new file mode 100644 index 0000000..4587012 --- /dev/null +++ b/12_c_plus_plus_and_python/practicals/cell_model/src/Simulation.hpp @@ -0,0 +1,64 @@ +#ifndef CELL_MODEL_SIMULATION +#define CELL_MODEL_SIMULATION + +#include +#include +#include + +#include "Functions.hpp" + +class Point { +public: + Point() : x(0.0), y(0.0) {} + Point(const double x, const double y) : x(x), y(y) {} + + bool operator==(const Point &other) const { + return other.x == x && other.y == y; + } + + double x; + double y; +}; + +struct PointHash { +public: + using Coord = std::pair; + PointHash(const double size); + + int total_number_of_buckets() const; + 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; + int operator()(const Point &p) const; + +private: + int m_sqrt_n_buckets; + double m_cutoff; +}; + +class Simulation { +public: + Simulation(const std::vector &x, const std::vector &y, + const double size, const double max_dt); + + void integrate(const double period); + const std::vector& get_positions(); + +private: + void boundaries(const double dt); + void diffusion(const double dt); + void interactions(const double dt); + void step(const double dt); + + std::default_random_engine m_generator; + std::normal_distribution m_normal; + double m_size; + double m_max_dt; + PointHash m_hash; + std::vector> m_bucket_offsets; + std::unordered_set m_positions; + std::vector m_next_positions; +}; + +#endif diff --git a/12_c_plus_plus_and_python/practicals/cell_model/src/python_wrapper.cpp b/12_c_plus_plus_and_python/practicals/cell_model/src/python_wrapper.cpp new file mode 100644 index 0000000..993c57b --- /dev/null +++ b/12_c_plus_plus_and_python/practicals/cell_model/src/python_wrapper.cpp @@ -0,0 +1,27 @@ +#include "Functions.hpp" +#include "Simulation.hpp" +#include +#include + +namespace py = pybind11; + +PYBIND11_MAKE_OPAQUE(std::vector); +PYBIND11_MAKE_OPAQUE(std::vector); + +PYBIND11_MODULE(cell_model_cpp, m) { + + py::bind_vector>(m, "VectorDouble"); + py::bind_vector>(m, "VectorPoint"); + + py::class_(m, "Point") + .def(py::init<>()) + .def(py::init()) + .def_readwrite("x", &Point::x) + .def_readwrite("y", &Point::y); + + py::class_(m, "Simulation") + .def(py::init &, const std::vector &, + const double, const double>()) + .def("integrate", &Simulation::integrate) + .def("get_positions", &Simulation::get_positions); +} diff --git a/12_c_plus_plus_and_python/practicals/excluded_volume_diffusion.py b/12_c_plus_plus_and_python/practicals/excluded_volume_diffusion.py deleted file mode 100644 index 575ef04..0000000 --- a/12_c_plus_plus_and_python/practicals/excluded_volume_diffusion.py +++ /dev/null @@ -1,133 +0,0 @@ -import numpy as np -import matplotlib.pyplot as plt -import time -import cProfile - -class Simulation: - def __init__(self, x, y, size, max_dt): - 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): - # (b) vectorise using np.where - #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 - - 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): - # (a) trivial vectorise - #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() - - 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): - # (c) vectorise with a n^2 dx matrix - #for i in range(len(self.x)): - # for j in range(len(self.y)): - # dx = np.array([self.x[i] - self.x[j], self.y[i] - self.y[j]]) - # r = np.sqrt(np.sum(dx**2)) - # if r > 0.0: - # dp = -(dt/self.size) * np.exp(-r/self.size) * dx / r - # self.xn[i] += dp[0] - # self.yn[i] += dp[1] - - #for i in range(len(self.x)): - # dx = np.array([self.x[i] - self.x, self.y[i] - self.y]) - # r = np.sqrt(np.sum(dx**2, axis=0)) - # dp = -(dt/self.size) * np.exp(-r/self.size) * dx / r - # step = np.sum(np.nan_to_num(dp, copy=False), axis=1) - # self.xn[i] += step[0] - # self.yn[i] += step[1] - - 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): - """ single 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 """ - - 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) - - -if __name__ == "__main__": - - # np.seterr(divide='ignore', invalid='ignore') - - # setup - n = 100 - mu, sigma = 0.5, 0.1 # mean and standard deviation - x = np.random.normal(mu, sigma, n) - y = np.random.normal(mu, sigma, n) - timestep_ratio = 0.23 - size = 0.02 - max_dt = (0.23 * size)**2 / 4.0 - end_time = 0.01 - nout = 10 - integrate_time = end_time/nout - max_dt - sim = Simulation(x, y, size, max_dt) - sim.calculate_interactions = True - - # profile the simulation - cProfile.run('sim.integrate(integrate_time)', sort='cumulative') - - # run the main simulation loop - time_for_simulation = 0.0 - f = plt.figure() - for i in range(nout): - start_time = time.perf_counter() - sim.integrate(integrate_time) - end_time = time.perf_counter() - time_for_simulation += end_time - start_time - f.clear() - plt.scatter(sim.x, sim.y, sim.size) - f.savefig('excluded_volume_visualisation{}.pdf'.format(i)) - print('finished simulation, time taken (excluding plotting) was {}'.format(time_for_simulation)) diff --git a/12_c_plus_plus_and_python/practicals/practical.md b/12_c_plus_plus_and_python/practicals/practical.md index 9b26437..cdc5646 100755 --- a/12_c_plus_plus_and_python/practicals/practical.md +++ b/12_c_plus_plus_and_python/practicals/practical.md @@ -97,7 +97,7 @@ void interactions(std::vector& xn, std::vector& yn, ``` 1. Write a pybind11 wrapper for `interactions` and use it in your python code. Measure - the speed-up obtained and take a note of the RAM usage scales with larger $N$. + the speed-up obtained and take a note of how the RAM usage scales with larger $N$. 1. Your computational cost is no longer bounded by RAM, but by the cost of evaluating $N^2$ interactions. There are many different ways of speeding this up in C++, one of diff --git a/12_c_plus_plus_and_python/practicals/requirements.txt b/12_c_plus_plus_and_python/practicals/requirements.txt deleted file mode 100644 index aa094d9..0000000 --- a/12_c_plus_plus_and_python/practicals/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -numpy -matplotlib