diff --git a/.gitignore b/.gitignore index dac0d1e8..33878298 100644 --- a/.gitignore +++ b/.gitignore @@ -1,41 +1,80 @@ -*~ -*.a +# Files that should be ignored by git for tracking +# For explanation see the gitignore man page +# +# ignore all compiled python files +*.pyc +*.pyo +# ignore all compiled C++ libraries and object files +*.lo *.la *.lai -*.lo -*.loT -*.o *.so -*.pyo -*.pyc +*.Plo +*.Po +*.o +*.a +*.loT +# ignore generated Bessel tables +/CylindricalBesselTable.hpp +/SphericalBesselTable.hpp + +# ignore files generated by pdflatex +*.aux +*.log +*.pdf +*.toc + +# ignore files generated by other tools such as pygmentize +*.png + +# ignore data files generated by the examples +*.out + +# ignore Makefiles.in generated by automake and Makefiles generated by configure +Makefile.in +/Makefile +/doc/Makefile +/binding/Makefile +/samples/benchmark/Makefile +/test/Makefile + +# all kinds of configuration files follow now +# ignore files generated by configure +/config.h +/config.log +/config.status +/stamp-h1 +# ignore scripts generated by configure (I think) +/config.guess +/config.sub +/libtool +/ltmain.sh +# ignore files generated by autoconf +/configure +/depcomp +/missing +/py-compile +/install-sh +/m4/libtool.m4 +/m4/ltoptions.m4 +/m4/ltsugar.m4 +/m4/ltversion.m4 +/m4/lt~obsolete.m4 +/autom4te.cache/ +# ignore files generated by autoheader +/config.h.in +# ignore files generated by aclocal +/aclocal.m4 + +# ignore some other files/directories +*~ *.orig *.rej .libs .deps .*_history* -m4 -Makefile -Makefile.in -depcomp -config.h -config.h.in -ltmain.sh -.deps -config.status -config.h.in -libtool -py-compile -configure -config.log -config.guess -config.sub -stamp-h1 -autom4te.cache -SphericalBesselTable.hpp -CylindricalBesselTable.hpp -missing -aclocal.m4 -install-sh + +# Note sure why these are excluded (added by Morioshi) test/World_test test/array_helper_test test/filters_test @@ -55,5 +94,6 @@ test/linear_algebra_test test/pointer_as_ref_test test/EGFRDSimulator_test test/geometry_test -samples/benchmark/hardbody -samples/*/*.out + +# ignore vi swp files open while editting +*.swp diff --git a/BDPropagator.hpp b/BDPropagator.hpp index 42b46879..d86d984a 100644 --- a/BDPropagator.hpp +++ b/BDPropagator.hpp @@ -10,6 +10,7 @@ #include #include #include +#include #include "Defs.hpp" #include "generator.hpp" #include "exceptions.hpp" @@ -97,7 +98,7 @@ class BDPropagator if (species.D() == 0.) return true; - position_type const displacement(drawR_free(species)); + position_type const displacement(drawR_free(pp)); position_type const new_pos( tx_.apply_boundary( add(pp.second.position(), displacement))); @@ -105,7 +106,7 @@ class BDPropagator particle_id_pair particle_to_update( pp.first, particle_type(species.id(), particle_shape_type(new_pos, species.radius()), - species.D())); + pp.second.structure_id(), species.D())); boost::scoped_ptr overlapped( tx_.check_overlap(particle_to_update.second.shape(), particle_to_update.first)); @@ -158,9 +159,10 @@ class BDPropagator } private: - position_type drawR_free(species_type const& species) + position_type drawR_free(particle_id_pair const& pp) { - return tx_.get_structure(species.structure_id())->bd_displacement(std::sqrt(2.0 * species.D() * dt_), rng_); + species_type const species(tx_.get_species( pp.second.sid()) ); + return tx_.get_structure(pp.second.structure_id())->bd_displacement(species.v() * dt_, std::sqrt(2.0 * species.D() * dt_), rng_); } bool attempt_reaction(particle_id_pair const& pp) @@ -196,6 +198,7 @@ class BDPropagator pp.first, particle_type(products[0], particle_shape_type(pp.second.position(), s0.radius()), + pp.second.structure_id(), s0.D())); boost::scoped_ptr overlapped(tx_.check_overlap(new_p.second.shape(), new_p.first)); if (overlapped && overlapped->size() > 0) @@ -238,14 +241,17 @@ class BDPropagator throw propagation_error("no space"); } - const Real rnd(rng_()); - length_type pair_distance( - drawR_gbd(rnd, r01, dt_, D01)); - const position_type m(random_unit_vector() * pair_distance); + boost::shared_ptr pp_structure( + tx_.get_structure( pp.second.structure_id()) ); + + position_type m( pp_structure-> + dissociation_vector( rng_, r01, dt_, D01, s0.v() - s1.v() ) ); + np0 = tx_.apply_boundary(pp.second.position() - + m * (s0.D() / D01)); + - m * (s0.D() / D01)); np1 = tx_.apply_boundary(pp.second.position() - - m * (s1.D() / D01)); + + m * (s1.D() / D01)); + boost::scoped_ptr overlapped_s0( tx_.check_overlap( particle_shape_type(np0, s0.radius()), @@ -268,8 +274,8 @@ class BDPropagator tx_.remove_particle(pp.first); const particle_id_pair - npp0(tx_.new_particle(s0.id(), np0)), - npp1(tx_.new_particle(s1.id(), np1)); + npp0(tx_.new_particle(s0.id(), pp.second.structure_id(), np0)), + npp1(tx_.new_particle(s1.id(), pp.second.structure_id(), np1)); if (rrec_) { @@ -299,8 +305,8 @@ class BDPropagator } const species_type s0(tx_.get_species(pp0.second.sid())), - s1(tx_.get_species(pp1.second.sid())); - const length_type r01(s0.radius() + s1.radius()); + s1(tx_.get_species(pp1.second.sid())); + length_type r01(s0.radius() + s1.radius()); const Real rnd(rng_()); Real prob = 0; @@ -309,7 +315,12 @@ class BDPropagator i(boost::begin(rules)), e(boost::end(rules)); i != e; ++i) { reaction_rule_type const& r(*i); - const Real p(r.k() * dt_ / ((I_bd(r01, dt_, s0.D()) + I_bd(r01, dt_, s1.D())) * 4.0 * M_PI)); + + boost::shared_ptr pp_structure( + tx_.get_structure( pp0.second.structure_id()) ); // we assume that both particles live on the same surface + + const Real p(pp_structure->p_acceptance( r.k(), dt_, r01, subtract( pp1.second.position(), + pp0.second.position() ), s0.D(), s1.D(), s0.v(), s1.v() )); BOOST_ASSERT(p >= 0.); prob += p; if (prob >= 1.) @@ -330,6 +341,7 @@ class BDPropagator switch (::size(products)) { case 1: + // If the number or product of the reaction is one. { const species_id_type product(products[0]); const species_type sp(tx_.get_species(product)); @@ -342,6 +354,7 @@ class BDPropagator pp1.second.position(), pp0.second.position()), s0.D())), (s0.D() + s1.D())))); + boost::scoped_ptr overlapped( tx_.check_overlap(particle_shape_type(new_pos, sp.radius()), pp0.first, pp1.first)); @@ -362,7 +375,7 @@ class BDPropagator remove_particle(pp0.first); remove_particle(pp1.first); - particle_id_pair npp(tx_.new_particle(product, new_pos)); + particle_id_pair npp(tx_.new_particle(product, pp0.second.structure_id(), new_pos)); if (rrec_) { (*rrec_)( @@ -372,6 +385,7 @@ class BDPropagator break; } case 0: + // If the two particles annihilate eachother. Just remove the particles. remove_particle(pp0.first); remove_particle(pp1.first); break; diff --git a/BDSimulator.hpp b/BDSimulator.hpp index 05c2fd4d..9ebad93a 100644 --- a/BDSimulator.hpp +++ b/BDSimulator.hpp @@ -5,7 +5,7 @@ #include #include #include "NetworkRules.hpp" -#include "BDPropagator.hpp" +#include "newBDPropagator.hpp" #include "World.hpp" #include "ParticleSimulator.hpp" #include "utils/pair.hpp" @@ -22,16 +22,20 @@ class BDSimulator: public ParticleSimulator typedef Ttraits_ traits_type; typedef ParticleSimulator base_type; typedef typename traits_type::world_type world_type; - typedef typename world_type::traits_type::rng_type rng_type; - typedef typename world_type::species_id_type species_id_type; - typedef typename world_type::species_type species_type; - typedef typename traits_type::time_type time_type; - typedef typename traits_type::network_rules_type network_rules_type; - typedef typename traits_type::reaction_rule_type reaction_rule_type; - typedef typename traits_type::rate_type rate_type; - typedef typename traits_type::reaction_record_type reaction_record_type; - typedef typename traits_type::reaction_recorder_type - reaction_recorder_type; + + typedef typename world_type::traits_type::rng_type rng_type; + typedef typename world_type::species_id_type species_id_type; + typedef typename world_type::species_type species_type; + typedef typename world_type::length_type length_type; + typedef typename world_type::structure_type structure_type; + typedef typename traits_type::time_type time_type; + typedef typename traits_type::network_rules_type network_rules_type; + typedef typename traits_type::reaction_rule_type reaction_rule_type; + typedef typename network_rules_type::reaction_rules reaction_rules; + typedef typename traits_type::rate_type rate_type; + typedef typename traits_type::reaction_record_type reaction_record_type; + typedef typename traits_type::reaction_recorder_type reaction_recorder_type; + typedef std::pair real_pair; public: Real const& dt_factor() @@ -43,18 +47,21 @@ class BDSimulator: public ParticleSimulator BDSimulator(boost::shared_ptr world, boost::shared_ptr network_rules, - rng_type& rng, Real dt_factor = 1., - int dissociation_retry_moves = 1) + rng_type& rng, Real dt_factor = .1, + int dissociation_retry_moves = 1, length_type reaction_length_factor = traits_type::MULTI_SHELL_FACTOR) : base_type(world, network_rules, rng), - dt_factor_(dt_factor), num_retries_(dissociation_retry_moves) + dt_factor_(dt_factor), num_retries_(dissociation_retry_moves), + reaction_length_factor_(reaction_length_factor) { - calculate_dt(); + calculate_dt_and_reaction_length(); } - virtual void calculate_dt() + void calculate_dt_and_reaction_length() { - base_type::dt_ = dt_factor_ * determine_dt(*base_type::world_); - LOG_DEBUG(("dt=%f", base_type::dt_)); + //base_type::dt_ = dt_factor_ * determine_dt(*base_type::world_); + base_type::dt_ = determine_dt(); + reaction_length_ = determine_reaction_length(); + log_.info( "dt=%e, reaction length=%e", base_type::dt_, reaction_length_ ); } virtual void step() @@ -76,6 +83,7 @@ class BDSimulator: public ParticleSimulator _step(lt); base_type::t_ = upto; } + return true; } @@ -92,16 +100,146 @@ class BDSimulator: public ParticleSimulator } return gsl_pow_2(radius_min * 2) / (D_max * 2); } + + Real determine_dt() + { + std::pair maxD_minr( maxD_minsigma() ); + const Real k_max( get_max_rate() ); + const Real D_max( maxD_minr.first ); + const Real r_min( maxD_minr.second ); + const Real Pacc_max( 0.01 ); //Maximum value of the acceptance probability. + const Real tau_D( 2 * r_min * r_min / D_max ); //~step over particle - time. + Real dt; + + if( k_max > 0) + { + Real dt_temp( 2 * Pacc_max * reaction_length_factor_ * r_min / k_max ); + dt = std::min( dt_temp, dt_factor_ * tau_D ); // dt_factor * tau_D is upper limit of dt. + } + else + dt = dt_factor_ * tau_D; + + return dt; + } + + length_type determine_reaction_length() + { + real_pair maxD_minr( maxD_minsigma() ); + return reaction_length_factor_ * maxD_minr.second; + } + + /* Returns largest diffusion constant and smallest particle radius in the multi. */ + real_pair maxD_minsigma() + { + Real D_max(0.), radius_min(std::numeric_limits::max()); + + BOOST_FOREACH(species_type s, base_type::world_->get_species()) + { + if (D_max < s.D()) + D_max = s.D(); + if (radius_min > s.radius()) + radius_min = s.radius(); + } + + return real_pair(D_max, radius_min); + } + + /* Functions returns largest _1D_ intrinsic reaction rate in the world. */ + Real get_max_rate() + { + Real k_max(0.); + int i = 0, j= 0; + + BOOST_FOREACH(species_type s, base_type::world_->get_species()) + { + if( s.structure_type_id() != base_type::world_->get_def_structure_type_id() ) + continue; + + BOOST_FOREACH(boost::shared_ptr structure, base_type::world_->get_structures()) + { + species_id_type const strc_sid( structure->sid() ); + reaction_rules const& rrules((*base_type::network_rules_).query_reaction_rule( s.id(), strc_sid )); + if (::size(rrules) == 0) + continue; + + for (typename boost::range_const_iterator::type + it(boost::begin(rrules)), e(boost::end(rrules)); it != e; ++it) + { + Real const k( structure->get_1D_rate_surface( (*it).k(), s.radius() ) ); + + if ( k_max < k ) + k_max = k; + } + } + } + + //Since surface rates are not devided by 2 to compensate for double reaction attempts. + k_max *= 2.0; + + BOOST_FOREACH(species_type s0, base_type::world_->get_species()) + { + j = 0; + BOOST_FOREACH(species_type s1, base_type::world_->get_species()) + { + if( j++ < i ) + continue; + + reaction_rules const& rrules((*base_type::network_rules_).query_reaction_rule( s0.id(), s1.id() )); + if (::size(rrules) == 0) + { + continue; + } + + for (typename boost::range_const_iterator::type + it(boost::begin(rrules)), e(boost::end(rrules)); it != e; ++it) + { +// const length_type r01( s0.radius() + s1.radius() ); // TODO + Real k; + + if(s0.structure_type_id() != s1.structure_type_id()) + { + if(s0.structure_type_id() == base_type::world_->get_def_structure_type_id()) + k = 0.001; //TODO k = base_type::world_->get_structure( s0.structure_type_id() )->get_1D_rate_geminate( (*it).k(), r01 ); + else + k = 0.001; //TODO k = base_type::world_->get_structure( s1.structure_type_id() )->get_1D_rate_geminate( (*it).k(), r01 ); + } + else + { + k = 0.001; //TODO k = base_type::world_->get_structure( s0.structure_id() )->get_1D_rate_geminate( (*it).k(), r01 ); + } + + if ( k_max < k ) + k_max = k; + } + } + i++; + } + + return k_max; + } + + Real get_reaction_length() const + { + return reaction_length_; + } + + void set_reaction_length_factor(length_type new_reaction_length_factor, time_type new_dt_factor) + { + dt_factor_ = new_dt_factor; + reaction_length_factor_ = new_reaction_length_factor; + + calculate_dt_and_reaction_length(); + } protected: void _step(time_type dt) { { - BDPropagator propagator( + newBDPropagator propagator( *base_type::world_, *base_type::network_rules_, base_type::rng_, - dt, num_retries_, + dt, num_retries_, reaction_length_, base_type::rrec_.get(), 0, make_select_first_range(base_type::world_-> get_particles_range())); @@ -114,9 +252,11 @@ class BDSimulator: public ParticleSimulator } private: - Real const dt_factor_; - int const num_retries_; - static Logger& log_; + Real dt_factor_; + int const num_retries_; + length_type reaction_length_factor_; + length_type reaction_length_; + static Logger& log_; }; template diff --git a/Box.hpp b/Box.hpp index 06499dc9..4b21dbfe 100644 --- a/Box.hpp +++ b/Box.hpp @@ -18,6 +18,7 @@ class Box typedef T_ value_type; typedef Vector3 position_type; typedef T_ length_type; + typedef enum side_enum_type {TOP=0, BOTTOM=1, LEFT=2, RIGHT=3, FRONT=4, BACK=5} side_enum_type; // The typedef is a little bit C style but doesn't matter for C++ public: Box(position_type const& position = position_type()) @@ -162,6 +163,11 @@ class Box { return half_extent_; } + + const int dof() const + { // degrees of freedom for particle movement + return 3; + } bool operator==(const Box& rhs) const { @@ -206,15 +212,41 @@ to_internal(Box const& obj, typename Box::position_type const& pos) template inline std::pair::position_type, - typename Box::length_type> -projected_point(Box const& obj, typename Box::position_type const& pos) + std::pair::length_type, + typename Box::length_type> > +project_point(Box const& obj, typename Box::position_type const& pos) +{ + typedef typename Box::length_type length_type; + + const boost::array x_y_z(to_internal(obj, pos)); + const boost::array dx_dy_dz(subtract(abs(x_y_z), obj.half_extent())); + const length_type min_dist ( (dx_dy_dz[0] <= 0 && + dx_dy_dz[1] <= 0 && + dx_dy_dz[2] <= 0) ? std::max(dx_dy_dz[0], std::max(dx_dy_dz[1], dx_dy_dz[2]) ) + : 1.0 ); // TODO make this is proper distance if we need it + + // TODO the projection of the point is not very well defined. + // The projection of a point on a box. + return std::make_pair(typename Box::position_type(), + std::make_pair(typename Box::length_type(), + min_dist) ); +} + +template +inline std::pair::position_type, + std::pair::length_type, + typename Box::length_type> > +project_point_on_surface(Box const& obj, + typename Box::position_type const& pos) { // Todo. If we ever need it. // The projection of a point on a box. return std::make_pair(typename Box::position_type(), - typename Box::length_type()); + std::make_pair(typename Box::length_type(), + typename Box::length_type()) ); } + template inline typename Box::length_type distance(Box const& obj, typename Box::position_type const& pos) @@ -277,6 +309,28 @@ distance(Box const& obj, typename Box::position_type const& pos) } } +template +inline std::pair::position_type, bool> +deflect(Box const& obj, + typename Box::position_type const& r0, + typename Box::position_type const& d ) +{ + // Displacements are not deflected on cuboidal regions, + // but this function has to be defined for every shape to be used in structure. + // For now it just returns original pos. + displacement. The changeflage = false. + return std::make_pair( add(r0, d), false ); +} +/* +template +inline typename Box::position_type +deflect_back(Box const& obj, + typename Box::position_type const& r, + typename Box::position_type const& u_z ) +{ + // Return the vector r without any changes + return r; +} +*/ template inline typename Box::position_type random_position(Box const& shape, Trng& rng) diff --git a/ConnectivityContainer.hpp b/ConnectivityContainer.hpp new file mode 100644 index 00000000..e963471d --- /dev/null +++ b/ConnectivityContainer.hpp @@ -0,0 +1,75 @@ +#ifndef CONNECTIVITY_CONTAINER_HPP +#define CONNECTIVITY_CONTAINER_HPP + +#include +#include "exceptions.hpp" +#include + + +template +class ConnectivityContainer +// implements the datastructure of objects that each have exactly 'num_neighbors' neighbors and have Tdata_ data associated +// with each edge from the one of the object to the other. +// NOTE the edges to neighbors are MANDATORY and have a specific ORDER. + +// It allows you to check what structure you enter into when you leave the current one. +{ +public: + typedef Tobj_ obj_type; // is in our case the StructureID + typedef Tdata_ data_type; // is the normal vector into the neighboring structure + typedef std::size_t size_type; // the type for the index in the array + + typedef std::pair obj_data_pair_type; + +protected: + // the structureID of the neighbor and the normal vector pointing from the + // intersection in the direction of the center of the neighbor + typedef boost::array obj_data_pair_array_type; + // the mapping from the structure and all its neighbors and how they are connected. + typedef std::map obj_neighbor_objdata_array_map; + typedef select_second obj_neighbor_objdata_second_selector_type; + typedef boost::transform_iterator obj_neighbor_objdata_array_iterator; + +public: + static const size_type max = num_neighbors; + +public: + void set_neighbor_info(obj_type const& obj, size_type const n, obj_data_pair_type const& obj_data_pair) + { + // check that the index is not too large + if ( n >= max ) + throw illegal_argument(std::string("Index out of range for neighbor (n= ") + boost::lexical_cast(n) + "), max= " + boost::lexical_cast(num_neighbors)); + + neighbor_mapping_[obj][n] = obj_data_pair; + } + + obj_data_pair_type get_neighbor_info(obj_type const& id, size_type const n) const + { + // check that the index is not too large + if ( n >= max ) + throw illegal_argument(std::string("Index out of range for neighbor (n= ") + boost::lexical_cast(n) + "), max= " + boost::lexical_cast(num_neighbors)); + + typename obj_neighbor_objdata_array_map::const_iterator it(neighbor_mapping_.find(id)); + //std::cout << "Checking neighbor info, id=" << boost::lexical_cast(id) << ", it=" << it->first << std::endl; // TODO remove after debugging + if (it==neighbor_mapping_.end()) + { + throw not_found(std::string("Object not found(id=") + boost::lexical_cast(id) + ")"); + } + // all fine + return (*it).second[n]; + } + + ConnectivityContainer() {} + // do we start with an empty container or do we construct it as a static object with n structures already connected. + +private: + // mapping StructureID -> array<(StructureID, vector3D), num_neighbors> + obj_neighbor_objdata_array_map neighbor_mapping_; +}; + + +////// Inline functions related to ConnectivityContainer + +#endif /* CONNECTIVITY_CONTAINER_HPP */ + diff --git a/CuboidalRegion.hpp b/CuboidalRegion.hpp index 2149da41..789d786b 100644 --- a/CuboidalRegion.hpp +++ b/CuboidalRegion.hpp @@ -4,25 +4,47 @@ #include #include "Region.hpp" #include "Box.hpp" +#include "freeFunctions.hpp" +#include "StructureFunctions.hpp" + +template +class StructureContainer; + template class CuboidalRegion - : public BasicRegionImpl > + : public BasicRegionImpl > { public: - typedef BasicRegionImpl > base_type; - typedef typename base_type::traits_type traits_type; - typedef typename base_type::identifier_type identifier_type; - typedef typename base_type::shape_type shape_type; - typedef typename base_type::rng_type rng_type; - typedef typename base_type::position_type position_type; - typedef typename base_type::length_type length_type; + typedef BasicRegionImpl > base_type; + typedef Ttraits_ traits_type; + + // name shorthands of types that we use. + typedef typename base_type::structure_name_type structure_name_type; + typedef typename base_type::structure_id_type structure_id_type; + typedef typename base_type::structure_type_id_type structure_type_id_type; + typedef typename base_type::shape_type shape_type; + typedef typename base_type::rng_type rng_type; + typedef typename base_type::position_type position_type; + typedef typename base_type::length_type length_type; + typedef typename base_type::side_enum_type side_enum_type; + typedef typename traits_type::species_type species_type; + typedef typename traits_type::structure_type structure_type; + + typedef StructureContainer structure_container_type; + + typedef std::pair position_pair_type; + typedef std::pair position_structid_pair_type; + typedef std::pair position_structid_pair_pair_type; - identifier_type const& id() const + + /*** Info functions ***/ + virtual position_type const& position() const { - return base_type::id_; + return base_type::shape().position(); } + /*** Simple structure-specific sampling functions ***/ virtual position_type random_position(rng_type& rng) const { return ::random_position(base_type::shape(), @@ -38,13 +60,301 @@ class CuboidalRegion rng.uniform(-1., 1.)), r); } - virtual position_type bd_displacement(length_type const& r, rng_type& rng) const + virtual position_type bd_displacement(length_type const& mean, length_type const& r, rng_type& rng) const { return create_vector( rng.normal(0., r), rng.normal(0., r), rng.normal(0., r)); } + + /*** New BD scheme functions ***/ + // Rate for binding to particle on the structure + virtual Real get_1D_rate_geminate( Real const& k, length_type const& r01 ) const + { + return k / ( 4 * M_PI * r01 * r01 ); + } + + // Rate for binding to the structure + virtual Real get_1D_rate_surface( Real const& k, length_type const& r0 ) const + { + return Real(); //No reaction rates with bulk; + } + + // Reaction volume for binding to particle in the structure + virtual Real particle_reaction_volume( length_type const& r01, length_type const& rl ) const + { + length_type const r01l( r01 + rl ); + length_type const r01l_cb( r01l * r01l * r01l ); + length_type const r01_cb( r01 * r01 * r01 ); + + return 4.0/3.0 * M_PI * ( r01l_cb - r01_cb ); + } + + // Reaction volume for binding to the structure + virtual Real surface_reaction_volume( length_type const& r0, length_type const& rl ) const + { + return Real(); //No surface interaction with the bulk + } + + // Vector of dissociation from the structure into the bulk + virtual position_type surface_dissociation_vector( rng_type& rng, length_type const& r0, length_type const& rl ) const + { + return position_type(); //No surface dissociation 'from' the bulk + } + + // Normed direction of dissociation from the structure to parent structure + virtual position_type surface_dissociation_unit_vector( rng_type& rng ) const + { + return position_type(); //No surface dissociation 'from' the bulk + } + + // Vector used to determine whether a particle has crossed the structure + // Here we return the zero-vector because there is no "sides" to cross + virtual position_type const side_comparison_vector() const + { + return create_vector(0.0, 0.0, 0.0); + } + + // Positions created at dissociation of one particle on the structure into two particles on the structure + virtual position_pair_type geminate_dissociation_positions( rng_type& rng, species_type const& s0, species_type const& s1, position_type const& op, + length_type const& rl ) const + { + length_type const r01( s0.radius() + s1.radius() ); + Real const D01( s0.D() + s1.D() ); + + Real const X( rng.uniform(0.,1.) ); + + length_type const r01l( r01 + rl ); + length_type const r01l_cb( r01l * r01l * r01l ); + length_type const r01_cb( r01 * r01 * r01 ); + + length_type const diss_vec_length( cbrt( X * (r01l_cb - r01_cb ) + r01_cb ) ); + + position_type const m( random_vector( diss_vec_length, rng ) ); + + return position_pair_type( op - m * s0.D() / D01, + op + m * s1.D() / D01 ); + } + + // Positions created at dissociation of one particle on the structure into two particles, one of which ends up in the bulk + virtual position_pair_type special_geminate_dissociation_positions( rng_type& rng, species_type const& s_surf, species_type const& s_bulk, + position_type const& op_surf, length_type const& rl ) const + { + return position_pair_type(); //No special geminate dissociation 'from' the bulk + } + + // Used by newBDPropagator + virtual length_type newBD_distance(position_type const& new_pos, length_type const& radius, position_type const& old_pos, length_type const& sigma) const + { + return base_type::distance(new_pos); + } + + /*** Boundary condition handling ***/ + // The apply boundary for the cuboidal structure doesn't have to do anything because we'll apply the world + // boundary conditions later too. + virtual position_structid_pair_type apply_boundary(position_structid_pair_type const& pos_struct_id, + structure_container_type const& structure_container) const + { + return pos_struct_id; + } + + virtual position_structid_pair_type cyclic_transpose(position_structid_pair_type const& pos_struct_id, + structure_container_type const& structure_container) const + { + return pos_struct_id; // The cyclic_transpose does nothing because we'll apply world cyclic transpose later. + } + + + // *** Dynamic dispatch for the structure functions *** // + // *** 1 *** - One new position + // This requires a double dynamic dispatch. + // First dispatch + virtual position_structid_pair_type get_pos_sid_pair(structure_type const& target_structure, position_type const& position, + length_type const& offset, length_type const& reaction_length, rng_type& rng) const + { + return target_structure.get_pos_sid_pair_helper(*this, position, offset, reaction_length, rng); + } + // Second dispatch + virtual position_structid_pair_type get_pos_sid_pair_helper(CuboidalRegion const& origin_structure, position_type const& position, + length_type const& offset, length_type const& rl, rng_type& rng) const + { + return this->get_pos_sid_pair_helper_any >(origin_structure, position, offset, rl, rng); + } + virtual position_structid_pair_type get_pos_sid_pair_helper(SphericalSurface const& origin_structure, position_type const& position, + length_type const& offset, length_type const& rl, rng_type& rng) const + { + return this->get_pos_sid_pair_helper_any >(origin_structure, position, offset, rl, rng); + } + virtual position_structid_pair_type get_pos_sid_pair_helper(CylindricalSurface const& origin_structure, position_type const& position, + length_type const& offset, length_type const& rl, rng_type& rng) const + { + return this->get_pos_sid_pair_helper_any >(origin_structure, position, offset, rl, rng); + } + virtual position_structid_pair_type get_pos_sid_pair_helper(DiskSurface const& origin_structure, position_type const& position, + length_type const& offset, length_type const& rl, rng_type& rng) const + { + return this->get_pos_sid_pair_helper_any >(origin_structure, position, offset, rl, rng); + } + virtual position_structid_pair_type get_pos_sid_pair_helper(PlanarSurface const& origin_structure, position_type const& position, + length_type const& offset, length_type const& rl, rng_type& rng) const + { + return this->get_pos_sid_pair_helper_any >(origin_structure, position, offset, rl, rng); + } + // The template function that defines the actual final dispatch procedure. + template + position_structid_pair_type get_pos_sid_pair_helper_any(Tstruct_ const& origin_structure, position_type const& position, + length_type const& offset, length_type const& rl, rng_type& rng) const + { + // redirect to structure function with well-defined typing + return ::get_pos_sid_pair(origin_structure, *this, position, offset, rl, rng); + }; + + // *** 2 *** - Two new positions + // Same principle as above, but different return type + // First dispatch + virtual position_structid_pair_pair_type get_pos_sid_pair_pair(structure_type const& target_structure, position_type const& position, + species_type const& s1, species_type const& s2, length_type const& reaction_length, rng_type& rng) const + { + return target_structure.get_pos_sid_pair_pair_helper(*this, position, s1, s2, reaction_length, rng); + } + // Second dispatch + virtual position_structid_pair_pair_type get_pos_sid_pair_pair_helper(CuboidalRegion const& origin_structure, position_type const& position, + species_type const& s_orig, species_type const& s_targ, length_type const& rl, rng_type& rng) const + { + return this->get_pos_sid_pair_pair_helper_any >(origin_structure, position, s_orig, s_targ, rl, rng); + } + virtual position_structid_pair_pair_type get_pos_sid_pair_pair_helper(SphericalSurface const& origin_structure, position_type const& position, + species_type const& s_orig, species_type const& s_targ, length_type const& rl, rng_type& rng) const + { + return this->get_pos_sid_pair_pair_helper_any >(origin_structure, position, s_orig, s_targ, rl, rng); + } + virtual position_structid_pair_pair_type get_pos_sid_pair_pair_helper(CylindricalSurface const& origin_structure, position_type const& position, + species_type const& s_orig, species_type const& s_targ, length_type const& rl, rng_type& rng) const + { + return this->get_pos_sid_pair_pair_helper_any >(origin_structure, position, s_orig, s_targ, rl, rng); + } + virtual position_structid_pair_pair_type get_pos_sid_pair_pair_helper(DiskSurface const& origin_structure, position_type const& position, + species_type const& s_orig, species_type const& s_targ, length_type const& rl, rng_type& rng) const + { + return this->get_pos_sid_pair_pair_helper_any >(origin_structure, position, s_orig, s_targ, rl, rng); + } + virtual position_structid_pair_pair_type get_pos_sid_pair_pair_helper(PlanarSurface const& origin_structure, position_type const& position, + species_type const& s_orig, species_type const& s_targ, length_type const& rl, rng_type& rng) const + { + return this->get_pos_sid_pair_pair_helper_any >(origin_structure, position, s_orig, s_targ, rl, rng); + } + // The template function that defines the actual final dispatch procedure. + template + position_structid_pair_pair_type get_pos_sid_pair_pair_helper_any(Tstruct_ const& origin_structure, position_type const& position, + species_type const& s_orig, species_type const& s_targ, length_type const& rl, rng_type& rng) const + { + // redirect to structure function with well-defined typing + return ::get_pos_sid_pair_pair(origin_structure, *this, position, s_orig, s_targ, rl, rng); + }; + + // *** 3 *** - Pair reactions => two origin structures + // First dispatch +// // Overloading get_pos_sid_pair with signature (origin_structure2, target_structure_type_id, ...) +// virtual position_structid_pair_type get_pos_sid_pair(structure_type const& origin_structure2, structure_type_id_type const& target_sid, position_type const& CoM, +// length_type const& offset, length_type const& reaction_length, rng_type& rng) const +// { +// // this just redirects +// return this->get_pos_sid_pair_2o(origin_structure2, target_sid, CoM, offset, reaction_length, rng); +// } +// // The actual implementation of the first dispatch + virtual position_structid_pair_type get_pos_sid_pair_2o(structure_type const& origin_structure2, structure_type_id_type const& target_sid, + position_type const& CoM, length_type const& offset, length_type const& reaction_length, rng_type& rng) const + { + return origin_structure2.get_pos_sid_pair_2o_helper(*this, target_sid, CoM, offset, reaction_length, rng); + } + // Second dispatch + virtual position_structid_pair_type get_pos_sid_pair_2o_helper(CuboidalRegion const& origin_structure1, structure_type_id_type const& target_sid, + position_type const& CoM, length_type const& offset, length_type const& rl, rng_type& rng) const + { + return this->get_pos_sid_pair_2o_helper_any >(origin_structure1, target_sid, CoM, offset, rl, rng); + } + virtual position_structid_pair_type get_pos_sid_pair_2o_helper(SphericalSurface const& origin_structure1, structure_type_id_type const& target_sid, + position_type const& CoM, length_type const& offset, length_type const& rl, rng_type& rng) const + { + return this->get_pos_sid_pair_2o_helper_any >(origin_structure1, target_sid, CoM, offset, rl, rng); + } + virtual position_structid_pair_type get_pos_sid_pair_2o_helper(CylindricalSurface const& origin_structure1, structure_type_id_type const& target_sid, + position_type const& CoM, length_type const& offset, length_type const& rl, rng_type& rng) const + { + return this->get_pos_sid_pair_2o_helper_any >(origin_structure1, target_sid, CoM, offset, rl, rng); + } + virtual position_structid_pair_type get_pos_sid_pair_2o_helper(DiskSurface const& origin_structure1, structure_type_id_type const& target_sid, + position_type const& CoM, length_type const& offset, length_type const& rl, rng_type& rng) const + { + return this->get_pos_sid_pair_2o_helper_any >(origin_structure1, target_sid, CoM, offset, rl, rng); + } + virtual position_structid_pair_type get_pos_sid_pair_2o_helper(PlanarSurface const& origin_structure1, structure_type_id_type const& target_sid, + position_type const& CoM, length_type const& offset, length_type const& rl, rng_type& rng) const + { + return this->get_pos_sid_pair_2o_helper_any >(origin_structure1, target_sid, CoM, offset, rl, rng); + } + // The template function that defines the actual final dispatch procedure. + template + position_structid_pair_type get_pos_sid_pair_2o_helper_any(Tstruct_ const& origin_structure1, structure_type_id_type const& target_sid, position_type const& CoM, + length_type const& offset, length_type const& reaction_length, rng_type& rng) const + { + // This method has to figure out where the product will be placed in case of a bimolecular reaction. + // As a default, we place particles on the substructure or the lower-dimensional structure. If the structures + // have the same structure type (=> same dimensionality) it does not matter on which structure we put the product, + // as long as it has the structure type id of the product species. This is handled in cases '1' below. + + // 1 - Check whether one of the structures is the parent of the other. If yes, the daughter structure is the target. + if( this->is_parent_of_or_has_same_sid_as(origin_structure1) && origin_structure1.has_valid_target_sid(target_sid) ) + // origin_structure1 is target + return ::get_pos_sid_pair(*this, origin_structure1, CoM, offset, reaction_length, rng); + + else if( origin_structure1.is_parent_of_or_has_same_sid_as(*this) && this->has_valid_target_sid(target_sid) ) + // this structure is target + return ::get_pos_sid_pair(origin_structure1, *this, CoM, offset, reaction_length, rng); + + // 2 - Check which structures has the lower dimensionality / particle degrees of freedom, and put the product there. + else if( origin_structure1.shape().dof() < this->shape().dof() && origin_structure1.has_valid_target_sid(target_sid) ) + // origin_structure1 is target + return ::get_pos_sid_pair(*this, origin_structure1, CoM, offset, reaction_length, rng); + + else if( this->shape().dof() < origin_structure1.shape().dof() && this->has_valid_target_sid(target_sid) ) + // this structure is target + return ::get_pos_sid_pair(origin_structure1, *this, CoM, offset, reaction_length, rng); + + else throw propagation_error("Invalid target structure type: does not match product species structure type or has wrong hierarchy or dimensionality."); + } + +// // *** 4 *** - Generalized functions for pair reactions with two origin structures and one target structure +// // NOTE: This is yet unused, but possibly useful in the future. +// // Overloading get_pos_sid_pair again with signature (origin_structure2, target_structure, ...) and introducing +// // a triple dynamic dispatch. +// virtual position_structid_pair_type get_pos_sid_pair(structure_type const& origin_structure2, structure_type const& target_structure, position_type const& position, +// length_type const& offset, length_type const& reaction_length, rng_type const& rng) const +// { +// return origin_structure2.get_pos_sid_pair_helper1(*this, target_structure, position, offset, reaction_length, rng); +// } + + + /*** Formerly used functions of the Morelli scheme ***/ + // DEPRECATED + virtual length_type drawR_gbd(Real const& rnd, length_type const& r01, Real const& dt, + Real const& D01, Real const& v) const + { + return drawR_gbd_3D(rnd, r01, dt, D01); + } + // DEPRECATED + virtual Real p_acceptance(Real const& k_a, Real const& dt, length_type const& r01, position_type const& ipv, + Real const& D0, Real const& D1, Real const& v0, Real const& v1) const + { + return k_a * dt / ((I_bd_3D(r01, dt, D0) + I_bd_3D(r01, dt, D1)) * 4.0 * M_PI); + } + // DEPRECATED + virtual position_type dissociation_vector( rng_type& rng, length_type const& r01, Real const& dt, + Real const& D01, Real const& v ) const + { + return random_vector( drawR_gbd(rng.uniform(0., 1.), r01, dt, D01, v ), rng ); + } virtual void accept(ImmutativeStructureVisitor const& visitor) const { @@ -56,8 +366,9 @@ class CuboidalRegion visitor(*this); } - CuboidalRegion(identifier_type const& id, shape_type const& shape) - : base_type(id, shape) {} + // Constructor + CuboidalRegion(structure_name_type const& name, structure_type_id_type const& sid, structure_id_type const& parent_struct_id, shape_type const& shape) + : base_type(name, sid, parent_struct_id, shape) {} }; #endif /* CUBOIDAL_REGION_HPP */ diff --git a/Cylinder.hpp b/Cylinder.hpp index c4566d35..7dc794fa 100644 --- a/Cylinder.hpp +++ b/Cylinder.hpp @@ -5,6 +5,7 @@ #include #include "Vector3.hpp" #include "Shape.hpp" +#include "linear_algebra.hpp" // Todo. Make sure cylinder is never larger than 1 cellsize or something. template @@ -14,8 +15,10 @@ class Cylinder typedef T_ value_type; typedef Vector3 position_type; typedef T_ length_type; + typedef enum side_enum_type {LEFT=0, RIGHT=1} side_enum_type; // The typedef is a little bit C style but doesn't matter for C++ public: + // constructors Cylinder() : position_(), radius_(0), unit_z_(), half_length_(0) {} @@ -24,6 +27,7 @@ class Cylinder : position_(position), radius_(radius), unit_z_(unit_z), half_length_(half_length) {} + bool operator==(const Cylinder& rhs) const { return position_ == rhs.position() && radius_ == rhs.radius() && unit_z_ == rhs.unit_z() && half_length_ == rhs.half_length(); @@ -73,6 +77,11 @@ class Cylinder { return half_length_; } + + const int dof() const + { // degrees of freedom for particle movement + return 1; + } std::string show(int precision) { @@ -82,13 +91,16 @@ class Cylinder return strm.str(); } +/////// Member variables private: - position_type position_; // centre. + position_type position_; // centre. length_type radius_; - position_type unit_z_; // Z-unit_z. should be normalized. + position_type unit_z_; // Z-unit_z. should be normalized. length_type half_length_; }; + +//////// Inline functions template inline std::basic_ostream& operator<<(std::basic_ostream& strm, const Cylinder& v) @@ -103,31 +115,64 @@ inline std::pair::length_type, to_internal(Cylinder const& obj, typename Cylinder::position_type const& pos) { // Return pos relative to position of cylinder. - typedef typename Cylinder::position_type position_type; - typedef typename Cylinder::length_type length_type; + typedef typename Cylinder::position_type position_type; + typedef typename Cylinder::length_type length_type; const position_type pos_vector(subtract(pos, obj.position())); - // z can be < 0 - const length_type z(dot_product(pos_vector, obj.unit_z())); - // r is always >= 0 - const length_type r(length(pos_vector - multiply(obj.unit_z(), z))); + + const length_type z(dot_product(pos_vector, obj.unit_z())); // z can be < 0 + const length_type r(length(subtract(pos_vector, multiply(obj.unit_z(), z)))); // r is always >= 0 return std::make_pair(r, z); } +// The following projects 'pos' on the cylinder +// It returns a pair of which the first component is the projected position. +// The second is again a pair of which the first entry is the distance of 'pos' +// from the cylinder axis, the second a length l which indicates whether the +// projected position is in the cylinder (true if l negative; l is the negative +// distance of the projected position to the cylinder edge). template inline std::pair::position_type, - typename Cylinder::length_type> -projected_point(Cylinder const& obj, - typename Cylinder::position_type const& pos) + std::pair::length_type, + typename Cylinder::length_type> > +project_point(Cylinder const& obj, typename Cylinder::position_type const& pos) { typedef typename Cylinder::length_type length_type; // The projection lies on the z-axis. std::pair r_z(to_internal(obj, pos)); - return std::make_pair( - add(obj.position(), multiply(obj.unit_z(), r_z.second)), - r_z.first); + + return std::make_pair( add(obj.position(), multiply(obj.unit_z(), r_z.second)), + std::make_pair(r_z.first, + subtract(abs(r_z.second), obj.half_length())) ); +} + +//Almost equal to projected point method, but for the substraction of the cylinder radius from the radial distance r. +//And projected point now lies on the surface, not on the central axis. +template +inline std::pair::position_type, + std::pair::length_type, + typename Cylinder::length_type> > +project_point_on_surface(Cylinder const& obj, + typename Cylinder::position_type const& pos) +{ + typedef typename Cylinder::length_type length_type; + typedef typename Cylinder::position_type position_type; + + // Here we do not call 'to_internal' for efficiency + const position_type pos_vector(subtract(pos, obj.position())); + + const length_type z ( dot_product(pos_vector, obj.unit_z()) ); + const position_type z_vector (multiply(obj.unit_z(), z)); + const position_type r_vector (subtract(pos_vector, z_vector)); + const length_type r (length(r_vector)); + + const position_type projected_point( add(obj.position(), z_vector) ); + + return std::make_pair( add(projected_point, multiply( normalize(r_vector), obj.radius() )), + std::make_pair(subtract(r, obj.radius()), + subtract(abs(z), obj.half_length())) ); } template @@ -135,10 +180,10 @@ inline typename Cylinder::length_type distance(Cylinder const& obj, typename Cylinder::position_type const& pos) { - typedef typename Cylinder::position_type position_type; + //typedef typename Cylinder::position_type position_type; typedef typename Cylinder::length_type length_type; - /* First compute the (z,r) components of pos in a coordinate system + /* First compute the (r,z) components of pos in a coordinate system * defined by the vectors unitR and unit_z, where unitR is * choosen such that unitR and unit_z define a plane in which * pos lies. */ @@ -177,6 +222,28 @@ distance(Cylinder const& obj, return distance; } +template +inline std::pair::position_type, bool> +deflect(Cylinder const& obj, + typename Cylinder::position_type const& r0, + typename Cylinder::position_type const& d ) +{ + // Displacements are not deflected on cylinders (yet), + // but this function has to be defined for every shape to be used in structure. + // For now it just returns original pos. + displacement. The changeflage = false. + return std::make_pair( add(r0, d), false ); +} +/* +template +inline typename Cylinder::position_type +deflect_back(Cylinder const& obj, + typename Cylinder::position_type const& r, + typename Cylinder::position_type const& u_z ) +{ + // Return the vector r without any changes + return r; +} +*/ template inline typename Cylinder::position_type random_position(Cylinder const& shape, Trng& rng) diff --git a/CylindricalBesselGenerator.hpp b/CylindricalBesselGenerator.hpp index 15a630ce..d0ff1ccb 100644 --- a/CylindricalBesselGenerator.hpp +++ b/CylindricalBesselGenerator.hpp @@ -11,9 +11,6 @@ class CylindricalBesselGenerator { - - typedef UnsignedInteger Index; - public: CylindricalBesselGenerator() diff --git a/CylindricalSurface.hpp b/CylindricalSurface.hpp index bd603edf..e27a4022 100644 --- a/CylindricalSurface.hpp +++ b/CylindricalSurface.hpp @@ -4,42 +4,457 @@ #include #include "Surface.hpp" #include "Cylinder.hpp" +#include "freeFunctions.hpp" +#include "StructureFunctions.hpp" +#include "geometry.hpp" + + +template +class StructureContainer; template class CylindricalSurface - : public BasicSurfaceImpl > + : public BasicSurfaceImpl > { + // The CylindricalSurface is the implementation of a Basic surface parameterized with a Cylinder + public: - typedef BasicSurfaceImpl > base_type; - typedef typename base_type::traits_type traits_type; - typedef typename base_type::identifier_type identifier_type; - typedef typename base_type::shape_type shape_type; - typedef typename base_type::rng_type rng_type; - typedef typename base_type::position_type position_type; - typedef typename base_type::length_type length_type; + typedef BasicSurfaceImpl > base_type; + typedef Ttraits_ traits_type; + + // name shorthands of types that we use. + typedef typename base_type::structure_name_type structure_name_type; // This is just the name of the structure + typedef typename base_type::structure_id_type structure_id_type; + typedef typename base_type::structure_type_id_type structure_type_id_type; + typedef typename base_type::shape_type shape_type; + typedef typename base_type::rng_type rng_type; + typedef typename base_type::position_type position_type; + typedef typename base_type::length_type length_type; + typedef typename base_type::side_enum_type side_enum_type; + typedef typename traits_type::species_type species_type; + typedef typename traits_type::structure_type structure_type; + + typedef StructureContainer structure_container_type; + + typedef std::pair position_pair_type; + typedef std::pair position_structid_pair_type; + typedef std::pair position_structid_pair_pair_type; - virtual position_type random_position(rng_type& rng) const + + /*** Info functions ***/ + virtual position_type const& position() const + { + return base_type::shape().position(); + } + + /*** Simple structure-specific sampling functions ***/ + // Produce a random position in the cylinder (1D) + virtual position_type random_position(rng_type& rng) const { return ::random_position(base_type::shape(), boost::bind(&rng_type::uniform, rng, -1., 1.)); } + // Produce a random vector along the axis of the cylinder virtual position_type random_vector(length_type const& r, rng_type& rng) const { return multiply(base_type::shape().unit_z(), (rng.uniform_int(0, 1) * 2 - 1) * r); } - virtual position_type bd_displacement(length_type const& r, rng_type& rng) const + // BD displacement for BD on the axis of the cylinder + virtual position_type bd_displacement(length_type const& mean, length_type const& r, rng_type& rng) const + { + return multiply(base_type::shape().unit_z(), rng.normal(mean, r)); + } + + /*** New BD scheme functions ***/ + // Rate for binding to particle on the structure + virtual Real get_1D_rate_geminate( Real const& k, length_type const& r01) const + { + return k; + } + + // Rate for binding to the structure + virtual Real get_1D_rate_surface( Real const& k, length_type const& r0 ) const + { + return k / ( 2 * M_PI * (base_type::shape().radius() + r0 ) ); + } + + // Reaction volume for binding to particle in the structure + virtual Real particle_reaction_volume( length_type const& r01, length_type const& rl ) const { - return multiply(base_type::shape().unit_z(), rng.normal(0., r)); + return rl; } + // Reaction volume for binding to the structure + virtual Real surface_reaction_volume( length_type const& r0, length_type const& rl ) const + { + length_type rc( base_type::shape().radius() + r0 ); + length_type rcl( rc + rl ); + length_type rcl_sq( rcl * rcl ); + length_type rc_sq( rc * rc ); + + return M_PI * ( rcl_sq - rc_sq ); + } + + // Vector of dissociation from the structure into the bulk + virtual position_type surface_dissociation_vector( rng_type& rng, length_type const& r0, length_type const& rl ) const + { + Real X( rng.uniform(0.,1.) ); + length_type const rod_radius = base_type::shape().radius(); + position_type const unit_z = base_type::shape().unit_z(); + + // Calculate the length of the vector first + length_type const rrl( rod_radius + r0 + rl ); + length_type const rrl_sq( gsl_pow_2(rrl) ); + length_type const rr_sq( gsl_pow_2(rod_radius + r0) ); + // Create a random length between rr_sq and rrl_sq + length_type const diss_vec_length( sqrt( rr_sq + X * (rrl_sq - rr_sq) ) ); + + // Create a 3D vector with totally random orientation + position_type v(rng.uniform(0.,1.) - .5, rng.uniform(0.,1.) - .5, rng.uniform(0.,1.) - .5); + // Subtract the part parallel to the axis to get the orthogonal components and normalize + // This creates a normed random vector orthogonal to the cylinder axis + v = normalize( subtract(v, multiply( unit_z, dot_product( unit_z, v ) ) ) ); + + // Return the created vector with the right length + return multiply( v, diss_vec_length); + } + + // Normed direction of dissociation from the structure to parent structure + virtual position_type surface_dissociation_unit_vector( rng_type& rng ) const + { + position_type const unit_z = base_type::shape().unit_z(); + // Create a 3D vector with totally random orientation + position_type v(rng.uniform(0.,1.) - .5, rng.uniform(0.,1.) - .5, rng.uniform(0.,1.) - .5); + // Subtract the part parallel to the axis to get the orthogonal components and normalize + // This creates a normed random vector orthogonal to the cylinder axis + v = normalize( subtract(v, multiply( unit_z, dot_product( unit_z, v ) ) ) ); + + return v; + } + + // Vector used to determine whether a particle has crossed the structure + // Here we return the zero-vector because there is no "sides" to cross + virtual position_type const side_comparison_vector() const + { + return create_vector(0.0, 0.0, 0.0); + } + + // Positions created at dissociation of one particle on the structure into two particles on the structure + virtual position_pair_type geminate_dissociation_positions( rng_type& rng, species_type const& s0, species_type const& s1, position_type const& op, + length_type const& rl ) const + { + length_type const r01( s0.radius() + s1.radius() ); + Real const D01( s0.D() + s1.D() ); + + Real const X( rng.uniform(0.,1.) ); + + length_type const diss_vec_length( X*rl + r01 ); + + position_type const m( random_vector( diss_vec_length, rng ) ); + + return position_pair_type( op - m * s0.D() / D01, + op + m * s1.D() / D01 ); + } + + // Positions created at dissociation of one particle on the structure into two particles, one of which ends up in the bulk + virtual position_pair_type special_geminate_dissociation_positions( rng_type& rng, species_type const& s_surf, species_type const& s_bulk, + position_type const& op_surf, length_type const& rl ) const + { + + // FIXME FIXME FIXME What the hell is all of that calculation? What are these angles / transformations? + // This should just place the dissociating particle in the reaction volume close to the cylinder! + // NOTE Probably just copied from PlanarSurface ? + + Real const rod_radius( base_type::shape().radius() ); + + //Species living on the rod should have a larger radius than the rod. + assert( rod_radius < s_surf.radius() ); // FIXME This is dangerous to put here! Revise! + + length_type const r01( s_bulk.radius() + s_surf.radius() ); + Real const D01( s_bulk.D() + s_surf.D() ); + Real const D_bulk_D01( s_bulk.D() / D01 ); + Real const D_surf_D01( s_surf.D() / D01 ); + + //Commented code for direct binding case with c.o.m. reaction. + //Real const theta_min( asin(rod_radius / r01) ); + Real const theta_min( asin( (rod_radius + s_bulk.radius()) / r01) ); + Real const theta( theta_min + rng.uniform(0.,1.) * (M_PI - 2 * theta_min) ); + Real const phi( rng.uniform(0.,1.) * 2 * M_PI ); + + Real const X( rng.uniform(0.,1.) ); + length_type const r01l( r01 + rl ); + length_type const r01l_cb( r01l * r01l * r01l ); + length_type const r01_cb( r01 * r01 * r01 ); + + length_type const diss_vec_length( cbrt( X * (r01l_cb - r01_cb) + r01_cb ) ); + + position_type v; + v[0] = 1.; v[1] = 1.; v[2] = 1.; + + position_type const unit_z( base_type::shape().unit_z() ); + position_type const unit_x( normalize( subtract( v, + multiply( unit_z, dot_product(v, unit_z) ) ) ) ); + position_type const unit_y( normalize( cross_product( unit_x, unit_z ) ) ); + + length_type const x( diss_vec_length * sin( theta ) * cos( phi ) ); + length_type const y( diss_vec_length * sin( theta ) * sin( phi ) ); + length_type const z( diss_vec_length * cos( theta ) ); + + position_pair_type pp01; + + // This is the particle that ends up on the structure + pp01.first = subtract( op_surf, unit_z * (z * D_surf_D01) ); + + // This is the particle that ends up in the bulk + pp01.second = add( op_surf, + add( unit_x * x, + add( unit_y * y, + unit_z * (z * D_bulk_D01) ) ) ); + + return pp01; + } + + // Used by newBDPropagator + virtual length_type newBD_distance(position_type const& new_pos, length_type const& radius, position_type const& old_pos, length_type const& sigma) const + { + return base_type::distance(new_pos); + } + + /* TODO what is that? virtual length_type minimal_distance(length_type const& radius) const { length_type cylinder_radius = base_type::shape().radius(); // Return minimal distance *to* surface. return (cylinder_radius + radius) * traits_type::MINIMAL_SEPARATION_FACTOR - cylinder_radius; } + */ + + /*** Boundary condition handling ***/ + // FIXME This is a mess but it works. See ParticleContainerBase.hpp for explanation. + virtual position_structid_pair_type apply_boundary(position_structid_pair_type const& pos_struct_id, + structure_container_type const& structure_container) const + { + return structure_container.apply_boundary(*this, pos_struct_id); + } + + virtual position_structid_pair_type cyclic_transpose(position_structid_pair_type const& pos_struct_id, + structure_container_type const& structure_container) const + { + return pos_struct_id; // for now we do not support connected cylindrical surfaces. + } + + + // *** Dynamic dispatch for the structure functions *** // + // *** 1 *** - One new position + // This requires a double dynamic dispatch. + // First dispatch + virtual position_structid_pair_type get_pos_sid_pair(structure_type const& target_structure, position_type const& position, + length_type const& offset, length_type const& reaction_length, rng_type& rng) const + { + return target_structure.get_pos_sid_pair_helper(*this, position, offset, reaction_length, rng); + } + // Second dispatch + virtual position_structid_pair_type get_pos_sid_pair_helper(CuboidalRegion const& origin_structure, position_type const& position, + length_type const& offset, length_type const& rl, rng_type& rng) const + { + return this->get_pos_sid_pair_helper_any >(origin_structure, position, offset, rl, rng); + } + virtual position_structid_pair_type get_pos_sid_pair_helper(SphericalSurface const& origin_structure, position_type const& position, + length_type const& offset, length_type const& rl, rng_type& rng) const + { + return this->get_pos_sid_pair_helper_any >(origin_structure, position, offset, rl, rng); + } + virtual position_structid_pair_type get_pos_sid_pair_helper(CylindricalSurface const& origin_structure, position_type const& position, + length_type const& offset, length_type const& rl, rng_type& rng) const + { + return this->get_pos_sid_pair_helper_any >(origin_structure, position, offset, rl, rng); + } + virtual position_structid_pair_type get_pos_sid_pair_helper(DiskSurface const& origin_structure, position_type const& position, + length_type const& offset, length_type const& rl, rng_type& rng) const + { + return this->get_pos_sid_pair_helper_any >(origin_structure, position, offset, rl, rng); + } + virtual position_structid_pair_type get_pos_sid_pair_helper(PlanarSurface const& origin_structure, position_type const& position, + length_type const& offset, length_type const& rl, rng_type& rng) const + { + return this->get_pos_sid_pair_helper_any >(origin_structure, position, offset, rl, rng); + } + // The template function that defines the actual final dispatch procedure. + template + position_structid_pair_type get_pos_sid_pair_helper_any(Tstruct_ const& origin_structure, position_type const& position, + length_type const& offset, length_type const& rl, rng_type& rng) const + { + // redirect to structure function with well-defined typing + return ::get_pos_sid_pair(origin_structure, *this, position, offset, rl, rng); + }; + + // *** 2 *** - Two new positions + // Same principle as above, but different return type + // First dispatch + virtual position_structid_pair_pair_type get_pos_sid_pair_pair(structure_type const& target_structure, position_type const& position, + species_type const& s1, species_type const& s2, length_type const& reaction_length, rng_type& rng) const + { + return target_structure.get_pos_sid_pair_pair_helper(*this, position, s1, s2, reaction_length, rng); + } + // Second dispatch + virtual position_structid_pair_pair_type get_pos_sid_pair_pair_helper(CuboidalRegion const& origin_structure, position_type const& position, + species_type const& s_orig, species_type const& s_targ, length_type const& rl, rng_type& rng) const + { + return this->get_pos_sid_pair_pair_helper_any >(origin_structure, position, s_orig, s_targ, rl, rng); + } + virtual position_structid_pair_pair_type get_pos_sid_pair_pair_helper(SphericalSurface const& origin_structure, position_type const& position, + species_type const& s_orig, species_type const& s_targ, length_type const& rl, rng_type& rng) const + { + return this->get_pos_sid_pair_pair_helper_any >(origin_structure, position, s_orig, s_targ, rl, rng); + } + virtual position_structid_pair_pair_type get_pos_sid_pair_pair_helper(CylindricalSurface const& origin_structure, position_type const& position, + species_type const& s_orig, species_type const& s_targ, length_type const& rl, rng_type& rng) const + { + return this->get_pos_sid_pair_pair_helper_any >(origin_structure, position, s_orig, s_targ, rl, rng); + } + virtual position_structid_pair_pair_type get_pos_sid_pair_pair_helper(DiskSurface const& origin_structure, position_type const& position, + species_type const& s_orig, species_type const& s_targ, length_type const& rl, rng_type& rng) const + { + return this->get_pos_sid_pair_pair_helper_any >(origin_structure, position, s_orig, s_targ, rl, rng); + } + virtual position_structid_pair_pair_type get_pos_sid_pair_pair_helper(PlanarSurface const& origin_structure, position_type const& position, + species_type const& s_orig, species_type const& s_targ, length_type const& rl, rng_type& rng) const + { + return this->get_pos_sid_pair_pair_helper_any >(origin_structure, position, s_orig, s_targ, rl, rng); + } + // The template function that defines the actual final dispatch procedure. + template + position_structid_pair_pair_type get_pos_sid_pair_pair_helper_any(Tstruct_ const& origin_structure, position_type const& position, + species_type const& s_orig, species_type const& s_targ, length_type const& rl, rng_type& rng) const + { + // redirect to structure function with well-defined typing + return ::get_pos_sid_pair_pair(origin_structure, *this, position, s_orig, s_targ, rl, rng); + }; + + // *** 3 *** - Pair reactions => two origin structures + // First dispatch +// // Overloading get_pos_sid_pair with signature (origin_structure2, target_structure_type_id, ...) +// virtual position_structid_pair_type get_pos_sid_pair(structure_type const& origin_structure2, structure_type_id_type const& target_sid, position_type const& CoM, +// length_type const& offset, length_type const& reaction_length, rng_type& rng) const +// { +// // this just redirects +// return this->get_pos_sid_pair_2o(origin_structure2, target_sid, CoM, offset, reaction_length, rng); +// } +// // The actual implementation of the first dispatch + virtual position_structid_pair_type get_pos_sid_pair_2o(structure_type const& origin_structure2, structure_type_id_type const& target_sid, + position_type const& CoM, length_type const& offset, length_type const& reaction_length, rng_type& rng) const + { + return origin_structure2.get_pos_sid_pair_2o_helper(*this, target_sid, CoM, offset, reaction_length, rng); + } + // Second dispatch + virtual position_structid_pair_type get_pos_sid_pair_2o_helper(CuboidalRegion const& origin_structure1, structure_type_id_type const& target_sid, + position_type const& CoM, length_type const& offset, length_type const& rl, rng_type& rng) const + { + return this->get_pos_sid_pair_2o_helper_any >(origin_structure1, target_sid, CoM, offset, rl, rng); + } + virtual position_structid_pair_type get_pos_sid_pair_2o_helper(SphericalSurface const& origin_structure1, structure_type_id_type const& target_sid, + position_type const& CoM, length_type const& offset, length_type const& rl, rng_type& rng) const + { + return this->get_pos_sid_pair_2o_helper_any >(origin_structure1, target_sid, CoM, offset, rl, rng); + } + virtual position_structid_pair_type get_pos_sid_pair_2o_helper(CylindricalSurface const& origin_structure1, structure_type_id_type const& target_sid, + position_type const& CoM, length_type const& offset, length_type const& rl, rng_type& rng) const + { + return this->get_pos_sid_pair_2o_helper_any >(origin_structure1, target_sid, CoM, offset, rl, rng); + } + virtual position_structid_pair_type get_pos_sid_pair_2o_helper(DiskSurface const& origin_structure1, structure_type_id_type const& target_sid, + position_type const& CoM, length_type const& offset, length_type const& rl, rng_type& rng) const + { + return this->get_pos_sid_pair_2o_helper_any >(origin_structure1, target_sid, CoM, offset, rl, rng); + } + virtual position_structid_pair_type get_pos_sid_pair_2o_helper(PlanarSurface const& origin_structure1, structure_type_id_type const& target_sid, + position_type const& CoM, length_type const& offset, length_type const& rl, rng_type& rng) const + { + return this->get_pos_sid_pair_2o_helper_any >(origin_structure1, target_sid, CoM, offset, rl, rng); + } + // The template function that defines the actual final dispatch procedure. + template + position_structid_pair_type get_pos_sid_pair_2o_helper_any(Tstruct_ const& origin_structure1, structure_type_id_type const& target_sid, position_type const& CoM, + length_type const& offset, length_type const& reaction_length, rng_type& rng) const + { + // This method has to figure out where the product will be placed in case of a bimolecular reaction. + // As a default, we place particles on the substructure or the lower-dimensional structure. If the structures + // have the same structure type (=> same dimensionality) it does not matter on which structure we put the product, + // as long as it has the structure type id of the product species. This is handled in cases '1' below. + + // 1 - Check whether one of the structures is the parent of the other. If yes, the daughter structure is the target. + if( this->is_parent_of_or_has_same_sid_as(origin_structure1) && origin_structure1.has_valid_target_sid(target_sid) ) + // origin_structure1 is target + return ::get_pos_sid_pair(*this, origin_structure1, CoM, offset, reaction_length, rng); + + else if( origin_structure1.is_parent_of_or_has_same_sid_as(*this) && this->has_valid_target_sid(target_sid) ) + // this structure is target + return ::get_pos_sid_pair(origin_structure1, *this, CoM, offset, reaction_length, rng); + + // 2 - Check which structures has the lower dimensionality / particle degrees of freedom, and put the product there. + else if( origin_structure1.shape().dof() < this->shape().dof() && origin_structure1.has_valid_target_sid(target_sid) ) + // origin_structure1 is target + return ::get_pos_sid_pair(*this, origin_structure1, CoM, offset, reaction_length, rng); + + else if( this->shape().dof() < origin_structure1.shape().dof() && this->has_valid_target_sid(target_sid) ) + // this structure is target + return ::get_pos_sid_pair(origin_structure1, *this, CoM, offset, reaction_length, rng); + + else throw propagation_error("Invalid target structure type: does not match product species structure type or has wrong hierarchy or dimensionality."); + } + +// // *** 4 *** - Generalized functions for pair reactions with two origin structures and one target structure +// // NOTE: This is yet unused, but possibly useful in the future. +// // Overloading get_pos_sid_pair again with signature (origin_structure2, target_structure, ...) and introducing +// // a triple dynamic dispatch. +// virtual position_structid_pair_type get_pos_sid_pair(structure_type const& origin_structure2, structure_type const& target_structure, position_type const& position, +// length_type const& offset, length_type const& reaction_length, rng_type& rng) const +// { +// return origin_structure2.get_pos_sid_pair_helper1(*this, target_structure, position, offset, reaction_length, rng); +// } + + + /*** Formerly used functions of the Morelli scheme ***/ + // DEPRECATED + virtual length_type drawR_gbd(Real const& rnd, length_type const& r01, Real const& dt, Real const& D01, Real const& v) const + { + return drawR_gbd_1D(rnd, r01, dt, D01, v); + } + // DEPRECATED + virtual Real p_acceptance(Real const& k_a, Real const& dt, length_type const& r01, position_type const& ipv, + Real const& D0, Real const& D1, Real const& v0, Real const& v1) const + { + /* + The I_bd factors used for calculating the acceptance probability are dependent on the direction + of the overlap step (r = r_1 - r_0), compared to the direction of the drift. + The I_bd factors are defined for a particle creating an overlap comming from the right (r < 0). + Since the I_bd terms calulated here are for the backward move, we have to invert their drifts. + When the particle comes from the left (r > 0) we have to invert its drift again. + + ---Code below is used for drift dependent backstep. + + Real numerator = g_bd_1D(ipv, r01, dt, D0, -v0); + Real denominator = g_bd_1D(ipv, r01, dt, D0, v0)*exp( ipv/abs_ipv*(abs_ipv - r01)*v/D01 ); + Real correction = numerator/denominator; + if( ipv < 0 ) + return correction*( k_a * dt / ( I_bd_1D(r01, dt, D0, -v0) + I_bd_1D(r01, dt, D1, v1) ) ); + else + return correction*( k_a * dt / ( I_bd_1D(r01, dt, D0, v0) + I_bd_1D(r01, dt, D1, -v1) ) ); + + Also change v -> -v in drawR for the dissociation move. + */ + + return 0.5*( k_a * dt / ( I_bd_1D(r01, dt, D0, v0) + I_bd_1D(r01, dt, D1, v1) ) ); + + } + // DEPRECATED + virtual position_type dissociation_vector( rng_type& rng, length_type const& r01, Real const& dt, + Real const& D01, Real const& v ) const + { + return random_vector(drawR_gbd(rng.uniform(0., 1.), r01, dt, D01, v), rng); + } virtual void accept(ImmutativeStructureVisitor const& visitor) const { @@ -51,8 +466,8 @@ class CylindricalSurface visitor(*this); } - CylindricalSurface(identifier_type const& id, shape_type const& shape) - : base_type(id, shape) {} + CylindricalSurface(structure_name_type const& name, structure_type_id_type const& sid, structure_id_type const& parent_struct_id, shape_type const& shape) + : base_type(name, sid, parent_struct_id, shape) {} }; #endif /* CYLINDRICAL_SURFACE_HPP */ diff --git a/Defs.hpp b/Defs.hpp index 59adb376..5ba7b46f 100644 --- a/Defs.hpp +++ b/Defs.hpp @@ -6,20 +6,13 @@ typedef double Real; typedef long int Integer; typedef unsigned long int UnsignedInteger; -typedef size_t Index; -// stringifiers. see preprocessor manual -#define XSTR( S ) STR( S ) #define STR( S ) #S +#define THROW_UNLESS( CLASS, EXPRESSION )\ + if(!(EXPRESSION))\ + throw CLASS("Check ["+std::string(STR(EXPRESSION)) + "] failed."); -#define THROW_UNLESS( CLASS, EXPRESSION ) \ - if( ! ( EXPRESSION ) )\ - {\ - throw CLASS( "Check [" + std::string( STR( EXPRESSION ) ) +\ - "] failed." );\ - }\ - - -#define IGNORE_RETURN (void) +const Real SEPARATION_TOLERANCE( 1e-07 ); +const Real MINIMAL_SEPARATION_FACTOR( 1.0 + SEPARATION_TOLERANCE ); #endif // __DEFS_HPP diff --git a/Disk.hpp b/Disk.hpp new file mode 100644 index 00000000..676cc33a --- /dev/null +++ b/Disk.hpp @@ -0,0 +1,301 @@ +#ifndef DISK_HPP +#define DISK_HPP + +#include +#include +#include "Vector3.hpp" +#include "Shape.hpp" +#include "linear_algebra.hpp" +#include "utils/math.hpp" + +template +class Disk +{ +public: + typedef T_ value_type; + typedef Vector3 position_type; + typedef T_ length_type; + typedef enum side_enum_type {} side_enum_type; // The typedef is a little bit C style but doesn't matter for C++ + +public: + // constructors + Disk() + : position_(), radius_(0), unit_z_() {} + + Disk(position_type const& position, length_type const& radius, + position_type const& unit_z) + : position_(position), radius_(radius), unit_z_(unit_z) {} + + + bool operator==(const Disk& rhs) const + { + return position_ == rhs.position() && radius_ == rhs.radius() && unit_z_ == rhs.unit_z(); + } + + bool operator!=(const Disk& rhs) const + { + return !operator==(rhs); + } + + position_type const& position() const + { + return position_; + } + + position_type& position() + { + return position_; + } + + length_type const& radius() const + { + return radius_; + } + + length_type& radius() + { + return radius_; + } + + position_type const& unit_z() const + { + return unit_z_; + } + + position_type& unit_z() + { + return unit_z_; + } + + const int dof() const + { // degrees of freedom for particle movement + return 0; + } + + std::string show(int precision) + { + std::ostringstream strm; + strm.precision(precision); + strm << *this; + return strm.str(); + } + +/////// Member variables +private: + position_type position_; // centre. + length_type radius_; + position_type unit_z_; // Z-unit_z. should be normalized. +}; + + +//////// Inline functions +template +inline std::basic_ostream& operator<<(std::basic_ostream& strm, + const Disk& v) +{ + strm << "{" << v.position() << ", " << v.radius() << ", " << v.unit_z() << ", " << "}"; + return strm; +} + +template +inline boost::array::length_type, 2> +to_internal(Disk const& obj, typename Disk::position_type const& pos) +{ + // Same as for the cylinder: + // Return pos relative to position of the disk. + typedef typename Disk::position_type position_type; + typedef typename Disk::length_type length_type; + + const position_type pos_vector(subtract(pos, obj.position())); + + const length_type z( dot_product(pos_vector, obj.unit_z()) ); // z can be < 0 + const length_type r( length(subtract(pos_vector, multiply(obj.unit_z(), z))) );// r is always >= 0 + + return array_gen::length_type>(r, z); +} + +template +inline std::pair::position_type, + std::pair::length_type, + typename Disk::length_type> > +project_point(Disk const& obj, typename Disk::position_type const& pos) +// Calculates the projection of 'pos' onto the disk 'obj' and also returns the coefficient +// for the normal component (z) of 'pos' in the basis of the disk and the distance of the +// projected point to the 'edge' of the disk. Here a positive number means the projected +// point is outside the disk, and a negative numbers means it is 'inside' the disk. +// As a special case, we calculate the projection differently for a position that is in +// the plane of the disk. Then we imagine that the particle is always 'above' the structure +// and return -1 as a standard, instead of the distance to the disk center. +{ + typedef typename Disk::length_type length_type; + typedef typename Disk::position_type position_type; + + // Here we do not call 'to_internal' for efficiency + const position_type pos_vector(subtract(pos, obj.position())); + + const length_type z ( dot_product(pos_vector, obj.unit_z()) ); + const position_type r_vector (subtract(pos_vector, multiply(obj.unit_z(), z))); + const length_type r (length(r_vector)); + assert(r >= 0.0); + + // The quantities that will be return, with default values + // for the standard case (pos is not in plane of disk) + position_type proj_pos( add(obj.position(), r_vector) ); + length_type normal_comp( z ); + length_type dist_to_edge( r - obj.radius() ); + + // Special case: pos is in the same plane as the disk + if( feq(z, 0.0, obj.radius()) ){ // third argument is typical scale + + proj_pos = obj.position(); // projected position = disk center + normal_comp = r; + dist_to_edge = -1.0; + } + + return std::make_pair( proj_pos, + std::make_pair(normal_comp, dist_to_edge) ); +} + +// The same as in case of the plane: project_point_on_surface = project_point +template +inline std::pair::position_type, + std::pair::length_type, + typename Disk::length_type> > +project_point_on_surface(Disk const& obj, typename Disk::position_type const& pos) +{ + return project_point(obj, pos); +} + +template +inline typename Disk::length_type +distance(Disk const& obj, + typename Disk::position_type const& pos) +{ + //typedef typename Disk::position_type position_type; + typedef typename Disk::length_type length_type; + + /* First compute the (r,z) components of pos in a coordinate system + * defined by the vectors unitR and unit_z, where unitR is + * choosen such that unitR and unit_z define a plane in which + * pos lies. */ + const boost::array r_z(to_internal(obj, pos)); + + /* Then compute distance to cylinder with zero length. */ + const length_type dz(std::fabs(r_z[1])); + const length_type dr(r_z[0] - obj.radius()); + length_type distance; + + if (dr > 0) + { + // pos is not above the disk. + // Compute distance to edge. + distance = std::sqrt( dz * dz + dr * dr ); + } + else + { + // pos is above the disk. + distance = dz; + } + + return distance; +} + +template +inline std::pair::position_type, bool> +deflect(Disk const& obj, + typename Disk::position_type const& r0, + typename Disk::position_type const& d ) +{ + // Displacements are not deflected on disks (yet), + // but this function has to be defined for every shape to be used in structure. + // For now it just returns original pos. + displacement. The changeflage = false. + return std::make_pair( add(r0, d), false ); +} +/* +template +inline typename Disk::position_type +deflect_back(Disk const& obj, + typename Disk::position_type const& r, + typename Disk::position_type const& u_z ) +{ + // Return the vector r without any changes + return r; +} +*/ + +template +inline typename Disk::position_type +random_position(Disk const& shape, Trng& rng) +{ + // The disk has only one "legal" position = its center + return shape.position(); +} + +template +inline Disk const& shape(Disk const& shape) +{ + return shape; +} + +template +inline Disk& shape(Disk& shape) +{ + return shape; +} + +template +struct is_shape >: public boost::mpl::true_ {}; + +template +struct shape_position_type > +{ + typedef typename Disk::position_type type; +}; + +template +struct shape_length_type > { + typedef typename Disk::length_type type; +}; + +template +inline typename shape_length_type >::type const& shape_size(Disk const& shape) +{ + return shape.radius(); +} + +template +inline typename shape_length_type >::type& shape_size(Disk& shape) +{ + return shape.radius(); +} + +#if defined(HAVE_TR1_FUNCTIONAL) +namespace std { namespace tr1 { +#elif defined(HAVE_STD_HASH) +namespace std { +#elif defined(HAVE_BOOST_FUNCTIONAL_HASH_HPP) +namespace boost { +#endif + +template +struct hash > +{ + typedef Disk argument_type; + + std::size_t operator()(argument_type const& val) + { + return hash()(val.position()) ^ + hash()(val.radius()) ^ + hash()(val.unit_z()); + } +}; + +#if defined(HAVE_TR1_FUNCTIONAL) +} } // namespace std::tr1 +#elif defined(HAVE_STD_HASH) +} // namespace std +#elif defined(HAVE_BOOST_FUNCTIONAL_HASH_HPP) +} // namespace boost +#endif + +#endif /* DISK_HPP */ diff --git a/DiskSurface.hpp b/DiskSurface.hpp new file mode 100644 index 00000000..3707f80a --- /dev/null +++ b/DiskSurface.hpp @@ -0,0 +1,538 @@ +#ifndef DISK_SURFACE_HPP +#define DISK_SURFACE_HPP + +#include +#include "Surface.hpp" +#include "Disk.hpp" +#include "freeFunctions.hpp" +#include "StructureFunctions.hpp" +#include "geometry.hpp" + +template +class StructureContainer; + +template +class CuboidalRegion; + +template +class CylindricalSurface; + +template +class SphericalSurface; + +template +class DiskSurface; + +template +class PlanarSurface; + + +template +class DiskSurface + : public BasicSurfaceImpl > +{ + // The DiskSurface is the implementation of a Basic surface parameterized with a Disk + +public: + typedef BasicSurfaceImpl > base_type; + typedef Ttraits_ traits_type; + typedef typename base_type::structure_name_type structure_name_type; + typedef typename base_type::structure_id_type structure_id_type; + typedef typename base_type::structure_type_id_type structure_type_id_type; + typedef typename base_type::shape_type shape_type; + typedef typename base_type::rng_type rng_type; + typedef typename base_type::position_type position_type; + typedef typename base_type::length_type length_type; + typedef typename base_type::side_enum_type side_enum_type; + typedef typename traits_type::species_type species_type; + typedef typename traits_type::structure_type structure_type; + + typedef StructureContainer, structure_id_type, traits_type> structure_container_type; + + typedef std::pair position_pair_type; + typedef std::pair position_structid_pair_type; + typedef std::pair position_structid_pair_pair_type; + + // As a specialty, the DiskStructure has flags that can be set to specify its usage/behavior. + // + // If a DiskStructure is set to be a "barrier" ("cap") particles cannot pass by it, unless they + // first bind to it and then unbind. Usually this is used to "cap" a cylinder, i.e. to place a + // reactive disk at the end of a finite cylinder. + // If it is a "sink" it is inside the cylinder; in that case, particle can diffuse past it without reacting + // and the main loop will try to make a CylindricalSurfaceSinkInteraction domain, which also allows + // for diffusion of the particle past the sink. If the disk is a cap, particles cannot diffuse past it! + // + // In addition, the flag "RADIAL_DISSOCIATION" can be used to make the particle not move radially upon + // a dissociation event, but axially (i.e., in direction of the cylinder axis of the parent cylinder) + // This can be used for disks that are "sinks" (binding sites) on cylindrical structures + // + bool IS_BARRIER; // by default the disk is a cap/barrier to particles; automatically set by constructor below + bool RADIAL_DISSOCIATION; // accordingly, this is assumed to be true by default as well + // Setters for these flags + virtual void treat_as_sink() + { + this->IS_BARRIER = false; + } + virtual void treat_as_barrier() + { + this->IS_BARRIER = true; + } + virtual void forbid_radial_dissociation() + { + this->RADIAL_DISSOCIATION = false; + } + virtual void allow_radial_dissociation() + { + this->RADIAL_DISSOCIATION = true; + } + // Getters + virtual bool const& is_barrier() const + { + return this->IS_BARRIER; + } + virtual bool const& dissociates_radially() const + { + return this->RADIAL_DISSOCIATION; + } + + /*** Info functions ***/ + virtual position_type const& position() const + { + return base_type::shape().position(); + } + + /*** Simple structure-specific sampling functions ***/ + // Produce a "random position" in the disk, which is always its center (the only legal pos.) + virtual position_type random_position(rng_type& rng) const + { + return ::random_position(base_type::shape(), boost::bind(&rng_type::uniform, rng, -1., 1.)); + // return base_type::shape().position(); // TODO maybe this variant is better + } + + // Procude a "random vector" on the disk; returns the same as random_position() + virtual position_type random_vector(length_type const& r, rng_type& rng) const + { + return base_type::shape().position(); + } + + // BD displacement = zero vector because there is only one legal position on the disk + virtual position_type bd_displacement(length_type const& mean, length_type const& r, rng_type& rng) const + { + return multiply(base_type::shape().unit_z(), 0.0); // TODO is there not cheaper way to pass a zero vector? + } + + /*** New BD scheme functions ***/ + // Rate for binding to particle on the structure + virtual Real get_1D_rate_geminate( Real const& k, length_type const& r01) const + { + // Same as for particle-particle reactions on the cylinder + return k; + } + + // Rate for binding to the structure + virtual Real get_1D_rate_surface( Real const& k, length_type const& r0 ) const + { + return k; + } + + // Reaction volume for binding to particle in the structure + virtual Real particle_reaction_volume( length_type const& r01, length_type const& rl ) const + { + // The disk can only hold 1 particle; this function therefore never should be called. + return rl; + + // FIXME: This reaction volume is only correct for particles coming from a rod. + // If the interaction partner of the disk particle comes from a plane or from + // the bulk, a different volume factor shall be used. Then the return value of + // this function would depend on properties of the asker -> how to do??? + } + + // Reaction volume for binding to the structure + virtual Real surface_reaction_volume( length_type const& r0, length_type const& rl ) const + { + // The reaction volume for a particle on the rod interacting with a disk; + // should be the same as for two particles interacting with each other on the rod. + return rl; + } + + // Vector of dissociation from the structure into the bulk + virtual position_type surface_dissociation_vector( rng_type& rng, length_type const& offset, length_type const& rl ) const + { + // This function produces a position for a particle unbinding from the disk. + // If it unbinds radially (standard case), it should lie within the reaction volume around the disk + // (Note that in that case this is the same code as for the CylindricalSurface!) + // If it unbinds axially (i.e., back to the cylinder), it does not change its position. + // We therefore initialize the new position with the Disk position by default, + // and only create a new vector if needed below. + position_type new_pos( base_type::shape().position() ); + + if( this->RADIAL_DISSOCIATION == true ){ + + Real X( rng.uniform(0.,1.) ); + length_type const disk_radius = base_type::shape().radius(); + position_type const unit_z = base_type::shape().unit_z(); + + // Calculate the length of the vector first + length_type const rrl( disk_radius + offset + rl ); + length_type const rrl_sq( gsl_pow_2(rrl) ); + length_type const rr_sq( gsl_pow_2(disk_radius + offset) ); + // Create a random length between rr_sq and rrl_sq + length_type const diss_vec_length( sqrt( rr_sq + X * (rrl_sq - rr_sq) ) ); + + // Create a 3D vector with totally random orientation + position_type v(rng.uniform(0.,1.) - .5, rng.uniform(0.,1.) - .5, rng.uniform(0.,1.) - .5); + // Subtract the part parallel to the axis to get the orthogonal components and normalize + // This creates a normed random vector in the disk plane + v = normalize( subtract(v, multiply( unit_z, dot_product( unit_z, v ) ) ) ); + + new_pos = multiply( v, MINIMAL_SEPARATION_FACTOR * diss_vec_length); + // TODO define a global MINIMAL_SEPARATION_FACTOR also for BD mode + } + else + ; // nothing changes, new_pos already correctly initialized above + + return new_pos; + + } + + // Normed direction of dissociation from the structure to parent structure + virtual position_type surface_dissociation_unit_vector( rng_type& rng ) const + { + return base_type::shape().unit_z(); // FIXME + } + + // Vector used to determine whether a particle has crossed the structure + // Here we return the zero-vector because there is no "sides" to cross + virtual position_type const side_comparison_vector() const + { + return create_vector(0.0, 0.0, 0.0); + } + + // Positions created at dissociation of one particle on the structure into two particles on the structure + virtual position_pair_type geminate_dissociation_positions( rng_type& rng, species_type const& s0, species_type const& s1, position_type const& op, + length_type const& rl ) const + { + // The positions of a particle dissociating into two new ones on the disk; should never happen, + // therefore this function just returns a dummy positions pair (2x the disk center) + return position_pair_type( base_type::shape().position(), base_type::shape().position() ); + } + + // Positions created at dissociation of one particle on the structure into two particles, one of which ends up in the bulk + virtual position_pair_type special_geminate_dissociation_positions( rng_type& rng, species_type const& s_disk, species_type const& s_diss, + position_type const& reactant_pos, length_type const& rl ) const + { + // This function produces two new positions for a dissociating particle in the case + // that one stays on the surface and the other one changes to the parent structure. + // TODO We have to distinguish between sink and cap here and between dissociation onto + // the rod and into the bulk/plane! + + // Note: s_disk = disk-bound species, s_diss = dissociating species (may go to bulk or cylinder) + + // Initialize the position_pair that will hold the new positions of the 2 particles + position_pair_type pp01; + // Particle 0 is the one that stays on the origin structure. It does not move. + pp01.first = reactant_pos; + // Particle 1 is the one that unbinds from the disk and is placed in the reaction volume + // around it (in case of radial unbinding) or next to/touching the first particle + // (taking into account the reaction vol.) on the cylinder (in case of axial unbinding) + + if( this->RADIAL_DISSOCIATION == true ) + { + length_type const disk_radius( base_type::shape().radius() ); + length_type const r01( s_disk.radius() + s_diss.radius() ); + + // The following is the additional distance that we have to pass to surface_dissociation_vector() below + // to place the unbinding particle in contact with the disk particle or the disk, whatever has the larger radius. + // Later surface_dissociation_vector() will add this offset length to the disk_radius. + // If the disk-bound particle is larger than the disk, the final distance should be equal to r01, + // so we subtract disk_radius here (because it will be automatically added later); if in turn the + // disk is larger than the disk-bound particle, the offset is the radius of the dissoc. particle. + length_type offset( s_disk.radius() > disk_radius ? r01 - disk_radius : s_diss.radius()); + + pp01.second = add(reactant_pos, surface_dissociation_vector(rng, offset, rl)); + } + else + { + // Generate a random distance within the reaction volume + Real X( rng.uniform(0.0, 1.0) ); + length_type const r01( s_disk.radius() + s_diss.radius() + X * rl ); + // Generate another random number to determine the direction of dissociaton + Real D( rng.uniform(0.0, 1.0) ); + // Construct the dissociation vector in axial direction (given by unit_z of the disk) + position_type const disk_unit_z( base_type::shape().unit_z() ); + position_type const axial_diss_vector( D>0.5? multiply(disk_unit_z, r01) : multiply(disk_unit_z, -r01) ); + // Add to the original position of the reactant + pp01.second = add(reactant_pos, axial_diss_vector); + } + + return pp01; + } + + // Used by newBDPropagator + virtual length_type newBD_distance(position_type const& new_pos, length_type const& radius, position_type const& old_pos, length_type const& sigma) const + { + const length_type disk_radius (base_type::shape().radius()); + const boost::array new_pos_rz(::to_internal(base_type::shape(), new_pos)); + const boost::array old_pos_rz(::to_internal(base_type::shape(), old_pos)); + if (new_pos_rz[1] * old_pos_rz[1] < 0 && + ( (new_pos_rz[0] < disk_radius ) || (old_pos_rz[0] < disk_radius) ) ) + { + return -1.0 * base_type::distance(new_pos) + sigma; + } + else + { + return base_type::distance(new_pos) + sigma; + } + } +/* + virtual length_type minimal_distance(length_type const& radius) const + { + // TODO + length_type cylinder_radius = base_type::shape().radius(); + // Return minimal distance *to* surface. + return (cylinder_radius + radius) * traits_type::MINIMAL_SEPARATION_FACTOR - cylinder_radius; + } +*/ + /*** Boundary condition handling ***/ + // FIXME This is a mess but it works. See ParticleContainerBase.hpp for explanation. + virtual position_structid_pair_type apply_boundary(position_structid_pair_type const& pos_struct_id, + structure_container_type const& structure_container) const + { + return pos_struct_id; // This seems a little strange, but we assume that particles are immobile on the disk + } + + virtual position_structid_pair_type cyclic_transpose(position_structid_pair_type const& pos_struct_id, + structure_container_type const& structure_container) const + { + return pos_struct_id; // Disks can also not be connected, so no cyclic transpose. + } + + + // *** Dynamic dispatch for the structure functions *** // + // *** 1 *** - One new position + // This requires a double dynamic dispatch. + // First dispatch + virtual position_structid_pair_type get_pos_sid_pair(structure_type const& target_structure, position_type const& position, + length_type const& offset, length_type const& reaction_length, rng_type& rng) const + { + return target_structure.get_pos_sid_pair_helper(*this, position, offset, reaction_length, rng); + } + // Second dispatch + virtual position_structid_pair_type get_pos_sid_pair_helper(CuboidalRegion const& origin_structure, position_type const& position, + length_type const& offset, length_type const& rl, rng_type& rng) const + { + return this->get_pos_sid_pair_helper_any >(origin_structure, position, offset, rl, rng); + } + virtual position_structid_pair_type get_pos_sid_pair_helper(SphericalSurface const& origin_structure, position_type const& position, + length_type const& offset, length_type const& rl, rng_type& rng) const + { + return this->get_pos_sid_pair_helper_any >(origin_structure, position, offset, rl, rng); + } + virtual position_structid_pair_type get_pos_sid_pair_helper(CylindricalSurface const& origin_structure, position_type const& position, + length_type const& offset, length_type const& rl, rng_type& rng) const + { + return this->get_pos_sid_pair_helper_any >(origin_structure, position, offset, rl, rng); + } + virtual position_structid_pair_type get_pos_sid_pair_helper(DiskSurface const& origin_structure, position_type const& position, + length_type const& offset, length_type const& rl, rng_type& rng) const + { + return this->get_pos_sid_pair_helper_any >(origin_structure, position, offset, rl, rng); + } + virtual position_structid_pair_type get_pos_sid_pair_helper(PlanarSurface const& origin_structure, position_type const& position, + length_type const& offset, length_type const& rl, rng_type& rng) const + { + return this->get_pos_sid_pair_helper_any >(origin_structure, position, offset, rl, rng); + } + // The template function that defines the actual final dispatch procedure. + template + position_structid_pair_type get_pos_sid_pair_helper_any(Tstruct_ const& origin_structure, position_type const& position, + length_type const& offset, length_type const& rl, rng_type& rng) const + { + // redirect to structure function with well-defined typing + return ::get_pos_sid_pair(origin_structure, *this, position, offset, rl, rng); + }; + + // *** 2 *** - Two new positions + // Same principle as above, but different return type + // First dispatch + virtual position_structid_pair_pair_type get_pos_sid_pair_pair(structure_type const& target_structure, position_type const& position, + species_type const& s1, species_type const& s2, length_type const& reaction_length, rng_type& rng) const + { + return target_structure.get_pos_sid_pair_pair_helper(*this, position, s1, s2, reaction_length, rng); + } + // Second dispatch + virtual position_structid_pair_pair_type get_pos_sid_pair_pair_helper(CuboidalRegion const& origin_structure, position_type const& position, + species_type const& s_orig, species_type const& s_targ, length_type const& rl, rng_type& rng) const + { + return this->get_pos_sid_pair_pair_helper_any >(origin_structure, position, s_orig, s_targ, rl, rng); + } + virtual position_structid_pair_pair_type get_pos_sid_pair_pair_helper(SphericalSurface const& origin_structure, position_type const& position, + species_type const& s_orig, species_type const& s_targ, length_type const& rl, rng_type& rng) const + { + return this->get_pos_sid_pair_pair_helper_any >(origin_structure, position, s_orig, s_targ, rl, rng); + } + virtual position_structid_pair_pair_type get_pos_sid_pair_pair_helper(CylindricalSurface const& origin_structure, position_type const& position, + species_type const& s_orig, species_type const& s_targ, length_type const& rl, rng_type& rng) const + { + return this->get_pos_sid_pair_pair_helper_any >(origin_structure, position, s_orig, s_targ, rl, rng); + } + virtual position_structid_pair_pair_type get_pos_sid_pair_pair_helper(DiskSurface const& origin_structure, position_type const& position, + species_type const& s_orig, species_type const& s_targ, length_type const& rl, rng_type& rng) const + { + return this->get_pos_sid_pair_pair_helper_any >(origin_structure, position, s_orig, s_targ, rl, rng); + } + virtual position_structid_pair_pair_type get_pos_sid_pair_pair_helper(PlanarSurface const& origin_structure, position_type const& position, + species_type const& s_orig, species_type const& s_targ, length_type const& rl, rng_type& rng) const + { + return this->get_pos_sid_pair_pair_helper_any >(origin_structure, position, s_orig, s_targ, rl, rng); + } + // The template function that defines the actual final dispatch procedure. + template + position_structid_pair_pair_type get_pos_sid_pair_pair_helper_any(Tstruct_ const& origin_structure, position_type const& position, + species_type const& s_orig, species_type const& s_targ, length_type const& rl, rng_type& rng) const + { + // redirect to structure function with well-defined typing + return ::get_pos_sid_pair_pair(origin_structure, *this, position, s_orig, s_targ, rl, rng); + }; + + // *** 3 *** - Pair reactions => two origin structures + // First dispatch +// // Overloading get_pos_sid_pair with signature (origin_structure2, target_structure_type_id, ...) +// virtual position_structid_pair_type get_pos_sid_pair(structure_type const& origin_structure2, structure_type_id_type const& target_sid, position_type const& CoM, +// length_type const& offset, length_type const& reaction_length, rng_type& rng) const +// { +// // this just redirects +// return this->get_pos_sid_pair_2o(origin_structure2, target_sid, CoM, offset, reaction_length, rng); +// } +// // The actual implementation of the first dispatch + virtual position_structid_pair_type get_pos_sid_pair_2o(structure_type const& origin_structure2, structure_type_id_type const& target_sid, + position_type const& CoM, length_type const& offset, length_type const& reaction_length, rng_type& rng) const + { + return origin_structure2.get_pos_sid_pair_2o_helper(*this, target_sid, CoM, offset, reaction_length, rng); + } + // Second dispatch + virtual position_structid_pair_type get_pos_sid_pair_2o_helper(CuboidalRegion const& origin_structure1, structure_type_id_type const& target_sid, + position_type const& CoM, length_type const& offset, length_type const& rl, rng_type& rng) const + { + return this->get_pos_sid_pair_2o_helper_any >(origin_structure1, target_sid, CoM, offset, rl, rng); + } + virtual position_structid_pair_type get_pos_sid_pair_2o_helper(SphericalSurface const& origin_structure1, structure_type_id_type const& target_sid, + position_type const& CoM, length_type const& offset, length_type const& rl, rng_type& rng) const + { + return this->get_pos_sid_pair_2o_helper_any >(origin_structure1, target_sid, CoM, offset, rl, rng); + } + virtual position_structid_pair_type get_pos_sid_pair_2o_helper(CylindricalSurface const& origin_structure1, structure_type_id_type const& target_sid, + position_type const& CoM, length_type const& offset, length_type const& rl, rng_type& rng) const + { + return this->get_pos_sid_pair_2o_helper_any >(origin_structure1, target_sid, CoM, offset, rl, rng); + } + virtual position_structid_pair_type get_pos_sid_pair_2o_helper(DiskSurface const& origin_structure1, structure_type_id_type const& target_sid, + position_type const& CoM, length_type const& offset, length_type const& rl, rng_type& rng) const + { + return this->get_pos_sid_pair_2o_helper_any >(origin_structure1, target_sid, CoM, offset, rl, rng); + } + virtual position_structid_pair_type get_pos_sid_pair_2o_helper(PlanarSurface const& origin_structure1, structure_type_id_type const& target_sid, + position_type const& CoM, length_type const& offset, length_type const& rl, rng_type& rng) const + { + return this->get_pos_sid_pair_2o_helper_any >(origin_structure1, target_sid, CoM, offset, rl, rng); + } + // The template function that defines the actual final dispatch procedure. + template + position_structid_pair_type get_pos_sid_pair_2o_helper_any(Tstruct_ const& origin_structure1, structure_type_id_type const& target_sid, position_type const& CoM, + length_type const& offset, length_type const& reaction_length, rng_type& rng) const + { + // This method has to figure out where the product will be placed in case of a bimolecular reaction. + // As a default, we place particles on the substructure or the lower-dimensional structure. If the structures + // have the same structure type (=> same dimensionality) it does not matter on which structure we put the product, + // as long as it has the structure type id of the product species. This is handled in cases '1' below. + + // 1 - Check whether one of the structures is the parent of the other. If yes, the daughter structure is the target. + if( this->is_parent_of_or_has_same_sid_as(origin_structure1) && origin_structure1.has_valid_target_sid(target_sid) ) + // origin_structure1 is target + return ::get_pos_sid_pair(*this, origin_structure1, CoM, offset, reaction_length, rng); + + else if( origin_structure1.is_parent_of_or_has_same_sid_as(*this) && this->has_valid_target_sid(target_sid) ) + // this structure is target + return ::get_pos_sid_pair(origin_structure1, *this, CoM, offset, reaction_length, rng); + + // 2 - Check which structures has the lower dimensionality / particle degrees of freedom, and put the product there. + else if( origin_structure1.shape().dof() < this->shape().dof() && origin_structure1.has_valid_target_sid(target_sid) ) + // origin_structure1 is target + return ::get_pos_sid_pair(*this, origin_structure1, CoM, offset, reaction_length, rng); + + else if( this->shape().dof() < origin_structure1.shape().dof() && this->has_valid_target_sid(target_sid) ) + // this structure is target + return ::get_pos_sid_pair(origin_structure1, *this, CoM, offset, reaction_length, rng); + + else throw propagation_error("Invalid target structure type: does not match product species structure type or has wrong hierarchy or dimensionality."); + } + +// // *** 4 *** - Generalized functions for pair reactions with two origin structures and one target structure +// // NOTE: This is yet unused, but possibly useful in the future. +// // Overloading get_pos_sid_pair again with signature (origin_structure2, target_structure, ...) and introducing +// // a triple dynamic dispatch. +// virtual position_structid_pair_type get_pos_sid_pair(structure_type const& origin_structure2, structure_type const& target_structure, position_type const& position, +// length_type const& offset, length_type const& reaction_length, rng_type const& rng) const +// { +// return origin_structure2.get_pos_sid_pair_helper1(*this, target_structure, position, offset, reaction_length, rng); +// } + + + /*** Formerly used functions of the Morelli scheme ***/ + // DEPRECATED + virtual length_type drawR_gbd(Real const& rnd, length_type const& r01, Real const& dt, Real const& D01, Real const& v) const + { + // TODO: This is part of the old BD scheme and should be removed at some point + return drawR_gbd_1D(rnd, r01, dt, D01, v); + } + // DEPRECATED + virtual Real p_acceptance(Real const& k_a, Real const& dt, length_type const& r01, position_type const& ipv, + Real const& D0, Real const& D1, Real const& v0, Real const& v1) const + { + // TODO: This is part of the old BD scheme and should be removed at some point + /* + The I_bd factors used for calculating the acceptance probability are dependent on the direction + of the overlap step (r = r_1 - r_0), compared to the direction of the drift. + The I_bd factors are defined for a particle creating an overlap comming from the right (r < 0). + Since the I_bd terms calulated here are for the backward move, we have to invert their drifts. + When the particle comes from the left (r > 0) we have to invert its drift again. + + ---Code below is used for drift dependent backstep. + + Real numerator = g_bd_1D(ipv, r01, dt, D0, -v0); + Real denominator = g_bd_1D(ipv, r01, dt, D0, v0)*exp( ipv/abs_ipv*(abs_ipv - r01)*v/D01 ); + Real correction = numerator/denominator; + if( ipv < 0 ) + return correction*( k_a * dt / ( I_bd_1D(r01, dt, D0, -v0) + I_bd_1D(r01, dt, D1, v1) ) ); + else + return correction*( k_a * dt / ( I_bd_1D(r01, dt, D0, v0) + I_bd_1D(r01, dt, D1, -v1) ) ); + + Also change v -> -v in drawR for the dissociation move. + */ + + return 0.5*( k_a * dt / ( I_bd_1D(r01, dt, D0, v0) + I_bd_1D(r01, dt, D1, v1) ) ); + + } + // DEPRECATED + virtual position_type dissociation_vector( rng_type& rng, length_type const& r01, Real const& dt, + Real const& D01, Real const& v ) const + { + // TODO: This is part of the old BD scheme and should be removed at some point + return random_vector(drawR_gbd(rng.uniform(0., 1.), r01, dt, D01, v), rng); + } + + virtual void accept(ImmutativeStructureVisitor const& visitor) const + { + visitor(*this); + } + + virtual void accept(MutativeStructureVisitor const& visitor) + { + visitor(*this); + } + + DiskSurface(structure_name_type const& name, structure_type_id_type const& sid, structure_id_type const& parent_struct_id, shape_type const& shape) + : base_type(name, sid, parent_struct_id, shape), IS_BARRIER(true), RADIAL_DISSOCIATION(true) {} +}; + +#endif /* DISK_SURFACE_HPP */ diff --git a/EGFRDSimulator.hpp b/EGFRDSimulator.hpp index a40fcecc..a961b27e 100644 --- a/EGFRDSimulator.hpp +++ b/EGFRDSimulator.hpp @@ -44,6 +44,7 @@ struct EGFRDSimulatorTraitsBase: public ParticleSimulatorTraitsBase { typedef ParticleSimulatorTraitsBase base_type; typedef Tworld_ world_type; + typedef ShellID shell_id_type; typedef DomainID domain_id_type; typedef SerialIDGenerator shell_id_generator; @@ -62,6 +63,7 @@ struct EGFRDSimulatorTraitsBase: public ParticleSimulatorTraitsBase static const Real SAFETY = 1. + 1e-5; static const Real SINGLE_SHELL_FACTOR = .1; + static const Real MULTI_SHELL_FACTOR = .05; static const Real DEFAULT_DT_FACTOR = 1e-5; static const Real CUTOFF_FACTOR = 5.6; }; @@ -164,9 +166,11 @@ class EGFRDSimulator: public ParticleSimulator typedef ParticleSimulator base_type; typedef typename base_type::sphere_type sphere_type; typedef typename base_type::cylinder_type cylinder_type; + typedef typename base_type::disk_type disk_type; typedef typename base_type::particle_simulation_structure_type particle_simulation_structure_type; typedef typename base_type::spherical_surface_type spherical_surface_type; typedef typename base_type::cylindrical_surface_type cylindrical_surface_type; + typedef typename base_type::disk_surface_type disk_surface_type; typedef typename base_type::planar_surface_type planar_surface_type; typedef typename base_type::cuboidal_region_type cuboidal_region_type; typedef typename traits_type::world_type world_type; @@ -570,11 +574,15 @@ class EGFRDSimulator: public ParticleSimulator position_type draw_com(cylindrical_pair_type const& domain, time_type dt) const { - boost::shared_ptr const _structure( +/* boost::shared_ptr const _structure( world_.get_structure( world_.get_species( domain.particles()[0].second.sid()) .structure_id())); +*/ + boost::shared_ptr const _structure( + world_.get_structure( + domain.particles()[0].second.structure_id())); cylindrical_surface_type const* const structure( dynamic_cast(_structure.get())); @@ -633,11 +641,15 @@ class EGFRDSimulator: public ParticleSimulator position_type draw_com(cylindrical_pair_type const& domain, time_type dt) const { - boost::shared_ptr const _structure( +/* boost::shared_ptr const _structure( world_.get_species( domain.particles()[0].second.sid()) - .structure_id()); - + .structure_type_id()); +*/ + boost::shared_ptr const _structure( + world_.get_structure( + domain.particles()[0].second.structure_id())); + cylindrical_surface_type const* const structure( dynamic_cast(_structure.get())); @@ -702,11 +714,15 @@ class EGFRDSimulator: public ParticleSimulator position_type draw_com(cylindrical_pair_type const& domain, time_type dt) { - boost::shared_ptr const _structure( +/* boost::shared_ptr const _structure( world_.get_structure( world_.get_species( domain.particles()[0].second.sid()) .structure_id())); +*/ + boost::shared_ptr const _structure( + world_.get_structure( + domain.particles()[0].second.structure_id())); cylindrical_surface_type const* const structure( dynamic_cast(_structure.get())); @@ -771,8 +787,7 @@ class EGFRDSimulator: public ParticleSimulator { boost::shared_ptr const _structure( world_.get_structure( - world_.get_species( - domain.particles()[0].second.sid()).structure_id())); + domain.particles()[0].second.structure_id())); cylindrical_surface_type const* const structure( dynamic_cast(_structure.get())); @@ -836,11 +851,15 @@ class EGFRDSimulator: public ParticleSimulator position_type draw_com(cylindrical_pair_type const& domain, time_type dt) { - boost::shared_ptr const _structure( +/* boost::shared_ptr const _structure( world_.get_structure( world_.get_species( domain.particles()[0].second.sid()) .structure_id())); +*/ + boost::shared_ptr const _structure( + world_.get_structure( + domain.particles()[0].second.structure_id())); cylindrical_surface_type const* const structure( dynamic_cast(_structure.get())); @@ -957,7 +976,7 @@ class EGFRDSimulator: public ParticleSimulator std::pair get_shell(shell_id_type const& id) { - shell_variant_type result; + shell_variant_type result(boost::none); boost::fusion::for_each(smatm_, shell_finder(id, result)); return std::make_pair(id, result); } @@ -1423,7 +1442,7 @@ class EGFRDSimulator: public ParticleSimulator single_type* new_single(0); domain_id_type did(didgen_()); - struct factory: ImmutativeStructureVisitor + struct factory: ImmutativeStructureVisitor { virtual ~factory() {} @@ -1452,6 +1471,21 @@ class EGFRDSimulator: public ParticleSimulator new_single = new cylindrical_single_type(did, p, new_shell); kind = CYLINDRICAL_SINGLE; } + + virtual void operator()(disk_surface_type const& structure) const + { + // This is only a copy of the operator for + // cylindrical_surface_type for now; + // TODO: Is that really what we want? + const cylindrical_shell_id_pair new_shell( + _this->new_shell( + did, cylinder_type( + p.second.position(), p.second.radius(), + structure.shape().unit_z(), + p.second.radius()))); + new_single = new cylindrical_single_type(did, p, new_shell); + kind = CYLINDRICAL_SINGLE; + } virtual void operator()(planar_surface_type const& structure) const { @@ -1487,8 +1521,8 @@ class EGFRDSimulator: public ParticleSimulator domain_kind& kind; }; - species_type const& species((*base_type::world_).get_species(p.second.sid())); - dynamic_cast(*(*base_type::world_).get_structure(species.structure_id())).accept(factory(this, p, did, new_single, kind)); +// species_type const& species((*base_type::world_).get_species(p.second.sid())); + dynamic_cast(*(*base_type::world_).get_structure(p.second.structure_id())).accept(factory(this, p, did, new_single, kind)); boost::shared_ptr const retval(new_single); domains_.insert(std::make_pair(did, retval)); BOOST_ASSERT(kind != NONE); @@ -1508,7 +1542,7 @@ class EGFRDSimulator: public ParticleSimulator pair_type* new_pair(0); domain_id_type did(didgen_()); - struct factory: ImmutativeStructureVisitor + struct factory: ImmutativeStructureVisitor { virtual void operator()(spherical_surface_type const& structure) const { @@ -1534,6 +1568,21 @@ class EGFRDSimulator: public ParticleSimulator kind = CYLINDRICAL_PAIR; } + virtual void operator()(disk_surface_type const& structure) const + { + // This is only a copy of the operator for + // cylindrical_surface_type for now; + // TODO: Is that really what we want? + cylindrical_shell_id_pair const new_shell( + _this->new_shell(did, cylinder_type( + com, + shell_size, + shape(structure).unit_z(), + std::max(p0.second.radius(), p1.second.radius())))); + new_pair = new cylindrical_pair_type(did, p0, p1, new_shell, + iv, rules); + kind = CYLINDRICAL_PAIR; + } virtual void operator()(planar_surface_type const& structure) const { @@ -1583,8 +1632,8 @@ class EGFRDSimulator: public ParticleSimulator domain_kind& kind; }; - species_type const& species((*base_type::world_).get_species(p0.second.sid())); - dynamic_cast(*(*base_type::world_).get_structure(species.structure_id())).accept(factory(this, p0, p1, com, iv, shell_size, did, new_pair, kind)); +// species_type const& species((*base_type::world_).get_species(p0.second.sid())); + dynamic_cast(*(*base_type::world_).get_structure(p0.second.structure_id())).accept(factory(this, p0, p1, com, iv, shell_size, did, new_pair, kind)); boost::shared_ptr const retval(new_pair); domains_.insert(std::make_pair(did, retval)); @@ -1825,7 +1874,7 @@ class EGFRDSimulator: public ParticleSimulator particle_type const& old(domain.particle().second); domain.particle().second = particle_type(old.sid(), - particle_shape_type(new_pos, old.radius()), old.D()); + particle_shape_type(new_pos, old.radius()), old.structure_id(), old.D()); (*base_type::world_).update_particle(domain.particle()); domain.position() = new_pos; @@ -2087,7 +2136,7 @@ class EGFRDSimulator: public ParticleSimulator (*base_type::world_).remove_particle(reactant.first); particle_id_pair product( (*base_type::world_).new_particle( - product_species.id(), reactant.second.position())); + product_species.id(), reactant.second.structure_id(), reactant.second.position())); boost::shared_ptr new_domain(create_single(product)); add_event(*new_domain, SINGLE_EVENT_ESCAPE); if (base_type::rrec_) @@ -2118,7 +2167,7 @@ class EGFRDSimulator: public ParticleSimulator { boost::shared_ptr structure( (*base_type::world_).get_structure( - reactant_species.structure_id())); + reactant.second.structure_id())); position_type vector( structure->random_vector( r01 * traits_type::MINIMAL_SEPARATION_FACTOR, @@ -2166,9 +2215,9 @@ class EGFRDSimulator: public ParticleSimulator particle_id_pair const pp[] = { (*base_type::world_).new_particle( - product_species[0]->id(), new_particles[0].position()), + product_species[0]->id(), reactant.second.structure_id(), new_particles[0].position()), (*base_type::world_).new_particle( - product_species[1]->id(), new_particles[1].position()) + product_species[1]->id(), reactant.second.structure_id(), new_particles[1].position()) }; // create domains for two particles and add them to // the event queue @@ -2282,9 +2331,6 @@ class EGFRDSimulator: public ParticleSimulator template void determine_next_event(AnalyticalSingle& domain) { - typedef Tshell shell_type; - typedef typename shell_type::shape_type shape_type; - typedef typename detail::get_greens_function::type greens_function; time_type const dt_reaction(draw_single_reaction_time(domain.particle().second.sid())); time_type const dt_escape_or_interaction(draw_escape_or_interaction_time(domain)); LOG_DEBUG(("determine_next_event: %s => dt_reaction=%.16g, " @@ -2405,7 +2451,6 @@ class EGFRDSimulator: public ParticleSimulator void restore_domain(AnalyticalSingle& domain, std::pair const& closest) { - typedef typename AnalyticalSingle::shell_type shell_type; domain_type const* closest_domain( closest.second == std::numeric_limits::infinity() ? (domain_type const*)0: get_domain(closest.first).get()); @@ -3153,8 +3198,6 @@ class EGFRDSimulator: public ParticleSimulator template void fire_event(AnalyticalPair& domain, pair_event_kind kind) { - typedef AnalyticalSingle corresponding_single_type; - if (kind == PAIR_EVENT_IV_UNDETERMINED) { // Draw actual pair event for iv at very last minute. @@ -3251,7 +3294,7 @@ class EGFRDSimulator: public ParticleSimulator particle_id_pair const new_particle( (*base_type::world_).new_particle( - new_species.id(), new_com)); + new_species.id(), domain.particles()[0].second.structure_id(), new_com)); boost::shared_ptr new_single( create_single(new_particle)); add_event(*new_single, SINGLE_EVENT_ESCAPE); diff --git a/EGFRDSimulatorFactory.hpp b/EGFRDSimulatorFactory.hpp index 88cffb1b..7ed636a0 100644 --- a/EGFRDSimulatorFactory.hpp +++ b/EGFRDSimulatorFactory.hpp @@ -10,16 +10,20 @@ template class EGFRDSimulatorFactory: public ParticleSimulatorFactory { public: - typedef ParticleSimulatorFactory base_type; - typedef Ttraits_ traits_type; - typedef typename traits_type::world_type::traits_type world_traits_type; - typedef typename traits_type::world_type world_type; - typedef typename traits_type::network_rules_type network_rules_type; - typedef typename world_traits_type::length_type length_type; - typedef typename world_traits_type::size_type size_type; - typedef typename world_traits_type::position_type position_type; - typedef typename world_traits_type::rng_type rng_type; - typedef CuboidalRegion cuboidal_region_type; + typedef ParticleSimulatorFactory base_type; + typedef Ttraits_ traits_type; + typedef typename traits_type::world_type world_type; + typedef typename world_type::traits_type world_traits_type; + + // shorthand typedefs + typedef typename world_traits_type::length_type length_type; + typedef typename world_traits_type::size_type size_type; + typedef typename world_traits_type::position_type position_type; + typedef typename world_traits_type::rng_type rng_type; + typedef typename traits_type::network_rules_type network_rules_type; + + typedef CuboidalRegion cuboidal_region_type; + public: EGFRDSimulatorFactory(rng_type& rng): rng_(rng) {} @@ -50,7 +54,7 @@ class EGFRDSimulatorFactory: public ParticleSimulatorFactory world->add_structure( boost::shared_ptr( new cuboidal_region_type( - "world", + "world", model.get_def_structure_type_id(), typename cuboidal_region_type::shape_type(x, x)))); BOOST_FOREACH (boost::shared_ptr st, @@ -60,18 +64,19 @@ class EGFRDSimulatorFactory: public ParticleSimulatorFactory // TODO: add surfaces to world } + // Making sure that all the species have a structure_type defined? BOOST_FOREACH (boost::shared_ptr st, model.get_species_types()) { - std::string const& structure_id((*st)["structure"]); + structure_type_id_type const& structure_type_id((*st)["structure_type"]); world->add_species( typename world_traits_type::species_type( st->id(), boost::lexical_cast( (*st)["D"]), boost::lexical_cast((*st)["radius"]), - boost::lexical_cast( - structure_id.empty() ? "world": structure_id) + boost::lexical_cast( + structure_type_id.empty() ? model.get_def_structure_type_id(): structure_type_id) )); } @@ -82,6 +87,8 @@ class EGFRDSimulatorFactory: public ParticleSimulatorFactory rng_, dissociation_retry_moves); } + +///// Member variables protected: rng_type& rng_; }; diff --git a/GreensFunction1DAbsAbs.cpp b/GreensFunction1DAbsAbs.cpp index 7f42c763..5549f4b1 100644 --- a/GreensFunction1DAbsAbs.cpp +++ b/GreensFunction1DAbsAbs.cpp @@ -4,6 +4,8 @@ #include #include +#include +#include #include #include #include @@ -17,104 +19,198 @@ #include "findRoot.hpp" #include "GreensFunction1DAbsAbs.hpp" -#include "Defs.hpp" +const unsigned int GreensFunction1DAbsAbs::MAX_TERMS; +const unsigned int GreensFunction1DAbsAbs::MIN_TERMS; -// Calculates the probability of finding the particle inside the domain at -// time t -Real -GreensFunction1DAbsAbs::p_survival (Real t) const +/* returns a guess for the number of terms needed for + the greensfunction to converge at time t */ +uint GreensFunction1DAbsAbs::guess_maxi(Real const& t) const +{ + const uint safety(2); + + if (t >= INFINITY) + { + return safety; + } + + const Real D( getD() ); + const Real L( fabs( geta() - getsigma() ) ); + + const Real root0( M_PI / L); + const Real Dt(D * t); + + const Real thr(exp(- Dt * root0 * root0) * EPSILON * 1e-1); + + if (thr <= 0.0) + { + return MAX_TERMS; + } + + const Real max_root( sqrt(root0 * root0 - log(thr) / Dt) ); + + const uint maxi(std::max( safety + + static_cast + (max_root * L / M_PI), + MIN_TERMS ) + ); + + return std::min(maxi, MAX_TERMS); +} + + +Real GreensFunction1DAbsAbs::p_survival(Real t) const +{ + RealVector table; + return p_survival_table(t, table); +} + +/* Calculates survival probability using a table. + Switchbox for which greensfunction to use. */ +Real GreensFunction1DAbsAbs::p_survival_table(Real t, RealVector& psurvTable) const { THROW_UNLESS( std::invalid_argument, t >= 0.0 ); - const Real a(this->geta()); - const Real sigma(this->getsigma()); - const Real L(this->geta() - this->getsigma()); - const Real r0(this->getr0()); - const Real D(this->getD()); - const Real v(this->getv()); + Real p; + + const Real a( geta() ); + const Real sigma( getsigma() ); + const Real L( a - sigma ); + const Real r0( getr0() ); + const Real D( getD() ); + const Real v( getv() ); if ( fabs(r0-sigma) < L*EPSILON || fabs(a-r0) < L*EPSILON || L < 0.0 ) { - // The survival probability of a zero domain is zero - return 0.0; + // The survival probability of a zero domain is zero + return 0.0; } - // Set values that are constant in this calculation - const Real expo(-D*t/(L*L)); // part of the exponent -D n^2 PI^2 t / L^2 - const Real r0s(r0 - sigma); - const Real r0s_L(r0s/L); + if (t == 0.0 || (D == 0.0 && v == 0.0) ) + { + //particle can't escape. + return 1.0; + } + + /* First check if we need full solution. + Else we use approximation. */ + const Real distToa( a - r0 ); + const Real distTos( r0 - sigma ); + const Real maxDist( CUTOFF_H * (sqrt(2.0 * D * t) + fabs(v*t)) ); - // some abbreviations for terms appearing in the sums with drift<>0 + if( distToa > maxDist ) //Absorbing boundary 'not in sight'. + { + if( distTos > maxDist )//And radiation boundary 'not in sight'. + return 1.0; //No prob. outflux. + else + return XS10(t, distTos, D, v); //Only absorbing BCn of s. + } + else + { + if( distTos > maxDist ) + return XS10(t, distToa, D, -v); //Only absorbing BCn of a. + } + + const uint maxi( guess_maxi(t) ); + + if( maxi >= MAX_TERMS ) + log_.warn("drawT: maxi was cut to MAX_TERMS for t = %.16g", t); + + if (psurvTable.size() < maxi) + { + createPsurvTable( maxi, psurvTable ); + } + + p = funcSum_all(boost::bind(&GreensFunction1DAbsAbs::p_survival_i, + this, _1, t, psurvTable), + maxi); + + if( v == 0.0 ) + { + p *= 2.0; + } + else + { + const Real vexpo(-v*v*t/4.0/D - v*r0/2.0/D); + p *= 2.0 * exp( vexpo ); + } + + return p; +} + + +/* Calculates the i'th term of the p_survival sum */ +Real GreensFunction1DAbsAbs::p_survival_i( uint i, + Real const& t, + RealVector const& table) const +{ + const Real L( geta() - getsigma() ); + return exp( - getD() * t * gsl_pow_2( (i + 1) * M_PI / L ) ) * table[ i ]; +} + + +/* Calculates the part of the i'th term of p_surv not dependent on t, with drift */ +Real GreensFunction1DAbsAbs::p_survival_table_i_v( uint const& i ) const +{ + Real nPI( ((Real)(i+1))*M_PI ); + + const Real sigma(this->getsigma()); + const Real L(this->geta() - this->getsigma()); + const Real r0(this->getr0()); + const Real D(this->getD()); + const Real v(this->getv()); + + const Real r0s_L((r0-sigma)/L); + const Real sigmav2D(sigma*v/2.0/D); const Real av2D(a*v/2.0/D); const Real Lv2D(L*v/2.0/D); - const Real vexpo(-v*v*t/4.0/D - v*r0/2.0/D); // exponent of the drift-prefactor + + return ( exp(sigmav2D) - cos(nPI)*exp(av2D) ) * + nPI/( Lv2D * Lv2D + nPI * nPI ) * sin(nPI*r0s_L); +} + + +/* Calculates the part of the i'th term of p_surv not dependent on t, without drift */ +Real GreensFunction1DAbsAbs::p_survival_table_i_nov( uint const& i ) const +{ + Real nPI( ((Real)(i+1))*M_PI ); + + const Real sigma( getsigma() ); + const Real L( geta() - getsigma() ); + const Real r0( getr0() ); + const Real r0s_L( (r0-sigma) / L ); + + return sin( nPI * r0s_L ) * (1.0 - cos(nPI)) / nPI; +} - // Initialize summation - Real sum = 0, term = 0, prev_term = 0; - Real nPI; +/* Fills table with terms in the p_survival sum which don't depend on t */ +void GreensFunction1DAbsAbs::createPsurvTable( uint const& maxi, RealVector& table) const +{ + uint i( table.size() ); - // Sum - Real n=1; - // different calculations depending on whether v=0 or not - if(v==0.0) // case without drift (v==0); in this case the summation is simpler, so do the complicated caluclation only if necessary - { - do - { - if (n >= MAX_TERMS ) - { - std::cerr << "Too many terms for p_survival. N: " << n << std::endl; - break; - } - - prev_term = term; - nPI = (double)n*M_PI; - term = exp(nPI*nPI*expo) * sin(nPI*r0s_L) * (1.0 - cos(nPI)) / nPI; - sum += term; - n++; - } - // Is 1 a good measure or will this fail at some point? - while ( fabs(term/sum) > EPSILON*1.0 || - fabs(prev_term/sum) > EPSILON*1.0 || - n < MIN_TERMS ); - - sum = 2.0*sum; // This is a prefactor of every term, so do only one multiplication here - } - else // case with drift (v<>0) - { - do - { - if (n >= MAX_TERMS ) - { - std::cerr << "Too many terms for p_survival. N: " << n << std::endl; - break; - } - - nPI = (double)n*M_PI; - prev_term = term; - term = exp(nPI*nPI*expo) * (exp(sigmav2D) - cos(nPI)*exp(av2D)) * nPI/(Lv2D*Lv2D+nPI*nPI) * sin(nPI*r0s_L); - sum += term; - n++; - } - // TODO: Is 1 a good measure or will this fail at some point? - while ( fabs(term/sum) > EPSILON*1.0 || - fabs(prev_term/sum) > EPSILON*1.0 || - n < MIN_TERMS ); - - sum = 2.0*exp(vexpo) * sum; // prefactor containing the drift - - } - - return sum; + if( getv() == 0.0 ) + { + while( i < maxi ) + { + table.push_back( p_survival_table_i_nov( i++ ) ); + } + } + else + { + while( i < maxi ) + { + table.push_back( p_survival_table_i_v( i++ ) ); + } + } } -// Calculates the probability density of finding the particle at location r at -// time t. -Real -GreensFunction1DAbsAbs::prob_r (Real r, Real t) const + +/* Calculates the probability density of finding the particle at location r at + time t. */ +Real GreensFunction1DAbsAbs::prob_r (Real r, Real t) const { THROW_UNLESS( std::invalid_argument, 0.0 <= (r-sigma) && r <= a ); THROW_UNLESS( std::invalid_argument, t >= 0.0 ); @@ -130,18 +226,18 @@ GreensFunction1DAbsAbs::prob_r (Real r, Real t) const if (t == 0 || D == 0) { // the probability density function is a delta function - if (r == r0) - { - return INFINITY; - } - else - { - return 0.0; - } + if (r == r0) + { + return INFINITY; + } + else + { + return 0.0; + } } else if ( fabs(r-sigma) < L*EPSILON || fabs(a-r) < L*EPSILON || L < 0.0 ) { - return 0.0; + return 0.0; } // Set values that are constant in this calculation @@ -155,38 +251,40 @@ GreensFunction1DAbsAbs::prob_r (Real r, Real t) const Real sum = 0, term = 0, prev_term = 0; // Sum - int n=1; + uint n = 0; do { - if (n >= MAX_TERMS ) - { - std::cerr << "Too many terms for prob_r. N: " << n << std::endl; - break; - } - - prev_term = term; - - nPI = n*M_PI; - term = exp(nPI*nPI*expo) * sin(nPI*r0s_L) * sin(nPI*rs_L); - sum += term; - n++; + if (n >= MAX_TERMS ) + { + log_.warn("Too many terms for prob_r. N: %6u", n); + break; + } + + prev_term = term; + + nPI = (n + 1) * M_PI; + term = exp( nPI*nPI*expo ) * sin( nPI * r0s_L ) * sin( nPI * rs_L ); + sum += term; + n++; } while (fabs(term/sum) > EPSILON*PDENS_TYPICAL || - fabs(prev_term/sum) > EPSILON*PDENS_TYPICAL || - n <= MIN_TERMS); + fabs(prev_term/sum) > EPSILON*PDENS_TYPICAL || + n < MIN_TERMS); return 2.0/L * exp(vexpo) * sum; } -// Calculates the probability density of finding the particle at location r at -// timepoint t, given that the particle is still in the domain. + +/* Calculates the probability density of finding the particle at location r at + timepoint t, given that the particle is still in the domain. */ Real GreensFunction1DAbsAbs::calcpcum (Real r, Real t) const { return prob_r(r, t) / p_survival(t); } -// Calculates the amount of flux leaving the left boundary at time t + +/* Calculates the amount of flux leaving the left boundary at time t */ Real GreensFunction1DAbsAbs::leaves(Real t) const { @@ -201,17 +299,16 @@ GreensFunction1DAbsAbs::leaves(Real t) const if ( fabs(r0-sigma) < L*EPSILON || fabs(a-r0) < L*EPSILON || L < 0.0 ) { - // The flux of a zero domain is INFINITY. Also if the particle - // started on the left boundary (leaking out immediately). - return INFINITY; + // The flux of a zero domain is INFINITY. Also if the particle + // started on the left boundary (leaking out immediately). + return INFINITY; } else if ( t < EPSILON*this->t_scale ) { - // if t=0.0 the flux must be zero - return 0.0; + // if t=0.0 the flux must be zero + return 0.0; } - Real sum = 0, term = 0, prev_term = 0; Real nPI; const Real D_L_sq(D/(L*L)); @@ -219,26 +316,26 @@ GreensFunction1DAbsAbs::leaves(Real t) const const Real r0s_L((r0-sigma)/L); const Real vexpo(-v*v*t/4.0/D - v*(r0-sigma)/2.0/D); - Real n=1; + uint n = 0; do { - if (n >= MAX_TERMS ) - { - std::cerr << "Too many terms for p_survival. N: " << n << std::endl; - break; - } - - nPI = n*M_PI; - prev_term = term; - term = nPI * exp(nPI*nPI*expo) * sin(nPI*r0s_L); - sum += term; - n++; + if (n >= MAX_TERMS ) + { + log_.warn("Too many terms for leaves. N: %6u", n); + break; + } + + nPI = (Real (n + 1.0)) * M_PI; + prev_term = term; + term = nPI * exp( nPI * nPI * expo) * sin( nPI * r0s_L ); + sum += term; + n++; } while (fabs(term/sum) > EPSILON*PDENS_TYPICAL || - fabs(prev_term/sum) > EPSILON*PDENS_TYPICAL || - n < MIN_TERMS ); + fabs(prev_term/sum) > EPSILON*PDENS_TYPICAL || + n < MIN_TERMS ); - return 2.0*D_L_sq * exp(vexpo) * sum; + return 2.0 * D_L_sq * exp( vexpo ) * sum; } // Calculates the amount of flux leaving the right boundary at time t @@ -256,14 +353,14 @@ GreensFunction1DAbsAbs::leavea(Real t) const if ( fabs(r0-sigma) < L*EPSILON || fabs(a-r0) < L*EPSILON || L < 0.0 ) { - // The flux of a zero domain is INFINITY. Also if the particle - // started on the right boundary (leaking out immediately). - return INFINITY; + // The flux of a zero domain is INFINITY. Also if the particle + // started on the right boundary (leaking out immediately). + return INFINITY; } else if ( t < EPSILON*this->t_scale ) { - // if t=0.0 the flux must be zero - return 0.0; + // if t=0.0 the flux must be zero + return 0.0; } @@ -274,32 +371,32 @@ GreensFunction1DAbsAbs::leavea(Real t) const const Real r0s_L((r0-sigma)/L); const Real vexpo(-v*v*t/4.0/D + v*(a-r0)/2.0/D); - Real n=1; + Real n = 0; do { - if (n >= MAX_TERMS ) - { - std::cerr << "Too many terms for leaves. N: " << n << std::endl; - break; - } + if (n >= MAX_TERMS ) + { + log_.warn("Too many terms for leavea. N: %6u ", n); + break; + } - nPI = n*M_PI; - prev_term = term; - term = nPI * exp(nPI*nPI*expo) * cos(nPI) * sin(nPI*r0s_L); - sum += term; - n++; + nPI = (n + 1) * M_PI; + prev_term = term; + term = nPI * exp( nPI * nPI * expo ) * cos( nPI ) * sin( nPI * r0s_L ); + sum += term; + n++; } while (fabs(term/sum) > EPSILON*PDENS_TYPICAL || - fabs(prev_term/sum) > EPSILON*PDENS_TYPICAL || - n < MIN_TERMS ); + fabs(prev_term/sum) > EPSILON*PDENS_TYPICAL || + n < MIN_TERMS ); - return -2.0*D_L_sq * exp(vexpo) * sum; + return - 2.0 * D_L_sq * exp(vexpo) * sum; } -// This draws an eventtype of time t based on the flux through the left (z=sigma) -// and right (z=a) boundary. Although not completely accurate, it returns an -// IV_ESCAPE for an escape through the right boundary and a IV_REACTION for an -// escape through the left boundary. +/* This draws an eventtype of time t based on the flux through the left (z=sigma) + and right (z=a) boundary. Although not completely accurate, it returns an + IV_ESCAPE for an escape through the right boundary and a IV_REACTION for an + escape through the left boundary. */ GreensFunction1DAbsAbs::EventKind GreensFunction1DAbsAbs::drawEventType( Real rnd, Real t ) const { @@ -315,13 +412,13 @@ GreensFunction1DAbsAbs::drawEventType( Real rnd, Real t ) const // For particles at the boundaries if ( fabs(a-r0) < EPSILON*L ) { - // if the particle started on the right boundary - return IV_ESCAPE; + // if the particle started on the right boundary + return IV_ESCAPE; } else if ( fabs(r0-sigma) < EPSILON*L ) { - // if the particle started on the left boundary - return IV_REACTION; + // if the particle started on the left boundary + return IV_REACTION; } const Real leaves_s (this->leaves(t)); @@ -331,62 +428,30 @@ GreensFunction1DAbsAbs::drawEventType( Real rnd, Real t ) const if (rnd > fluxratio ) { - return IV_ESCAPE; + return IV_ESCAPE; //escape through a. } else { - return IV_REACTION; + return IV_REACTION; //escape through sigma. } } -// This is a help function that casts the drawT_params parameter structure into -// the right form and calculates the survival probability from it (and returns it). -// The routine drawTime uses this one to sample the next-event time from the -// survival probability using a rootfinder from GSL. -double -GreensFunction1DAbsAbs::drawT_f (double t, void *p) + +/* This is a help function that casts the drawT_params parameter structure into + the right form and calculates the survival probability from it (and returns it). + The routine drawTime uses this one to sample the next-event time from the + survival probability using a rootfinder from GSL.*/ +Real GreensFunction1DAbsAbs::drawT_f (Real t, void *p) { - // casts p to type 'struct drawT_params *' struct drawT_params *params = (struct drawT_params *)p; - - Real sum = 0, term = 0, prev_term = 0; - Real Xn, exponent, prefactor; - // the maximum number of terms in the params table - int terms = params->terms; - // the timescale used - Real tscale = params->tscale; - - int n=0; - do - { - if ( n >= terms ) - { - std::cerr << "Too many terms needed for DrawTime. N: " - << n << std::endl; - break; - } - prev_term = term; - - Xn = params->Xn[n]; - exponent = params->exponent[n]; - term = Xn * exp(exponent * t); - sum += term; - n++; - } - while (fabs(term/sum) > EPSILON*tscale || - fabs(prev_term/sum) > EPSILON*tscale || - n <= MIN_TERMS ); - - prefactor = params->prefactor; - - // find intersection with the random number - return 1.0 - prefactor*sum - params->rnd; + return params->rnd - params->gf->p_survival_table( t, params->psurvTable ); } -// Draws the first passage time from the propensity function. -// Uses the help routine drawT_f and structure drawT_params for some technical -// reasons related to the way to input a function and parameters required by -// the GSL library. + +/* Draws the first passage time from the propensity function. + Uses the help routine drawT_f and structure drawT_params for some technical + reasons related to the way to input a function and parameters required by + the GSL library. */ Real GreensFunction1DAbsAbs::drawTime (Real rnd) const { @@ -398,148 +463,99 @@ GreensFunction1DAbsAbs::drawTime (Real rnd) const const Real r0(this->getr0()); const Real D(this->getD()); const Real v(this->getv()); - + if (D == 0.0 ) { - return INFINITY; + return INFINITY; } else if ( L < 0.0 || fabs(a-r0) < EPSILON*L || fabs(r0-sigma) > (1.0 - EPSILON)*L ) { - // if the domain had zero size - return 0.0; + return 0.0; } - const Real expo(-D/(L*L)); - const Real r0s_L((r0-sigma)/L); - // some abbreviations for terms appearing in the sums with drift<>0 - const Real sigmav2D(sigma*v/2.0/D); - const Real av2D(a*v/2.0/D); - const Real Lv2D(L*v/2.0/D); - // exponent of the prefactor present in case of v<>0; has to be split because it has a t-dep. and t-indep. part - const Real vexpo_t(-v*v/4.0/D); - const Real vexpo_pref(-v*r0/2.0/D); - - // the structure to store the numbers to calculate the numbers for 1-S - struct drawT_params parameters; - Real Xn, exponent, prefactor; - - Real nPI; - - // Construct the coefficients and the terms in the exponent and put them - // into the params structure - int n = 0; - // a simpler sum has to be computed for the case w/o drift, so distinguish here - if(v==0) - { - do - { - nPI = ((Real)(n+1))*M_PI; // why n+1 : this loop starts at n=0 (1st index of the arrays), while the sum starts at n=1 ! - Xn = sin(nPI*r0s_L) * (1.0 - cos(nPI)) / nPI; - exponent = nPI*nPI*expo; - - // store the coefficients in the structure - parameters.Xn[n] = Xn; - // also store the values for the exponent - parameters.exponent[n]=exponent; - n++; - } - // TODO: Modify this later to include a cutoff when changes are small - while (n0 - { - do - { - nPI = ((Real)(n+1))*M_PI; // why n+1 : this loop starts at n=0 (1st index of the arrays), while the sum starts at n=1 ! - Xn = (exp(sigmav2D) - cos(nPI)*exp(av2D)) * nPI/(Lv2D*Lv2D+nPI*nPI) * sin(nPI*r0s_L); - exponent = nPI*nPI*expo + vexpo_t; - - // store the coefficients in the structure - parameters.Xn[n] = Xn; - // also store the values for the exponent - parameters.exponent[n]=exponent; - n++; - } - // TODO: Modify this later to include a cutoff when changes are small - while (n0 : - if(v==0) prefactor = 2.0*exp(vexpo_pref); - else prefactor = 2.0; - parameters.prefactor = prefactor; - - parameters.rnd = rnd; - parameters.terms = MAX_TERMS; - parameters.tscale = this->t_scale; + /* Set params structure. */ + RealVector psurvTable; + struct drawT_params parameters = {this, psurvTable, rnd}; gsl_function F; F.function = &drawT_f; F.params = ¶meters; - // Find a good interval to determine the first passage time in - const Real dist( std::min(r0-sigma, a-r0) ); + /* Find a good interval to determine the first passage time in */ + const Real dist( std::min(r0 - sigma, a - r0) ); + Real t_guess ( 0 ); + if( v == 0.0 ) + { + t_guess = dist * dist / ( 2.0 * D ) ; + } + else + { + // When drifting towards the closest boundary... + if( ( r0-sigma >= L/2.0 && v > 0.0 ) || + ( r0-sigma <= L/2.0 && v < 0.0 ) ) + t_guess = sqrt(D*D/(v*v*v*v)+dist*dist/(v*v)) - D/(v*v); + + // When drifting away from the closest boundary... + if( ( r0-sigma < L/2.0 && v > 0.0 ) || + ( r0-sigma > L/2.0 && v < 0.0 ) ) + t_guess = D/(v*v) - sqrt(D*D/(v*v*v*v)-dist*dist/(v*v)); + } + + t_guess *= .1; - // construct a guess: MSD = sqrt (2*d*D*t) - Real t_guess( dist * dist / ( 2.0 * D ) ); - // A different guess has to be made in case of nonzero drift to account for the displacement due to it - // When drifting towards the closest boundary... - if( ( r0-sigma >= L/2.0 && v > 0.0 ) || ( r0-sigma <= L/2.0 && v < 0.0 ) ) t_guess = sqrt(D*D/(v*v*v*v)+dist*dist/(v*v)) - D/(v*v); - // When drifting away from the closest boundary... - if( ( r0-sigma < L/2.0 && v > 0.0 ) || ( r0-sigma > L/2.0 && v < 0.0 ) ) t_guess = D/(v*v) - sqrt(D*D/(v*v*v*v)-dist*dist/(v*v)); - - Real value( GSL_FN_EVAL( &F, t_guess ) ); Real low( t_guess ); Real high( t_guess ); if( value < 0.0 ) { - // scale the interval around the guess such that the function - // straddles if the guess was too low - do - { - // keep increasing the upper boundary until the - // function straddles - high *= 10.0; - value = GSL_FN_EVAL( &F, high ); - - if( fabs( high ) >= t_guess * 1e6 ) - { - std::cerr << "Couldn't adjust high. F(" << high << ") = " - << value << std::endl; - throw std::exception(); - } - } - while ( value <= 0.0 ); + // scale the interval around the guess such that the function + // straddles if the guess was too low + do + { + if( fabs( high ) >= t_guess * 1e6 ) + { + log_.error("drawTime: couldn't adjust high. F( %.16g ) = %.16g" + , high, value); + throw std::exception(); + } + // keep increasing the upper boundary until the + // function straddles + high *= 10.0; + value = GSL_FN_EVAL( &F, high ); + } + while ( value <= 0.0 ); } else { - // if the guess was too high initialize with 2 so the test - // below survives the first iteration - Real value_prev( 2.0 ); - do - { - if( fabs( low ) <= t_guess * 1.0e-6 || - fabs(value-value_prev) < EPSILON*this->t_scale ) - { - std::cerr << "Couldn't adjust low. F(" << low << ") = " - << value << " t_guess: " << t_guess << " diff: " - << (value - value_prev) << " value: " << value - << " value_prev: " << value_prev << " t_scale: " - << this->t_scale << std::endl; - return low; - } - - value_prev = value; - // keep decreasing the lower boundary until the - // function straddles - low *= 0.1; - // get the accompanying value - value = GSL_FN_EVAL( &F, low ); - - } - while ( value >= 0.0 ); + // if the guess was too high initialize with 2 so the test + // below survives the first iteration + Real value_prev( 2.0 ); + do + { + if( fabs( low ) <= t_guess * 1.0e-6 || + fabs(value-value_prev) < EPSILON*this->t_scale ) + { + log_.warn("drawTime: couldn't adjust low. F( %.16g ) = %.16g" + , low, value); + /* + std::cerr << "GF1DAbs::drawTime Couldn't adjust low. F(" << low << ") = " + << value << " t_guess: " << t_guess << " diff: " + << (value - value_prev) << " value: " << value + << " value_prev: " << value_prev << std::endl; + */ + return low; + } + + value_prev = value; + // keep decreasing the lower boundary until the + // function straddles + low *= 0.1; + // get the accompanying value + value = GSL_FN_EVAL( &F, low ); + + } + while ( value >= 0.0 ); } // find the intersection on the y-axis between the random number and @@ -556,56 +572,128 @@ GreensFunction1DAbsAbs::drawTime (Real rnd) const return t; } - -// This is a help function that casts the drawR_params parameter structure into -// the right form and calculates the survival probability from it (and returns it). -// The routine drawR uses this function to sample the exit point, making use of the -// GSL root finder to draw the random position. -double -GreensFunction1DAbsAbs::drawR_f (double r, void *p) +Real GreensFunction1DAbsAbs::p_int_r_table(Real const& r, + Real const& t, + RealVector& table) const { - struct drawR_params *params = (struct drawR_params *)p; - double sum = 0, term = 0, prev_term = 0; - double S_Cn_An, n_L; - int terms = params->terms; - double sigma = params->H[0]; - double v2D = params->H[1]; // =v/(2D) + const Real a( geta() ); + const Real sigma( getsigma() ); + const Real L( a - sigma ); + const Real r0( getr0() ); + const Real D( getD() ); + const Real v( getv() ); + + const Real distToa( a - r0 ); + const Real distTos( r0 - sigma ); + const Real maxDist( CUTOFF_H * ( sqrt(2.0 * D * t) + fabs(v * t) ) ); + + if( distToa > maxDist ) //Absorbing boundary a 'not in sight'. + { + if( distTos > maxDist ) //Absorbing boundary sigma 'not in sight'. + return XI00(r, t, r0, D, v); //free particle. + else + //Only absorbing BCn at sigma. + return XI10(r - sigma, t, distTos, D, v); + } + else + { + if( distTos > maxDist ) + //Only absorbing BCn at a. + return XI10(a - r, t, distToa, D, -v); + } - int n=0; - do + + const Real vexpo(-v*v*t/4.0/D - v*r0/2.0/D); + const Real prefac = 2.0 * exp(vexpo); + + Real p; + const uint maxi( guess_maxi( t ) ); + if( table.size() < maxi ) + { + create_p_int_r_Table(t, maxi, table); + } + + if( maxi >= MAX_TERMS ) { - if (n >= terms ) - { - std::cerr << "Too many terms for DrawR. N: " << n << std::endl; - break; - } - prev_term = term; - - S_Cn_An = params->S_Cn_An[n]; - n_L = params->n_L[n]; // this is n*pi/L - if(v2D==0.0) term = S_Cn_An * ( 1.0 - cos(n_L*(r-sigma)) ); - else term = S_Cn_An * ( exp(v2D*sigma) + exp(v2D*r)*( v2D/n_L*sin(n_L*(r-sigma)) - cos(n_L*(r-sigma)) )); - // S_Cn_An contains all expon. prefactors, the 1/S(t) term and all parts - // of the terms that do not depend on r. - // - // In case of zero drift the terms become S_An_Cn * ( 1 - cos(nPi/L*(r-sigma)) ) - // as it should be. The if-statement is only to avoid calculation costs. - - sum += term; - n++; - } - while (fabs(term/sum) > EPSILON || - fabs(prev_term/sum) > EPSILON || - n <= MIN_TERMS ); - - // find the intersection with the random number - return sum - params->rnd; + log_.warn("drawR: maxi was cut to MAX_TERMS for t = %.16g", t); + std::cerr << dump(); + std::cerr << "L: " << L << " r0: " << r0 - sigma << std::endl; + } + + p = funcSum(boost::bind(&GreensFunction1DAbsAbs::p_int_r_i, + this, _1, r, t, table), + MAX_TERMS); + + return prefac * p; } -// Draws the position of the particle at a given time from p(r,t), assuming -// that the particle is still in the domain -Real -GreensFunction1DAbsAbs::drawR (Real rnd, Real t) const +Real GreensFunction1DAbsAbs::p_int_r_i(uint i, + Real const& r, + Real const& t, + RealVector& table) const +{ + const Real D( getD() ); + const Real sigma( getsigma() ); + const Real L( geta() - sigma ); + const Real v2D( getv()/(2*D) ); + const Real n_L = ((Real)(i + 1.0)) * M_PI / L; + + Real term; + + if(v2D==0.0) + term = 1.0 - cos(n_L*(r-sigma)); + else + term = exp(v2D*sigma) + exp(v2D*r)* + ( v2D/n_L*sin(n_L*(r-sigma)) + - cos(n_L*(r-sigma)) ); + + return term * get_p_int_r_Table_i(i , t, table); +} + +/* Fills table for p_int_r of factors independent of r. */ +void GreensFunction1DAbsAbs::create_p_int_r_Table( Real const& t, + uint const& maxi, + RealVector& table ) const +{ + uint n( table.size() ); + + const Real sigma( getsigma() ); + const Real L( geta() - getsigma() ); + const Real r0( getr0() ); + const Real D( getD() ); + const Real v( getv() ); + + const Real expo (-D*t/(L*L)); + const Real r0s_L((r0-sigma)/L); + const Real Lv2D(L*v/2.0/D); + + Real nPI, term; + + while( n < maxi ) + { + nPI = ((Real)(n+1))*M_PI; + + if( v == 0.0 ) + term = exp(nPI*nPI*expo) * sin(nPI*r0s_L) / nPI; + else + term = exp(nPI*nPI*expo) * sin(nPI*r0s_L) * nPI/(nPI*nPI + Lv2D*Lv2D); + + table.push_back( term ); + n++; + } +} + +/* Function for GSL rootfinder of drawR. */ +Real GreensFunction1DAbsAbs::drawR_f(Real r, void *p) +{ + struct drawR_params *params = (struct drawR_params *)p; + return params->gf->p_int_r_table(r, params->t, params->table) + - params->rnd; +} + +/* Draws the position of the particle at a given time from p(r,t), assuming + that the particle is still in the domain */ +Real GreensFunction1DAbsAbs::drawR (Real rnd, Real t) const { THROW_UNLESS( std::invalid_argument, 0.0 <= rnd && rnd < 1.0 ); THROW_UNLESS( std::invalid_argument, t >= 0.0 ); @@ -618,79 +706,33 @@ GreensFunction1DAbsAbs::drawR (Real rnd, Real t) const const Real v(this->getv()); // the trivial case: if there was no movement or the domain was zero - if ( (D==0.0 && v==0.0) || L<0.0 || t==0.0) + if ( (D == 0.0 && v == 0.0) || L < 0.0 || t == 0.0) { - return r0; + return r0; } else { - // if the initial condition is at the boundary, raise an error - // The particle can only be at the boundary in the ABOVE cases - THROW_UNLESS( std::invalid_argument, - (r0-sigma) >= L*EPSILON && (r0-sigma) <= L*(1.0-EPSILON) ); + // if the initial condition is at the boundary, raise an error + // The particle can only be at the boundary in the ABOVE cases + THROW_UNLESS( std::invalid_argument, + (r0-sigma) >= L*EPSILON && (r0-sigma) <= L*(1.0-EPSILON) ); } - // else the normal case - // From here on the problem is well defined - - - // structure to store the numbers to calculate numbers for 1-S(t) - struct drawR_params parameters; - Real S_Cn_An; - Real nPI; - const Real expo (-D*t/(L*L)); - const Real r0s_L((r0-sigma)/L); - const Real v2D(v/2.0/D); - const Real Lv2D(L*v/2.0/D); - const Real vexpo(-v*v*t/4.0/D - v*r0/2.0/D); // exponent of the drift-prefactor, same as in survival prob. - const Real S = 2.0*exp(vexpo)/p_survival(t); // This is a prefactor to every term, so it also contains there - // exponential drift-prefactor. - - // Construct the coefficients and the terms in the exponent and put them - // in the params structure - int n=0; - do - { - nPI = ((Real)(n+1))*M_PI; // note: summation starting with n=1, indexing with n=0, therefore we need n+1 here - - if(v==0.0) S_Cn_An = S * exp(nPI*nPI*expo) * sin(nPI*r0s_L) / nPI; - else S_Cn_An = S * exp(nPI*nPI*expo) * sin(nPI*r0s_L) * nPI/(nPI*nPI + Lv2D*Lv2D); - // The rest is the z-dependent part, which has to be defined directly in drawR_f(z). - // Of course also the summation happens there because the terms now are z-dependent. - // The last term originates from the integrated prob. density including drift. - // - // In case of zero drift this expression becomes: 2.0/p_survival(t) * exp(nPI*nPI*expo) * sin(nPI*r0s_L) / nPI - - // also store the values for the exponent, so they don't have to be recalculated in drawR_f - parameters.S_Cn_An[n]= S_Cn_An; - parameters.n_L[n] = nPI/L; - n++; - } - while (n #include @@ -20,7 +20,9 @@ #include "findRoot.hpp" #include "Defs.hpp" -#include "OldDefs.hpp" // TODO: this must be removed at some point! +#include "funcSum.hpp" +#include "freeFunctions.hpp" +#include "Logger.hpp" #include "GreensFunction.hpp" #include "PairGreensFunction.hpp" // needed to declare EventType @@ -28,26 +30,26 @@ class GreensFunction1DAbsAbs: public GreensFunction { +public: + typedef std::vector RealVector; + typedef unsigned int uint; + private: // This is a typical length scale of the system, may not be true! static const Real L_TYPICAL = 1E-8; // The typical timescale of the system, may also not be true!! static const Real T_TYPICAL = 1E-6; // measure of 'sameness' when comparing floating points numbers - static const Real EPSILON = 1E-12; + static const Real EPSILON = 1E-10; //E3; Is 1E3 a good measure for the probability density?! static const Real PDENS_TYPICAL = 1; // The maximum number of terms in the sum - static const int MAX_TERMS = 500; + static const uint MAX_TERMS = 500; // The minimum - static const int MIN_TERMS = 20; + static const uint MIN_TERMS = 20; + // Cutoff distance: When H * sqrt(2Dt) < 1/2*L, use free greensfunction instead of absorbing. + static const Real CUTOFF_H = 6.0; -public: - enum EventKind - { - IV_ESCAPE, - IV_REACTION - }; public: GreensFunction1DAbsAbs(Real D, Real r0, Real sigma, Real a) @@ -171,30 +173,52 @@ class GreensFunction1DAbsAbs: public GreensFunction private: struct drawT_params { - // use 10 terms in the summation for now - double exponent[MAX_TERMS]; - double Xn[MAX_TERMS]; - double prefactor; - int terms; - Real tscale; - // random number - double rnd; + GreensFunction1DAbsAbs const* gf; + RealVector& psurvTable; + Real rnd; }; - static double drawT_f (double t, void *p); - - struct drawR_params + struct drawR_params { - double S_Cn_An[MAX_TERMS]; - double n_L[MAX_TERMS]; - // variables H: for additional terms appearing as multiplicative factors etc. - double H[5]; - int terms; - // random number - double rnd; + GreensFunction1DAbsAbs const* gf; + const Real t; + RealVector table; + Real rnd; }; - static double drawR_f (double z, void *p); + uint guess_maxi(Real const& t ) const; + + + static Real drawT_f (Real t, void *p); + + Real p_survival_table( Real t, RealVector& psurvTable ) const; + + Real p_survival_i(uint i, Real const& t, RealVector const& table ) const; + + Real p_survival_table_i_v( uint const& i ) const; + + Real p_survival_table_i_nov( uint const& i ) const; + + void createPsurvTable( uint const& maxi, RealVector& table) const; + + + static Real drawR_f (Real z, void* p); + + Real p_int_r_table(Real const& r, Real const& t, RealVector& table) const; + + Real p_int_r_i(uint i, Real const& r, Real const& t, RealVector& table) const; + + void create_p_int_r_Table( Real const& t, uint const& maxi, RealVector& table ) const; + + Real get_p_int_r_Table_i( uint& i, Real const& t, RealVector& table ) const + { + if( i >= table.size() ) + { + create_p_int_r_Table(t, i+1, table); + } + + return table[i]; + } private: // The diffusion constant and drift velocity @@ -208,5 +232,7 @@ class GreensFunction1DAbsAbs: public GreensFunction Real l_scale; // This is the time scale of the system, used by drawTime_f Real t_scale; + + static Logger& log_; }; -#endif // __FIRSTPASSAGEGREENSFUNCTION1D_HPP +#endif // __GREENSFUNCTION1DABSABS_HPP diff --git a/GreensFunction1DAbsSinkAbs.cpp b/GreensFunction1DAbsSinkAbs.cpp new file mode 100644 index 00000000..943efc40 --- /dev/null +++ b/GreensFunction1DAbsSinkAbs.cpp @@ -0,0 +1,1077 @@ +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "findRoot.hpp" +#include "GreensFunction1DAbsSinkAbs.hpp" + +const unsigned int GreensFunction1DAbsSinkAbs::MAX_TERMS; +const unsigned int GreensFunction1DAbsSinkAbs::MIN_TERMS; + +/* This is the appropriate definition of the function defining + the roots of our Green's functions in GSL. + Later needed by the rootfinder. */ +Real GreensFunction1DAbsSinkAbs::root_f (Real x, void *p) +{ + struct root_f_params *params = (struct root_f_params *)p; + const Real Lm_L = (params->Lm_L); + const Real h = (params->h); + + // L = Lr + Ll + // h = L * k / (2 * D) + // L_Lm = Lr + Ll / Lr - Ll + // x = q * L + + return x * sin(x) + h * ( cos(x * Lm_L) - cos(x) ); + +} + +/* Calculates the first n roots of root_f */ +void GreensFunction1DAbsSinkAbs::calculate_n_roots(uint const& n) const +{ + uint i( rootList_size() ); + if( n <= i ) + return; + + const Real Lr( getLr() ); + const Real Ll( getLl() ); + const Real L( Lr + Ll ); + const Real Lm( Lr - Ll ); + const Real Lm_L( Lm / L ); + const Real h( getk() * L / ( 2 * getD() ) ); + + Real root_i; + real_pair lower_upper_pair; + + /* Define the root function. */ + gsl_function F; + struct root_f_params params = { Lm_L, h }; + + F.function = &GreensFunction1DAbsSinkAbs::root_f; + F.params = ¶ms; + + /* define and ad a new solver type brent */ + const gsl_root_fsolver_type* solverType( gsl_root_fsolver_brent ); + gsl_root_fsolver* solver( gsl_root_fsolver_alloc( solverType ) ); + + /* If this is the first run, set parameters.*/ + if(i == 0) + { + lo_up_params.h = h; + lo_up_params.Lm_L = Lm_L; + lo_up_params.long_period = std::max( L/Lr * M_PI, L/Ll * M_PI ); + lo_up_params.short_period = std::min( L/Lr * M_PI, L/Ll * M_PI ); + lo_up_params.last_long_root = 0.0; + lo_up_params.last_short_root = 0.0; + } + + /* Find all the roots up to the nth */ + while(i++ < n) + { + lower_upper_pair = get_lower_and_upper(); + + root_i = findRoot( F, solver, lower_upper_pair.first, lower_upper_pair.second, + 1.0*EPSILON, EPSILON, "GreensFunction1DAbsSinkAbs::root_f" ); + + assert( root_i > std::max(lo_up_params.last_long_root, + lo_up_params.last_short_root) + - EPSILON ); + + ad_to_rootList( root_i / L ); + + if(lo_up_params.last_was_long) + lo_up_params.last_long_root = root_i; + else + lo_up_params.last_short_root = root_i; + } + + gsl_root_fsolver_free( solver ); +} + + +/* returns two points on the x-axis which straddle the next root. */ +std::pair GreensFunction1DAbsSinkAbs::get_lower_and_upper() const +{ + const Real root_n( std::max(lo_up_params.last_long_root, + lo_up_params.last_short_root) ); + const Real safety( .75 ); + + Real lower, upper, next_root_est, left_offset, right_offset; + + const Real last_root( root_n == 0.0 ? M_PI : root_n ); + + if( lo_up_params.h / last_root < 1 ) + { + right_offset = M_PI; + next_root_est = root_n + M_PI; + } + else + { + const Real next_root_long( lo_up_params.last_long_root + + lo_up_params.long_period ); + const Real next_root_short( lo_up_params.last_short_root + + lo_up_params.short_period ); + + if( next_root_long < next_root_short ) + { + next_root_est = next_root_long; + + right_offset = std::min( next_root_short - next_root_est, + lo_up_params.long_period ); + + lo_up_params.last_was_long = true; + } + else + { + next_root_est = next_root_short; + + right_offset = std::min( next_root_long - next_root_est, + lo_up_params.short_period ); + + lo_up_params.last_was_long = false; + } + } + + left_offset = next_root_est - root_n - 1000 * EPSILON; + + lower = next_root_est - left_offset; + upper = next_root_est + safety * right_offset; + + struct root_f_params p = { lo_up_params.Lm_L, lo_up_params.h }; + + Real f_lower( root_f( lower, &p ) ); + Real f_upper( root_f( upper, &p ) ); + + /* set the parity operator for the next root: + +1 for an even root. + -1 for an odd root. */ + const int parity_op( 2 * ( rootList_size()%2 ) - 1 ); + + /* f_lower must have correct sign. */ + if( f_lower * parity_op > 0 ) + { + log_.warn("f(lower) has wrong sign at root# %6u, for h = %.5g, Lm/L = %.5g.", + rootList_size() + 1, lo_up_params.h, lo_up_params.Lm_L ); + + log_.warn("f_low( %.16g ) = %.16g , f_high( %.16g ) = %.16g", + lower, f_lower, upper, f_upper); + } + + /* If the parity is incorrect -> correct it */ + if( f_upper * parity_op < 0) + { + int cntr( 0 ); + + Real delta( .1 * safety * right_offset ); + const Real save_upper( upper ); + + /* Assuming the upper point has overshoot the straddle region, + subtract from the upper limit, until endpoits do straddle. + */ + while(f_upper * parity_op < 0 && cntr++ < 10 ) + { + //Correct for overshoot. + upper -= delta; + f_upper = root_f( upper, &p ); + } + + /* If upper point still doesn't straddle the root, increase the old estimate + of upper, until it does straddle*/ + if(cntr >= 10) + { + cntr = 0; + upper = save_upper; + f_upper = root_f( upper, &p ); + + while(f_upper * parity_op < 0 && cntr++ < 10 ) + { + //Correct for undershoot. + upper += delta; + f_upper = root_f( upper, &p ); + } + + // Still no straddle? => Crash is imminent. + if(cntr >= 10) + { + log_.warn("Failed to straddle root# %6u. ", + rootList_size() + 1); + log_.warn("next root est. = %.16g with stepsize: %.16g, ll: %.16g, ls: %.16g.", + next_root_est, delta, lo_up_params.last_long_root, lo_up_params.last_short_root); + log_.warn("f_low( %.16g ) = %.16g, f_high( %.16g ) = %.16g.", + lower, f_lower, upper, f_upper); + } + } + + } + + return real_pair( lower, upper ); +} + +/* returns a guess for the number of terms needed for + the greensfunction to converge at time t */ +uint GreensFunction1DAbsSinkAbs::guess_maxi(Real const& t) const +{ + const uint safety(2); + + if (t >= INFINITY) + { + return safety; + } + + const Real D( getD() ); + + const Real root0( get_root( 0 ) ); + const Real Dt(D * t); + + const Real thr(exp(- Dt * root0 * root0) * EPSILON * 1e-1); + + if (thr <= 0.0) + { + return MAX_TERMS; + } + + const Real max_root( sqrt(root0 * root0 - log(thr) / Dt) ); + + const uint maxi(safety + + static_cast(max_root * ( getLr() + getLl() ) / M_PI)); + + return std::min(maxi, MAX_TERMS); +} + + +/* Standart form of the greensfunction without numerator */ +inline Real GreensFunction1DAbsSinkAbs::p_exp_den_i(Real const& t, + Real const& root_i, + Real const& root_i2) const +{ + return exp( - getD() * root_i2 * t ) / p_denominator_i( root_i ); +} + + +/* Denominator of the greensfunction. */ +inline Real GreensFunction1DAbsSinkAbs::p_denominator_i(Real const& root_n) const +{ + const Real Lm( getLr() - getLl() ); + const Real L( getLr() + getLl() ); + + const Real term1( root_n * L * cos( root_n * L ) + sin( root_n * L ) ); + const Real term2( L * sin( root_n * L ) - Lm * sin( root_n * Lm ) ); + + return getD() * term1 + getk() / 2. * term2; +} + + +Real GreensFunction1DAbsSinkAbs::p_survival(Real t) const +{ + RealVector table; + return p_survival_table(t, table); +} + +/* Calculates survival probability using a table. + Switchbox for which greensfunction to use. */ +Real GreensFunction1DAbsSinkAbs::p_survival_table(Real t, RealVector& psurvTable) const +{ + THROW_UNLESS( std::invalid_argument, t >= 0.0 ); + + Real p; + + const Real D( getD() ); + const Real sigma(getsigma()); + const Real a( geta() ); + const Real L0( getL0() ); + + if (t == 0.0 || D == 0.0 ) + { + //particle can't escape. + return 1.0; + } + + /* First check if we need full solution. + Else we use approximation. */ + const Real maxDist(CUTOFF_H * sqrt(2.0 * D * t)); + // dist to nearest absorbing boundary. + const Real distToAbs( std::min(a - r0, r0 -sigma) ); + + if( L0 > maxDist ) //Sink not in sight + { + if( distToAbs > maxDist ) + return 1.0; + else + return XS10( t, distToAbs, D, 0.0 ); + } + else + { + if( distToAbs > maxDist ) //Only sink in sight. + { + return XS030( t, L0, getk(), D ); + } + } + + const uint maxi( guess_maxi(t) ); + + if( maxi == MAX_TERMS ) + log_.info("drawT: maxi was cut to MAX_TERMS for t = %.16g", t); + + if (psurvTable.size() < maxi ) + { + calculate_n_roots( maxi ); // this updates the table + createPsurvTable( psurvTable ); + } + + p = funcSum_all(boost::bind(&GreensFunction1DAbsSinkAbs::p_survival_i, + this, _1, t, psurvTable), + maxi); + + return p; +} + + +/* Calculates the i'th term of the p_survival sum. */ +Real GreensFunction1DAbsSinkAbs::p_survival_i( uint i, + Real const& t, + RealVector const& table) const +{ + const Real root_i( get_root( i ) ); + return exp( - getD() * t * gsl_pow_2( root_i ) ) * table[ i ]; +} + + +/* Calculates the part of the i'th term of p_surv not dependent on t */ +Real GreensFunction1DAbsSinkAbs::p_survival_table_i( Real const& root_i ) const +{ + const Real D ( getD() ); + const Real k ( getk() ); + const Real Lr( getLr() ); + const Real Ll( getLl() ); + const Real L0( getL0() ); + const Real L ( Lr + Ll ); + const Real LrmL0( Lr - L0 ); + + const Real term1( sin( root_i * L ) - + sin( root_i * LrmL0 ) - sin( root_i * (Ll + L0) ) ); + + const Real term2( sin( root_i * Lr ) - sin( root_i * L0 ) + - sin( root_i * LrmL0 ) ); + + Real numerator( D * term1 + k * sin( root_i * Ll ) * term2 / root_i ); + numerator *= 2.0; + + return numerator / p_denominator_i( root_i ); +} + + +/* Fills table with terms in the p_survival sum which don't depend on t. */ +void GreensFunction1DAbsSinkAbs::createPsurvTable(RealVector& table) const +{ + uint const root_nbr( rootList_size() ); + uint i( table.size() ); + + while( i < root_nbr ) + { + table.push_back( p_survival_table_i( get_root( i++ ) ) ); + } +} + + +/* Returns i'th term of prob_r (domain containing r0) function */ +Real GreensFunction1DAbsSinkAbs::prob_r_r0_i(uint i, + Real const& rr, + Real const& t) const +{ + const Real root_i( get_root(i) ); + const Real Lr( getLr() ); + const Real Ll( getLl() ); + Real L0( getL0() ); + Real rr2( rr ); + + /* Check if r is left or right of the starting position r0. + If so, interchange rr with L0. */ + if( rr < L0 ) + { + rr2 = L0; + L0 = rr; + } + + const Real LlpL0( Ll + L0 ); + const Real Lrmrr( Lr - rr2 ); + + Real numerator( getD() * root_i * sin( root_i * LlpL0 ) + + getk() * sin( root_i * Ll ) * sin( root_i * L0) ); + + numerator *= sin( root_i * Lrmrr ); + + return - 2.0 * p_exp_den_i(t, root_i, gsl_pow_2( root_i ) ) * numerator; +} + + +/* Returns i'th term of prob_r (domain not containing r0) function */ +Real GreensFunction1DAbsSinkAbs::prob_r_nor0_i(uint i, + Real const& rr, + Real const&t) const +{ + const Real root_i( get_root(i) ); + const Real Lr( getLr() ); + const Real Ll( getLl() ); + const Real L0( getL0() ); + + const Real LrmL0( Lr - L0 ); + const Real Llprr( Ll + rr ); + + const Real numerator( getD() * root_i * sin( root_i * Llprr ) * sin( root_i * LrmL0 ) ); + + return - 2.0 * p_exp_den_i(t, root_i, gsl_pow_2( root_i ) ) * numerator; +} + + +/* Calculates the probability density of finding the particle at location r + at time t. */ +Real GreensFunction1DAbsSinkAbs::prob_r(Real r, Real t) const +{ + THROW_UNLESS( std::invalid_argument, t >= 0.0 ); + THROW_UNLESS( std::invalid_argument, (r-sigma) >= 0.0 && r <= a && (r0 - sigma) >= 0.0 && r0<=a ); + + const Real D( getD() ); + const Real Lr( getLr() ); + const Real Ll( getLl() ); + const Real L( Lr + Ll ); + + // if there was no time change or zero diffusivity => no movement + if (t == 0 || D == 0) + { + // the probability density function is a delta function + if (r == r0) + { + return INFINITY; + } + else + { + return 0.0; + } + } + + // if r is at one of the the absorbing boundaries + if ( fabs( a - r ) < EPSILON * L || fabs( r - sigma ) < EPSILON * L ) + { + return 0.0; + } + + const Real rr( getr0() - getrsink() >= 0 ? r - rsink : rsink - r ); + + Real p; + + /* Determine wether rr lies in the same sub-domain as r0. + A different function is caculated when this is the case. */ + if( rr >= 0 ) + { + p = funcSum(boost::bind(&GreensFunction1DAbsSinkAbs::prob_r_r0_i, + this, _1, rr, t), + MAX_TERMS); + } + else + { + p = funcSum(boost::bind(&GreensFunction1DAbsSinkAbs::prob_r_nor0_i, + this, _1, rr, t), + MAX_TERMS); + } + + return p; +} + + +/* Calculates the probability density of finding the particle at location r at + time t, given that the particle is still in the domain. */ +Real GreensFunction1DAbsSinkAbs::calcpcum(Real r, Real t) const +{ + return prob_r(r, t)/p_survival( t ); +} + + +/* Function returns flux at absorbing bounday sigma. */ +Real GreensFunction1DAbsSinkAbs::flux_leaves(Real t) const +{ + if( t == 0 || D == 0 ) + { + return 0.0; + } + + const uint maxi( guess_maxi( t ) ); + + if( getr0() >= getrsink() ) + return flux_abs_Ll( t, maxi ); + else + return - flux_abs_Lr( t, maxi ); +} + + +/* Function returns flux at absorbing bounday a. */ +Real GreensFunction1DAbsSinkAbs::flux_leavea(Real t) const +{ + if( t == 0 || D == 0 ) + { + return 0.0; + } + + const uint maxi( guess_maxi( t ) ); + + if( getr0() < getrsink() ) + return - flux_abs_Ll( t, maxi ); + else + return flux_abs_Lr( t, maxi ); +} + + +/* Calculates the total probability flux leaving the domain at time t + This is simply the negative of the time derivative of the survival prob. + at time t [-dS(t')/dt' for t'=t]. */ +Real GreensFunction1DAbsSinkAbs::flux_tot(Real t) const +{ + const Real D( getD() ); + + if( t == 0 || ( D == 0 && getr0() != getrsink() ) ) + { + return 0.0; + } + + Real p; + + p = funcSum(boost::bind(&GreensFunction1DAbsSinkAbs::flux_tot_i, + this, _1, t), + MAX_TERMS); + + return D * p; +} + + +/* Calculates i'th term of total flux leaving at time t. */ +Real GreensFunction1DAbsSinkAbs::flux_tot_i(uint i, Real const& t) const +{ + const Real root_i( get_root( i ) ); + const Real root_i2( gsl_pow_2( root_i ) ); + + return root_i2 * exp( - getD() * t * root_i2 ) * + p_survival_table_i( root_i ); +} + + +/* Flux leaving throught absorbing boundary from sub-domain containing r0. */ +Real GreensFunction1DAbsSinkAbs::flux_abs_Lr(Real t, uint const& maxi) const +{ + const Real D( getD() ); + Real p; + + p = funcSum(boost::bind(&GreensFunction1DAbsSinkAbs::flux_abs_Lr_i, + this, _1, t), + MAX_TERMS); + + return - D * 2 * p; +} + + +/* Calculates the i'th term of the flux at Lr. */ +Real GreensFunction1DAbsSinkAbs::flux_abs_Lr_i(uint i, Real const& t) const +{ + const Real root_i( get_root( i ) ); + const Real D( getD() ); + const Real k( getk() ); + const Real Ll( getLl() ); + const Real L0( getL0() ); + const Real LlpL0( Ll + L0 ); + + Real numerator( k * sin( root_i * Ll ) * sin ( root_i * L0 ) + + D * root_i * sin( root_i * LlpL0 ) ); + + numerator *= root_i; + + return p_exp_den_i(t, root_i, gsl_pow_2( root_i ) ) * numerator; +} + + +/* Flux leaving throught absorbing boundary from sub-domain not containing r0. */ +Real GreensFunction1DAbsSinkAbs::flux_abs_Ll(Real t, uint const& maxi) const +{ + const Real D2( gsl_pow_2( getD() ) ); + Real p; + + p = funcSum(boost::bind(&GreensFunction1DAbsSinkAbs::flux_abs_Ll_i, + this, _1, t), + MAX_TERMS); + + return 2 * D2 * p; +} + + +/* Calculates the i'th term of the flux at Ll. */ +Real GreensFunction1DAbsSinkAbs::flux_abs_Ll_i(uint i, Real const& t) const +{ + const Real root_i( get_root( i ) ); + const Real root_i2( gsl_pow_2( root_i ) ); + const Real LrmL0( getLr() - getL0() ); + + Real numerator( root_i2 * sin( root_i * LrmL0 ) ); + + return p_exp_den_i(t, root_i, root_i2 ) * numerator; +} + + +/* Calculates the probability flux leaving the domain through the sink + at time t */ +Real GreensFunction1DAbsSinkAbs::flux_sink(Real t) const +{ + const Real D( getD() ); + + if( t == 0 || ( D == 0 && getr0() != getrsink() ) ) + { + return 0.0; + } + + return getk() * prob_r(getrsink(), t); +} + + +/* Determine which event has occured, an escape or a reaction. Based on the + fluxes through the boundaries and the sink at the given time. */ +GreensFunction1DAbsSinkAbs::EventKind +GreensFunction1DAbsSinkAbs::drawEventType( Real rnd, Real t ) const +{ + THROW_UNLESS( std::invalid_argument, rnd < 1.0 && rnd >= 0.0 ); + THROW_UNLESS( std::invalid_argument, t > 0.0 ); + + const Real a( geta() ); + const Real sigma( getsigma() ); + const Real r0( getr0() ); + const Real L( a - sigma ); + + /* If the sink is impermeable (k==0) or + the particle is at one the absorbing boundaries (sigma or a) => IV_ESCAPE event */ + if ( k == 0 || fabs(a - r0) < EPSILON * L || fabs(sigma - r0) < EPSILON * L) + { + return IV_ESCAPE; + } + + /* The event is sampled from the flux ratios. + Two possiblities: + (1) Leave through left or right boundary - IV_ESCAPE + (2) Leave through sink - IV_REACTION + */ + + /* First check if we need to compare flux ratio's. + If particle is near only one boundary or sink, this is the escape event. */ + const Real maxDist(CUTOFF_H * sqrt(2.0 * getD() * t)); + // dist to nearest absorbing boundary. + const Real distToAbs( std::min(a - r0, r0 -sigma) ); + + if( getL0() > maxDist ) //Sink not in sight + { + if( distToAbs < maxDist ) + return IV_ESCAPE; + } + else + { + if( distToAbs > maxDist ) //Only sink in sight. + return IV_REACTION; + } + + /* Allready fill rootList with needed roots. */ + const uint maxi( guess_maxi( t ) ); + + if( maxi == MAX_TERMS ) + log_.info("drawEventType: maxi was cut to MAX_TERMS for t = %.16g", t); + + calculate_n_roots( maxi ); + + rnd *= flux_tot( t ); + + const Real p_sink( flux_sink( t ) ); + if (rnd < p_sink) + { + return IV_REACTION; + } + else + { + return IV_ESCAPE; + } +} + + +/* This function is needed to cast the math. form of the function + into the form needed by the GSL root solver. */ +Real GreensFunction1DAbsSinkAbs::drawT_f(Real t, void *p) +{ + struct drawT_params *params = (struct drawT_params *)p; + return params->rnd - params->gf->p_survival_table( t, params->table ); +} + + +/* Draws the first passage time from the survival probability, + using an assistance function drawT_f that casts the math. function + into the form needed by the GSL root solver. */ +Real GreensFunction1DAbsSinkAbs::drawTime(Real rnd) const +{ + THROW_UNLESS( std::invalid_argument, 0.0 <= rnd && rnd < 1.0 ); + + const Real a( geta() ); + const Real r0( getr0() ); + const Real D( getD() ); + //const Real k( getk() ); + const Real Lr( getLr() ); + const Real Ll( getLl() ); + const Real L0( getL0() ); + const Real L( getLr() + getLl() ); + + if ( D == 0.0 || L == INFINITY ) + { + return INFINITY; + } + + if ( rnd > (1 - EPSILON) || L < 0.0 || fabs( a - r0 ) < EPSILON * L ) + { + return 0.0; + } + + /* the structure to store the numbers to calculate the numbers for 1-S */ + RealVector psurvTable; + drawT_params params = { this, psurvTable, rnd }; + + /* Find a good interval to determine the first passage time. + First we get the distance to one of the absorbing boundaries or the sink. */ + Real dist( std::min(Lr - L0, Ll + L0) ); + Real t_guess; + + /* For a particle at contact, the time for the particle to be absorbed by the + radiation boundary is 4 D / (k*k). If the particle is at a distace x0 from the + radiation boundary, we approximate the gues for the next event-time by: + t_guess = D / (k*k) + x0 * x0 / D. + */ + const Real t_Abs( gsl_pow_2( dist ) / D ); + const Real t_Rad( 4 * D / (k * k) + gsl_pow_2( L0 ) / D ); + + t_guess = std::min(t_Abs, t_Rad ); + t_guess *= .1; + + /* Define the function for the rootfinder */ + gsl_function F; + F.function = &GreensFunction1DAbsSinkAbs::drawT_f; + F.params = ¶ms; + + Real value( GSL_FN_EVAL( &F, t_guess ) ); + Real low( t_guess ); + Real high( t_guess ); + + // scale the interval around the guess such that the function straddles + if( value < 0.0 ) + { + // if the guess was too low + do + { + if( fabs( high ) >= t_guess * 1e10 ) + { + log_.error("drawTime: couldn't adjust high. F( %.16g ) = %.16g" + , high, value); + throw std::exception(); + } + // keep increasing the upper boundary until the + // function straddles + high *= 10; + value = GSL_FN_EVAL( &F, high ); + } + while ( value <= 0.0 ); + } + else + { + // if the guess was too high + // initialize with 2 so the test below survives the first + // iteration + Real value_prev( 2 ); + do + { + if( fabs( low ) <= t_guess * 1e-10 || + fabs(value-value_prev) < EPSILON*1.0 ) + { + log_.warn("drawTime Couldn't adjust low. F( %.16g ) = %.16g" + , low, value); + /* + std::cerr << "GF1DSink::drawTime Couldn't adjust low. F(" << low << ") = " + << value << " t_guess: " << t_guess << " diff: " + << (value - value_prev) << " value: " << value + << " value_prev: " << value_prev << " rnd: " + << rnd << std::endl; + */ + return low; + } + value_prev = value; + // keep decreasing the lower boundary until the function straddles + low *= 0.1; + // get the accompanying value + value = GSL_FN_EVAL( &F, low ); + } + while ( value >= 0.0 ); + } + + /* find the intersection on the y-axis between the random number and + the function */ + + // define a new solver type brent + const gsl_root_fsolver_type* solverType( gsl_root_fsolver_brent ); + // make a new solver instance + gsl_root_fsolver* solver( gsl_root_fsolver_alloc( solverType ) ); + const Real t( findRoot( F, solver, low, high, t_scale*EPSILON, EPSILON, + "GreensFunction1DAbsSinkAbs::drawTime" ) ); + // return the drawn time + return t; +} + + +/* Retrurns the c.d.f. with respect to the position at time t. + - Also a switchbox for which GF to choose. */ +Real GreensFunction1DAbsSinkAbs::p_int_r_table(Real const& r, + Real const& t, + RealVector& table) const +{ + Real p; + const Real rsink( getrsink() ); + + /* when r0 lies left of the sink, mirror the domain around rsink + : rr -> -rr. */ + const Real rr( getr0() - rsink >= 0 ? r - rsink : rsink - r ); + + /* First check if we need full solution. + Else we use approximation. */ + const Real maxDist(CUTOFF_H * sqrt(2.0 * D * t)); + const Real distToa( a - r0 ); + const Real distTos( r0 - sigma ); + + if( getL0() > maxDist ) //Sink not in sight + { + if( distTos > maxDist ) + { + if( distToa > maxDist ) + return XI00(r, t, r0, D, 0.0); //Nothing in sight. + else + return XI10(a - r, t, distToa, D, 0.0); //Only a boundary in sight. + } + else + { + if( distToa > maxDist ) + return XI10( r - sigma, t, distTos, D, 0.0 ); //Only s boundary in sight. + } + } + else + { + if( distToa > maxDist && distTos > maxDist ) //Only sink in sight. + { + return XI030( rr, t, getL0(), getk(), D ); + } + } + + const uint maxi( guess_maxi(t) ); + + if( maxi == MAX_TERMS ) + log_.warn("p_int_r_table: maxi was cut to MAX_TERMS for t = %.16g" + , t); + + if (table.size() < maxi ) + { + calculate_n_roots( maxi ); // this updates the table + create_p_int_r_Table( t, table ); + } + + /* Determine in which part of the domain rr lies, and + thus which function to use. */ + Real (GreensFunction1DAbsSinkAbs::*p_int_r_i) + (uint, Real const&, Real const&, RealVector& table) const = NULL; + + if( rr <= 0 ) + p_int_r_i = &GreensFunction1DAbsSinkAbs::p_int_r_leftdomain; + else if( rr < getL0() ) + p_int_r_i = &GreensFunction1DAbsSinkAbs::p_int_r_rightdomainA; + else + p_int_r_i = &GreensFunction1DAbsSinkAbs::p_int_r_rightdomainB; + + p = funcSum(boost::bind(p_int_r_i, + this, _1, rr, t, table), + MAX_TERMS); + + return 2.0 * p; +} + + +Real GreensFunction1DAbsSinkAbs::p_int_r(Real const& r, + Real const& t) const +{ + THROW_UNLESS( std::invalid_argument, r >= getsigma() && r <= geta() ); + THROW_UNLESS( std::invalid_argument, t >= 0.0 ); + + RealVector table; + return p_int_r_table(r, t, table); +} + + +void GreensFunction1DAbsSinkAbs::create_p_int_r_Table( Real const& t, + RealVector& table ) const +{ + const uint root_nbr( rootList_size() ); + uint i( table.size() ); + + while( i < root_nbr ) + { + const Real root_i( get_root( i ) ); + table.push_back( p_exp_den_i(t, root_i, gsl_pow_2( root_i ) ) ); + i++; + } +} + + +//Integrated Greens function for rr part of [-Ll, 0] +Real GreensFunction1DAbsSinkAbs::p_int_r_leftdomain(uint i, + Real const& rr, + Real const& t, + RealVector& table) const +{ + const Real root_i( get_root( i ) ); + const Real LrmL0( getLr() - getL0() ); + const Real Llprr( getLl() + rr ); + + const Real temp( getD() * sin( root_i * LrmL0 ) * + ( cos( root_i * Llprr ) - 1.0 ) ); + + return get_p_int_r_Table_i(i, t, table) * temp; +} + + +//Integrated Greens function for rr part of (0, L0] +Real GreensFunction1DAbsSinkAbs::p_int_r_rightdomainA(uint i, + Real const& rr, + Real const& t, + RealVector& table) const +{ + const Real root_i( get_root( i ) ); + const Real LrmL0( getLr() - getL0() ); + const Real Llprr( getLl() + rr ); + const Real root_i_rr( root_i * rr ); + + const Real temp( getD() * ( cos( root_i * Llprr ) - 1.0 ) + + getk() / root_i * ( cos( root_i_rr ) - 1.0 ) + * sin( root_i * getLl() ) ); + + return get_p_int_r_Table_i(i, t, table) * sin( root_i * LrmL0 ) * temp; +} + + +//Integrated Greens function for rr part of (L0, Lr] +Real GreensFunction1DAbsSinkAbs::p_int_r_rightdomainB(uint i, + Real const& rr, + Real const& t, + RealVector& table) const +{ + const Real root_i( get_root( i ) ); + const Real Lr( getLr() ); + const Real Ll( getLl() ); + const Real L0( getL0() ); + const Real L( Lr + Ll ); + const Real LrmL0( Lr - L0 ); + const Real Lrmrr( Lr - rr ); + const Real LlpL0( Ll + L0 ); + + const Real term1( sin( root_i * L ) - sin( root_i * LrmL0 ) - + sin( root_i * LlpL0 ) * cos( root_i * Lrmrr ) ); + + const Real term2( sin( root_i * Lr ) - sin( root_i * LrmL0 ) - + sin( root_i * L0 ) * cos( root_i * Lrmrr )); + + const Real temp( getD() * term1 + + getk() * sin( root_i * Ll ) * term2 / root_i ); + + return get_p_int_r_Table_i(i, t, table) * temp; +} + + +/* Function for GFL rootfinder of drawR. */ +Real GreensFunction1DAbsSinkAbs::drawR_f(Real r, void *p) +{ + struct drawR_params *params = (struct drawR_params *)p; + return params->gf->p_int_r_table(r, params->t, params->table) + - params->rnd; +} + + +Real GreensFunction1DAbsSinkAbs::drawR(Real rnd, Real t) const +{ + THROW_UNLESS( std::invalid_argument, 0.0 <= rnd && rnd <= 1.0 ); + THROW_UNLESS( std::invalid_argument, t >= 0.0 ); + + const Real D( getD() ); + const Real Lr( getLr() ); + const Real Ll( getLl() ); + const Real r0( getr0() ); + const Real L( Lr + Ll ); + + if (t == 0.0 || D == 0.0 ) + { + // the trivial case + return r0; + } + + if ( L < 0.0 ) + { + // if the domain had zero size + return 0.0; + } + + if ( rnd <= EPSILON ) + { + return getsigma(); + } + + if( rnd >= ( 1 - EPSILON ) ) + { + return geta(); + } + + // the structure to store the numbers to calculate r. + RealVector drawR_table; + drawR_params parameters = { this, t, drawR_table, rnd * p_survival( t ) }; + + // define gsl function for rootfinder + gsl_function F; + F.function = &drawR_f; + F.params = ¶meters; + + // define a new solver type brent + const gsl_root_fsolver_type* solverType( gsl_root_fsolver_brent ); + + gsl_root_fsolver* solver( gsl_root_fsolver_alloc( solverType ) ); + Real r( findRoot( F, solver, getsigma(), geta(), + EPSILON*L, EPSILON, "GreensFunction1AbsSinkAbs::drawR" ) ); + + // Convert the position rr to 'world' coordinates and return it. + return r; +} + + +std::string GreensFunction1DAbsSinkAbs::dump() const +{ + std::ostringstream ss; + ss << "D = " << getD() << ", sigma = " << getsigma() << + ", a = " << geta() << + ", r0 = " << getr0() << + ", rsink = " << getrsink() << + ", k = " << getk() << std::endl; + return ss.str(); +} + +Logger& GreensFunction1DAbsSinkAbs::log_( + Logger::get_logger("GreensFunction1DAbsSinkAbs")); diff --git a/GreensFunction1DAbsSinkAbs.hpp b/GreensFunction1DAbsSinkAbs.hpp new file mode 100644 index 00000000..3182e5da --- /dev/null +++ b/GreensFunction1DAbsSinkAbs.hpp @@ -0,0 +1,368 @@ +#if !defined( __GREENSFUNCTION1DABSSINKABS_HPP ) +#define __GREENSFUNCTION1DABSSINKABS_HPP + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "findRoot.hpp" +#include "Defs.hpp" +#include "GreensFunction.hpp" +#include "funcSum.hpp" +#include "freeFunctions.hpp" +#include "Logger.hpp" + +class GreensFunction1DAbsSinkAbs: public GreensFunction +{ +public: + typedef std::pair real_pair; + typedef std::vector RealVector; + typedef unsigned int uint; + +private: + // This is a typical length scale of the system, may not be true! + static const Real L_TYPICAL = 1E-8; + // The typical timescale of the system, may also not be true!! + static const Real T_TYPICAL = 1E-6; + // measure of 'sameness' when comparing floating points numbers + static const Real EPSILON = 1E-10; + // Is 1E3 a good measure for the probability density?! + static const Real PDENS_TYPICAL = 1; + // The maximum number of terms used in calculating the sum + static const uint MAX_TERMS = 500; + // The minimum number of terms + static const uint MIN_TERMS = 20; + /* Cutoff distance: When H * sqrt(2Dt) < a - r0 OR ro - sigma + use free greensfunction instead of absorbing. */ + static const Real CUTOFF_H = 6.0; + + +public: + GreensFunction1DAbsSinkAbs(Real D, Real k, Real r0, Real rsink, Real sigma, Real a) + : GreensFunction(D), k(k), r0(r0), sigma(sigma), a(a), rsink(rsink), l_scale(L_TYPICAL), t_scale(T_TYPICAL) + { + /* Set variables which define a domain with the sink at the origin. + Futhermore r0 is assumed to be ringht from the sink. */ + assert( a > sigma ); + + L0 = fabs( r0 - rsink ); + if( r0 >= rsink ) + { + Lr = a - rsink; + Ll = rsink - sigma; + } + else + { + Lr = rsink - sigma; + Ll = a - rsink; + } + + calculate_n_roots( 1 ); + } + + ~GreensFunction1DAbsSinkAbs() + { + ; // empty + } + + Real geta() const + { + return a; + } + + Real getLr() const + { + return Lr; + } + + Real getLl() const + { + return Ll; + } + + Real getL0() const + { + return L0; + } + + Real getrsink() const + { + return rsink; + } + + Real getsigma() const + { + return sigma; + } + + Real getr0() const + { + return r0; + } + + Real getk() const + { + return k; + } + + /* Calculates the probability density of finding the particle at + location z at timepoint t, given that the particle is still in the + domain. */ + Real calcpcum(Real r, Real t) const; + + /* Determine which event has occured at time t. Either an escape + (left or right boundary) or a reaction with the sink. + Based on the fluxes through the boundaries at the given time. */ + EventKind drawEventType( Real rnd, Real t ) const; + + /* Draws the first passage time from the propensity function */ + Real drawTime(Real rnd) const; + + /* Draws the position of the particle at a given time, assuming that + the particle is still in the domain. */ + Real drawR(Real rnd, Real t) const; + + /* Calculates the probability flux leaving the domain through the right + absorbing boundary at time t. */ + Real flux_leavea(Real t) const; + + /* Calculates the probability flux leaving the domain through the left + absorbing boundary at time t. */ + Real flux_leaves(Real t) const; + + /* Calculates the probability flux leaving the domain through the sink + at time t. */ + Real flux_sink(Real t) const; + + /* Calculates the probability of finding the particle inside the + domain at time t -> the survival probability */ + Real p_survival(Real t) const; + + /* c.d.f. of the greensfunction with respect to position. */ + Real p_int_r_table( Real const& r, Real const& t, RealVector& table ) const; + + Real p_int_r(Real const& r, Real const& t) const; + + /* TODO: Private methods which are public for now - debugging */ + + /* Calculates the total probability flux leaving the domain at time t. */ + Real flux_tot(Real t) const; + + /* Calculates the probability flux leaving the domain through the + sub-domain containing r0 via the absorbing boundary and the flux + leaving the sub-domain not containing r0 via the absorbing boundary. */ + Real flux_abs_Lr(Real t, uint const& maxi) const; + Real flux_abs_Ll(Real t, uint const& maxi) const; + + /* Calculates the probability density of finding the particle at + location r at time t. */ + Real prob_r(Real r, Real t) const; + + std::string dump() const; + + const char* getName() const + { + return "GreensFunction1DAbsSinkAbs"; + } + +private: + /* used structures */ + + struct drawR_params + { + GreensFunction1DAbsSinkAbs const* gf; + const Real t; + RealVector& table; + const Real rnd; + }; + + struct drawT_params + { + GreensFunction1DAbsSinkAbs const* gf; + RealVector& table; + const Real rnd; + }; + + struct root_f_params + { + Real Lm_L; + Real h; + }; + + struct lower_upper_params + { + Real h; + Real Lm_L; + Real long_period; + Real short_period; + Real last_long_root; + Real last_short_root; + bool last_was_long; + }; + + + /* Functions managing the rootList */ + + /* return the rootList size */ + uint rootList_size() const + { + return rootList.size(); + } + + /* returns the last root. */ + Real get_last_root() const + { + return rootList.back(); + } + + /* ad a root to the rootList */ + void ad_to_rootList( Real const& root_i ) const + { + rootList.push_back( root_i ); + } + + /* remove n'th root from rootList */ + void remove_from_rootList(uint const& n) const + { + rootList.erase( rootList.begin() + n ); + } + + /* return the n + 1'th root */ + Real get_root( uint const& n ) const + { + if( n >= rootList.size() ) + calculate_n_roots( n+1 ); + + return rootList[ n ]; + } + + + /* Functions concerned with finding the roots. */ + + /* Fills the rootList with the first n roots */ + void calculate_n_roots( uint const& n ) const; + + /* Function returns two positions on the x-axis which straddle the next root. */ + real_pair get_lower_and_upper() const; + + /* Function of which we need the roots. */ + static Real root_f(Real x, void *p); + + /* Guess the number of terms needed for convergence, given t. */ + uint guess_maxi( Real const& t ) const; + + + /* Function for calculating the survival probability. */ + Real p_survival_table(Real t, RealVector& psurvTable) const; + + Real p_survival_i( uint i, Real const& t, RealVector const& table ) const; + + Real p_survival_table_i( Real const& root_i ) const; + + void createPsurvTable( RealVector& table ) const; + + + /* Functions for calculating the greensfunction. */ + Real prob_r_r0_i(uint i, Real const& rr, Real const& t) const; + + Real prob_r_nor0_i( uint i, Real const& rr, Real const& t) const; + + + /* Functions for calculating the fluxes. */ + Real flux_tot_i(uint i, Real const& t) const; + + Real flux_abs_Lr_i(uint i, Real const& t) const; + + Real flux_abs_Ll_i(uint i, Real const& t) const; + + + + /* functions for calculating the c.d.f. */ + + /* i'th term of p_int_r(r') for r' in left domain */ + Real p_int_r_leftdomain(uint i, Real const& rr, Real const& t, RealVector& table) const; + + /* i'th term of p_int_r(r') for r' in right domain, left of r0 */ + Real p_int_r_rightdomainA(uint i, Real const& rr, Real const& t, RealVector& table) const; + + /* i'th term of p_int_r(r') for r' in right domain, right of r0 */ + Real p_int_r_rightdomainB(uint i, Real const& rr, Real const& t, RealVector& table) const; + + /* Fills table with r-independent part of p_int_r_i. */ + void create_p_int_r_Table( Real const& t, RealVector& table ) const; + + /* Returns i'th r-independent term of p_int_r_i. + Term is created if not in table. */ + Real get_p_int_r_Table_i( uint& i, Real const& t, RealVector& table) const + { + if( i >= table.size() ) + { + calculate_n_roots( i+1 ); + create_p_int_r_Table( t, table ); + } + + return table[i]; + } + + /* Denominator of the Greens function */ + inline Real p_denominator_i( Real const& root_n ) const; + + /* Standard form of Greens Function: exp( -Dt root_n ** 2 ) / denominator */ + inline Real p_exp_den_i(Real const& t, Real const& root_n, Real const& root_n2) const; + + /* Function for drawR */ + static Real drawT_f (Real t, void *p); + + /* Function for drawTime */ + static Real drawR_f (Real r, void *p); + + + /* Class variables */ + + // The reaction constant + const Real k; + //starting position + const Real r0; + // The left and right boundary of the domain (sets the l_scale, see below) + const Real sigma; + const Real a; + //Position of the sink in the domain. + const Real rsink; + // This is the length scale of the system + Real l_scale; + // This is the time scale of the system. + Real t_scale; + + /* Greensfunction assumes that the sink is at the origin, and + consists of two sub-domains: one between a boundary and the sink including + r0, and one between boundary and sink not inlcuding r0. */ + + // Length of sub-domain which does not include r0. + Real Lr; + // Length of sub-domain which does include r0. + Real Ll; + // Distance between the sink and r0. + Real L0; + + // Stores all the roots. + mutable RealVector rootList; + + // Stores params for rootfiner. + mutable struct lower_upper_params lo_up_params; + + static Logger& log_; +}; +#endif // __GREENSFUNCTION1DRADABS_HPP diff --git a/GreensFunction1DRadAbs.cpp b/GreensFunction1DRadAbs.cpp index 13e9f5f4..01f9ead4 100644 --- a/GreensFunction1DRadAbs.cpp +++ b/GreensFunction1DRadAbs.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -18,6 +19,8 @@ #include "findRoot.hpp" #include "GreensFunction1DRadAbs.hpp" +const unsigned int GreensFunction1DRadAbs::MAX_TERMS; +const unsigned int GreensFunction1DRadAbs::MIN_TERMS; // This is the appropriate definition of the function defining // the roots of our Green's functions in GSL. @@ -32,40 +35,43 @@ GreensFunction1DRadAbs::tan_f (double x, void *p) const Real a = (params->a); const Real h = (params->h); const Real h_a (h*a); - if ( h_a < 1 ) + + if ( fabs( h_a ) < 1 ) { - // h = k/D - return 1/tan(x) + (h_a)/x; + // h = k/D + return 1/tan(x) + (h_a)/x; } else { - // h = k/D - return tan(x) + x/(h_a); + // h = k/D + return tan(x) + x/(h_a); } + } -// Calculates the roots of tan(x*a)=-x/h -Real -GreensFunction1DRadAbs::root_n(int n) const +/* Fills the rootList with all the roots of tan(x*a)=-x/h up to n */ +void GreensFunction1DRadAbs::calculate_n_roots(uint const& n) const { + uint i( rootList_size() ); + if( n <= i ) + return; + const Real L( this->geta()-this->getsigma() ); const Real h( (this->getk()+this->getv()/2.0) / this->getD() ); // the drift v also comes into this constant, h=(k+v/2)/D - Real upper, lower; + Real upper, lower, root_i; - if ( h*L < 1 ) - { - // 1E-10 to make sure that he doesn't include the transition - lower = (n-1)*M_PI + 1E-10; - // (asymptotic) from infinity to -infinity - upper = n *M_PI - 1E-10; - } - else + //No drift, and k = 0, use reflective solution. + if (getk() < EPSILON && fabs( getv() ) < EPSILON ) { - lower = (n-1)*M_PI + M_PI_2 + 1E-10; - upper = n *M_PI + M_PI_2 - 1E-10; + while(i < n) + { + ad_to_rootList( M_PI * ( i + 1.0/2 ) / L ); + i++; + } + return; } - + gsl_function F; struct tan_f_params params = { L, h }; @@ -74,21 +80,71 @@ GreensFunction1DRadAbs::root_n(int n) const // define a new solver type brent const gsl_root_fsolver_type* solverType( gsl_root_fsolver_brent ); - - // make a new solver instance - // TODO: incl typecast? gsl_root_fsolver* solver( gsl_root_fsolver_alloc( solverType ) ); - // get the root = run the rootsolver - const Real root( findRoot( F, solver, lower, upper, 1.0*EPSILON, EPSILON, - "GreensFunction1DRadAbs::root_tan" ) ); + + /* Find all the roots up to the nth */ + if ( h*L < 1 ) + { + lower = i*M_PI + 1E-10; + upper = ( i + 1 ) * M_PI - 1E-10; + } + else + { + lower = i * M_PI + M_PI_2 + 1E-10; + upper = ( i + 1 ) * M_PI + M_PI_2 - 1E-10; + } + + while(i++ < n) + { + root_i = findRoot( F, solver, lower, upper, + 1.0*EPSILON, EPSILON, "GreensFunction1DRadAbs::root_tan" ); + + ad_to_rootList( root_i / L ); + + lower += M_PI; + upper += M_PI; + } + gsl_root_fsolver_free( solver ); - - return root/L; - // This rescaling is important, because the function tan_f is used to solve for - // tan(x)+x/h/L=0, whereas we actually need tan(x*L)+x/h=0, So if x solves the - // subsidiary equation, x/L solves the original one. } + +/* returns a guess for the number of terms needed for + the greensfunction to converge at time t */ +uint GreensFunction1DRadAbs::guess_maxi(Real const& t) const +{ + const uint safety(2); + + if (t >= INFINITY) + { + return safety; + } + + const Real D( getD() ); + const Real L( fabs( geta() - getsigma() ) ); + + const Real root0( get_root( 0 ) ); + const Real Dt(D * t); + + const Real thr(exp(- Dt * root0 * root0) * EPSILON * 1e-1); + + if (thr <= 0.0) + { + return MAX_TERMS; + } + + const Real max_root( sqrt(root0 * root0 - log(thr) / Dt) ); + + const uint maxi(std::max( safety + + static_cast + (max_root * L / M_PI), + MIN_TERMS ) + ); + + return std::min(maxi, MAX_TERMS); +} + + // This is the non-exponential factor in the Green's function sum, not // including the factor containing the explicit r-dependency (The latter // is given by the Bn's, see below). @@ -130,7 +186,7 @@ GreensFunction1DRadAbs::Bn (Real root_n) const const Real h2(h*h); const Real v2D(v/2.0/D); - if(v==0.0) return (h2 - (rootn2 + h2)*cos(rootnL)) / (h*root_n); + if(v==0.0) return (h2 - (rootn2 + h2)*cos(rootnL)) / (h*root_n); else return (exp(v2D*sigma)*h*k/D - exp(v2D*a)*(rootn2+h2)*cos(rootnL) ) / (h/root_n*(rootn2+v2D*v2D)); } @@ -138,62 +194,184 @@ GreensFunction1DRadAbs::Bn (Real root_n) const // appearing in the survival prob. and prop. function. // // Also here the root is the one refering to the interval of length L. -Real -GreensFunction1DRadAbs::Cn (Real root_n, Real t) +Real GreensFunction1DRadAbs::Cn (Real root_n, Real t) const { const Real D(this->getD()); - return std::exp(-D*root_n*root_n*t); + return exp(-D*root_n*root_n*t); } -// Calculates the probability of finding the particle inside the domain -// at time t, the survival probability. -Real -GreensFunction1DRadAbs::p_survival (Real t) const + +Real GreensFunction1DRadAbs::p_survival(Real t) const +{ + RealVector table; + return p_survival_table(t, table); +} + + +/* Calculates survival probability using a table. + Switchbox for which greensfunction to use. */ +Real GreensFunction1DRadAbs::p_survival_table(Real t, RealVector& psurvTable) const { THROW_UNLESS( std::invalid_argument, t >= 0.0 ); - - const Real D(this->getD()); - const Real v(this->getv()); - const Real vexpo(-v*v*t/4.0/D - v*r0/2.0/D); + + Real p; + + const Real a( geta() ); + const Real sigma( getsigma() ); + const Real L(a - sigma ); + const Real r0( getr0() ); + const Real D( getD() ); + const Real v( getv() ); + + if ( fabs(a-r0) < L*EPSILON || L < 0.0 ) + { + // The survival probability of a zero domain is zero + return 0.0; + } if (t == 0.0 || (D == 0.0 && v == 0.0) ) { - // if there was no time or no movement the particle was always - // in the domain - return 1.0; + //particle can't escape. + return 1.0; } + /* First check if we need full solution. + Else we use approximation. */ + const Real distToa( a - r0 ); + const Real distTos( r0 - sigma ); + const Real maxDist( CUTOFF_H * ( sqrt(2.0 * D * t) + fabs(v * t) ) ); - Real root_n; - Real sum = 0, term = 0, term_prev = 0; - int n = 1; + if( distToa > maxDist ) //Absorbing boundary 'not in sight'. + { + if( distTos > maxDist ) //Radiation boundary 'not in sight'. + return 1.0; //No prob. outflux. + else + return XS30(t, distTos, getk(), D, v); //Only radiation BCn. + } + else + { + if( distTos > maxDist ) + return XS10(t, distToa, D, -v); //Only absorbing BCn. + } - do + const uint maxi( guess_maxi(t) ); + + if( maxi >= MAX_TERMS ) + log_.warn("drawT: maxi was cut to MAX_TERMS for t = %.16g", t); + + if ( psurvTable.size() < maxi ) { - root_n = this->root_n(n); - term_prev = term; - term = this->Cn(root_n, t) * this->An(root_n) * this->Bn(root_n); - sum += term; - n++; + calculate_n_roots( maxi ); + createPsurvTable( psurvTable ); } - while ( fabs(term/sum) > EPSILON || - fabs(term_prev/sum) > EPSILON || - n <= MIN_TERMS); - return 2.0*exp(vexpo)*sum; + p = funcSum_all(boost::bind(&GreensFunction1DRadAbs::p_survival_i, + this, _1, t, psurvTable), + maxi); + + if( v == 0.0 ) + { + p *= 2.0; + } + else + { + const Real vexpo(-v*v*t/4.0/D - v*r0/2.0/D); + p *= 2.0 * exp( vexpo ); + } + + return p; } -// Calculates the probability density of finding the particle at location r -// at time t. -Real -GreensFunction1DRadAbs::prob_r (Real r, Real t) -const +/* Calculates the i'th term of the p_survival sum */ +Real GreensFunction1DRadAbs::p_survival_i( uint i, + Real const& t, + RealVector const& table) const +{ + return exp( - getD() * t * gsl_pow_2( get_root( i ) ) ) * table[ i ]; +} + + +/* Calculates the part of the i'th term of p_surv not dependent on t, with drift */ +Real GreensFunction1DRadAbs::p_survival_table_i_v( uint const& i ) const +{ + const Real sigma( getsigma() ); + const Real L( geta() - sigma ); + const Real r0( getr0() ); + const Real D( getD() ); + const Real v( getv() ); + const Real h( (getk() + v / 2.0) / D ); + + const Real v2D(v/2.0/D); + const Real exp_av2D(exp(a*v2D)); + const Real exp_sigmav2D(exp(sigma*v2D)); + + const Real root_n( get_root( i ) ); + const Real root_n2 = root_n * root_n; + const Real root_n_r0_s = root_n * (r0-sigma); + const Real root_n_L = root_n * L; + const Real h_root_n = h / root_n; + + return ( h * sin( root_n_r0_s ) + root_n * cos( root_n_r0_s ) ) / + ( L * ( root_n2 + h * h) + h ) * ( exp_sigmav2D * h * k / D - + exp_av2D * ( root_n2 + h * h )* + cos( root_n_L ) ) / + ( h_root_n * (root_n2 + v2D * v2D) ); +} + + +/* Calculates the part of the i'th term of p_surv not dependent on t, without drift */ +Real GreensFunction1DRadAbs::p_survival_table_i_nov( uint const& i ) const +{ + const Real sigma( getsigma() ); + const Real L( geta() - sigma ); + const Real r0( getr0() ); + const Real h( getk()/getD() ); + + const Real root_n ( get_root( i ) ); + const Real root_n2( root_n * root_n ); + const Real root_n_r0_s( root_n * (r0-sigma) ); + const Real root_n_L( root_n * L ); + const Real h_root_n( h / root_n ); + + return (h*sin(root_n_r0_s) + root_n*cos(root_n_r0_s)) + / (L*(root_n2+h*h)+h) * ( h_root_n + sin(root_n_L) + - h_root_n*cos(root_n_L) ); +} + + +/* Fills table with terms in the p_survival sum which don't depend on t */ +void GreensFunction1DRadAbs::createPsurvTable( RealVector& table) const +{ + const uint root_nbr( rootList_size() ); + uint i( table.size() ); + + if( getv() == 0.0 ) + { + while( i < root_nbr ) + { + table.push_back( p_survival_table_i_nov( i++ ) ); + } + } + else + { + while( i < root_nbr ) + { + table.push_back( p_survival_table_i_v( i++ ) ); + } + } +} + + +/* Calculates the probability density of finding the particle at location r + at time t. */ +Real GreensFunction1DRadAbs::prob_r (Real r, Real t) const { THROW_UNLESS( std::invalid_argument, t >= 0.0 ); - THROW_UNLESS( std::invalid_argument, (r-sigma) >= 0.0 && r <= a && (r0 - sigma) >= 0.0 && r0<=a ); + THROW_UNLESS( std::invalid_argument, (r-sigma) >= 0.0 && r <= a + && (r0 - sigma) >= 0.0 && r0<=a ); const Real sigma(this->getsigma()); const Real a(this->geta()); @@ -208,65 +386,67 @@ const // if there was no time change or zero diffusivity => no movement if (t == 0 || D == 0) { - // the probability density function is a delta function - if (r == r0) - { - return INFINITY; - } - else - { - return 0.0; - } + // the probability density function is a delta function + if (r == r0) + { + return INFINITY; + } + else + { + return 0.0; + } } // if r is at the absorbing boundary if ( fabs(a-r) < EPSILON*L ) { - return 0.0; + return 0.0; } Real root_n, root_n_r_s; Real sum = 0, term = 0, prev_term = 0; - int n=1; + const uint maxi( guess_maxi( t ) ); + calculate_n_roots( maxi ); + + uint n = 0; do { - if ( n >= MAX_TERMS ) - { - std::cerr << "Too many terms needed for GF1DRad::prob_r. N: " - << n << std::endl; - break; - } + if ( n >= MAX_TERMS ) + { + log_.warn("Too many terms needed for prob_r. N: %5u", n); + break; + } - root_n = this->root_n(n); - root_n_r_s = root_n*(r-sigma); + root_n = this->get_root(n); + root_n_r_s = root_n*(r-sigma); - prev_term = term; - term = Cn(root_n, t) * An(root_n) * (h*sin(root_n_r_s) + root_n*cos(root_n_r_s)); - sum += term; + prev_term = term; + term = Cn(root_n, t) * An(root_n) * (h*sin(root_n_r_s) + root_n*cos(root_n_r_s)); + sum += term; - n++; + n++; } while (fabs(term/sum) > EPSILON*PDENS_TYPICAL || - fabs(prev_term/sum) > EPSILON*PDENS_TYPICAL || - n <= MIN_TERMS ); + fabs(prev_term/sum) > EPSILON*PDENS_TYPICAL || + n < MIN_TERMS ); return 2.0*exp(vexpo)*sum; } -// Calculates the probability density of finding the particle at location z at -// timepoint t, given that the particle is still in the domain. -Real -GreensFunction1DRadAbs::calcpcum (Real r, Real t) const + +/* Calculates the probability density of finding the particle at location z at + timepoint t, given that the particle is still in the domain. */ +Real GreensFunction1DRadAbs::calcpcum (Real r, Real t) const { return prob_r(r, t)/p_survival(t); } -// Calculates the total probability flux leaving the domain at time t -// This is simply the negative of the time derivative of the survival prob. -// at time t [-dS(t')/dt' for t'=t]. -Real -GreensFunction1DRadAbs::flux_tot (Real t) const + +/* Calculates the total probability flux leaving the domain at time t + This is simply the negative of the time derivative of the survival prob. + at time t [-dS(t')/dt' for t'=t]. */ +Real GreensFunction1DRadAbs::flux_tot (Real t) const { Real root_n; const Real D(this->getD()); @@ -276,51 +456,53 @@ GreensFunction1DRadAbs::flux_tot (Real t) const const Real D2 = D*D; const Real v2Dv2D = v*v/4.0/D2; double sum = 0, term = 0, prev_term = 0; - int n=1; + const uint maxi( guess_maxi( t ) ); + calculate_n_roots( maxi ); + + uint n = 0; do { - if ( n >= MAX_TERMS ) - { - std::cerr << "Too many terms needed for GF1DRad::flux_tot. N: " - << n << std::endl; - break; - } - - root_n = this->root_n(n); - prev_term = term; - term = (root_n * root_n + v2Dv2D) * Cn(root_n, t) * An(root_n) * Bn(root_n); - n++; - sum += term; + if ( n >= MAX_TERMS ) + { + log_.warn("Too many terms needed for flux_tot. N: %5u", n ); + break; + } + + root_n = this->get_root(n); + prev_term = term; + term = (root_n * root_n + v2Dv2D) * Cn(root_n, t) * An(root_n) * Bn(root_n); + n++; + sum += term; } while (fabs(term/sum) > EPSILON*PDENS_TYPICAL || - fabs(prev_term/sum) > EPSILON*PDENS_TYPICAL || - n <= MIN_TERMS ); + fabs(prev_term/sum) > EPSILON*PDENS_TYPICAL || + n < MIN_TERMS ); return 2.0*D*exp(vexpo)*sum; } -// Calculates the probability flux leaving the domain through the radiative -// boundary at time t -Real -GreensFunction1DRadAbs::flux_rad (Real t) const + +/* Calculates the probability flux leaving the domain through the radiative + boundary at time t */ +Real GreensFunction1DRadAbs::flux_rad (Real t) const { return this->getk() * prob_r(this->getsigma(), t); } -// Calculates the flux leaving the domain through the radiative boundary as a -// fraction of the total flux. This is the probability that the particle left -// the domain through the radiative boundary instead of the absorbing -// boundary. -Real -GreensFunction1DRadAbs::fluxRatioRadTot (Real t) const + +/* Calculates the flux leaving the domain through the radiative boundary as a + fraction of the total flux. This is the probability that the particle left + the domain through the radiative boundary instead of the absorbing + boundary. */ +Real GreensFunction1DRadAbs::fluxRatioRadTot (Real t) const { return flux_rad(t)/flux_tot(t); } -// Determine which event has occured, an escape or a reaction. Based on the -// fluxes through the boundaries at the given time. Beware: if t is not a -// first passage time you still get an answer! +/* Determine which event has occured, an escape or a reaction. Based on the + fluxes through the boundaries at the given time. Beware: if t is not a + first passage time you still get an answer! */ GreensFunction1DRadAbs::EventKind GreensFunction1DRadAbs::drawEventType( Real rnd, Real t ) const @@ -330,14 +512,32 @@ const // if t=0 nothing has happened => no event const Real a(this->geta()); - const Real L(this->geta()-this->getsigma()); + const Real sigma( this->getsigma() ); + const Real L(this->geta()-sigma); const Real r0(this->getr0()); // if the radiative boundary is impermeable (k==0) or // the particle is at the absorbing boundary (at a) => IV_ESCAPE event if ( k == 0 || fabs(a-r0) < EPSILON*L ) { - return IV_ESCAPE; + return IV_ESCAPE; + } + + /* First check if we need to compare flux ratio's. + If only one boundary is 'visible' to the particle, use this boudnary as escape.*/ + const Real distToa( a - r0 ); + const Real distTos( r0 - sigma ); + const Real maxDist( CUTOFF_H * ( sqrt(2.0 * D * t) + fabs(v * t) ) ); + + if( distToa > maxDist ) //Absorbing boundary 'not in sight'. + { + if( distTos < maxDist ) //Only radiation boundary 'in sight'. + return IV_REACTION; + } + else + { + if( distTos > maxDist ) //Only absorbing boundary 'in sight'. + return IV_ESCAPE; } // Else the event is sampled from the flux ratio @@ -345,56 +545,27 @@ const if (rnd > fluxratio ) { - return IV_ESCAPE; + return IV_ESCAPE; } else { - return IV_REACTION; + return IV_REACTION; } } -// This function is needed to cast the math. form of the function -// into the form needed by the GSL root solver. -double -GreensFunction1DRadAbs::drawT_f (double t, void *p) + +/* This function is needed to cast the math. form of the function + into the form needed by the GSL root solver. */ +Real GreensFunction1DRadAbs::drawT_f (double t, void *p) { - // casts p to type 'struct drawT_params *' struct drawT_params *params = (struct drawT_params *)p; - Real Xn, exponent; - Real prefactor = params->prefactor; - int terms = params->terms; - - Real sum = 0, term = 0, prev_term = 0; - int n=0; - do - { - if ( n >= terms ) - { - std::cerr << "Too many terms needed for GF1DRad::DrawTime. N: " - << n << std::endl; - break; - } - prev_term = term; - - Xn = params->Xn[n]; - exponent = params->exponent[n]; - term = Xn * exp(exponent * t); - sum += term; - n++; - } - while (fabs(term/sum) > EPSILON*1.0 || - fabs(prev_term/sum) > EPSILON*1.0 || - n <= MIN_TERMS ); - - // find the intersection with the random number - return 1.0 - prefactor*sum - params->rnd; + return params->rnd - params->gf->p_survival_table( t, params->psurvTable ); } -// Draws the first passage time from the survival probability, -// using an assistance function drawT_f that casts the math. function -// into the form needed by the GSL root solver. -Real -GreensFunction1DRadAbs::drawTime (Real rnd) const +/* Draws the first passage time from the survival probability, + using an assistance function drawT_f that casts the math. function + into the form needed by the GSL root solver. */ +Real GreensFunction1DRadAbs::drawTime (Real rnd) const { THROW_UNLESS( std::invalid_argument, 0.0 <= rnd && rnd < 1.0 ); @@ -404,149 +575,108 @@ GreensFunction1DRadAbs::drawTime (Real rnd) const const Real r0(this->getr0()); const Real k(this->getk()); const Real D(this->getD()); - const Real v(this->getv()); - const Real h((this->getk()+this->getv()/2.0)/this->getD()); - if ( D == 0.0 || L == INFINITY ) { - return INFINITY; + return INFINITY; } - if ( rnd <= EPSILON || L < 0.0 || fabs(a-r0) < EPSILON*L ) + if ( rnd > 1 - EPSILON || L < 0.0 || fabs(a-r0) < EPSILON*L ) { - return 0.0; + return 0.0; } - const Real v2D(v/2.0/D); - const Real exp_av2D(exp(a*v2D)); - const Real exp_sigmav2D(exp(sigma*v2D)); - // exponent of the prefactor present in case of v<>0; has to be split because it has a t-dep. and t-indep. part - const Real vexpo_t(-v*v/4.0/D); - const Real vexpo_pref(-v*r0/2.0/D); + /* Find a good interval to determine the first passage time. */ + Real t_guess; - // the structure to store the numbers to calculate the numbers for 1-S - struct drawT_params parameters; - // some temporary variables - double root_n = 0; - double root_n2, root_n_r0_s, root_n_L, h_root_n; - double Xn, exponent, prefactor; - - - // produce the coefficients and the terms in the exponent and put them - // in the params structure. This is not very efficient at this point, - // coefficients should be calculated on demand->TODO - for (int n=0; nroot_n(n+1); // get the n-th root of tan(root*a)=root/-h (Note: root numbering starts at n=1) - - root_n2 = root_n * root_n; - root_n_r0_s = root_n * (r0-sigma); - root_n_L = root_n * L; - h_root_n = h / root_n; - - if(v==0) Xn = (h*sin(root_n_r0_s) + root_n*cos(root_n_r0_s)) / (L*(root_n2+h*h)+h) - * ( h_root_n + sin(root_n_L) - h_root_n*cos(root_n_L) ); - else Xn = (h*sin(root_n_r0_s) + root_n*cos(root_n_r0_s)) / (L*(root_n2+h*h)+h) - * (exp_sigmav2D*h*k/D - exp_av2D*(root_n2+h*h)*cos(root_n_L)) / (h_root_n * (root_n2 + v2D*v2D)); - - exponent = -D*root_n2 + vexpo_t; - - // store the coefficients in the structure - parameters.Xn[n] = Xn; - // also store the values for the exponent - parameters.exponent[n] = exponent; - } - - // the prefactor of the sum is also different in case of drift<>0 : - if(v==0) prefactor = 2.0; - else prefactor = 2.0*exp(vexpo_pref); - parameters.prefactor = prefactor; - - // store the random number for the probability - parameters.rnd = rnd; - // store the number of terms used - parameters.terms = MAX_TERMS; - parameters.tscale = this->t_scale; + if( k != 0.0 ) + { + const Real t_Abs( gsl_pow_2( a - r0 ) / D ); + const Real t_Rad( D / (k * k) + gsl_pow_2( r0 - sigma ) / D ); - // Define the function for the rootfinder - gsl_function F; - F.function = &GreensFunction1DRadAbs::drawT_f; - F.params = ¶meters; + t_guess = std::min(t_Abs, t_Rad ); + } + else + { + t_guess = gsl_pow_2( a - r0 ) / D; + } + t_guess *= .1; - // Find a good interval to determine the first passage time in - // get the distance to absorbing boundary (disregard rad BC) - const Real dist(fabs(a-r0)); - //const Real dist( std::min(r0, a-r0)); // for test purposes - // construct a guess: MSD = sqrt (2*d*D*t) - Real t_guess( dist * dist / ( 2.0*D ) ); // A different guess has to be made in case of nonzero drift to account for the displacement due to it // TODO: This does not work properly in this case yet, but we don't know why... // When drifting towards the closest boundary //if( (r0 >= a/2.0 && v > 0.0) || (r0 <= a/2.0 && v < 0.0) ) t_guess = sqrt(D*D/(v*v*v*v)+dist*dist/(v*v)) - D/(v*v); // When drifting away from the closest boundary - //if( ( r0 < a/2.0 && v > 0.0) || ( r0 > a/2.0 && v < 0.0) ) t_guess = D/(v*v) - sqrt(D*D/(v*v*v*v)-dist*dist/(v*v)); + //if( ( r0 < a/2.0 && v > 0.0) || ( r0 > a/2.0 && v < 0.0) ) t_guess = D/(v*v) - sqrt(D*D/(v*v*v*v)-dist*dist/(v*v)); + + /* Set params structure. */ + RealVector exponent_table; + RealVector psurvTable; + struct drawT_params parameters = {this, psurvTable, rnd}; + /* Define the function for the rootfinder. */ + gsl_function F; + F.function = &GreensFunction1DRadAbs::drawT_f; + F.params = ¶meters; + Real value( GSL_FN_EVAL( &F, t_guess ) ); Real low( t_guess ); Real high( t_guess ); - // scale the interval around the guess such that the function straddles if( value < 0.0 ) { - // if the guess was too low - do - { - // keep increasing the upper boundary until the - // function straddles - high *= 10; - value = GSL_FN_EVAL( &F, high ); - - if( fabs( high ) >= t_guess * 1e6 ) - { - std::cerr << "GF1DRad: Couldn't adjust high. F(" - << high << ") = " << value << std::endl; - throw std::exception(); - } - } - while ( value <= 0.0 ); + // if the guess was too low + do + { + if( fabs( high ) >= t_guess * 1e10 ) + { + log_.error("drawTime: couldn't adjust high. F( %.16g ) = %.16g" + , high, value); + throw std::exception(); + } + // keep increasing the upper boundary until the + // function straddles + high *= 10; + value = GSL_FN_EVAL( &F, high ); + } + while ( value <= 0.0 ); } else { // if the guess was too high // initialize with 2 so the test below survives the first // iteration - Real value_prev( 2 ); - do - { - if( fabs( low ) <= t_guess * 1e-6 || - fabs(value-value_prev) < EPSILON*1.0 ) - { - std::cerr << "GF1DRad: Couldn't adjust low. F(" << low << ") = " - << value << " t_guess: " << t_guess << " diff: " - << (value - value_prev) << " value: " << value - << " value_prev: " << value_prev << " rnd: " - << rnd << std::endl; - return low; - } - value_prev = value; - // keep decreasing the lower boundary until the function straddles - low *= 0.1; - // get the accompanying value - value = GSL_FN_EVAL( &F, low ); - } - while ( value >= 0.0 ); - } - - // find the intersection on the y-axis between the random number and - // the function - - // define a new solver type brent + Real value_prev( 2 ); + do + { + if( fabs( low ) <= t_guess * 1e-10 || + fabs(value-value_prev) < EPSILON*1.0 ) + { + log_.warn("drawTime: couldn't adjust low. F( %.16g ) = %.16g" + , low, value); + /* + std::cerr << "GF1DRad::drawTime Couldn't adjust low. F(" << low << ") = " + << value << " t_guess: " << t_guess << " diff: " + << (value - value_prev) << " value: " << value + << " value_prev: " << value_prev << " rnd: " + << rnd << std::endl; + */ + return low; + } + value_prev = value; + // keep decreasing the lower boundary until the function straddles + low *= 0.1; + // get the accompanying value + value = GSL_FN_EVAL( &F, low ); + } + while ( value >= 0.0 ); + } + + /* define a new solver type brent */ const gsl_root_fsolver_type* solverType( gsl_root_fsolver_brent ); - // make a new solver instance - // TODO: incl typecast? + /* make a new solver instance */ gsl_root_fsolver* solver( gsl_root_fsolver_alloc( solverType ) ); const Real t( findRoot( F, solver, low, high, t_scale*EPSILON, EPSILON, "GreensFunction1DRadAbs::drawTime" ) ); @@ -555,130 +685,178 @@ GreensFunction1DRadAbs::drawTime (Real rnd) const return t; } -double -GreensFunction1DRadAbs::drawR_f (double z, void *p) +/* Returns c.d.f. for drawR */ +Real GreensFunction1DRadAbs::p_int_r_table(Real const& r, + Real const& t, + RealVector& table) const { - // casts p to type 'struct drawR_params *' - struct drawR_params *params = (struct drawR_params *)p; - Real v2D = params->H[0]; // = v2D = v/(2D) - Real costerm = params->H[1]; // = k/D - Real sinterm = params->H[2]; // = h*v2D - Real sigma = params->H[3]; // = sigma - int terms = params->terms; - - Real expsigma(exp(sigma*v2D)); - Real zs(z-sigma); - - Real sum = 0, term = 0, prev_term = 0; - Real root_n, S_Cn_root_n; - - int n = 0; - do + const Real a( geta() ); + const Real sigma( getsigma() ); + const Real L( a - sigma ); + const Real r0( getr0() ); + const Real D( getD() ); + const Real k( getk() ); + const Real v( getv() ); + + /* If not all boundaries are 'visible' to the particle, + use approximation. */ + const Real distToa( a - r0 ); + const Real distTos( r0 - sigma ); + const Real maxDist( CUTOFF_H * ( sqrt(2.0 * D * t) + fabs(v * t) ) ); + + //TODO: include XI30 (c.d.f) with drift. + if( distToa > maxDist ) //Absorbing boundary 'not in sight'. + { + if( distTos > maxDist ) //Radiation boundary 'not in sight'. + return XI00(r, t, r0, D, v); //free particle. + else + { + if( k != 0.0 && v == 0.0 ) + //Only radiation BCn. + return XI30(r - sigma, t, distTos, getk(), D, 0.0); + else if( k == 0.0 && v == 0.0 ) + //Only reflecting BCn. + return XI20(r - sigma, t, distTos, D, 0.0); + } + } + else { - if ( n >= terms ) - { - std::cerr << "GF1DRad: Too many terms needed for DrawR. N: " - << n << std::endl; - break; - } - prev_term = term; + if( distTos > maxDist ) + //Only absorbing BCn. + return XI10(a - r, t, distToa, D, -v); + } + + Real p; + const uint maxi( guess_maxi( t ) ); - S_Cn_root_n = params->S_Cn_root_n[n]; - root_n = params->root_n[n]; - term = S_Cn_root_n * ( expsigma*costerm - exp(v2D*z)*( costerm*cos(root_n*zs) - (root_n+sinterm/root_n)*sin(root_n*zs) )); + const Real vexpo(-v*v*t/4.0/D - v*r0/2.0/D); + const Real prefac( 2.0*exp(vexpo) ); - sum += term; - n++; + if( maxi >= MAX_TERMS ) + { + log_.warn("drawR: maxi was cut to MAX_TERMS for t = %.16g", t); + std::cerr << dump(); + std::cerr << "L: " << L << " r0: " << r0 - sigma << std::endl; + } + + if( table.size() < maxi ) + { + calculate_n_roots( maxi ); + create_p_int_r_Table(t, table); } - while (fabs(term/sum) > EPSILON*1.0 || - fabs(prev_term/sum) > EPSILON*1.0 || - n <= MIN_TERMS ); - // Find the intersection with the random number - return sum - params->rnd; + p = funcSum(boost::bind(&GreensFunction1DRadAbs::p_int_r_i, + this, _1, r, t, table), + MAX_TERMS); + + return prefac * p; } -Real -GreensFunction1DRadAbs::drawR (Real rnd, Real t) const +Real GreensFunction1DRadAbs::p_int_r_i(uint i, + Real const& r, + Real const& t, + RealVector& table) const { - THROW_UNLESS( std::invalid_argument, 0.0 <= rnd && rnd < 1.0 ); - THROW_UNLESS( std::invalid_argument, t >= 0.0 ); + const Real sigma( getsigma() ); + const Real D( getD() ); + const Real k( getk() ); + const Real v( getv() ); + const Real h(( k + v/2.0)/ D ); + const Real v2D( v/(2*D) ); + + const Real costerm( k / D ); + const Real sinterm( h * v2D ); + + const Real expsigma(exp(sigma*v2D)); + const Real zs(r - sigma); + + Real root_n( get_root( i ) ); - const Real sigma(this->getsigma()); - const Real a(this->geta()); - const Real L(this->geta()-this->getsigma()); - const Real r0(this->getr0()); - const Real D(this->getD()); - const Real v(this->getv()); - const Real k(this->getk()); - const Real h((this->getk()+this->getv()/2.0)/this->getD()); + Real term( ( expsigma*costerm - exp(v2D*r)* + ( costerm*cos(root_n*zs) - + (root_n+sinterm/root_n)*sin(root_n*zs) )) ); + return get_p_int_r_Table_i(i, t, table) * term; +} - if (t==0.0 || (D==0.0 && v==0.0) ) - { - // the trivial case - //return r0*this->l_scale; // renormalized version, discontinued - return r0; - } - if ( a<0.0 ) - { - // if the domain had zero size - return 0.0; - } +/* Fills table for p_int_r of factors independent of r. */ +void GreensFunction1DRadAbs::create_p_int_r_Table( Real const& t, + RealVector& table ) const +{ + const uint root_nmbr( rootList_size() ); + uint n( table.size() ); + + const Real sigma( getsigma() ); + const Real L( geta() - getsigma() ); + const Real r0( getr0() ); + const Real D( getD() ); + const Real v( getv() ); + const Real h(( k + v/2.0)/ D ); - // the structure to store the numbers to calculate the numbers for 1-S - struct drawR_params parameters; - double root_n = 0; - double S_Cn_root_n; - double root_n2, root_n_r0_s; - const Real vexpo(-v*v*t/4.0/D - v*r0/2.0/D); // exponent of the drift-prefactor, same as in survival prob. const Real v2D(v/2.0/D); const Real v2Dv2D(v2D*v2D); - const Real S = 2.0*exp(vexpo)/p_survival(t); // This is a prefactor to every term, so it also contains - // the exponential drift-prefactor. + Real term; + Real root_n2, root_n_r0_s, root_n; - // produce the coefficients and the terms in the exponent and put them - // in the params structure - for (int n=0; nroot_n(n+1); // get the n-th root of tan(alfa*a)=alfa/-k - root_n2 = root_n * root_n; - root_n_r0_s = root_n * (r0-sigma); - S_Cn_root_n = S * exp(-D*root_n2*t) - * (root_n*cos(root_n_r0_s) + h*sin(root_n_r0_s)) / (L*(root_n2 + h*h) + h) - * root_n / (root_n2 + v2Dv2D); + root_n = get_root(n); + root_n2 = root_n * root_n; + root_n_r0_s = root_n * (r0-sigma); + + term = exp(-D*root_n2*t) + * (root_n*cos(root_n_r0_s) + h*sin(root_n_r0_s)) / (L*(root_n2 + h*h) + h) + * root_n / (root_n2 + v2Dv2D); - // store the coefficients in the structure - parameters.root_n[n] = root_n; - // also store the values for the exponent - parameters.S_Cn_root_n[n] = S_Cn_root_n; + table.push_back( term ); + n++; } +} + +/* Function for GSL rootfinder of drawR. */ +Real GreensFunction1DRadAbs::drawR_f(Real r, void *p) +{ + struct drawR_params *params = (struct drawR_params *)p; + return params->gf->p_int_r_table(r, params->t, params->table) + - params->rnd; +} + +/* Return new position */ +Real GreensFunction1DRadAbs::drawR (Real rnd, Real t) const +{ + THROW_UNLESS( std::invalid_argument, 0.0 <= rnd && rnd < 1.0 ); + THROW_UNLESS( std::invalid_argument, t >= 0.0 ); - // store the random number for the probability - parameters.rnd = rnd; - // store the number of terms used - parameters.terms = MAX_TERMS; + const Real sigma( getsigma() ); + const Real a( geta() ); + const Real L( a - sigma ); + const Real r0( getr0() ); + const Real D( getD() ); + const Real v( getv() ); - // also store constant prefactors that appear in the calculation of the - // r-dependent terms - parameters.H[0] = v2D; // appears together with z in one of the prefactors - parameters.H[1] = k/D; // further constant terms of the cosine prefactor - parameters.H[2] = h*v2D; // further constant terms of the sine prefactor - parameters.H[3] = sigma; + if (t == 0.0 || (D == 0.0 && v == 0.0) ) + { + return r0; + } + if ( a < 0.0 ) + { + return 0.0; + } + // the structure to store the numbers to calculate the numbers for 1-S + RealVector pintTable; + struct drawR_params parameters = {this, t, pintTable, rnd * p_survival( t )}; - // find the intersection on the y-axis between the random number and - // the function + // define gsl function for rootfinder gsl_function F; F.function = &GreensFunction1DRadAbs::drawR_f; F.params = ¶meters; // define a new solver type brent const gsl_root_fsolver_type* solverType( gsl_root_fsolver_brent ); - // make a new solver instance - // TODO: incl typecast? gsl_root_fsolver* solver( gsl_root_fsolver_alloc( solverType ) ); + Real r( findRoot( F, solver, sigma, a, EPSILON*L, EPSILON, "GreensFunction1DRadAbs::drawR" ) ); @@ -691,7 +869,10 @@ std::string GreensFunction1DRadAbs::dump() const std::ostringstream ss; ss << "D = " << this->getD() << ", sigma = " << this->getsigma() << ", a = " << this->geta() << + ", r0 = " << this->getr0() << ", k = " << this->getk() << std::endl; return ss.str(); } +Logger& GreensFunction1DRadAbs::log_( + Logger::get_logger("GreensFunction1DRadAbs")); diff --git a/GreensFunction1DRadAbs.hpp b/GreensFunction1DRadAbs.hpp index f7a8575f..60920ef3 100644 --- a/GreensFunction1DRadAbs.hpp +++ b/GreensFunction1DRadAbs.hpp @@ -1,5 +1,5 @@ -#if !defined( __FIRSTPASSAGEGREENSFUNCTION1DRAD_HPP ) -#define __FIRSTPASSAGEGREENSFUNCTION1DRAD_HPP +#if !defined( __GREENSFUNCTION1DRADABS_HPP ) +#define __GREENSFUNCTION1DRADABS_HPP #include #include @@ -19,12 +19,18 @@ #include #include "findRoot.hpp" +#include "funcSum.hpp" +#include "freeFunctions.hpp" #include "Defs.hpp" -#include "OldDefs.hpp" // TODO: this must be removed at some point! #include "GreensFunction.hpp" +#include "Logger.hpp" class GreensFunction1DRadAbs: public GreensFunction { +public: + typedef std::vector RealVector; + typedef unsigned int uint; + private: // This is a typical length scale of the system, may not be true! static const Real L_TYPICAL = 1E-8; @@ -35,23 +41,28 @@ class GreensFunction1DRadAbs: public GreensFunction // Is 1E3 a good measure for the probability density?! static const Real PDENS_TYPICAL = 1; // The maximum number of terms used in calculating the sum - static const int MAX_TERMS = 500; + static const uint MAX_TERMS = 500; // The minimum number of terms - static const int MIN_TERMS = 20; + static const uint MIN_TERMS = 20; + /* Cutoff distance: When H * sqrt(2Dt) < a - r0 OR ro - sigma + use free greensfunction instead of absorbing. */ + static const Real CUTOFF_H = 6.0; public: GreensFunction1DRadAbs(Real D, Real k, Real r0, Real sigma, Real a) : GreensFunction(D), v(0.0), k(k), r0(r0), sigma(sigma), a(a), l_scale(L_TYPICAL), t_scale(T_TYPICAL) { - // do nothing + //set first root. + calculate_n_roots( 1 ); } // The constructor is overloaded and can be called with or without drift v // copy constructor including drift variable v - GreensFunction1DRadAbs(Real D, Real k, Real v, Real r0, Real sigma, Real a) + GreensFunction1DRadAbs(Real D, Real v, Real k, Real r0, Real sigma, Real a) : GreensFunction(D), v(v), k(k), r0(r0), sigma(sigma), a(a), l_scale(L_TYPICAL), t_scale(T_TYPICAL) { - // do nothing + //set first root; + calculate_n_roots( 1 ); } ~GreensFunction1DRadAbs() @@ -83,12 +94,12 @@ class GreensFunction1DRadAbs: public GreensFunction Real geta() const { - return this->a; + return this->a; } Real getsigma() const { - return this->sigma; + return this->sigma; } void setr0(Real r0) @@ -111,17 +122,17 @@ class GreensFunction1DRadAbs: public GreensFunction Real getr0() const { - return r0; + return r0; } Real getk() const { - return this->k; + return this->k; } Real getv() const { - return this->v; + return this->v; } // Calculates the probability density of finding the particle at @@ -178,9 +189,6 @@ class GreensFunction1DRadAbs: public GreensFunction { return "GreensFunction1DRadAbs"; } - - // Calculates the roots of tan(a*x)=-xk/h - Real root_n(int n) const; private: @@ -192,39 +200,107 @@ class GreensFunction1DRadAbs: public GreensFunction struct tan_f_params { - Real a; - Real h; + Real a; + Real h; }; - static double tan_f (double x, void *p); - // this is the appropriate definition of the function in gsl - struct drawT_params { - double exponent[MAX_TERMS]; - double Xn[MAX_TERMS]; - double prefactor; - int terms; - // the timescale used for convergence - Real tscale; - // the random number associated with the time - double rnd; + GreensFunction1DRadAbs const* gf; + RealVector& psurvTable; + Real rnd; }; - static double drawT_f (double t, void *p); - - struct drawR_params + struct drawR_params { - double root_n[MAX_TERMS]; - double S_Cn_root_n[MAX_TERMS]; - // variables H: for additional terms appearing as multiplicative factors etc. - double H[5]; - int terms; - // the random number associated with the time - double rnd; + GreensFunction1DRadAbs const* gf; + const Real t; + RealVector table; + Real rnd; }; - static double drawR_f (double z, void *p); + + /* Functions managing the rootList */ + + /* return the rootList size */ + uint rootList_size() const + { + return rootList.size(); + } + + /* returns the last root. */ + Real get_last_root() const + { + return rootList.back(); + } + + /* ad a root to the rootList */ + void ad_to_rootList( Real const& root_i ) const + { + rootList.push_back( root_i ); + } + + /* remove n'th root from rootList */ + void remove_from_rootList(uint const& n) const + { + rootList.erase( rootList.begin() + n ); + } + + /* return the n + 1'th root */ + Real get_root( uint const& n ) const + { + if( n >= rootList.size() ) + calculate_n_roots( n+1 ); + + return rootList[ n ]; + } + + /* Fills the rootList with the first n roots. */ + void calculate_n_roots(uint const& n) const; + + /* Guess the number of terms needed for convergence, given t. */ + uint guess_maxi( Real const& t ) const; + + /* this is the appropriate definition of the function in gsl. */ + static Real tan_f (Real x, void *p); + + /* functions for drawTime / p_survival */ + + static Real drawT_f (Real t, void *p); + + Real p_survival_table( Real t, RealVector& psurvTable ) const; + + Real p_survival_i(uint i, Real const& t, RealVector const& table ) const; + + Real p_survival_table_i_v( uint const& i ) const; + + Real p_survival_table_i_nov( uint const& i ) const; + + void createPsurvTable( RealVector& table) const; + + + /* functions for drawR */ + + static Real drawR_f (Real z, void* p); + + Real p_int_r_table(Real const& r, Real const& t, RealVector& table) const; + + Real p_int_r_i(uint i, Real const& r, Real const& t, RealVector& table) const; + + void create_p_int_r_Table( Real const& t, RealVector& table ) const; + + Real get_p_int_r_Table_i( uint& i, Real const& t, RealVector& table) const + { + if( i >= table.size() ) + { + calculate_n_roots( i+1 ); + create_p_int_r_Table(t, table); + } + + return table[i]; + } + + /* Member variables */ // The diffusion constant and drift velocity Real v; @@ -238,5 +314,10 @@ class GreensFunction1DRadAbs: public GreensFunction Real l_scale; // This is the time scale of the system. Real t_scale; + + /* vector containing the roots 0f tan_f. */ + mutable RealVector rootList; + + static Logger& log_; }; -#endif // __FIRSTPASSAGEGREENSFUNCTION1DRAD_HPP +#endif // __GREENSFUNCTION1DRADABS_HPP diff --git a/GreensFunction2DAbsSym.cpp b/GreensFunction2DAbsSym.cpp new file mode 100644 index 00000000..a7289e30 --- /dev/null +++ b/GreensFunction2DAbsSym.cpp @@ -0,0 +1,307 @@ +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "findRoot.hpp" + +#include "GreensFunction2DAbsSym.hpp" + + + + +// an alternative form, which is not very convergent. +const Real +GreensFunction2DAbsSym::p_survival( const Real t ) const +{ + + const Real D( getD() ); + const Real a( geta() ); + const Real Dt( -D * t ); + + const Integer N( 100 ); // number of terms to use + Real sum( 0. ); + Real aAn (0); + Real An (0); + Real J1_aAn(0); + Real term(0); + + const Real threshold( CUTOFF ); // + + //std::cout << "p_survival_2D "; + //std::cout << "time: " << t << std::endl; + for( Integer n( 1 ); n <= N; ++n ) + { + aAn = gsl_sf_bessel_zero_J0(n); // gsl roots of J0(aAn) + An = aAn/a; + J1_aAn = gsl_sf_bessel_J1(aAn); + term = (exp(An*An*Dt))/(An*J1_aAn); + sum += term; + + //std::cout << n << " " << aAn << " " << J1_aAn << " " << term << " " << value << std::endl; + + if( fabs( term/sum ) < threshold ) + { + // normal exit. + //std::cout << n << std::endl; + break; + } + } + return (2.0/a) * sum; +} + + +const Real +GreensFunction2DAbsSym::p_int_r_free( const Real r, const Real t ) const +{ + + const Real D( getD() ); + const Real Dt( D * t ); + const Real sqrtDt( sqrt( Dt ) ); + const Real sqrtPI( sqrt( M_PI ) ); + + return erf( r / ( sqrtDt + sqrtDt ) ) + - r * exp( - r * r / ( 4.0 * Dt ) ) / ( sqrtPI * sqrtDt ); +} + + +const Real +GreensFunction2DAbsSym::p_int_r( const Real r, + const Real t ) const +{ + + const Real a( geta() ); + const Real D( getD() ); + const Real Dt( -D * t ); + Real J1_aAn, J1_rAn; + Real aAn, rAn, An; + Real term; + Real sum( 0.0 ); + int n(1); + +// const Real maxn( ( a / M_PI ) * sqrt( log( exp( DtPIsq_asq ) / CUTOFF ) / +// ( D * t ) ) ); + + const Integer N_MAX( 10000 ); + const Real threshold( CUTOFF ); + + do + { + aAn = gsl_sf_bessel_zero_J0(n); // gsl roots of J0(aAn) + An = aAn/a; + rAn = r*An; + J1_aAn = gsl_sf_bessel_J1(aAn); + J1_rAn = gsl_sf_bessel_J1(rAn); + term = (exp(An*An*Dt) * r * J1_rAn) / (An*J1_aAn*J1_aAn); + sum += term; + n++; + + //std::cout << n << " " << aAn << " " << J1_aAn << " " << term << " " << value << std::endl; + } + while (fabs( term/sum ) > threshold && n <= N_MAX); + + return (2.0/(a*a)) * sum; +} + + +const Real +GreensFunction2DAbsSym::p_survival_F( const Real t, + const p_survival_params* params ) +{ + + const GreensFunction2DAbsSym* const gf( params->gf ); + const Real rnd( params->rnd ); + + return 1 - gf->p_survival( t ) - rnd; +} + + +const Real +GreensFunction2DAbsSym::drawTime( const Real rnd ) const +{ + + THROW_UNLESS( std::invalid_argument, rnd < 1.0 && rnd >= 0.0 ); + + const Real a( geta() ); + + if( getD() == 0.0 || a == INFINITY ) + { + return INFINITY; + } + if( a == 0.0 ) + { + return 0.0; + } + + p_survival_params params = { this, rnd }; + + gsl_function F = + { + reinterpret_cast( &p_survival_F ), ¶ms + }; + + //for (Real t=0.0001; t<=1; t+=0.0001) + //{ std::cout << t << " " << GSL_FN_EVAL( &F, t) << std::endl; + //} + + // Find a good interval to determine the first passage time in + const Real t_guess( a * a / ( 4. * D ) ); // construct a guess: msd = sqrt (2*d*D*t) + Real value( GSL_FN_EVAL( &F, t_guess ) ); + Real low( t_guess ); + Real high( t_guess ); + + // scale the interval around the guess such that the function straddles + if( value < 0.0 ) // if the guess was too low + { + do + { high *= 10; // keep increasing the upper boundary until the function straddles + value = GSL_FN_EVAL( &F, high ); + + if( fabs( high ) >= t_guess * 1e6 ) + { + log_.warn("Couldn't adjust high. F(%.16g) = %.16g", high, value); + throw std::exception(); + } + } + while ( value <= 0.0 ); + } + else // if the guess was too high + { + Real value_prev( value ); + do + { low *= .1; // keep decreasing the lower boundary until the function straddles + value = GSL_FN_EVAL( &F, low ); // get the accompanying value + + if( fabs( low ) <= t_guess * 1e-6 || fabs( value - value_prev ) < CUTOFF ) + { + log_.warn("Couldn't adjust high. F(%.16g) = %.16g", low, value); + return low; + } + value_prev = value; + } + while ( value >= 0.0 ); + } + + // find the root + const gsl_root_fsolver_type* solverType( gsl_root_fsolver_brent ); // a new solver type brent + gsl_root_fsolver* solver( gsl_root_fsolver_alloc( solverType ) ); // make a new solver instance + const Real t( findRoot( F, solver, low, high, 1e-18, 1e-12, + "GreensFunction2DAbsSym::drawTime" ) ); + gsl_root_fsolver_free( solver ); + + return t; +} + + +const Real +GreensFunction2DAbsSym::p_r_free_F( const Real r, + const p_r_params* params ) +{ + + const GreensFunction2DAbsSym* const gf( params->gf ); + const Real t( params->t ); + const Real target( params->target ); + + return gf->p_int_r_free( r, t ) - target; +} + + +const Real +GreensFunction2DAbsSym::p_r_F( const Real r, + const p_r_params* params ) +{ + + const GreensFunction2DAbsSym* const gf( params->gf ); + const Real t( params->t ); + const Real target( params->target ); + + return gf->p_int_r( r, t ) - target; +} + + +const Real +GreensFunction2DAbsSym::drawR( const Real rnd, const Real t ) const +{ + + THROW_UNLESS( std::invalid_argument, rnd <= 1.0 && rnd >= 0.0 ); + THROW_UNLESS( std::invalid_argument, t >= 0.0 ); + + const Real a( geta() ); + const Real D( getD() ); + + if( a == 0.0 || t == 0.0 || D == 0.0 ) + { + return 0.0; + } + + //const Real thresholdDistance( this->CUTOFF_H * sqrt( 4.0 * D * t ) ); + + gsl_function F; + Real psurv; + +// if( a <= thresholdDistance ) // if the domain is not so big, the boundaries are felt +// { + psurv = p_survival( t ); + //psurv = p_int_r( a, t ); + //printf("dr %g %g\n",psurv, p_survival( t )); + //assert( fabs(psurv - p_int_r( a, t )) < psurv * 1e-8 ); + + assert( psurv > 0.0 ); + + F.function = reinterpret_cast( &p_r_F ); +/* } + else // if the domain is very big, just use the free solution + { + // p_int_r < p_int_r_free + if( p_int_r_free( a, t ) < rnd ) // if the particle is outside the domain? + { + std::cerr << "p_int_r_free( a, t ) < rnd, returning a." + << std::endl; + return a; + } + + psurv = 1.0; + F.function = reinterpret_cast( &p_r_free_F ); + } +*/ + const Real target( psurv * rnd ); + p_r_params params = { this, t, target }; + + F.params = ¶ms; + + + const Real low( 0.0 ); + const Real high( a ); + //const Real high( std::min( thresholdDistance, a ) ); + + const gsl_root_fsolver_type* solverType( gsl_root_fsolver_brent ); + gsl_root_fsolver* solver( gsl_root_fsolver_alloc( solverType ) ); + + const Real r( findRoot( F, solver, low, high, 1e-18, 1e-12, + "GreensFunction2DAbsSym::drawR" ) ); + + gsl_root_fsolver_free( solver ); + + return r; +} + + +const std::string GreensFunction2DAbsSym::dump() const +{ + std::ostringstream ss; + ss << "D = " << this->getD() << ", a = " << this->geta() << std::endl; + return ss.str(); +} + +Logger& GreensFunction2DAbsSym::log_( + Logger::get_logger("GreensFunction2DAbsSym")); diff --git a/GreensFunction2DAbsSym.hpp b/GreensFunction2DAbsSym.hpp new file mode 100644 index 00000000..ed799e7c --- /dev/null +++ b/GreensFunction2DAbsSym.hpp @@ -0,0 +1,98 @@ +#if !defined( __FIRSTPASSAGEGREENSFUNCTION2D_HPP ) +#define __FIRSTPASSAGEGREENSFUNCTION2D_HPP + +#include +#include + +#include "Defs.hpp" +#include "Logger.hpp" + +class GreensFunction2DAbsSym +{ + +public: + + GreensFunction2DAbsSym( const Real D, const Real a ) + : + D( D ), + a( a ) + { + ; + } + + virtual ~GreensFunction2DAbsSym() + { + ; + } + + const char* getName() const + { + return "GreensFunction2DAbsSym"; + } + + const Real getD() const + { + return this->D; + } + + const Real geta() const + { + return this->a; + } + + const Real p_survival( const Real t ) const; + + const Real drawTime( const Real rnd ) const; + + const Real drawR( const Real rnd, const Real t ) const; + + const Real p_int_r( const Real r, const Real t ) const; + const Real p_int_r_free( const Real r, const Real t ) const; + + const std::string dump() const; + +// private methods +private: + + struct p_survival_params + { + const GreensFunction2DAbsSym* const gf; + const Real rnd; + }; + + static const Real p_survival_F( const Real t, + const p_survival_params* params ); + + struct p_r_params + { + const GreensFunction2DAbsSym* const gf; + const Real t; + const Real target; + }; + + static const Real p_r_free_F( const Real r, + const p_r_params* params ); + + static const Real p_r_F( const Real r, + const p_r_params* params ); + +// private variables +private: + + static const Real CUTOFF = 1e-10; + + // H = 4.0: ~3e-5, 4.26: ~1e-6, 5.0: ~3e-7, 5.2: ~1e-7, + // 5.6: ~1e-8, 6.0: ~1e-9 + static const Real CUTOFF_H = 6.0; + + const Real D; + + const Real a; + + static Logger& log_; + +}; + + + +#endif // __PAIRGREENSFUNCTION2D_HPP diff --git a/GreensFunction2DRadAbs.cpp b/GreensFunction2DRadAbs.cpp new file mode 100644 index 00000000..8a4509f9 --- /dev/null +++ b/GreensFunction2DRadAbs.cpp @@ -0,0 +1,1734 @@ +// Greens function class for 2d Green's Function for 2d annulus with radial and +// axial dependence. Inner boundary is radiative (rad) (reaction event), outer +// boundary is absorbing (abs) (escape event). Different "draw" functions +// provide a way to draw certain values from the Green's Function, e.g. an +// escape angle theta ("drawTheta" function). +// +// Based upon code from Riken Institute. +// Written by Laurens Bossen, Adapted by Martijn Wehrens. FOM Institute AMOLF. + +//#define NDEBUG +//#define BOOST_DISABLE_ASSERTS + +#include "compat.h" + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "factorial.hpp" +#include "funcSum.hpp" +#include "findRoot.hpp" +#include "freeFunctions.hpp" +#include "CylindricalBesselGenerator.hpp" +#include "GreensFunction2DRadAbs.hpp" + +const Real GreensFunction2DRadAbs::MIN_T_FACTOR; +const unsigned int GreensFunction2DRadAbs::MAX_ORDER; +const unsigned int GreensFunction2DRadAbs::MAX_ALPHA_SEQ; + + +// This is the constructor +GreensFunction2DRadAbs:: +GreensFunction2DRadAbs( const Real D, + const Real kf, + const Real r0, + const Real Sigma, + const Real a ) + : + PairGreensFunction(D, kf, r0, Sigma), + h( kf / (D * 2.0 * M_PI * Sigma) ), + a( a ), + estimated_alpha_root_distance_(M_PI/(a-Sigma)) // observed convergence of + // distance roots f_alpha(). + // ^: Here parent "constructors" are specified that are executed, + // also a constructor initialization list can be (and is) specified. + // These variables will be set before the contents of the constructor are + // called. +{ + + const Real sigma(this->getSigma()); + + // Check wether input makes sense, outer boundary a should be > inner + // boundary sigma. + if (a < sigma) + { + throw std::invalid_argument((boost::format("GreensFunction2DRadAbs: a >= sigma : a=%.16g, sigma=%.16g") % a % sigma).str()); + } + + // Clear AlphaTables + GreensFunction2DRadAbs::clearAlphaTable(); + + +} + +GreensFunction2DRadAbs::~GreensFunction2DRadAbs() +{ + ; // do nothing +} + + +// +// Alpha-related methods +// +// Resets the alpha-tables +void GreensFunction2DRadAbs::clearAlphaTable() const +{ + + const Real estimated_alpha_root_distance(getestimated_alpha_root_distance_()); + + // Clears all vectors in the alphaTable + std::for_each( this->alphaTable.begin(), this->alphaTable.end(), + boost::mem_fn( &RealVector::clear ) ); + + // Sets all values of the alpha_x_scan_table_ to zero. + std::fill( this->alpha_x_scan_table_.begin(), + this->alpha_x_scan_table_.end(), + SCAN_START*estimated_alpha_root_distance); + // DEBUG TODO; actual number here might + // be important for functioning of Bessel functions. + + // Sets all values of the alpha_correctly_estimated_ table to zero. + std::fill( this->alpha_correctly_estimated_.begin(), + this->alpha_correctly_estimated_.end(), + 0 ); +} + + +// The method evaluates the equation for finding the alphas for given alpha. This +// is needed to find the alpha's at which the expression is zero -> alpha is the root. +const Real +GreensFunction2DRadAbs::f_alpha0( const Real alpha ) const +{ + + const Real a( this->geta() ); + const Real sigma( getSigma() ); + const Real h( this->geth() ); + const Real s_An( sigma * alpha ); + const Real a_An( a * alpha ); + // Needed? TODO +// const Real h_s( h * sigma); + + const Real J0_s_An (gsl_sf_bessel_J0(s_An)); + const Real J1_s_An (gsl_sf_bessel_J1(s_An)); + const Real J0_a_An (gsl_sf_bessel_J0(a_An)); + + const Real Y0_s_An (gsl_sf_bessel_Y0(s_An)); + const Real Y1_s_An (gsl_sf_bessel_Y1(s_An)); + const Real Y0_a_An (gsl_sf_bessel_Y0(a_An)); + +// const double rho1 ( ( (h_s * J0_s_An) + (s_An * J1_s_An) ) * Y0_a_An ); +// const double rho2 ( ( (h_s * Y0_s_An) + (s_An * Y1_s_An) ) * J0_a_An ); +// return (rho1 - rho2); + + // Sigma can be divided out, roots will remain same: + // (Note: actually double checked this). + const Real rho1 ( ( (h * J0_s_An) + (alpha * J1_s_An) ) * Y0_a_An ); + const Real rho2 ( ( (h * Y0_s_An) + (alpha * Y1_s_An) ) * J0_a_An ); + + return rho1 - rho2; +} + + +// Wraps function of which we need to find roots alpha. +// +// Params contains pointer to gf object (params.gf), where f_alpha is the member +// function of which we have to find the roots. This function is thus a mere +// wrapper of f_alpha. +const Real +GreensFunction2DRadAbs::f_alpha0_aux_F( const Real alpha, + const f_alpha0_aux_params* const params ) +{ + // create pointer to Green's Function object + const GreensFunction2DRadAbs* const gf( params->gf ); + + return gf->f_alpha0( alpha ); +} + + +// f_alpha() Calculates the value of the mathematical function f_alpha(). The +// roots (y=0) of this function are constants in the Green's Functions. +const Real GreensFunction2DRadAbs::f_alpha( const Real alpha, + const Integer n ) const +{ + + const Real a( this->geta() ); + const Real sigma( getSigma() ); + const Real h( this->geth() ); + const Real s_An( sigma * alpha ); + const Real a_An( a * alpha ); + const Real realn( static_cast( n ) ); + const Real h_sigma( h * sigma); + + const Real Jn_s_An (gsl_sf_bessel_Jn(n,s_An)); + const Real Jn1_s_An (gsl_sf_bessel_Jn(n+1,s_An)); + const Real Jn_a_An (gsl_sf_bessel_Jn(n,a_An)); + + const Real Yn_s_An (gsl_sf_bessel_Yn(n,s_An)); + const Real Yn1_s_An (gsl_sf_bessel_Yn(n+1,s_An)); + const Real Yn_a_An (gsl_sf_bessel_Yn(n,a_An)); + + const Real rho1 ( ( (h_sigma * Jn_s_An) + (s_An * Jn1_s_An) - realn*Jn_s_An ) * Yn_a_An ); + const Real rho2 ( ( (h_sigma * Yn_s_An) + (s_An * Yn1_s_An) - realn*Yn_s_An ) * Jn_a_An ); + return (rho1 - rho2); + +// Or..? +// const double rho1 ( ( ((h*sigma-realn) * Jn_s_An) + (s_An * Jn1_s_An) ) * Yn_a_An ); +// const double rho2 ( ( ((h*sigma-realn) * Yn_s_An) + (s_An * Yn1_s_An) ) * Jn_a_An ); +// return (rho1 - rho2); + +} + + +// Simply a wrapper for f_alpha(). +const Real +GreensFunction2DRadAbs::f_alpha_aux_F( const Real alpha, + const f_alpha_aux_params* const params ) +{ + // Params contains pointer to gf object (params.gf), which has f_alpha() as + // a member. + const GreensFunction2DRadAbs* const gf( params->gf ); + const Integer n( params->n ); + + return gf->f_alpha( alpha, n ); +} + + +// calculates the constant part of the i-th term for the survival probability +const Real +GreensFunction2DRadAbs::p_survival_i( const Real alpha) const +{ + + const Real a( geta() ); // get the needed parameters + const Real sigma( getSigma() ); + + const Real s_An (sigma*alpha); + const Real a_An (a*alpha); + const Real alpha_sq (alpha*alpha); + + // calculate all the required Bessel functions + const Real J1_sAn (gsl_sf_bessel_J1(s_An)); + const Real J0_aAn (gsl_sf_bessel_J0(a_An)); + const Real Y0_aAn (gsl_sf_bessel_Y0(a_An)); + const Real Y1_sAn (gsl_sf_bessel_Y1(s_An)); + + // calculate C0,n + const Real C_i_0 (calc_A_i_0(alpha)); + + // calculate the integral over Bn,0 + const Real dB_n_0dr (J1_sAn*Y0_aAn - Y1_sAn*J0_aAn); + // this is only the part without alpha of dB0,n(sigma)/dr + const Real B_n_0_int (Real(2.0)/(M_PI*alpha_sq) - (sigma/alpha)*dB_n_0dr); + + // return the total result + const Real result (C_i_0 * B_n_0_int); + + return result; +} + + +// Calculates the factor An,0 for (for example) determination of the flux +// through the outer interface +const Real +GreensFunction2DRadAbs::calc_A_i_0( const Real alpha) const +{ + // Get the required parameters + const Real a( geta() ); + const Real sigma( getSigma() ); + const Real h (this->geth()); + + const Real s_An (sigma*alpha); + const Real a_An (a*alpha); + const Real r0An (r0*alpha); + + // Calculate all the required Bessel functions + const Real J0_sAn (gsl_sf_bessel_J0(s_An)); + const Real J1_sAn (gsl_sf_bessel_J1(s_An)); + const Real J0_aAn (gsl_sf_bessel_J0(a_An)); + + const Real J0_r0An (gsl_sf_bessel_J0(r0An)); + const Real Y0_aAn (gsl_sf_bessel_Y0(a_An)); + const Real Y0_r0An (gsl_sf_bessel_Y0(r0An)); + + // Calculate return value + const Real alpha_sq (alpha*alpha); + + const Real rho (h*J0_sAn + alpha*J1_sAn); + const Real rho_sq (rho*rho); + + const Real B_n_0 (J0_r0An*Y0_aAn - Y0_r0An*J0_aAn); + + const Real A_i_0 ((alpha_sq * rho_sq * B_n_0)/( rho_sq - (J0_aAn*J0_aAn)*(h*h + alpha_sq))); + + return A_i_0; +} + + +// Calculates the n-th term of the summation for calculating the flux through +// the inner interface (reaction) +const Real +GreensFunction2DRadAbs::leaves_i( const Real alpha) const +{ + + const Real a( geta() ); // get the needed parameters + const Real sigma( getSigma() ); + + const Real s_An (sigma*alpha); + const Real a_An (a*alpha); + + // calculate all the required Bessel functions + const Real J1_sAn (gsl_sf_bessel_J1(s_An)); + const Real Y1_sAn (gsl_sf_bessel_Y1(s_An)); + const Real J0_aAn (gsl_sf_bessel_J0(a_An)); + const Real Y0_aAn (gsl_sf_bessel_Y0(a_An)); + + // calculate An,0 + const Real A_i_0 (calc_A_i_0(alpha)); // calculate the coefficient A0,n + + // calculate dBn,0(sigma)/dr + const Real dB_n_0dr (-alpha*(J1_sAn*Y0_aAn - Y1_sAn*J0_aAn)); + + // calculate the total result + const Real result (A_i_0 * dB_n_0dr); + + return result; +} + + +// calculates a table with all the constant factors for the survival probability +void +GreensFunction2DRadAbs::createPsurvTable( RealVector& table) const +{ + + const RealVector& alphaTable_0( this->getAlphaTable( 0 ) ); // get the roots for the survival probability + + table.clear(); // empty the table + table.reserve( alphaTable_0.size() ); // and get the nescessary memory + + std::transform( alphaTable_0.begin(), alphaTable_0.end(), + std::back_inserter( table ), + boost::bind( &GreensFunction2DRadAbs::p_survival_i, + this, _1) ); // This gets all the roots from 'begin' to 'end' + // passes them as an argument to p_survival_i and + // the result is passed to back_inserter +} + + +// Creates the tables with various Bessel functions used in drawR, the table is used to speed things up +void +GreensFunction2DRadAbs::createY0J0Tables( RealVector& Y0_Table, + RealVector& J0_Table, + RealVector& Y0J1J0Y1_Table, + const Real t ) const +{ + + const RealVector& alphaTable_0( this->getAlphaTable( 0 ) ); + // get the roots for the survival probability + Y0_Table.clear(); // empty the table + J0_Table.clear(); + Y0J1J0Y1_Table.clear(); + + Y0_Table.reserve( alphaTable_0.size() ); // and get the nescessary memory + J0_Table.reserve( alphaTable_0.size() ); + Y0J1J0Y1_Table.reserve( alphaTable_0.size() ); + + boost::tuple result; + + for (unsigned int count = 0; count < alphaTable_0.size(); count++) + { result = Y0J0J1_constants(alphaTable_0[count], t); + + Y0_Table.push_back (result.get<0>()); + J0_Table.push_back (result.get<1>()); + Y0J1J0Y1_Table.push_back (result.get<2>()); + } +} + + +// Creates the values for in the tables Y0, J0 and Y0J1J0Y1 +const boost::tuple +GreensFunction2DRadAbs::Y0J0J1_constants ( const Real alpha, + const Real t) const +{ + + const Real D(this->getD()); + const Real sigma(this->getSigma()); + const Real a(this->geta()); + + const Real s_An (sigma*alpha); + const Real a_An (a*alpha); + const Real alpha_sq (alpha*alpha); + + // calculate all the required Bessel functions + const Real J0_aAn (gsl_sf_bessel_J0(a_An)); + const Real Y0_aAn (gsl_sf_bessel_Y0(a_An)); + const Real J1_sAn (gsl_sf_bessel_J1(s_An)); + const Real Y1_sAn (gsl_sf_bessel_Y1(s_An)); + + + // calculate An,0 + const Real A_i_0 (calc_A_i_0(alpha)); //_sq * rho_sq * B_n_0)/( rho_sq - J0_bAn*J0_bAn*(h*h + alpha_sq))); + + // calculate the exponent with the time + const Real expT( std::exp(-D*alpha_sq*t)); + // and the product + const Real Ai0_expT (A_i_0 * expT / alpha); + + // calculate the large constant term in the intergral of Bn,0 + const Real Y0J1_J0Y1 (Y0_aAn*sigma*J1_sAn - J0_aAn*sigma*Y1_sAn); + + return boost::make_tuple (Ai0_expT*Y0_aAn, Ai0_expT*J0_aAn, Ai0_expT*Y0J1_J0Y1); +} + + +// ============================================================================= +// Root finding algorithm for alpha function. +// Roots (y=0) are necessary to calculate +// Modified by wehrens@amolf.nl. nov, 2011 +// +// Note mar 2012: +// The modified root finding algorithm can only work if the roots are calculated +// in a "chronological" sequence (i.e. i = 0, i = 1, i = 2 etc). Therefore roots +// are now all calculated immediately when the roots-table is expanded. +// ============================================================================= + +// Scans for next interval where a sign change is observed, and thus where a +// root is expected. +const void GreensFunction2DRadAbs::GiveRootInterval( + Real& low, // Variable to return left boundary interval + Real& high, // Variable to return right boundary interval + const Integer n) const // Order of Bessel functions +{ + // Variables for function values @ resp. left and right boundary interval. + Real f_low , f_high; + + // # Get/calculate boundaries and determine width of interval to check for + // sign change. + const Real estimated_alpha_root_distance_( this->getestimated_alpha_root_distance_() ); + const Real interval( FRACTION_SCAN_INTERVAL * estimated_alpha_root_distance_); + + // If order (n) is zero, the offset is zero, otherwhise take n-1 offset + // value as first estimate. + // (This can be done because the offsets only get bigger, the roots + // shift to the right with the order of the Bessel functions n.) + if (alpha_x_scan_table_[n] == 0) { // which implies i == 0 + if (n > 0) + { + alpha_x_scan_table_[n] = ( this->alphaTable[n-1][0] ); + } + } + + // Define new interval as between x1=offset ("low") and x2=(offset+interval) + // ("high"). Make sure "low" is > 0. + low = alpha_x_scan_table_[n]; + high = alpha_x_scan_table_[n] + interval; + if (low <= 0) // TODO this check should be redundant + { + log_.error("Left alpha search interval boundary < 0.");//low = EPSILON/L_TYPICAL; + throw std::exception(); + } + + // # Look for the sign change: + + // Get the values of the function at x "low" and x "high". + // Different for n=0 because this results in a simpler function. + // (Note: This code could be optimized by duplicating all involved + // functions and removing this if-statement. + + if (n == 0) { + f_low = f_alpha0(low); + f_high = f_alpha0(high); + } else { + f_low = f_alpha(low,n); + f_high = f_alpha(high,n); + } + + // Continue shifting the search interval until a sign change is detected. + while( f_low * f_high > 0 ) + { + low = high; + f_low = f_high; + + high += interval; + f_high = f_alpha( high, n ); + } + + // When above loop has finished, low and high have values inbetween which + // a root of the alpha function should lie have been found. Make sure that + // scanning for the next root starts at the end of the domain [low, high] + // found here. + alpha_x_scan_table_[n] = high; + + return; +} + + +// Simply returns an interval based upon previous root, estimated interval +// inbetween roots and INTERVAL_MARGIN (see .hpp). +const void GreensFunction2DRadAbs::GiveRootIntervalSimple( + Real& low, // Variable to return left boundary interval + Real& high, // Variable to return right boundary interval + const Integer n, // Order of Bessel functions + const Integer i) const // ith root +{ + // Offset is simply based on previous root, the interval in which the first + // root (i=0) lies is never calculated with this function. + const Real previous_root (getAlpha(n, i-1)); + + // get estimated interval + const Real estimated_alpha_root_distance_( this->getestimated_alpha_root_distance_() ); + + // Calculates interval [low, high] where root is expected based on the + // assumption where in a converging regime, where the deviation from this + // estimate is not more than INTERVAL_MARGIN. + low = previous_root + + estimated_alpha_root_distance_ * (1 - INTERVAL_MARGIN); + high = previous_root + + estimated_alpha_root_distance_ * (1 + INTERVAL_MARGIN); + + return; +} + + +// This function calls the GSL root finder, for roots for which n = 0. (This is +// a special case for which the function simplifies.) +const Real +GreensFunction2DRadAbs::getAlphaRoot0( const Real low, // root lies between low + const Real high // .. and high + ) const +{ + // Reinterpret_cast converts any pointer type to any other pointer type, + // even of unrelated classes. Hence below code converts pointer to + // function ::f_alpha_aux_F() to function pointer, which then points to + // memory location of params (¶ms). + + // f_alpha0_aux_params is a struct: {gf, value} + f_alpha0_aux_params params = { this, 0 }; // TODO: purpose of this zero is unclear!!! + + gsl_function F = + { + reinterpret_cast + ( &GreensFunction2DRadAbs::f_alpha0_aux_F ), + ¶ms + }; + + // Define solvertype. + const gsl_root_fsolver_type* solverType( gsl_root_fsolver_brent ); + // Initialize the solver. + gsl_root_fsolver* solver( gsl_root_fsolver_alloc( solverType ) ); + + // Call rootfinder, find root called "alpha" and return it. + const Real alpha ( findRoot( F, solver, low, high, + EPSILON/L_TYPICAL, EPSILON, "GreensFunction2DRadAbs::getAlphaRoot0" ) ); + gsl_root_fsolver_free( solver ); + +// const Real sigma(getSigma()); + return alpha; +} + + +// This function calls the GSL root finder, for roots for which n > 0. (n = 0 is +// a special case for which the function simplifies.) +const Real +GreensFunction2DRadAbs::getAlphaRootN( const Real low, // root lies between low + const Real high, // .. and high + const Integer n // nth order Bessel + ) const +{ + // f_alpha_aux_params is a struct: {gf, n, value} + // n is the summation index (the order of the Bessel functions used + f_alpha_aux_params params = { this, n, 0 }; // TODO: purpose of this zero is unclear!!! + + gsl_function F = + { + reinterpret_cast + ( &GreensFunction2DRadAbs::f_alpha_aux_F ), + ¶ms + }; + + // Define solvertype. + const gsl_root_fsolver_type* solverType( gsl_root_fsolver_brent ); + // Initialize the solver. + gsl_root_fsolver* solver( gsl_root_fsolver_alloc( solverType ) ); + + // Call rootfinder, find root called "alpha" and return it. + const Real alpha ( findRoot( F, solver, low, high, + EPSILON/L_TYPICAL, EPSILON, "GreensFunction2DRadAbs::getAlphaRootN" ) ); + gsl_root_fsolver_free( solver ); + + return alpha; +} + + +// Simply calls the correct function to call the rootfinder (either +// getAlphaRoot0 or getAlphaRootN). +const Real +GreensFunction2DRadAbs::getAlphaRoot( const Real low, // root lies between low + const Real high, // .. and high + const Integer n // nth order Bessel + ) const +{ + + Real alpha; + + if (n == 0) { + alpha = getAlphaRoot0(low, high); + } else { + alpha = getAlphaRootN(low, high, n); + } + + return alpha; +} + + +// Subfunction of getAlpha +// +// The function just counts the number of times the root lies in the interval +// that can be used to estimate the next root. If this happens enough, then it +// edits alpha_correctly_estimated_ to a negative value to signify that it can +// be assumed that for all next roots the distance inbetween roots will +// equal the interval +/- margin. +// TODO: This could be more sophisticated. +const void GreensFunction2DRadAbs::decideOnMethod2(size_t n, + RealVector::size_type i + ) const +{ + // Since the function can only decide with two alpha's already calculated, + // it can't do anything if i = 0. + if (i > 0) { + + const Real dx(getAlpha(n, i)-getAlpha(n, i-1)); // note the recursiveness! + const Real estimated_alpha_root_distance_( this->getestimated_alpha_root_distance_() ); + + // If the relative deviation from the expected difference is smaller + // than the expected margin, increase number of would-be correct + // guesses. + if (fabs(1-dx/estimated_alpha_root_distance_) < INTERVAL_MARGIN) { + ++alpha_correctly_estimated_[n]; + } else { + alpha_correctly_estimated_[n] = 0; + } + + // If guessing would have worked for last CONVERGENCE_ASSUMED roots, + // assume it will for all following roots. + if (alpha_correctly_estimated_[n] > CONVERGENCE_ASSUMED) { + alpha_x_scan_table_[n] = -2; // permanently switch + } + } + + return; +} + + +// This function searches for roots (y=0) of the so-called alpha-function +// (::f_alpha()). It either moves a small search interval along the x-axis to +// check for sign-change (which would indicate a root), and calls the GSL +// root finder, or directly calls the root finder if the spacing between +// roots is found to be converged. +const Real GreensFunction2DRadAbs::getAlpha( size_t n, // order + RealVector::size_type i // ith root + ) const +{ + +// const Real sigma(this->getSigma()); + Real current_root_, low, high; + + // # "Administration" + // Equals reading/writing to/from alphaTable to reading/writing from/to to + // this->alphaTable[n]; n being the order of the Bessel function. + // (Funky pointer operations.) + RealVector& alphaTable( this->alphaTable[n] ); + + // Gets it's size + const RealVector::size_type oldSize( alphaTable.size() ); + + // # Expansion of root table + + // If doesn't contain requested value, expand table until value + if( i >= oldSize ) + { + // Expand the table, temporarily fill with zeroes + alphaTable.resize( i+1, 0 ); + + // # Calculating the necessary roots to expand the table + for(unsigned int j = oldSize; j <= i; j++) + { + if (alphaTable[j] != 0) + { + log_.error("tried accessing root that's not 0. Didn't search.."); + log_.error(" i = %g, oldSize = %g, j = %g", i, oldSize, j); + } else + { + // Method 1. SCANNING. If the roots are not expected to lie close enough + // to the estimate, use the "scanning" procedure to find an interval + // that contains a root. (More robust method.) + // If it is established that we can use method 2, + // alpha_x_scan_table_[n] will contain a value < 0. + if (alpha_x_scan_table_[n] >= 0) + { + // ### Gets estimate of interval by sign-change-searching + // high and low are the return-values of GiveRootInterval. + GiveRootInterval(low, high, n); + + // ### Finds the root using the GSL rootfinder + current_root_ = getAlphaRoot(low, high, n); + + // ### Puts the found root in the table. + alphaTable[j]= current_root_; + + // Check if we can use method 2 for next roots + decideOnMethod2(n, j); + } + // Method 2. ASSUMING ROOTS AT ~FIXED INTERVAL. If next root is expected + // to lie at distance close enough to estimated distance the root finder + // can be called using a simple estimated interval. + else + { + // ### Get interval by simple extrapolation + GiveRootIntervalSimple(low, high, n, j); + + // ### Finds the root using the GSL rootfinder + current_root_ = getAlphaRoot(low, high, n); + + // ### Puts the found root in the table. + alphaTable[j] = current_root_; + } + } + } + + } + + return alphaTable[i]; + +} + +// ============================================================================= +// End modification +// ============================================================================= + + +// calculates the ith term with exponent and time for the survival probability +const Real +GreensFunction2DRadAbs::p_survival_i_exp_table( const unsigned int i, + const Real t, + const RealVector& table ) const +{ + + const Real alpha( this->getAlpha( 0, i ) ); + return std::exp( - getD() * t * alpha * alpha ) * table[i]; +} + + +// adds the exponential with the time to the sum. Needed for the calculation of the flux throught the outer +// interface +const Real +GreensFunction2DRadAbs::leavea_i_exp( const unsigned int i, + const Real t) const +{ + + const Real alpha( this->getAlpha( 0,i ) ); + return std::exp( - getD() * t * alpha * alpha ) * calc_A_i_0( alpha ); +} + + +// adds the exponential with the time to the sum. Needed for the inner interface (reaction) +const Real +GreensFunction2DRadAbs::leaves_i_exp( const unsigned int i, + const Real t) const +{ + + const Real alpha( this->getAlpha( 0, i ) ); + + return std::exp( - getD() * t * alpha * alpha ) * leaves_i( alpha ); +} + + +// calculates the Bossen function for a given r +const Real +GreensFunction2DRadAbs::p_int_r_i_exp_table( const unsigned int i, + const Real r, + const RealVector& Y0_aAnTable, + const RealVector& J0_aAnTable, + const RealVector& Y0J1J0Y1Table ) const +{ + + const Real alpha( this->getAlpha( 0, i ) ); // get the root An + const Real r_An( r*alpha); + + const Real J1_rAn (gsl_sf_bessel_J1(r_An)); + const Real Y1_rAn (gsl_sf_bessel_Y1(r_An)); + + const Real result (Y0_aAnTable[i]*r*J1_rAn - J0_aAnTable[i]*r*Y1_rAn - Y0J1J0Y1Table[i]); + return result; +} + + +// This tries to guess the maximum number of n iterations it needs for calculating the survival probability +// Not really sure yet how this works +const unsigned int +GreensFunction2DRadAbs::guess_maxi( const Real t ) const +{ + + const unsigned int safety( 2 ); + + if( t >= INFINITY ) + { + return safety; + } + + const Real D( getD() ); + const Real sigma( getSigma() ); + const Real a( geta() ); + + const Real alpha0( getAlpha( 0, 0 ) ); + const Real Dt( D * t ); + const Real thr( ( exp( - Dt * alpha0 * alpha0 ) / alpha0 ) * this->EPSILON * 1e-1 ); + const Real thrsq( thr * thr ); + + if( thrsq <= 0.0 ) + { + return this->MAX_ALPHA_SEQ; + } + + const Real max_alpha( 1.0 / ( sqrt( exp( gsl_sf_lambert_W0( 2 * Dt / thrsq ) ) * thrsq ) ) ); + const unsigned int maxi( safety + static_cast( max_alpha * ( a - sigma ) / M_PI ) ); + + return std::min( maxi, this->MAX_ALPHA_SEQ ); +} + + +// Calculates the survival probability at a given time. +// This is a little wrapper for the p_survival_table so that you can easily calculate the survival probability +// at a given time +const Real +GreensFunction2DRadAbs::p_survival( const Real t) const +{ + + RealVector psurvTable; + + const Real p( p_survival_table( t, psurvTable ) ); + + return p; +} + + +// This actually calculates the Survival probability at time t given the particle was at r0 at time 0 +// It uses the pSurvTable for efficiency (so you don't have to calculate all the constant factors all +// the time) +const Real +GreensFunction2DRadAbs::p_survival_table( const Real t, + RealVector& psurvTable ) const +{ + + Real p; + const unsigned int maxi( guess_maxi( t ) ); // guess the maximum number of iterations required +// const unsigned int maxi( 500 ); // THIS LEADS TO BIZARRE RESULTS + + // If the estimated # terms needed for convergence is bigger than number + // of terms summed over (MAX_ALPHA_SEQ), give error. + if( maxi == this->MAX_ALPHA_SEQ ) + log_.warn("p_survival_table (used by drawTime) couldn't converge; max terms reached: %u", maxi); + + if( psurvTable.size() < maxi + 1 ) // if the dimensions are good then this means + { // that the table is filled + getAlpha( 0, maxi ); // this updates the table of roots + this->createPsurvTable( psurvTable); // then the table is filled with data + } + // A sum over terms is performed, where convergence is assumed. It is not + // clear if this is a just assumption. + // TODO! + p = funcSum_all( boost::bind( &GreensFunction2DRadAbs::p_survival_i_exp_table, + this, + _1, t, psurvTable ), + maxi ); // calculate the sum at time t + return p*M_PI*M_PI_2; +} + + +// calculates the flux leaving through the inner interface at a given moment +// FIXME: This is inaccurate for small t's!! +const Real +GreensFunction2DRadAbs::leaves( const Real t) const +{ + + const Real sigma(this->getSigma()); + const Real D(this->getD() ); + + const Real p( funcSum( boost::bind( &GreensFunction2DRadAbs::leaves_i_exp, + this, + _1, t), + this->MAX_ALPHA_SEQ ) ); + + return M_PI_2*M_PI*D*sigma*p; // The minus is not there because the flux is in the negative r + // direction, and the direction is already accounted for in the derivative of B0,n(r) + // See also leaves_i +} + + +// calculates the flux leaving through the outer interface at a given moment +const Real +GreensFunction2DRadAbs::leavea( const Real t) const +{ + + const Real D(this->getD() ); + + const Real p( funcSum( boost::bind( &GreensFunction2DRadAbs::leavea_i_exp, + this, + _1, t), + this->MAX_ALPHA_SEQ ) ); + return M_PI*D*p; +} + + +// calculates the sum of the sequence for drawR based upon the values in the tables and r +const Real +GreensFunction2DRadAbs::p_int_r_table( const Real r, + const RealVector& Y0_aAnTable, + const RealVector& J0_aAnTable, + const RealVector& Y0J1J0Y1Table ) const +{ + + const Real p( funcSum( boost::bind( &GreensFunction2DRadAbs::p_int_r_i_exp_table, + this, + _1, r, Y0_aAnTable, J0_aAnTable, Y0J1J0Y1Table ), + Y0_aAnTable.size() ) ); + return p*M_PI*M_PI_2; +} + + +// Used by drawTime +// Wrapper for p_survival_table for the interator to find the root for drawTime +const Real +GreensFunction2DRadAbs::p_survival_table_F( const Real t, + const p_survival_table_params* params ) +{ + + const GreensFunction2DRadAbs* const gf( params->gf ); // the current gf (not sure why this is here) + RealVector& table( params->table ); // table is empty but will be filled in p_survival_table + const Real rnd( params->rnd ); + + return rnd - gf->p_survival_table( t, table ); +} + + +// a wrapper to make p_int_r_table available to the iterator calculating the root +const Real +GreensFunction2DRadAbs::p_int_r_F( const Real r, + const p_int_r_params* params ) +{ + + const GreensFunction2DRadAbs* const gf( params->gf ); + const RealVector& Y0_aAnTable( params->Y0_aAnTable ); + const RealVector& J0_aAnTable( params->J0_aAnTable ); + const RealVector& Y0J1J0Y1Table( params->Y0J1J0Y1Table ); + const Real rnd( params->rnd ); + + return gf->p_int_r_table( r, Y0_aAnTable, J0_aAnTable, Y0J1J0Y1Table) - rnd; +} + + +// Draws a first passage time, this could be an escape (through the outer boundary) or a reaction (through +// the inner boundary) +Real GreensFunction2DRadAbs::drawTime( const Real rnd) const +{ + + const Real D( this->getD() ); + const Real sigma( this->getSigma() ); + const Real a( this->geta() ); + const Real kf( this->getkf() ); + const Real r0( this->getr0() ); + + THROW_UNLESS( std::invalid_argument, 0.0 <= rnd && rnd < 1.0 ); + THROW_UNLESS( std::invalid_argument, sigma <= r0 && r0 <= a ); + + Real t_guess; + + if( r0 == a || a == sigma ) // when the particle is at the border or if the PD has no real size + { + return 0.0; + } + + // get some initial guess for the time, dr=sqrt(2dDt) with d + // the dimensionality (2 in this case) + const Real t_Abs( gsl_pow_2( a - r0 ) / ( 4.0 * D ) ); + if ( kf == 0.0 ) // if there was only one absorbing boundary + { + t_guess = t_Abs; + } + else + { + const Real t_Rad( D / gsl_pow_2( kf/(2*M_PI*a) ) + gsl_pow_2( r0 - sigma ) / D ); + t_guess = std::min( t_Abs, t_Rad ); // take the shortest time to a boundary + } + + t_guess *= .1; + + const Real minT( std::min( sigma * sigma / D * this->MIN_T_FACTOR, + t_guess * 1e-7 ) ); // something with determining the lowest possible t + + + RealVector psurvTable; // this is still empty as of now->not used + p_survival_table_params params = { this, psurvTable, rnd }; + gsl_function F = + { + reinterpret_cast( &p_survival_table_F ), + ¶ms + }; + + // put in a upper and lower limit (the picked time cannot be infinite!) + Real low( t_guess ); + Real high( t_guess ); + + // adjust high and low to make sure that f( low ) and f( high ) straddle. + Real value( GSL_FN_EVAL( &F, t_guess ) ); + + if( value < 0.0 ) // if the function is below zero at the guess the upper + { // boundary should be moved (passed the zero point) + do + { + high *= 10; + value = GSL_FN_EVAL( &F, high ); + + if( fabs( high ) >= 1e10 ) // if high time is way too high forget about it + { + log_.warn("Couldn't adjust high. F(%.16g) = %.16g; r0 = %.16g,", high, GSL_FN_EVAL( &F, high ), r0); + std::cerr << dump() << std::endl; + throw std::exception(); + } + } + while ( value < 0.0 ); + } + else // if the function is over zero (or at zero!) then the lower + { // boundary should be moved + Real value_prev( value ); + do + { low *= .1; // keep decreasing the lower boundary until the function straddles + value = GSL_FN_EVAL( &F, low ); // get the accompanying value + + if( fabs( low ) <= minT || fabs( value - value_prev ) < EPSILON*this->T_TYPICAL ) + { + log_.warn("Couldn't adjust low. F(%.16g) = %.16g", low, value); + return low; + } + value_prev = value; + } + while ( value >= 0.0 ); + } + + // find the intersection of the cummulative survival probability and the randomly generated number + const gsl_root_fsolver_type* solverType( gsl_root_fsolver_brent ); // initialize the solver + gsl_root_fsolver* solver( gsl_root_fsolver_alloc( solverType ) ); + + const Real t( findRoot( F, solver, low, high, // find the intersection between the random + EPSILON*T_TYPICAL, EPSILON, "GreensFunction2DRadAbs::drawTime" ) ); + // number and the cumm probability + gsl_root_fsolver_free( solver ); + + return t; +} + + +// This determines based on the flux at a certain time, if the 'escape' was a reaction or a proper escape +GreensFunction2DRadAbs::EventKind +GreensFunction2DRadAbs::drawEventType( const Real rnd, + const Real t ) const +{ + + const Real D( this->getD() ); + const Real sigma( this->getSigma() ); + const Real kf( this->getkf() ); + const Real a( this->geta() ); + const Real r0( this->getr0() ); + + THROW_UNLESS( std::invalid_argument, 0 <= rnd && rnd < 1.0 ); + THROW_UNLESS( std::invalid_argument, sigma <= r0 && r0 < a ); + THROW_UNLESS( std::invalid_argument, t > 0.0 ); + + if( kf == 0.0 ) // if there cannot be any flow through the radiating boundary it is always an escape + { + return IV_ESCAPE; + } + + // First, check if r0 is close only either to a or sigma relative + // to Dt. In such cases, the event type is always ESCAPE or REACTION, + // respectively. This avoids numerical instability in calculating + // leavea() and/or leaves(). + + // Here, use a rather large threshold for safety. + const unsigned int H( 6 ); // 6 times the msd travelled as threshold + const Real max_dist( H * sqrt( 4.0 * D * t ) ); + const Real a_dist( a - r0 ); + const Real s_dist( r0 - sigma ); + + + if( a_dist > max_dist ) + { + if( s_dist < max_dist ) + { + return IV_REACTION; + } + } + else // a_dist < max_dist + { + if( s_dist > max_dist ) + { + return IV_ESCAPE; + } + } + + const Real reaction( leaves( t ) ); // flux through rad boundary + const Real escape( leavea( t ) ); // flux through abs boundary + const Real value( reaction / ( reaction + escape ) ); + + if( rnd <= value ) + { + return IV_REACTION; // leaves -> return 0 + } + else + { + return IV_ESCAPE; // leavea -> return 1 + } +} + + +// This draws a radius R at a given time, provided that the particle was at r0 at t=0 +Real GreensFunction2DRadAbs::drawR( const Real rnd, + const Real t ) const +{ + + // Diffusion constant, inner boundary, outer boundary, starting r. + const Real D( this->getD() ); + const Real sigma( getSigma() ); + const Real a( this->geta() ); + const Real r0( this->getr0() ); + + THROW_UNLESS( std::invalid_argument, rnd < 1.0 && rnd >= 0.0 ); + THROW_UNLESS( std::invalid_argument, r0 >= sigma && r0 < a ); + + if( t == 0.0 ) // if no time has passed + { + return r0; + } + + const Real psurv( p_survival( t ) ); // calculate the survival probability at this time + // this is used as the normalization factor + // BEWARE!!! This also produces the roots An and therefore + // SETS THE HIGHEST INDEX -> side effect + // VERY BAD PROGRAMMING PRACTICE!! + + RealVector Y0_aAnTable; + RealVector J0_aAnTable; + RealVector Y0J1J0Y1Table; + createY0J0Tables( Y0_aAnTable, J0_aAnTable, Y0J1J0Y1Table, t); + + p_int_r_params params = { this, t, Y0_aAnTable, J0_aAnTable, Y0J1J0Y1Table, rnd * psurv }; + + gsl_function F = + { + reinterpret_cast( &p_int_r_F ), + ¶ms + }; + + // adjust low and high starting from r0. + // this is necessary to avoid root finding in the long tails where + // numerics can be unstable. + Real low( r0 ); // start with the initial position as the first guess + Real high( r0 ); + Real value (0); + unsigned int H( 3 ); + + const Real msd( sqrt( 4.0 * D * t ) ); + if( GSL_FN_EVAL( &F, r0 ) < 0.0 ) + { + do + { + high = r0 + H * msd; + if( high > a ) + { + if( GSL_FN_EVAL( &F, a ) < 0.0 ) // something is very wrong, this should never happen + { + log_.warn("drawR: p_int_r_table( a ) < 0.0. Returning a."); + return a; + } + high = a; + break; + } + value = GSL_FN_EVAL( &F, high ); + ++H; + } + while (value < 0.0); + + } + else + { + do + { + low = r0 - H * msd; + if( low < sigma ) + { + if( GSL_FN_EVAL( &F, sigma ) > 0.0 ) + { + log_.warn("drawR: p_int_r_table( sigma ) > 0.0. Returning sigma."); + return sigma; + } + + low = sigma; + break; + } + + value = GSL_FN_EVAL( &F, low ); + ++H; + } + while ( value > 0.0 ); + } + + + // root finding by iteration. + const gsl_root_fsolver_type* solverType( gsl_root_fsolver_brent ); + gsl_root_fsolver* solver( gsl_root_fsolver_alloc( solverType ) ); + const Real r( findRoot( F, solver, low, high, // find the intersection between the random + L_TYPICAL*EPSILON, EPSILON, "GreensFunction2DRadAbs::drawR" ) ); + // number and the cumm probability + gsl_root_fsolver_free( solver ); + + return r; +} + + +// The calculates constant factor m,n for the drawing of theta. These factors are summed later. +const Real GreensFunction2DRadAbs::p_m_alpha( const unsigned int n, + const unsigned int m, + const Real r, + const Real t ) const +{ + + const Real sigma( this->getSigma() ); + const Real h( this->geth() ); + const Real a( this->geta() ); + const Real D( this->getD() ); + const Real alpha( this->getAlpha( m, n ) ); // Gets the n-th root using the + // besselfunctions of order m. + const Real r0( this->getr0() ); + + const Real alpha_sq( alpha * alpha ); + const Real realm( static_cast( m ) ); + const Real msq( realm * realm); + const Real ssq( sigma * sigma); + + const Real s_Anm (sigma * alpha); + const Real a_Anm (a * alpha); + const Real r0Anm (r0 * alpha); + const Real r_Anm (r * alpha); + +// const CylindricalBesselGenerator& s(CylindricalBesselGenerator::instance()); + + // calculate the needed bessel functions + const Real Jm_sAnm (gsl_sf_bessel_Jn(m, s_Anm)); + const Real Jmp1_sAnm (gsl_sf_bessel_Jn(m+1, s_Anm)); // prime +// const Real Jm_sAnm (s.J(m, s_Anm)); +// const Real Jmp1_sAnm (s.J(m+1, s_Anm)); // prime + + const Real Jm_aAnm (gsl_sf_bessel_Jn(m, a_Anm)); + const Real Ym_aAnm (gsl_sf_bessel_Yn(m, a_Anm)); +// const Real Jm_aAnm (s.J(m, a_Anm)); +// const Real Ym_aAnm (s.Y(m, a_Anm)); + + const Real Jm_r0Anm (gsl_sf_bessel_Jn(m, r0Anm)); + const Real Ym_r0Anm (gsl_sf_bessel_Yn(m, r0Anm)); +// const Real Jm_r0Anm (s.J(m, r0Anm)); +// const Real Ym_r0Anm (s.Y(m, r0Anm)); + + const Real Jm_rAnm (gsl_sf_bessel_Jn(m, r_Anm)); + const Real Ym_rAnm (gsl_sf_bessel_Yn(m, r_Anm)); +// const Real Jm_rAnm (s.J(m, r_Anm)); +// const Real Ym_rAnm (s.Y(m, r_Anm)); + + // calculating An,m + const Real h_ma (h - realm/sigma); + const Real rho (h_ma*Jm_sAnm + alpha*Jmp1_sAnm); + const Real rho_sq (rho*rho); + // calculating Bn,m(r') + const Real B_n_m_r0 (Jm_r0Anm * Ym_aAnm - Ym_r0Anm * Jm_aAnm); + + const Real A_n_m ((alpha_sq * rho_sq * B_n_m_r0)/( rho_sq - (Jm_aAnm*Jm_aAnm)*(h*h + alpha_sq - msq/ssq))); + + // calculating Bn,m(r*) + const Real B_n_m_r (Jm_rAnm * Ym_aAnm - Ym_rAnm * Jm_aAnm); + + // calculating the result + const Real result( A_n_m * B_n_m_r * exp(-D*alpha_sq*t) ); + + return result; +} + + +// This calculates the m-th constant factor for the drawTheta method. +const Real +GreensFunction2DRadAbs::p_m( const Integer m, + const Real r, + const Real t ) const +{ + + const Real p( funcSum( boost::bind( &GreensFunction2DRadAbs::p_m_alpha, + this, + _1, m, r, t ), // The m-th factor is a summation over n + MAX_ALPHA_SEQ, EPSILON ) ); + return p; +} + + +// this should make the table of constants used in the iteration for finding the root for drawTheta +// The index of the array is consistent with the index of the summation +void +GreensFunction2DRadAbs::makep_mTable( RealVector& p_mTable, + const Real r, + const Real t ) const +{ + + p_mTable.clear(); + + const Real p_0 ( this->p_m( 0, r, t ) ); // This is the p_m where m is 0, for the denominator + p_mTable.push_back( p_0 ); // put it in the table + + const Real p_1 ( this->p_m( 1, r, t ) / p_0 ); + p_mTable.push_back( p_1 ); // put the first result in the table + + if( p_1 == 0 ) + { + return; // apparantly all the terms are zero? We are finished + // TODO: is this assumption correct?? + } + + const Real threshold( fabs( EPSILON * p_1 ) ); // get a measure for the allowed error, is this correct? + + Real p_m_abs (fabs (p_1)); + Real p_m_prev_abs; + unsigned int m( 1 ); + do + { + m++; + if( m >= this->MAX_ORDER ) // If the number of terms is too large + { + log_.warn("p_m didn't converge (m=%u, t=%.16g, r0=%.16g, r=%.16g, t_est=%.16g, continuing...", + m, t, getr0(), r, gsl_pow_2( r - getr0() ) / getD() + ); + std::cerr << dump() << std::endl; + break; + } + + p_m_prev_abs = p_m_abs; // store the previous term + const Real p_m( this->p_m( m, r, t ) / p_0 ); // get the next term + + if( ! std::isfinite( p_m ) ) // if the calculated value is not valid->exit + { + log_.warn("makep_mTable: invalid value (p_m = %.16g, m=%u)", p_m, m); + break; + } + + p_mTable.push_back( p_m ); // put the result in the table + p_m_abs = fabs( p_m ); // take the absolute value + } + while (p_m_abs >= threshold || p_m_prev_abs >= threshold || p_m_abs >= p_m_prev_abs ); + + // truncate when converged enough. + // if the current term is smaller than threshold + // AND the previous term is also smaller than threshold + // AND the current term is smaller than the previous +} + + +// This method calculates the constants for the drawTheta method when the particle is at the boundary +const Real +GreensFunction2DRadAbs::dp_m_alpha_at_a( const unsigned int n, + const unsigned int m, + const Real t ) const +{ + + const Real sigma( this->getSigma() ); + const Real h( this->geth() ); + const Real a( this->geta() ); + const Real D( this->getD() ); + const Real r0( this->getr0() ); + + const Real alpha( this->getAlpha( m, n ) ); // get the n-th root using the besselfunctions of order m + + const Real alpha_sq( alpha * alpha ); + const Real realm( static_cast( m ) ); + const Real msq( realm * realm); + const Real ssq( sigma * sigma); + + const Real s_Anm (sigma*alpha); + const Real a_Anm (a*alpha); + const Real r0Anm (r0*alpha); + + const Real Jm_sAnm (gsl_sf_bessel_Jn(m, s_Anm)); + const Real Jmp1_sAnm (gsl_sf_bessel_Jn(m+1, s_Anm)); + const Real Jm_aAnm (gsl_sf_bessel_Jn(m, a_Anm)); + const Real Ym_aAnm (gsl_sf_bessel_Yn(m, a_Anm)); + + const Real Jm_r0Anm (gsl_sf_bessel_Jn(m, r0Anm)); + const Real Ym_r0Anm (gsl_sf_bessel_Yn(m, r0Anm)); + + // calculating An,m + const Real h_ma (h - realm/sigma); + const Real rho (h_ma*Jm_sAnm + alpha*Jmp1_sAnm); + const Real rho_sq (rho*rho); + + // calculating Bn,m(r') + const Real B_n_m_r0 (Jm_r0Anm * Ym_aAnm - Ym_r0Anm * Jm_aAnm); + + const Real A_n_m ((alpha_sq * rho_sq * B_n_m_r0)/( rho_sq - (Jm_aAnm*Jm_aAnm)*(h*h + alpha_sq - msq/ssq))); + + // calculating the result + const Real result( A_n_m * exp(-D*alpha_sq*t) ); + + return result; +} + + +// Makes the sum over n for order m for the constants for the drawtheta Method +const Real +GreensFunction2DRadAbs::dp_m_at_a( const Integer m, + const Real t ) const +{ + const Real p( funcSum( + boost::bind( + &GreensFunction2DRadAbs::dp_m_alpha_at_a, + this, + _1, + m, + t + ), + MAX_ALPHA_SEQ, + EPSILON + ) + ); + + // boost::bind + // explanation by example: + // "bind(f, _1, 5)(x)" is equivalent to "f(x, 5)" + // this means funcsum receives f(x, m=.., t=..) as + // input, with m and t already determined. + + // Arguments of dp_m_alpha_at_a: + // n, m, t + + return p; +} + + +// creates a tables of constants for drawTheta when the particle is at the edge of the domain +void +GreensFunction2DRadAbs::makedp_m_at_aTable( RealVector& p_mTable, + const Real t ) const +{ + + p_mTable.clear(); + + const Real p_0 ( this->dp_m_at_a( 0, t ) ); // This is the p_m where m is 0, for the denominator + p_mTable.push_back( p_0 ); // put it in the table + + const Real p_1 ( this->dp_m_at_a( 1, t ) / p_0 ); + p_mTable.push_back( p_1 ); // put the first result in the table + + + if( p_1 == 0 ) + { + return; // apparantly all the terms are zero? We are finished + } + + const Real threshold( fabs( EPSILON * p_1 ) ); // get a measure for the allowed error + + Real p_m_abs (fabs (p_1)); + Real p_m_prev_abs; + unsigned int m( 1 ); + do + { + m++; + if( m >= this->MAX_ORDER ) // If the number of terms is too large + { + log_.warn("dp_m didn't converge (m=%u), continuing...", m); + break; + } + + + p_m_prev_abs = p_m_abs; // store the previous term + const Real p_m( this->dp_m_at_a( m, t ) / p_0 ); // get the next term + + // DEBUG (something to check in the future?) + // if (p_m_abs == 0) { + // std::cerr << "Zero valued term found, but convergence is:" << + // p_mTable[p_mTable.size()-1-1]/p_mTable[p_mTable.size()-2-1]; + // } + // END DEBUG + + if( ! std::isfinite( p_m ) ) // if the calculated value is not valid->exit + { + log_.warn("makedp_m_at_aTable: invalid value (p_m=%.16g, m=%u, t=%.16g, p_0=%.16g)", p_m, m, t, p_0); + break; + } + + p_mTable.push_back( p_m ); // put the result in the table + p_m_abs = fabs( p_m ); // take the absolute value + } + while (p_m_abs >= threshold || p_m_prev_abs >= threshold || p_m_abs >= p_m_prev_abs ); + // truncate when converged enough. + // if the current term is smaller than threshold + // AND the previous term is also smaller than threshold + // AND the current term is smaller than the previous +} + + +// This calculates the m-th term of the summation for the drawTheta calculation +// Note that m here starts at 0 and in the equations the sum starts at 1! +const Real +GreensFunction2DRadAbs::ip_theta_n( const unsigned int m, + const Real theta, + const RealVector& p_nTable ) const +{ + + const unsigned int m_p1 (m+1); // artificial increase of m to make sure m starts at 1 + return p_nTable[m_p1] * sin (m_p1*theta)/m_p1; +} + + +// calculates the cummulative probability of finding the particle at a certain theta +// It is used by the drawTheta method +// It uses the p_nTable for it to speed things up +const Real +GreensFunction2DRadAbs::ip_theta_table( const Real theta, + const RealVector& p_nTable ) const +{ + + const unsigned int maxm( p_nTable.size()-1 ); // get the length of the sum + // it is shifted one because the first entry should + // be used (m=0) + + const Real p( funcSum_all( boost::bind( &GreensFunction2DRadAbs::ip_theta_n, + this, + _1, theta, p_nTable ), + maxm ) ); + return p; +} + + +// function to iterate when drawing the theta +const Real +GreensFunction2DRadAbs::ip_theta_F( const Real theta, + const ip_theta_params* params ) +{ + + const GreensFunction2DRadAbs* const gf( params->gf ); + const RealVector& p_nTable( params->p_nTable ); // table with useful constants + const Real value( params->value ); + + return theta/(M_PI*2) + (gf->ip_theta_table( theta, p_nTable )/M_PI) - value; +} + + +// This method draws a theta given a certain r and time (and intial condition of course) +Real +GreensFunction2DRadAbs::drawTheta( const Real rnd, + const Real r, + const Real t ) const +{ + + const Real sigma( this->getSigma() ); + const Real a( this->geta() ); + const Real D( this->getD() ); + const Real r0( this->getr0() ); + + // input parameter range checks. + THROW_UNLESS( std::invalid_argument, 0.0 <= rnd && rnd < 1.0 ); + THROW_UNLESS( std::invalid_argument, sigma <= r0 && r0 <= a ); + THROW_UNLESS( std::invalid_argument, sigma <= r && r <= a); + THROW_UNLESS( std::invalid_argument, t >= 0.0 ); + + // t == 0 means no move. + if( t <= T_TYPICAL*EPSILON || D == 0 || fabs(r0 - a) <= EPSILON*L_TYPICAL || rnd <= EPSILON) + { + return 0.0; + } + else if (r == sigma) // a reaction has occured, the angle is irrelevant + { + return 0.0; + } + + // making the tables with constants + + RealVector p_mTable; // a table with constants to make calculationdraws much faster + if( fabs(r - a) <= EPSILON*L_TYPICAL ) // If the r is at the outer boundary + { + makedp_m_at_aTable( p_mTable, t ); // making the table if particle on the outer boundary + } + else + { + makep_mTable( p_mTable, r, t ); // making the table of constants for the regular case + } + + // preparing the function + + // ip_theta_params is a struct. + ip_theta_params params = { this, r, t, p_mTable, rnd*0.5 }; // r, r0, t are not required + // 0.5 is not even used + // F is a struct of type "gsl_function" that contains a function pointer + // and the required parameters. + gsl_function F = + { + reinterpret_cast( &ip_theta_F ), // ip_theta_F is + // theta pdf function. + ¶ms + // reinterpret_cast converts any pointer type to any other pointer type. + // reinterpret_cast variable + }; + + // finding the root + const gsl_root_fsolver_type* solverType( gsl_root_fsolver_brent ); + gsl_root_fsolver* solver( gsl_root_fsolver_alloc( solverType ) ); + const Real theta( findRoot( F, solver, 0, M_PI, EPSILON, EPSILON, + "GreensFunction2DRadAbs::drawTheta" ) ); + gsl_root_fsolver_free( solver ); + + return theta; +} + + +// +// DEBUG +// +// Debug functionality +std::string GreensFunction2DRadAbs::dump() const +{ + + std::ostringstream ss; + ss << "Parameters dump: "; + ss << "D = " << this->getD() << ", sigma = " << this->getSigma() << + ", a = " << this->geta() << + ", kf = " << this->getkf() << + ", r0 = " << this->getr0() << + ", h = " << this->geth() << std::endl; + return ss.str(); +} + + +// Debug functionality +// Directly outputs probability distribution function value of leaving angle +// for given theta, r and t. +Real +GreensFunction2DRadAbs::givePDFTheta( const Real theta, + const Real r, + const Real t ) const +{ + + const Real sigma( this->getSigma() ); + const Real a( this->geta() ); +// const Real D( this->getD() ); + const Real r0( this->getr0() ); + + // input parameter range checks. + THROW_UNLESS( std::invalid_argument, sigma <= r0 && r0 <= a ); + THROW_UNLESS( std::invalid_argument, sigma <= r && r <= a); + THROW_UNLESS( std::invalid_argument, t >= 0.0 ); + + // making the tables with constants + RealVector p_mTable; // a table with constants to make calculations much faster + if( fabs(r - a) <= EPSILON*L_TYPICAL ) // If the r is at the outer boundary + { + makedp_m_at_aTable( p_mTable, t ); // making the table if particle on the outer boundary + } + else + { + makep_mTable( p_mTable, r, t ); // making the table of constants for the regular case + } + + // Return the pdf + ip_theta_params params = { this, r, t, p_mTable, 0.0 }; // r, r0, t are not required + Real PDF (ip_theta_F( theta, ¶ms )); + return PDF; + +} + + +// Output the PDF of r, given time t has passed. +Real GreensFunction2DRadAbs::givePDFR( const Real r, const Real t ) const +{ + +// const Real D( this->getD() ); +// const Real sigma( this->getSigma() ); +// const Real a( this->geta() ); + const Real r0( this->getr0() ); + + if( t == 0.0 ) // if no time has passed + { + return r0; + } + + p_survival(t); // calculate the survival probability at this time + // this is used as the normalization factor + // BEWARE!!! This also produces the roots An and therefore + // SETS THE HIGHEST INDEX -> side effect + // VERY BAD PROGRAMMING PRACTICE!! + RealVector Y0_aAnTable; + RealVector J0_aAnTable; + RealVector Y0J1J0Y1Table; + createY0J0Tables( Y0_aAnTable, J0_aAnTable, Y0J1J0Y1Table, t); + + // Create a struct params with the corrects vars. + p_int_r_params params = { this, t, Y0_aAnTable, J0_aAnTable, Y0J1J0Y1Table, 0.0 }; + + // Calculate PDF(r) with these vars, + Real PDF (p_int_r_F( r, ¶ms )); + + return PDF; +} + + +void GreensFunction2DRadAbs::dumpRoots( int n ) +{ + +// return this->alphaTable[n]; + std::cout << "Roots are: {"; + + const int size( alphaTable.size() ); + RealVector& alphaTable( this->alphaTable[n] ); + + for (int i = 0; i < size; i++) { + std::cout << alphaTable[i] << ","; + } + std::cout << "}.\n"; +} + +// It is used by the drawTheta method +// It uses the p_nTable for it to speed things up +/* +const Real +GreensFunction2DRadAbs::debug_ip_theta_table( const Real theta) const +{ + const RealVector& p_nTable( params->p_nTable ); // table with useful constants + + const unsigned int maxm( p_nTable.size()-1 ); // get the length of the sum + // it is shifted one because the first entry should + // be used (m=0) + + const Real p( funcSum_all( boost::bind( &GreensFunction2DRadAbs::ip_theta_n, + this, + _1, theta, p_nTable ), + maxm ) ); + return p; +} +*/ + +Logger& GreensFunction2DRadAbs::log_( + Logger::get_logger("GreensFunction2DRadAbs")); + diff --git a/GreensFunction2DRadAbs.hpp b/GreensFunction2DRadAbs.hpp new file mode 100644 index 00000000..06b2870e --- /dev/null +++ b/GreensFunction2DRadAbs.hpp @@ -0,0 +1,355 @@ +// Greens function class for 2d Green's Function for 2d annulus with radial and +// axial dependence. Inner boundary is radiative (rad) (reaction event), outer +// boundary is absorbing (abs) (escape event). Different "draw" functions +// provide a way to draw certain values from the Green's Function, e.g. an +// escape angle theta ("drawTheta" function). +// +// Written by Laurens Bossen. Adapted by Martijn Wehrens. +// FOM Institute AMOLF. + +#if !defined( __FIRSTPASSAGEPAIRGREENSFUNCTION2D_HPP ) +#define __FIRSTPASSAGEPAIRGREENSFUNCTION2D_HPP + +#include +#include +#include +#include + +#include +#include "Logger.hpp" + +#include "PairGreensFunction.hpp" + + +class GreensFunction2DRadAbs + : + public PairGreensFunction +{ + +public: + // Defines vector from template defined in standard template library, + // gives it's membervalues type Real. std:: clarifies the std namespace. + typedef std::vector RealVector; + +private: + // Error tolerance used by default. + static const Real TOLERANCE = 1e-8; + + // SphericalBesselGenerator's accuracy, used by some + // theta-related calculations. + + static const Real MIN_T_FACTOR = 1e-8; + + static const Real L_TYPICAL = 1E-7; // typical length scale + static const Real T_TYPICAL = 1E-5; // typical time scale + static const Real EPSILON = 1E-12; // relative numeric error // TESTING temporarily increased; was 1e-12 + + // DEFAULT = 30 + static const unsigned int MAX_ORDER = 30; // The maximum number of m + // terms + static const unsigned int MAX_ALPHA_SEQ = 500; // The maximum number of n + // terms + + // Parameters for alpha-root finding + // ====== + // See getAlpha() in cpp file for more information. + // + // Parameters for scanning method + // Left boundary of 1st search interval 1st root + static const Real SCAN_START = 0.001; + // Length of the scanning interval relative to estimated interval + static const Real FRACTION_SCAN_INTERVAL = .5; // TODO CHANGED THIS FROM .5 to .2 + + // Other paramters + // After CONVERGENCE_ASSUMED subsequent roots that lay within +/- + // INTERVAL_MARGIN from the distance to which the distance is known to + // converge, it is assumed all following roots have a distances inbetween + // that don't deviate for more than INTERVAL_MARGIN from the distance to + // which the roots are known to converge (Pi/(a-sigma)). + static const Real CONVERGENCE_ASSUMED = 25; + static const Real INTERVAL_MARGIN = .33; + + + +public: + + const char* getName() const + { + return "GreensFunction2DRadAbs"; + } + + GreensFunction2DRadAbs( const Real D, + const Real kf, + const Real r0, + const Real Sigma, + const Real a ); + + virtual ~GreensFunction2DRadAbs(); + + const Real geth() const + { + return this->h; + } + + const Real geta() const + { + return this->a; + } + + const Real getestimated_alpha_root_distance_() const + { + return this->estimated_alpha_root_distance_; + } + + virtual Real drawTime( const Real rnd) const; + + virtual EventKind drawEventType( const Real rnd, + const Real t ) const; + + virtual Real drawR( const Real rnd, + const Real t ) const; + + virtual Real drawTheta( const Real rnd, + const Real r, + const Real t ) const; + + const Real f_alpha0( const Real alpha ) const; + + const Real f_alpha( const Real alpha, const Integer n ) const; + + + const Real p_survival( const Real t) const; + + const Real p_survival_table( const Real t, + RealVector& table ) const; + + + const Real leaves( const Real t) const; + + const Real leavea( const Real t) const; + + const Real p_m( const Integer n, const Real r, const Real t ) const; + + const Real dp_m_at_a( const Integer m, const Real t ) const; + + + const Real p_m_alpha( const unsigned int n, + const unsigned int m, + const Real r, + const Real t ) const; + + const Real dp_m_alpha_at_a( const unsigned int n, + const unsigned int m, + const Real t ) const; + + // methods below are kept public for debugging purpose. + + std::string dump() const; + + + const void GiveRootInterval( Real& low, + Real& high, + const Integer n + ) const; + + + const void GiveRootIntervalSimple( Real& low, Real& high, + const Integer n, const Integer i + ) const; + + const Real getAlphaRoot0( const Real low, + const Real high + ) const; + + const Real getAlphaRootN( const Real low, + const Real high, + const Integer n + ) const; + + const Real getAlphaRoot( const Real high, const Real low, const Integer n + ) const; + + const void decideOnMethod2(size_t n, RealVector::size_type i) const; + + const void + needToSwitchBackMethod1( size_t n, RealVector::size_type i ) const; + + const Real getAlpha( size_t n, RealVector::size_type i ) const; + + + const Real p_survival_i( const Real alpha) const; + + const Real calc_A_i_0( const Real alpha) const; + + const Real leaves_i( const Real alpha) const; + + const boost::tuple Y0J0J1_constants ( const Real alpha, + const Real t) const; + +// const Real getAlpha( const size_t n, const RealVector::size_type i ) const; + +// const Real getAlpha0( const RealVector::size_type i ) const; + + Real givePDFTheta( const Real theta, + const Real r, + const Real t ) const; + + Real givePDFR( const Real r, const Real t ) const; + + void dumpRoots( int n ); + +protected: + + void clearAlphaTable() const; + + + RealVector& getAlphaTable( const size_t n ) const + { + return this->alphaTable[n]; + } + + const Real p_int_r_table( const Real r, + const RealVector& Y0_aAnTable, + const RealVector& J0_aAnTable, + const RealVector& Y0J1J0Y1Table ) const; + + const Real ip_theta_table( const Real theta, + const RealVector& p_nTable ) const; + + const Real p_survival_i_exp_table( const unsigned int i, + const Real t, + const RealVector& table ) const; + + const Real leavea_i_exp( const unsigned int i, + const Real alpha) const; + + const Real leaves_i_exp( const unsigned int i, + const Real alpha) const; + + const Real ip_theta_n( const unsigned int m, + const Real theta, + const RealVector& p_nTable ) const; + + + const Real p_int_r_i_exp_table( const unsigned int i, + const Real r, + const RealVector& Y0_aAnTable, + const RealVector& J0_aAnTable, + const RealVector& Y0J1J0Y1Table ) const; + + void createPsurvTable( RealVector& table) const; + + void createY0J0Tables( RealVector& Y0_Table, RealVector& J0_Table, RealVector& Y0J1J0Y1_Table, + const Real t ) const; + + void makep_mTable( RealVector& p_mTable, + const Real r, + const Real t ) const; + + void makedp_m_at_aTable( RealVector& p_mTable, + const Real t ) const; + + const unsigned int guess_maxi( const Real t ) const; + + struct f_alpha0_aux_params + { + const GreensFunction2DRadAbs* const gf; + const Real value; + }; + + static const Real + f_alpha0_aux_F( const Real alpha, + const f_alpha0_aux_params* const params ); + + + struct f_alpha_aux_params + { + const GreensFunction2DRadAbs* const gf; + const Integer n; + Real value; + }; + + static const Real + f_alpha_aux_F( const Real alpha, + const f_alpha_aux_params* const params ); + + struct p_survival_table_params + { + const GreensFunction2DRadAbs* const gf; +// const Real r0; + RealVector& table; + const Real rnd; + }; + + static const Real + p_survival_table_F( const Real t, + const p_survival_table_params* const params ); + + struct p_int_r_params + { + const GreensFunction2DRadAbs* const gf; + const Real t; +// const Real r0; + const RealVector& Y0_aAnTable; + const RealVector& J0_aAnTable; + const RealVector& Y0J1J0Y1Table; + const Real rnd; + }; + + static const Real + p_int_r_F( const Real r, + const p_int_r_params* const params ); + + struct ip_theta_params + { + const GreensFunction2DRadAbs* const gf; + const Real r; +// const Real r0; + const Real t; + const RealVector& p_nTable; + const Real value; + }; + + static const Real + ip_theta_F( const Real theta, + const ip_theta_params* const params ); + +private: + + const Real h; + const Real a; + + // Tables that hold calculated roots (y=0) of "alpha" function for each + // order n. + mutable boost::array alphaTable; + + // Constants used in the roots of f_alpha() finding algorithm. + // ==== + // + // This constant will simply be M_PI/(a-Sigma), the value to which the + // distance between roots of f_alpha() should converge. + const Real estimated_alpha_root_distance_; + // + // Table which tells us at which x we're left with scanning the alpha + // function for a sign change, for a given order n. (A sign change would + // indicate a root (y=0) lies between the boundaries of the "scanned" + // interval.) + // If x_scan[n] < 0, this indicates scanning is no longer required + // because the distance between the roots is converging and within + // boundaries that allow the direct use of the estimate interval width + // pi/(sigma-a). + // Initial values are set by constructor. + mutable boost::array alpha_x_scan_table_; + // + // Table that keeps track of the number of previous subsequent roots that + // we're within margin of the distance to which they're expected to + // converge. + mutable boost::array alpha_correctly_estimated_; + + static Logger& log_; + +}; + + + +#endif // __FIRSTPASSAGEPAIRGREENSFUNCTION2D_HPP diff --git a/GreensFunction3D.cpp b/GreensFunction3D.cpp index b44b3c57..91c36ff2 100644 --- a/GreensFunction3D.cpp +++ b/GreensFunction3D.cpp @@ -86,17 +86,17 @@ Real GreensFunction3D::drawR(Real rnd, Real t) const // input parameter range checks. if ( !(rnd <= 1.0 && rnd >= 0.0 ) ) { - throw std::invalid_argument( ( boost::format( "rnd <= 1.0 && rnd >= 0.0 : rnd=%.16g" ) % rnd ).str() ); + throw std::invalid_argument( ( boost::format( "GreensFunction3D: rnd <= 1.0 && rnd >= 0.0 : rnd=%.16g" ) % rnd ).str() ); } if ( !(r0 >= 0.0 ) ) { - throw std::invalid_argument( ( boost::format( "r0 >= 0.0 : r0=%.16g" ) % r0 ).str() ); + throw std::invalid_argument( ( boost::format( "GreensFunction3D: r0 >= 0.0 : r0=%.16g" ) % r0 ).str() ); } if ( !(t >= 0.0 ) ) { - throw std::invalid_argument( ( boost::format( "t >= 0.0 : t=%.16g" ) % t ).str() ); + throw std::invalid_argument( ( boost::format( "GreensFunction3D: t >= 0.0 : t=%.16g" ) % t ).str() ); } @@ -150,7 +150,7 @@ Real GreensFunction3D::drawR(Real rnd, Real t) const if( i >= maxIter ) { gsl_root_fsolver_free( solver ); - throw std::runtime_error("drawR: failed to converge"); + throw std::runtime_error("GreensFunction3D: drawR: failed to converge"); } } else @@ -185,22 +185,22 @@ Real GreensFunction3D::drawTheta(Real rnd, Real r, Real t) const // input parameter range checks. if ( !(rnd <= 1.0 && rnd >= 0.0 ) ) { - throw std::invalid_argument( ( boost::format( "rnd <= 1.0 && rnd >= 0.0 : rnd=%.16g" ) % rnd ).str() ); + throw std::invalid_argument( ( boost::format( "GreensFunction3D: rnd <= 1.0 && rnd >= 0.0 : rnd=%.16g" ) % rnd ).str() ); } if ( !(r >= 0.0 ) ) { - throw std::invalid_argument( ( boost::format( "r >= 0.0 : r=%.16g" ) % r ).str() ); + throw std::invalid_argument( ( boost::format( "GreensFunction3D: r >= 0.0 : r=%.16g" ) % r ).str() ); } if ( !(r0 >= 0.0 ) ) { - throw std::invalid_argument( ( boost::format( "r0 >= 0.0 : r0=%.16g" ) % r0 ).str() ); + throw std::invalid_argument( ( boost::format( "GreensFunction3D: r0 >= 0.0 : r0=%.16g" ) % r0 ).str() ); } if ( !(t >= 0.0 ) ) { - throw std::invalid_argument( ( boost::format( "t >= 0.0 : t=%.16g" ) % t ).str() ); + throw std::invalid_argument( ( boost::format( "GreensFunction3D: t >= 0.0 : t=%.16g" ) % t ).str() ); } @@ -241,7 +241,7 @@ Real GreensFunction3D::drawTheta(Real rnd, Real r, Real t) const if( i >= maxIter ) { gsl_root_fsolver_free( solver ); - throw std::runtime_error("drawTheta: failed to converge"); + throw std::runtime_error("GreensFunction3D: drawTheta: failed to converge"); } } else diff --git a/GreensFunction3DAbs.cpp b/GreensFunction3DAbs.cpp index 6d141701..feef57e7 100644 --- a/GreensFunction3DAbs.cpp +++ b/GreensFunction3DAbs.cpp @@ -31,7 +31,7 @@ GF3DA::GreensFunction3DAbs(Real D, Real r0, Real a) { if (a < 0.0) { - throw std::invalid_argument((boost::format("a >= 0.0 : a=%.16g") % a).str()); + throw std::invalid_argument((boost::format("GreensFunction3DAbs: a >= 0.0 : a=%.16g") % a).str()); } } @@ -271,22 +271,22 @@ Real GF3DA::p_theta(Real theta, Real r, Real t) const if (!(theta >= 0.0 && theta <= M_PI)) { - throw std::invalid_argument((boost::format("theta >= 0.0 && theta <= M_PI : theta=%.16g, M_PI=%.16g") % theta % M_PI).str()); + throw std::invalid_argument((boost::format("GreensFunction3DAbs: theta >= 0.0 && theta <= M_PI : theta=%.16g, M_PI=%.16g") % theta % M_PI).str()); } if (!(r >= 0 && r < a)) { - throw std::invalid_argument((boost::format("r >= 0 && r < a : r=%.16g, a=%.16g") % r % a).str()); + throw std::invalid_argument((boost::format("GreensFunction3DAbs: r >= 0 && r < a : r=%.16g, a=%.16g") % r % a).str()); } if (!(r0 >= 0 && r0 < a)) { - throw std::invalid_argument((boost::format("r0 >= 0 && r0 < a : r0=%.16g, a=%.16g") % r0 % a).str()); + throw std::invalid_argument((boost::format("GreensFunction3DAbs: r0 >= 0 && r0 < a : r0=%.16g, a=%.16g") % r0 % a).str()); } if (!(t >= 0.0)) { - throw std::invalid_argument((boost::format("t >= 0.0 : t=%.16g") % t).str()); + throw std::invalid_argument((boost::format("GreensFunction3DAbs: t >= 0.0 : t=%.16g") % t).str()); } } @@ -315,23 +315,23 @@ Real GF3DA::ip_theta(Real theta, Real r, Real t) const if (!(theta >= 0.0 && theta <= M_PI)) { - throw std::invalid_argument((boost::format("theta >= 0.0 && theta <= M_PI : theta=%.16g, M_PI=%.16g") % theta % M_PI).str()); + throw std::invalid_argument((boost::format("GreensFunction3DAbs: theta >= 0.0 && theta <= M_PI : theta=%.16g, M_PI=%.16g") % theta % M_PI).str()); } // r \in (sigma, a) if (!(r >= 0.0 && r < a)) { - throw std::invalid_argument((boost::format("r >= 0.0 && r < a : r=%.16g, a=%.16g") % r % a).str()); + throw std::invalid_argument((boost::format("GreensFunction3DAbs: r >= 0.0 && r < a : r=%.16g, a=%.16g") % r % a).str()); } if (!(r0 >= 0.0 && r0 < a)) { - throw std::invalid_argument((boost::format("r0 >= 0.0 && r0 < a : r0=%.16g, a=%.16g") % r0 % a).str()); + throw std::invalid_argument((boost::format("GreensFunction3DAbs: r0 >= 0.0 && r0 < a : r0=%.16g, a=%.16g") % r0 % a).str()); } if (!(t >= 0.0)) { - throw std::invalid_argument((boost::format("t >= 0.0 : t=%.16g") % t).str()); + throw std::invalid_argument((boost::format("GreensFunction3DAbs: t >= 0.0 : t=%.16g") % t).str()); } } @@ -504,7 +504,7 @@ GF3DA::dp_theta(Real theta, Real r, Real t) const if (!(theta >= 0.0 && theta <= M_PI)) { - throw std::invalid_argument((boost::format("theta >= 0.0 && theta <= M_PI : theta=%.16g, M_PI=%.16g") % theta % M_PI).str()); + throw std::invalid_argument((boost::format("GreensFunction3DAbs: theta >= 0.0 && theta <= M_PI : theta=%.16g, M_PI=%.16g") % theta % M_PI).str()); } @@ -512,17 +512,17 @@ GF3DA::dp_theta(Real theta, Real r, Real t) const // defined at r == sigma and r == a. if (!(r >= 0.0 && r <= a)) { - throw std::invalid_argument((boost::format("r >= 0.0 && r <= a : r=%.16g, a=%.16g") % r % a).str()); + throw std::invalid_argument((boost::format("GreensFunction3DAbs: r >= 0.0 && r <= a : r=%.16g, a=%.16g") % r % a).str()); } if (!(r0 >= 0.0 && r0 < a)) { - throw std::invalid_argument((boost::format("r0 >= 0.0 && r0 < a : r0=%.16g, a=%.16g") % r0 % a).str()); + throw std::invalid_argument((boost::format("GreensFunction3DAbs: r0 >= 0.0 && r0 < a : r0=%.16g, a=%.16g") % r0 % a).str()); } if (!(t >= 0.0)) { - throw std::invalid_argument((boost::format("t >= 0.0 : t=%.16g") % t).str()); + throw std::invalid_argument((boost::format("GreensFunction3DAbs: t >= 0.0 : t=%.16g") % t).str()); } } @@ -549,23 +549,23 @@ GF3DA::idp_theta(Real theta, Real r, Real t) const if (!(theta >= 0.0 && theta <= M_PI)) { - throw std::invalid_argument((boost::format("theta >= 0.0 && theta <= M_PI : theta=%.16g, M_PI=%.16g") % theta % M_PI).str()); + throw std::invalid_argument((boost::format("GreensFunction3DAbs: theta >= 0.0 && theta <= M_PI : theta=%.16g, M_PI=%.16g") % theta % M_PI).str()); } // r \in [ sigma, a ] if (!(r >= 0.0 && r <= a)) { - throw std::invalid_argument((boost::format("r >= 0.0 && r <= a : r=%.16g, a=%.16g") % r % a).str()); + throw std::invalid_argument((boost::format("GreensFunction3DAbs: r >= 0.0 && r <= a : r=%.16g, a=%.16g") % r % a).str()); } if (!(r0 >= 0.0 && r0 < a)) { - throw std::invalid_argument((boost::format("r0 >= 0.0 && r0 < a : r0=%.16g, a=%.16g") % r0 % a).str()); + throw std::invalid_argument((boost::format("GreensFunction3DAbs: r0 >= 0.0 && r0 < a : r0=%.16g, a=%.16g") % r0 % a).str()); } if (!(t >= 0.0)) { - throw std::invalid_argument((boost::format("t >= 0.0 : t=%.16g") % t).str()); + throw std::invalid_argument((boost::format("GreensFunction3DAbs: t >= 0.0 : t=%.16g") % t).str()); } } @@ -591,12 +591,12 @@ GF3DA::drawTime(Real rnd) const if (!(rnd <= 1.0 && rnd >= 0.0)) { - throw std::invalid_argument((boost::format("rnd <= 1.0 && rnd >= 0.0 : rnd=%.16g") % rnd).str()); + throw std::invalid_argument((boost::format("GreensFunction3DAbs: rnd <= 1.0 && rnd >= 0.0 : rnd=%.16g") % rnd).str()); } if (!(r0 >= 0.0 && r0 <= a)) { - throw std::invalid_argument((boost::format("r0 >= 0.0 && r0 <= a : r0=%.16g, a=%.16g") % r0 % a).str()); + throw std::invalid_argument((boost::format("GreensFunction3DAbs: r0 >= 0.0 && r0 <= a : r0=%.16g, a=%.16g") % r0 % a).str()); } @@ -623,7 +623,7 @@ GF3DA::drawTime(Real rnd) const if (fabs(high) >= 1e10) { throw std::runtime_error( - (boost::format("couldn't adjust high. F(%.16g) = %.16g; r0=%.16g, %s") % + (boost::format("GreensFunction3DAbs: couldn't adjust high. F(%.16g) = %.16g; r0=%.16g, %s") % high % GSL_FN_EVAL(&F, high) % r0 % dump()).str()); } } @@ -670,7 +670,7 @@ GF3DA::drawTime(Real rnd) const if (i >= maxIter) { gsl_root_fsolver_free(solver); - throw std::runtime_error("drawTime: failed to converge"); + throw std::runtime_error("GreensFunction3DAbs: drawTime: failed to converge"); } } else @@ -694,12 +694,12 @@ GF3DA::drawR(Real rnd, Real t) const if (!(rnd <= 1.0 && rnd >= 0.0)) { - throw std::invalid_argument((boost::format("rnd <= 1.0 && rnd >= 0.0 : rnd=%.16g") % rnd).str()); + throw std::invalid_argument((boost::format("GreensFunction3DAbs: rnd <= 1.0 && rnd >= 0.0 : rnd=%.16g") % rnd).str()); } if (!(r0 >= 0.0 && r0 < a)) { - throw std::invalid_argument((boost::format("r0 >= 0.0 && r0 < a : r0=%.16g, a=%.16g") % r0 % a).str()); + throw std::invalid_argument((boost::format("GreensFunction3DAbs: r0 >= 0.0 && r0 < a : r0=%.16g, a=%.16g") % r0 % a).str()); } @@ -753,7 +753,7 @@ GF3DA::drawR(Real rnd, Real t) const if (i >= maxIter) { gsl_root_fsolver_free(solver); - throw std::runtime_error("drawR: failed to converge"); + throw std::runtime_error("GreensFunction3DAbs: drawR: failed to converge"); } } else @@ -780,22 +780,22 @@ GF3DA::drawTheta(Real rnd, Real r, Real t) const // input parameter range checks. if (!(rnd <= 1.0 && rnd >= 0.0)) { - throw std::invalid_argument((boost::format("rnd <= 1.0 && rnd >= 0.0 : rnd=%.16g") % rnd).str()); + throw std::invalid_argument((boost::format("GreensFunction3DAbs: rnd <= 1.0 && rnd >= 0.0 : rnd=%.16g") % rnd).str()); } if (!(r0 >= 0.0 && r0 < a)) { - throw std::invalid_argument((boost::format("r0 >= 0.0 && r0 < a : r0=%.16g, a=%.16g") % r0 % a).str()); + throw std::invalid_argument((boost::format("GreensFunction3DAbs: r0 >= 0.0 && r0 < a : r0=%.16g, a=%.16g") % r0 % a).str()); } if (!(r >= 0.0 && r <= a)) { - throw std::invalid_argument((boost::format("r >= 0.0 && r <= a : r=%.16g, a=%.16g") % r % a).str()); + throw std::invalid_argument((boost::format("GreensFunction3DAbs: r >= 0.0 && r <= a : r=%.16g, a=%.16g") % r % a).str()); } if (!(t >= 0.0)) { - throw std::invalid_argument((boost::format("t >= 0.0 : t=%.16g") % t).str()); + throw std::invalid_argument((boost::format("GreensFunction3DAbs: t >= 0.0 : t=%.16g") % t).str()); } @@ -847,7 +847,7 @@ GF3DA::drawTheta(Real rnd, Real r, Real t) const if (i >= maxIter) { gsl_root_fsolver_free(solver); - throw std::runtime_error("drawTheta: failed to converge"); + throw std::runtime_error("GreensFunction3DAbs: drawTheta: failed to converge"); } } else diff --git a/GreensFunction3DAbsSym.cpp b/GreensFunction3DAbsSym.cpp index d1496c54..7af011dc 100644 --- a/GreensFunction3DAbsSym.cpp +++ b/GreensFunction3DAbsSym.cpp @@ -31,7 +31,7 @@ Real GreensFunction3DAbsSym::ellipticTheta4Zero(Real q) { if (fabs(q) > 1.0) { - throw std::invalid_argument((boost::format("fabs(%.16g) <= 1.0") % q).str()); + throw std::invalid_argument((boost::format("GreensFunction3DAbsSym: fabs(%.16g) <= 1.0") % q).str()); } // et4z(1 - 1e4) ~= 7.2e-23 @@ -215,7 +215,7 @@ Real GreensFunction3DAbsSym::drawTime(Real rnd) const if (rnd >= 1.0 || rnd < 0.0) { - throw std::invalid_argument((boost::format("0.0 <= %.16g < 1.0") % rnd).str()); + throw std::invalid_argument((boost::format("GreensFunction3DAbsSym: 0.0 <= %.16g < 1.0") % rnd).str()); } const Real a(geta()); @@ -261,7 +261,7 @@ Real GreensFunction3DAbsSym::drawTime(Real rnd) const if (fabs(high) >= t_guess * 1e6) { throw std::runtime_error( - (boost::format("couldn't adjust high. F(%.16g) = %.16g; %s") % + (boost::format("GreensFunction3DAbsSym: couldn't adjust high. F(%.16g) = %.16g; %s") % high % GSL_FN_EVAL(&F, high) % boost::lexical_cast(*this)).str()); } @@ -330,12 +330,12 @@ Real GreensFunction3DAbsSym::drawR(Real rnd, Real t) const { if (rnd >= 1.0 || rnd < 0.0) { - throw std::invalid_argument((boost::format("0.0 <= %.16g < 1.0") % rnd).str()); + throw std::invalid_argument((boost::format("GreensFunction3DAbsSym: 0.0 <= %.16g < 1.0") % rnd).str()); } if (t < 0.0) { - throw std::invalid_argument((boost::format("%.16g < 0.0") % t).str()); + throw std::invalid_argument((boost::format("GreensFunction3DAbsSym: %.16g < 0.0") % t).str()); } const Real a(geta()); diff --git a/GreensFunction3DAbsSym.hpp b/GreensFunction3DAbsSym.hpp index 6de3dcb4..534ea62f 100644 --- a/GreensFunction3DAbsSym.hpp +++ b/GreensFunction3DAbsSym.hpp @@ -34,7 +34,7 @@ class GreensFunction3DAbsSym: public GreensFunction Real p_int_r_free(Real r, Real t) const; Real p_r_fourier(Real r, Real t) const; - + std::string dump() const; const char* getName() const diff --git a/GreensFunction3DRadAbs.cpp b/GreensFunction3DRadAbs.cpp index c1e57acf..b7152ad4 100644 --- a/GreensFunction3DRadAbs.cpp +++ b/GreensFunction3DRadAbs.cpp @@ -40,7 +40,7 @@ GreensFunction3DRadAbs::GreensFunction3DRadAbs( if (a < sigma) { - throw std::invalid_argument((boost::format("a >= sigma : a=%.16g, sigma=%.16g") % a % sigma).str()); + throw std::invalid_argument((boost::format("GreensFunction3DRadAbs: a >= sigma : a=%.16g, sigma=%.16g") % a % sigma).str()); } clearAlphaTable(); } @@ -118,7 +118,7 @@ Real GreensFunction3DRadAbs::alpha0_i(Integer i) const { if (!(i >= 0)) { - throw std::out_of_range((boost::format("i >= 0 : i=%.16g") % i).str()); + throw std::out_of_range((boost::format("GreensFunction3DRadAbs: i >= 0 : i=%.16g") % i).str()); } @@ -161,7 +161,7 @@ Real GreensFunction3DRadAbs::alpha0_i(Integer i) const if (j >= maxIter) { gsl_root_fsolver_free(solver); - throw std::runtime_error("alpha0_i: failed to converge"); + throw std::runtime_error("GreensFunction3DRadAbs: alpha0_i: failed to converge"); } } else @@ -476,7 +476,7 @@ GreensFunction3DRadAbs::alpha_i(Integer i, Integer n, if (k >= maxIter) { gsl_root_fsolver_free(solver); - throw std::runtime_error("alpha_i: failed to converge"); + throw std::runtime_error("GreensFunction3DRadAbs: alpha_i: failed to converge"); } } else @@ -557,7 +557,7 @@ GreensFunction3DRadAbs::updateAlphaTable(const unsigned int n, { if (!(n >= 0 && n <= this->MAX_ORDER)) { - throw std::range_error((boost::format("n >= 0 && n <= this->MAX_ORDER : n=%.16g, this->MAX_ORDER=%.16g") % n % this->MAX_ORDER).str()); + throw std::range_error((boost::format("GreensFunction3DRadAbs: n >= 0 && n <= this->MAX_ORDER : n=%.16g, this->MAX_ORDER=%.16g") % n % this->MAX_ORDER).str()); } @@ -1171,7 +1171,7 @@ GreensFunction3DRadAbs::p_survival_table(Real t, RealVector& psurvTable) const if (psurvTable.size() < maxi + 1) { - IGNORE_RETURN getAlpha0(maxi); // this updates the table + getAlpha0(maxi); // this updates the table this->createPsurvTable(psurvTable); } @@ -1270,10 +1270,10 @@ struct p_survival_params const Real rnd; }; -static Real p_survival_F(Real t, p_survival_params const* params) -{ - return params->rnd - params->gf->p_survival(t); -} +//NOT USED static Real p_survival_F(Real t, p_survival_params const* params) +//{ +// return params->rnd - params->gf->p_survival(t); +//} struct p_survival_2i_params { @@ -1281,11 +1281,11 @@ struct p_survival_2i_params const Real t; }; -static Real p_survival_2i_F(Real ri, p_survival_2i_params const* params) -{ - return params->gf->p_survival_2i_exp(static_cast(ri), - params->t); -} +//NOT USED static Real p_survival_2i_F(Real ri, p_survival_2i_params const* params) +//{ +// return params->gf->p_survival_2i_exp(static_cast(ri), +// params->t); +//} struct p_survival_i_alpha_params { @@ -1293,11 +1293,11 @@ struct p_survival_i_alpha_params const Real t; }; -static Real p_survival_i_alpha_F(Real alpha, - p_survival_i_alpha_params const* params) -{ - return params->gf->p_survival_i_alpha(alpha, params->t); -} +//NOT USED static Real p_survival_i_alpha_F(Real alpha, +// p_survival_i_alpha_params const* params) +//{ +// return params->gf->p_survival_i_alpha(alpha, params->t); +//} struct p_leave_params { @@ -1333,12 +1333,12 @@ Real GreensFunction3DRadAbs::drawTime(Real rnd) const if (!(rnd < 1.0 && rnd >= 0.0)) { - throw std::invalid_argument((boost::format("rnd < 1.0 && rnd >= 0.0 : rnd=%.16g") % rnd).str()); + throw std::invalid_argument((boost::format("GreensFunction3DRadAbs: rnd < 1.0 && rnd >= 0.0 : rnd=%.16g") % rnd).str()); } if (!(r0 >= sigma && r0 <= a)) { - throw std::invalid_argument((boost::format("r0 >= sigma && r0 <= a : r0=%.16g, sigma=%.16g, a=%.16g") % r0 % sigma % a).str()); + throw std::invalid_argument((boost::format("GreensFunction3DRadAbs: r0 >= sigma && r0 <= a : r0=%.16g, sigma=%.16g, a=%.16g") % r0 % sigma % a).str()); } @@ -1396,7 +1396,7 @@ Real GreensFunction3DRadAbs::drawTime(Real rnd) const { throw std::runtime_error( (boost::format( - "couldn't adjust high. F(%.16g) = %.16g; r0 = %.16g, %s") % + "GreensFunction3DRadAbs: couldn't adjust high. F(%.16g) = %.16g; r0 = %.16g, %s") % high % GSL_FN_EVAL(&F, high) % r0 % dump()).str()); } @@ -1422,7 +1422,7 @@ Real GreensFunction3DRadAbs::drawTime(Real rnd) const if (fabs(low) <= minT || fabs(low_value - low_value_prev) < TOLERANCE) { - log_.info("couldn't adjust low. F(%.16g) = %.16g; r0 = %.16g, %s", + log_.info("GreensFunction3DRadAbs: couldn't adjust low. F(%.16g) = %.16g; r0 = %.16g, %s", low, GSL_FN_EVAL(&F, low), r0, dump().c_str()); log_.info("returning %.16g", low); @@ -1455,17 +1455,17 @@ GreensFunction3DRadAbs::drawEventType(Real rnd, Real t) const if (!(rnd < 1.0 && rnd >= 0.0)) { - throw std::invalid_argument((boost::format("rnd < 1.0 && rnd >= 0.0 : rnd=%.16g") % rnd).str()); + throw std::invalid_argument((boost::format("GreensFunction3DRadAbs: rnd < 1.0 && rnd >= 0.0 : rnd=%.16g") % rnd).str()); } if (!(r0 >= sigma && r0 < a)) { - throw std::invalid_argument((boost::format("r0 >= sigma && r0 < a : r0=%.16g, sigma=%.16g, a=%.16g") % r0 % sigma % a).str()); + throw std::invalid_argument((boost::format("GreensFunction3DRadAbs: r0 >= sigma && r0 < a : r0=%.16g, sigma=%.16g, a=%.16g") % r0 % sigma % a).str()); } if (!(t > 0.0)) { - throw std::invalid_argument((boost::format("t > 0.0 : t=%.16g") % t).str()); + throw std::invalid_argument((boost::format("GreensFunction3DRadAbs: t > 0.0 : t=%.16g") % t).str()); } @@ -1545,7 +1545,7 @@ GreensFunction3DRadAbs::drawPleavea(gsl_function const& F, { throw std::runtime_error( (boost::format( - "couldn't adjust high. Fa(%.16g) = %.16g; r0 = %.16g, %s") % + "GreensFunction3DRadAbs: couldn't adjust high. Fa(%.16g) = %.16g; r0 = %.16g, %s") % high % GSL_FN_EVAL(&F, high) % r0 % dump()).str()); } @@ -1625,7 +1625,7 @@ GreensFunction3DRadAbs::drawPleaves(gsl_function const& F, { throw std::runtime_error( (boost::format( - "couldn't adjust high. Fs(%.16g) = %.16g; r0 = %.16g, %s") % + "GreensFunction3DRadAbs: couldn't adjust high. Fs(%.16g) = %.16g; r0 = %.16g, %s") % high % GSL_FN_EVAL(&F, high) % r0 % dump()).str()); } @@ -1636,7 +1636,7 @@ GreensFunction3DRadAbs::drawPleaves(gsl_function const& F, } else { - Real low_value_prev(value); + //Real low_value_prev(value); low *= .1; for (;;) @@ -1661,7 +1661,7 @@ GreensFunction3DRadAbs::drawPleaves(gsl_function const& F, minT, low, GSL_FN_EVAL(&F, low), r0, dump().c_str()); return minT; } - low_value_prev = low_value; + //low_value_prev = low_value; log_.info("drawTime2: adjusting low: %.16g, Fs = %.16g", low, low_value); low *= .1; @@ -1685,12 +1685,12 @@ Real GreensFunction3DRadAbs::drawR(Real rnd, Real t) const if (!(rnd < 1.0 && rnd >= 0.0)) { - throw std::invalid_argument((boost::format("rnd < 1.0 && rnd >= 0.0 : rnd=%.16g") % rnd).str()); + throw std::invalid_argument((boost::format("GreensFunction3DRadAbs: rnd < 1.0 && rnd >= 0.0 : rnd=%.16g") % rnd).str()); } if (!(r0 >= sigma && r0 < a)) { - throw std::invalid_argument((boost::format("r0 >= sigma && r0 < a : r0=%.16g, sigma=%.16g, a=%.16g") % r0 % sigma % a).str()); + throw std::invalid_argument((boost::format("GreensFunction3DRadAbs: r0 >= sigma && r0 < a : r0=%.16g, sigma=%.16g, a=%.16g") % r0 % sigma % a).str()); } @@ -1804,7 +1804,7 @@ Real GreensFunction3DRadAbs::drawR(Real rnd, Real t) const if (i >= maxIter) { gsl_root_fsolver_free(solver); - throw std::runtime_error("drawR: failed to converge"); + throw std::runtime_error("GreensFunction3DRadAbs: drawR: failed to converge"); } } else @@ -2126,23 +2126,23 @@ GreensFunction3DRadAbs::p_theta(Real theta, Real r, Real t) const if (!(theta >= 0.0 && theta <= M_PI)) { - throw std::invalid_argument((boost::format("theta >= 0.0 && theta <= M_PI : theta=%.16g, M_PI=%.16g") % theta % M_PI).str()); + throw std::invalid_argument((boost::format("GreensFunction3DRadAbs: theta >= 0.0 && theta <= M_PI : theta=%.16g, M_PI=%.16g") % theta % M_PI).str()); } // r \in (sigma, a); not defined at r == sigma and r == a. if (!(r >= sigma && r < a)) { - throw std::invalid_argument((boost::format("r >= sigma && r < a : r=%.16g, sigma=%.16g, a=%.16g") % r % sigma % a).str()); + throw std::invalid_argument((boost::format("GreensFunction3DRadAbs: r >= sigma && r < a : r=%.16g, sigma=%.16g, a=%.16g") % r % sigma % a).str()); } if (!(r0 >= sigma && r0 < a)) { - throw std::invalid_argument((boost::format("r0 >= sigma && r0 < a : r0=%.16g, sigma=%.16g, a=%.16g") % r0 % sigma % a).str()); + throw std::invalid_argument((boost::format("GreensFunction3DRadAbs: r0 >= sigma && r0 < a : r0=%.16g, sigma=%.16g, a=%.16g") % r0 % sigma % a).str()); } if (!(t >= 0.0)) { - throw std::invalid_argument((boost::format("t >= 0.0 : t=%.16g") % t).str()); + throw std::invalid_argument((boost::format("GreensFunction3DRadAbs: t >= 0.0 : t=%.16g") % t).str()); } } @@ -2169,7 +2169,7 @@ Real GreensFunction3DRadAbs::dp_theta(Real theta, Real r, Real t) const if (!(theta >= 0.0 && theta <= M_PI)) { - throw std::invalid_argument((boost::format("theta >= 0.0 && theta <= M_PI : theta=%.16g, M_PI=%.16g") % theta % M_PI).str()); + throw std::invalid_argument((boost::format("GreensFunction3DRadAbs: theta >= 0.0 && theta <= M_PI : theta=%.16g, M_PI=%.16g") % theta % M_PI).str()); } @@ -2177,17 +2177,17 @@ Real GreensFunction3DRadAbs::dp_theta(Real theta, Real r, Real t) const // defined at r == sigma and r == a. if (!(r >= sigma && r <= a)) { - throw std::invalid_argument((boost::format("r >= sigma && r <= a : r=%.16g, sigma=%.16g, a=%.16g") % r % sigma % a).str()); + throw std::invalid_argument((boost::format("GreensFunction3DRadAbs: r >= sigma && r <= a : r=%.16g, sigma=%.16g, a=%.16g") % r % sigma % a).str()); } if (!(r0 >= sigma && r0 < a)) { - throw std::invalid_argument((boost::format("r0 >= sigma && r0 < a : r0=%.16g, sigma=%.16g, a=%.16g") % r0 % sigma % a).str()); + throw std::invalid_argument((boost::format("GreensFunction3DRadAbs: r0 >= sigma && r0 < a : r0=%.16g, sigma=%.16g, a=%.16g") % r0 % sigma % a).str()); } if (!(t >= 0.0)) { - throw std::invalid_argument((boost::format("t >= 0.0 : t=%.16g") % t).str()); + throw std::invalid_argument((boost::format("GreensFunction3DRadAbs: t >= 0.0 : t=%.16g") % t).str()); } } @@ -2285,23 +2285,23 @@ GreensFunction3DRadAbs::ip_theta(Real theta, Real r, Real t) const if (!(theta >= 0.0 && theta <= M_PI)) { - throw std::invalid_argument((boost::format("theta >= 0.0 && theta <= M_PI : theta=%.16g, M_PI=%.16g") % theta % M_PI).str()); + throw std::invalid_argument((boost::format("GreensFunction3DRadAbs: theta >= 0.0 && theta <= M_PI : theta=%.16g, M_PI=%.16g") % theta % M_PI).str()); } // r \in (sigma, a) if (!(r >= sigma && r < a)) { - throw std::invalid_argument((boost::format("r >= sigma && r < a : r=%.16g, sigma=%.16g, a=%.16g") % r % sigma % a).str()); + throw std::invalid_argument((boost::format("GreensFunction3DRadAbs: r >= sigma && r < a : r=%.16g, sigma=%.16g, a=%.16g") % r % sigma % a).str()); } if (!(r0 >= sigma && r0 < a)) { - throw std::invalid_argument((boost::format("r0 >= sigma && r0 < a : r0=%.16g, sigma=%.16g, a=%.16g") % r0 % sigma % a).str()); + throw std::invalid_argument((boost::format("GreensFunction3DRadAbs: r0 >= sigma && r0 < a : r0=%.16g, sigma=%.16g, a=%.16g") % r0 % sigma % a).str()); } if (!(t >= 0.0)) { - throw std::invalid_argument((boost::format("t >= 0.0 : t=%.16g") % t).str()); + throw std::invalid_argument((boost::format("GreensFunction3DRadAbs: t >= 0.0 : t=%.16g") % t).str()); } } @@ -2330,23 +2330,23 @@ GreensFunction3DRadAbs::idp_theta(Real theta, Real r, Real t) const if (!(theta >= 0.0 && theta <= M_PI)) { - throw std::invalid_argument((boost::format("theta >= 0.0 && theta <= M_PI : theta=%.16g, M_PI=%.16g") % theta % M_PI).str()); + throw std::invalid_argument((boost::format("GreensFunction3DRadAbs: theta >= 0.0 && theta <= M_PI : theta=%.16g, M_PI=%.16g") % theta % M_PI).str()); } // r \in [ sigma, a ] if (!(r >= sigma && r <= a)) { - throw std::invalid_argument((boost::format("r >= sigma && r <= a : r=%.16g, sigma=%.16g, a=%.16g") % r % sigma % a).str()); + throw std::invalid_argument((boost::format("GreensFunction3DRadAbs: r >= sigma && r <= a : r=%.16g, sigma=%.16g, a=%.16g") % r % sigma % a).str()); } if (!(r0 >= sigma && r0 < a)) { - throw std::invalid_argument((boost::format("r0 >= sigma && r0 < a : r0=%.16g, sigma=%.16g, a=%.16g") % r0 % sigma % a).str()); + throw std::invalid_argument((boost::format("GreensFunction3DRadAbs: r0 >= sigma && r0 < a : r0=%.16g, sigma=%.16g, a=%.16g") % r0 % sigma % a).str()); } if (!(t >= 0.0)) { - throw std::invalid_argument((boost::format("t >= 0.0 : t=%.16g") % t).str()); + throw std::invalid_argument((boost::format("GreensFunction3DRadAbs: t >= 0.0 : t=%.16g") % t).str()); } } @@ -2431,22 +2431,22 @@ GreensFunction3DRadAbs::drawTheta(Real rnd, Real r, Real t) const // input parameter range checks. if (!(rnd < 1.0 && rnd >= 0.0)) { - throw std::invalid_argument((boost::format("rnd < 1.0 && rnd >= 0.0 : rnd=%.16g") % rnd).str()); + throw std::invalid_argument((boost::format("GreensFunction3DRadAbs: rnd < 1.0 && rnd >= 0.0 : rnd=%.16g") % rnd).str()); } if (!(r0 >= sigma && r0 < a)) { - throw std::invalid_argument((boost::format("r0 >= sigma && r0 < a : r0=%.16g, sigma=%.16g, a=%.16g") % r0 % sigma % a).str()); + throw std::invalid_argument((boost::format("GreensFunction3DRadAbs: r0 >= sigma && r0 < a : r0=%.16g, sigma=%.16g, a=%.16g") % r0 % sigma % a).str()); } if (!(r >= sigma)) { - throw std::invalid_argument((boost::format("r >= sigma : r=%.16g, sigma=%.16g") % r % sigma).str()); + throw std::invalid_argument((boost::format("GreensFunction3DRadAbs: r >= sigma : r=%.16g, sigma=%.16g") % r % sigma).str()); } if (!(t >= 0.0)) { - throw std::invalid_argument((boost::format("t >= 0.0 : t=%.16g") % t).str()); + throw std::invalid_argument((boost::format("GreensFunction3DRadAbs: t >= 0.0 : t=%.16g") % t).str()); } @@ -2497,7 +2497,7 @@ GreensFunction3DRadAbs::drawTheta(Real rnd, Real r, Real t) const if (i >= maxIter) { gsl_root_fsolver_free(solver); - throw std::runtime_error("drawTheta: failed to converge"); + throw std::runtime_error("GreensFunction3DRadAbs: drawTheta: failed to converge"); } } else diff --git a/GreensFunction3DRadInf.cpp b/GreensFunction3DRadInf.cpp index 10faa458..2a89c2e5 100644 --- a/GreensFunction3DRadInf.cpp +++ b/GreensFunction3DRadInf.cpp @@ -234,12 +234,12 @@ Real GreensFunction3DRadInf::drawTime(Real rnd) const if (!(rnd < 1.0 && rnd >= 0.0)) { - throw std::invalid_argument((boost::format("rnd < 1.0 && rnd >= 0.0 : rnd=%.16g") % rnd).str()); + throw std::invalid_argument((boost::format("GreensFunction3DRadInf: rnd < 1.0 && rnd >= 0.0 : rnd=%.16g") % rnd).str()); } if (!(r0 >= sigma)) { - throw std::invalid_argument((boost::format("r0 >= sigma : r0=%.16g, sigma=%.16g") % r0 % sigma).str()); + throw std::invalid_argument((boost::format("GreensFunction3DRadInf: r0 >= sigma : r0=%.16g, sigma=%.16g") % r0 % sigma).str()); } @@ -283,7 +283,7 @@ Real GreensFunction3DRadInf::drawTime(Real rnd) const if(i >= maxIter) { gsl_root_fsolver_free(solver); - throw std::runtime_error("drawTime: failed to converge"); + throw std::runtime_error("GreensFunction3DRadInf: drawTime: failed to converge"); } } else @@ -310,17 +310,17 @@ Real GreensFunction3DRadInf::drawR(Real rnd, Real t) const if (!(rnd < 1.0 && rnd >= 0.0)) { - throw std::invalid_argument((boost::format("rnd < 1.0 && rnd >= 0.0 : rnd=%.16g") % rnd).str()); + throw std::invalid_argument((boost::format("GreensFunction3DRadInf: rnd < 1.0 && rnd >= 0.0 : rnd=%.16g") % rnd).str()); } if (!(r0 >= sigma)) { - throw std::invalid_argument((boost::format("r0 >= sigma : r0=%.16g, sigma=%.16g") % r0 % sigma).str()); + throw std::invalid_argument((boost::format("GreensFunction3DRadInf: r0 >= sigma : r0=%.16g, sigma=%.16g") % r0 % sigma).str()); } if (!(t >= 0.0)) { - throw std::invalid_argument((boost::format("t >= 0.0 : t=%.16g") % t).str()); + throw std::invalid_argument((boost::format("GreensFunction3DRadInf: t >= 0.0 : t=%.16g") % t).str()); } @@ -367,7 +367,7 @@ Real GreensFunction3DRadInf::drawR(Real rnd, Real t) const if(H > 20) { - throw std::runtime_error("drawR: H > 20 while adjusting upper bound of r"); + throw std::runtime_error("GreensFunction3DRadInf: drawR: H > 20 while adjusting upper bound of r"); } } @@ -426,7 +426,7 @@ Real GreensFunction3DRadInf::drawR(Real rnd, Real t) const if(i >= maxIter) { gsl_root_fsolver_free(solver); - throw std::runtime_error("drawR: failed to converge"); + throw std::runtime_error("GreensFunction3DRadInf: drawR: failed to converge"); } } else @@ -490,7 +490,7 @@ Real GreensFunction3DRadInf::ip_corr_n(unsigned int n, RealVector const& RnTable Real GreensFunction3DRadInf::p_corr_table(Real theta, Real r, Real t, RealVector const& RnTable) const { - const Index tableSize(RnTable.size()); + const size_t tableSize(RnTable.size()); if(tableSize == 0) { return 0.0; @@ -524,7 +524,7 @@ Real GreensFunction3DRadInf::p_corr_table(Real theta, Real r, Real t, RealVector Real GreensFunction3DRadInf::ip_corr_table(Real theta, Real r, Real t, RealVector const& RnTable) const { - const Index tableSize(RnTable.size()); + const size_t tableSize(RnTable.size()); if(tableSize == 0) { return 0.0; @@ -694,22 +694,22 @@ Real GreensFunction3DRadInf::drawTheta(Real rnd, Real r, Real t) const // input parameter range checks. if (!(rnd < 1.0 && rnd >= 0.0)) { - throw std::invalid_argument((boost::format("rnd < 1.0 && rnd >= 0.0 : rnd=%.16g") % rnd).str()); + throw std::invalid_argument((boost::format("GreensFunction3DRadInf: rnd < 1.0 && rnd >= 0.0 : rnd=%.16g") % rnd).str()); } if (!(r >= sigma)) { - throw std::invalid_argument((boost::format("r >= sigma : r=%.16g, sigma=%.16g") % r % sigma).str()); + throw std::invalid_argument((boost::format("GreensFunction3DRadInf: r >= sigma : r=%.16g, sigma=%.16g") % r % sigma).str()); } if (!(r0 >= sigma)) { - throw std::invalid_argument((boost::format("r0 >= sigma : r0=%.16g, sigma=%.16g") % r0 % sigma).str()); + throw std::invalid_argument((boost::format("GreensFunction3DRadInf: r0 >= sigma : r0=%.16g, sigma=%.16g") % r0 % sigma).str()); } if (!(t >= 0.0)) { - throw std::invalid_argument((boost::format("t >= 0.0 : t=%.16g") % t).str()); + throw std::invalid_argument((boost::format("GreensFunction3DRadInf: t >= 0.0 : t=%.16g") % t).str()); } @@ -755,7 +755,7 @@ Real GreensFunction3DRadInf::drawTheta(Real rnd, Real r, Real t) const if(i >= maxIter) { gsl_root_fsolver_free(solver); - throw std::runtime_error("drawTheta: failed to converge"); + throw std::runtime_error("GreensFunction3DRadInf: drawTheta: failed to converge"); } } else diff --git a/GreensFunction3DSym.cpp b/GreensFunction3DSym.cpp index 530bb712..9f6c5ab1 100644 --- a/GreensFunction3DSym.cpp +++ b/GreensFunction3DSym.cpp @@ -65,12 +65,12 @@ Real GreensFunction3DSym::drawR(Real rnd, Real t) const // input parameter range checks. if ( !(rnd <= 1.0 && rnd >= 0.0 ) ) { - throw std::invalid_argument( ( boost::format( "rnd <= 1.0 && rnd >= 0.0 : rnd=%.16g" ) % rnd ).str() ); + throw std::invalid_argument( ( boost::format( "GreensFunction3DSym: rnd <= 1.0 && rnd >= 0.0 : rnd=%.16g" ) % rnd ).str() ); } if ( !(t >= 0.0 ) ) { - throw std::invalid_argument( ( boost::format( "t >= 0.0 : t=%.16g" ) % t ).str() ); + throw std::invalid_argument( ( boost::format( "GreensFunction3DSym: t >= 0.0 : t=%.16g" ) % t ).str() ); } @@ -115,7 +115,7 @@ Real GreensFunction3DSym::drawR(Real rnd, Real t) const if( i >= maxIter ) { gsl_root_fsolver_free( solver ); - throw std::runtime_error("drawR: failed to converge"); + throw std::runtime_error("GreensFunction3DSym: drawR: failed to converge"); } } else diff --git a/INSTALL b/INSTALL index 5123dbb1..f0dbb717 100644 --- a/INSTALL +++ b/INSTALL @@ -1,12 +1,13 @@ :author: Koichi Takahashi +update okt-2014: Marco Seynen + Prerequisites ------------------------- -1. autotools - (autoconf (>=2.50), automake (>=1.11) libtool (>=2.2.6)) +1. autotools (autoconf >=2.50, automake >=1.11, libtool (>=2.2.6) or later. 2. GNU Scientific Library (GSL) 1.11 or later. 3. Boost C++ Library 1.37 or later. 4. Python 2.4 or later. @@ -15,16 +16,17 @@ Prerequisites If you use Fedora or any other RedHat lines of linux, check if the following RPM packages are installed: -gsl, gsl-devel, numpy, scipy, boost, and boost-devel. +autoconf automake libtool gcc-c++ gsl gsl-devel boost boost-devel python scipy numpy + On Ubuntu Linux and some other Debian families, install the following: -gsl-bin libgsl0-dev libboost-dev libboost-date-time-dev libboost-python-dev libboost-regex-dev libboost-test-dev python-scipy. +autoconf automake autotools-dev libtool gsl-bin libgsl0-dev libboost-all-dev python-scipy python-numpy + +In addition to above, optionally also install packages: -In addition to above, optionally, -6. python-matplotlib if you want to use the plotting scripts in the samples. -7. Pychecker if you want to run 'make pycheck'. +python-matplotlib python-h5py pychecker A note on GSL version @@ -39,17 +41,19 @@ For this reason, use of GSL version 1.11 or later is recommended. Building this package ------------------------- -1. ./configure -2. make - - -If you cannot find the configure script, or the build process -fails to complete even if you have set up the environment correctly, -try +1. ./autogen.sh +2. ./configure +3. make -./autogen.sh +has been tested on: +Ubuntu 14.04 LTS +Ubuntu 10.04 LTS +Debian 7.7 +Debian 7.9 +Debian 7.11 +Fedora 20.1 +CentOS 7.0.14.06 -to remake the entire build mechanism. Testing diff --git a/Makefile.am b/Makefile.am index 70c10298..74f6ecbe 100755 --- a/Makefile.am +++ b/Makefile.am @@ -17,13 +17,11 @@ pkgpyexecdir = @pkgpyexecdir@ LIBPYTHON = -lpython$(PYTHON_VERSION) -INCLUDES = ${PYTHON_INCLUDES} -I${NUMPY_INCLUDE_DIR} -AM_CXXFLAGS = @CXXFLAGS@ @GSL_CFLAGS@ +AM_CXXFLAGS = @BOOST_CPPFLAGS@ @GSL_CFLAGS@ ${PYTHON_INCLUDES} -I${NUMPY_INCLUDE_DIR} + LIBBOOSTPYTHON = -l@BOOST_PYTHON_LIBNAME@ LIBBOOSTREGEX = -l@BOOST_REGEX_LIBNAME@ -CFLAGS = @GSL_CFLAGS@ @BOOST_CPPFLAGS@ - PYCHECKER = @PYCHECKER@ PYCHECKER_FLAGS = --limit 0 @@ -47,11 +45,14 @@ noinst_HEADERS = \ BasicNetworkRulesImpl.hpp\ GreensFunction3DRadInf.hpp\ BDPropagator.hpp\ + newBDPropagator.hpp\ BDSimulator.hpp\ bessel.hpp\ Box.hpp\ + ConnectivityContainer.hpp\ ConsoleAppender.hpp\ Cylinder.hpp\ + Disk.hpp\ Defs.hpp\ DomainFactory.hpp\ Domain.hpp\ @@ -65,6 +66,9 @@ noinst_HEADERS = \ findRoot.hpp\ GreensFunction1DRadAbs.hpp\ GreensFunction1DAbsAbs.hpp\ + GreensFunction1DAbsSinkAbs.hpp\ + GreensFunction2DAbsSym.hpp\ + GreensFunction2DRadAbs.hpp\ GreensFunction3DAbsSym.hpp\ GreensFunction3DAbs.hpp\ GreensFunction3DRadAbs.hpp\ @@ -113,6 +117,9 @@ noinst_HEADERS = \ SphericalBesselGenerator.hpp\ SphericalBesselTable.hpp\ Structure.hpp\ + StructureFunctions.hpp\ + StructureID.hpp\ + StructureContainer.hpp\ StructureUtils.hpp\ StructureType.hpp\ Surface.hpp\ @@ -195,6 +202,9 @@ _greens_functions_la_SOURCES=\ freeFunctions.cpp\ GreensFunction1DAbsAbs.cpp\ GreensFunction1DRadAbs.cpp\ + GreensFunction1DAbsSinkAbs.cpp \ + GreensFunction2DAbsSym.cpp\ + GreensFunction2DRadAbs.cpp\ GreensFunction3DSym.cpp\ GreensFunction3DAbsSym.cpp\ GreensFunction3DRadInf.cpp\ @@ -205,16 +215,12 @@ _greens_functions_la_SOURCES=\ Logger.cpp\ ConsoleAppender.cpp -_gfrd_la_LDFLAGS = -module -export-dynamic -avoid-version $(no_undefined) -_gfrd_la_LIBADD = binding/libbinding_utils.la -_gfrd_la_LIBADD += $(LIBBOOSTPYTHON) $(LIBPYTHON) $(GSL_LIBS) +_gfrd_la_LDFLAGS = -module -export-dynamic -avoid-version -Wl,--no-undefined +_gfrd_la_LIBADD = binding/libbinding_utils.la $(LIBBOOSTPYTHON) $(LIBPYTHON) $(GSL_LIBS) -_greens_functions_la_LDFLAGS = -module -export-dynamic -avoid-version $(no_undefined) +_greens_functions_la_LDFLAGS = -module -export-dynamic -avoid-version _greens_functions_la_LIBADD = $(LIBBOOSTPYTHON) $(LIBPYTHON) $(GSL_LIBS) -#nodist__gfrd_la_SOURCES = \ -# SphericalBesselTable.hpp - nodist__greens_functions_la_SOURCES = \ SphericalBesselTable.hpp\ CylindricalBesselTable.hpp diff --git a/MatrixSpace.hpp b/MatrixSpace.hpp index 3851dca9..9933ad32 100644 --- a/MatrixSpace.hpp +++ b/MatrixSpace.hpp @@ -286,7 +286,7 @@ class MatrixSpace typename all_values_type::size_type const old_index(i - values_.begin()); - BOOST_ASSERT(cell(index((*i).second.position())).erase(old_index)); + BOOST_VERIFY(cell(index((*i).second.position())).erase(old_index)); rmap_.erase((*i).first); typename all_values_type::size_type const last_index(values_.size() - 1); @@ -295,7 +295,7 @@ class MatrixSpace { value_type const& last(values_[last_index]); cell_type& old_c(cell(index(last.second.position()))); - BOOST_ASSERT(old_c.erase(last_index)); + BOOST_VERIFY(old_c.erase(last_index)); old_c.push(old_index); rmap_[last.first] = old_index; reinterpret_cast(*i) = last; diff --git a/Model.cpp b/Model.cpp index 34e2ca33..e687bb7e 100644 --- a/Model.cpp +++ b/Model.cpp @@ -3,6 +3,7 @@ #endif /* HAVE_CONFIG_H */ #include +#include #include #include @@ -14,6 +15,8 @@ #include "Model.hpp" Model::Model(): network_rules_(new BasicNetworkRulesImpl()) +// The model class is more or less an implementation of the network rules +// The network rules define the possible reactions between species. { } @@ -25,9 +28,11 @@ void Model::add_species_type(boost::shared_ptr const& species { species->bind_to_model(this, species_type_id_generator_()); species_type_map_.insert(std::make_pair(species->id(), species)); + // the species_type_map_ is a mapping (container) between the species_id and the species object } boost::shared_ptr Model::get_species_type_by_id(species_id_type const& id) const +// Returns the species if you give it an species 'id' { species_type_map_type::const_iterator i(species_type_map_.find(id)); if (species_type_map_.end() == i) @@ -39,6 +44,7 @@ boost::shared_ptr Model::get_species_type_by_id(specie } Model::species_type_range Model::get_species_types() const +// Just gets all the species in the model { return species_type_range( species_type_iterator(species_type_map_.begin(), species_second_selector_type()), @@ -46,6 +52,7 @@ Model::species_type_range Model::get_species_types() const } std::string const& Model::operator[](std::string const& name) const +// Gets the name of the species? Or gets the value of an attribute by name? { string_map_type::const_iterator i(attrs_.find(name)); if (i == attrs_.end()) diff --git a/Model.hpp b/Model.hpp index 2e9505e0..c3df14e2 100644 --- a/Model.hpp +++ b/Model.hpp @@ -19,25 +19,29 @@ class NetworkRules; class Model: private boost::noncopyable { public: - typedef SpeciesType species_type_type; - typedef species_type_type::identifier_type species_id_type; + typedef SpeciesType species_type_type; + typedef species_type_type::identifier_type species_id_type; private: - typedef SerialIDGenerator species_type_id_generator_type; - typedef std::map > species_type_map_type; - typedef select_second species_second_selector_type; + typedef SerialIDGenerator species_type_id_generator_type; + typedef std::map > species_type_map_type; + typedef select_second species_second_selector_type; - typedef get_mapper_mf::type string_map_type; + typedef get_mapper_mf::type string_map_type; public: typedef boost::transform_iterator species_type_iterator; - typedef boost::iterator_range species_type_range; - typedef NetworkRules network_rules_type; - typedef string_map_type::const_iterator string_map_iterator; - typedef boost::iterator_range attributes_range; + species_type_map_type::const_iterator> species_type_iterator; + typedef boost::iterator_range species_type_range; + typedef NetworkRules network_rules_type; + typedef string_map_type::const_iterator string_map_iterator; + typedef boost::iterator_range attributes_range; public: + // The model class is more or less an implementation of the network rules + // The network rules define the possible reactions between species. + + // Constructor Model(); virtual ~Model(); @@ -47,27 +51,32 @@ class Model: private boost::noncopyable return *network_rules_; } + // Add a species to the model void add_species_type(boost::shared_ptr const& species); + // Get a species by the species 'id' boost::shared_ptr get_species_type_by_id(species_id_type const& id) const; + // Get all the species in the model species_type_range get_species_types() const; std::string const& operator[](std::string const& name) const; std::string& operator[](std::string const& name); + // Not sure. Get attribute by name? + // Get all the attributes attributes_range attributes() const { return attributes_range(attrs_.begin(), attrs_.end()); } - +//////// Member variables (why are they public?) public: - species_type_id_generator_type species_type_id_generator_; - species_type_map_type species_type_map_; - boost::scoped_ptr network_rules_; - string_map_type attrs_; + species_type_id_generator_type species_type_id_generator_; // The id generator which makes sure that all species have a unique id + species_type_map_type species_type_map_; // mapping: species_type_id->species_type + boost::scoped_ptr network_rules_; // The network rules in the model + string_map_type attrs_; // All the attributes }; diff --git a/Multi.hpp b/Multi.hpp index 6f3dcba0..f10151e0 100644 --- a/Multi.hpp +++ b/Multi.hpp @@ -3,6 +3,9 @@ #include #include +#include + +#include #include "exceptions.hpp" #include "Domain.hpp" @@ -13,36 +16,71 @@ #include "BDPropagator.hpp" #include "Logger.hpp" #include "PairGreensFunction.hpp" +#include "Transaction.hpp" #include "VolumeClearer.hpp" #include "utils/array_helper.hpp" #include "utils/range.hpp" + template class MultiParticleContainer : public Ttraits_::world_type::particle_container_type { public: typedef ParticleContainerUtils utils; - typedef typename Ttraits_::world_type world_type; - typedef typename world_type::traits_type traits_type; - typedef typename traits_type::particle_type particle_type; - typedef typename particle_type::shape_type particle_shape_type; - typedef typename traits_type::species_type species_type; - typedef typename traits_type::species_id_type species_id_type; - typedef typename traits_type::position_type position_type; - typedef typename traits_type::particle_id_type particle_id_type; - typedef typename traits_type::length_type length_type; - typedef typename traits_type::size_type size_type; - typedef typename traits_type::structure_id_type structure_id_type; - typedef typename traits_type::structure_type structure_type; - typedef std::pair particle_id_pair; - typedef Transaction transaction_type; - typedef abstract_limited_generator particle_id_pair_generator; - typedef std::pair particle_id_pair_and_distance; - typedef unassignable_adapter particle_id_pair_and_distance_list; - typedef std::map particle_map; - typedef sized_iterator_range particle_id_pair_range; + typedef typename Ttraits_::world_type world_type; + typedef typename world_type::traits_type traits_type; + + // shorthands types for used types + typedef typename traits_type::length_type length_type; + typedef typename traits_type::size_type size_type; + typedef typename traits_type::position_type position_type; + typedef typename traits_type::species_type species_type; + typedef typename traits_type::species_id_type species_id_type; + typedef typename traits_type::particle_type particle_type; + typedef typename traits_type::particle_id_type particle_id_type; + typedef typename traits_type::structure_type_type structure_type_type; + typedef typename traits_type::structure_type_id_type structure_type_id_type; + typedef typename traits_type::structure_type structure_type; + typedef typename traits_type::structure_id_type structure_id_type; + + typedef typename particle_type::shape_type particle_shape_type; + typedef Transaction transaction_type; + + // types inherited from the ParticleContainerBase class + typedef typename world_type::particle_container_type::structure_types_range structure_types_range; + typedef typename world_type::particle_container_type::structures_range structures_range; + typedef typename world_type::particle_container_type::particle_id_pair particle_id_pair; + typedef typename world_type::particle_container_type::structure_id_pair structure_id_pair; + typedef typename world_type::particle_container_type::structure_id_set structure_id_set; + typedef typename world_type::particle_container_type::particle_id_pair_and_distance_list particle_id_pair_and_distance_list; + typedef typename world_type::particle_container_type::structure_id_pair_and_distance_list structure_id_pair_and_distance_list; + typedef typename world_type::particle_container_type::position_structid_pair_type position_structid_pair_type; + + typedef typename Ttraits_::network_rules_type network_rules_type; + typedef typename Ttraits_::reaction_rule_type reaction_rule_type; + typedef typename network_rules_type::reaction_rules reaction_rules; + + typedef abstract_limited_generator particle_id_pair_generator; + typedef std::pair particle_id_pair_and_distance; +// typedef unassignable_adapter particle_id_pair_and_distance_list; + typedef std::map particle_map; + typedef sized_iterator_range particle_id_pair_range; + typedef std::pair real_pair; + +private: + typedef std::map species_map; + typedef select_second species_second_selector_type; + +public: + typedef boost::transform_iterator species_iterator; + typedef sized_iterator_range species_range; + + + // virtual ~MultiParticleContainer() {} virtual size_type num_particles() const @@ -54,22 +92,73 @@ class MultiParticleContainer { return world_.world_size(); } - + + // Species stuff virtual species_type const& get_species(species_id_type const& id) const { return world_.get_species(id); } + // Start structure stuff virtual boost::shared_ptr get_structure(structure_id_type const& id) const { return world_.get_structure(id); } + virtual structures_range get_structures() const + { + return world_.get_structures(); // TODO now gets all structures in world, -> make structure local to Multi + } + virtual boost::shared_ptr get_some_structure_of_type(structure_type_id_type const& sid) const + { + return world_.get_some_structure_of_type(sid); + } + // virtual structure_id_type add_structure(structure_type const& structure); // TODO add structure from the world to multi + template + bool update_structure(Tstructid_pair_ const& structid_pair) + { + return world_.update_structure(structid_pair); + } + virtual bool remove_structure(structure_id_type const& id) + { + return world_.remove_structure(id); + } + virtual structure_id_set get_structure_ids(structure_type_id_type const& sid) const + { + return world_.get_structure_ids(sid); + } + virtual structure_id_type get_def_structure_id() const + { + return world_.get_def_structure_id(); + } + virtual structure_id_pair_and_distance_list* get_close_structures(position_type const& pos, structure_id_type const& current_struct_id, + structure_id_type const& ignore) const + { + return world_.get_close_structures(pos, current_struct_id, ignore); + } + // End structure stuff + + // StructureType stuff + // virtual bool add_structure_type(structure_type_type const& structure_type); // TODO add structure_type from the world to multi + virtual structure_type_type get_structure_type(structure_type_id_type const& sid) const + { + return world_.get_structure_type(sid); // TODO make local just like structures. + } + virtual structure_types_range get_structure_types() const + { + return world_.get_structure_types(); // TODO ditto + } + virtual structure_type_id_type get_def_structure_type_id() const + { + return world_.get_def_structure_type_id(); // This information stays in the world. + } - virtual particle_id_pair new_particle(species_id_type const& sid, + + virtual particle_id_pair new_particle(species_id_type const& sid, structure_id_type const& structure_id, position_type const& pos) { - particle_id_pair const retval(world_.new_particle(sid, pos)); + particle_id_pair const retval(world_.new_particle(sid, structure_id, pos)); particles_.insert(retval); + add_species_to_multi(sid); return retval; } @@ -80,19 +169,22 @@ class MultiParticleContainer if (i != particles_.end()) { (*i).second = pi_pair.second; + add_species_to_multi(pi_pair.second.sid()); return false; } else { - particles_.insert(i, pi_pair); + particles_.insert(i, pi_pair); + add_species_to_multi(pi_pair.second.sid()); return true; } } virtual bool remove_particle(particle_id_type const& id) { + species_.erase( get_particle(id).second.sid() ); world_.remove_particle(id); - return particles_.erase(id); + return particles_.erase(id); } virtual particle_id_pair get_particle(particle_id_type const& id) const @@ -129,7 +221,7 @@ class MultiParticleContainer template particle_id_pair_and_distance_list* check_overlap(Tsph_ const& s, Tset_ const& ignore) const { - typename utils::template overlap_checker checker(ignore); + typename utils::template overlap_checker checker(ignore); for (typename particle_map::const_iterator i(particles_.begin()), e(particles_.end()); i != e; ++i) @@ -143,6 +235,24 @@ class MultiParticleContainer return checker.result(); } + virtual structure_id_pair_and_distance_list* check_surface_overlap(particle_shape_type const& s, position_type const& old_pos, structure_id_type const& current, + length_type const& sigma) const + { + return world_.check_surface_overlap(s, old_pos, current, sigma); + } + + virtual structure_id_pair_and_distance_list* check_surface_overlap(particle_shape_type const& s, position_type const& old_pos, structure_id_type const& current, + length_type const& sigma, structure_id_type const& ignore) const + { + return world_.check_surface_overlap(s, old_pos, current, sigma, ignore); + } + + virtual structure_id_pair_and_distance_list* check_surface_overlap(particle_shape_type const& s, position_type const& old_pos, structure_id_type const& current, + length_type const& sigma, structure_id_type const& ignore1, structure_id_type const& ignore2) const + { + return world_.check_surface_overlap(s, old_pos, current, sigma, ignore1, ignore2); + } + virtual particle_id_pair_generator* get_particles() const { return make_range_generator(particles_); @@ -169,6 +279,11 @@ class MultiParticleContainer return world_.apply_boundary(v); } + virtual position_structid_pair_type apply_boundary(position_structid_pair_type const& pos_struct_id) const + { + return world_.apply_boundary(pos_struct_id); + } + virtual position_type cyclic_transpose(position_type const& p0, position_type const& p1) const { return world_.cyclic_transpose(p0, p1); @@ -179,49 +294,340 @@ class MultiParticleContainer return world_.cyclic_transpose(p0, p1); } + virtual position_structid_pair_type cyclic_transpose(position_structid_pair_type const& pos_struct_id, + structure_type const& structure) const + { + return world_.cyclic_transpose(pos_struct_id, structure); + } + particle_id_pair_range get_particles_range() const { return particle_id_pair_range(particles_.begin(), particles_.end(), particles_.size()); } + void add_species_to_multi(species_id_type const& sid) + { + typename species_map::const_iterator i(species_.find( sid )); + if (species_.end() == i) + { + species_[sid] = world_.get_species( sid ); + } + } + + species_range get_species_in_multi() const + { + return species_range( + species_iterator(species_.begin(), species_second_selector_type()), + species_iterator(species_.end(), species_second_selector_type()), + species_.size()); + } + + /* Determines the largest diffusion constant and largest drift coefficient inside the mpc. + and whether movement is dominated by diffusion or drift for a given length scale. + Then it returns the typical time to travel that length scale based on which type + of movement is dominant. */ + Real get_min_tau_Dv(Real r_typical) const + { + Real tau_D(0.0), tau_v(0.0); // the estimated times to travel r_typical by diffusion / convection + Real tau_dominant(0.0), tau_min(-1.0); // always keep tau_min negative initially! + + const Real LARGE_PREF( GSL_POSINF ); + assert( r_typical > 0.0 ); + + BOOST_FOREACH(species_type s, get_species_in_multi()) + { + // First we calculate the typical times to travel r_typical by + // diffusion and convection, respectively. + if( s.D()==0 && s.v()==0) + + tau_dominant = GSL_POSINF; // static particle, will never move + + else{ + + tau_D = s.D() > 0.0 ? 2.0 * gsl_pow_2(r_typical) / s.D() : LARGE_PREF * r_typical / s.v(); + tau_v = s.v() > 0.0 ? r_typical / s.v() : LARGE_PREF * 2.0 * gsl_pow_2(r_typical) / s.D() ; + + tau_dominant = tau_v < 0.1 * tau_D ? tau_v : tau_D; + // We believe that convective movement is dominant if it takes a tenth of the + // time to travel the typical distance by convection as compared to diffusion, + // and then also the associated time is the relevant timescale. + // Note that the 0.1 prefactor is to ensure that we pick the convective + // timescale only when the movement is clearly dominated by convection, + // i.e. when both timescales are comparable we prefer the diffusion timescale. + if(tau_dominant == tau_D){ LOG_DEBUG(("Diffusion dominates on given reaction length"));} + else { LOG_DEBUG(("Convection dominates on given reaction length")); } + } + + // Now compare if within the current set of species this is the smallest time scale. + assert( tau_dominant > 0.0 ); + if( tau_min < 0.0 || tau_dominant < tau_min ) + tau_min = tau_dominant; + } + + assert( tau_min > 0.0 ); + + return tau_min; + } + + /* Returns smallest particle radius inside the mpc. */ + Real get_min_radius() const + { + Real radius_min(std::numeric_limits::max()); + + BOOST_FOREACH(species_type s, get_species_in_multi()) + { + if(radius_min > s.radius()) + radius_min = s.radius(); + } + + return radius_min; + } + + /* Functions returns largest _1D_ intrinsic reaction rate in the multi. */ + Real get_max_rate(network_rules_type const& rules) const + { + Real k_max(0.); + int i = 0, j= 0; + + // Rates for particle-surface interactions + BOOST_FOREACH(particle_id_pair pp, get_particles_range()) + { + // s = species of the current particle + species_type const s( get_species(pp.second.sid()) ); + + // Get list of close structures with ids and distances from current particle's position + const boost::scoped_ptr close_struct_id_distance ( + get_close_structures(pp.second.position(), pp.second.structure_id(), pp.second.structure_id()) ); + + // Take the closest one and pass it as an id-and-distance pair + const std::pair, length_type> struct_and_dist ( + close_struct_id_distance ? std::make_pair(close_struct_id_distance->at(0).first.second, close_struct_id_distance->at(0).second) + : std::make_pair(get_structure(pp.second.structure_id()), std::numeric_limits::max())); + + //structure_id_and_distance_pair const struct_id_and_dist( + // get_closest_surface( pp.second.position(), pp.second.structure_id() ) ); // only ignore structure that the particle is on. + + // If the structure is within a specified range + // Here we assume that the user defined the reaction rules for allowed combinations of origin and + // target structure type (if this is not the case the simulation will fail at a later stage when + // propagation is attempted). + if( struct_and_dist.second < 2.0 * s.radius() ) + { + // Get the reaction rule for this particle-structure interaction + structure_type_id_type const struct_sid( struct_and_dist.first->sid() ); + reaction_rules const& rrules(rules.query_reaction_rule( s.id(), struct_sid )); + if (::size(rrules) == 0) + continue; // no reaction rule for this structure type + + // If there is rules, determine the largest on-rate for this interaction + for (typename boost::range_const_iterator::type + it(boost::begin(rrules)), e(boost::end(rrules)); it != e; ++it) + { + Real const k( struct_and_dist.first->get_1D_rate_surface( (*it).k(), s.radius() ) ); + + if ( k_max < k ) + k_max = k; + } + } + + } + + // since surface rates are not devided by 2 to compensate for double reaction attempts. + k_max *= 2.0; + + // Rates for particle-particle interactions + BOOST_FOREACH(species_type s0, get_species_in_multi()) + { + j = 0; + BOOST_FOREACH(species_type s1, get_species_in_multi()) + { + if( j++ < i ) + continue; + + reaction_rules const& rrules(rules.query_reaction_rule( s0.id(), s1.id() )); + if (::size(rrules) == 0) + continue; // no rule for reactions between these two species + + for (typename boost::range_const_iterator::type + it(boost::begin(rrules)), e(boost::end(rrules)); it != e; ++it) + { + // We have found a valid rule - get the rate + const length_type r01( s0.radius() + s1.radius() ); + Real k(0.0), k0, k1; + + // To access the rate functions we first find some structure of the structure type of the + // species considered and then calculate the modified rate. This is a necessary workaround + // because as yet the rate functions are methods to the structure, not the structure type. + + // If one of the two particles lives in the default structure (bulk) we always take the + // 3D rate, i.e. call the get_1D_rate_geminate() method of the bulk structure. + if (s0.structure_type_id() == get_def_structure_type_id() ) + + k = get_some_structure_of_type(s0.structure_type_id())->get_1D_rate_geminate( (*it).k(), r01 ); + + else if (s1.structure_type_id() == get_def_structure_type_id() ) + + k = get_some_structure_of_type(s1.structure_type_id())->get_1D_rate_geminate( (*it).k(), r01 ); + + else + // If both particles live on lower dimensionality structures more care has to be taken in determining + // what is the right modifier function for the bare rate. + // For now by default we take the maximal rate as determined from get_1D_rate_geminate() of both structures. + // However, if one of the two interacting species is static (D=0, v=0), the "dimensionality" of the motion + // is exclusively determined by the mobile species, and we have to take the modifier function of that one. + // FIXME Taking the max by default may lead to a rate which actually is higher than the one that should + // be used in BD propagation and therefore wastes resources. + // Until now this case basically only comprises the rod-particle/cap-particle interaction. Since + // both rates are equal in this case we do not make any approximation in that case. However, if we + // allow for other types of lower dimensionality particle-particle reactions we should fix this. + { + // In case that one of the two particles is static, make sure we modify the rate using the + // modifier function of the structure that holds the *mobile* species: + if(s0.D()==0.0 and s0.v()==0.0) // s1 is the mobile species + k = get_some_structure_of_type(s1.structure_type_id())->get_1D_rate_geminate( (*it).k(), r01 ); + + else if(s1.D()==0.0 and s1.v()==0.0) // s0 is the mobile species + k = get_some_structure_of_type(s0.structure_type_id())->get_1D_rate_geminate( (*it).k(), r01 ); + + else{ + // Pick the higher of the modified rates + k0 = get_some_structure_of_type(s0.structure_type_id())->get_1D_rate_geminate( (*it).k(), r01 ); + k1 = get_some_structure_of_type(s1.structure_type_id())->get_1D_rate_geminate( (*it).k(), r01 ); + + k = k0 > k1 ? k0 : k1; + // FIXME This is not very elegant yet and may cause that a rate that is way too high is picked + // if the species' structures have different dimensionalities + } + } + // NOTE: If a structure of the required structure type can not be found a not_found exception + // is risen. This however should never happen, because whenever a particle of species s0 (s1) + // is in the system also at least one structure of the associated structure type should exist. + + // Compare with the fastest rate found so far + k_max = k > k_max ? k : k_max; + } + } + i++; + } + + return k_max; + } + + /* Function returns the timestep and reaction length (rl) for BD propegator. + + --dt is calulated with the constraints: + (1) The reaction length is equal to step_size_factor (ssf) * r_min, + where r_min is the radius of the smallest particle in the multi. + (2) The largest acceptance probability in the multi is smaller than Pacc_max (.01). + (3) particles escape the multi with a maximum step size in the order of the + reaction length. (Dmax * dt ~ (ssf * r_min)**2 ). + + TODO: This function should be a method of the Multi Class, + but I put it in the mpc (multi particle container) such that we can use it in python. + + PROBLEM: for certain parameters (large k) dt can be very small and the simulation will slow down. + */ + real_pair determine_dt_and_reaction_length(network_rules_type const& rules, Real const& step_size_factor, Real const& dt_hardcore_min = -1.0) const + { + + const Real k_max( get_max_rate(rules) ); + const Real r_min( get_min_radius() ); + + // The following gives us the typical timescale to travel the length step_size_factor * r_min, + // taking into account all species in the Multi and automatically comparing whether their movement + // is dominated by convection or diffusion, respectively. + const Real tau_Dv( get_min_tau_Dv(step_size_factor * r_min) ); + + const Real Pacc_max( 0.1 ); // Maximum allowed value of the acceptance probability. // TESTING was 0.01 + // This should be kept very low (max. 0.01), otherwise the approximation of + // treating the reaction as two sequential attempts (first move, then react) + // might break down! + Real dt, dt_temp; + + if( k_max > 0) + { + // step_size_factor * r_min is the reaction length + // Here it is assumed that the RL is linear in any dimension, + // which requires it to be very small! + dt_temp = 2. * Pacc_max * step_size_factor * r_min / k_max; + dt = std::min( dt_temp, tau_Dv ); // tau_Dv is upper limit of dt. + + if(dt == tau_Dv){ LOG_DEBUG(("Time step set by timescale of motion")); } + else { LOG_DEBUG(("Time step set by largest reaction rate, k_max = %e, r_min = %e", k_max, r_min));} + } + else + dt = tau_Dv; + + if( dt < dt_hardcore_min ){ + dt = dt_hardcore_min; + LOG_WARNING(("Setting timestep to hard-coded minimal bound, dt = %e", dt)); + } + + return real_pair(dt, step_size_factor * r_min); + } + + // The constructor MultiParticleContainer(world_type& world): world_(world) {} +////// Member variables private: - world_type& world_; - particle_map particles_; + world_type& world_; // reference to the world. The MultiParticleContainer reflects a part of the World(ParticleContainer) + particle_map particles_; // local copy of the particles (the particles in the Multi are a subset of the particles in the world) + species_map species_; // local copy of the species }; + + + +//////////////////////// template class Multi: public Domain { public: typedef Tsim_ simulator_type; - typedef typename simulator_type::traits_type traits_type; - typedef Domain base_type; - typedef typename traits_type::world_type::particle_type particle_type; - typedef typename particle_type::shape_type particle_shape_type; - typedef typename traits_type::world_type::species_type species_type; - typedef typename traits_type::world_type::species_id_type species_id_type; - typedef typename traits_type::world_type::position_type position_type; - typedef typename traits_type::world_type::particle_id_type particle_id_type; - typedef typename traits_type::world_type::length_type length_type; - typedef typename traits_type::world_type::size_type size_type; - typedef typename traits_type::world_type::structure_type structure_type; - typedef typename traits_type::world_type::particle_id_pair particle_id_pair; - typedef typename traits_type::shell_id_type shell_id_type; - typedef typename traits_type::domain_id_type identifier_type; + typedef typename simulator_type::traits_type traits_type; + typedef typename traits_type::world_type world_type; + typedef Domain base_type; + + // shorthand typenames that we use a lot + typedef typename world_type::length_type length_type; + typedef typename world_type::size_type size_type; + typedef typename world_type::position_type position_type; + typedef typename world_type::particle_type particle_type; + typedef typename world_type::particle_id_type particle_id_type; + typedef typename particle_type::shape_type particle_shape_type; + typedef typename world_type::species_type species_type; + typedef typename world_type::species_id_type species_id_type; + typedef typename world_type::structure_type structure_type; + typedef typename world_type::particle_id_pair particle_id_pair; + typedef typename traits_type::network_rules_type network_rules_type; + typedef typename traits_type::reaction_rule_type reaction_rule_type; + typedef typename network_rules_type::reaction_rules reaction_rules; + typedef typename traits_type::shell_id_type shell_id_type; + typedef typename traits_type::domain_id_type identifier_type; typedef typename traits_type::template shell_generator< - typename simulator_type::sphere_type>::type spherical_shell_type; - typedef std::pair spherical_shell_id_pair; - typedef std::pair particle_id_pair_and_distance; - typedef unassignable_adapter particle_id_pair_and_distance_list; - typedef typename traits_type::reaction_record_type reaction_record_type; + typename simulator_type::sphere_type>::type spherical_shell_type; - typedef std::map spherical_shell_map; - typedef sized_iterator_range spherical_shell_id_pair_range; - typedef MultiParticleContainer multi_particle_container_type; + typedef std::pair spherical_shell_id_pair; + typedef std::pair particle_id_pair_and_distance; + typedef unassignable_adapter particle_id_pair_and_distance_list; + typedef typename traits_type::reaction_record_type reaction_record_type; + typedef std::pair real_pair; +private: + typedef std::map species_map; + typedef select_second species_second_selector_type; + +public: + typedef boost::transform_iterator species_iterator; + typedef sized_iterator_range species_range; + + typedef std::map spherical_shell_map; + typedef sized_iterator_range spherical_shell_id_pair_range; + typedef MultiParticleContainer multi_particle_container_type; + enum event_kind { NONE, @@ -301,6 +707,7 @@ class Multi: public Domain : base_type(id), main_(main), pc_(*main.world()), dt_factor_(dt_factor), shells_(), last_event_(NONE) { + //TODO Do not base dt and rl on all particles in the world but only those in the multi. BOOST_ASSERT(dt_factor > 0.); base_type::dt_ = dt_factor_ * BDSimulator::determine_dt(*main_.world()); } @@ -364,6 +771,7 @@ class Multi: public Domain { spherical_shell_id_pair const& sp(*i); position_type ppos(main_.world()->cyclic_transpose(sphere.position(), (sp).second.position())); + //TODO for newBD scheme, add reaction length to sphere.radius(). if (distance(ppos, (sp).second.shape().position()) < (sp).second.shape().radius() - sphere.radius()) { return true; @@ -403,16 +811,13 @@ class Multi: public Domain { return pc_.get_particles_range(); } - + void step() { - boost::scoped_ptr< - typename multi_particle_container_type::transaction_type> - tx(pc_.create_transaction()); - typedef typename multi_particle_container_type::transaction_type::particle_id_pair_generator particle_id_pair_generator; - typedef typename multi_particle_container_type::transaction_type::particle_id_pair_and_distance_list particle_id_pair_and_distance_list; + boost::scoped_ptr tx(pc_.create_transaction()); last_reaction_setter rs(*this); volume_clearer vc(*this); + BDPropagator ppg( *tx, *main_.network_rules(), main_.rng(), base_type::dt_, diff --git a/OldDefs.hpp b/OldDefs.hpp deleted file mode 100644 index fb182fb0..00000000 --- a/OldDefs.hpp +++ /dev/null @@ -1,50 +0,0 @@ -#if !defined( __OLDDEFS_HPP ) -#define __OLDDEFS_HPP - -#ifdef HAVE_CONFIG_H -#include "config.h" -#endif /* HAVE_CONFIG_H */ - -#include -#include -#include - -#include "Defs.hpp" - -// This file is needed temporarily by GreensFunction1DAbsAbs.cpp -// and GreensFunction1DRadAbs.cpp -// -// At some point it should be taken out of the new code version. -// This requires some major refurbishment of the above functions, so postponed for later. -// -// All passages conflicting with analogous definitions in the new Defs.hpp are taken out. -// - - - - -typedef std::vector< Real > RealVector; -typedef boost::multi_array -Real2DArray; -typedef boost::multi_array -Real3DArray; -typedef boost::multi_array -Real4DArray; - - -// stringifiers. see preprocessor manual -#define XSTR( S ) STR( S ) -#define STR( S ) #S - -#define THROW_UNLESS( CLASS, EXPRESSION ) \ - if( ! ( EXPRESSION ) )\ - {\ - throw CLASS( "Check [" + std::string( STR( EXPRESSION ) ) +\ - "] failed." );\ - }\ - - -#define IGNORE_RETURN (void) - - -#endif // __OLDDEFS_HPP diff --git a/Particle.hpp b/Particle.hpp index 1ab25273..72217197 100644 --- a/Particle.hpp +++ b/Particle.hpp @@ -13,26 +13,42 @@ #include "Sphere.hpp" #include "Shape.hpp" -template +template struct Particle +// T_ is a length_type { - typedef Sphere shape_type; - typedef Td_ D_type; - typedef Td_ v_type; // the drift v has the same type as diffusion constant D for now, may be generalized at a later stage - typedef Tsid_ species_id_type; - typedef typename shape_type::position_type position_type; - typedef typename shape_type::length_type length_type; - - Particle(): shape_(), species_id_(), D_(0.), v_(0.) {} - - Particle(species_id_type const& species_id, shape_type const& shape, + // defining shorthands for used types + typedef Sphere shape_type; + typedef Td_ D_type; + // the drift v has the same type as diffusion constant D for now, may be generalized at a later stage + typedef Td_ v_type; + typedef Tsid_ species_id_type; + typedef typename shape_type::position_type position_type; + typedef typename shape_type::length_type length_type; + typedef Tstructure_id_ structure_id_type; // The structure (not structure_type) on which the particle currently lives. + + // constructors + // Note that the structure_id is mandatory (?) + Particle(): shape_(), species_id_(), structure_id_(), D_(0.), v_(0.) {} + +/* Particle(species_id_type const& species_id, shape_type const& shape, D_type const& D) - : shape_(shape), species_id_(species_id), D_(D), v_(0.){} + : shape_(shape), species_id_(species_id), structure_id_(), D_(D), v_(0.){} Particle(species_id_type const& species_id, shape_type const& shape, D_type const& D, v_type const& v) - : shape_(shape), species_id_(species_id), D_(D), v_(v) {} + : shape_(shape), species_id_(species_id), structure_id_(), D_(D), v_(v) {} +*/ + Particle(species_id_type const& species_id, shape_type const& shape, + structure_id_type const& structure_id, D_type const& D) + : shape_(shape), species_id_(species_id), structure_id_(structure_id), D_(D), v_(0.){} + Particle(species_id_type const& species_id, shape_type const& shape, + structure_id_type const& structure_id, D_type const& D, v_type const& v) + : shape_(shape), species_id_(species_id), structure_id_(structure_id), D_(D), v_(v) {} + + + // Get basic properties such as position, radius, D, v (drift) position_type& position() { return shape_.position(); @@ -73,6 +89,7 @@ struct Particle return v_; } + // Get the shape of the particle (Sphere) shape_type& shape() { return shape_; @@ -83,6 +100,7 @@ struct Particle return shape_; } + // Get the species_id species_id_type const& sid() const { return species_id_; @@ -93,9 +111,23 @@ struct Particle return species_id_; } + // Get the id of the structure that the particle lives on. + structure_id_type const& structure_id() const + { + return structure_id_; + } + + structure_id_type& structure_id() + { + return structure_id_; + } + + // Check equality/inequality bool operator==(Particle const& rhs) const { - return species_id_ == rhs.sid() && shape_ == rhs.shape(); + return species_id_ == rhs.sid() && + shape_ == rhs.shape() && + structure_id_ == rhs.structure_id(); } bool operator!=(Particle const& rhs) const @@ -111,20 +143,26 @@ struct Particle return strm.str(); } +////// Member variables private: - shape_type shape_; - species_id_type species_id_; - D_type D_; - v_type v_; + shape_type shape_; + species_id_type species_id_; + structure_id_type structure_id_; + D_type D_; + v_type v_; }; -template -inline std::basic_ostream& operator<<(std::basic_ostream& strm, const Particle& p) + +///// Inline functions +template +inline std::basic_ostream& operator<<(std::basic_ostream& strm, const Particle& p) { - strm << "Particle(" << p.shape() << ", D=" << p.D() << ", v=" << p.v() << ", " << p.sid() << ")"; + strm << "Particle(" << p.shape() << ", D=" << p.D() << ", v=" << p.v() << ", " << p.sid() << ", " << p.structure_id() << ")"; return strm; } + + #if defined(HAVE_TR1_FUNCTIONAL) namespace std { namespace tr1 { #elif defined(HAVE_STD_HASH) @@ -133,17 +171,18 @@ namespace std { namespace boost { #endif -template -struct hash > +template +struct hash > { - typedef Particle argument_type; + typedef Particle argument_type; std::size_t operator()(argument_type const& val) { return hash()(val.position()) ^ - hash()(val.radius()) ^ - hash()(val.D()) ^ - hash()(val.sid()); + hash()(val.radius()) ^ + hash()(val.D()) ^ + hash()(val.sid()) ^ + hash()(val.structure_id()); } }; diff --git a/ParticleContainer.hpp b/ParticleContainer.hpp index da1399e0..7a8659d2 100644 --- a/ParticleContainer.hpp +++ b/ParticleContainer.hpp @@ -6,6 +6,11 @@ #include "generator.hpp" #include "utils/get_default_impl.hpp" #include "utils/unassignable_adapter.hpp" +#include "CuboidalRegion.hpp" +#include "PlanarSurface.hpp" +#include "CylindricalSurface.hpp" +#include "SphericalSurface.hpp" +#include "DiskSurface.hpp" template class Transaction; @@ -13,23 +18,67 @@ class Transaction; template class ParticleContainer { +// This class just defines the properties that the ParticleContainer should have. +// The ParticleContainerBase implements some of them. public: typedef Ttraits_ traits_type; - typedef typename traits_type::particle_type particle_type; - typedef typename particle_type::shape_type particle_shape_type; - typedef typename traits_type::species_type species_type; - typedef typename traits_type::species_id_type species_id_type; - typedef typename traits_type::position_type position_type; - typedef typename traits_type::particle_id_type particle_id_type; - typedef typename traits_type::length_type length_type; - typedef typename traits_type::size_type size_type; - typedef typename traits_type::structure_id_type structure_id_type; - typedef typename traits_type::structure_type structure_type; - typedef std::pair particle_id_pair; - typedef Transaction transaction_type; - typedef abstract_limited_generator particle_id_pair_generator; - typedef std::pair particle_id_pair_and_distance; - typedef unassignable_adapter particle_id_pair_and_distance_list; + + // some shorthand typename inherited from the traits + typedef typename traits_type::length_type length_type; + typedef typename traits_type::size_type size_type; + typedef typename traits_type::position_type position_type; + typedef typename traits_type::species_type species_type; + typedef typename traits_type::species_id_type species_id_type; + typedef typename traits_type::particle_type particle_type; + typedef typename traits_type::particle_id_type particle_id_type; + typedef typename traits_type::structure_type_type structure_type_type; + typedef typename traits_type::structure_type_id_type structure_type_id_type; + typedef typename traits_type::structure_type structure_type; + typedef typename traits_type::structure_id_type structure_id_type; + + typedef typename particle_type::shape_type particle_shape_type; + typedef Transaction transaction_type; + + typedef CuboidalRegion cuboidal_region_type; + typedef PlanarSurface planar_surface_type; + typedef CylindricalSurface cylindrical_surface_type; + typedef DiskSurface disk_surface_type; + typedef SphericalSurface spherical_surface_type; + +// typedef std::pair > cuboidal_region_id_pair_type; +// typedef std::pair > planar_surface_id_pair_type; +// typedef std::pair > cylindrsurf_id_pair_type; +// typedef std::pair > disk_surface_id_pair_type; +// typedef std::pair > spherical_surface_id_pair_type; + + typedef std::set particle_id_set; + typedef std::pair particle_id_pair; + typedef abstract_limited_generator particle_id_pair_generator; + typedef std::pair particle_id_pair_and_distance; + typedef unassignable_adapter particle_id_pair_and_distance_list; + typedef std::set structure_id_set; + typedef std::pair > structure_id_pair; + typedef abstract_limited_generator structure_id_pair_generator; + typedef std::pair structure_id_pair_and_distance; + typedef unassignable_adapter structure_id_pair_and_distance_list; + typedef std::map > structure_map; + +private: + typedef select_second structure_second_selector_type; + typedef boost::transform_iterator structure_iterator; + typedef std::map structure_type_map; + typedef select_second structure_type_second_selector_type; + typedef boost::transform_iterator structure_type_iterator; + +public: + typedef sized_iterator_range structures_range; + typedef sized_iterator_range structure_types_range; + typedef std::pair position_structid_pair_type; + virtual ~ParticleContainer() {}; @@ -37,11 +86,49 @@ class ParticleContainer virtual length_type world_size() const = 0; + // Species stuff virtual species_type const& get_species(species_id_type const& id) const = 0; + // StructureType stuff +// virtual bool add_structure_type(structure_type_type const& structure_type) = 0; // TODO + + virtual structure_type_type get_structure_type(structure_type_id_type const& sid) const = 0; + + virtual structure_types_range get_structure_types() const = 0; + + virtual structure_type_id_type get_def_structure_type_id() const = 0; + + // Structure stuff +// virtual structure_id_type add_structure(structure_type const& structure) = 0; // TODO + virtual boost::shared_ptr get_structure(structure_id_type const& id) const = 0; + + virtual structures_range get_structures() const = 0; + + virtual boost::shared_ptr get_some_structure_of_type(structure_type_id_type const& sid) const = 0; + +// virtual bool update_structure(structure_id_pair const& structid_pair) = 0; - virtual particle_id_pair new_particle(species_id_type const& sid, + virtual bool remove_structure(structure_id_type const& id) = 0; + + virtual structure_id_set get_structure_ids(structure_type_id_type const& sid) const = 0; + + virtual structure_id_type get_def_structure_id() const = 0; + + virtual structure_id_pair_and_distance_list* get_close_structures(position_type const& pos, structure_id_type const& current_struct_id, + structure_id_type const& ignore) const = 0; + + virtual structure_id_pair_and_distance_list* check_surface_overlap(particle_shape_type const& s, position_type const& old_pos, structure_id_type const& current, + length_type const& sigma) const = 0; + + virtual structure_id_pair_and_distance_list* check_surface_overlap(particle_shape_type const& s, position_type const& old_pos, structure_id_type const& current, + length_type const& sigma, structure_id_type const& ignore) const = 0; + + virtual structure_id_pair_and_distance_list* check_surface_overlap(particle_shape_type const& s, position_type const& old_pos, structure_id_type const& current, + length_type const& sigma, structure_id_type const& ignore1, structure_id_type const& ignore2) const = 0; + + // Particle stuff + virtual particle_id_pair new_particle(species_id_type const& sid, structure_id_type const& structure_id, position_type const& pos) = 0; virtual bool update_particle(particle_id_pair const& pi_pair) = 0; @@ -49,7 +136,7 @@ class ParticleContainer virtual bool remove_particle(particle_id_type const& id) = 0; virtual particle_id_pair get_particle(particle_id_type const& id) const = 0; - + virtual bool has_particle(particle_id_type const& id) const = 0; virtual particle_id_pair_and_distance_list* check_overlap(particle_shape_type const& s) const = 0; @@ -69,10 +156,15 @@ class ParticleContainer virtual length_type apply_boundary(length_type const& v) const = 0; + virtual position_structid_pair_type apply_boundary(position_structid_pair_type const& pos_struct_id) const = 0; + virtual position_type cyclic_transpose(position_type const& p0, position_type const& p1) const = 0; virtual length_type cyclic_transpose(length_type const& p0, length_type const& p1) const = 0; + virtual position_structid_pair_type cyclic_transpose(position_structid_pair_type const& pos_struct_id, + structure_type const& structure) const = 0; + }; diff --git a/ParticleContainerBase.hpp b/ParticleContainerBase.hpp index 04f998aa..c788bfeb 100644 --- a/ParticleContainerBase.hpp +++ b/ParticleContainerBase.hpp @@ -8,28 +8,33 @@ #include "generator.hpp" #include "exceptions.hpp" #include "ParticleContainer.hpp" +#include "StructureContainer.hpp" #include "Transaction.hpp" template struct ParticleContainerUtils { + // The structure is parameterized with the traits of the world typedef Ttraits_ traits_type; - typedef typename traits_type::length_type length_type; - typedef typename traits_type::particle_type particle_type; - typedef typename traits_type::particle_id_type particle_id_type; - typedef std::pair particle_id_pair; - typedef std::pair particle_id_pair_and_distance; - typedef unassignable_adapter particle_id_pair_and_distance_list; - + // shorthand names for all the types that we use + typedef typename traits_type::length_type length_type; + typedef typename traits_type::particle_type particle_type; + typedef typename traits_type::particle_id_type particle_id_type; + typedef std::pair particle_id_pair; + typedef std::pair particle_id_pair_and_distance; + + // This distance_comparator orders two tuples based on the second part of the tuple + // example (obj1, distance1) < (obj2, distance2) if distance1 < distance2 + template struct distance_comparator: public std::binary_function< - typename particle_id_pair_and_distance_list::placeholder, - typename particle_id_pair_and_distance_list::placeholder, + typename Tobject_and_distance_list_::placeholder, + typename Tobject_and_distance_list_::placeholder, bool> { - typedef typename particle_id_pair_and_distance_list::placeholder - first_argument_type; - typedef typename particle_id_pair_and_distance_list::const_caster const_caster; + typedef Tobject_and_distance_list_ list_type; + typedef typename list_type::placeholder first_argument_type; + typedef typename list_type::const_caster const_caster; bool operator()(first_argument_type const& lhs, first_argument_type const& rhs) const { @@ -39,9 +44,15 @@ struct ParticleContainerUtils const_caster c_; }; - template + + // The overlap checker is sorted list of tuples (object, distance) which is sorted on + // the second part of the tuple (the distance). + template struct overlap_checker { + typedef Tobject_and_distance_list_ list_type; + + // The constructor overlap_checker(Tset_ const& ignore = Tset_()): ignore_(ignore), result_(0) {} template @@ -51,13 +62,14 @@ struct ParticleContainerUtils { if (!result_) { - result_ = new particle_id_pair_and_distance_list(); + result_ = new list_type(); } result_->push_back(std::make_pair(*i, dist)); } } - particle_id_pair_and_distance_list* result() const + // The list of items is only sorted when the results are queried. + list_type* result() const { if (result_) { @@ -67,45 +79,71 @@ struct ParticleContainerUtils } private: - Tset_ const& ignore_; - particle_id_pair_and_distance_list* result_; - distance_comparator compare_; + Tset_ const& ignore_; + list_type* result_; + distance_comparator compare_; }; }; + template class ParticleContainerBase : public ParticleContainer { +// This inherits from the ParticleContainer class which is just an abstract data type. +// Here most of the methods of the ParticleContainer are actually implemented. public: typedef ParticleContainerUtils utils; typedef ParticleContainer base_type; typedef Ttraits_ traits_type; - typedef typename traits_type::length_type length_type; - typedef typename traits_type::species_type species_type; - typedef typename traits_type::position_type position_type; - typedef typename traits_type::particle_type particle_type; - typedef typename traits_type::particle_id_type particle_id_type; - typedef typename traits_type::particle_id_generator particle_id_generator; - typedef typename traits_type::species_id_type species_id_type; + + // define some shorthands for all the types from the traits that we use. + typedef typename traits_type::length_type length_type; + typedef typename traits_type::species_type species_type; + typedef typename traits_type::position_type position_type; + typedef typename traits_type::particle_type particle_type; + typedef typename traits_type::particle_id_type particle_id_type; + typedef typename traits_type::particle_id_generator particle_id_generator; + typedef typename traits_type::species_id_type species_id_type; typedef typename traits_type::particle_type::shape_type particle_shape_type; - typedef typename traits_type::size_type size_type; - typedef typename traits_type::structure_id_type structure_id_type; - typedef typename traits_type::structure_type structure_type; - typedef std::pair particle_id_pair; - typedef Transaction transaction_type; + typedef typename traits_type::size_type size_type; + typedef typename traits_type::structure_id_type structure_id_type; + typedef typename traits_type::structure_type structure_type; + typedef typename traits_type::structure_type_id_type structure_type_id_type; + typedef typename base_type::particle_id_set particle_id_set; + typedef typename base_type::particle_id_pair particle_id_pair; + typedef typename base_type::structure_id_set structure_id_set; + typedef typename base_type::structure_id_pair structure_id_pair; + typedef typename base_type::structure_types_range structure_types_range; + + typedef typename base_type::structure_map structure_map; + typedef typename base_type::structures_range structures_range; + typedef Transaction transaction_type; typedef MatrixSpace particle_matrix_type; - typedef abstract_limited_generator particle_id_pair_generator; - typedef std::pair particle_id_pair_and_distance; + typedef abstract_limited_generator particle_id_pair_generator; + typedef std::pair particle_id_pair_and_distance; typedef sized_iterator_range particle_id_pair_range; - typedef unassignable_adapter particle_id_pair_and_distance_list; + typedef StructureContainer structure_container_type; + typedef typename structure_container_type::spherical_surface_type spherical_surface_type; + typedef typename structure_container_type::disk_surface_type disk_surface_type; + typedef typename structure_container_type::cylindrical_surface_type cylindrical_surface_type; + typedef typename structure_container_type::planar_surface_type planar_surface_type; + typedef typename structure_container_type::cuboidal_region_type cuboidal_region_type; + + typedef typename base_type::particle_id_pair_and_distance_list particle_id_pair_and_distance_list; + typedef typename base_type::structure_id_pair_and_distance_list structure_id_pair_and_distance_list; + typedef typename base_type::structure_id_pair_and_distance structure_id_pair_and_distance; + typedef typename base_type::position_structid_pair_type position_structid_pair_type; + -protected: public: + // constructors ParticleContainerBase(length_type world_size, size_type size) - : pmat_(world_size, size) {} + : pmat_(world_size, size), structures_() {} +// ParticleContainerBase(length_type world_size, size_type size, structure_id_type default_structid) +// : pmat_(world_size, size), structures_(default_structid) {} virtual size_type num_particles() const { @@ -149,6 +187,40 @@ class ParticleContainerBase return traits_type::apply_boundary(v, world_size()); } + // Version of apply_boundary that takes the structure on which the particle lives into account + // This also applies the 'local' boundary conditions of the individual structures (reflecting, periodic, moving + // onto another adjacent structure etc). + // + // To call the right function for a particular Surface we: + // + // - first need to call the 'apply_boundary method of the structure in question with the StructureContainer as an argument (to get + // the StructureContainer that we need to use ->structures don't know about the container they're in!) + // + // - second call the apply_boundary method of the structure_container using the structure (which is now of a fully specified type!!!) + // as an argument (for example see PlanarSurface.hpp, NOT Surface.hpp) + // + // - Use this fully defined type to call the right function through the template method 'apply_boundary' of the StructureContainer. + // (See StructureContainer.hpp) + virtual position_structid_pair_type apply_boundary(position_structid_pair_type const& pos_struct_id) const + { +// // cyclic transpose the position with the structure +// const boost::shared_ptr structure (get_structure(pos_struct_id.second)); +// const position_structid_pair_type cyc_pos_struct_id (std::make_pair(cyclic_transpose(pos_struct_id.first, structure->position()), +// pos_struct_id.second)); + + // TESTING: cyclic transpose should not be applied when going around an edge! + // TODO Clean this up when it is clear that cyclic_transpose is unnecessary here. + const position_structid_pair_type cyc_pos_struct_id( pos_struct_id ); + // Get the structure + const boost::shared_ptr structure( get_structure(pos_struct_id.second) ); + // The generalized boundary condition application first: + // 1. applies the boundary of the structure/surface (go around the corner etc) + const position_structid_pair_type new_pos_struct_id( structure->apply_boundary(cyc_pos_struct_id, structures_) ); + + // 2. Then also apply the boundary condition of the world + return std::make_pair(apply_boundary(new_pos_struct_id.first), new_pos_struct_id.second); + } + virtual position_type cyclic_transpose(position_type const& p0, position_type const& p1) const { return traits_type::cyclic_transpose(p0, p1, world_size()); @@ -159,13 +231,22 @@ class ParticleContainerBase return traits_type::cyclic_transpose(p0, p1, world_size()); } + virtual position_structid_pair_type cyclic_transpose(position_structid_pair_type const& pos_struct_id, + structure_type const& structure) const + { + const position_structid_pair_type cyc_pos_struct_id (std::make_pair(cyclic_transpose(pos_struct_id.first, structure.position()), + pos_struct_id.second)); + return structure.cyclic_transpose(cyc_pos_struct_id, structures_); + } + + // THIS SEEMS STRANGE TO PUT THIS HERE. template T1_ calculate_pair_CoM( T1_ const& p1, T1_ const& p2, typename element_type_of::type const& D1, typename element_type_of::type const& D2) { - typedef typename element_type_of< T1_ >::type element_type; + //typedef typename element_type_of< T1_ >::type element_type; T1_ retval; @@ -185,7 +266,7 @@ class ParticleContainerBase virtual particle_id_pair_and_distance_list* check_overlap(particle_shape_type const& s, particle_id_type const& ignore) const { - return check_overlap(s, array_gen(ignore)); + return check_overlap(s, array_gen(ignore)); // Why no parameterization of the template here? } virtual particle_id_pair_and_distance_list* check_overlap(particle_shape_type const& s, particle_id_type const& ignore1, particle_id_type const& ignore2) const @@ -197,18 +278,76 @@ class ParticleContainerBase particle_id_pair_and_distance_list* check_overlap(Tsph_ const& s, Tset_ const& ignore, typename boost::disable_if >::type* =0) const { - typename utils::template overlap_checker oc(ignore); + typename utils::template overlap_checker oc(ignore); traits_type::take_neighbor(pmat_, oc, s); return oc.result(); } - + // This is the same method as the one above but now without the 'ignore' list template particle_id_pair_and_distance_list* check_overlap(Tsph_ const& s, typename boost::disable_if >::type* =0) const { - typename utils::template overlap_checker > oc; + typename utils::template overlap_checker > oc; traits_type::take_neighbor(pmat_, oc, s); return oc.result(); + } + + virtual structure_id_pair_and_distance_list* check_surface_overlap(particle_shape_type const& s, position_type const& old_pos, structure_id_type const& current, + length_type const& sigma) const + { + typename utils::template overlap_checker > checker; + surface_overlap_checker(s, old_pos, current, sigma, checker); + return checker.result(); + } + + virtual structure_id_pair_and_distance_list* check_surface_overlap(particle_shape_type const& s, position_type const& old_pos, structure_id_type const& current, + length_type const& sigma, structure_id_type const& ignore) const + { + typename utils::template overlap_checker > checker(array_gen(ignore)); + surface_overlap_checker(s, old_pos, current, sigma, checker); + return checker.result(); + } + + virtual structure_id_pair_and_distance_list* check_surface_overlap(particle_shape_type const& s, position_type const& old_pos, structure_id_type const& current, + length_type const& sigma, structure_id_type const& ignore1, structure_id_type const& ignore2) const + { + typename utils::template overlap_checker > checker(array_gen(ignore1, ignore2)); + surface_overlap_checker(s, old_pos, current, sigma, checker); + return checker.result(); + } + + template + void surface_overlap_checker(particle_shape_type const& s, position_type const& old_pos, structure_id_type const& current, + length_type const& sigma, Tfun_& checker ) const + { + const structure_id_set visible_structure_IDs (structures_.get_visible_structures(current)); + + // Get and temporarily store all the visibles structures (upto now we only had their IDs) + structure_map visible_structures; + for (typename structure_id_set::const_iterator i(visible_structure_IDs.begin()), + e(visible_structure_IDs.end()); + i != e; ++i) + { + visible_structures[(*i)] = get_structure(*i); + } + + // Calculate the distances and store the surface,distance tuple in the overlap checker if smaller than the particle radius. + for (typename structure_map::const_iterator i(visible_structures.begin()), + e(visible_structures.end()); + i != e; ++i) + { + const position_type cyc_old_pos ( cyclic_transpose(old_pos, s.position()) ); // The old position transposed towards the new position (which may also be modified by periodic BC's) + const position_type displacement ( subtract(s.position(), cyc_old_pos) ); // the relative displacement from the 'old' position towards the real new position + const position_type cyc_pos ( cyclic_transpose(s.position(), ((*i).second)->position()) ); // new position transposed to the structure in question + const position_type cyc_old_pos2 ( subtract(cyc_pos, displacement) ); // calculate the old_pos relative to the transposed new position. + // This is where the actual distance measurement happens + // TODO What precisely does newBD_distance calculate? Clarify! + const length_type dist((*i).second->newBD_distance(cyc_pos, s.radius(), cyc_old_pos2, sigma)); + if (dist < s.radius()) + { + checker(i, dist); + } + } } particle_id_pair get_particle(particle_id_type const& id, bool& found) const @@ -237,7 +376,7 @@ class ParticleContainerBase return pmat_.end() != pmat_.find(id); } - virtual transaction_type* create_transaction(); + virtual transaction_type* create_transaction(); // The implementation is below as an inline function? virtual particle_id_pair_generator* get_particles() const { @@ -259,10 +398,88 @@ class ParticleContainerBase return pmat_.erase(id); } + ///// Structure methods + ///// The following are mostly wrappers of the methods defined in StructureContainer + ///// and handle structure connectivity and boundary application. + + // Check for whether a container contains a structure with a certain ID + virtual bool has_structure(structure_id_type const& id) const + { + return structures_.has_structure(id); + } + + // Getter structure_id -> structure + virtual boost::shared_ptr get_structure(structure_id_type const& id) const + { + return structures_.get_structure(id); + } + + // Get range of structures in container + virtual structures_range get_structures() const + { + return structures_.get_structures_range(); + } + + // Getter structure_type_id -> some structure of that type + virtual boost::shared_ptr get_some_structure_of_type(structure_type_id_type const& sid) const + { + return structures_.get_some_structure_of_type(sid); + } + + // Update structure wrapper + template + bool update_structure(Tstructid_pair_ const& structid_pair) + { + return structures_.update_structure(structid_pair); + } + + // Remove structure wrapper + virtual bool remove_structure(structure_id_type const& id) + { + return structures_.remove_structure(id); + } + + // Get all structures close to a position pos, taking care of structure types + // and an ignore parameter (defining a set of ignored structures) + // The actual distance measurement is performed by method structure->distance(cyc_pos) below, + // which is implemented in the respective structure (sub-) classes. + virtual structure_id_pair_and_distance_list* get_close_structures(position_type const& pos, structure_id_type const& current_struct_id, + structure_id_type const& ignore) const + { + typename utils::template overlap_checker > checker(array_gen(ignore)); + + const structure_id_set visible_structure_IDs (structures_.get_visible_structures(current_struct_id)); + + // Get and temporarily store all the visibles structures (upto now we only had their IDs) + structure_map visible_structures; + for (typename structure_id_set::const_iterator i(visible_structure_IDs.begin()), + e(visible_structure_IDs.end()); + i != e; ++i) + { + visible_structures[(*i)] = get_structure(*i); + } + + // Calculate the distances and store the surface,distance tuple in the overlap checker (in a list is sorted by distance). + for (typename structure_map::const_iterator i(visible_structures.begin()), + e(visible_structures.end()); + i != e; ++i) + { + const position_type cyc_pos(cyclic_transpose(pos, ((*i).second)->position())); + // Here we perform the actual distance measurement + const length_type dist((*i).second->distance(cyc_pos)); + checker(i, dist); + } + return checker.result(); + } + +///////// Member variables protected: - particle_matrix_type pmat_; + particle_matrix_type pmat_; // the structure (MatrixSpace) containing the particles. + structure_container_type structures_; // an object containing the structures. }; + +//////// Inline methods are defined separately template inline Transaction* ParticleContainerBase::create_transaction() diff --git a/ParticleID.hpp b/ParticleID.hpp index f7710041..fc28fb45 100644 --- a/ParticleID.hpp +++ b/ParticleID.hpp @@ -16,6 +16,7 @@ #include "Identifier.hpp" struct ParticleID: public Identifier +// The ParticleID is a class for the identification of particles { typedef Identifier base_type; @@ -33,6 +34,7 @@ namespace boost { template<> struct hash +// Hashing function?? { std::size_t operator()(ParticleID const& val) const { @@ -51,6 +53,7 @@ struct hash template inline std::basic_ostream& operator<<(std::basic_ostream& strm, const ParticleID& v) +// Provides a stream of characters (a string) of the 'particle id' that allows for printing. { strm << "PID(" << v().first << ":" << v().second << ")"; return strm; diff --git a/ParticleModel.cpp b/ParticleModel.cpp index 1f3a81ac..64d67b59 100644 --- a/ParticleModel.cpp +++ b/ParticleModel.cpp @@ -3,6 +3,7 @@ #endif /* HAVE_CONFIG_H */ #include +#include #include #include @@ -10,42 +11,58 @@ #include "ParticleModel.hpp" +// Constructor ParticleModel::ParticleModel() { + // TODO add default structure_type for the bulk? + boost::shared_ptr default_structure_type(new StructureType()); + add_structure_type(default_structure_type); + default_structure_type_id_ = default_structure_type->id(); } ParticleModel::~ParticleModel() { } -void ParticleModel::add_structure_type(boost::shared_ptr const& structure) +// Add a structure type to the model +void ParticleModel::add_structure_type(boost::shared_ptr const& structure_type) { - std::pair r( - structure_type_map_.insert(std::make_pair(structure->id(), structure))); - if (!r.second) - { - throw already_exists( - (boost::format("structure id \"%s\" is already used by %s") % - structure->id() % - boost::lexical_cast(*(*(r.first)).second)).str()); - } - structure->bind_to_model(this, structure->id()); + // std::pair r( + // structure_type_map_.insert(std::make_pair(structure_type->id(), structure_type))); + // if (!r.second) + // { + // throw already_exists( + // (boost::format("structure_type id \"%s\" is already used by %s") % + // structure_type->id() % + // boost::lexical_cast(*(*(r.first)).second)).str()); + // } + structure_type->bind_to_model(this, species_type_id_generator_()); + structure_type_map_.insert(std::make_pair(structure_type->id(), structure_type)); } -boost::shared_ptr ParticleModel::get_structure_type_by_id(structure_id_type const& id) const +// Get a structure type from the model +boost::shared_ptr ParticleModel::get_structure_type_by_id(structure_type_id_type const& id) const { - structure_type_map_type::const_iterator i(structure_type_map_.find(id)); + structure_type_map::const_iterator i(structure_type_map_.find(id)); if (structure_type_map_.end() == i) { - throw not_found(boost::lexical_cast(id)); + throw not_found(std::string("Unknown structure_type (id=") + boost::lexical_cast(id) + ")"); } return (*i).second; } -ParticleModel::structure_type_range ParticleModel::get_structure_types() const +// Get all the structure types that are present in the particle model +ParticleModel::structure_types_range ParticleModel::get_structure_types() const +{ + return structure_types_range( + structure_type_iterator(structure_type_map_.begin(), structure_type_second_selector_type()), + structure_type_iterator(structure_type_map_.end(), structure_type_second_selector_type())); +} + +ParticleModel::structure_type_id_type ParticleModel::get_def_structure_type_id() const { - return structure_type_range( - structure_type_iterator(structure_type_map_.begin(), structure_second_selector_type()), - structure_type_iterator(structure_type_map_.end(), structure_second_selector_type())); +// return boost::shared_ptr(default_structure_type_); + return default_structure_type_id_; } + diff --git a/ParticleModel.hpp b/ParticleModel.hpp index 03421079..37dd43cf 100644 --- a/ParticleModel.hpp +++ b/ParticleModel.hpp @@ -6,34 +6,46 @@ #include "StructureType.hpp" class ParticleModel: public Model +// The ParticleModel class add spatial properties (e.g. particles) to the model. The model by itself could +// namely also just model well mixed systems. { public: - typedef Model base_type; - typedef StructureType structure_type_type; - typedef structure_type_type::identifier_type structure_id_type; + // defining shorthands for the used types + typedef Model base_type; - typedef std::map > structure_type_map_type; + typedef StructureType structure_type_type; + typedef structure_type_type::identifier_type structure_type_id_type; - typedef select_second structure_second_selector_type; + typedef std::map > structure_type_map; + typedef select_second structure_type_second_selector_type; public: - typedef boost::transform_iterator structure_type_iterator; - typedef boost::iterator_range structure_type_range; + typedef boost::transform_iterator structure_type_iterator; + typedef boost::iterator_range structure_types_range; public: + // Constructor ParticleModel(); virtual ~ParticleModel(); - boost::shared_ptr get_structure_type_by_id(structure_id_type const& id) const; + // Gets a structure type + boost::shared_ptr get_structure_type_by_id(structure_type_id_type const& id) const; - void add_structure_type(boost::shared_ptr const& structure); + // Add a structure type + void add_structure_type(boost::shared_ptr const& structure_type); - structure_type_range get_structure_types() const; + // Get all structure types in the model + structure_types_range get_structure_types() const; + // Gets and sets the default structure_type + structure_type_id_type get_def_structure_type_id() const; + +/////// Member variables public: - structure_type_map_type structure_type_map_; + structure_type_map structure_type_map_; // mapping: structure_type_id -> structure_type + structure_type_id_type default_structure_type_id_; // The id of the default structure_type ("world") }; diff --git a/ParticleSimulationStructure.hpp b/ParticleSimulationStructure.hpp index 0fccbd33..127b26b4 100644 --- a/ParticleSimulationStructure.hpp +++ b/ParticleSimulationStructure.hpp @@ -6,15 +6,18 @@ template struct ImmutativeStructureVisitor; -template +template // Note that Ttraits_ refers to a the World traits struct MutativeStructureVisitor; template -struct ParticleSimulationStructure: public Structure +struct ParticleSimulationStructure: public Structure { typedef Ttraits_ traits_type; - typedef Structure base_type; - typedef typename base_type::identifier_type identifier_type; + typedef Structure base_type; + + typedef typename base_type::structure_name_type structure_name_type; + typedef typename base_type::structure_type_id_type structure_type_id_type; + typedef typename base_type::structure_id_type structure_id_type; virtual ~ParticleSimulationStructure() {} @@ -22,7 +25,9 @@ struct ParticleSimulationStructure: public Structure const&) = 0; - ParticleSimulationStructure(identifier_type const& id): base_type(id) {} + // Constructor + ParticleSimulationStructure(structure_name_type const& name, structure_type_id_type const& sid, structure_id_type const& parent_struct_id): + base_type(name, sid, parent_struct_id) {} }; #endif /* PARTICLE_SIMULATION_STRUCTURE_HPP */ diff --git a/ParticleSimulator.hpp b/ParticleSimulator.hpp index c59d649c..908ff58c 100644 --- a/ParticleSimulator.hpp +++ b/ParticleSimulator.hpp @@ -4,13 +4,16 @@ #include #include #include "Sphere.hpp" +#include "Disk.hpp" #include "Cylinder.hpp" #include "Box.hpp" #include "ParticleSimulationStructure.hpp" #include "CuboidalRegion.hpp" #include "PlanarSurface.hpp" #include "CylindricalSurface.hpp" +#include "DiskSurface.hpp" #include "SphericalSurface.hpp" +#include "ParticleContainer.hpp" #include "NetworkRules.hpp" #include "NetworkRulesWrapper.hpp" #include "ReactionRuleInfo.hpp" @@ -22,19 +25,22 @@ template struct ParticleSimulatorTraitsBase { typedef Tworld_ world_type; + + // shorthand typedefs typedef Real rate_type; typedef Real time_type; - typedef int reaction_rule_id_type; + typedef int reaction_rule_id_type; typedef ReactionRuleInfo< reaction_rule_id_type, typename world_type::traits_type::species_id_type, - rate_type> reaction_rule_type; + rate_type> reaction_rule_type; typedef NetworkRulesWrapper network_rules_type; + reaction_rule_type> network_rules_type; typedef ReactionRecord reaction_record_type; - typedef ReactionRecorder reaction_recorder_type; - typedef VolumeClearer volume_clearer_type; + reaction_rule_id_type> reaction_record_type; + typedef ReactionRecorder reaction_recorder_type; + typedef VolumeClearer volume_clearer_type; static const Real MINIMAL_SEPARATION_FACTOR = (1.0 + 1e-7); }; @@ -42,20 +48,27 @@ struct ParticleSimulatorTraitsBase template class ParticleSimulator; +template +class ParticleContainer; + template struct ImmutativeStructureVisitor { typedef Ttraits_ traits_type; - typedef typename ParticleSimulator::spherical_surface_type spherical_surface_type; - typedef typename ParticleSimulator::cylindrical_surface_type cylindrical_surface_type; - typedef typename ParticleSimulator::planar_surface_type planar_surface_type; - typedef typename ParticleSimulator::cuboidal_region_type cuboidal_region_type; + typedef typename ParticleContainer::spherical_surface_type spherical_surface_type; + typedef typename ParticleContainer::cylindrical_surface_type cylindrical_surface_type; + typedef typename ParticleContainer::planar_surface_type planar_surface_type; + typedef typename ParticleContainer::cuboidal_region_type cuboidal_region_type; + typedef typename ParticleContainer::disk_surface_type disk_surface_type; + virtual ~ImmutativeStructureVisitor() {} virtual void operator()(spherical_surface_type const&) const = 0; virtual void operator()(cylindrical_surface_type const&) const = 0; + + virtual void operator()(disk_surface_type const&) const = 0; virtual void operator()(planar_surface_type const&) const = 0; @@ -66,16 +79,20 @@ template struct MutativeStructureVisitor { typedef Ttraits_ traits_type; - typedef typename ParticleSimulator::spherical_surface_type spherical_surface_type; - typedef typename ParticleSimulator::cylindrical_surface_type cylindrical_surface_type; - typedef typename ParticleSimulator::planar_surface_type planar_surface_type; - typedef typename ParticleSimulator::cuboidal_region_type cuboidal_region_type; + typedef typename ParticleContainer::spherical_surface_type spherical_surface_type; + typedef typename ParticleContainer::cylindrical_surface_type cylindrical_surface_type; + typedef typename ParticleContainer::planar_surface_type planar_surface_type; + typedef typename ParticleContainer::cuboidal_region_type cuboidal_region_type; + typedef typename ParticleContainer::disk_surface_type disk_surface_type; + virtual ~MutativeStructureVisitor() {} virtual void operator()(spherical_surface_type&) const = 0; virtual void operator()(cylindrical_surface_type&) const = 0; + + virtual void operator()(disk_surface_type const&) const = 0; virtual void operator()(planar_surface_type&) const = 0; @@ -86,25 +103,31 @@ template class ParticleSimulator { public: - typedef Ttraits_ traits_type; - typedef typename traits_type::world_type world_type; - typedef Sphere sphere_type; - typedef Cylinder cylinder_type; - typedef Box box_type; - typedef Plane plane_type; - typedef ParticleSimulationStructure particle_simulation_structure_type; - typedef Surface surface_type; - typedef Region region_type; - typedef SphericalSurface spherical_surface_type; - typedef CylindricalSurface cylindrical_surface_type; - typedef PlanarSurface planar_surface_type; - typedef CuboidalRegion cuboidal_region_type; - typedef typename traits_type::network_rules_type network_rules_type; - typedef typename world_type::traits_type::rng_type rng_type; - typedef typename traits_type::time_type time_type; - typedef typename traits_type::reaction_record_type reaction_record_type; + typedef Ttraits_ traits_type; + typedef typename traits_type::world_type world_type; + typedef typename world_type::traits_type world_traits_type; + + // shorthand typedefs + typedef Sphere sphere_type; + typedef Cylinder cylinder_type; + typedef Disk disk_type; + typedef Box box_type; + typedef Plane plane_type; + typedef ParticleSimulationStructure particle_simulation_structure_type; + typedef Surface surface_type; + typedef Region region_type; + typedef SphericalSurface spherical_surface_type; + typedef CylindricalSurface cylindrical_surface_type; + typedef DiskSurface disk_surface_type; + typedef PlanarSurface planar_surface_type; + typedef CuboidalRegion cuboidal_region_type; + + typedef typename traits_type::network_rules_type network_rules_type; + typedef typename world_traits_type::rng_type rng_type; + typedef typename traits_type::time_type time_type; + typedef typename traits_type::reaction_record_type reaction_record_type; typedef typename traits_type::reaction_recorder_type reaction_recorder_type; - typedef typename traits_type::volume_clearer_type volume_clearer_type; + typedef typename traits_type::volume_clearer_type volume_clearer_type; public: virtual ~ParticleSimulator() {} @@ -169,6 +192,8 @@ class ParticleSimulator virtual bool step(time_type upto) = 0; + +////// Member variables protected: boost::shared_ptr world_; boost::shared_ptr network_rules_; diff --git a/PlanarSurface.hpp b/PlanarSurface.hpp index ef0e97b9..57fe0d95 100644 --- a/PlanarSurface.hpp +++ b/PlanarSurface.hpp @@ -4,25 +4,54 @@ #include #include "Surface.hpp" #include "Plane.hpp" +#include "freeFunctions.hpp" +#include "StructureFunctions.hpp" + +template +class StructureContainer; + template class PlanarSurface - : public BasicSurfaceImpl > + : public BasicSurfaceImpl > { + // The planar surface is an implementation of a Basic surface parameterized with the plane + public: - typedef BasicSurfaceImpl > base_type; - typedef typename base_type::traits_type traits_type; - typedef typename base_type::identifier_type identifier_type; - typedef typename base_type::shape_type shape_type; - typedef typename base_type::rng_type rng_type; - typedef typename base_type::position_type position_type; - typedef typename base_type::length_type length_type; + typedef BasicSurfaceImpl > base_type; + typedef Ttraits_ traits_type; + typedef typename base_type::structure_name_type structure_name_type; // This is just the name of the structure + typedef typename base_type::structure_id_type structure_id_type; + typedef typename base_type::structure_type_id_type structure_type_id_type; + typedef typename base_type::shape_type shape_type; + typedef typename base_type::rng_type rng_type; + typedef typename base_type::position_type position_type; + typedef typename base_type::length_type length_type; + typedef typename base_type::side_enum_type side_enum_type; + typedef typename traits_type::species_type species_type; + typedef typename traits_type::structure_type structure_type; + typedef StructureContainer, structure_id_type, traits_type> structure_container_type; + + typedef std::pair position_pair_type; + typedef std::pair position_structid_pair_type; + typedef std::pair position_structid_pair_pair_type; + + + /*** Info functions ***/ + virtual position_type const& position() const + { + return base_type::shape().position(); + } + + /*** Simple structure-specific sampling functions ***/ + // Draw a random position in the plane virtual position_type random_position(rng_type& rng) const { return ::random_position(base_type::shape(), boost::bind(&rng_type::uniform, rng, -1., 1.)); } + // Create a random vector in the plane virtual position_type random_vector(length_type const& r, rng_type& rng) const { return multiply( @@ -34,7 +63,8 @@ class PlanarSurface base_type::shape().units()[1], rng.uniform(-1., 1.)))), r); } - virtual position_type bd_displacement(length_type const& r, rng_type& rng) const + // Calculate a bd displacement for particles diffusing in the plane + virtual position_type bd_displacement(length_type const& mean, length_type const& r, rng_type& rng) const { length_type const x(rng.normal(0., r)), y(rng.normal(0., r)); return add( @@ -42,11 +72,378 @@ class PlanarSurface multiply(base_type::shape().unit_y(), y)); } + /*** New BD scheme functions ***/ + // Rate for binding to particle on the structure + virtual Real get_1D_rate_geminate( Real const& k, length_type const& r01) const + { + return k / (2 * M_PI * r01); + } + + // Rate for binding to the structure + virtual Real get_1D_rate_surface( Real const& k, length_type const& r0 ) const + { + return k; + } + + // Reaction volume for binding to particle in the structure + virtual Real particle_reaction_volume( length_type const& r01, length_type const& rl ) const + { + length_type r01l( r01 + rl ); + length_type r01l_sq( r01l * r01l ); + length_type r01_sq( r01 * r01 ); + + return M_PI * ( r01l_sq - r01_sq ); + // TODO Is there a correction factor if binding is allowed from both sides of the planes? + } + + // Reaction volume for binding to the structure + virtual Real surface_reaction_volume( length_type const& r0, length_type const& rl ) const + { + if( base_type::shape().is_one_sided() ) + return rl; + else + return 2.0*rl; + } + + // Vector of dissociation from the structure to parent structure + virtual position_type surface_dissociation_vector( rng_type& rng, length_type const& r0, length_type const& rl ) const + { + Real X( rng.uniform(0.,1.) ); + length_type diss_vec_length( X*rl ); + + if( base_type::shape().is_one_sided() ) + { + return multiply( base_type::shape().unit_z(), diss_vec_length ); + } + else + { + Real sign( rng.uniform_int(0, 1) * 2 - 1 ); + diss_vec_length *= sign; + + return multiply( base_type::shape().unit_z(), diss_vec_length ); + } + } + + // Normed direction of dissociation from the structure to parent structure + virtual position_type surface_dissociation_unit_vector( rng_type& rng ) const + { + return base_type::shape().unit_z(); + } + + // Vector used to determine whether a particle has crossed the structure + // For the plane the normal vector is the natural choice + virtual position_type const side_comparison_vector() const + { + return base_type::shape().unit_z(); + } + + // Positions created at dissociation of one particle on the structure into two particles on the structure + virtual position_pair_type geminate_dissociation_positions( rng_type& rng, species_type const& s0, species_type const& s1, + position_type const& op, length_type const& rl ) const + { + length_type const r01( s0.radius() + s1.radius() ); + Real const D01( s0.D() + s1.D() ); + + Real X( rng.uniform(0.,1.) ); + + length_type const r01l( r01 + rl ); + length_type const r01l_sq( r01l * r01l ); + length_type const r01_sq( r01 * r01 ); + + // The square takes into account the radial character of the sampled length + length_type const diss_vec_length( sqrt( r01_sq + X * (r01l_sq - r01_sq) ) ); + assert(diss_vec_length >= r01); + + position_type const m( random_vector( diss_vec_length, rng ) ); + + return position_pair_type( op - m * s0.D() / D01, + op + m * s1.D() / D01 ); + } + + // Positions created at dissociation of one particle on the structure into two particles, one of which ends up in the bulk + virtual position_pair_type special_geminate_dissociation_positions( rng_type& rng, species_type const& s_surf, species_type const& s_bulk, + position_type const& op_surf, length_type const& rl ) const + { + length_type const r01( s_bulk.radius() + s_surf.radius() ); + Real const D01( s_bulk.D() + s_surf.D() ); + Real const D_bulk_D01( s_bulk.D() / D01 ); + Real const D_surf_D01( s_surf.D() / D01 ); + + //Real theta_max( M_PI/2 - asin(s_bulk.radius() / ( r01 )) ); + //theta_max = theta_max < 0 ? 0 : theta_max; + + // Create random angles for later construction of randomly oriented vector + Real const theta( rng.uniform(0.,1.) * M_PI ); + Real const phi( rng.uniform(0.,1.) * 2 * M_PI ); + + // Construct dissociation length + Real const X( rng.uniform(0.,1.) ); + length_type const r01l( r01 + rl ); + length_type const r01l_cb( r01l * r01l * r01l ); + length_type const r01_cb( r01 * r01 * r01 ); + + length_type const diss_vec_length( cbrt( X * (r01l_cb - r01_cb ) + r01_cb ) ); + + + // Determine direction of dissociation for the particle that ends up in the bulk + // As a standard it is the plane's unit_z vector + position_type unit_z( base_type::shape().unit_z() ); + // If the plane however is two-sided: randomize! + if( not(base_type::shape().is_one_sided()) ) + unit_z = multiply(unit_z, rng.uniform_int(0, 1) * 2 - 1); + + // Construct randomly oriented dissociation vector + length_type const x( diss_vec_length * sin( theta ) * cos( phi ) ); + length_type const y( diss_vec_length * sin( theta ) * sin( phi ) ); + length_type const z( diss_vec_length * cos( theta ) ); + + position_pair_type pp01; + + // This is the particle that will end up on the surface + pp01.first = subtract( op_surf, + add( base_type::shape().unit_x() * (x * D_surf_D01), + base_type::shape().unit_y() * (y * D_surf_D01) ) ); + + // This is the particle that will end up in the bulk + pp01.second = add( op_surf, + add( base_type::shape().unit_x() * (x * D_bulk_D01), + add( base_type::shape().unit_y() * (y * D_bulk_D01), + unit_z * abs(z) ) ) ); + + return pp01; + + } + + // Used by newBDPropagator + virtual length_type newBD_distance(position_type const& new_pos, length_type const& radius, position_type const& old_pos, length_type const& sigma) const + { + const boost::array half_lengths(base_type::shape().half_extent()); + const boost::array new_pos_xyz(::to_internal(base_type::shape(), new_pos)); + const boost::array old_pos_xyz(::to_internal(base_type::shape(), old_pos)); + if (new_pos_xyz[2] * old_pos_xyz[2] < 0 && + ((abs(new_pos_xyz[0]) < half_lengths[0] && abs(new_pos_xyz[1]) < half_lengths[1]) || + (abs(old_pos_xyz[0]) < half_lengths[0] && abs(old_pos_xyz[1]) < half_lengths[1])) + ) // If the new and old positions lie on different sides of the plane + { + return -1.0 * base_type::distance(new_pos) + sigma; + } + else // new and old position are on the same side of the plane + { + return base_type::distance(new_pos) + sigma; + } + } +/* virtual length_type minimal_distance(length_type const& radius) const { // PlanarSurface has thickness of 0. return radius * traits_type::MINIMAL_SEPARATION_FACTOR; } +*/ + /*** Boundary condition handling ***/ + // FIXME This is a mess but it works. See ParticleContainerBase.hpp for explanation. + virtual position_structid_pair_type apply_boundary(position_structid_pair_type const& pos_struct_id, + structure_container_type const& structure_container) const + { + return structure_container.apply_boundary(*this, pos_struct_id); + } + + virtual position_structid_pair_type cyclic_transpose(position_structid_pair_type const& pos_struct_id, + structure_container_type const& structure_container) const + { + return structure_container.cyclic_transpose(*this, pos_struct_id); + } + + + // *** Dynamic dispatch for the structure functions *** // + // *** 1 *** - One new position + // This requires a double dynamic dispatch. + // First dispatch + virtual position_structid_pair_type get_pos_sid_pair(structure_type const& target_structure, position_type const& position, + length_type const& offset, length_type const& reaction_length, rng_type& rng) const + { + return target_structure.get_pos_sid_pair_helper(*this, position, offset, reaction_length, rng); + } + // Second dispatch + virtual position_structid_pair_type get_pos_sid_pair_helper(CuboidalRegion const& origin_structure, position_type const& position, + length_type const& offset, length_type const& rl, rng_type& rng) const + { + return this->get_pos_sid_pair_helper_any >(origin_structure, position, offset, rl, rng); + } + virtual position_structid_pair_type get_pos_sid_pair_helper(SphericalSurface const& origin_structure, position_type const& position, + length_type const& offset, length_type const& rl, rng_type& rng) const + { + return this->get_pos_sid_pair_helper_any >(origin_structure, position, offset, rl, rng); + } + virtual position_structid_pair_type get_pos_sid_pair_helper(CylindricalSurface const& origin_structure, position_type const& position, + length_type const& offset, length_type const& rl, rng_type& rng) const + { + return this->get_pos_sid_pair_helper_any >(origin_structure, position, offset, rl, rng); + } + virtual position_structid_pair_type get_pos_sid_pair_helper(DiskSurface const& origin_structure, position_type const& position, + length_type const& offset, length_type const& rl, rng_type& rng) const + { + return this->get_pos_sid_pair_helper_any >(origin_structure, position, offset, rl, rng); + } + virtual position_structid_pair_type get_pos_sid_pair_helper(PlanarSurface const& origin_structure, position_type const& position, + length_type const& offset, length_type const& rl, rng_type& rng) const + { + return this->get_pos_sid_pair_helper_any >(origin_structure, position, offset, rl, rng); + } + // The template function that defines the actual final dispatch procedure. + template + position_structid_pair_type get_pos_sid_pair_helper_any(Tstruct_ const& origin_structure, position_type const& position, + length_type const& offset, length_type const& rl, rng_type& rng) const + { + // redirect to structure function with well-defined typing + return ::get_pos_sid_pair(origin_structure, *this, position, offset, rl, rng); + }; + + // *** 2 *** - Two new positions + // Same principle as above, but different return type + // First dispatch + virtual position_structid_pair_pair_type get_pos_sid_pair_pair(structure_type const& target_structure, position_type const& position, + species_type const& s1, species_type const& s2, length_type const& reaction_length, rng_type& rng) const + { + return target_structure.get_pos_sid_pair_pair_helper(*this, position, s1, s2, reaction_length, rng); + } + // Second dispatch + virtual position_structid_pair_pair_type get_pos_sid_pair_pair_helper(CuboidalRegion const& origin_structure, position_type const& position, + species_type const& s_orig, species_type const& s_targ, length_type const& rl, rng_type& rng) const + { + return this->get_pos_sid_pair_pair_helper_any >(origin_structure, position, s_orig, s_targ, rl, rng); + } + virtual position_structid_pair_pair_type get_pos_sid_pair_pair_helper(SphericalSurface const& origin_structure, position_type const& position, + species_type const& s_orig, species_type const& s_targ, length_type const& rl, rng_type& rng) const + { + return this->get_pos_sid_pair_pair_helper_any >(origin_structure, position, s_orig, s_targ, rl, rng); + } + virtual position_structid_pair_pair_type get_pos_sid_pair_pair_helper(CylindricalSurface const& origin_structure, position_type const& position, + species_type const& s_orig, species_type const& s_targ, length_type const& rl, rng_type& rng) const + { + return this->get_pos_sid_pair_pair_helper_any >(origin_structure, position, s_orig, s_targ, rl, rng); + } + virtual position_structid_pair_pair_type get_pos_sid_pair_pair_helper(DiskSurface const& origin_structure, position_type const& position, + species_type const& s_orig, species_type const& s_targ, length_type const& rl, rng_type& rng) const + { + return this->get_pos_sid_pair_pair_helper_any >(origin_structure, position, s_orig, s_targ, rl, rng); + } + virtual position_structid_pair_pair_type get_pos_sid_pair_pair_helper(PlanarSurface const& origin_structure, position_type const& position, + species_type const& s_orig, species_type const& s_targ, length_type const& rl, rng_type& rng) const + { + return this->get_pos_sid_pair_pair_helper_any >(origin_structure, position, s_orig, s_targ, rl, rng); + } + // The template function that defines the actual final dispatch procedure. + template + position_structid_pair_pair_type get_pos_sid_pair_pair_helper_any(Tstruct_ const& origin_structure, position_type const& position, + species_type const& s_orig, species_type const& s_targ, length_type const& rl, rng_type& rng) const + { + // redirect to structure function with well-defined typing + return ::get_pos_sid_pair_pair(origin_structure, *this, position, s_orig, s_targ, rl, rng); + }; + + // *** 3 *** - Pair reactions => two origin structures + // First dispatch +// // Overloading get_pos_sid_pair with signature (origin_structure2, target_structure_type_id, ...) +// virtual position_structid_pair_type get_pos_sid_pair(structure_type const& origin_structure2, structure_type_id_type const& target_sid, position_type const& CoM, +// length_type const& offset, length_type const& reaction_length, rng_type& rng) const +// { +// // this just redirects +// return this->get_pos_sid_pair_2o(origin_structure2, target_sid, CoM, offset, reaction_length, rng); +// } +// // The actual implementation of the first dispatch + virtual position_structid_pair_type get_pos_sid_pair_2o(structure_type const& origin_structure2, structure_type_id_type const& target_sid, + position_type const& CoM, length_type const& offset, length_type const& reaction_length, rng_type& rng) const + { + return origin_structure2.get_pos_sid_pair_2o_helper(*this, target_sid, CoM, offset, reaction_length, rng); + } + // Second dispatch + virtual position_structid_pair_type get_pos_sid_pair_2o_helper(CuboidalRegion const& origin_structure1, structure_type_id_type const& target_sid, + position_type const& CoM, length_type const& offset, length_type const& rl, rng_type& rng) const + { + return this->get_pos_sid_pair_2o_helper_any >(origin_structure1, target_sid, CoM, offset, rl, rng); + } + virtual position_structid_pair_type get_pos_sid_pair_2o_helper(SphericalSurface const& origin_structure1, structure_type_id_type const& target_sid, + position_type const& CoM, length_type const& offset, length_type const& rl, rng_type& rng) const + { + return this->get_pos_sid_pair_2o_helper_any >(origin_structure1, target_sid, CoM, offset, rl, rng); + } + virtual position_structid_pair_type get_pos_sid_pair_2o_helper(CylindricalSurface const& origin_structure1, structure_type_id_type const& target_sid, + position_type const& CoM, length_type const& offset, length_type const& rl, rng_type& rng) const + { + return this->get_pos_sid_pair_2o_helper_any >(origin_structure1, target_sid, CoM, offset, rl, rng); + } + virtual position_structid_pair_type get_pos_sid_pair_2o_helper(DiskSurface const& origin_structure1, structure_type_id_type const& target_sid, + position_type const& CoM, length_type const& offset, length_type const& rl, rng_type& rng) const + { + return this->get_pos_sid_pair_2o_helper_any >(origin_structure1, target_sid, CoM, offset, rl, rng); + } + virtual position_structid_pair_type get_pos_sid_pair_2o_helper(PlanarSurface const& origin_structure1, structure_type_id_type const& target_sid, + position_type const& CoM, length_type const& offset, length_type const& rl, rng_type& rng) const + { + return this->get_pos_sid_pair_2o_helper_any >(origin_structure1, target_sid, CoM, offset, rl, rng); + } + // The template function that defines the actual final dispatch procedure. + template + position_structid_pair_type get_pos_sid_pair_2o_helper_any(Tstruct_ const& origin_structure1, structure_type_id_type const& target_sid, position_type const& CoM, + length_type const& offset, length_type const& reaction_length, rng_type& rng) const + { + // This method has to figure out where the product will be placed in case of a bimolecular reaction. + // As a default, we place particles on the substructure or the lower-dimensional structure. If the structures + // have the same structure type (=> same dimensionality) it does not matter on which structure we put the product, + // as long as it has the structure type id of the product species. This is handled in cases '1' below. + + // 1 - Check whether one of the structures is the parent of the other. If yes, the daughter structure is the target. + if( this->is_parent_of_or_has_same_sid_as(origin_structure1) && origin_structure1.has_valid_target_sid(target_sid) ) + // origin_structure1 is target + return ::get_pos_sid_pair(*this, origin_structure1, CoM, offset, reaction_length, rng); + + else if( origin_structure1.is_parent_of_or_has_same_sid_as(*this) && this->has_valid_target_sid(target_sid) ) + // this structure is target + return ::get_pos_sid_pair(origin_structure1, *this, CoM, offset, reaction_length, rng); + + // 2 - Check which structures has the lower dimensionality / particle degrees of freedom, and put the product there. + else if( origin_structure1.shape().dof() < this->shape().dof() && origin_structure1.has_valid_target_sid(target_sid) ) + // origin_structure1 is target + return ::get_pos_sid_pair(*this, origin_structure1, CoM, offset, reaction_length, rng); + + else if( this->shape().dof() < origin_structure1.shape().dof() && this->has_valid_target_sid(target_sid) ) + // this structure is target + return ::get_pos_sid_pair(origin_structure1, *this, CoM, offset, reaction_length, rng); + + else throw propagation_error("Invalid target structure type: does not match product species structure type or has wrong hierarchy or dimensionality."); + } + +// // *** 4 *** - Generalized functions for pair reactions with two origin structures and one target structure +// // NOTE: This is yet unused, but possibly useful in the future. +// // Overloading get_pos_sid_pair again with signature (origin_structure2, target_structure, ...) and introducing +// // a triple dynamic dispatch. +// virtual position_structid_pair_type get_pos_sid_pair(structure_type const& origin_structure2, structure_type const& target_structure, position_type const& position, +// length_type const& offset, length_type const& reaction_length, rng_type const& rng) const +// { +// return origin_structure2.get_pos_sid_pair_helper1(*this, target_structure, position, offset, reaction_length, rng); +// } + + + /*** Formerly used functions of the Morelli scheme ***/ + // DEPRECATED + virtual length_type drawR_gbd(Real const& rnd, length_type const& r01, Real const& dt, Real const& D01, Real const& v) const + { + //TODO: use the 2D BD function instead of the 3D one - failed on very hard integral. + return drawR_gbd_3D(rnd, r01, dt, D01); + } + // DEPRECATED + virtual Real p_acceptance(Real const& k_a, Real const& dt, length_type const& r01, position_type const& ipv, + Real const& D0, Real const& D1, Real const& v0, Real const& v1) const + { + //TODO: use the 2D BD function instead of the 3D one. - Solution known + return k_a * dt / ((I_bd_3D(r01, dt, D0) + I_bd_3D(r01, dt, D1)) * 4.0 * M_PI); + } + // DEPRECATED + virtual position_type dissociation_vector( rng_type& rng, length_type const& r01, Real const& dt, + Real const& D01, Real const& v ) const + { + return random_vector( drawR_gbd(rng(), r01, dt, D01, v), rng ); + } virtual void accept(ImmutativeStructureVisitor const& visitor) const { @@ -58,8 +455,8 @@ class PlanarSurface visitor(*this); } - PlanarSurface(identifier_type const& id, shape_type const& shape) - : base_type(id, shape) {} + PlanarSurface(structure_name_type const& name, structure_type_id_type const& sid, structure_id_type const& parent_struct_id, shape_type const& shape) + : base_type(name, sid, parent_struct_id, shape) {} }; diff --git a/Plane.hpp b/Plane.hpp index 705d4b3a..c5e4c132 100644 --- a/Plane.hpp +++ b/Plane.hpp @@ -18,6 +18,7 @@ class Plane typedef T_ value_type; typedef Vector3 position_type; typedef T_ length_type; + typedef enum side_enum_type {TOP=0, BOTTOM=1, LEFT=2, RIGHT=3} side_enum_type; // The typedef is a little bit C style but doesn't matter for C++ public: Plane(position_type const& position = position_type()) @@ -26,7 +27,8 @@ class Plane create_vector(1., 0., 0.), create_vector(0., 1., 0.), create_vector(0., 0., 1.))), - half_extent_(array_gen(0.5, 0.5)) {} + half_extent_(array_gen(0.5, 0.5)), + is_one_sided_(true) {} template Plane(position_type const& position, Tarray_ const& half_extent) @@ -34,7 +36,8 @@ class Plane units_(array_gen( create_vector(1., 0., 0.), create_vector(0., 1., 0.), - create_vector(0., 0., 1.))) + create_vector(0., 0., 1.))), + is_one_sided_(true) { std::copy(boost::begin(half_extent), boost::end(half_extent), boost::begin(half_extent_)); @@ -43,7 +46,7 @@ class Plane template Plane(position_type const& position, Tarray1 const& units, Tarray2 const& half_extent) - : position_(position) + : position_(position), is_one_sided_(true) { std::copy(boost::begin(units), boost::end(units), boost::begin(units_)); @@ -56,7 +59,8 @@ class Plane position_type const& vx, position_type const& vy, Tarray_ const& half_extent = array_gen(0.5, 0.5)) - : position_(position), units_(array_gen(vx, vy, cross_product(vx, vy))) + : position_(position), units_(array_gen(vx, vy, cross_product(vx, vy))), + is_one_sided_(true) { std::copy(boost::begin(half_extent), boost::end(half_extent), boost::begin(half_extent_)); @@ -66,9 +70,10 @@ class Plane position_type const& vx, position_type const& vy, length_type const& half_lx, - length_type const& half_ly) + length_type const& half_ly, + bool const& is_one_sided) : position_(position), units_(array_gen(vx, vy, cross_product(vx, vy))), - half_extent_(array_gen(half_lx, half_ly)) {} + half_extent_(array_gen(half_lx, half_ly)), is_one_sided_(is_one_sided) {} position_type const& position() const { @@ -149,6 +154,21 @@ class Plane { return half_extent_; } + + bool const& is_one_sided() const + { + return is_one_sided_; + } + + bool& is_one_sided() + { + return is_one_sided_; + } + + const int dof() const + { // degrees of freedom for particle movement + return 2; + } bool operator==(const Plane& rhs) const { @@ -167,17 +187,19 @@ class Plane strm.precision(precision); strm << *this; return strm.str(); - } + } protected: position_type position_; boost::array units_; boost::array half_extent_; + bool is_one_sided_; }; template inline boost::array::length_type, 3> to_internal(Plane const& obj, typename Plane::position_type const& pos) +// The function calculates the coefficients to express 'pos' into the base of the plane 'obj' { typedef typename Plane::position_type position_type; position_type pos_vector(subtract(pos, obj.position())); @@ -190,64 +212,224 @@ to_internal(Plane const& obj, typename Plane::position_type const& pos) template inline std::pair::position_type, - typename Plane::length_type> -projected_point(Plane const& obj, typename Plane::position_type const& pos) + std::pair::length_type, + typename Plane::length_type> > +project_point(Plane const& obj, typename Plane::position_type const& pos) +// Calculates the projection of 'pos' onto the plane 'obj' and also returns the coefficient +// for the normal component (z) of 'pos' in the basis of the plane (pair entry .second.first) +// and the negative distance to the closest edge (pair entry .second.second). Note that the +// latter also indicates whether the projected point is in the plane. { + typedef typename Plane::length_type length_type; + boost::array::length_type, 3> x_y_z(to_internal(obj, pos)); - return std::make_pair( - add(add(obj.position(), multiply(obj.unit_x(), x_y_z[0])), - multiply(obj.unit_y(), x_y_z[1])), - x_y_z[2]); + + const length_type dx(subtract( abs(x_y_z[0]), obj.half_extent()[0])); + const length_type dy(subtract( abs(x_y_z[1]), obj.half_extent()[1])); + const length_type min_dist ( (dx <= 0 && dy <= 0) ? std::max(dx, dy) + : 1.0 ); // TODO make this is proper distance if we need it + + return std::make_pair( add(add(obj.position(), multiply(obj.unit_x(), x_y_z[0])), + multiply(obj.unit_y(), x_y_z[1])), + std::make_pair(x_y_z[2], min_dist) ); +} + +template +inline std::pair::position_type, + std::pair::length_type, + typename Plane::length_type> > +project_point_on_surface(Plane const& obj, typename Plane::position_type const& pos) +// Since the projected point on the plane, is already on the surface of the plane, +// this function is just a wrapper of projected point. +{ + return project_point(obj, pos); } template inline typename Plane::length_type distance(Plane const& obj, typename Plane::position_type const& pos) +// Calculates the distance from 'pos' to plane 'obj' Note that when the plane is finite, +// it also calculates the distance to the edge of the plane if necessary { typedef typename Plane::length_type length_type; boost::array const x_y_z(to_internal(obj, pos)); - length_type const dx(subtract(abs(x_y_z[0]), obj.half_extent()[0])); - length_type const dy(subtract(abs(x_y_z[1]), obj.half_extent()[1])); + // The (negative) distances to the plane edges (in x/y direction) + length_type const dx(subtract( abs(x_y_z[0]), obj.half_extent()[0])); + length_type const dy(subtract( abs(x_y_z[1]), obj.half_extent()[1])); + // The z-distance (may be positive or negative, depending on the side) + length_type const dz( x_y_z[2] ); if (dx < 0 && dy < 0) { - // Projected point of pos is on the plane. - // Probably an infinite plane anyway. - return x_y_z[2]; + // pos is positioned over the plane (projected point is in the plane, + // not next to it). + return abs(dz); } - if (dx > 0) + if (dx > 0) // outside the plane in the x direction { if (dy > 0) { - // Far away from plane. - return std::sqrt(gsl_pow_2(dx) + gsl_pow_2(dy) + - gsl_pow_2(x_y_z[2])); + // outside the plane in both x and y direction + return std::sqrt(gsl_pow_2(dx) + gsl_pow_2(dy) + gsl_pow_2(dz)); } else { - return std::sqrt(gsl_pow_2(dx) + gsl_pow_2(x_y_z[2])); + // outside the plane in x, but inside in y direction + return std::sqrt(gsl_pow_2(dx) + gsl_pow_2(dz)); } } - else + else // inside the plane in x direction { if (dy > 0) { - return std::sqrt(gsl_pow_2(dy) + gsl_pow_2(x_y_z[2])); + // outside the plane in y, but inside in x direction + return std::sqrt(gsl_pow_2(dy) + gsl_pow_2(dz)); } else { - // Already tested above. - return x_y_z[2]; + // inside the plane in both x and y direction (see above) + return abs(dz); } } } +///// Function below are inline functions that can be applied on Plane objects. + +template +inline std::pair::position_type, bool> +deflect(Plane const& obj, typename Plane::position_type const& r0, typename Plane::position_type const& d) +// This routine deflects a displacement on a plane. +// If the displacement vector intersects with the plane (starting from r0) the part +// ranging out of the plane will be deflected into the plane, always into the direction +// towards the plane center. +// ATTENTION! As for now, this only works for displacement vectors perpendicular to the plane +{ + + // Type abbreviations + typedef typename Plane::length_type length_type; + typedef typename Plane::position_type position_type; + + // Plane properties to make use of + position_type center_pt = obj.position(); + position_type u_x = obj.unit_x(); + position_type u_y = obj.unit_y(); + position_type u_z = obj.unit_z(); + + // Variables used for calculation + position_type intersect_pt; // the point at which the displacement vector intersects the edge + position_type d_out, d_edge, d_perp; // the part of the displacement ranging out of the plane and + // the components parallel and perpendicular to the edge + position_type d_edge_n; // normalized d_edge + position_type icv; // = intersect_to_center_vector + position_type icv_edge, icv_perp; // the components of icv parallel and perpendicular to the edge + + position_type new_pos; + + length_type intersect_parameter; + length_type l_perp; + + bool changeflag = false; + + // Calculate the intersection parameter and intersection point. + // r0 is the origin position, d is a displacement vector. + // If intersect_parameter <= 1 we have an intersection. + if(dot_product(d, u_z) != 0.0) + intersect_parameter = divide( dot_product(subtract(center_pt, r0), u_z), dot_product(d, u_z) ); + else + intersect_parameter = (length_type)1e100; // infinity, displacement is parallel to edge + + // Check whether the displacement actually crosses the plane; + // If not, just return original position plus displacement; + // if yes, calculate the deflection. + if(intersect_parameter > 1.0 || intersect_parameter < 0.0){ + + // No intersection; the new position is just r0+displacement + new_pos = add(r0, d); + changeflag = false; + } + else{ + + // Calculate the intersection point and the part of the displacement + // that ranges out of the target plane. + // Project all points that are supposed to be in the plane into it, + // just to be sure. + intersect_pt = project_point( obj, add(r0, multiply(d, intersect_parameter)) ).first; + d_out = multiply(d, subtract(1.0, intersect_parameter)); + + // Calculate the length of the component of d_out perpendicular to the edge + // and the vector of the component parallel to the edge + l_perp = dot_product(d_out, u_z); // note that this can be positive or negative, + // depending on whether u_z points in or out! + d_edge = subtract(d_out, multiply(u_z, l_perp)); + d_edge_n = normalize(d_edge); + + // Find the vector pointing from the edge towards the center of the plane, which is + // the component of (center_pt - intersect_pt) perpendicular to the edge. + // First we calculate the component parallel to the edge + icv = subtract(center_pt, intersect_pt); + icv_edge = multiply(d_edge_n, dot_product(icv, d_edge_n)); + icv_perp = subtract(icv, icv_edge); + + // Calculate the component perpendicular to the edge in the plane. + // Note that l_perp can be pos. or neg. while icv_perp always points towards + // the center of the plane; therefore use abs(l_perp). + d_perp = multiply( normalize(icv_perp), abs(l_perp) ); + + // Construct the new position vector, make sure it's in the plane to + // avoid trouble with periodic boundary conditions + new_pos = project_point(obj, add(intersect_pt, add(d_edge, d_perp)) ).first; + changeflag = true; + } + + + // for now this returns the new position without changes + return std::make_pair( new_pos, changeflag ); +} +/* +template +inline typename Plane::position_type +deflect_back(Plane const& obj, typename Plane::position_type const& r, typename Plane::position_type const& u_z) +// This function projects a vector onto plane obj and prolongates the projection by adding the part of it orthogonal +// to the plane rotated by 90 degrees towards the outward of plane obj; it thus performs the deflection transition backward. +// The function is meant to be used to calculate the shortest distance between two particles on different orthogonal planes +// by projecting an orthogonal position back into the plane; note that the shortest distance in general is not simply the sum +// of the projections of the interparticle vector on the two planes. +// ATTENTION: u_z must be the normal vector of the plane that the particle at pos. r lives on! +// The function will only yield meaningful results if the input information is provided correctly! +{ + // Type abbreviations + typedef typename Plane::length_type length_type; + typedef typename Plane::position_type position_type; + typedef std::pair > projected_type; + + projected_type r_proj(project_point(obj, r)); + position_type dc(subtract(r_proj.first, obj.position())); + // vector pointing towards the projection of r on plane obj + // from the center of the latter + + position_type u_perp; // unit vector pointing away from the edge between the two planes + position_type r_new; // r1 projected into plane 0 + rotated orthogonal part of it + + // TODO: Assert that u_z is perpendicular to obj.unit_z ? + + // Find the unit vector perpendicular to the edge by comparing with u_z + // (u_z might point outwards or inwards with respect to plane obj) + if(dot_product(dc, u_z) > 0.0) + u_perp = u_z; + else + u_perp = multiply(u_z, -1.0); + + r_new = add(r_proj.first, multiply(u_perp, abs(r_proj.second.first)) ); + + return project_point(obj, r_new).first; +} +*/ template inline typename Plane::position_type random_position(Plane const& shape, Trng& rng) { - typedef typename Plane::length_type length_type; + //typedef typename Plane::length_type length_type; // -1 < rng() < 1. See for example PlanarSurface.hpp. return add( diff --git a/README b/README index 1d226859..0b84f86f 100644 --- a/README +++ b/README @@ -1,46 +1,62 @@ -:author: Koichi Takahashi +:author: Thomas R. Sokolowski, Koichi Takahashi -E-Cell Particle Dynamics Prototype +=== eGFRD2 Prototype === +originally based on the E-Cell Particle Dynamics Prototype + +Copyright (C) 2009-2017 AMOLF Copyright (C) 2008-2010 RIKEN -Copyright (C) 2009-2010 AMOLF Copyright (C) 2005-2008 The Molecular Sciences Institute About this package ======================== -This package is tentatively named E-Cell Particle Dynamics Prototype -(EPDP). It currently implements the enhanced Greens Function Reaction -Dynamics (eGFRD) algorithm and the reaction Brownian Dynamics (BD) simulation -algorithm. Implementation of the original version of GFRD will -possibly be added in future. The code is implemented with the hope -that it will eventually be part of the E-Cell System Version 4 -multi-algorithm, multi-space simulation platform. +This package is tentatively named "eGFRD2 Prototype", and was +originally forked from RIKEN's E-Cell Particle Dynamics Prototype (EPDP). +It currently implements the enhanced Greens Function Reaction Dynamics +(eGFRD) algorithm working in all dimensions, the Reaction Brownian +Dynamics (RBD) simulation algorithm, and another BD simulator based on +the Rection Volume Method (rvm-BD). Implementation of the original +version of GFRD will possibly be added in future. The code was designed +and implemented with the hope that it will eventually be part of the +"E-Cell System Version 4" multi-algorithm, multi-space simulation +platform, or its subsequent versions. The purpose of this prototype code written in mixed Python and C++ is to establish a solid and practical implementation of the algorithms, and to extend it into a form that is suitable for large-scale biochemical and cell simulations. -The eGFRD algorithm first appeared and be used in a paper by -Takahashi, Tanase-Nicola and ten Wolde [1], and will be described more -in detail in a forthcoming paper[2]. +The eGFRD algorithm first appeared and was used in a paper by +Takahashi, Tanase-Nicola and ten Wolde [1]. A detailed introduction +to eGFRD, and in particular a description of the extensions to lower +dimensions is found in the PhD Thesis of Sokolowski [2]. Review [3] +also contains a good introduction into the current eGFRD version. +Moreover, eGFRD2 is described in detail in a forthcoming paper, +currently available as a preprint on arXiv [4]. -The reaction Brownian Dynamics algorithm is described in a paper by -Morrelli and ten Wolde[3]. +The Reaction Brownian Dynamics algorithm is described in a paper by +Morrelli and ten Wolde [5]; the Reaction Volume Method and rvm-BD +are described in detail in the Master's Thesis of Paijmans [6], and +also briefly in [2-4]. Authors ======================== (alphabetical order) + +Laurens Bossen Kazunari Kaizu Moriyoshi Koizumi -Thomase Miedema +Thomas Miedema +Joris Paijmans +Thomas R. Sokolowski Koichi Takahashi +Martijn Wehrens License @@ -62,34 +78,59 @@ History of the Code Koichi Takahashi initially stated development of the code in 2005 to implement his prototype of Greens Function Reaction Dynamics simulation method invented by Jeroen van Zon and Pieter Rein ten Wolde -in AMOLF, Amsterdam[4]. He gave a brief invited talk about +in AMOLF, Amsterdam [7]. He gave a brief invited talk about performance evaluation and applicability of the method to yeast pheromon response pathway (the Alpha pathway) using the prototype in -the Third Annual Alpha Project Research Symposium (June 16-27, 2005, at UC -Berkeley Art Museum). +the Third Annual Alpha Project Research Symposium (June 16-27, 2005, at +UC Berkeley Art Museum). -Later, in December 2006, ten Wolde, Sorin Tanase-Nicola, and Takahashi -introduced the concept called first-passage processes[5][6] to Greens +Later, in December 2006, ten Wolde, Tanase-Nicola, and Takahashi +introduced the concept called first-passage processes [8,9] to Greens Function Reaction Dynamics by putting protective domains around particles to further boost the performance and accuracy of the method. The new method was named eGFRD (enhanced Greens Function Reaction -Dynamics). +Dynamics). By 2010, it was implemented for simulating particle-based +reaction-diffusion systems in 3D, and fully integrated with a BD +fallback system; the framework was applied to study details of +enzymatic reactions in the MAPK push-pull networks [1]. + +Starting from 2009, Sokolowski, Bossen, Miedema and ten Wolde began +to derive the Green's functions and implementing the new domains +needed for extending eGFRD to lower dimensions. Later Paijmans and +Wehrens joined these efforts. Since extension of RBD to lower dimensions +turned out to be difficult, Paijmans and ten Wolde devised rvm-BD, a new +BD simulation method that preserves detailed balance, based on the +Reaction Volume Method (described in more detail in [6, 2-4]). + +All extensions to lower dimensions were assembled together by Bossen +and Sokolowski in 2012-2013. In 2013, Sokolowski created a first working +prototype comprising all new features mentioned above. In the following +years, this prototype was tested and improved, and some further new features, +such as the option of transiting particles between cylinders and planes, +were integrated. The prototype code then was used to simulate an idealized +model of Pom1 gradient formation, as described in [4]. Plans ========================= -Some features planned to be added are; surfaces, interactions between -membrane proteins and proteins in solution, optimistic discrete-event -scheduling for massive parallelization, connections to non-spatial -simulation methods such as ODE and Gillespie methods. - +Some features planned to be added are: +- outsourcing further core routines from Python to C++ +- optimistic discrete-event scheduling for massive parallelization +- connections to non-spatial simulation methods such as ODE and Gillespie methods +- more efficient visualizers / visualization interfaces -[1] K. Takahashi, S. Tanase-Nicola and P.R. ten Wolde, PNAS doi:10.1073/pnas.0906885107 (2010). -[2] K. Takahashi, S. Tanase-Nicola and P.R. ten Wolde, in preparation. -[3] M.J. Morelli and P.R. ten Wolde, J. Chem. Phys. 7;129(5):054112 (2008). -[4] van Zon and ten Wolde, Phys. Rev. Lett. 94 (2005). -[5] T. Opplestrup, V.V. Bulatov, G.H. Gilmer, M.H. Kalos, and B. Sadigh, Phys. Rev. Lett. 97 (2006). -[6] M.H. Kalos, D. Levesque and L. Verlet, Phys. Rev. A 9 (1974). +References +========================= +[1] K. Takahashi, S. Tanase-Nicola and P.R. ten Wolde, PNAS doi:10.1073/pnas.0906885107 (2010). +[2] T.R. Sokolowski, "A Computational Study of Robust Formation of Spatial Protein Patterns", + PhD Thesis, VU Amsteram, ISBN: 978-90-77209-72-1 (2013). +[3] T.R. Sokolowski and P.R. ten Wolde, arXiv:1705.08669 [q-bio.MN] (2017). +[4] T.R. Sokolowski et al., arXiv:1708.09364 [q-bio.MN] (2017). +[5] M.J. Morelli and P.R. ten Wolde, J. Chem. Phys. 7;129(5):054112 (2008). +[6] J. Paijmans, Master's Thesis, University of Amsterdam (2012). +[7] van Zon and ten Wolde, Phys. Rev. Lett. 94 (2005). +[8] T. Opplestrup, V.V. Bulatov, G.H. Gilmer, M.H. Kalos, and B. Sadigh, Phys. Rev. Lett. 97 (2006). +[9] M.H. Kalos, D. Levesque and L. Verlet, Phys. Rev. A 9 (1974). diff --git a/Region.hpp b/Region.hpp index a9a211b9..07dd66de 100644 --- a/Region.hpp +++ b/Region.hpp @@ -18,24 +18,38 @@ template class Region: public ParticleSimulationStructure { public: - typedef ParticleSimulationStructure base_type; - typedef typename base_type::identifier_type identifier_type; + typedef ParticleSimulationStructure base_type; + typedef typename base_type::structure_name_type structure_name_type; + typedef typename base_type::structure_id_type structure_id_type; + typedef typename base_type::structure_type_id_type structure_type_id_type; public: virtual ~Region() {} - Region(identifier_type const& id): base_type(id) {} + // Constructor + Region(structure_name_type const& name, structure_type_id_type const& sid, structure_id_type const& parent_struct_id) + : base_type(name, sid, parent_struct_id) {} }; + + template class BasicRegionImpl: public Region { public: - typedef Region base_type; - typedef Tshape_ shape_type; - typedef typename base_type::identifier_type identifier_type; - typedef typename base_type::length_type length_type; - typedef typename base_type::position_type position_type; + typedef Region base_type; + + typedef Tshape_ shape_type; + typedef typename shape_type::side_enum_type side_enum_type; // Defines the type of enum to use for the sides of the region + + typedef typename base_type::structure_name_type structure_name_type; + typedef typename base_type::structure_id_type structure_id_type; + typedef typename base_type::structure_type_id_type structure_type_id_type; + typedef typename base_type::length_type length_type; + typedef typename base_type::position_type position_type; + typedef std::pair position_flag_pair_type; + typedef std::pair components_pair_type; + typedef std::pair projected_type; public: virtual ~BasicRegionImpl() {} @@ -50,14 +64,19 @@ class BasicRegionImpl: public Region return shape_; } - virtual bool operator==(Structure const& rhs) const + virtual bool operator==(Structure const& rhs) const { BasicRegionImpl const* _rhs(dynamic_cast(&rhs)); - return _rhs && base_type::id_ == rhs.id() && shape_ == _rhs->shape(); + return _rhs && + base_type::id_ == rhs.id() && + base_type::sid_ == rhs.sid() && + shape_ == _rhs->shape(); } virtual std::size_t hash() const - { + { // Displacements are not deflected on cylinders (yet), + // but this function has to be defined for every shape to be used in structure + // For now it just returns the new position #if defined(HAVE_TR1_FUNCTIONAL) using std::tr1::hash; #elif defined(HAVE_STD_HASH) @@ -65,7 +84,9 @@ class BasicRegionImpl: public Region #elif defined(HAVE_BOOST_FUNCTIONAL_HASH_HPP) using boost::hash; #endif - return hash()(base_type::id_) ^ hash()(shape()); + return hash()(base_type::name_) ^ + hash()(base_type::sid_) ^ + hash()(shape()); } virtual std::string as_string() const @@ -75,14 +96,39 @@ class BasicRegionImpl: public Region return out.str(); } - std::pair - projected_point(position_type const& pos) const + virtual projected_type project_point(position_type const& pos) const + { + return ::project_point(shape(), pos); + } + + virtual projected_type project_point_on_surface(position_type const& pos) const + { + return ::project_point_on_surface(shape(), pos); + } + + virtual length_type distance(position_type const& pos) const + { + return ::distance(shape(), pos); + } + + virtual position_type const& position() const { - return ::projected_point(shape(), pos); + return ::shape_position(shape()); } - BasicRegionImpl(identifier_type const& id, shape_type const& shape) - : base_type(id), shape_(shape) {} + virtual position_flag_pair_type deflect(position_type const& pos0, position_type const& displacement) const + { + return ::deflect(shape(), pos0, displacement); + } +/* + virtual position_type deflect_back(position_type const& pos, position_type const& u_z) const + { + return ::deflect_back(shape(), pos, u_z); + } +*/ + // The constructor + BasicRegionImpl(structure_name_type const& name, structure_type_id_type const& sid, structure_id_type const& parent_struct_id, shape_type const& shape) + : base_type(name, sid, parent_struct_id), shape_(shape) {} protected: shape_type shape_; diff --git a/SpeciesInfo.hpp b/SpeciesInfo.hpp index 167e7f35..a3aedb5d 100644 --- a/SpeciesInfo.hpp +++ b/SpeciesInfo.hpp @@ -6,20 +6,35 @@ #include #include "Defs.hpp" -template +template struct SpeciesInfo +// SpeciesInfo seems to extend the SpeciesType with extra information which is relevant for spatial simulations. +// A Species itself holds very little information. +// Note that the id is the same id as the SpeciesType and can be used to refer to the SpeciesType. { - typedef Tid_ identifier_type; - typedef TD_ D_type; - typedef TD_ v_type; - typedef Tlen_ length_type; - typedef Tstructure_id_ structure_id_type; + // defining shorthands for used types + typedef Tid_ identifier_type; // This is the SpeciesTypeID + typedef TD_ D_type; + typedef TD_ v_type; + typedef Tlen_ length_type; + typedef Tstructure_type_id_ structure_type_id_type; // The structure type that the species lives on + + // constructors + SpeciesInfo() {} + + SpeciesInfo(identifier_type const& id, structure_type_id_type const& s, D_type const& D = 0., + length_type const& r = 0., v_type const& v = 0.) + : id_(id), diffusion_coef_(D), drift_velocity_(v), radius_(r), structure_type_id_(s) {} + + + // Get the id identifier_type const& id() const { return id_; } + // Get the particle radius length_type const& radius() const { return radius_; @@ -30,16 +45,18 @@ struct SpeciesInfo return radius_; } - structure_id_type const& structure_id() const + // Get the id of the structure type the species lives on + structure_type_id_type const& structure_type_id() const { - return structure_id_; + return structure_type_id_; } - structure_id_type& structure_id() + structure_type_id_type& structure_type_id() { - return structure_id_; + return structure_type_id_; } + // Get the diffusion constant D_type const& D() const { return diffusion_coef_; @@ -49,7 +66,8 @@ struct SpeciesInfo { return diffusion_coef_; } - + + // Get the drift v_type const& v() const { return drift_velocity_; @@ -60,10 +78,14 @@ struct SpeciesInfo return drift_velocity_; } + // Check equality/inequality bool operator==(SpeciesInfo const& rhs) const { - return id_ == rhs.id() && diffusion_coef_ == rhs.D() && drift_velocity_ == rhs.v() && - radius_ == rhs.radius() && structure_id_ == rhs.structure_id(); + return id_ == rhs.id() && + diffusion_coef_ == rhs.D() && + drift_velocity_ == rhs.v() && + radius_ == rhs.radius() && + structure_type_id_ == rhs.structure_type_id(); } bool operator!=(SpeciesInfo const& rhs) const @@ -71,26 +93,24 @@ struct SpeciesInfo return !operator==(rhs); } - SpeciesInfo() {} - - SpeciesInfo(identifier_type const& id, D_type const& D = 0., - length_type const& r = 0., structure_id_type const& s = "", v_type const& v = 0.) - : id_(id), diffusion_coef_(D), drift_velocity_(v), radius_(r), structure_id_(s) {} - - +/////// Member variables private: - identifier_type id_; - D_type diffusion_coef_; - v_type drift_velocity_; - length_type radius_; - structure_id_type structure_id_; + identifier_type id_; + D_type diffusion_coef_; + v_type drift_velocity_; + length_type radius_; + structure_type_id_type structure_type_id_; // The structure type that the particle lives on }; -template + +//////// Inline functions +template inline std::basic_ostream& -operator<<(std::basic_ostream& strm, const SpeciesInfo& s) +operator<<(std::basic_ostream& strm, const SpeciesInfo& s) +// Allows for printing of the object +// -> provides a stream of 'attributename : attribute_value' pairs. { - strm << "SpeciesInfo(id=" << s.id() << ", D=" << s.D() << ", v=" << s.v() << ", radius=" << s.radius() << ", surface=" << s.structure_id() << ")"; + strm << "SpeciesInfo(id=" << s.id() << ", D=" << s.D() << ", v=" << s.v() << ", radius=" << s.radius() << ", structure_type=" << s.structure_type_id() << ")"; return strm; } diff --git a/SpeciesType.cpp b/SpeciesType.cpp index 82fb55ad..2873b57f 100644 --- a/SpeciesType.cpp +++ b/SpeciesType.cpp @@ -5,6 +5,7 @@ #include "SpeciesType.hpp" SpeciesType::identifier_type const& SpeciesType::id() const +// Gets the id of the SpeciesType (ONLY DEFINED WHEN SpeciesType is bound to model) { if (!model_) { @@ -14,6 +15,7 @@ SpeciesType::identifier_type const& SpeciesType::id() const } std::string const& SpeciesType::operator[](std::string const& name) const +// Get an attribute value by name. Usage for example: a = 'species_type['name']' { string_map_type::const_iterator i(attrs_.find(name)); if (i == attrs_.end()) @@ -22,11 +24,13 @@ std::string const& SpeciesType::operator[](std::string const& name) const } std::string& SpeciesType::operator[](std::string const& name) +// Same thing as above but messier? { return attrs_[name]; } SpeciesType::attributes_range SpeciesType::attributes() const +// Gets all the attributes { return attributes_range(attrs_.begin(), attrs_.end()); } diff --git a/SpeciesType.hpp b/SpeciesType.hpp index d0913558..a3208d6c 100644 --- a/SpeciesType.hpp +++ b/SpeciesType.hpp @@ -22,9 +22,9 @@ class SpeciesType typedef get_mapper_mf::type string_map_type; public: - typedef SpeciesTypeID identifier_type; - typedef string_map_type::const_iterator string_map_iterator; - typedef boost::iterator_range attributes_range; + typedef SpeciesTypeID identifier_type; // The identifier. Note that we also use this for structures. + typedef string_map_type::const_iterator string_map_iterator; + typedef boost::iterator_range attributes_range; public: identifier_type const& id() const; @@ -36,28 +36,38 @@ class SpeciesType attributes_range attributes() const; Model* model() const + // The model with which this species type is associated? { return model_; } SpeciesType(): model_(0) {} + // The constructor initializes the model with '0' protected: void bind_to_model(Model* model, identifier_type const& id) + // Later the species type can be bound to the model. + // Note that also here the id is fixed -> Only when bound to a model is the id known { model_ = model; id_ = id; } + +//////// Member variables private: - Model* model_; - identifier_type id_; - string_map_type attrs_; + Model* model_; // The model to which the species type is bound + identifier_type id_; // The id of the species type + string_map_type attrs_; // all the attributes? }; + +//////// Inline functions template inline std::basic_ostream& operator<<(std::basic_ostream& out, const SpeciesType& v) +// Allows for printing of the object +// -> provides a stream of 'attributename : attribute_value' pairs. { bool first = true; out << "SpeciesType(id=" << v.id() << ", attributes={"; diff --git a/SpeciesTypeID.hpp b/SpeciesTypeID.hpp index 3680e481..c7722424 100644 --- a/SpeciesTypeID.hpp +++ b/SpeciesTypeID.hpp @@ -16,13 +16,20 @@ #include "Identifier.hpp" struct SpeciesTypeID: public Identifier +// The SpeciesTypeID is an indentifier structure (same as class) for species types (species) but is also used for structure types +// NOTE The superclass is parameterized with the SpeciesTypeID class itself. { + // shorthand name for the super class typedef Identifier base_type; + // The constructor SpeciesTypeID(value_type const& value = value_type(0, 0)) : base_type(value) {} }; + + + #if defined(HAVE_TR1_FUNCTIONAL) namespace std { namespace tr1 { #elif defined(HAVE_STD_HASH) @@ -48,6 +55,9 @@ struct hash } // namespace boost #endif + + +///////// Inline functions template inline std::basic_ostream& operator<<(std::basic_ostream& strm, const SpeciesTypeID& v) diff --git a/Sphere.hpp b/Sphere.hpp index 565f45db..eabf8d70 100644 --- a/Sphere.hpp +++ b/Sphere.hpp @@ -4,6 +4,7 @@ #include #include "Vector3.hpp" #include "Shape.hpp" +#include "linear_algebra.hpp" template class Sphere @@ -12,6 +13,7 @@ class Sphere typedef T_ value_type; typedef Vector3 position_type; typedef T_ length_type; + typedef enum side_enum_type {} side_enum_type; // The typedef is a little bit C style but doesn't matter for C++ public: Sphere() @@ -49,6 +51,11 @@ class Sphere { return radius_; } + + const int dof() const + { // degrees of freedom for particle movement + return 2; + } std::string show(int precision) { @@ -71,16 +78,43 @@ inline std::basic_ostream& operator<<(std::basic_ostream +inline typename Sphere::length_type +to_internal(Sphere const& obj, typename Sphere::position_type const& pos) +// The function calculates the coefficients to express 'pos' into the base of the sphere 'obj' +{ + typedef typename Sphere::position_type position_type; + position_type pos_vector(subtract(pos, obj.position())); + + // Todo. If we ever need it. + return length(pos_vector); +} + +template +inline std::pair::position_type, + std::pair::length_type, + typename Sphere::length_type> > +project_point(Sphere const& obj, typename Sphere::position_type const& pos) +{ + typename Sphere::length_type r(to_internal(obj, pos)); + + // The projection of a point on a sphere is always the centerpoint of the sphere. + return std::make_pair(obj.position(), + std::make_pair(r, 0.0) ); +} + template inline std::pair::position_type, - typename Sphere::length_type> -projected_point(Sphere const& obj, + std::pair::length_type, + typename Sphere::length_type> > +project_point_on_surface(Sphere const& obj, typename Sphere::position_type const& pos) { // Todo. If we ever need it. // The projection of a point on a sphere. return std::make_pair(typename Sphere::position_type(), - typename Sphere::length_type()); + std::make_pair(typename Sphere::length_type(), + typename Sphere::length_type()) ); } template @@ -90,6 +124,26 @@ distance(Sphere const& obj, typename Sphere::position_type const& pos) return distance(pos, obj.position()) - obj.radius(); } +template +inline std::pair::position_type, bool> +deflect(Sphere const& obj, typename Sphere::position_type const& r0, typename Sphere::position_type const& d) +{ + // Displacements are not deflected on spheres (yet), + // but this function has to be defined for every shape to be used in structure. + // For now it just returns the new position. The changeflag = 0. + return std::make_pair( add(r0, d), false ); +} +/* +template +inline typename Sphere::position_type +deflect_back(Sphere const& obj, + typename Sphere::position_type const& r, + typename Sphere::position_type const& u_z ) +{ + // Return the vector r without any changes + return r; +} +*/ template inline Sphere const& shape(Sphere const& shape) { diff --git a/SphericalBesselGenerator.hpp b/SphericalBesselGenerator.hpp index 4cd3dc18..40f602f2 100644 --- a/SphericalBesselGenerator.hpp +++ b/SphericalBesselGenerator.hpp @@ -12,9 +12,6 @@ class SphericalBesselGenerator { - - typedef UnsignedInteger Index; - public: SphericalBesselGenerator() diff --git a/SphericalSurface.hpp b/SphericalSurface.hpp index b602f5ba..9c929cd2 100644 --- a/SphericalSurface.hpp +++ b/SphericalSurface.hpp @@ -3,20 +3,44 @@ #include "Surface.hpp" #include "Sphere.hpp" +#include "freeFunctions.hpp" +#include "StructureFunctions.hpp" + +template +class StructureContainer; template class SphericalSurface - : public BasicSurfaceImpl > + : public BasicSurfaceImpl > { public: - typedef BasicSurfaceImpl > base_type; - typedef typename base_type::traits_type traits_type; - typedef typename base_type::identifier_type identifier_type; - typedef typename base_type::shape_type shape_type; - typedef typename base_type::rng_type rng_type; - typedef typename base_type::position_type position_type; - typedef typename base_type::length_type length_type; + typedef BasicSurfaceImpl > base_type; + typedef Ttraits_ traits_type; + typedef typename base_type::structure_name_type structure_name_type; // THis is just the name of the structure + typedef typename base_type::structure_id_type structure_id_type; + typedef typename base_type::structure_type_id_type structure_type_id_type; + typedef typename base_type::shape_type shape_type; + typedef typename base_type::rng_type rng_type; + typedef typename base_type::position_type position_type; + typedef typename base_type::length_type length_type; + typedef typename base_type::side_enum_type side_enum_type; + typedef typename traits_type::species_type species_type; + typedef typename traits_type::structure_type structure_type; + + typedef StructureContainer structure_container_type; + + typedef std::pair position_pair_type; + typedef std::pair position_structid_pair_type; + typedef std::pair position_structid_pair_pair_type; + + + /*** Info functions ***/ + virtual position_type const& position() const + { + return base_type::shape().position(); + } + /*** Simple structure-specific sampling functions ***/ virtual position_type random_position(rng_type& rng) const { return position_type(); // TODO @@ -27,16 +51,274 @@ class SphericalSurface return position_type(); // TODO } - virtual position_type bd_displacement(length_type const& r, rng_type& rng) const + virtual position_type bd_displacement(length_type const& mean, length_type const& r, rng_type& rng) const { return position_type(); // TODO } + /*** New BD scheme functions ***/ + virtual Real get_1D_rate_geminate( Real const& k, length_type const& r01) const + { + return Real(); //TODO + } + + virtual Real get_1D_rate_surface( Real const& k, length_type const& r0 ) const + { + return Real(); //TODO + } + + virtual Real particle_reaction_volume( length_type const& r01, length_type const& rl ) const + { + return Real(); //TODO + } + + virtual Real surface_reaction_volume( length_type const& r0, length_type const& rl ) const + { + return Real(); //TODO + } + + virtual position_type surface_dissociation_vector( rng_type& rng, length_type const& r0, length_type const& rl ) const + { + return position_type(); //TODO + } + + virtual position_type surface_dissociation_unit_vector( rng_type& rng ) const + { + return position_type(); //TODO + } + + // Vector used to determine whether a particle has crossed the structure + // Here we return the zero-vector because there is no "sides" to cross + virtual position_type const side_comparison_vector() const + { + return create_vector(0.0, 0.0, 0.0); + } + + virtual position_pair_type geminate_dissociation_positions( rng_type& rng, species_type const& s0, species_type const& s1, position_type const& op, length_type const& rl ) const + { + return position_pair_type(); //TODO + } + + virtual position_pair_type special_geminate_dissociation_positions( rng_type& rng, species_type const& s_surf, species_type const& s_bulk, position_type const& op_surf, length_type const& rl ) const + { + return position_pair_type(); //TODO + } + + virtual length_type newBD_distance(position_type const& new_pos, length_type const& radius, position_type const& old_pos, length_type const& sigma) const + { + return base_type::distance(new_pos); + } +/* virtual length_type minimal_distance(length_type const& radius) const { return 0.; // TODO } +*/ + /*** Boundary condition handling ***/ + virtual position_structid_pair_type apply_boundary(position_structid_pair_type const& pos_struct_id, + structure_container_type const& structure_container) const + { + // The apply_boundary for a spherical surface is trivial (since there is no boundary!) + return pos_struct_id; + } + + virtual position_structid_pair_type cyclic_transpose(position_structid_pair_type const& pos_struct_id, + structure_container_type const& structure_container) const + { + return pos_struct_id; // Two spherical surface cannot be connected (there is no boundary!) + } + + + // *** Dynamic dispatch for the structure functions *** // + // *** 1 *** - One new position + // This requires a double dynamic dispatch. + // First dispatch + virtual position_structid_pair_type get_pos_sid_pair(structure_type const& target_structure, position_type const& position, + length_type const& offset, length_type const& reaction_length, rng_type& rng) const + { + return target_structure.get_pos_sid_pair_helper(*this, position, offset, reaction_length, rng); + } + // Second dispatch + virtual position_structid_pair_type get_pos_sid_pair_helper(CuboidalRegion const& origin_structure, position_type const& position, + length_type const& offset, length_type const& rl, rng_type& rng) const + { + return this->get_pos_sid_pair_helper_any >(origin_structure, position, offset, rl, rng); + } + virtual position_structid_pair_type get_pos_sid_pair_helper(SphericalSurface const& origin_structure, position_type const& position, + length_type const& offset, length_type const& rl, rng_type& rng) const + { + return this->get_pos_sid_pair_helper_any >(origin_structure, position, offset, rl, rng); + } + virtual position_structid_pair_type get_pos_sid_pair_helper(CylindricalSurface const& origin_structure, position_type const& position, + length_type const& offset, length_type const& rl, rng_type& rng) const + { + return this->get_pos_sid_pair_helper_any >(origin_structure, position, offset, rl, rng); + } + virtual position_structid_pair_type get_pos_sid_pair_helper(DiskSurface const& origin_structure, position_type const& position, + length_type const& offset, length_type const& rl, rng_type& rng) const + { + return this->get_pos_sid_pair_helper_any >(origin_structure, position, offset, rl, rng); + } + virtual position_structid_pair_type get_pos_sid_pair_helper(PlanarSurface const& origin_structure, position_type const& position, + length_type const& offset, length_type const& rl, rng_type& rng) const + { + return this->get_pos_sid_pair_helper_any >(origin_structure, position, offset, rl, rng); + } + // The template function that defines the actual final dispatch procedure. + template + position_structid_pair_type get_pos_sid_pair_helper_any(Tstruct_ const& origin_structure, position_type const& position, + length_type const& offset, length_type const& rl, rng_type& rng) const + { + // redirect to structure function with well-defined typing + return ::get_pos_sid_pair(origin_structure, *this, position, offset, rl, rng); + }; + + // *** 2 *** - Two new positions + // Same principle as above, but different return type + // First dispatch + virtual position_structid_pair_pair_type get_pos_sid_pair_pair(structure_type const& target_structure, position_type const& position, + species_type const& s1, species_type const& s2, length_type const& reaction_length, rng_type& rng) const + { + return target_structure.get_pos_sid_pair_pair_helper(*this, position, s1, s2, reaction_length, rng); + } + // Second dispatch + virtual position_structid_pair_pair_type get_pos_sid_pair_pair_helper(CuboidalRegion const& origin_structure, position_type const& position, + species_type const& s_orig, species_type const& s_targ, length_type const& rl, rng_type& rng) const + { + return this->get_pos_sid_pair_pair_helper_any >(origin_structure, position, s_orig, s_targ, rl, rng); + } + virtual position_structid_pair_pair_type get_pos_sid_pair_pair_helper(SphericalSurface const& origin_structure, position_type const& position, + species_type const& s_orig, species_type const& s_targ, length_type const& rl, rng_type& rng) const + { + return this->get_pos_sid_pair_pair_helper_any >(origin_structure, position, s_orig, s_targ, rl, rng); + } + virtual position_structid_pair_pair_type get_pos_sid_pair_pair_helper(CylindricalSurface const& origin_structure, position_type const& position, + species_type const& s_orig, species_type const& s_targ, length_type const& rl, rng_type& rng) const + { + return this->get_pos_sid_pair_pair_helper_any >(origin_structure, position, s_orig, s_targ, rl, rng); + } + virtual position_structid_pair_pair_type get_pos_sid_pair_pair_helper(DiskSurface const& origin_structure, position_type const& position, + species_type const& s_orig, species_type const& s_targ, length_type const& rl, rng_type& rng) const + { + return this->get_pos_sid_pair_pair_helper_any >(origin_structure, position, s_orig, s_targ, rl, rng); + } + virtual position_structid_pair_pair_type get_pos_sid_pair_pair_helper(PlanarSurface const& origin_structure, position_type const& position, + species_type const& s_orig, species_type const& s_targ, length_type const& rl, rng_type& rng) const + { + return this->get_pos_sid_pair_pair_helper_any >(origin_structure, position, s_orig, s_targ, rl, rng); + } + // The template function that defines the actual final dispatch procedure. + template + position_structid_pair_pair_type get_pos_sid_pair_pair_helper_any(Tstruct_ const& origin_structure, position_type const& position, + species_type const& s_orig, species_type const& s_targ, length_type const& rl, rng_type& rng) const + { + // redirect to structure function with well-defined typing + return ::get_pos_sid_pair_pair(origin_structure, *this, position, s_orig, s_targ, rl, rng); + }; + + // *** 3 *** - Pair reactions => two origin structures + // First dispatch +// // Overloading get_pos_sid_pair with signature (origin_structure2, target_structure_type_id, ...) +// virtual position_structid_pair_type get_pos_sid_pair(structure_type const& origin_structure2, structure_type_id_type const& target_sid, position_type const& CoM, +// length_type const& offset, length_type const& reaction_length, rng_type& rng) const +// { +// // this just redirects +// return this->get_pos_sid_pair_2o(origin_structure2, target_sid, CoM, offset, reaction_length, rng); +// } +// // The actual implementation of the first dispatch + virtual position_structid_pair_type get_pos_sid_pair_2o(structure_type const& origin_structure2, structure_type_id_type const& target_sid, + position_type const& CoM, length_type const& offset, length_type const& reaction_length, rng_type& rng) const + { + return origin_structure2.get_pos_sid_pair_2o_helper(*this, target_sid, CoM, offset, reaction_length, rng); + } + // Second dispatch + virtual position_structid_pair_type get_pos_sid_pair_2o_helper(CuboidalRegion const& origin_structure1, structure_type_id_type const& target_sid, + position_type const& CoM, length_type const& offset, length_type const& rl, rng_type& rng) const + { + return this->get_pos_sid_pair_2o_helper_any >(origin_structure1, target_sid, CoM, offset, rl, rng); + } + virtual position_structid_pair_type get_pos_sid_pair_2o_helper(SphericalSurface const& origin_structure1, structure_type_id_type const& target_sid, + position_type const& CoM, length_type const& offset, length_type const& rl, rng_type& rng) const + { + return this->get_pos_sid_pair_2o_helper_any >(origin_structure1, target_sid, CoM, offset, rl, rng); + } + virtual position_structid_pair_type get_pos_sid_pair_2o_helper(CylindricalSurface const& origin_structure1, structure_type_id_type const& target_sid, + position_type const& CoM, length_type const& offset, length_type const& rl, rng_type& rng) const + { + return this->get_pos_sid_pair_2o_helper_any >(origin_structure1, target_sid, CoM, offset, rl, rng); + } + virtual position_structid_pair_type get_pos_sid_pair_2o_helper(DiskSurface const& origin_structure1, structure_type_id_type const& target_sid, + position_type const& CoM, length_type const& offset, length_type const& rl, rng_type& rng) const + { + return this->get_pos_sid_pair_2o_helper_any >(origin_structure1, target_sid, CoM, offset, rl, rng); + } + virtual position_structid_pair_type get_pos_sid_pair_2o_helper(PlanarSurface const& origin_structure1, structure_type_id_type const& target_sid, + position_type const& CoM, length_type const& offset, length_type const& rl, rng_type& rng) const + { + return this->get_pos_sid_pair_2o_helper_any >(origin_structure1, target_sid, CoM, offset, rl, rng); + } + // The template function that defines the actual final dispatch procedure. + template + position_structid_pair_type get_pos_sid_pair_2o_helper_any(Tstruct_ const& origin_structure1, structure_type_id_type const& target_sid, position_type const& CoM, + length_type const& offset, length_type const& reaction_length, rng_type& rng) const + { + // This method has to figure out where the product will be placed in case of a bimolecular reaction. + // As a default, we place particles on the substructure or the lower-dimensional structure. If the structures + // have the same structure type (=> same dimensionality) it does not matter on which structure we put the product, + // as long as it has the structure type id of the product species. This is handled in cases '1' below. + + // 1 - Check whether one of the structures is the parent of the other. If yes, the daughter structure is the target. + if( this->is_parent_of_or_has_same_sid_as(origin_structure1) && origin_structure1.has_valid_target_sid(target_sid) ) + // origin_structure1 is target + return ::get_pos_sid_pair(*this, origin_structure1, CoM, offset, reaction_length, rng); + + else if( origin_structure1.is_parent_of_or_has_same_sid_as(*this) && this->has_valid_target_sid(target_sid) ) + // this structure is target + return ::get_pos_sid_pair(origin_structure1, *this, CoM, offset, reaction_length, rng); + + // 2 - Check which structures has the lower dimensionality / particle degrees of freedom, and put the product there. + else if( origin_structure1.shape().dof() < this->shape().dof() && origin_structure1.has_valid_target_sid(target_sid) ) + // origin_structure1 is target + return ::get_pos_sid_pair(*this, origin_structure1, CoM, offset, reaction_length, rng); + + else if( this->shape().dof() < origin_structure1.shape().dof() && this->has_valid_target_sid(target_sid) ) + // this structure is target + return ::get_pos_sid_pair(origin_structure1, *this, CoM, offset, reaction_length, rng); + + else throw propagation_error("Invalid target structure type: does not match product species structure type or has wrong hierarchy or dimensionality."); + } + +// // *** 4 *** - Generalized functions for pair reactions with two origin structures and one target structure +// // NOTE: This is yet unused, but possibly useful in the future. +// // Overloading get_pos_sid_pair again with signature (origin_structure2, target_structure, ...) and introducing +// // a triple dynamic dispatch. +// virtual position_structid_pair_type get_pos_sid_pair(structure_type const& origin_structure2, structure_type const& target_structure, position_type const& position, +// length_type const& offset, length_type const& reaction_length, rng_type const& rng) const +// { +// return origin_structure2.get_pos_sid_pair_helper1(*this, target_structure, position, offset, reaction_length, rng); +// } + + + /*** Formerly used functions of the Morelli scheme ***/ + // DEPRECATED + virtual length_type drawR_gbd(Real const& rnd, length_type const& r01, Real const& dt, Real const& D01, Real const& v) const + { + return length_type(); // TODO + } + // DEPRECATED + virtual Real p_acceptance(Real const& k_a, Real const& dt, length_type const& r01, position_type const& ipv, + Real const& D0, Real const& D1, Real const& v0, Real const& v1) const + { + return Real(); //TODO + } + // DEPRECATED + virtual position_type dissociation_vector( rng_type& rng, length_type const& r01, Real const& dt, + Real const& D01, Real const& v ) const + { + return position_type(); //TODO + } + virtual void accept(ImmutativeStructureVisitor const& visitor) const { visitor(*this); @@ -47,8 +329,9 @@ class SphericalSurface visitor(*this); } - SphericalSurface(identifier_type const& id, shape_type const& shape) - : base_type(id, shape) {} + // The Constructor + SphericalSurface(structure_name_type const& name, structure_type_id_type const& sid, structure_id_type const& parent_struct_id, shape_type const& shape) + : base_type(name, sid, parent_struct_id, shape) {} }; #endif /* SPHERICAL_SURFACE_HPP */ diff --git a/Structure.hpp b/Structure.hpp index d09c3010..0bede63c 100644 --- a/Structure.hpp +++ b/Structure.hpp @@ -12,29 +12,99 @@ #include #include "Vector3.hpp" +#include "exceptions.hpp" +#include "freeFunctions.hpp" +#include "SpeciesTypeID.hpp" +#include "StructureFunctions.hpp" + +// Forward declarations +template +class StructureContainer; + +template +class CuboidalRegion; + +template +class CylindricalSurface; + +template +class SphericalSurface; + +template +class DiskSurface; + +template +class PlanarSurface; + + template class Structure { public: typedef Ttraits_ traits_type; - typedef typename traits_type::rng_type rng_type; - typedef typename traits_type::structure_id_type identifier_type; - typedef typename traits_type::length_type length_type; - typedef typename traits_type::position_type position_type; - typedef std::pair projected_type; + // shorthands for types that we use + typedef typename traits_type::rng_type rng_type; + typedef typename traits_type::structure_name_type structure_name_type; + typedef typename traits_type::structure_id_type structure_id_type; + typedef typename traits_type::structure_type structure_type; + typedef typename traits_type::length_type length_type; + typedef typename traits_type::position_type position_type; + typedef typename traits_type::species_type species_type; + typedef typename traits_type::structure_type_id_type structure_type_id_type; + typedef std::pair components_pair_type; + typedef std::pair projected_type; + typedef std::pair position_pair_type; + typedef std::pair position_flag_pair_type; + typedef std::pair position_structid_pair_type; + typedef std::pair position_structid_pair_pair_type; + typedef StructureContainer structure_container_type; public: virtual ~Structure() {} - identifier_type const& id() const + structure_id_type const& id() const { + if (!id_) + { + throw illegal_state("ID for structure not defined"); + } return id_; } + void set_id(structure_id_type const& id) + { + id_ = id; + } + + structure_name_type const& name() + { + return name_; + } + + // Get the StructureType of the structure + structure_type_id_type const& sid() const + { + if (!sid_) + { + throw illegal_state("not bound to StructureType"); + } + return sid_; + } + + structure_type_id_type& sid() + { + return sid_; + } + + structure_id_type const& structure_id() const + { + return parent_struct_id_; + } + virtual bool operator==(Structure const& rhs) const { - return id_ == rhs.id(); + return id_ == rhs.id() && sid_ == rhs.sid(); } bool operator!=(Structure const& rhs) const @@ -43,12 +113,169 @@ class Structure } virtual position_type random_position(rng_type& rng) const = 0; - virtual position_type random_vector(length_type const& r, rng_type& rng) const = 0; - virtual position_type bd_displacement(length_type const& r, rng_type& rng) const = 0; + // Methods used in the 'old' BDPropagator // DEPRECATED + virtual position_type dissociation_vector(rng_type& rng, length_type const& r01, Real const& dt, Real const& D01, Real const& v) const = 0; + virtual length_type drawR_gbd(Real const& rnd, length_type const& r01, Real const& dt, Real const& D01, Real const& v) const = 0; + virtual Real p_acceptance(Real const& k_a, Real const& dt, length_type const& r01, position_type const& ipv, Real const& D0, Real const& D1, Real const& v0, Real const& v1) const = 0; + + // Methods used in the 'new' BDPropagator + virtual position_type bd_displacement(length_type const& mean, length_type const& r, rng_type& rng) const = 0; + virtual length_type newBD_distance(position_type const& new_pos, length_type const& radius, position_type const& old_pos, length_type const& sigma) const = 0; + + // TODO this are just functions->move somewhere else + virtual Real get_1D_rate_geminate( Real const& k, length_type const& r01) const = 0; + virtual Real get_1D_rate_surface( Real const& k, length_type const& r0 ) const = 0; + virtual Real particle_reaction_volume( length_type const& r01, length_type const& rl ) const = 0; + virtual Real surface_reaction_volume( length_type const& r0, length_type const& rl ) const = 0; // This does contain a surface dependent component. + + // Methods used to calculate dissociation positions + virtual position_type surface_dissociation_vector( rng_type& rng, length_type const& r0, length_type const& rl ) const = 0; + virtual position_type surface_dissociation_unit_vector( rng_type& rng ) const = 0; + virtual position_pair_type geminate_dissociation_positions( rng_type& rng, species_type const& s0, species_type const& s1, position_type const& op, length_type const& rl ) const = 0; + virtual position_pair_type special_geminate_dissociation_positions( rng_type& rng, species_type const& s_surf, species_type const& s_bulk, position_type const& op_surf, length_type const& rl ) const = 0; + + // General method for getting some measures/info + virtual projected_type project_point(position_type const& pos) const = 0; + virtual projected_type project_point_on_surface(position_type const& pos) const = 0; + virtual length_type distance(position_type const& pos) const = 0; + virtual position_type const& position() const = 0; + virtual position_type const side_comparison_vector() const = 0; + + // Methods used for edge crossing (only for the planes so far) + virtual position_flag_pair_type deflect(position_type const& pos0, position_type const& displacement) const = 0; +// virtual position_type deflect_back(position_type const& pos, position_type const& u_z) const = 0; + + virtual position_structid_pair_type apply_boundary(position_structid_pair_type const& pos_struct_id, + structure_container_type const& structure_container) const = 0; + virtual position_structid_pair_type cyclic_transpose(position_structid_pair_type const& pos_struct_id, + structure_container_type const& structure_container) const = 0; + + // *** Structure functions dynamic dispatch *** + // + // FIXME For now the second dispatch requires the helper functions to be defined here for each structure type separately. + // This is because C++ does not allow virtual templates. The current solution is functional, but ugly, and may be replaced + // by a more elegant solution in the future. + // + // *** 1 *** - Producing one new position + // First dispatch + virtual position_structid_pair_type get_pos_sid_pair(structure_type const& target_structure, position_type const& position, + length_type const& offset, length_type const& rl, rng_type& rng) const = 0; + // Second dispatch + // The helper functions are the dispatch acceptors and have to be declared for each derived structure class because C++ does not support virtual templates. + virtual position_structid_pair_type get_pos_sid_pair_helper(CuboidalRegion const& origin_structure, position_type const& position, + length_type const& offset, length_type const& rl, rng_type& rng) const = 0; + virtual position_structid_pair_type get_pos_sid_pair_helper(SphericalSurface const& origin_structure, position_type const& position, + length_type const& offset, length_type const& rl, rng_type& rng) const = 0; + virtual position_structid_pair_type get_pos_sid_pair_helper(CylindricalSurface const& origin_structure, position_type const& position, + length_type const& offset, length_type const& rl, rng_type& rng) const = 0; + virtual position_structid_pair_type get_pos_sid_pair_helper(DiskSurface const& origin_structure, position_type const& position, + length_type const& offset, length_type const& rl, rng_type& rng) const = 0; + virtual position_structid_pair_type get_pos_sid_pair_helper(PlanarSurface const& origin_structure, position_type const& position, + length_type const& offset, length_type const& rl, rng_type& rng) const = 0; + // *** 2 *** - Producing two new positions + // First dispatch + virtual position_structid_pair_pair_type get_pos_sid_pair_pair(structure_type const& target_structure, position_type const& position, + species_type const& s_orig, species_type const& s_targ, length_type const& rl, rng_type& rng) const = 0; + // Second dispatch + // The helper functions are dispatch acceptors and have to be declared for each derived structure class because C++ does not support virtual templates. + virtual position_structid_pair_pair_type get_pos_sid_pair_pair_helper(CuboidalRegion const& origin_structure, position_type const& position, + species_type const& s_orig, species_type const& s_targ, length_type const& rl, rng_type& rng) const = 0; + virtual position_structid_pair_pair_type get_pos_sid_pair_pair_helper(SphericalSurface const& origin_structure, position_type const& position, + species_type const& s_orig, species_type const& s_targ, length_type const& rl, rng_type& rng) const = 0; + virtual position_structid_pair_pair_type get_pos_sid_pair_pair_helper(CylindricalSurface const& origin_structure, position_type const& position, + species_type const& s_orig, species_type const& s_targ, length_type const& rl, rng_type& rng) const = 0; + virtual position_structid_pair_pair_type get_pos_sid_pair_pair_helper(DiskSurface const& origin_structure, position_type const& position, + species_type const& s_orig, species_type const& s_targ, length_type const& rl, rng_type& rng) const = 0; + virtual position_structid_pair_pair_type get_pos_sid_pair_pair_helper(PlanarSurface const& origin_structure, position_type const& position, + species_type const& s_orig, species_type const& s_targ, length_type const& rl, rng_type& rng) const = 0; + + // *** 3 *** - Pair reactions => two origin structures + // The following functions handle the case of two origin structures. + // First again the (C++) structure types have to be determined by a double dispatch. + // As a next step, the helper function has to determine which of the two structures + // is the target structure. + // For now, the target structure is either: + // - the lower hierarchy level structure, i.e. one of the origin structures has + // to be the daughter structure of the other and the particle will end up on + // the daughter structure; or: + // - in case of equal structure type id's it can end up on either origin structure + // and apply_boundary will handle the right placement afterwards. + + // First dispatch + // This is called as a method of origin_structure1 with origin_structure2 as an argument. + virtual position_structid_pair_type get_pos_sid_pair_2o(structure_type const& origin_structure2, structure_type_id_type const& target_sid, position_type const& CoM, + length_type const& offset, length_type const& reaction_length, rng_type& rng) const = 0; +// // Some convenient method overloading; this is just a redirect to the above +// virtual position_structid_pair_type get_pos_sid_pair(structure_type const& origin_structure2, structure_type_id_type const& target_sid, position_type const& CoM, +// length_type const& offset, length_type const& reaction_length, rng_type& rng) const = 0; + // Second dispatch + // The helper functions are the dispatch acceptors and have to be declared for each derived structure class because C++ does not support virtual templates. + virtual position_structid_pair_type get_pos_sid_pair_2o_helper(CuboidalRegion const& origin_structure1, structure_type_id_type const& target_sid, position_type const& CoM, + length_type const& offset, length_type const& reaction_length, rng_type& rng) const = 0; + virtual position_structid_pair_type get_pos_sid_pair_2o_helper(SphericalSurface const& origin_structure1, structure_type_id_type const& target_sid, position_type const& CoM, + length_type const& offset, length_type const& reaction_length, rng_type& rng) const = 0; + virtual position_structid_pair_type get_pos_sid_pair_2o_helper(CylindricalSurface const& origin_structure1, structure_type_id_type const& target_sid, position_type const& CoM, + length_type const& offset, length_type const& reaction_length, rng_type& rng) const = 0; + virtual position_structid_pair_type get_pos_sid_pair_2o_helper(DiskSurface const& origin_structure1, structure_type_id_type const& target_sid, position_type const& CoM, + length_type const& offset, length_type const& reaction_length, rng_type& rng) const = 0; + virtual position_structid_pair_type get_pos_sid_pair_2o_helper(PlanarSurface const& origin_structure1, structure_type_id_type const& target_sid, position_type const& CoM, + length_type const& offset, length_type const& reaction_length, rng_type& rng) const = 0; + + // Some further helper functions used by template get_pos_sid_pair_helper_two_origins_any(...), + // which is the final dispatch template defined in each of the derived classes and makes use of the two following checker functions: + template + inline bool is_parent_of_or_has_same_sid_as(Tstruct_ const& s) const + { + structure_id_type s_parent_id( s.structure_id() ); + structure_type_id_type s_sid( s.sid() ); + structure_id_type this_id( this->id() ); + structure_type_id_type this_sid( this->sid() ); + + return ( s_parent_id == this_id || s_sid == this_sid ); + } + inline bool has_valid_target_sid(structure_type_id_type const& target_sid) const + { + structure_type_id_type this_sid( this->sid() ); + + return ( this_sid == target_sid ); + } + +// // TODO +// // *** 4 *** - Generalized functions for pair reactions => two origin structures and one target_structure +// // This introduces a triple dynamic dispatch, overloading method call structure.get_pos_sid_pair once more. +// // NOTE: As yet these methods are unused but might prove useful in the future. +// virtual position_structid_pair_type get_pos_sid_pair(structure_type const& origin_structure2, structure_type const& target_structure, position_type const& position, +// length_type const& offset, length_type const& reaction_length, rng_type& rng) const = 0; +// template +// position_structid_pair_type get_pos_sid_pair_helper1(Tstruct1_ const& origin_structure1, structure_type const& target_structure, position_type const& position, +// length_type const& offset, length_type const& reaction_length, rng_type& rng) const +// { +// return target_structure.get_pos_sid_pair_helper2(origin_structure1, *this, position, offset, reaction_length, rng); +// } +// template +// position_structid_pair_type get_pos_sid_pair_helper2(Tstruct1_ const& origin_structure1, Tstruct2_ const& origin_structure2, position_type const& position, +// length_type const& offset, length_type const& reaction_length, rng_type& rng) const +// { +// structure_id_type this_id( this->id ); +// structure_id_type os1_id( origin_structure1.id ); +// structure_id_type os2_id( origin_structure2.id ); +// +// if(os1_id == this_id) +// // Dispatch to function with well-defined typing +// return ::get_pos_sid_pair(origin_structure2, *this, position, offset, reaction_length, rng); +// +// else if(os2_id == this_id) +// // Dispatch to function with well-defined typing +// return ::get_pos_sid_pair(origin_structure1, *this, position, offset, reaction_length, rng); +// +// else +// throw propagation_error("Target structure must be one of the origin structures for pair reaction."); +// } +// +// NOTE: The template based variant will not work! The helper methods have to be defined for each structure type separately or in a smarter way! - virtual projected_type projected_point(position_type const& pos) const = 0; virtual std::size_t hash() const { @@ -59,23 +286,31 @@ class Structure #elif defined(HAVE_BOOST_FUNCTIONAL_HASH_HPP) using boost::hash; #endif - return hash()(id_); + return hash()(name_) ^ + hash()(sid_); } virtual std::string as_string() const { std::ostringstream out; - out << "Structure(" << id() << ")"; + out << "Structure(" << id() << ", " << sid() << ")"; return out.str(); } - Structure(identifier_type const& id) - : id_(id) {} + // Constructor + Structure(structure_name_type const& name, structure_type_id_type const& sid, structure_id_type const& parent_struct_id) + : name_(name), sid_(sid), parent_struct_id_(parent_struct_id) {} +////// Member variables protected: - identifier_type id_; + structure_name_type name_; // just the name + structure_type_id_type sid_; // id of the structure_type of the structure + structure_id_type id_; // id of the structure (filled in later) + structure_id_type parent_struct_id_; // id of the parent structure (filled in later) }; + +//////// Inline functions template inline std::basic_ostream& operator<<(std::basic_ostream& strm, const Structure& v) { diff --git a/StructureContainer.hpp b/StructureContainer.hpp new file mode 100644 index 00000000..cdb105d6 --- /dev/null +++ b/StructureContainer.hpp @@ -0,0 +1,751 @@ +#ifndef STRUCTURE_CONTAINER_HPP +#define STRUCTURE_CONTAINER_HPP + +#include +#include "exceptions.hpp" +#include "Logger.hpp" +#include "linear_algebra.hpp" +#include "ConnectivityContainer.hpp" +#include "CuboidalRegion.hpp" +#include "PlanarSurface.hpp" +#include "CylindricalSurface.hpp" +#include "DiskSurface.hpp" +#include "SphericalSurface.hpp" + +// Forward declaration of the StructureContainer to allow the forward declarations of the 'apply_boundary' functions +template +class StructureContainer; + +// Forward declaration of the 'apply_boundary' functions needed in the definition of the StructureContainer below +template +std::pair +apply_boundary (std::pair const& pos_structure_id, + PlanarSurface const& planar_surface, + StructureContainer const& sc); + +template +inline std::pair +cyclic_transpose (std::pair const& pos_structure_id, + PlanarSurface const& planar_surface, + StructureContainer const& sc); + +template +std::pair +apply_boundary (std::pair const& pos_structure_id, + CylindricalSurface const& cylindrical_surface, + StructureContainer const& sc); + +template +inline std::pair +cyclic_transpose (std::pair const& pos_structure_id, + CylindricalSurface const& cylindrical_surface, + StructureContainer const& sc); + + + +template +class StructureContainer +{ +public: + typedef Tobj_ structure_type; + typedef Tid_ structure_id_type; + typedef Ttraits_ traits_type; + typedef std::set structure_id_set; + typedef std::pair > structure_id_pair; + typedef std::map > structure_map; + +protected: + typedef select_second structures_second_selector_type; + typedef boost::transform_iterator structure_iterator; + typedef typename std::map per_structure_substructure_id_set; + + typedef typename structure_type::length_type length_type; + typedef typename structure_type::position_type position_type; + typedef typename structure_type::position_type vector_type; + typedef typename structure_type::structure_type_id_type structure_type_id_type; + + typedef std::pair neighbor_id_vector_type; // FIXME this is actually a datatype of the ConnectivityContainer. + +public: + typedef SphericalSurface spherical_surface_type; + typedef std::pair > spherical_surface_id_pair_type; + + typedef DiskSurface disk_surface_type; + typedef std::pair > disk_surface_id_pair_type; + + typedef CylindricalSurface cylindrical_surface_type; + typedef std::pair > cylindrical_surface_id_pair_type; + typedef typename cylindrical_surface_type::side_enum_type cylindrical_surface_side_type; + typedef ConnectivityContainer cylindrical_surface_bc_type; // FIXME number of neighbors should be the length of the side enumerator + + typedef PlanarSurface planar_surface_type; + typedef std::pair > planar_surface_id_pair_type; + typedef typename planar_surface_type::side_enum_type planar_surface_side_type; + typedef ConnectivityContainer planar_surface_bc_type; + + typedef CuboidalRegion cuboidal_region_type; + typedef std::pair > cuboidal_region_id_pair_type; + typedef typename cuboidal_region_type::side_enum_type cuboidal_region_side_type; + typedef ConnectivityContainer cuboidal_region_bc_type; + +public: + typedef sized_iterator_range structures_range; + typedef std::pair position_structid_type; + + + +public: + // The destructor + virtual ~StructureContainer() {}; + + // Update_structure methods for the different kind of structures supported by the StructureContainer + virtual bool update_structure (spherical_surface_id_pair_type const& structid_sphere) // can I make this into a template function? + { + return update_structure_base(structid_sphere); // No connecting needs to be done + } + virtual bool update_structure (disk_surface_id_pair_type const& structid_disk) + { + return update_structure_base(structid_disk); + } + virtual bool update_structure (cuboidal_region_id_pair_type const& structid_cube) + { + if ( update_structure_base(structid_cube)) + { + // add to Connectivity container for cuboidal regions + // NOTE this information is never queried!! Maybe leave the neighbor info out? + cuboidal_structs_bc_.set_neighbor_info(structid_cube.first, 0, std::make_pair(structid_cube.first, structid_cube.second->shape().unit_z())); + cuboidal_structs_bc_.set_neighbor_info(structid_cube.first, 1, std::make_pair(structid_cube.first, multiply(structid_cube.second->shape().unit_z(), -1.0) )); + cuboidal_structs_bc_.set_neighbor_info(structid_cube.first, 2, std::make_pair(structid_cube.first, structid_cube.second->shape().unit_y())); + cuboidal_structs_bc_.set_neighbor_info(structid_cube.first, 3, std::make_pair(structid_cube.first, multiply(structid_cube.second->shape().unit_y(), -1.0) )); + cuboidal_structs_bc_.set_neighbor_info(structid_cube.first, 4, std::make_pair(structid_cube.first, structid_cube.second->shape().unit_x())); + cuboidal_structs_bc_.set_neighbor_info(structid_cube.first, 5, std::make_pair(structid_cube.first, multiply(structid_cube.second->shape().unit_x(), -1.0) )); + return true; + } + return false; + } + virtual bool update_structure (cylindrical_surface_id_pair_type const& structid_cylinder) + { + if ( update_structure_base(structid_cylinder)) + { + // add to Connectivity container for cylindrical surfaces + cylindrical_structs_bc_.set_neighbor_info(structid_cylinder.first, 0, std::make_pair(structid_cylinder.first, multiply(structid_cylinder.second->shape().unit_z(), -1.0) )); + cylindrical_structs_bc_.set_neighbor_info(structid_cylinder.first, 1, std::make_pair(structid_cylinder.first, structid_cylinder.second->shape().unit_z() )); + return true; + } + return false; + } + virtual bool update_structure (planar_surface_id_pair_type const& structid_plane) + { + // call regular update method (this also checks if the structure was already present) + if ( update_structure_base(structid_plane)) + { + // We now assume that the structure was not already in the boundary_conditions_thing + // add to Connectivity container for planar surfaces and set *reflective* boundary conditions. // TODO This may cause many problems in single reactions!!! Overthink!!! + planar_structs_bc_.set_neighbor_info(structid_plane.first, 0, std::make_pair(structid_plane.first, multiply(structid_plane.second->shape().unit_y(), -1.0) )); + planar_structs_bc_.set_neighbor_info(structid_plane.first, 1, std::make_pair(structid_plane.first, structid_plane.second->shape().unit_y() )); + planar_structs_bc_.set_neighbor_info(structid_plane.first, 2, std::make_pair(structid_plane.first, structid_plane.second->shape().unit_x() )); + planar_structs_bc_.set_neighbor_info(structid_plane.first, 3, std::make_pair(structid_plane.first, multiply(structid_plane.second->shape().unit_x(), -1.0) )); + return true; + } + // if the structure was already present, don't change anything + return false; + } + + // Methods to get the 'n-th' neighbor structure of a particular structure from the container. + // We pass the structure and not just the ID because with just the ID we can't differentiate between the various types of structures. + virtual neighbor_id_vector_type get_neighbor_info(planar_surface_type const& structure, int n) const + { + return planar_structs_bc_.get_neighbor_info(structure.id(), n); + } + virtual neighbor_id_vector_type get_neighbor_info(cylindrical_surface_type const& structure, int n) const + { + return cylindrical_structs_bc_.get_neighbor_info(structure.id(), n); + } +// virtual bool connect_structures(planar_surface_type const& structure1, planar_surface_side_type const& side1, +// planar_surface_type const& structure2, planar_surface_side_type const& side2) + virtual bool connect_structures(planar_surface_type const& structure1, int const& side1, + planar_surface_type const& structure2, int const& side2) + { + // TODO if the structure are not the same, check that the planes actually touch at the right edge. + + // First check whether the planes are orthogonal or parallel, or none of this. + bool planes_are_orthogonal(false); + bool planes_are_parallel(false); + vector_type structure1_unit_z( structure1.shape().unit_z() ); + vector_type structure2_unit_z( structure2.shape().unit_z() ); + + if( feq(dot_product(structure1_unit_z, structure2_unit_z), 0.0, 1.0) ) + planes_are_orthogonal = true; + if( feq(dot_product(structure1_unit_z, structure2_unit_z), 1.0, 1.0) ) + planes_are_parallel = true; + // If they are neither parallel or orthogonal we cannot treat this situation and stop here + if( not(planes_are_orthogonal or planes_are_parallel) ) + throw unsupported("StructureContainer: only orthogonal or parallel planes can be connected."); + + // Now determine which of the unit vectors of the neighboring plane is the one that shall be + // used for transforming the position that reaches out of the origin plane. This is relevant + // only for the orthogonal case. If the planes are parallel, the position does not have + // to be transformed at all (only the StructureID changes). + // For parallel planes we therefore return a zero vector below. This at once serves as + // a flag indicating that the planes are indeed parallel. + //vector_type zero_vector( zero_vector() ); + vector_type structure1_vector( vector_type(0.0, 0.0, 0.0) ); + vector_type structure2_vector( vector_type(0.0, 0.0, 0.0) ); + // this is already fine for parallel planes + + if( planes_are_orthogonal ) + { + // Side convention for coding neighbors: + // + // |-------------| + // | 0 | + // | | + // | 2 3 | + // | | + // | 1 | + // |-------------| + // + // FIXME cleanup code below + switch (side1) + { + case 0: structure1_vector = multiply(structure1.shape().unit_y(), -1.0); + break; + case 1: structure1_vector = structure1.shape().unit_y(); + break; + case 2: structure1_vector = structure1.shape().unit_x(); + break; + case 3: structure1_vector = multiply(structure1.shape().unit_x(), -1.0); + break; + } + switch (side2) + { + case 0: structure2_vector = multiply(structure2.shape().unit_y(), -1.0); + break; + case 1: structure2_vector = structure2.shape().unit_y(); + break; + case 2: structure2_vector = structure2.shape().unit_x(); + break; + case 3: structure2_vector = multiply(structure2.shape().unit_x(), -1.0); + break; + } + } + + planar_structs_bc_.set_neighbor_info(structure1.id(), side1, std::make_pair(structure2.id(), structure2_vector) ); + planar_structs_bc_.set_neighbor_info(structure2.id(), side2, std::make_pair(structure1.id(), structure1_vector) ); + + return true; + } +// virtual bool connect_structures(cylindrical_surface_type const& structure1, cylindrical_surface_side_type const& side1, +// cylindrical_surface_type const& structure2, cylindrical_surface_side_type const& side2) + virtual bool connect_structures(cylindrical_surface_type const& structure1, int const& side1, + cylindrical_surface_type const& structure2, int const& side2) + { + if ( structure1.id() != structure2.id() ) + { + throw unsupported("Two different cylinders cannot be connected (yet...)."); + } + const length_type factor( (side1 == 1) ? 1.0 : -1.0); + if ( side1 == side2 ) + { + cylindrical_structs_bc_.set_neighbor_info(structure1.id(), side1, std::make_pair(structure2.id(), multiply( structure2.shape().unit_z(), -factor ) ) ); + cylindrical_structs_bc_.set_neighbor_info(structure2.id(), side2, std::make_pair(structure1.id(), multiply( structure1.shape().unit_z(), -factor ) ) ); + } + else + { + cylindrical_structs_bc_.set_neighbor_info(structure1.id(), side1, std::make_pair(structure2.id(), multiply( structure2.shape().unit_z(), factor ) ) ); + cylindrical_structs_bc_.set_neighbor_info(structure2.id(), side2, std::make_pair(structure1.id(), multiply( structure1.shape().unit_z(), -factor ) ) ); + } + return true; + } + + // Template member function to call the right 'apply_boundary' function for the various types of structures. + template + position_structid_type apply_boundary(Tstructure_ const& structure, position_structid_type const& pos_struct_id) const + { + return ::apply_boundary(pos_struct_id, structure, *this); + } + template + position_structid_type cyclic_transpose(Tstructure_ const& structure, position_structid_type const& pos_struct_id) const + { + return ::cyclic_transpose(pos_struct_id, structure, *this); + } + + + /////////////////// Methods that are general for all the structures. + virtual structure_id_type get_def_structure_id() const + { + return default_structure_id_; + } + virtual bool has_structure(structure_id_type const& id) const + { + typename structure_map::const_iterator i(structure_map_.find(id)); + return i != structure_map_.end(); + } + virtual boost::shared_ptr get_structure(structure_id_type const& id) const + { + typename structure_map::const_iterator i(structure_map_.find(id)); + if (structure_map_.end() == i) + { + throw not_found(std::string("Unknown structure (id=") + boost::lexical_cast(id) + ")"); + } + return (*i).second; + } + virtual structures_range get_structures_range() const + { + return structures_range( + structure_iterator(structure_map_.begin(), structures_second_selector_type()), + structure_iterator(structure_map_.end(), structures_second_selector_type()), + structure_map_.size()); + } + virtual boost::shared_ptr get_some_structure_of_type(structure_type_id_type const& sid) const + { + // This returns a structure of the structure type given as an argument, if it exists + // in the container. Otherwise it throws an exception. + typename structure_map::const_iterator i; + + for(i = structure_map_.begin(); i != structure_map_.end(); i++) + + if( (*i).second->sid() == sid ) break; + + if (structure_map_.end() == i) + { + throw not_found(std::string("Could not find any structure of specified structure type (sid=") + boost::lexical_cast(sid) + ")"); + } + return (*i).second; + } + virtual bool remove_structure(structure_id_type const& id) + { + // TODO + // -find StructureID in all ConnectivityContainers -> remove references + // -get all the substructures of structure + // -only remove if no substructures + return false; + } + structure_id_set get_visible_structures(structure_id_type const& id) const + { + // This function returns all structures that are visible form the structure with + // the ID passed to the function. + // A structure is visible if it is a substructure or if it has the same parent + // structure as the current structure. + structure_id_set visible_structures; + + const boost::shared_ptr structure(get_structure(id)); + // First check the "sibling" structures + if (structure->structure_id() == id) + { + visible_structures = structure_id_set(); + } + else + { + visible_structures = get_visible_structures(structure->structure_id()); + visible_structures.erase(id); + } + + // Now the substructures + const structure_id_set substructures (get_substructure_ids(id)); + visible_structures.insert(substructures.begin(), substructures.end()); + + return visible_structures; + } + + void initialize(structure_id_type const& default_structid) + { + default_structure_id_ = default_structid; + structure_substructures_map_[default_structid] = structure_id_set(); + } + +// // The constructor +// StructureContainer() {} +// StructureContainer(structure_id_type default_structid) : default_structure_id_(default_structid) +// { +// structure_substructures_map_[default_structid] = structure_id_set(); +// } + + +private: + // Update the datastructures that are general for all the structures. + bool update_structure_base(structure_id_pair const& structid_pair) + { + typename structure_map::const_iterator i(structure_map_.find(structid_pair.first)); + if (i != structure_map_.end()) + // The item was already found in the map + { + if ((*i).second->structure_id() != structid_pair.second->structure_id()) + // If the structure had a different parent structure + // Note that all the substructures of the structures are kept (they are also moved moved through the hierarchy) + { + structure_substructures_map_[(*i).second->structure_id()].erase((*i).first); + structure_substructures_map_[structid_pair.second->structure_id()].insert(structid_pair.first); + } + structure_map_[(*i).first] = structid_pair.second; + return false; + } + + // The structure was not yet in the world. + // create a new item in the structure mapping + structure_map_[structid_pair.first] = structid_pair.second; + // add the id the mapping 'super_structure_id -> set of substructures' + structure_substructures_map_[structid_pair.second->structure_id()].insert(structid_pair.first); + // create a new mapping from structure id -> set of substructures + structure_substructures_map_[structid_pair.first] = structure_id_set(); + return true; + } + + // Get all the structure ids of the substructures + structure_id_set get_substructure_ids(structure_id_type const& id) const + { + typename per_structure_substructure_id_set::const_iterator i( + structure_substructures_map_.find(id)); + if (i == structure_substructures_map_.end()) + { + throw not_found(std::string("Unknown structure (id=") + boost::lexical_cast(id) + ")"); + } + return (*i).second; + } + +///// Member variables +protected: + structure_map structure_map_; // mapping: structure_id -> structure + per_structure_substructure_id_set structure_substructures_map_; + structure_id_type default_structure_id_; + + // The next object describe the boundary conditions that are applied to the different types of structures. + cylindrical_surface_bc_type cylindrical_structs_bc_; + planar_surface_bc_type planar_structs_bc_; + cuboidal_region_bc_type cuboidal_structs_bc_; + static Logger& log_; + +}; // end of class definition + + +//////// Link logger to the global logging system +template +Logger& StructureContainer::log_(Logger::get_logger("ecell.StructureContainer")); +//////// Also define one that can be used by the inline functions below +static Logger& log_(Logger::get_logger("ecell.StructureContainer")); + + +//////// Inline functions applicable to the StructureContainer +//// functions for Planes +template +inline std::pair +apply_boundary (std::pair const& pos_structure_id, + PlanarSurface const& planar_surface, + StructureContainer const& sc) +// The template needs to be parameterized with the appropriate shape (which then parameterizes the type +// of the ConnectivityContainer that we use. +// We supply the structure container as an argument so that we can get the structures that we need, and to +// query the boundary conditions and connectivity. +{ + // useful typedefs + typedef Ttraits_ traits_type; + typedef typename traits_type::structure_id_type structure_id_type; + + typedef typename PlanarSurface::shape_type plane_type; + typedef typename plane_type::length_type length_type; + typedef typename plane_type::position_type position_type; + typedef typename plane_type::position_type vector_type; + + typedef std::pair neighbor_id_vector_type; + //typedef std::pair position_structid_pair_type; + + // Note that we assume that the new position is in the plane (dot(pos, unit_z)==0) + // and that the position is already transposed for the plane. + const plane_type origin_plane( planar_surface.shape() ); + const boost::array half_extents( origin_plane.half_extent() ); + + const position_type pos_vector( subtract(pos_structure_id.first, origin_plane.position()) ); + const length_type component_x ( dot_product(pos_vector, origin_plane.unit_x()) ); + const length_type component_y ( dot_product(pos_vector, origin_plane.unit_y()) ); + const vector_type zero_vector( vector_type(0.0, 0.0, 0.0) ); + + // declare the variables that will be written + structure_id_type new_id( pos_structure_id.second ); // initialized with old structure ID + position_type neighbor_plane_par; + position_type neighbor_plane_inl; + + // info variables + bool planes_are_orthogonal( false ); + + //bool planes_are_parallel( false ); + + // Check for (currently unsupported) self-connections + for( int i=0; i<4; i++ ) + { + const neighbor_id_vector_type neighbor_id_vector (sc.get_neighbor_info(planar_surface, 0)); + new_id = neighbor_id_vector.first; + + if( new_id == pos_structure_id.second ) + { + log_.warn("Plane is connected to itself, which is unsupported; no boundary condition will be applied at this call."); + return pos_structure_id; + } + }; + + if( (abs(component_x) <= half_extents[0] && abs(component_y) <= half_extents[1]) ) + { + // we are still in the plane (did not pass any of the boundaries) + // don't have to do anything + return pos_structure_id; + } + else + { + // We are outside of the plane. + if ( half_extents[1]*abs(component_x) < half_extents[0]*abs(component_y) ) + { + // Top or bottom (and may also be in one of the corners) + if ( half_extents[1] < component_y ) + { + // we are at the 'top' of the plane (side nr. 0) + // the 'vector' is the unit vector pointing from the edge between the two planes to the center of the plane + const neighbor_id_vector_type neighbor_id_vector (sc.get_neighbor_info(planar_surface, 0)); + + new_id = neighbor_id_vector.first; + + if(neighbor_id_vector.second == zero_vector) + ;//planes_are_parallel = true; + else{ + planes_are_orthogonal = true; + neighbor_plane_par = multiply(origin_plane.unit_x(), component_x); + neighbor_plane_inl = add(multiply(origin_plane.unit_y(), half_extents[1]), + multiply(neighbor_id_vector.second, (component_y - half_extents[1]) ) ); + } + } + else if ( component_y < -half_extents[1] ) + { + // we are at the 'bottom' of the plane (side nr. 1) + const neighbor_id_vector_type neighbor_id_vector (sc.get_neighbor_info(planar_surface, 1)); + + new_id = neighbor_id_vector.first; + + if(neighbor_id_vector.second == zero_vector) + ;//planes_are_parallel = true; + else{ + planes_are_orthogonal = true; + neighbor_plane_par = multiply(origin_plane.unit_x(), component_x); + neighbor_plane_inl = add(multiply(origin_plane.unit_y(), -half_extents[1] ), + multiply(neighbor_id_vector.second, (-component_y - half_extents[1]) ) ); + } + } + } + else // half_extents[1]*abs(component_x) >= half_extents[0]*abs(component_y) + { + if ( component_x < -half_extents[0] ) + { + // we are at the 'left' of the plane (side nr. 2) + const neighbor_id_vector_type neighbor_id_vector (sc.get_neighbor_info(planar_surface, 2)); + + new_id = neighbor_id_vector.first; + if(neighbor_id_vector.second == zero_vector) + ;//planes_are_parallel = true; + else{ + planes_are_orthogonal = true; + neighbor_plane_par = multiply(origin_plane.unit_y(), component_y); + neighbor_plane_inl = add(multiply(origin_plane.unit_x(), -half_extents[0]), + multiply(neighbor_id_vector.second, (-component_x - half_extents[0]) ) ); + } + } + else if ( half_extents[0] < component_x ) + { + // we are at the 'right' of the plane (side nr. 3) + const neighbor_id_vector_type neighbor_id_vector (sc.get_neighbor_info(planar_surface, 3)); + + new_id = neighbor_id_vector.first; + if(neighbor_id_vector.second == zero_vector) + ;//planes_are_parallel = true; + else{ + planes_are_orthogonal = true; + neighbor_plane_par = multiply(origin_plane.unit_y(), component_y); + neighbor_plane_inl = add(multiply(origin_plane.unit_x(), half_extents[0]), + multiply(neighbor_id_vector.second, (component_x - half_extents[0]) ) ); + } + } + } + + position_type new_pos( origin_plane.position() ); + if(planes_are_orthogonal) + new_pos = add(origin_plane.position(), add(neighbor_plane_par, neighbor_plane_inl)); + + // Check if we are in one of the corners. If yes -> do another round of border crossing from neighboring plane. + // Only do this if the particle does not end up on another side of the same plane (the scenario of one plane + // with periodic BCs), as this may cause infinite loops. + if( not new_id==pos_structure_id.second && abs(component_x) > half_extents[0] && abs(component_y) > half_extents[1] ) + { + return sc.get_structure(new_id)->apply_boundary(std::make_pair(new_pos, new_id), sc); + } + else + { + return std::make_pair(new_pos, new_id); + } + } +} + +template +inline std::pair +cyclic_transpose (std::pair const& pos_structure_id, + PlanarSurface const& planar_surface, + StructureContainer const& sc) +{ + // useful typedefs + typedef Ttraits_ traits_type; + typedef typename traits_type::structure_id_type structure_id_type; + + typedef typename PlanarSurface::shape_type plane_type; + typedef typename plane_type::length_type length_type; + typedef typename plane_type::position_type position_type; + + typedef std::pair neighbor_id_vector_type; + + + + if ( pos_structure_id.second == planar_surface.id() ) + return pos_structure_id; + + position_type new_pos_par; + position_type new_pos_inl; + + int side_nr; + neighbor_id_vector_type neighbor_id_vector; + for (side_nr = 0; side_nr < 4; ++side_nr) + { + neighbor_id_vector = sc.get_neighbor_info(planar_surface, side_nr); + if ( neighbor_id_vector.first == pos_structure_id.second ) + // we have found the correct side + break; + } + // TODO use iterator so that we can check whether match was found or that we escaped by reaching the end of the list. + + const plane_type plane (planar_surface.shape()); + boost::array half_extents(plane.half_extent()); + switch ( side_nr ) + { + case 0: // top side + { + const position_type pos (subtract(pos_structure_id.first, add(plane.position(), + multiply(plane.unit_y(), + half_extents[1]) ) )); + const length_type plane_par (dot_product(pos, plane.unit_x())); // TODO replace by subtraction? + const length_type plane_inl (dot_product(pos, neighbor_id_vector.second)); + new_pos_par = multiply(plane.unit_x(), plane_par); + new_pos_inl = multiply(plane.unit_y(), half_extents[1] + plane_inl); + break; + } + case 1: // botom + { + const position_type pos (subtract(pos_structure_id.first, add(plane.position(), + multiply(plane.unit_y(), + -half_extents[1]) ) )); + const length_type plane_par (dot_product(pos, plane.unit_x())); // TODO replace by subtraction? + const length_type plane_inl (dot_product(pos, neighbor_id_vector.second)); + new_pos_par = multiply(plane.unit_x(), plane_par); + new_pos_inl = multiply(plane.unit_y(), -(half_extents[1] + plane_inl) ); + break; + } + case 2: // left + { + const position_type pos (subtract(pos_structure_id.first, add(plane.position(), + multiply(plane.unit_x(), + -half_extents[0]) ) )); + const length_type plane_par (dot_product(pos, plane.unit_y())); // TODO replace by subtraction? + const length_type plane_inl (dot_product(pos, neighbor_id_vector.second)); + new_pos_par = multiply(plane.unit_y(), plane_par); + new_pos_inl = multiply(plane.unit_x(), -(half_extents[0] + plane_inl) ); + break; + } + case 3: // right + { + const position_type pos (subtract(pos_structure_id.first, add(plane.position(), + multiply(plane.unit_x(), + half_extents[0]) ) )); + const length_type plane_par (dot_product(pos, plane.unit_y())); // TODO replace by subtraction? + const length_type plane_inl (dot_product(pos, neighbor_id_vector.second)); + new_pos_par = multiply(plane.unit_y(), plane_par); + new_pos_inl = multiply(plane.unit_x(), half_extents[0] + plane_inl ); + break; + } + } + const position_type new_pos ( add(plane.position(), + add(new_pos_par, new_pos_inl))); + return std::make_pair(new_pos, planar_surface.id()); +} + + + +///// Functions for CylindricalSurfaces +template +inline std::pair +apply_boundary (std::pair const& pos_structure_id, + CylindricalSurface const& cylindrical_surface, + StructureContainer const& sc) + +// The template needs to be parameterized with the appropriate shape (which then parameterizes the type +// of the ConnectivityContainer that we use. +// We supply the structure container as an argument so that we can get the structures that we need and +// to query the boundary conditions and connectivity. +{ + // useful typedefs + typedef Ttraits_ traits_type; + typedef typename traits_type::structure_id_type structure_id_type; + + typedef typename CylindricalSurface::shape_type cylinder_type; + typedef typename cylinder_type::length_type length_type; + typedef typename cylinder_type::position_type position_type; + + typedef std::pair neighbor_id_vector_type; + + + // Note that we assume that the new position is in the cylinder (dot(pos, unit_r)==0) + const cylinder_type origin_cylinder (cylindrical_surface.shape()); + const length_type half_length(origin_cylinder.half_length()); + + const position_type pos_vector( subtract(pos_structure_id.first, origin_cylinder.position()) ); + const length_type component_z ( dot_product(pos_vector, origin_cylinder.unit_z()) ); + + // declare the variables that will be written + structure_id_type new_id(pos_structure_id.second); + position_type neighbor_cylinder_inl; + + if ( half_length < component_z ) + { + // we are at the 'right' side of the cylinder (side nr. 0, side in the direction of the normal vector) + // get the unit vector pointing from the edge between the two planes to the center of the plane + const neighbor_id_vector_type neighbor_id_vector (sc.get_neighbor_info(cylindrical_surface, 0)); + + new_id = neighbor_id_vector.first; + neighbor_cylinder_inl = add( multiply(origin_cylinder.unit_z(), half_length), + multiply(neighbor_id_vector.second, (component_z - half_length) ) ); + } + else if ( component_z < -half_length ) + { + // we are at the 'right' side of the cylinder (side nr. 0, side in the direction of the normal vector) + // get the unit vector pointing from the edge between the two planes to the center of the plane + const neighbor_id_vector_type neighbor_id_vector (sc.get_neighbor_info(cylindrical_surface, 1)); + + new_id = neighbor_id_vector.first; + neighbor_cylinder_inl = add( multiply(origin_cylinder.unit_z() , -half_length), + multiply(neighbor_id_vector.second, (-component_z - half_length) ) ); + } + else + { + // we are still in the cylinder (did not pass any of the boundaries) + // don't have to do anything + return pos_structure_id; + } + + const position_type new_pos ( add(origin_cylinder.position(), neighbor_cylinder_inl) ); + return std::make_pair(new_pos, new_id); +} + +template +inline std::pair +cyclic_transpose (std::pair const& pos_structure_id, + CylindricalSurface const& cylindrical_surface, + StructureContainer const& sc) +{ + // This is not used and not implemented yet. +} +#endif /* STRUCTURE_CONTAINER_HPP */ + diff --git a/StructureFunctions.hpp b/StructureFunctions.hpp new file mode 100644 index 00000000..af1f7c70 --- /dev/null +++ b/StructureFunctions.hpp @@ -0,0 +1,1380 @@ +#ifndef STRUCTFUNC_HPP +#define STRUCTFUNC_HPP + +//#include "CuboidalRegion.hpp" +//#include "SphericalSurface.hpp" +//#include "CylindricalSurface.hpp" +//#include "DiskSurface.hpp" +//#include "PlanarSurface.hpp" +#include "linear_algebra.hpp" +#include "exceptions.hpp" +#include "Logger.hpp" +#include "utils/math.hpp" + +// Forward declaration of structures +template +class Structure; + +template +class CuboidalRegion; + +template +class SphericalSurface; + +template +class CylindricalSurface; + +template +class DiskSurface; + +template +class PlanarSurface; + + +/******************************************************************************************/ + +// Define the structure functions and the actions performed in each case. +// There are two types: 1) yielding one new position and structure id +// 2) yielding two new positions and structure ids +// (for single reactions with two products) + +/******************************************************************************************/ + +/************************/ +/*** ONE NEW POSITION ***/ +/************************/ + +/******************************/ +/* Coming from CuboidalRegion */ +/******************************/ +// CuboidalRegion -> CuboidalRegion +template +inline std::pair +get_pos_sid_pair( CuboidalRegion const& origin_structure, + CuboidalRegion const& target_structure, + typename Ttraits_::position_type const& old_pos, + typename Ttraits_::length_type const& offset, + typename Ttraits_::length_type const& reaction_length, + typename Ttraits_::rng_type &rng ) +{ + //typedef typename Ttraits_::structure_id_type structure_id_type; + //typedef typename Ttraits_::position_type position_type; + //typedef typename Ttraits_::length_type length_type; + + // Currently only species change and decay are supported + if(origin_structure.id() == target_structure.id()){ + + return std::make_pair( old_pos, origin_structure.id() ); + } + else // structure transition not allowed + + throw illegal_propagation_attempt("Origin structure must be equal to target structure for this type of structure transition (CuboidalRegion->CuboidalRegion)."); +}; + +// CuboidalRegion -> SphericalSurface +template +inline std::pair +get_pos_sid_pair( CuboidalRegion const& origin_structure, + SphericalSurface const& target_structure, + typename Ttraits_::position_type const& old_pos, + typename Ttraits_::length_type const& offset, + typename Ttraits_::length_type const& reaction_length, + typename Ttraits_::rng_type &rng ) +{ + typedef typename Ttraits_::structure_id_type structure_id_type; + typedef typename Ttraits_::position_type position_type; + //typedef typename Ttraits_::length_type length_type; + + /*** COMBINATION NOT SUPPORTED ***/ + throw illegal_propagation_attempt("Structure transition between combination of origin structure and target structure not supported (CuboidalRegion->Sphere)."); + + return std::make_pair(position_type(), structure_id_type()); +}; + +// CuboidalRegion -> CylindricalSurface +template +inline std::pair +get_pos_sid_pair( CuboidalRegion const& origin_structure, + CylindricalSurface const& target_structure, + typename Ttraits_::position_type const& old_pos, + typename Ttraits_::length_type const& offset, + typename Ttraits_::length_type const& reaction_length, + typename Ttraits_::rng_type &rng ) +{ + typedef typename Ttraits_::structure_id_type structure_id_type; + typedef typename Ttraits_::position_type position_type; + typedef typename Ttraits_::length_type length_type; + + structure_id_type new_id( target_structure.id() ); + position_type new_pos( target_structure.project_point(old_pos).first ); + length_type proj_dist( target_structure.project_point(old_pos).second.second ); + // the distance of the projection of old_pos on target_structure to target_structure + + if(proj_dist < 0){ // if projection of old_pos is in structure + + return std::make_pair( new_pos, new_id ); + } + else // structure transition not allowed + + throw illegal_propagation_attempt("Illegal original particle position for structure transition (CuboidalRegion->Cylinder)."); + +}; + +// CuboidalRegion -> DiskSurface +template +inline std::pair +get_pos_sid_pair( CuboidalRegion const& origin_structure, + DiskSurface const& target_structure, + typename Ttraits_::position_type const& old_pos, + typename Ttraits_::length_type const& offset, + typename Ttraits_::length_type const& reaction_length, + typename Ttraits_::rng_type &rng ) +{ + typedef typename Ttraits_::structure_id_type structure_id_type; + typedef typename Ttraits_::position_type position_type; + typedef typename Ttraits_::length_type length_type; + + structure_id_type new_id( target_structure.id() ); + position_type new_pos( target_structure.project_point(old_pos).first ); + length_type proj_dist( target_structure.project_point(old_pos).second.second ); + // the distance of the projection of old_pos on target_structure to target_structure + + if(proj_dist < 0){ // if projection of old_pos is in structure + + return std::make_pair( new_pos, new_id ); + } + else // structure transition not allowed + + throw illegal_propagation_attempt("Illegal original particle position for structure transition (CuboidalRegion->Disk)."); +}; + +// CuboidalRegion -> PlanarSurface +template +inline std::pair +get_pos_sid_pair( CuboidalRegion const& origin_structure, + PlanarSurface const& target_structure, + typename Ttraits_::position_type const& old_pos, + typename Ttraits_::length_type const& offset, + typename Ttraits_::length_type const& reaction_length, + typename Ttraits_::rng_type &rng ) +{ + typedef typename Ttraits_::structure_id_type structure_id_type; + typedef typename Ttraits_::position_type position_type; + typedef typename Ttraits_::length_type length_type; + + structure_id_type new_id( target_structure.id() ); + position_type new_pos( target_structure.project_point(old_pos).first ); + length_type proj_dist( target_structure.project_point(old_pos).second.second ); + // the distance of the projection of old_pos on target_structure to target_structure + + if(proj_dist < 0){ // if projection of old_pos is in structure + + return std::make_pair( new_pos, new_id ); + } + else // structure transition not allowed + + throw illegal_propagation_attempt("Illegal original particle position for structure transition (CuboidalRegion->Plane)."); +}; + +/********************************/ +/* Coming from SphericalSurface */ +/********************************/ +// SphericalSurface -> CuboidalRegion +template +inline std::pair +get_pos_sid_pair( SphericalSurface const& origin_structure, + CuboidalRegion const& target_structure, + typename Ttraits_::position_type const& old_pos, + typename Ttraits_::length_type const& offset, + typename Ttraits_::length_type const& reaction_length, + typename Ttraits_::rng_type &rng ) +{ + typedef typename Ttraits_::structure_id_type structure_id_type; + typedef typename Ttraits_::position_type position_type; + //typedef typename Ttraits_::length_type length_type; + + /*** COMBINATION NOT SUPPORTED ***/ + throw illegal_propagation_attempt("Structure transition between combination of origin structure and target structure not supported (Sphere->CuboidalRegion)."); + + return std::make_pair(position_type(), structure_id_type()); +}; + +// SphericalSurface -> SphericalSurface +template +inline std::pair +get_pos_sid_pair( SphericalSurface const& origin_structure, + SphericalSurface const& target_structure, + typename Ttraits_::position_type const& old_pos, + typename Ttraits_::length_type const& offset, + typename Ttraits_::length_type const& reaction_length, + typename Ttraits_::rng_type &rng ) +{ + typedef typename Ttraits_::structure_id_type structure_id_type; + typedef typename Ttraits_::position_type position_type; + //typedef typename Ttraits_::length_type length_type; + + /*** COMBINATION NOT SUPPORTED ***/ + throw illegal_propagation_attempt("Structure transition between combination of origin structure and target structure not supported (Sphere->Sphere)."); + + return std::make_pair(position_type(), structure_id_type()); +}; + +// SphericalSurface -> CylindricalSurface +template +inline std::pair +get_pos_sid_pair( SphericalSurface const& origin_structure, + CylindricalSurface const& target_structure, + typename Ttraits_::position_type const& old_pos, + typename Ttraits_::length_type const& offset, + typename Ttraits_::length_type const& reaction_length, + typename Ttraits_::rng_type &rng ) +{ + typedef typename Ttraits_::structure_id_type structure_id_type; + typedef typename Ttraits_::position_type position_type; + //typedef typename Ttraits_::length_type length_type; + + /*** COMBINATION NOT SUPPORTED ***/ + throw illegal_propagation_attempt("Structure transition between combination of origin structure and target structure not supported (Sphere->Cylinder)."); + + return std::make_pair(position_type(), structure_id_type()); +}; + +// SphericalSurface -> DiskSurface +template +inline std::pair +get_pos_sid_pair( SphericalSurface const& origin_structure, + DiskSurface const& target_structure, + typename Ttraits_::position_type const& old_pos, + typename Ttraits_::length_type const& offset, + typename Ttraits_::length_type const& reaction_length, + typename Ttraits_::rng_type &rng ) +{ + typedef typename Ttraits_::structure_id_type structure_id_type; + typedef typename Ttraits_::position_type position_type; + //typedef typename Ttraits_::length_type length_type; + + /*** COMBINATION NOT SUPPORTED ***/ + throw illegal_propagation_attempt("Structure transition between combination of origin structure and target structure not supported (Sphere->Disk)."); + + return std::make_pair(position_type(), structure_id_type()); +}; + +// SphericalSurface -> PlanarSurface +template +inline std::pair +get_pos_sid_pair( SphericalSurface const& origin_structure, + PlanarSurface const& target_structure, + typename Ttraits_::position_type const& old_pos, + typename Ttraits_::length_type const& offset, + typename Ttraits_::length_type const& reaction_length, + typename Ttraits_::rng_type &rng ) +{ + typedef typename Ttraits_::structure_id_type structure_id_type; + typedef typename Ttraits_::position_type position_type; + //typedef typename Ttraits_::length_type length_type; + + /*** COMBINATION NOT SUPPORTED ***/ + throw illegal_propagation_attempt("Structure transition between combination of origin structure and target structure not supported (Sphere->Plane)."); + + return std::make_pair(position_type(), structure_id_type()); +}; + +/**********************************/ +/* Coming from CylindricalSurface */ +/**********************************/ +// CylindricalSurface -> CuboidalRegion +template +inline std::pair +get_pos_sid_pair( CylindricalSurface const& origin_structure, + CuboidalRegion const& target_structure, + typename Ttraits_::position_type const& old_pos, + typename Ttraits_::length_type const& offset, + typename Ttraits_::length_type const& reaction_length, + typename Ttraits_::rng_type &rng ) +{ + //typedef typename Ttraits_::structure_id_type structure_id_type; + typedef typename Ttraits_::position_type position_type; + //typedef typename Ttraits_::length_type length_type; + + position_type displacement( origin_structure.surface_dissociation_vector(rng, offset, reaction_length) ); + position_type new_pos( add(old_pos, displacement) ); + // TODO assert that new_pos is in target_structure + + return std::make_pair(new_pos, target_structure.id()); +}; + +// CylindricalSurface -> CylindricalSurface +template +inline std::pair +get_pos_sid_pair( CylindricalSurface const& origin_structure, + CylindricalSurface const& target_structure, + typename Ttraits_::position_type const& old_pos, + typename Ttraits_::length_type const& offset, + typename Ttraits_::length_type const& reaction_length, + typename Ttraits_::rng_type &rng ) +{ + //typedef typename Ttraits_::structure_id_type structure_id_type; + //typedef typename Ttraits_::position_type position_type; + //typedef typename Ttraits_::length_type length_type; + + // Currently only species change and decay are supported + if(origin_structure.id() == target_structure.id()){ + + return std::make_pair( old_pos, origin_structure.id() ); + } + else // structure transition not allowed + + throw illegal_propagation_attempt("Origin structure must be equal to target structure for this type of structure transition (Cylinder->Cylinder)."); +}; + +// CylindricalSurface -> SphericalSurface +template +inline std::pair +get_pos_sid_pair( CylindricalSurface const& origin_structure, + SphericalSurface const& target_structure, + typename Ttraits_::position_type const& old_pos, + typename Ttraits_::length_type const& offset, + typename Ttraits_::length_type const& reaction_length, + typename Ttraits_::rng_type &rng ) +{ + typedef typename Ttraits_::structure_id_type structure_id_type; + typedef typename Ttraits_::position_type position_type; + //typedef typename Ttraits_::length_type length_type; + + /*** COMBINATION NOT SUPPORTED ***/ + throw illegal_propagation_attempt("Structure transition between combination of origin structure and target structure not supported (Cylinder->Sphere)."); + + return std::make_pair(position_type(), structure_id_type()); +}; + +// CylindricalSurface -> DiskSurface +template +inline std::pair +get_pos_sid_pair( CylindricalSurface const& origin_structure, + DiskSurface const& target_structure, + typename Ttraits_::position_type const& old_pos, + typename Ttraits_::length_type const& offset, + typename Ttraits_::length_type const& reaction_length, + typename Ttraits_::rng_type &rng ) +{ + typedef typename Ttraits_::structure_id_type structure_id_type; + typedef typename Ttraits_::position_type position_type; + typedef typename Ttraits_::length_type length_type; + + structure_id_type new_id( target_structure.id() ); + position_type new_pos( target_structure.project_point(old_pos).first ); + length_type proj_dist( target_structure.project_point(old_pos).second.second ); + // the distance of the projection of old_pos on target_structure to the boundary of target_structure + + // Note that this function also correctly handles the pair reaction between a particle on a cylinder + // and a particle on a disk. Since we assume that the product will end up on the disk, new_pos + // (which is the projection of the center of mass of both particles on the disk) will be the + // correct product position. + if(proj_dist < 0){ // if projection of old_pos is in structure + + return std::make_pair( new_pos, new_id ); + } + else // structure transition not allowed + + throw illegal_propagation_attempt("Illegal original particle position for structure transition (Cylinder->Disk)."); +}; + +// CylindricalSurface -> PlanarSurface +template +inline std::pair +get_pos_sid_pair( CylindricalSurface const& origin_structure, + PlanarSurface const& target_structure, + typename Ttraits_::position_type const& old_pos, + typename Ttraits_::length_type const& offset, + typename Ttraits_::length_type const& reaction_length, + typename Ttraits_::rng_type &rng ) +{ + typedef typename Ttraits_::structure_id_type structure_id_type; + typedef typename Ttraits_::position_type position_type; + typedef typename Ttraits_::length_type length_type; + + structure_id_type new_id( target_structure.id() ); + position_type proj_pos( target_structure.project_point(old_pos).first ); + length_type proj_dist( target_structure.project_point(old_pos).second.second ); + // the distance of the projection of old_pos on target_structure to the boundary of target_structure + + if(proj_dist < 0){ // if projection of old_pos is in structure + + // The old position projected on the plane is not yet the correct new position! + // We still have to displace the particle by the cylinder (!) radius at a random angle + // The latter is generated in the same way as for regular cylinder unbinding + position_type displacement( origin_structure.surface_dissociation_vector(rng, offset, reaction_length) ); + position_type new_pos( add(proj_pos, displacement) ); + + return std::make_pair( new_pos, new_id ); + } + else // structure transition not allowed + + throw illegal_propagation_attempt("Illegal original particle position for structure transition (Cylinder->Plane)."); + + // TODO Does that also handle correctly the interaction of a rod particle with a particle located on the plane? + // (whether this is relevant for now is the other question...) +}; + +/***************************/ +/* Coming from DiskSurface */ +/***************************/ +// DiskSurface -> CuboidalRegion +template +inline std::pair +get_pos_sid_pair( DiskSurface const& origin_structure, + CuboidalRegion const& target_structure, + typename Ttraits_::position_type const& old_pos, + typename Ttraits_::length_type const& offset, + typename Ttraits_::length_type const& reaction_length, + typename Ttraits_::rng_type &rng ) +{ + //typedef typename Ttraits_::structure_id_type structure_id_type; + typedef typename Ttraits_::position_type position_type; + //typedef typename Ttraits_::length_type length_type; + + // Here offset should be the radius of the product species + + position_type displacement( origin_structure.surface_dissociation_vector(rng, offset, reaction_length) ); + position_type new_pos( add(old_pos, displacement) ); + // TODO assert that new_pos is in target_structure + + return std::make_pair(new_pos, target_structure.id()); +}; + +// DiskSurface -> SphericalSurface +template +inline std::pair +get_pos_sid_pair( DiskSurface const& origin_structure, + SphericalSurface const& target_structure, + typename Ttraits_::position_type const& old_pos, + typename Ttraits_::length_type const& offset, + typename Ttraits_::length_type const& reaction_length, + typename Ttraits_::rng_type &rng ) +{ + typedef typename Ttraits_::structure_id_type structure_id_type; + typedef typename Ttraits_::position_type position_type; + //typedef typename Ttraits_::length_type length_type; + + /*** COMBINATION NOT SUPPORTED ***/ + throw illegal_propagation_attempt("Structure transition between combination of origin structure and target structure not supported (Disk->Sphere)."); + + return std::make_pair(position_type(), structure_id_type()); +}; + +// DiskSurface -> CylindricalSurface +template +inline std::pair +get_pos_sid_pair( DiskSurface const& origin_structure, + CylindricalSurface const& target_structure, + typename Ttraits_::position_type const& old_pos, + typename Ttraits_::length_type const& offset, + typename Ttraits_::length_type const& reaction_length, + typename Ttraits_::rng_type &rng ) +{ + //typedef typename Ttraits_::structure_id_type structure_id_type; + typedef typename Ttraits_::position_type position_type; + //typedef typename Ttraits_::length_type length_type; + + position_type u( origin_structure.surface_dissociation_unit_vector(rng) ); + + // When a particle is dissociation from a disk to the cylinder, the direction is randomized + Real r( rng.uniform(0.,1.) ); + Real r_rl( rng.uniform(0.,reaction_length) ); + Real r_displace( r < 0.5 ? -1.0*r_rl : +1.0*r_rl ); + + position_type new_pos( add(old_pos, multiply(u, r_displace)) ); + // TODO assert that new_pos is in target_structure + + return std::make_pair(new_pos, target_structure.id()); +}; + +// DiskSurface -> DiskSurface +template +inline std::pair +get_pos_sid_pair( DiskSurface const& origin_structure, + DiskSurface const& target_structure, + typename Ttraits_::position_type const& old_pos, + typename Ttraits_::length_type const& offset, + typename Ttraits_::length_type const& reaction_length, + typename Ttraits_::rng_type &rng ) +{ + //typedef typename Ttraits_::structure_id_type structure_id_type; + //typedef typename Ttraits_::position_type position_type; + //typedef typename Ttraits_::length_type length_type; + + // Currently only species change and decay are supported + if(origin_structure.id() == target_structure.id()){ + + return std::make_pair( old_pos, origin_structure.id() ); + } + else // structure transition not allowed + + throw illegal_propagation_attempt("Origin structure must be equal to target structure for this type of structure transition (Disk->Disk)."); +}; + +// DiskSurface -> PlanarSurface +template +inline std::pair +get_pos_sid_pair( DiskSurface const& origin_structure, + PlanarSurface const& target_structure, + typename Ttraits_::position_type const& old_pos, + typename Ttraits_::length_type const& offset, + typename Ttraits_::length_type const& reaction_length, + typename Ttraits_::rng_type &rng ) +{ + //typedef typename Ttraits_::structure_id_type structure_id_type; + typedef typename Ttraits_::position_type position_type; + //typedef typename Ttraits_::length_type length_type; + + // Treated in the same way as unbinding from disk to bulk (see above) + // Here offset should be the radius of the product species + + position_type displacement( origin_structure.surface_dissociation_vector(rng, offset, reaction_length) ); + position_type new_pos( add(old_pos, displacement) ); + // TODO assert that new_pos is in target_structure + + return std::make_pair(new_pos, target_structure.id()); +}; + +/*****************************/ +/* Coming from PlanarSurface */ +/*****************************/ +// PlanarSurface -> CuboidalRegion +template +inline std::pair +get_pos_sid_pair( PlanarSurface const& origin_structure, + CuboidalRegion const& target_structure, + typename Ttraits_::position_type const& old_pos, + typename Ttraits_::length_type const& offset, + typename Ttraits_::length_type const& reaction_length, + typename Ttraits_::rng_type &rng ) +{ + //typedef typename Ttraits_::structure_id_type structure_id_type; + typedef typename Ttraits_::position_type position_type; + //typedef typename Ttraits_::length_type length_type; + + position_type displacement( origin_structure.surface_dissociation_vector(rng, offset, reaction_length) ); + position_type new_pos( add(old_pos, displacement) ); + // TODO assert that new_pos is in target_structure + + return std::make_pair(new_pos, target_structure.id()); +}; + +// PlanarSurface -> SphericalSurface +template +inline std::pair +get_pos_sid_pair( PlanarSurface const& origin_structure, + SphericalSurface const& target_structure, + typename Ttraits_::position_type const& old_pos, + typename Ttraits_::length_type const& offset, + typename Ttraits_::length_type const& reaction_length, + typename Ttraits_::rng_type &rng ) +{ + typedef typename Ttraits_::structure_id_type structure_id_type; + typedef typename Ttraits_::position_type position_type; + //typedef typename Ttraits_::length_type length_type; + + /*** COMBINATION NOT SUPPORTED ***/ + throw illegal_propagation_attempt("Structure transition between combination of origin structure and target structure not supported (Plane->Sphere)."); + + return std::make_pair(position_type(), structure_id_type()); +}; + +// PlanarSurface -> CylindricalSurface +template +inline std::pair +get_pos_sid_pair( PlanarSurface const& origin_structure, + CylindricalSurface const& target_structure, + typename Ttraits_::position_type const& old_pos, + typename Ttraits_::length_type const& offset, + typename Ttraits_::length_type const& reaction_length, + typename Ttraits_::rng_type &rng ) +{ + typedef typename Ttraits_::structure_id_type structure_id_type; + typedef typename Ttraits_::position_type position_type; + typedef typename Ttraits_::length_type length_type; + + // This is treated in the same way as the case CuboidalRegion->Cylinder + structure_id_type new_id( target_structure.id() ); + position_type new_pos( target_structure.project_point(old_pos).first ); + length_type proj_dist( target_structure.project_point(old_pos).second.second ); + // the distance of the projection of old_pos on target_structure to target_structure + + if(proj_dist < 0){ // if projection of old_pos is in structure + + return std::make_pair( new_pos, new_id ); + } + else // structure transition not allowed + + throw illegal_propagation_attempt("Illegal original particle position for structure transition (Plane->Cylinder)."); + + /*** COMBINATION NOT SUPPORTED + throw illegal_propagation_attempt("Structure transition between combination of origin structure and target structure not supported (Plane->Cylinder)."); + ***/ + + return std::make_pair(position_type(), structure_id_type()); +}; + +// PlanarSurface -> DiskSurface +template +inline std::pair +get_pos_sid_pair( PlanarSurface const& origin_structure, + DiskSurface const& target_structure, + typename Ttraits_::position_type const& old_pos, + typename Ttraits_::length_type const& offset, + typename Ttraits_::length_type const& reaction_length, + typename Ttraits_::rng_type &rng ) +{ + typedef typename Ttraits_::structure_id_type structure_id_type; + typedef typename Ttraits_::position_type position_type; + typedef typename Ttraits_::length_type length_type; + + structure_id_type new_id( target_structure.id() ); + position_type new_pos( target_structure.project_point(old_pos).first ); + length_type proj_dist( target_structure.project_point(old_pos).second.second ); + // the distance of the projection of old_pos on target_structure to the boundary of target_structure + + if(proj_dist < 0){ // if projection of old_pos is in structure + // Note: Disk.project_point also returns a neg. value if the position is in one plane with the disk + + return std::make_pair( new_pos, new_id ); + } + else // structure transition not allowed + + throw illegal_propagation_attempt("Illegal original particle position for structure transition (Plane->Disk)."); + + return std::make_pair(position_type(), structure_id_type()); +}; + +// PlanarSurface -> PlanarSurface +template +inline std::pair +get_pos_sid_pair( PlanarSurface const& origin_structure, + PlanarSurface const& target_structure, + typename Ttraits_::position_type const& old_pos, + typename Ttraits_::length_type const& typical_length, + typename Ttraits_::length_type const& reaction_length, + typename Ttraits_::rng_type &rng ) +{ + //typedef typename Ttraits_::structure_id_type structure_id_type; + typedef typename Ttraits_::position_type position_type; + typedef typename Ttraits_::length_type length_type; + typedef std::pair length_pair_type; + typedef std::pair projected_type; + + // Species change and decay: no structure change + if( origin_structure.id() == target_structure.id() ) + { + return std::make_pair( old_pos, origin_structure.id() ); + } + // Function is called with two different structure of the same type: Pair reaction "over the edge" + else if( origin_structure.sid() == target_structure.sid() ) + { + // In this case old_pos = CoM of the two reactants, + // origin_structure = the origin structure of the first reactant, + // target_structure = the origin structure of the second reactant. + // We assume here that the problem has been correctly transformed + // into the structure passed as target_structure to this function + // before; however, to be sure, we check again and return the + // ID of the structure that the CoM (old_pos) lies in. + // As a standard, this should be target_structure. + + // Calculate the projections of the CoM (old_pos) into both involved planes + // This will also give the normal components of old_pos in the CS of the planes + projected_type proj_o( origin_structure.project_point(old_pos) ); + projected_type proj_t( target_structure.project_point(old_pos) ); + + // Check in which plane old_pos lies by comparing the normal components of the projection + // Note: pair entries proj_X.second.first are the normal components + // Then return the right position-structure_id pair; for safety we return the projected CoM + // Note: apply_boundary will modify the product position accordingly if it lies outside of the (bounded) plane + assert(typical_length > 0.0); // typical_length should contain the particle radius if this function was called correctly + if( feq(proj_t.second.first, 0.0, typical_length) ) + // CoM is in target_structure + return std::make_pair( proj_t.first, target_structure.id() ); + + else if( feq(proj_o.second.first, 0.0, typical_length) ) + // CoM is in origin_structure + return std::make_pair( proj_o.first, origin_structure.id() ); + else + // CoM is in neither plane; something is terribly wrong... + throw illegal_propagation_attempt("Center of mass is not in plane of either origin_structure or target_structure in pair reaction involving two PlanarSurfaces."); + } + else // structure transition not allowed + + throw illegal_propagation_attempt("Origin structure type must be equal to target structure type for this type of structure transition (Plane->Plane)."); +}; + + +/******************************************************************************************/ + + +/*************************/ +/*** TWO NEW POSITIONS ***/ +/*************************/ + +/******************************/ +/* Coming from CuboidalRegion */ +/******************************/ +// CuboidalRegion -> CuboidalRegion +template +inline std::pair< std::pair, + std::pair > +get_pos_sid_pair_pair( CuboidalRegion const& origin_structure, + CuboidalRegion const& target_structure, + typename Ttraits_::position_type const& old_pos, + typename Ttraits_::species_type const& s_orig, + typename Ttraits_::species_type const& s_targ, + typename Ttraits_::length_type const& reaction_length, + typename Ttraits_::rng_type &rng ) +{ + typedef typename Ttraits_::structure_id_type structure_id_type; + typedef typename Ttraits_::position_type position_type; + //typedef typename Ttraits_::length_type length_type; + + // Currently we do not allow for pair forward/backward reactions between two different cubes + if(origin_structure.id() == target_structure.id()){ + + structure_id_type new_sid( origin_structure.id() ); + std::pair new_positions( origin_structure.geminate_dissociation_positions(rng, s_orig, s_targ, old_pos, reaction_length) ); + // geminate_dissociation_positions will produce two new positions close to old_pos taking into account + // the type of origin_structure and the properties of the two product species + // (the displacements from old_pos are weighted by the diffusion constants) + + return std::make_pair( std::make_pair(new_positions.first, new_sid), + std::make_pair(new_positions.second, new_sid) ); + } + else // structure transition not allowed + + throw illegal_propagation_attempt("Origin structure must be equal to target structure for this type of structure transition (CuboidalRegion->CuboidalRegion/CuboidalRegion)."); +}; + +// CuboidalRegion -> SphericalSurface +template +inline std::pair< std::pair, + std::pair > +get_pos_sid_pair_pair( CuboidalRegion const& origin_structure, + SphericalSurface const& target_structure, + typename Ttraits_::position_type const& old_pos, + typename Ttraits_::species_type const& s_orig, + typename Ttraits_::species_type const& s_targ, + typename Ttraits_::length_type const& reaction_length, + typename Ttraits_::rng_type &rng ) +{ + typedef typename Ttraits_::structure_id_type structure_id_type; + typedef typename Ttraits_::position_type position_type; + //typedef typename Ttraits_::length_type length_type; + + /*** COMBINATION NOT SUPPORTED ***/ + throw illegal_propagation_attempt("Structure transition between combination of origin structure and target structure not supported (CuboidalRegion->CuboidalRegion/Sphere)."); + + return std::make_pair( std::make_pair(position_type(), structure_id_type()), + std::make_pair(position_type(), structure_id_type()) ); +}; + +// CuboidalRegion -> CylindricalSurface +template +inline std::pair< std::pair, + std::pair > +get_pos_sid_pair_pair( CuboidalRegion const& origin_structure, + CylindricalSurface const& target_structure, + typename Ttraits_::position_type const& old_pos, + typename Ttraits_::species_type const& s_orig, + typename Ttraits_::species_type const& s_targ, + typename Ttraits_::length_type const& reaction_length, + typename Ttraits_::rng_type &rng ) +{ + typedef typename Ttraits_::structure_id_type structure_id_type; + typedef typename Ttraits_::position_type position_type; + //typedef typename Ttraits_::length_type length_type; + + /*** COMBINATION NOT SUPPORTED ***/ + throw illegal_propagation_attempt("Structure transition between combination of origin structure and target structure not supported (CuboidalRegion->CuboidalRegion/Cylinder)."); + + return std::make_pair( std::make_pair(position_type(), structure_id_type()), + std::make_pair(position_type(), structure_id_type()) ); + +}; + +// CuboidalRegion -> DiskSurface +template +inline std::pair< std::pair, + std::pair > +get_pos_sid_pair_pair( CuboidalRegion const& origin_structure, + DiskSurface const& target_structure, + typename Ttraits_::position_type const& old_pos, + typename Ttraits_::species_type const& s_orig, + typename Ttraits_::species_type const& s_targ, + typename Ttraits_::length_type const& reaction_length, + typename Ttraits_::rng_type &rng ) +{ + typedef typename Ttraits_::structure_id_type structure_id_type; + typedef typename Ttraits_::position_type position_type; + //typedef typename Ttraits_::length_type length_type; + + /*** COMBINATION NOT SUPPORTED ***/ + throw illegal_propagation_attempt("Structure transition between combination of origin structure and target structure not supported (CuboidalRegion->CuboidalRegion/Disk)."); + + return std::make_pair( std::make_pair(position_type(), structure_id_type()), + std::make_pair(position_type(), structure_id_type()) ); +}; + +// CuboidalRegion -> PlanarSurface +template +inline std::pair< std::pair, + std::pair > +get_pos_sid_pair_pair( CuboidalRegion const& origin_structure, + PlanarSurface const& target_structure, + typename Ttraits_::position_type const& old_pos, + typename Ttraits_::species_type const& s_orig, + typename Ttraits_::species_type const& s_targ, + typename Ttraits_::length_type const& reaction_length, + typename Ttraits_::rng_type &rng ) +{ + typedef typename Ttraits_::structure_id_type structure_id_type; + typedef typename Ttraits_::position_type position_type; + //typedef typename Ttraits_::length_type length_type; + + /*** COMBINATION NOT SUPPORTED ***/ + throw illegal_propagation_attempt("Structure transition between combination of origin structure and target structure not supported (CuboidalRegion->CuboidalRegion/Plane)."); + + return std::make_pair( std::make_pair(position_type(), structure_id_type()), + std::make_pair(position_type(), structure_id_type()) ); +}; + +/********************************/ +/* Coming from SphericalSurface */ +/********************************/ +// SphericalSurface -> CuboidalRegion +template +inline std::pair< std::pair, + std::pair > +get_pos_sid_pair_pair( SphericalSurface const& origin_structure, + CuboidalRegion const& target_structure, + typename Ttraits_::position_type const& old_pos, + typename Ttraits_::species_type const& s_orig, + typename Ttraits_::species_type const& s_targ, + typename Ttraits_::length_type const& reaction_length, + typename Ttraits_::rng_type &rng ) +{ + typedef typename Ttraits_::structure_id_type structure_id_type; + typedef typename Ttraits_::position_type position_type; + //typedef typename Ttraits_::length_type length_type; + + /*** COMBINATION NOT SUPPORTED ***/ + throw illegal_propagation_attempt("Structure transition between combination of origin structure and target structure not supported (Sphere->Sphere/CuboidalRegion)."); + + return std::make_pair( std::make_pair(position_type(), structure_id_type()), + std::make_pair(position_type(), structure_id_type()) ); +}; + +// SphericalSurface -> SphericalSurface +template +inline std::pair< std::pair, + std::pair > +get_pos_sid_pair_pair( SphericalSurface const& origin_structure, + SphericalSurface const& target_structure, + typename Ttraits_::position_type const& old_pos, + typename Ttraits_::species_type const& s_orig, + typename Ttraits_::species_type const& s_targ, + typename Ttraits_::length_type const& reaction_length, + typename Ttraits_::rng_type &rng ) +{ + typedef typename Ttraits_::structure_id_type structure_id_type; + typedef typename Ttraits_::position_type position_type; + //typedef typename Ttraits_::length_type length_type; + + /*** COMBINATION NOT SUPPORTED ***/ + throw illegal_propagation_attempt("Structure transition between combination of origin structure and target structure not supported (Sphere->Sphere/Sphere)."); + + return std::make_pair( std::make_pair(position_type(), structure_id_type()), + std::make_pair(position_type(), structure_id_type()) ); +}; + +// SphericalSurface -> CylindricalSurface +template +inline std::pair< std::pair, + std::pair > +get_pos_sid_pair_pair( SphericalSurface const& origin_structure, + CylindricalSurface const& target_structure, + typename Ttraits_::position_type const& old_pos, + typename Ttraits_::species_type const& s_orig, + typename Ttraits_::species_type const& s_targ, + typename Ttraits_::length_type const& reaction_length, + typename Ttraits_::rng_type &rng ) +{ + typedef typename Ttraits_::structure_id_type structure_id_type; + typedef typename Ttraits_::position_type position_type; + //typedef typename Ttraits_::length_type length_type; + + /*** COMBINATION NOT SUPPORTED ***/ + throw illegal_propagation_attempt("Structure transition between combination of origin structure and target structure not supported (Sphere->Sphere/Cylinder)."); + + return std::make_pair( std::make_pair(position_type(), structure_id_type()), + std::make_pair(position_type(), structure_id_type()) ); +}; + +// SphericalSurface -> DiskSurface +template +inline std::pair< std::pair, + std::pair > +get_pos_sid_pair_pair( SphericalSurface const& origin_structure, + DiskSurface const& target_structure, + typename Ttraits_::position_type const& old_pos, + typename Ttraits_::species_type const& s_orig, + typename Ttraits_::species_type const& s_targ, + typename Ttraits_::length_type const& reaction_length, + typename Ttraits_::rng_type &rng ) +{ + typedef typename Ttraits_::structure_id_type structure_id_type; + typedef typename Ttraits_::position_type position_type; + //typedef typename Ttraits_::length_type length_type; + + /*** COMBINATION NOT SUPPORTED ***/ + throw illegal_propagation_attempt("Structure transition between combination of origin structure and target structure not supported (Sphere->Sphere/Disk)."); + + return std::make_pair( std::make_pair(position_type(), structure_id_type()), + std::make_pair(position_type(), structure_id_type()) ); +}; + +// SphericalSurface -> PlanarSurface +template +inline std::pair< std::pair, + std::pair > +get_pos_sid_pair_pair( SphericalSurface const& origin_structure, + PlanarSurface const& target_structure, + typename Ttraits_::position_type const& old_pos, + typename Ttraits_::species_type const& s_orig, + typename Ttraits_::species_type const& s_targ, + typename Ttraits_::length_type const& reaction_length, + typename Ttraits_::rng_type &rng ) +{ + typedef typename Ttraits_::structure_id_type structure_id_type; + typedef typename Ttraits_::position_type position_type; + //typedef typename Ttraits_::length_type length_type; + + /*** COMBINATION NOT SUPPORTED ***/ + throw illegal_propagation_attempt("Structure transition between combination of origin structure and target structure not supported (Sphere->Sphere/Plane)."); + + return std::make_pair( std::make_pair(position_type(), structure_id_type()), + std::make_pair(position_type(), structure_id_type()) ); +}; + +/**********************************/ +/* Coming from CylindricalSurface */ +/**********************************/ +// CylindricalSurface -> CuboidalRegion +template +inline std::pair< std::pair, + std::pair > +get_pos_sid_pair_pair( CylindricalSurface const& origin_structure, + CuboidalRegion const& target_structure, + typename Ttraits_::position_type const& old_pos, + typename Ttraits_::species_type const& s_orig, + typename Ttraits_::species_type const& s_targ, + typename Ttraits_::length_type const& reaction_length, + typename Ttraits_::rng_type &rng ) +{ + //typedef typename Ttraits_::structure_id_type structure_id_type; + typedef typename Ttraits_::position_type position_type; + //typedef typename Ttraits_::length_type length_type; + + std::pair new_positions( origin_structure.special_geminate_dissociation_positions(rng, s_orig, s_targ, old_pos, reaction_length) ); + // special_geminate_dissociation_positions will produce two new positions close to old_pos taking into account + // the types of origin_structure and target_structure and the properties of the two product species + // (the displacements from old_pos are weighted by the diffusion constants); + // the first pair entry is the particle staying on the surface, the second entry the particle + // that goes into the bulk + + return std::make_pair( std::make_pair(new_positions.first, origin_structure.id()), + std::make_pair(new_positions.second, target_structure.id()) ); +}; + +// CylindricalSurface -> CylindricalSurface +template +inline std::pair< std::pair, + std::pair > +get_pos_sid_pair_pair( CylindricalSurface const& origin_structure, + CylindricalSurface const& target_structure, + typename Ttraits_::position_type const& old_pos, + typename Ttraits_::species_type const& s_orig, + typename Ttraits_::species_type const& s_targ, + typename Ttraits_::length_type const& reaction_length, + typename Ttraits_::rng_type &rng ) +{ + typedef typename Ttraits_::structure_id_type structure_id_type; + typedef typename Ttraits_::position_type position_type; + //typedef typename Ttraits_::length_type length_type; + + // Currently we do not allow for pair forward/backward reactions between two different cylinders + if(origin_structure.id() == target_structure.id()){ + + structure_id_type new_sid( origin_structure.id() ); + std::pair new_positions( origin_structure.geminate_dissociation_positions(rng, s_orig, s_targ, old_pos, reaction_length) ); + // geminate_dissociation_positions will produce two new positions close to old_pos taking into account + // the type of origin_structure and the properties of the two product species + // (the displacements from old_pos are weighted by the diffusion constants) + + return std::make_pair( std::make_pair(new_positions.first, new_sid), + std::make_pair(new_positions.second, new_sid) ); + } + else // structure transition not allowed + + throw illegal_propagation_attempt("Origin structure must be equal to target structure for this type of structure transition (Cylinder->Cylinder/Cylinder)."); +}; + +// CylindricalSurface -> SphericalSurface +template +inline std::pair< std::pair, + std::pair > +get_pos_sid_pair_pair( CylindricalSurface const& origin_structure, + SphericalSurface const& target_structure, + typename Ttraits_::position_type const& old_pos, + typename Ttraits_::species_type const& s_orig, + typename Ttraits_::species_type const& s_targ, + typename Ttraits_::length_type const& reaction_length, + typename Ttraits_::rng_type &rng ) +{ + typedef typename Ttraits_::structure_id_type structure_id_type; + typedef typename Ttraits_::position_type position_type; + //typedef typename Ttraits_::length_type length_type; + + /*** COMBINATION NOT SUPPORTED ***/ + throw illegal_propagation_attempt("Structure transition between combination of origin structure and target structure not supported (Cylinder->Cylinder/Sphere)."); + + return std::make_pair( std::make_pair(position_type(), structure_id_type()), + std::make_pair(position_type(), structure_id_type()) ); +}; + +// CylindricalSurface -> DiskSurface +template +inline std::pair< std::pair, + std::pair > +get_pos_sid_pair_pair( CylindricalSurface const& origin_structure, + DiskSurface const& target_structure, + typename Ttraits_::position_type const& old_pos, + typename Ttraits_::species_type const& s_orig, + typename Ttraits_::species_type const& s_targ, + typename Ttraits_::length_type const& reaction_length, + typename Ttraits_::rng_type &rng ) +{ + typedef typename Ttraits_::structure_id_type structure_id_type; + typedef typename Ttraits_::position_type position_type; + //typedef typename Ttraits_::length_type length_type; + + /*** COMBINATION NOT SUPPORTED ***/ + throw illegal_propagation_attempt("Structure transition between combination of origin structure and target structure not supported (Cylinder->Cylinder/Disk)."); + + return std::make_pair( std::make_pair(position_type(), structure_id_type()), + std::make_pair(position_type(), structure_id_type()) ); +}; + +// CylindricalSurface -> PlanarSurface +template +inline std::pair< std::pair, + std::pair > +get_pos_sid_pair_pair( CylindricalSurface const& origin_structure, + PlanarSurface const& target_structure, + typename Ttraits_::position_type const& old_pos, + typename Ttraits_::species_type const& s_orig, + typename Ttraits_::species_type const& s_targ, + typename Ttraits_::length_type const& reaction_length, + typename Ttraits_::rng_type &rng ) +{ + typedef typename Ttraits_::structure_id_type structure_id_type; + typedef typename Ttraits_::position_type position_type; + //typedef typename Ttraits_::length_type length_type; + + /*** COMBINATION NOT SUPPORTED ***/ + throw illegal_propagation_attempt("Structure transition between combination of origin structure and target structure not supported (Cylinder->Cylinder/Plane)."); + + return std::make_pair( std::make_pair(position_type(), structure_id_type()), + std::make_pair(position_type(), structure_id_type()) ); +}; + +/***************************/ +/* Coming from DiskSurface */ +/***************************/ +// DiskSurface -> CuboidalRegion +template +inline std::pair< std::pair, + std::pair > +get_pos_sid_pair_pair( DiskSurface const& origin_structure, + CuboidalRegion const& target_structure, + typename Ttraits_::position_type const& old_pos, + typename Ttraits_::species_type const& s_orig, + typename Ttraits_::species_type const& s_targ, + typename Ttraits_::length_type const& reaction_length, + typename Ttraits_::rng_type &rng ) +{ + //typedef typename Ttraits_::structure_id_type structure_id_type; + typedef typename Ttraits_::position_type position_type; + //typedef typename Ttraits_::length_type length_type; + + std::pair new_positions( origin_structure.special_geminate_dissociation_positions(rng, s_orig, s_targ, old_pos, reaction_length) ); + // special_geminate_dissociation_positions will produce two new positions close to old_pos taking into account + // the types of origin_structure and target_structure and the properties of the two product species + // (the displacements from old_pos are weighted by the diffusion constants); + // the first pair entry is the particle staying on the surface, the second entry the particle + // that goes into the bulk + + return std::make_pair( std::make_pair(new_positions.first, origin_structure.id()), + std::make_pair(new_positions.second, target_structure.id()) ); +}; + +// DiskSurface -> SphericalSurface +template +inline std::pair< std::pair, + std::pair > +get_pos_sid_pair_pair( DiskSurface const& origin_structure, + SphericalSurface const& target_structure, + typename Ttraits_::position_type const& old_pos, + typename Ttraits_::species_type const& s_orig, + typename Ttraits_::species_type const& s_targ, + typename Ttraits_::length_type const& reaction_length, + typename Ttraits_::rng_type &rng ) +{ + typedef typename Ttraits_::structure_id_type structure_id_type; + typedef typename Ttraits_::position_type position_type; + //typedef typename Ttraits_::length_type length_type; + + /*** COMBINATION NOT SUPPORTED ***/ + throw illegal_propagation_attempt("Structure transition between combination of origin structure and target structure not supported (Disk->Disk/Sphere)."); + + return std::make_pair( std::make_pair(position_type(), structure_id_type()), + std::make_pair(position_type(), structure_id_type()) ); +}; + +// DiskSurface -> CylindricalSurface +template +inline std::pair< std::pair, + std::pair > +get_pos_sid_pair_pair( DiskSurface const& origin_structure, + CylindricalSurface const& target_structure, + typename Ttraits_::position_type const& old_pos, + typename Ttraits_::species_type const& s_orig, + typename Ttraits_::species_type const& s_targ, + typename Ttraits_::length_type const& reaction_length, + typename Ttraits_::rng_type &rng ) +{ + typedef typename Ttraits_::structure_id_type structure_id_type; + typedef typename Ttraits_::position_type position_type; + //typedef typename Ttraits_::length_type length_type; + + // TODO TODO TODO + // IMPLEMENT THE UNBINDING FROM THE DISK ACTING AS A SINK! + + /*** COMBINATION NOT SUPPORTED ***/ + throw illegal_propagation_attempt("Structure transition between combination of origin structure and target structure not supported (Disk->Disk/Cylinder)."); + + return std::make_pair( std::make_pair(position_type(), structure_id_type()), + std::make_pair(position_type(), structure_id_type()) ); +}; + +// DiskSurface -> DiskSurface +template +inline std::pair< std::pair, + std::pair > +get_pos_sid_pair_pair( DiskSurface const& origin_structure, + DiskSurface const& target_structure, + typename Ttraits_::position_type const& old_pos, + typename Ttraits_::species_type const& s_orig, + typename Ttraits_::species_type const& s_targ, + typename Ttraits_::length_type const& reaction_length, + typename Ttraits_::rng_type &rng ) +{ + typedef typename Ttraits_::structure_id_type structure_id_type; + typedef typename Ttraits_::position_type position_type; + //typedef typename Ttraits_::length_type length_type; + + /*** COMBINATION NOT SUPPORTED ***/ + throw illegal_propagation_attempt("Structure transition between combination of origin structure and target structure not supported (Disk->Disk/Disk)."); + + return std::make_pair( std::make_pair(position_type(), structure_id_type()), + std::make_pair(position_type(), structure_id_type()) ); +}; + +// DiskSurface -> PlanarSurface +template +inline std::pair< std::pair, + std::pair > +get_pos_sid_pair_pair( DiskSurface const& origin_structure, + PlanarSurface const& target_structure, + typename Ttraits_::position_type const& old_pos, + typename Ttraits_::species_type const& s_orig, + typename Ttraits_::species_type const& s_targ, + typename Ttraits_::length_type const& reaction_length, + typename Ttraits_::rng_type &rng ) +{ + //typedef typename Ttraits_::structure_id_type structure_id_type; + typedef typename Ttraits_::position_type position_type; + //typedef typename Ttraits_::length_type length_type; + + std::pair new_positions( origin_structure.special_geminate_dissociation_positions(rng, s_orig, s_targ, old_pos, reaction_length) ); + // special_geminate_dissociation_positions will produce two new positions close to old_pos taking into account + // the types of origin_structure and target_structure and the properties of the two product species + // (the displacements from old_pos are weighted by the diffusion constants). + // Here this works in the same way as for the corresponding function for Disk->Disk/CuboidalRegion; + // the first pair entry is the particle staying on the surface, the second entry the particle + // that goes onto the plane + + return std::make_pair( std::make_pair(new_positions.first, origin_structure.id()), + std::make_pair(new_positions.second, target_structure.id()) ); +}; + +/*****************************/ +/* Coming from PlanarSurface */ +/*****************************/ +// PlanarSurface -> CuboidalRegion +template +inline std::pair< std::pair, + std::pair > +get_pos_sid_pair_pair( PlanarSurface const& origin_structure, + CuboidalRegion const& target_structure, + typename Ttraits_::position_type const& old_pos, + typename Ttraits_::species_type const& s_orig, + typename Ttraits_::species_type const& s_targ, + typename Ttraits_::length_type const& reaction_length, + typename Ttraits_::rng_type &rng ) +{ + //typedef typename Ttraits_::structure_id_type structure_id_type; + typedef typename Ttraits_::position_type position_type; + typedef typename Ttraits_::length_type length_type; + + std::pair new_positions; + length_type dist_to_edge_1; + length_type dist_to_edge_2; + + bool positions_legal(false); + + do{ + // Produce two new positions + new_positions = origin_structure.special_geminate_dissociation_positions(rng, s_orig, s_targ, old_pos, reaction_length); + // special_geminate_dissociation_positions will produce two new positions close to old_pos taking into account + // the types of origin_structure and target_structure and the properties of the two product species + // (the displacements from old_pos are weighted by the diffusion constants); + // the first pair entry is the particle staying on the surface, the second entry the particle + // that goes into the bulk + + // Check whether the newly produced positions are above the origin plane + dist_to_edge_1 = origin_structure.project_point(new_positions.first).second.second; + dist_to_edge_2 = origin_structure.project_point(new_positions.second).second.second; + + positions_legal = (dist_to_edge_1 < 0.0) and (dist_to_edge_2 < 0.0); + } + while( not(positions_legal) ); + + return std::make_pair( std::make_pair(new_positions.first, origin_structure.id()), + std::make_pair(new_positions.second, target_structure.id()) ); +}; + +// PlanarSurface -> SphericalSurface +template +inline std::pair< std::pair, + std::pair > +get_pos_sid_pair_pair( PlanarSurface const& origin_structure, + SphericalSurface const& target_structure, + typename Ttraits_::position_type const& old_pos, + typename Ttraits_::species_type const& s_orig, + typename Ttraits_::species_type const& s_targ, + typename Ttraits_::length_type const& reaction_length, + typename Ttraits_::rng_type &rng ) +{ + typedef typename Ttraits_::structure_id_type structure_id_type; + typedef typename Ttraits_::position_type position_type; + //typedef typename Ttraits_::length_type length_type; + + /*** COMBINATION NOT SUPPORTED ***/ + throw illegal_propagation_attempt("Structure transition between combination of origin structure and target structure not supported (Plane->Plane/Sphere)."); + + return std::make_pair( std::make_pair(position_type(), structure_id_type()), + std::make_pair(position_type(), structure_id_type()) ); +}; + +// PlanarSurface -> CylindricalSurface +template +inline std::pair< std::pair, + std::pair > +get_pos_sid_pair_pair( PlanarSurface const& origin_structure, + CylindricalSurface const& target_structure, + typename Ttraits_::position_type const& old_pos, + typename Ttraits_::species_type const& s_orig, + typename Ttraits_::species_type const& s_targ, + typename Ttraits_::length_type const& reaction_length, + typename Ttraits_::rng_type &rng ) +{ + typedef typename Ttraits_::structure_id_type structure_id_type; + typedef typename Ttraits_::position_type position_type; + //typedef typename Ttraits_::length_type length_type; + + /*** COMBINATION NOT SUPPORTED ***/ + throw illegal_propagation_attempt("Structure transition between combination of origin structure and target structure not supported (Plane->Plane/Cylinder)."); + + return std::make_pair( std::make_pair(position_type(), structure_id_type()), + std::make_pair(position_type(), structure_id_type()) ); +}; + +// PlanarSurface -> DiskSurface +template +inline std::pair< std::pair, + std::pair > +get_pos_sid_pair_pair( PlanarSurface const& origin_structure, + DiskSurface const& target_structure, + typename Ttraits_::position_type const& old_pos, + typename Ttraits_::species_type const& s_orig, + typename Ttraits_::species_type const& s_targ, + typename Ttraits_::length_type const& reaction_length, + typename Ttraits_::rng_type &rng ) +{ + typedef typename Ttraits_::structure_id_type structure_id_type; + typedef typename Ttraits_::position_type position_type; + //typedef typename Ttraits_::length_type length_type; + + /*** COMBINATION NOT SUPPORTED ***/ + throw illegal_propagation_attempt("Structure transition between combination of origin structure and target structure not supported (Plane->Plane/Disk)."); + + return std::make_pair( std::make_pair(position_type(), structure_id_type()), + std::make_pair(position_type(), structure_id_type()) ); +}; + +// PlanarSurface -> PlanarSurface +template +inline std::pair< std::pair, + std::pair > +get_pos_sid_pair_pair( PlanarSurface const& origin_structure, + PlanarSurface const& target_structure, + typename Ttraits_::position_type const& old_pos, + typename Ttraits_::species_type const& s_orig, + typename Ttraits_::species_type const& s_targ, + typename Ttraits_::length_type const& reaction_length, + typename Ttraits_::rng_type &rng ) +{ + typedef typename Ttraits_::structure_id_type structure_id_type; + typedef typename Ttraits_::position_type position_type; + //typedef typename Ttraits_::length_type length_type; + + // As a default we produce two new positions on the same plane; in principle the particles can end up + // on different planes, but this should be treated afterwards via apply_boundary. + // TODO: Move apply_boundary into structure functions? + if(origin_structure.id() == target_structure.id()){ + + structure_id_type new_sid( origin_structure.id() ); + std::pair new_positions( origin_structure.geminate_dissociation_positions(rng, s_orig, s_targ, old_pos, reaction_length) ); + // geminate_dissociation_positions will produce two new positions close to old_pos taking into account + // the type of origin_structure and the properties of the two product species + // (the displacements from old_pos are weighted by the diffusion constants) + + return std::make_pair( std::make_pair(new_positions.first, new_sid), + std::make_pair(new_positions.second, new_sid) ); + } + else // structure transition not allowed + + throw illegal_propagation_attempt("Origin structure must be equal to target structure for this type of structure transition (Plane->Plane/Plane)."); +}; + +/******************************************************************************************************/ + + +#endif /* STRUCTUREFUNCTIONS_HPP */ + diff --git a/StructureID.hpp b/StructureID.hpp new file mode 100644 index 00000000..e819d45f --- /dev/null +++ b/StructureID.hpp @@ -0,0 +1,62 @@ +#ifndef STRUCTURE_ID_HPP +#define STRUCTURE_ID_HPP + +#ifdef HAVE_CONFIG_H +#include +#endif /* HAVE_CONFIG_H */ + +#include +#if defined(HAVE_TR1_FUNCTIONAL) +#include +#elif defined(HAVE_STD_HASH) +#include +#elif defined(HAVE_BOOST_FUNCTIONAL_HASH_HPP) +#include +#endif +#include "Identifier.hpp" + +struct StructureID: public Identifier +// The StructureID is a class for the identification of structures +{ + typedef Identifier base_type; + + StructureID(value_type const& value = value_type(0, 0)) + : base_type(value) {} +}; + +#if defined(HAVE_TR1_FUNCTIONAL) +namespace std { namespace tr1 { +#elif defined(HAVE_STD_HASH) +namespace std { +#elif defined(HAVE_BOOST_FUNCTIONAL_HASH_HPP) +namespace boost { +#endif + +template<> +struct hash +// Hashing function?? +{ + std::size_t operator()(StructureID const& val) const + { + return static_cast(val().first ^ val().second); + } +}; + +#if defined(HAVE_TR1_FUNCTIONAL) +} } // namespace std::tr1 +#elif defined(HAVE_STD_HASH) +} // namespace std +#elif defined(HAVE_BOOST_FUNCTIONAL_HASH_HPP) +} // namespace boost +#endif + +template +inline std::basic_ostream& operator<<(std::basic_ostream& strm, + const StructureID& v) +// Provides a stream of characters (a string) of the 'structure_id' that allows for printing. +{ + strm << "StructureID(" << v().first << ":" << v().second << ")"; + return strm; +} + +#endif /* STRUCTURE_ID_HPP */ diff --git a/StructureType.cpp b/StructureType.cpp index e847663c..63ff6f98 100644 --- a/StructureType.cpp +++ b/StructureType.cpp @@ -13,6 +13,29 @@ StructureType::identifier_type const& StructureType::id() const return id_; } +StructureType::structure_type_id_type const& StructureType::structure_type_id() const +{ + if (!structure_type_id_) + { + throw illegal_state("no structure_type defined"); + } + return structure_type_id_; +} + +// Check equality/inequality +bool StructureType::operator==(StructureType const& rhs) const +{ + return id_ == rhs.id() && + structure_type_id_ == rhs.structure_type_id() && + model_ == rhs.model(); +} + +bool StructureType::operator!=(StructureType const& rhs) const +{ + return !operator==(rhs); +} + + std::string const& StructureType::operator[](std::string const& name) const { string_map_type::const_iterator i(attrs_.find(name)); diff --git a/StructureType.hpp b/StructureType.hpp index 9adedc93..e9d38c2f 100644 --- a/StructureType.hpp +++ b/StructureType.hpp @@ -10,6 +10,7 @@ #include "exceptions.hpp" #include "utils/get_mapper_mf.hpp" +#include "SpeciesTypeID.hpp" class ParticleModel; @@ -20,26 +21,41 @@ class StructureType typedef get_mapper_mf::type string_map_type; public: - typedef std::string identifier_type; - typedef string_map_type::const_iterator string_map_iterator; - typedef boost::iterator_range attributes_range; + typedef SpeciesTypeID identifier_type; // NOTE: we use the same identifier as for the species! + typedef string_map_type::const_iterator string_map_iterator; + typedef boost::iterator_range attributes_range; + typedef identifier_type structure_type_id_type; public: + // Constructor + // TODO should add StructureType as property of StructureType so that we can + // build a hyarchie of StructureTypes living in Structuretypes just like structure living in structures. + StructureType(): model_(0) {} + + // Get the id identifier_type const& id() const; + // Get the id of the structure type the structure_type lives on/in + structure_type_id_type const& structure_type_id() const; + + // Check equality/inequality + bool operator==(StructureType const& rhs) const; + + bool operator!=(StructureType const& rhs) const; + std::string const& operator[](std::string const& name) const; std::string& operator[](std::string const& name); + // Get all the attributes attributes_range attributes() const; + // Get the particle model to which the structuretype is associated ParticleModel* model() const { return model_; } - StructureType(): model_(0) {} - protected: void bind_to_model(ParticleModel* model, identifier_type const& id) { @@ -47,12 +63,16 @@ class StructureType id_ = id; } + +//////// Member variables private: - ParticleModel* model_; - identifier_type id_; - string_map_type attrs_; + ParticleModel* model_; // to what model is it associated + identifier_type id_; // identifier Note that these are only defined if the object is bound to a model. + structure_type_id_type structure_type_id_; // The structure type that the structure_type lives on/in + string_map_type attrs_; }; +/////// Inline functions template inline std::basic_ostream& operator<<(std::basic_ostream& out, const StructureType& v) diff --git a/StructureUtils.hpp b/StructureUtils.hpp index bc9e037b..207cd418 100644 --- a/StructureUtils.hpp +++ b/StructureUtils.hpp @@ -12,83 +12,146 @@ template struct StructureUtils { - typedef Tsim_ simulator_type; - typedef typename simulator_type::traits_type traits_type; - typedef typename traits_type::world_type::position_type position_type; - typedef typename traits_type::world_type::length_type length_type; - typedef typename traits_type::world_type::structure_id_type structure_id_type; - typedef typename traits_type::world_type::structure_type structure_type; - typedef typename simulator_type::surface_type surface_type; - typedef typename simulator_type::region_type region_type; - typedef typename simulator_type::sphere_type sphere_type; - typedef typename simulator_type::cylinder_type cylinder_type; - typedef typename simulator_type::box_type box_type; - typedef typename simulator_type::plane_type plane_type; - typedef typename simulator_type::spherical_surface_type spherical_surface_type; - typedef typename simulator_type::cylindrical_surface_type cylindrical_surface_type; - typedef typename simulator_type::planar_surface_type planar_surface_type; - typedef typename simulator_type::cuboidal_region_type cuboidal_region_type; - typedef typename simulator_type::world_type::traits_type::rng_type rng_type; + typedef Tsim_ simulator_type; + typedef typename simulator_type::traits_type traits_type; + + // some typedefs + typedef typename traits_type::world_type::position_type position_type; + typedef typename traits_type::world_type::length_type length_type; + typedef typename traits_type::world_type::structure_name_type structure_name_type; + typedef typename traits_type::world_type::structure_id_type structure_id_type; + typedef typename traits_type::world_type::structure_type structure_type; + typedef typename traits_type::world_type::structure_type_id_type structure_type_id_type; + + typedef typename simulator_type::surface_type surface_type; + typedef typename simulator_type::region_type region_type; + typedef typename simulator_type::sphere_type sphere_type; + typedef typename simulator_type::cylinder_type cylinder_type; + typedef typename simulator_type::disk_type disk_type; + typedef typename simulator_type::box_type box_type; + typedef typename simulator_type::plane_type plane_type; + typedef typename simulator_type::spherical_surface_type spherical_surface_type; + typedef typename simulator_type::cylindrical_surface_type cylindrical_surface_type; + typedef typename simulator_type::disk_surface_type disk_surface_type; + typedef typename simulator_type::planar_surface_type planar_surface_type; + typedef typename simulator_type::cuboidal_region_type cuboidal_region_type; + typedef typename simulator_type::world_type::traits_type::rng_type rng_type; static planar_surface_type* create_planar_surface( - structure_id_type const& id, + structure_type_id_type const& sid, // This refers to the structure type of the planar surface + structure_name_type const& name, + position_type const& corner, + position_type const& unit_x, + position_type const& unit_y, + length_type const& lx, + length_type const& ly, + structure_id_type const& parent_struct_id) + { + BOOST_ASSERT(is_cartesian_versor(unit_x)); + BOOST_ASSERT(is_cartesian_versor(unit_y)); + BOOST_ASSERT(is_cartesian_versor(cross_product(unit_x, unit_y))); + + // Note that when calling the function the origin is in the corner and the length + // is the whole length of the plane, whereas for 'plane_type' the origin is in the + // center of the plane (pos) and that the length is only half lengths 'half_lx' and 'half_ly'. + const length_type half_lx(lx / 2); + const length_type half_ly(ly / 2); + + const position_type pos(add(add(corner, multiply(unit_x, half_lx)), + multiply(unit_y, half_ly))); + const bool is_one_sided(true); + + return new planar_surface_type(name, sid, parent_struct_id, + plane_type(pos, unit_x, unit_y, + half_lx, half_ly, is_one_sided)); + } + + static planar_surface_type* create_double_sided_planar_surface( + structure_type_id_type const& sid, // This refers to the structure type of the planar surface + structure_name_type const& name, position_type const& corner, position_type const& unit_x, position_type const& unit_y, length_type const& lx, - length_type const& ly) + length_type const& ly, + structure_id_type const& parent_struct_id) { BOOST_ASSERT(is_cartesian_versor(unit_x)); BOOST_ASSERT(is_cartesian_versor(unit_y)); BOOST_ASSERT(is_cartesian_versor(cross_product(unit_x, unit_y))); + // Note that when calling the function the origin is in the corner and the length + // is the whole length of the plane, whereas for 'plane_type' the origin is in the + // center of the plane (pos) and that the length is only half lengths 'half_lx' and 'half_ly'. const length_type half_lx(lx / 2); const length_type half_ly(ly / 2); const position_type pos(add(add(corner, multiply(unit_x, half_lx)), - multiply(unit_y, half_ly))); + multiply(unit_y, half_ly))); + const bool is_one_sided(false); - return new planar_surface_type(id, + return new planar_surface_type(name, sid, parent_struct_id, plane_type(pos, unit_x, unit_y, - half_lx, half_ly)); + half_lx, half_ly, is_one_sided)); } static spherical_surface_type* create_spherical_surface( - structure_id_type const& id, + structure_type_id_type const& sid, + structure_name_type const& name, position_type const& pos, - length_type const& radius) + length_type const& radius, + structure_id_type const& parent_struct_id) { - return new spherical_surface_type(id, sphere_type(pos, radius)); + return new spherical_surface_type(name, sid, parent_struct_id, + sphere_type(pos, radius)); } static cylindrical_surface_type* create_cylindrical_surface( - structure_id_type const& id, + structure_type_id_type const& sid, + structure_name_type const& name, position_type const& corner, length_type const& radius, position_type const& unit_z, - length_type const& length) + length_type const& length, + structure_id_type const& parent_struct_id) { BOOST_ASSERT(is_cartesian_versor(unit_z)); const length_type half_length(length / 2); const position_type pos(add(corner, multiply(unit_z, half_length))); - return new cylindrical_surface_type(id, - cylinder_type(pos, radius, unit_z, half_length)); + return new cylindrical_surface_type(name, sid, parent_struct_id, + cylinder_type(pos, radius, unit_z, half_length)); + } + + static disk_surface_type* create_disk_surface( + structure_type_id_type const& sid, + structure_name_type const& name, + position_type const& center, + length_type const& radius, + position_type const& unit_z, + structure_id_type const& parent_struct_id) + { + BOOST_ASSERT(is_cartesian_versor(unit_z)); + + return new disk_surface_type(name, sid, parent_struct_id, + disk_type(center, radius, unit_z)); } static cuboidal_region_type* create_cuboidal_region( - structure_id_type const& id, + structure_type_id_type const& sid, + structure_name_type const& name, position_type const& corner, - boost::array const& extent) + boost::array const& extent, + structure_id_type const& parent_struct_id) { const boost::array half_extent(divide(extent, 2)); - return new cuboidal_region_type(id, - box_type(add(corner, half_extent), - create_vector(1, 0, 0), - create_vector(0, 1, 0), - create_vector(0, 0, 1), - half_extent)); + return new cuboidal_region_type(name, sid, parent_struct_id, + box_type(add(corner, half_extent), + create_vector(1, 0, 0), + create_vector(0, 1, 0), + create_vector(0, 0, 1), + half_extent)); } static position_type random_vector(structure_type const& structure, @@ -101,11 +164,12 @@ struct StructureUtils { return structure.random_position(rng); } - +/* static length_type minimal_distance_from_surface(surface_type const& surface, length_type const& radius) { return surface.minimal_distance(radius); } +*/ }; #endif /* STRUCTURE_UTILS_HPP */ diff --git a/Surface.hpp b/Surface.hpp index d653fbff..aeffda08 100644 --- a/Surface.hpp +++ b/Surface.hpp @@ -12,6 +12,7 @@ #include #include "ParticleSimulationStructure.hpp" +#include "Disk.hpp" #include "Cylinder.hpp" #include "Sphere.hpp" #include "Plane.hpp" @@ -20,28 +21,43 @@ template class Surface: public ParticleSimulationStructure { public: - typedef ParticleSimulationStructure base_type; - typedef typename base_type::identifier_type identifier_type; - typedef typename base_type::length_type length_type; + typedef ParticleSimulationStructure base_type; + typedef typename base_type::structure_name_type structure_name_type; // This is just a string + typedef typename base_type::structure_id_type structure_id_type; + typedef typename base_type::structure_type_id_type structure_type_id_type; + typedef typename base_type::length_type length_type; public: virtual ~Surface() {} - Surface(identifier_type const& id): base_type(id) {} + // Constructor + Surface(structure_name_type const& name, structure_type_id_type const& sid, structure_id_type const& parent_struct_id): + base_type(name, sid, parent_struct_id) {} - virtual length_type minimal_distance(length_type const& radius) const = 0; +// virtual length_type minimal_distance(length_type const& radius) const = 0; }; + + + + template class BasicSurfaceImpl: public Surface { public: typedef Surface base_type; - typedef Tshape_ shape_type; - typedef typename base_type::identifier_type identifier_type; - typedef typename base_type::length_type length_type; - typedef typename base_type::position_type position_type; - typedef std::pair projected_type; + + typedef Tshape_ shape_type; + typedef typename shape_type::side_enum_type side_enum_type; // Defines the type of enum to use for the sides of the surface + + typedef typename base_type::structure_name_type structure_name_type; + typedef typename base_type::structure_id_type structure_id_type; + typedef typename base_type::structure_type_id_type structure_type_id_type; + typedef typename base_type::length_type length_type; + typedef typename base_type::position_type position_type; + typedef std::pair components_pair_type; + typedef std::pair projected_type; + typedef std::pair position_flag_pair_type; public: virtual ~BasicSurfaceImpl() {} @@ -55,11 +71,14 @@ class BasicSurfaceImpl: public Surface { return shape_; } - - virtual bool operator==(Structure const& rhs) const + + virtual bool operator==(Structure const& rhs) const { BasicSurfaceImpl const* _rhs(dynamic_cast(&rhs)); - return _rhs && base_type::id_ == rhs.id() && shape_ == _rhs->shape(); + return _rhs && + base_type::id_ == rhs.id() && + base_type::sid_ == rhs.sid() && + shape_ == _rhs->shape(); } virtual std::size_t hash() const @@ -71,7 +90,9 @@ class BasicSurfaceImpl: public Surface #elif defined(HAVE_BOOST_FUNCTIONAL_HASH_HPP) using boost::hash; #endif - return hash()(base_type::id_) ^ hash()(shape()); + return hash()(base_type::name_) ^ + hash()(base_type::sid_) ^ + hash()(shape()); } virtual std::string as_string() const @@ -81,14 +102,39 @@ class BasicSurfaceImpl: public Surface return out.str(); } - projected_type - projected_point(position_type const& pos) const + virtual projected_type project_point(position_type const& pos) const + { + return ::project_point(shape(), pos); + } + + virtual projected_type project_point_on_surface(position_type const& pos) const { - return ::projected_point(shape(), pos); + return ::project_point_on_surface(shape(), pos); + } + + virtual length_type distance(position_type const& pos) const + { + return ::distance(shape(), pos); + } + + virtual position_type const& position() const + { + return ::shape_position(shape()); } - BasicSurfaceImpl(identifier_type const& id, shape_type const& shape) - : base_type(id), shape_(shape) {} + virtual position_flag_pair_type deflect(position_type const& pos0, position_type const& displacement) const + { + return ::deflect(shape(), pos0, displacement); + } +/* + virtual position_type deflect_back(position_type const& pos, position_type const& u_z) const + { + return ::deflect_back(shape(), pos, u_z); + } +*/ + // Constructor + BasicSurfaceImpl(structure_name_type const& name, structure_type_id_type const& sid, structure_id_type const& parent_struct_id, shape_type const& shape) + : base_type(name, sid, parent_struct_id), shape_(shape) {} protected: shape_type shape_; diff --git a/Transaction.hpp b/Transaction.hpp index af7e6b68..c19e2ce3 100644 --- a/Transaction.hpp +++ b/Transaction.hpp @@ -14,23 +14,24 @@ template class Transaction: public ParticleContainer +// A transaction 'is a' ParticleContainer that makes it possible to revert the moves of the particles +// The Transaction is actually just an abstract datatype, not a class from which can be instantiated. { public: typedef Ttraits_ traits_type; - typedef typename traits_type::particle_type particle_type; - typedef typename particle_type::shape_type particle_shape_type; - typedef typename traits_type::species_type species_type; - typedef typename traits_type::species_id_type species_id_type; - typedef typename traits_type::position_type position_type; - typedef typename traits_type::particle_id_type particle_id_type; - typedef typename traits_type::size_type size_type; - typedef typename traits_type::length_type length_type; - typedef typename traits_type::structure_id_type structure_id_type; - typedef typename traits_type::structure_type structure_type; - typedef std::pair particle_id_pair; - typedef abstract_limited_generator particle_id_pair_generator; - typedef std::pair particle_id_pair_and_distance; - typedef unassignable_adapter particle_id_pair_and_distance_list; + + // useful shorthands for types that we are using. + typedef typename traits_type::length_type length_type; + typedef typename traits_type::position_type position_type; + typedef typename traits_type::size_type size_type; + typedef typename traits_type::particle_type particle_type; + typedef typename traits_type::particle_id_type particle_id_type; + typedef typename particle_type::shape_type particle_shape_type; + typedef typename traits_type::species_type species_type; + typedef typename traits_type::species_id_type species_id_type; + + typedef std::pair particle_id_pair; + typedef abstract_limited_generator particle_id_pair_generator; virtual ~Transaction() {} @@ -45,35 +46,57 @@ class Transaction: public ParticleContainer template class TransactionImpl: public Transaction +// Tpc_ is the 'particlecontainer_type' { public: typedef Tpc_ particle_container_type; - typedef typename particle_container_type::traits_type traits_type; - typedef typename traits_type::particle_type particle_type; - typedef typename particle_type::shape_type particle_shape_type; - typedef typename traits_type::species_type species_type; - typedef typename traits_type::species_id_type species_id_type; - typedef typename traits_type::position_type position_type; - typedef typename traits_type::particle_id_type particle_id_type; - typedef typename traits_type::size_type size_type; - typedef typename traits_type::length_type length_type; - typedef typename traits_type::structure_id_type structure_id_type; - typedef typename traits_type::structure_type structure_type; - typedef std::pair particle_id_pair; - typedef abstract_limited_generator particle_id_pair_generator; - typedef std::pair particle_id_pair_and_distance; - typedef unassignable_adapter particle_id_pair_and_distance_list; + typedef typename particle_container_type::traits_type traits_type; // get the traits that are passed on from the particlecontainer. + + // define shorthands to the types that we are using here. + typedef typename traits_type::length_type length_type; + typedef typename traits_type::size_type size_type; + typedef typename traits_type::position_type position_type; + typedef typename traits_type::species_type species_type; + typedef typename traits_type::species_id_type species_id_type; + typedef typename traits_type::particle_type particle_type; + typedef typename traits_type::particle_id_type particle_id_type; + typedef typename particle_type::shape_type particle_shape_type; + typedef typename traits_type::structure_type_type structure_type_type; + typedef typename traits_type::structure_type_id_type structure_type_id_type; + typedef typename traits_type::structure_type structure_type; + typedef typename traits_type::structure_id_type structure_id_type; + + typedef typename particle_container_type::particle_id_pair_and_distance_list particle_id_pair_and_distance_list; + typedef typename particle_container_type::structure_id_pair_and_distance_list structure_id_pair_and_distance_list; + + typedef std::pair particle_id_pair; + typedef abstract_limited_generator particle_id_pair_generator; private: typedef std::map particle_id_pair_set_type; - typedef sorted_list > particle_id_list_type; - -public: - virtual particle_id_pair new_particle(species_id_type const& sid, + typename particle_id_pair::second_type> particle_id_pair_set_type; + typedef sorted_list > particle_id_list_type; + typedef std::map > structure_map; + typedef select_second structure_second_selector_type; + typedef std::map structure_type_map; + typedef select_second structure_type_second_selector_type; + +public: + typedef boost::transform_iterator structure_iterator; + typedef sized_iterator_range structures_range; + typedef boost::transform_iterator structure_type_iterator; + typedef typename particle_container_type::structure_types_range structure_types_range; + typedef typename particle_container_type::structure_id_set structure_id_set; + typedef typename particle_container_type::structure_id_pair structure_id_pair; + typedef typename particle_container_type::position_structid_pair_type position_structid_pair_type; + + // Particle Stuff + virtual particle_id_pair new_particle(species_id_type const& sid, structure_id_type const& structure_id, position_type const& pos) { - particle_id_pair retval(pc_.new_particle(sid, pos)); + particle_id_pair retval(pc_.new_particle(sid, structure_id, pos)); const bool result(added_particles_.push_no_duplicate(retval.first)); BOOST_ASSERT(result); return retval; @@ -145,20 +168,91 @@ class TransactionImpl: public Transaction return pc_.check_overlap(s, ignore1, ignore2); } + virtual structure_id_pair_and_distance_list* check_surface_overlap(particle_shape_type const& s, position_type const& old_pos, structure_id_type const& current, + length_type const& sigma) const + { + return pc_.check_surface_overlap(s, old_pos, current, sigma); + } + + virtual structure_id_pair_and_distance_list* check_surface_overlap(particle_shape_type const& s, position_type const& old_pos, structure_id_type const& current, + length_type const& sigma, structure_id_type const& ignore) const + { + return pc_.check_surface_overlap(s, old_pos, current, sigma, ignore); + } + + virtual structure_id_pair_and_distance_list* check_surface_overlap(particle_shape_type const& s, position_type const& old_pos, structure_id_type const& current, + length_type const& sigma, structure_id_type const& ignore1, structure_id_type const& ignore2) const + { + return pc_.check_surface_overlap(s, old_pos, current, sigma, ignore1, ignore2); + } + virtual Transaction* create_transaction() { return new TransactionImpl(*this); } + ///// All the other methods are passed on to the associated ParticleContainer. + // Species stuff + virtual species_type const& get_species(species_id_type const& id) const + { + return pc_.get_species(id); + } + + // StructureType stuff +// virtual bool add_structure_type(structure_type_type const& structure_type); // TODO + + virtual structure_type_type get_structure_type(structure_type_id_type const& sid) const + { + return pc_.get_structure_type(sid); + } + virtual structure_types_range get_structure_types() const + { + return pc_.get_structure_types(); + } + virtual structure_type_id_type get_def_structure_type_id() const + { + return pc_.get_def_structure_type_id(); + } + + // Start Structure stuff +// virtual structure_id_type add_structure(structure_type const& structure); // TODO + virtual boost::shared_ptr get_structure(structure_id_type const& id) const { return pc_.get_structure(id); } - - virtual species_type const& get_species(species_id_type const& id) const + virtual structures_range get_structures() const { - return pc_.get_species(id); + return pc_.get_structures(); + } + virtual boost::shared_ptr get_some_structure_of_type(structure_type_id_type const& sid) const + { + return pc_.get_some_structure_of_type(sid); + } + template + bool update_structure(Tstructid_pair_ const& structid_pair) + { + return pc_.update_structure(structid_pair); + } + virtual bool remove_structure(structure_id_type const& id) + { + return pc_.remove_structure(id); + } + virtual structure_id_set get_structure_ids(structure_type_id_type const& sid) const + { + return pc_.get_structure_ids(sid); } + virtual structure_id_type get_def_structure_id() const + { + return pc_.get_def_structure_id(); + } + virtual structure_id_pair_and_distance_list* get_close_structures(position_type const& pos, structure_id_type const& current_struct_id, + structure_id_type const& ignore) const + { + return pc_.get_close_structures(pos, current_struct_id, ignore); + } + // End structure stuff + virtual size_type num_particles() const { @@ -175,6 +269,7 @@ class TransactionImpl: public Transaction return pc_.get_particles(); } + // Begin Transaction methods. The following methods are prototyped in the base class Transaction (see above) virtual particle_id_pair_generator* get_added_particles() const { return make_range_generator( @@ -217,6 +312,8 @@ class TransactionImpl: public Transaction removed_particles_.clear(); orig_particles_.clear(); } + // end Transaction methods. + virtual length_type distance(position_type const& lhs, position_type const& rhs) const @@ -234,6 +331,11 @@ class TransactionImpl: public Transaction return pc_.apply_boundary(v); } + virtual position_structid_pair_type apply_boundary(position_structid_pair_type const& pos_struct_id) const + { + return pc_.apply_boundary(pos_struct_id); + } + virtual position_type cyclic_transpose(position_type const& p0, position_type const& p1) const { return pc_.cyclic_transpose(p0, p1); @@ -244,6 +346,12 @@ class TransactionImpl: public Transaction return pc_.cyclic_transpose(p0, p1); } + virtual position_structid_pair_type cyclic_transpose(position_structid_pair_type const& pos_struct_id, + structure_type const& structure) const + { + return pc_.cyclic_transpose(pos_struct_id, structure); + } + virtual ~TransactionImpl() {} TransactionImpl(particle_container_type& pc): pc_(pc) {} @@ -260,12 +368,13 @@ class TransactionImpl: public Transaction return *i; } +//////// Member variables. private: - particle_container_type& pc_; - particle_id_list_type added_particles_; - particle_id_list_type modified_particles_; - particle_id_pair_set_type orig_particles_; - particle_id_list_type removed_particles_; + particle_container_type& pc_; // the associated particle container + particle_id_list_type added_particles_; + particle_id_list_type modified_particles_; + particle_id_pair_set_type orig_particles_; + particle_id_list_type removed_particles_; }; #endif /* TRANSACTION_HPP */ diff --git a/World.hpp b/World.hpp index a6150d8d..dda8b34c 100644 --- a/World.hpp +++ b/World.hpp @@ -1,6 +1,8 @@ #ifndef WORLD_HPP #define WORLD_HPP +#include + #include "ParticleContainerBase.hpp" #include @@ -8,6 +10,8 @@ #include #include #include +#include +#include #include "exceptions.hpp" #include "generator.hpp" #include "filters.hpp" @@ -18,56 +22,85 @@ #include "SerialIDGenerator.hpp" #include "Transaction.hpp" #include "Structure.hpp" +#include "StructureType.hpp" +#include "StructureID.hpp" #include "Surface.hpp" #include "Region.hpp" #include "geometry.hpp" #include "GSLRandomNumberGenerator.hpp" #include "Point.hpp" // XXX: workaround. should be removed later. #include "utils/pair.hpp" +#include "utils/range.hpp" template struct WorldTraitsBase +// Just defines some basic properties of a world. Basically just types and one constant. +// Tderived_ is the class/structure that inherits from this struct (subclass) -> WorldTraits and CyclicWorldTraits { - typedef std::size_t size_type; - typedef Tlen_ length_type; - typedef TD_ D_type; - typedef TD_ v_type; - typedef ParticleID particle_id_type; - typedef SerialIDGenerator particle_id_generator; - typedef SpeciesTypeID species_id_type; - typedef Particle particle_type; - typedef std::string structure_id_type; - typedef SpeciesInfo species_type; - typedef Vector3 point_type; - typedef typename particle_type::shape_type::position_type position_type; - typedef GSLRandomNumberGenerator rng_type; - typedef Structure structure_type; - - static const Real TOLERANCE = 1e-7; + // General + typedef std::size_t size_type; + typedef Tlen_ length_type; + typedef TD_ D_type; + typedef TD_ v_type; + + // Structure types + typedef StructureType structure_type_type; // definition of what class to use for structure_type + typedef SpeciesTypeID structure_type_id_type; + // Structures + typedef Structure structure_type; // The structure_type is parameterized with the subclass?? + typedef StructureID structure_id_type; // identifier type for structures + typedef SerialIDGenerator structure_id_generator; + typedef std::string structure_name_type; + // Species + typedef SpeciesTypeID species_id_type; // identifier type for species + typedef SpeciesInfospecies_type; // This is ADDITIONAL species information for use in a (spatial) world. + // Particles + typedef Particle particle_type; // type for particles, NOTE why is there no v_type here? + typedef ParticleID particle_id_type; // identifier type for particles + typedef SerialIDGenerator particle_id_generator; + + typedef Vector3 point_type; + typedef typename particle_type::shape_type::position_type position_type; + typedef GSLRandomNumberGenerator rng_type; + + static const Real TOLERANCE = 1e-7; // tolerance of what? }; + + template struct WorldTraits: public WorldTraitsBase, Tlen_, TD_> { +// This defines a structure (like a class) with some rudimentairy properties of the world +// It inherits from WorldTraitsBase which is parameterized with the fully defined WorldTraits class/structure. +// +// Implements mostly boundaryconditions related stuff (distance, apply_boundary, cyclic_transpose) + public: + // This is the normal world (without periodic/cyclic boundary conditions) typedef WorldTraitsBase, Tlen_, TD_> base_type; typedef typename base_type::length_type length_type; typedef typename base_type::position_type position_type; template static Tval_ apply_boundary(Tval_ const& v, length_type const& world_size) + // apply_boundary is here defined for consistency { return v; } template static Tval_ cyclic_transpose(Tval_ const& p0, Tval_ const& p1, length_type const& world_size) + // ditto { return p0; } template static length_type distance(T1_ const& p0, T2_ const& p1, length_type const& world_size) + // The distance function { return ::distance(p0, p1); } @@ -100,29 +133,38 @@ struct WorldTraits: public WorldTraitsBase, Tlen_, TD_> template struct CyclicWorldTraits: public WorldTraitsBase, Tlen_, TD_> { +// Use these world traits for a world WITH periodic/cyclic boundary conditions + public: + // Specify the types being used. typedef WorldTraitsBase, Tlen_, TD_> base_type; - typedef typename base_type::length_type length_type; - typedef typename base_type::position_type position_type; + typedef typename base_type::length_type length_type; + typedef typename base_type::position_type position_type; template static Tval_ apply_boundary(Tval_ const& v, length_type const& world_size) + // This applied the periodic boundary conditions, unclear what kind of type 'Tval_' is. { return ::apply_boundary(v, world_size); } static length_type cyclic_transpose(length_type const& p0, length_type const& p1, length_type const& world_size) + // selects the copy of p0 (over the periodic boundaries) such that the distance between p0 and p1 is minimized over + // over the periodic boundary conditions. + // This is the length_type version { return ::cyclic_transpose(p0, p1, world_size); } static position_type cyclic_transpose(position_type const& p0, position_type const& p1, length_type const& world_size) + // This is the position_type version { return ::cyclic_transpose(p0, p1, world_size); } template static length_type distance(T1_ const& p0, T2_ const& p1, length_type const& world_size) + // This distance function in the presence of the periodic boundary conditions { return distance_cyclic(p0, p1, world_size); } @@ -155,92 +197,148 @@ struct CyclicWorldTraits: public WorldTraitsBase, template class World: public ParticleContainerBase, Ttraits_> { +// The world 'is a' ParticleContainerBase which is parameterize with the same World class +// The world is a template that is parametrized with the traits (these define a number of datatypes used in the definition of +// the world) public: - typedef Ttraits_ traits_type; - typedef ParticleContainerBase base_type; - typedef ParticleContainer particle_container_type; - typedef typename traits_type::length_type length_type; - typedef typename traits_type::species_type species_type; - typedef typename traits_type::position_type position_type; - typedef typename traits_type::particle_type particle_type; - typedef typename traits_type::particle_id_type particle_id_type; - typedef typename traits_type::particle_id_generator particle_id_generator; - typedef typename traits_type::species_id_type species_id_type; + typedef Ttraits_ traits_type; // Needs to be of WorldTraits type??? + typedef ParticleContainerBase base_type; // the class from which is inherited -> base class + typedef ParticleContainer particle_container_type; // ParticleContainer is again the superclass of ParticleContainerBase + + // define some short hand names for all the properties (traits) of the world + typedef typename traits_type::length_type length_type; + typedef typename traits_type::species_type species_type; + typedef typename traits_type::position_type position_type; + typedef typename traits_type::particle_type particle_type; + typedef typename traits_type::particle_id_type particle_id_type; + typedef typename traits_type::particle_id_generator particle_id_generator; + typedef typename traits_type::species_id_type species_id_type; // Note that this is also the structure_type_id_type typedef typename traits_type::particle_type::shape_type particle_shape_type; - typedef typename traits_type::size_type size_type; - typedef typename traits_type::structure_id_type structure_id_type; - typedef typename traits_type::structure_type structure_type; - typedef std::pair particle_id_pair; + typedef typename traits_type::size_type size_type; + typedef typename traits_type::structure_type structure_type; + typedef typename traits_type::structure_id_type structure_id_type; + typedef typename traits_type::structure_name_type structure_name_type; + typedef typename traits_type::structure_id_generator structure_id_generator; + typedef typename traits_type::structure_type_type structure_type_type; + typedef typename traits_type::structure_type_id_type structure_type_id_type; + + // + typedef typename base_type::particle_id_pair particle_id_pair; // defines the pid_particle_pair tuple + typedef typename base_type::structure_id_pair structure_id_pair; + typedef std::pair projected_type; + + typedef typename base_type::particle_id_set particle_id_set; + typedef typename base_type::structure_id_set structure_id_set; + typedef typename base_type::structure_types_range structure_types_range; + + typedef typename base_type::cuboidal_region_type cuboidal_region_type; + typedef typename base_type::planar_surface_type planar_surface_type; + typedef typename base_type::cylindrical_surface_type cylindrical_surface_type; + typedef typename base_type::disk_surface_type disk_surface_type; + typedef typename base_type::spherical_surface_type spherical_surface_type; + typedef typename planar_surface_type::side_enum_type planar_surface_side_type; + typedef typename cylindrical_surface_type::side_enum_type cylindrical_surface_side_type; + + typedef typename structure_type::position_type vector_type; + typedef std::pair neighbor_id_vector_type; protected: - typedef std::map species_map; - typedef std::map > structure_map; - typedef std::set particle_id_set; - typedef std::map per_species_particle_id_set; - typedef select_second species_second_selector_type; - typedef select_second surface_second_selector_type; + typedef std::map species_map; + typedef std::map > structure_map; // note: this is a structure_map_type + typedef std::map structure_type_map; + typedef std::map per_species_particle_id_set; + typedef std::map per_structure_type_structure_id_set; + typedef std::map per_structure_particle_id_set; + typedef select_second species_second_selector_type; // not sure what this does. + typedef select_second structure_types_second_selector_type; public: typedef boost::transform_iterator species_iterator; - typedef boost::transform_iterator surface_iterator; - typedef sized_iterator_range species_range; - typedef sized_iterator_range structures_range; + typename species_map::const_iterator> species_iterator; + typedef boost::transform_iterator structure_type_iterator; + typedef sized_iterator_range species_range; public: + // The constructor World(length_type world_size = 1., size_type size = 1) - : base_type(world_size, size) {} + : base_type(world_size, size) + { + base_type::structures_.initialize(structidgen_()); + } - virtual particle_id_pair new_particle(species_id_type const& sid, + // To create new particles + virtual particle_id_pair new_particle(species_id_type const& sid, structure_id_type const& structure_id, position_type const& pos) { species_type const& species(get_species(sid)); + const boost::shared_ptr structure(base_type::get_structure(structure_id)); + // assert that the structure is of the type denoted by the species of the particle. + BOOST_ASSERT(structure->sid() == species.structure_type_id()); + particle_id_pair retval(pidgen_(), - particle_type(sid, particle_shape_type(pos, species.radius()), - species.D(), species.v() )); + particle_type(sid, particle_shape_type(pos, species.radius()), + structure_id, species.D(), species.v() )); + update_particle(retval); return retval; } - + // To update particles virtual bool update_particle(particle_id_pair const& pi_pair) { typename base_type::particle_matrix_type::iterator i( base_type::pmat_.find(pi_pair.first)); if (i != base_type::pmat_.end()) + // The particle was already in the matrix { if ((*i).second.sid() != pi_pair.second.sid()) + // If the species changed we need to change the 'species_id -> particle ids' mapping { particle_pool_[(*i).second.sid()].erase((*i).first); particle_pool_[pi_pair.second.sid()].insert(pi_pair.first); } + if ((*i).second.structure_id() != pi_pair.second.structure_id()) + // If the structure changed we need to change the 'structure_id -> particle ids' mapping + { + particleonstruct_pool_[(*i).second.structure_id()].erase((*i).first); + particleonstruct_pool_[pi_pair.second.structure_id()].insert(pi_pair.first); + } base_type::pmat_.update(i, pi_pair); return false; } - BOOST_ASSERT(base_type::update_particle(pi_pair)); - particle_pool_[pi_pair.second.sid()].insert(pi_pair.first); + + // The particle didn't exist yet + BOOST_VERIFY(base_type::update_particle(pi_pair)); // update particle + particle_pool_[pi_pair.second.sid()].insert(pi_pair.first); // update species->particles map + particleonstruct_pool_[pi_pair.second.structure_id()].insert(pi_pair.first); // update structure->particles map return true; } - + // Remove the particle by id 'id' if present in the world. virtual bool remove_particle(particle_id_type const& id) { bool found(false); - particle_id_pair pp(get_particle(id, found)); + particle_id_pair pp(base_type::get_particle(id, found)); // call method in ParticleContainerBase if (!found) { return false; } - particle_pool_[pp.second.sid()].erase(id); - base_type::remove_particle(id); + particle_pool_[pp.second.sid()].erase(id); // remove particle from species->particles + particleonstruct_pool_[pp.second.structure_id()].erase(id); // remove particle from structure->particles + base_type::remove_particle(id); // remove particle return true; } + ////// Species stuff + // Adds a species to the world. void add_species(species_type const& species) { + // make sure that the structure_type defined in the species exists + //structure_type_type const& structure_type(get_structure_type(species.structure_type_id())); + species_map_[species.id()] = species; particle_pool_[species.id()] = particle_id_set(); } - + // Get a species in the world by id virtual species_type const& get_species(species_id_type const& id) const { typename species_map::const_iterator i(species_map_.find(id)); @@ -250,54 +348,206 @@ class World: public ParticleContainerBase, Ttraits_> } return (*i).second; } - + // Get all the species species_range get_species() const { return species_range( species_iterator(species_map_.begin(), species_second_selector_type()), - species_iterator(species_map_.end(), species_second_selector_type()), + species_iterator(species_map_.end(), species_second_selector_type()), species_map_.size()); } + // Get all the particle ids by species + particle_id_set get_particle_ids(species_id_type const& sid) const + { + typename per_species_particle_id_set::const_iterator i( + particle_pool_.find(sid)); + if (i == particle_pool_.end()) + { + + throw not_found(std::string("Unknown species (id=") + boost::lexical_cast(sid) + ")"); + } + return (*i).second; + } + + ////// Structure stuff + // Add a structure + template + structure_id_type add_structure(boost::shared_ptr structure) + { + // check that the structure_type that is defined in the structure exists! + //structure_type_type const& structure_type(get_structure_type(structure->sid())); + + const structure_id_type structure_id(structidgen_()); + structure->set_id(structure_id); + update_structure(std::make_pair(structure_id, structure)); + return structure_id; + } + + // Update structure + template + bool update_structure(Tstructid_pair_ const& structid_pair) + { + if ( base_type::has_structure(structid_pair.first) ) + // The item was already found + { + // FIXME what to do when the structure has particles and moves or changes structure_type?!?! + // get all particles on the structure + // assert that no particles if structure type changes. + + const boost::shared_ptr structure (base_type::get_structure(structid_pair.first)); + if (structure->sid() != structid_pair.second->sid()) + // If the structuretype changed we need to update the 'structure_type_id->structure ids' mapping + { + structure_pool_[structure->sid()].erase(structure->id()); + structure_pool_[structid_pair.second->sid()].insert(structid_pair.first); + } + base_type::update_structure(structid_pair); + return false; + } - bool add_structure(boost::shared_ptr surface) + // The structure was not yet in the world. + BOOST_VERIFY(base_type::update_structure(structid_pair)); + // update the mapping 'structure_type_id -> set of structure ids' + structure_pool_[structid_pair.second->sid()].insert(structid_pair.first); + // create a new mapping from structure id -> set of particles + particleonstruct_pool_[structid_pair.first] = particle_id_set(); + return true; + } + + // Remove structure + virtual bool remove_structure(structure_id_type const& id) + { + // TODO + // -get all particles on structure + // -only remove if no particles + return base_type::remove_structure(id); + } + + // Connectivity related stuff + template + bool connect_structures(Tstructure_ const& structure1, Tside_enum_ const& side1, + Tstructure_ const& structure2, Tside_enum_ const& side2) + { + return base_type::structures_.connect_structures(structure1, side1, structure2, side2); + } + + template + neighbor_id_vector_type get_neighbor_info(Tstructure_ const& structure, Tside_enum_ side) + { + return base_type::structures_.get_neighbor_info(structure, side); + } + + template + structure_id_type get_neighbor_id(Tstructure_ const& structure, Tside_enum_ side) { - return structure_map_.insert(std::make_pair(surface->id(), surface)).second; + return base_type::structures_.get_neighbor_info(structure, side).first; } - virtual boost::shared_ptr get_structure(structure_id_type const& id) const + // Get all the structure ids by structure_type id + structure_id_set get_structure_ids(structure_type_id_type const& sid) const { - typename structure_map::const_iterator i(structure_map_.find(id)); - if (structure_map_.end() == i) + typename per_structure_type_structure_id_set::const_iterator i( + structure_pool_.find(sid)); + if (i == structure_pool_.end()) { - throw not_found(std::string("Unknown surface (id=") + boost::lexical_cast(id) + ")"); + throw not_found(std::string("Unknown structure_type (id=") + boost::lexical_cast(sid) + ")"); } return (*i).second; } + + // Get and set the default structure of the World + // The getter + virtual structure_id_type get_def_structure_id() const + { + return base_type::structures_.get_def_structure_id(); + } + // The setter + void set_def_structure(const boost::shared_ptr cuboidal_region) + { + const structure_id_type default_struct_id(get_def_structure_id()); + + // check that the structure_type is the default structure_type + if (!default_structure_type_id_ || + (cuboidal_region->sid() != default_structure_type_id_) ) + { + throw illegal_state("Default structure is not of default StructureType"); + } + if ( cuboidal_region->structure_id() != default_struct_id) + { + throw illegal_state("Default structure should have itself as parent."); + } + + // check that the structure_type that is defined in the structure exists! + //structure_type_type const& structure_type(get_structure_type(cuboidal_region->sid())); - structures_range get_structures() const + cuboidal_region->set_id(default_struct_id); + update_structure(std::make_pair(default_struct_id, cuboidal_region)); + } + + // Get all the particle ids of the particles on a structure + particle_id_set get_particle_ids_on_struct(structure_id_type const& struct_id) const { - return structures_range( - surface_iterator(structure_map_.begin(), surface_second_selector_type()), - surface_iterator(structure_map_.end(), surface_second_selector_type()), - structure_map_.size()); + typename per_structure_particle_id_set::const_iterator i( + particleonstruct_pool_.find(struct_id)); + if (i == particleonstruct_pool_.end()) + { + throw not_found(std::string("Unknown structure (id=") + boost::lexical_cast(struct_id) + ")"); + } + return (*i).second; } - particle_id_set get_particle_ids(species_id_type const& sid) const + + ////// StructureType stuff + // Add structureType + void add_structure_type(structure_type_type const& structure_type) { - typename per_species_particle_id_set::const_iterator i( - particle_pool_.find(sid)); - if (i == particle_pool_.end()) + structure_type_map_[structure_type.id()] = structure_type; + structure_pool_[structure_type.id()] = structure_id_set(); + } + // Get structureType by id + virtual structure_type_type get_structure_type(structure_type_id_type const& sid) const + { + typename structure_type_map::const_iterator i(structure_type_map_.find(sid)); + if (structure_type_map_.end() == i) { - throw not_found(std::string("Unknown species (id=") + boost::lexical_cast(sid) + ")"); + throw not_found(std::string("Unknown structure_type (id=") + boost::lexical_cast(sid) + ")"); } return (*i).second; } + // Get all structureTypes in the world + virtual structure_types_range get_structure_types() const + { + return structure_types_range( + structure_type_iterator(structure_type_map_.begin(), structure_types_second_selector_type()), + structure_type_iterator(structure_type_map_.end(), structure_types_second_selector_type()), + structure_type_map_.size()); + } + // Get and set the default structure_type of the world + virtual structure_type_id_type get_def_structure_type_id() const + { + if (!default_structure_type_id_) + { + throw not_found("Default structure_type is not defined."); + } + return default_structure_type_id_; + } + void set_def_structure_type_id(structure_type_id_type const& sid) + { + default_structure_type_id_ = sid; + } +///////////// Member variables private: - particle_id_generator pidgen_; - species_map species_map_; - structure_map structure_map_; - per_species_particle_id_set particle_pool_; + particle_id_generator pidgen_; // generator used to produce the unique ids for the particles + structure_id_generator structidgen_; + species_map species_map_; // mapping: species_id -> species + structure_type_map structure_type_map_;// mapping: structure_type_id -> structure_type + per_structure_type_structure_id_set structure_pool_; // mapping: structure_type_id -> set of structure ids of that structure type + per_species_particle_id_set particle_pool_; // mapping: species_id -> set of particle ids of that species + per_structure_particle_id_set particleonstruct_pool_; + + structure_id_type default_structure_id_; // The default structure of the World (is a CuboidalRegion of the size of the world) + structure_type_id_type default_structure_type_id_; // The default structure_type of the World (every structure must have a StructureType) }; #endif /* WORLD_HPP */ diff --git a/acinclude.m4 b/acinclude.m4 index 06192923..8e321e8a 100644 --- a/acinclude.m4 +++ b/acinclude.m4 @@ -126,6 +126,18 @@ AC_DEFUN([ECELL_CHECK_NUMPY_ARRAY_DESCR], [ ], [], [ #include #include -]) + ]) CPPFLAGS="$ac_save_CPPFLAGS" + dnl AC_DEFINE([NPY_NO_DEPRECATED_API], [7], [Source uses numpy deprecated api (known issue, hard to fix).]) +]) + +AC_DEFUN([ECELL_CHECK_LOGGING_MODULE], [ + AC_MSG_CHECKING([if classes in the Python's logging module is old-fashioned]) + AC_REQUIRE([AM_PATH_PYTHON]) + if "$PYTHON" -c "import sys, logging; sys.exit(type(logging.Handler) == type)"; then + AC_MSG_RESULT(yes) + AC_DEFINE([HAVE_OLD_FASHIONED_LOGGER_CLASSES], [1], [Defined to 1 if Python's logging module is old-fashioned]) + else + AC_MSG_RESULT(no) + fi ]) diff --git a/bd.py b/bd.py index 0cd91d22..78a2f16b 100644 --- a/bd.py +++ b/bd.py @@ -37,12 +37,12 @@ def calculate_bd_dt(species_list): class BDSimulatorCore(object): - ''' + """ BDSimulatorCore borrows the following from the main simulator: - species_list - reaction_types list (both 1 and 2) - ''' + """ def __init__(self, world, rng, network_rules, dissociation_retry_moves): self.world = world self.rng = rng @@ -82,7 +82,7 @@ def increment_reaction_events(rr): def dummy(shape, ignore0, ignore1=None): return True - ppg = _gfrd.BDPropagator(self.world, self.network_rules, + ppg = _gfrd.newBDPropagator(self.world, self.network_rules, self.rng, self.dt, self.dissociation_retry_moves, increment_reaction_events, dummy, self.world.particle_ids) ppg.propagate_all() @@ -94,7 +94,11 @@ def check(self): assert not self.tx.check_overlap(pp) class BDSimulator(ParticleSimulatorBase): - def __init__(self, world, rng, network_rules): + def __init__(self, world, rng, network_rules=None): + + if network_rules == None: + network_rules = NetworkRulesWrapper(world.model.network_rules) + ParticleSimulatorBase.__init__(self, world, rng, network_rules) self.is_dirty = True self.core = BDSimulatorCore(self.world, self.rng, self.network_rules, diff --git a/binding/BDPropagator.hpp b/binding/BDPropagator.hpp index 9dc027f9..6a68c2e5 100644 --- a/binding/BDPropagator.hpp +++ b/binding/BDPropagator.hpp @@ -9,28 +9,34 @@ namespace binding { +// Not sure what this does template static void BDPropagator_propagate_all(Timpl_& self) { while (self()); } + +////// Registering master function template inline boost::python::objects::class_base register_bd_propagator_class(char const* name) { using namespace boost::python; typedef Timpl_ impl_type; - typedef typename impl_type::traits_type simulator_traits_type; - typedef typename simulator_traits_type::world_type world_type; - typedef typename world_type::particle_id_type particle_id_type; - typedef typename simulator_traits_type::network_rules_type network_rules_type; - typedef typename world_type::traits_type::rng_type rng_type; - typedef typename simulator_traits_type::time_type time_type; - typedef typename world_type::particle_container_type particle_container_type; - typedef typename impl_type::reaction_recorder_type reaction_recorder_type; - typedef typename impl_type::volume_clearer_type volume_clearer_type; + typedef typename impl_type::traits_type simulator_traits_type; + typedef typename impl_type::reaction_recorder_type reaction_recorder_type; + typedef typename impl_type::volume_clearer_type volume_clearer_type; + typedef typename simulator_traits_type::world_type world_type; + typedef typename simulator_traits_type::network_rules_type network_rules_type; + typedef typename simulator_traits_type::time_type time_type; + typedef typename world_type::particle_id_type particle_id_type; + typedef typename world_type::particle_container_type particle_container_type; + typedef typename world_type::traits_type::rng_type rng_type; + + // registering range converter from standard boost template peer::converters::register_pyiterable_range_converter(); + return class_( name, init< particle_container_type&, network_rules_type const&, rng_type&, diff --git a/binding/BDSimulator.hpp b/binding/BDSimulator.hpp index edf654e0..c61fbd8e 100644 --- a/binding/BDSimulator.hpp +++ b/binding/BDSimulator.hpp @@ -10,12 +10,15 @@ namespace binding { + +////// Registering master function template void register_bd_simulator_class(char const* name) { using namespace boost::python; using boost::shared_ptr; typedef Timpl impl_type; + class_, boost::noncopyable>( name, init, @@ -27,6 +30,8 @@ void register_bd_simulator_class(char const* name) .def(init, boost::shared_ptr, typename impl_type::rng_type&, double, int>()) + .def("get_reaction_length", &impl_type::get_reaction_length) + .def("set_reaction_length_factor",&impl_type::set_reaction_length_factor) // .def("check", &impl_type::check) // .def("__len__", &impl_type::num_domains) // .def("__getitem__", &impl_type::get_domain) diff --git a/binding/Box.hpp b/binding/Box.hpp index 07a635d1..28757597 100644 --- a/binding/Box.hpp +++ b/binding/Box.hpp @@ -8,24 +8,29 @@ namespace binding { +// Not sure template static std::string Box___str__(Timpl_* impl) { return boost::lexical_cast(*impl); } + +////// Registering master function template inline boost::python::objects::class_base register_box_class(char const* name) { using namespace boost::python; typedef Timpl impl_type; + // registering converters from standard boost templates to_python_converter, peer::util::detail::to_ndarray_converter< boost::array > >(); peer::converters::register_iterable_to_ra_container_converter< boost::array, 3>(); + // defining the python class return class_(name) .def(init inline boost::python::objects::class_base register_cuboidal_region_class(char const *name) { @@ -13,7 +15,9 @@ inline boost::python::objects::class_base register_cuboidal_region_class(char co return class_, boost::shared_ptr, boost::noncopyable>( - name, init()) .add_property("shape", make_function((typename impl_type::shape_type const&(impl_type::*)()const)&impl_type::shape, @@ -21,6 +25,6 @@ inline boost::python::objects::class_base register_cuboidal_region_class(char co ; } -} // namespce binding +} // namespace binding #endif /* BINDING_CUBOIDAL_REGION_HPP */ diff --git a/binding/Cylinder.hpp b/binding/Cylinder.hpp index ca92bdd0..6572331d 100644 --- a/binding/Cylinder.hpp +++ b/binding/Cylinder.hpp @@ -6,12 +6,15 @@ namespace binding { +// A template to perform string conversions? template static std::string Cylinder___str__(Timpl_* impl) { return boost::lexical_cast(*impl); } + +////// Registering master function template inline boost::python::objects::class_base register_cylinder_class(char const* name) { diff --git a/binding/CylindricalPair.hpp b/binding/CylindricalPair.hpp index b9169cf1..a378d0c9 100644 --- a/binding/CylindricalPair.hpp +++ b/binding/CylindricalPair.hpp @@ -6,6 +6,8 @@ namespace binding { + +////// Registering master function template inline boost::python::objects::class_base register_cylindrical_pair_class(char const* name) { diff --git a/binding/CylindricalSingle.hpp b/binding/CylindricalSingle.hpp index 3c35bd3a..be8702ac 100644 --- a/binding/CylindricalSingle.hpp +++ b/binding/CylindricalSingle.hpp @@ -7,6 +7,8 @@ namespace binding { + +////// Registering master function template inline boost::python::objects::class_base register_cylindrical_single_class(char const* name) { diff --git a/binding/CylindricalSurface.hpp b/binding/CylindricalSurface.hpp index 77a2eb2d..d956b4e3 100644 --- a/binding/CylindricalSurface.hpp +++ b/binding/CylindricalSurface.hpp @@ -5,6 +5,8 @@ namespace binding { + +////// Registering master function template inline boost::python::objects::class_base register_cylindrical_surface_class(char const *name) { @@ -13,7 +15,9 @@ inline boost::python::objects::class_base register_cylindrical_surface_class(cha return class_, boost::shared_ptr, boost::noncopyable>( - name, init()) .add_property("shape", make_function((typename impl_type::shape_type const&(impl_type::*)()const)&impl_type::shape, return_value_policy())) diff --git a/binding/Disk.hpp b/binding/Disk.hpp new file mode 100644 index 00000000..65560922 --- /dev/null +++ b/binding/Disk.hpp @@ -0,0 +1,77 @@ +#ifndef BINDING_DISK_HPP +#define BINDING_DISK_HPP + +#include +#include "peer/utils.hpp" + +namespace binding { + +// A template to perform string conversions? +template +static std::string Disk___str__(Timpl_* impl) +{ + return boost::lexical_cast(*impl); +} + + +////// Registering master function +template +inline boost::python::objects::class_base register_disk_class(char const* name) +{ + using namespace boost::python; + typedef Timpl_ impl_type; + + return class_(name) + .def(init()) + .add_property("position", + make_function( + &peer::util::reference_accessor_wrapper< + impl_type, + typename impl_type::position_type, + &impl_type::position, + &impl_type::position>::get, + return_value_policy()), + make_function( + &peer::util::reference_accessor_wrapper< + impl_type, + typename impl_type::position_type, + &impl_type::position, + &impl_type::position>::set)) + .add_property("radius", + make_function( + &peer::util::reference_accessor_wrapper< + impl_type, + typename impl_type::length_type, + &impl_type::radius, + &impl_type::radius>::get, + return_value_policy()), + make_function( + &peer::util::reference_accessor_wrapper< + impl_type, + typename impl_type::length_type, + &impl_type::radius, + &impl_type::radius>::set)) + .add_property("unit_z", + make_function( + &peer::util::reference_accessor_wrapper< + impl_type, + typename impl_type::position_type, + &impl_type::unit_z, + &impl_type::unit_z>::get, + return_value_policy()), + make_function( + &peer::util::reference_accessor_wrapper< + impl_type, + typename impl_type::position_type, + &impl_type::unit_z, + &impl_type::unit_z>::set)) + .def("__str__", &Disk___str__) + .def("show", &impl_type::show); + +} + +} // namespace binding + +#endif /* BINDING_DISK_HPP */ diff --git a/binding/DiskSurface.hpp b/binding/DiskSurface.hpp new file mode 100644 index 00000000..56256282 --- /dev/null +++ b/binding/DiskSurface.hpp @@ -0,0 +1,35 @@ +#ifndef BINDING_DISK_SURFACE_HPP +#define BINDING_DISK_SURFACE_HPP + +#include + +namespace binding { + + +////// Registering master function +template +inline boost::python::objects::class_base register_disk_surface_class(char const *name) +{ + using namespace boost::python; + typedef Timpl impl_type; + + return class_, + boost::shared_ptr, boost::noncopyable>( + name, init()) + .add_property("shape", + make_function((typename impl_type::shape_type const&(impl_type::*)()const)&impl_type::shape, return_value_policy())) + .def("treat_as_barrier", &impl_type::treat_as_barrier) + .def("treat_as_sink", &impl_type::treat_as_sink) + .def("allow_radial_dissociation", &impl_type::allow_radial_dissociation) + .def("forbid_radial_dissociation", &impl_type::forbid_radial_dissociation) + .def("is_barrier", &impl_type::is_barrier, return_value_policy()) + .def("dissociates_radially", &impl_type::dissociates_radially, return_value_policy()) + ; +} + +} // namespace binding + +#endif /* BINDING_DISK_SURFACE_HPP */ diff --git a/binding/Domain.hpp b/binding/Domain.hpp index 71fa9020..6b565f45 100644 --- a/binding/Domain.hpp +++ b/binding/Domain.hpp @@ -6,6 +6,8 @@ namespace binding { + +////// Registering master function template inline boost::python::objects::class_base register_domain_class(char const* name) { diff --git a/binding/EGFRDSimulator.hpp b/binding/EGFRDSimulator.hpp index 2357316f..45c6e855 100644 --- a/binding/EGFRDSimulator.hpp +++ b/binding/EGFRDSimulator.hpp @@ -10,6 +10,9 @@ namespace binding { +//// Converters. These convert C++ types to something that Python can handle. + +// template struct shell_variant_converter { @@ -41,16 +44,20 @@ struct shell_variant_converter return boost::python::incref(boost::apply_visitor(visitor(), val).ptr()); } + // Also need to register the converter. static void __register() { boost::python::to_python_converter(); } }; + +////// Registering master function template void register_egfrd_simulator_class(char const* name) { using namespace boost::python; + // typedefs typedef Timpl impl_type; typedef std::pair get_shell_result_type; enum_("DomainKind") @@ -78,6 +85,8 @@ void register_egfrd_simulator_class(char const* name) .value("ESCAPE", impl_type::multi_type::ESCAPE) .value("REACTION", impl_type::multi_type::REACTION) ; + + // defining the python class class_, boost::noncopyable>( name, init, @@ -105,6 +114,7 @@ void register_egfrd_simulator_class(char const* name) return_value_policy()) ; + // register wrappers and converters peer::wrappers::generator_wrapper< ptr_generator inline boost::python::object register_event_class(char const* name) { diff --git a/binding/EventScheduler.hpp b/binding/EventScheduler.hpp index c0e92088..a85e7e8d 100644 --- a/binding/EventScheduler.hpp +++ b/binding/EventScheduler.hpp @@ -7,6 +7,8 @@ namespace binding { + +////// Registering master function template inline boost::python::objects::class_base register_event_scheduler_class(char const* name) @@ -14,6 +16,7 @@ register_event_scheduler_class(char const* name) using namespace boost::python; typedef Timpl impl_type; + // registering converters peer::converters::register_tuple_converter< typename impl_type::value_type>(); @@ -23,6 +26,7 @@ register_event_scheduler_class(char const* name) peer::converters::register_tuple_converter< typename impl_type::value_type>(); + // defining the python class return class_(name) .add_property("time", &impl_type::time) .add_property("top", diff --git a/binding/LogAppender.cpp b/binding/LogAppender.cpp index a980ac17..18bee108 100644 --- a/binding/LogAppender.cpp +++ b/binding/LogAppender.cpp @@ -33,12 +33,15 @@ static void log_appender_call(Timpl& self, enum Logger::level lv, self(lv, name, &chunks.front()); } + +////// Registering master function boost::python::objects::class_base register_log_appender_class(char const* name) { using namespace boost::python; typedef LogAppender impl_type; + // defining the python class return class_, boost::noncopyable>(name, no_init) .def("flush", &impl_type::flush) .def("__call__", &log_appender_call) diff --git a/binding/Logger.cpp b/binding/Logger.cpp index 792acbb2..d88f1fc7 100644 --- a/binding/Logger.cpp +++ b/binding/Logger.cpp @@ -8,12 +8,15 @@ namespace binding { + +////// Registering master function boost::python::objects::class_base register_logger_class(char const* name) { using namespace boost::python; typedef Logger impl_type; + // defining the python class return class_(name, no_init) .add_property("level", static_cast( diff --git a/binding/LoggerManager.cpp b/binding/LoggerManager.cpp index 70b75d8c..921fc19f 100644 --- a/binding/LoggerManager.cpp +++ b/binding/LoggerManager.cpp @@ -8,6 +8,7 @@ namespace binding { +// defining the loglevel boost::python::objects::enum_base register_logger_level_enum(char const* name) { @@ -22,6 +23,8 @@ register_logger_level_enum(char const* name) ; } + +////// Registering master function boost::python::objects::class_base register_logger_manager_class(char const* name) { @@ -31,6 +34,7 @@ register_logger_manager_class(char const* name) peer::converters::register_range_to_tuple_converter< std::vector > >(); + // defining the python class return class_, boost::noncopyable>(name, no_init) .add_property("level", static_cast(&impl_type::level), diff --git a/binding/Makefile.am b/binding/Makefile.am index e048eb4d..a2261390 100644 --- a/binding/Makefile.am +++ b/binding/Makefile.am @@ -1,15 +1,15 @@ NUMPY_INCLUDE_DIR = @NUMPY_INCLUDE_DIR@ PYTHON_INCLUDES = @PYTHON_INCLUDES@ -INCLUDES = ${PYTHON_INCLUDES} -I${NUMPY_INCLUDE_DIR} -AM_CXXFLAGS = @CXXFLAGS@ @GSL_CFLAGS@ -I.. -Wall -g -Wstrict-aliasing=0 -Wno-invalid-offsetof -AM_CPPFLAGS = @CPPFLAGS@ -DNO_IMPORT +AM_CXXFLAGS = -DNO_IMPORT -I.. @BOOST_CPPFLAGS@ @GSL_CFLAGS@ ${PYTHON_INCLUDES} -I${NUMPY_INCLUDE_DIR} noinst_LTLIBRARIES = libbinding_utils.la noinst_HEADERS = \ - bd_propagator_class.hpp \ + bd_propagator_class.hpp \ + new_bd_propagator_class.hpp \ BDPropagator.hpp \ + newBDPropagator.hpp \ binding_common.hpp \ box_class.hpp \ Box.hpp \ @@ -18,6 +18,9 @@ noinst_HEADERS = \ cylinder_class.hpp \ Cylinder.hpp \ CylindricalSurface.hpp \ + disk_class.hpp \ + Disk.hpp \ + DiskSurface.hpp \ domain_classes.hpp \ domain_id_class.hpp \ egfrd_simulator_classes.hpp \ @@ -81,6 +84,7 @@ noinst_HEADERS = \ Sphere.hpp \ SphericalSurface.hpp \ structure_classes.hpp \ + structure_id_class.hpp \ structure_type_class.hpp \ StructureType.hpp \ transaction_classes.hpp \ @@ -90,9 +94,11 @@ noinst_HEADERS = \ libbinding_utils_la_SOURCES = \ bd_propagator_class.cpp \ + new_bd_propagator_class.cpp \ box_class.cpp \ ConsoleAppender.cpp \ cylinder_class.cpp \ + disk_class.cpp \ domain_classes.cpp \ domain_id_class.cpp \ egfrd_simulator_classes.cpp \ @@ -127,6 +133,7 @@ libbinding_utils_la_SOURCES = \ species_type_class.cpp \ sphere_class.cpp \ structure_classes.cpp \ + structure_id_class.cpp \ structure_type_class.cpp \ transaction_classes.cpp \ world_class.cpp diff --git a/binding/MatrixSpace.hpp b/binding/MatrixSpace.hpp index 3df6894d..a2681cf5 100644 --- a/binding/MatrixSpace.hpp +++ b/binding/MatrixSpace.hpp @@ -10,6 +10,8 @@ #include #include +#include + #include #include #include @@ -41,6 +43,8 @@ namespace binding { + + struct MatrixSpaceExtrasBase { template @@ -76,7 +80,7 @@ struct MatrixSpaceExtrasBase { static PyObject* convert(const result_type& val) { - const npy_intp dims[1] = { val.size() }; + const npy_intp dims[1] = { static_cast(val.size()) }; boost::python::incref(reinterpret_cast(result_type_descr_)); PyObject* retval = PyArray_NewFromDescr(&PyArray_Type, result_type_descr_, @@ -372,6 +376,7 @@ class MatrixSpaceExtras: public MatrixSpaceExtrasBase // take over the ownership of the arrays to the Numpy facility alloc.giveup_ownership(); + return retval; } @@ -427,6 +432,8 @@ class MatrixSpaceExtras: public MatrixSpaceExtrasBase } }; + +////// Registering master function template inline void register_matrix_space_class(char const* class_name) { @@ -434,6 +441,7 @@ inline void register_matrix_space_class(char const* class_name) typedef MatrixSpaceExtras extras_type; using namespace boost::python; + // register converters from standard boost templates extras_type::Builders::__register_converter(); std::string const _class_name(class_name); @@ -446,6 +454,7 @@ inline void register_matrix_space_class(char const* class_name) typename extras_type::key_iterator, boost::python::object>::__class_init__((_class_name + ".keyiterator").c_str()); + // defining the python class for the matrix space class_(class_name, init()) diff --git a/binding/Model.hpp b/binding/Model.hpp index 7de293f4..d3c64202 100644 --- a/binding/Model.hpp +++ b/binding/Model.hpp @@ -13,6 +13,42 @@ static void Model___setitem__(Timpl* model, std::string const& key, std::string (*model)[key] = value; } + +template +struct rfr_attr +{ + typedef Tmodel_ impl_type; + typedef typename boost::remove_reference::type range_type; + typedef typename boost::range_const_iterator::type result_type; + + typedef peer::wrappers::stl_iterator_wrapper wrapper_type; + + static PyObject* create_wrapper(boost::python::back_reference backref) + { + wrapper_type::__class_init__(typeid(result_type).name()); + return wrapper_type::create(backref.get().attributes(), backref.source()); + } +}; + +template +struct rfr_species +{ + typedef Tmodel_ impl_type; + typedef typename boost::remove_reference::type range_type; + typedef typename boost::range_const_iterator::type result_type; + + typedef peer::wrappers::stl_iterator_wrapper wrapper_type; + + static PyObject* create_wrapper(boost::python::back_reference backref) + { + wrapper_type::__class_init__(typeid(result_type).name()); + return wrapper_type::create(backref.get().get_species_types(), backref.source()); + } +}; + + + +////// Registering master function template inline void register_model_class(char const* name) { @@ -22,6 +58,7 @@ inline void register_model_class(char const* name) docstring_options doc_options; doc_options.disable_signatures(); + // defining the python class class_(name) .add_property("network_rules", make_function(&impl_type::network_rules, @@ -32,16 +69,13 @@ inline void register_model_class(char const* name) &impl_type::operator[], return_value_policy()) .def("__setitem__", &Model___setitem__) .add_property("attributes", - peer::util::range_from_range< - typename impl_type::attributes_range, - impl_type, &impl_type::attributes>()) + boost::python::make_function(&rfr_attr::create_wrapper)) .add_property("species_types", - peer::util::range_from_range< - typename impl_type::species_type_range, - impl_type, &impl_type::get_species_types>()) - ; + boost::python::make_function(&rfr_species::create_wrapper)) + ; } } // namespace binding #endif /* BINDING_MODEL_HPP */ + diff --git a/binding/Multi.hpp b/binding/Multi.hpp index 0914c9fb..33216bc4 100644 --- a/binding/Multi.hpp +++ b/binding/Multi.hpp @@ -7,6 +7,8 @@ namespace binding { + +////// Registering master function template inline boost::python::objects::class_base register_multi_class(char const* name) { diff --git a/binding/MultiParticleContainer.hpp b/binding/MultiParticleContainer.hpp index d27c8da8..f04ee4c6 100644 --- a/binding/MultiParticleContainer.hpp +++ b/binding/MultiParticleContainer.hpp @@ -2,16 +2,28 @@ #define BINDING_MULTI_PARTICLE_CONTAINER_HPP #include +#include + +#include "peer/converters/tuple.hpp" namespace binding { + +////// Registering master function template boost::python::objects::class_base register_multi_particle_container_class(char const* name) { using namespace boost::python; + typedef Timpl impl_type; + + // registering converters from standard boost templates + peer::converters::register_tuple_converter(); - return class_, - boost::noncopyable>(name, init()); + // defining the python class + return class_, + boost::noncopyable>(name, init()) + .def("determine_dt_and_reaction_length",&impl_type::determine_dt_and_reaction_length) + ; } } // namespace binding diff --git a/binding/NetworkRules.hpp b/binding/NetworkRules.hpp index 43850619..ae33810a 100644 --- a/binding/NetworkRules.hpp +++ b/binding/NetworkRules.hpp @@ -5,6 +5,8 @@ namespace binding { + +////// Registering master function template boost::python::objects::class_base register_network_rules_class(char const* name) { diff --git a/binding/NetworkRulesWrapper.hpp b/binding/NetworkRulesWrapper.hpp index 83768423..84114349 100644 --- a/binding/NetworkRulesWrapper.hpp +++ b/binding/NetworkRulesWrapper.hpp @@ -10,6 +10,7 @@ namespace binding { +////// Registering master function template boost::python::objects::class_base register_network_rules_wrapper_class(char const* name) { diff --git a/binding/Pair.hpp b/binding/Pair.hpp index d594ebee..9e8c859b 100644 --- a/binding/Pair.hpp +++ b/binding/Pair.hpp @@ -6,15 +6,19 @@ namespace binding { + +////// Registering master function template inline boost::python::objects::class_base register_pair_class(char const* name) { using namespace boost::python; typedef Timpl impl_type; + // converters from standard boost templates. peer::converters::register_range_to_tuple_converter< typename impl_type::particle_array_type>(); + // defining the python class return class_, boost::shared_ptr, boost::noncopyable>(name, no_init) .add_property("particles", diff --git a/binding/Particle.hpp b/binding/Particle.hpp index d8aff859..82718b27 100644 --- a/binding/Particle.hpp +++ b/binding/Particle.hpp @@ -35,6 +35,9 @@ namespace binding { template class ParticleWrapper { +// This is a class that wraps Timpl (the Particle class) such that Python can handle it. +// Timpl_ is the implemented class (Particle in this case) + public: struct to_python_converter { @@ -95,16 +98,17 @@ class ParticleWrapper return reinterpret_cast(new ParticleWrapper(impl)); } + // Python constructor method? static PyObject* __new__(PyTypeObject* klass, PyObject* arg, PyObject* kwarg) { PyObject* retval = NULL; switch (PyTuple_Size(arg)) { default: - PyErr_SetString(PyExc_TypeError, "the number of arguments must be either 0 or 4"); + PyErr_SetString(PyExc_TypeError, "the number of arguments must be either 0 or 6"); return NULL; - case 4: + case 6: retval = create(); if (set_position(reinterpret_cast(retval), PyTuple_GetItem(arg, 0), 0) @@ -112,8 +116,12 @@ class ParticleWrapper PyTuple_GetItem(arg, 1), 0) || set_D(reinterpret_cast(retval), PyTuple_GetItem(arg, 2), 0) + || set_v(reinterpret_cast(retval), + PyTuple_GetItem(arg, 3), 0) || set_sid(reinterpret_cast(retval), - PyTuple_GetItem(arg, 3), 0)) + PyTuple_GetItem(arg, 4), 0) + || set_structure_id(reinterpret_cast(retval), + PyTuple_GetItem(arg, 5), 0)) { ParticleWrapper::operator delete(retval); return NULL; @@ -144,15 +152,17 @@ class ParticleWrapper return __str__(self); } + // Python destructor method? static void __dealloc__(ParticleWrapper* self) { delete self; } + // Methods used to implement the getters/setters for Python (I think) static PyObject* get_position(ParticleWrapper* self) { typename Timpl_::position_type const& pos(self->impl_.position()); - const npy_intp dims[1] = { boost::size(pos) }; + const npy_intp dims[1] = { static_cast(boost::size(pos)) }; PyObject* retval = PyArray_New(&PyArray_Type, 1, const_cast(dims), peer::util::get_numpy_typecode::value, @@ -232,6 +242,20 @@ class ParticleWrapper return 0; } + static PyObject* get_v(ParticleWrapper* self) + { + return PyFloat_FromDouble(self->impl_.v()); + } + + static int set_v(ParticleWrapper* self, PyObject* val, void *) + { + const double tmp(PyFloat_AsDouble(val)); + if (PyErr_Occurred()) + return -1; + self->impl_.v() = tmp; + return 0; + } + static PyObject* get_sid(ParticleWrapper* self) { return boost::python::incref( @@ -249,12 +273,31 @@ class ParticleWrapper return -1; } + // Getter and setter for the structure_id, the id of the structure that the particle lives on. + static PyObject* get_structure_id(ParticleWrapper* self) + { + return boost::python::incref( + boost::python::object(self->impl_.structure_id()).ptr()); + } + + static int set_structure_id(ParticleWrapper* self, PyObject* val, void *) + try + { + self->impl_.structure_id() = boost::python::extract(val); + return 0; + } + catch (boost::python::error_already_set const&) + { + return -1; + } + + static PyObject* __getstate__(ParticleWrapper* self) try { return boost::python::incref( boost::python::make_tuple( - boost::python::borrowed(get_position(self)), + boost::python::borrowed(get_position(self)), // why here only position, radius and sid?? boost::python::borrowed(get_radius(self)), boost::python::borrowed(get_sid(self))).ptr()); } @@ -286,13 +329,14 @@ class ParticleWrapper } static Py_ssize_t __sq_len__(PyObject *self) + // Give the size of the object (6 fields/properties) { - return 4; + return 6; // I think this is the number of elements (and not the max index) } static PyObject* __sq_item__(PyObject *self, Py_ssize_t idx) { - if (idx < 0 || idx >= 4) + if (idx < 0 || idx >= 6) { PyErr_SetString(PyExc_IndexError, "index out of range"); return NULL; @@ -306,15 +350,19 @@ class ParticleWrapper return get_radius(reinterpret_cast(self)); case 2: return get_D(reinterpret_cast(self)); - case 3: + case 3: + return get_v(reinterpret_cast(self)); + case 4: return get_sid(reinterpret_cast(self)); + case 5: + return get_structure_id(reinterpret_cast(self)); } return NULL; // never get here } static int __sq_ass_item__(PyObject *self, Py_ssize_t idx, PyObject *val) { - if (idx < 0 || idx >= 4) + if (idx < 0 || idx >= 6) { PyErr_SetString(PyExc_IndexError, "index out of range"); return -1; @@ -328,8 +376,12 @@ class ParticleWrapper return set_radius(reinterpret_cast(self), val, 0); case 2: return set_D(reinterpret_cast(self), val, 0); - case 3: + case 3: + return set_v(reinterpret_cast(self), val, 0); + case 4: return set_sid(reinterpret_cast(self), val, 0); + case 5: + return set_structure_id(reinterpret_cast(self), val, 0); } return -1; // never get here @@ -405,6 +457,7 @@ PyMethodDef ParticleWrapper::__methods__[] = { template PyGetSetDef ParticleWrapper::__getsets__[] = { +// Defines the list of getters and setters for Python? { const_cast("position"), (getter)ParticleWrapper::get_position, @@ -429,12 +482,24 @@ PyGetSetDef ParticleWrapper::__getsets__[] = { (setter)ParticleWrapper::set_D, const_cast("") }, + { + const_cast("v"), + (getter)ParticleWrapper::get_v, + (setter)ParticleWrapper::set_v, + const_cast("") + }, { const_cast("sid"), (getter)ParticleWrapper::get_sid, (setter)ParticleWrapper::set_sid, const_cast("") }, + { + const_cast("structure_id"), + (getter)ParticleWrapper::get_structure_id, + (setter)ParticleWrapper::set_structure_id, + const_cast("") + }, { NULL } }; diff --git a/binding/ParticleContainer.hpp b/binding/ParticleContainer.hpp index 6c1ef59e..d8565b09 100644 --- a/binding/ParticleContainer.hpp +++ b/binding/ParticleContainer.hpp @@ -1,11 +1,24 @@ #ifndef BINDING_PARTICLE_CONTAINER_HPP #define BINDING_PARTICLE_CONTAINER_HPP +#include +#include + #include #include #include #include +#include +#include +#include +#include +#include + #include "../exceptions.hpp" +#include "../utils/get_default_impl.hpp" +#include "../generator.hpp" +#include "../utils/pair.hpp" + #include "peer/compat.h" #include "peer/wrappers/generator/pyiterator_generator.hpp" #include "peer/wrappers/generator/generator_wrapper.hpp" @@ -14,6 +27,9 @@ namespace binding { +//// Converters. These convert C++ types to something that Python can handle. + +// template struct particle_id_pair_generator_converter { @@ -39,6 +55,7 @@ struct particle_id_pair_generator_converter } }; + // Also need to register the converter. static void __register() { peer::wrappers::generator_wrapper< @@ -54,6 +71,47 @@ struct particle_id_pair_generator_converter } }; +template +struct structure_id_pair_generator_converter +{ + typedef Timpl_ native_type; + + struct to_native_converter + { + static void* convert(PyObject* ptr) + { + boost::python::handle<> iter( + boost::python::allow_null(PyObject_GetIter(ptr))); + if (!iter) + { + boost::python::throw_error_already_set(); + } + return new peer::wrappers::pyiterator_generator< + typename native_type::result_type>(iter); + } + + static PyTypeObject const* expected_pytype() + { + return &PyBaseObject_Type; + } + }; + + // Also need to register the converter. + static void __register() + { + peer::wrappers::generator_wrapper< + ptr_generator > > + ::__register_class("StructureIDPairGenerator"); + boost::python::to_python_converter< + native_type*, + peer::converters::ptr_generator_to_pyiterator_converter< + native_type, std::auto_ptr > >(); + + + peer::util::to_native_lvalue_converter(); + } +}; + template struct particle_id_pair_and_distance_list_converter { @@ -151,6 +209,7 @@ struct particle_id_pair_and_distance_list_converter } }; + // Also need to register the converter. static void __register() { to_python_converter::wrapper_type::__class_init__("ParticleIDAndDistanceVector", boost::python::scope().ptr()); @@ -159,6 +218,114 @@ struct particle_id_pair_and_distance_list_converter } }; +template +struct structure_id_pair_and_distance_list_converter +{ + typedef Timpl_ native_type; + + struct to_python_converter + { + template + struct policy + { + typedef typename boost::range_size::type size_type; + typedef typename boost::range_value::type value_type; + typedef value_type const& reference; + typedef value_type const& const_reference; + typedef typename boost::range_const_iterator::type iterator; + typedef typename boost::range_const_iterator::type const_iterator; + + static size_type size(T_ const& c) + { + return ::size(c); + } + + static void set(T_& c, size_type i, const_reference v) + { + c.set(i, v); + } + + static reference get(T_ const& c, size_type i) + { + return c.at(i); + } + + static iterator begin(T_ const& c) + { + return boost::begin(c); + } + + static iterator end(T_ const& c) + { + return boost::end(c); + } + }; + + typedef peer::wrappers::stl_container_wrapper, peer::wrappers::default_policy_generator > wrapper_type; + static PyObject* convert(native_type* v) + { + return reinterpret_cast(wrapper_type::create(std::auto_ptr(v ? v: new native_type()))); + } + }; + + struct to_native_converter + { + static PyTypeObject const* expected_pytype() + { + return &PyBaseObject_Type; + } + + static void* convert(PyObject* pyo) + { + if (pyo == Py_None) + { + return 0; + } + + Py_ssize_t hint(-1); + hint = PyObject_Size(pyo); + if (hint < 0) + { + PyErr_Clear(); + } + + boost::python::handle<> iter(PyObject_GetIter(pyo)); + if (!iter) + { + boost::python::throw_error_already_set(); + } + + if (hint < 0) + { + hint = compat_PyObject_LengthHint(iter.get()); + if (hint < 0) + { + hint = 0; + } + } + + native_type* obj(new native_type); + obj->reserve(hint); + while (PyObject* item = PyIter_Next(iter.get())) + { + obj->push_back(boost::python::extract(item)()); + } + + return obj; + } + }; + + // Also need to register the converter. + static void __register() + { + to_python_converter::wrapper_type::__class_init__("StructureIDAndDistanceVector", boost::python::scope().ptr()); + boost::python::to_python_converter(); + peer::util::to_native_lvalue_converter(); + } +}; + + +// ParticleContainer wrapper class template class ParticleContainerWrapper : public Tbase_, public boost::python::wrapper @@ -166,21 +333,42 @@ class ParticleContainerWrapper public: typedef boost::python::wrapper py_wrapper_type; typedef Tbase_ wrapped_type; - typedef typename wrapped_type::size_type size_type; - typedef typename wrapped_type::length_type length_type; - typedef typename wrapped_type::particle_id_type particle_id_type; - typedef typename wrapped_type::particle_shape_type particle_shape_type; - typedef typename wrapped_type::position_type position_type; - typedef typename wrapped_type::species_id_type species_id_type; - typedef typename wrapped_type::species_type species_type; - typedef typename wrapped_type::structure_id_type structure_id_type; - typedef typename wrapped_type::structure_type structure_type; - typedef typename wrapped_type::particle_id_pair particle_id_pair; - typedef typename wrapped_type::transaction_type transaction_type; - typedef typename wrapped_type::particle_id_pair_generator particle_id_pair_generator; - typedef typename wrapped_type::particle_id_pair_and_distance_list particle_id_pair_and_distance_list; - -public: + // Get types that are defined in the ParticleContainer class that we are wrapping. + typedef typename wrapped_type::size_type size_type; + typedef typename wrapped_type::length_type length_type; + typedef typename wrapped_type::particle_id_type particle_id_type; + typedef typename wrapped_type::particle_shape_type particle_shape_type; + typedef typename wrapped_type::position_type position_type; + typedef typename wrapped_type::species_type species_type; + typedef typename wrapped_type::species_id_type species_id_type; + typedef typename wrapped_type::structure_type structure_type; + typedef typename wrapped_type::structure_id_type structure_id_type; + typedef typename wrapped_type::structure_type_type structure_type_type; + typedef typename wrapped_type::structure_type_id_type structure_type_id_type; + + typedef typename wrapped_type::particle_id_pair particle_id_pair; + typedef typename wrapped_type::structure_id_pair structure_id_pair; + typedef typename wrapped_type::particle_id_pair_generator particle_id_pair_generator; + typedef typename wrapped_type::structure_id_pair_generator structure_id_pair_generator; + typedef typename wrapped_type::particle_id_pair_and_distance_list particle_id_pair_and_distance_list; + typedef typename wrapped_type::structure_id_pair_and_distance_list structure_id_pair_and_distance_list; + typedef typename wrapped_type::position_structid_pair_type position_structid_pair_type; + typedef typename wrapped_type::structure_id_set structure_id_set; + typedef typename wrapped_type::structures_range structures_range; + typedef typename wrapped_type::structure_types_range structure_types_range; + +// typedef typename wrapped_type::cuboidal_region_id_pair_type cuboidal_region_id_pair_type; +// typedef typename wrapped_type::planar_surface_id_pair_type planar_surface_id_pair_type; +// typedef typename wrapped_type::cylindrsurf_id_pair_type cylindrsurf_id_pair_type; +// typedef typename wrapped_type::disk_surface_id_pair_type disk_surface_id_pair_type; +// typedef typename wrapped_type::spherical_surface_id_pair_type spherical_surface_id_pair_type; + + + typedef typename wrapped_type::transaction_type transaction_type; + +public: + + // virtual ~ParticleContainerWrapper() {} virtual size_type num_particles() const @@ -208,15 +396,100 @@ class ParticleContainerWrapper return py_wrapper_type::get_override("get_species")(id).template unchecked(); } + // structure stuff virtual boost::shared_ptr get_structure(structure_id_type const& id) const { return py_wrapper_type::get_override("get_structure")(id); } + + virtual structures_range get_structures() const + { + return py_wrapper_type::get_override("get_structures")(); + } + + virtual boost::shared_ptr get_some_structure_of_type(structure_type_id_type const& sid) const + { + return py_wrapper_type::get_override("get_some_structure_of_type")(sid); + } +/* + virtual bool update_structure(cuboidal_region_id_pair_type const& structid_pair) + { + return py_wrapper_type::get_override("update_structure")(structid_pair); + } + + virtual bool update_structure(planar_surface_id_pair_type const& structid_pair) + { + return py_wrapper_type::get_override("update_structure")(structid_pair); + } + + virtual bool update_structure(cylindrsurf_id_pair_type const& structid_pair) + { + return py_wrapper_type::get_override("update_structure")(structid_pair); + } + + virtual bool update_structure(disk_surface_id_pair_type const& structid_pair) + { + return py_wrapper_type::get_override("update_structure")(structid_pair); + } + + virtual bool update_structure(spherical_surface_id_pair_type const& structid_pair) + { + return py_wrapper_type::get_override("update_structure")(structid_pair); + } + + template + bool update_structure(Tstructid_pair_ const& structid_pair) + { + return py_wrapper_type::get_override("update_structure")(structid_pair); + } +*/ + + virtual bool remove_structure(structure_id_type const& id) + { + return py_wrapper_type::get_override("remove_structure")(id); + } + + virtual structure_id_set get_structure_ids(structure_type_id_type const& sid) const + { + return py_wrapper_type::get_override("get_structure_ids")(sid); + } + + virtual structure_id_type get_def_structure_id() const + { + return py_wrapper_type::get_override("get_def_structure_id")(); + } + + virtual structure_id_pair_and_distance_list* get_close_structures(position_type const& pos, structure_id_type const& current_struct_id, + structure_id_type const& ignore) const + { + return py_wrapper_type::get_override("get_close_structures")( + pos, current_struct_id, boost::python::make_tuple(ignore)) + .template unchecked(); + } + + // end structure stuff + + // Begin StructureType stuff + virtual structure_type_type get_structure_type(structure_type_id_type const& sid) const + { + return py_wrapper_type::get_override("get_structure_type")(sid); + } + + virtual structure_types_range get_structure_types() const + { + return py_wrapper_type::get_override("get_structure_types")(); + } + + virtual structure_type_id_type get_def_structure_type_id() const + { + return py_wrapper_type::get_override("get_def_structure_type_id")(); + } + // end StructureType stuff virtual particle_id_pair new_particle(species_id_type const& sid, - position_type const& pos) + structure_id_type const& structure_id, position_type const& pos) { - return py_wrapper_type::get_override("new_particle")(sid, pos); + return py_wrapper_type::get_override("new_particle")(sid, structure_id, pos); } virtual bool update_particle(particle_id_pair const& pi_pair) @@ -266,6 +539,30 @@ class ParticleContainerWrapper .template unchecked(); } + virtual structure_id_pair_and_distance_list* check_surface_overlap(particle_shape_type const& s, position_type const& old_pos, structure_id_type const& current, + length_type const& sigma) const + { + return py_wrapper_type::get_override("check_surface_overlap")( + s, old_pos, current, sigma) + .template unchecked(); + } + + virtual structure_id_pair_and_distance_list* check_surface_overlap(particle_shape_type const& s, position_type const& old_pos, structure_id_type const& current, + length_type const& sigma, structure_id_type const& ignore) const + { + return py_wrapper_type::get_override("check_surface_overlap")( + s, old_pos, current, sigma, ignore) + .template unchecked(); + } + + virtual structure_id_pair_and_distance_list* check_surface_overlap(particle_shape_type const& s, position_type const& old_pos, structure_id_type const& current, + length_type const& sigma, structure_id_type const& ignore1, structure_id_type const& ignore2) const + { + return py_wrapper_type::get_override("check_surface_overlap")( + s, old_pos, current, sigma, ignore1, ignore2) + .template unchecked(); + } + virtual particle_id_pair_generator* get_particles() const { return py_wrapper_type::get_override("__iter__")() @@ -294,6 +591,11 @@ class ParticleContainerWrapper return py_wrapper_type::get_override("apply_boundary")(v); } + virtual position_structid_pair_type apply_boundary(position_structid_pair_type const& pos_struct_id) const + { + return py_wrapper_type::get_override("apply_boundary")(pos_struct_id); + } + virtual position_type cyclic_transpose(position_type const& p0, position_type const& p1) const { return py_wrapper_type::get_override("cyclic_transpose")(p0, p1); @@ -303,42 +605,83 @@ class ParticleContainerWrapper { return py_wrapper_type::get_override("cyclic_transpose")(p0, p1); } + + virtual position_structid_pair_type cyclic_transpose(position_structid_pair_type const& pos_struct_id, + structure_type const& structure) const + { + return py_wrapper_type::get_override("cyclic_transpose")(pos_struct_id, structure); + } + }; +////// Registering master function template inline boost::python::objects::class_base register_particle_container_class( char const *name) { using namespace boost::python; typedef Timpl impl_type; + + // registering converters from standard boost templates. peer::converters::register_tuple_converter(); + peer::converters::register_tuple_converter(); peer::converters::register_tuple_converter(); + peer::converters::register_tuple_converter(); + peer::converters::register_tuple_converter(); + // register the converters that are defined above (not sure what this does) particle_id_pair_and_distance_list_converter::__register(); particle_id_pair_generator_converter::__register(); + structure_id_pair_and_distance_list_converter::__register(); + structure_id_pair_generator_converter::__register(); + // defining the python class return class_, boost::noncopyable>(name) .add_property("num_particles", &impl_type::num_particles) .add_property("world_size", &impl_type::world_size) + // Species stuff .def("get_species", pure_virtual((typename impl_type::species_type const&(impl_type::*)(typename impl_type::species_id_type const&) const)&impl_type::get_species), return_internal_reference<>()) + // StructureType stuff + .def("get_structure_type", pure_virtual(&impl_type::get_structure_type)) + .def("get_structure_types", pure_virtual(&impl_type::get_structure_types)) + .def("get_def_structure_type_id", pure_virtual(&impl_type::get_def_structure_type_id)) + // Structure stuff .def("get_structure", pure_virtual(&impl_type::get_structure)) + .def("get_structures", pure_virtual(&impl_type::get_structures)) + .def("get_some_structure_of_type", pure_virtual(&impl_type::get_some_structure_of_type)) +// .def("update_structure", pure_virtual(&impl_type::update_structure)) +// .def("update_structure", &impl_type::template update_structure) +// .def("update_structure", &impl_type::template update_structure) +// .def("update_structure", &impl_type::template update_structure) +// .def("update_structure", &impl_type::template update_structure) +// .def("update_structure", &impl_type::template update_structure) + .def("remove_structure", pure_virtual(&impl_type::remove_structure)) + .def("get_structure_ids", pure_virtual(&impl_type::get_structure_ids)) + .def("get_def_structure_id", pure_virtual(&impl_type::get_def_structure_id)) + .def("get_close_structures", pure_virtual((typename impl_type::structure_id_pair_and_distance_list*(impl_type::*)(typename impl_type::position_type const& pos, typename impl_type::structure_id_type const&, typename impl_type::structure_id_type const&) const)&impl_type::get_close_structures), return_value_policy()) + // Particle stuff .def("new_particle", pure_virtual(&impl_type::new_particle)) .def("update_particle", pure_virtual(&impl_type::update_particle)) .def("remove_particle", pure_virtual(&impl_type::remove_particle)) .def("get_particle", pure_virtual(&impl_type::get_particle)) .def("check_overlap", pure_virtual((typename impl_type::particle_id_pair_and_distance_list*(impl_type::*)(typename impl_type::particle_type::shape_type const&, typename impl_type::particle_id_type const&) const)&impl_type::check_overlap), return_value_policy()) .def("check_overlap", pure_virtual((typename impl_type::particle_id_pair_and_distance_list*(impl_type::*)(typename impl_type::particle_type::shape_type const&, typename impl_type::particle_id_type const&, typename impl_type::particle_id_type const&) const)&impl_type::check_overlap), return_value_policy()) - .def("check_overlap", pure_virtual((typename impl_type::particle_id_pair_and_distance_list*(impl_type::*)(typename impl_type::particle_type::shape_type const&) const)&impl_type::check_overlap), return_value_policy()) + .def("check_overlap", pure_virtual((typename impl_type::particle_id_pair_and_distance_list*(impl_type::*)(typename impl_type::particle_type::shape_type const&) const)&impl_type::check_overlap), return_value_policy()) + .def("check_surface_overlap", pure_virtual((typename impl_type::structure_id_pair_and_distance_list*(impl_type::*)(typename impl_type::particle_type::shape_type const&, typename impl_type::position_type const&, typename impl_type::structure_id_type const&, typename impl_type::length_type const&) const)&impl_type::check_surface_overlap), return_value_policy()) + .def("check_surface_overlap", pure_virtual((typename impl_type::structure_id_pair_and_distance_list*(impl_type::*)(typename impl_type::particle_type::shape_type const&, typename impl_type::position_type const&, typename impl_type::structure_id_type const&, typename impl_type::length_type const&, typename impl_type::structure_id_type const&) const)&impl_type::check_surface_overlap), return_value_policy()) + .def("check_surface_overlap", pure_virtual((typename impl_type::structure_id_pair_and_distance_list*(impl_type::*)(typename impl_type::particle_type::shape_type const&, typename impl_type::position_type const&, typename impl_type::structure_id_type const&, typename impl_type::length_type const&, typename impl_type::structure_id_type const&, typename impl_type::structure_id_type const&) const)&impl_type::check_surface_overlap), return_value_policy()) .def("create_transaction", pure_virtual(&impl_type::create_transaction), return_value_policy()) .def("distance", pure_virtual(&impl_type::distance)) .def("apply_boundary", pure_virtual((typename impl_type::position_type(impl_type::*)(typename impl_type::position_type const&) const)&impl_type::apply_boundary)) .def("apply_boundary", pure_virtual((typename impl_type::length_type(impl_type::*)(typename impl_type::length_type const&) const)&impl_type::apply_boundary)) + .def("apply_boundary", pure_virtual((typename impl_type::position_structid_pair_type(impl_type::*)(typename impl_type::position_structid_pair_type const&) const)&impl_type::apply_boundary)) .def("cyclic_transpose", pure_virtual((typename impl_type::position_type(impl_type::*)(typename impl_type::position_type const&, typename impl_type::position_type const&) const)&impl_type::cyclic_transpose)) .def("cyclic_transpose", pure_virtual((typename impl_type::length_type(impl_type::*)(typename impl_type::length_type const&, typename impl_type::length_type const&) const)&impl_type::cyclic_transpose)) + .def("cyclic_transpose", pure_virtual((typename impl_type::position_structid_pair_type(impl_type::*)(typename impl_type::position_structid_pair_type const&, typename impl_type::structure_type const&) const)&impl_type::cyclic_transpose)) .def("__contains__", pure_virtual(&impl_type::has_particle)) .def("__iter__", pure_virtual(&impl_type::get_particles), return_value_policy()) diff --git a/binding/ParticleModel.hpp b/binding/ParticleModel.hpp index 1cd4b9b3..59ca17d2 100644 --- a/binding/ParticleModel.hpp +++ b/binding/ParticleModel.hpp @@ -6,22 +6,44 @@ namespace binding { + + +template +struct rfr_structure +{ + typedef Tmodel_ impl_type; + typedef typename boost::remove_reference::type range_type; + typedef typename boost::range_const_iterator::type result_type; + + typedef peer::wrappers::stl_iterator_wrapper wrapper_type; + + static PyObject* create_wrapper(boost::python::back_reference backref) + { + wrapper_type::__class_init__(typeid(result_type).name()); + return wrapper_type::create(backref.get().get_structure_types(), backref.source()); + } +}; + + + +////// Registering master function template inline void register_particle_model_class(char const* name) { using namespace boost::python; typedef Tparticle_model_ impl_type; + // defining the python class class_, boost::noncopyable>(name) .def("add_structure_type", &impl_type::add_structure_type) .def("get_structure_type_by_id", &impl_type::get_structure_type_by_id) + .def("get_def_structure_type_id", &impl_type::get_def_structure_type_id) .add_property("structure_types", - peer::util::range_from_range< - typename impl_type::structure_type_range, - impl_type, &impl_type::get_structure_types>()) - ; + boost::python::make_function(&rfr_structure::create_wrapper)) + ; } } // namespace binding #endif /* BINDING_MODEL_HPP */ + diff --git a/binding/ParticleSimulationStructure.hpp b/binding/ParticleSimulationStructure.hpp index 5c7b61b2..aa722877 100644 --- a/binding/ParticleSimulationStructure.hpp +++ b/binding/ParticleSimulationStructure.hpp @@ -5,12 +5,15 @@ namespace binding { + +////// Registering master function template inline boost::python::objects::class_base register_particle_simulation_structure_class(char const *name) { using namespace boost::python; typedef Timpl impl_type; + // defining the python class (kinda empty) return class_, boost::shared_ptr, boost::noncopyable>(name, no_init) diff --git a/binding/ParticleSimulator.hpp b/binding/ParticleSimulator.hpp index 7e08e959..cbd63190 100644 --- a/binding/ParticleSimulator.hpp +++ b/binding/ParticleSimulator.hpp @@ -9,12 +9,15 @@ namespace binding { + +////// Registering master function template void register_particle_simulator_class(char const* name) { using namespace boost::python; typedef Timpl impl_type; + // defining the python class class_(name, no_init) .add_property("world", make_function( diff --git a/binding/PlanarSurface.hpp b/binding/PlanarSurface.hpp index 1a3b1fc3..68b518ca 100644 --- a/binding/PlanarSurface.hpp +++ b/binding/PlanarSurface.hpp @@ -5,15 +5,20 @@ namespace binding { + +////// Registering master function template inline boost::python::objects::class_base register_planar_surface_class(char const *name) { using namespace boost::python; typedef Timpl impl_type; + // defining the python class return class_, boost::shared_ptr, boost::noncopyable>( - name, init()) .add_property("shape", make_function((typename impl_type::shape_type const&(impl_type::*)()const)&impl_type::shape, return_value_policy())) diff --git a/binding/Plane.hpp b/binding/Plane.hpp index 23ab2342..eb417267 100644 --- a/binding/Plane.hpp +++ b/binding/Plane.hpp @@ -8,24 +8,29 @@ namespace binding { + +////// Registering master function template inline boost::python::objects::class_base register_plane_class(char const* name) { using namespace boost::python; typedef Timpl impl_type; + // registering converters from standard boost templates. to_python_converter, peer::util::detail::to_ndarray_converter< boost::array > >(); peer::converters::register_iterable_to_ra_container_converter< boost::array, 2>(); + // defining the python class return class_(name) .def(init()) + typename impl_type::length_type, + bool>()) .def(init >()) .add_property("position", @@ -97,8 +102,23 @@ inline boost::python::objects::class_base register_plane_class(char const* name) impl_type, typename impl_type::position_type, &impl_type::unit_z, - &impl_type::unit_z>::set)); - + &impl_type::unit_z>::set)) + .add_property("is_one_sided", + make_function( + &peer::util::reference_accessor_wrapper< + impl_type, + bool, + &impl_type::is_one_sided, + &impl_type::is_one_sided>::get, + return_value_policy()), + make_function( + &peer::util::reference_accessor_wrapper< + impl_type, + bool, + &impl_type::is_one_sided, + &impl_type::is_one_sided>::set)) + ; + } } // namespace binding diff --git a/binding/PythonAppender.cpp b/binding/PythonAppender.cpp index 91e27112..099a1f7f 100644 --- a/binding/PythonAppender.cpp +++ b/binding/PythonAppender.cpp @@ -65,10 +65,11 @@ class PythonAppender: public LogAppender boost::python::object makeRecord_; }; -// XXX: logging.Handler is a old-style class... *sigh* class CppLoggerHandler { public: +// XXX: logging.Handler is a old-style class... *sigh* +#ifdef HAVE_OLD_FASHIONED_LOGGER_CLASSES static PyObject* __class_init__(const char* name, PyObject* mod) { using namespace boost::python; @@ -120,11 +121,6 @@ class CppLoggerHandler return klass.get(); } - static void __dealloc__(void* ptr) - { - delete reinterpret_cast(ptr); - } - static CppLoggerHandler* get_self(PyObject* _self) { boost::python::handle<> ptr( @@ -200,6 +196,98 @@ class CppLoggerHandler return NULL; } +#else + void* operator new(size_t) + { + PyObject* retval = PyObject_New(PyObject, &__class__); + return retval; + } + + void operator delete(void* ptr) + { + reinterpret_cast(ptr)->ob_type->tp_free(reinterpret_cast(ptr)); + } + + static PyObject* __class_init__(const char* name, PyObject* mod) + { + using namespace boost::python; + + import_logging_module(); + __name__ = static_cast( + extract(object(borrowed(mod)).attr("__name__"))) + + "." + name; + __class__.tp_name = const_cast(__name__.c_str()); + __class__.tp_base = reinterpret_cast(getattr(logging_module, "Handler").ptr()); + if (PyType_Ready(&__class__) < 0) + { + return 0; + } + return reinterpret_cast(&__class__); + } + + static CppLoggerHandler* get_self(PyObject* _self) + { + return reinterpret_cast(_self); + } + + static PyObject* __new__(PyTypeObject* klass, PyObject* arg, PyObject* kwarg) + try + { + if (PyTuple_Size(arg) != 1) + { + PyErr_SetString(PyExc_TypeError, "the number of arguments must be 1"); + return NULL; + } + + CppLoggerHandler* retval( + new CppLoggerHandler( + *boost::python::extract( + PyTuple_GetItem(arg, 0))())); + + if (PyErr_Occurred()) + { + boost::python::decref(reinterpret_cast(retval)); + return NULL; + } + return reinterpret_cast(retval); + } + catch (boost::python::error_already_set const&) + { + return NULL; + } + + static int __init__(PyObject* self, PyObject* arg, PyObject* kwarg) + { + using namespace boost::python; + return handle<>(allow_null( + PyObject_Call( + PyObject_GetAttrString( + reinterpret_cast(Py_TYPE(self)->tp_base), + const_cast("__init__")), + boost::python::handle<>(PyTuple_Pack(1, self)).get(), + NULL))) ? 0: 1; + } + + static int __traverse__(PyObject *self, visitproc visit, void *arg) + { + Py_VISIT(reinterpret_cast(self)->__dict__.get()); + return 0; + } + + ~CppLoggerHandler() + { + if (__weakreflist__) + { + PyObject_ClearWeakRefs(reinterpret_cast(this)); + } + } +#endif + + static void __dealloc__(void* ptr) + { + delete reinterpret_cast(ptr); + } + static PyObject* createLock(PyObject* _self) { CppLoggerHandler* const self(get_self(_self)); @@ -308,7 +396,25 @@ class CppLoggerHandler return boost::python::reference_existing_object::apply::type()(self->impl_); } - CppLoggerHandler(Logger& impl): impl_(impl) {} + +#ifndef HAVE_OLD_FASHIONED_LOGGER_CLASSES + static PyObject* get___dict__(PyObject *_self, void* context) + { + CppLoggerHandler* const self(get_self(_self)); + if (!self) + return NULL; + return boost::python::incref(self->__dict__.get()); + } +#endif + + CppLoggerHandler(Logger& impl) + : +#ifndef HAVE_OLD_FASHIONED_LOGGER_CLASSES + __weakreflist__(0), + __dict__(PyDict_New()), +#endif + impl_(impl) + {} protected: static enum Logger::level translate_level(PyObject* _level) @@ -330,16 +436,28 @@ class CppLoggerHandler protected: +#ifdef HAVE_OLD_FASHIONED_LOGGER_CLASSES static boost::python::handle<> __class__; +#else + PyObject_VAR_HEAD + static PyTypeObject __class__; + static std::string __name__; + PyObject *__weakreflist__; + boost::python::handle<> __dict__; +#endif static PyMethodDef __methods__[]; static PyGetSetDef __getsets__[]; Logger& impl_; }; -boost::python::handle<> CppLoggerHandler::__class__; +#ifndef HAVE_OLD_FASHIONED_LOGGER_CLASSES +std::string CppLoggerHandler::__name__; +#endif PyMethodDef CppLoggerHandler::__methods__[] = { +#ifdef HAVE_OLD_FASHIONED_LOGGER_CLASSES { "__init__", (PyCFunction)CppLoggerHandler::__init__, METH_O, "" }, +#endif { "createLock", (PyCFunction)CppLoggerHandler::createLock, METH_NOARGS, "" }, { "acquire", (PyCFunction)CppLoggerHandler::acquire, METH_NOARGS, "" }, { "release", (PyCFunction)CppLoggerHandler::release, METH_NOARGS, "" }, @@ -353,10 +471,61 @@ PyMethodDef CppLoggerHandler::__methods__[] = { }; PyGetSetDef CppLoggerHandler::__getsets__[] = { +#ifndef HAVE_OLD_FASHIONED_LOGGER_CLASSES + { const_cast("__dict__"), (getter)&CppLoggerHandler::get___dict__, NULL, NULL }, +#endif { const_cast("logger"), (getter)&CppLoggerHandler::get_logger, NULL, NULL }, { NULL, NULL } }; +#ifdef HAVE_OLD_FASHIONED_LOGGER_CLASSES +boost::python::handle<> CppLoggerHandler::__class__; +#else +PyTypeObject CppLoggerHandler::__class__ = { + PyObject_HEAD_INIT(&PyType_Type) + 0, /* ob_size */ + 0, /* tp_name */ + sizeof(CppLoggerHandler), /* tp_basicsize */ + 0, /* tp_itemsize */ + /* methods */ + (destructor)&CppLoggerHandler::__dealloc__, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + PyObject_GenericGetAttr, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_HAVE_CLASS | Py_TPFLAGS_HAVE_WEAKREFS | Py_TPFLAGS_BASETYPE, /* tp_flags */ + 0, /* tp_doc */ + CppLoggerHandler::__traverse__, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + offsetof(CppLoggerHandler, __weakreflist__), /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + CppLoggerHandler::__methods__, /* tp_methods */ + 0, /* tp_members */ + CppLoggerHandler::__getsets__, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + offsetof(CppLoggerHandler, __dict__), /* tp_dictoffset */ + CppLoggerHandler::__init__, /* tp_init */ + 0, /* tp_alloc */ + CppLoggerHandler::__new__, /*tp_new */ + 0 /* tp_free */ +}; +#endif + boost::python::object register_logger_handler_class(char const* name) { diff --git a/binding/PythonEvent.hpp b/binding/PythonEvent.hpp index c5f8cec1..01f34421 100644 --- a/binding/PythonEvent.hpp +++ b/binding/PythonEvent.hpp @@ -5,11 +5,14 @@ namespace binding { + +////// Registering master function template inline boost::python::object register_python_event_class(char const* name) { using namespace boost::python; + // defining the python class return class_, boost::shared_ptr, boost::noncopyable>(name, init()) .def(init()) .add_property("data", diff --git a/binding/RandomNumberGenerator.hpp b/binding/RandomNumberGenerator.hpp index 328f9c39..beb9d4c0 100644 --- a/binding/RandomNumberGenerator.hpp +++ b/binding/RandomNumberGenerator.hpp @@ -65,6 +65,8 @@ static PyObject* RandomNumberGenerator_normal(Timpl_& impl, Real loc, Real scale } } + +////// Registering master function template static void register_random_number_generator_class(char const* name) { diff --git a/binding/ReactionRecord.hpp b/binding/ReactionRecord.hpp index 3ab4b001..277a67af 100644 --- a/binding/ReactionRecord.hpp +++ b/binding/ReactionRecord.hpp @@ -6,16 +6,20 @@ namespace binding { + +////// Registering master function template boost::python::objects::class_base register_reaction_record_class(char const* name) { using namespace boost::python; typedef Timpl_ impl_type; + // registering converters from standard boost templates peer::converters::register_range_to_tuple_converter(); peer::converters::register_range_to_tuple_converter(); peer::converters::register_pyiterable_range_converter(); + // defining the python class return class_(name, init<>()) .def(init struct seq_to_reactants_converter { @@ -62,6 +65,8 @@ struct seq_to_reactants_converter } }; +////// +// structure defining some helper functions. template struct ReactionRuleExtras { @@ -104,6 +109,8 @@ struct ReactionRuleExtras } }; + +////// Registering master function template inline void register_reaction_rule_class(const char* name) { @@ -111,6 +118,7 @@ inline void register_reaction_rule_class(const char* name) typedef Trr_ impl_type; typedef ReactionRuleExtras extras_type; + // registering the converters from templates defined above. typedef std::vector species_type_id_vector; peer::util::to_native_converter< typename impl_type::Reactants, @@ -120,6 +128,7 @@ inline void register_reaction_rule_class(const char* name) peer::converters::register_iterable_to_range_converter< species_type_id_vector>(); + // defining the class class_(name, init()) .add_property("reactants", diff --git a/binding/ReactionRuleInfo.hpp b/binding/ReactionRuleInfo.hpp index 886b959a..4dea0abd 100644 --- a/binding/ReactionRuleInfo.hpp +++ b/binding/ReactionRuleInfo.hpp @@ -7,12 +7,19 @@ namespace binding { + +////// Registering master function template inline void register_reaction_rule_info_class(char const *name) { using namespace boost::python; typedef Timpl impl_type; + // registering converters from standard boost templates. + peer::converters::register_range_to_tuple_converter(); + peer::converters::register_iterable_to_range_converter(); + + // defining the python class class_(name, init())); - peer::converters::register_range_to_tuple_converter(); - - peer::converters::register_iterable_to_range_converter(); } } // namespace binding diff --git a/binding/Region.hpp b/binding/Region.hpp index f43c13f1..ef92bc58 100644 --- a/binding/Region.hpp +++ b/binding/Region.hpp @@ -5,12 +5,14 @@ namespace binding { +////// Registering master function template inline boost::python::objects::class_base register_region_class(char const *name) { using namespace boost::python; typedef Timpl impl_type; + // defining the python class (kinda empty) return class_, boost::shared_ptr, boost::noncopyable>( name, no_init); diff --git a/binding/SerialIDGenerator.hpp b/binding/SerialIDGenerator.hpp index d5e0f012..31aef59e 100644 --- a/binding/SerialIDGenerator.hpp +++ b/binding/SerialIDGenerator.hpp @@ -4,11 +4,14 @@ #include #include "../SerialIDGenerator.hpp" + +////// Registering master function template inline void register_serial_id_generator_class(char const* class_name) { using namespace boost::python; + // defining the python class class_ >(class_name, init()) .def("__call__", &SerialIDGenerator::operator()) ; diff --git a/binding/ShapedDomain.hpp b/binding/ShapedDomain.hpp index 2293f9aa..6af44019 100644 --- a/binding/ShapedDomain.hpp +++ b/binding/ShapedDomain.hpp @@ -6,12 +6,14 @@ namespace binding { +////// Registering master function template inline boost::python::objects::class_base register_shaped_domain_class(char const* name) { using namespace boost::python; typedef Timpl impl_type; + // defining the python class return class_, boost::noncopyable>(name, no_init) .add_property("position", diff --git a/binding/Single.hpp b/binding/Single.hpp index 287d6ba6..8e590d13 100644 --- a/binding/Single.hpp +++ b/binding/Single.hpp @@ -5,6 +5,9 @@ namespace binding { +////// Helper functions + +// makes a set consisting of a single particle? template static void single_set_particle(Timpl& impl, typename Timpl::particle_id_pair const& pair) { @@ -12,12 +15,15 @@ static void single_set_particle(Timpl& impl, typename Timpl::particle_id_pair co impl.particle().second = pair.second; } + +////// Registering master function template inline boost::python::objects::class_base register_single_class(char const* name) { using namespace boost::python; typedef Timpl impl_type; + // defining the python class return class_, boost::shared_ptr, boost::noncopyable>(name, no_init) .add_property("particle", diff --git a/binding/SpeciesInfo.hpp b/binding/SpeciesInfo.hpp index 64a342f9..59d9feda 100644 --- a/binding/SpeciesInfo.hpp +++ b/binding/SpeciesInfo.hpp @@ -6,6 +6,8 @@ namespace binding { + +////// Registering master function template inline boost::python::objects::class_base register_species_info_class( char const* name) @@ -13,12 +15,25 @@ inline boost::python::objects::class_base register_species_info_class( using namespace boost::python; typedef Timpl_ impl_type; + // defining the python class return class_("SpeciesInfo", init<>() ) - .def(init()) + .def(init()) .add_property("id", make_function(&impl_type::id, return_value_policy())) + .add_property("structure_type_id", + make_function( + &peer::util::reference_accessor_wrapper< + impl_type, typename impl_type::structure_type_id_type, + &impl_type::structure_type_id, + &impl_type::structure_type_id>::get, + return_value_policy()), + &peer::util::reference_accessor_wrapper< + impl_type, typename impl_type::structure_type_id_type, + &impl_type::structure_type_id, + &impl_type::structure_type_id>::set) .add_property("radius", make_function( &peer::util::reference_accessor_wrapper< @@ -30,17 +45,6 @@ inline boost::python::objects::class_base register_species_info_class( impl_type, typename impl_type::length_type, &impl_type::radius, &impl_type::radius>::set) - .add_property("structure_id", - make_function( - &peer::util::reference_accessor_wrapper< - impl_type, typename impl_type::structure_id_type, - &impl_type::structure_id, - &impl_type::structure_id>::get, - return_value_policy()), - &peer::util::reference_accessor_wrapper< - impl_type, typename impl_type::structure_id_type, - &impl_type::structure_id, - &impl_type::structure_id>::set) .add_property("D", make_function( &peer::util::reference_accessor_wrapper< diff --git a/binding/SpeciesType.hpp b/binding/SpeciesType.hpp index 64bae5de..f3f79864 100644 --- a/binding/SpeciesType.hpp +++ b/binding/SpeciesType.hpp @@ -14,6 +14,7 @@ namespace binding { +// Defining a structure with some helper functions. template struct SpeciesTypeExtras { @@ -35,6 +36,8 @@ struct SpeciesTypeExtras } }; + +////// Registering master function template static boost::python::objects::class_base register_species_type_class( char const* name) @@ -43,6 +46,7 @@ static boost::python::objects::class_base register_species_type_class( typedef Timpl_ impl_type; typedef SpeciesTypeExtras extras_type; + // defining the python class return class_ >(name, init<>()) .add_property("id", make_function((typename impl_type::identifier_type const&(impl_type::*)() const)&impl_type::id, diff --git a/binding/Sphere.hpp b/binding/Sphere.hpp index f889e238..1046e247 100644 --- a/binding/Sphere.hpp +++ b/binding/Sphere.hpp @@ -6,6 +6,7 @@ namespace binding { +////// Some helper functions (why are these templater, why here and not downstairs?) template static boost::python::object Sphere___getitem__(Timpl_ const& obj, int index) { @@ -30,12 +31,15 @@ static std::string Sphere___str__(Timpl_* impl) return boost::lexical_cast(*impl); } + +////// Registering master function template inline boost::python::objects::class_base register_sphere_class(char const *name) { using namespace boost::python; typedef Timpl_ impl_type; + // defining the python class return class_(name) .def(init()) diff --git a/binding/SphericalPair.hpp b/binding/SphericalPair.hpp index fd1fa013..bcf44f61 100644 --- a/binding/SphericalPair.hpp +++ b/binding/SphericalPair.hpp @@ -6,12 +6,15 @@ namespace binding { + +////// Registering master function template inline boost::python::objects::class_base register_spherical_pair_class(char const* name) { using namespace boost::python; typedef Timpl impl_type; + // defining the python class return class_, boost::shared_ptr, boost::noncopyable>(name, init inline boost::python::objects::class_base register_spherical_single_class(char const* name) { using namespace boost::python; typedef Timpl impl_type; + // defining the python class return class_, boost::shared_ptr, boost::noncopyable>(name, init inline boost::python::objects::class_base register_spherical_surface_class(char const *name) { using namespace boost::python; typedef Timpl impl_type; + // defining the python class return class_, boost::shared_ptr, boost::noncopyable>( - name, init()) .add_property("shape", make_function((typename impl_type::shape_type const&(impl_type::*)()const)&impl_type::shape, return_value_policy())) diff --git a/binding/Structure.hpp b/binding/Structure.hpp index 2fc6ca4f..05c31e96 100644 --- a/binding/Structure.hpp +++ b/binding/Structure.hpp @@ -6,24 +6,52 @@ namespace binding { + +////// Registering master function template inline boost::python::objects::class_base register_structure_class(char const *name) { using namespace boost::python; typedef Timpl impl_type; - peer::converters::register_tuple_converter< - typename impl_type::projected_type>(); + // registering converters from standard boost templates + // registers the projected_point/(projected_distance, dist_to_edge) tuple defined in ../Structure.hpp + peer::converters::register_tuple_converter(); + peer::converters::register_tuple_converter(); + peer::converters::register_tuple_converter(); + // defining the python class return class_, boost::noncopyable>(name, no_init) .add_property("id", make_function(&impl_type::id, return_value_policy())) + .add_property("sid", + make_function( + &peer::util::reference_accessor_wrapper< + impl_type, typename impl_type::structure_type_id_type, + &impl_type::sid, + &impl_type::sid>::get, + return_value_policy()), + &peer::util::reference_accessor_wrapper< + impl_type, typename impl_type::structure_type_id_type, + &impl_type::sid, + &impl_type::sid>::set) + .add_property("structure_id", + make_function(&impl_type::structure_id, + return_value_policy())) + .add_property("name", + make_function(&impl_type::name, + return_value_policy())) .def("random_position", &impl_type::random_position) .def("random_vector", &impl_type::random_vector) .def("bd_displacement", &impl_type::bd_displacement) - .def("projected_point", &impl_type::projected_point) + .def("project_point", &impl_type::project_point) + .def("deflect", &impl_type::deflect) +// .def("deflect_back", &impl_type::deflect_back) + .def("get_pos_sid_pair", &impl_type::get_pos_sid_pair) + .def("get_pos_sid_pair_pair", &impl_type::get_pos_sid_pair_pair) + .def("get_pos_sid_pair_2o", &impl_type::get_pos_sid_pair_2o) ; } diff --git a/binding/StructureType.hpp b/binding/StructureType.hpp index 63964f4b..e5c11736 100644 --- a/binding/StructureType.hpp +++ b/binding/StructureType.hpp @@ -14,6 +14,8 @@ namespace binding { + +////// Defining structure with some helper functions. template struct StructureTypeExtras { @@ -35,6 +37,8 @@ struct StructureTypeExtras } }; + +////// Registering master function template static boost::python::objects::class_base register_structure_type_class( char const* name) @@ -43,6 +47,7 @@ static boost::python::objects::class_base register_structure_type_class( typedef Timpl_ impl_type; typedef StructureTypeExtras extras_type; + // defining the python class return class_ >(name, init<>()) .add_property("id", make_function((typename impl_type::identifier_type const&(impl_type::*)() const)&impl_type::id, diff --git a/binding/Surface.hpp b/binding/Surface.hpp index b4a1e961..ba26e5ff 100644 --- a/binding/Surface.hpp +++ b/binding/Surface.hpp @@ -5,16 +5,19 @@ namespace binding { + +////// Registering master function template inline boost::python::objects::class_base register_surface_class(char const *name) { using namespace boost::python; typedef Timpl impl_type; + // defining the python class return class_, boost::shared_ptr, boost::noncopyable>( name, no_init) - .def("minimal_distance", &impl_type::minimal_distance) +// .def("minimal_distance", &impl_type::minimal_distance) ; } diff --git a/binding/World.hpp b/binding/World.hpp index 9fa39efa..f806a6d3 100644 --- a/binding/World.hpp +++ b/binding/World.hpp @@ -14,6 +14,9 @@ namespace binding { +//// Converters. These convert C++ types to something that Python can handle. + +// Species_range converter template struct species_range_converter: public boost::python::default_call_policies { @@ -118,12 +121,126 @@ struct species_range_converter: public boost::python::default_call_policies return result; } + // Register the converter. static void __register() { wrapper_type::__class_init__("SpeciesRange", boost::python::scope().ptr()); } }; +// Structure_types_range converter +template +struct structure_types_range_converter: public boost::python::default_call_policies +{ + typedef Timpl_ native_type; + + template + struct policy + { + typedef typename boost::range_size::type size_type; + typedef typename boost::range_value::type value_type; + typedef value_type const& reference; + typedef value_type const& const_reference; + typedef typename boost::range_const_iterator::type iterator; + typedef typename boost::range_const_iterator::type const_iterator; + + static size_type size(T_ const& c) + { + return ::size(c); + } + + static void set(T_& c, size_type i, const_reference v) + { + PyErr_SetString(PyExc_TypeError, "object does not support indexing"); + boost::python::throw_error_already_set(); + } + + static reference get(T_ const& c, size_type i) + { + PyErr_SetString(PyExc_TypeError, "object does not support indexing"); + boost::python::throw_error_already_set(); + throw 0; + } + + static iterator begin(T_ const& c) + { + return boost::begin(c); + } + + static iterator end(T_ const& c) + { + return boost::end(c); + } + }; + + struct instance_holder + { + instance_holder(native_type const& instance, + boost::python::handle<> owner) + : instance_(instance), owner_(owner) {} + + native_type const& operator*() const + { + return instance_; + } + + native_type const* operator->() const + { + return &(**this); + } + + native_type& operator*() + { + PyErr_SetString(PyExc_RuntimeError, "object is immutable"); + boost::python::throw_error_already_set(); + return *static_cast(0); + } + + native_type* operator->() + { + return &(**this); + } + + native_type instance_; + boost::python::handle<> owner_; + }; + + typedef peer::wrappers::stl_container_wrapper > wrapper_type; + + struct result_converter + { + template + struct apply + { + struct type { + PyObject* operator()(native_type const& val) const + { + return wrapper_type::create(instance_holder(val, boost::python::handle<>())); + } + + PyTypeObject const* get_pytype() const + { + return &wrapper_type::__class__; + } + }; + }; + }; + + template + static PyObject* postcall(Targs const& arg, PyObject* result) + { + reinterpret_cast(result)->ptr().owner_ = boost::python::handle<>(boost::python::borrowed(PyTuple_GET_ITEM(arg, 0))); + return result; + } + + // Register the converter. + static void __register() + { + wrapper_type::__class_init__("StructureTypesRange", boost::python::scope().ptr()); + } +}; + +// Structures_range converter template struct structures_range_converter: public boost::python::default_call_policies { @@ -228,9 +345,10 @@ struct structures_range_converter: public boost::python::default_call_policies return result; } + // Register the converter. static void __register() { - wrapper_type::__class_init__("SurfacesRange", boost::python::scope().ptr()); + wrapper_type::__class_init__("StructuresRange", boost::python::scope().ptr()); } }; @@ -242,21 +360,33 @@ World_get_particle_ids(T const& world) } + +////// Registering master function template inline boost::python::objects::class_base register_world_class(char const* name) { using namespace boost::python; - typedef Timpl_ impl_type; + typedef Timpl_ impl_type; // The class World that is being wrapped. + typedef species_range_converter species_range_converter_type; + typedef structure_types_range_converter structure_types_range_converter_type; typedef structures_range_converter structures_range_converter_type; + // registering the converters defined above species_range_converter_type::__register(); + structure_types_range_converter_type::__register(); structures_range_converter_type::__register(); + // registering converters from standard boost templates class_ >("ParticleIDSet") .def(peer::util::set_indexing_suite >()) ; + class_ >("StructureIDSet") + .def(peer::util::set_indexing_suite >()) + ; + + // defining the python class return class_, boost::shared_ptr >( "World", init()) @@ -268,13 +398,35 @@ inline boost::python::objects::class_base register_world_class(char const* name) .add_property("structures", make_function( (typename impl_type::structures_range(impl_type::*)() const)&impl_type::get_structures, structures_range_converter_type())) + .add_property("structure_types", + make_function( + (typename impl_type::structure_types_range(impl_type::*)() const)&impl_type::get_structure_types, + structure_types_range_converter_type())) .add_property("particle_ids", &World_get_particle_ids) - .def("add_species", &impl_type::add_species) - .def("add_structure", &impl_type::add_structure) .def("get_particle_ids", &impl_type::get_particle_ids) + .def("get_particle_ids_on_struct", &impl_type::get_particle_ids_on_struct) + .def("add_species", &impl_type::add_species) + // Structure stuff + .def("add_structure", &impl_type::template add_structure) + .def("add_structure", &impl_type::template add_structure) + .def("add_structure", &impl_type::template add_structure) + .def("add_structure", &impl_type::template add_structure) + .def("add_structure", &impl_type::template add_structure) + .def("connect_structures", &impl_type::template connect_structures) + .def("connect_structures", &impl_type::template connect_structures) + .def("get_neighbor_info", &impl_type::template get_neighbor_info) + .def("get_neighbor_info", &impl_type::template get_neighbor_info) + // TODO get_neighbor_info still needs a type converter to work properly! + .def("get_neighbor_id", &impl_type::template get_neighbor_id) + .def("get_neighbor_id", &impl_type::template get_neighbor_id) + .def("set_def_structure", &impl_type::set_def_structure) + // StructureType stuff + .def("add_structure_type", &impl_type::add_structure_type) + .def("set_def_structure_type_id", &impl_type::set_def_structure_type_id) .def("distance", &impl_type::template distance) .def("distance", &impl_type::template distance) .def("distance", &impl_type::template distance) + .def("distance", &impl_type::template distance) .def("distance", &impl_type::template distance) .def("distance", &impl_type::template distance) .def("calculate_pair_CoM", &impl_type::template calculate_pair_CoM) diff --git a/binding/binding_common.hpp b/binding/binding_common.hpp index 1ea2f523..5907128e 100644 --- a/binding/binding_common.hpp +++ b/binding/binding_common.hpp @@ -16,6 +16,7 @@ #include "../Vector3.hpp" #include "../Sphere.hpp" #include "../Cylinder.hpp" +#include "../Disk.hpp" #include "../Box.hpp" #include "../Plane.hpp" #include "../Point.hpp" @@ -28,72 +29,87 @@ #include "../ParticleSimulator.hpp" #include "../EGFRDSimulator.hpp" #include "../BDPropagator.hpp" +#include "../newBDPropagator.hpp" #include "../BDSimulator.hpp" #include "../StructureUtils.hpp" #include "../AnalyticalSingle.hpp" #include "../AnalyticalPair.hpp" #include "../EventScheduler.hpp" #include "../Logger.hpp" +#include "../StructureType.hpp" #include "peer/wrappers/range/pyiterable_range.hpp" namespace binding { -typedef ::not_found NotFound; -typedef ::already_exists AlreadyExists; -typedef ::illegal_state IllegalState; +typedef ::not_found NotFound; +typedef ::already_exists AlreadyExists; +typedef ::illegal_state IllegalState; typedef ::GSLRandomNumberGenerator GSLRandomNumberGenerator; -typedef ::CyclicWorldTraits WorldTraits; -typedef WorldTraits::particle_type Particle; -typedef WorldTraits::structure_id_type StructureID; -typedef WorldTraits::species_id_type SpeciesID; -typedef WorldTraits::species_type SpeciesInfo; -typedef WorldTraits::structure_type Structure; -typedef WorldTraits::length_type Length; -typedef WorldTraits::position_type Position; -typedef ::World World; -typedef ::Model Model; -typedef ::ParticleModel ParticleModel; -typedef ::NetworkRules NetworkRules; -typedef NetworkRules::reaction_rule_generator ReactionRuleGenerator; -typedef World::transaction_type Transaction; -typedef World::base_type::base_type ParticleContainer; -typedef ::TransactionImpl TransactionImpl; -typedef ::EGFRDSimulatorTraitsBase EGFRDSimulatorTraits; -typedef ::ParticleSimulator ParticleSimulator; -typedef ::BDSimulator BDSimulator; -typedef ::EGFRDSimulator EGFRDSimulator; -typedef ::MultiParticleContainer MultiParticleContainer; -typedef EGFRDSimulator::box_type Box; -typedef EGFRDSimulator::sphere_type Sphere; -typedef EGFRDSimulator::cylinder_type Cylinder; -typedef EGFRDSimulator::plane_type Plane; -typedef ::BDPropagator BDPropagator; -typedef EGFRDSimulatorTraits::shell_id_type ShellID; -typedef EGFRDSimulatorTraits::domain_id_type DomainID; -typedef EGFRDSimulatorTraits::reaction_rule_type ReactionRuleInfo; -typedef EGFRDSimulatorTraits::network_rules_type NetworkRulesWrapper; -typedef EGFRDSimulator::domain_type Domain; -typedef ::ShapedDomain ShapedDomain; -typedef EGFRDSimulator::spherical_shell_type SphericalShell; -typedef EGFRDSimulator::cylindrical_shell_type CylindricalShell; -typedef EGFRDSimulator::spherical_single_type::base_type Single; -typedef EGFRDSimulator::spherical_pair_type::base_type Pair; -typedef EGFRDSimulator::spherical_single_type SphericalSingle; -typedef EGFRDSimulator::cylindrical_single_type CylindricalSingle; -typedef EGFRDSimulator::spherical_pair_type SphericalPair; -typedef EGFRDSimulator::cylindrical_pair_type CylindricalPair; -typedef EGFRDSimulator::multi_type Multi; -typedef ::MatrixSpace SphericalShellContainer; -typedef ::MatrixSpace CylindricalShellContainer; -typedef ::StructureUtils StructureUtils; -typedef EGFRDSimulator::particle_simulation_structure_type ParticleSimulationStructure; -typedef EGFRDSimulator::surface_type Surface; -typedef EGFRDSimulator::region_type Region; -typedef EGFRDSimulator::planar_surface_type PlanarSurface; -typedef EGFRDSimulator::spherical_surface_type SphericalSurface; -typedef EGFRDSimulator::cylindrical_surface_type CylindricalSurface; -typedef EGFRDSimulator::cuboidal_region_type CuboidalRegion; + +typedef ::CyclicWorldTraits WorldTraits; // parameterize the World traits here -> determines many types!! +typedef WorldTraits::particle_type Particle; +typedef WorldTraits::structure_id_type StructureID; +typedef WorldTraits::species_id_type SpeciesID; +typedef WorldTraits::species_type SpeciesInfo; +typedef WorldTraits::structure_type Structure; +typedef StructureType::identifier_type StructureTypeID; +typedef WorldTraits::length_type Length; +typedef WorldTraits::position_type Position; +typedef ::World World; // plug in the WorldTraits type to get a fully specified World type. + +typedef ::Model Model; +typedef ::ParticleModel ParticleModel; +typedef ::NetworkRules NetworkRules; +typedef NetworkRules::reaction_rule_generator ReactionRuleGenerator; + +typedef World::transaction_type Transaction; +typedef World::base_type::base_type ParticleContainer; +typedef ::TransactionImpl TransactionImpl; + +typedef ::EGFRDSimulatorTraitsBase EGFRDSimulatorTraits; +typedef ::ParticleSimulator ParticleSimulator; +typedef ::BDSimulator BDSimulator; +typedef ::EGFRDSimulator EGFRDSimulator; +typedef ::MultiParticleContainer MultiParticleContainer; + +typedef EGFRDSimulator::box_type Box; +typedef EGFRDSimulator::sphere_type Sphere; +typedef EGFRDSimulator::cylinder_type Cylinder; +typedef EGFRDSimulator::disk_type Disk; +typedef EGFRDSimulator::plane_type Plane; + +typedef ::BDPropagator BDPropagator; +typedef ::newBDPropagator newBDPropagator; + +typedef EGFRDSimulatorTraits::shell_id_type ShellID; +typedef EGFRDSimulatorTraits::domain_id_type DomainID; +typedef EGFRDSimulatorTraits::reaction_rule_type ReactionRuleInfo; +typedef EGFRDSimulatorTraits::network_rules_type NetworkRulesWrapper; +typedef EGFRDSimulator::domain_type Domain; +typedef ::ShapedDomain ShapedDomain; +typedef EGFRDSimulator::spherical_shell_type SphericalShell; +typedef EGFRDSimulator::cylindrical_shell_type CylindricalShell; +typedef EGFRDSimulator::spherical_single_type::base_type Single; +typedef EGFRDSimulator::spherical_pair_type::base_type Pair; +typedef EGFRDSimulator::spherical_single_type SphericalSingle; +typedef EGFRDSimulator::cylindrical_single_type CylindricalSingle; +typedef EGFRDSimulator::spherical_pair_type SphericalPair; +typedef EGFRDSimulator::cylindrical_pair_type CylindricalPair; +typedef EGFRDSimulator::multi_type Multi; +typedef ::MatrixSpace SphericalShellContainer; +typedef ::MatrixSpace CylindricalShellContainer; +typedef ::StructureUtils StructureUtils; +typedef EGFRDSimulator::particle_simulation_structure_type ParticleSimulationStructure; + +typedef EGFRDSimulator::surface_type Surface; +typedef EGFRDSimulator::region_type Region; +typedef EGFRDSimulator::planar_surface_type PlanarSurface; +typedef EGFRDSimulator::spherical_surface_type SphericalSurface; +typedef EGFRDSimulator::cylindrical_surface_type CylindricalSurface; +typedef EGFRDSimulator::disk_surface_type DiskSurface; +typedef EGFRDSimulator::cuboidal_region_type CuboidalRegion; + typedef EGFRDSimulatorTraits::reaction_record_type ReactionRecord; typedef EGFRDSimulatorTraits::reaction_recorder_type ReactionRecorder; typedef EGFRDSimulatorTraits::volume_clearer_type VolumeClearer; diff --git a/binding/disk_class.cpp b/binding/disk_class.cpp new file mode 100644 index 00000000..237b71ac --- /dev/null +++ b/binding/disk_class.cpp @@ -0,0 +1,15 @@ +#ifdef HAVE_CONFIG_H +#include +#endif /* HAVE_CONFIG_H */ + +#include "Disk.hpp" +#include "binding_common.hpp" + +namespace binding { + +void register_disk_class() +{ + register_disk_class("Disk"); +} + +} // namespace binding diff --git a/binding/disk_class.hpp b/binding/disk_class.hpp new file mode 100644 index 00000000..82f38e50 --- /dev/null +++ b/binding/disk_class.hpp @@ -0,0 +1,10 @@ +#ifndef BINDING_DISK_CLASS_HPP +#define BINDING_DISK_CLASS_HPP + +namespace binding { + +void register_disk_class(); + +} // namespace binding + +#endif /* BINDING_DISK_CLASS_HPP */ diff --git a/binding/module_functions.cpp b/binding/module_functions.cpp index 552c2be2..fd800e25 100644 --- a/binding/module_functions.cpp +++ b/binding/module_functions.cpp @@ -15,8 +15,6 @@ calculate_pair_CoM(Position const& p1, element_type_of< Position >::type const& D2, element_type_of< Position >::type const& world_size) { - typedef element_type_of::type element_type; - Position retval; const Position p2t(cyclic_transpose(p2, p1, world_size)); @@ -47,10 +45,14 @@ void register_module_functions() def( "cyclic_transpose", &cyclic_transpose::type> ); def("create_planar_surface", &StructureUtils::create_planar_surface, return_value_policy()); + def("create_double_sided_planar_surface", &StructureUtils::create_double_sided_planar_surface, + return_value_policy()); def("create_spherical_surface", &StructureUtils::create_spherical_surface, return_value_policy()); def("create_cylindrical_surface", &StructureUtils::create_cylindrical_surface, return_value_policy()); + def("create_disk_surface", &StructureUtils::create_disk_surface, + return_value_policy()); def("create_cuboidal_region", &StructureUtils::create_cuboidal_region, return_value_policy()); def("_random_vector", (Position(*)(Structure const&, Length const&, GSLRandomNumberGenerator&))&StructureUtils::random_vector); diff --git a/binding/newBDPropagator.hpp b/binding/newBDPropagator.hpp new file mode 100644 index 00000000..ffde6e22 --- /dev/null +++ b/binding/newBDPropagator.hpp @@ -0,0 +1,59 @@ +#ifndef BINDING_NEW_BD_PROPAGATOR_HPP +#define BINDING_NEW_BD_PROPAGATOR_HPP + +#include +#include "utils/pair.hpp" +#include "peer/utils.hpp" +#include "peer/wrappers/range/pyiterable_range.hpp" +#include "peer/converters/sequence.hpp" + +namespace binding { + +template +static void newBDPropagator_propagate_all(Timpl_& self) +{ + while (self()); +} + + +////// Registering master function +template +inline boost::python::objects::class_base register_new_bd_propagator_class(char const* name) +{ + using namespace boost::python; + typedef Timpl_ impl_type; + typedef typename impl_type::traits_type simulator_traits_type; + typedef typename simulator_traits_type::world_type world_type; + + typedef typename impl_type::reaction_recorder_type reaction_recorder_type; + typedef typename impl_type::volume_clearer_type volume_clearer_type; + typedef typename simulator_traits_type::time_type time_type; + typedef typename simulator_traits_type::network_rules_type network_rules_type; + typedef typename world_type::traits_type::length_type length_type; + typedef typename world_type::traits_type::rng_type rng_type; + typedef typename world_type::particle_id_type particle_id_type; + typedef typename world_type::particle_container_type particle_container_type; + + // registering converter from standard boost template + peer::converters::register_pyiterable_range_converter(); + + // defining the python class + return class_( + name, init< + particle_container_type&, network_rules_type const&, rng_type&, + time_type, int, length_type, reaction_recorder_type*, volume_clearer_type*, + peer::wrappers::pyiterable_range >()) + .def(init< + particle_container_type&, network_rules_type const&, rng_type&, + time_type, int, length_type, reaction_recorder_type*, volume_clearer_type*, + typename get_select_first_range::type>()) + .add_property("rejected_move_count", + &impl_type::get_rejected_move_count) + .def("__call__", &impl_type::operator()) + .def("propagate_all", &newBDPropagator_propagate_all) + ; +} + +} // namespace binding + +#endif /* BINDING_NEW_BD_PROPAGATOR_HPP */ diff --git a/binding/new_bd_propagator_class.cpp b/binding/new_bd_propagator_class.cpp new file mode 100644 index 00000000..920741d2 --- /dev/null +++ b/binding/new_bd_propagator_class.cpp @@ -0,0 +1,15 @@ +#ifdef HAVE_CONFIG_H +#include +#endif /* HAVE_CONFIG_H */ + +#include "newBDPropagator.hpp" +#include "binding_common.hpp" + +namespace binding { + +void register_new_bd_propagator_class() +{ + register_new_bd_propagator_class("newBDPropagator"); +} + +} // namespace binding diff --git a/binding/new_bd_propagator_class.hpp b/binding/new_bd_propagator_class.hpp new file mode 100644 index 00000000..7758a254 --- /dev/null +++ b/binding/new_bd_propagator_class.hpp @@ -0,0 +1,10 @@ +#ifndef BINDING_NEW_BD_PROPAGATOR_CLASS_HPP +#define BINDING_NEW_BD_PROPAGATOR_CLASS_HPP + +namespace binding { + +void register_new_bd_propagator_class(); + +} // namespace binding + +#endif /* BINDING_NEW_BD_PROPAGATOR_CLASS_HPP */ diff --git a/binding/particle_class.cpp b/binding/particle_class.cpp index a4dac2b8..b4d97707 100644 --- a/binding/particle_class.cpp +++ b/binding/particle_class.cpp @@ -8,6 +8,7 @@ namespace binding { void register_particle_class() +// Parameterize the particle wrapper with the correct Particle class and registers it { ParticleWrapper::__register_class("Particle"); } diff --git a/binding/position_converters.cpp b/binding/position_converters.cpp index 2fb996c1..0bd5f321 100644 --- a/binding/position_converters.cpp +++ b/binding/position_converters.cpp @@ -11,11 +11,14 @@ namespace binding { +//// Converters. These convert C++ types to something that Python can handle. + typedef peer::util::detail::array_to_ndarray_converter< WorldTraits::position_type, WorldTraits::position_type::value_type, 3> position_to_ndarray_converter; +// struct ndarray_to_position_converter { typedef Position native_type; @@ -57,6 +60,7 @@ struct ndarray_to_position_converter } }; +// struct tuple_to_position_converter { typedef Position native_type; @@ -86,6 +90,7 @@ struct tuple_to_position_converter } }; +// struct list_to_position_converter { typedef Position native_type; @@ -115,6 +120,8 @@ struct list_to_position_converter } }; + +////// Registering master function void register_position_converters() { boost::python::to_python_converter("PlanarSurface"); register_spherical_surface_class("SphericalSurface"); register_cylindrical_surface_class("CylindricalSurface"); + register_disk_surface_class("DiskSurface"); } } // namespace binding diff --git a/binding/structure_id_class.cpp b/binding/structure_id_class.cpp new file mode 100644 index 00000000..45a92d2e --- /dev/null +++ b/binding/structure_id_class.cpp @@ -0,0 +1,18 @@ +#ifdef HAVE_CONFIG_H +#include +#endif /* HAVE_CONFIG_H */ + +#include "Identifier.hpp" +#include "SerialIDGenerator.hpp" +#include "binding_common.hpp" +#include "peer/utils.hpp" + +namespace binding { + +void register_structure_id_class() +{ + IdentifierWrapper::__register_class("StructureID"); + register_serial_id_generator_class("StructureIDGenerator"); +} + +} // namespace binding diff --git a/binding/structure_id_class.hpp b/binding/structure_id_class.hpp new file mode 100644 index 00000000..fac97283 --- /dev/null +++ b/binding/structure_id_class.hpp @@ -0,0 +1,10 @@ +#ifndef BINDING_STRUCTURE_ID_CLASS_HPP +#define BINDING_STRUCTURE_ID_CLASS_HPP + +namespace binding { + +void register_structure_id_class(); + +} // namespace binding + +#endif /* BINDING_STRUCTURE_ID_CLASS_HPP */ diff --git a/binding/structure_type_class.cpp b/binding/structure_type_class.cpp index c60a6aa6..435b27e6 100644 --- a/binding/structure_type_class.cpp +++ b/binding/structure_type_class.cpp @@ -42,7 +42,7 @@ struct structure_type_to_structure_id_converter void register_structure_type_class() { structure_type_class = register_structure_type_class("StructureType"); - peer::util::to_native_converter >(); + peer::util::to_native_converter >(); } } // namespace binding diff --git a/binding/volume_clearer_converter.hpp b/binding/volume_clearer_converter.hpp index 89215247..e9b20f10 100644 --- a/binding/volume_clearer_converter.hpp +++ b/binding/volume_clearer_converter.hpp @@ -8,6 +8,7 @@ namespace binding { +// Defining a wrapper for the volume clearer template class VolumeClearerWrapper: public Tbase_ { @@ -69,6 +70,8 @@ struct volume_clearer_converter } }; + +////// Registering master function template void register_volume_clearer_converter() { diff --git a/binding/world_class.cpp b/binding/world_class.cpp index 87098e96..63f0386e 100644 --- a/binding/world_class.cpp +++ b/binding/world_class.cpp @@ -105,6 +105,7 @@ struct particle_id_pair_range_select_first_converter } }; + // Also need to register the converter static void __register() { wrapper_type::__class_init__("ParticleIDRange", boost::python::scope().ptr()); @@ -114,6 +115,7 @@ struct particle_id_pair_range_select_first_converter }; +////// Registering master function void register_world_class() { register_world_class("World"); @@ -124,4 +126,4 @@ void register_world_class() peer::converters::register_iterable_to_range_converter >(); } -} // namesapce binding +} // namespace binding diff --git a/configure.ac b/configure.ac index fe16c47d..1b422acc 100644 --- a/configure.ac +++ b/configure.ac @@ -1,46 +1,25 @@ +AC_PREREQ([2.50]) AC_REVISION([$Id$]) -dnl -dnl -AC_INIT +AC_INIT([eGFRD],[0.9a],[gfrd@amolf.nl]) AC_CONFIG_SRCDIR([autogen.sh]) -dnl AC_DISABLE_STATIC -AM_PROG_LIBTOOL -dnl AC_CONFIG_AUX_DIR() -dnl -dnl -AH_TEMPLATE(HAVE_SINCOS) -AH_TEMPLATE(HAVE_INLINE) -dnl -dnl +AC_LANG_CPLUSPLUS AC_CANONICAL_TARGET([]) -AM_INIT_AUTOMAKE([epdp],[0.3b]) +AM_INIT_AUTOMAKE([subdir-objects]) -dnl Check pdflatex +dnl checks for programs +AM_PROG_LIBTOOL AC_PROG_PDFLATEX() - -dnl +AC_PROG_CXX +AC_PROG_LN_S AC_PROG_MAKE_SET -dnl -dnl checks for programs -dnl -AM_PATH_PYTHON(2.4) +AC_PROG_RANLIB +AM_PATH_PYTHON(2.6) -dnl -dnl dnl checks for libraries -dnl -dnl AX_PATH_GSL([1.11],,AC_MSG_ERROR([could not find required version of GSL.])) -dnl AC_CHECK_LIB(m,exp,,AC_MSG_ERROR([could not find libm.])) -dnl AC_CHECK_LIB(python${PYTHON_VERSION},main,,AC_MSG_ERROR([could not find libpython.])) -dnl -AC_PROG_CXX -AC_LANG_CPLUSPLUS -dnl -dnl DEBUG= AC_ARG_ENABLE([debug], @@ -50,26 +29,16 @@ AC_ARG_ENABLE([debug], ) if test -n "$DEBUG"; then - CFLAGS="" - CXXFLAGS="" - CPPFLAGS="-DDEBUG=1" + CXXFLAGS="-DDEBUG=1" +else + CXXFLAGS="-g -O2" fi - -if test "$GXX" = "yes"; then - CXXFLAGS="$CXXFLAGS -Wall -Wstrict-aliasing=0 -Wno-invalid-offsetof" - CFLAGS="$CFLAGS -Wall" -fi - AC_SUBST(DEBUG) -CXXFLAGS="$CXXFLAGS -g" -CFLAGS="$CFLAGS -g" +CXXFLAGS="$CXXFLAGS -std=gnu++98 -Wall" AX_BOOST_BASE([1.37],,AC_MSG_ERROR([could not find required version of BOOST.])) -CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS" -LDFLAGS="$LDFLAGS $BOOST_LDFLAGS" - BOOST_PYTHON_LIBNAME=boost_python AC_ARG_WITH(boost-python-libname, @@ -92,21 +61,29 @@ AC_ARG_WITH(boost-regex-libname, AC_CHECK_LIB($BOOST_REGEX_LIBNAME,main,,AC_MSG_ERROR([could not find libboost_regex.])) AC_SUBST(BOOST_REGEX_LIBNAME) -dnl -dnl AM_CHECK_PYMOD(numpy,,,[AC_MSG_ERROR([could not find Python numpy module.])]) AM_CHECK_PYMOD(scipy,,,[AC_MSG_ERROR([could not find Python scipy module.])]) -dnl -dnl -dnl + + dnl checks for header files -dnl -dnl -dnl +AC_CHECK_HEADERS([limits.h unistd.h]) +dnl AC_CHECK_HEADER_STDBOOL + +# Checks for typedefs, structures, and compiler characteristics. +AC_TYPE_SIZE_T +AC_TYPE_SSIZE_T +AC_CHECK_TYPES([ptrdiff_t]) + +# Checks for library functions. +AC_FUNC_ERROR_AT_LINE +AC_CHECK_FUNCS([memmove pow sqrt sincos isfinite]) +AH_TEMPLATE(HAVE_SINCOS) +AH_TEMPLATE(HAVE_INLINE) ECELL_CHECK_NUMPY ECELL_CHECK_NUMPY_ARRAY_DESCR -dnl +ECELL_CHECK_LOGGING_MODULE AM_CHECK_PYTHON_HEADERS() + ac_save_CPPFLAGS="$CPPFLAGS" CPPFLAGS="$CPPFLAGS $PYTHON_INCLUDES" AC_CHECK_TYPES([PyBaseExceptionObject],[],[],[ @@ -117,8 +94,7 @@ CPPFLAGS="$ac_save_CPPFLAGS" AC_CHECK_FUNCS([PyInt_FromSize_t],[],[],[ #include ]) -dnl -dnl + AC_CHECK_HEADERS([unordered_map boost/unordered_map.hpp boost/functional/hash.hpp]) AC_CHECK_HEADER([tr1/unordered_map], [ AC_LANG_SAVE @@ -147,29 +123,20 @@ AC_CHECK_HEADERS([tr1/functional], [ #include ]) ]) -dnl -dnl checks for types -dnl -dnl -dnl checks for structures -dnl -dnl + dnl checks for compiler characteristics -dnl -dnl AC_C_INLINE if test "$ac_cv_c_inline" != no ; then AC_DEFINE(HAVE_INLINE,1) AC_SUBST(HAVE_INLINE) fi -dnl + dnl extract LTDL_SHLIB_EXT -dnl rm -f conftest ./libtool --config > conftest . ./conftest rm -f conftest -dnl + dnl compatibility for libtool 1.5.6 LTDL_SHLIB_EXT="" if test -n "$shrext_cmds"; then @@ -180,17 +147,13 @@ elif test -n "$shrext"; then LTDL_SHLIB_EXT=$shrext AC_SUBST(LTDL_SHLIB_EXT) fi -dnl + dnl checks for library functions -dnl -AC_CHECK_FUNCS([sincos isfinite]) AC_CHECK_DECLS([INFINITY, HUGE_VAL],,,[ #include ]) -dnl -dnl + dnl no checks for system services -dnl AM_CONFIG_HEADER([config.h]) AC_CONFIG_FILES([Makefile binding/Makefile test/Makefile doc/Makefile samples/benchmark/Makefile]) AC_OUTPUT([]) diff --git a/constants.py b/constants.py index 7ee65be1..33b103e8 100644 --- a/constants.py +++ b/constants.py @@ -22,3 +22,5 @@ def __repr__(self): EventType(18, "MULTI_UNIMOLECULAR_REACTION") EventType.MULTI_BIMOLECULAR_REACTION = \ EventType(19, "MULTI_BIMOLECULAR_REACTION") +EventType.MULTI_DIFFUSION = EventType(20, "MULTI_DIFFUSION") + diff --git a/doc/Makefile.am b/doc/Makefile.am index 091794b5..84c40f14 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -8,7 +8,10 @@ if HAVE_PDFLATEX # Compile and install; do not distribute. pdf_DATA = \ implementation_notes.pdf\ - p1_fp.pdf + p1_fp.pdf\ + interface_functions.pdf\ + shell_making.pdf\ + event_processing.pdf endif diff --git a/doc/event_processing.tex b/doc/event_processing.tex new file mode 100644 index 00000000..0063f690 --- /dev/null +++ b/doc/event_processing.tex @@ -0,0 +1,142 @@ +\documentclass[a4paper, 11pt]{article} +\usepackage{amsmath} +\usepackage{amsfonts} + +\title{Realization of the main eGFRD algorithm in the current python code} +\author{Laurens Bossen} + +\begin{document} +\maketitle + +A number of independent processes or 'coordinates' take place inside a Domain. Independent processes are: +-Diffusion in a number of dimensions +-Decay reactions + +When one of the processes in the domain has produced the current event, then a fire\_domain is executed. However, when an other +event external to the domain is the current event but the processes need to be propagated anyhow (a burst), then a burst\_domain +is executed. +Fire happens when the domain produced the top event of the scheduler. A burst event happens when the event associated with the +domain is still in the scheduler. +Fire->Event is no longer in scheduler +Burst->Event is is still in scheduler + + +Fire\_Single +-process event causing coordinate (a coordinate is an independent process in a domain, eg diffusion, decay) +-process all other coordinates + +Burst\_Single +-process all coordinates (event was external) + +After a fire or burst the domain is 'reduced' to a default NonInteractionSingle and a new domain (Pair, Interaction, +NonInteractionSingle) should be made. + +Then reschedule the event + + +Note that from a Pair, Interaction (Multi) after a Fire or Burst you always 'go back' to a NonInteractionSingle. This means +that you change domain. +A speedup trick for this is to 'save' the original single in the Pair or Interaction domain so that we can potentially reuse +them when a fire or burst event takes place + +In the old code, the processing of the coordinates is dealt with differently. When possible the particles in the domain are +first 'propagated'. This means that the particles in the domain are propagated to their new positions and that then new +zero shells are made for the particles. The putative decay reaction is then handled separately after the 'propagation'. In this +way the processes are modular. Similarly we could perform an interaction reaction after performing the propagation of the particle +towards the surface. +Doing a pair reaction however requires a different procedure since the two particles cannot be propagated on top of eachother. + +Since the procedures of firing and bursting are similar, the current approach leads to a lot of code duplication. Since we also +intoduced a great number of new domains with different interactions, the number of combination grow introducing even more code +duplication. +To keep the code clear, we chose to generalize the procedure and perform the 'propagation' and 'reaction' steps in one step. In +this way also the pair reaction fits in the same framework. +This means that we first calculate the new position(s) of the particle(s) and directly perform the putative identity change. So, +we do not make new domains after calculating the new positions, but only do this after the putative reaction has taken place. + +It is important to note that the reaction can fail because there is no space for the product particles. Currently this is caught +by an exception (the procedure for the decay reaction fails) and no changes are made to the system. In the new framework we will +have to handle it differently. We will need to make sure that the positional change still gets processed (the propagation) whereas +the identity change (the reaction) is not, the identity of the particle(s) stays the same. + +A lot of the procedures have to be generalized anyway to facilitate the different dimensions. Also the fire\_pair method needs to +facilitate a mixed pair and therefore needs to be generalized to avoid code duplication. +Also the procedures are all very similar and should be easily generalized. This will make the code clearer and easier to understand. + +Originally, the pair consists of two singles. These are also used after the propagation and are only removed when a reaction takes +place. There is in a sence a sort of a cache of the domains from which the pair domain was formed. When the event of the pair is then +processed we can make use of the cached singles to make the domains for the product particles. In the old structure, the singles +are also used to draw the decay times for single particles. +In the new framework we can reuse the singles if the particle identity has not changed, otherwise reuse is not possible. The domains +are coupled to the particles (their size, structure and reactionrules). + + + +Processing the event means: +%if Single && zero-shell && D != 0 then + the single was a 'special' single (zero shell) -> make\_new\_domain +else + the single was a 'normal' domain and event should be processed + + (calculate new position(s)) +% if (D != 0) && dt>0 (time has passed) + (there was actual relevant diffusion) + determine new position(s) of particle(s) + apply boundary conditions + -check if still in shell + else + (no diffusion has taken place) + positions are old position(s) + + + (determine identitie(s)) + remove old domain (domain should be removed because otherwise it interferes with + bursting during the 'reaction process' + + if event is Single\_Reaction, IV\_Reaction, IV\_Interaction + get new identity for the resulting particle + calculate new position for particles (for example Interaction, Decay reactions) + apply boundary conditions to new position(s) + actually move/remove/add the particle (update\_world) + + else + (event was Burst or one of the possible Escape events) + positions are 'old' positions + particle identities are old identities + particle number is old number + + apply boundary conditions to position(s) + check that particles do not leave domain and do not overlap with other particles + move particles to new positions (make change in the world) + + (we now have n particles in the world with certain identity and position) + (In principle all particles should still be in the domain) + + for all particles + if particle\_id is old\_particle\_id + NonInteractionSingle = cached NonInteractionSingle + re-initialize time and size of NonInteractionSingle(s) + add to shell container and domain hash + log reused NonInteractionSingle + else + create default NonInteractionSingle for particle + log newly created NonInteractionSingle + schedule domain + + + +Note that when a Pair or Interaction is made, the shells and domain\_id are removed from the shell container and +domain hash respectively. This doesn't mean that the objects too have gone. The NonInteractionSingles are subsequently +cached in the made domain (Pair, Interaction, Multi?). When the made domain is now fired --the event produced by the +domain is processed-- and the resulting particle has not changed its identity, then the cached domain can be used. The +shell of the cached domain will recreated in the shell container and its domain\_id will be added to the domains hash. + + + +Create a Protective Domain: +-generate identifiers for domain and shell +-make domain object (instantiate with particle, shell dimensions, structure/surface, reaction\_rules, misc info) + -make appropriate shell (done by constructor or later in case of Multi) +-place shell in container (move\_shell) +- +\end{document} diff --git a/doc/greens_functions/Makefile b/doc/greens_functions/Makefile new file mode 100755 index 00000000..5670cce2 --- /dev/null +++ b/doc/greens_functions/Makefile @@ -0,0 +1,7 @@ + +.PHONY: all +all: + cd tex && $(MAKE) $(MKFLAGS) && cd ../ && mv -f build/*.pdf . + +clean: + rm -rf build/*.aux build/*.log build/include/*.aux build/include/*.log *.pdf diff --git a/samples/bd_test/data/.empty-dir b/doc/greens_functions/build/include/.empty-dir similarity index 100% rename from samples/bd_test/data/.empty-dir rename to doc/greens_functions/build/include/.empty-dir diff --git a/doc/greens_functions/tex/Makefile b/doc/greens_functions/tex/Makefile new file mode 100644 index 00000000..945c889b --- /dev/null +++ b/doc/greens_functions/tex/Makefile @@ -0,0 +1,37 @@ +# Todo. Use that Makefile.am magic. And why is it recompiling even though +# nothing changed? + +# make should produce: +# * 1 pdf for the derivation of the Greens function of each diffusion +# problem. (Todo) +# * 1 pdf for each problem giving the results: Greens function, survival +# probability and propensity function. +# * greensFunctions.pdf, all results combined. + + +PDFLATEX=pdflatex -output-directory=../build + +greensFunctions.pdf: greensFunctions.tex greensFunction1DAbsAbs.pdf greensFunction1DAbsInf.pdf greensFunction1DRadAbs.pdf greensFunction1DRadInf.pdf greensFunction1DAbsSinkAbs.pdf greensFunction1DInfSinkInf.pdf greensFunction2DRadAbs.pdf + $(PDFLATEX) greensFunctions.tex + +greensFunction1DAbsAbs.pdf: greensFunction1DAbsAbs.tex include/greensFunction1DAbsAbs.include.tex + $(PDFLATEX) greensFunction1DAbsAbs.tex + +greensFunction1DAbsInf.pdf: greensFunction1DAbsInf.tex include/greensFunction1DAbsInf.include.tex + $(PDFLATEX) greensFunction1DAbsInf.tex + +greensFunction1DRadAbs.pdf: greensFunction1DRadAbs.tex include/greensFunction1DRadAbs.include.tex + $(PDFLATEX) greensFunction1DRadAbs.tex + +greensFunction1DRadInf.pdf: greensFunction1DRadInf.tex include/greensFunction1DRadInf.include.tex + $(PDFLATEX) greensFunction1DRadInf.tex + +greensFunction1DAbsSinkAbs.pdf: greensFunction1DAbsSinkAbs.tex include/greensFunction1DAbsSinkAbs.include.tex + $(PDFLATEX) greensFunction1DAbsSinkAbs.tex + +greensFunction1DInfSinkInf.pdf: greensFunction1DInfSinkInf.tex include/greensFunction1DInfSinkInf.include.tex + $(PDFLATEX) greensFunction1DInfSinkInf.tex + +greensFunction2DRadAbs.pdf: greensFunction2DRadAbs.tex include/greensFunction2DRadAbs.include.tex + $(PDFLATEX) greensFunction2DRadAbs.tex + diff --git a/doc/greens_functions/tex/greensFunction1DAbsAbs.tex b/doc/greens_functions/tex/greensFunction1DAbsAbs.tex new file mode 100644 index 00000000..77538747 --- /dev/null +++ b/doc/greens_functions/tex/greensFunction1DAbsAbs.tex @@ -0,0 +1,13 @@ + +\documentclass{article} + +\include{include/include} +\include{include/subSectionOnly} + + +\begin{document} + +\include{include/greensFunction1DAbsAbs.include} +\include{include/greensFunction1DwDriftAbsAbs.include} + +\end{document} diff --git a/doc/greens_functions/tex/greensFunction1DAbsInf.tex b/doc/greens_functions/tex/greensFunction1DAbsInf.tex new file mode 100644 index 00000000..f0a1dfd2 --- /dev/null +++ b/doc/greens_functions/tex/greensFunction1DAbsInf.tex @@ -0,0 +1,13 @@ + +\documentclass{article} + +\include{include/include} +\include{include/subSectionOnly} + + +\begin{document} + +\include{include/greensFunction1DAbsInf.include} +\include{include/greensFunction1DwDriftAbsInf.include} + +\end{document} diff --git a/doc/greens_functions/tex/greensFunction1DAbsSinkAbs.tex b/doc/greens_functions/tex/greensFunction1DAbsSinkAbs.tex new file mode 100644 index 00000000..05c7874b --- /dev/null +++ b/doc/greens_functions/tex/greensFunction1DAbsSinkAbs.tex @@ -0,0 +1,12 @@ + +\documentclass{article} + +\include{include/include} +\include{include/subSectionOnly} + + +\begin{document} + +\include{include/greensFunction1DAbsSinkAbs.include} + +\end{document} diff --git a/doc/greens_functions/tex/greensFunction1DInfSinkInf.tex b/doc/greens_functions/tex/greensFunction1DInfSinkInf.tex new file mode 100644 index 00000000..389eaf15 --- /dev/null +++ b/doc/greens_functions/tex/greensFunction1DInfSinkInf.tex @@ -0,0 +1,12 @@ + +\documentclass{article} + +\include{include/include} +\include{include/subSectionOnly} + + +\begin{document} + +\include{include/greensFunction1DInfSinkInf.include} + +\end{document} diff --git a/doc/greens_functions/tex/greensFunction1DRadAbs.tex b/doc/greens_functions/tex/greensFunction1DRadAbs.tex new file mode 100644 index 00000000..83ce9cf2 --- /dev/null +++ b/doc/greens_functions/tex/greensFunction1DRadAbs.tex @@ -0,0 +1,13 @@ + +\documentclass{article} + +\include{include/include} +\include{include/subSectionOnly} + + +\begin{document} + +\include{include/greensFunction1DRadAbs.include} +\include{include/greensFunction1DwDriftRadAbs.include} + +\end{document} diff --git a/doc/greens_functions/tex/greensFunction1DRadInf.tex b/doc/greens_functions/tex/greensFunction1DRadInf.tex new file mode 100644 index 00000000..a2adf5e3 --- /dev/null +++ b/doc/greens_functions/tex/greensFunction1DRadInf.tex @@ -0,0 +1,13 @@ + +\documentclass{article} + +\include{include/include} +\include{include/subSectionOnly} + + +\begin{document} + +\include{include/greensFunction1DRadInf.include} +\include{include/greensFunction1DwDriftRadInf.include} + +\end{document} diff --git a/doc/greens_functions/tex/greensFunction2DRadAbs.tex b/doc/greens_functions/tex/greensFunction2DRadAbs.tex new file mode 100644 index 00000000..f6b04146 --- /dev/null +++ b/doc/greens_functions/tex/greensFunction2DRadAbs.tex @@ -0,0 +1,12 @@ + +\documentclass{article} + +\include{include/include} +\include{include/subSectionOnly} + + +\begin{document} + +\include{include/greensFunction2DRadAbs.include} + +\end{document} diff --git a/doc/greens_functions/tex/greensFunctions.tex b/doc/greens_functions/tex/greensFunctions.tex new file mode 100644 index 00000000..31ee61bb --- /dev/null +++ b/doc/greens_functions/tex/greensFunctions.tex @@ -0,0 +1,35 @@ + +\documentclass[twoside]{report} + +\include{include/include} +\include{include/subSectionOnly} +%Page numbers instead of name. +\fancyfoot[RO, LE]{\thepage} + + +\begin{document} + +\title{Greens functions, survival probabilies and propensity functions (1D and +2D)} +\thispagestyle{empty} +\maketitle + + +\newpage\input{include/greensFunction1DAbsAbs.include} +\newpage\input{include/greensFunction1DwDriftAbsAbs.include} + +\newpage\include{include/greensFunction1DAbsInf.include} +\newpage\include{include/greensFunction1DwDriftAbsInf.include} + +\newpage\include{include/greensFunction1DRadAbs.include} +\newpage\include{include/greensFunction1DwDriftRadAbs.include} + +\newpage\include{include/greensFunction1DRadInf.include} +\newpage\include{include/greensFunction1DwDriftRadInf.include} + +\newpage\input{include/greensFunction1DAbsSinkAbs.include} +\newpage\input{include/greensFunction1DInfSinkInf.include} + +\newpage\include{include/greensFunction2DRadAbs.include} + +\end{document} diff --git a/doc/greens_functions/tex/greensFunctionsWDrift.tex b/doc/greens_functions/tex/greensFunctionsWDrift.tex new file mode 100644 index 00000000..efc4649d --- /dev/null +++ b/doc/greens_functions/tex/greensFunctionsWDrift.tex @@ -0,0 +1,24 @@ + +\documentclass[twoside]{report} + +\include{include/include} +\include{include/subSectionOnly} +%Page numbers instead of name. +\fancyfoot[RO, LE]{\thepage} + + +\begin{document} + +\title{Greens functions, survival probabilies and propensity functions (1D and +2D)} +\thispagestyle{empty} +\maketitle + + +\input{include/greensFunction1DwDriftAbsAbs.include} +\include{include/greensFunction1DwDriftAbsInf.include} +\include{include/greensFunction1DwDriftRadAbs.include} +\include{include/greensFunction1DwDriftRadInf.include} +\include{include/greensFunction2DwDriftRadAbs.include} + +\end{document} diff --git a/doc/greens_functions/tex/include/greensFunction1DAbsAbs.include.tex b/doc/greens_functions/tex/include/greensFunction1DAbsAbs.include.tex new file mode 100644 index 00000000..e5118ced --- /dev/null +++ b/doc/greens_functions/tex/include/greensFunction1DAbsAbs.include.tex @@ -0,0 +1,29 @@ + +\subsection{1D Abs Abs} + +\paragraph{Green's function} +\begin{align} + p(z,t|z',t'=0) = \frac{2}{L}\sum_{n=1}^{\infty} + e^{-D_1n^2\pi^2t/L^2} \sin\bigg(\frac{n\pi z}{L}\bigg) \sin\left(\frac{n\pi z'}{L}\right). +\end{align} + +\paragraph{Survival probability} +\begin{align} + S_z(t) = \int_0^L p dz = \frac{4}{\pi} \sum_{m=0}^{\infty} \frac{1}{2m+1} + e^{-D_1(2m+1)^2\pi^2t/L^2} \sin\left(\frac{(2m+1)\pi z'}{L}\right). +\end{align} + +\paragraph{Propensity function} +\begin{align} + q_{z=0}(t) &= \left.D_1 \pd{p}{z}\right|_{z=0} = \frac{2\pi D_1}{L^2} + \sum_{n=1}^{\infty} e^{-D_1n^2\pi^2t/L^2} n \sin\left(\frac{n\pi z'}{L}\right),\\ + q_{z=L}(t) &= \left.- D_1 \pd{p}{z}\right|_{z=L} = \frac{2\pi D_1}{L^2} + \sum_{n=1}^{\infty} e^{-D_1n^2\pi^2t/L^2} n (-1)^{n+1} \sin\left(\frac{n\pi z'}{L}\right). +\end{align} + +\paragraph{Cumulative Green's function} +\begin{align} + P(Z,t|z',t'=0) = \int_0^Z p dz = + \frac{2}{\pi} \sum_{n=1}^{\infty} e^{-D_1n^2\pi^2 t/L^2} + \frac{1}{n} \left[ 1 - \cos\left(\frac{n\pi Z}{L}\right) \right] \sin\left(\frac{n\pi z'}{L}\right). +\end{align} \ No newline at end of file diff --git a/doc/greens_functions/tex/include/greensFunction1DAbsInf.include.tex b/doc/greens_functions/tex/include/greensFunction1DAbsInf.include.tex new file mode 100644 index 00000000..2329a085 --- /dev/null +++ b/doc/greens_functions/tex/include/greensFunction1DAbsInf.include.tex @@ -0,0 +1,27 @@ + +\subsection{1D Abs Inf} + +\paragraph{Green's function} +\begin{align} + p(z,t|z',t'=0) &= \frac{1}{\sqrt{4\pi D_1t}} \left[ e^{-\frac{1}{4D_1t}(z-z')^2} - e^{-\frac{1}{4D_1t}(z+z')^2} \right]. \\ + &= \frac{1}{\sqrt{4\pi D_1t}} \left[ 1 - e^{-\frac{zz'}{D_1t}} \right]e^{-\frac{1}{4D_1t}(z-z')^2}. +\end{align} + +\paragraph{Survival probability} +\begin{align} + S_{z=0}(t) = \int_0^\infty p dz = + \text{erf}\left( \frac{z'}{\sqrt{4D_1 t}} \right). +\end{align} + +\paragraph{Propensity functions} +\begin{align} + q_{z=0}(t) = -\frac{d}{dt} S_{z=0}(t)= + \frac{z'}{2\sqrt{\pi D_1 t^3}} e^{-\frac{z'^2}{4D_1t}}. +\end{align} + +\paragraph{Cumulative Green's function} +\begin{align} + P_{z=0}(Z,t) = \int_a^Z p dz = + \erf{\left(\frac{z'}{\sqrt{4D_1t}}\right)} + + \frac{1}{2} \left[ \erf{\left(\frac{Z-z'}{\sqrt{4D_1t}}\right)} - \erf{\left(\frac{Z+z'}{\sqrt{4D_1t}}\right)} \right] +\end{align} \ No newline at end of file diff --git a/doc/greens_functions/tex/include/greensFunction1DAbsSinkAbs.include.tex b/doc/greens_functions/tex/include/greensFunction1DAbsSinkAbs.include.tex new file mode 100644 index 00000000..9ef89846 --- /dev/null +++ b/doc/greens_functions/tex/include/greensFunction1DAbsSinkAbs.include.tex @@ -0,0 +1,38 @@ + +\subsection{1D Abs Sink Abs} + +\paragraph{Green's function} +is divided into two composite domains with functions $p_l$ and $p_r$ and the sink (rate k) at the origin. The left and right domains have lengths of respectively $L_l$ and $L_r$. $x_0$ is assumed to be in the right domain. + \begin{multline} + p_r(x,t|x_0) = - \sum_{i=1}^{\infty} \, e^{- D \beta_i ^2 t} \, 2 \, \mathrm{sin} \, \beta_i (L_r - x) \, \frac{D \beta_i \, \mathrm{sin} \, \beta_i (L_l + x_0) + k \, \mathrm{sin} \, \beta_i L_l \,\, \mathrm{sin} \, \beta_i x_0}{h'( \beta_i )}, +\end{multline} +\begin{multline} + p_l(x,t,|x_0) = -\sum_{i=1}^{\infty} e^{- D \beta_i ^2 t} \, 2 D \beta_i \, \frac{\mathrm{sin} \, \beta_i (L_l + x) \,\, \mathrm{sin} \, \beta_i (L_r - x_0)}{h'( \beta_i )}, +\end{multline} +where $L=L_r + L_l$ in both equations. Note that when $p_r$ is evaluated for $x_reaction_rule. + + """ +\end{verbatim} + +\subsubsection{Creating regions} + +\begin{verbatim} +_gfrd.create_cuboidal_region.__doc__ = \ +"""create_cuboidal_region(id, corner, diagonal) + +Create and return a new cuboidal Region. + +Arguments: + - id + a descriptive name. + - corner + the point [x, y, z] of the cuboidal Region closest to + [0, 0, 0]. Units: [meters, meters, meters] + - diagonal + the vector [x, y, z] from the corner closest to [0, 0, 0], to + the corner furthest away from [0, 0, 0]. Units: + [meters, meters, meters] + +""" + +_gfrd.create_cylindrical_surface.__doc__ = \ +"""create_cylindrical_surface(id, corner, radius, orientation, length) + +Create and return a new cylindrical Surface. + +Arguments: + - id + a descriptive name. + - corner + the point [x, y, z] on the axis of the cylinder closest to + [0, 0, 0]. Units: [meters, meters, meters] + - radius + the radius of the cylinder. Units: meters. + - orientation + the unit vector [1, 0, 0], [0, 1, 0] or [0, 0, 1] along the + axis of the cylinder. + - length + the length of the cylinder. Should be equal to the world_size. + Units: meters. + +Surfaces are not allowed to touch or overlap. + +""" + +_gfrd.create_planar_surface.__doc__ = \ +"""create_planar_surface(id, corner, unit_x, unit_y, length_x, length_y) + +Create and return a new planar Surface. + +Arguments: + - id + a descriptive name. + - corner + the point [x, y, z] on the plane closest to [0, 0, 0]. Units: + [meters, meters, meters] + - unit_x + a unit vector [1, 0, 0], [0, 1, 0] or [0, 0, 1] along the + plane. + - unit_y + a unit vector [1, 0, 0], [0, 1, 0] or [0, 0, 1] along the plane + and perpendicular to unit_x. + - length_x + the length of the plane along the unit vector unit_x. Should be + equal to the world_size. Units: meters. + - length_y + the length of the plane along the unit vector unit_y. Should be + equal to the world_size. Units: meters. + +Surfaces are not allowed to touch or overlap. + +""" +\end{verbatim} + +As particles, regions should be added to the model. +\begin{verbatim} + def add_structure(self, structure): + """Add a Structure (Region or Surface) to the ParticleModel. + + Arguments: + - structure + a Region or Surface created with one of the functions + model.create_<>_region or model.create_<>_surface. + + """ + assert isinstance(structure, _gfrd.Structure) + self.structures[structure.id] = structure + return structure +\end{verbatim} + +\subsubsection{Adding reaction rules to the model} + +Function set\_all\_repulsive is called automatically, but it's docstring is instructive. +\begin{verbatim} + def set_all_repulsive(self): + """Set all 'other' possible ReactionRules to be repulsive. + + By default an EGFRDSimulator will assume: + - a repulsive bimolecular reaction rule (k=0) for each + possible combination of reactants for which no + bimolecular reaction rule is specified. + + This method explicitly adds these ReactionRules to the + ParticleModel. + + """ +\end{verbatim} + +\begin{verbatim} +def create_unimolecular_reaction_rule(reactant, product, k): + """Example: A -> B. + + Arguments: + - reactant + a Species. + - product + a Species. + - k + reaction rate. Units: per second. (Rough order of magnitude: + 1e-2 /s to 1e2 /s). + + The reactant and the product should be in/on the same + Region or Surface. + + There is no distinction between an intrinsic and an overall reaction + rate for a unimolecular ReactionRule. + + A unimolecular reaction rule defines a Poissonian process. + + """ +\end{verbatim} + +\begin{verbatim} +def create_decay_reaction_rule(reactant, k): + """Example: A -> 0. + + Arguments: + - reactant + a Species. + - k + reaction rate. Units: per second. (Rough order of magnitude: + 1e-2 /s to 1e2 /s). + + There is no distinction between an intrinsic and an overall reaction + rate for a decay ReactionRule. + + A decay reaction rule defines a Poissonian process. + + """ +\end{verbatim} + +\begin{verbatim} +def create_annihilation_reaction_rule(reactant1, reactant2, ka): + """Example: A + B -> 0. + + Arguments: + - reactant1 + a Species. + - reactant2 + a Species. + - ka + intrinsic reaction rate. Units: meters^3 per second. (Rough + order of magnitude: 1e-16 m^3/s to 1e-20 m^3/s). + + The reactants should be in/on the same Region or Surface. + + ka should be an *intrinsic* reaction rate. You can convert an + overall reaction rate (kon) to an intrinsic reaction rate (ka) with + the function utils.k_a(kon, kD), but only for reaction rules in 3D. + + By default an EGFRDSimulator will assume a repulsive + bimolecular reaction rule (ka=0) for each possible combination of + reactants for which no bimolecular reaction rule is specified. + You can explicitly add these reaction rules to the model with the + method model.ParticleModel.set_all_repulsive. + + """ +\end{verbatim} + +\begin{verbatim} +def create_binding_reaction_rule(reactant1, reactant2, product, ka): + """Example: A + B -> C. + + Arguments: + - reactant1 + a Species. + - reactant2 + a Species. + - product + a Species. + - ka + intrinsic reaction rate. Units: meters^3 per second. (Rough + order of magnitude: 1e-16 m^3/s to 1e-20 m^3/s) + + The reactants and the product should be in/on the same + Region or Surface. + + A binding reaction rule always has exactly one product. + + ka should be an *intrinsic* reaction rate. You can convert an + overall reaction rate (kon) to an intrinsic reaction rate (ka) with + the function utils.k_a(kon, kD), but only for reaction rules in 3D. + + By default an EGFRDSimulator will assume a repulsive + bimolecular reaction rule (ka=0) for each possible combination of + reactants for which no bimolecular reaction rule is specified. + You can explicitly add these reaction rules to the model with the + method model.ParticleModel.set_all_repulsive. + + """ +\end{verbatim} + +\begin{verbatim} +def create_unbinding_reaction_rule(reactant, product1, product2, kd): + """Example: A -> B + C. + + Arguments: + - reactant + a Species. + - product1 + a Species. + - product2 + a Species. + - kd + intrinsic reaction rate. Units: per second. (Rough order of + magnitude: 1e-2 /s to 1e2 /s). + + The reactant and the products should be in/on the same + Region or Surface. + + An unbinding reaction rule always has exactly two products. + + kd should be an *intrinsic* reaction rate. You can convert an + overall reaction rate (koff) for this reaction rule to an intrinsic + reaction rate (kd) with the function utils.k_d(koff, kon, kD) or + utils.k_d_using_ka(koff, ka, kD). + + An unbinding reaction rule defines a Poissonian process. + + """ +\end{verbatim} + +To put this reactions into effect one should also call \texttt{add\_reaction\_rule}, e.g.: +\begin{verbatim} + r1 = model.create_binding_reaction_rule(A, B, C, kf) + m.network_rules.add_reaction_rule(r1) +\end{verbatim} + +\subsection{Functions from \texttt{gfrdbase.py}} + +\subsubsection{Core functions} + +\begin{verbatim} +def create_world(m, matrix_size=10): + """Create a world object. + + The world object keeps track of the positions of the particles + and the protective domains during an eGFRD simulation. + + Arguments: + - m + a ParticleModel previously created with model.ParticleModel. + - matrix_size + the number of cells in the MatrixSpace along the x, y and z + axis. Leave it to the default number if you don't know what + to put here. + + The simulation cube "world" is divided into (matrix_size x matrix_size + x matrix_size) cells. Together these cells form a MatrixSpace. The + MatrixSpace keeps track in which cell every particle and protective + domain is at a certain point in time. To find the neigherest + neighbours of particle, only objects in the same cell and the 26 + (3x3x3 - 1) neighbouring cells (the simulation cube has periodic + boundary conditions) have to be taken into account. + + The matrix_size limits the size of the protective domains. If you + have fewer particles, you want a smaller matrix_size, such that the + protective domains and thus the eGFRD timesteps can be larger. If + you have more particles, you want a larger matrix_size, such that + finding the neigherest neighbours is faster. + + Example. In samples/dimer/dimer.py a matrix_size of + (N * 6) ** (1. / 3.) is used, where N is the average number of + particles in the world. + + """ +\end{verbatim} + + +\subsubsection{Handling regions (surfaces, cylinders, etc)} + +\begin{verbatim} +def get_closest_surface(world, pos, ignore): + """Return + - closest surface + - distance to closest surface + + We can not use matrix_space, it would miss a surface if the + origin of the surface would not be in the same or neighboring + cells as pos.""" +\end{verbatim} + +\begin{verbatim} +def get_closest_surface_within_radius(world, pos, radius, ignore): + """Return: + - surface within radius or None + - closest surface (regardless of radius) + - distance to closest surface""" +\end{verbatim} + +\subsubsection{Adding particles} +\begin{verbatim} +functions +def throw_in_particles(world, sid, n): + """Add n particles of a certain Species to the specified world. + + Arguments: + - sid + a Species previously created with the function + model.Species. + - n + the number of particles to add. + + Make sure to first add the Species to the model with the method + model.ParticleModel.add_species_type. + + """ +\end{verbatim} + +\begin{verbatim} +def place_particle(world, sid, position): + """Place a particle of a certain Species at a specific position in + the specified world. + + Arguments: + - sid + a Species previously created with the function + model.Species. + - position + a position vector [x, y, z]. Units: [meters, meters, meters]. + + Make sure to first add the Species to the model with the method + model.ParticleModel.add_species_type. + + """ +\end{verbatim} + +\subsubsection{Obtaining information about particles and species} + +\begin{verbatim} + + def get_species(self): + """ + Return an iterator over the Species in the simulator. + To be exact, it returns an iterator that returns + all SpeciesInfo instances defined in the simulator. + + Arguments: + - sim an EGFRDSimulator. + + More on output: + - SpeciesInfo is defined in the C++ code, and has + the following attributes that might be of interest + to the user: + * SpeciesInfo.id + * SpeciesInfo.radius + * SpeciesInfo.structure_type_id + * SpeciesInfo.D + * SpeciesInfo.v + + Example: + + for species in s.get_species(): + print str(species.radius) + + Note: + Dumper.py also holds a copy of this function. + + """ + + def get_first_pid(self, sid): + """ + Returns the first particle ID of a certain species. + + Arguments: + - sid: a(n) (eGFRD) simulator. + """ + + def get_position(self, object): + """ + Function that returns particle position. Can take multiple + sort of arguments as input. + + Arguments: + - object: can be: + * pid_particle_pair tuple + (of which pid_particle_pair[0] is _gfrd.ParticleID). + * An instance of _gfrd.ParticleID. + + * An instance of _gfrd.Particle. + * An instance of _gfrd.SpeciesID. + In the two latter cases, the function returns the + first ID of the position of the first particle of + the given species. + """ + +\end{verbatim} + +\subsection{Functions from \texttt{egfrd.py}} + +\subsubsection{Core functions} +\begin{verbatim} +class EGFRDSimulator(ParticleSimulatorBase): + """ + """ + def __init__(self, world, rng=myrandom.rng, network_rules=None): + """Create a new EGFRDSimulator. + + Arguments: + - world + a world object created with the function + gfrdbase.create_world. + - rng + a random number generator. By default myrandom.rng is + used, which uses Mersenne Twister from the GSL library. + You can set the seed of it with the function + myrandom.seed. + - network_rules + you don't need to use this, for backward compatibility only. + + """ +\end{verbatim} + +\begin{verbatim} + def step(self): + """Execute one eGFRD step. + + """ +\end{verbatim} +(Function belongs to EGFRDSimulator class, call with \texttt{EGFRDSimulator.get\_next\_time}.) + +\begin{verbatim} + def stop(self, t): + """Synchronize all particles at time t. + + With eGFRD, particle positions are normally updated + asynchronously. This method bursts all protective domains and + assigns a position to each particle. + + Arguments: + - t + the time at which to synchronize the particles. Usually + you will want to use the current time of the simulator: + EGFRDSimulator.t. + + This method is called stop because it is usually called at the + end of a simulation. It is possible to call this method at an + earlier time. For example the Logger module does this, because + it needs to know the positions of the particles at each log + step. + + """ +\end{verbatim} +(Function belongs to EGFRDSimulator class, call with \texttt{EGFRDSimulator.get\_next\_time}.) + +\subsubsection{Simulator time (manipulation) functions} + +\begin{verbatim} + def get_next_time(self): + """ + Returns the time it will be when the next egfrd timestep + is completed. + """ +\end{verbatim} +(Function belongs to EGFRDSimulator class, call with \texttt{EGFRDSimulator.get\_next\_time}.) + + +\begin{verbatim} + def reset(self): + """ + This function resets the "records" of the simulator. This means + the simulator time is reset, the step counter is reset, events + are reset, etc. + Can be for example usefull when users want to "stirr" the + simulation before starting the "real experiment". + """ +\end{verbatim} +(Function belongs to EGFRDSimulator class, call with \texttt{EGFRDSimulator.get\_next\_time}.) + +\subsubsection{Get data} + +\begin{verbatim} + + def print_report(self, out=None): + """Print various statistics about the simulation. + + Arguments: + - None + + """ + +\end{verbatim} + +\subsubsection{Get position and distance data} +\begin{verbatim} +def get_closest_surface(world, pos, ignore): + """Return + - closest surface + - distance to closest surface + + We can not use matrix_space, it would miss a surface if the + origin of the surface would not be in the same or neighboring + cells as pos.""" + +def get_closest_surface_within_radius(world, pos, radius, ignore): + """Return: + - surface within radius or None + - closest surface (regardless of radius) + - distance to closest surface""" + + +\end{verbatim} + +\subsubsection{Manipulating the system} +\begin{verbatim} + def clear_volume(self, pos, radius, ignore=[]): + """ Burst domains within a certain volume and give their ids. + + This function actually has a confusing name, as it only bursts + domains within a certain radius, and gives their ids. It doesn't + remove the particles or something like that. + + (Bursting means it propagates the particles within the domain until + the current time, and then creates a new, minimum-sized domain.) + + Arguments: + - pos: position of area to be "bursted" + - radius: radius of area to be "bursted" + - ignore: domains that should be ignored, none by default. + """ +\end{verbatim} + + +\subsubsection{Additional functions} +The class \texttt{EGFRDSimulator} contains several functions which might be usefull to eGFRD users in very specific cases. Because of their specific nature, they don't contain docstrings. +\begin{verbatim} + def get_matrix_cell_size(self): + return self.containers[0].cell_size + + def set_user_max_shell_size(self, size): + self.user_max_shell_size = size + + def get_user_max_shell_size(self): + return self.user_max_shell_size + + def get_max_shell_size(self): + return min(self.get_matrix_cell_size() * .5 / SAFETY, + self.user_max_shell_size) +\end{verbatim} + +\subsection{Functions from \texttt{dumper.py}} + +\subsubsection{Getting information on species/particles} +\begin{verbatim} +def get_species(sim): + """Return an iterator over the Species in the simulator. + + Arguments: + - sim + an EGFRDSimulator. + + """ +\end{verbatim} + +\begin{verbatim} +def dump_species(sim): + """Return a string containing the Species in the simulator. + + Arguments: + - sim + an EGFRDSimulator. + + """ +\end{verbatim} + +\begin{verbatim} +def get_species_names(sim): + """Return an iterator over the names of the Species in the + simulator. + + Arguments: + - sim + an EGFRDSimulator. + + """ +\end{verbatim} + +\begin{verbatim} +def dump_species_names(sim): + """Return a string containing the names of the Species in the + simulator. + + Arguments: + - sim + an EGFRDSimulator. + + """ +\end{verbatim} + +\begin{verbatim} +def _get_species_type_by_name(sim, name): + # Return the type of a species with a certain name + # (Function not intended for public use.) + # + # Arguments: + # - sim + # an EGFRDSimulator + # - name + # species name +\end{verbatim} +Note that this function is NOT intended for users. It is listed because it MIGHT come in handy. (But should perhaps be removed from the list as there are other more user-friendly functions that give the same result.) + +\begin{verbatim} +def _get_particles_by_sid(sim, sid): + # Return a generator (using "yield") to loop over (pid, particle). + # (Function not intended for public use.) + # + # Arguments: + # - sim + # an EGFRDSimulator + # - sid + # ID of a species + # + # E.g.: + # + # myparticles = _get_particles_by_sid(sim, sid) + # + # for mypid, myparticle in myparticles: + # print str(str(mypid), str(myparticle)) +\end{verbatim} +Note that this function is NOT intended for users. It is listed because it MIGHT come in handy. (But should perhaps be removed from the list as there are other more user-friendly functions that give the same result.) + +\begin{verbatim} +def get_particles(sim, identifier=None): + """Return an iterator over the + (particle identifier, particle)-pairs in the simulator. + + Arguments: + - sim + an EGFRDSimulator. + - identifier + a Species or the name of a Species. If none is specified, + all (particle identifier, particle)-pairs will be returned. + + """ +\end{verbatim} +There is actually a case of bad naming here. (particle identifier, particle)-pairs refers to tuples containing particle information. It has nothing to do with particles being paired in the algorithm. + +\begin{verbatim} +def dump_particles(sim, identifier=None): + """Return a string containing the + (particle identifier, particle)-pairs in the simulator. + + Arguments: + - sim + an EGFRDSimulator. + - identifier + a Species or the name of a Species. If none is specified, + all (particle identifier, particle)-pairs will be returned. + + """ +\end{verbatim} +There is actually a case of bad naming here. (particle identifier, particle)-pairs refers to tuples containing particle information. It has nothing to do with particles being paired in the algorithm. + +\begin{verbatim} +def _get_number_of_particles_by_sid(sim, sid): + """ + Returns the number of particles of a certain species. + + Arguments: + - sim + an EGFRDSimulator. + - sid + ID of a species + + """ +\end{verbatim} +There is actually a case of bad naming here. (particle identifier, particle)-pairs refers to tuples containing particle information. It has nothing to do with particles being paired in the algorithm. + +\begin{verbatim} +def get_number_of_particles(sim, identifier=None): + """Return the number of particles of a certain Species in the + simulator. + + Arguments: + - sim + either an EGFRDSimulator or a GillespieSimulator. + - identifier + a Species. Optional. If none is specified, a list of + (Species name, number of particles)-pairs will be returned. + + """ +\end{verbatim} + +\begin{verbatim} +def dump_number_of_particles(sim, identifier=None): + """Return a string containing the number of particles of a certain + Species in the simulator. + + Arguments: + - sim + either an EGFRDSimulator or a GillespieSimulator. + - identifier + a Species. Optional. If none is specified, + a string of (Species name, number of particles)-pairs will + be returned. + + """ +\end{verbatim} + +\subsubsection{Get information on domains} + +\begin{verbatim} +def get_domains(egfrdsim): + """Return an iterator over the protective domains in the simulator. + + Arguments: + - egfrdsim + an EGFRDSimulator + """ +\end{verbatim} + +\begin{verbatim} +def dump_domains(egfrdsim): + """Return an string containing the protective domains in the + simulator. + + """ +\end{verbatim} + +\subsubsection{Get information on reaction rules} + +\begin{verbatim} +def get_reaction_rules(model_or_simulator): + """Return three lists with all the reaction rules defined in the + ParticleModel or EGFRDSimulator. + + The three lists are: + - reaction rules of only one reactant. + - reaction rules between two reactants with a reaction rate + larger than 0. + - repulsive reaction rules between two reactants with a + reaction rate equal to 0. + + Arguments: + - model_or_simulator + a ParticleModel or EGFRDSimulator. + + """ +\end{verbatim} + +\begin{verbatim} +def _dump_reaction_rule(model, reaction_rule): + """Helper. Return ReactionRule as string. + + ReactionRule.__str__ would be good, but we are actually getting a + ReactionRuleInfo or ReactionRuleCache object.""" +\end{verbatim} + +\begin{verbatim} +def dump_reaction_rules(model_or_simulator): + """Return a formatted string containing all the reaction rules + defined in the ParticleModel or EGFRDSimulator. + + Arguments: + - model_or_simulator + a ParticleModel or EGFRDSimulator. + + """ +\end{verbatim} + +\subsection{Functions from \texttt{utils.py}} + +This file contains functions on common mathematical objects, transformations and formulas. Some of these functions are used throughout the algorithm, and some are supplied for convenience. +For the user, none of these functions are \textit{needed} to make a simulation work, but some might come in handy. + +\subsubsection{Mathematical comparisons} +\begin{verbatim} +def feq(a, b, typical=1, tolerance=TOLERANCE): + """Return True if a and b are equal, subject to given tolerances. + Float comparison. + + Also see numpy.allclose(). + + The (relative) tolerance must be positive and << 1.0 + + Instead of specifying an absolute tolerance, you can speciy a + typical value for a or b. The absolute tolerance is then the + relative tolerance multipied by this typical value, and will be + used when comparing a value to zero. By default, the typical + value is 1.""" + +def fgreater(a, b, typical=1, tolerance=TOLERANCE): + """Return True if a is greater than b, subject to given tolerances. + Float comparison.""" + +def fless(a, b, typical=1, tolerance=TOLERANCE): + """Return True if a is less than b, subject to given tolerances. + Float comparison.""" + +def fgeq(a, b, typical=1, tolerance=TOLERANCE): + """Return True if a is greater or equal than b, subject to given + tolerances. Float comparison.""" + +def fleq(a, b, typical=1, tolerance=TOLERANCE): + """Return True if a is less than or equal than b, subject to given + tolerances. Float comparison.""" + +\end{verbatim} + +\subsubsection{Conversions} + +As these functions only contain a single line of formula code, this line is also supplied. +\begin{verbatim} +def per_M_to_m3(rate): + """Convert a reaction rate from units 'per molar per second' to + units 'meters^3 per second'. + + """ + return rate / (1000 * N_A) + +def per_microM_to_m3(rate): + """Convert a reaction rate from units 'per micromolar per second' to + units 'meters^3 per second'. + + """ + return per_M_to_m3(rate * 1e6) + +def M_to_per_m3(molar): + """Convert a concentration from units 'molar' to units 'per + meters^3'. + + """ + return molar * (1000 * N_A) + +def microM_to_per_m3(micromolar): + """Convert a concentration from units 'micromolar' to units 'per + meters^3'. + + """ + return M_to_per_m3(micromolar / 1e6) + +def C2N(c, V): + """Calculate the number of particles in a volume 'V' (dm^3) + with a concentration 'c' (mol/dm^3). + + """ + return c * V * N_A # round() here? +\end{verbatim} + +Conversions involving rates: +\begin{verbatim} + +def k_D(Dtot, sigma): + """Calculate the 'pseudo-'reaction rate (kD) caused by diffusion. + + kD is equal to 1 divided by the time it takes for two particles to + meet each other by diffusion. It is needed when converting from + an intrinsic reaction rate to an overall reaction rates or vice + versa. + + Example: + - A + B -> C. + + Arguments: + - Dtot: + the diffusion constant of particle A plus the diffusion + constant of particle B. Units: meters^2/second. + - sigma + the radius of particle A plus the radius of particle B. + Units: meters. + + This function is only available for reaction rules in 3D. No + analytical expression for kD in 1D or 2D is currently known. + + """ + return 4.0 * numpy.pi * Dtot * sigma + +def k_a(kon, kD): + """Convert an overall reaction rate (kon) for a binding/annihilation + reaction rule to an intrinsic reaction rate (ka). + + Example: + - A + B -> C + binding reaction rule + - A + B -> 0 + annihilation reaction rule + + Arguments: + - kon + the overall reaction rate for the reaction rule. Units: + meters^3/second. + - kD + the 'pseudo-'reaction rate caused by the diffusion of + particles A and B. See the function k_D(). Units: + meters^3/second. + + This function is only available for reaction rules in 3D. No + analytical expression for kD in 1D or 2D is currently known. + + """ + if kon > kD: + raise RuntimeError, 'kon > kD.' + ka = 1. / ((1. / kon) - (1. / kD)) + return ka + +def k_d(koff, kon, kD): + """Convert an overall reaction rate (koff) for an unbinding reaction + rule to an intrinsic reaction rate (kd). + + This one is a bit tricky. We consider reaction rules with only 1 + reactant. In case there is only 1 product also, no conversion in + necessary. But when the reaction rule has 2 products, we need to + take the reverse reaction rule into account and do the proper + conversion. + + Example: + - C -> A + B + unbinding reaction rule + - A + B -> C + reverse reaction rule + + Arguments: + - koff + the overall reaction rate for the unbinding reaction rule. + Units: meters^3/second. + - kon + the overall reaction rate for the reverse reaction rule. + Units: meters^3/second. + - kD + the 'pseudo-'reaction rate caused by the diffusion of + particles A and B. See the function k_D(). Units: + meters^3/second. + + This function is only available for reaction rules in 3D. No + analytical expression for kD in 1D or 2D is currently known. + + """ + ka = k_a(kon, kD) + kd = k_d_using_ka(koff, ka, kD) + return kd + +def k_d_using_ka(koff, ka, kD): + """Convert an overall reaction rate (koff) for an unbinding reaction + rule to an intrinsic reaction rate (kd). + + Similar to the function k_d(), but expects an intrinsic rate (ka) + instead of an overall rate (kon) for the reversed reaction rule as + the second argument. + + This function is only available for reaction rules in 3D. No + analytical expression for kD in 1D or 2D is currently known. + + """ + kd = koff * (1 + float(ka) / kD) + return kd + +def k_on(ka, kD): + """Convert an intrinsic reaction rate (ka) for a binding/annihilation + reaction rule to an overall reaction rate (kon). + + The inverse of the function k_a(). + + Rarely needed. + + This function is only available for reaction rules in 3D. No + analytical expression for kD in 1D or 2D is currently known. + + """ + kon = 1. / ((1. / kD) + (1. / ka)) # m^3/s + return kon + +def k_off(kd, kon, kD): + """Convert an intrinsic reaction rate (kd) for an unbinding reaction + rule to an overall reaction rate (koff). + + The inverse of the function k_d(). + + Rarely needed. + + This function is only available for reaction rules in 3D. No + analytical expression for kD in 1D or 2D is currently known. + + """ + ka = k_a(kon, kD) + koff = k_off_using_ka(kd, ka, kD) + return koff + +def k_off_using_ka(kd, ka, kD): + """Convert an intrinsic reaction rate (kd) for an unbinding reaction + rule to an overall reaction rate (koff). + + Similar to the function k_off(), but expects an intrinsic rate + (ka) instead of an overall rate (kon) as the second argument. + + Rarely needed. + + This function is only available for reaction rules in 3D. No + analytical expression for kD in 1D or 2D is currently known. + + """ + koff = 1. / (float(ka) / (kd * kD) + (1. / kd)) + return koff +\end{verbatim} + + +\subsubsection{Some convenient functoins} + +These functions do not contain docstrings, are sometimes self-explanatory and probably not needed that often in simulations. Therefore only definitions of functions that might be of interest to the user are listed here: + +Mean arrival time: +\begin{verbatim} +def mean_arrival_time(r, D): + return (r * r) / (6.0 * D) +\end{verbatim} + +Calculating with distances: +\begin{verbatim} +def distance_sq_array_simple(position1, positions, fsize = None): + +def distance_array_simple(position1, positions, fsize = None): + +distance = _gfrd.distance + +distance_cyclic = _gfrd.distance_cyclic + +def distance_sq_array_cyclic(position1, positions, fsize): + +def distance_array_cyclic(position1, positions, fsize = 0): + +\end{verbatim} + +Some vector functions: +\begin{verbatim} +def cartesian_to_spherical(c): + +def spherical_to_cartesian(s): + +def random_unit_vector_s(): + +def random_unit_vector(): + +def random_vector(r): + +def random_vector2D(r): + +def length(a): + +def normalize(a, l=1): + +def vector_angle(a, b): + +def vector_angle_against_z_axis(b): + +def crossproduct(a, b): + +def crossproduct_against_z_axis(a): + +def rotate_vector(v, r, alpha): +\end{verbatim} + +\subsection{Notes on other files} + +\subsubsection{\texttt{bd.py}: Brownian Dynamic Simulator} +The class \texttt{BDSimulator} can be used in the same way as the EGFRDSimulator class, but performs Brownian Dynamics (BD) instead. Users who want to perform eGFRD simulations never need this. The simulator can be used for comparison of the eGFRD algorithm with Brownian Dynamics. + +\subsubsection{\texttt{gillespie.py}: Gillespie Simulator} +Similar to the BD simulator, the class \texttt{GillespieSimulatorBase} can be used for Gillespie type simulations. + +\subsubsection{\texttt{legacy.py}: Old redundant functions} +This module is called nowhere in the Python code. It contains an archive of outdated code. + +\subsubsection{\texttt{multi.py}, \texttt{pair.py}, \texttt{single.py}} +These file contain the code that handle the specific events that happen in the different categories of domains. + +\subsubsection{\texttt{myrandom.py}} +Contains a few convenient lines of code used when using random functions. + +\subsubsection{\texttt{make\_cjy\_table.py.py}, \texttt{make\_sjy\_table.py.py}} +Generate (respectively cylindrical and spherical) bessel function tables. + + +\subsection{Function from module \texttt{logger.py}} + +This module contains two loggers. One logger that logs in the hdf5 format, for obvious reasons in class \texttt{HDF5Logger}. Note that this logger requires the module h5py. The other logger gives output dictated more by the nature of the eGFRD algorithm. +The loggers are not (yet) explained in very much detail here, but both function in the same way. A logger class is made, which takes input on to which file to write, the logger can be started by \texttt{start()} and steps are logged by the function \texttt{log()}. + +\subsubsection{HDF5 logger} + +\begin{verbatim} +class HDF5Logger(object): + def __init__(self, logname, directory='data', split=False): + + def log(self, sim, time): + + def start(self, sim): +\end{verbatim} + +\subsubsection{"Normal" logger} +\begin{verbatim} +class Logger(object): + def __init__(self, logname='log', directory='data', comment=''): + + def log(self, sim, time): + + def start(self, sim): +\end{verbatim} + +(Note that not all functions contained by this class are listed, just functions deemed usefull for user interface.) + +\section{Todo} + +- \texttt{sid = identifier.id} gives the sid, but what exactly is identifier? +- Updating following text (On C++ data structures).. + +\section{On C++ data structures} + +\subsection{SpeciesInfo} + +The information on species can be found in object created in the C++ code. Some Python functions return the SpeciesInfo object, which has the following attributes\footnote{Data extracted from Doxygen files.}: + +\begin{verbatim} +Public Types +typedef Tid_ identifier_type +typedef TD_ D_type +typedef TD_ v_type +typedef Tlen_ length_type +typedef Tstructure_type_id_ structure_type_id_type + +Public Member Functions +identifier_type const & id () const +length_type const & radius () const +length_type & radius () +structure_type_id_type const & structure_type_id () const +structure_type_id_type & structure_type_id () +D_type const & D () const +D_type & D () +v_type const & v () const +v_type & v () +bool operator== (SpeciesInfo const &rhs) const +bool operator!= (SpeciesInfo const &rhs) const + SpeciesInfo (identifier_type const &id, D_type const &D=0., length_type const &r=0., structure_type_id_type const &s= NULL, v_type const &v=0.) + +template +struct SpeciesInfo< Tid_, TD_, Tlen_, Tstructure_type_id_ > +\end{verbatim} + +\subsection{Particle} + +The particle information is held by a class in the C++ code. A single particle is + +\end{document} diff --git a/doc/pseudocode/Makefile b/doc/pseudocode/Makefile new file mode 100644 index 00000000..5536a9c9 --- /dev/null +++ b/doc/pseudocode/Makefile @@ -0,0 +1,15 @@ + +# sudo yum install python-pygments + +all: pseudocode diagrams + +pseudocode: + pygmentize -l python -f html -O full -o pseudocode.html pseudocode.py + +.PHONY: diagrams +diagrams: + cd diagrams && make + +clean: + rm -rf pseudocode.html + cd diagrams && make clean diff --git a/doc/pseudocode/diagrams.html b/doc/pseudocode/diagrams.html new file mode 100644 index 00000000..9a4fdde8 --- /dev/null +++ b/doc/pseudocode/diagrams.html @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/doc/pseudocode/diagrams/Makefile b/doc/pseudocode/diagrams/Makefile new file mode 100644 index 00000000..4ba1126e --- /dev/null +++ b/doc/pseudocode/diagrams/Makefile @@ -0,0 +1,22 @@ + +# sudo yum install graphviz + +all: egfrd fireSingle firePair fireMulti createMulti + +egfrd: + dot egfrd.dot -Tpng -o egfrd.png + +fireSingle: + dot fireSingle.dot -Tpng -o fireSingle.png + +firePair: + dot firePair.dot -Tpng -o firePair.png + +fireMulti: + dot fireMulti.dot -Tpng -o fireMulti.png + +createMulti: + dot createMulti.dot -Tpng -o createMulti.png + +clean: + rm -rf *.png diff --git a/doc/pseudocode/diagrams/createMulti.dot b/doc/pseudocode/diagrams/createMulti.dot new file mode 100644 index 00000000..a352c7eb --- /dev/null +++ b/doc/pseudocode/diagrams/createMulti.dot @@ -0,0 +1,23 @@ + +// dot createMulti.dot -Tpng -o createMulti.png + +digraph createMulti { + +edge [color=blue]; +node [color=red]; + +createMulti -> addToMultiRecursive +createMulti -> addToMultiRecursive +createMulti -> addToMultiRecursive [label=" neighbors"] + +addToMultiRecursive -> "type?" + +"type?" -> single +single -> addToMulti +single -> burstNonMultis +single -> addToMultiRecursive2 +addToMultiRecursive2 [label="addToMultiRecursive"] + +"type?" -> multi +multi -> mergeMultis +} diff --git a/doc/pseudocode/diagrams/egfrd.dot b/doc/pseudocode/diagrams/egfrd.dot new file mode 100644 index 00000000..3f49720e --- /dev/null +++ b/doc/pseudocode/diagrams/egfrd.dot @@ -0,0 +1,29 @@ + +// http://linuxdevcenter.com/pub/a/linux/2004/05/06/graphviz_dot.html +// dot egfrd.dot -Tpng -o egfrd.png + +// Directed graph. +digraph egfrd { + +edge [color=blue]; +node [color=red]; + +EGFRDSimulator [shape=box] + +EGFRDSimulator -> initialize +EGFRDSimulator -> step + +initialize -> createSingle +initialize -> createSingle +initialize -> createSingle + +step -> scheduler + +scheduler -> "topEvent" + +"topEvent" -> fireSingle [label="single?"] +"topEvent" -> firePair [label=" pair?"] +"topEvent" -> fireMulti [label="multi?"] + +} + diff --git a/doc/pseudocode/diagrams/fireMulti.dot b/doc/pseudocode/diagrams/fireMulti.dot new file mode 100644 index 00000000..63e006fc --- /dev/null +++ b/doc/pseudocode/diagrams/fireMulti.dot @@ -0,0 +1,15 @@ +// dot fireMulti.dot -Tpng -o fireMulti.png + +digraph fireMulti { + +edge [color=blue]; +node [color=red]; + +fireMulti -> "bd step (not discussed)" +fireMulti -> burstMulti [label="reaction\nor escape?"] + +burstMulti -> createSingle +burstMulti -> createSingle +burstMulti -> createSingle +} + diff --git a/doc/pseudocode/diagrams/firePair.dot b/doc/pseudocode/diagrams/firePair.dot new file mode 100644 index 00000000..da1ebf98 --- /dev/null +++ b/doc/pseudocode/diagrams/firePair.dot @@ -0,0 +1,42 @@ +// dot firePair.dot -Tpng -o firePair.png + +digraph firePair { + +edge [color=blue]; +node [color=red]; + +firePair -> "eventType?" + +"eventType?" -> "Single Reaction" +"eventType?" -> "Pair Reaction" +"eventType?" -> Escape + +"Single Reaction" -> burstPair +burstPair -> propagatePair1 +propagatePair1 [label=propagatePair] +propagatePair1 -> drawNewPositions1 +drawNewPositions1 [label=drawNewPositions] +drawNewPositions1 -> drawNewCoM1 +drawNewCoM1 [label=drawNewCoM] +drawNewPositions1 -> drawNewIV1 +drawNewIV1 [label=drawNewIV] + + +"Single Reaction" -> fireSingleReaction +fireSingleReaction -> createSingle [label=" product?"] + +"Pair Reaction" -> drawNewCoM +"Pair Reaction" -> createSingle1 +createSingle1 [label=createSingle] + + +Escape -> propagatePair2 +propagatePair2 [label=propagatePair] +propagatePair2 -> drawNewPositions2 +drawNewPositions2 [label=drawNewPositions] +drawNewPositions2 -> drawNewCoM2 +drawNewCoM2 [label=drawNewCoM] +drawNewPositions2 -> drawNewIV2 +drawNewIV2 [label=drawNewIV] +} + diff --git a/doc/pseudocode/diagrams/fireSingle.dot b/doc/pseudocode/diagrams/fireSingle.dot new file mode 100644 index 00000000..a5acf4f1 --- /dev/null +++ b/doc/pseudocode/diagrams/fireSingle.dot @@ -0,0 +1,38 @@ +// dot fireSingle.dot -Tpng -o fireSingle.png + +digraph fireSingle { + +edge [color=blue]; +node [color=red]; + +fireSingle -> "eventType?" + +"eventType?" -> "Single Reaction" +"eventType?" -> Else + +"Single Reaction" -> propagateSingle1 +propagateSingle1 [label=propagateSingle] +propagateSingle1 -> drawNewPosition1 +drawNewPosition1 [label=drawNewPosition] +"Single Reaction" -> fireSingleReaction +fireSingleReaction -> createSingle1 [label=" product?"] +createSingle1 [label=createSingle] + +Else -> propagateSingle2 +propagateSingle2 [label=propagateSingle] +propagateSingle2 -> drawNewPosition2 +drawNewPosition2 [label=drawNewPosition] + +Else -> eventType2 +eventType2 [label="eventType?"] + +eventType2 -> Interaction +Interaction -> fireSingleReaction2 +fireSingleReaction2 [label=fireSingleReaction] +fireSingleReaction2 -> createSingle2 [label=" product?"] +createSingle2 [label=createSingle] + +eventType2 -> Escape +Escape -> "See code" +} + diff --git a/doc/pseudocode/pseudocode.py b/doc/pseudocode/pseudocode.py new file mode 100644 index 00000000..221dbbfc --- /dev/null +++ b/doc/pseudocode/pseudocode.py @@ -0,0 +1,265 @@ +class Single: + """ A Single contains: + - 1 Particle of a certain Species + - 1 shell (Sphere or Cylinder) + - 1 Domain (Cartesian or Radial, wrapper around appropriate GF) + - public methods determineNextEvent and drawNewPosition + + """ + + +class Pair: + """A pair contains: + - 2 Particles + - 1 shell (Sphere or Cylinder) + - 1 domain for center of mass (Cartesian or Radial) + - 1 domain for interparticle vector (Composite: r and theta) + - public methods determineNextEvent, drawNewPositions, drawNewCoM + + """ + + +class EGFRDSimulator: + """An EGFRDSimulator stores information about: + + surfaces: + - surfaceList + + types of particles that can exist on each surface: + - speciesList + + possible reactions each species can undergo: + - reactionTypeMap1 (for the monomolecular reactions) + - reactionTypeMap2 (for the bimolecular reactions) + - interactionTypeMap (for the particle-surface interactions) + + position of each particle: + - particleMatrix + + position and size of each shell: + - sphereMatrix (for the spherical shells) + - cylinderMatrix (for the cylindrical shells) + + order of events: + - scheduler (contains Singles, Pairs and Multis) + + """ + def initialize: + for particle in all particles: + createSingle( particle ) + + + def step: + # The event scheduler calls fireSingle, firePair or fireMulti, + # depending on the type of the top event. + + + + # singles + + def createSingle( particle ): + # Create single with dt=0. The surface the particle is on determines + # the type of the single (CylindricalSurfaceSingle, + # PlanarSurfaceSingle, SphericalSingle). + + + def createInteraction( single, surface ): + # Create interaction single including the particle and the surface. + # The surface the particle is on determines the type of the + # interaction single (CylindricalSurfaceInteraction, + # PlanarSurfaceInteraction). + + + def fireSingle( single ): + if eventType == REACTION: + propagateSingle( single ) + fireSingleReaction( single ) + return + if eventType == INTERACTION: + propagateSingle( single ) + # The reactionType of the single distinguishes this case from the + # above in fireSingleReaction. + fireSingleReaction( single ) + return + + # ESCAPE + propagateSingle( single ) + + # Find neighbors within burstVolume, and closestObject outside of + # burstVolume. BurstVolume depends on the surface the particle is on. + # Spherical by default. + ( neighbors, closestObject, distanceToShellOfClosestObject ) = + getNeighbors( position, burstVolume ) + + if neighbors: + burstedSingles = burstNonMultis( neighbor ) + if tryInteractionOrPairOrMulti( single, bursted ): + return + else: + # If nothing was formed, recheck closest and restore shells. + ( closestObject, distanceToShellOfClosestObject ) = + getClosestObj( position ) + + updateSingle( single, closestObject, distanceToShellOfClosestObject ) + + # Avoid livelock. + restoreSingleShells( burstedSingles ) + + + def tryInteractionOrPairOrMulti( single1, neighbors ): + # Try to make an interaction or a pair with single1 and the closest + # neighbor. If single1 and closest neighbor are too far apart, do + # nothing. If more than 2 objects are close together, make a multi. + + + def burstSingle( single ): + # There is a technical difference, not mentioned now. + propagateSingle( single ) + + + def propagateSingle( single ): + drawNewPosition() + # If the single is an interaction single, remove it and create a + # normal single at the same position using createSingle(). + + + def restoreSingleShells( singles ): + for single in singles: + ( closestObject, distanceToShellOfClosestObject ) = getClosestObj() + updateSingle( single, closestObject, + distanceToShellOfClosestObject ) + + + def updateSingle( single, closestObject, distanceToShellOfClosestObject ): + if closestObject is Single: + radiusOfSingle = + calculateSingleShellSize( single, closestObject, + distanceToShellOfClosestObject ) + else: + # closestObject is Pair or Multi or Surface. + # It is harder to determine the optimal shellSize. Make single as + # big as possible for now. + radiusOfSingle = distanceToShellOfClosestObject + + ( dt, eventType ) = determineNextEvent( ) + + + def calculateSingleShellSize( single, single2, + distanceToShellOfSingle2 ): + # Optimal shellSize is half of the distance between the 2 particles if + # the radii and the diffusion constants are the same. + return min( radius1 + sqrtD1 / ( sqrtD1 + sqrtD2 ) * + ( distanceBetweenParticles - + ( radius1 + radius2 ) ), + distanceToShellOfSingle2 ) + + + def fireSingleReaction( single ): + if number of products is 1: + if reactionType is SurfaceUnbindingReactionType: + newpos = randomUnbindingSite( oldpos ) + elif reactionType is SurfaceBindingReactionType: + newpos = projectedPoint( oldpos ) + else: + # A -> B, no surfaces involved. + newpos = oldpos + + createSingle( productParticle ) # at newpos + + elif number of products is 2: + # A -> B + C, no surfaces involved. + # Draw a random vector of a fixed length. The orientation depends + # on the surface the particle is on. + length = ( particleRadius1 + particleRadius2 ) * + MINIMAL_SEPERATION_FACTOR ) + vector = randomVector( length ) + + newpos1 = oldpos + vector * ( D1 / D12 ) + newpos2 = oldpos - vector * ( D2 / D12 ) + + createSingle( particle1 ) # at newpos1 + createSingle( particle2 ) # at newpos2 + + + + # pairs + + def createPair( single1, single2, shellSize ) + # Create pair around the CoM of particle1 and particle2 with shellSize + # as radius. The surface the particles are on determines the type of + # the pair (CylindricalSurfacePair, PlanarSurfacePair, SphericalPair). + + + def firePair( pair ): + if eventType == SINGLE_REACTION: + burstPair( pair ) + fireSingleReaction( reactingsingle ) + elif eventType == PAIR_REACTION: + newCenterOfMass = drawNewCoM() + createSingle( productParticle ) # at newCenterOfMass + else: + # Escape through either r or R. + propagatePair( pair ) + + + def burstPair( pair ): + # There is a technical difference, not mentioned now. + propagatePair( pair ) + + + def propagatePair( pair ): + drawNewPositions() # for both particles. + + + + # multis + + def createMulti( single, neighbors ): + # Create multi and add the single and all objects in the neighbors + # list recursively. A multi has it's own Brownian Dynamics simulator. + addToMulti( single, multi ) + for neighbor in neighbors: + addToMultiRecursive( neighbor, multi ) + + + def addToMultiRecursive( thisObject, multi ): + if thisObject is Single: + addToMulti( thisObject, multi ) + # First burst neighboring objects that have a shell within + # MULTI_SHELL_FACTOR * radius of thisObject. + neighbors = burstNonMulti( neighbors ) + # Then select only those neighbors who have a particle that is + # within MULTI_SHELL_FACTOR * radius of thisObject. + (no pseudocode here) + # Finally, add those neighbors recursively. + for neighbor in neighbors: + addToMultiRecursive( neighbor, multi ) + else: + mergeMultis( thisObject, multi ) + + + def addToMulti( single, multi ): + addParticle( particle ) + # Add a static shell with a radius that is the radius of the particle + # multiplied by MULTI_SHELL_FACTOR. When the particle leaves this + # shell, the multi is bursted (see fireMulti). + shellSize = radiusOfParticle * MULTI_SHELL_FACTOR + addShell( position, shellSize ) + + + def mergeMultis( multi1, multi2 ): + #Merge multi1 into multi2. + + + def fireMulti( multi ): + # Do one execution step of the Brownian Dynamics simulator. Not + # further discussed here. + + if reaction occured or particle escaped: + burstMulti( multi ) + + + def burstMulti( multi ): + for particle in all multi particles: + createSingle( particle ) + diff --git a/doc/shell_making.tex b/doc/shell_making.tex new file mode 100644 index 00000000..49459b49 --- /dev/null +++ b/doc/shell_making.tex @@ -0,0 +1,269 @@ +\documentclass[a4paper,11pt]{article} +\usepackage{amsmath} +\usepackage{amsfonts} + +\title{Algorithm for deciding on, and dimensioning a new shell} +\author{Laurens Bossen} + + +\begin{document} +\maketitle + +\section{Introduction} +The eGFRD algorithm works with protective domains. In each domain one or two partcles can exist. Also a surface can be +involved and the particles can live on different structures or surfaces. The domains produde event that are organized +in the scheduler. + +When an event from the scheduler is processes, the changed position (and potentially its identity) of the particles is +processed and a new domain for the particles has to be formed. The processing of the event +namely leaves the particles in the old domain as NonInteractionSingles with a size equal to the size of the particle +(they are zero-dt domains). The system will now decide what domain to make next. + +There are a number of different domains that can be created from a NonInteractionSingle, and we need to decide which one is +the most appropriate. The options for a domain in descending order of computational cost per unit clock time are: +\begin{itemize} + \item A NonInteractionSingle (a single particle in a symmetric domain modeling diffusion and potential decay of the + particle) + \item An InteractionSingle (a single particle in the 3D space in a cylindrical domain modeling the diffusion of the particle + and non-specific binding of the particle to a surface). + \item A Pair (two particles potentially on two different structures in a domain modeling diffusion and reaction between + the particles). + \item A Multi (any number of particles in a collection of connected shells performing Brownian Dynamics) +\end{itemize} + +\paragraph{Goal of algorithm} +The goal of the algorithm is to minimize the total time spent on computations while still allowing for reactions between +particles and non-specific association of a particle with a surface such as the membrane or DNA. This means in practice +that we want to make NonInteractionSingles for as long as possible and that only when a reaction or interaction becomes +probable switch to +making Pairs and InteractionSingles. We also want to defer the creation of a Multi a long a possible, since this really +takes up a lot of computation time. + +Also we don't want to waste investments. This means that when we have just made a new +shell, we don't want it to be bursted right away. In short: +\begin{itemize} + \item allow reactions and interactions + \item spend CPU time well + \item don't waste CPU time +\end{itemize} + +There two different classes of domains, NonInteractionSingles and the rest. The rest is composed of +Pairs, Interactions and Multis. The separation in these two classes is caused by the fact that the NonInteractionSingle +is the default domain since they are the most efficient, and from the NonInteractionSingles we can go to other domains. +Also, the most appararent property of the NonInteractionSingle is then that it can 'burst' other domains to initiate the +creation of a more complex domain, typically a Pair or Interaction. +The making of a new Domain such as a Pair/Interaction/Multi is therefore initiated by a NonInteractionSingle, and it is +not possible to go from an Interaction domain directly to a Pair domain, we first need to 'go back' to the +NonInteractionSingle. + +\section{Algorithm} +\subsection{Burst or reaction volume} +First, we define a $burst volume$ or $reaction volume$. The +$reaction volume$ effectively defines the threshold for when the algorithm should stop trying to make NonInteractionSingles and +should start trying to do Interactions or Pairs. +More specifically, it defines a domain in which the particle can move, and when other shells enter into this space it is a +signal that a reaction or interaction between the particle and the intruding object is probable and that a Pair or Interaction +should be attempted. + +This also means that the algorithm will make NonInteractionSingles as long as no other object intrude into its +$reaction volume$. Making the $reaction volume$ small, therefore makes sure that we use the efficient NonInteractionSingles +for as long as possible and that the algorithm will only attempt a Pair or Interaction when the particles get 'close'. + +For three dimensionally diffusing particles, the threshold is simply a $burst radius$, since the diffusoin is in three +dimensions, and the protective domain is spherical. +For particles that only diffuse in two or one dimension, there is a certain direction in which the reaction takes place. +For these particles we define the $reaction volume$ in the same shape as its appropriate protective domain, where the +$reaction radius$ is in the same dimension as we would size its domain. + +Note that since overlap calculation between spheres and cylinders are annoying, we approximate the $reaction volume$ using +a sphere. The approximation introduces some inefficiency, since space that is not relevant for the particle is also covered +by the spherical $reaction volume$. However, when the size of the $reaction radius$ is small (typically 1.1 times the radius +of the particle), then the effect should be only small. +Note also that although the approximation could in principle allow two shell to overlap, since the corners of the cylinders +are not covered by the sphere, it still makes sure that no particles +overlap because the particle cannot leave the sphere. In this sence, the approximation does not introduce any errors. + + +\subsection{Bursting and finding potential partners for domains} +When objects intrude into the $reaction volume$, the algorithm will burst the objects and will start to try +to make a more complex domain than a NonInterationSingle. Bursting of domains should not be indiscriminatively though. +When an intruding domain has just been made (no time has passed since the making of the domain), then the domain should not +be bursted. +This prevents us from going back to the situation before the domain was made, eliminating the potential for a cycle. A newly +made Pair, Interaction or Mixed Pair domain is always sized such that it will not directly be bursted (see below), +however, a NonInteractionSingle could be made in the $reaction volume$ of another NonInteractionSingle. In this case the +NonInteractionSingle will not be bursted when no time has passed since its creation. + +However, when sizing a NonInteractionSingle we will burst intruding domains in which time has passed. This means that if the +domain is just freshly made, we assume that it has been done with some wisdom (socially), and we shouldn't throw away the +investment. + +Also note that object such as surfaces and Multi cannot be bursted. + +We can only partner up with objects whose position is exactly known. Objects whose position is exactly known are: +\begin{itemize} + \item surfaces + \item particles in Multis + \item just bursted (initialized) NonInteractionSingles ($dt=0$ shells) + \item 'dead' NonInteractionSingles (note that $dt=\infty$ for these shells) +\end{itemize} +The position of a surface or particles in a Multi is always known, so we can use them directly as partners in domain making. +The position of particles in a non-zero shell ($dt \neq 0$) is not exactly known, and therefore we first have to burst the +domain if we want to use its contents as partners. A burst will propagate the particles in the domain and make a +NonInteractionSingle domain for each particle. +In the same way we can burst 'dead' NonInteractionSingles, although little would change. The particle will stay in the +same position, just the $dt$ for the domain will be reset to zero, and a new event time will be drawn ($dt=\infty$) if the +particle was not used in the new domain. + + +\subsection{Deciding what domain to try, defining the horizon} +To define a threshold to determine if a reaction or interaction with an other object is a good idea, we define the $horizon$. +To form a new domain that includes an other particle or surface than the current one, the current particle 'looks around' in +its $horizon$ after bursting the domains in its $reaction volume$. Since partners can +not only be found in the same structure, but also in other structures, we look around in 3D. This means that the horizon +defines a sphere with $horizon$ radius. + +The $horizon$ is different for different partners, but in general +is the distance between two objects (two particles for example) that is the sum of their $reaction radii$. The +reasoning is +that if the two particles are so far away from each other that two NonInteractionSingles can be made with sizes larger than the +$reaction volume$, then a reaction is no longer probable between the objects and making one (or two) NonInteractionSingles +would be better. However, if the $reaction radii$ still overlap, a reaction is deamed probably and a Pair, Interaction or Multi +should be attempted. + +When we would make the $horizon$ smaller, the algorithm would still make NonInteractionSingles when the $reaction volumes$ +overlap. After an event of one of the domains, this would also lead to a burst of the other domain without attempting a more +complex domain. This is inefficient use the domain. Making the $horizon$ larger would result in larger Pair or Interaction +domains which have a lower probability of success. + +The $horizon$ varies for different partners. For the interaction of a 3D diffusing particle with a membrane for +example, a different threshold is used than for a reaction between two particles in 3D. +For a Pair in the same structure, we define the $horizon$ +as the sum of the $reaction radii$, although this is possibly slightly different for a MixedPair domain. +The $horizon$ for an Interaction on the other hand, is defined as the $reaction radius$ of the particle since the surface is +immobile and the domain making decision lies solely with the particle. + +Note that when a Pair or Interaction is not possible, the algorithm will fall back to making NonInteractionSingles. +However, when also NonInteractionSingles become inefficient and no Pair or Interaction can be made, the algorithm will need to +decide to swith to a Multi. The threshold for a Multi can also be regarded as a horizon since a Multi is only efficient when +the two objects are close enough. +The $horizon$ for a Multi therefore denotes when NonInteractionSingles are no longer efficient and a Multi should be +made to propagate the particle. In general the Multi horizon is really small reflecting the efficiency of the Green's functions +over brute force Brownian Dynamics. + + +\subsection{Sizing up different domains} +Above we discussed how we try to make certain domains. +When we try to make a certain domain, such as an Interaction, we need to know if the domain will fit in the current configuration +of domains in the system. +The different domain such as a 2D Pair, an Interaction or 3D NonInteractionSingle have different shapes and requirements, and +can be sized in different directions. The currently possible domains and their shapes are: +\begin{itemize} + \item 1D NonInteractionSingle -> cylinder, scale in 1D ($z$) + \item 2D NonInteractionSingle -> cylinder, scale in 2D ($r$) + \item 3D NonInteractionSingle -> sphere, scale in 3D ($r$) + \item Pairs -> same shape as NonInteractionSingle, minimal size + \item Interaction -> cylinder, scale in 3D ($r$ and $z$), minimal size, stick to membrane or rod + \item Mixed Pair -> cylinder, scale in 3D ($r$ and $z$), minimal size, stick to membrane + \item Multi -> same as NonInteractionSingle but fixed in scalable coordinate +\end{itemize} + +\paragraph{Sizing up an Interaction, Pair or Mixed Pair} +Although the shapes and requirements for the domains are all quite different, there are some fixed rules for the maximum size +of the domains. +When we are making a Pair, Interaction or Mixed Pair, we want to make it such that NonInteractionSingles do not have the +tendency to burst this domain. The reasoning is that we are making the current domain because a reaction or interaction +between the object is probable and other particles should be kept at a distance. Sizing such a domain into the $reaction +volume$ of a NonInteractionSingle suggests that this NonInteractionSingle should attempt to make a reaction with the contents +of the current domain, which is against making the domain in the first place. + +So, since we don't want to throw away the investment in the current shell, and we decided that it was a good idea to make this +domain in the first place, we leave all NonInteractionSingles at a distance such that the $reaction volume$ of the +NonInteractionSingle in question does not overlap with the new domain. This way these Single won't immediately try to +interfere and burst the newly made domain. +Note that domains on a surface are only sized in the coordinates 'in' the surface and that NonInteractionSingles in the bulk +are mostly irrelevant. + +As an approximation, the $reaction volume$ of the NonInteractionSingle is chosen to be spherical also in the case of a 1D or +2D diffusing particle +(see above). This means that also when making a Pair, Interaction or Mixed Pair, the particles in a NonInteractionSingle will +be kept at a distance of $reaction radius$. + +When the nearest relevant object is an object other than a NonInteractionSingle, such as a surface (or 'dead' +NonInteractionSingle), a +Pair, Interaction or Multi domain, then the maximum size of the domain is the distance to the object, since these objects do +not have a $reaction volume$ and cannot burst the domain directly. + +\paragraph{Sizing up NonInteractionSingles} +To size up a NonInteractionSingle, the rules are similar. +The maximum size of a NonInteractionSingle is the distance to the nearest relevant object when the nearest relevant object is +a surface (or 'dead' NonInteractionSingle), Pair, Interaction or Multi. +In the case the nearest relevant neighbor is another NonInteractionSingle we should be more careful +and leave enough space for the other one (the maximum size is then for example half-way), especially if the $reaction volumes$ +of the two NonInteractionSingles overlap. +This is to prevent unnecessary mutual bursting of the two NonInteractionSingles. + +Note again that the nearest relevant object is the object +that is met first when scaling the domain in its coordinate. This is for example the $r$ coordinate for a 2D +NonInteractionSingle and the $z$ coordinate for a 1D NonInteractionSingle. +Note also that when the closest relevant Pair or Interaction domain was just made (no time has passed since its creation), it is +trivial for a NonInteractionSingle to make a domain of at least the $reaction volume$, since the $reaction volume$ was taken +into account when the Pair or Interaction was made (see above). + +\paragraph{Sizing up Multis} +The shell for a particle participating in a Multi should be at least the size of the Multi horizon. The reasoning is that +when the particle leaves its shell it is at least horizon away from the multi. Making the shell smaller that the horizon, +will cause repeated re-creation of the Multi, since the particle can leave its domain but is not yet $horizon$ away from the +Multi. Making the shells bigger than the horizon will cause more Multi-ing than necessary to get out of the Multi horizon. +Making the shell exactly the size of the Multi horizon will then make sure that the Multi is not used longer than strictly +necessary. + +Note that the shape of the shells for the particles in the Multi depends on the dimensions in which the particle can diffuse, +similarly as the domains for NonInteractionSingles and Pairs. This means that for a particle in +the bulk the shell is a sphere, for a particle in 2D and 1D it is a cylinder. As an approximation, however, we currently use +spheres as shells for all particles in a Multi. + + +\subsection{The algorithm in short} +From a NonInteractionSingle that has just been processed we will make a new domain. The NonInteractionSingle is the default +domain from which other domains can be formed. To decide what domain to make we use the following algorithm. +\begin{enumerate} + \item First we check if there are any intruders in our $reaction volume$. The $reaction volume$ has in principle the same + shape as the shell of the NonInteractionSingle, but we approximate it with a sphere. Note that these can be Singles, + Pairs, Multis and even surfaces. If there are intruders, then this means that an other particle or surface so close + in the relevant direction that a reaction or + interaction is probable and that we should start thinking about making Interactions, Pairs or Multis. + \item burst the 'burstable' intruders within the $burstradius=reaction volume$ with the exception of domains in which no + time has passed yet (not burstable are: Multi, Surface, zero-shell NonInteractionSingles ($dt=0$ or 'dead')). + \item compile a list of neighboring potential partners in all three dimensions (Multis, surface, zero-shell + NonInteractionSingles). + \item get the closest potential partner and decide on the domain to make. Note that the closest potential partner does not + always leads to an allowed + interaction. A combination of a 1D diffusing particle and a 3D diffusing particle is for example not possible. + \begin{itemize} + \item if the closest potential partner is a surface AND is within the $surface horizon$, then try an Interaction. + \item elseif the closest potential partner is a NonInteractionSingle AND within the Pair horizon, then try a Pair + \item if the above fails, then the closest partner is a surface AND outside the surface horizon OR a NonInteractionSingle + AND outside the Pair horizon OR the domain could not be made. + \item If the closest potential partner is further than the Multi horizon, then make a NonInteractionSingle. + \item else make/join a Multi. + \end{itemize} + \item Make sure that the domain has the proper size. +\end{enumerate} + + + +\section{Notes on special domains} +Note that a surface behaves similarly to a stationary (D=0), non-decaying particle. It doens't produce any events and is +therefore never fired and never bursts other domains. Since it is stationary it also doesn't contribute to the horizon. + +A zero shell is made in a number of situations. They are made when a new particle is introduced in the simulation system +(for example at the beginning of a simulation run), or when a shell is bursted and is reduced to a collection of zero-dt +NonInteractionSingles. +Since zero shells have a dt of zero their events are all at the top of the +scheduler, and consequently these events will be processed before and other events. Note that these shells always produce +an $ESCAPE$ event, and that there can be only other zero shells in the simulation system if the shell that is associated with +the currently event is also a zero shell. + + +\end{document} diff --git a/doc/visualization.txt b/doc/visualization.txt new file mode 100644 index 00000000..b13c9553 --- /dev/null +++ b/doc/visualization.txt @@ -0,0 +1,158 @@ +Visualization +============= + +Notes +----- +* Right now this is not real-time. First you run your simulation, then you use + Paraview to visualize it. + + +Setting up the simulation +------------------------- +### Option 1 +Let a vtklogger write visualization data for the last 100 steps. + +* Setup the vtklogger like this in your simulation script (also see + /example/example.py): + + {% highlight python %} + + s.initialize() + vtklogger = VTKLogger( s, 'myVisualizationDataDirectory', 100 ) + while(True): + try: + vtklogger.log() + s.step() + except RuntimeError, message: + print 'Error.', message + break + vtklogger.stop() + s.stop( s.t ) + + {% endhighlight %} + +* Replace 'myVisualizationDataDirectory' by a descriptive name for each + simulation that you run. It can be a nested directory structure, like + 'data/10000steps/run1', and is automatically created. + +* This stores all data in a buffer during the simulation, and only writes the + last 100 steps after the simulation has finished. + +### Option 2 +Let a vtklogger write visualization data for all steps. + +* This is done the same way as option 1, except remove the last argument to + VTKLogger. + +* This writes the visualization data files directly during the simulation. See + below. + + + + +Running the simulation +---------------------- +* Run the simulation script. + + PYTHONPATH=../../ python run.py + +* This should produce the files static.pvd and files.pvd inside the + visualization data directory specified in your simulation script. The former + contains static information about the surfaces you defined in your + simulation. The latter contains a list of references to .vtu files. For each + timestep there is a .vtu file that contains information about the particles, + a .vtu file for the sphericle shells, and a .vtu file for all the + cylindrical shells. + +* When not using a buffer, i.e. option 2, the .vtu files are created during + the simulation. However, we have to wait for the simulation to finish before + we can visualize the data using Paraview. It is not till the call to + vtklogger.stop() that files.pvd is created with an overview of all .vtu + files. This is the reason that the visualization can not be shown real-time + at the moment. + + + + +Configuring Paraview +-------------------- +* Install Paraview. http://paraview.org/ + +* Copy the plugins /paraview/tensorGlyph.xml and + /paraview/tensorGlyphCustomSource.xml to + /home//.config/ParaView/ParaView/Plugins/ + replacing by the version of your Paraview installation (for + example 3.6). These 2 files are needed to show cylinders of different sizes + and different orientations correctly. + +* In /paraview/pipeline.pvsm search for and replace + /full/path/to/some/files.pvd and /full/path/to/some/static.pvd by the full + paths to those files that were created by the simulation. So you should get + something like: + /home///simulations///files.pvd + and + /home///simulations///static.pvd. + + Note that Paraview will crash if these files don't exist. And they have are + to be absolute paths, using ~ for home directory, or ./../ for the egfrd + directory doesn't seem to work. This means Paraview will also crash if you + move your egfrd directory or something at some later point. + +* Start Paraview. + +* Go to Tools > Manage Plugins/Extensions and check that the plugins are + loaded, or load them manually. + + Note that Paraview will crash if the plugins are not loaded when you try + to load the pipeline in the next step. + + + + +Visualizing the data +-------------------- +* Go to File > Load State, and select /paraview/pipeline.pvsm. + +* Click the play button. You should see an animation of your simulation. Yeah! + + + + +Tweaking the pipeline +--------------------- +* From now on you can make changes to the pipeline from inside Paraview. Here + is an example of what to do when you want to use data from a completely + different simulation. + - Right click in the Pipeline Browswer window. Open files.pvd in the + visualization data directory of the new simulation. Then click apply in + the Object Inspector window. The new files.pvd is now added to the + pipeline, but doesn't do anything yet. + - Left click on the 'plus' next to files.pvd in the pipeline browser, then + right click on Objects below files.pvd, then Change Input. In the middle + window that appears, called Select Sources, select the new files.pvd file + you just added. It might be a bit hard to find. Click Ok. Objects is now + rewired to use the new files.pvd. + - Right click the old files.pvd, and delete it. Note that this is only + possible if you actually rewired Objects to a new files.pvd file in the + previous step, otherwise this options is grayed out. + - If the definition of the surfaces also changed: open and apply the new + static.pvd, rewire Surfaces to it and delete the old static.pvd. + - Save the new pipeline. File > Save State. + +* If you run a new simulation that writes to the same visualization data + directory, usually Paraview will reload the new data correctly. There can be + a problem if you increase the number of timesteps. If so, open the new + files.pvd into the pipeline and rewire Objects as explained above. + +* Different species are given different colors. If you run a new simulation + that has many more species, you might need to rescale the color range. Select + Particle in the Pipeline Browser window. Then in the Object Inspector window + click the middle tab called Display. Click Rescale to Data Range in the + Color subsection. + Typically you don't want to rescale the colors for Sphere and Cylinder. + Pairs are given a different color (2) than Singles (1) by default. The + sphere or cylinder that will be updated next is highlighed (color 0). + + + + diff --git a/domain.py b/domain.py new file mode 100644 index 00000000..d0187bf9 --- /dev/null +++ b/domain.py @@ -0,0 +1,116 @@ +#!/usr/env python + +import numpy +import myrandom +import math + +from constants import * + +__all__ = [ + 'Domain', + 'ProtectiveDomain', + ] + + +class Domain(object): +# The domain is the main unit of eGFRD. Single, Pairs and Multis are all domains +# with an own domain_id + + def __init__(self, domain_id): + # Calls required parent inits and sets variables. + # + # Sets: domain_id, event_id, event_type, last_time, dt + # Requires nothing to be set. + + self.domain_id = domain_id # identifier for this domain object + self.event_id = None # identifier for the event coupled to this domain + + self.event_type = None + + self.last_time = 0.0 + self.dt = 0.0 + + def initialize(self, time): # this method needs to be overloaded in all subclasses + ''' + Sets the local time variables for the Domain (usually to the current simulation time). + Do not forget to reschedule this Domain after calling this method. + + For the NonInteractingSingle, the radius (shell size) should be shrunken to the actual + radius of the particle. + + initialize allows you to reuse the same Domain (of the same class with the same particles) + at a different time. + ''' + pass + + def calc_ktot(self, reactionrules): + # calculates the total rate for a list of reaction rules + # The probability for the reaction to happen is proportional to + # the sum of the rates of all the possible reaction types. + k_tot = 0 + for rr in reactionrules: + k_tot += rr.k + return k_tot + + def draw_reaction_rule(self, reactionrules): + # draws a reaction rules out of a list of reaction rules based on their + # relative rates + k_array = numpy.add.accumulate([rr.k for rr in reactionrules]) + k_max = k_array[-1] + + rnd = myrandom.uniform() + i = numpy.searchsorted(k_array, rnd * k_max) + + return reactionrules[i] + +# def create_new_shell(self): # needs to be overloaded in subclasses +# pass # creates an appropriate shell object + + +class ProtectiveDomain(Domain): +# Protective Domains are the proper eGFRD domains. The shell associated with a Protective +# Domains really exclude other particles from the simulation. These Domains are Singles and Pairs +# which have only one shell. Multi's on the other hand, have multiple shells and can still leave +# their shell within a timestep, breaking the principle of a Protective Domain + + def __init__(self, domain_id, shell_id): + # Calls required parent inits and sets variables. + # + # Sets: shell_id, num_shells + # Requires nothing to be set. + + # Inits parent classes + Domain.__init__(self, domain_id) + + # Definitions + self.shell_id = shell_id +# self.shell = None + self.num_shells = 1 # all protective domains have only one shell + + def get_shell_id_shell_pair(self): + return (self.shell_id, self.shell) + + def set_shell_id_shell_pair(self, shell_id_shell_pair): + self.shell_id = shell_id_shell_pair[0] + self.shell = shell_id_shell_pair[1] + + shell_id_shell_pair = property(get_shell_id_shell_pair, set_shell_id_shell_pair) + + def get_shell_list(self): + return [(self.shell_id, self.shell), ] + + shell_list = property(get_shell_list) + + def get_shell_radius(self): # returns the radius of the shell + return self.shell.shape.radius + + def check(self): + ### checks some basic parameters of the Protective Domain + + # check that the domain has the proper number of shells. + assert len(self.shell_list) == 1 + # check event_type != BURST, note that this means that the check can't take place when the + # event by the domain is processed. + assert self.event_type != EventType.BURST + + return True diff --git a/dumper.py b/dumper.py index 839764e2..5193dea1 100644 --- a/dumper.py +++ b/dumper.py @@ -2,7 +2,7 @@ from pair import Pair from multi import Multi from egfrd import EGFRDSimulator -from gillespie import GillespieSimulator +#from gillespie import GillespieSimulator import _gfrd # get methods return an iterator, dump methods return a string. @@ -66,7 +66,16 @@ def dump_species_names(sim): return ' '.join(get_species_names(sim)) def _get_species_type_by_name(sim, name): - #Helper. + # Return the type of a species with a certain name + # (Function not intended for public use.) + # + # Arguments: + # - sim + # an EGFRDSimulator + # - name + # species name + # + #TODO: Added by wehrens@AMOLF.nl; Please revise. for species_type in sim.world.model.species_types: if species_type['name'] == name: return species_type @@ -74,7 +83,23 @@ def _get_species_type_by_name(sim, name): raise RuntimeError('SpeciesType %s does not exist.' % (name)) def _get_particles_by_sid(sim, sid): - # Helper. + # Return a generator (using "yield") to loop over (pid, particle). + # (Function not intended for public use.) + # + # Arguments: + # - sim + # an EGFRDSimulator + # - sid + # ID of a species + # + # E.g.: + # + # myparticles = _get_particles_by_sid(sim, sid) + # + # for mypid, myparticle in myparticles: + # print str(str(mypid), str(myparticle)) + # + #TODO: Added by wehrens@AMOLF.nl; Please revise. for pid in sim.world.get_particle_ids(sid): particle = sim.world.get_particle(pid)[1] yield (pid, particle) @@ -91,9 +116,12 @@ def get_particles(sim, identifier=None): all (particle identifier, particle)-pairs will be returned. """ - if isinstance(sim, GillespieSimulator): - raise RuntimeError('GillespieSimulator does not keep track ' - 'of individual particles.') +# TODO: Code below leads to crash in some situations, currently +# Gillespie simulator is not implemented, so code can be +# left out. +# if isinstance(sim, GillespieSimulator): +# raise RuntimeError('GillespieSimulator does not keep track ' +# 'of individual particles.') if identifier == None: return sim.world @@ -121,13 +149,29 @@ def dump_particles(sim, identifier=None): return '\n'.join((str(x) for x in get_particles(sim, identifier))) def _get_number_of_particles_by_sid(sim, sid): - # Helper. - if isinstance(sim, EGFRDSimulator): - return len(sim.world.get_particle_ids(sid)) - else: - # Gillespie. - species_index = sim.speciesDict[sid] - return sim.stateArray[species_index] + # Returns the number of particles of a certain species. + # (Not intended for public use.) + # + # Arguments: + # - sim + # an EGFRDSimulator. + # - sid + # ID of a species + # + #TODO: Added by wehrens@AMOLF.nl; Please revise. + + # OLD CODE, facilitating Gillespie: + # if isinstance(sim, EGFRDSimulator): + # return len(sim.world.get_particle_ids(sid)) + # else: + # if isinstance(sim, BDSimulator): + # return len(sim.world.get_particle_ids(sid)) + # if isinstance(sim, EGFRDSimulator): + # # Gillespie. + # species_index = sim.speciesDict[sid] + # return sim.stateArray[species_index] + + return len(sim.world.get_particle_ids(sid)) def get_number_of_particles(sim, identifier=None): """Return the number of particles of a certain Species in the @@ -174,7 +218,10 @@ def dump_number_of_particles(sim, identifier=None): def get_domains(egfrdsim): """Return an iterator over the protective domains in the simulator. - """ + Arguments: + - egfrdsim + an EGFRDSimulator + """ #TODO: Added by wehrens@AMOLF.nl; Please revise. for did, domain in egfrdsim.domains.iteritems(): shell_list = domain.shell_list pid_particle_pair_list = [] @@ -193,7 +240,7 @@ def get_domains(egfrdsim): yield ((did, domain), pid_particle_pair_list, shell_list) def dump_domains(egfrdsim): - """Return an string containing the protective domains in the + """Return a string containing the protective domains in the simulator. """ @@ -245,9 +292,10 @@ def get_reaction_rules(model_or_simulator): def _dump_reaction_rule(model, reaction_rule): # Helper. Return ReactionRule as string. - - #ReactionRule.__str__ would be good, but we are actually getting a - #ReactionRuleInfo or ReactionRuleCache object. + # (Not intended for public use.) + # + # ReactionRule.__str__ would be good, but we are actually getting a + # ReactionRuleInfo or ReactionRuleCache object. buf = ('k=%.3g' % reaction_rule.k + ': ').ljust(15) for index, sid in enumerate(reaction_rule.reactants): if index != 0: diff --git a/egfrd.py b/egfrd.py index b3abadcf..859230de 100644 --- a/egfrd.py +++ b/egfrd.py @@ -1,78 +1,333 @@ #!/usr/env python - +# -*- coding: utf-8 -*- from weakref import ref import math - import numpy +import sys from _gfrd import ( Event, EventScheduler, Particle, SphericalShell, - SphericalShellContainer, CylindricalShell, - CylindricalShellContainer, DomainIDGenerator, ShellIDGenerator, DomainID, ParticleContainer, CuboidalRegion, + SphericalSurface, CylindricalSurface, + DiskSurface, PlanarSurface, + Surface, _random_vector, + Cylinder, + Disk, Sphere, + Plane, NetworkRulesWrapper, ) from gfrdbase import * +from gfrdbase import DomainEvent from single import * from pair import * from multi import * from utils import * from constants import * +from shellcontainer import ShellContainer +from shells import ( + testShellError, + ShellmakingError, + testPair, + testInteractionSingle, + hasSphericalShell, + hasCylindricalShell, + SphericalSingletestShell, + SphericalPairtestShell, + PlanarSurfaceSingletestShell, + PlanarSurfacePairtestShell, + PlanarSurfaceTransitionSingletestShell, + PlanarSurfaceTransitionPairtestShell, + CylindricalSurfaceSingletestShell, + CylindricalSurfacePairtestShell, + DiskSurfaceSingletestShell, + PlanarSurfaceInteractiontestShell, + PlanarSurfaceDiskSurfaceInteractiontestShell, + PlanarSurfaceCylindricalSurfaceInteractiontestShell, + CylindricalSurfaceInteractiontestShell, + CylindricalSurfaceDiskInteractiontestShell, + CylindricalSurfacePlanarSurfaceInteractiontestShell, + CylindricalSurfacePlanarSurfaceIntermediateSingletestShell, + CylindricalSurfacePlanarSurfaceInterfaceSingletestShell, + CylindricalSurfaceSinktestShell, + MixedPair2D3DtestShell, + MixedPair2DStatictestShell, + MixedPair1DStatictestShell, + ) + +import loadsave +from histograms import * +from time import sleep import logging -import os +log = logging.getLogger('ecell') -from bd import DEFAULT_DT_FACTOR +import os -log = logging.getLogger('ecell') -if __debug__: - PRECISION = 3 - FORMAT_DOUBLE = '%.' + str(PRECISION) + 'g' - -def create_default_single(domain_id, pid_particle_pair, shell_id, rt, surface): - if isinstance(surface, CuboidalRegion): - return SphericalSingle(domain_id, pid_particle_pair, - shell_id, rt, surface) - elif isinstance(surface, CylindricalSurface): - return CylindricalSurfaceSingle(domain_id, pid_particle_pair, - shell_id, rt, surface) - elif isinstance(surface, PlanarSurface): - return PlanarSurfaceSingle(domain_id, pid_particle_pair, - shell_id, rt, surface) - -def create_default_pair(domain_id, com, single1, single2, shell_id, - r0, shell_size, rt, surface): - if isinstance(surface, CuboidalRegion): - return SphericalPair(domain_id, com, single1, single2, - shell_id, r0, shell_size, rt, surface) - elif isinstance(surface, CylindricalSurface): - return CylindricalSurfacePair(domain_id, com, single1, single2, - shell_id, r0, shell_size, rt, surface) - elif isinstance(surface, PlanarSurface): - return PlanarSurfacePair(domain_id, com, single1, single2, - shell_id, r0, shell_size, rt, surface) - - -class DomainEvent(Event): - __slot__ = ['data'] - def __init__(self, time, domain): - Event.__init__(self, time) - self.data = domain +# Hashtable that selectively decides which domains can be constructed, +# irrespective of all other circumstances; this can be used, e.g., to +# switch off the construction of certain domains for testing, and to +# force the system to use Brownian Dynamics (make a Multi) in certain +# situations / circumstances instead. +# The "filter" map is based on the test shells, because certain special +# test shells result in the same domain type later in spite of different +# initial circumstances. +# Note that for the Singles setting the respective value to "False" will +# only block their "upscaling" in update_single(), because by convention +# we always allow for the creation of "zero-Singles". +# Use this feature with care, because switching off the formation of +# domains with central importance, such as the SphericalSingle, can lead +# to substantial performance loss. This is mainly to be used for testing. +allowed_to_make = { + # Singles + SphericalSingletestShell: True, + PlanarSurfaceSingletestShell: True, + CylindricalSurfaceSingletestShell: True, + DiskSurfaceSingletestShell: True, + # (Special singles) + CylindricalSurfacePlanarSurfaceInterfaceSingletestShell: True, + # Interactions + # (Bulk -> 2D and 1D) + PlanarSurfaceInteractiontestShell: True, + CylindricalSurfaceInteractiontestShell: True, + # (Among 2D and 1D structures) + PlanarSurfaceCylindricalSurfaceInteractiontestShell: True, + PlanarSurfaceDiskSurfaceInteractiontestShell: True, + CylindricalSurfacePlanarSurfaceInteractiontestShell: True, + CylindricalSurfacePlanarSurfaceIntermediateSingletestShell: True, + CylindricalSurfaceDiskInteractiontestShell: True, + CylindricalSurfaceSinktestShell: True, + # Transitions + PlanarSurfaceTransitionSingletestShell: True, + # Pairs + SphericalPairtestShell: True, + PlanarSurfacePairtestShell: True, + CylindricalSurfacePairtestShell: True, + PlanarSurfaceTransitionPairtestShell: True, + MixedPair2D3DtestShell: True, + MixedPair2DStatictestShell: True, + MixedPair1DStatictestShell: True +} + +# A helper function to unify/facilitate checking whether construction of Pairs +# is activated, used further below +def pair_testShell_if_allowed(pair_testShell_class, singleA, singleB, geometrycontainer, domains): + + if allowed_to_make[pair_testShell_class]: + return pair_testShell_class(singleA, singleB, geometrycontainer, domains) + else: + raise testShellError('Creation of test shell type deactivated for %s' % pair_testShell_class) + + +# Now in the following we define the "gearbox" functions that figure out +# which domain type should be constructed + +### Singles +def create_default_single(domain_id, shell_id, pid_particle_pair, structure, reaction_rules, geometrycontainer, domains): + # Bulk single + if isinstance(structure, CuboidalRegion): + # First make the test shell + testSingle = SphericalSingletestShell(pid_particle_pair, structure, geometrycontainer, domains) + return SphericalSingle (domain_id, shell_id, testSingle, reaction_rules) + # Plane single + elif isinstance(structure, PlanarSurface): + # First make the test shell + testSingle = PlanarSurfaceSingletestShell(pid_particle_pair, structure, geometrycontainer, domains) + return PlanarSurfaceSingle (domain_id, shell_id, testSingle, reaction_rules) + # Cylinder single + elif isinstance(structure, CylindricalSurface): + # First make the test shell + testSingle = CylindricalSurfaceSingletestShell(pid_particle_pair, structure, geometrycontainer, domains) + return CylindricalSurfaceSingle (domain_id, shell_id, testSingle, reaction_rules) + # Disk single + elif isinstance(structure, DiskSurface): + # Here we first want to try a special case, in which the particle is at the "interface" btw. cylinder and plane + # The corresponding test shell is akin to the standard DiskSurfaceSingle, but has an extended ignore list + try: + if allowed_to_make[CylindricalSurfacePlanarSurfaceInterfaceSingletestShell]: + testSingle = CylindricalSurfacePlanarSurfaceInterfaceSingletestShell (pid_particle_pair, structure, geometrycontainer, domains) + else: + raise testShellError('Creation of test shell type deactivated for %s' % CylindricalSurfacePlanarSurfaceInterfaceSingletestShell) + except testShellError as e: + if __debug__: + log.warn('Could not make CylindricalSurfacePlanarSurfaceInterfaceSingletestShell, %s' % str(e)) + # Making the default testShell should never fail + testSingle = DiskSurfaceSingletestShell (pid_particle_pair, structure, geometrycontainer, domains) + + return DiskSurfaceSingle (domain_id, shell_id, testSingle, reaction_rules) + +### Interactions +def try_default_testinteraction(single, target_structure, geometrycontainer, domains): + + # [Bulk -> Plane] or [Bulk -> Cylinder] + if isinstance(single.structure, CuboidalRegion): + # [Bulk -> Plane] + if isinstance(target_structure, PlanarSurface) and allowed_to_make[PlanarSurfaceInteractiontestShell]: + return PlanarSurfaceInteractiontestShell (single, target_structure, geometrycontainer, domains) + # [Bulk -> Cylinder] + elif isinstance(target_structure, CylindricalSurface) and allowed_to_make[CylindricalSurfaceInteractiontestShell]: + return CylindricalSurfaceInteractiontestShell (single, target_structure, geometrycontainer, domains) + else: + raise testShellError('(Interaction). Combination of (3D particle, target_structure) is not supported or deactivated') + + # [Plane -> Cylinder] or [Plane -> Disk] + elif isinstance(single.structure, PlanarSurface): + # [Plane -> Cylinder] + if isinstance(target_structure, CylindricalSurface) and allowed_to_make[PlanarSurfaceCylindricalSurfaceInteractiontestShell]: ### TESTING ### + return PlanarSurfaceCylindricalSurfaceInteractiontestShell (single, target_structure, geometrycontainer, domains) + # [Plane -> Disk] + elif isinstance(target_structure, DiskSurface) \ + and ( allowed_to_make[CylindricalSurfacePlanarSurfaceIntermediateSingletestShell] \ + or allowed_to_make[PlanarSurfaceDiskSurfaceInteractiontestShell] ): + # Here we have 2 possibilities; first we try the less probable one (special conditions apply that are checked + # upon test shell construction), then the more common one. + try: + if allowed_to_make[CylindricalSurfacePlanarSurfaceIntermediateSingletestShell]: + return CylindricalSurfacePlanarSurfaceIntermediateSingletestShell (single, target_structure, geometrycontainer, domains) + else: + raise testShellError('Creation of test shell type deactivated for %s' % CylindricalSurfacePlanarSurfaceIntermediateSingletestShell) + except testShellError as e: + if __debug__: + log.warn('Could not make CylindricalSurfacePlanarSurfaceIntermediateSingletestShell, %s.' % str(e)) + # OK, we could not make the special domain, but we still want to try the more standard one instead + # If both shells do not work in this situation the second try will result in raising another shellmaking exception + if allowed_to_make[PlanarSurfaceDiskSurfaceInteractiontestShell]: + log.info('Now trying PlanarSurfaceDiskSurfaceInteractiontestShell.') + return PlanarSurfaceDiskSurfaceInteractiontestShell (single, target_structure, geometrycontainer, domains) + else: + raise testShellError('Creation of test shell type deactivated for %s' % PlanarSurfaceDiskSurfaceInteractiontestShell) + # Unsupported cases + else: + raise testShellError('(Interaction). Combination of (2D particle, target_structure) is not supported or deactivated') + + # [Cylinder -> Disk] or [Cylinder -> Plane] + elif isinstance(single.structure, CylindricalSurface): + # [Cylinder -> Disk] + if isinstance(target_structure, DiskSurface) \ + and ( allowed_to_make[CylindricalSurfaceSinktestShell] \ + or allowed_to_make[CylindricalSurfaceDiskInteractiontestShell] ): + try: + if allowed_to_make[CylindricalSurfaceSinktestShell]: + return CylindricalSurfaceSinktestShell (single, target_structure, geometrycontainer, domains) + else: + raise testShellError('Creation of test shell type deactivated for %s' % CylindricalSurfaceSinktestShell) + except testShellError as e: + if __debug__: + log.warn('Could not make CylindricalSurfaceSinktestShell, %s; now trying CylindricalSurfaceDiskInteractiontestShell.' % str(e)) + if allowed_to_make[CylindricalSurfaceDiskInteractiontestShell]: + return CylindricalSurfaceDiskInteractiontestShell (single, target_structure, geometrycontainer, domains) + else: + raise testShellError('Creation of test shell type deactivated for %s' % CylindricalSurfaceDiskInteractiontestShell) + # [Cylinder -> Plane] + elif isinstance(target_structure, PlanarSurface) and allowed_to_make[CylindricalSurfacePlanarSurfaceInteractiontestShell]: + return CylindricalSurfacePlanarSurfaceInteractiontestShell (single, target_structure, geometrycontainer, domains) + # Unsupported cases + else: + raise testShellError('(Interaction). Combination of (1D particle, target_structure) is not supported or deactivated') + + # All other unsupported cases + else: + raise testShellError('(Interaction). Structure of particle was of invalid type') + +def create_default_interaction(domain_id, shell_id, testShell, reaction_rules, interaction_rules): + if isinstance(testShell, PlanarSurfaceCylindricalSurfaceInteractiontestShell): # must be first because it is a special case of PlanarSurfaceDiskSurfaceInteractiontestShell below + return PlanarSurfaceCylindricalSurfaceInteraction (domain_id, shell_id, testShell, reaction_rules, interaction_rules) + if isinstance(testShell, PlanarSurfaceDiskSurfaceInteractiontestShell): # must be first because it is a special case of CylindricalSurfaceInteractiontestShell below + return PlanarSurfaceDiskSurfaceInteraction (domain_id, shell_id, testShell, reaction_rules, interaction_rules) + elif isinstance(testShell, CylindricalSurfaceInteractiontestShell): + return CylindricalSurfaceInteraction (domain_id, shell_id, testShell, reaction_rules, interaction_rules) + elif isinstance(testShell, PlanarSurfaceInteractiontestShell): + return PlanarSurfaceInteraction (domain_id, shell_id, testShell, reaction_rules, interaction_rules) + elif isinstance(testShell, CylindricalSurfacePlanarSurfaceInteractiontestShell): # must be first because it is a special case of CylindricalSurfaceDiskInteractiontestShell + return CylindricalSurfacePlanarSurfaceInteraction (domain_id, shell_id, testShell, reaction_rules, interaction_rules) + elif isinstance(testShell, CylindricalSurfacePlanarSurfaceIntermediateSingletestShell): # this is actually not a "real" interaction, but we need to put it here to ignore the target structure + return CylindricalSurfacePlanarSurfaceIntermediateSingle (domain_id, shell_id, testShell, reaction_rules, interaction_rules) + elif isinstance(testShell, CylindricalSurfaceDiskInteractiontestShell): + return CylindricalSurfaceDiskInteraction (domain_id, shell_id, testShell, reaction_rules, interaction_rules) + elif isinstance(testShell, CylindricalSurfaceSinktestShell): # FIXME not sure we ever construct this; we have to give it priority over Cyl.Surf.DiskInteraction for non-capping disks! + return CylindricalSurfaceSink (domain_id, shell_id, testShell, reaction_rules, interaction_rules) + +### Transitions +def try_default_testtransition(single, target_structure, geometrycontainer, domains): + if isinstance(single.structure, CuboidalRegion): + raise testShellError('(Transition). Combination of (3D particle, target_structure) is not supported') + elif isinstance(single.structure, PlanarSurface): + if isinstance(target_structure, PlanarSurface): + if allowed_to_make[PlanarSurfaceTransitionSingletestShell]: + return PlanarSurfaceTransitionSingletestShell (single, target_structure, geometrycontainer, domains) + else: + raise testShellError('Creation of test shell type deactivated for %s' % PlanarSurfaceTransitionSingletestShell) + else: + raise testShellError('(Transition). Combination of (2D particle, target_structure other than plane) is not supported') + elif isinstance(single.structure, CylindricalSurface): + raise testShellError('(Transition). Combination of (1D particle, target_structure) is not supported') + else: + raise testShellError('(Transition). Structure of particle was of invalid type') + +def create_default_transition(domain_id, shell_id, testShell, reaction_rules): + if isinstance(testShell, PlanarSurfaceTransitionSingletestShell): + return PlanarSurfaceTransitionSingle (domain_id, shell_id, testShell, reaction_rules) + +### Pairs +def try_default_testpair(single1, single2, geometrycontainer, domains): + if single1.structure == single2.structure: + if isinstance(single1.structure, CuboidalRegion): + return pair_testShell_if_allowed(SphericalPairtestShell, single1, single2, geometrycontainer, domains) + elif isinstance(single1.structure, PlanarSurface): + return pair_testShell_if_allowed(PlanarSurfacePairtestShell, single1, single2, geometrycontainer, domains) + elif isinstance(single1.structure, CylindricalSurface): + return pair_testShell_if_allowed(CylindricalSurfacePairtestShell, single1, single2, geometrycontainer, domains) + elif (isinstance(single1.structure, PlanarSurface) and isinstance(single2.structure, PlanarSurface)): + return pair_testShell_if_allowed(PlanarSurfaceTransitionPairtestShell, single1, single2, geometrycontainer, domains) + elif (isinstance(single1.structure, PlanarSurface) and isinstance(single2.structure, CuboidalRegion)): + return pair_testShell_if_allowed(MixedPair2D3DtestShell, single1, single2, geometrycontainer, domains) + elif (isinstance(single2.structure, PlanarSurface) and isinstance(single1.structure, CuboidalRegion)): + return pair_testShell_if_allowed(MixedPair2D3DtestShell, single2, single1, geometrycontainer, domains) + # (same as above with other particle order) + elif (isinstance(single1.structure, PlanarSurface) and isinstance(single2.structure, DiskSurface)): + return pair_testShell_if_allowed(MixedPair2DStatictestShell, single1, single2, geometrycontainer, domains) + elif (isinstance(single2.structure, PlanarSurface) and isinstance(single1.structure, DiskSurface)): + return pair_testShell_if_allowed(MixedPair2DStatictestShell, single2, single1, geometrycontainer, domains) + # (same as above with other particle order) + elif (isinstance(single1.structure, CylindricalSurface) and isinstance(single2.structure, DiskSurface)): + return pair_testShell_if_allowed(MixedPair1DStatictestShell, single1, single2, geometrycontainer, domains) + elif (isinstance(single2.structure, CylindricalSurface) and isinstance(single1.structure, DiskSurface)): + return pair_testShell_if_allowed(MixedPair1DStatictestShell, single2, single1, geometrycontainer, domains) + # (same as above with other particle order) + else: + # another mixed pair was supposed to be formed -> unsupported + raise testShellError('(MixedPair). Combination of structures not supported') + +def create_default_pair(domain_id, shell_id, testShell, reaction_rules): + # Either SphericalPair, PlanarSurfacePair, or CylindricalSurfacePair. + if isinstance(testShell, SphericalPairtestShell): + return SphericalPair (domain_id, shell_id, testShell, reaction_rules) + elif isinstance(testShell, PlanarSurfacePairtestShell): + return PlanarSurfacePair (domain_id, shell_id, testShell, reaction_rules) + elif isinstance(testShell, PlanarSurfaceTransitionPairtestShell): + return PlanarSurfaceTransitionPair (domain_id, shell_id, testShell, reaction_rules) + elif isinstance(testShell, CylindricalSurfacePairtestShell): + return CylindricalSurfacePair (domain_id, shell_id, testShell, reaction_rules) + # or MixedPair (3D/2D or 1D/Cap) + elif isinstance(testShell, MixedPair2D3DtestShell): + return MixedPair2D3D (domain_id, shell_id, testShell, reaction_rules) + elif isinstance(testShell, MixedPair1DStatictestShell): + return MixedPair1DStatic (domain_id, shell_id, testShell, reaction_rules) + # NOTE MixedPair2DStatic does not exist, a regular PlanarSurfacePair works well in that case class Delegate(object): def __init__(self, obj, method, arg): @@ -85,9 +340,13 @@ def __call__(self): class EGFRDSimulator(ParticleSimulatorBase): + """The eGFRDSimulator implements the asynchronous egfrd scheme of performing + the diffusing and reaction of n particles. The eGFRDsimulator acts on a 'world' + object containing particles and structures, and can be attached and detached from + this 'world'. """ - """ - def __init__(self, world, rng=myrandom.rng, network_rules=None): + + def __init__(self, world, rng=myrandom.rng, network_rules=None, reset=True): """Create a new EGFRDSimulator. Arguments: @@ -110,73 +369,152 @@ def __init__(self, world, rng=myrandom.rng, network_rules=None): self.domain_id_generator = DomainIDGenerator(0) self.shell_id_generator = ShellIDGenerator(0) - self.MULTI_SHELL_FACTOR = 0.05 - self.SINGLE_SHELL_FACTOR = 1.1 + # some constants + self.MAX_NUM_DT0_STEPS = 100000 - self.is_dirty = True - self.scheduler = EventScheduler() + self.MAX_TIME_STEP = 10 - self.user_max_shell_size = numpy.inf + self.MAX_BURST_RADIUS = 0.5 * self.world.cell_size - self.domains = {} - - self.reset() - - def get_matrix_cell_size(self): - return self.containers[0].cell_size + self.DEFAULT_DT_FACTOR = 1e-5 # Diffusion time prefactor in oldBD algortithm to determine time step. + + self.DEFAULT_STEP_SIZE_FACTOR = 0.05 # The maximum step size in the newBD algorithm is determined as DSSF * sigma_min. + # Make sure that DEFAULT_STEP_SIZE_FACTOR < MULTI_SHELL_FACTOR, or else the + # reaction volume sticks out of the multi. + + self.BD_DT_HARDCORE_MIN = +1e-9 # This is to define a hardcore lower bound for the timestep that will be + # dynamically determined by the new BD scheme. It will prevent the algorithm + # to calculate ridiculously small timesteps, but will break detail balance. + # Take care: This is for testing only! Keep this at a negative value for normal sims! + if reset: + self.BD_ONLY_FLAG = False # Will force the algorithm into Multi-creation, i.e. always to use BD + # Take care: This is for testing only! Keep this 'False' for normal sims! + # (However, we want to retain user-set values if the __init__ is not a reset) + + self.REMOVE_OVERLAPS = True # Ignore overlaps, only warn when they happen and move particles apart + self.max_overlap_error = 0.0 # This remembers the largest relative error produced by removing overlaps + + # used datastructrures + self.scheduler = EventScheduler() # contains the events. Note that every domains has exactly one event + + self.domains = {} # a dictionary containing references to the domains that are defined + # in the simulation system. The id of the domain (domain_id) is the key. + # The domains can be a single, pair or multi of any type. + self.world = world + + # spatial histograms of domain type creation + self.CREATION_HISTOGRAMS = False + self.UPDATES_HISTOGRAMS = False # Attention, this may slow down simulation significantly! + # Use method self.(de)activate_histograms() to switch this on from an external script + self.DomainCreationHists = None + self.DomainUpdatesHists = None + + if self.CREATION_HISTOGRAMS: + self.activate_histograms('creation') + + if self.UPDATES_HISTOGRAMS: + self.activate_histograms('updates') + + # other stuff + self.is_dirty = True # The simulator is dirty if the state if the simulator is not + # consistent with the content of the world that it represents + # (or if we don't know for sure) + + if reset: + self.reset() # The Simulator is only initialized at the first step, allowing + # modifications to the world to be made before simulation starts def get_next_time(self): + """ + Returns the time it will be when the next egfrd timestep + is completed. + """ #~MW if self.scheduler.size == 0: - return self.t - - return self.scheduler.top[1].time - - def set_user_max_shell_size(self, size): - self.user_max_shell_size = size - - def get_user_max_shell_size(self): - return self.user_max_shell_size - - def get_max_shell_size(self): - return min(self.get_matrix_cell_size() * .5 / SAFETY, - self.user_max_shell_size) - + return self.t # self.t is the current time of the simulator + else: + return self.scheduler.top[1].time + + # Some alias methods for the one above + def get_next_event_time(self): + return self.get_next_time() + def next_time(self): + return self.get_next_time() + def next_event_time(self): + return self.get_next_time() + + ##################################################### + #### METHODS FOR GENERAL SIMULATOR FUNCTIONALITY #### + ##################################################### def reset(self): - self.t = 0.0 + """ + This function resets the "records" of the simulator. This means + the simulator time is reset, the step counter is reset, events + are reset, etc. + Can be for example usefull when users want to first do an equilibration + run before starting the "real experiment". + """ #~ MW + self.t = 0.0 self.dt = 0.0 + self.t0 = self.t # Saves the starting time before the first event was fired + # Can be reset when loading a state via load_state() self.step_counter = 0 self.single_steps = {EventType.SINGLE_ESCAPE:0, - EventType.SINGLE_REACTION:0} + EventType.SINGLE_REACTION:0, + EventType.BURST:0, + 'MAKE_NEW_DOMAIN':0} self.interaction_steps = {EventType.IV_INTERACTION:0, - EventType.IV_ESCAPE:0} + EventType.IV_ESCAPE:0, + EventType.SINGLE_ESCAPE:0, + EventType.BURST:0} self.pair_steps = {EventType.SINGLE_REACTION:0, EventType.IV_REACTION:0, EventType.IV_ESCAPE:0, - EventType.COM_ESCAPE:0} + EventType.COM_ESCAPE:0, + EventType.BURST:0} self.multi_steps = {EventType.MULTI_ESCAPE:0, EventType.MULTI_UNIMOLECULAR_REACTION:0, - EventType.MULTI_BIMOLECULAR_REACTION:0, 3:0} + EventType.MULTI_BIMOLECULAR_REACTION:0, + EventType.MULTI_DIFFUSION:0, + EventType.BURST:0, + 3:0} self.zero_steps = 0 + + self.multi_time = 0.0 + self.nonmulti_time = 0.0 + + self.multi_rl = 0.0 + self.rejected_moves = 0 self.reaction_events = 0 self.last_event = None self.last_reaction = None - self.is_dirty = True + self.t0 = 0.0 + + self.is_dirty = True # simulator needs to be re-initialized + def initialize(self): + """Initialize the eGFRD simulator + + This method (re-)initializes the simulator with the current state of the 'world' + that it represents. + Call this method if you change the 'world' using methods outside of the Simulator + thereby invalidating the state of the eGFRD simulator (the simulator is dirty), or + in an other case that the state of the simulator is inconsistent with the state of + the world. + """ + # 1. (re-)initialize previously set datastructures ParticleSimulatorBase.initialize(self) - self.scheduler.clear() - self.containers = [SphericalShellContainer(self.world.world_size, - self.world.matrix_size), - CylindricalShellContainer(self.world.world_size, - self.world.matrix_size)] self.domains = {} - singles = [] + # create/clear other datastructures + self.geometrycontainer = ShellContainer(self.world) + # 2. Couple all the particles in 'world' to a new 'single' in the eGFRD simulator # Fix order of adding particles (always, or at least in debug mode). + singles = [] pid_particle_pairs = list(self.world) pid_particle_pairs.sort() @@ -188,16 +526,20 @@ def initialize(self): singles.append(single) assert len(singles) == self.world.num_particles for single in singles: - self.add_single_event(single) + self.add_domain_event(single) + # 3. The simulator is now consistent with 'world' self.is_dirty = False + def stop(self, t): - """Synchronize all particles at time t. + """Bring the simulation to a full stop, which synchronizes all + particles at time t. The state is similar to the state after + initialization. With eGFRD, particle positions are normally updated - asynchronously. This method bursts all protective domains and - assigns a position to each particle. + asynchronously. This method bursts all protective domains so that + the position of each particle is known. Arguments: - t @@ -215,40 +557,49 @@ def stop(self, t): if __debug__: log.info('stop at %s' % (FORMAT_DOUBLE % t)) - if self.t == t: - return + if self.t == t: # We actually already stopped? + return # FIXME: is this accurate? Probably use feq from utils.py - if t >= self.scheduler.top[1].time: - raise RuntimeError('Stop time >= next event time.') + if t >= self.scheduler.top[1].time: # Can't schedule a stop later than the next event time + raise RuntimeError('stop: stop time (%g) >= next event time (%g).' % (t, self.next_time())) if t < self.t: - raise RuntimeError('Stop time < current time.') + raise RuntimeError('stop: stop time (%g) < current time (%g).' % (t, self.t)) self.t = t - non_single_list = [] - - # first burst all Singles. - for id, event in self.scheduler: - obj = event.data - if isinstance(obj, Pair) or isinstance(obj, Multi): - non_single_list.append(obj) - elif isinstance(obj, Single): - if __debug__: - log.debug('burst %s, last_time= %s' % - (obj, FORMAT_DOUBLE % obj.last_time)) - self.burst_single(obj) - else: - assert False, 'do not reach here' - - - # then burst all Pairs and Multis. - if __debug__: - log.debug('burst %s' % non_single_list) - self.burst_objs(non_single_list) + #non_single_list = [] + + # Burst all the domains that we know of. + self.burst_all_domains() + + ##first burst all Singles, and put Pairs and Multis in a list. + #for id, event in self.scheduler: + #ignore = [] + #for domain_id, domain in self.domains.items(): + #if domain_id not in ignore: + ##domain = self.domains[event.data] + #if isinstance(domain, Pair) or isinstance(domain, Multi): + #non_single_list.append(domain) + #elif isinstance(domain, Single): + #if __debug__: + #log.debug('burst %s, last_time= %s' % + #(domain, FORMAT_DOUBLE % domain.last_time)) + #ignore.append(domain.domain_id) + #ignore = self.burst_single(domain, ignore) + #else: + #assert False, 'domain from domains{} was no Single, Pair or Multi' + + + ## then burst all Pairs and Multis. + #if __debug__: + #log.debug('burst %s' % non_single_list) + #non_single_ids = [non_single.domain_id for non_single in non_single_list] + #self.burst_domains(non_single_ids, ignore) self.dt = 0.0 + def step(self): """Execute one eGFRD step. @@ -261,1171 +612,2494 @@ def step(self): if __debug__: if int("0" + os.environ.get("ECELL_CHECK", ""), 10): self.check() + + if __debug__ and self.control_bounds: + self.check_particle_consistency() self.step_counter += 1 if __debug__: if self.scheduler.size == 0: - raise RuntimeError('No particles in scheduler.') - + raise RuntimeError('step: no Events in scheduler.') + + # 1. Get the next event from the scheduler + # + log.info('step: getting next event from scheduler') id, event = self.scheduler.pop() - self.t, self.last_event = event.time, event - - if __debug__: - domain_counts = self.count_domains() - log.info('\n\n%d: t=%s dt=%s\t' % - (self.step_counter, FORMAT_DOUBLE % self.t, - FORMAT_DOUBLE % self.dt) + - 'Singles: %d, Pairs: %d, Multis: %d\n' % domain_counts + - 'event=#%d reactions=%d rejectedmoves=%d' % - (id, self.reaction_events, self.rejected_moves)) - + domain = self.domains[event.data] + if event.time == numpy.inf: + self.t, self.last_event = self.MAX_TIME_STEP, event + else: + self.t, self.last_event = event.time, event + + # Retained from earlier, but currently switched off + #if __debug__: + # domain_counts = self.count_domains() + # log.info('\n\n%d: t=%s dt=%e (next_time=%s)\t' % + # (self.step_counter, self.t, + # self.dt, self.scheduler.top[1].time if self.scheduler.size > 0 else 0) + + # 'Singles: %d, Pairs: %d, Multis: %d\n' % domain_counts + + # 'event=#%d reactions=%d rejectedmoves=%d' % + # (id, self.reaction_events, self.rejected_moves)) + + # 2. Use the correct method to process (fire) the shell that produced the event + # + # Dispatch is a dictionary (hash) of what function to use to fire + # different classes of shells (see bottom egfrd.py) + # event.data holds the object (Single, Pair, Multi) that is associated with the next event. + # e.g. "if class is Single, then process_single_event" ~ MW + log.info('step: dispatching to appropriate event handler') for klass, f in self.dispatch: - if isinstance(event.data, klass): - f(self, event.data) + + if isinstance(domain, klass): + f(self, domain, [domain.domain_id]) # fire the correct method for the class (e.g. process_single_event(self, Single)) + + if self.UPDATES_HISTOGRAMS: + self.DomainUpdatesHists.bin_domain(domain) if __debug__: if self.scheduler.size == 0: - raise RuntimeError('Zero particles left.') + raise RuntimeError('step: zero events left.') + # 3. Adjust the simulation time + # next_time = self.scheduler.top[1].time + log.info('step: next_time=%s' % next_time) self.dt = next_time - self.t + # Print some info + # Make sure this comes after the new event has been created, + # otherwise we risk a SEGFAULT here at low particle numbers, + # because self.scheduler.top[1] may not exist! + if __debug__: + domain_counts = self.count_domains() + log.info('\n\n%d: t=%s dt=%e (next_time=%s)\t' % + (self.step_counter, self.t, + self.dt, self.scheduler.top[1].time) + + 'Singles: %d, Pairs: %d, Multis: %d\n' % domain_counts + + 'event=#%d reactions=%d rejectedmoves=%d' % + (id, self.reaction_events, self.rejected_moves)) + # assert if not too many successive dt=0 steps occur. if __debug__: - if self.dt == 0: + if self.dt < 1e-10: # We consider 0.1 nanoseconds a zero-step + + log.warning('dt = %e = %.10e-%.10e = zero step, working in s.t >> dt~0 Python limit.' % (self.dt, next_time, self.t)) self.zero_steps += 1 - if self.zero_steps >= max(self.scheduler.size * 3, 10): - raise RuntimeError('too many dt=zero steps. ' - 'Simulator halted?') + + if self.zero_steps >= max(self.scheduler.size * 3, self.MAX_NUM_DT0_STEPS): + raise RuntimeError('step: too many successive dt=zero steps --> Simulator stalled???' + 'dt= %.10e-%.10e' % (next_time, self.t)) + else: self.zero_steps = 0 + + ##################################### + #### METHODS FOR DOMAIN CREATION #### + ##################################### def create_single(self, pid_particle_pair): - rts = self.network_rules.query_reaction_rule(pid_particle_pair[1].sid) + # Create a new single domain from a particle. + # The interaction can be any NonInteractionSingle (SphericalSingle, PlanarSurface or CylindricalSurface + # NonInteractionSingle). + + # 1. generate identifiers for the domain and shell. The event_id is + # generated by the scheduler domain_id = self.domain_id_generator() shell_id = self.shell_id_generator() - # Get structure (region or surface). + # get unimolecular reaction rules + reaction_rules = self.network_rules.query_reaction_rule(pid_particle_pair[1].sid) + # Get structure (region or surface) where the particle lives. species = self.world.get_species(pid_particle_pair[1].sid) - structure = self.world.get_structure(species.structure_id) + structure = self.world.get_structure(pid_particle_pair[1].structure_id) - # Create single. The type of the single that will be created + # 2. Create and register the single domain. + # The type of the single that will be created # depends on the structure (region or surface) this particle is # in/on. Either SphericalSingle, PlanarSurfaceSingle, or # CylindricalSurfaceSingle. - single = create_default_single(domain_id, pid_particle_pair, - shell_id, rts, structure) + single = create_default_single(domain_id, shell_id, pid_particle_pair, + structure, reaction_rules, + self.geometrycontainer, self.domains) - single.initialize(self.t) - self.move_shell(single.shell_id_shell_pair) + assert isinstance(single, NonInteractionSingle) + single.initialize(self.t) # set the initial event and event time self.domains[domain_id] = single - if __debug__: - # Used in __str__. - single.world = self.world + # 3. update the proper shell container + self.geometrycontainer.move_shell(single.shell_id_shell_pair) + + #if __debug__: + ## Used in __str__. + single.world = self.world + return single - def create_pair(self, single1, single2, com, r0, shell_size): - assert single1.dt == 0.0 - assert single2.dt == 0.0 - - # Select 1 reaction type out of all possible reaction types. - rts = self.network_rules.query_reaction_rule( - single1.pid_particle_pair[1].sid, - single2.pid_particle_pair[1].sid) - k_array = numpy.add.accumulate([rt.k for rt in rts]) - k_max = k_array[-1] - rnd = myrandom.uniform() - i = numpy.searchsorted(k_array, rnd * k_max) - rt = rts[i] - # The probability for this reaction to happen is proportional to - # the sum of the rates of all the possible reaction types. - rt.ktot = k_max + def create_interaction(self, testShell): + # Create a new interaction domain from a testShell. + # The interaction can be any interaction (PlanarSurface or CylindricalSurface). The creation of the testShell was + # succesful, so here we can just make the domain. + + assert isinstance(testShell, testInteractionSingle) + # TODO? Assert that the particle is not already associated with another domain + + # 1. generate identifiers for the domain and shell. event_id is generated by + # the scheduler domain_id = self.domain_id_generator() shell_id = self.shell_id_generator() - pos1 = single1.shell.shape.position - pos2 = single2.shell.shape.position + # get unimolecular reaction rules + species_id = testShell.pid_particle_pair[1].sid + reaction_rules = self.network_rules.query_reaction_rule(species_id) + # get reaction rules for interaction + structure_type_id = testShell.target_structure.sid + interaction_rules = self.network_rules.query_reaction_rule(species_id, structure_type_id) - # Get structure (region or surface). - species = self.world.get_species(single1.pid_particle_pair[1].sid) - structure = self.world.get_structure(species.structure_id) + # 2. Create and register the interaction domain. + # The type of the interaction that will be created + # depends on the surface (planar or cylindrical) the particle is + # trying to associate with. Either PlanarSurfaceInteraction or + # CylindricalSurfaceInteraction. + interaction = create_default_interaction(domain_id, shell_id, testShell, + reaction_rules, interaction_rules) - # Create pair. The type of the pair that will be created depends - # on the structure (region or surface) the particles are in/on. - # Either SphericalPair, PlanarSurfacePair, or - # CylindricalSurfacePair. - pair = create_default_pair(domain_id, com, single1, single2, shell_id, - r0, shell_size, rt, structure) + assert isinstance(interaction, InteractionSingle) + interaction.initialize(self.t) + self.domains[domain_id] = interaction - pair.initialize(self.t) + # 3. update the shell containers + self.geometrycontainer.move_shell(interaction.shell_id_shell_pair) - self.move_shell(pair.shell_id_shell_pair) - self.domains[domain_id] = pair + #if __debug__: + ## Used in __str__. + interaction.world = self.world - if __debug__: - # Used in __str__. - pair.world = self.world + return interaction - return pair - def create_multi(self): - domain_id = self.domain_id_generator() - if __debug__: - try: - # Option to make multis run faster for nicer visualization. - dt_factor = DEFAULT_DT_FACTOR * self.bd_dt_factor - except AttributeError: - dt_factor = DEFAULT_DT_FACTOR - else: - dt_factor = DEFAULT_DT_FACTOR - multi = Multi(domain_id, self, dt_factor) - self.domains[domain_id] = multi - return multi + def create_transition(self, testShell): + # Create a new transition domain from a testShell. + # Currently only plane-plane transitions are supported. + # We assume here that the testShell was successfully created. - def move_single(self, single, position, radius=None): - self.move_single_shell(single, position, radius) - self.move_single_particle(single, position) + assert isinstance(testShell, PlanarSurfaceTransitionSingletestShell) or \ + isinstance(testShell, CylindricalSurfacePlanarSurfaceInteractiontestShell) + # TODO should be generalized - def move_single_shell(self, single, position, radius=None): - if radius == None: - # By default, don't change radius. - radius = single.shell.shape.radius + # 1. generate identifiers for the domain and shell. event_id is generated by + # the scheduler + domain_id = self.domain_id_generator() + shell_id = self.shell_id_generator() - # Reuse shell_id and domain_id. - shell_id = single.shell_id - domain_id = single.domain_id + # get unimolecular reaction rules + species_id = testShell.pid_particle_pair[1].sid + reaction_rules = self.network_rules.query_reaction_rule(species_id) - # Replace shell. - shell = single.create_new_shell(position, radius, domain_id) - shell_id_shell_pair = (shell_id, shell) + # 2. Create and register the transition domain. + transition = create_default_transition(domain_id, shell_id, testShell, reaction_rules) - single.shell_id_shell_pair = shell_id_shell_pair - self.move_shell(shell_id_shell_pair) + assert isinstance(transition, Single) + transition.initialize(self.t) + self.domains[domain_id] = transition - def move_single_particle(self, single, position): - new_pid_particle_pair = (single.pid_particle_pair[0], - Particle(position, - single.pid_particle_pair[1].radius, - single.pid_particle_pair[1].D, - single.pid_particle_pair[1].sid)) - single.pid_particle_pair = new_pid_particle_pair + # 3. update the shell containers + self.geometrycontainer.move_shell(transition.shell_id_shell_pair) + # TODO What precisely happens here? - self.world.update_particle(new_pid_particle_pair) + transition.world = self.world - def get_container(self, shell): - if type(shell) is SphericalShell: - return self.containers[0] - elif type(shell) is CylindricalShell: - return self.containers[1] + return transition - def remove_domain(self, obj): - if __debug__: - log.info("remove: %s" % obj) - del self.domains[obj.domain_id] - for shell_id, shell in obj.shell_list: - container = self.get_container(shell) - del container[shell_id] - def move_shell(self, shell_id_shell_pair): - shell = shell_id_shell_pair[1] - container = self.get_container(shell) - container.update(shell_id_shell_pair) + def create_pair(self, testShell): + # Create a new pair domain from a testShell. + # The pair can be any pair (Simple or Mixed). The creation of the testShell was + # succesful, so here we can just make the domain. - def add_single_event(self, single): - event_id = self.scheduler.add( - DomainEvent(self.t + single.dt, single)) - if __debug__: - log.info('add_event: %s, event=#%d, t=%s' % - (single.domain_id, event_id, - FORMAT_DOUBLE % (self.t + single.dt))) - single.event_id = event_id + assert isinstance(testShell, testPair) - def add_pair_event(self, pair): - event_id = self.scheduler.add( - DomainEvent(self.t + pair.dt, pair)) - if __debug__: - log.info('add_event: %s, event=#%d, t=%s' % - (pair.domain_id, event_id, - FORMAT_DOUBLE % (self.t + pair.dt))) - pair.event_id = event_id + # 1. generate needed identifiers + domain_id = self.domain_id_generator() + shell_id = self.shell_id_generator() - def add_multi_event(self, multi): - event_id = self.scheduler.add( - DomainEvent(self.t + multi.dt, multi)) + # Select 1 reaction type out of all possible reaction types between the two particles. + reaction_rules = self.network_rules.query_reaction_rule(testShell.pid_particle_pair1[1].sid, + testShell.pid_particle_pair2[1].sid) - if __debug__: - log.info('add_event: %s, event=#%d, t=%s' % - (multi.domain_id, event_id, - FORMAT_DOUBLE % (self.t + multi.dt))) - multi.event_id = event_id - def remove_event(self, event): - if __debug__: - log.info('remove_event: event=#%d' % event.event_id) - del self.scheduler[event.event_id] + # 2. Create pair. The type of the pair that will be created depends + # on the structure (region or surface) the particles are in/on. + pair = create_default_pair(domain_id, shell_id, testShell, reaction_rules) - def update_single_event(self, t, single): - if __debug__: - log.info('update_event: %s, event=#%d, t=%s' % - (single.domain_id, single.event_id, FORMAT_DOUBLE % t)) - self.scheduler.update((single.event_id, DomainEvent(t, single))) + assert isinstance(pair, Pair) + pair.initialize(self.t) + self.domains[domain_id] = pair - def update_multi_event(self, t, multi): - if __debug__: - log.info('update_event: %s, event=#%d, t=%s' % - (multi.domain_id, multi.event_id, FORMAT_DOUBLE % t)) - self.scheduler.update((multi.event_id, DomainEvent(t, multi))) + # 3. update the shell containers with the new shell + self.geometrycontainer.move_shell(pair.shell_id_shell_pair) - def burst_obj(self, obj): - if __debug__: - log.info('burst_obj: %s' % obj) + #if __debug__: + ## Used in __str__. + pair.world = self.world - if isinstance(obj, Single): - # TODO. Compare with gfrd. - obj = self.burst_single(obj) - bursted = [obj, ] - elif isinstance(obj, Pair): # Pair - single1, single2 = self.burst_pair(obj) - # Don't schedule events in burst/propagate_pair, because - # scheduling is different after a single reaction in - # fire_pair. - self.add_single_event(single1) - self.add_single_event(single2) - self.remove_event(obj) - bursted = [single1, single2] - else: # Multi - bursted = self.burst_multi(obj) - self.remove_event(obj) + return pair - if __debug__: - log.info('bursted = %s' % ',\n\t '.join(str(i) for i in bursted)) - return bursted + def create_multi(self): + # 1. generate necessary id's + domain_id = self.domain_id_generator() - def burst_objs(self, objs): - bursted = [] - for obj in objs: - b = self.burst_obj(obj) - bursted.extend(b) + # 2. Create and register domain object + multi = Multi(domain_id, self, self.DEFAULT_STEP_SIZE_FACTOR, self.BD_DT_HARDCORE_MIN) + self.domains[domain_id] = multi - return bursted + # 3. no shells are yet made, since these are added later + # -> a multi can have 0 shells + return multi - def clear_volume(self, pos, radius, ignore=[]): - neighbors = self.get_neighbors_within_radius_no_sort(pos, radius, - ignore) - return self.burst_objs(neighbors) - def burst_non_multis(self, neighbors): - bursted = [] + ############################################## + #### METHODS FOR PARTICLE / DOMAIN UPDATE #### + ############################################## + #def move_single(self, single, position, radius=None): + #single.pid_particle_pair = self.move_particle(single.pid_particle_pair, position) + #self.update_single_shell(single, position, radius) - for obj in neighbors: - if not isinstance(obj, Multi): - b = self.burst_obj(obj) - bursted.extend(b) - else: - bursted.append(obj) + #def update_single_shell(self, single, shell):#position, radius=None): + ## Reuse shell_id. + #shell_id = single.shell_id - return bursted + ## Replace shell. + #shell_id_shell_pair = (shell_id, shell) - def fire_single_reaction(self, single): - reactant_species_radius = single.pid_particle_pair[1].radius - oldpos = single.pid_particle_pair[1].position - current_surface = single.surface - - rt = single.draw_reaction_rule() + #single.shell_id_shell_pair = shell_id_shell_pair + #self.geometrycontainer.move_shell(shell_id_shell_pair) - if len(rt.products) == 0: - - self.world.remove_particle(single.pid_particle_pair[0]) - self.last_reaction = (rt, (single.pid_particle_pair[1], None), []) + def move_particle(self, pid_particle_pair, position, structure_id): + # Moves a particle in World and performs the required change of structure_id + # based on an existing particle. - - elif len(rt.products) == 1: - - product_species = self.world.get_species(rt.products[0]) + new_pid_particle_pair = (pid_particle_pair[0], + Particle(position, + pid_particle_pair[1].radius, + pid_particle_pair[1].D, + pid_particle_pair[1].v, + pid_particle_pair[1].sid, + structure_id)) - if reactant_species_radius < product_species.radius: - self.clear_volume(oldpos, product_species.radius) + self.world.update_particle(new_pid_particle_pair) - if self.world.check_overlap((oldpos, product_species.radius), - single.pid_particle_pair[0]): - if __debug__: - log.info('no space for product particle.') - raise NoSpace() + return new_pid_particle_pair - self.world.remove_particle(single.pid_particle_pair[0]) - newparticle = self.world.new_particle(product_species.id, oldpos) - newsingle = self.create_single(newparticle) - if __debug__: - log.info('product = %s' % newsingle) - self.add_single_event(newsingle) - self.last_reaction = (rt, (single.pid_particle_pair[1], None), - [newparticle]) + def activate_overlap_remover(): + """ + Sets internal variable REMOVE_OVERLAPS to True (False by default), + activating the removal of overlaps while monitoring the relative + error produced by that. + The rel. error is defined as the separation added between particles + divided by the largest involved radius. - - elif len(rt.products) == 2: - product_species1 = self.world.get_species(rt.products[0]) - product_species2 = self.world.get_species(rt.products[1]) - - D1 = product_species1.D - D2 = product_species2.D - D12 = D1 + D2 - - particle_radius1 = product_species1.radius - particle_radius2 = product_species2.radius - particle_radius12 = particle_radius1 + particle_radius2 + Use deactivate_overlap_remover() to reverse this. + """ - # clean up space. - rad = max(particle_radius12 * (D1 / D12) + particle_radius1, - particle_radius12 * (D2 / D12) + particle_radius2) + self.REMOVE_OVERLAPS = True - self.clear_volume(oldpos, rad) - for _ in range(self.dissociation_retry_moves): - vector = _random_vector(current_surface, particle_radius12 * - MINIMAL_SEPARATION_FACTOR, self.rng) - - # place particles according to the ratio D1:D2 - # this way, species with D=0 doesn't move. - # FIXME: what if D1 == D2 == 0? + def deactivate_overlap_remover(): + """ + Sets internal variable REMOVE_OVERLAPS to False, + deactivating the removal of overlaps. + + Use activate_overlap_remover() to activate it. + """ - while 1: - newpos1 = oldpos + vector * (D1 / D12) - newpos2 = oldpos - vector * (D2 / D12) - newpos1 = self.world.apply_boundary(newpos1) - newpos2 = self.world.apply_boundary(newpos2) + self.REMOVE_OVERLAPS = False - if(self.world.distance(newpos1, newpos2) >= - particle_radius12): - break - vector *= 1.0 + 1e-7 + def remove_overlap(self, reactant, target_position, ignore_p=None): + # Get overlaps when moved to target position + if ignore_p: + co = self.world.check_overlap((target_position, reactant[1].radius), + reactant[0], ignore_p[0]) + else: + co = self.world.check_overlap((target_position, reactant[1].radius), reactant[0]) - # accept the new positions if there is enough space. - if(not self.world.check_overlap((newpos1, particle_radius1), - single.pid_particle_pair[0]) - and - not self.world.check_overlap((newpos2, particle_radius2), - single.pid_particle_pair[0])): - break - else: - if __debug__: - log.info('no space for product particles.') - raise NoSpace() + # Extract the overlap data for the closest particle + closest = co[0] + overlap_PID = closest[0][0] + overlap_particle = closest[0][1] + overlap_length = closest[1] - self.world.remove_particle(single.pid_particle_pair[0]) + log.warn('Removing overlap: reported overlap length = %s', overlap_length) - particle1 = self.world.new_particle(product_species1.id, newpos1) - particle2 = self.world.new_particle(product_species2.id, newpos2) - newsingle1 = self.create_single(particle1) - newsingle2 = self.create_single(particle2) + #log.debug('Target position: %s', target_position) + #log.debug('Overlapping particle: %s, overlap: %s', overlap_particle, overlap_length) + #log.debug('ID: %s', overlap_PID) + #log.debug('pos: %s', overlap_particle.position) + # TODO remove that debugging stuff - if __debug__: - log.info('product1 = %s\nproduct2 = %s' % - (newsingle1, newsingle2)) + target_position_t = self.world.cyclic_transpose(target_position, overlap_particle.position) + IP_vector = target_position_t - overlap_particle.position + correction = SAFETY * (reactant[1].radius + overlap_particle.radius) + + new_position = self.world.apply_boundary( target_position + correction * normalize(IP_vector) ) + # TODO: Check that new position is in reactant structure!!! - self.add_single_event(newsingle1) - self.add_single_event(newsingle2) + #log.debug('target_position_t = %s', target_position_t) + #log.debug('IP_vector = %s, length = %s, normalized = %s', IP_vector, length(IP_vector), normalize(IP_vector) ) + #log.debug('new_pos = %s', new_position) + # TODO remove that debugging stuff - self.last_reaction = (rt, (single.pid_particle_pair[1], None), - [particle1, particle2]) + co = self.world.check_overlap((new_position, reactant[1].radius), reactant[0]) + + if co: + for c in co: + # Failed to remove overlap, sth. serious going on + log.debug('remove_overlap: RE-detected overlap with %s' % str(c)) + RuntimeError('remove_overlap: failed.') else: - raise RuntimeError('num products >= 3 not supported.') - self.reaction_events += 1 + # The overlap error is defined as the artificially added displacement divided by the smallest particle radius involved + overlap_error = abs(correction - length(IP_vector)) / min(reactant[1].radius, overlap_particle.radius) + self.max_overlap_error = max(self.max_overlap_error, overlap_error) + # Drop a warning + log.warn('Removing overlap: error = %s, maximal error of overlap removal = %s', overlap_error, self.max_overlap_error) + + return new_position - def propagate_single(self, single): - # The difference between a burst and a propagate is that a burst - # always takes place before the actual scheduled event for the - # single, while propagate_single can be called for an escape event. - # Another subtle difference is that burst_single always - # reschedules (update_event) the single, while just calling - # propagate does not. So whoever calls propagate_single - # directly should reschedule the single afterwards. + def remove_domain(self, obj): + # Removes all the ties to a domain (single, pair, multi) from the system. + # Note that the particles that it represented still exist + # in 'world' and that obj also still persits (?!) if __debug__: - log.debug("single.dt=%s, single.last_time=%s, self.t=%s" % - (FORMAT_DOUBLE % single.dt, - FORMAT_DOUBLE % single.last_time, - FORMAT_DOUBLE % self.t)) + log.info("remove: %s" % obj) + # TODO assert that the domain is not still in the scheduler + del self.domains[obj.domain_id] + for shell_id_shell_pair in obj.shell_list: + self.geometrycontainer.remove_shell(shell_id_shell_pair) - newpos = single.draw_new_position(single.dt, single.event_type) - newpos = self.world.apply_boundary(newpos) - if __debug__: - log.debug("propagate %s: %s => %s" % - (single, single.pid_particle_pair[1].position, newpos)) - - if self.world.check_overlap((newpos, - single.pid_particle_pair[1].radius), - single.pid_particle_pair[0]): - raise RuntimeError('propagate_single: check_overlap failed.') - - if(single.event_type == EventType.SINGLE_REACTION and - single.event_type != EventType.BURST): - # SINGLE_REACTION, and not a burst. No need to update, single is - # removed anyway. - self.move_single_particle(single, newpos) - return single - else: - # Todo. if isinstance(single, InteractionSingle): - single.initialize(self.t) - self.move_single(single, newpos, - single.pid_particle_pair[1].radius) + ####################################### + #### METHODS FOR EVENT BOOKKEEPING #### + ####################################### + # TODO This method can be made a method to the scheduler class + def add_domain_event(self, domain): + # This method makes an event for domain 'domain' in the scheduler. + # The event will have the domain_id pointing to the appropriate domain in 'domains{}'. + # The domain will have the event_id pointing to the appropriate event in the scheduler. - return single + # Adapt the sampled dt to the preset scheduler precision + dt_rounded = round(domain.dt, SCHEDULER_DIGITS) + domain.dt = dt_rounded - def fire_single(self, single): - assert abs(single.dt + single.last_time - self.t) <= 1e-18 * self.t + event_time = self.t + domain.dt + event_id = self.scheduler.add( + DomainEvent(event_time, domain)) + if __debug__: + log.info('add_event: %s, event=#%d, t=%s' % + (domain.domain_id, event_id, event_time ) + ) + domain.event_id = event_id # FIXME side effect programming -> unclear!! - # Reaction. - if single.event_type == EventType.SINGLE_REACTION: - if __debug__: - log.info('%s' % single.event_type) - log.info('reactant = %s' % single) - self.single_steps[single.event_type] += 1 + # TODO This method can be made a method to the scheduler class + def remove_event(self, event): + if __debug__: + log.info('remove_event: event=#%d' % event.event_id) + del self.scheduler[event.event_id] - single = self.propagate_single(single) + # TODO This method can be made a method to the scheduler class + def update_domain_event(self, t, domain): + if __debug__: + log.info('update_event: %s, event=#%d, t=%s' % + (domain.domain_id, domain.event_id, t)) + self.scheduler.update((domain.event_id, DomainEvent(t, domain))) - try: - self.remove_domain(single) - self.fire_single_reaction(single) - except NoSpace: - self.reject_single_reaction(single) - return + ##################################### + #### METHODS FOR DOMAIN BURSTING #### + ##################################### + def burst_domain(self, domain, ignore): + # Reduces 'domain' (Single, Pair or Multi) to 'zero_singles', singles + # with the zero shell, and dt=0. + # returns: + # - list of zero_singles that was the result of the bursting + # - updated ignore list - if single.event_type == EventType.IV_EVENT: - # Draw actual pair event for iv at very last minute. - single.event_type = single.draw_iv_event_type() - self.interaction_steps[single.event_type] += 1 - else: - self.single_steps[single.event_type] += 1 + domain_id = domain.domain_id if __debug__: - log.info('%s' % single.event_type) - log.info('single = %s' % single) - - # Handle immobile case first. - if single.getD() == 0: - # no propagation, just calculate next reaction time. - single.dt, single.event_type = single.determine_next_event() - single.last_time = self.t - self.add_single_event(single) - return - - if single.dt != 0.0: - # Propagate this particle to the exit point on the shell. - single = self.propagate_single(single) - - singlepos = single.shell.shape.position - - # (2) Clear volume. - - min_shell = single.pid_particle_pair[1].radius * \ - self.SINGLE_SHELL_FACTOR + log.info('burst_domain: %s' % domain) - intruders, closest, closest_distance = \ - self.get_intruders(singlepos, min_shell, - ignore=[single.domain_id, ]) + if isinstance(domain, Single): # Single + # TODO. Compare with gfrd. + zero_singles, ignore = self.burst_single(domain, ignore) + elif isinstance(domain, Pair): # Pair + zero_singles, ignore = self.burst_pair(domain, ignore) + else: # Multi + assert isinstance(domain, Multi) + zero_singles, ignore = self.burst_multi(domain, ignore) if __debug__: - log.debug("intruders: %s, closest: %s (dist=%s)" % - (', '.join(str(i) for i in intruders), - closest, FORMAT_DOUBLE % closest_distance)) + # After a burst, the domain should be gone and should be on the ignore list. + # Also the zero_singles should only be NonInteractionSingles + assert domain_id not in self.domains + assert domain_id in ignore + assert all(isinstance(b, NonInteractionSingle) for b in zero_singles) + log.info('zero_singles = %s' % ',\n\t '.join(str(i) for i in zero_singles)) - if intruders: - burst = self.burst_non_multis(intruders) + return zero_singles, ignore - obj = self.form_pair_or_multi(single, burst) - if obj: - return + def burst_single(self, single, ignore): + # Bursts the 'single' domain and updates the 'ignore' list. + # Returns: + # - zero_singles that are the result of bursting the single (can be multiple + # because the burst is recursive). The zero_single have zero shell and are scheduled. + # - the updated ignore list - # if nothing was formed, recheck closest and restore shells. - burst = uniq(burst) - - closest, closest_distance = \ - self.get_closest_obj(singlepos, ignore=[single.domain_id], - ignores=[single.surface.id]) - self.update_single(single, closest, closest_distance) - for s in burst: - if not isinstance(s, Single): - continue - assert s.is_reset() - closest, closest_distance = self.get_closest_obj( - s.shell.shape.position, ignore=[s.domain_id], - ignores=[s.surface.id]) - - self.update_single(s, closest, closest_distance) - self.update_single_event(self.t + s.dt, s) - if __debug__: - log.debug('restore shell %s radius %s dt %s\n' - 'closest %s distance %s' % - (s, FORMAT_DOUBLE % s.shell.shape.radius, - FORMAT_DOUBLE % s.dt, closest, - FORMAT_DOUBLE % closest_distance)) - else: - self.update_single(single, closest, closest_distance) - if __debug__: - log.info('updated shell: (%s,\n Shell(%s, %s)' % - (single.shell_id, single.shell.did, - single.shell.shape.show(PRECISION))) + log.debug('burst single: %s', single) - self.add_single_event(single) - return + # Check correct timeline ~ MW + assert single.last_time <= self.t + assert self.t <= single.last_time + single.dt - def reject_single_reaction(self, single): - if __debug__: - log.info('single reaction; placing product failed.') - self.domains[single.domain_id] = single - self.move_shell(single.shell_id_shell_pair) - self.rejected_moves += 1 - single.initialize(self.t) - self.add_single_event(single) - - def calculate_single_shell_size(self, single, closest, - distance, shell_distance): - assert isinstance(closest, Single) - - min_radius1 = single.pid_particle_pair[1].radius - D1 = single.getD() - - if D1 == 0: - return min_radius1 - - D2 = closest.getD() - min_radius2 = closest.pid_particle_pair[1].radius - min_radius12 = min_radius1 + min_radius2 - sqrtD1 = math.sqrt(D1) - - shell_size = min(sqrtD1 / (sqrtD1 + math.sqrt(D2)) - * (distance - min_radius12) + min_radius1, - shell_distance / SAFETY) - if shell_size < min_radius1: - shell_size = min_radius1 - - return shell_size - - def update_single(self, single, closest, distance_to_shell): - # Todo. assert not isinstance(single, InteractionSingle) - - singlepos = single.shell.shape.position - if isinstance(closest, Single): - closestpos = closest.shell.shape.position - distance_to_closest = self.world.distance(singlepos, closestpos) - new_shell_size = self.calculate_single_shell_size(single, closest, - distance_to_closest, - distance_to_shell) - else: # Pair or Multi or Surface - new_shell_size = distance_to_shell / SAFETY - new_shell_size = max(new_shell_size, - single.pid_particle_pair[1].radius) - - new_shell_size = min(new_shell_size, self.get_max_shell_size()) - - # Resize shell, don't change position. - # Note: this should be done before determine_next_event. - self.move_single_shell(single, singlepos, new_shell_size) + # Override dt, the burst happens before the single's scheduled event. + # Override event_type. + single.dt = self.t - single.last_time + single.event_type = EventType.BURST + # with a burst there is always an associated event in the scheduler. + # to simulate the natural occurence of the event we have to remove it from the scheduler + self.remove_event(single) - single.dt, single.event_type = single.determine_next_event() - single.last_time = self.t + return self.process_single_event(single, ignore) - def fire_pair(self, pair): - assert self.check_obj(pair) - single1 = pair.single1 - single2 = pair.single2 - particle1 = single1.pid_particle_pair - particle2 = single2.pid_particle_pair - pos1 = particle1[1].position - pos2 = particle2[1].position - - if pair.event_type == EventType.IV_EVENT: - # Draw actual pair event for iv at very last minute. - r0 = self.world.distance(pos1, pos2) - pair.event_type = pair.draw_iv_event_type(r0) + def burst_pair(self, pair, ignore): + # Bursts the 'pair' domain and updates the 'ignore' list. + # Returns: + # - zero_singles that are the result of bursting the pair (can be more than two + # since the burst is recursive). The zero_single have zero shell and are scheduled. + # - the updated ignore list - self.pair_steps[pair.event_type] += 1 + if __debug__: + log.debug('burst_pair: %s', pair) + # make sure that the current time is between the last time and the event time if __debug__: - log.info('FIRE PAIR: %s' % pair.event_type) - log.info('single1 = %s' % pair.single1) - log.info('single2 = %s' % pair.single2) + assert pair.last_time <= self.t + assert self.t <= pair.last_time + pair.dt + # Override event time and event_type. + pair.dt = self.t - pair.last_time + pair.event_type = EventType.BURST + # with a burst there is always an associated event in the scheduler. + # to simulate the natural occurence of the event we have to remove it from the scheduler + self.remove_event(pair) - old_com = pair.com + return self.process_pair_event(pair, ignore) - # Four cases: - # 1. Single reaction - # 2. Pair reaction - # 3a. IV escape - # 3b. com escape - # - # 1. Single reaction - # - if pair.event_type == EventType.SINGLE_REACTION: - reactingsingle = pair.reactingsingle + def burst_multi(self, multi, ignore): + # Bursts the 'multi' domain and updates the 'ignore' list. + # Returns: + # - zero_singles that are the result of bursting the multi (note that the burst is + # recursive). The zero_single have zero shell and are scheduled. + # - the updated ignore list - if reactingsingle == single1: - theothersingle = single2 - else: - theothersingle = single1 + if __debug__: + log.debug('burst multi: %s', multi) - self.burst_pair(pair) + #multi.sim.sync() + # make sure that the current time is between the last time and the event time + if __debug__: + assert multi.last_time <= self.t + assert self.t <= multi.last_time + multi.dt - self.add_single_event(theothersingle) + # with a burst there is always an associated event in the scheduler. + # to simulate the natural occurence of the event we have to remove it from the scheduler + self.remove_event(multi) # The old event was still in the scheduler - if __debug__: - log.info('reactant = %s' % reactingsingle) - try: - self.remove_domain(reactingsingle) - self.fire_single_reaction(reactingsingle) - except NoSpace: - self.reject_single_reaction(reactingsingle) + return self.break_up_multi(multi, ignore) - return - - # - # 2. Pair reaction - # - if pair.event_type == EventType.IV_REACTION: - self.world.remove_particle(single1.pid_particle_pair[0]) - self.world.remove_particle(single2.pid_particle_pair[0]) - if len(pair.rt.products) == 1: - species3 = self.world.get_species(pair.rt.products[0]) + def burst_volume(self, pos, radius, ignore=[]): + """ Burst domains within a certain volume and give their ids. - # calculate new R - event_type = pair.event_type - new_com = pair.draw_new_com(pair.dt, event_type) + (Bursting means it propagates the particles within the domain until + the current time, and then creates a new, minimum-sized domain.) - if __debug__: - shell_size = pair.get_shell_size() - assert self.world.distance(old_com, new_com) < \ - shell_size - species3.radius + Arguments: + - pos: position of area to be "bursted" + - radius: radius of spherical area to be "bursted" + - ignore: ids of domains that should be ignored, none by default. + """ + # TODO burst_volume now always assumes a spherical volume to burst. + # It is inefficient to burst on the 'other' side of the membrane or to + # burst in a circle, when you want to make a new shell in the membrane. + # Then we may want to burst in a cylindrical volume, not a sphere. + # Same for the 1D particles on rods. - new_com = self.world.apply_boundary(new_com) + burst_radius = SAFETY*radius - particle = self.world.new_particle(species3.id, new_com) - product = self.create_single(particle) - self.add_single_event(product) + if burst_radius > self.MAX_BURST_RADIUS: - self.reaction_events += 1 + log.warn('Setting burst radius (= %s) to maximally allowed limit ( = %s).' % (burst_radius, self.MAX_BURST_RADIUS)) + burst_radius = self.MAX_BURST_RADIUS / SAFETY - self.last_reaction = (pair.rt, (particle1, particle2), - [particle]) - elif len(pair.rt.products) == 0: - product = [] - else: - raise NotImplementedError('num products >= 2 not supported.') + neighbor_ids = self.geometrycontainer.get_neighbors_within_radius_no_sort(pos, burst_radius, ignore) - if __debug__: - log.info('product = %s' % product) - self.remove_domain(pair) + return self.burst_domains(neighbor_ids, ignore) - return - # - # 3a. Escaping through a_r. - # 3b. Escaping through a_R. - # - elif(pair.event_type == EventType.IV_ESCAPE or - pair.event_type == EventType.COM_ESCAPE): - dt = pair.dt - event_type = pair.event_type - single1, single2 = self.propagate_pair(pair, dt, event_type) - self.add_single_event(single1) - self.add_single_event(single2) - else: - raise SystemError('Bug: invalid event_type.') + def burst_all_domains(self): + # Bursts all the domains in the simulator - return + # First apply all changes that may have been triggered by the last event + # These are for example new shellmaking steps with dt==0 which logically + # still are part of the last update. This is also to ensure that we do not + # attempt to "double burst" domains, i.e. run this routine on domains which + # are just bursted. In that case the checks below would fail. + while self.dt == 0.0: + self.step() - def fire_multi(self, multi): - self.multi_steps[3] += 1 # multi_steps[3]: total multi steps - multi.step() + all_domains = self.domains.items() + all_domain_ids =[domain_id for domain_id, _ in all_domains] + zero_singles, ignore = self.burst_domains(all_domain_ids, []) + # Check if the bursting procedure was correct. if __debug__: - log.info('FIRE MULTI: %s' % multi.last_event) - if(multi.last_event == EventType.MULTI_UNIMOLECULAR_REACTION or - multi.last_event == EventType.MULTI_BIMOLECULAR_REACTION): + if len(zero_singles) != self.world.num_particles : + log.warn('No. of zero singles (= %s) is not equal to world.num_particles (= %s)!' \ + % (len(zero_singles), self.world.num_particles) ) + + if len(ignore) != len(all_domains) + self.world.num_particles: + log.warn('Length of ignore list (= %s) is not equal to world.num_particles (= %s) plus length of all domains list (%s)!' \ + % (len(ignore), len(all_domains), self.world.num_particles) ) + + #assert len(zero_singles) == self.world.num_particles + #assert len(ignore) == (len(all_domains) + self.world.num_particles) + + + def burst_domains(self, domain_ids, ignore=[]): + # bursts all the domains that are in the list 'domain_ids' + # ignores all the domain ids on 'ignore' + # returns: + # - list of zero_singles that was the result of the bursting + # - updated ignore list + + zero_singles = [] + for domain_id in domain_ids: + if domain_id not in ignore: + domain = self.domains[domain_id] + + # add the domain_id to the list of domains that is already bursted (ignore list), + # and burst the domain. + ignore.append(domain_id) + more_zero_singles, ignore = self.burst_domain(domain, ignore) + # add the resulting zero_singles from the burst to the total list of zero_singles. + zero_singles.extend(more_zero_singles) + + return zero_singles, ignore + + + def burst_non_multis(self, pos, radius, ignore): + # Recursively bursts the domains within 'radius' centered around 'pos' + # Similarly to burst_volume but now doesn't burst all domains. + # Returns: + # - the updated list domains that are already bursted -> ignore list + # - zero_singles that were the result of the burst + # get the neighbors in the burstradius that are not already bursted. + neighbor_ids = self.geometrycontainer.get_neighbors_within_radius_no_sort(pos, SAFETY*radius, ignore) + + zero_singles = [] + for domain_id in neighbor_ids: + if domain_id not in ignore: # the list 'ignore' may have been updated in a + domain = self.domains[domain_id] # previous iteration of the loop + + if isinstance(domain, NonInteractionSingle) and domain.is_reset(): + # If the domain was already bursted, but was not on the ignore list yet, put it there. + # NOTE: we assume that the domain has already bursted around it when it was made! + ignore.append(domain_id) + + elif (not isinstance(domain, Multi)): #and (self.t != domain.last_time): ## TESTING TESTING TESTING + # Burst domain only if (AND): + # -within burst radius + # -not already bursted before (not on 'ignore' list) + # -domain is not zero dt NonInteractionSingle + # -domain is not a multi + # -domain has time passed # TESTING we switch this off because sometimes indeed it is needed to + # burst newly created domains. For example, when two Multis are dissolved + # at the same time. Then the first Multi-breakup will result in the creation + # of new domains that will not be bursted at the second Multi-breakup + # because dt=0. This may lead to overlaps. + + # add the domain_id to the list of domains that is already bursted (ignore list), + # and burst the domain. + ignore.append(domain_id) + more_zero_singles, ignore = self.burst_domain(domain, ignore) + # add the resulting zero_singles from the burst to the total list of zero_singles. + zero_singles.extend(more_zero_singles) + + #else: + # Don't burst domain if (OR): + # -domain is farther than burst radius + # -domain is already a burst NonInteractionSingle (is on the 'ignore' list) + # -domain is a multi + # -domain is domain in which no time has passed + + # NOTE that the domain id is NOT added to the ignore list since it may be bursted at a later + # time + + return zero_singles, ignore + + + #################################### + #### METHODS FOR EVENT HANDLING #### + #################################### + def fire_single_reaction(self, single, reactant_pos, reactant_structure_id, ignore): + # This takes care of the identity change when a single particle decays into + # one or a number of other particles + # It performs the reactions: + # A(any structure) -> 0 + # A(any structure) -> B(same structure or 3D) + # A(any structure) -> B(same structure) + C(same structure or 3D) + if __debug__: + assert isinstance(single, Single) + + # Note that the reactant_structure_id is the id of the structure on which the particle was located at the time of the reaction. + if __debug__: + assert isinstance(single, Single) + + # 0. Get reactant info + reactant = single.pid_particle_pair + reactant_radius = reactant[1].radius + reactant_species = self.world.get_species(reactant[1].sid) + reactant_structure_type_id = reactant_species.structure_type_id + reactant_structure = self.world.get_structure(reactant_structure_id) + reactant_structure_parent_id = reactant_structure.structure_id + reactant_structure_parent_structure = self.world.get_structure(reactant_structure_parent_id) + # Get reaction rule + rr = single.reactionrule + + # Some abbreviations used below + def_sid = self.world.get_def_structure_type_id() + parent_sid = reactant_structure_parent_structure.sid + + # The zero_singles are the NonInteractionSingles that are the total result of the recursive + # bursting process. + zero_singles = [] + + if len(rr.products) == 0: + + # 1. No products, no info + # 1.5 No new position/structure_id required + # 2. No space required + # 3. No space required + # 4. process the changes (remove particle, make new ones) + self.world.remove_particle(reactant[0]) + products = [] + # zero_singles/ignore is unchanged. + + # 5. Update counters self.reaction_events += 1 - self.last_reaction = multi.last_reaction + self.last_reaction = (rr, (reactant, None), products) - if multi.last_event is not None: - self.break_up_multi(multi) - self.multi_steps[multi.last_event] += 1 - else: - self.add_multi_event(multi) + # 6. Log the change + if __debug__: + log.info('single reaction: product = None.') + + elif len(rr.products) == 1: + + # 1. get product info + product_species = self.world.get_species(rr.products[0]) + product_radius = product_species.radius + product_structure_type_id = product_species.structure_type_id + + # 1.5 get new position and structure_id of particle + # If the particle falls off a surface (other than CuboidalRegion) + if product_structure_type_id != reactant_structure_type_id: + + # Figure out where the product goes. Only default structure or parent structure allowed. + if product_structure_type_id == def_sid: + product_structure_id = self.world.get_def_structure_id() # the default structure + + elif product_structure_type_id == parent_sid: + product_structure_id = reactant_structure.structure_id # the parent structure + + else: + raise RuntimeError('fire_single_reaction: Product structure must be default structure or parent structure of reactant structure.') + + # produce a number of new possible positions for the product particle + # TODO make these generators for efficiency + product_pos_list = [] + if isinstance(reactant_structure, PlanarSurface): + if reactant_structure.shape.is_one_sided: + # unbinding always in direction of unit_z + directions = [1] + else: + # randomize unbinding direction + a = myrandom.choice(-1, 1) + directions = [-a,a] + # place the center of mass of the particle 'at contact' with the membrane + vector_length = (product_radius + 0.0) * (MINIMAL_SEPARATION_FACTOR - 1.0) # the thickness of the membrane is 0.0 + product_pos_list = [reactant_pos + vector_length * reactant_structure.shape.unit_z * direction \ + for direction in directions] + + elif isinstance(reactant_structure, CylindricalSurface): + vector_length = (product_radius + reactant_structure.shape.radius) * MINIMAL_SEPARATION_FACTOR + for _ in range(self.dissociation_retry_moves): + unit_vector3D = random_unit_vector() + unit_vector2D = normalize(unit_vector3D - + (reactant_structure.shape.unit_z * numpy.dot(unit_vector3D, reactant_structure.shape.unit_z))) + vector = reactant_pos + vector_length * unit_vector2D + product_pos_list.append(vector) + + elif isinstance(reactant_structure, DiskSurface): + # Initially check whether the DiskSurface dissociates the particles in radial direction (default setting) + if reactant_structure.dissociates_radially(): + + # The particle unbinds perpendicularly to disk unit vector (i.e. like on cylinder) + vector_length = (product_radius + reactant_structure.shape.radius) * MINIMAL_SEPARATION_FACTOR + for _ in range(self.dissociation_retry_moves): + unit_vector3D = random_unit_vector() + unit_vector2D = normalize(unit_vector3D - + (reactant_structure.shape.unit_z * numpy.dot(unit_vector3D, reactant_structure.shape.unit_z))) + vector = reactant_pos + vector_length * unit_vector2D + product_pos_list.append(vector) + + else: + # The particle stays where it is + product_pos_list.append(reactant_pos) - def break_up_multi(self, multi): - self.remove_domain(multi) + else: + # cannot decay from 3D to other structure + raise RuntimeError('fire_single_reaction: Can not decay from 3D to other structure') - singles = [] - for pid_particle_pair in multi.particles: - single = self.create_single(pid_particle_pair) - self.add_single_event(single) - singles.append(single) - return singles + # If decay happens to same structure_type + else: + + product_structure_id = reactant_structure_id + product_pos_list = [reactant_pos] # no change of position is required if structure_type doesn't change - def burst_multi(self, multi): - #multi.sim.sync() - assert isinstance(multi, Multi) - singles = self.break_up_multi(multi) - return singles + # 2. make space for the products (kinda brute force, but now we only have to burst once). + if not ((product_pos_list[0] == reactant_pos).all()) or product_radius > reactant_radius: + product_pos = self.world.apply_boundary(product_pos_list[0]) + radius = self.world.distance(product_pos, reactant_pos) + product_radius + zero_singles, ignore = self.burst_volume(product_pos, radius*SINGLE_SHELL_FACTOR, ignore) - def burst_single(self, single): - assert self.t >= single.last_time - assert self.t <= single.last_time + single.dt + # 3. check that there is space for the products (try different positions if possible) + # accept the new positions if there is enough space. + for product_pos in product_pos_list: + product_pos = self.world.apply_boundary(product_pos) - oldpos = single.shell.shape.position - old_shell_size = single.get_shell_size() + # check that there is space for the products + # Note that we do not check overlap with surfaces (we assume that its no problem) + if (not self.world.check_overlap((product_pos, product_radius), reactant[0])): - particle_radius = single.pid_particle_pair[1].radius + # 4. process the changes (remove particle, make new ones) + self.world.remove_particle(reactant[0]) + newparticle = self.world.new_particle(product_species.id, product_structure_id, product_pos) + products = [newparticle] - # Override dt, burst happens before single's scheduled event. - single.dt = self.t - single.last_time - # Override event_type. Always call gf.drawR on BURST. - single.event_type = EventType.BURST - newsingle = self.propagate_single(single) + # 5. update counters + self.reaction_events += 1 + self.last_reaction = (rr, (reactant, None), products) - newpos = newsingle.pid_particle_pair[1].position - assert self.world.distance(newpos, oldpos) <= \ - old_shell_size - particle_radius - # Displacement check is in NonInteractionSingle.draw_new_position. + # 6. Log the change + if __debug__: + log.info('single reaction: product (%s) = %s' % (len(products), products)) - # Todo. if isinstance(single, InteractionSingle): - self.update_single_event(self.t, single) + # exit the loop, we have found a new position + break - assert newsingle.shell.shape.radius == particle_radius + else: + # 4. Process (the lack of) change + moved_reactant = self.move_particle(reactant, reactant_pos, reactant_structure_id) + products = [moved_reactant] - # Returned single is different from original single in the case - # of an InteractionSingle only. - return newsingle + # 5. update counters + self.rejected_moves += 1 - def burst_pair(self, pair): - if __debug__: - log.debug('burst_pair: %s', pair) - - assert self.t >= pair.last_time - assert self.t <= pair.last_time + pair.dt + # 6. Log the event + if __debug__: + log.info('single reaction: placing product failed.') - dt = self.t - pair.last_time - # Override event_type. Always call sgf.drawR and pgf.drawR on BURST. - event_type = EventType.BURST - single1, single2 = self.propagate_pair(pair, dt, event_type) + + elif len(rr.products) == 2: + # 1. get product info + product1_species = self.world.get_species(rr.products[0]) + product2_species = self.world.get_species(rr.products[1]) + product1_structure_type_id = product1_species.structure_type_id + product2_structure_type_id = product2_species.structure_type_id + product1_radius = product1_species.radius + product2_radius = product2_species.radius + D1 = product1_species.D + D2 = product2_species.D + particle_radius12 = product1_radius + product2_radius + + + # 1.5 Get new positions and structure_ids of particles + # FIXME This should be made more elegant, using as much as possible the + # structure functions from the BD mode. + # If one of the particle is not on the surface of the reactant. + if product1_structure_type_id != product2_structure_type_id: + # Make sure that the reactant was on a surface, not in the bulk + if reactant_structure_type_id != def_sid: + log.warn('Reactant structure is default structure, but products do not end up in bulk. Something seems wrong!') + + # Figure out which product stays in the surface and which one goes to the bulk or parent structure + # Note that A is a particle staying in the surface of origin (of the reactant) and B goes to the "higher" structure + if product2_structure_type_id == def_sid or product2_structure_type_id == parent_sid: + # product2 goes to bulk or parent structure and is now particleB (product1 is particleA and is on the surface) + product1_structure_id = reactant_structure_id # TODO after the displacement the structure can change! + if product2_structure_type_id == def_sid: + product2_structure_id = self.world.get_def_structure_id() # the parent structure + else: + product2_structure_id = reactant_structure.structure_id # the parent structure + productA_radius = product1_radius + productB_radius = product2_radius + DA = D1 + DB = D2 + default = True # we like to think of this as the default + elif product1_structure_type_id == def_sid or product1_structure_type_id == parent_sid: + # product1 goes to 3D and is now particleB (product2 is particleA) + product2_structure_id = reactant_structure_id + if product1_structure_type_id == def_sid: + product1_structure_id = self.world.get_def_structure_id() # the parent structure + else: + product1_structure_id = reactant_structure.structure_id # the parent structure + productA_radius = product2_radius + productB_radius = product1_radius + DA = D2 + DB = D1 + default = False + else: + raise RuntimeError('fire_single_reaction: One product particle must go to the default structure or parent structure of reactant structure.') + + # We have to readjust this again in the end, when creating product positions, etc. + # This is handled by the following function. + def conditional_swap(newposA, newposB): + + if default: + return (newposA, newposB) + else: + return (newposB, newposA) + + # Initialize product positions list + product_pos_list = [] + + # OK. + # Now let's check what actually happens. + if isinstance(reactant_structure, PlanarSurface): + # draw a number of new positions for the two product particles + # TODO make this a generator + + # Make sure that the new positions lie within the region bounded by neighboring orthogonal + # planes to avoid particles leaking out of a box + # NOTE surface lists have format (surface, distance_to_surface_from_reactant_position) + neighbor_surfaces = get_neighbor_structures(self.world, reactant_pos, reactant_structure.id, ignores=[]) + + # Code snippet to get surfaces that might bound the dissociation positions; + # not used at the moment... + # Determine which of the neighbor surfaces set bounds to the dissociation region + #bounding_surfaces = [] + #for surface, surface_distance in neighbor_surfaces: + + #if isinstance(surface, PlanarSurface) and \ + #feq(numpy.dot(surface.shape.unit_z, reactant_structure.shape.unit_z), 0.0) : + + #bounding_surfaces.append( (surface, surface_distance) ) + + # OK, now sample new positions + for _ in range(self.dissociation_retry_moves): + + # determine the side of the membrane the dissociation takes place + if(reactant_structure.shape.is_one_sided): + unit_z = reactant_structure.shape.unit_z + else: + unit_z = reactant_structure.shape.unit_z * myrandom.choice(-1, 1) + + Ns = 0 + positions_legal = False + while not positions_legal: + + # draw the random angle for the 3D particle relative to the particle left in the membrane + # do the backtransform with a random iv with length such that the particles are at contact + # Note we make the iv slightly longer because otherwise the anisotropic transform will produce illegal + # positions + iv = random_vector(particle_radius12 * MixedPair2D3D.calc_z_scaling_factor(DA, DB)) + iv *= MINIMAL_SEPARATION_FACTOR + + # calculate the new positions and structure IDs + newposA, newposB, _, _ = MixedPair2D3D.do_back_transform(reactant_pos, iv, DA, DB, + productA_radius, productB_radius, + reactant_structure, reactant_structure, + unit_z, self.world) + # the second reactant_structure parameter passed is ignored here + # therefore also the structure IDs returned by the classmethod will not be right + + # Test whether the created positions are above the plane + # This is to ensure that 3D particles will stay inside a box made from planes + dist_to_edgeA = reactant_structure.project_point(newposA)[1][1] + dist_to_edgeB = reactant_structure.project_point(newposB)[1][1] + + if dist_to_edgeA < 0.0 and dist_to_edgeB < 0.0 : + # Note that project_point()[1][1] returns the negative length to the closest edge + positions_legal = True + + elif __debug__: + + log.warning('Dissociation positions in fire_single on PlanarSurface with two products out of bounds => resampling') + + Ns = Ns + 1 + if Ns > self.MAX_NUM_DT0_STEPS: # To avoid an infinite loop we need some kind of break condition + # We take the same looping limit as for dt=0 steps because this is a similar situation + raise RuntimeError('fire_single_reaction: too many resampling attempts in reaction on PlanarSurface with two products: Ns = %s > %s' \ + % (Ns, self.MAX_NUM_DT0_STEPS) ) + # TODO This should not necessarily raise RuntimeError but just a warning; + # this is for TESTING only; replace by something better + + product_pos_list.append(conditional_swap(newposA, newposB)) + + elif isinstance(reactant_structure, CylindricalSurface): + + for _ in range(self.dissociation_retry_moves): + iv = random_vector(particle_radius12 * MixedPair1D3D.calc_r_scaling_factor(DA, DB)) + iv *= MINIMAL_SEPARATION_FACTOR + + unit_z = reactant_structure.shape.unit_z + + newposA, newposB, _, _ = MixedPair1D3D.do_back_transform(reactant_pos, iv, DA, DB, + productA_radius, productB_radius, + reactant_structure, reactant_structure, + unit_z, self.world) + # the second reactant_structure parameter passed is ignored here + # unit_z is passed here for completeness but not used in the method + + newposB = self.world.apply_boundary(newposB) + if __debug__: + assert (self.world.distance(reactant_structure.shape, newposB) >= productB_radius) + + product_pos_list.append(conditional_swap(newposA, newposB)) + + elif isinstance(reactant_structure, DiskSurface): + + # productA always must stay on the disk, in the position of the reactant + newposA = reactant_pos + + # Check whether the DiskSurface dissociates the particles in radial direction (default setting) + if reactant_structure.dissociates_radially(): + + # Create the position of the particle that goes into the bulk (productB) + # This is the same as for the single reaction of a completely dissociating disk-bound particle (see above) + vector_length = (productB_radius + max(productA_radius, reactant_structure.shape.radius)) * MINIMAL_SEPARATION_FACTOR + for _ in range(self.dissociation_retry_moves): + unit_vector3D = random_unit_vector() + unit_vector2D = normalize(unit_vector3D - + (reactant_structure.shape.unit_z * numpy.dot(unit_vector3D, reactant_structure.shape.unit_z))) + newposB = reactant_pos + vector_length * unit_vector2D + + product_pos_list.append(conditional_swap(newposA, newposB)) + + else: + # The particle does not dissociate into the bulk but moves back onto the parent cylinder + # and is placed adjacent (touching) next to particle A + vector_length = (productB_radius + productA_radius) * MINIMAL_SEPARATION_FACTOR + for _ in range(self.dissociation_retry_moves): + # Determine a random dissociation direction + unit_vector1D = random_sign() * reactant_structure.shape.unit_z + newposB = reactant_pos + vector_length * unit_vector1D + # TODO We may have to carry out additional checks for free space here first, + # but in principle the code below should handle it correctly as in all other cases + + product_pos_list.append(conditional_swap(newposA, newposB)) + + else: + # cannot decay from 3D to other structure + raise RuntimeError('fire_single_reaction: can not decay from 3D to other structure') + + + # 1.5 Get new positions and structure_ids of particles + # If the two particles stay on the same structure type as the reactant + # (but not necessarily on the same structure!) + else: + # Case product1_structure_type_id == product2_structure_type_id + # Generate new positions in the structure + # TODO Make this into a generator + product_pos_list = [] + for _ in range(self.dissociation_retry_moves): + # FIXME for particles on the cylinder there are only two possibilities + # calculate a random vector in the structure with unit length + iv = _random_vector(reactant_structure, particle_radius12, self.rng) + iv *= MINIMAL_SEPARATION_FACTOR + + unit_z = reactant_structure.shape.unit_z # not used but passed for completeness + + # If reactant_structure is a finite PlanarSurface the new position potentially can + # lie outside of the plane. The two product particles then will end up on different + # planes of the same structure_type. Therefore this case is treated separately. + # We use a specific do_back_transform method here which automatically takes care + # of the potential structure change and potentially resulting overlaps. + if isinstance(reactant_structure, PlanarSurface): + + # Recalculate the back transform taking into account the deflection + newpos1, newpos2, sid1, sid2 = PlanarSurfaceTransitionPair.do_back_transform(reactant_pos, iv, + D1, D2, + product1_radius, product2_radius, + reactant_structure, reactant_structure, + unit_z, self.world) + else: + + # Calculate the standard back transform + newpos1, newpos2, sid1, sid2 = SimplePair.do_back_transform(reactant_pos, iv, D1, D2, + product1_radius, product2_radius, + reactant_structure, reactant_structure, + unit_z, self.world) + # for the SimplePair do_back_transform() requires that the two structures passed are the same + # because it will check for this! + + # Pass on the newly generated information + product_pos_list.append((newpos1, newpos2)) + product1_structure_id = sid1 + product2_structure_id = sid2 + + + # 2. make space for the products. + # calculate the sphere around the two product particles + product1_pos = self.world.apply_boundary(product_pos_list[0][0]) + product2_pos = self.world.apply_boundary(product_pos_list[0][1]) + radius = max(self.world.distance(product1_pos, reactant_pos) + product1_radius, + self.world.distance(product2_pos, reactant_pos) + product2_radius) + zero_singles, ignore = self.burst_volume(reactant_pos, radius*SINGLE_SHELL_FACTOR, ignore) + + # 3. check that there is space for the products (try different positions if possible) + # accept the new positions if there is enough space. + for newpos1, newpos2 in product_pos_list: + + # First, for each neighboring plane, we check whether the new positions lie + # on the same side of the plane as the reactant; if not, this trial will be rejected + product_crossed_plane = False # by default everything is OK + neighbor_surfaces = get_neighbor_structures(self.world, reactant_pos, reactant_structure.id, ignores=[]) + # NOTE surface lists have format (surface, distance_to_surface_from_reactant_position) + for surface, surf_dist in neighbor_surfaces: + + if isinstance(surface, PlanarSurface): + + # Products and reactant are on the same side if the dot product of the position minus + # a point on the plane with the normal vector of the plane has the same sign + unit_z = surface.shape.unit_z + center = surface.shape.position + newpos1_crossed = numpy.dot(reactant_pos - center, unit_z) * numpy.dot(newpos1 - center, unit_z) + newpos2_crossed = numpy.dot(reactant_pos - center, unit_z) * numpy.dot(newpos2 - center, unit_z) + + if newpos1_crossed < 0.0 or newpos2_crossed < 0.0: + + product_crossed_plane = True + + if __debug__: + log.info('single reaction: product crosses plane, reaction will be rejected.') + + newpos1 = self.world.apply_boundary(newpos1) + newpos2 = self.world.apply_boundary(newpos2) - return single1, single2 + if __debug__: + assert (self.world.distance(newpos1, newpos2) >= particle_radius12) + + # check that there is space for the products + # Note that we do not check overlap with surfaces -> TODO + if not self.world.check_overlap((newpos1, product1_radius), reactant[0]) and \ + not self.world.check_overlap((newpos2, product2_radius), reactant[0]) and \ + not product_crossed_plane: + + # 4. process the changes (remove particle, make new ones) + self.world.remove_particle(reactant[0]) + product1_pair = self.world.new_particle(product1_species.id, product1_structure_id, newpos1) + product2_pair = self.world.new_particle(product2_species.id, product2_structure_id, newpos2) + products = [product1_pair, product2_pair] + + # 5. update counters + self.reaction_events += 1 + self.last_reaction = (rr, (reactant, None), products) + + # 6. Log the change + if __debug__: + log.info('single reaction: products (%s) = %s' % (len(products), products)) - def propagate_pair(self, pair, dt, event_type): - single1 = pair.single1 - single2 = pair.single2 + # exit the loop, we have found new positions + break + else: + # Log the lack of change + if __debug__: + log.info('single reaction: placing products failed.') - particle1 = single1.pid_particle_pair - particle2 = single2.pid_particle_pair + # 4. process the changes (move the particle and update structure) + moved_reactant = self.move_particle(reactant, reactant_pos, reactant_structure_id) + products = [moved_reactant] - pos1 = particle1[1].position - pos2 = particle2[1].position + # 5. update counters. + self.rejected_moves += 1 - if dt > 0.0: - D1 = particle1[1].D - D2 = particle2[1].D + else: + raise RuntimeError('fire_single_reaction: num products >= 3 not supported.') - pos2t = self.world.cyclic_transpose(pos2, pos1) - old_inter_particle = pos2t - pos1 - r0 = self.world.distance(pos1, pos2) - assert feq(r0, length(old_inter_particle)) + return products, zero_singles, ignore - old_com = pair.com - newpos1, newpos2 = pair.draw_new_positions(dt, r0, - old_inter_particle, - event_type) + def fire_interaction(self, single, reactant_pos, reactant_structure_id, ignore): + # This takes care of the identity change when a particle associates with a surface. + # It performs the reactions: + # A(structure) + surface -> 0 + # A(structure) + surface -> B(surface) - newpos1 = self.world.apply_boundary(newpos1) - newpos2 = self.world.apply_boundary(newpos2) - assert not self.world.check_overlap((newpos1, particle1[1].radius), - particle1[0], particle2[0]) - assert not self.world.check_overlap((newpos2, particle2[1].radius), - particle1[0], particle2[0]) - assert self.check_pair_pos(pair, newpos1, newpos2, old_com, - pair.get_shell_size()) - else: - newpos1 = particle1[1].position - newpos2 = particle2[1].position + # Note that the reactant_structure_id is the id of the structure on which the particle was located at the time of the reaction. if __debug__: - log.debug("fire_pair: #1 { %s: %s => %s }" % - (single1, pos1, newpos1)) - log.debug("fire_pair: #2 { %s: %s => %s }" % - (single2, str(pos2), str(newpos2))) + assert isinstance(single, InteractionSingle) - single1.initialize(self.t) - single2.initialize(self.t) - - self.remove_domain(pair) - assert single1.domain_id not in self.domains - assert single2.domain_id not in self.domains - self.domains[single1.domain_id] = single1 - self.domains[single2.domain_id] = single2 - self.move_single(single1, newpos1, particle1[1].radius) - self.move_single(single2, newpos2, particle2[1].radius) + # 0. get reactant info + reactant = single.pid_particle_pair + reactant_radius = reactant[1].radius + rr = single.interactionrule - if __debug__: - container = self.get_container(single1.shell) - assert container[single1.shell_id].shape.radius == \ - single1.shell.shape.radius - assert container[single2.shell_id].shape.radius == \ - single2.shell.shape.radius + # The zero_singles are the NonInteractionSingles that are the total result of the recursive + # bursting process. + zero_singles = [] - if type(single1.shell) is CylindricalShell: - assert container[single1.shell_id].shape.half_length == \ - single1.shell.shape.half_length - assert container[single2.shell_id].shape.half_length == \ - single2.shell.shape.half_length + if len(rr.products) == 0: + + # 1. No products, no info + # 1.5 No new position/structure_id required + # 2. No space required + # 3. No space required + # 4. process the changes (remove particle, make new ones) + self.world.remove_particle(reactant[0]) + products = [] + # zero_singles/ignore is unchanged. + + # 5. Update counters + self.reaction_events += 1 + self.last_reaction = (rr, (reactant, None), products) - assert single1.shell.shape.radius == particle1[1].radius - assert single2.shell.shape.radius == particle2[1].radius + # 6. Log the change + if __debug__: + log.info('interaction: product = None.') + + elif len(rr.products) == 1: + + # 1. get product info + product_species = self.world.get_species(rr.products[0]) + product_radius = product_species.radius + # In some special domains that are formally an interaction the product actually stays + # behind on the origin structure (e.g. in the PlanarSurfaceDiskSurfaceInteraction, + # which is a plane particle interacting with a cylinder touching the plane). + # Therefore we have to make a distinction here. TODO FIXME this is not very elegant, and probably DEPRECATED by now + if single.product_structure: + product_structure = single.product_structure # treating some special cases + target_structure = single.target_structure + else: + product_structure = single.target_structure # the default + target_structure = single.target_structure + + # TODO make sure that the product species lives on the same type of the interaction surface + #product_structure_type = self.world.get_structure(product_species.structure_id) + #assert (single.target_structure.sid == self.world.get_structure(product_species.structure_id)), \ + #'Product particle should live on the surface of interaction after the reaction.' + + # 1.5 get new position and structure_id of particle + transposed_pos = self.world.cyclic_transpose(reactant_pos, product_structure.shape.position) + product_pos, _ = target_structure.project_point(transposed_pos) # we always project on the target structure, even if it is not the product structure + product_pos = self.world.apply_boundary(product_pos) # not sure this is still necessary + product_structure_id = product_structure.id + + # 2. burst the volume that will contain the products. + # Note that an interaction domain is already sized such that it includes the + # reactant particle moving into the surface. + # Note that the burst is recursive!! + if product_radius > reactant_radius: + zero_singles, ignore = self.burst_volume(product_pos, product_radius*SINGLE_SHELL_FACTOR, ignore) + + # 3. check that there is space for the products + # Note that we do not check for interfering surfaces (we assume this is no problem) + if self.world.check_overlap((product_pos, product_radius), reactant[0]): + + # Log the (absence of) change + if __debug__: + log.info('interaction: no space for product particle.') - assert self.check_obj(single1) - assert self.check_obj(single2) + # 4. process the changes (only move particle, stay on same structure) + moved_reactant = self.move_particle(reactant, reactant_pos, reactant_structure_id) + products = [moved_reactant] - return single1, single2 + # 5. update counters + self.rejected_moves += 1 - def form_pair_or_multi(self, single, neighbors): - assert neighbors + else: + # 4. process the changes (remove particle, make new ones) + self.world.remove_particle(reactant[0]) + newparticle = self.world.new_particle(product_species.id, product_structure_id, product_pos) + products = [newparticle] - singlepos = single.shell.shape.position + # 5. update counters + self.reaction_events += 1 + self.last_reaction = (rr, (reactant, None), products) - # sort burst neighbors by distance - dists = self.obj_distance_array(singlepos, neighbors) - if len(dists) >= 2: - n = dists.argsort() - dists = dists.take(n) - neighbors = numpy.take(neighbors, n) + # 6. Log the change + if __debug__: + log.info('interaction: product (%s) = %s' % (len(products), products)) - # First, try forming a Pair. - if isinstance(neighbors[0], Single): - obj = self.form_pair(single, singlepos, - neighbors[0], neighbors[1:]) - if obj: - return obj + else: + raise RuntimeError('fire_interaction: num products > 1 not supported.') - # If a Pair is not formed, then try forming a Multi. - obj = self.form_multi(single, neighbors, dists) - if obj: - return obj + return products, zero_singles, ignore - def form_pair(self, single1, pos1, single2, burst): + def fire_pair_reaction(self, pair, reactant1_pos, reactant2_pos, reactant1_structure_id, reactant2_structure_id, ignore): + # This takes care of the identity change when two particles react with each other + # It performs the reactions: + # A(any structure) + B(same structure) -> 0 + # A(any structure) + B(same structure or 3D) -> C(same structure) if __debug__: - log.debug('trying to form Pair(%s, %s)' % - (single1.pid_particle_pair, single2.pid_particle_pair)) + assert isinstance(pair, Pair) - assert single1.is_reset() - assert single2.is_reset() + # Note that the reactant_structure_ids are the ids of the structures on which the particles were located at the time of the reaction. - # 1. Determine min shell size. - radius1 = single1.pid_particle_pair[1].radius - radius2 = single2.pid_particle_pair[1].radius + # 0. get reactant info + pid_particle_pair1 = pair.pid_particle_pair1 + pid_particle_pair2 = pair.pid_particle_pair2 + rr = pair.draw_reaction_rule(pair.interparticle_rrs) - sigma = radius1 + radius2 + # The zero_singles are the NonInteractionSingles that are the Total result of the recursive + # bursting process. + zero_singles = [] - D1 = single1.pid_particle_pair[1].D - D2 = single2.pid_particle_pair[1].D - D12 = D1 + D2 - - assert (pos1 - single1.shell.shape.position).sum() == 0 - pos2 = single2.shell.shape.position - r0 = self.world.distance(pos1, pos2) - distance_from_sigma = r0 - sigma - assert distance_from_sigma >= 0, \ - 'distance_from_sigma (pair gap) between %s and %s = %s < 0' % \ - (single1, single2, FORMAT_DOUBLE % distance_from_sigma) - - shell_size1 = r0 * D1 / D12 + radius1 - shell_size2 = r0 * D2 / D12 + radius2 - shell_size_margin1 = radius1 * 2 - shell_size_margin2 = radius2 * 2 - shell_size_with_margin1 = shell_size1 + shell_size_margin1 - shell_size_with_margin2 = shell_size2 + shell_size_margin2 - if shell_size_with_margin1 >= shell_size_with_margin2: - min_shell_size = shell_size1 - shell_size_margin = shell_size_margin1 - else: - min_shell_size = shell_size2 - shell_size_margin = shell_size_margin2 - - # 2. Check if min shell size not larger than max shell size or - # sim cell size. - com = self.world.calculate_pair_CoM(pos1, pos2, D1, D2) - com = self.world.apply_boundary(com) - min_shell_size_with_margin = min_shell_size + shell_size_margin - max_shell_size = min(self.get_max_shell_size(), - distance_from_sigma * 100 + - sigma + shell_size_margin) - - if min_shell_size_with_margin >= max_shell_size: - if __debug__: - log.debug('%s not formed: min_shell_size %s >=' - 'max_shell_size %s' % - ('Pair(%s, %s)' % (single1.pid_particle_pair[0], - single2.pid_particle_pair[0]), - FORMAT_DOUBLE % min_shell_size_with_margin, - FORMAT_DOUBLE % max_shell_size)) - return None + if len(rr.products) == 0: + + # 1. no product particles, no info + # 1.5 No new position/structure_id required + # 2. No space required + # 3. No space required + # 4. process the change (remove particles, make new ones) + self.world.remove_particle(pid_particle_pair1[0]) + self.world.remove_particle(pid_particle_pair2[0]) + products = [] + # zero_singles/ignore is unchanged. - # 3. Check if bursted Singles not too close. - # The simple check for closest below could miss - # some of them, because sizes of these Singles for this - # distance check has to include SINGLE_SHELL_FACTOR, while - # these burst objects have zero mobility radii. This is not - # beautiful, a cleaner framework may be possible. - - closest, closest_shell_distance = None, numpy.inf - for b in burst: - if isinstance(b, Single): - bpos = b.shell.shape.position - d = self.world.distance(com, bpos) - \ - b.pid_particle_pair[1].radius * self.SINGLE_SHELL_FACTOR - if d < closest_shell_distance: - closest, closest_shell_distance = b, d - - if closest_shell_distance <= min_shell_size_with_margin: + # 5. update counters + self.reaction_events += 1 + self.last_reaction = (rr, (pid_particle_pair1, pid_particle_pair2), products) + + # 6. Log the change if __debug__: - log.debug('%s not formed: squeezed by burst neighbor %s' % - ('Pair(%s, %s)' % (single1.pid_particle_pair[0], - single2.pid_particle_pair[0]), - closest)) - return None + log.info('pair reaction: product = None.') - assert closest_shell_distance > 0 + elif len(rr.products) == 1: - # 4. Determine shell size and check if closest object not too - # close (squeezing). - c, d = self.get_closest_obj(com, ignore=[single1.domain_id, - single2.domain_id], - ignores=[single1.surface.id]) - if d < closest_shell_distance: - closest, closest_shell_distance = c, d + # 1. get product info + product_species = self.world.get_species(rr.products[0]) + product_radius = product_species.radius - if __debug__: - log.debug('Pair closest neighbor: %s %s, ' - 'min_shell_with_margin %s' % - (closest, FORMAT_DOUBLE % closest_shell_distance, - FORMAT_DOUBLE % min_shell_size_with_margin)) - assert closest_shell_distance > 0 + # 1.5 get new position and structure_id for product particle + product_pos = pair.draw_new_com(pair.dt, pair.event_type) + product_pos = self.world.apply_boundary(product_pos) + + # select the structure_id for the product particle to live on. + # TODO doesn't work for all needed cases + product_structure_ids = self.world.get_structure_ids(self.world.get_structure_type(product_species.structure_type_id)) + if reactant1_structure_id in product_structure_ids: + product_structure_id = reactant1_structure_id + elif reactant2_structure_id in product_structure_ids: + product_structure_id = reactant2_structure_id - if isinstance(closest, Single): + # 2. make space for products + # 2.1 if the product particle sticks out of the shell - D_closest = closest.pid_particle_pair[1].D - D_tot = D_closest + D12 - closest_particle_distance = self.world.distance( - com, closest.pid_particle_pair[1].position) + #if self.world.distance(old_com, product_pos) > (pair.get_shell_size() - product_radius): # TODO What is that? + zero_singles, ignore = self.burst_volume(product_pos, product_radius*SINGLE_SHELL_FACTOR, ignore) - closest_min_radius = closest.pid_particle_pair[1].radius - closest_min_shell = closest_min_radius * self.SINGLE_SHELL_FACTOR + # 3. check that there is space for the products ignoring the reactants + # Note that we do not check for interfering surfaces (we assume this is no problem) + if self.world.check_overlap((product_pos, product_radius), pid_particle_pair1[0], + pid_particle_pair2[0]): + # Log the (lack of) change + if __debug__: + log.info('pair reaction: no space for product particle.') - # options for shell size: - # a. ideal shell size - # b. closest shell is from a bursted single - # c. closest shell is closer than ideal shell size - shell_size = min((D12 / D_tot) * - (closest_particle_distance - min_shell_size - - closest_min_radius) + min_shell_size, - closest_particle_distance - closest_min_shell, - closest_shell_distance) + # 4. process the changes (move particles, change structure) + moved_reactant1 = self.move_particle(pid_particle_pair1, reactant1_pos, reactant1_structure_id) + moved_reactant2 = self.move_particle(pid_particle_pair2, reactant2_pos, reactant2_structure_id) + products = [moved_reactant1, moved_reactant2] - shell_size /= SAFETY - assert shell_size < closest_shell_distance + # 5. update counters + self.rejected_moves += 1 + + else: + # 4. process the changes (remove particle, make new ones) + self.world.remove_particle(pid_particle_pair1[0]) + self.world.remove_particle(pid_particle_pair2[0]) + newparticle = self.world.new_particle(product_species.id, product_structure_id, product_pos) + products = [newparticle] + + # 5. update counters + self.reaction_events += 1 + self.last_reaction = (rr, (pid_particle_pair1, pid_particle_pair2), + products) + + # 6. Log the change + if __debug__: + log.info('pair reaction: product (%s) = %s' % (len(products), products)) else: - assert isinstance(closest, (Pair, Multi, None.__class__)) + raise NotImplementedError('fire_pair_reaction: num products >= 2 not supported.') + + return products, zero_singles, ignore - shell_size = closest_shell_distance / SAFETY - if shell_size <= min_shell_size_with_margin: + def fire_move(self, single, reactant_pos, reactant_structure_id, ignore_p=None): + # No reactions/Interactions have taken place -> no identity change of the particle + # Just move the particles and process putative structure change + + # Note that the reactant_structure_id is the id of the structure on which the particle was located at the time of the move. + + if __debug__: + assert isinstance(single, Single) + + # 0. get reactant info + reactant = single.pid_particle_pair + # 1. No product->no info + # 1.5 No additional positional/structure change is needed + # 2. Position is in protective shell + + # 3. check that there is space for the reactants + if ignore_p: + co = self.world.check_overlap((reactant_pos, reactant[1].radius), + reactant[0], ignore_p[0]) + if co: + for c in co: + log.debug('fire_move: detected overlap with %s' % str(c)) + + if self.REMOVE_OVERLAPS: + reactant_pos = self.remove_overlap(reactant, reactant_pos, ignore_p) + # still TESTING + else: + raise RuntimeError('fire_move: particle overlap failed.') + else: + co = self.world.check_overlap((reactant_pos, reactant[1].radius), + reactant[0]) + if co: + for c in co: + log.debug('fire_move: detected overlap with %s' % str(c)) + + if self.REMOVE_OVERLAPS: + reactant_pos = self.remove_overlap(reactant, reactant_pos) + # still TESTING + else: + raise RuntimeError('fire_move: particle overlap failed.') + + # 4. process the changes (move particles, change structure) + moved_reactant = self.move_particle(reactant, reactant_pos, reactant_structure_id) + # 5. No counting + # 6. No Logging + return [moved_reactant] + + + ######################################### + #### METHODS FOR NEW DOMAIN CREATION #### + ######################################### + def make_new_domain(self, single): + ### Make a new domain out of a NonInteractionSingle that was + ### just fired + + # can only make new domain from NonInteractionSingle + assert isinstance(single, NonInteractionSingle) + assert single.is_reset() + + # Special case: When D=0, nothing needs to change and only new event + # time is drawn + # TODO find nicer construction than just this if statement + #if single.getD() == 0: + #single.dt, single.event_type = single.determine_next_event() + #single.last_time = self.t + #self.add_domain_event(single) + #return single + + + # 0. Get generic info + single_pos = single.pid_particle_pair[1].position + single_radius = single.pid_particle_pair[1].radius + reaction_threshold = single_radius * SINGLE_SHELL_FACTOR + + # 1.0 Get neighboring domains and surfaces + neighbor_distances = self.geometrycontainer.get_neighbor_domains(single_pos, self.domains, ignore=[single.domain_id]) + # Get also surfaces + # Some singles have an extended ignore list, which we have to figure out first + ignores = [] # by default + if isinstance(single, DiskSurfaceSingle): + ignores = single.ignored_structure_ids if __debug__: - log.debug('%s not formed: squeezed by %s' % - ('Pair(%s, %s)' % (single1.pid_particle_pair[0], - single2.pid_particle_pair[0]), - closest)) - return None + log.info('Extended structure ignore list when making %s, ignored IDs = %s' % (single, ignores)) + + surface_distances = get_neighbor_structures(self.world, single_pos, single.structure.id, ignores) + #log.info('surface_distances = %s' % str(surface_distances)) + + # 2.3 We prefer to make NonInteractionSingles for efficiency. + # But if objects (Shells and surfaces) get close enough (closer than + # reaction_threshold) we want to try Interaction and/or Pairs. + + # From the list of neighbors, we calculate the thresholds (horizons) for trying to form + # the various domains. The domains can be: + # -zero-shell NonInteractionSingle -> Pair or Multi + # -Surface -> Interaction + # -Multi -> Multi + # + # Note that we do not differentiate between directions. This means that we + # look around in a sphere, the horizons are spherical. + pair_interaction_partners = [] + for domain, _ in neighbor_distances: + if (isinstance (domain, NonInteractionSingle) and domain.is_reset()): + # distance from the center of the particles/domains + pair_distance = self.world.distance(single_pos, domain.shell.shape.position) + pair_horizon = (single_radius + domain.pid_particle_pair[1].radius) * SINGLE_SHELL_FACTOR + pair_interaction_partners.append((domain, pair_distance - pair_horizon)) + + for surface, surface_distance in surface_distances: + if isinstance(surface, PlanarSurface) or isinstance(surface, DiskSurface): + # with a planar surface it is the center of mass that 'looks around' + surface_horizon = single_radius * (SINGLE_SHELL_FACTOR - 1.0) + else: + # with a cylindrical surface it is the surface of the particle + surface_horizon = single_radius * SINGLE_SHELL_FACTOR + + pair_interaction_partners.append((surface, surface_distance - surface_horizon)) + + pair_interaction_partners = sorted(pair_interaction_partners, key=lambda domain_overlap: domain_overlap[1]) + + # For each particles/particle or particle/surface pair, check if a pair or interaction can be made + # Note that we check the closest one first and only check if the object are within the horizon + domain = None + for obj, hor_overlap in pair_interaction_partners: + + if hor_overlap > 0.0 or domain or self.BD_ONLY_FLAG : + # there are no more potential partners (everything is out of range) + # or a domain was formed successfully previously + # or the user wants to force the system into BD mode = Multi creation + break + + if isinstance(obj, NonInteractionSingle): + # try making a Pair (can be Mixed Pair or Normal Pair) + domain = self.try_pair (single, obj) + + elif isinstance(obj, PlanarSurface) and isinstance(single.structure, PlanarSurface): + # currently we only support transitions from plane to plane and from cylinder to plane + domain = self.try_transition(single, obj) + + elif ( isinstance(obj, CylindricalSurface) or isinstance(obj, PlanarSurface) or isinstance(obj, DiskSurface) ): + # try making an Interaction + domain = self.try_interaction (single, obj) + + + if not domain: + # No Pair or Interaction domain was formed + # Now choose between NonInteractionSingle and Multi + + # make a new list of potential partners + # Is now zero-dt NonInteractionSingles and Multi's + # TODO: combine with first selection such that we have to do this loop only once + multi_partners = [] + for domain, dist_to_shell in neighbor_distances: + + if (isinstance (domain, NonInteractionSingle) and domain.is_reset()): + multi_horizon = (single_radius + domain.pid_particle_pair[1].radius) * MULTI_SHELL_FACTOR + distance = self.world.distance(single_pos, domain.shell.shape.position) + multi_partners.append((domain, distance - multi_horizon)) + + elif isinstance(domain, Multi): + # The dist_to_shell = dist_to_particle - multi_horizon_of_target_particle + # So only the horizon and distance of the current single needs to be taken into account + # Note: this is built on the assumption that the shell of a Multi has the size of the horizon. + multi_horizon = (single_radius * MULTI_SHELL_FACTOR) + multi_partners.append((domain, dist_to_shell - multi_horizon)) + + # Also add surfaces + for surface, distance in surface_distances: + if isinstance(surface, PlanarSurface) and single.structure.id == self.world.get_def_structure_id(): + # With a planar surface it is the center of mass that 'looks around', + # but only for the classical situation of a 3D/bulk particle interacting with a plane. + # In all other situations the horizon for surface collisions should be measured from + # from the particle radius, otherwise we get situations in which a single cannot be + # constructed any more, yet a close plane will not be identified as a Multi partner. + surface_horizon = single_radius * (MULTI_SHELL_FACTOR - 1.0) + else: + # With a cylindrical surface it is the surface of the particle + surface_horizon = single_radius * MULTI_SHELL_FACTOR + + multi_partners.append((surface, distance - surface_horizon)) + + + # get the closest object (if there) + if multi_partners: + multi_partners = sorted(multi_partners, key=lambda domain_overlap: domain_overlap[1]) + #log.debug('multi_partners: %s' % str(multi_partners)) + closest_overlap = multi_partners[0][1] + else: + # In case there is really nothing + closest_overlap = numpy.inf + if __debug__: + log.debug('Single or Multi: closest_overlap = %s' % (FORMAT_DOUBLE % closest_overlap)) + if closest_overlap < numpy.inf: + log.debug('Overlap partner = %s' % str(multi_partners[0][0])) + + # If the closest partner is within the multi horizon we do Multi, otherwise Single + # Here we also check whether the uses force-deactivated the construction of this particular + # Single type, or wants to run the simulator in BD mode only anyhow + if closest_overlap > 0.0 and not self.BD_ONLY_FLAG: + try: + if allowed_to_make[single.testShell.__class__]: + # Just make a normal NonInteractionSingle + self.update_single(single) + bin_domain = single + else: + raise testShellError('Creation of domain type deactivated.') + + except Exception as e: + # If in rare cases the single update fails, make at least a Multi and warn + # Note that this case is supposed to occur extremely rarely, and that it means sth. is not quite working well. + log.warn('Single update failed, exception: %s / %s' % (str(e), str(sys.exc_info()) ) ) + log.warn('Single update: creating a Multi domain instead. Single = %s, multi_partners = %s' % (single, multi_partners)) + domain = self.form_multi(single, multi_partners) + bin_domain = domain + else: + # An object was closer than the Multi horizon + # Form a multi with everything that is in the multi_horizon + domain = self.form_multi(single, multi_partners) + bin_domain = domain - # 5. Check if singles would not be better. + else: + + # A non-Single / non-Multi has been successfully created + bin_domain = domain + + # Update statistics on domain creation + if self.CREATION_HISTOGRAMS: + self.DomainCreationHists.bin_domain(bin_domain) + + self.single_steps['MAKE_NEW_DOMAIN'] += 1 + + return domain + + + #### Redundant but maybe useful for parts + def calculate_simplepair_shell_size(self, single1, single2): + # 3. Calculate the maximum based on some other criterium (convergence?) + radius1 = single1.pid_particle_pair[1].radius + radius2 = single2.pid_particle_pair[1].radius + sigma = radius1 + radius2 + distance_from_sigma = r0 - sigma # the distance between the surfaces of the particles + #convergence_max = distance_from_sigma * 100 + sigma + shell_size_margin # FIXME + #max_shell_size = min(max_shell_size, convergence_max) + + #5. Calculate the 'ideal' shell size, it matches the expected first-passage time of the closest particle + #with the expected time of the particles in the pair. + #D1 = single1.pid_particle_pair[1].D + #D2 = single2.pid_particle_pair[1].D + #D12 = D1 + D2 + + #D_closest = closest.pid_particle_pair[1].D + #D_tot = D_closest + D12 + #ideal_shell_size = min((D12 / D_tot) * + #(closest_particle_distance - min_shell_size - closest_min_radius) + + #min_shell_size, + ideal_shell_size = numpy.inf + shell_size = min(max_shell_size, ideal_shell_size) + + + # 6. Check if singles would not be better. + # TODO clear this up -> strange rule + pos1 = single1.pid_particle_pair[1].position + pos2 = single2.pid_particle_pair[1].position d1 = self.world.distance(com, pos1) d2 = self.world.distance(com, pos2) if shell_size < max(d1 + single1.pid_particle_pair[1].radius * - self.SINGLE_SHELL_FACTOR, \ + SINGLE_SHELL_FACTOR, \ d2 + single2.pid_particle_pair[1].radius * \ - self.SINGLE_SHELL_FACTOR) * 1.3: + SINGLE_SHELL_FACTOR) * 1.3: if __debug__: log.debug('%s not formed: singles are better' % 'Pair(%s, %s)' % (single1.pid_particle_pair[0], single2.pid_particle_pair[0])) - return None + return None, None, None - # 6. Ok, Pair makes sense. Create one. - shell_size = min(shell_size, max_shell_size) - pair = self.create_pair(single1, single2, com, r0, shell_size) + ################################################# + #### METHODS FOR EVENT CONSEQUENCES HANDLING #### + ################################################# + def update_single(self, single): + # updates a NonInteractionSingle given that the single already holds its new position - pair.dt, pair.event_type, pair.reactingsingle = \ - pair.determine_next_event(r0) + # The method only works for NonInteractionSingles + assert isinstance(single, NonInteractionSingle) - assert pair.dt >= 0 + single_pos = single.pid_particle_pair[1].position # TODO get the position as an argument - self.last_time = self.t + # Create a new updated shell + # If this does not work for some reason an exception is risen; + # then make_new_domain() will make a Multi as a default fallback + new_shell = single.create_updated_shell(single_pos) + assert new_shell, 'Method single.create_updated_shell() returned None.' - self.remove_domain(single1) - self.remove_domain(single2) + # Replace shell in domain and geometrycontainer. + # Note: this should be done before determine_next_event. + # Reuse shell_id. + shell_id_shell_pair = (single.shell_id, new_shell) + single.shell_id_shell_pair = shell_id_shell_pair + self.geometrycontainer.move_shell(shell_id_shell_pair) + if __debug__: + log.info(' * Updated single: single: %s, new_shell = %s' % \ + (single, str(new_shell))) - # single1 will be removed by the scheduler. - self.remove_event(single2) + single.dt, single.event_type = single.determine_next_event() + assert single.dt >= 0 + if __debug__: + log.info('dt=%s' % (FORMAT_DOUBLE % single.dt)) - assert closest_shell_distance == numpy.inf or \ - shell_size < closest_shell_distance - assert shell_size >= min_shell_size_with_margin - assert shell_size <= max_shell_size + single.last_time = self.t + # Check whether everything is OK if __debug__: - log.info('%s,\ndt=%s, r0=%s, shell_size=%s, ' - 'closest_shell_distance=%s,\nclosest = %s' % - (pair, FORMAT_DOUBLE % pair.dt, FORMAT_DOUBLE % r0, - FORMAT_DOUBLE % shell_size, - FORMAT_DOUBLE % closest_shell_distance, closest)) + assert self.check_domain(single) - assert self.check_obj(pair) - self.add_pair_event(pair) + # Add to scheduler + # Make sure this is really at the end of the process, i.e. that we only + # get here if no exceptions have been risen. If exceptions will be + # produced after adding the (erroneous) single to the scheduler we will + # have a zombie domain in it and break the whole scheduler queue! + self.add_domain_event(single) - return pair - + return single - def form_multi(self, single, neighbors, dists): - min_shell = single.pid_particle_pair[1].radius * \ - (1.0 + self.MULTI_SHELL_FACTOR) - # Multis shells need to be contiguous. - if dists[0] > min_shell: - return None + def process_single_event(self, single, ignore): + # This method handles the things that need to be done when the current event was + # produced by a single. The single can be a NonInteractionSingle or an InteractionSingle. + # Note that this method is also called when a single is bursted, in that case the event + # is a BURST event. + # + # Note that 'ignore' should already contain the id of 'single'. + # + # This method returns: + # + # - the 'domains' that were created as a result of the processing. This can be a newly made domains + # (since make_new_domain is also called from here) or zero_singles + # Note that these could be many since the event can induce recursive bursting. + # - the updated ignore list. This contains the id of the 'single' domain, IDs of domains + # bursted in the process and the IDs of zero-dt singles. - neighbors = [neighbors[i] for i in (dists <= min_shell).nonzero()[0]] + if __debug__: + ## TODO assert that there is no event associated with this domain in the scheduler + #assert self.check_domain(single) + assert (single.domain_id in ignore), \ + 'Domain_id should already be on ignore list before processing event.' + + ### SPECIAL CASE 1: + # If just need to make new domain. + if single.is_reset(): + if __debug__: + log.info('FIRE SINGLE: make_new_domain()') + log.info('single = %s' % single) + domains = [self.make_new_domain(single)] + # domain is already scheduled in make_new_domain + + ### SPECIAL CASE 2: + # In case nothing is scheduled to happen: do nothing and just reschedule + elif single.dt == numpy.inf: + if __debug__: + log.info('FIRE SINGLE: Nothing happens-> single.dt=inf') + log.info('single = %s' % single) + self.add_domain_event(single) + domains = [single] - closest = neighbors[0] + ### NORMAL: + # Process 'normal' event produced by the single + else: + if __debug__: + log.info('FIRE SINGLE: %s' % single.event_type) + log.info('single = %s' % single) - # if the closest to this Single is a Single, create a new Multi - if isinstance(closest, Single): + ### 1. check that everything is ok + if __debug__: + if single.event_type != EventType.BURST: + # The burst of the domain may be caused by an overlapping domain + assert self.check_domain(single) + + # check that the event time of the single (last_time + dt) is equal to the + # simulator time + assert (abs(single.last_time + single.dt - self.t) <= TIME_TOLERANCE * self.t), \ + 'Timeline incorrect. single.last_time = %s, single.dt = %s, self.t = %s' % \ + (FORMAT_DOUBLE % single.last_time, FORMAT_DOUBLE % single.dt, FORMAT_DOUBLE % self.t) + + self.nonmulti_time += single.dt + + + ### 2. get some necessary information + pid_particle_pair = single.pid_particle_pair + # in case of an interaction domain: determine real event before doing anything + if single.event_type == EventType.IV_EVENT: + single.event_type = single.draw_iv_event_type() + + # get the (new) position and structure on which the particle is located. + if single.getD() != 0 and single.dt > 0.0: + # If the particle had the possibility to diffuse + newpos, struct_id = single.draw_new_position(single.dt, single.event_type) + newpos, struct_id = self.world.apply_boundary((newpos,struct_id)) + else: + # no change in position has taken place + newpos = pid_particle_pair[1].position + struct_id = pid_particle_pair[1].structure_id + # TODO? Check if the new positions are within domain + + # newpos now hold the new position of the particle (not yet committed to the world) + # if we would here move the particles and make new shells, then it would be similar to a propagate - multi = self.create_multi() - self.add_to_multi(single, multi) self.remove_domain(single) - for neighbor in neighbors: - self.add_to_multi_recursive(neighbor, multi) - multi.initialize(self.t) + # If the single had a decay reaction or interaction. + if single.event_type == EventType.SINGLE_REACTION or \ + single.event_type == EventType.IV_INTERACTION: + if __debug__: + log.info('%s' % single.event_type) + log.info('reactant = %s' % single) + + if single.event_type == EventType.SINGLE_REACTION: + self.single_steps[single.event_type] += 1 + particles, zero_singles_b, ignore = self.fire_single_reaction(single, newpos, struct_id, ignore) + # The 'zero_singles_b' list now contains the resulting singles of a putative burst + # that occured in fire_single_reaction + + else: + self.interaction_steps[single.event_type] += 1 + particles, zero_singles_b, ignore = self.fire_interaction(single, newpos, struct_id, ignore) + + else: + particles = self.fire_move(single, newpos, struct_id) + zero_singles_b = [] # no bursting takes place, ignore list remains unchanged + # Update statistics + if single.event_type == EventType.SINGLE_ESCAPE or\ + single.event_type == EventType.IV_ESCAPE or\ + single.event_type == EventType.BURST: + if isinstance(single, InteractionSingle): + self.interaction_steps[single.event_type] += 1 + else: + self.single_steps[single.event_type] += 1 + elif __debug__: + log.warning('Omitting to count a single event with unforeseen event type (%s).' % single.event_type) + + + domains = zero_singles_b + + ### 5. Make a new domain (or reuse the old one) for each particle(s) + # (Re)schedule the (new) domain + if __debug__ and particles == []: + log.info('Empty particles list, no new domains will be made.') + + zero_singles = [] + for pid_particle_pair in particles: +# single.pid_particle_pair = pid_particle_pair # TODO reuse single + zero_single = self.create_single(pid_particle_pair) # TODO re-use NonInteractionSingle domain if possible + self.add_domain_event(zero_single) # TODO re-use event if possible + zero_singles.append(zero_single) # Add the newly made zero-dt singles to the list + ignore.append(zero_single.domain_id) # Ignore these newly made singles (they should not be bursted) + domains.extend(zero_singles) + + ### 6. Recursively burst around the newly made zero-dt NonInteractionSingles that surround the particles. + # For each zero_single the burst radius equals the particle radius * SINGLE_SHELL_FACTOR + # Add the resulting zero_singles to the already existing list. + for zero_single in zero_singles: + more_zero_singles, ignore = self.burst_non_multis(zero_single.pid_particle_pair[1].position, + zero_single.pid_particle_pair[1].radius*SINGLE_SHELL_FACTOR, + ignore) + domains.extend(more_zero_singles) + + if __debug__: + # check that at least all the zero_singles are on the ignore list + assert all(zero_single.domain_id in ignore for zero_single in domains) + + ### 7. Log change? + + #NOTE Code snippets to re-use the domain/event + #if isinstance(single, InteractionSingle): + ## When bursting an InteractionSingle, the domain changes from Interaction + ## NonInteraction domain. This needs to be reflected in the event + #self.remove_event(single) + #self.add_domain_event(newsingle) + #else: + #assert single == newsingle + #self.update_domain_event(self.t, single) + + #particle_radius = single.pid_particle_pair[1].radius + #assert newsingle.shell.shape.radius == particle_radius + + return domains, ignore + + + def process_pair_event(self, pair, ignore): + # This method handles the things that need to be done when the current event was + # produced by a pair. The pair can be any type of pair (Simple or Mixed). + # Note that this method is also called when a pair is bursted, in that case the event + # is a BURST event. + # Note that ignore should already contain the id of 'pair'. + # Returns: + # - the 'domains' that were the results of the processing. These are zero_singles that are + # the result of the process. Note that these could be many since the event can induce + # recursive bursting. + # - the updated ignore list. This contains the id of the 'pair' domain, ids of domains + # bursted in the process and the ids of zero-dt singles. + + if __debug__: + log.info('FIRE PAIR: %s' % pair.event_type) + log.info('single1 = %s' % pair.single1) + log.info('single2 = %s' % pair.single2) + + ### 1. check that everything is ok + if __debug__: + assert (pair.domain_id in ignore), \ + 'Domain_id should already be on ignore list before processing event.' + if pair.event_type != EventType.BURST: + assert self.check_domain(pair) + # check only non-bursted domains, because the burst may have been caused by an overlap + assert pair.single1.domain_id not in self.domains + assert pair.single2.domain_id not in self.domains + # TODO assert that there is no event associated with this domain in the scheduler + + # check that the event time of the pair (last_time + dt) is equal to the + # simulator time + assert (abs(pair.last_time + pair.dt - self.t) <= TIME_TOLERANCE * self.t), \ + 'Timeline incorrect. pair.last_time = %s, pair.dt = %s, self.t = %s' % \ + (FORMAT_DOUBLE % pair.last_time, FORMAT_DOUBLE % pair.dt, FORMAT_DOUBLE % self.t) + + self.nonmulti_time += pair.dt + + ### 2. get some necessary information + single1 = pair.single1 + single2 = pair.single2 + pid_particle_pair1 = pair.pid_particle_pair1 + pid_particle_pair2 = pair.pid_particle_pair2 + oldpos1 = pid_particle_pair1[1].position + oldpos2 = pid_particle_pair2[1].position + + if pair.event_type == EventType.IV_EVENT: + # Draw actual pair event for iv at very last minute. + pair.event_type = pair.draw_iv_event_type(pair.r0) + + if __debug__: + log.info('Specified event type: IV_EVENT = %s' % pair.event_type) + + + ### 3. Process the event produced by the pair + + # Count the event in the event statistics + self.pair_steps[pair.event_type] += 1 + + ### 3.1 Get new position and current structures of particles + if pair.dt > 0.0: - self.add_multi_event(multi) + newpos1, newpos2, struct1_id, struct2_id = pair.draw_new_positions(pair.dt, pair.r0, pair.iv, pair.event_type) + newpos1, struct1_id = self.world.apply_boundary((newpos1, struct1_id)) + newpos2, struct2_id = self.world.apply_boundary((newpos2, struct2_id)) + + # Check that the particles do not overlap with any other particles in the world + # Ignore the two singles because they are still at there old positions and most + # certainly would cause check_overlap to find them! + assert not self.world.check_overlap((newpos1, pid_particle_pair1[1].radius), + pid_particle_pair1[0], pid_particle_pair2[0]) + assert not self.world.check_overlap((newpos2, pid_particle_pair2[1].radius), + pid_particle_pair1[0], pid_particle_pair2[0]) - return multi + else: + newpos1 = pid_particle_pair1[1].position + newpos2 = pid_particle_pair2[1].position + struct1_id = pid_particle_pair1[1].structure_id + struct2_id = pid_particle_pair2[1].structure_id + # TODO? Check if the new positions are within domain + # TODO? some more consistency checking of the positions + # assert self.check_pair_pos(pair, newpos1, newpos2, old_com, + # pair.get_shell_size()) - # if the closest to this Single is a Multi, reuse the Multi. - elif isinstance(closest, Multi): - multi = closest - self.add_to_multi(single, multi) - self.remove_domain(single) - for neighbor in neighbors[1:]: - self.add_to_multi_recursive(neighbor, multi) + # newpos1/2 now hold the new positions of the particles (not yet committed to the world) + # if we would here move the particles and make new shells, then it would be similar to a propagate + + self.remove_domain(pair) + + ### 3.2 If identity changing processes have taken place + # Four cases: + # 1. Single reaction + if pair.event_type == EventType.SINGLE_REACTION: + reactingsingle = pair.reactingsingle - multi.initialize(self.t) + if reactingsingle == single1: + theothersingle = single2 + # first move the non-reacting particle + particles = self.fire_move(single2, newpos2, struct2_id, pid_particle_pair1) + # don't ignore the (moved) non-reacting particle + more_particles, zero_singles_b, ignore = self.fire_single_reaction(single1, newpos1, struct1_id, ignore) + particles.extend(more_particles) + else: + theothersingle = single1 + particles = self.fire_move(single1, newpos1, struct1_id, pid_particle_pair2) + more_particles, zero_singles_b, ignore = self.fire_single_reaction(single2, newpos2, struct2_id, ignore) + particles.extend(more_particles) - self.update_multi_event(self.t + multi.dt, multi) + if __debug__: + log.info('reactant = %s' % reactingsingle) - return multi + # Make new NonInteractionSingle domains for every particle after the reaction. + zero_singles = [] + for pid_particle_pair in particles: + # 5. make a new single and schedule + zero_single = self.create_single(pid_particle_pair) # TODO reuse the non-reacting single domain + self.add_domain_event(zero_single) + zero_singles.append(zero_single) + # + # 2. Pair reaction + # + elif pair.event_type == EventType.IV_REACTION: - assert False, 'do not reach here' + particles, zero_singles_b, ignore = self.fire_pair_reaction(pair, newpos1, newpos2, struct1_id, struct2_id, ignore) + # Make new NonInteractionSingle domains for every particle after the reaction. + zero_singles = [] + for pid_particle_pair in particles: + # 5. make a new single and schedule + zero_single = self.create_single(pid_particle_pair) + self.add_domain_event(zero_single) + zero_singles.append(zero_single) - def add_to_multi_recursive(self, obj, multi): - if isinstance(obj, Single): - if multi.has_particle(obj.pid_particle_pair[0]): - # Already in the Multi. - return - assert obj.is_reset() - objpos = obj.shell.shape.position + # Just moving the particles + # 3a. IV escape + # 3b. CoM escape + elif(pair.event_type == EventType.IV_ESCAPE or + pair.event_type == EventType.COM_ESCAPE or + pair.event_type == EventType.BURST): + + particles = self.fire_move (single1, newpos1, struct1_id, pid_particle_pair2) + particles.extend(self.fire_move (single2, newpos2, struct2_id)) + zero_singles_b = [] + # ignore is unchanged + + # Make new NonInteractionSingle domains for every particle after the reaction. + zero_singles = [] + for pid_particle_pair in particles: + # 5. make a new single and schedule + zero_single = self.create_single(pid_particle_pair) # TODO reuse domains that were cached in the pair + self.add_domain_event(zero_single) + zero_singles.append(zero_single) + + else: + raise SystemError('process_pair_event: invalid event_type.') + + ##NOTE code snippit to reuse domains that were cached in pair domain + ## re-use single domains for particles + ## normally we would take the particles + new positions from the old pair + ## and use them to make new singles. Now we re-use the singles stored in the + ## pair + #single1 = pair.single1 + #single2 = pair.single2 + #assert single1.domain_id not in self.domains + #assert single2.domain_id not in self.domains + #single1.pid_particle_pair = pid_particle_pair1 # this is probably redundant + #single2.pid_particle_pair = pid_particle_pair2 + + ## 'make' the singles + #single1.initialize(self.t) + #single2.initialize(self.t) + + #self.update_single_shell(single1, newpos1, pid_particle_pair1[1].radius) + #self.update_single_shell(single2, newpos2, pid_particle_pair2[1].radius) + + + #self.domains[single1.domain_id] = single1 + #self.domains[single2.domain_id] = single2 + + ## Check the dimensions of the shells of the singles with the shell in the container + #if __debug__: + #container1 = self.geometrycontainer.get_container(single1.shell) + #assert container1[single1.shell_id].shape.radius == \ + #single1.shell.shape.radius + #if type(single1.shell) is CylindricalShell: + #assert container1[single1.shell_id].shape.half_length == \ + #single1.shell.shape.half_length + + #container2 = self.geometrycontainer.get_container(single2.shell) + #assert container2[single2.shell_id].shape.radius == \ + #single2.shell.shape.radius + #if type(single2.shell) is CylindricalShell: + #assert container2[single2.shell_id].shape.half_length == \ + #single2.shell.shape.half_length + + #assert single1.shell.shape.radius == pid_particle_pair1[1].radius + #assert single2.shell.shape.radius == pid_particle_pair2[1].radius + ## even more checking + #assert self.check_domain(single1) + #assert self.check_domain(single2) + ## Now finally we are convinced that the singles were actually made correctly + + + # Put the zero singles resulting from putative bursting in fire_... in the final list. + zero_singles_fin = zero_singles_b + # Ignore the zero singles that were made around the particles (cannot be bursted) + for zero_single in zero_singles: + ignore.append(zero_single.domain_id) + zero_singles_fin.extend(zero_singles) # Add the zero-dt singles around the particles + + ### 6. Recursively burst around the newly made zero-dt NonInteractionSingles that surround the particles. + for zero_single in zero_singles: - self.add_to_multi(obj, multi) - self.remove_domain(obj) - self.remove_event(obj) + more_zero_singles, ignore = self.burst_non_multis(zero_single.pid_particle_pair[1].position, + zero_single.pid_particle_pair[1].radius*SINGLE_SHELL_FACTOR, + ignore) + # Also add the zero-dt singles from this bursting to the final list + zero_singles_fin.extend(more_zero_singles) + + # Some usefull checks in debugging mode + # Check that at least all the zero_singles are on the ignore list + if __debug__: + assert all(zero_single.domain_id in ignore for zero_single in zero_singles_fin) + + ### 7. Log the event + if __debug__: + log.debug("process_pair_event: #1 { %s: %s => %s }" % + (single1, str(oldpos1), str(newpos1))) + log.debug("process_pair_event: #2 { %s: %s => %s }" % + (single2, str(oldpos2), str(newpos2))) + + return zero_singles_fin, ignore + + + def process_multi_event(self, multi, ignore): + # This method handles the things that need to be done when the current event was + # produced by a multi. + # Returns: + # - Nothing if the particles were just propagated. + # Or: + # - The zero_singles that are the result when the multi was broken up due to an event. + # Note that these could be many since the breakup can induce recursive bursting. + # - The updated ignore list in case of an event. This contains the id of the 'multi' + # domain, ids of domains bursted in the process and the ids of zero-dt singles of the + # particles. + + if __debug__: + log.info('FIRE MULTI: %s (dt=%s)' % (multi.last_event, multi.dt)) - radius = obj.pid_particle_pair[1].radius * \ - (1.0 + self.MULTI_SHELL_FACTOR) - neighbors = self.get_neighbors_within_radius_no_sort( - objpos, radius, ignore=[obj.domain_id]) + if __debug__: + assert (multi.domain_id in ignore), \ + 'Domain_id should already be on ignore list before processing event.' + + # check that the event time of the multi (last_time + dt) is equal to the + # simulator time + assert (abs(multi.last_time + multi.dt - self.t) <= TIME_TOLERANCE * self.t), \ + 'Timeline incorrect. multi.last_time = %s, multi.dt = %s, self.t = %s' % \ + (FORMAT_DOUBLE % multi.last_time, FORMAT_DOUBLE % multi.dt, FORMAT_DOUBLE % self.t) + + self.multi_steps[multi.last_event] += 1 + self.multi_steps[3] += 1 # multi_steps[3]: total multi steps + self.multi_time += multi.dt + self.multi_rl += multi.reaction_length + multi.step() - burst = self.burst_non_multis(neighbors) - neighbor_dists = self.obj_distance_array(objpos, burst) - neighbors = [burst[i] for i - in (neighbor_dists <= radius).nonzero()[0]] + if(multi.last_event == EventType.MULTI_UNIMOLECULAR_REACTION or + multi.last_event == EventType.MULTI_BIMOLECULAR_REACTION): + self.reaction_events += 1 + self.last_reaction = multi.last_reaction + + if multi.last_event is not EventType.MULTI_DIFFUSION: # if an event took place + zero_singles, ignore = self.break_up_multi(multi, ignore) + #self.multi_steps[multi.last_event] += 1 + else: + multi.last_time = self.t + self.add_domain_event(multi) + zero_singles = [] + # ignore is unchanged + + return zero_singles, ignore + + + def break_up_multi(self, multi, ignore): + # Dissolves 'multi' into zero_singles, single with a zero shell (dt=0) + # - 'ignore' contains domain ids that should be ignored + # returns: + # - updated ignore list + # - zero_singles that were the product of the breakup + + self.remove_domain(multi) + + zero_singles = [] + for pid_particle_pair in multi.particles: + zero_single = self.create_single(pid_particle_pair) + self.add_domain_event(zero_single) + zero_singles.append(zero_single) + ignore.append(zero_single.domain_id) + + zero_singles_fin = zero_singles # Put the zero-dt singles around the particles into the final list + + ### Recursively burst around the newly made zero-dt NonInteractionSingles that surround the particles. + for zero_single in zero_singles: + more_zero_singles, ignore = self.burst_non_multis(zero_single.pid_particle_pair[1].position, + zero_single.pid_particle_pair[1].radius*SINGLE_SHELL_FACTOR, + ignore) + zero_singles_fin.extend(more_zero_singles) + + # check that at least all the zero_singles are on the ignore list + if __debug__: + assert all(zero_single.domain_id in ignore for zero_single in zero_singles_fin) + + return zero_singles_fin, ignore + + + ################################################### + #### METHODS FOR NEW SHELL CONSTRUCTION TRIALS #### + ################################################### + def try_interaction(self, single, target_structure): + # Try to form an interaction between the 'single' particle and the 'target_structure'. The interaction can be a: + # -CylindricalSurfaceInteraction + # -PlanarSurfaceInteraction + + if __debug__: + log.debug('trying to form Interaction(%s, %s)' % (single.pid_particle_pair[1], target_structure)) + + + ### 1. Attempt to make a testShell. If it fails it will throw an exception. + try: + testShell = try_default_testinteraction(single, target_structure, self.geometrycontainer, self.domains) + except testShellError as e: + testShell = None + if __debug__: + log.debug('%s not formed: %s' % \ + ('Interaction(%s, %s)' % (single.pid_particle_pair[0], target_structure), + str(e) )) - for obj in neighbors: - self.add_to_multi_recursive(obj, multi) - elif isinstance(obj, Multi): + ### 2. The testShell was made succesfully. Now make the complete domain + if testShell: + # make the domain + interaction = self.create_interaction(testShell) + if __debug__: + log.info(' * Created: %s' % (interaction)) + log.info(' * Total interaction rate: k_tot = %g' % interaction.interaction_ktot) + + # get the next event time + interaction.dt, interaction.event_type, = interaction.determine_next_event() + if __debug__: + assert interaction.dt >= 0.0 + log.info('dt=%s' % (FORMAT_DOUBLE % interaction.dt)) + + self.last_time = self.t + + self.remove_domain(single) + # the event associated with the single will be removed by the scheduler. + + # add to scheduler + self.add_domain_event(interaction) + # check everything is ok + if __debug__: + assert self.check_domain(interaction) + + return interaction + else: + return None + + + def try_transition(self, single, target_structure): + # Try to form a transition between the 'single' particle on a structure and another structure. + # This is called after an interaction trial has failed. + # Currently only transitions between two planar surfaces are supported. + + if __debug__: + log.debug('trying to form Transition(%s, %s)' % (single.pid_particle_pair[1], target_structure)) + + + ### 1. Attempt to make a testShell. If it fails it will throw an exception. + try: + testShell = try_default_testtransition(single, target_structure, self.geometrycontainer, self.domains) + except testShellError as e: + testShell = None + if __debug__: + log.debug('%s not formed: %s' % \ + ('Transition(%s, %s)' % (single.pid_particle_pair[0], target_structure), + str(e) )) + + + ### 2. The testShell was made succesfully. Now make the complete domain + if testShell: + # make the domain + transition = self.create_transition(testShell) + if __debug__: + log.info(' * Created: %s' % (transition)) + + # get the next event time + transition.dt, transition.event_type, = transition.determine_next_event() + if __debug__: + assert transition.dt >= 0.0 + log.info('dt=%s' % (FORMAT_DOUBLE % transition.dt)) + + self.last_time = self.t + + self.remove_domain(single) + # the event associated with the single will be removed by the scheduler. + + # add to scheduler + self.add_domain_event(transition) + # check everything is ok + if __debug__: + #assert self.check_domain(transition) # HACK + pass + + return transition + else: + return None + + + def try_pair(self, single1, single2): + # Try to make a pair domain out of the two singles. A pair domain can be a: + # -SimplePair (both particles live on the same structure) + # -MixedPair (the particles live on different structures -> MixedPair2D3D) + # Note that single1 is always the single that initiated the creation of the pair + # and is therefore no longer in the scheduler + + if __debug__: + log.debug('trying to form Pair(%s, %s)' % + (single1.pid_particle_pair, single2.pid_particle_pair)) + + ### 1. Attempt to make a testShell. If it fails it will throw an exception. + try: + testShell = try_default_testpair(single1, single2, self.geometrycontainer, self.domains) + except testShellError as e: + testShell = None + if __debug__: + log.debug('%s not formed: %s' % \ + ('Pair(%s, %s)' % (single1.pid_particle_pair[0], single2.pid_particle_pair[0]), + str(e) )) + + + ### 2. If the testShell could be formed, make the complete domain. + if testShell: + pair = self.create_pair(testShell) + if __debug__: + log.info(' * Created: %s' % (pair)) + + pair.dt, pair.event_type, pair.reactingsingle = pair.determine_next_event(pair.r0) + if __debug__: + assert pair.dt >= 0 + log.info('dt=%s' % (FORMAT_DOUBLE % pair.dt)) + + self.last_time = self.t + + self.remove_domain(single1) + self.remove_domain(single2) + + # single1 will be removed by the scheduler, since it initiated the creation of the + # pair. + self.remove_event(single2) + + # add to scheduler + self.add_domain_event(pair) + # check everything is ok + if __debug__: + assert self.check_domain(pair) + + return pair + else: + return None + + + ############################################## + #### METHODS FOR MULTI SHELL CONSTRUCTION #### + ############################################## + def form_multi(self, single, multi_partners): + # form a Multi with the 'single' + + # The 'multi_partners' are neighboring NonInteractionSingles, Multis and surfaces which + # can be added to the Multi (this can also be empty) + for partner, overlap in multi_partners: + assert ((isinstance(partner, NonInteractionSingle) and partner.is_reset()) or \ + isinstance(partner, Multi) or \ + isinstance(partner, PlanarSurface) or \ + isinstance(partner, DiskSurface) or \ + isinstance(partner, CylindricalSurface)) or \ + isinstance(partner, SphericalSurface), \ + 'multi_partner %s was not of proper type' % (partner) + + + # Only consider objects that are within the Multi horizon + # assume that the multi_partners are already sorted by overlap + neighbors = [obj for (obj, overlap) in multi_partners if overlap < 0] + + # TODO there must be a nicer way to do this + if neighbors: + closest = neighbors[0] + else: + closest = None + + + # 1. Make new Multi if Necessary (optimization) + if isinstance(closest, Multi): + # if the closest to this Single is a Multi, reuse the Multi. + multi = closest + neighbors = neighbors[1:] # don't add the closest multi with add_to_multi_recursive below + else: + # the closest is not a Multi. Can be NonInteractionSingle, surface or + # nothing. Create new Multi + multi = self.create_multi() + + # 2. Add the single to the Multi + self.add_to_multi(single, multi) + self.remove_domain(single) + + + # Add all partner domains (multi and dt=0 NonInteractionSingles) in the horizon to the multi + for partner in neighbors: + if (isinstance(partner, NonInteractionSingle) and partner.is_reset()) or \ + isinstance(partner, Multi): + self.add_to_multi_recursive(partner, multi) + else: + # neighbor is a surface + log.debug('add_to_multi: object(%s) is surface, not added to multi.' % partner) + + + # 3. Initialize the multi and (re-)schedule + multi.initialize(self.t) + if isinstance(closest, Multi): + # Multi existed before + self.update_domain_event(self.t + multi.dt, multi) + else: + # In case of new multi + self.add_domain_event(multi) + + if __debug__: + assert self.check_domain(multi) + + return multi + + + def add_to_multi_recursive(self, domain, multi): + # adds 'domain' (which can be zero-dt NonInteractionSingle or Multi) to 'multi' + + + if __debug__: + log.debug('add_to_multi_recursive:\t domain: %s, multi: %s' % (domain, multi)) + + if isinstance(domain, NonInteractionSingle): + assert domain.is_reset() # domain must be zero-dt NonInteractionSingle + + # check that the particles were not already added to the multi previously + if multi.has_particle(domain.pid_particle_pair[0]): + # Already in the Multi. + if __debug__: + log.debug('%s already in multi. skipping.' % domain) + return + + # 1. Get the neighbors of the domain + dompos = domain.shell.shape.position + + # neighbor_distances has same format as before + # It contains the updated list of domains in the neighborhood with distances + neighbor_distances = self.geometrycontainer.get_neighbor_domains(dompos, self.domains, + ignore=[domain.domain_id, multi.domain_id]) + + # 3. add domain to the multi + self.add_to_multi(domain, multi) + self.remove_domain(domain) + self.remove_event(domain) + + # 4. Add recursively to multi, neighbors that: + # - have overlapping 'multi_horizons' + # - are Multi or + # - are just bursted (initialized) NonInteractionSingles + for neighbor, dist_to_shell in neighbor_distances: + + if (isinstance (neighbor, NonInteractionSingle) and neighbor.is_reset()): + multi_horizon = (domain.pid_particle_pair[1].radius + neighbor.pid_particle_pair[1].radius) * \ + MULTI_SHELL_FACTOR + # distance from the center of the particles/domains + distance = self.world.distance(dompos, neighbor.shell.shape.position) + overlap = distance - multi_horizon + + elif isinstance(neighbor, Multi): + # The dist_to_shell = dist_to_particle - multi_horizon_of_target_particle + # So only the horizon and distance of the current single needs to be taken into account + # Note: this is built on the assumption that the shell of a Multi has the size of the horizon. + multi_horizon = (domain.pid_particle_pair[1].radius * MULTI_SHELL_FACTOR) + overlap = dist_to_shell - multi_horizon + else: + # neighbor is not addible to multi + overlap = numpy.inf + + # 4.2 if the domains are within the multi horizon + if overlap < 0: + self.add_to_multi_recursive(neighbor, multi) + + + elif isinstance(domain, Multi): + + # check that the particles were not already added to the multi previously for pp in multi.particles: - if obj.has_particle(pp[0]): + if domain.has_particle(pp[0]): if __debug__: - log.debug('%s already added. skipping.' % obj) + log.debug('%s already added. skipping.' % domain) break else: - self.merge_multis(obj, multi) + # 1. No bursting is needed, since the shells in the multi already exist and no space has be made + # 2. Add the domain (the partner multi) to the existing multi + self.merge_multis(domain, multi) + + # 3/4. No neighbors are searched and recursively added since everything that was in the multi + # horizon of the domain was already in the multi. + else: - assert False, 'do not reach here.' # Pairs are burst + # only NonInteractionSingles and Multi should be selected + assert False, 'Trying to add non Single or Multi to Multi.' - def new_spherical_shell(self, domain_id, pos, size): - shell_id_shell_pair = ( - self.shell_id_generator(), - SphericalShell(domain_id, Sphere(pos, size))) - self.move_shell(shell_id_shell_pair) - return shell_id_shell_pair def add_to_multi(self, single, multi): + # This method is similar to the 'create_' methods for pair and interaction, except it's now for a multi + # adding a single to the multi instead of creating a pair or interaction out of single(s) + if __debug__: - log.info('add to multi:\n %s\n %s' % (single, multi)) - - sid_shell_pair = self.new_spherical_shell( - multi.domain_id, - single.pid_particle_pair[1].position, - single.pid_particle_pair[1].radius * \ - (1.0 + self.MULTI_SHELL_FACTOR)) - multi.add_shell(sid_shell_pair) + log.info('add_to_multi:\t Adding %s to %s' % (single, multi)) + + shell_id = self.shell_id_generator() + shell = multi.create_new_shell(single.pid_particle_pair[1].position, + single.pid_particle_pair[1].radius * MULTI_SHELL_FACTOR, + multi.domain_id) + shell_id_shell_pair = (shell_id, shell) + self.geometrycontainer.move_shell(shell_id_shell_pair) + multi.add_shell(shell_id_shell_pair) multi.add_particle(single.pid_particle_pair) + # TODO maybe also cache the singles in the Multi for reuse? + + def merge_multis(self, multi1, multi2): # merge multi1 into multi2. multi1 will be removed. if __debug__: - log.info('merging %s to %s' % (multi1.domain_id, multi2.domain_id)) + log.info('merge_multis: merging %s to %s' % (multi1.domain_id, multi2.domain_id)) log.info(' %s' % multi1) log.info(' %s' % multi2) @@ -1438,7 +3112,7 @@ def merge_multis(self, multi1, multi2): for sid_shell_pair in multi1.shell_list: sid_shell_pair[1].did = multi2.domain_id - self.move_shell(sid_shell_pair) + self.geometrycontainer.move_shell(sid_shell_pair) multi2.add_shell(sid_shell_pair) for pid_particle_pair in multi1.particles: @@ -1447,236 +3121,660 @@ def merge_multis(self, multi1, multi2): del self.domains[multi1.domain_id] self.remove_event(multi1) - def get_neighbors_within_radius_no_sort(self, pos, radius, ignore=[]): - # Get neighbor domains within given radius. - # - # ignore: domain ids. - # - # Only returns neighbors, not the distances towards their - # shells. Can for example be used to try to clear all objects - # from a certain volume. - - for container in self.containers: - result = container.get_neighbors_within_radius(pos, radius) - # result = [((shell_id_shell_pair), distance), ] - # Since a domain can have more than 1 shell (multis for - # example), and for each shell there is an entry in the - # shell container, we make sure each domain occurs only once - # in the returned list here. - for did in uniq(s[0][1].did for s in result): - if did not in ignore: - yield self.domains[did] - - def get_intruders(self, position, radius, ignore): - intruders = [] # intruders are domains within radius - closest_domain = None # closest domain, excluding intruders. - closest_distance = numpy.inf # distance to the shell of the closest. - - seen = set(ignore) - for container in self.containers: - neighbors = container.get_neighbors(position) - for n in neighbors: - domain_id = n[0][1].did - distance = n[1] - if distance > radius: - if distance < closest_distance: - # This is domain (the first one for this - # container) that has a shell that is more than - # radius away from pos. If it is closer than - # the closest such one we found so far: store - # it. Always break out of the inner for loop and - # check the other containers. - closest_domain = self.domains[domain_id] - closest_distance = distance - break - else: - break - elif domain_id not in seen: - # Since a domain can have more than 1 shell (multis - # for example), and for each shell there is an entry - # in the shell container, we make sure each domain - # occurs only once in the returned list here. - seen.add(domain_id) - intruders.append(self.domains[domain_id]) - - return intruders, closest_domain, closest_distance - - def get_closest_obj(self, pos, ignore=[], ignores=[]): - # ignore: domain ids. - closest_domain = None - closest_distance = numpy.inf - - for container in self.containers: - result = container.get_neighbors(pos) - - for shell_id_shell_pair, distance in result: - domain_id = shell_id_shell_pair[1].did - - if domain_id not in ignore and distance < closest_distance: - domain = self.domains[domain_id] - closest_domain, closest_distance = domain, distance - # Found yet a closer domain. Break out of inner for - # loop and check other containers. - break - - surface, distance = get_closest_surface(self.world, pos, ignores) - - if distance < closest_distance: - return surface, distance - else: - return closest_domain, closest_distance - def obj_distance(self, pos, obj): + ############################## + #### SOME HELPER METHODS #### + ############################## + def domain_distance(self, pos, domain): + # calculates the shortest distance from 'pos' to A shell (a Multi + # can have multiple) of 'domain'. + # Note: it returns the distance between pos and the surface of the shell return min(self.world.distance(shell.shape, pos) - for i, (_, shell) in enumerate(obj.shell_list)) + for i, (_, shell) in enumerate(domain.shell_list)) - def obj_distance_array(self, pos, objs): - dists = numpy.array([self.obj_distance(pos, obj) for obj in objs]) - return dists - - # - # statistics reporter - # + def obj_distance_array(self, pos, domains): + dists = numpy.array([self.domain_distance(pos, domain) for domain in domains]) + return dists + + #################### + #### STATISTICS #### + #################### def print_report(self, out=None): """Print various statistics about the simulation. Arguments: - None - """ + """ + single_steps = numpy.array(self.single_steps.values()).sum() + interaction_steps = numpy.array(self.interaction_steps.values()).sum() + pair_steps = numpy.array(self.pair_steps.values()).sum() + multi_steps = self.multi_steps[3] # total multi steps + total_steps = single_steps + interaction_steps + pair_steps + multi_steps + + if multi_steps: + avg_multi_time = 1.0 * self.multi_time / multi_steps + avg_multi_rl = 1.0 * self.multi_rl / multi_steps + else: + avg_multi_time = 0.0 + avg_multi_rl = 0.0 + report = ''' t = %g -steps = %d -\tSingle:\t%d\t(escape: %d, reaction: %d) -\tInteraction: %d\t(escape: %d, interaction: %d) -\tPair:\t%d\t(escape r: %d, R: %d, reaction pair: %d, single: %d) -\tMulti:\t%d\t(escape: %d, reaction pair: %d, single: %d) -total reactions = %d -rejected moves = %d +t0 = %g +sim. time: %g +\tNonmulti: %g\tMulti: %g +steps: %d +updates: %d +\tSingle:\t%d\t(%.2f %%)\t(escape: %d, reaction: %d, bursted: %d, make_new_domain: %d) +\tInteraction: %d\t(%.2f %%)\t(escape: %d, interaction: %d, bursted: %d) +\tPair:\t%d\t(%.2f %%)\t(r-escape: %d, R-escape: %d, reaction pair: %d, single: %d, bursted: %d) +\tMulti:\t%d\t(%.2f %%)\t(diffusion: %d, escape: %d, reaction pair: %d, single: %d, bursted: %d) +\tavg. multi time step: %e, avg. multi reaction length: %e +\tmulti hardcore time step minimum: %s +total reactions: %d +rejected moves: %d +overlap remover was: %s +max. overlap error: %g ''' \ - % (self.t, self.step_counter, - numpy.array(self.single_steps.values()).sum(), + % (self.t, + self.t0, + self.t - self.t0, + self.nonmulti_time, self.multi_time, + self.step_counter, total_steps, + single_steps, + (100.0*single_steps) / total_steps, self.single_steps[EventType.SINGLE_ESCAPE], self.single_steps[EventType.SINGLE_REACTION], - numpy.array(self.interaction_steps.values()).sum(), + self.single_steps[EventType.BURST], + self.single_steps['MAKE_NEW_DOMAIN'], + interaction_steps, + (100.0*interaction_steps) / total_steps, self.interaction_steps[EventType.IV_ESCAPE], self.interaction_steps[EventType.IV_INTERACTION], - numpy.array(self.pair_steps.values()).sum(), + self.interaction_steps[EventType.BURST], + pair_steps, + (100.0*pair_steps) / total_steps, self.pair_steps[EventType.IV_ESCAPE], self.pair_steps[EventType.COM_ESCAPE], self.pair_steps[EventType.IV_REACTION], self.pair_steps[EventType.SINGLE_REACTION], - self.multi_steps[3], # total multi steps + self.pair_steps[EventType.BURST], + multi_steps, + (100.0*multi_steps) / total_steps, + self.multi_steps[EventType.MULTI_DIFFUSION], self.multi_steps[EventType.MULTI_ESCAPE], self.multi_steps[EventType.MULTI_BIMOLECULAR_REACTION], self.multi_steps[EventType.MULTI_UNIMOLECULAR_REACTION], + self.multi_steps[EventType.BURST], + avg_multi_time, + avg_multi_rl, + ('inactive' if self.BD_DT_HARDCORE_MIN < 0.0 else self.BD_DT_HARDCORE_MIN), self.reaction_events, - self.rejected_moves + self.rejected_moves, + ('active' if self.REMOVE_OVERLAPS else 'inactive'), + self.max_overlap_error ) print >> out, report - # - # consistency checkers - # - def check_obj(self, obj): - obj.check() + def activate_histograms(self, modes=['creation', 'updates']): + """ Activate and initialize the shell creation and / or updates histograms. + + The argument is a list containing keywords of the histograms to activate, + i.e. 'creation' and/or 'updates'. + + The default is ['creation', 'updates'], i.e. both histograms will be + activated. + + """ + for m in modes: + + log.info('Activating %s histograms ...', str(m)) + + if str(m) == 'creation': + self.CREATION_HISTOGRAMS = True + + if str(m) == 'updates': + self.UPDATES_HISTOGRAMS = True + - for shell_id, shell in obj.shell_list: - if not isinstance(obj, Multi): - ignores = [obj.surface.id] + if self.CREATION_HISTOGRAMS: + self.DomainCreationHists = DomainsHistogramCollection(self.world, nbins=100, name='Creation Histograms Collection') + + if self.UPDATES_HISTOGRAMS: + self.DomainUpdatesHists = DomainsHistogramCollection(self.world, nbins=100, name='Updates Histograms Collection') + + + def deactivate_histograms(self, modes=['creation', 'updates']): + """ Deactivate the shell creation and / or updates histograms. + + The argument is a list containing keywords of the histograms to activate, + i.e. 'creation' and/or 'updates'. + + The default is ['creation', 'updates'], i.e. both histograms will be + deactivated. + + """ + for m in modes: + + log.info('Deactivating %s histograms ...', str(m)) + + if str(m) == 'creation': + self.CREATION_HISTOGRAMS = False + + if str(m) == 'updates': + self.UPDATES_HISTOGRAMS = False + + + ########################################## + #### METHODS FOR CONSISTENCY CHECKING #### + ########################################## + def check_domain(self, domain): + domain.check() + + # Construct ignore list for surfaces and the structures that the domain is associated with + # __ + # This is necessary because structures which are involved in the + # mathematical solution need to be excluded from the overlap + # check. + if isinstance(domain, Multi): + # Ignore all surfaces, multi shells can overlap with + # surfaces. + ignores = [s.id for s in self.world.structures] + associated = [] + + elif isinstance(domain, SphericalSingle) or isinstance(domain, SphericalPair) or isinstance(domain, PlanarSurfaceSingle): # TODO Why not PlanarSurfacePair ? + # 3D NonInteractionSingles can overlap with planar surfaces but not with rods + ignores = [domain.structure.structure_id] + [s.id for s in self.world.structures if isinstance(s, PlanarSurface)] + associated = [] + + elif isinstance(domain, DiskSurfaceSingle): + # Particles bound to DiskSurfaces ignore all rods and other disks for now. + ignores_cyl = [s.id for s in self.world.structures if isinstance(s, CylindricalSurface)] # TODO Only ignore closest cylinder + ignores_dsk = [s.id for s in self.world.structures if isinstance(s, DiskSurface)] # TODO Only ignore closest disks + ignores = ignores_cyl + ignores_dsk + associated = [domain.structure.id, domain.structure.structure_id] # the latter is the ID of the parent structure + + elif isinstance(domain, CylindricalSurfacePlanarSurfaceIntermediateSingle) or isinstance(domain, MixedPair1DStatic) \ + or isinstance(domain, PlanarSurfaceDiskSurfaceInteraction): + # Ignore the planar surface and sub-disk that holds/will hold the particle, and the neighboring cylinder PLUS its disks + # Note that the plane is the parent structure of the disk, which in both cases is the target structure + ignores_cyl = [s.id for s in self.world.structures if isinstance(s, CylindricalSurface)] # TODO Only ignore closest cylinder + ignores_dsk = [s.id for s in self.world.structures if isinstance(s, DiskSurface)] # TODO Only ignore closest disks + ignores = ignores_cyl + ignores_dsk + associated = [domain.origin_structure.id, domain.target_structure.id, domain.target_structure.structure_id] + # the latter is the ID of the parent structure + + elif isinstance(domain, CylindricalSurfaceInteraction) or isinstance(domain, CylindricalSurfacePlanarSurfaceInteraction): + # Ignore surface of the particle and interaction surface and all DiskSurfaces for now. + ignores = [s.id for s in self.world.structures if isinstance(s, DiskSurface)] # TODO Only ignore closest disk + associated = [domain.origin_structure.id, domain.target_structure.id] + + elif isinstance(domain, InteractionSingle) or isinstance(domain, MixedPair2D3D): + # Ignore surface of the particle and interaction surface + ignores = [] + associated = [domain.origin_structure.id, domain.target_structure.id] + + elif isinstance(domain, PlanarSurfacePair): + # PlanarSurfacePair domains are also formed with a static particle located on a (sub-) disk of the plane + # In these cases, the disk and--if present--a cylinder next to it shall be ignored. + ignores = [domain.structure.structure_id] + domain.ignored_structure_ids # ignore parent structure and extra-ignore-list + associated = [domain.structure.id] + + elif isinstance(domain, PlanarSurfaceTransitionSingle) or isinstance(domain, PlanarSurfaceTransitionPair): + # Ignore surface of the particle and interaction surface + ignores = [domain.structure1.structure_id, domain.structure2.structure_id] # parent structures + associated = [domain.structure1.id, domain.structure2.id] + + else: + # Ignore the structure that the particles are associated with + ignores = [] + associated = [domain.structure.id] + + ###### check shell consistency + # check that shells of the domain don't overlap with surfaces that are not associated with the domain. + # check that shells of the domain DO overlap with the structures that it is associated with. + # check that shells of the domain do not overlap with shells of other domains + # check that shells are not bigger than the allowed maximum + + for shell_id, shell in domain.shell_list: + + ### Determine the size of the shell; take sth. representative + if (type(shell.shape) is Cylinder): + # the cylinder should at least fit in the maximal sphere + shell_size = math.sqrt(shell.shape.radius**2 + shell.shape.half_length**2) + elif (type(shell.shape) is Sphere): + shell_size = shell.shape.radius else: - ignores = [] - closest, distance = self.get_closest_obj(shell.shape.position, - ignore=[obj.domain_id], - ignores=ignores) - if(type(obj) is CylindricalSurfaceSingle or - type(obj) is CylindricalSurfacePair): - shell_size = shell.shape.half_length + raise RuntimeError('check_domain error: Shell shape was not Cylinder of Sphere') + + overlap_tolerance = shell_size*TOLERANCE + + ### Check that shells do not overlap with non associated surfaces + surfaces = get_all_surfaces(self.world, shell.shape.position, ignores + associated) + for surface, _ in surfaces: + overlap = self.check_shape_overlap(shell.shape, surface.shape) + if __debug__ and overlap < 0.0: + log.warn('%s (%s) overlaps with %s by %s.' % \ + (str(domain), str(shell), str(surface), FORMAT_DOUBLE % overlap) ) + assert overlap >= -1.0*overlap_tolerance, \ + 'Overlap (%s) out of overlap_tolerance (%s).' % (str(overlap), str(overlap_tolerance)) + + + ### Check that shells DO overlap with associated surfaces. + surfaces = get_all_surfaces(self.world, shell.shape.position, ignores) + for surface, _ in surfaces: + if surface.id in associated: + assert self.check_shape_overlap(shell.shape, surface.shape) < 0.0, \ + '%s (%s) should overlap with associated surface %s.' % \ + (str(domain), str(shell), str(surface)) + + ### Check shell overlap with other shells + neighbors = self.geometrycontainer.get_neighbor_domains(shell.shape.position, + self.domains, ignore=[domain.domain_id]) + # TODO maybe don't check all the shells, this takes a lot of time + # testing overlap criteria + for neighbor, _ in neighbors: + # note that the shell of a MixedPair or Multi that has have just been bursted can stick into each other. + if not (((isinstance(domain, hasCylindricalShell) and isinstance(domain, NonInteractionSingle) and domain.is_reset()) and \ + (isinstance(neighbor, hasSphericalShell) and isinstance(neighbor, NonInteractionSingle) and domain.is_reset()) ) or \ + ((isinstance(domain, hasSphericalShell) and isinstance(domain, NonInteractionSingle) and domain.is_reset()) and \ + (isinstance(neighbor, hasCylindricalShell) and isinstance(neighbor, NonInteractionSingle) and domain.is_reset()) )): + + for _, neighbor_shell in neighbor.shell_list: + overlap = self.check_shape_overlap(shell.shape, neighbor_shell.shape) + if __debug__ and overlap < 0.0: + log.warn('%s (%s) overlaps with %s (%s) by %s.' %\ + (domain, str(shell), str(neighbor), str(neighbor_shell), FORMAT_DOUBLE % overlap) ) + assert overlap >= -1.0*overlap_tolerance, \ + 'Overlap out of overlap_tolerance = %s.' % str(overlap_tolerance) + + ### Check if the shell does not exceed the maximum size + assert shell_size <= self.geometrycontainer.get_user_max_shell_size(), \ + '%s shell size larger than user-set max shell size, shell_size = %s, max = %s.' % \ + (str(shell_id), FORMAT_DOUBLE % shell_size, FORMAT_DOUBLE % self.geometrycontainer.get_user_max_shell_size()) + + assert shell_size <= self.geometrycontainer.get_max_shell_size(), \ + '%s shell size larger than simulator cell size / 2, shell_size = %s, max = %s.' % \ + (str(shell_id), FORMAT_DOUBLE % shell_size, FORMAT_DOUBLE % self.geometrycontainer.get_max_shell_size()) + + return True + + + def check_shape_overlap(self, shape1, shape2): + # Return the distance between two shapes (positive number means no overlap, negative means overlap) + + # overlap criterium when one of the shapes is a sphere + if (type(shape1) is Sphere): + distance = self.world.distance(shape2, shape1.position) + return (1.0+TOLERANCE)*distance - shape1.radius + elif (type(shape2) is Sphere): + distance = self.world.distance(shape1, shape2.position) + return (1.0+TOLERANCE)*distance - shape2.radius + + # overlap criterium when one of the shapes is a cylinder and the other is a plane + elif ((type(shape1) is Cylinder) and (type(shape2) is Plane)) or \ + ((type(shape2) is Cylinder) and (type(shape1) is Plane)): + + # putatively swap to make sure that shape1 is Cylinder and shape2 is Plane + if (type(shape2) is Cylinder) and (type(shape1) is Plane): + temp = shape2 + shape2 = shape1 + shape1 = temp + + if math.sqrt(shape1.half_length**2 + shape1.radius**2) < self.world.distance(shape2, shape1.position): + # if the plane is outside the sphere surrounding the cylinder. + return 1 + + else: + shape1_pos = shape1.position + shape2_pos = shape2.position + shape1_post = self.world.cyclic_transpose(shape1_pos, shape2_pos) + inter_pos = shape1_post - shape2_pos + relative_orientation = numpy.dot(shape1.unit_z, shape2.unit_z) + # if the unit_z of the plane is perpendicular to the axis of the cylinder. + if feq(relative_orientation, 0): + if abs(numpy.dot(inter_pos, shape2.unit_x)) - shape1.half_length > shape2.half_extent[0] or \ + abs(numpy.dot(inter_pos, shape2.unit_y)) - shape1.half_length > shape2.half_extent[1] or \ + abs(numpy.dot(inter_pos, shape2.unit_z)) > shape1.radius: + return 1 + else: + # NOTE this is still incorrect. By the above if statement the cylinder is made into a cuboid + return -1 + + elif feq(abs(relative_orientation), 1): + if abs(numpy.dot(inter_pos, shape2.unit_x)) - shape1.radius > shape2.half_extent[0] or \ + abs(numpy.dot(inter_pos, shape2.unit_y)) - shape1.radius > shape2.half_extent[1] or \ + abs(numpy.dot(inter_pos, shape2.unit_z)) > shape1.half_length: + return 1 + else: + # NOTE this is still incorrect. By the above if statement the cylinder is made into a cuboid + return -1 + else: + raise RuntimeError('check_shape_overlap: planes and cylinders should be either parallel or perpendicular.') + + # FIXME FIXME separate Disk overlap criterium in separate check (probably clearer/faster than combining) + # overlap criterium when both shells are cylindrical or disks (= cylinders with half_length = 0.0) + elif (type(shape1) is Cylinder) and (type(shape2) is Cylinder) or \ + (type(shape1) is Cylinder) and (type(shape2) is Disk) or \ + (type(shape1) is Disk) and (type(shape2) is Cylinder) or \ + (type(shape1) is Disk) and (type(shape2) is Disk) : + + # Disk has no property half_length, so check type before any calculation + shape1_hl = 0.0 + shape2_hl = 0.0 + if type(shape1) is Cylinder: + shape1_hl = shape1.half_length + if type(shape2) is Cylinder: + shape2_hl = shape2.half_length + + # Check whether the cylinders are inside the sphere of which the radius is the shortest distance + # to the other cylinder; in that case, there cannot be any overlap and we are fine. + if math.sqrt(shape1_hl**2 + shape1.radius**2) < self.world.distance(shape2, shape1.position) or \ + math.sqrt(shape2_hl**2 + shape2.radius**2) < self.world.distance(shape1, shape2.position): + + return 1 + else: - shell_size = shell.shape.radius - assert shell_size <= self.get_user_max_shell_size(), \ - '%s shell size larger than user-set max shell size' % \ - str(shell_id) + # Calculate the inter-object vector (inter_pos) + # Don't forget the periodic boundary conditions + shape1_pos = shape1.position + shape2_pos = shape2.position + shape1_post = self.world.cyclic_transpose(shape1_pos, shape2_pos) + inter_pos = shape1_post - shape2_pos - assert shell_size <= self.get_max_shell_size(), \ - '%s shell size larger than simulator cell size / 2' % \ - str(shell_id) + relative_orientation = numpy.dot(shape1.unit_z, shape2.unit_z) - assert distance - shell_size >= 0.0, \ - '%s overlaps with %s. (shell: %s, dist: %s, diff: %s.' % \ - (str(obj), str(closest), FORMAT_DOUBLE % shell_size, - FORMAT_DOUBLE % distance, - FORMAT_DOUBLE % (distance - shell_size)) + # If the unit_z of cylinder1 (disk1) is perpendicular to the axis of cylinder2 (disk2). + if feq(relative_orientation, 0.0): - return True + # Project the inter-cylinder distance on the axes of the two cylinders (disks) + inter_pos_proj_1 = numpy.dot(inter_pos, shape1.unit_z) * shape1.unit_z + inter_pos_proj_2 = numpy.dot(inter_pos, shape2.unit_z) * shape2.unit_z + # Also calculate the part orthogonal to both projections + inter_pos_proj_3 = inter_pos - inter_pos_proj_1 - inter_pos_proj_2 + + delta_1 = length(inter_pos_proj_1) + delta_2 = length(inter_pos_proj_2) + delta_3 = length(inter_pos_proj_3) + + # Collision is impossible if the half-length of the cylinder that we projected on + # plus the radius of the other cylinder are smaller then the projected distance + if delta_1 > shape1_hl + shape2.radius: + return 1 + # Also check for the vice-versa projection + if delta_2 > shape2_hl + shape1.radius: + return 1 + + # Treat the obvious collisions: + # If the following criterion is fulfilled we certainly have one + if delta_3 <= shape1.radius + shape2.radius and \ + shape1_hl > delta_1 and shape2_hl > delta_2: + + return -1 + + else: + # The cylinders overlap in one of the planes defined by the cylinder axes + # as plane normal vectors. That does not necessarily mean that they also overlap + # in 3D precisely because of the missing corner volume (when compared to a box). + # Collision here of course is possible but it is nontrivial to calculate it + # in a generic way. For now we just warn. + # TODO We need really sth. more sophisticated here! + log.warning('check_shape_overlap: Incomplete check for orthogonal cylinders overlap.') + return 1 + + # If the two objects (cylinder or disk) are parallel + elif feq(abs(relative_orientation), 1.0): + + # Calculate the components of the inter-object vector in the COS + # defined by shape2 + inter_pos_z = shape2.unit_z * numpy.dot(inter_pos, shape2.unit_z) + inter_pos_r = inter_pos - inter_pos_z + overlap_r = (1.0+TOLERANCE)*length(inter_pos_r) - (shape1.radius + shape2.radius) + overlap_z = (1.0+TOLERANCE)*length(inter_pos_z) - (shape1_hl + shape2_hl) + + if (overlap_r < 0.0) and (overlap_z < 0.0): + + if length(inter_pos_r) == 0.0: + total_overlap = overlap_z + elif length(inter_pos_z) == 0.0: + total_overlap = overlap_r + else: + total_overlap = -1.0 * math.sqrt(overlap_r*overlap_r + overlap_z*overlap_z) + + return total_overlap + + else: + return 1 + else: + raise RuntimeError('check_shape_overlap: cylinders are not oriented parallel or perpendicular.') + + # If both shapes are planes. + elif (type(shape1) is Plane) and (type(shape2) is Plane): + # TODO + return 1 + + # something was wrong (wrong type of shape provided) + else: + raise RuntimeError('check_shape_overlap: wrong shape type(s) provided.') + + + def check_domain_for_all(self): + # For every domain in domains, checks the consistency + for domain in self.domains.itervalues(): + self.check_domain(domain) - def check_obj_for_all(self): - for id, event in self.scheduler: - self.check_obj(event.data) def check_event_stoichiometry(self): - event_population = 0 + ### check scheduler + # -check that every event on scheduler is associated with domain or is a non domain related event + # -check that every event with domain is also in domains{} + + SMT = 6 # just some temporary definition to account for non-domain related events + self.other_objects = {} # ditto for id, event in self.scheduler: - event_population += event.data.multiplicity + domain_id = event.data + if domain_id != SMT: + # in case the domain_id is not special, it needs to be in self.domains + assert domain_id in self.domains + else: + # if it is special in needs to be in self.other_objects + assert domain_id in self.other_objects + + + ### performs two checks: + # -check that every domain in domains{} has one and only one event in the scheduler + # -check that dt, last_time of the domain is consistent with the event time on the scheduler + already_in_scheduler = set() # make a set of domain_ids that we found in the scheduler + for domain_id, domain in self.domains.iteritems(): + # find corresponding event in the scheduler + for id, event in self.scheduler: + if event.data == domain_id: + # We found the event in the scheduler that is associated with the domain + assert (abs(domain.last_time + domain.dt - event.time) <= TIME_TOLERANCE * event.time) + break + else: + raise RuntimeError('check_event_stoichiometry: event for domain_id %s could not be found in scheduler' % str(domain_id) ) + + # make sure that we had not already found it earlier + assert domain_id not in already_in_scheduler + already_in_scheduler.add(domain_id) + + + def check_particle_consistency(self): + ### Checks the particles consistency between the world and the domains of + # the simulator + + # First, check num particles in domains is equal to num particles in the world + world_population = self.world.num_particles + domain_population = 0 + for domain in self.domains.itervalues(): + domain_population += domain.multiplicity + + if world_population != domain_population: + raise RuntimeError('check_event_stoichiometry: population %d != domain_population %d' % + (world_population, domain_population)) + + # If control bounds are defined, check whether all particles are within + # (with a certain tolerance): + world_particles = list(self.world) + + for particle in world_particles: + + pos = self.get_position(particle) + + for corner1, corner2 in self.control_bounds: + + tolerance = length(corner2) * TOLERANCE + + if not( all([diff >= -tolerance for diff in numpy.subtract(pos, corner1)]) \ + and all([diff >= -tolerance for diff in numpy.subtract(corner2, pos)]) ): + + log.warn('Particle out of control bounds: particle = %s, bounds = %s, tolerance = %s' \ + % (particle, (corner1, corner2), tolerance) ) - if self.world.num_particles != event_population: - raise RuntimeError('population %d != event_population %d' % - (population, event_population)) + + + ## Next, check that every particle in the domains is once and only once in the world. + #already_in_domain = set() + #world_particles = list(self.world) + #for domain in self.domains.itervalues(): + #for domain_particle in domain.particles: + #for world_particle in world_particles: + #if domain_particle == world_particle: + #break + #else: + #raise RuntimeError('particle %s not in world' % str(domain_particle)) + + #assert domain_particle not in already_in_domain, 'particle %s twice in domains' % str(domain_particle) + #already_in_domain.add(domain_particle) + + ## This now means that all the particles in domains are represented in the world and that + ## all the particles in the domains are unique (no particle is represented in two domains) + + #assert already_in_domain == len(world_particles) + ## Since the number of particles in the domains is equal to the number of particles in the + ## world this now also means that all the particles in the world are represented in the + ## domains. def check_shell_matrix(self): - did_map = {} - shell_map = {} - for container in self.containers: - if self.world.world_size != container.world_size: - raise RuntimeError('self.world.world_size != ' - 'container.world_size') - for shell_id, shell in container: - did_map.setdefault(shell.did, []).append(shell_id) - shell_map[shell_id] = shell + ### checks the consistency between the shells in the geometry container and the domains + # -check that shells of a domain in domains{} are once and only once in the shell matrix + # -check that all shells in the matrix are once and only once in a domain + + did_map, shell_map = self.geometrycontainer.get_dids_shells() shell_population = 0 - for id, event in self.scheduler: - shell_population += event.data.num_shells - shell_ids = did_map[event.data.domain_id] - if len(shell_ids) != event.data.num_shells: + for domain in self.domains.itervalues(): + shell_population += domain.num_shells + + # get the shells associated with the domain according to the geometrycontainer + shell_ids = did_map[domain.domain_id] + for shell_id, shell in domain.shell_list: + # check that all shells in the domain are in the matrix and have the proper domain_id + assert shell_id in shell_ids + + # check that the number of shells in the shell_list is equal to the number of shells the domain + # says it has is equal to the number of shell the geometrycontainer has registered to the domain. + assert domain.num_shells == len(list(domain.shell_list)) + assert domain.num_shells == len(shell_ids) + if len(shell_ids) != domain.num_shells: diff = set(sid for (sid, _) - in event.data.shell_list).difference(shell_ids) + in domain.shell_list).difference(shell_ids) for sid in diff: print shell_map.get(sid, None) - raise RuntimeError('number of shells are inconsistent ' + raise RuntimeError('check_shell_matrix: number of shells are inconsistent ' '(%d != %d; %s) - %s' % - (len(shell_ids), event.data.num_shells, - event.data.domain_id, diff)) + (len(shell_ids), domain.num_shells, + domain.domain_id, diff)) - matrix_population = sum(len(container) - for container in self.containers) + # check that total number of shells in domains==total number of shells in shell container + matrix_population = self.geometrycontainer.get_total_num_shells() if shell_population != matrix_population: - raise RuntimeError('num shells (%d) != matrix population (%d)' % + raise RuntimeError('check_shell_matrix: num shells (%d) != matrix population (%d)' % (shell_population, matrix_population)) + # Since the number of shells in the domains is equal to the number of shells in the + # geometrycontainer this now also means that all the shells in the geometrycontainer are + # represented in the domains. + + # This now also means that the shells of all the cached singles in Pairs are not in the containers. + + +##################################################### +#### TODO: METHODS PROTOTYPES FOR FURTHER CHECKS #### +##################################################### +### check that all the cached singles in Pairs are not in domains{}. + +### check world +# check that particles do not overlap with cylinderical surfaces unless the domain is associated with the surface + +### check domains +# check that testShell is of proper type +# check that the shell(s) of domain have the proper geometry (sphere/cylinder) +# check that shell is within minimum and maximum +# check that shell obeys scaling properties +# check that the particles are within the shells of the domain +# check that the position of the shell(s) and particle(s) are within the structures that the particles live on +# check that dt != 0 unless NonInteractionSingle.is_reset() +# check associated structures are of proper type (plane etc, but also of proper StructureTypeID) +# check that Green's functions are defined. + +### check NonInteractionSingle +# check if is_reset() then shellsize is size particle, dt==0, event_type==ESCAPE +# check shell.unit_z == structure.unit_z +# check drift < inf +# check inner space < shell + +### check Pair +# check number of particles==2 +# check D_R, D_r > 0 +# check r0 is between sigma and a (within bounds of greens function) +# check sigma + +### check SimplePair +# check CoM, iv lies in the structure of the particles +# check shell.unit_z == structure.unit_z + +### check MixedPair +# check CoM is in surface, IV is NOT + +### check Interaction +# check shell.unit_z = +- surface.unit_z + +### check consistency of the model +# check that the product species of an interaction lives on the interaction surface. +# check that the product species of a bimolecular reaction lives on either structure of reactant species +# check that the product species of a unimolecular reaction lives on structure of the reactant species or the bulk. +# structures cannot overlap unless substructure def check_domains(self): + # checks that the events in the scheduler are consistent with the domains + + # make set of event_id that are stored in all the domains event_ids = set(domain.event_id for domain in self.domains.itervalues()) + # check that all the event_id in the scheduler are also stored in a domain for id, event in self.scheduler: if id not in event_ids: - raise RuntimeError('%s in EventScheduler not in self.domains' % - event.data) - event_ids.remove(id) + raise RuntimeError('check_domains: event %s in event scheduler has no domain in self.domains' % + event) + else: + event_ids.remove(id) # self.domains always include a None --> this can change in future if event_ids: - raise RuntimeError('following domains in self.domains not in ' - 'Event Scheduler: %s' % str(tuple(event_ids))) + raise RuntimeError('check_domains: following domains in self.domains are not in ' + 'event scheduler: %s' % str(tuple(event_ids))) def check_pair_pos(self, pair, pos1, pos2, com, radius): - particle1 = pair.single1.pid_particle_pair[1] - particle2 = pair.single2.pid_particle_pair[1] + particle1 = pair.pid_particle_pair1[1] + particle2 = pair.pid_particle_pair2[1] old_com = com @@ -1700,16 +3798,16 @@ def check_pair_pos(self, pair, pos1, pos2, com, radius): d1 = self.world.distance(old_com, pos1) + particle1.radius d2 = self.world.distance(old_com, pos2) + particle2.radius if d1 > radius or d2 > radius: - raise RuntimeError('New particle(s) out of protective sphere. ' + raise RuntimeError('check_pair_pos: new particle(s) out of protective sphere. ' 'radius = %s, d1 = %s, d2 = %s ' % (FORMAT_DOUBLE % radius, FORMAT_DOUBLE % d1, FORMAT_DOUBLE % d2)) return True - - - + ########################## + #### CHECKLIST METHOD #### + ########################## def check(self): ParticleSimulatorBase.check(self) @@ -1721,13 +3819,101 @@ def check(self): self.check_shell_matrix() self.check_domains() self.check_event_stoichiometry() + self.check_particle_consistency() - self.check_obj_for_all() + self.check_domain_for_all() - # - # methods for debugging. - # + ######################################## + #### METHODS FOR LOADING AND SAVING #### + ######################################## + # These are just wrappers around the methods + # in loadsave.py : + + def save_state(self, filename, reload=False, delay=0.0): + """ Save the state of the simulator after bursting + all domains. The latter facilitates reconstruction + of precisely the state saved. + + Arguments: + - filename : a string specifying the name of + the file that the state will be + saved in. + - [reload] : (optional) if this this set + to 'True' the simulator will + be re-initialized with the state + from the save-file ('filename'). + Use this if you experience divergence + between the original and saved + simulations caused by limited + Python float precision. + + reload = False by default. + + - [delay] : (optional) remain idle for a + certain amount of seconds defined + by this parameter before reloading the + saved state. This may be useful to avoid + reloading of a file which has not yet + been completely written. + Only has an effect when reload = True. + """ + loadsave.save_state(self, filename) + + if(reload): + + if(delay > 0.0): + sleep(delay) + + self.load_state(filename, reset_t0=False) + # When reloading to continue the simulation, we + # want to make sure that the recorded starting time + # is not overwritten + + + def load_state(self, filename, reset_t0=True): + """ Load a state previously saved via save_state() + and re-initialize the simulator with the loaded + data. + + Takes the name of the save-file as an argument + of string-type. + + If the optional parameter reset_t0 is set to 'True' + the starting time of the scheduler (self.t0) will be + set to the scheduler time saved in the input file. + It is 'True' by default and typically only set to + 'False' during temporary interrupts with saving and + reloading of the current state. + """ + # Run the load function which will return a + # new world containing the read-in model and + # objects in the right positions and the seed + # that was used to reset the RNG at output + world, seed, time_info = loadsave.load_state(filename) + + if __debug__: + log.info('Loaded state from file %s, re-initializing the simulator...' % filename) + + # Re-initialize the simulator using the seed + # from the input file + self.reset_seed(seed) + self.__init__(world, myrandom.rng, reset=False) + # reset=False ensures the statistics are not lost + + # Set the simulator time to the time it had at output + # This is to avoid divergence between the original and the + # restarted simulation because of the limited floating point + # precision of Python + self.t = time_info[0] + + if reset_t0: + self.t0 = self.t + + + ######################################### + #### METHODS FOR DEBUGGING & TESTING #### + ######################################### def dump_scheduler(self): """Dump scheduler information. @@ -1740,7 +3926,7 @@ def dump(self): """ for id, event in self.scheduler: - print id, event, event.data + print id, event, self.domains[event.data] def count_domains(self): # Returns a tuple (# Singles, # Pairs, # Multis). @@ -1756,14 +3942,24 @@ def count_domains(self): elif isinstance(d, Multi): num_multis += 1 else: - raise RuntimeError('DO NOT GET HERE') + raise RuntimeError('count_domains: domain has unknown type!') return (num_singles, num_pairs, num_multis) + def set_BD_only(self): + + self.BD_ONLY_FLAG = True + + def unset_BD_only(self): + + self.BD_ONLY_FLAG = False + + dispatch = [ - (Single, fire_single), - (Pair, fire_pair), - (Multi, fire_multi) + (Single, process_single_event), + (Pair, process_pair_event), + (Multi, process_multi_event) ] + diff --git a/exceptions.hpp b/exceptions.hpp index 3cd58dcd..abc0d9ed 100644 --- a/exceptions.hpp +++ b/exceptions.hpp @@ -96,6 +96,18 @@ class propagation_error: public std::runtime_error std::string str_; }; +class illegal_propagation_attempt: public std::runtime_error +{ +public: + illegal_propagation_attempt(std::string const& msg): std::runtime_error(msg) {} + // TODO: remove string output once everything works + + virtual ~illegal_propagation_attempt() throw() {} + +private: + std::string str_; +}; + class not_implemented: public std::exception { public: diff --git a/filters.hpp b/filters.hpp index a8cb454d..c7e02730 100644 --- a/filters.hpp +++ b/filters.hpp @@ -34,13 +34,13 @@ class neighbor_filter distance(shape(offset(item.second, off)), cmp_.position())); if (dist < cmp_.radius()) { - next_(i, dist); + next_(i, dist); // put the item in the sorted list of overlapping particles. } } private: - Tfun_& next_; - const sphere_type cmp_; + Tfun_& next_; // structure (overlap checker) storing the overlapping particles. + const sphere_type cmp_; // The spherical particle whose neighbors are being checked. }; template diff --git a/findRoot.cpp b/findRoot.cpp index f46fdf43..b962313c 100644 --- a/findRoot.cpp +++ b/findRoot.cpp @@ -1,3 +1,10 @@ +// Function findRoot iterates the GSL root finder until a root has been found +// with the requested precision. +// +// Author, amongst others: Laurens Bossen. +// FOM Insitute AMOLF + + #ifdef HAVE_CONFIG_H #include #endif /* HAVE_CONFIG_H */ @@ -8,9 +15,12 @@ #include "Logger.hpp" #include "findRoot.hpp" +// Iterates the solver until desired precision has been reached or a maximum +// number of iterations have been performed. Real findRoot(gsl_function const& F, gsl_root_fsolver* solver, Real low, Real high, Real tol_abs, Real tol_rel, char const* funcName) { + // low and high should constitute an interval that straddles a root. Real l(low); Real h(high); @@ -21,13 +31,19 @@ Real findRoot(gsl_function const& F, gsl_root_fsolver* solver, Real low, unsigned int i(0); for (;;) { + + // iterate gsl_root_fsolver_iterate(solver); + + // get found bracketing interval l = gsl_root_fsolver_x_lower(solver); - h = gsl_root_fsolver_x_upper(solver); + h = gsl_root_fsolver_x_upper(solver); + // see if this is acceptable const int status(gsl_root_test_interval(l, h, tol_abs, tol_rel)); + // stop finder if convergence or too much iterations if (status == GSL_CONTINUE) { if (i >= maxIter) @@ -46,6 +62,11 @@ Real findRoot(gsl_function const& F, gsl_root_fsolver* solver, Real low, const Real root(gsl_root_fsolver_root(solver)); - - return root; + + return root; } + + + + + diff --git a/freeFunctions.cpp b/freeFunctions.cpp index e6aabd2e..eb2e4ea7 100644 --- a/freeFunctions.cpp +++ b/freeFunctions.cpp @@ -62,6 +62,301 @@ Real W(Real a, Real b) return std::exp(- a * a) * expxsq_erfc(a + b); } + +/* List of 1D functions used as approximations for the greensfunctions + of the full domain. For small t, a particle only scans part of the domain + and thus not all the boundary conditions have to be included. Because the + greensfunctions converge bad for small t, we use these approximations. + + note: drift not included yet! + + *** Naming: + All 1D function names start with an X, followed by: + + P for position density function. + S for survival probability function. + I for culumative distribution function of position. + + ** Last two numer give boundaries + (By J.V. Beck - Heat Conduction in Green's Functions) + + 0 - No boundary. + 1 - Absorbing boundary. + 2 - Reflective boundary. + 3 - Radiation boundary with rate ka. + + The approximation for the greensfunction with a sink has 3 boundaries: 030 + left absorbing boundary, sink, right absorbing boundary. + */ + +Real XP00( Real r, Real t, Real r0, Real D, Real v ) +{ + const Real fourDt( 4 * D * t ); + const Real rminr0minv2( gsl_pow_2( r - r0 - v * t) ); + + return 1. / sqrt( fourDt * M_PI ) * exp( - rminr0minv2 / fourDt ); +} + + +Real XI00( Real r, Real t, Real r0, Real D, Real v ) +{ + const Real sqrt4Dt( sqrt( 4 * D * t ) ); + const Real rminr0minv( r - r0 - v * t); + + return 0.5 * ( 1.0 + erf( rminr0minv / sqrt4Dt ) ); +} + + +Real XS00( Real t, Real r0, Real D, Real v ) +{ + return 1.0; +} + + +Real XP10( Real r, Real t, Real r0, Real D, Real v ) +{ + const Real fourDt( 4 * D * t ); + const Real rminr02( gsl_pow_2( r - r0 ) ); + const Real rplusr02( gsl_pow_2( r + r0 ) ); + const Real v_2( v /2.0 ); + + Real drift_prefac; + if( v == 0.0 ) + drift_prefac = 1.0; + else + drift_prefac = exp( v_2 / D * ( r - r0 - v_2 * t ) ); + + return drift_prefac / sqrt( fourDt * M_PI ) * + ( exp( - rminr02 / fourDt ) - exp( - rplusr02 / fourDt ) ); +} + + +Real XI10( Real r, Real t, Real r0, Real D, Real v ) +{ + const Real sqrt4Dt( sqrt( 4 * D * t ) ); + + if( v == 0.0 ) + { + const Real rminr0( r - r0 ); + const Real rplusr0( r + r0 ); + + const Real temp( 2.0 * erf( r0 / sqrt4Dt ) + + erf( rminr0/sqrt4Dt ) - erf( rplusr0/sqrt4Dt ) ); + + return 0.5 * temp; + } + else + { + const Real r0minvt( r0 - v*t); + const Real r0plusvt( r0 + v*t ); + + const Real term1( erfc( (r0minvt + r)/sqrt4Dt ) - erfc( r0minvt/sqrt4Dt ) ); + const Real term2( erf( r0plusvt/sqrt4Dt ) - erf( (r0plusvt - r)/sqrt4Dt ) ); + + return 0.5 * ( exp(- v * r0 / D ) * term1 + term2 ); + } +} + + +Real XS10( Real t, Real r0, Real D, Real v ) +{ + const Real sqrt4Dt( sqrt( 4 * D * t ) ); + const Real vt( v * t ); + + if( v == 0.0 ) + return erf( r0 / sqrt4Dt ); + else + { + const Real corr( exp((6*v*r0 - v*vt ) / (4 * D)) ); + //erfc( -(r0 + vt) / sqrt4Dt ) - exp(- v * r0 / D ) * erfc( (r0 - vt) / sqrt4Dt) ); + return 0.5 * corr * W(r0/sqrt4Dt, -vt/sqrt4Dt); + } +} + + +Real XP20( Real r, Real t, Real r0, Real D, Real v ) +{ + const Real fourDt( 4 * D * t ); + const Real sqrt4Dt( fourDt ); + const Real rminr02( gsl_pow_2( r - r0 ) ); + const Real rplusr02( gsl_pow_2( r + r0 ) ); + + const Real XP20_nov( 1. / sqrt( fourDt * M_PI ) * + ( exp( - rminr02 / fourDt ) + + exp( - rplusr02 / fourDt ) ) ); + + if( v == 0.0 ) + { + return XP20_nov; + } + else + { + const Real v_2( v / 2.0 ); + const Real drift_prefac( exp( v_2 / D * ( r - r0 - v_2 * t ) ) ); + + const Real XP20_v( exp(v_2 / D * (r + r0) + v_2 * v_2 / D * t) + * erfc( (r + r0 + v * t)/sqrt4Dt ) ); + + return drift_prefac * ( v_2 / D * XP20_v + XP20_nov ); + } +} + + +Real XI20( Real r, Real t, Real r0, Real D, Real v ) +{ + const Real sqrt4Dt( sqrt( 4 * D * t ) ); + + if( v == 0.0 ) + { + const Real temp( erf( (r - r0)/sqrt4Dt ) + erf( (r + r0)/sqrt4Dt ) ); + return 0.5 * temp; + } + else + { + //TODO: c.d.f. with drift. It exist! + return Real(); + } +} + + +Real XS20( Real t, Real r0, Real D, Real v ) +{ + return 1.0; +} + + +Real XP30term_nov( Real r, Real t, Real r0, Real ka, Real D ) +{ + r0 = fabs( r0 ); + const Real fourDt( 4 * D * t ); + const Real k_D( ka / D ); + const Real rplusr02( gsl_pow_2( r + r0 ) ); + const Real arg( (r + r0)/sqrt(fourDt) + ka * sqrt( t / D ) ); + + return -k_D * exp( - rplusr02 / fourDt ) * expxsq_erfc( arg ); +} + + +Real XP30term_v( Real r, Real t, Real r0, Real ka, Real D, Real v ) +{ + r0 = fabs( r0 ); + const Real sqrt4Dt( sqrt( 4 * D * t ) ); + const Real v_2( v / 2.0 ); + const Real kplusv2( ka + v_2 ); + + const Real erfc_arg( (r + r0 + 2 * kplusv2 * t ) / sqrt4Dt); + + const Real exp_arg( 1.0 / D * ( kplusv2 * kplusv2 * t + + kplusv2 * (r + r0) ) ); + + return -exp( exp_arg ) * erfc( erfc_arg ); +} + + +Real XP30( Real r, Real t, Real r0, Real ka, Real D, Real v ) +{ + r0 = fabs( r0 ); + const Real XP20temp( XP20(r, t, r0, D, 0.0) ); + + if( v == 0.0 ) + return XP20temp + XP30term_nov(r, t, r0, ka, D); + else + { + const Real v_2( v / 2.0 ); + const Real drift_prefac( exp( v_2 / D * ( r - r0 - v_2 * t ) ) ); + + return drift_prefac * ( XP20temp + + 1/D * (ka + v/2) * XP30term_v(r, t, r0, ka, D, v) ); + } +} + + +Real XI30term_nov( Real r, Real t, Real r0, Real ka, Real D ) +{ + const Real sqrt4Dt( sqrt(4 * D * t) ); + r0 = fabs( r0 ); + + const Real term1( erf( r0/sqrt4Dt ) - erf( (r+r0)/sqrt4Dt ) ); + + const Real term2( W( r0/sqrt4Dt, 2*ka*t/sqrt4Dt ) ); + + //exp( k_D * (ka * t + r0) ) * erfc( (2 * ka * t + r0)/sqrt4Dt ) ); + + const Real term3( W( (r + r0)/sqrt4Dt, 2*ka*t/sqrt4Dt ) ); + + //exp( k_D * (ka * t + r0 + r) ) * erfc( (2 * ka * t + r0 + r)/sqrt4Dt ) ); + + return term1 + term2 - term3; +} + + +Real XI30( Real r, Real t, Real r0, Real ka, Real D, Real v ) +{ + r0 = fabs( r0 ); + if( v == 0.0 ) + return XI20(r, t, r0, D, 0.0) + XI30term_nov(r, t, r0, ka, D); + else + //TODO: c.d.f with drift not included yet. Does exist! + return Real(); +} + + +Real XS30( Real t, Real r0, Real ka, Real D, Real v ) +{ + r0 = fabs( r0 ); + const Real sqrt4Dt( sqrt( 4 * D * t ) ); + const Real k_D( ka / D ); + + if( v == 0.0 ) + return erf( r0 / sqrt4Dt ) + W( r0 / sqrt4Dt, 2 * ka * t / sqrt4Dt ); + // exp( k_D * ka * t + k_D * r0 ) * erfc( (2 * ka * t + r0) / sqrt4Dt ); + else + { + const Real v_2( v / 2.0 ); + const Real kplusv2( ka + v_2 ); + const Real r0plusvt( r0 + v * t ); + const Real r0minvt( r0 - v * t ); + + const Real erfc_arg( ( r0 + 2 * kplusv2 * t ) / sqrt4Dt ); + + const Real exp_arg( k_D * ( r0 + (ka + v) * t ) ); + + const Real term2( erfc( -r0plusvt / sqrt4Dt ) - + ka / (ka + v) * exp(- v / D * r0) * erfc( r0minvt/sqrt4Dt ) ); + + return (ka + v_2)/(ka + v) * exp( exp_arg ) * erfc( erfc_arg ) + 0.5 * term2; + } +} + + +Real XP030( Real r, Real t, Real r0, Real ka, Real D ) +{ + r0 = fabs( r0 ); + + return XP00(r, t, r0, D, 0.0) + .5 * XP30term_nov(fabs(r), t, r0, .5 * ka, D); +} + + +Real XI030( Real r, Real t, Real r0, Real ka, Real D ) +{ + ka *= .5; + r0 = fabs( r0 ); + Real sign( 1 ); + + if( r < 0 ) + sign *= -1; + + return XI00(r, t, r0, D, 0.0) + .5 * ( ( XS30(t, r0, ka, D, 0.0) - 1 ) + + sign * XI30term_nov( fabs(r), t, r0, ka, D ) ); +} + + +Real XS030( Real t, Real r0, Real ka, Real D ) +{ + return XS30( t, fabs( r0 ), 0.5 * ka, D, 0.0 ); +} + + Real __p_irr(Real r, Real t, Real r0, Real kf, Real D, Real sigma, Real alpha) { // printf("irrp %.16g %.16g %.16g\n",r,r0,t); @@ -257,7 +552,7 @@ Real ip_theta_free(Real theta, Real r, Real r0, Real t, Real D) return (term1 - term2) / den; } -Real g_bd(Real r, Real sigma, Real t, Real D) +Real g_bd_3D(Real r, Real sigma, Real t, Real D) { const Real Dt4(4.0 * D * t); const Real mDt4_r(- 1.0 / Dt4); @@ -275,8 +570,23 @@ Real g_bd(Real r, Real sigma, Real t, Real D) return 0.5 * (term1 + term2) * r * r; } + +Real g_bd_1D(Real r, Real sigma, Real t, Real D, Real v) +{ + const Real Dt4(4.0 * D * t); + const Real sqrtDt4(std::sqrt(Dt4)); + const Real sqrtDt4_r(1.0 / sqrtDt4); + const Real vt = v*t; + + const Real s_plus_r_plus_vt(sigma + r + vt); + const Real s_min_r_min_vt(sigma - r - vt); + + const Real result(erfl(s_plus_r_plus_vt * sqrtDt4_r) + erfl(s_min_r_min_vt * sqrtDt4_r)); + + return 0.5 * result; +} -Real I_bd(Real sigma, Real t, Real D) +Real I_bd_3D(Real sigma, Real t, Real D) { const Real sqrtPi(std::sqrt(M_PI)); @@ -297,8 +607,27 @@ Real I_bd(Real sigma, Real t, Real D) return result; } +Real I_bd_1D(Real sigma, Real t, Real D, Real v) +{ + if(D == 0) + return 0; + + const Real sqrtPi(std::sqrt(M_PI)); + + const Real Dt4(4 * D * t); + const Real sqrt4Dt(std::sqrt(Dt4)); + Real vt = v*t; + + const Real arg1(-(2*sigma + vt)*(2*sigma + vt)/Dt4); + const Real term1(expl( -vt*vt/Dt4 ) - expl( arg1 )); + const Real term2(vt*erfl( vt/sqrt4Dt ) - (2*sigma + vt)*erfl( (2*sigma + vt)/sqrt4Dt )); + const Real result(1./2*(sqrt4Dt/sqrtPi*term1 + term2 + 2*sigma)); + + return result; + +} -Real I_bd_r(Real r, Real sigma, Real t, Real D) +Real I_bd_r_3D(Real r, Real sigma, Real t, Real D) { const Real sqrtPi(std::sqrt(M_PI)); @@ -334,6 +663,34 @@ Real I_bd_r(Real r, Real sigma, Real t, Real D) return result; } +Real I_bd_r_1D(Real r, Real sigma, Real t, Real D, Real v) +{ + if(D == 0) + return 0; + + const Real sqrtPi(std::sqrt(M_PI)); + + const Real Dt(D * t); + const Real Dt2(Dt + Dt); + const Real Dt4(Dt2 + Dt2); + const Real sqrt4Dt(std::sqrt(Dt4)); + const Real vt = v*t; + + const Real smrmvt_sq(gsl_pow_2(sigma - r - vt)); + const Real sprpvt_sq(gsl_pow_2(sigma + r + vt)); + const Real twospvt_sq(gsl_pow_2(2*sigma + vt)); + + const Real temp1(-expl( -smrmvt_sq/Dt4 ) + expl( -sprpvt_sq/Dt4 )); + const Real temp2(expl( -vt*vt/Dt4 ) - expl( -twospvt_sq/Dt4 )); + const Real term1(sqrt4Dt/sqrtPi*( temp1 + temp2 )); + + const Real term2(vt*erfl(vt/sqrt4Dt) - (2*sigma + vt)*erfl( (2*sigma + vt)/sqrt4Dt )); + const Real term3((r - sigma + vt)*erfl( (sigma - r - vt)/sqrt4Dt )); + const Real term4((r + sigma + vt)*erfl( (r + sigma + vt)/sqrt4Dt )); + const Real result(1./2*(term1 + term2 + term3 + term4)); + + return result; +} struct g_bd_params { @@ -341,28 +698,41 @@ struct g_bd_params const Real t; const Real D; const Real target; + const Real v; }; -static Real I_gbd_r_F(Real r, const g_bd_params* params) +static Real I_gbd_r_3D_F(Real r, const g_bd_params* params) +{ + const Real sigma(params->sigma); + const Real t(params->t); + const Real D(params->D); + const Real target(params->target); + + return I_bd_r_3D(r, sigma, t, D) - target; +} + +static Real I_gbd_r_1D_F(Real r, const g_bd_params* params) { const Real sigma(params->sigma); const Real t(params->t); const Real D(params->D); const Real target(params->target); + const Real v(params->v); - return I_bd_r(r, sigma, t, D) - target; + return I_bd_r_1D(r, sigma, t, D, v) - target; } -Real drawR_gbd(Real rnd, Real sigma, Real t, Real D) + +Real drawR_gbd_3D(Real rnd, Real sigma, Real t, Real D) { - const Real I(I_bd(sigma, t, D)); + const Real I(I_bd_3D(sigma, t, D)); - g_bd_params params = { sigma, t, D, rnd * I }; + g_bd_params params = { sigma, t, D, rnd * I, 0 }; gsl_function F = { - reinterpret_cast(&I_gbd_r_F), + reinterpret_cast(&I_gbd_r_3D_F), ¶ms }; @@ -404,3 +774,54 @@ Real drawR_gbd(Real rnd, Real sigma, Real t, Real D) return low; } + +Real drawR_gbd_1D(Real rnd, Real sigma, Real t, Real D, Real v) +{ + const Real I(I_bd_1D(sigma, t, D, v)); + + g_bd_params params = { sigma, t, D, rnd * I, v }; + + gsl_function F = + { + reinterpret_cast(&I_gbd_r_1D_F), + ¶ms + }; + + Real low(sigma); + Real high(sigma + 100.0 * std::sqrt (2.0 * D * t)); + + const gsl_root_fsolver_type* solverType(gsl_root_fsolver_brent); + gsl_root_fsolver* solver(gsl_root_fsolver_alloc(solverType)); + gsl_root_fsolver_set(solver, &F, low, high); + + const unsigned int maxIter(100); + + unsigned int i(0); + while(true) + { + gsl_root_fsolver_iterate(solver); + + low = gsl_root_fsolver_x_lower(solver); + high = gsl_root_fsolver_x_upper(solver); + int status(gsl_root_test_interval(low, high, 1e-18, 1e-12)); + + if(status == GSL_CONTINUE) + { + if(i >= maxIter) + { + gsl_root_fsolver_free(solver); + throw std::runtime_error("drawR_gbd: failed to converge"); + } + } + else + { + break; + } + + ++i; + } + + gsl_root_fsolver_free(solver); + + return low; +} diff --git a/freeFunctions.hpp b/freeFunctions.hpp index 696cec6e..f7ae61fa 100644 --- a/freeFunctions.hpp +++ b/freeFunctions.hpp @@ -7,6 +7,47 @@ Real expxsq_erfc(Real x); Real W(Real a, Real b); +/* Functions needed in 1D Green's functions. For + explanation, see the cpp file. */ + +Real XP00( Real r, Real t, Real r0, Real D, Real v ); + +Real XI00( Real r, Real t, Real r0, Real D, Real v ); + +Real XS00( Real t, Real r0, Real D, Real v ); + +Real XP10( Real r, Real t, Real r0, Real D, Real v ); + +Real XI10( Real r, Real t, Real r0, Real D, Real v ); + +Real XS10( Real t, Real r0, Real D, Real v ); + +Real XP20( Real r, Real t, Real r0, Real D, Real v ); + +Real XI20( Real r, Real t, Real r0, Real D, Real v ); + +Real XS20( Real t, Real r0, Real D, Real v ); + +Real XP30term_nov( Real r, Real t, Real r0, Real ka, Real D ); + +Real XP30term_v( Real r, Real t, Real r0, Real ka, Real D, Real v ); + +Real XP30( Real r, Real t, Real r0, Real ka, Real D, Real v ); + +Real XI30term_nov( Real r, Real t, Real r0, Real ka, Real D ); + +Real XI30( Real r, Real t, Real r0, Real ka, Real D, Real v ); + +Real XS30( Real t, Real r0, Real ka, Real D, Real v ); + +Real XP030( Real r, Real t, Real r0, Real ka, Real D ); + +Real XI030( Real r, Real t, Real r0, Real ka, Real D ); + +Real XS030( Real t, Real r0, Real ka, Real D ); + +/* 3D functions below. */ + Real p_irr(Real r, Real t, Real r0, Real kf, Real D, Real sigma); Real __p_irr(Real r, Real t, Real r0, Real kf, Real D, Real sigma, Real alpha); @@ -27,12 +68,22 @@ Real p_theta_free(Real theta, Real r, Real r0, Real t, Real D); Real ip_theta_free(Real theta, Real r, Real r0, Real t, Real D); -Real g_bd(Real r0, Real sigma, Real t, Real D); +/* Functions used in old Brownian Dynamic scheme. */ + +Real g_bd_3D(Real r0, Real sigma, Real t, Real D); + +Real I_bd_3D(Real sigma, Real t, Real D); + +Real I_bd_r_3D(Real r, Real sigma, Real t, Real D); + +Real drawR_gbd_3D(Real rnd, Real sigma, Real t, Real D); + +Real g_bd_1D(Real r0, Real sigma, Real t, Real D, Real v); -Real I_bd(Real sigma, Real t, Real D); +Real I_bd_1D(Real sigma, Real t, Real D, Real v); -Real I_bd_r(Real r, Real sigma, Real t, Real D); +Real I_bd_r_1D(Real r, Real sigma, Real t, Real D, Real v); -Real drawR_gbd(Real rnd, Real sigma, Real t, Real D); +Real drawR_gbd_1D(Real rnd, Real sigma, Real t, Real D, Real v); #endif /* FREE_FUNTIONS_HPP */ diff --git a/funcSum.cpp b/funcSum.cpp index 82bac5d0..c570a373 100644 --- a/funcSum.cpp +++ b/funcSum.cpp @@ -74,6 +74,7 @@ funcSum_all_accel(boost::function f, " (rel error: %.16g), terms_used = %d (%d given)", fabs(error), fabs(error / sum), workspace->terms_used, pTable.size()); + // TODO look into this crashing behaviour } gsl_sum_levin_utrunc_free(workspace); @@ -84,8 +85,27 @@ funcSum_all_accel(boost::function f, Real funcSum(boost::function f, size_t max_i, Real tolerance) +// funcSum +// == +// Will simply calculate the sum over a certain function f, until it converges +// (i.e. the sum > tolerance*current_term for a CONVERGENCE_CHECK number of +// terms), or a maximum number of terms is summed (usually 2000). +// +// Input: +// - f: A function object +// - max_i: maximum number of terms it will evaluate +// - tolerance: convergence condition, default value 1-e8 (see .hpp) +// +// About Boost::function +// === +// Boost::function doesn't do any type checking: It will take any object +// and any signature you provide in its template parameter, and create an +// object that's callable according to your signature and calls the object. +// If that's impossible, it's a compile error. +// (From: http://stackoverflow.com/questions/527413/how-boostfunction-and-boostbind-work) { - const unsigned int CONVERGENCE_CHECK(4); + // DEFAULT = 4 + const unsigned int CONVERGENCE_CHECK(4); Real sum(0.0); RealVector pTable; @@ -114,15 +134,14 @@ funcSum(boost::function f, size_t max_i, Real tolerance) if (fabs(sum) * tolerance >= fabs(p_i)) // '=' is important { - ++convergenceCounter; + ++convergenceCounter; } - /* // this screws it up; why? else { convergenceCounter = 0; } - */ + if (convergenceCounter >= CONVERGENCE_CHECK) { @@ -138,7 +157,7 @@ funcSum(boost::function f, size_t max_i, Real tolerance) gsl_sum_levin_utrunc_workspace* workspace(gsl_sum_levin_utrunc_alloc(i)); gsl_sum_levin_utrunc_accel(&pTable[0], pTable.size(), workspace, - &sum, &error); + &sum, &error); if (fabs(error) >= fabs(sum * tolerance * 10)) { _log.error("series acceleration error: %.16g" diff --git a/geometry.hpp b/geometry.hpp index eccc3223..87cc0c09 100644 --- a/geometry.hpp +++ b/geometry.hpp @@ -8,14 +8,15 @@ template< typename T1_, typename T2_ > inline typename element_type_of< T1_ >::type distance( - T1_ const& p1, T2_ const p2, + T1_ const& p1, + T2_ const& p2, typename boost::enable_if< typename boost::mpl::and_< is_vector3, is_vector3 > >::type* = 0) { return std::sqrt( - gsl_pow_2( p1[0] - p2[0] ) + gsl_pow_2( p1[0] - p2[0] ) + gsl_pow_2( p1[1] - p2[1] ) + gsl_pow_2( p1[2] - p2[2] ) ); } @@ -51,7 +52,11 @@ inline T_ normalize(T_ const& p, * note that the returned transposed pos1 may not be within the cyclic boundary. */ template -inline T_ cyclic_transpose(T_ const& p0, T_ const& p1, T_ const& world_size, typename boost::enable_if >::type*) +inline T_ cyclic_transpose(T_ const& p0, + T_ const& p1, + T_ const& world_size, + typename boost::enable_if >::type*) +// Used when T_ is only a number (one dimensionally) { const T_ diff(p1 - p0), half(world_size / 2); if (diff > half) @@ -69,7 +74,11 @@ inline T_ cyclic_transpose(T_ const& p0, T_ const& p1, T_ const& world_size, typ } template -inline T_ cyclic_transpose(T_ const& p0, T_ const& p1, typename element_type_of::type const& world_size, typename boost::enable_if >::type*) +inline T_ cyclic_transpose(T_ const& p0, + T_ const& p1, + typename element_type_of::type const& world_size, + typename boost::enable_if >::type*) +// Used when the arguments are vectors of length three { T_ retval; retval[0] = cyclic_transpose(p0[0], p1[0], world_size, (void*)0); @@ -79,11 +88,17 @@ inline T_ cyclic_transpose(T_ const& p0, T_ const& p1, typename element_type_of< } template -inline T1_ cyclic_transpose(T1_ const& p0, T1_ const& p1, T2_ const& world_size) +inline T1_ cyclic_transpose(T1_ const& p0, + T1_ const& p1, + T2_ const& world_size) +// Used when last argument is not given { return cyclic_transpose(p0, p1, world_size, (void*)0); } + + + template inline T_ apply_boundary(T_ const& p1, T_ const& world_size, typename boost::enable_if >::type*) { diff --git a/gfrdbase.py b/gfrdbase.py index 3a25f875..83377093 100644 --- a/gfrdbase.py +++ b/gfrdbase.py @@ -8,6 +8,15 @@ import _gfrd +from _gfrd import ( + CuboidalRegion, + SphericalSurface, + CylindricalSurface, + DiskSurface, + PlanarSurface, + Surface + ) # to perform instance checks + from utils import * import os @@ -24,11 +33,17 @@ 'p_free', 'throw_in_particles', 'place_particle', + 'remove_all_particles', 'NoSpace', 'create_world', + 'create_box', + 'create_rod', + 'periodic_connect', 'ParticleSimulatorBase', - 'get_closest_surface', - 'get_closest_surface_within_radius' + 'get_all_surfaces', + 'get_neighbor_structures', + 'get_closest_structure', + 'create_network_rules_wrapper', ] World = _gfrd.World @@ -70,6 +85,9 @@ def setup_logging(): def p_free(r, t, D): + """ The free Gaussian propagator. + + """ Dt4 = D * t * 4.0 Pi4Dt = numpy.pi * Dt4 rsq = r * r @@ -79,47 +97,112 @@ def p_free(r, t, D): jacobian = 4.0 * numpy.pi * rsq return p * jacobian - -class NoSpace(Exception): - pass -def get_closest_surface(world, pos, ignore): - # Return - # - closest surface - # - distance to closest surface - # - # We can not use matrix_space, it would miss a surface if the - # origin of the surface would not be in the same or neighboring - # cells as pos. - surfaces_and_distances_to_surfaces = [] +def get_all_surfaces(world, pos, ignores=[]): + """ Returns a list of all surfaces in the world, sorted by increasing distance. + The output format is (surface, distance from search position). + + Arguments: + - world + the world that contains the surfaces + - pos + the search position + - ignores (optional) + a list of structure IDs that will define surfaces that will be + ignored in the search + """ + surfaces_distances = [] + + for surf in world.structures: + if isinstance(surf, _gfrd.Surface) and surf.id not in ignores: + + pos_transposed = world.cyclic_transpose(pos, surf.shape.position) + distance = world.distance(surf.shape, pos_transposed) + surfaces_distances.append((surf, distance)) + + return sorted(surfaces_distances, key=lambda surf_and_dist: surf_and_dist[1]) + + +def get_neighbor_structures(world, pos, current_struct_id, ignores=[], structure_class=None): + """ Returns a list of neighboring structures, optionally restricted to type 'structure_class', + sorted by increasing distance from the search position. + The output format is (structure, distance from search position). + + Arguments: + - world + the world that contains the structures + - pos + the search position + - current_struct_id + the structure that the search position is assumed to belong to + (this defines which other structures are 'visible' from that point) + - ignores (optional) + a list of structure IDs that will define structures that will be + ignored in the search; the world's default structure will be ignored + by default + - structure_class (optional) + restrict the search to a certain structure class; must be one of + CuboidalRegion, PlanarSurface, DiskSurface, CylindricalSurface, SphericalSurface + """ + if ignores: + ignore = ignores[0] + # FIXME world.get_close_structures currently only supports one ignored structure (why?). + # In case that there is more than one ignored structure, we have to filter out + # the ignored structures manually afterwards until this is fixed. + else: + ignore = world.get_def_structure_id() - for surface in world.structures: - if isinstance(surface, _gfrd.Surface) and surface.id not in ignore: - pos_transposed = \ - world.cyclic_transpose(pos, surface.shape.position) - distance = world.distance(surface.shape, pos_transposed) - surfaces_and_distances_to_surfaces.append((surface, distance)) + if structure_class is not None: + # The user has specified a restriction of the search to a particular geometric class + assert( structure_class==CuboidalRegion or structure_class==PlanarSurface \ + or structure_class==DiskSurface or structure_class==CylindricalSurface \ + or structure_class==SphericalSurface ) - if surfaces_and_distances_to_surfaces: - return min(surfaces_and_distances_to_surfaces) + structure_distances = [(structure, distance) for ((id, structure), distance) \ + in world.get_close_structures(pos, current_struct_id, ignore) \ + if isinstance(structure, structure_class)] else: - return None, numpy.inf - -def get_closest_surface_within_radius(world, pos, radius, ignore): - # Return: - # - surface within radius or None - # - closest surface (regardless of radius) - # - distance to closest surface + # Take into account all structure types + structure_distances = [(structure, distance) for ((id, structure), distance) \ + in world.get_close_structures(pos, current_struct_id, ignore)] + + # Filter out the ignored structures + filtered_structure_distances = filter(lambda (s, d) : s.id not in ignores, structure_distances) + + return sorted(filtered_structure_distances, key=lambda struct_and_dist: struct_and_dist[1]) + + +def get_closest_structure(world, pos, current_struct_id, ignores=[], structure_class=None): + """ Returns the closest structure of type 'structure_class' (optionally). + The output format is (structure, distance from search position). + + Arguments: + - world + the world that contains the structures + - pos + the search position + - current_struct_id + the structure that the search position is assumed to belong to + (this defines which other structures are 'visible' from that point) + - ignores (optional) + a list of structure IDs that will define structures that will be + ignored in the search + - structure_class (optional) + restrict the search to a certain structure class; must be one of + CuboidalRegion, PlanarSurface, CylindricalSurface, DiskSurface, SphericalSurface + """ + sorted_close_structures = \ + get_neighbor_structures(world, pos, current_struct_id, ignores, structure_class) - surface, distance = get_closest_surface(world, pos, ignore) - if distance < radius: - return surface, surface, distance + if sorted_close_structures != []: + return sorted_close_structures[0] else: - return None, surface, distance + return None + def create_world(m, matrix_size=10): - """Create a world object. + """ Create a world object. The world object keeps track of the positions of the particles and the protective domains during an eGFRD simulation. @@ -152,40 +235,281 @@ def create_world(m, matrix_size=10): """ m.set_all_repulsive() - world_region = m.get_structure("world") - if not isinstance(world_region, _gfrd.CuboidalRegion): - raise TypeError("the world should be a CuboidalRegion") - - if not numpy.all(world_region.shape.half_extent == - world_region.shape.half_extent[0]): - raise NotImplementedError("non-cuboidal world is not supported") - world_size = world_region.shape.half_extent[0] * 2 + # make the world + world = _gfrd.World(m.world_size, matrix_size) - world = _gfrd.World(world_size, matrix_size) + # copy all structure_types to the world (including the default one) and set the default id. + for st in m.structure_types: + world.add_structure_type(st) + world_structure_type_id = m.get_def_structure_type_id() + world.set_def_structure_type_id(world_structure_type_id) + # The model (a ParticleModel) now only holds SpeciesTypes + # The SpeciesTYpes hold the information that is required for spatial simulations + # in auxiliairy string fields. This information is here converted to the right + # SpeciesInfo object and added to the world. for st in m.species_types: - try: - structure = st["structure"] - except _gfrd.NotFound: - structure = "world" + if st["structure_type"] == "world": + # The default structure_type + structure_type_id = world_structure_type_id + else: + # Find the corresponding structure_type between all the structure_types known + # FIXME: ugly hack + for stt in m.structure_types: + if st["structure_type"] == stt['name']: + structure_type_id = stt.id + break + else: + raise RuntimeError("StructureType used in Species not found in Model when initializing World.") + world.add_species( _gfrd.SpeciesInfo(st.id, + structure_type_id, float(st["D"]), float(st["radius"]), - structure, float(st["v"]))) - for r in m.structures.itervalues(): - world.add_structure(r) + # make the default structure: A cuboidal region of the default structure_type + # This needs to be done after making the structure_types or add_structure can't + # find the proper structure_type. + x = numpy.repeat(m.world_size / 2, 3) + region = _gfrd.CuboidalRegion("world", world_structure_type_id, world.get_def_structure_id(), _gfrd.Box(x, x)) + world.set_def_structure(region) world.model = m return world + +def create_box(world, structure_type, center, size, one_sided=True): + """ Creates a box of PlanarSurface sections and adds it to the world. + + Returns a list of the IDs of the separate planes in the following + order: [front.id, back.id, left.id, right.id, top.id, bottom.id]. + + Naming convention: + + front = yz-plane intersecting the x-axis at the larger value + back = yz-plane intersecting the x-axis at the smaller value + left = xz-plane intersecting the y-axis at the smaller value + right = xz-plane intersecting the y-axis at the larger value + top = xy-plane intersecting the z-axis at the larger value + bottom = xy-plane intersecting the z-axis at the smaller value + + Arguments: + - world + the world that the geometry is constructed in + - structure_type + the structure type of the planar surfaces that make up + the box + - center + a 3D vector defining the center of the box + - size + a 3D vector defining the box extensions + - one_sided (optional, 'True' by default) + create a box with 'one sided' planes, i.e. particles + can will unbind only towards the interior of the box + """ + + # Assert that the center and size is ok + center = numpy.array(center) + size = numpy.array(size) + assert all(0 < center) and all(center < world.world_size) + assert all(0 < size) and all(size < world.world_size) + assert all(0 < center - size/2.0) and all(center + size/2.0 < world.world_size) + + sid = structure_type.id + name = 'box' + def_struct_id = world.get_def_structure_id() + + if one_sided: + create_planar_surface = model.create_planar_surface + else: + create_planar_surface = model.create_double_sided_planar_surface + + # Create the planes and add them to the world + front = create_planar_surface(sid, name+'_front', [center[0] + size[0]/2, center[1] - size[1]/2, center[2] - size[2]/2], \ + [0, 0, 1], [0, 1, 0], size[2], size[1], def_struct_id) + back = create_planar_surface(sid, name+'_back', [center[0] - size[0]/2, center[1] - size[1]/2, center[2] - size[2]/2], \ + [0, 1, 0], [0, 0, 1], size[1], size[2], def_struct_id) + left = create_planar_surface(sid, name+'_left', [center[0] - size[0]/2, center[1] - size[1]/2, center[2] - size[2]/2], \ + [0, 0, 1], [1, 0, 0], size[2], size[0], def_struct_id) + right = create_planar_surface(sid, name+'_right', [center[0] - size[0]/2, center[1] + size[1]/2, center[2] - size[2]/2], \ + [1, 0, 0], [0, 0, 1], size[0], size[2], def_struct_id) + top = create_planar_surface(sid, name+'_top', [center[0] - size[0]/2, center[1] - size[1]/2, center[2] + size[2]/2], \ + [0, 1, 0], [1, 0, 0], size[1], size[0], def_struct_id) + bottom = create_planar_surface(sid, name+'_bottom',[center[0] - size[0]/2, center[1] - size[1]/2, center[2] - size[2]/2], \ + [1, 0, 0], [0, 1, 0], size[0], size[1], def_struct_id) + + world.add_structure(front) + world.add_structure(back) + world.add_structure(right) + world.add_structure(left) + world.add_structure(top) + world.add_structure(bottom) + + # Update the connectivity container + # This has to follow the plane side convention strictly! + # The convention is: + # + # - neighbor 0 is the one in positive unit_y direction ("top neighbor") + # - neighbor 1 is the one in negative unit_y direction ("bottom neighbor") + # - neighbor 2 is the one in negative unit_x direction ("left neighbor") + # - neighbor 3 is the one in positive unit_x direction ("right neighbor") + # + # See StructureContainer.hpp for more details. + world.connect_structures( left, 3, top, 2) + world.connect_structures( left, 2, bottom, 1) + world.connect_structures( left, 1, back, 2) + world.connect_structures( left, 0, front, 1) + world.connect_structures( right, 3, front, 0) + world.connect_structures( right, 2, back, 3) + world.connect_structures( right, 1, bottom, 0) + world.connect_structures( right, 0, top, 3) + world.connect_structures( top, 1, back, 0) + world.connect_structures( top, 0, front, 3) + world.connect_structures(bottom, 3, front, 2) + world.connect_structures(bottom, 2, back, 1) + + # Return a list containing the IDs of the created planes + # in case that the user wishes to operate on them separately + return [front.id, back.id, left.id, right.id, top.id, bottom.id] + + +def create_rod(world, cyl_structure_type, cap_structure_type, name, \ + position, radius, orientation, length, \ + parent_structure_id = None, \ + place_front_cap = True, place_back_cap = True, \ + front_cap_structure_type = None, back_cap_structure_type = None, \ + front_cap_parent_structure_id = None, back_cap_parent_structure_id = None ): + """ Creates a cylinder with two disk-caps at its ends and adds it to the world. + + The function ensures that the orientation vectors of the disks point + outwards, i.e. away from the cylinder center. + + It returns a structure id which is the structure id of the cylindrical + surface. This can be used to create further sub-structures of the rod, + i.e. a disk acting as a sink on the rod. + + Arguments: + - world + the world that the geometry is constructed in + - cyl_structure_type + the structure type of the cylinder; + this is relevant to properly define interactions + - cap_structure_type + the structure type of the disks representing the caps; + this is relevant to properly define interactions + - name + name of the rod; names of the sub-components will + be created from this + - position + the 3D position vector from which the rod is + constructed; note: this is not the center, but one + of the cylinder ends + - radius + the rod radius; affects both the cylinder and the + cap disks + - orientation + a 3D vector defining the the rod orientation + - length + the rod length + + Optional arguments: + - parent_structure_id + if the rod is not in the topmost structure hierarchy, + i.e. substructure of the bulk, the parent structure can + be specified here by the user. Only rarely needed. + - place_front_cap + True by default + - place_back_cap + True by default + - front_cap_structure_type + overrides the default structure type for the front cap + - back_cap_structure_type + same for the back cap + - front_cap_parent_structure + overrides the default parent structure (= the created rod) + of the front cap + - back_cap_parent_structure + same for the back cap + """ + # Assert that the location and size is ok + position = numpy.array(position) + orientation = numpy.array(orientation) + assert all(0 < position) and all(position < world.world_size) + assert (radius < world.world_size/2) + assert (length < world.world_size) + + # Some abbreviations + cyl_sid = cyl_structure_type.id + cap_sid = cap_structure_type.id + def_struct_id = world.get_def_structure_id() + p = position + o = orientation + l = length + + front_cap_sid = cap_sid + back_cap_sid = cap_sid + + if front_cap_structure_type: + front_cap_sid = front_cap_structure_type.id + if back_cap_structure_type: + back_cap_sid = back_cap_structure_type.id + + parent_id = def_struct_id + if parent_structure_id: + parent_id = parent_structure_id + + # Create the cylinder of the rod + rod = model.create_cylindrical_surface(cyl_sid, name+'_cylinder', \ + position, radius, orientation, length, parent_id) + world.add_structure(rod) # This must happen directly here otherwise rod.id will be undefined + + # By default the parent structure of the caps is the rod: + front_cap_parent_id = rod.id + back_cap_parent_id = rod.id + # Check whether the user wishes to make the caps child structures of sth. else than the rod + if front_cap_parent_structure_id: + front_cap_parent_id = front_cap_parent_structure_id + # same for the other cap + if back_cap_parent_structure_id: + back_cap_parent_id = back_cap_parent_structure_id + + # Create the caps + if place_front_cap: + front_cap_pos = [p[0]+l*o[0], p[1]+l*o[1], p[2]+l*o[2]] + front_cap = model.create_disk_surface(front_cap_sid, name+'_front_cap', front_cap_pos, \ + radius, orientation, front_cap_parent_id) + world.add_structure(front_cap) + + if place_back_cap: + back_cap_pos = position + back_cap = model.create_disk_surface(back_cap_sid, name+'_back_cap', back_cap_pos, \ + radius, [-o[0],-o[1],-o[2]], back_cap_parent_id) + world.add_structure(back_cap) + + return rod.id + + +def periodic_connect(world, planar_surface): + """ Makes connections between the opposite edges of a plane. + + Use this to simulate particles on an infinite plane + with periodic boundary conditions. + + """ + #world.connect_structures(planar_surface, 0, planar_surface, 1) + #world.connect_structures(planar_surface, 2, planar_surface, 3) + pass + # FIXME DOES NOT YET WORK CORRECTLY, THE STRUCTURE CONTAINER IS ASSUMING TRANSFER TO ADJACENT PLANES ONLY! + + def create_network_rules_wrapper(model): return _gfrd.NetworkRulesWrapper(model.network_rules) -def throw_in_particles(world, sid, n): + +def throw_in_particles(world, sid, n, bound_1=[0,0,0], bound_2=[0,0,0], ignore_structure_overlaps=False): """Add n particles of a certain Species to the specified world. Arguments: @@ -195,48 +519,99 @@ def throw_in_particles(world, sid, n): - n the number of particles to add. + Optional arguments: + - bound_1 + a 3D vector specifying corner 1 of a bounding box + that defines a rectangular region to which the placement + of the particles is restricted. + All vector components must be >=0 and <=world_size. + - bound_2 + a 3D vector specifying corner 2 of the bounding box. + All vector components must be bigger than the components + of bound_1 and <=world_size. + Make sure to first add the Species to the model with the method model.ParticleModel.add_species_type. """ species = world.get_species(sid) - structure = world.get_structure(species.structure_id) + structure_type = world.get_structure_type(species.structure_type_id) + structure_list = list(world.get_structure_ids(structure_type)) + + ws = world.world_size + + if species.radius >= 0.05 * ws: + log.warn('Particle radius >= 0.05*world_size - this may break simulation efficiency!') + + # For comparison purposes: + bound_1 = numpy.array(bound_1) + bound_2 = numpy.array(bound_2) + + if all(bound_1 == bound_2): + + # Don't allow a zero bounding box + bound_1 = numpy.array([0,0,0]) + bound_2 = numpy.array([ws,ws,ws]) + + log.info('\n\tZero bounding box; setting bound_1 = %s and bound_2 = %s' % (bound_1, bound_2) ) + + correct_bounding_box = ( all(bound_1 >= 0) and all(bound_1 - bound_2 < 0) and all(bound_2 - [ws,ws,ws] <= 0) ) if __debug__: - name = world.model.get_species_type_by_id(sid)["name"] - if name[0] != '(': - name = '(' + name + ')' - log.info('\n\tthrowing in %s particles of type %s to %s' % - (n, name, structure.id)) - # This is a bit messy, but it works. + if not correct_bounding_box: + log.error('\n\tIncorrect bounding box!') + else: + name = world.model.get_species_type_by_id(sid)["name"] + if name[0] != '(': + name = '(' + name + ')' + log.info('\n\tthrowing in %s particles of type %s to %s' % (n, name, structure_type.id)) + log.info('\n\tbounding box = (%s, %s)' % (bound_1, bound_2)) + + assert(correct_bounding_box) + i = 0 while i < int(n): + # This is messy, but it works. + myrandom.shuffle(structure_list) + structure = world.get_structure(structure_list[0]) position = structure.random_position(myrandom.rng) - position = apply_boundary(position, world.world_size) - - # Check overlap. - if not world.check_overlap((position, species.radius)): - create = True - # Check if not too close to a neighbouring structures for - # particles added to the world, or added to a self-defined - # box. - if isinstance(structure, _gfrd.CuboidalRegion): - surface, distance = get_closest_surface(world, position, []) - if(surface and - distance < surface.minimal_distance(species.radius)): - if __debug__: - log.info('\t%d-th particle rejected. Too close to ' - 'surface. I will keep trying.' % i) - create = False - if create: - # All checks passed. Create particle. - p = world.new_particle(sid, position) - i += 1 - if __debug__: - log.info('(%s,\n %s' % (p[0], p[1])) + + # Check immediately whether the position is out of the bounding box + # If yes, discard before applying any boundary conditions + out_of_bounds = ( any(position < bound_1) or any(position > bound_2) ) + if out_of_bounds: + log.info('random position rejected. Out of bounding box. I will keep trying.') + continue + + # OK, now apply boundary conditions + position, structure_id = world.apply_boundary((position, structure.id)) + + # Check overlap. TODO put in 'if' statement for improved efficiency? + particle_overlaps = world.check_overlap((position, species.radius)) + surface_overlaps = world.check_surface_overlap((position, species.radius*MINIMAL_SEPARATION_FACTOR), + position, structure_id, species.radius) + + # Check again whether we are within the imposed bounds + out_of_bounds = ( any(position < bound_1) or any(position > bound_2) ) + + if ((not particle_overlaps) and ((not surface_overlaps) or ignore_structure_overlaps)) \ + and (not out_of_bounds): + # All checks passed. Create particle. + p = world.new_particle(sid, structure_id, position) + i += 1 + if __debug__: + log.info('particle accepted: (%s,\n %s)' % (p[0], p[1])) + if ignore_structure_overlaps: + log.warn('explicitly ignoring structure overlaps.') elif __debug__: - log.info('\t%d-th particle rejected. I will keep trying.' % i) + if particle_overlaps: + log.info('\t%d-th particle rejected. Too close to particle. I will keep trying.' % i) + if surface_overlaps: + log.info('\t%d-th particle rejected. Too close to surface. I will keep trying.' % i) + if out_of_bounds: + log.info('\t%d-th particle rejected. Out of bounding box. I will keep trying.' % i) + def place_particle(world, sid, position): """Place a particle of a certain Species at a specific position in @@ -253,35 +628,136 @@ def place_particle(world, sid, position): model.ParticleModel.add_species_type. """ + if __debug__: + name = world.model.get_species_type_by_id(sid)["name"] + if name[0] != '(': + name = '(' + name + ')' + log.info('Attempting to place particle of species %s at position %s' % + (name, position) ) + species = world.get_species(sid) - structure = world.get_structure(species.structure_id) radius = species.radius + if radius >= 0.05 * world.world_size: + log.warn('Particle radius >= 0.05*world_size - this may break simulation efficiency!') + + # FIXME This is a mess!! + + # check that particle doesn't overlap with other particles. if world.check_overlap((position, radius)): - raise NoSpace, 'overlap check failed' + raise NoSpace, ' Placing particle failed: overlaps with other particle.' + + # Get the IDs of all structures of the structure type that this particle species lives on + structure_ids = world.get_structure_ids(world.get_structure_type(species.structure_type_id)) + + # Get all neighboring surfaces ordered by their distance to the particle + surfaces = get_all_surfaces(world, position, []) + + # If this particle does not live on the default structure first check whether + # the position is indeed within a structure of the right structure type + if species.structure_type_id != world.get_def_structure_type_id(): + + if surfaces: + # Get the closest surface of the structure type + # for this particle species. We cannot simply take + # the closest structure because for a disk-bound + # particle this can be either the disk or cylinder + # => choose the one with the right structure type + for s in range(0, len(surfaces)): + surface, distance = surfaces[s] + + if surface.id in structure_ids: + # we have found the closest surface with + # the required structure type + break; + else: + # there is no such structure, placement + # will be impossible + surface, distance = None, numpy.inf + + if __debug__ and surface == None: + log.warning(' Could not find any structure with required structure type in the system.') + else: + # no structure around, we can't place the particle + surface, distance = None, numpy.inf + if __debug__: + log.warning(' No surfaces around, cannot place particle of surface-bound species %s.' % str(species)) - # Check if not too close to a neighbouring structures for particles + # Check if not too close to neighbouring structures for particles # added to the world, or added to a self-defined box. - if isinstance(structure, _gfrd.CuboidalRegion): - surface, distance = get_closest_surface(world, position, []) - if(surface and - distance < surface.minimal_distance(species.radius)): - raise RuntimeError('Placing particle failed: %s %s. ' - 'Too close to surface: %s.' % - (sid, position, distance)) + if species.structure_type_id == world.get_def_structure_type_id(): + + structure_id = world.get_def_structure_id() + + if surfaces: + + surface, distance = surfaces[0] # the closest surface + overlaps = world.check_surface_overlap((position, radius*MINIMAL_SEPARATION_FACTOR), + position, structure_id, radius) + + # Raise an error in case of overlap with a surface other than PlanarSurface + # (the latter we allow to overlap with bulk particles) + if overlaps and not isinstance(surface, PlanarSurface): + raise RuntimeError(' Placing particle failed: %s %s. ' + ' Too close to surface: %s.' % + (sid, position, surface) ) + + if overlaps and isinstance(surface, PlanarSurface): + log.warning(' Particle of species %s placed at position %s overlaps with surface %s.' % \ + (sid, position, surface) ) + else: + # If the particle lives on a surface then the position should be in the closest surface. + # The closest surface should also be of the structure_type associated with the species. + if not (surface and + distance < TOLERANCE*radius and + surface.id in structure_ids): + raise RuntimeError(' Placing particle failed: %s %s. Position should be in structure of structure_type \"%s\" (distance to %s = %s).' % + (sid, position, world.get_structure_type(species.structure_type_id)['name'], surface, distance) ) + else: + structure_id = surface.id if __debug__: - species = world.get_species(sid) - structure = world.get_structure(species.structure_id) name = world.model.get_species_type_by_id(sid)["name"] if name[0] != '(': name = '(' + name + ')' - log.info('\n\tplacing particle of type %s to %s at position %s' % - (name, structure.id, position)) + log.info(' Placed particle of species %s on structure \"%s\" at position %s' % + (name, world.get_structure(structure_id).name, position)) + + position, structure_id = world.apply_boundary((position, structure_id)) + particle = world.new_particle(sid, structure_id, position) + + if __debug__: + log.info(' Particle info: (%s, %s)' % (particle[0], particle[1]) ) - particle = world.new_particle(sid, position) return particle + +def remove_all_particles(world, species=None): + + if species == None: + + all_particles = list(world) + # Keep in mind that the entries of this list + # are actually pid_particle_pairs + + for p in all_particles: + + world.remove_particle(p[0]) + # p[0] is the particle ID, see comment above + else: + + for pid in world.get_particle_ids(species): + + world.remove_particle(pid) + + +class DomainEvent(_gfrd.Event): + __slot__ = ['data'] + def __init__(self, time, domain): + _gfrd.Event.__init__(self, time) + self.data = domain.domain_id # Store the domain_id key refering to the domain + # in domains{} in the scheduler + class ParticleSimulatorBase(object): def __init__(self, world, rng, network_rules): self.world = world @@ -309,16 +785,99 @@ def __init__(self, world, rng, network_rules): self.last_reaction = None + # control bounds + # to allow the user to check whether particles leak out + # of certain parts of the system + self.control_bounds = [] + def initialize(self): pass + def reset_seed(self, s): + + self.rng.seed(s) + def get_species(self): + """ + Return an iterator over the Species in the simulator. + To be exact, it returns an iterator that returns + all SpeciesInfo instances defined in the simulator. + + Arguments: + - sim an EGFRDSimulator. + + More on output: + - SpeciesInfo is defined in the C++ code, and has + the following attributes that might be of interest + to the user: + * SpeciesInfo.id + * SpeciesInfo.radius + * SpeciesInfo.structure_type_id + * SpeciesInfo.D + * SpeciesInfo.v + + Example: + + for species in s.get_species(): + print str(species.radius) + + Note: + Dumper.py also holds a copy of this function. + + """ #TODO: Added by wehrens@amolf.nl; please revise return self.world.species + def get_structure_types(self): + """ + Return an iterator over the StructureTypes in the simulator. + + Structure types basically have only an id and a name, the + latter of which is accessible via a dictionary lookup: + + e.g. structure_type['name'] = 'membrane' + + Arguments: + - sim an EGFRDSimulator. + + """ + return self.world.structure_types + + def get_structures(self): + """ + Return an iterator over the Structures in the simulator. + + Arguments: + - sim an EGFRDSimulator. + + """ + return self.world.structures + def get_first_pid(self, sid): + """ + Returns the first particle ID of a certain species. + + Arguments: + - sid: a(n) (eGFRD) simulator. + """ #TODO: Added by wehrens@amolf.nl; please revise return iter(self.world.get_particle_ids(sid)).next() def get_position(self, object): + """ + Function that returns particle position. Can take multiple + sorts of arguments as input. + + Arguments: + - object: can be: + * pid_particle_pair tuple + (of which pid_particle_pair[0] is _gfrd.ParticleID). + * An instance of _gfrd.ParticleID. + + * An instance of _gfrd.Particle. + * An instance of _gfrd.SpeciesID. + In the two latter cases, the function returns the + first ID of the position of the first particle of + the given species. + """ #TODO: Added by wehrens@amolf.nl; please revise if type(object) is tuple and type(object[0]) is _gfrd.ParticleID: pid = object[0] elif type(object) is _gfrd.ParticleID: @@ -361,3 +920,41 @@ def check(self): def print_report(self): pass + def add_control_bound(self, corner1, corner2): + """ + Adds a control bounding box to the system. + + Given that logging is on, the system will warn + whenever particles leave the predefined bounds. + + Arguments: + + - corner1 + 3D array defining the lower-left corner + of the bounding box + + - corner2 + 3D array defining the upper right corner + of the bounding box + + Make sure you pass it in the right format. + """ + + assert len(corner1)==3 and len(corner2)==3 + + if all([c > 0.0 for c in numpy.subtract(corner2, corner1)]): + + self.control_bounds.append((corner1, corner2)) + if __debug__: + log.info('Added control bound %s' % str((corner1, corner2)) ) + log.info('Control bounds list: %s' % str(self.control_bounds) ) + + else: + + raise RuntimeError('Illegal control bounds; make sure ' + 'all corner coordinates are positive ' + 'and corner2 coordinates are larger than corner1 coordinates.') + +class NoSpace(Exception): + pass + diff --git a/greens_function_wrapper.py b/greens_function_wrapper.py index 16ad17dc..ba259ba6 100644 --- a/greens_function_wrapper.py +++ b/greens_function_wrapper.py @@ -12,7 +12,7 @@ def draw_time_wrapper(gf): rnd = myrandom.uniform() if __debug__: - log.debug(' *drawTime. ' + gf.__class__.__name__) + log.debug(' * drawTime: ' + gf.__class__.__name__) try: dt = gf.drawTime(rnd) except Exception, e: @@ -25,7 +25,7 @@ def draw_event_type_wrapper(gf, dt): rnd = myrandom.uniform() if __debug__: - log.debug(' *drawEventType. ' + gf.__class__.__name__) + log.debug(' * drawEventType: ' + gf.__class__.__name__) try: event_type = gf.drawEventType(rnd, dt) except Exception, e: @@ -38,12 +38,12 @@ def draw_r_wrapper(gf, dt, a, sigma=None): rnd = myrandom.uniform() if __debug__: - log.debug(' *drawR. ' + gf.__class__.__name__) + log.debug(' * drawR: ' + gf.__class__.__name__) try: r = gf.drawR(rnd, dt) while r > a or r <= sigma: # redraw; shouldn't happen often if __debug__: - log.debug(' *drawR: redraw') + log.debug(' * drawR: redraw') rnd = myrandom.uniform() r = gf.drawR(rnd, dt) except Exception, e: @@ -57,16 +57,20 @@ def draw_theta_wrapper(gf, r, dt): """Draw theta for the inter-particle vector. """ + # We're interested in value between [-pi,pi], but this problem is symmetric: + # the cumulative pdf is odd. Thus drawTheta() returns a value between + # [0,pi], which is then with a 50% probability made negative or kept + # positive. rnd = myrandom.uniform() if __debug__: - log.debug(' *drawTheta. ' + gf.__class__.__name__) + log.debug(' * drawTheta: ' + gf.__class__.__name__) try: theta = gf.drawTheta(rnd, r, dt) except Exception, e: - raise Exception('gf.drawTheta() failed, ' - '%s, rnd = %g, r = %g, dt = %g' % - (str(e), rnd, r, dt))#, gf.dump())) + print 'gf.drawTheta() failed, %s, rnd = %g, r = %g, dt = %g; %s' %(str(e), rnd, r, dt, gf.dump()) + #ugly hack: When drawTheta fails, return uniformly distributed theta. + theta = rnd * numpy.pi # Heads up. For cylinders theta should be between [-pi, pi]. For # spheres it doesn't matter. diff --git a/greens_functions.cpp b/greens_functions.cpp index ca2d0375..3f31cde5 100644 --- a/greens_functions.cpp +++ b/greens_functions.cpp @@ -7,6 +7,9 @@ #include "freeFunctions.hpp" #include "GreensFunction1DAbsAbs.hpp" #include "GreensFunction1DRadAbs.hpp" +#include "GreensFunction1DAbsSinkAbs.hpp" +#include "GreensFunction2DAbsSym.hpp" +#include "GreensFunction2DRadAbs.hpp" #include "GreensFunction3DSym.hpp" #include "GreensFunction3DAbsSym.hpp" #include "GreensFunction3DRadInf.hpp" @@ -20,18 +23,33 @@ BOOST_PYTHON_MODULE( _greens_functions ) //import_array(); // free functions + def( "XP030", XP030 ); + def( "XS030", XS030 ); + def( "XI030", XI030 ); + + def( "XP30", XP30 ); + def( "XI30", XI30 ); + def( "XS30", XS30 ); + + def( "XP10", XP10 ); + def( "XI10", XI10 ); + def( "XS10", XS10 ); + def( "p_irr", p_irr ); def( "p_survival_irr", p_survival_irr ); def( "p_theta_free", p_theta_free ); def( "ip_theta_free", ip_theta_free ); - def( "g_bd", g_bd ); - def( "I_bd", I_bd ); - def( "I_bd_r", I_bd_r ); - def( "drawR_gbd", drawR_gbd ); def( "p_reaction_irr", __p_reaction_irr ); def( "p_reaction_irr_t_inf", __p_reaction_irr_t_inf ); + def( "g_bd_1D", g_bd_1D ); + def( "I_bd_1D", I_bd_1D ); + def( "I_bd_r_1D", I_bd_r_1D ); + def( "drawR_gbd_1D", drawR_gbd_1D ); + def( "g_bd_3D", g_bd_3D ); + def( "I_bd_3D", I_bd_3D ); + def( "I_bd_r_3D", I_bd_r_3D ); + def( "drawR_gbd_3D", drawR_gbd_3D ); - class_("GreensFunction1DAbsAbs", init() ) .def( init()) @@ -50,7 +68,7 @@ BOOST_PYTHON_MODULE( _greens_functions ) .def( "leavea", &GreensFunction1DAbsAbs::leavea ) .def( "p_survival", &GreensFunction1DAbsAbs::p_survival ) .def( "calcpcum", &GreensFunction1DAbsAbs::calcpcum ) - .def( "dump", &GreensFunction1DRadAbs::dump ) + .def( "dump", &GreensFunction1DAbsAbs::dump ) ; class_("GreensFunction1DRadAbs", @@ -75,6 +93,73 @@ BOOST_PYTHON_MODULE( _greens_functions ) .def( "calcpcum", &GreensFunction1DRadAbs::calcpcum ) .def( "dump", &GreensFunction1DRadAbs::dump ) ; + + class_("GreensFunction1DAbsSinkAbs", + init() ) + .def( "getName", &GreensFunction1DAbsSinkAbs::getName ) + .def( "getk", &GreensFunction1DAbsSinkAbs::getk ) + .def( "getD", &GreensFunction1DAbsSinkAbs::getD ) + .def( "getsigma", &GreensFunction1DAbsSinkAbs::getsigma ) + .def( "geta", &GreensFunction1DAbsSinkAbs::geta ) + .def( "getr0", &GreensFunction1DAbsSinkAbs::getr0 ) + .def( "getrsink", &GreensFunction1DAbsSinkAbs::getrsink ) + .def( "drawTime", &GreensFunction1DAbsSinkAbs::drawTime ) + .def( "drawR", &GreensFunction1DAbsSinkAbs::drawR ) + .def( "drawEventType", &GreensFunction1DAbsSinkAbs::drawEventType ) + .def( "flux_tot", &GreensFunction1DAbsSinkAbs::flux_tot ) + .def( "flux_leaves", &GreensFunction1DAbsSinkAbs::flux_leaves ) + .def( "flux_leavea", &GreensFunction1DAbsSinkAbs::flux_leavea ) + .def( "flux_sink", &GreensFunction1DAbsSinkAbs::flux_sink ) + .def( "prob_r", &GreensFunction1DAbsSinkAbs::prob_r ) + .def( "p_int_r", &GreensFunction1DAbsSinkAbs::p_int_r ) + .def( "p_survival", &GreensFunction1DAbsSinkAbs::p_survival ) + .def( "calcpcum", &GreensFunction1DAbsSinkAbs::calcpcum ) + .def( "dump", &GreensFunction1DAbsSinkAbs::dump ) + ; + + class_( "GreensFunction2DAbsSym", + init() ) + .def( "getD", &GreensFunction2DAbsSym::getD ) + .def( "geta", &GreensFunction2DAbsSym::geta ) + .def( "drawTime", &GreensFunction2DAbsSym::drawTime ) + .def( "drawR", &GreensFunction2DAbsSym::drawR ) + .def( "p_survival", &GreensFunction2DAbsSym::p_survival ) + .def( "dump", &GreensFunction2DAbsSym::dump ) + //.def( "p_int_r", &GreensFunction2DAbsSym::p_int_r ) + //.def( "p_int_r_free", &GreensFunction2DAbsSym::p_int_r_free ) + //.def( "p_r_fourier", &GreensFunction2DAbsSym::p_r_fourier ) + ; + + + class_( "GreensFunction2DRadAbs", + init() ) + .def( "geta", &GreensFunction2DRadAbs::geta ) + .def( "getD", &GreensFunction2DRadAbs::getD ) + .def( "getkf", &GreensFunction2DRadAbs::getkf ) + .def( "geth", &GreensFunction2DRadAbs::geth ) + .def( "getSigma", &GreensFunction2DRadAbs::getSigma ) + .def( "drawTime", &GreensFunction2DRadAbs::drawTime ) + .def( "drawEventType", &GreensFunction2DRadAbs::drawEventType ) + .def( "drawR", &GreensFunction2DRadAbs::drawR ) + .def( "drawTheta", &GreensFunction2DRadAbs::drawTheta ) + .def( "getAlpha", &GreensFunction2DRadAbs::getAlpha ) +// .def( "getAlpha0", &GreensFunction2DRadAbs::getAlpha0 ) // LEGACY; TODO REMOVE + .def( "f_alpha", &GreensFunction2DRadAbs::f_alpha ) + .def( "f_alpha0", &GreensFunction2DRadAbs::f_alpha0 ) +// .def( "alpha_i", &GreensFunction2DRadAbs::alpha_i ) // LEGACY; TODO REMOVE + .def( "p_survival", &GreensFunction2DRadAbs::p_survival ) + .def( "leaves", &GreensFunction2DRadAbs::leaves ) + .def( "leavea", &GreensFunction2DRadAbs::leavea ) + .def( "dump", &GreensFunction2DRadAbs::dump ) + // DEBUG: TODO REMOVE? + .def( "givePDFR", &GreensFunction2DRadAbs::givePDFR ) + .def( "givePDFTheta", &GreensFunction2DRadAbs::givePDFTheta ) + .def( "dumpRoots", &GreensFunction2DRadAbs::dumpRoots ) + ; class_("GreensFunction3DSym", init()) .def( "getName", &GreensFunction3DSym::getName ) @@ -97,6 +182,7 @@ BOOST_PYTHON_MODULE( _greens_functions ) .def( "p_int_r", &GreensFunction3DAbsSym::p_int_r ) .def( "p_int_r_free", &GreensFunction3DAbsSym::p_int_r_free ) //.def( "p_r_fourier", &GreensFunction3DAbsSym::p_r_fourier ) + .def( "dump", &GreensFunction3DAbsSym::dump ) ; class_("GreensFunction3DRadInf", @@ -140,10 +226,10 @@ BOOST_PYTHON_MODULE( _greens_functions ) ; enum_("PairEventKind") - .value( "IV_ESCAPE", GreensFunction3DRadAbs::IV_ESCAPE ) - .value( "IV_REACTION", GreensFunction3DRadAbs::IV_REACTION ) - .value( "IV_ESCAPE", GreensFunction1DRadAbs::IV_ESCAPE ) - .value( "IV_REACTION", GreensFunction1DRadAbs::IV_REACTION ) + .value( "IV_ESCAPE", GreensFunction::IV_ESCAPE ) + .value( "IV_REACTION", GreensFunction::IV_REACTION ) +// .value( "IV_ESCAPE", GreensFunction1DRadAbs::IV_ESCAPE ) +// .value( "IV_REACTION", GreensFunction1DRadAbs::IV_REACTION ) ; class_("GreensFunction3DRadAbs", @@ -210,4 +296,7 @@ BOOST_PYTHON_MODULE( _greens_functions ) .def( "dump", &GreensFunction3DAbs::dump ) ; + + + } diff --git a/histograms.py b/histograms.py new file mode 100644 index 00000000..9ebbd878 --- /dev/null +++ b/histograms.py @@ -0,0 +1,185 @@ +#!/usr/env python + +import math +import numpy + +from single import * +from pair import * +from multi import * + +__all__ = [ 'Histogram3D', 'DomainsHistogramCollection' ] + + +class Histogram3D(object): + + def __init__(self, nbins, dimensions, name='noname'): + + self.name = str(name) + + self.nbins = nbins + self.dimensions = dimensions + self.histogram = numpy.zeros(nbins) + self.total_cnt = 0 + + self.binsizes = self.calculate_binsizes() + + self.normalized = False + + + def calculate_binsizes(self): + + binsizes = numpy.zeros(3) + + for d in [0, 1, 2]: + + binsizes[d] = 1.0 * self.dimensions[d] / self.nbins[d] + + if not all([bs>0 for bs in binsizes]): + raise HistogramError('At least one bin size was zero in histogram %s.' % self.__str__() ) + + return binsizes + + + def bin(self, data): + + index = [-1, -1, -1] + + for d in [0, 1, 2]: + + b = int(data[d] / self.binsizes[d]) + + if b >= 0 and b < self.nbins[d]: + + index[d] = b + + ilist = [index[0], index[1], index[2]] + + if all([i>=0 and i<=self.nbins for i in ilist]): + + [ix, iy, iz] = ilist + self.histogram[ix][iy][iz] = self.histogram[ix][iy][iz] + 1 + + self.total_cnt = self.total_cnt + 1 + + + def normalize(self): + + if not all([bs>0 for bs in self.binsizes]): + raise HistogramError('At least one bin size was zero in histogram %s.' % self.__str__() ) + + if not self.total_cnt > 0: + raise HistogramError('No counts in histogram %s, cannot normalize!' % self.__str__() ) + + binvolume = self.binsizes[0] * self.binsizes[1] * self.binsizes[2] + normfactor = self.total_cnt * binvolume + + for i0 in range(0, self.nbins[0]): + for i1 in range(0, self.nbins[1]): + for i2 in range(0, self.nbins[2]): + + self.histogram[i0][i1][i2] = self.histogram[i0][i1][i2] / normfactor + + + def integrate(self): + + binvolume = self.binsizes[0] * self.binsizes[1] * self.binsizes[2] + + I = 0.0 + for i0 in range(0, self.nbins[0]): + for i1 in range(0, self.nbins[1]): + for i2 in range(0, self.nbins[2]): + + I = I + self.histogram[i0][i1][i2] * binvolume + + return I + + + def check(self): + + I = self.integrate() + + if not feq(I, 1.0): + + raise HistogramError( 'Integrated density does not equal 1 in histogram %s; integral = %s.' % (self.__str__(), I) ) + + + def __str__(self): + + if self.name != '': + + return self.name + + else: + return 'Histogram3D' + + + +class DomainsHistogramCollection(object): + + def __init__(self, world, nbins, name='noname'): + + self.name = name + + self.world = world + self.ws = world.world_size + + self.nbins = nbins + + self.SinglesHistogram = Histogram3D([self.nbins, self.nbins, self.nbins], [self.ws, self.ws, self.ws], name='SinglesHistogram') + self.PairsHistogram = Histogram3D([self.nbins, self.nbins, self.nbins], [self.ws, self.ws, self.ws], name='PairsHistogram') + self.MultisHistogram = Histogram3D([self.nbins, self.nbins, self.nbins], [self.ws, self.ws, self.ws], name='MultisHistogram') + self.NonMultisHistogram = Histogram3D([self.nbins, self.nbins, self.nbins], [self.ws, self.ws, self.ws], name='NonMultisHistogram') + + + def bin_domain(self, domain): + + if isinstance(domain, Single): + + pos = domain.pid_particle_pair[1].position + + self.SinglesHistogram.bin(pos) + self.NonMultisHistogram.bin(pos) + + elif isinstance(domain, Pair): + + pos1 = domain.pid_particle_pair1[1].position + pos2 = domain.pid_particle_pair2[1].position + + for pos in [pos1, pos2]: + self.PairsHistogram.bin(pos) + self.NonMultisHistogram.bin(pos) + + elif isinstance(domain, Multi): + + for pid_particle_pair in domain.particle_container: + + pos = pid_particle_pair[1].position + self.MultisHistogram.bin(pos) + + else: + raise HistogramError( 'Cannot classify domain type for binning, domain = %s' % str(domain) ) + + + def normalize(self): + + for h in [self.SinglesHistogram, self.PairsHistogram, self.MultisHistogram, self.NonMultisHistogram]: + + try: + + h.normalize() + h.check() + + except HistogramError as e: + + print "Warning: in %s: %s" % (self.__str__(), str(e)) + + + def __str__(self): + + return 'DomainsHistogramCollection \'' + str(self.name) + '\'' + + + +class HistogramError(Exception): + pass + diff --git a/linear_algebra.hpp b/linear_algebra.hpp index 701123d5..c7d64c52 100644 --- a/linear_algebra.hpp +++ b/linear_algebra.hpp @@ -5,7 +5,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/loadsave.py b/loadsave.py new file mode 100644 index 00000000..bf7dfc45 --- /dev/null +++ b/loadsave.py @@ -0,0 +1,1028 @@ +#!/usr/env python + +import sys +import math +import numpy + +from array import * + +import ConfigParser as CP + +from _gfrd import ( + Event, + EventScheduler, + Particle, + SphericalShell, + CylindricalShell, + DomainIDGenerator, + ShellIDGenerator, + DomainID, + ParticleContainer, + CuboidalRegion, + SphericalSurface, + CylindricalSurface, + DiskSurface, + PlanarSurface, + Surface, + _random_vector, + Cylinder, + Disk, + Sphere, + Plane, + NetworkRulesWrapper, + ) + +from gfrdbase import * +from gfrdbase import DomainEvent +from utils import * +import model +import _gfrd + +import logging + + +__all__ = [ 'save_state', 'load_state' ] + + +log = logging.getLogger('ecell') + + +def save_state(simulator, filename): + + + #### BURST ALL DOMAINS #### + # This is an important precondition for this + # save routine to work properly. + # In particular running this without bursting + # all the domains will mess up the EventScheduler + # because it will reconstruct it with different + # event IDs + + if __debug__: + log.info('Attempting to save the state of the simulator: running burst_all_domains()') + + simulator.burst_all_domains() + + # Re-seed the random number generator + # (with a randomly drawn seed) + # This seed has to be saved and used to + # (re)seed the newly setup simulator when + # loading to ensure that we will reproduce + # the same trajectory. + new_seed = simulator.rng.uniform_int(0, 1000000) + simulator.reset_seed(new_seed) + + # Reset counters + N_structure_types = 0 + N_species = 0 + N_rules = 0 + + #### DEFINE THE CONFIG PARSER OBJECT #### + cp = CP.ConfigParser() + cp.optionxform = str # for upper case option names + + # Now add the relevant info in separate sections + #### MODEL, WORLD and SEED #### + cp.add_section('MODEL') + cp.set('MODEL', 'world_size', simulator.world.world_size) + + cp.add_section('WORLD') + cp.set('WORLD', 'world_size', simulator.world.world_size) + cp.set('WORLD', 'matrix_size', simulator.world.matrix_size) + + cp.add_section('SEED') + cp.set('SEED', 'seed', new_seed) + + #### STRUCTURE TYPES #### + if __debug__: + log.info('Saving structure types...') + + structure_type_names = [] # will store the names in a lookup list + for structure_type in simulator.get_structure_types(): + + id_int = id_to_int(structure_type.id) + + if structure_type['name'] != '' : + name = structure_type['name'] + + elif id_int == 1 : # the std. structure type + name = 'default' + + sectionname = 'STRUCTURETYPE_' + str(id_int) + cp.add_section(sectionname) + + cp.set(sectionname, 'id', id_int) + cp.set(sectionname, 'name', name) + + # Remember name for later + structure_type_names.append( (id_int, name) ) + + # Update counter + N_structure_types += 1 + + #### SPECIES #### + if __debug__: + log.info('Saving particle species...') + + for species in simulator.get_species(): + + id_int = id_to_int(species.id) + sectionname = 'SPECIES_' + str(id_int) + cp.add_section(sectionname) + + cp.set(sectionname, 'id', id_int) + #cp.set(sectionname, 'name', species.name]) # TODO SpeciesInfo does not have this property for some reason... + cp.set(sectionname, 'radius', species.radius) + cp.set(sectionname, 'D', species.D) + cp.set(sectionname, 'v', species.v) + cp.set(sectionname, 'structure_type_id', id_to_int(species.structure_type_id)) + + # Update counter + N_species += 1 + + #### RULES #### + if __debug__: + log.info('Saving rules network...') + + extracted_rules = [] # to avoid double-extraction + for species0 in simulator.get_species(): + + # Reactions with one reactant (unimolecular or unbinding) + rules = simulator.network_rules.query_reaction_rule(species0) + + for rr in rules: + + if not rr.id in extracted_rules: + + rate = rr.k + + r0 = rr.reactants[0] + reactant_ids = ( id_to_int(r0), ) + + if len(rr.products) == 0: + + rtype = 'decay' + product_ids = () + + elif len(rr.products) == 1: + + rtype = 'unimolecular' + p0 = rr.products[0] + product_ids = ( id_to_int(p0), ) + + elif len(rr.products) == 2: + + rtype = 'unbinding' + p0 = rr.products[0] + p1 = rr.products[1] + product_ids = ( id_to_int(p0), id_to_int(p1) ) + + else: + raise LoadSaveError('Unimolecular or unbinding reaction rule has more than two products. Something is utterly wrong.') + + sectionname = 'REACTIONRULE_' + str(rr.id) + cp.add_section(sectionname) + + cp.set(sectionname, 'type', rtype) + cp.set(sectionname, 'reactant_ids', reactant_ids) + cp.set(sectionname, 'product_ids', product_ids) + cp.set(sectionname, 'rate', rate) + + extracted_rules.append(rr.id) + + # Reactions with two reactants (particle species) + for species1 in list(simulator.get_species()) + list(simulator.get_structure_types()): + + rules = simulator.network_rules.query_reaction_rule(species0, species1) + + for rr in rules: + + if not rr.id in extracted_rules and not len(rr.products)==0: + + rate = rr.k + + # Check whether we have a surface binding reaction. + # Since only species1 loops over StructureTypes we only have to check + # whether species1 is a SstructureType or not. + if species1.id in [st.id for st in list(simulator.get_structure_types())]: + # We must construct an id list from the iterator because otherwise + # the comparison does not work + rtype = 'binding_surface' + + else: + rtype = 'binding_particle' + + # Make sure that the first reactant stores species1, which is the surface + # in case we have a surface reaction. + if rr.reactants[0] == species1.id: + r0 = rr.reactants[0] + r1 = rr.reactants[1] + else: + # reverse order + r0 = rr.reactants[1] + r1 = rr.reactants[0] + + reactant_ids = ( id_to_int(r0), id_to_int(r1) ) + + # Check whether there is a legal number of products + if len(rr.products) == 0: + + product_ids = None + rtype = 'annihilation' + + elif len(rr.products) == 1: + + p0 = rr.products[0] + product_ids = ( id_to_int(p0), ) + + else: + raise LoadSaveError('Binding reaction rule has %s product(s). Something is utterly wrong.' % str(len(rr.products)) ) + + # Construct the section info + sectionname = 'REACTIONRULE_' + str(rr.id) + cp.add_section(sectionname) + + cp.set(sectionname, 'type', rtype) + cp.set(sectionname, 'reactant_ids', reactant_ids) + cp.set(sectionname, 'product_ids', product_ids) + cp.set(sectionname, 'rate', rate) + + extracted_rules.append(rr.id) + + #### STRUCTURES #### + if __debug__: + log.info('Saving structures...') + + for structure in simulator.get_structures(): + + id_int = id_to_int(structure.id) + sid_int = id_to_int(structure.sid) + parent_id_int = id_to_int(structure.structure_id) + # the structure_id property of structures is the parent structure id + + # Find the name of the structure type + for i, name in structure_type_names: ## FIXME replace by dictionary? + if i == sid_int: + st_name = name + + # Find the keyword for the geometric type of this structure + for object_type, key in structure_keywords: + if isinstance(structure, object_type): + geometry_key = key + + sectionname = 'STRUCTURE_' + geometry_key + '_' + str(id_int) + cp.add_section(sectionname) + + cp.set(sectionname, 'id', id_int) + cp.set(sectionname, 'parent_id', parent_id_int) + cp.set(sectionname, 'structure_type_id', sid_int) + cp.set(sectionname, 'structure_type_name', st_name) + cp.set(sectionname, 'name', structure.name) + + cp.set(sectionname, 'position', list(structure.shape.position)) + + if isinstance(structure, CuboidalRegion): + + cp.set(sectionname, 'unit_x', list(structure.shape.unit_x)) + cp.set(sectionname, 'unit_y', list(structure.shape.unit_y)) + cp.set(sectionname, 'unit_z', list(structure.shape.unit_z)) + cp.set(sectionname, 'half_extent', list(structure.shape.half_extent)) + + elif isinstance(structure, SphericalSurface): + + cp.set(sectionname, 'radius', structure.shape.radius) + + elif isinstance(structure, CylindricalSurface): + + cp.set(sectionname, 'unit_z', list(structure.shape.unit_z)) + cp.set(sectionname, 'radius', structure.shape.radius) + cp.set(sectionname, 'half_length', structure.shape.half_length) + + elif isinstance(structure, DiskSurface): + + cp.set(sectionname, 'unit_z', list(structure.shape.unit_z)) + cp.set(sectionname, 'radius', structure.shape.radius) + cp.set(sectionname, 'dissociates_radially', int(structure.dissociates_radially())) + # we store boolean flags as int, which facilitates reading them in later + + elif isinstance(structure, PlanarSurface): + + cp.set(sectionname, 'unit_x', list(structure.shape.unit_x)) + cp.set(sectionname, 'unit_y', list(structure.shape.unit_y)) + cp.set(sectionname, 'unit_z', list(structure.shape.unit_z)) + cp.set(sectionname, 'half_extent', list(structure.shape.half_extent)) + + + #### STRUCTURE CONNECTIVITY #### + if __debug__: + log.info('Saving structure connectivity information...') + + for structure in simulator.get_structures(): + + if isinstance(structure, PlanarSurface): + + id_int = id_to_int(structure.id) + + sectionname = 'STRUCTURECONNECTION_' + str(id_int) + cp.add_section(sectionname) + + # Retrieve the neighbor structure IDs for this structure + for n in range(0, 4): + + nid = simulator.world.get_neighbor_id(structure, n) + nid_int = id_to_int(nid) + + cp.set(sectionname, 'neighbor_id_'+str(n), nid_int) + + #### PARTICLES #### + if __debug__: + log.info('Saving particles...') + + pid_particle_pairs = list(simulator.world) + pid_particle_pairs.sort() + + for pp in pid_particle_pairs: + + pid = pp[0] + particle = pp[1] + + species_id_int = id_to_int(particle.sid) + structure_id_int = id_to_int(pp[1].structure_id) + + pid_int = id_to_int(pid) + + sectionname = 'PARTICLE_' + str(pid_int) + cp.add_section(sectionname) + + cp.set(sectionname, 'id', pid_int) + cp.set(sectionname, 'species_id', species_id_int) + cp.set(sectionname, 'radius', particle.radius) + cp.set(sectionname, 'D', particle.D) + cp.set(sectionname, 'v', particle.v) + cp.set(sectionname, 'position', list(particle.position)) + cp.set(sectionname, 'structure_id', structure_id_int) + + #### PARTICLE ORDER IN SCHEDULER #### + # Pop the events from the scheduler and remember + # them to push them back again afterwards. + # This seems awkward but it is convenient because + # we will do the same procedure when rebuilding the + # system at loading / read-in. + # Event IDs will change but this does not matter + # for the simulated trajectory as such. + if __debug__: + log.info('Saving particle order in scheduler...') + + eventlist = [] + scheduler_order = [] + while not simulator.scheduler.size == 0 : + + id, event = simulator.scheduler.pop() + domain = simulator.domains[event.data] + + if __debug__: + log.info(' Removed domain %s, particle id = %s from scheduler.' % (domain, domain.pid_particle_pair[0]) ) + + # Store the events in a list + # inserting from the beginning; thus it + # will already have the right order for + # reinsertion + eventlist.insert(0, (event, domain)) + + pid, particle = domain.pid_particle_pair + pid_int = id_to_int(pid) + + # Also output the scheduler order in reverse + # fashion already, so we don't have to bother later + scheduler_order.insert(0, pid_int) + + # Put the events back into the scheduler (in reverse order) + # Note that the eventlist already has been correctly inverted at this point + assert simulator.scheduler.size == 0 # just to be sure + for (event, domain) in list(eventlist): + if __debug__: + log.info(' Re-instering domain %s, particle id = %s into scheduler.' % (domain, domain.pid_particle_pair[0]) ) + event_id = simulator.scheduler.add(DomainEvent(event.time, domain)) + # The event_id is changed in this process, so don't forget to update the domain + domain.event_id = event_id + + # To be sure, check that event IDs in reconstructed scheduler + # correspond to event IDs of the domains + simulator.check_domains() + + # Create a new section in the save file + sectionname = 'SCHEDULER' + cp.add_section(sectionname) + cp.set(sectionname, 'particle_order', list(scheduler_order)) + cp.set(sectionname, 't', simulator.t) + cp.set(sectionname, 'dt', simulator.dt) + cp.set(sectionname, 'step_counter', simulator.step_counter) + + #### ADD COUNTERS TO MODEL SECTION #### + if __debug__: + log.info('Saving object counters...') + + cp.set('MODEL', 'N_structure_types', N_structure_types) + cp.set('MODEL', 'N_species', N_species) + + #### WRITE FILE #### + if __debug__: + log.info('Writing file...') + + with open(filename, 'wb') as outfile: + cp.write(outfile) + + + +def load_state(filename): + """ Loads a file previously generated with save_state() and + constructs a model, world and info needed to construct the + EGFRDSimulator. + + Returns: world, seed, time_info + + time_info is a tuple containing (t, dt, step_counter) + where t, dt and step_counter are the values of these + quantities at the moment of output / saving. + """ + + if __debug__: + log.info('Attempting to load simulator state from file %s.' % str(filename)) + + #### DEFINE THE CONFIG PARSER OBJECT #### + cp = CP.ConfigParser() + cp.optionxform = str # for upper case option names + + # Load the saved file + # TODO Check for right format + cp.read(filename) + + if __debug__: + log.info('Sections in file: ' + str(cp.sections()) ) + + #### FIRST GET GLOBAL INFO #### + if __debug__: + log.info('Reading global info...') + + world_size = cp.getfloat('WORLD', 'world_size') + matrix_size = cp.getint('WORLD', 'matrix_size') + N_structure_types = cp.getint('MODEL', 'N_structure_types') + N_species = cp.getint('MODEL', 'N_species') + seed = cp.getint('SEED', 'seed') + + #### CREATE THE WORLD AND THE MODEL #### + if __debug__: + log.info('Creating the model...') + + m = model.ParticleModel(world_size) + + + #### STRUCTURE_TYPES #### + if __debug__: + log.info('Reconstructing structure types...') + + structure_types_dict = {} # will map the old (read-in) ID to the StructureType + + # First get the default structure type already defined at model creation + # and add it to the internal dictionary + def_structure_type_id = id_to_int(m.get_def_structure_type_id()) + def_structure_type = m.get_structure_type_by_id(m.get_def_structure_type_id()) + structure_types_dict[def_structure_type_id] = def_structure_type + if __debug__: + log.info(' Added %s' % str(def_structure_type) ) + + structure_types_sections = filter_sections(cp.sections(), 'STRUCTURETYPE') + # Now read in the the structure types and add them to the new model + # in the right order. + # Note that add_structure_type() will create IDs automatically, + # so it is important that we sort the sectionnames list by the ids. + for sectionname in sorted(structure_types_sections, key = lambda name : name_to_int(name)): + + id = cp.getint(sectionname, 'id') + name = cp.get(sectionname, 'name') + + # Assert that the read id corresponds to the one in the sectionname + assert name_to_int(sectionname) == id + + # Create a new StructureType object and add it to the model + structure_type = _gfrd.StructureType() + structure_type['name'] = name + + if id != def_structure_type_id: # Skip adding the default structure_type a second time + m.add_structure_type(structure_type) + assert id_to_int(structure_type.id) == id + structure_types_dict[id] = structure_type + if __debug__: + log.info(' Added %s' % str(structure_type) ) + + if __debug__: + log.info(' structure_types_dict = %s' % str(structure_types_dict) ) + + + #### SPECIES #### + if __debug__: + log.info('Reconstructing species...') + + # Now the same for the species + # Again we have to make sure we add the species in the right order => sort sections list + species_dict = {} # will map the old (read-in) ID to the Species + + species_sections = filter_sections(cp.sections(), 'SPECIES') + for sectionname in sorted(species_sections, key = lambda name : name_to_int(name)): + + id = cp.getint(sectionname, 'id') + #name = cp.getfloat(sectionname, 'name') # FIXME + radius = cp.getfloat(sectionname, 'radius') + D = cp.getfloat(sectionname, 'D') + v = cp.getfloat(sectionname, 'v') + structure_type_id = cp.getint(sectionname, 'structure_type_id') + + # Assert that the read id corresponds to the one in the sectionname + assert name_to_int(sectionname) == id + + # FIXME name cannot be output yet, so we have to make up one here + name = 'Species_'+str(id) + + # Get the structure type for this species from the structure_type_id + structure_type = structure_types_dict[structure_type_id] + assert id_to_int(structure_type.id) == structure_type_id + + # Create a new Species object and add it to the model + # Make sure to choose the right argument signature for each case + if structure_type_id == def_structure_type_id: + species = model.Species(name, D, radius) + elif v == 0: + species = model.Species(name, D, radius, structure_type) + else: + species = model.Species(name, D, radius, structure_type, v) + + m.add_species_type(species) + assert id_to_int(species.id) == id + species_dict[id] = species + if __debug__: + log.info(' Added %s' % str(species) ) + + if __debug__: + log.info(' species_dict = %s' % str(species_dict) ) + + + #### RULES #### + if __debug__: + log.info('Reconstructing rules network...') + + rules_dict = {} # will map the old (read-in) ID to the reaction rule + + rules_sections = filter_sections(cp.sections(), 'REACTIONRULE') + for sectionname in sorted(rules_sections, key = lambda name : name_to_int(name)): + + rtype = cp.get(sectionname, 'type') + rate = cp.getfloat(sectionname, 'rate') + reactant_ids = eval(cp.get(sectionname, 'reactant_ids')) # eval makes sure we get a list + product_ids = eval(cp.get(sectionname, 'product_ids')) + + rule = None + if rtype == 'decay': + + assert len(reactant_ids) == 1 and reactant_ids[0] is not None and \ + len(product_ids) == 0, \ + 'Saved reaction rule does not have the proper signature: rule = %s' % sectionname + + # Get the reactant species + reactant = species_dict[int(reactant_ids[0])] + + # Create the decay rule + rule = model.create_decay_reaction_rule(reactant, rate) + + elif rtype == 'unimolecular': + + assert len(reactant_ids) == 1 and reactant_ids[0] is not None and \ + len(product_ids) == 1 and product_ids[0] is not None, \ + 'Saved reaction rule does not have the proper signature: rule = %s' % sectionname + + # Get the involved species + reactant = species_dict[int(reactant_ids[0])] + product = species_dict[int(product_ids[0])] + + # Create the rule + rule = model.create_unimolecular_reaction_rule(reactant, product, rate) + + elif rtype == 'unbinding': + + assert len(reactant_ids) == 1 and reactant_ids[0] is not None and \ + product_ids[0] is not None and product_ids[1] is not None, \ + 'Saved reaction rule does not have the proper signature: rule = %s' % sectionname + + # Get the involved species + reactant = species_dict[int(reactant_ids[0])] + product0 = species_dict[int(product_ids[0])] + product1 = species_dict[int(product_ids[1])] + + # Create the rule + rule = model.create_unbinding_reaction_rule(reactant, product0, product1, rate) + + elif rtype == 'binding_particle': + + assert reactant_ids[0] is not None and reactant_ids[1] is not None and \ + len(product_ids) == 1 and product_ids[0] is not None, \ + 'Saved reaction rule does not have the proper signature: rule = %s' % sectionname + + # Get the involved species + reactant0 = species_dict[int(reactant_ids[0])] + reactant1 = species_dict[int(reactant_ids[1])] + product = species_dict[int(product_ids[0])] + + # Create the rule + rule = model.create_binding_reaction_rule(reactant0, reactant1, product, rate) + + elif rtype == 'binding_surface': + + assert reactant_ids[0] is not None and reactant_ids[1] is not None and \ + len(product_ids) == 1 and product_ids[0] is not None, \ + 'Saved reaction rule does not have the proper signature: rule = %s' % sectionname + + # Get the involved species + structure_type = structure_types_dict[int(reactant_ids[0])] + reactant = species_dict[int(reactant_ids[1])] + product = species_dict[int(product_ids[0])] + + # Create the rule + rule = model.create_binding_reaction_rule(reactant, structure_type, product, rate) + + else: + + raise LoadSaveError('Cannot create reaction rule when loading state: unknown reaction rule type.') + + # If we have successfully constructed a reaction rule, add it to the model + # and to the internal rules dictionary + if rule: + + added = m.add_reaction_rule(rule) + + if added: + rules_dict[name_to_int(sectionname)] = rule + if __debug__: + log.info(' Added %s, type = %s' % (rule, rtype) ) + + if __debug__: + log.info(' rules_dict = %s' % str(rules_dict) ) + + #### CREATE THE WORLD #### + if __debug__: + log.info('Creating the world...') + + w = create_world(m, matrix_size) + # This will come in handy later + def_structure_id = w.get_def_structure_id() + + #### STRUCTURES #### + if __debug__: + log.info('Reconstructing structures...') + + structures_dict = {} # will map the old (read-in) ID to the structure + + # The default structure should be always ID 1 and has to be added + # from the beginning because it is created automatically at the init + # of world. + # To be sure we check that the default ID is indeed 1. + # FIXME There must be a more elegant solution... + assert id_to_int(def_structure_id) == 1 + def_structure = w.get_structure(def_structure_id) + structures_dict[id_to_int(def_structure_id)] = def_structure + + structures_sections = filter_sections(cp.sections(), 'STRUCTURE') + for sectionname in sorted(structures_sections, key = lambda name : name_to_int(name)): + + # Get the geometry key + # TODO This assumes that the section name has the right signature + # => Implement a check + geometry_key = sectionname.split(get_default_separator())[-2] + + # Find the geometric type of this structure according to its key + for object_type, key in structure_keywords: + if geometry_key == key: + structure_object_type = object_type + + # Read in the information common to all object types + id = cp.getint(sectionname, 'id') + name = cp.get(sectionname, 'name') + parent_id = cp.getint(sectionname, 'parent_id') + st_id = cp.getint(sectionname, 'structure_type_id') + st_name = cp.get(sectionname, 'structure_type_name') + position = vectorize(cp.get(sectionname, 'position')) + # eval makes sure we import a list + + # Assert that the read id corresponds to the one in the sectionname + # This is particularly important in this part of the loading routine + # because structures may be defined as substructures of previously + # defined structures. Thus we want to define them in the order in + # which they were defined before they have been saved to the file. + assert name_to_int(sectionname) == id + + # Get the structure_type of this structure + structure_type = structure_types_dict[st_id] + if st_id != def_structure_type_id: + assert structure_type['name'] == st_name + + # Get the parent structure from the structures dictionary + # For this to work it is of critical importance that the order + # in which the structures are defined is the same as the order + # in which they were defined in before the output + parent_structure = structures_dict[parent_id] + + structure = None + if structure_object_type == CuboidalRegion: + + unit_x = vectorize(cp.get(sectionname, 'unit_x')) + unit_y = vectorize(cp.get(sectionname, 'unit_y')) + unit_z = vectorize(cp.get(sectionname, 'unit_z')) + half_extent = vectorize(cp.get(sectionname, 'half_extent')) + + assert id == id_to_int(def_structure_id) + # TODO Add more than default cuboidal regions + # Right now we assume that there is only one which + # is created automatically via create_world() + + elif structure_object_type == SphericalSurface: + + radius = cp.getfloat(sectionname, 'radius') + + # Create the structure + structure = model.create_spherical_surface(structure_type.id, name, \ + position, radius, parent_structure.id) + + elif structure_object_type == CylindricalSurface: + + unit_z = vectorize(cp.get(sectionname, 'unit_z')) + half_length = cp.getfloat(sectionname, 'half_length') + radius = cp.getfloat(sectionname, 'radius') + + # The cylinder creation function does not take the midpoint of + # the cylinder (which is stored in position) and its half length + # but the edge point and the total length. Thus we need: + edge_pos = position - half_length * unit_z + + # Create the structure + structure = model.create_cylindrical_surface(structure_type.id, name, edge_pos, radius, \ + unit_z, 2.0*half_length, parent_structure.id) + + elif structure_object_type == DiskSurface: + + unit_z = vectorize(cp.get(sectionname, 'unit_z')) + radius = cp.getfloat(sectionname, 'radius') + diss_rad = cp.getint(sectionname, 'dissociates_radially') + + structure_type = structure_types_dict[st_id] + + # Create the structure + structure = model.create_disk_surface(structure_type.id, name, position, radius, \ + unit_z, parent_structure.id) + + # Check whether it does not dissociate radially (standard setting by constructor) + if not(diss_rad): + structure.forbid_radial_dissociation() + + elif structure_object_type == PlanarSurface: + + unit_x = vectorize(cp.get(sectionname, 'unit_x')) + unit_y = vectorize(cp.get(sectionname, 'unit_y')) + unit_z = vectorize(cp.get(sectionname, 'unit_z')) + half_extent = vectorize(cp.get(sectionname, 'half_extent')) + + # The plane creation function does not take the midpoint of + # the plane (which is stored in position) and its half extents + # but the corner point and the total extensions. Thus we need: + corner_pos = position - half_extent[0] * unit_x - half_extent[1] * unit_y + + # Create the structure + structure = model.create_planar_surface(structure_type.id, name, corner_pos, unit_x, unit_y, \ + 2.0*half_extent[0], 2.0*half_extent[1], parent_structure.id) + + assert all_feq(structure.shape.unit_z, unit_z, tolerance=TOLERANCE) + + # Add it to the world + if structure: + w.add_structure(structure) + assert id_to_int(structure.id) == id # TODO + structures_dict[id] = structure + if __debug__: + log.info(' Added %s, type = %s, id = %s, parent = %s' \ + % (structure, structure_type, structure.id, structure.structure_id) ) + + if __debug__: + log.info(' structures_dict = %s' % str(structures_dict) ) + + #### STRUCTURE CONNECTIONS #### + if __debug__: + log.info('Reconstructing structure connections...') + + connections_dict = {} # will map the old (read-in) ID to the structure connection + + # Here we first read in all the connections and then figure out which sides are + # connected in order to actually connect them. + sc_sections = filter_sections(cp.sections(), 'STRUCTURECONNECTION') + + if sc_sections: + + for sectionname in sorted(sc_sections, key = lambda name : name_to_int(name)): + + to_connect_id = name_to_int(sectionname) + + neighbor_id_0 = cp.getint(sectionname, 'neighbor_id_0') + neighbor_id_1 = cp.getint(sectionname, 'neighbor_id_1') + neighbor_id_2 = cp.getint(sectionname, 'neighbor_id_2') + neighbor_id_3 = cp.getint(sectionname, 'neighbor_id_3') + + connections_dict[to_connect_id] = [neighbor_id_0, neighbor_id_1, \ + neighbor_id_2, neighbor_id_3] + + # Figure out which sides are connected + connections = [] + + if any([ to_connect_id==i for i in connections_dict[to_connect_id] ]): + + # Drop a warning in case someone tries to connect a plane to itself (currently unsupported) + log.warn(' Avoiding connecting plane to itself. This may result in incorrectly applied boundary conditions and further errors!') + + else: + + # Everything fine, find the connections + for to_connect_id, neighbor_list in connections_dict.iteritems(): + + # For each neighbor_id find the side with which the neighbor is + # connected to the structure with con_id + for side in range(0,4): + + n_id = neighbor_list[side] + nn_list = connections_dict[n_id] + + for n_side in range(0,4): + + nn_id = nn_list[n_side] + if nn_id == to_connect_id: + # We have found the neighbor_id that links to con_id + connections.append( (to_connect_id, side, n_id, n_side) ) + break + + if __debug__: + log.info(' connections = %s' % str(connections) ) + + # Establish the connections + for struct_id0, side0, struct_id1, side1 in connections: + + struct0 = structures_dict[struct_id0] + struct1 = structures_dict[struct_id1] + + w.connect_structures(struct0, side0, struct1, side1) + if __debug__: + log.info(' Connected side %s of structure %s (id=%s) with side %s of structure %s (id=%s)' \ + % (side0, struct0, struct0.id, side1, struct1, struct1.id) ) + + else: # sc_sections empty + + if __debug__: + log.info(' No connections found in input file.') + + + #### PARTICLES #### + if __debug__: + log.info('Reconstructing particles...') + + # Here we first read in the scheduler order, i.e. the + # order with which particles were sitting in the scheduler + # before the output, and the particles as such. + # Then we create the particles in the right order (FILO principle). + # Note that the particle_order list of the SCHEDULER section + # already contains the particles in reverse order as compared + # to their order in the scheduler before saving, i.e. they + # will be added to the system in the right order here. + + # Get the particle order and time counter values at output + if __debug__: + log.info(' Obtaining particle order in scheduler...') + assert cp.has_section('SCHEDULER') + particle_order = eval(cp.get('SCHEDULER', 'particle_order')) + t = cp.getfloat('SCHEDULER', 't') + dt = cp.getfloat('SCHEDULER', 'dt') + step_counter = cp.getfloat('SCHEDULER', 'step_counter') + + time_info = (t, dt, step_counter) + + if __debug__: + log.info(' particle_order = %s' % str(particle_order) ) + + # Read in the particle information + if __debug__: + log.info(' Reading particles info...') + particles_dict = {} # will map the old (read-in) ID to the particle info + + particle_sections = filter_sections(cp.sections(), 'PARTICLE') + for sectionname in particle_sections: + + pid = cp.getint(sectionname, 'id') + sid = cp.getint(sectionname, 'species_id') + radius = cp.getfloat(sectionname, 'radius') + D = cp.getfloat(sectionname, 'D') + v = cp.getfloat(sectionname, 'v') + position = vectorize(cp.get(sectionname, 'position')) + str_id = cp.getint(sectionname, 'structure_id') + + # Store the info in an internal particle dictionary + # and add it to the collection (particles_dict) + particle = { 'id' : pid, + 'species_id' : sid, + 'radius' : radius, + 'D' : D, + 'v' : v, + 'position' : position, + 'structure_id' : str_id + } + particles_dict[pid] = particle + + if __debug__: + log.info(' Placing particles...') + for pid in particle_order: + + particle = particles_dict[pid] + particle_species = species_dict[ particle['species_id'] ] + + placed = place_particle(w, particle_species, particle['position']) + # place_particle generates log info when placing + + + # Return the world and the seed that was used + # to re-seed the simulator at output + return w, seed, time_info + + + +########################## +#### HELPER FUNCTIONS #### +########################## +def id_to_int(ID): + """ Strips an eGFRD-type ID-specifier off everything + but the integer ID numbers. + + I.e., PID(0:2) for example will be converted to 2. + + TODO Is the first number entry not important, too? + """ + + p = str(ID).partition(':') + + if p[2] != '': + q = p[2].partition(')') + + if q[0] != '': + return int(q[0]) + + else: + raise LoadSaveError('Could not extract number, probably the argument is not a valid ID.') + + +def get_default_separator(): + + return '_' + + +def separate(sectionname): + + separator = get_default_separator() + part = sectionname.partition(separator) + + return (part[0], part[2]) + + # TODO Check for right tag format + # raise LoadSaveError('Section name in input file did not have the required format.') + + +def filter_sections(sectionlist, tagstring): + + assert isinstance(tagstring, str) + + return [section for section in sectionlist if separate(section)[0] == tagstring] + + +def name_to_int(sectionname): + + separator = get_default_separator() + + return int(sectionname.split(separator)[-1]) + + +def vectorize(input_list): + + return numpy.array(eval(input_list)) + + +structure_keywords = [ + (CuboidalRegion, 'CUBE'), + (SphericalSurface, 'SPHERE'), + (CylindricalSurface, 'CYLINDER'), + (DiskSurface, 'DISK'), + (PlanarSurface, 'PLANE') + ] + + +class LoadSaveError(Exception): + pass + diff --git a/logger.py b/logger.py index 611fa2c9..f0355f4e 100644 --- a/logger.py +++ b/logger.py @@ -5,7 +5,7 @@ import numpy import logging -import h5py # added by sakurai@advancesoft.jp +#import h5py # added by sakurai@advancesoft.jp from egfrd import Single, Pair, Multi # added by sakurai@advancesoft.jp __all__ = [ diff --git a/make_cjy_table.py b/make_cjy_table.py index 42b14a5a..24f8347c 100644 --- a/make_cjy_table.py +++ b/make_cjy_table.py @@ -196,7 +196,7 @@ def write_table(file, name, N, x_start, delta_x): jdot = jdot_table[n][start:end] write_arrays(file, 'cj_table%d_f' % n, j, jdot) write_table(file, 'cj_table%d' % n, end-start, z_start, delta_z) - print 'j', len(j) + print('j', len(j)) # y for n in range(minn_y, maxn_y + 1): @@ -209,7 +209,7 @@ def write_table(file, name, N, x_start, delta_x): write_arrays(file, 'cy_table%d_f' % n, y, ydot) write_table(file, 'cy_table%d' % n, end-start, z_start, delta_z) - print 'y', len(y) + print('y', len(y)) write_table_array(file, 'cj_table', minn_j, maxn_j) write_table_array(file, 'cy_table', minn_y, maxn_y) diff --git a/make_sjy_table.py b/make_sjy_table.py index 83c63001..493d7e59 100644 --- a/make_sjy_table.py +++ b/make_sjy_table.py @@ -231,7 +231,7 @@ def write_table(file, name, N, x_start, delta_x): jdot = jdot_table[n][start:end] write_arrays(file, 'sj_table%d_f' % n, j, jdot) write_table(file, 'sj_table%d' % n, end-start, z_start, delta_z) - print 'j', len(j) + print('j', len(j)) # y for n in range(minn_y, maxn_y + 1): @@ -244,7 +244,7 @@ def write_table(file, name, N, x_start, delta_x): write_arrays(file, 'sy_table%d_f' % n, y, ydot) write_table(file, 'sy_table%d' % n, end-start, z_start, delta_z) - print 'y', len(y) + print('y', len(y)) write_table_array(file, 'sj_table', minn_j, maxn_j) write_table_array(file, 'sy_table', minn_y, maxn_y) diff --git a/model.py b/model.py index 59dc00f0..e6905a0b 100644 --- a/model.py +++ b/model.py @@ -1,6 +1,6 @@ import _gfrd -from _gfrd import create_cuboidal_region, create_cylindrical_surface, \ - create_planar_surface +from _gfrd import create_cuboidal_region, create_spherical_surface, create_cylindrical_surface, \ + create_disk_surface, create_planar_surface, create_double_sided_planar_surface import numpy __all__ = [ @@ -11,23 +11,35 @@ 'create_annihilation_reaction_rule', 'create_binding_reaction_rule', 'create_unbinding_reaction_rule', + 'create_surface_absorption_reaction_rule', + 'create_surface_binding_reaction_rule', + 'create_surface_unbinding_reaction_rule', - # From _gfrd. Should be part of the model class. + # From _gfrd. Should be part of the world class. 'create_cuboidal_region', + 'create_spherical_surface', 'create_cylindrical_surface', + 'create_disk_surface', 'create_planar_surface', + 'create_double_sided_planar_surface', ] +import logging +log = logging.getLogger('ecell') -# Define _gfrd docstrigns here, much easier to format than in C++. +# Define _gfrd docstrings here, much easier to format than in C++. +# TODO These functions should be moved to gfrdbase.py because structures are no longer a part of the model, +# but part of the world instead. _gfrd.create_cuboidal_region.__doc__ = \ -"""create_cuboidal_region(id, corner, diagonal) +"""create_cuboidal_region(sid, id, corner, diagonal) Create and return a new cuboidal Region. Arguments: + - sid + the structure type of the cuboidal region. - id - a descriptive name. + the name of the structure - corner the point [x, y, z] of the cuboidal Region closest to [0, 0, 0]. Units: [meters, meters, meters] @@ -39,13 +51,15 @@ """ _gfrd.create_cylindrical_surface.__doc__ = \ -"""create_cylindrical_surface(id, corner, radius, orientation, length) +"""create_cylindrical_surface(sid, id, corner, radius, orientation, length) Create and return a new cylindrical Surface. Arguments: + - sid + the structure type of the cylindrical surface. - id - a descriptive name. + the name of the structure - corner the point [x, y, z] on the axis of the cylinder closest to [0, 0, 0]. Units: [meters, meters, meters] @@ -62,14 +76,43 @@ """ +_gfrd.create_disk_surface.__doc__ = \ +"""create_disk_surface(sid, id, center, radius, orientation) + +Create and return a new disk Surface. + +Arguments: + - sid + the structure type of the cylindrical surface. + - id + the name of the structure + - center + The center point of the circle defining the disk. + Units: [meters, meters, meters] + - radius + the radius of the disk. Units: meters. + - orientation + the unit vector [1, 0, 0], [0, 1, 0] or [0, 0, 1] + perpendicular to the disk. This vector typically defines + the direction of dissociation from the disk if it is a + cap. + +Surfaces are not allowed to touch or overlap. + +""" + _gfrd.create_planar_surface.__doc__ = \ -"""create_planar_surface(id, corner, unit_x, unit_y, length_x, length_y) +"""create_planar_surface(sid, id, corner, unit_x, unit_y, length_x, length_y) -Create and return a new planar Surface. +Create and return a new planar surface with one-sided particle unbinding, +i.e. particles on this surface will unbind in the direction of the normal +vector unit_z (cross product of unit_x and unit_y). Arguments: + - sid + the structure type of the planar surface. - id - a descriptive name. + the name of the structure - corner the point [x, y, z] on the plane closest to [0, 0, 0]. Units: [meters, meters, meters] @@ -86,7 +129,45 @@ the length of the plane along the unit vector unit_y. Should be equal to the world_size. Units: meters. -Surfaces are not allowed to touch or overlap. +Surfaces are not allowed to overlap. + +Todo: allow the user to specify the position of a planar surface +relative to a region. + +""" + +_gfrd.create_double_sided_planar_surface.__doc__ = \ +"""create_double_sided_planar_surface(sid, id, corner, unit_x, unit_y, length_x, length_y) + +Create and return a new planar surface with double-sided particle unbinding, +i.e. particles on this surface will unbind randomly in either unit_z or -unit_z +direction (unit_z = cross product of unit_x and unit_y). + +Arguments: + - sid + the structure type of the planar surface. + - id + the name of the structure + - corner + the point [x, y, z] on the plane closest to [0, 0, 0]. Units: + [meters, meters, meters] + - unit_x + a unit vector [1, 0, 0], [0, 1, 0] or [0, 0, 1] along the + plane. + - unit_y + a unit vector [1, 0, 0], [0, 1, 0] or [0, 0, 1] along the plane + and perpendicular to unit_x. + - length_x + the length of the plane along the unit vector unit_x. Should be + equal to the world_size. Units: meters. + - length_y + the length of the plane along the unit vector unit_y. Should be + equal to the world_size. Units: meters. + +Surfaces are not allowed to overlap. + +Todo: allow the user to specify the position of a planar surface +relative to a region. """ @@ -101,8 +182,9 @@ """ - -def Species(name, D, radius=0, structure="world", drift=0): +# Species is just a function, not a class. It seems to be a frontend to the +# constructor of _gfrd.SpeciesType +def Species(name, D, radius=0, structure_type=None, drift=0): """Define a new Species (in/on a specific Region or Surface). Arguments: @@ -114,7 +196,7 @@ def Species(name, D, radius=0, structure="world", drift=0): - radius the radius for this Species in/on this Region or Surface. Units: meters. - - structure + - structure_type the Region or Surface in/on which this Species can exist. Optional. If you do not specify a Structure the Species is added to the "world". @@ -129,16 +211,32 @@ def Species(name, D, radius=0, structure="world", drift=0): without an explicit Structure argument. """ + # The SpeciesType actually only holds the information needed for a general species + # The information needed for spatial simulations (D, r, v etc) is stored in SpeciesInfo + # Here it is stored temporarily in string fields and only when the world in create + # put in the SpeciesInfo -> TODO SpeciesInfo should be in the ParticleModel st = _gfrd.SpeciesType() st["name"] = str(name) st["D"] = str(D) st["v"] = str(drift) st["radius"] = str(radius) - st["structure"] = structure +# st["structure_type"] = str(id(structure_type)) # The most ugly way of getting the reference in there. + + # Particles of a Species whose Surface is not specified will be + # added to the "world". + if structure_type: + st["structure_type"] = structure_type['name'] # The most ugly way of getting the reference in there. + else: + st["structure_type"] = "world" + +# # create the associated SpeciesInfo +# si = _gfrd.SpeciesInfo(st.id, structure_type.id, +# D, radius, v) + return st -class ParticleModel(_gfrd.Model): +class ParticleModel(_gfrd.ParticleModel): """ """ def __init__(self, world_size): @@ -155,31 +253,35 @@ def __init__(self, world_size): [world_size, world_size, world_size]. """ - _gfrd.Model.__init__(self) - self.world_size = world_size - self.structures = {} + _gfrd.ParticleModel.__init__(self) + # Note that in _gfrd.ParticleModel the default structure_type is + # created and added to the model. - # Particles of a Species whose Surface is not specified will be - # added to the "world". Dimensions don't matter, except for + self.world_size = world_size + # Dimensions don't matter, except for # visualization. - x = numpy.repeat(world_size / 2, 3) - region = _gfrd.CuboidalRegion('world', _gfrd.Box(x, x)) - self.add_structure(region) - - def add_structure(self, structure): - """Add a Structure (Region or Surface) to the ParticleModel. - - Arguments: - - structure - a Region or Surface created with one of the functions - model.create_<>_region or model.create_<>_surface. - """ - assert isinstance(structure, _gfrd.Structure) - self.structures[structure.id] = structure - return structure - - def add_reaction_rule(self, reaction_rule): +# self.structures = {} + + +### TODO change to add_structure_type +# def add_structure(self, structure): +# """Add a Structure (Region or Surface) to the ParticleModel. +# +# Arguments: +# - structure +# a Region or Surface created with one of the functions +# model.create_<>_region or model.create_<>_surface. +# +# """ +# assert isinstance(structure, _gfrd.Structure) +# self.structures[structure.id] = structure +# return structure +# +# def get_structure(self, id): +# return self.structures[id] + + def add_reaction_rule(self, reaction_rule, safe=True): """Add a ReactionRule to the ParticleModel. Argument: @@ -188,10 +290,21 @@ def add_reaction_rule(self, reaction_rule): model.create_<>_reaction_rule. """ - self.network_rules.add_reaction_rule(reaction_rule) + if safe and float(reaction_rule['k']) == 0.0: + + log.warn('Omitting to add reaction rule with zero reaction rate..') + return 0 - def get_structure(self, id): - return self.structures[id] + elif float(reaction_rule['k']) == 0.0: + + log.warn('Adding reaction rule with zero reaction rate. That creates unnecessary overhead and should be avoided.') + self.network_rules.add_reaction_rule(reaction_rule) + return 1 + + else: + + self.network_rules.add_reaction_rule(reaction_rule) + return 1 def set_all_repulsive(self): """Set all 'other' possible ReactionRules to be repulsive. @@ -245,7 +358,7 @@ def create_unimolecular_reaction_rule(reactant, product, k): A unimolecular reaction rule defines a Poissonian process. - """ + """ rr = _gfrd.ReactionRule([reactant], [product]) rr['k'] = '%.16g' % k return rr @@ -270,7 +383,23 @@ def create_decay_reaction_rule(reactant, k): rr['k'] = '%.16g' % k return rr -def create_annihilation_reaction_rule(reactant1, reactant2, ka): +def create_creation_reaction_rule(product, k): + """Example: 0 -> A. + + Arguments: + - product + a Species + - k + reaction rate. Units: per second. + + A creation reaction rule defines a Poissonian process. + + """ + rr = _gfrd.ReactionRule([], [product]) + rr['k'] = '%.16g' % k + return rr + +def create_annihilation_reaction_rule(reactant1, reactant2, k): """Example: A + B -> 0. Arguments: @@ -278,28 +407,28 @@ def create_annihilation_reaction_rule(reactant1, reactant2, ka): a Species. - reactant2 a Species. - - ka + - k intrinsic reaction rate. Units: meters^3 per second. (Rough order of magnitude: 1e-16 m^3/s to 1e-20 m^3/s). The reactants should be in/on the same Region or Surface. - ka should be an *intrinsic* reaction rate. You can convert an - overall reaction rate (kon) to an intrinsic reaction rate (ka) with + k should be an *intrinsic* reaction rate. You can convert an + overall reaction rate (kon) to an intrinsic reaction rate (k) with the function utils.k_a(kon, kD), but only for reaction rules in 3D. By default an EGFRDSimulator will assume a repulsive - bimolecular reaction rule (ka=0) for each possible combination of + bimolecular reaction rule (k=0) for each possible combination of reactants for which no bimolecular reaction rule is specified. You can explicitly add these reaction rules to the model with the method model.ParticleModel.set_all_repulsive. """ rr = _gfrd.ReactionRule([reactant1, reactant2], []) - rr['k'] = '%.16g' % ka + rr['k'] = '%.16g' % k return rr -def create_binding_reaction_rule(reactant1, reactant2, product, ka): +def create_binding_reaction_rule(reactant1, reactant2, product, k): """Example: A + B -> C. Arguments: @@ -309,7 +438,7 @@ def create_binding_reaction_rule(reactant1, reactant2, product, ka): a Species. - product a Species. - - ka + - k intrinsic reaction rate. Units: meters^3 per second. (Rough order of magnitude: 1e-16 m^3/s to 1e-20 m^3/s) @@ -318,22 +447,22 @@ def create_binding_reaction_rule(reactant1, reactant2, product, ka): A binding reaction rule always has exactly one product. - ka should be an *intrinsic* reaction rate. You can convert an - overall reaction rate (kon) to an intrinsic reaction rate (ka) with + k should be an *intrinsic* reaction rate. You can convert an + overall reaction rate (kon) to an intrinsic reaction rate (k) with the function utils.k_a(kon, kD), but only for reaction rules in 3D. By default an EGFRDSimulator will assume a repulsive - bimolecular reaction rule (ka=0) for each possible combination of + bimolecular reaction rule (k=0) for each possible combination of reactants for which no bimolecular reaction rule is specified. You can explicitly add these reaction rules to the model with the method model.ParticleModel.set_all_repulsive. """ rr = _gfrd.ReactionRule([reactant1, reactant2], [product]) - rr['k'] = '%.16g' % ka + rr['k'] = '%.16g' % k return rr -def create_unbinding_reaction_rule(reactant, product1, product2, kd): +def create_unbinding_reaction_rule(reactant, product1, product2, k): """Example: A -> B + C. Arguments: @@ -343,7 +472,7 @@ def create_unbinding_reaction_rule(reactant, product1, product2, kd): a Species. - product2 a Species. - - kd + - k intrinsic reaction rate. Units: per second. (Rough order of magnitude: 1e-2 /s to 1e2 /s). @@ -352,15 +481,116 @@ def create_unbinding_reaction_rule(reactant, product1, product2, kd): An unbinding reaction rule always has exactly two products. - kd should be an *intrinsic* reaction rate. You can convert an + k should be an *intrinsic* reaction rate. You can convert an overall reaction rate (koff) for this reaction rule to an intrinsic - reaction rate (kd) with the function utils.k_d(koff, kon, kD) or + reaction rate (k) with the function utils.k_d(koff, kon, kD) or utils.k_d_using_ka(koff, ka, kD). An unbinding reaction rule defines a Poissonian process. """ rr = _gfrd.ReactionRule([reactant], [product1, product2]) - rr['k'] = '%.16g' % kd + rr['k'] = '%.16g' % k return rr +def create_surface_absorption_reaction_rule(reactant, surface, k): + """Example: A + some_surface -> 0 + some_surface + + Arguments: + - reactant + a Species in the "world" or in a Region. + - surface + a Surface created with one of the functions + model.create_<>_surface. + - k + intrinsic reaction rate. Units: meters^3 per second. (Rough + order of magnitude: 1e-16 m^3/s to 1e-20 m^3/s) + + k should be an *intrinsic* reaction rate. No analytical expression + is currently known to convert an overall reaction rate (kon) to + an intrinsic reaction rate (k) for surface binding or absorption + reaction rules. + + By default an EGFRDSimulator will assume a repulsive + surface binding reaction rule (k=0) for each possible + combination of reactant and Surface for which no surface + binding or absorption reaction rule is specified. You can + explicitly add these reaction rules to the model by calling + model.ParticleModel.set_all_repulsive(). + + """ + assert isinstance(reactant['structure'], _gfrd.BoxShapedRegion) + +def create_surface_binding_reaction_rule(reactant, surface, product, k): + """Example: A + some_surface -> A_on_surface + some_surface + + Arguments: + - reactant + a Species in the "world" or in a Region. + - surface + a Surface created with one of the functions + model.create_<>_surface. + - product + a Species that can exist on the Surface mentioned in the + previous argument. For example: if you assigned that + Surface to the variable some_surface, then a valid product + Species would be: + model.Species('A_on_surface', some_D, some_r, some_surface) + - k + intrinsic reaction rate. Units: meters^3 per second. (Rough + order of magnitude: 1e-16 m^3/s to 1e-20 m^3/s) + + A surface binding reaction rule always has exactly one product. + + k should be an *intrinsic* reaction rate. No analytical expression + is currently known to convert an overall reaction rate (kon) to + an intrinsic reaction rate (k) for surface binding or absorption + reaction rules. + + By default an EGFRDSimulator will assume a repulsive + surface binding reaction rule (k=0) for each possible + combination of reactant and Surface for which no surface + binding or absorption reaction rule is specified. You can + explicitly add these reaction rules to the model by calling + model.ParticleModel.set_all_repulsive(). + + """ + assert product['structure'] == surface + +def create_surface_unbinding_reaction_rule(reactant, surface, product, k): + """Example: A_on_surface + some_surface -> A + some_surface + + Arguments: + - reactant + a Species that can exist on the Surface mentioned in the + next argument. For example: if you assign that + Surface to the variable some_surface, then a valid reactant + Species would be: + model.Species('A_on_surface', some_D, some_r, some_surface) + - surface + a Surface created with one of the functions + model.create_<>_surface. + - product + a Species in the "world" or in a Region. + - k + intrinsic reaction rate. Units: per second. (Rough order of + magnitude: 1e-2 /s to 1e2 /s). + + A surface unbinding reaction rule always has exactly one product. + + k should be an *intrinsic* reaction rate. No analytical expression + is currently known to convert an overall reaction rate (koff) to + an intrinsic reaction rate (k) for surface unbinding reaction rules. + + A surface unbinding reaction rule defines a Poissonian process. + + """ + assert reactant['structure'] == surface + +def create_membrane_traversal_reaction_rule(reactant, surface1, + product, surface2, k): + """ + + """ + # Implementation: also flip orientation of cylinder. + pass diff --git a/multi.py b/multi.py index d515f3b8..4a689a72 100644 --- a/multi.py +++ b/multi.py @@ -1,4 +1,3 @@ -import bd from weakref import ref from gfrdbase import * @@ -6,46 +5,81 @@ import itertools import _gfrd +from _gfrd import (SphericalShell, Sphere) from constants import EventType +from domain import Domain +from shells import (hasSphericalShell, Others) import os -class Multi(object): - def __init__(self, domain_id, main, dt_factor): +import logging +log = logging.getLogger('ecell') + + +class Multi(Domain, hasSphericalShell, Others): + def __init__(self, domain_id, main, step_size_factor, dt_hardcore_min = -1): + Domain.__init__(self, domain_id) + # note: hasSphericalShell.__init__ is not called and that the Multi has no self.testShell + # Only the methods of the hasSphericalShell class are used + self.main = ref(main) - self.domain_id = domain_id - self.event_id = None - self.last_event = None + self.last_event = EventType.MULTI_DIFFUSION #None +# self.event_type = EventType.MULTI_DIFFUSION + self.sphere_container = _gfrd.SphericalShellContainer(main.world.world_size, 3) self.particle_container = _gfrd.MultiParticleContainer(main.world) self.escaped = False - self.dt_factor = dt_factor + self.step_size_factor = step_size_factor + self.dt_hardcore_min = dt_hardcore_min self.last_reaction = None + self.reaction_length = 0 def initialize(self, t): self.last_time = t self.start_time = t main = self.main() - self.dt = self.dt_factor * bd.calculate_bd_dt(main.world.get_species(sid) for sid in main.world.species) + self.set_dt_and_reaction_length() + if __debug__: + log.info("multi: initialized with time step = %s and reaction length = %s" % + (self.dt, self.reaction_length)) + + def calculate_bd_dt(species_list): + D_list = [] + radius_list = [] + D_max = 0. + radius_min = numpy.inf + for species in species_list: + if D_max < species.D: + D_max = species.D + if radius_min > species.radius: + radius_min = species.radius + return (radius_min * 2) ** 2 / (D_max * 2) def get_multiplicity(self): return self.particle_container.num_particles multiplicity = property(get_multiplicity) def within_shell(self, pp): - return bool(self.sphere_container.get_neighbors_within_radius(pp[1].position, -pp[1].radius)) + # test if pid_particle_pair 'pp' is within at least one shell. + shells = self.sphere_container.get_neighbors_within_radius(pp[1].position, -(pp[1].radius + self.reaction_length)) + return len(shells) >= 1 def add_shell(self, shell_id_shell_pair): if __debug__: - log.info("add shell to multi:\n (%s,\n %s)" % + log.info("add shell to multi:\t (%s,\t%s)" % (shell_id_shell_pair[0], shell_id_shell_pair[1])) self.sphere_container.update(shell_id_shell_pair) def add_particle(self, pid_particle_pair): if __debug__: - log.info("add particle to multi:\n (%s,\n %s)" % + log.info("add particle to multi:\t (%s,\t\t%s)" % (pid_particle_pair[0], pid_particle_pair[1])) self.particle_container.update_particle(pid_particle_pair) + + def set_dt_and_reaction_length(self): + main = self.main() + self.dt, self.reaction_length = \ + self.particle_container.determine_dt_and_reaction_length(main.network_rules, self.step_size_factor, self.dt_hardcore_min) def step(self): self.escaped = False @@ -64,14 +98,17 @@ def __init__(self, outer): self.outer_ = outer def __call__(self, shape, ignore0, ignore1=None): - within_radius = bool( - self.outer_.sphere_container.get_neighbors_within_radius( - shape.position, -shape.radius)) - if not within_radius: + + within_radius = self.outer_.sphere_container.get_neighbors_within_radius( + shape.position, -(shape.radius + self.outer_.reaction_length) ) + + if list(within_radius) == []: + # Convert to list first because of tricky C++/Python binding here + # Type casting bool(within_radius) caused segfaults in some simulations main = self.outer_.main() - if self.outer_.last_event == None: + if self.outer_.last_event == EventType.MULTI_DIFFUSION: #None: self.outer_.last_event = EventType.MULTI_ESCAPE - main.clear_volume(shape.position, shape.radius, + main.burst_volume(shape.position, shape.radius, ignore=[self.outer_.domain_id, ]) if ignore1 is None: return not main.world.check_overlap( @@ -79,16 +116,17 @@ def __call__(self, shape, ignore0, ignore1=None): else: return not main.world.check_overlap( (shape.position, shape.radius), ignore0, ignore1) + return True cr = check_reaction() vc = clear_volume(self) - ppg = _gfrd.BDPropagator(tx, main.network_rules, - myrandom.rng, self.dt, main.dissociation_retry_moves, + ppg = _gfrd.newBDPropagator(tx, main.network_rules, + myrandom.rng, self.dt, main.dissociation_retry_moves, self.reaction_length, cr, vc, [pid for pid, _ in self.particle_container]) - self.last_event = None + self.last_event = EventType.MULTI_DIFFUSION #None while ppg(): if cr.reactions: self.last_reaction = cr.reactions[-1] @@ -97,7 +135,7 @@ def __call__(self, shape, ignore0, ignore1=None): else: self.last_event = EventType.MULTI_BIMOLECULAR_REACTION break - + # for pid_particle_pair in itertools.chain( # tx.modified_particles, tx.added_particles): # overlapped = main.world.check_overlap(pid_particle_pair[1].shape, pid_particle_pair[0]) @@ -108,38 +146,62 @@ def __call__(self, shape, ignore0, ignore1=None): # return # if not self.within_shell(pid_particle_pair): - # if self.last_event == None: + # if self.last_event == EventType.MULTI_DIFFUSION #None: # self.last_event = EventType.MULTI_ESCAPE # main.clear_volume( # pid_particle_pair[1].position, # pid_particle_pair[1].radius, ignore=[self.domain_id, ]) def check(self): - # shells are contiguous - # FIXME: this code cannot detect a pair of shells that are isolated - # from others. - for _, shell in self.shell_list: - result = self.sphere_container.get_neighbors(shell.shape.position) - # Check contiguity with nearest neighbor only (get_neighbors - # returns a sorted list). - nearest = result[1] - distance = nearest[1] - assert distance - shell.shape.radius < 0.0,\ - 'shells of %s are not contiguous.' % str(self) - - # all particles within the shell. + # check Multi num particles >= 1 + assert self.multiplicity >= 1, 'Multi needs to have at least one particle' + # check number of shells==num of particles in Multi + assert self.multiplicity == self.num_shells, \ + 'number of particles is not equal to number of shells' + # Event is DIFFUSION, SINGLE_REACTION, BIMOL_REACTION, ESCAPE + assert self.last_event == EventType.MULTI_DIFFUSION or \ + self.last_event == EventType.MULTI_ESCAPE or \ + self.last_event == EventType.MULTI_UNIMOLECULAR_REACTION or \ + self.last_event == EventType.MULTI_BIMOLECULAR_REACTION, \ + 'event_type of Multi was not of proper type' + + # check that the shells of the multi are contiguous + # FIXME: This can be done more efficiently because we already have an + # exhaustive list of the shells to check + def check_connected(shell_id_shell_pair, already_checked): + + shell_id, shell = shell_id_shell_pair + if shell_id not in already_checked: + already_checked.add(shell_id) + # TODO get only the neighbors that are not in already_checked + neighbor_distances = self.sphere_container.get_neighbors_within_radius(shell.shape.position, shell.shape.radius) + for shell_id_shell_pair, _ in neighbor_distances: + already_checked = check_connected(shell_id_shell_pair, already_checked) + + return already_checked + + connected_shell_ids = check_connected(self.shell_list.next(), set([])) + assert len(connected_shell_ids) == self.num_shells, \ + 'shells of %s are not contiguous.' % str(self) + + + # check that all particles are within one of the shells. for pid_particle_pair in self.particle_container: assert self.within_shell(pid_particle_pair),\ 'not all particles within the shell.' + # check that all the shells in the private multi shell container + # are also in the shell container of the Simulator main = self.main() for shell_id, shell in self.shell_list: - container = main.get_container(shell) + container = main.geometrycontainer.get_container(shell) if not container.contains(shell_id): raise RuntimeError,\ 'self.sim.main.sphere_container does not contain %s'\ % str(shell_id) - for shell_id, shell in main.containers[0]: + # check that all the shells in the container of the Simulator that belong to this multi + # domain are also in the private shell container. + for shell_id, shell in main.geometrycontainer.containers[0]: if shell.did == self.domain_id: if not self.sphere_container.contains(shell_id): raise RuntimeError,\ @@ -161,14 +223,15 @@ def has_particle(self, pid): # except: # return False - def particles(self): + def get_particles(self): return iter(self.particle_container) - particles = property(particles) + particles = property(get_particles) def num_shells(self): return len(self.sphere_container) num_shells = property(num_shells) def shell_list(self): + # returns a list of (shell_id, shell) pairs of the Multi return iter(self.sphere_container) shell_list = property(shell_list) diff --git a/myrandom.py b/myrandom.py index 64e93dd1..2d2e06a5 100644 --- a/myrandom.py +++ b/myrandom.py @@ -23,7 +23,13 @@ def uniform(min=0.0, max=1.0, size=None): get_raw = rng.get_raw random = rng normal = rng.normal -seed = rng.seed + +def seed(myseed): + + rng.seed(myseed) + + # for the case we would like to remember the seed used + return myseed # By default seed is 0. myseed = 0 diff --git a/newBDPropagator.hpp b/newBDPropagator.hpp new file mode 100644 index 00000000..984f1424 --- /dev/null +++ b/newBDPropagator.hpp @@ -0,0 +1,1353 @@ +#ifndef NEW_BD_PROPAGATOR_HPP +#define NEW_BD_PROPAGATOR_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "Defs.hpp" +#include "generator.hpp" +#include "exceptions.hpp" +#include "freeFunctions.hpp" +#include "utils.hpp" +#include "utils/random.hpp" +#include "utils/get_default_impl.hpp" +#include "Logger.hpp" + + +#include + +template +class newBDPropagator +{ +public: + typedef Ttraits_ traits_type; + typedef typename Ttraits_::world_type::particle_container_type particle_container_type; + typedef typename Ttraits_::world_type::structure_container_type structure_container_type; + + // shorthand typedef that we use + typedef typename particle_container_type::length_type length_type; + typedef typename particle_container_type::position_type position_type; + typedef typename particle_container_type::species_type species_type; + typedef typename particle_container_type::species_id_type species_id_type; + typedef typename particle_container_type::particle_id_type particle_id_type; + typedef typename particle_container_type::particle_type particle_type; + typedef typename particle_container_type::particle_id_pair particle_id_pair; + typedef typename particle_container_type::particle_shape_type particle_shape_type; + typedef typename particle_container_type::structure_type structure_type; + typedef typename particle_container_type::structure_id_type structure_id_type; + typedef typename particle_container_type::structure_type_id_type structure_type_id_type; + + typedef typename particle_container_type::particle_id_pair_generator particle_id_pair_generator; + typedef typename particle_container_type::particle_id_pair_and_distance particle_id_pair_and_distance; + typedef typename particle_container_type::particle_id_pair_and_distance_list particle_id_pair_and_distance_list; + typedef typename particle_container_type::structure_id_pair_and_distance structure_id_pair_and_distance; + typedef typename particle_container_type::structure_id_pair_and_distance_list structure_id_pair_and_distance_list; + + typedef typename traits_type::world_type::traits_type::rng_type rng_type; + typedef typename traits_type::time_type time_type; + typedef typename traits_type::network_rules_type network_rules_type; + typedef typename network_rules_type::reaction_rules reaction_rules; + typedef typename network_rules_type::reaction_rule_type reaction_rule_type; + typedef typename traits_type::reaction_record_type reaction_record_type; + typedef typename traits_type::reaction_recorder_type reaction_recorder_type; + typedef typename traits_type::volume_clearer_type volume_clearer_type; + + typedef std::vector particle_id_vector_type; + typedef std::pair position_pair_type; + typedef std::pair position_structid_pair_type; + typedef std::pair posstructid_posstructid_pair_type; + typedef std::pair component_pair_type; + typedef std::pair projected_type; + +public: + // The constructor + template + newBDPropagator( + particle_container_type& tx, network_rules_type const& rules, + rng_type& rng, time_type dt, int max_retry_count, length_type reaction_length, + reaction_recorder_type* rrec, volume_clearer_type* vc, + Trange_ const& particle_ids) + : tx_(tx), rules_(rules), rng_(rng), dt_(dt), + max_retry_count_(max_retry_count), rrec_(rrec), vc_(vc), + queue_(), rejected_move_count_(0), reaction_length_( reaction_length ) + { + call_with_size_if_randomly_accessible( + boost::bind(&particle_id_vector_type::reserve, &queue_, _1), + particle_ids); + + // initialize the queue with the particle_ids of the particles to propagate. + for (typename boost::range_const_iterator::type + i(boost::begin(particle_ids)), + e(boost::end(particle_ids)); i != e; ++i) + { + queue_.push_back(*i); + } + // randomize the queue + shuffle(rng, queue_); + } + + /****************************/ + /**** MAIN 'step' method ****/ + /****************************/ + // This method is called to process the change of position and putative reaction of every particle. + // The method only treats one particle at a time and returns 'True' as long as there are still particles + // in the container that need to be processed. + bool operator()() + { + + /*** 1. TREAT QUEUE ***/ + // if there are no more particle to treat -> end + if (queue_.empty()) + return false; + + // Get the next particle from the queue + particle_id_type pid(queue_.back()); + queue_.pop_back(); + particle_id_pair pp(tx_.get_particle(pid)); + // Log some info + LOG_DEBUG(("propagating particle %s, dt = %g, reaction_length = %g", + boost::lexical_cast(pp.first).c_str(), + dt_, + reaction_length_ + )); + + /*** 2. TRY SINGLE (DECAY) REACTION ***/ + // Consider decay reactions first. Particles can move after decay, but this is done + // inside the 'attempt_single_reaction' function. + try + { + /*** ATTEMPT MONOMOLECULAR REACTION ***/ + if (attempt_single_reaction(pp)) + return true; + } + catch (propagation_error const& reason) + { + log_.info("first-order reaction rejected (reason: %s)", reason.what()); + ++rejected_move_count_; + return true; + } + catch (illegal_propagation_attempt const& reason) + { + log_.info("first-order reaction: illegal propagation attempt (reason: %s)", reason.what()); + ++rejected_move_count_; + return true; + } + + // If no single reaction happened, + // we will check for reactions/interactions upon particle moves. + + // First treat special cases. + // Particles that are immobile cannot move nor hit another particle, + // so exit immediately if this is the case +// if(pp.second.D() == 0.0 && pp.second.v() == 0.0) // particle is immobile +// { +// LOG_DEBUG(("particle is immobile (D=0, v=0), no further action")); +// return true; +// } // FIXME + + // OK, we seem to be in the regular case. Continue. + + /*** 3.1 COLLECT INFO ***/ + // Get info of the particle + const species_type pp_species(tx_.get_species(pp.second.sid())); + const length_type r0( pp_species.radius() ); + const position_type old_pos( pp.second.position() ); + const structure_id_type old_struct_id(pp.second.structure_id()); + const boost::shared_ptr pp_structure( tx_.get_structure( old_struct_id ) ); + // declare variables to use + position_type new_pos(old_pos); + structure_id_type new_structure_id( old_struct_id ); + bool immobile( true ); + + /*** 3.2 DISPLACEMENT TRIAL ***/ + /* Sample a potential move, and check if the particle _core_ has overlapped with another + particle or surface. If this is the case the particle bounces, and is returned + to its original position. For immobile particles with D = 0 and v=0, new_pos = old_pos. + Note that apply_boundary takes care of instant transitions between structures of + the same structure_type. + */ + if (pp_species.D() != 0.0 || pp_species.v() != 0.0) + { + immobile = false; + + // Get a new position on the structure that the particle lives on. + const position_type displacement( pp_structure->bd_displacement(pp_species.v() * dt_, + std::sqrt(2.0 * pp_species.D() * dt_), + rng_) ); + // Apply boundary conditions + const position_structid_pair_type pos_structid(tx_.apply_boundary( std::make_pair( add(old_pos, displacement), old_struct_id ))); + // Remember new position and structure ID + new_pos = pos_structid.first; + new_structure_id = pos_structid.second; + } + + /*** 4 CHECK OVERLAPS ***/ + bool bounced( false ); + // Get all the particles that are in the reaction volume at the new position (and that may consequently also be in the core) + /* Use a spherical shape with radius = particle_radius + reaction_length. + Note that at this point this is only a search radius, not a radius that defines volume exclusion; + for this, only r0 (instead of r0 + reaction_length_) is used further below. */ + boost::scoped_ptr overlap_particles( + tx_.check_overlap(particle_shape_type( new_pos, r0 + reaction_length_ ), pp.first)); + int particles_in_overlap(overlap_particles ? overlap_particles->size(): 0); + if(particles_in_overlap) + LOG_DEBUG( ("%u particles in overlap at new position.", particles_in_overlap) ); + + //// 4.1 CHECK FOR CORE OVERLAPS WITH PARTICLES + /* Check if the particle at new_pos overlaps with any particle cores. */ + int j( 0 ); + while(!bounced && j < particles_in_overlap) + bounced = overlap_particles->at(j++).second < r0; + // use only r0 as an exclusion radius here! + + if(bounced) + LOG_DEBUG( ("particle bounced with another particle.") ); + + //// 4.2 CHECK FOR CORE OVERLAPS WITH STRUCTURES + /* If the particle has not bounced with another particle check for overlap with a surface. */ + boost::scoped_ptr overlap_structures; + int structures_in_overlap(0); + if (!bounced) + { + // Get all the structures within the reaction volume (and that may consequently also be in the core) + boost::scoped_ptr overlap_structures_tmp( + tx_.check_surface_overlap(particle_shape_type( new_pos, r0 + reaction_length_ ), old_pos, new_structure_id, r0, old_struct_id)); + overlap_structures.swap(overlap_structures_tmp); + structures_in_overlap = (int)(overlap_structures ? overlap_structures->size(): 0); + + if(structures_in_overlap) + LOG_DEBUG( ("%u structures in overlap at new position.", structures_in_overlap) ); + + j = 0; + while(!bounced && j < structures_in_overlap) + bounced = overlap_structures->at(j++).second < r0; + // use only r0 as an exclusion radius here! + + if(bounced) + LOG_DEBUG( ("particle bounced with structure.") ); + } + + /*** 5. TREAT BOUNCING => CHECK FOR POTENTIAL REACTIONS / INTERACTIONS ***/ + /* If particle is bounced, restore old position and check reaction_volume for reaction partners at old_pos. */ + if(bounced) + { + // restore old position and structure_id + new_pos = old_pos; + new_structure_id = old_struct_id; + + if( immobile ){ + LOG_DEBUG( ("particle immobile, using old position and structure id.") );} + else{ + LOG_DEBUG( ("particle bounced, restoring old position and structure id.") );} + + // re-get the reaction partners (particles), now on old position. + boost::scoped_ptr overlap_particles_after_bounce( + tx_.check_overlap( particle_shape_type( old_pos, r0 + reaction_length_ ), pp.first) ); + overlap_particles.swap( overlap_particles_after_bounce ); // FIXME is there no better way? + // NOTE that it is asserted that the particle overlap criterium for the particle with + // other particles and surfaces is False! + particles_in_overlap = (int)(overlap_particles ? overlap_particles->size(): 0); + LOG_DEBUG( ("now %u particles in overlap at old position", particles_in_overlap) ); + + // re-get the reaction partners (structures), now on old position. + boost::scoped_ptr overlap_structures_after_bounce( + tx_.check_surface_overlap( particle_shape_type( old_pos, r0 + reaction_length_ ), old_pos, old_struct_id, r0) ); + overlap_structures.swap( overlap_structures_after_bounce ); // FIXME is there no better way? + // NOTE that it is asserted that the particle overlap criterium for the particle with + // other particles and surfaces is False! + structures_in_overlap = (int)(overlap_structures ? overlap_structures->size(): 0); + LOG_DEBUG( ("now %u structures in overlap at old position", structures_in_overlap) ); + } + // NOTE that overlap_structures and overlap_particles also have been defined properly above when bounced = false; + // in that case, there were no core overlaps, but possibly still overlaps within the reaction volume + + + /*** 6. REACTIONS & INTERACTIONS ***/ + /* Attempt a reaction (and/or interaction) with all the particles (and/or a surface) that + are/is inside the reaction volume. */ + Real accumulated_prob (0); + Real prob_increase (0); + Real rnd( rng_() ); + + const boost::shared_ptr current_struct( tx_.get_structure( new_structure_id) ); + //// 6.1 INTERACTIONS WITH STRUCTURES + // First, if a surface is inside the reaction volume, and the particle is in the 3D attempt an interaction. + // TODO Rework this using the new structure functions? + // TODO Don't check only the closest but check all overlapping surfaces. + j = 0; + while(j < structures_in_overlap) + { + const structure_id_pair_and_distance & overlap_struct( overlap_structures->at(j) ); + const projected_type new_pos_projected (overlap_struct.first.second->project_point(new_pos)); + + // For an interaction the structure must be: + if( (overlap_struct.first.second->sid() != current_struct->sid()) && // - of a different structure_type + (overlap_struct.second < r0 + reaction_length_) && // - within the reaction volume + (new_pos_projected.second.second < 0) ) // - 'alongside' of the particle + { + prob_increase = k_total( pp.second.sid(), overlap_struct.first.second->sid() ) * dt_ / + overlap_struct.first.second->surface_reaction_volume( r0, reaction_length_ ); + accumulated_prob += prob_increase; + + LOG_DEBUG( ("check for surface interaction, acc_prob = %g", accumulated_prob) ); // TESTING + + if(prob_increase >= 1.) // sth. is wrong in this case + { + LOG_WARNING(("probability increase exceeded one in particle-surface interaction: prob_increase = %f, k_total = %e, surface_reaction_volume = %e.", + prob_increase, k_total( pp.second.sid(), overlap_struct.first.second->sid() ), + overlap_struct.first.second->surface_reaction_volume( r0, reaction_length_ ) )); + } + if(accumulated_prob >= 1.) // sth. is wrong in this case + { + LOG_WARNING(("the acceptance probability of an interaction/reaction exceeded one in particle-surface interaction: p_acc = %f, reaction_length = %e, dt = %e.", + accumulated_prob, reaction_length_, dt_)); + } + + if(accumulated_prob > rnd) // OK, try to fire the interaction + { + LOG_DEBUG( ("attempting surface interaction, acc_prob = %g", accumulated_prob) ); // TESTING + try + { + LOG_DEBUG( ("fire surface interaction with surface %s.", + boost::lexical_cast(overlap_struct.first.first).c_str()) ); + + /*** ATTEMPT INTERACTION ***/ + if(attempt_interaction(pp, new_pos_projected.first, overlap_struct.first.second )) + return true; + } + catch (propagation_error const& reason) + { + log_.info("surface interaction rejected (reason: %s).", reason.what()); + ++rejected_move_count_; + // Revert the addition of the prob. increment to avoid that the a reaction will + // fire in the (following) particle overlap loop even if the reaction rate and + // prob. increment zero there. + // Conceptually this means that the prob. for the reaction that was just attempted + // and failed was zero in the first place (due to lack of space). + log_.info("treating reaction as forbidden and reducing acc. probability by %f", prob_increase); + accumulated_prob -= prob_increase; + } + catch (illegal_propagation_attempt const& reason) + { + log_.info("surface interaction: illegal propagation attempt (reason: %s)", reason.what()); + ++rejected_move_count_; + log_.info("treating reaction as forbidden and reducing acc. probability by %f", prob_increase); + accumulated_prob -= prob_increase; + } + } + else + { + LOG_DEBUG( ("particle attempted an interaction with surface %s; interaction not accepted.", + boost::lexical_cast(overlap_struct.first.first).c_str()) ); + } + } + + j++; // next overlapping structure index + } + + //// 6.1 REACTIONS WITH OTHER PARTICLES + /* Now attempt a reaction with all particles inside the reaction volume. */ + j = 0; + prob_increase = 0; + while(j < particles_in_overlap) + { + const particle_id_pair_and_distance & overlap_particle( overlap_particles->at(j) ); + + species_type s0(pp_species); + species_type s1(tx_.get_species(overlap_particle.first.second.sid())); + + // TODO Remove this when everything works fine! +/* // If the structure_types of the reactants are not equal, one of the reactants has to come from the bulk, + // and we let this be s1, the particle from the surface is named s0. + if(s0.structure_type_id() != s1.structure_type_id()) + { + if( !(s0.structure_type_id() == tx_.get_def_structure_type_id() || s1.structure_type_id() == tx_.get_def_structure_type_id()) ) + { + log_.warn("A surface particle overlapped with a particle living on a different structure_type. No reaction implemented."); + continue; // FIXME this now blocks the cap dissociation. + } + if(s0.structure_type_id() == tx_.get_def_structure_type_id()) + std::swap(s0,s1); // ?????? + } +*/ + const boost::shared_ptr s1_struct( tx_.get_structure( overlap_particle.first.second.structure_id()) ); + prob_increase = k_total(s0.id(), s1.id()) * dt_ / + ( 2. * s1_struct->particle_reaction_volume( s0.radius() + s1.radius(), reaction_length_ ) ); + accumulated_prob += prob_increase; + + LOG_DEBUG(( + "checking for reaction between %s and %s, prob. increase = %g.", + boost::lexical_cast(s0).c_str(), + boost::lexical_cast(s1).c_str(), + prob_increase + )); + + if (accumulated_prob >= 1.) + { + LOG_WARNING(( + "the accumulated acceptance probability inside a reaction volume exeededs one in particle-particle reaction: p_acc = %g, reaction_length = %e, dt = %e.", + accumulated_prob, reaction_length_, dt_)); + } + + if(accumulated_prob > rnd) + { + LOG_DEBUG(("attempting to fire reaction.")); + try + { + /*** ATTEMPT INTERPARTICLE REACTION ***/ + if( attempt_pair_reaction(pp, overlap_particle.first, s0, s1) ) + return true; + } + catch (propagation_error const& reason) + { + log_.info("second-order reaction rejected (reason: %s)", reason.what()); + ++rejected_move_count_; + // Set the increase to zero a posteriori + log_.info("treating reaction as forbidden and reducing acc. probability by %g", prob_increase); + accumulated_prob -= prob_increase; + } + } + else + { + LOG_DEBUG(("particle attempted a reaction with particle %s; reaction not accepted.", + boost::lexical_cast(overlap_particle.first.first).c_str())); + } + + j++; + } + + /*** 7. DEFAULT CASE: ACCEPT DISPLACEMENT TRIAL ***/ + // If the particle did neither react, interact or bounce, update it to it's new position. + if(!bounced) + { + const particle_id_pair particle_to_update( pp.first, + particle_type(pp_species.id(), + particle_shape_type(new_pos, r0), + new_structure_id, + pp_species.D(), + pp_species.v()) ); + + if (vc_) + { + if (!(*vc_)(particle_to_update.second.shape(), particle_to_update.first)) + { + log_.info("propagation move rejected."); + return true; + } + } + + tx_.update_particle(particle_to_update); + } + + return true; + } + + /*** HELPER METHODS ***/ + // Getter of rejected_move + std::size_t get_rejected_move_count() const + { + return rejected_move_count_; + } + + +/***************************/ +/***** PRIVATE METHODS *****/ +/***************************/ +private: + + /************************/ + /*** SINGLE REACTIONS ***/ + /************************/ + bool attempt_single_reaction(particle_id_pair const& pp) + // Handles all monomolecular reactions + { + reaction_rules const& rules(rules_.query_reaction_rule(pp.second.sid())); + + if ((int)::size(rules) == 0) + { + //LOG_DEBUG(("No single reaction rules." )); + return false; + } + + const Real rnd(rng_() / dt_); + Real k_cumm = 0.; + + // select one of the available reaction rules that led to the monomolecular reaction + for (typename boost::range_const_iterator::type + i(boost::begin(rules)), e(boost::end(rules)); i != e; ++i) + { + reaction_rule_type const& reaction_rule(*i); + k_cumm += reaction_rule.k(); + + LOG_DEBUG(("k_cumm=%g", k_cumm)); + + if (k_cumm > rnd) + // We have found the reaction rule that is in effect + { + // get the products + typename reaction_rule_type::species_id_range products(reaction_rule.get_products()); + + switch (::size(products)) + { + /*** NO PRODUCT / DECAY ***/ + case 0: + { + remove_particle(pp.first); // pp was just popped off the local queue so only has to be removed from the upstream particlecontainer. + break; + } + + /*** ONE PRODUCT ***/ + case 1: + { + const position_type reactant_pos( pp.second.position() ); + const structure_id_type reactant_structure_id( pp.second.structure_id() ); + const boost::shared_ptr reactant_structure( tx_.get_structure(reactant_structure_id) ); + const structure_id_type parent_structure_id( reactant_structure->structure_id() ); + const boost::shared_ptr parent_structure( tx_.get_structure( parent_structure_id ) ); + const species_type product_species( tx_.get_species(products[0]) ); + structure_id_type product_structure_id( reactant_structure_id ); + //default case, may be altered below + + // Set default values for the position and structure of the product + // As a default, the product stays on the structure of origin + boost::shared_ptr product_structure( reactant_structure ); + position_structid_pair_type product_pos_struct_id (std::make_pair(reactant_pos, reactant_structure_id)); + + //// 1 - CREATE NEW POSITION AND STRUCTURE ID + if( product_species.structure_type_id() != reactant_structure->sid() ) + { + LOG_DEBUG(("Attempting monomolecular reaction with one product." )); + + // Now determine the target structure id; if not staying on the origin structure, + // the particle is assumed to go to either the default structure or its parent structure, + // if different from the bulk. + // For a disk-bound particle it can be both the bulk and the cylinder + + // Initialize: by default the particle goes to the bulk + structure_id_type product_structure_id( tx_.get_def_structure_id() ); + + if( product_species.structure_type_id() == tx_.get_def_structure_type_id() ) + ; // the product lives in the bulk; do nothing, set correctly already above + + else if( product_species.structure_type_id() == parent_structure->sid() ) + // the product lives on the parent structure + product_structure_id = parent_structure_id; + + else + + throw not_implemented("Invalid product structure in monomolecular reaction: Must be either parent or default structure!"); + + // Get the structure behind the product_structure_id + const boost::shared_ptr product_structure( tx_.get_structure(product_structure_id) ); + // Just one more check for safety + assert( product_species.structure_type_id() == product_structure->sid() ); + + // Some debug info // TESTING + LOG_DEBUG(("Calling get_pos_sid_pair with:" )); + LOG_DEBUG((" reactant_structure = %s, sid = %s", + boost::lexical_cast(*reactant_structure).c_str(), + boost::lexical_cast( reactant_structure->sid()).c_str() )); + LOG_DEBUG((" product_structure = %s, sid = %s", + boost::lexical_cast(*product_structure).c_str(), + boost::lexical_cast( product_structure->sid()).c_str() )); + + // Produce new position and structure id + const position_structid_pair_type new_pos_sid_pair( reactant_structure->get_pos_sid_pair(*product_structure, reactant_pos, + product_species.radius(), reaction_length_, rng_) ); + // Apply boundary conditions + product_pos_struct_id = tx_.apply_boundary( new_pos_sid_pair ); + + // Particle is allowed to move after dissociation from surface. TODO Isn't it always allowed to move? + product_pos_struct_id = make_move(product_species, product_pos_struct_id, pp.first); + } + + //// 2 - CHECK FOR OVERLAPS + const particle_shape_type new_shape(product_pos_struct_id.first, product_species.radius()); + // Check for overlap with particles that are inside the propagator + const boost::scoped_ptr overlap_particles( + tx_.check_overlap(new_shape, pp.first)); + if(overlap_particles && overlap_particles->size() > 0) + { + throw propagation_error("no space due to other particle"); + } + // Check overlap with surfaces + // Ignore reactant and product structure + const bool product_mobile( product_species.D()>0.0 or product_species.v()>0 ); + if( product_mobile ) + { + const boost::scoped_ptr overlap_surfaces( + tx_.check_surface_overlap(new_shape, reactant_pos, product_pos_struct_id.second, product_species.radius(), + reactant_structure_id, product_structure_id )); + if (overlap_surfaces && overlap_surfaces->size() > 0) + { + throw propagation_error("no space due to near surface"); + } + } + else + LOG_WARNING(("Skipping surface overlap check for immobile particle.")); + // FIXME this is an ugly workaround to make disk particles ignore neighboring cylinders + + // Check for overlap with particles outside of the propagator (for example the eGFRD simulator) + if(vc_) + { + if (!(*vc_)(new_shape, pp.first)) + { + throw propagation_error("no space due to other particle (vc)"); + } + } + + //// 3 - PROCESS CHANGES + remove_particle(pp.first); + // Make new particle on right surface + const particle_id_pair product_particle( tx_.new_particle(product_species.id(), product_pos_struct_id.second, product_pos_struct_id.first) ); + + // Record changes + if(rrec_) + { + (*rrec_)( + reaction_record_type( + reaction_rule.id(), array_gen(product_particle.first), pp.first)); + } + break; + } + + /*** TWO PRODUCTS ***/ + case 2: + { + //// 0 - SOME NECESSARY DEFINITONS + const position_type reactant_pos(pp.second.position()); + const structure_id_type reactant_structure_id( pp.second.structure_id() ); + const boost::shared_ptr reactant_structure( tx_.get_structure( pp.second.structure_id() ) ); + const structure_type_id_type reactant_structure_type_id( reactant_structure->sid() ); + const structure_id_type parent_structure_id( reactant_structure->structure_id() ); + const boost::shared_ptr parent_structure( tx_.get_structure(parent_structure_id) ); + const structure_type_id_type parent_structure_type_id( parent_structure->sid() ); + const structure_id_type default_structure_id( tx_.get_def_structure_id() ); + const boost::shared_ptr default_structure( tx_.get_structure(default_structure_id) ); + const structure_type_id_type default_structure_type_id( tx_.get_def_structure_type_id() ); + + // The following will store the new positions and structure IDs + structure_id_type product0_struct_id; + structure_id_type product1_struct_id; + posstructid_posstructid_pair_type pos0pos1_pair; + + // Determine the structures on which the products will end up. + // By default we set both product structure to reactant_structure. + const boost::shared_ptr product0_structure(reactant_structure); + boost::shared_ptr product1_structure(reactant_structure); + // Also for the structure IDs + structure_id_type product0_structure_id( reactant_structure_id ); + structure_id_type product1_structure_id( reactant_structure_id ); + // Now check whether one of the products actually leaves reactant_structure according to the reaction rules. + species_type product0_species(tx_.get_species(products[0])); + species_type product1_species(tx_.get_species(products[1])); + + LOG_DEBUG(("Attempting monomolecular reaction with two products." )); + + // If the products live on different structures, one must be the parent of the other or the default structure. + // In this case we have to bring the products into the right order. + // We set: species 0 lives on the current structure; species 1 lives in the product structure (if applicable). + // Compare the structure type IDs of the product species to determine where products will end up: + + // If product structure types are different + if( product0_species.structure_type_id() != product1_species.structure_type_id() ) + { + // First check whether the reaction rule defines a legal dissociation reaction + // One of the products must stay in the reactant structure + if( !( product0_species.structure_type_id() == reactant_structure_type_id || + product1_species.structure_type_id() == reactant_structure_type_id ) + ) + throw not_implemented("Invalid product structure for first product in monomolecular reaction: One particle must stay behind on structure of origin!"); + + // One of the products must go into the parent or default structure + if( !( product0_species.structure_type_id() == parent_structure_type_id || + product0_species.structure_type_id() == default_structure_type_id || + product1_species.structure_type_id() == parent_structure_type_id || + product1_species.structure_type_id() == default_structure_type_id ) + ) + throw not_implemented("Invalid product structure for second product in monomolecular reaction: Must be either parent or default structure!"); + + // OK, reaction rule is legal; set the product species according to the above convention + if ( !(product0_species.structure_type_id() == reactant_structure_type_id) ) + std::swap(product0_species, product1_species); + + // Determine where the dissociating particle (product1) goes + if( product1_species.structure_type_id() == default_structure_type_id ) + product1_structure_id = default_structure_id; + + else if( product1_species.structure_type_id() == parent_structure_type_id ) + // the product lives on the parent structure + product1_structure_id = parent_structure_id; + + else + + throw not_implemented("Invalid product structure: Must be either parent structure or default structure!"); + + // If no exception was thrown, everything should be OK now. + // Change the default setting for product1 + product1_structure = tx_.get_structure(product1_structure_id); + } + // TODO Can we not outsource this part to the structure functions, too? + + //// 1 - GENERATE POSITIONS AND STRUCTURE IDs & CHECK FOR OVERLAPS + /* Create positions (np0, np1) for the reaction products. + The iv between the products lies within the reaction volume. */ + int i (max_retry_count_); + std::string error_message; + while (true) + { + // Escape clause #1: Too many tries + if (i-- == 0) + { + throw propagation_error(error_message); + } + + //// 1a - GENERATE POSITIONS AND STRUCTURE IDs FOR PRODUCT PARTICLES AFTER REACTION + // If the product particles do NOT live on the same structure => structure -> structure + (parent or default structure) dissociation. + // Else, the products live on the same structure AND on the same structure as the reactant. + // This is all taken care of by the structure functions get_pos_sid_pair_pair; the latter has to be called + // with the right order of parameters, i.e. passing first the species of the particle staying on the reactant_structure, + // then the species of the particle that leaves reactant_structure + + // Some debug info // TESTING + LOG_DEBUG(("Calling get_pos_sid_pair_pair with:" )); + LOG_DEBUG((" reactant_structure = %s, sid = %s", + boost::lexical_cast(*reactant_structure).c_str(), + boost::lexical_cast( reactant_structure->sid()).c_str() )); + LOG_DEBUG((" product1_structure = %s, sid = %s", + boost::lexical_cast(*product1_structure).c_str(), + boost::lexical_cast( product1_structure->sid()).c_str() )); + LOG_DEBUG((" Note: product0_structure = reactant_structure")); + + // Produce two new positions and structure IDs + // Note that reactant_structure = product0_structure here. + pos0pos1_pair = reactant_structure->get_pos_sid_pair_pair(*product1_structure, reactant_pos, product0_species, product1_species, reaction_length_, rng_ ); + // Remember the new structure IDs + product0_struct_id = pos0pos1_pair.first.second; + product1_struct_id = pos0pos1_pair.second.second; + LOG_DEBUG((" Applying boundaries to sampled positions: pos0=%s, pos1=%s", boost::lexical_cast(pos0pos1_pair.first.first).c_str(), + boost::lexical_cast(pos0pos1_pair.second.first).c_str() )); + // Apply the boundary conditions + const position_structid_pair_type pos_structid0 (tx_.apply_boundary( pos0pos1_pair.first )); + const position_structid_pair_type pos_structid1 (tx_.apply_boundary( pos0pos1_pair.second )); + pos0pos1_pair = std::make_pair(pos_structid0, pos_structid1); + + LOG_DEBUG((" New positions: pos0=%s, pos1=%s", boost::lexical_cast(pos0pos1_pair.first.first).c_str(), + boost::lexical_cast(pos0pos1_pair.second.first).c_str() )); + LOG_DEBUG((" New structure IDs: id0=%s, id1=%s", boost::lexical_cast(pos0pos1_pair.first.second).c_str(), + boost::lexical_cast(pos0pos1_pair.second.second).c_str() )); + + + //// 1b - CHECK FOR OVERLAPS + // Re-query positions, structure IDs + structures (may have changed after apply boundary) + product0_struct_id = pos0pos1_pair.first.second; + product1_struct_id = pos0pos1_pair.second.second; + const boost::shared_ptr product0_struct( tx_.get_structure(product0_struct_id) ); + const boost::shared_ptr product1_struct( tx_.get_structure(product1_struct_id) ); + const bool product0_mobile (product0_species.D()>0.0 or product0_species.v()>0); + const bool product1_mobile (product1_species.D()>0.0 or product1_species.v()>0); + + /* Now check for overlaps */ + const particle_shape_type new_shape0(pos0pos1_pair.first.first, product0_species.radius()); + // Check for overlap with particles in the propagator + const boost::scoped_ptr overlap_particles_product0( + tx_.check_overlap( new_shape0, pp.first)); + if(overlap_particles_product0 && overlap_particles_product0->size() > 0) + { + error_message = "no space for product0 due to particle overlap"; + continue; // try other positions pair + } + // Check for possible overlap with a surface, ignoring the product structures + if( product0_mobile ){ + + const boost::scoped_ptr overlap_structures_product0( + tx_.check_surface_overlap(new_shape0, reactant_pos, product0_struct_id, product0_species.radius(), + product0_struct_id, product1_struct_id ) ); + if(overlap_structures_product0 && overlap_structures_product0->size() > 0) + { + error_message = "no space for product0 due to surface overlap"; + continue; // try other positions pair + } + } + else + LOG_WARNING(("Skipping surface overlap check for immobile particle.")); + // FIXME this is an ugly workaround to make disk particles ignore neighboring cylinders + + /* Same for the other particle */ + const particle_shape_type new_shape1(pos0pos1_pair.second.first, product1_species.radius()); + const boost::scoped_ptr overlap_particles_product1( + tx_.check_overlap( new_shape1, pp.first)); + // Particle overlaps + if(overlap_particles_product1 && overlap_particles_product1->size() > 0) + { + error_message = "no space for product1 due to particle overlap"; + continue; // try other positions pair + } + // Surface overlaps + if( product1_mobile ){ + + const boost::scoped_ptr overlap_structures_product1( + tx_.check_surface_overlap(new_shape1, reactant_pos, product1_struct_id, product1_species.radius(), + product0_struct_id, product1_struct_id) ); + if(overlap_structures_product1 && overlap_structures_product1->size() > 0) + { + error_message = "no space for product1 due to surface overlap"; + continue; // try other positions pair + } + } + else + LOG_WARNING(("Skipping surface overlap check for immobile particle.")); + // FIXME this is an ugly workaround to make disk particles ignore neighboring cylinders + + //// 1c - CHECK FOR PLANE CROSSINGS + // first determine which one is the largest involved particle radius + length_type max_radius( pp.second.radius() ); // initialized with the reactant radius + if( product0_species.radius() > max_radius) max_radius = product0_species.radius(); + if( product1_species.radius() > max_radius) max_radius = product1_species.radius(); + + // get the "close" structures, i.e. the ones that are within max radius distance + boost::scoped_ptr close_structures( + tx_.check_surface_overlap( particle_shape_type( reactant_pos, max_radius + reaction_length_ ), reactant_pos, reactant_structure->id(), max_radius) ); + int N_close_structures(close_structures ? close_structures->size(): 0); + + // loop over all close structures + int j = 0; + while(j < N_close_structures) + { + // get the next close structure + const structure_id_pair_and_distance & close_struct( close_structures->at(j) ); + // and its center point and normal vector + position_type cs_center( close_struct.first.second->position() ); + position_type cs_comp_v( close_struct.first.second->side_comparison_vector() ); + + // project the distance of reactant and product positions to the center + // onto the normal vector of the structure + Real deltaR( dot_product(cs_comp_v, subtract(reactant_pos, cs_center)) ); + Real delta0( dot_product(cs_comp_v, subtract(pos0pos1_pair.first.first, cs_center)) ); + Real delta1( dot_product(cs_comp_v, subtract(pos0pos1_pair.second.first, cs_center)) ); + + // if the sign of the projections for the products is different from the one + // for the reactant the particle crossed the plane; in that case reject this + // set of new positions + if( deltaR * delta0 < 0.0 || deltaR * delta1 < 0.0) + { + LOG_WARNING(("Rejecting new product positions because of structure side crossing." )); + continue; // try other positions pair + } + + j++; + } + + // If no overlap occured -> positions are ok + break; + } + + //// 2 - GENERATE PARTICLE MOVES AFTER DISSOCIATION + pos0pos1_pair = make_pair_move(product0_species, product1_species, pos0pos1_pair, pp.first); + + // check overlap with particle outside of propagator (for example in eGFRD simulator) + if (vc_) + { + if (!(*vc_)(particle_shape_type(pos0pos1_pair.first.first, product0_species.radius()), pp.first) || + !(*vc_)(particle_shape_type(pos0pos1_pair.second.first, product1_species.radius()), pp.first)) + { + throw propagation_error("no space due to near particle (vc)"); + } + } + + //// 3 - PROCESS CHANGES + remove_particle(pp.first); + const particle_id_pair product0_particle(tx_.new_particle(product0_species.id(), pos0pos1_pair.first.second, pos0pos1_pair.first.first)); + const particle_id_pair product1_particle(tx_.new_particle(product1_species.id(), pos0pos1_pair.second.second, pos0pos1_pair.second.first)); + + // Record changes + if (rrec_) + { + (*rrec_)( + reaction_record_type( + reaction_rule.id(), + array_gen(product0_particle.first, product1_particle.first), + pp.first)); + } + break; + } + + /*** HIGHER PRODUCT NUMBERS - not supported ***/ + default: + throw not_implemented("monomolecular reactions that produce more than two products are not supported"); + } + // After we processed the reaction, we return -> no other reaction should be done. + return true; + } + } + // No monomolecular reaction has taken place. + return false; + } + + + /* The following are the methods that pick up the information from overlap situations + with both particles and structures. Then they query the reaction rules to figure out + with what precisely the particle is interacting. This is used to determine the outcome + of the interaction, which can fail for several reasons: + + - There is no interaction / reaction rule that supports this interaction. + - The reaction / interaction is not supported for the combination of structures involved + - The new particle position is not in the target structure + (TODO this should possibly be checked by structure functions) + - There is no space for the product (due to overlaps) + - The number of products as read from the reaction rule is unsupported for the current + type of reaction / interaction + */ + + /**********************/ + /*** PAIR REACTIONS ***/ + /**********************/ + const bool attempt_pair_reaction(particle_id_pair const& pp0, particle_id_pair const& pp1, + species_type const& s0, species_type const& s1) + // This handles the reaction between a pair of particles. + // pp0 is the initiating particle (also no longer in the queue) and pp1 is the target particle (still in the queue). + // pp0, pp1 and s0,s1 can either be in 3D/2D/1D or mixed. + { + reaction_rules const& rules(rules_.query_reaction_rule(pp0.second.sid(), pp1.second.sid())); + if(::size(rules) == 0) + throw propagation_error("trying to fire a reaction between particles without a reaction rule."); + + const Real k_tot( k_total(pp0.second.sid(), pp1.second.sid()) ); + const Real rnd( k_tot * rng_() ); + Real k_cumm = 0; + + // select one of the available reaction rules that led to the pair reaction + for (typename boost::range_const_iterator::type + i(boost::begin(rules)), e(boost::end(rules)); i != e; ++i) + { + reaction_rule_type const& reaction_rule(*i); + k_cumm += reaction_rule.k(); + + if (k_cumm >= rnd) + // We have found the reaction rule that is in effect + { + // get the products + const typename reaction_rule_type::species_id_range products(reaction_rule.get_products()); + + switch (::size(products)) + { + /*** NO PRODUCTS / DECAY ***/ + case 0: + { + remove_particle(pp0.first); + remove_particle(pp1.first); + break; + } + /*** ONE PRODUCT ***/ + case 1: + { + const structure_id_type reactant0_structure_id (pp0.second.structure_id()); + const structure_id_type reactant1_structure_id (pp1.second.structure_id()); + boost::shared_ptr reactant0_structure (tx_.get_structure( reactant0_structure_id )); + boost::shared_ptr reactant1_structure (tx_.get_structure( reactant1_structure_id )); + position_type reactant0_pos (pp0.second.position()); + position_type reactant1_pos (pp1.second.position()); + + const species_type product_species (tx_.get_species(products[0])); + const structure_type_id_type product_structure_type_id (product_species.structure_type_id()); + structure_id_type product_structure_id (reactant0_structure_id); + boost::shared_ptr product_structure (tx_.get_structure( product_structure_id )); + position_type product_pos (reactant0_pos); + + + //// 1 - GENERATE NEW POSITION AND STRUCTURE ID + // If the reactants live on adjacent structures of the same type + // TODO Can we outsource this also into the structure functions? + if ((s0.structure_type_id() == s1.structure_type_id()) && + (reactant0_structure_id != reactant1_structure_id)) + { + LOG_DEBUG(("Transforming postion of reactant1 into structure of reactant0")); // TESTING + const position_structid_pair_type pos_cyclic1 (tx_.cyclic_transpose(std::make_pair(reactant1_pos, reactant1_structure_id), + (*reactant0_structure) )); + reactant1_pos = pos_cyclic1.first; + LOG_DEBUG((" reactant1_pos=%s", boost::lexical_cast(reactant1_pos).c_str() )); // TESTING + } + + // calculate the product position (CoM of the two particles) + position_type reactants_CoM( tx_.apply_boundary( + divide( + add(multiply(reactant0_pos, s1.D()), + multiply(tx_.cyclic_transpose( + reactant1_pos, + reactant0_pos), s0.D())), + (s0.D() + s1.D()))) ); +// position_type reactants_CoM (tx_.calculate_pair_CoM(pp0.second.position(), pp1.second.position()), s0.D(), s1.D()); // TODO What is that? + + // Create a new position and structure_id for the center of mass. + // The function get_pos_sid_pair here automatically checks which of the two reactant structures is lower in hierarchy and makes + // this the target structure. It also checks whether that structure has the right structure_type_id as queried from the reaction + // rules before and passed via product_sid. + // get_pos_sid_pair should result in projecting the CoM onto the lower level origin structure in this case. If both reactant + // structures are the same, no projection should occur. This is handled correctly by the structure functions defined for equal + // origin_structure types. + + // This has to be passed to get_pos_sid_pair_2o because in some cases it will use feq() to compare whether a normal component + // is zero; for that a typical length scale is needed + const length_type typical_length( s0.radius() ); + + // Some debug info // TESTING + LOG_DEBUG(("Attempting pair reaction: calling get_pos_sid_pair with:" )); + LOG_DEBUG((" reactant0_structure = %s, sid = %s", + boost::lexical_cast(*reactant0_structure).c_str(), + boost::lexical_cast( reactant0_structure->sid()).c_str() )); + LOG_DEBUG((" reactant1_structure = %s, sid = %s", + boost::lexical_cast(*reactant1_structure).c_str(), + boost::lexical_cast( reactant1_structure->sid()).c_str() )); + LOG_DEBUG((" Note: product_structure_type_id = %s", boost::lexical_cast( product_structure_type_id).c_str() )); + LOG_DEBUG((" Note: reactants_CoM=%s", boost::lexical_cast(reactants_CoM).c_str() )); + + const position_structid_pair_type product_info_in_reactant_structure( reactant0_structure->get_pos_sid_pair_2o(*reactant1_structure, + product_structure_type_id, + reactants_CoM, typical_length, + reaction_length_, rng_ ) ); + // Apply the boundary conditions; this is particularly important here because the CoM projection as produced by the function above + // in some cases might end up out of the target_structure (e.g. in case the two reactants are coming from adjacent planes + const position_structid_pair_type product_pos_struct_id = tx_.apply_boundary(product_info_in_reactant_structure); + // TODO cyclic_transpose ? + // TODO check whether the new position is in the target structure? + + // Store the newly created particle info + product_pos = product_pos_struct_id.first; + product_structure_id = product_pos_struct_id.second; + + + //// 2 - CHECK FOR OVERLAPS + const particle_shape_type new_shape(product_pos, product_species.radius()); + // Check for overlap with particles that are inside the propagator + const boost::scoped_ptr overlap_particles( + tx_.check_overlap(new_shape, pp0.first, pp1.first)); + if( overlap_particles && overlap_particles->size() > 0 ) + { + throw propagation_error("no space due to particle"); + } + // Check for overlap with surfaces, ignoring the reactant structures + bool product_mobile( product_species.D() > 0.0 or product_species.v() > 0.0 ); + if( product_mobile ){ + + const boost::scoped_ptr overlap_structures( + tx_.check_surface_overlap(new_shape, product_pos, product_structure_id, product_species.radius(), + reactant0_structure_id, reactant1_structure_id )); + if(overlap_structures && overlap_structures->size() > 0) + { + throw propagation_error("no space due to near surface"); + } + } + else + LOG_WARNING(("Skipping surface overlap check for immobile particle.")); + // FIXME this is an ugly workaround to make disk particles ignore neighboring cylinders + + // Check for overlap with particles outside of the propagator (for example the eGFRD simulator) + if( vc_ ) + { + if (!(*vc_)(new_shape, pp0.first, pp1.first)) + { + throw propagation_error("no space due to particle (vc)"); + } + } + + //// 3 - PROCESS CHANGES + remove_particle(pp0.first); + remove_particle(pp1.first); + // Make new particle on right structure + particle_id_pair product_particle(tx_.new_particle(product_species.id(), product_structure_id, product_pos)); + // Record changes + if (rrec_) + { + (*rrec_)( + reaction_record_type( + reaction_rule.id(), array_gen(product_particle.first), pp0.first, pp1.first)); + } + break; + } + + /*** HIGHER PRODUCT NUMBERS - not supported ***/ + default: + throw not_implemented("bimolecular reactions that produce more than one product are not supported"); + } + // After we processed the reaction, we return -> no other reaction should be done. + return true; + } + } + // Should not reach here. + throw illegal_state("Pair reaction failed, no reaction selected when reaction was supposed to happen."); + } + + /********************/ + /*** INTERACTIONS ***/ + /********************/ + const bool attempt_interaction(particle_id_pair const& pp, position_type const& pos_in_struct, boost::shared_ptr const& product_structure) + // This handles the 'reaction' (interaction) between a particle and a structure. + // Returns True if the interaction was succesfull + { + // Get the reaction rules for the interaction with this structure. + reaction_rules const& rules(rules_.query_reaction_rule(pp.second.sid(), product_structure->sid() )); + if (::size(rules) == 0) + throw propagation_error("trying to fire an interaction with a structure without a reaction rule."); + + + const Real k_tot( k_total(pp.second.sid(), product_structure->sid()) ); + const Real rnd( k_tot * rng_() ); + Real k_cumm = 0; + + for (typename boost::range_const_iterator::type + i(boost::begin(rules)), e(boost::end(rules)); i != e; ++i) + { + reaction_rule_type const& reaction_rule(*i); + k_cumm += reaction_rule.k(); + + if (k_cumm >= rnd) + // We have found the reaction rule that is in effect + { + // get the products + const typename reaction_rule_type::species_id_range products(reaction_rule.get_products()); + + switch (::size(products)) + { + /*** NO PRODUCTS ***/ + case 0: + { + + remove_particle(pp.first); + break; + } + /*** ONE PRODUCT ***/ + case 1: + { + + const position_type reactant_pos( pp.second.position() ); + const boost::shared_ptr reactant_structure( tx_.get_structure(pp.second.structure_id()) ); + const structure_id_type reactant_structure_id( pp.second.structure_id() ); + const species_type product_species( tx_.get_species(products[0]) ); + + //// 1 - GET NEW POSITION ON THE TARGET STRUCTURE + // Some debug info // TESTING + LOG_DEBUG(("Attempting interaction: calling get_pos_sid_pair with:" )); + LOG_DEBUG((" reactant_structure = %s, sid = %s", + boost::lexical_cast(*reactant_structure).c_str(), + boost::lexical_cast( reactant_structure->sid()).c_str() )); + LOG_DEBUG((" product_structure = %s, sid = %s", + boost::lexical_cast(*product_structure).c_str(), + boost::lexical_cast( product_structure->sid()).c_str() )); + + const position_structid_pair_type new_pos_sid_pair_tmp( reactant_structure->get_pos_sid_pair(*product_structure, reactant_pos, + product_species.radius(), reaction_length_, rng_) ); + // Apply boundary conditions + // NOTE make_move is not called here because interactions are the result of a move before + const position_structid_pair_type new_pos_sid_pair = tx_.apply_boundary( new_pos_sid_pair_tmp ); + const position_type product_pos( new_pos_sid_pair.first ); + const structure_id_type product_structure_id( new_pos_sid_pair.second); + + // OLD VERSION + //const position_type product_pos( tx_.apply_boundary(pos_in_struct) ); + + + //// 2 - CHECK FOR OVERLAPS + const particle_shape_type new_shape(product_pos, product_species.radius()); + // Check for overlap with particles that are in the propagator + const boost::scoped_ptr overlap_particles( + tx_.check_overlap(new_shape, pp.first)); + if(overlap_particles && overlap_particles->size() > 0) + { + throw propagation_error("no space"); + } + // Check overlap with structures, ignoring reactant_structures + const bool product_mobile( product_species.D()>0.0 or product_species.v()>0 ); + if( product_mobile ){ + + const boost::scoped_ptr overlap_structures( + tx_.check_surface_overlap(new_shape, product_pos, product_structure_id, product_species.radius(), reactant_structure_id)); + + if(overlap_structures && overlap_structures->size() > 0 && product_mobile) + { + throw propagation_error("no space due to near surface"); + } + } + else + LOG_WARNING(("Skipping surface overlap check for immobile particle.")); + // FIXME this is an ugly workaround to make disk particles ignore neighboring cylinders + + // check for overlap with particles outside of the propagator (e.g. the eGFRD simulator) + if(vc_) + { + if (!(*vc_)(new_shape, pp.first)) + { + throw propagation_error("no space (vc)"); + } + } + + //// 3 - PROCESS CHANGES + remove_particle(pp.first); + // Make new particle in interaction structure + const particle_id_pair product_particle( tx_.new_particle(product_species.id(), product_structure_id, product_pos) ); + // Record changes + if(rrec_) + { + (*rrec_)( + reaction_record_type( + reaction_rule.id(), array_gen(product_particle.first), pp.first)); + } + break; + } + + /*** HIGHER PRODUCT NUMBERS - not supported ***/ + default: + throw not_implemented("structure interactions that produce more than one product are not supported"); + } + // After we processed the reaction, we return -> no other reaction should be done. + return true; + } + } + //should not reach here. + throw illegal_state("Surface interaction failed, no interaction selected when interaction was supposed to happen."); + } + + /*******************************/ + /*** COMMON HELPER FUNCTIONS ***/ + /*******************************/ + const position_structid_pair_type make_move(species_type const& species, position_structid_pair_type const& old_pos_struct_id, + particle_id_type const& ignore) const + // generates a move for a particle and checks if the move was allowed. + { + + // FIXME this is just redoing what we are doing above when moving a particle. + position_type new_pos(old_pos_struct_id.first); + structure_id_type new_structure_id(old_pos_struct_id.second); + + if(species.D() != 0) + { + const boost::shared_ptr old_structure( tx_.get_structure( new_structure_id ) ); + + const position_type displacement( old_structure->bd_displacement(species.v() * dt_, std::sqrt(2.0 * species.D() * dt_), rng_) ); + + const position_structid_pair_type pos_structid(tx_.apply_boundary( std::make_pair( add(old_pos_struct_id.first, displacement), old_pos_struct_id.second) )); + new_pos = pos_structid.first; + new_structure_id = pos_structid.second; + + // See if it overlaps with any particles + const particle_shape_type new_shape(new_pos, species.radius()); + const boost::scoped_ptr overlap_particles( + tx_.check_overlap( new_shape, ignore)); + if (overlap_particles && overlap_particles->size() > 0) + return old_pos_struct_id; + + // See if it overlaps with any surfaces. + const boost::scoped_ptr overlap_surfaces( + tx_.check_surface_overlap(new_shape, old_pos_struct_id.first, new_structure_id, species.radius())); // TODO include to ignore old_structure + if (overlap_surfaces && overlap_surfaces->size() > 0) + return old_pos_struct_id; + } + + return std::make_pair(new_pos, new_structure_id); + } + + + /* Given a position pair and two species, the function generates two new + positions based on two moves drawn from the free propagator. */ + const posstructid_posstructid_pair_type make_pair_move(species_type const& s0, species_type const& s1, + posstructid_posstructid_pair_type const& posstruct0_posstruct1, + particle_id_type const& ignore) const + { + const length_type min_distance_01( s0.radius() + s1.radius() ); + + position_structid_pair_type temp_posstruct0(posstruct0_posstruct1.first), + temp_posstruct1(posstruct0_posstruct1.second); + + //Randomize which species moves first, and if there will be one or two moves. (50% of the cases) + int move_count( 1 + rng_.uniform_int(0, 1) ); // determine number of particles to move (1 or 2) + bool move_first( rng_.uniform_int(0, 1) ); // Set to true if the first particle is moved, false means second particle. + while( move_count-- ) + { + if( move_first ) + // Move the first particle + { + temp_posstruct0 = make_move(s0, posstruct0_posstruct1.first, ignore); + // check for overlap with the second particle and potentially revert move + if (tx_.distance(temp_posstruct0.first, temp_posstruct1.first) < min_distance_01) + temp_posstruct0 = posstruct0_posstruct1.first; + } + else + // Move the second particle + { + temp_posstruct1 = make_move(s1, posstruct0_posstruct1.second, ignore); + // check for overlap with the first particle and potentially revert move + if (tx_.distance(temp_posstruct1.first, temp_posstruct0.first) < min_distance_01) + temp_posstruct1 = posstruct0_posstruct1.second; + } + + // Swap what particle to move + move_first = !move_first; + } + + return std::make_pair(temp_posstruct0, temp_posstruct1); + } + + /* Function returns the accumulated intrinsic reaction rate between to species. */ + /* Note that the species_id_type is equal to the structure_type_id_type for structure_types */ + const Real k_total(species_id_type const& s0_id, species_id_type const& s1_id) const + { + Real k_tot (0); + reaction_rules const& rules(rules_.query_reaction_rule(s0_id, s1_id)); + + // This should also work when 'rules' is empty. + for (typename boost::range_const_iterator::type + i(boost::begin(rules)), e(boost::end(rules)); i != e; ++i) + { + k_tot += (*i).k(); + } + return k_tot; + } + + void remove_particle(particle_id_type const& pid) + // Removes a particle from the propagator and particle container. + // Note that if the particle is no longer in the local list (we just popped it off and treating it currently), it will + // only be removed in the particle container (tx_) + { + LOG_DEBUG(("remove particle %s", boost::lexical_cast(pid).c_str())); + + tx_.remove_particle(pid); + typename particle_id_vector_type::iterator i( + std::find(queue_.begin(), queue_.end(), pid)); + if (queue_.end() != i) + { + queue_.erase(i); + } + } + + +/****************************/ +/***** MEMBER VARIABLES *****/ +/****************************/ +private: + particle_container_type& tx_; // The 'ParticleContainer' object that contains the particles. + network_rules_type const& rules_; + rng_type& rng_; + const Real dt_; + const int max_retry_count_; + reaction_recorder_type* const rrec_; + volume_clearer_type* const vc_; + particle_id_vector_type queue_; + int rejected_move_count_; + const Real reaction_length_; + static Logger& log_; +}; + +/*** LOGGER ***/ +template +Logger& newBDPropagator::log_(Logger::get_logger("ecell.newBDPropagator")); + + +#endif /* NEW_BD_PROPAGATOR_HPP */ + diff --git a/pair.py b/pair.py index f9e2643a..5b044004 100644 --- a/pair.py +++ b/pair.py @@ -3,14 +3,34 @@ from _greens_functions import * from greens_function_wrapper import * +from domain import ( + Domain, + ProtectiveDomain) +from single import ( + NonInteractionSingle, + InteractionSingle) +from multi import ( + Multi) +from shells import * +from utils import * + __all__ = [ 'CylindricalSurfacePair', 'PlanarSurfacePair', + 'PlanarSurfaceTransitionPair', 'SphericalPair', 'Pair', + 'SimplePair', + 'MixedPair2D3D', + 'MixedPair1D3D', + 'MixedPair1DStatic', ] -class Pair(object): +import logging +log = logging.getLogger('ecell') + + +class Pair(ProtectiveDomain, Others): """There are 3 types of pairs: * SphericalPair * PlanarSurfacePair @@ -23,172 +43,74 @@ class Pair(object): # 5.6: ~1e-8, 6.0: ~1e-9 CUTOFF_FACTOR = 5.6 - def __init__(self, domain_id, com, single1, single2, shell_id, r0, - shell_size, rt, surface): + def __init__(self, domain_id, shell_id, rrs): + # Calls required parent inits and sets variables. + # + # Sets: multiplicity, single1, single2, pid_particle_pair1, + # pid_particle_pair2, structure1, structure2, + # interparticle_rrs, interparticle_ktot, com, iv, r0, + # sigma + # + # Requires nothing to be set. + + # Call init from parent class(es) + ProtectiveDomain.__init__(self, domain_id, shell_id) + # Note: for the Others superclass nothing is to be initialized. + + assert self.testShell # assume that the testShell exists + assert isinstance(self.testShell, testPair) + + # Definitions self.multiplicity = 2 - self.num_shells = 1 - self.single1 = single1 - self.single2 = single2 + self.single1 = self.testShell.single1 + self.single2 = self.testShell.single2 + self.pid_particle_pair1 = self.testShell.pid_particle_pair1 + self.pid_particle_pair2 = self.testShell.pid_particle_pair2 - self.a_R, self.a_r = self.determine_radii(r0, shell_size) + self.structure1 = self.testShell.structure1 + self.structure2 = self.testShell.structure2 - self.rt = rt - - self.event_id = None - - self.last_time = 0.0 - self.dt = 0.0 - self.event_type = None - - self.surface = surface - - # Create shell. - shell = self.create_new_shell(com, shell_size, domain_id) - - self.shell_list = [(shell_id, shell), ] - self.domain_id = domain_id + self.interparticle_rrs = rrs + self.interparticle_ktot = self.calc_ktot(rrs) + # copy constants from the testShell so that we don't have to recalculate them. + self.com = self.testShell.com + self.iv = self.testShell.iv + self.r0 = self.testShell.r0 + self.sigma = self.testShell.sigma def __del__(self): if __debug__: log.debug('del %s' % str(self)) - def get_com(self): - return self.shell_list[0][1].shape.position - com = property(get_com) - - def get_shell_id(self): - return self.shell_list[0][0] - shell_id = property(get_shell_id) - - def get_shell(self): - return self.shell_list[0][1] - shell = property(get_shell) - - def get_shell_id_shell_pair(self): - return self.shell_list[0] - def set_shell_id_shell_pair(self, value): - self.shell_list[0] = value - shell_id_shell_pair = property(get_shell_id_shell_pair, - set_shell_id_shell_pair) - - def get_shell_size(self): - return self.shell_list[0][1].shape.radius - def get_D_tot(self): - return self.single1.pid_particle_pair[1].D + \ - self.single2.pid_particle_pair[1].D + return self.pid_particle_pair1[1].D + \ + self.pid_particle_pair2[1].D D_tot = property(get_D_tot) + D_r = property(get_D_tot) def get_D_R(self): - return (self.single1.pid_particle_pair[1].D * - self.single2.pid_particle_pair[1].D) / self.D_tot + return (self.pid_particle_pair1[1].D * + self.pid_particle_pair2[1].D) / self.D_tot D_R = property(get_D_R) - def get_v_tot(self): - return self.single2.pid_particle_pair[1].v - \ - self.single1.pid_particle_pair[1].v - v_tot = property(get_v_tot) - - v_r = 0 # Todo. - - def get_v_R(self): - return 0 # Todo. - return (self.single1.pid_particle_pair[1].v * - self.single2.pid_particle_pair[1].D + - self.single2.pid_particle_pair[1].v * - self.single1.pid_particle_pair[1].D) / self.D_tot - v_R = property(get_v_R) - - def getSigma(self): - return self.single1.pid_particle_pair[1].radius + \ - self.single2.pid_particle_pair[1].radius - sigma = property(getSigma) + def get_shell_direction(self): + return self.testShell.get_orientation_vector() def initialize(self, t): + # re-initialize the time parameters of the Domain to reuse it. + # THIS IS PROBABLY USELESS self.last_time = t self.dt = 0 self.event_type = None - def determine_radii(self, r0, shell_size): - """Determine a_r and a_R. - - Todo. Make dimension (1D/2D/3D) specific someday. Optimization only. - - """ - single1 = self.single1 - single2 = self.single2 - radius1 = single1.pid_particle_pair[1].radius - radius2 = single2.pid_particle_pair[1].radius - - D1 = single1.pid_particle_pair[1].D - D2 = single2.pid_particle_pair[1].D - - # Make sure that D1 != 0 to avoid division by zero in the followings. - if D1 == 0: - D1, D2 = D2, D1 - - shell_size /= SAFETY - - D_tot = D1 + D2 - D_geom = math.sqrt(D1 * D2) - - assert r0 >= self.sigma, \ - '%s; r0 %g < sigma %g' % (self, r0, self.sigma) - - # equalize expected mean t_r and t_R. - if ((D_geom - D2) * r0) / D_tot + shell_size +\ - math.sqrt(D2 / D1) * (radius1 - shell_size) - radius2 >= 0: - Da = D1 - Db = D2 - radiusa = radius1 - radiusb = radius2 - else: - Da = D2 - Db = D1 - radiusa = radius2 - radiusb = radius1 - - - #aR - a_R = (D_geom * (Db * (shell_size - radiusa) + \ - Da * (shell_size - r0 - radiusa))) /\ - (Da * Da + Da * Db + D_geom * D_tot) - - #ar - a_r = (D_geom * r0 + D_tot * (shell_size - radiusa)) / (Da + D_geom) - - assert a_R + a_r * Da / D_tot + radius1 >= \ - a_R + a_r * Db / D_tot + radius2 - - assert abs(a_R + a_r * Da / D_tot + radiusa - shell_size) \ - < 1e-12 * shell_size - - - if __debug__: - log.debug('a %g, r %g, R %g r0 %g' % - (shell_size, a_r, a_R, r0)) - if __debug__: - tr = ((a_r - r0) / math.sqrt(6 * self.D_tot))**2 - if self.D_R == 0: - tR = numpy.inf - else: - tR = (a_R / math.sqrt(6*self.D_R))**2 - log.debug('tr %g, tR %g' % (tr, tR)) - - - assert a_r > 0 - assert a_r > r0, '%g %g' % (a_r, r0) - assert a_R > 0 or (a_R == 0 and (D1 == 0 or D2 == 0)) - - return a_R, a_r - + # draws a first event time and the (not fully specified) type of event def draw_com_escape_or_iv_event_time_tuple(self, r0): """Returns a (event time, event type, reactingsingle=None) tuple. """ dt_com = draw_time_wrapper(self.com_greens_function()) - dt_iv = draw_time_wrapper(self.iv_greens_function(r0)) + dt_iv = draw_time_wrapper(self.iv_greens_function(r0)) # uses the full solution to draw IV first event time if dt_com < dt_iv: return dt_com, EventType.COM_ESCAPE, None else: @@ -217,8 +139,17 @@ def determine_next_event(self, r0): self.draw_single_reaction_time_tuple()) def draw_iv_event_type(self, r0): + gf = self.iv_greens_function(r0) - event_kind = draw_event_type_wrapper(gf, self.dt) + + if self.dt < TIME_TOLERANCE**2: + log.warning('Ridiculously small pair next-event time: pair.dt = %.30g < TIME_TOLERANCE^2, setting pair.dt = TIME_TOLERANCE^2 = %.30g' \ + % (self.dt, TIME_TOLERANCE**2) ) + dt = TIME_TOLERANCE**2 + else: + dt = self.dt + + event_kind = draw_event_type_wrapper(gf, dt) if event_kind == PairEventKind.IV_REACTION: return EventType.IV_REACTION elif event_kind == PairEventKind.IV_ESCAPE: @@ -234,60 +165,249 @@ def draw_new_positions(self, dt, r0, old_iv, event_type): new_com = self.draw_new_com(dt, event_type) new_iv = self.draw_new_iv(dt, r0, old_iv, event_type) - D1 = self.single1.pid_particle_pair[1].D - D2 = self.single2.pid_particle_pair[1].D + unit_z = self.get_shell_direction() + newpos1, newpos2, sid1, sid2 = self.do_back_transform(new_com, new_iv, + self.pid_particle_pair1[1].D, + self.pid_particle_pair2[1].D, + self.pid_particle_pair1[1].radius, + self.pid_particle_pair2[1].radius, + self.structure1, + self.structure2, + unit_z, self.world) + # sid = structure_id - newpos1 = new_com - new_iv * (D1 / self.D_tot) - newpos2 = new_com + new_iv * (D2 / self.D_tot) - return newpos1, newpos2 + return newpos1, newpos2, sid1, sid2 def draw_new_com(self, dt, event_type): + # draws a new coordinate for the CoM in world coordinates if event_type == EventType.COM_ESCAPE: r = self.a_R else: gf = self.com_greens_function() r = draw_r_wrapper(gf, dt, self.a_R) - displacement = self.create_com_vector(r) - # Add displacement to old CoM. This assumes (correctly) that # r0=0 for the CoM. Compare this to 1D singles, where r0 is not # necesseraly 0. - return self.com + displacement + + # note that we need to make sure that the com and com_vector are in the structure to prevent + # the particle leaving the structure due to numerical errors + return self.com + self.create_com_vector(r) def draw_new_iv(self, dt, r0, old_iv, event_type): gf = self.choose_pair_greens_function(r0, dt) if event_type == EventType.IV_ESCAPE: r = self.a_r elif event_type == EventType.IV_REACTION: - r = self.sigma + r = self.sigma # maybe this should be zero (product particle is at CoM) else: r = draw_r_wrapper(gf, dt, self.a_r, self.sigma) + # note that we need to make sure that the interparticle vector is in the structure to prevent + # the particle leaving the structure due to numerical errors return self.create_interparticle_vector(gf, r, dt, r0, old_iv) + def get_particles(self): + return [self.pid_particle_pair1, self.pid_particle_pair2] + particles = property(get_particles) + def check(self): + # perform internal consistency check pass def __str__(self): - sid = self.single1.pid_particle_pair[1].sid - sid2 = self.single2.pid_particle_pair[1].sid - name = self.world.model.get_species_type_by_id(sid)["name"] + sid1 = self.pid_particle_pair1[1].sid + sid2 = self.pid_particle_pair2[1].sid + name1 = self.world.model.get_species_type_by_id(sid1)["name"] name2 = self.world.model.get_species_type_by_id(sid2)["name"] - return 'Pair[%s: %s, %s, (%s, %s)]' % ( + return 'Pair[%s: %s, %s, (%s, %s)] with shell: %s' % ( self.domain_id, - self.single1.pid_particle_pair[0], - self.single2.pid_particle_pair[0], - name, name2) + self.pid_particle_pair1[0], + self.pid_particle_pair2[0], + name1, name2, + self.shell) + +class StandardPair(Pair): + + def __init__(self, domain_id, shell_id, rrs): + # Calls required parent inits and sets variables. + # + # Sets: a_R, a_r, strucutre, shell_size + # Requires: r0, LD_MAX + + # Inits from parent classes + Pair.__init__(self, domain_id, shell_id, rrs) + + # Definitions + self.structure = self.testShell.structure # structure on which both particles live + + shell_size = self.get_shell_size() # FIXME + # Needed by pair.init()?? No? + self.a_R, self.a_r = self.determine_radii(self.r0, shell_size) + # set the radii of the inner domains as a function of the outer protective domain + # (self.r0 is determined in Pair.__init__) + # determine_radii requires LD_MAX to be set + + + @ classmethod + def do_back_transform(cls, com, iv, D1, D2, radius1, radius2, structure1, structure2, unit_z, world): + # Here we assume that the com and iv are really in the structure and no adjustments + # have to be made + + # Since this is meant to be a general class, we do not check explicitly whether the structures + # are really the same, but drop a warning if they are not (A possible situation where this matters + # is when one particle is on a substructure of the other particle's structure; then the structure IDs + # are different, but the calculations then still work perfectly fine). + if __debug__ and structure1.id != structure2.id: + log.warn('Particles live on different structures in StandardPair, structure1=%s, structure2=%s' \ + % (structure1, structure2) ) + D_tot = D1 + D2 + if (D_tot != 0): + pos1 = com - iv * (D1 / D_tot) + pos2 = com + iv * (D2 / D_tot) + else: + pos1 = com - iv * 0.5 + pos2 = com + iv * 0.5 + + return pos1, pos2, structure1.id, structure2.id + + def get_shell_size(self): + return self.shell.shape.radius + + def determine_radii(self, r0, shell_size): + """Determine a_r and a_R from the size of the protective domain. + + Todo. Make dimension (1D/2D/3D) specific someday. Optimization only. + + """ + radius1 = self.pid_particle_pair1[1].radius + radius2 = self.pid_particle_pair2[1].radius + + D1 = self.pid_particle_pair1[1].D + D2 = self.pid_particle_pair2[1].D + + LD_MAX = self.LD_MAX # temporary value + a_r_max = LD_MAX * (r0 - self.sigma) + self.sigma + # a_r_max is the maximum size of a_r for a given maximum ratio of l/delta -class SphericalPair(Pair): + # Make sure that D1 != 0 to avoid division by zero in the followings. + if D1 == 0: + D1, D2 = D2, D1 # FIXME shouldn't we also here swap the particle radii if we're swapping diffusion contants? + if __debug__: + log.info('Swapping D1 and D2 because D1==0 (to avoid division by zero).') + + shell_size /= SAFETY #?? + + D_tot = D1 + D2 + D_geom = math.sqrt(D1 * D2) + + assert r0 >= self.sigma, \ + '%s; r0 %g < sigma %g' % (self, r0, self.sigma) + + # equalize expected mean t_r and t_R. + if ((D_geom - D2) * r0) / D_tot + shell_size +\ + math.sqrt(D2 / D1) * (radius1 - shell_size) - radius2 >= 0: + Da = D1 + Db = D2 + radiusa = radius1 + radiusb = radius2 + else: + Da = D2 + Db = D1 + radiusa = radius2 + radiusb = radius1 + + + # aR + a_R = (D_geom * (Db * (shell_size - radiusa) + \ + Da * (shell_size - r0 - radiusa))) /\ + (Da * Da + Da * Db + D_geom * D_tot) + + # ar + a_r = (D_geom * r0 + D_tot * (shell_size - radiusa)) / (Da + D_geom) + + + # Now if the planned domainsize for r is too large for proper convergence of the Green's + # functions, make it the maximum allowed size + if (a_r > a_r_max): + a_r = a_r_max + a_R = shell_size - radiusa - a_r * Da / D_tot + if __debug__: + log.info('Domainsize changed for convergence: a_r = a_r_max = %s, a_R = %s' % (a_r, a_R)) + + assert a_R + a_r * Da / D_tot + radiusa >= \ + a_R + a_r * Db / D_tot + radiusb + + assert abs(a_R + a_r * Da / D_tot + radiusa - shell_size) \ + < 1e-12 * shell_size # here the shell_size is the relevant scale + + + if __debug__: + log.debug('shell_size = %g, a_r = %g, a_R = %g r0 = %g' % + (shell_size, a_r, a_R, r0)) + if __debug__: + tr = ((a_r - r0)**2) / (6 * self.D_r) # the expected escape time of the IV + if self.D_R == 0: + tR = numpy.inf + else: + tR = (a_R**2) / (6 * self.D_R) # the expected escape time of the CoM + log.debug('tr = %g, tR = %g' % (tr, tR)) + + + assert fgreater(a_r, 0, typical=radiusa) + assert fgreater(a_r, r0, typical=radiusa), '%g %g' % (a_r, r0) + assert fgreater(a_R, 0, typical=radiusa) or (feq(a_R, 0) and (D1 == 0 or D2 == 0)) + + return a_R, a_r + + def __str__(self): + pass + +class SimplePair(StandardPair): + # Same as StandardPair, but we check explicitly whether the particles live on the same structure + # in the transform function. This function is widely used, so be careful in making changes, and + # make sure to also make them in the parent class. + def __init__(self, domain_id, shell_id, rrs): + + # Inits from parent classes + StandardPair.__init__(self, domain_id, shell_id, rrs) + + @ classmethod + def do_back_transform(cls, com, iv, D1, D2, radius1, radius2, structure1, structure2, unit_z, world): + # Here we assume that the com and iv are really in the structure and no adjustments + # have to be made + + # For simple pairs the particles live on the same structure + # Check explicitly + assert(structure1.id == structure2.id) + + D_tot = D1 + D2 + if (D_tot != 0): + pos1 = com - iv * (D1 / D_tot) + pos2 = com + iv * (D2 / D_tot) + else: + pos1 = com - iv * 0.5 + pos2 = com + iv * 0.5 + + return pos1, pos2, structure1.id, structure2.id + +class SphericalPair(SimplePair, hasSphericalShell): """2 Particles inside a (spherical) shell not on any surface. """ - def __init__(self, domain_id, com, single1, single2, shell_id, - r0, shell_size, rt, surface): - Pair.__init__(self, domain_id, com, single1, single2, shell_id, - r0, shell_size, rt, surface) + def __init__(self, domain_id, shell_id, testShell, rrs): + # Calls required parent inits and sets variables. + # + # Sets: LD_MAX + # Requires: nothing to be set + + assert isinstance(testShell, SphericalPairtestShell) + + # Parent classes + hasSphericalShell.__init__(self, testShell, domain_id) + + self.LD_MAX = numpy.inf # required by simplepair, set_radii + SimplePair.__init__(self, domain_id, shell_id, rrs) # Always initialize AFTER hasSphericalShell def com_greens_function(self): # Green's function for centre of mass inside absorbing sphere. @@ -296,21 +416,22 @@ def com_greens_function(self): def iv_greens_function(self, r0): # Green's function for interparticle vector inside absorbing # sphere. This exact solution is used for drawing times. - return GreensFunction3DRadAbs(self.D_tot, self.rt.ktot, r0, + return GreensFunction3DRadAbs(self.D_r, self.interparticle_ktot, r0, self.sigma, self.a_r) - def create_new_shell(self, position, radius, domain_id): - return SphericalShell(domain_id, Sphere(position, radius)) - + # selects between the full solution or an approximation where one of + # the boundaries is ignored def choose_pair_greens_function(self, r0, t): distance_from_sigma = r0 - self.sigma distance_from_shell = self.a_r - r0 threshold_distance = Pair.CUTOFF_FACTOR * \ - math.sqrt(6.0 * self.D_tot * t) + math.sqrt(6.0 * self.D_r * t) + # if sigma reachable if distance_from_sigma < threshold_distance: + # if shell reachable if distance_from_shell < threshold_distance: # near both a and sigma; # use GreensFunction3DRadAbs @@ -321,172 +442,849 @@ def choose_pair_greens_function(self, r0, t): # near sigma; use GreensFunction3DRadInf if __debug__: log.debug('GF: only sigma') - return GreensFunction3DRadInf(self.D_tot, self.rt.ktot, r0, + return GreensFunction3DRadInf(self.D_r, self.interparticle_ktot, r0, self.sigma) + + # sigma unreachable else: if distance_from_shell < threshold_distance: # near a; if __debug__: log.debug('GF: only a') - return GreensFunction3DAbs(self.D_tot, - r0, self.a_r) + return GreensFunction3DAbs(self.D_r, r0, self.a_r) else: # distant from both a and sigma; if __debug__: log.debug('GF: free') - return GreensFunction3D(self.D_tot, r0) + return GreensFunction3D(self.D_r, r0) def create_com_vector(self, r): return random_vector(r) def create_interparticle_vector(self, gf, r, dt, r0, old_iv): + if __debug__: log.debug("create_interparticle_vector: r=%g, dt=%g", r, dt) theta = draw_theta_wrapper(gf, r, dt) - new_inter_particle_s = numpy.array([r, theta, - myrandom.uniform() * 2 * Pi]) - new_iv = spherical_to_cartesian(new_inter_particle_s) - - #FIXME: need better handling of angles near zero and pi. - - # I rotate the new interparticle vector along the - # rotation axis that is perpendicular to both the - # z-axis and the original interparticle vector for - # the angle between these. - - # the rotation axis is a normalized cross product of - # the z-axis and the original vector. - # rotation_axis = crossproduct([0,0,1], inter_particle) - angle = vector_angle_against_z_axis(old_iv) - if angle % numpy.pi != 0.0: + if theta == 0.0: + # no rotation is necessary -> new_iv = new_iv + new_iv = old_iv + elif theta % numpy.pi != 0.0: + # alternative calculation + # rotate the old_iv to the new theta rotation_axis = crossproduct_against_z_axis(old_iv) rotation_axis = normalize(rotation_axis) - rotated = rotate_vector(new_iv, rotation_axis, angle) - elif angle == 0.0: - rotated = new_iv + new_iv = rotate_vector(old_iv, rotation_axis, theta) + + # rotate the new_iv around the old_iv with angle phi + phi = myrandom.uniform() * 2 * Pi + rotation_axis = normalize(old_iv) + new_iv = rotate_vector(new_iv, rotation_axis, phi) else: - rotated = numpy.array([new_iv[0], new_iv[1], - new_iv[2]]) - return rotated + # theta == numpi.pi -> just mirror the old_iv + new_iv = -old_iv + + # adjust length of the vector + # note that r0 = length (old_iv) + new_iv = (r/r0) * new_iv + + return new_iv def __str__(self): return 'Spherical' + Pair.__str__(self) -class PlanarSurfacePair(Pair): - """2 Particles inside a (cylindrical) shell on a PlanarSurface. - (Hockey pucks). +class PlanarSurfacePair(StandardPair, hasCylindricalShell): + """ 2 Particles inside a (cylindrical) shell on a PlanarSurface. + (Hockey pucks). + Note that in this class they are in principle allowed to + live on different structures. """ - def __init__(self, domain_id, com, single1, single2, shell_id, - r0, shell_size, rt, surface): - Pair.__init__(self, domain_id, com, single1, single2, shell_id, - r0, shell_size, rt, surface) + def __init__(self, domain_id, shell_id, testShell, rrs): + # Calls required parent inits and sets variables. + # + # Sets: LD_MAX + # Requires: Nothing to be set. + + assert isinstance(testShell, PlanarSurfacePairtestShell) + hasCylindricalShell.__init__(self, testShell, domain_id) + + self.LD_MAX = 20 # Required by SimplePair.__init__ + StandardPair.__init__(self, domain_id, shell_id, rrs) + + self.ignored_structure_ids = testShell.ignored_structure_ids def com_greens_function(self): - # Todo. 2D gf Abs Sym. - return GreensFunction3DAbsSym(self.D_R, self.a_R) + return GreensFunction2DAbsSym(self.D_R, self.a_R) def iv_greens_function(self, r0): - # Todo. 2D gf Rad Abs. - # This exact solution is used for drawing times. - return GreensFunction3DRadAbs(self.D_tot, self.rt.ktot, r0, - self.sigma, self.a_r) - - def create_new_shell(self, position, radius, domain_id): - # The half_length (thickness/2) of a hockey puck is not more - # than it has to be (namely the radius of the particle), so if - # the particle undergoes an unbinding reaction we still have to - # clear the target volume and the move may be rejected (NoSpace - # error). - orientation = crossproduct(self.surface.shape.unit_x, - self.surface.shape.unit_y) - half_length = max(self.single1.pid_particle_pair[1].radius, - self.single2.pid_particle_pair[1].radius) - return CylindricalShell(domain_id, Cylinder(position, radius, - orientation, half_length)) - - a_R, a_r = self.determine_radii() + return GreensFunction2DRadAbs(self.D_r, self.interparticle_ktot, r0, + self.sigma, self.a_r) def choose_pair_greens_function(self, r0, t): - # Todo - return self.iv_greens_function(r0) + # selects between the full solution or an approximation where one of + # the boundaries is ignored + distance_from_sigma = r0 - self.sigma + distance_from_shell = self.a_r - r0 + + threshold_distance = Pair.CUTOFF_FACTOR * \ + math.sqrt(4.0 * self.D_r * t) + + if distance_from_sigma < threshold_distance: + + if distance_from_shell < threshold_distance: + # near both a and sigma; + # use GreensFunction2DRadAbs + if __debug__: + log.debug('GF2D: normal') + return self.iv_greens_function(r0) + else: + # near sigma; use GreensFunction2DRadInf + if __debug__: + log.debug('GF2D: only sigma') + return self.iv_greens_function(r0) + # Todo + # return GreensFunction2DRadInf(self.D_r, self.interparticle_ktot, r0, self.sigma) + else: + if distance_from_shell < threshold_distance: + # near a; + if __debug__: + log.debug('GF2D: only a') + return self.iv_greens_function(r0) + # Todo + # return GreensFunction2DAbs(self.D_r, r0, self.a_r) + + else: + # distant from both a and sigma; + if __debug__: + log.debug('GF2D: free') + return self.iv_greens_function(r0) + # Todo + # return GreensFunction2D(self.D_r, r0) def create_com_vector(self, r): x, y = random_vector2D(r) - return x * self.surface.shape.unit_x + y * self.surface.shape.unit_y + # project the com onto the surface unit vectors to make sure that the coordinates are in the surface + return x * self.structure.shape.unit_x + y * self.structure.shape.unit_y def create_interparticle_vector(self, gf, r, dt, r0, old_iv): + if __debug__: log.debug("create_interparticle_vector: r=%g, dt=%g", r, dt) theta = draw_theta_wrapper(gf, r, dt) - #FIXME: need better handling of angles near zero and pi? - unit_x = self.surface.shape.unit_x - unit_y = self.surface.shape.unit_y - angle = vector_angle(unit_x, old_iv) - # Todo. Test if nothing changes when theta == 0. - new_angle = angle + theta + # note that r0 = length (old_iv) + new_iv = (r/r0) * rotate_vector(old_iv, self.structure.shape.unit_z, theta) + # note that unit_z can point two ways rotating the vector clockwise or counterclockwise + # Since theta is symmetric this doesn't matter. - new_iv = r * math.cos(new_angle) * unit_x + \ - r * math.sin(new_angle) * unit_y + # project the new_iv down on the unit vectors of the surface to prevent the particle from + # leaving the surface due to numerical problem + unit_x = self.structure.shape.unit_x + unit_y = self.structure.shape.unit_y - return new_iv + new_iv_x = unit_x * numpy.dot(new_iv, unit_x) + new_iv_y = unit_y * numpy.dot(new_iv, unit_y) + + return new_iv_x + new_iv_y def __str__(self): return 'PlanarSurface' + Pair.__str__(self) -class CylindricalSurfacePair(Pair): +class PlanarSurfaceTransitionPair(SimplePair, hasSphericalShell): + """2 Particles on two different but adjacent and orthogonal planar surfaces + + """ + # The test shell already defines structure1 and structure2 and should set + # structure=structure1 by default. + # We do all calculations here in structure1 assuming that pos2 has been + # "deflected back" properly into structure1 at test shell construction. + # For safety each occurance of structure is replaced by structure1. + # Otherwise this class is equal to PlanarSurfacePair inheriting from + # hasSphericalShell instead of hasCylindricalShell and a modified + # do_back_transform() method. + def __init__(self, domain_id, shell_id, testShell, rrs): + # Calls required parent inits and sets variables. + # + # Sets: structure1, strucutre2, structure, LD_MAX + # Requires: nothing to be set + + assert isinstance(testShell, PlanarSurfaceTransitionPairtestShell) + + hasSphericalShell.__init__(self, testShell, domain_id) + self.LD_MAX = numpy.inf # TODO was 20. Why? Because of convergence issues! Maybe not really an issue here though + # Required by SimplePair.__init__ + SimplePair.__init__(self, domain_id, shell_id, rrs) # Always initialize AFTER hasSphericalShell + + # extra definitions for the TransitionPair + self.structure1 = self.testShell.structure1 + self.structure2 = self.testShell.structure2 + # self.structure is equal to self.structure1 and inherited from the testShell + # and SimplePair + + def com_greens_function(self): + return GreensFunction2DAbsSym(self.D_R, self.a_R) + + def iv_greens_function(self, r0): + return GreensFunction2DRadAbs(self.D_r, self.interparticle_ktot, r0, + self.sigma, self.a_r) + + def choose_pair_greens_function(self, r0, t): + # selects between the full solution or an approximation where one of + # the boundaries is ignored + distance_from_sigma = r0 - self.sigma + distance_from_shell = self.a_r - r0 + + threshold_distance = Pair.CUTOFF_FACTOR * \ + math.sqrt(4.0 * self.D_r * t) + + if distance_from_sigma < threshold_distance: + + if distance_from_shell < threshold_distance: + # near both a and sigma; + # use GreensFunction2DRadAbs + if __debug__: + log.debug('GF2D: normal') + return self.iv_greens_function(r0) + else: + # near sigma; use GreensFunction2DRadInf + if __debug__: + log.debug('GF2D: only sigma') + return self.iv_greens_function(r0) + # Todo + # return GreensFunction2DRadInf(self.D_r, self.interparticle_ktot, r0, self.sigma) + else: + if distance_from_shell < threshold_distance: + # near a; + if __debug__: + log.debug('GF2D: only a') + return self.iv_greens_function(r0) + # Todo + # return GreensFunction2DAbs(self.D_r, r0, self.a_r) + + else: + # distant from both a and sigma; + if __debug__: + log.debug('GF2D: free') + return self.iv_greens_function(r0) + # Todo + # return GreensFunction2D(self.D_r, r0) + + def create_com_vector(self, r): + x, y = random_vector2D(r) + # project the com onto the surface unit vectors to make sure that the coordinates are in the surface + # note that we use structure1 here, assuming that position of particle2 has been projected corretly + # into this plane at construction of the test shell + return x * self.structure1.shape.unit_x + y * self.structure1.shape.unit_y + + def create_interparticle_vector(self, gf, r, dt, r0, old_iv): + + if __debug__: + log.debug("create_interparticle_vector: r=%g, dt=%g", r, dt) + theta = draw_theta_wrapper(gf, r, dt) + + # note that r0 = length (old_iv) + new_iv = (r/r0) * rotate_vector(old_iv, self.structure1.shape.unit_z, theta) + # note that unit_z can point two ways rotating the vector clockwise or counterclockwise + # Since theta is symmetric this doesn't matter. + + # project the new_iv down on the unit vectors of the surface to prevent the particle from + # leaving the surface due to numerical problem + unit_x = self.structure1.shape.unit_x + unit_y = self.structure1.shape.unit_y + + new_iv_x = unit_x * numpy.dot(new_iv, unit_x) + new_iv_y = unit_y * numpy.dot(new_iv, unit_y) + + return new_iv_x + new_iv_y + + @ classmethod + def do_back_transform(cls, com, iv, D1, D2, radius1, radius2, structure1, structure2, unit_z, world): + + # This function is called every time a PlanarSurfaceSingle dissociates. Sometimes this may + # require to deflect the particle towards an orthogonal neighboring plane. Then an additional + # seperation factor is applied to the interparticle vector to overcome predictable overlaps. + # + # Note that this function also is called when singles dissociate on one single plane + # in periodic boundary conditions. An extra check prevents that the correction factor + # is erronously applied in these cases. + + # Calculate the new positions (should be still in structure1) + D_tot = D1 + D2 + pos1 = com - iv * (D1 / D_tot) + pos2 = com + iv * (D2 / D_tot) + + # Tentatively apply boundaries; this will also set the new structure IDs. + # In some cases the new positions may end up on adjacent planar surfaces. + # This may lead to an overlap which has to be checked for and removed if present + # in the next step. + # Note that this will be also done for unconnected planes, so it has to be + # made sure that apply_boundary does work correctly in these cases. + new_pos1, new_sid1 = world.apply_boundary((pos1, structure1.id)) + new_pos2, new_sid2 = world.apply_boundary((pos2, structure1.id)) + + new_structure1 = world.get_structure(new_sid1) + new_structure2 = world.get_structure(new_sid2) + + # If the new positions lead to an overlap we have to enlarge the IV by a safety factor + # Only to this correction if the two planes are really orthogonal (assumed in the calculation) + if world.distance(new_pos1, new_pos2) <= (radius1+radius2) * MINIMAL_SEPARATION_FACTOR \ + and feq( numpy.dot(new_structure1.shape.unit_z, new_structure2.shape.unit_z), 0.0 ) : + + log.warn('do_back_transform: Removing overlap resulting from deflection of particles at the edge of orthogonal planes.') + + # Calculate the distances from the two new positions to the edge between the planes + # in which the particles temporarily ended up + # This is easily done by projecting new_pos1 into new_structure2 + vice versa and + # computing the orthogonal components of the projections: + _, (pos1_orth, _) = new_structure2.project_point(new_pos1) + _, (pos2_orth, _) = new_structure1.project_point(new_pos2) + + l_iv = length(iv) + assert(2.0*abs(pos1_orth*pos2_orth) < (l_iv*l_iv)) # That should never fail! + iv_safety = 1.0/( 1.0 - 2.0*abs(pos1_orth*pos2_orth)/(l_iv*l_iv) ) + # Recalculate the interparticle vector and new positions + iv_rescaled = iv_safety * iv + pos1 = com - iv_rescaled * (D1 / D_tot) + pos2 = com + iv_rescaled * (D2 / D_tot) + + # Re-apply boundaries + new_pos1, new_sid1 = world.apply_boundary((pos1, structure1.id)) + new_pos2, new_sid2 = world.apply_boundary((pos2, structure1.id)) + + # TODO: Check that the new positions are in their new planes and + # maybe also that pos1 and pos2 are in structure1 in the first place? + + return new_pos1, new_pos2, new_sid1, new_sid2 + + def draw_new_com(self, dt, event_type): + # Draws a new coordinate for the CoM in world coordinates. + # Since fire_pair_reaction() will call this function we have to + # change the default draw_new_com() to ensure that the new CoM + # is deflected into the right plane + if event_type == EventType.COM_ESCAPE: + r = self.a_R + else: + gf = self.com_greens_function() + r = draw_r_wrapper(gf, dt, self.a_R) + + new_com = self.com + self.create_com_vector(r) + + if event_type == EventType.IV_REACTION: + # Express the new CoM in plane1 and deflect it to plane2 if necessary + # TODO This should be taken care of by apply_boundary() in the main routine + s1_ctr = self.structure1.shape.position + new_com, changeflag = self.structure2.deflect(s1_ctr, new_com - s1_ctr) + if __debug__: + log.debug('Deflected new_com on structure2, changeflag=%s' % str(changeflag)) + + # TODO: Check whether the new CoM is in the right structure + return new_com + + def get_shell_size(self): + return self.shell.shape.radius + + def __str__(self): + return 'PlanarSurfaceTransition' + Pair.__str__(self) + + +class CylindricalSurfacePair(SimplePair, hasCylindricalShell): """2 Particles inside a (cylindrical) shell on a CylindricalSurface. (Rods). """ - def __init__(self, domain_id, com, single1, single2, shell_id, - r0, shell_size, rt, surface): - Pair.__init__(self, domain_id, com, single1, single2, shell_id, - r0, shell_size, rt, surface) + def __init__(self, domain_id, shell_id, testShell, rrs): + # Calls required parent inits and sets variables. + # + # Sets: LD_MAX + # Requires: nothing to be set + + assert isinstance(testShell, CylindricalSurfacePairtestShell) + + hasCylindricalShell.__init__(self, testShell, domain_id) + + self.LD_MAX = numpy.inf # Required by SimplePair.__init__ + SimplePair.__init__(self, domain_id, shell_id, rrs) + + def get_shell_size(self): + return self.shell.shape.half_length + + def get_v_tot(self): + # 'direction' signifies the direction of the IV with respect to the surface unit_z + # (which defines the direction of positive drift) + direction = cmp(numpy.dot (self.iv, self.structure.shape.unit_z), 0) + return (self.pid_particle_pair2[1].v - \ + self.pid_particle_pair1[1].v) * direction + + # NOTE the v_tot and v_r are now positive in the direction of the IV! + v_tot = property(get_v_tot) + + v_r = property(get_v_tot) + + def get_v_R(self): + # The v_R is always in the direction of the structure, no adjustment required. + return (self.pid_particle_pair1[1].v * + self.pid_particle_pair2[1].D + + self.pid_particle_pair2[1].v * + self.pid_particle_pair1[1].D) / self.D_tot + v_R = property(get_v_R) def com_greens_function(self): # The domain is created around r0, so r0 corresponds to r=0 within the domain return GreensFunction1DAbsAbs(self.D_R, self.v_R, 0.0, -self.a_R, self.a_R) def iv_greens_function(self, r0): - return GreensFunction1DRadAbs(self.D_tot, self.v_r, self.rt.ktot, r0, self.sigma, self.a_r) - - def create_new_shell(self, position, half_length, domain_id): - # The radius of a rod is not more than it has to be (namely the - # radius of the biggest particle), so if the particle undergoes - # an unbinding reaction we still have to clear the target volume - # and the move may be rejected (NoSpace error). - radius = max(self.single1.pid_particle_pair[1].radius, - self.single2.pid_particle_pair[1].radius) - orientation = self.surface.shape.unit_z - return CylindricalShell(domain_id, Cylinder(position, radius, - orientation, half_length)) + return GreensFunction1DRadAbs(self.D_r, self.v_r, self.interparticle_ktot, r0, self.sigma, self.a_r) def choose_pair_greens_function(self, r0, t): # Todo return self.iv_greens_function(r0) - def create_com_vector(self, r): - return r * self.surface.shape.unit_z + def create_com_vector(self, z): + # 'z' can be interpreted in two different ways here, it may a coordinate in the z direction or it may + # be a displacement from the origin. In the first case it will already contain the drift information. + if self.v_R == 0.0: + # if there is no drift then we regard 'z' as a displacement from the origin. Although + # it actually represents a coordinate when drawR was called. It is a displacement when z=a. + # We randomize the direction. + z = myrandom.choice(-1, 1) * z + + elif self.v_R != 0.0 and feq(z, self.a_R): + # When there is drift and z=a, the 'z' actually represent the displacement from the origin and a + # boundary must be chosen. + + # The Escape can be either to the left or to the right. + # The side of escape should be decided on by the flux through both boundaries at the escape time + gf = self.com_greens_function() + event_kind = draw_event_type_wrapper(gf, self.dt) + if event_kind == PairEventKind.IV_REACTION: # IV_REACTION -> ESCAPE through 'left' boundary + z = -z # -self.a_R + elif event_kind == PairEventKind.IV_ESCAPE: # IV_ESCAPE -> ESCAPE through 'right' boundary + #z = z # self.a_R + pass + else: + raise NotImplemented() + else: + # When there was drift and the particle was not at the boundary. + # -> In this case the 'z' actually signifies a coordinate and nothing has to be done. + pass + + # project the com onto the surface unit vector to make sure that the coordinates are in the surface + return z * self.structure.shape.unit_z def create_interparticle_vector(self, gf, r, dt, r0, old_iv): if __debug__: log.debug("create_interparticle_vector: r=%g, dt=%g", r, dt) - # Note: using self.surface.shape.unit_z here might accidently - # interchange the particles. - return r * normalize(old_iv) - def get_shell_size(self): - # Heads up. - return self.shell_list[0][1].shape.half_length + # note that r0 = length (old_iv) + new_iv = (r/r0) * old_iv + + # project the new_iv down on the unit vectors of the surface to prevent the particle from + # leaving the surface due to numerical problem + unit_z = self.structure.shape.unit_z + new_iv_z = unit_z * numpy.dot(new_iv, unit_z) + + return new_iv_z def __str__(self): return 'CylindricalSurface' + Pair.__str__(self) +class MixedPair2D3D(Pair, hasCylindricalShell): + + def __init__(self, domain_id, shell_id, testShell, rrs): + # Calls required parent inits and sets variables. + # + # Sets: structure2D, structure3D, z_scaling_factor, a_R, a_r + # Requires: nothing to be set + + assert isinstance(testShell, MixedPair2D3DtestShell) + hasCylindricalShell.__init__(self, testShell, domain_id) + Pair.__init__(self, domain_id, shell_id, rrs) + + assert isinstance(self.testShell, testMixedPair2D3D) + + # Some definitions + self.structure2D = self.testShell.structure2D # the surface on which particle1 lives + self.structure3D = self.testShell.structure3D # structure on which particle2 lives + self.particle2D = self.testShell.particle2D + self.particle3D = self.testShell.particle3D + + # initialize some useful constants + self.z_scaling_factor = self.testShell.get_scaling_factor() + + # set the radii of the inner domains as a function of the outer protective domain + self.a_R, self.a_r = self.determine_radii() + + # The latter is defined for completeness and used by check_domain() in egfrd.py: + # (Note that that origin and target end up in same list.) + self.origin_structure = self.testShell.origin_structure + self.target_structure = self.testShell.target_structure + + def determine_radii(self): + # Determines the dimensions of the domains used for the Green's functions + # from the dimensions of the cylindrical shell. + # Note that the function assumes that the cylinder is dimensioned properly. + radius2D = self.particle2D.radius + radius3D = self.particle3D.radius + D_2D = self.particle2D.D + D_3D = self.particle3D.D + + shell_radius = self.shell.shape.radius + shell_half_length = self.shell.shape.half_length + z_left = radius2D + z_right = 2.0*shell_half_length - z_left + + # Use class methods to check dimensions of the cylinder + r_check = self.testShell.r_right(z_right) + hl_check = (self.testShell.z_right(shell_radius) + self.testShell.z_left(shell_radius)) / 2.0 + if __debug__: + assert feq(r_check, shell_radius) and feq(hl_check, shell_half_length), \ + 'MixedPair2D3D: Domain did not obey scaling relationship. ' \ + 'shell_radius = %s, shell_half_length = %s, radius_check = %s, half_length_check = %s, ' \ + 'z_left = %s, z_right = %s' % \ + (FORMAT_DOUBLE % shell_radius, FORMAT_DOUBLE % shell_half_length, + FORMAT_DOUBLE % r_check, FORMAT_DOUBLE % hl_check, + FORMAT_DOUBLE % z_left, FORMAT_DOUBLE % z_right ) + + # Partition the space in the protective domain over the IV and the CoM domains + # The outer bound of the interparticle vector is set by the particle diffusing in 3D via: + a_r = (z_right - radius3D) * self.z_scaling_factor + + # Calculate the maximal displacement of a particle given an interparticle vector bound a_r + # taking into account the radius for both the 2D and 3D particle. This sets the remaining + # space for the CoM diffusion. + space_for_iv = max( (D_2D/self.D_tot) * a_r + radius2D, + (D_3D/self.D_tot) * a_r + radius3D) + + # It then determines the CoM vector bound via + a_R = shell_radius - space_for_iv + if feq(a_R, 0.0, typical=shell_radius): + a_R = 0.0 + log.warn('determine_radii: setting a_R = %s to zero' % str(a_R)) + + # Print the domain sizes and their estimated first passage times. + if __debug__: + tr = ((a_r - self.r0)**2.0) / (6.0 * self.D_r) # the expected escape time of the iv + if self.D_R == 0: + tR = numpy.inf + log.warn('determine_radii: infinite diffusion time for CoM, tR = inf, because D_R = 0') + else: + tR = (a_R**2.0) / (4.0 * self.D_R) # the expected escape time of the CoM + log.debug('determine_radii: a_r = %s, tr = %s, a_R = %s, tR = %s, delta_tRr = %s' % \ + (FORMAT_DOUBLE % a_r, FORMAT_DOUBLE % tr, + FORMAT_DOUBLE % a_R, FORMAT_DOUBLE % tR, + FORMAT_DOUBLE % (tr-tR) )) + +# assert feq(tr, tR), 'estimate first passage times were not equal' ## FIXME + + # Some checks that shall never fail + assert (self.sigma < a_r) and (a_r < 2.0*shell_half_length * self.z_scaling_factor) + assert (0 <= a_R) and (a_R <= shell_radius) + if a_R == 0.0: + log.warn('determine_radii: a_R = 0') + if a_R == shell_radius: + log.warn('determine_radii: a_R = shell_radius') + + return a_R, a_r + + @ classmethod + def do_back_transform(cls, com, iv, D1, D2, radius1, radius2, structure2D, structure3D, unit_z, world): + # here we assume that the com and iv are really in the structure and no adjustments have to be + # made + # + # structure3D is not used here but has to be passed because of the classmethod property + + D_tot = D1 + D2 + weight1 = D1 / D_tot + weight2 = D2 / D_tot + + z_btf = math.sqrt(D2/D_tot) # z back transform factor + + # get the coordinates of the iv relative to the system of the surface (or actually the shell) + iv_x = structure2D.shape.unit_x * numpy.dot(iv, structure2D.shape.unit_x) + iv_y = structure2D.shape.unit_y * numpy.dot(iv, structure2D.shape.unit_y) + + iv_x_length = length(iv_x) + iv_y_length = length(iv_y) + + # reflect the coordinates in the unit_z direction back to the side of the membrane + # where the domain is. Note that it's implied that the origin of the coordinate system lies in the + # plane of the membrane + iv_z_length = abs(numpy.dot(iv, unit_z)) # FIXME maybe first project the shell unit_z onto the + # surface unit_z to prevent numerical problems? + # do the reverse scaling in z-direction + iv_z_length_btf = iv_z_length * z_btf + + # The following is to avoid overlaps in case that the z-component of the IV is scaled down + # so much that it would cause a particle overlap. + # This can happen because the inner boundary in the space of transformed coordinates is not a + # prolate spheroid, as it should, but approximated by a sphere. This can lead to IV exit points + # within the prolate spheroid, i.e. within the sphere with radius sigma = radius1 + radius2 in + # the real space after inverse transform. FIXME This works, but is still somewhat of a HACK! + min_iv_length = (radius1 + radius2) * MINIMAL_SEPARATION_FACTOR + + if (iv_x_length*iv_x_length + iv_y_length*iv_y_length + iv_z_length_btf*iv_z_length_btf) < min_iv_length*min_iv_length: + + z_safety_factor_sq = (min_iv_length*min_iv_length - iv_x_length*iv_x_length - iv_y_length*iv_y_length) \ + / (iv_z_length_btf*iv_z_length_btf) + + assert(z_safety_factor_sq >= 0.0) + log.warn('MixedPair2D3D: Applying z_safety_factor = %s to enlarge too small interparticle vector.' % str(math.sqrt(z_safety_factor_sq)) ) + + iv_z_length_btf = math.sqrt( z_safety_factor_sq ) * iv_z_length_btf + + + # TODO Also check for membrane overlapping? + + # Construct the z-vector-component and the new position vectors + iv_z = unit_z * iv_z_length_btf + + pos1 = com - weight1 * (iv_x + iv_y) # the new 2D particle position + pos2 = com + weight2 * (iv_x + iv_y) + iv_z # the new 3D particle position + + # Class method; so don't use self.(..) + return pos1, pos2, structure2D.id, structure3D.id + + @ classmethod + def calc_z_scaling_factor(cls, D_2D, D_3D): + # calculates the scaling factor to make the anisotropic diffusion problem into an isotropic one + return math.sqrt( (D_2D + D_3D) / D_3D) + + def choose_pair_greens_function(self, r0, t): + return self.iv_greens_function(r0) + + def com_greens_function(self): + # diffusion of the CoM of a mixed pair is only two dimensional + return GreensFunction2DAbsSym(self.D_R, self.a_R) + + def iv_greens_function(self, r0): + # Diffusion of the interparticle vector is three dimensional + # TODO Fix this ugly HACK to prevent particle overlap problem + return GreensFunction3DRadAbs(self.D_r, self.interparticle_ktot, max(r0, self.sigma), + self.sigma, self.a_r) + + def create_com_vector(self, r): + x, y = random_vector2D(r) + # project the com onto the surface unit vectors to make sure that the coordinates are in the surface + return x * self.structure2D.shape.unit_x + y * self.structure2D.shape.unit_y + + def create_interparticle_vector(self, gf, r, dt, r0, old_iv): + # FIXME This is actually the same method as for SphericalPair + + if __debug__: + log.debug("create_interparticle_vector: r=%g, dt=%g", r, dt) + + theta = draw_theta_wrapper(gf, r, dt) # draw_theta return a value between -Pi and Pi + + if theta == 0.0: + new_iv = old_iv + elif theta % numpy.pi != 0.0: + # rotate the old_iv to the new theta + rotation_axis = crossproduct_against_z_axis(old_iv) + rotation_axis = normalize(rotation_axis) + new_iv = rotate_vector(old_iv, rotation_axis, theta) + + # rotate the new_iv around the old_iv with angle phi + phi = myrandom.uniform() * 2 * Pi + rotation_axis = normalize(old_iv) + new_iv = rotate_vector(new_iv, rotation_axis, phi) + else: + # theta == Pi -> just mirror the old_iv + new_iv = -old_iv + + # adjust length of the vector to new r + # note that r0 = length (old_iv) + new_iv = (r/r0) * new_iv + + return new_iv + + def check(self): + # perform internal consistency check + radius2D = self.particle2D.radius + radius3D = self.particle3D.radius + D_2D = self.particle2D.D + D_3D = self.particle3D.D + + radius = self.shell.shape.radius + half_length = self.shell.shape.half_length + + # check that the shell obeys the scaling rules dimensions of the cylinder + r_check = self.testShell.r_right(2.0*half_length - radius2D) + hl_check = (self.testShell.z_right(radius) + self.testShell.z_left(radius))/2.0 + assert feq(r_check, radius) and feq(hl_check, half_length), \ + 'MixedPair2D3D: Domain did not obey scaling relationship. ' + + # check that the CoM is in the surface + assert feq(numpy.dot(self.com-self.structure2D.shape.position, self.structure2D.shape.unit_z), 0.0, \ + typical=radius) + # check that the IV is NOT in the surface + + def __str__(self): + return 'Mixed2D3D' + Pair.__str__(self) + +class MixedPair1D3D(Pair): + + @ classmethod + def do_back_transform(cls, com, iv, D1, D2, radius1, radius2, structure1D, structure3D, unit_z, world): + # here we assume that the com and iv are really in the structure and no adjustments have to be + # made + # unit_z is a putatively different unit_z than that available in the surface object and should not be + # of use here (however the method is defined with the same parameter signature as elsewhere) + # + # note that structure1D is not a 1D object but the surface that the 1D particle is bound to + # + # structure3D is not used here but has to be passed because of the classmethod property + D_tot = D1 + D2 + weight1 = D1 / D_tot + weight2 = D2 / D_tot + + min_iv_r_length = radius2 + structure1D.shape.radius + + # get the coordinates of the iv relative to the system of the surface (or actually the shell) + iv_z = structure1D.shape.unit_z * numpy.dot(iv, structure1D.shape.unit_z) + iv_r = iv - iv_z + + # reflect the coordinates in the unit_z direction back to the side of the membrane + # where the domain is. Note that it's implied that the origin of the coordinate system lies in the + # plane of the membrane + iv_r_length = length(iv_r) + # do the reverse scaling + iv_r_length_new = iv_r_length / cls.calc_r_scaling_factor(D1, D2) + + # if the particle is overlapping with the cylinder, make sure it doesn't + if iv_r_length_new < min_iv_r_length: + iv_r_length_new = min_iv_r_length * MINIMAL_SEPARATION_FACTOR + + iv_r = (iv_r_length_new/iv_r_length) * iv_r + + pos1 = com - weight1 * iv_z + pos2 = com + weight2 * iv_z + iv_r + + # Class method; so don't use self.(..) + return pos1, pos2, structure1D.id, structure1D.id + + @ classmethod + def calc_r_scaling_factor(cls, D1, D2): + return math.sqrt((D1 + D2)/D2) + + +class MixedPair1DStatic(Pair, hasCylindricalShell): + # This domain is for the interaction of a particle drifting and diffusing on a cylinder (1D particle) + # and another (static) particle which is bound to a cap of the same cylinder or its interface with a plane. + # Since the cap particle is immobile, we effectively have a one-particle problem + # and the CoM is not used. The displacement of the 1D particle is sampled via iv_greens_function. + # However, we have to make this a pair domain because we have two particles involved which + # can decay (fire single reactions) independently. + # TODO: Is the new pos. of the cap particle sampled correctly in case of a decay event? + + def __init__(self, domain_id, shell_id, testShell, rrs): + # Calls required parent inits and sets variables. + # + # Sets: particle1D, structure1D, static_particle, static_structure + # Requires: nothing to be set + + assert isinstance(testShell, MixedPair1DStatictestShell) + hasCylindricalShell.__init__(self, testShell, domain_id) + Pair.__init__(self, domain_id, shell_id, rrs) + + assert isinstance(self.testShell, MixedPair1DStatictestShell) + + # Some useful definitions + self.particle1D = self.testShell.particle1D + self.structure1D = self.testShell.structure1D + self.static_particle = self.testShell.static_particle + self.static_structure = self.testShell.static_structure + + # The latter is defined for completeness and used by check_domain() in egfrd.py: + self.origin_structure = self.structure1D + self.target_structure = self.static_structure + + # The static_particle should be immobile; check to be sure: + assert self.static_particle.D == 0 + assert self.static_particle.v == 0 + # D_r and D_R are inherited from Pair class; + # This results in D_r = particle1D.D and D_R = 0 + + self.LD_MAX = numpy.inf + + def get_shell_size(self): # TODO Is that even used? + return self.shell.shape.half_length + + def get_a_r(self): + + ref_pt = self.testShell.get_referencepoint() + assert all_feq(self.static_particle.position - ref_pt, 0.0) + + return abs(self.testShell.dz_right) - self.particle1D.radius + a_r = property(get_a_r) + a_R = property(get_a_r) + + def get_v_tot(self): + # 'direction' signifies the direction of the IV with respect to the surface unit_z + # (which defines the direction of positive drift). + # Assuming self.pid_particle_pair2[1].v == 0 here, i.e. relative drift = drift of 1D part. + direction = cmp(numpy.dot (self.iv, self.structure1D.shape.unit_z), 0) + return self.particle1D.v * direction + + # NOTE the v_tot and v_r are now positive in the direction of the IV! + v_tot = property(get_v_tot) + v_r = property(get_v_tot) + + def get_v_R(self): + # With our transform D_R and v_R are zero if one of the particles is immobile + return 0.0 + v_R = property(get_v_R) + + def com_greens_function(self): + # The Green's function used is largely irrelevant, because D_R=0 and v_R=0 + return GreensFunction1DAbsAbs(self.D_R, self.v_R, 0.0, -self.a_R, self.a_R) + + def iv_greens_function(self, r0): + if( numpy.isinf(self.interparticle_ktot) ): + return GreensFunction1DAbsAbs(self.D_r, self.v_r, r0, self.sigma, self.a_r) + else: + return GreensFunction1DRadAbs(self.D_r, self.v_r, self.interparticle_ktot, r0, self.sigma, self.a_r) + + def choose_pair_greens_function(self, r0, t): + # Todo + return self.iv_greens_function(r0) + + @ classmethod + def do_back_transform(cls, com, iv, D1, D2, radius1, radius2, structure1, structure2, unit_z, world): + # here we assume that structure1 = structure1D and structure2 = static_structure + # and that com has been correctly initialized with the initial (and constant) position + # of the static particle. + + pos2 = com # static particle (no. 2) stays in place + pos1 = pos2 - iv # IV always points from particle1 to particle2 + + return pos1, pos2, structure1.id, structure2.id + # TODO Does this give the correct new positions in case of a single reaction of static_particle ? + + def create_com_vector(self, z): + # This returns the displacement of the com, which is zero here. + return 0.0 + + def create_interparticle_vector(self, gf, r, dt, r0, old_iv): + if __debug__: + log.debug("create_interparticle_vector: r=%g, dt=%g", r, dt) + + # note that r0 = length (old_iv) + new_iv = (r/r0) * old_iv + + # project the new_iv down on the unit vectors of the surface to prevent the particle from + # leaving the surface due to numerical problem + unit_z = self.structure1D.shape.unit_z + new_iv_z = unit_z * numpy.dot(new_iv, unit_z) + + return new_iv_z + + def __str__(self): + return 'MixedPair1DStatic' + Pair.__str__(self) diff --git a/peer/numpy/pyarray_backed_allocator.hpp b/peer/numpy/pyarray_backed_allocator.hpp index 70fe0b82..f21aeb8c 100644 --- a/peer/numpy/pyarray_backed_allocator.hpp +++ b/peer/numpy/pyarray_backed_allocator.hpp @@ -3,6 +3,7 @@ #include #include +#include #include namespace peer { @@ -117,6 +118,10 @@ namespace util pyarray_backed_allocator(const pyarray_backed_allocator_base& that) : pyarray_backed_allocator_base(that) {} + + ~pyarray_backed_allocator() + { + } pointer address(reference r) const { diff --git a/peer/wrappers/range/stl_container_wrapper.hpp b/peer/wrappers/range/stl_container_wrapper.hpp index abc47838..f9640809 100644 --- a/peer/wrappers/range/stl_container_wrapper.hpp +++ b/peer/wrappers/range/stl_container_wrapper.hpp @@ -189,7 +189,6 @@ class stl_container_wrapper } catch (boost::python::error_already_set const&) { - return NULL; } return 0; } diff --git a/pyGFRD.cpp b/pyGFRD.cpp index 98ff1839..fc252791 100644 --- a/pyGFRD.cpp +++ b/pyGFRD.cpp @@ -28,9 +28,11 @@ #include "peer/numpy/scalar_converters.hpp" #include "binding/bd_propagator_class.hpp" +#include "binding/new_bd_propagator_class.hpp" #include "binding/binding_common.hpp" #include "binding/box_class.hpp" #include "binding/cylinder_class.hpp" +#include "binding/disk_class.hpp" #include "binding/domain_id_class.hpp" #include "binding/domain_classes.hpp" #include "binding/egfrd_simulator_classes.hpp" @@ -61,9 +63,26 @@ #include "binding/species_type_class.hpp" #include "binding/sphere_class.hpp" #include "binding/structure_classes.hpp" +#include "binding/structure_type_class.hpp" +#include "binding/structure_id_class.hpp" #include "binding/transaction_classes.hpp" #include "binding/world_class.hpp" +///////////////// +// This is the masterfile that takes care of the binding of the C++ classes to Python +///////////////// + +// Every 'register_...' line executes some code that makes the class/datastructure avaiable to python +// The 'register_...classes' methods are defined in the 'binding/....class(es).cpp' files, but usually directly +// call another (parameterized) 'register' method that is defined in the corresponding binding/....hpp file. +// For example: +// pyGFRD.cpp -> register_particle_class() +// |_ binding/particle_class.cpp -> ParticleWrapper::__register_class("Particle") +// |_ binding/Particle.hpp +// +// The file 'binding/Particle.hpp' now finally defines 'register_class' and includes all the things it needs to +// function properly. This includes converters for datatypes and other stuff. + namespace b = binding; BOOST_PYTHON_MODULE(_gfrd) @@ -81,6 +100,7 @@ BOOST_PYTHON_MODULE(_gfrd) b::register_model_class(); b::register_bd_propagator_class(); + b::register_new_bd_propagator_class(); b::register_box_class(); b::register_domain_id_class(); b::register_domain_classes(); @@ -88,6 +108,7 @@ BOOST_PYTHON_MODULE(_gfrd) b::register_spherical_shell_container_class(); b::register_plane_class(); b::register_cylinder_class(); + b::register_disk_class(); b::register_cylindrical_shell_container_class(); b::register_network_rules_class(); b::register_network_rules_wrapper_class(); @@ -111,6 +132,8 @@ BOOST_PYTHON_MODULE(_gfrd) b::register_transaction_classes(); b::register_world_class(); b::register_structure_classes(); + b::register_structure_id_class(); + b::register_structure_type_class(); b::register_module_functions(); b::register_volume_clearer_classes(); b::register_reaction_record_classes(); diff --git a/samples/1D+2D+3D_example/1D+2D+3D_example.py b/samples/1D+2D+3D_example/1D+2D+3D_example.py new file mode 100755 index 00000000..1ebe7d1d --- /dev/null +++ b/samples/1D+2D+3D_example/1D+2D+3D_example.py @@ -0,0 +1,491 @@ +#!/usr/bin/python + +##################################################### +##### An example featuring a closed box made up ##### +##### from six planes ("membranes"), involving: ##### +##### ##### +##### - 3D diffusion inside the box ##### +##### - 2D diffusion on the planes ##### +##### - 1D transport on rods inside the box ##### +##### - reactions in 3D ##### +##### - reactions in 2D ##### +##### - reactions (binding to a cap) in 1D ##### +##### - "direct binding" between 3D and 2D ##### +##### ##### +##### by TR Sokolowski, 2015 ##### +##################################################### + +##### Start with: +##### PYTHONPATH=[egfrd directory] LOGLEVEL=[ERROR|DEBUG] python ./1D+2D+3D_example.py [parameters] +##### where [parameters] = +##### [seed] (random generator seed) +##### [surface_to_bulk_ratio] (ratio btw. no. of particles on membrane/in bulk) +##### [cap_dwelltime] (1 / unbinding rate from the rod caps) +##### [membrane_dwelltime] (1 / unbinding rate from membrane) +##### [membrane_slowdown] (ratio bulk diff. const. / membrane single part. diff. const.) +##### [complex_slowdown] (ratio bulk diff. const. / membrane complex diff. const.) +##### +##### (in that order) + + +# #### Importing necessary files #### +# import egfrd libraries +from egfrd import * +from bd import * +import model +import gfrdbase +import _gfrd +import os +import math +import sys + +# Set up the model +def set_constants(): + + global N_run, t_max, N_log, log_time, log_step, N_save, save_points, loadfile, ws + global _SEED_, _VLOG_, _INFO_ + global place_bulk_particles, place_planes, place_plane_particles, place_rods, place_rod_particles, place_sinks, place_sphere + global N_bulk_particles, N_rod_particles, N_plane_particles + global Pi + + # Define some simulation parameters + world_size = 10e-6 # side length of simulation box [m] + ws = world_size # only an abbreviation + + Pi = math.pi + + # Run and logging parameters + N_run = 1e6 # number of simulation steps + N_log = 1e9 # number of *last* steps it will log; if N_log>N_run, we will log N_run steps + log_step = 0 # number of steps between two log outputs + # if this is 0 the script will log in time intervals instead (defined below) + log_time = 0.1 # seconds between two log events of the VTK logger + t_max = 3600.0 # maximal bound for simulated time [sec] + + N_save = 5e4 # saving interval + save_points = [] # define arbitrary save points (in steps) here; can be useful in debugging + + loadfile='' # specify a file with a previous state here if desired + + if(log_step > 0): + log_time = 0.0 + + # The random seed + _SEED_ = int(input_seed) + + # Some control flags + _INFO_ = 1 + _VLOG_ = 1 + + # Define flags + place_bulk_particles = 1 + place_planes = 1 + place_plane_particles = 1 + place_rods = 1 + place_rod_particles = 1 + place_sinks = 0 + place_sphere = 0 + + # How many particles to place + N_bulk_particles = 40 + N_rod_particles = 20 + N_plane_particles = surface_to_bulk_no_ratio * N_bulk_particles + + +def setup_model(): + + global m, structure_types, membrane, microtubule, cap, nr, sink + global A, Am, At, Ac, B, Bm + global L_cyl, R_cyl, R_nucl + + # Create an instance of the ParticleModel class + m = model.ParticleModel(ws) + + # Define the membranes as structure_types + structure_types = [] + membrane = _gfrd.StructureType() + membrane['name'] = 'membrane' + m.add_structure_type(membrane) + structure_types.append(membrane) + + # Define the rod/microtubule structure type + microtubule = _gfrd.StructureType() + microtubule['name'] = 'microtubule' + m.add_structure_type(microtubule) + structure_types.append(microtubule) + + # Define the cap and sink structure type + cap = _gfrd.StructureType() + cap['name'] = 'cap' + m.add_structure_type(cap) + structure_types.append(cap) + + sink = _gfrd.StructureType() + sink['name'] = 'sink' + m.add_structure_type(sink) + structure_types.append(sink) + + nr = _gfrd.StructureType() + nr['name'] = 'non-reactive' + m.add_structure_type(nr) + structure_types.append(nr) + + ############################# + ##### General parameters #### + ############################# + # Define species parameters + R_part = 30.0e-9 # Particle radius [m] + D_part = 1e-12 # Particle bulk diff. const. [m^2/sec] + v_drift = 0.5e-6 # [m/sec] + L_cyl = 0.45*ws + R_cyl = 25.0e-9 # cylinder radius, 25 nm = MT radius + R_nucl = 0.15*ws + + #################################### + ##### Define the reaction rates #### + #################################### + # Binding to the membrane + kb_to_membrane = 1e-6 + ku_from_membrane = 0 + # Which of the bulk species should bind to the membrane + bind_A = 1 + bind_B = 0 + + # Binding to the rod + kb_to_rod = 1e-11 + ku_from_rod = 0 + + # Binding to the rod cap + kb_to_cap = 0 + ku_from_cap = 0 + # should be zero if the following are not, and vice versa + # Binding to cap cluster + kb_to_cc = 1e-5 + ku_from_cc = 1.0/cap_dwelltime + + # Binding to a sink on the rod + kb_to_sink = 0 + ku_from_sink = 0 # not used so far + + # Reactions on the membrane + kb_on_membrane = 1e-11 + ku_on_membrane = 1.0/membrane_dwelltime + + # Reactions between bulk and membrane ("direct binding") + kb_to_membrane_particle = kb_on_membrane * 2.0*Pi*R_part + ku_from_membrane_particle = ku_on_membrane + + ############################## + ##### Create the species ##### + ############################## + # Define chemical species + # Syntax: Species(name, D, radius, [surface], [drift]) + A = model.Species('A', D_part, R_part) + Am = model.Species('Am', D_part/membrane_slowdown, R_part, membrane) + At = model.Species('At', D_part/10, R_part, microtubule, v_drift) + Ac = model.Species('Ac', 0, R_part, cap) + B = model.Species('B', D_part, R_part) + Bm = model.Species('Bm', D_part/membrane_slowdown, R_part, membrane) + AB = model.Species('AB', D_part, 1.5*R_part) + ABm = model.Species('ABm', D_part/complex_slowdown, 1.5*R_part, membrane) + As = model.Species('E', 0, R_part, sink) + + # Add these species to the previously defined particle model + m.add_species_type(A) + m.add_species_type(Am) + m.add_species_type(At) + m.add_species_type(Ac) + m.add_species_type(B) + m.add_species_type(Bm) + m.add_species_type(AB) + m.add_species_type(ABm) + m.add_species_type(As) + + ####################################### + ##### Create the reaction network ##### + ####################################### + existing_rules = [] + # Binding of A to the membrane + # Reaction rule A + membrane <-> Am(membrane) + if(kb_to_membrane > 0.0 and bind_A): + rb_Am = model.create_binding_reaction_rule(A, membrane, Am, kb_to_membrane) + existing_rules.append(rb_Am) + if(ku_from_membrane > 0.0): + ru_Am = model.create_unimolecular_reaction_rule(Am, A, ku_from_membrane) + existing_rules.append(ru_Am) + + # Binding of B to the membrane + # Reaction rule B + membrane <-> Bm + if(kb_to_membrane > 0.0 and bind_B): + rb_Bm = model.create_binding_reaction_rule(B, membrane, Bm, kb_to_membrane) + existing_rules.append(rb_Bm) + if(ku_from_membrane > 0.0): + ru_Bm = model.create_unimolecular_reaction_rule(Bm, B, ku_from_membrane) + existing_rules.append(ru_Bm) + + # Binding of A to the MTs + # Reaction rule A + microtubule <-> At(microtubule) + if(kb_to_rod > 0.0): + rb_At = model.create_binding_reaction_rule(A, microtubule, At, kb_to_rod) + existing_rules.append(rb_At) + if(ku_from_rod > 0.0): + ru_At = model.create_unimolecular_reaction_rule(At, A, ku_from_rod) + existing_rules.append(ru_At) + + # Binding to the microtubule cap + # Reaction rule At(microtubule) + cap -> Ac(cap) -> A(bulk) + if(kb_to_cap > 0.0): + rb_Ac = model.create_binding_reaction_rule(At, cap, Ac, kb_to_cap) + existing_rules.append(rb_Ac) + if(ku_from_cap > 0.0): + ru_Ac = model.create_unimolecular_reaction_rule(Ac, A, ku_from_cap) + existing_rules.append(ru_Ac) + # ATTENTION! Make sure that this does not repeat the clustering reactions below! + + # Binding to the sink on the microtubule + # Reaction rule At(microtubule) + sink -> As(sink) -> At(microtubule) + if(kb_to_sink > 0.0): + rb_As = model.create_binding_reaction_rule(At, sink, As, kb_to_sink) + existing_rules.append(rb_As) + if(ku_from_sink > 0.0): + ru_As = model.create_unimolecular_reaction_rule(As, At, ku_from_sink) + existing_rules.append(ru_As) + + # Reaction between A and B both on the membrane + # Reaction rule Am + Bm <-> ABm + if(kb_on_membrane > 0.0): + rb_ABm_mem = model.create_binding_reaction_rule(Am, Bm, ABm, kb_on_membrane) + existing_rules.append(rb_ABm_mem) + if(ku_on_membrane > 0.0): + ru_ABm_mem = model.create_unbinding_reaction_rule(ABm, Am, Bm, ku_on_membrane) + existing_rules.append(ru_ABm_mem) + + # "Direct" binding of A from bulk to membrane-bound Bm + # Reaction rule A + Bm <-> ABm + if(kb_to_membrane_particle > 0.0): + rb_ABm_dir = model.create_binding_reaction_rule(A, Bm, ABm, kb_to_membrane_particle) + existing_rules.append(rb_ABm_dir) + if(ku_from_membrane_particle > 0.0): + ru_ABm_dir = model.create_unbinding_reaction_rule(ABm, A, Bm, ku_from_membrane_particle) + existing_rules.append(ru_ABm_dir) + + # "Direct" binding of B from bulk to membrane-bound Am + # Reaction rule Am + B <-> ABm + if(kb_to_membrane_particle > 0.0): + rb_ABm_dir_2 = model.create_binding_reaction_rule(Am, B, ABm, kb_to_membrane_particle) + existing_rules.append(rb_ABm_dir_2) + if(ku_from_membrane_particle > 0.0): + ru_ABm_dir_2 = model.create_unbinding_reaction_rule(ABm, Am, B, ku_from_membrane_particle) + existing_rules.append(ru_ABm_dir_2) + + ### TODO: A + B <-> C in bulk + + # Create a cascade of cap bound species and reactions between them + # ATTENTION! Make sure that this does not repeat the cap-binding reaction above! + ### TODO: polish this + Ncluster_max = 50 + Ac = [] + for i in range(0,Ncluster_max): + + cluster_radius = R_part * (1.0+1.0*i/Ncluster_max) + Ac.append( model.Species('Ac'+str(i+1), 0, cluster_radius, cap) ) + m.add_species_type(Ac[i]) + + existing_rules.append( model.create_binding_reaction_rule(At, cap, Ac[0], kb_to_cc) ) + existing_rules.append( model.create_unimolecular_reaction_rule(Ac[0], A, ku_from_cc) ) + + for i in range(1,Ncluster_max): + existing_rules.append( model.create_binding_reaction_rule(At, Ac[i-1], Ac[i], kb_to_cc) ) + existing_rules.append( model.create_unbinding_reaction_rule(Ac[i], Ac[i-1], A, ku_from_cc) ) + + # Finalize: + # Add all the rules to the particle model + for rule in existing_rules: + m.add_reaction_rule(rule) + + +def setup_simulator(): + + global w, s, bb_1, bb_2 + + ############################### + ##### Set up the simulator #### + ############################### + # Create a world with 3x3x3 cells for the matrixsize + w = gfrdbase.create_world(m, 3) + + # Set default bounding box + bb_1 = [0,0,0] + bb_2 = [0,0,0] + + # Get default structure id + def_sid = w.get_def_structure_id() + + # Create membranes + if place_planes : + create_box(w, membrane, [0.5*ws, 0.5*ws, 0.5*ws], [0.95*ws, 0.4*ws, 0.4*ws],one_sided=True) + # redefine the bounding box for bulk particle placement + bb_1 = [0.10*ws, 0.33*ws, 0.33*ws] + bb_2 = [0.90*ws, 0.66*ws, 0.66*ws] + + # Create microtubules/rods with caps + if place_rods : + + yz_frac=0.40 + ry1=yz_frac + ry2=1.0-yz_frac + rz1=yz_frac + rz2=1.0-yz_frac + + rod_1f_id = create_rod(w, microtubule, cap, 'rod_1_left', [0.5*ws, ry1*ws, rz1*ws], R_cyl, [+1,0,0], L_cyl, back_cap_structure_type=nr) + rod_1b_id = create_rod(w, microtubule, cap, 'rod_1_right', [0.5*ws, ry1*ws, rz1*ws], R_cyl, [-1,0,0], L_cyl, back_cap_structure_type=nr) + rod_2f_id = create_rod(w, microtubule, cap, 'rod_2_left', [0.5*ws, ry1*ws, rz2*ws], R_cyl, [+1,0,0], L_cyl, back_cap_structure_type=nr) + rod_2b_id = create_rod(w, microtubule, cap, 'rod_2_right', [0.5*ws, ry1*ws, rz2*ws], R_cyl, [-1,0,0], L_cyl, back_cap_structure_type=nr) + rod_3f_id = create_rod(w, microtubule, cap, 'rod_3_left', [0.5*ws, ry2*ws, rz1*ws], R_cyl, [+1,0,0], L_cyl, back_cap_structure_type=nr) + rod_3b_id = create_rod(w, microtubule, cap, 'rod_3_right', [0.5*ws, ry2*ws, rz1*ws], R_cyl, [-1,0,0], L_cyl, back_cap_structure_type=nr) + rod_4f_id = create_rod(w, microtubule, cap, 'rod_4_left', [0.5*ws, ry2*ws, rz2*ws], R_cyl, [+1,0,0], L_cyl, back_cap_structure_type=nr) + rod_4b_id = create_rod(w, microtubule, cap, 'rod_4_right', [0.5*ws, ry2*ws, rz2*ws], R_cyl, [-1,0,0], L_cyl, back_cap_structure_type=nr) + + if place_sinks : + sink_1a = model.create_disk_surface(sink.id, 'sink_1a', [0.55*ws, rx1*ws, ry1*ws], R_cyl, [1,0,0], rod_1f_id) + #w.add_structure(sink_1a) + sink_1b = model.create_disk_surface(sink.id, 'sink_1b', [0.65*ws, rx1*ws, ry1*ws], R_cyl, [1,0,0], rod_1f_id) + #w.add_structure(sink_1b) + sink_1c = model.create_disk_surface(sink.id, 'sink_1c', [0.75*ws, rx1*ws, ry1*ws], R_cyl, [1,0,0], rod_1f_id) + #w.add_structure(sink_1c) + + if place_sphere : + + nucleus = model.create_spherical_surface(nr.id, 'nucleus', [0.5*ws, 0.5*ws, 0.5*ws], R_nucl, def_sid) + w.add_structure(nucleus) + + # Create an eGFRD simulator for the world + myrandom.seed(_SEED_) + s = EGFRDSimulator(w, myrandom.rng) + + ########################## + ##### Place particles #### + ########################## + if place_planes and place_plane_particles : + throw_in_particles(w, Bm, N_plane_particles) + + if place_rods and place_rod_particles : + throw_in_particles(w, At, N_rod_particles) + + if place_bulk_particles: + # this one needs a bounding box to prevent bulk particles to be placed outside of the cell + throw_in_particles(w, A, N_bulk_particles, bb_1, bb_2) + #throw_in_particles(w, B, N_bulk_particles/2, bb_1, bb_2) + + # throw_in_particles() for particles on disks (Ac, As) does not work correctly yet! + # but e.g. place_particle(w, Ac[1], [1e-6, 3.8e-6, 3.8e-6]) does + + # Print info about created structures + print "***** STRUCTURE TYPES *****" + for st in structure_types: + print ("%s \t %s" % (st.id, st['name']) ) + print "***** STRUCTURES *****" + for struct in w.structures: + print ("%s \t %s \t %s" % (struct.id, struct.sid, struct.name) ) + + +def setup_VTK(): + + global vlogger + + # Set up the visualization toolkit (VTK): + + # Set VTK output directory and check if output directory isn't + # already there + vtk_output_directory = 'VTK_out' + if (os.path.exists(vtk_output_directory)): + print '** WARNING: VTK output directory already exists, possible '+ \ + 'present old VTK files might lead to errors.' + + # import lib and create logger instance + from visualization import vtklogger + vlogger = vtklogger.VTKLogger(s, vtk_output_directory, extra_particle_step=True) + # VTKLogger(self, sim, dir='vtkdata', buffer_size=None, show_shells=True, + # extra_particle_step=True, color_dict=None) + + +def main(): + + global input_seed, surface_to_bulk_no_ratio, cap_dwelltime, membrane_dwelltime, membrane_slowdown, complex_slowdown + + # #### Read input parameters #### + input_seed = int(sys.argv[1]) + surface_to_bulk_no_ratio = float(sys.argv[2]) + cap_dwelltime = float(sys.argv[3]) + membrane_dwelltime = float(sys.argv[4]) + membrane_slowdown = float(sys.argv[5]) + complex_slowdown = float(sys.argv[6]) + + assert input_seed >= 0 + assert surface_to_bulk_no_ratio >= 0.0 + assert membrane_dwelltime > 0.0 + assert membrane_slowdown > 0.0 + assert complex_slowdown > 0.0 + + + # #### Set everything up: #### + set_constants() + setup_model() + setup_simulator() + if _VLOG_: + setup_VTK() + print '* Initialization complete, starting simulation.' + print '* SINGLE_SHELL_FACTOR=%s, MULTI_SHELL_FACTOR=%s, MINIMAL_SEPARATION_FACTOR=%s, SAFETY=%s, TOLERANCE=%s' \ + % (SINGLE_SHELL_FACTOR, MULTI_SHELL_FACTOR, MINIMAL_SEPARATION_FACTOR, SAFETY, TOLERANCE) + print '* surface_to_bulk_no_ratio=%s, cap_dwelltime=%s, membrane_dwelltime=%s, membrane_slowdown=%s, complex_slowdown=%s, input_seed=%s' \ + % (surface_to_bulk_no_ratio, cap_dwelltime, membrane_dwelltime, membrane_slowdown, complex_slowdown, input_seed) + print [structure.id for structure in w.structures] + + + # #### Let's roll: #### + step = 0 + tlast = 0.0 + + if _VLOG_: + # log initial configuration + vlogger.log() + + if loadfile != '': + + s.load_state(loadfile) + + while step < N_run: + + step += 1 + s.step() + #s.check() # can be expensive + + if step % (N_run/100) == 0: + print "***** SIMULATION PROGRESS = "+str(int(step/N_run*100.0))+"%" + + if step % N_save == 0 or step in save_points: + print "***** SAVING STATE at step "+str(int(step)) + s.save_state('state_'+str(step)+'.ini', reload=True) + + if _VLOG_ and step > (N_run-N_log) and \ + ( (log_step and step%log_step==0) or \ + (log_time and s.t-tlast>=log_time) ): + + print "***** LOGGER: logging time = "+str(s.t) + tlast = s.t + vlogger.log() + + + #### Quitting: #### + print '* Done, quitting.' + s.stop(s.t) + #s.burst_all_domains() + s.print_report() + if _VLOG_: + vlogger.stop() + + print '* Bye.' + + +if __name__ == "__main__": + main() diff --git a/samples/2D_example/2D_example.py b/samples/2D_example/2D_example.py new file mode 100755 index 00000000..0321be8d --- /dev/null +++ b/samples/2D_example/2D_example.py @@ -0,0 +1,254 @@ +#!/usr/bin/python + +##################################################### +##### A simple A+B <-> C reaction on a 2D plane ##### +##### by TR Sokolowski, 2013 ##### +##### ##### +##################################################### + +##### Start with: +##### PYTHONPATH=[egfrd directory] LOGLEVEL=[ERROR|DEBUG] python ./2D_example [seed] [no. of particles] + + +##### Importing necessary files ##### +# import egfrd libraries +from egfrd import * +from bd import * +import model +import gfrdbase +import _gfrd +import os +import math + + +# Set up the model +def set_constants(): + + global N_run, t_max, N_log, log_time, log_step, N_save, save_points, loadfile, ws + global _SEED_, _VLOG_, _INFO_ + global N_plane_particles + global Pi + + # Define some simulation parameters + world_size = 10e-6 # side length of simulation box [m] + ws = world_size # only an abbreviation + + Pi = math.pi + + # Run and logging parameters + N_run = 1e5 # number of simulation steps + N_log = 1e9 # number of *last* steps it will log; if N_log>N_run, we will log N_run steps + log_step = 0 # number of steps between two log outputs + # if this is 0 the script will log in time intervals instead (defined below) + log_time = 0.1 # seconds between two log events of the VTK logger + t_max = 3600.0 # maximal bound for simulated time [sec] + + N_save = 5e4 # saving interval + save_points = [] # define arbitrary save points (in steps) here; can be useful in debugging + + loadfile='' # specify a file with a previous state here if desired + + if(log_step > 0): + log_time = 0.0 + + # The random seed + _SEED_ = int(par1) + + # Some control flags + _INFO_ = 1 + _VLOG_ = 1 + + # How many particles to place + N_plane_particles = int(par2) + + +def setup_model(): + + global m, structure_types, membrane, A, B, C + + # Create an instance of the ParticleModel class + m = model.ParticleModel(ws) + + # Define the membrane structure_type + structure_types = [] + membrane = _gfrd.StructureType() + membrane['name'] = 'membrane' + m.add_structure_type(membrane) + structure_types.append(membrane) + + ############################# + ##### General parameters #### + ############################# + # Define species parameters + R_part = 10.0e-9 # Particle radius [m] + D_mem = 0.5e-12 # Particle membrane diff. const. [um^2/s] + + #################################### + ##### Define the reaction rates #### + #################################### + kb_intr = 1.0e-5 # Intrinsic rate of binding at contact; + # This is without the 2*pi*sigma prefactor (~1e-7 m), + # which is added automatically when calculating the Green's function; + # Remember this is in [m/s] + + ku = 1.0 # Unbinding rate = 1/complex lifetime [1/s] + + ############################## + ##### Create the species ##### + ############################## + # Define chemical species + A = model.Species('A', D_mem, R_part, membrane) + B = model.Species('B', D_mem, R_part, membrane) + C = model.Species('C', D_mem/2.0, 2.0*R_part, membrane) + + # Add species to model + for S in [A, B, C]: + m.add_species_type(S) + + ####################################### + ##### Create the reaction network ##### + ####################################### + + rb = model.create_binding_reaction_rule( A, B, C, kb_intr ) + ru = model.create_unbinding_reaction_rule(C, A, B, ku) + + for rule in [rb, ru]: + m.network_rules.add_reaction_rule(rule) + + +def setup_simulator(): + + global w, s, bb_1, bb_2 + + # Create the world with 3x3x3 cells for the matrixsize + w = gfrdbase.create_world(m, 3) + + # Get default structure id + def_sid = w.get_def_structure_id() + + # Create membranes + plane_z = 0.5*ws + plane_corner = [-1.0*ws, -1.0*ws, plane_z] + unit_x = [1, 0, 0] + unit_y = [0, 1, 0] + + plane = model.create_planar_surface(membrane.id, 'plane', plane_corner, unit_x, unit_y, 3.0*ws, 3.0*ws, def_sid) + w.add_structure(plane) # Note: last argument of create_planar_surface() is the parent structure's ID + # This is important for correct handling of unbindings from the substructures! + + # Define a bounding box for particle placement + # If you want to restrict initial positions to a certain region change parameters here + bb_1 = [0.01*ws, 0.01*ws, plane_z - 0.1*ws] + bb_2 = [0.99*ws, 0.99*ws, plane_z + 0.1*ws] + + # Create a simulator for the world: + myrandom.seed(_SEED_) + s = EGFRDSimulator(w, myrandom.rng) + + # Randomly place the particles, using the bounding box from above + throw_in_particles(w, A, N_plane_particles/2, bb_1, bb_2) + throw_in_particles(w, B, N_plane_particles/2, bb_1, bb_2) + + if _INFO_: + # Print info about created structures + print "***** STRUCTURE TYPES *****" + for st in structure_types: + print ("%s \t %s" % (st.id, st['name']) ) + print "***** STRUCTURES *****" + for struct in w.structures: + print ("%s \t %s \t %s" % (struct.id, struct.sid, struct.name) ) + + +def setup_VTK(): + + global vlogger + + # Set up the visualization toolkit (VTK): + + # Set VTK output directory and check if output directory isn't + # already there + vtk_output_directory = 'VTK_out' + if (os.path.exists(vtk_output_directory)): + print '***** WARNING: VTK output directory already exists, possible '+ \ + 'present old VTK files might lead to errors.' + + # import lib and create logger instance + from visualization import vtklogger + vlogger = vtklogger.VTKLogger(s, vtk_output_directory, extra_particle_step=False) + # VTKLogger(self, sim, dir='vtkdata', buffer_size=None, show_shells=True, + # extra_particle_step=True, color_dict=None) + + +def main(): + + global par1, par2 + + # Parameters passed to script + par1 = sys.argv[1] # used for seed + par2 = sys.argv[2] # used for no. of plane particles + + # Make sure to convert parameters to right format when passing on! + + + #### Set everything up: #### + set_constants() + setup_model() + setup_simulator() + if _VLOG_: + setup_VTK() + + if _INFO_: + print '* Initialization complete, starting simulation.' + print '* SINGLE_SHELL_FACTOR=%s, MULTI_SHELL_FACTOR=%s, MINIMAL_SEPARATION_FACTOR=%s, SAFETY=%s, TOLERANCE=%s' %(SINGLE_SHELL_FACTOR, MULTI_SHELL_FACTOR, MINIMAL_SEPARATION_FACTOR, SAFETY, TOLERANCE) + + + #### Let's roll: #### + step = 0 + tlast = 0.0 + + if _VLOG_: + # log initial configuration + vlogger.log() + + if loadfile != '': + + s.load_state(loadfile) + + while step < N_run and s.t <= t_max: + + step += 1 + s.step() + #s.check() # still sporadically causes problems with development version + + if step % (N_run/100) == 0: + print "***** SIMULATION PROGRESS = "+str(int(step/N_run*100.0))+"%" + + if step % N_save == 0 or step in save_points: + print "***** SAVING STATE at step "+str(int(step)) + s.save_state('state_'+str(step)+'.ini', reload=True, delay=5.0) + + if _VLOG_ and step > (N_run-N_log) and \ + ( (log_step and step%log_step==0) or \ + (log_time and s.t-tlast>=log_time) ): + + print "***** LOGGER: logging time = "+str(s.t) + tlast = s.t + s.burst_all_domains() + vlogger.log() + + + # #### Quitting: #### + print '* Done, quitting.' + + s.stop(s.t) + #s.burst_all_domains() + s.print_report() + if _VLOG_: + vlogger.stop() + + print '* Bye.' + + +if __name__ == "__main__": + + main() diff --git a/samples/2d/2dtest.py b/samples/2d/2dtest.py new file mode 100755 index 00000000..57ec98f2 --- /dev/null +++ b/samples/2d/2dtest.py @@ -0,0 +1,125 @@ +#!/usr/bin/python + +# Small scripts that demonstrates 2d +# +# ... which as of yet is bug-prone. +# Run by executing: +# $ python 2dtest.py +# +# In the script below VTK-logging can be +# turned of or on. (logging = False/True.) +# When logging is on, VTK logs will be +# created automatically, errormessages will +# be supressed however. When logging is +# off, then errormessages are not supressed. +# +# +# Make sure that the egfrd system is added to your PYTHONPATH +# This means, in bash for example: +# $ export PYTHONPATH=$HOME/egfrd + +# Modules +# =============================== +import sys +import os +import datetime + +from egfrd import * +from visualization import vtklogger + +import model +import gfrdbase +import _gfrd + + +# Unique seed +# =============================== +currenttime = (long( +datetime.datetime.now().month*30.5*24*3600*1e6+ +datetime.datetime.now().day*24*3600*1e6+datetime.datetime.now().hour*3600*1e6+ +datetime.datetime.now().minute*60*1e6+datetime.datetime.now().second*1e6+ +datetime.datetime.now().microsecond)) +myrandom.seed(currenttime) +print "(Seed " + str(currenttime) + ")" + + +# Constants +# =============================== +logging = False +sigma = 1e-9 # Diameter particle +D = 1e-13 # Diffusion constant +N = 200000 # Number of steps simulation will last +world_size = 1e-6 # Lengths of simulation box +NParticles = 50 # Number of particles in the simulation +k = 2e-14 # Reaction constant + +# VTK Logger +# =============================== +if (logging == True): + vtk_output_directory = 'VTK_out' + if (os.path.exists(vtk_output_directory)): + print '** WARNING: VTK output directory already exists, possible '+ \ + 'present old VTK files might lead to errors.' + + +# Basic set up simulator +# =============================== +# Model +m = model.ParticleModel(world_size) + +# Plane ("membrane") +the_plane = _gfrd.create_planar_surface('the_plane', + [world_size/2,0,0], [0,0,1], [0,1,0], + world_size, world_size) +m.add_structure(the_plane) + +# Species +A = model.Species('A', D, sigma, 'the_plane') +m.add_species_type(A) +B = model.Species('B', D, 3*sigma, 'the_plane') +m.add_species_type(B) +C = model.Species('C', D, 3*sigma, 'the_plane') +m.add_species_type(C) +# Reaction rules +r1 = model.create_binding_reaction_rule(A, B, C, k) +m.network_rules.add_reaction_rule(r1) +r2 = model.create_unbinding_reaction_rule(C, A, B, (1/10)) +m.network_rules.add_reaction_rule(r2) + + +# World +w = gfrdbase.create_world(m, 3) +# Simulator +s = EGFRDSimulator(w, myrandom.rng) + +# Set up logger +if (logging == True): + l = vtklogger.VTKLogger(s, vtk_output_directory, extra_particle_step=True) + +# Throw in particles +throw_in_particles(w, A, NParticles) +throw_in_particles(w, B, int(NParticles/2)) + +# Running 2 +# =============================== +for t in range(N): + + # if logging, log and also use "try statement" + if (logging == True): + # take a step (but ignore errors) + try: + s.step() + except: + print "Broken (run with logging=False for errormessage)!" + break + # log the step + l.log() + # otherwise just make steps. + else: + s.step() + +s.stop(s.t) + +if (logging == True): + l.stop() + diff --git a/samples/PBS/README b/samples/PBS/README new file mode 100644 index 00000000..498e6cec --- /dev/null +++ b/samples/PBS/README @@ -0,0 +1,20 @@ + +This directory contains a sample PBS script (egfrd.qsub) to be used for starting +jobs on a TORQUE/Maui operated cluster with the "qsub" command and a bash script +(submit) which automatically submits series of jobs to the cluster (using "qsub"). + +To run a sample on the cluster, copy both scripts into the directory you want to +run the sample from and replace the start command in "egfrd.qsub". +To submit one job type + + ./submit 1 egfrd.qsub [error path] + +[error path] is an optional argument specifying the output path for the default +and error output streams and is set to the directory you start "submit" from +by default. + +Both scripts are commented and an example for a job start command is given +in "egfrd.qsub". + + +Written by: Thomas Sokolowski (sokolowski@amolf.nl), Aug 2011 diff --git a/samples/PBS/egfrd.qsub b/samples/PBS/egfrd.qsub new file mode 100644 index 00000000..4f5c27d8 --- /dev/null +++ b/samples/PBS/egfrd.qsub @@ -0,0 +1,70 @@ +######################################################################## +# Abstract : Sample PBS/bash script to start a simple eGFRD simulation # +# Author: Thomas Sokolowski (sokolowski@amolf.nl) # +# Date : 2011-08-02 # +######################################################################## + +######################################################################## +# OPTIONS FOR PBS MACRO: # +# # +# -S: shell to be used for your scripts # +# -l: provide extra options/resources # +# notes=x:ppn=y # +# allocate x nodes with y processors each # +# walltime=hh:mm:ss # +# allocate cpu time # +# -N: job name as shown in the queue # +# # +######################################################################## + +#PBS -S /bin/bash +#PBS -l nodes=1:ppn=1,walltime=24:00:00 +#PBS -N eGFRDjob + +# Specify the job directory to which the queing system will redirect +# its output +JOB_DIR="/home/thomas/egfrd/samples/irreversible" + +# Specify the eGFRD directory +# PYTHONPATH is set to this directory before the script is started +EGFRD_DIR="/home/thomas/egfrd/" + +# Specify the start command +# This could also be put into a startscript +JOB_CMD="***PUT IN JOB START COMMAND WITH FULL PARAMTER LIST HERE***" +# ( example: JOB_CMD="python26 run.py cluster.out 0.0001 100000" ) + +# Define output log file +OUTLOG="run.log" + +######################################################################## +# Here the job starts # +######################################################################## + +# First print some log info +echo "----------------------------------------------------------------" +echo "Attempting to start PBS job..." +echo +echo "Start time: $(date)" +echo "Host: $(hostname)" +echo "Root directory: $(pwd)" +echo "Job directory: ${JOB_DIR}" +echo "Job command: ${JOB_CMD}" +echo "----------------------------------------------------------------" + +# Enter job directory +pushd ${JOB_DIR} + +# Run job command with parameters and redirect output +PYTHONPATH=${EGFRD_DIR} ${JOB_CMD} + +# Return to previous directory +popd + +# Print some final log info +echo "----------------------------------------------------------------" +echo "Job finished." +echo "End time: $(date)" +echo "----------------------------------------------------------------" + + diff --git a/samples/PBS/submit b/samples/PBS/submit new file mode 100755 index 00000000..4bf581ee --- /dev/null +++ b/samples/PBS/submit @@ -0,0 +1,65 @@ +#!/bin/sh +# +################################################################ +# Submits a job to a TORQUE/Maui operated cluster using qsub # +# Started by Koos van Meel, modified by Thomas Sokolowski # +# # +# The script can start iterations of a job and make the # +# subsequent job be dependent on the outcome of the previous. # +# # +# For the error path argument is optional and most likely # +# you want to set ".", i.e. the error output is redirected # +# into the directory the script is started from. # +################################################################ + +# Check command syntax +if [ $# -lt 2 ]; +then + echo "Usage: submit