LCOV - code coverage report
Current view: top level - cell - SimulationFactory.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 140 160 87.5 %
Date: 2025-12-06 00:15:40 Functions: 19 20 95.0 %

          Line data    Source code
       1             : #include "SimulationFactory.hpp"
       2             : #include "Cell.hpp"
       3             : #include "CellPopulator.hpp"
       4             : #include "CollisionDetector.hpp"
       5             : #include "CollisionHandler.hpp"
       6             : #include "Disc.hpp"
       7             : #include "Membrane.hpp"
       8             : #include "ReactionEngine.hpp"
       9             : #include "ReactionTable.hpp"
      10             : #include "Settings.hpp"
      11             : #include "SimulationContext.hpp"
      12             : #include "StringUtils.hpp"
      13             : 
      14             : #include <glog/logging.h>
      15             : 
      16             : #include <random>
      17             : 
      18             : namespace cell
      19             : {
      20             : 
      21          10 : SimulationFactory::SimulationFactory() = default;
      22          10 : SimulationFactory::~SimulationFactory() = default;
      23             : 
      24          10 : void SimulationFactory::buildSimulationFromConfig(const SimulationConfig& simulationConfig)
      25             : {
      26             :     // Building might fail and we don't want anything dangling
      27          10 :     reset();
      28             : 
      29             :     try
      30             :     {
      31          10 :         discTypeRegistry_ = std::make_unique<DiscTypeRegistry>(buildDiscTypeRegistry(simulationConfig));
      32          10 :         membraneTypeRegistry_ = std::make_unique<MembraneTypeRegistry>(buildMembraneTypeRegistry(simulationConfig));
      33             :     }
      34           0 :     catch (const std::exception& e)
      35             :     {
      36           0 :         throw InvalidTypesException(e.what());
      37           0 :     }
      38             : 
      39             :     try
      40             :     {
      41             :         reactionTable_ =
      42          10 :             std::make_unique<ReactionTable>(buildReactionTable(simulationConfig, std::as_const(*discTypeRegistry_)));
      43             :     }
      44           0 :     catch (const std::exception& e)
      45             :     {
      46           0 :         throw InvalidReactionsException(e.what());
      47           0 :     }
      48             : 
      49             :     try
      50             :     {
      51             :         reactionEngine_ =
      52          10 :             std::make_unique<ReactionEngine>(std::as_const(*discTypeRegistry_), std::as_const(*reactionTable_));
      53          20 :         collisionDetector_ = std::make_unique<CollisionDetector>(std::as_const(*discTypeRegistry_),
      54          20 :                                                                  std::as_const(*membraneTypeRegistry_));
      55          20 :         collisionHandler_ = std::make_unique<CollisionHandler>(std::as_const(*discTypeRegistry_),
      56          20 :                                                                std::as_const(*membraneTypeRegistry_));
      57             : 
      58          10 :         cell_ = buildCell(simulationConfig);
      59             :     }
      60           2 :     catch (const std::exception& e)
      61             :     {
      62           2 :         throw InvalidSetupException(e.what());
      63           2 :     }
      64           8 : }
      65             : 
      66          49 : SimulationContext SimulationFactory::getSimulationContext()
      67             : {
      68          49 :     if (!discTypeRegistry_ || !membraneTypeRegistry_ || !reactionEngine_ || !collisionDetector_ || !collisionHandler_)
      69           0 :         throw ExceptionWithLocation("Can't get simulation context, dependencies haven't been fully created yet");
      70             : 
      71          49 :     return SimulationContext{.discTypeRegistry = *discTypeRegistry_,
      72          49 :                              .membraneTypeRegistry = *membraneTypeRegistry_,
      73          49 :                              .reactionEngine = *reactionEngine_,
      74          49 :                              .collisionDetector = *collisionDetector_,
      75          49 :                              .collisionHandler = *collisionHandler_};
      76             : }
      77             : 
      78           8 : Cell& SimulationFactory::getCell()
      79             : {
      80           8 :     if (!cell_)
      81           0 :         throw ExceptionWithLocation("Can't access cell, it hasn't been created yet");
      82             : 
      83           8 :     return *cell_;
      84             : }
      85             : 
      86           0 : bool SimulationFactory::cellIsBuilt() const
      87             : {
      88           0 :     return static_cast<bool>(cell_);
      89             : }
      90             : 
      91           6 : DiscTypeMap<int> SimulationFactory::getAndResetCollisionCounts()
      92             : {
      93           6 :     if (!collisionDetector_)
      94           0 :         throw ExceptionWithLocation("Can't access collision detector, it hasn't been created yet");
      95             : 
      96           6 :     return collisionDetector_->getAndResetCollisionCounts();
      97             : }
      98             : 
      99          10 : DiscTypeRegistry SimulationFactory::buildDiscTypeRegistry(const SimulationConfig& simulationConfig)
     100             : {
     101          10 :     DiscTypeRegistry discTypeRegistry;
     102          10 :     std::vector<DiscType> discTypes;
     103          43 :     for (const auto& discType : simulationConfig.discTypes)
     104          33 :         discTypes.emplace_back(discType.name, Radius{discType.radius}, Mass{discType.mass});
     105             : 
     106          10 :     discTypeRegistry.setValues(std::move(discTypes));
     107             : 
     108          20 :     return discTypeRegistry;
     109          10 : }
     110             : 
     111          10 : MembraneTypeRegistry SimulationFactory::buildMembraneTypeRegistry(const SimulationConfig& simulationConfig)
     112             : {
     113          10 :     MembraneTypeRegistry registry;
     114          10 :     std::vector<MembraneType> types;
     115             : 
     116          22 :     auto convertPermeabilityMap = [&](const std::unordered_map<std::string, MembraneType::Permeability>& configMap)
     117             :     {
     118          22 :         MembraneType::PermeabilityMap permeabilityMap;
     119          27 :         for (const auto& [discTypeName, permeability] : configMap)
     120           5 :             permeabilityMap[discTypeRegistry_->getIDFor(discTypeName)] = permeability;
     121             : 
     122          22 :         return permeabilityMap;
     123           0 :     };
     124             : 
     125          10 :     const auto& cellMembraneType = simulationConfig.cellMembraneType;
     126          10 :     types.emplace_back(cellMembraneType.name, cellMembraneType.radius,
     127          20 :                        convertPermeabilityMap(cellMembraneType.permeabilityMap));
     128             : 
     129          22 :     for (const auto& type : simulationConfig.membraneTypes)
     130          12 :         types.emplace_back(type.name, type.radius, convertPermeabilityMap(type.permeabilityMap));
     131             : 
     132          10 :     registry.setValues(std::move(types));
     133             : 
     134          20 :     return registry;
     135          10 : }
     136             : 
     137          10 : ReactionTable SimulationFactory::buildReactionTable(const SimulationConfig& simulationConfig,
     138             :                                                     const DiscTypeRegistry& discTypeRegistry)
     139             : {
     140          10 :     ReactionTable reactionTable(discTypeRegistry);
     141             : 
     142          16 :     for (const auto& reaction : simulationConfig.reactions)
     143             :     {
     144           6 :         DiscTypeID educt1 = discTypeRegistry.getIDFor(reaction.educt1);
     145             :         std::optional<DiscTypeID> educt2 =
     146           6 :             reaction.educt2.empty() ? std::nullopt : std::make_optional(discTypeRegistry.getIDFor(reaction.educt2));
     147             : 
     148           6 :         DiscTypeID product1 = discTypeRegistry.getIDFor(reaction.product1);
     149             :         std::optional<DiscTypeID> product2 =
     150           6 :             reaction.product2.empty() ? std::nullopt : std::make_optional(discTypeRegistry.getIDFor(reaction.product2));
     151             : 
     152           6 :         Reaction newReaction(educt1, educt2, product1, product2, reaction.probability);
     153           6 :         reactionTable.addReaction(newReaction);
     154             :     }
     155             : 
     156          10 :     return reactionTable;
     157           0 : }
     158             : 
     159          10 : std::unique_ptr<Cell> SimulationFactory::buildCell(const SimulationConfig& simulationConfig)
     160             : {
     161          10 :     const auto& configMembranes = simulationConfig.membranes;
     162          10 :     if (std::find_if(configMembranes.begin(), configMembranes.end(), [&](const auto& membrane)
     163          33 :                      { return membrane.membraneTypeName == config::cellMembraneTypeName; }) != configMembranes.end())
     164           0 :         throw ExceptionWithLocation("There can't be more than 1 cell membrane in the simulation");
     165             : 
     166          10 :     for (const auto& [discTypeID, permeability] : simulationConfig.cellMembraneType.permeabilityMap)
     167             :     {
     168           0 :         if (permeability != MembraneType::Permeability::None)
     169           0 :             throw ExceptionWithLocation("Currently the outer cell membrane does not support permeability");
     170             :     }
     171             : 
     172          10 :     throwIfDiscsCanBeLargerThanMembranes(simulationConfig);
     173             : 
     174           9 :     std::vector<Membrane> membranes = getMembranesFromConfig(simulationConfig);
     175             : 
     176           9 :     Membrane cellMembrane(membraneTypeRegistry_->getIDFor(config::cellMembraneTypeName));
     177           9 :     cellMembrane.setPosition({0, 0});
     178             : 
     179           9 :     std::unique_ptr<Cell> cell(std::make_unique<Cell>(std::move(cellMembrane), getSimulationContext()));
     180          10 :     createCompartments(*cell, std::move(membranes));
     181             : 
     182           8 :     CellPopulator cellPopulator(*cell, simulationConfig, getSimulationContext());
     183           8 :     cellPopulator.populateCell();
     184             : 
     185          16 :     return cell;
     186          10 : }
     187             : 
     188           9 : std::vector<Membrane> SimulationFactory::getMembranesFromConfig(const SimulationConfig& simulationConfig_)
     189             : {
     190           9 :     std::vector<Membrane> membranes;
     191             : 
     192          19 :     for (const auto& membrane : simulationConfig_.membranes)
     193             :     {
     194          10 :         Membrane newMembrane(membraneTypeRegistry_->getIDFor(membrane.membraneTypeName));
     195          10 :         newMembrane.setPosition({membrane.x, membrane.y});
     196             : 
     197          10 :         membranes.push_back(std::move(newMembrane));
     198             :     }
     199             : 
     200           9 :     return membranes;
     201           0 : }
     202             : 
     203          10 : void SimulationFactory::reset()
     204             : {
     205          10 :     discTypeRegistry_.reset();
     206          10 :     membraneTypeRegistry_.reset();
     207          10 :     reactionTable_.reset();
     208          10 :     reactionEngine_.reset();
     209          10 :     collisionDetector_.reset();
     210          10 :     collisionHandler_.reset();
     211          10 :     cell_.reset();
     212          10 : }
     213             : 
     214           9 : void SimulationFactory::createCompartments(Cell& cell, std::vector<Membrane> membranes)
     215             : {
     216             :     // We'll sort ascending by size and convert membranes to compartments from large to small
     217             :     // This way, compartments are sorted descending by size, and finding a parent compartment is simple:
     218             :     // Iterate from small to large and find the first compartment that contains the current fully
     219             : 
     220           9 :     std::sort(membranes.begin(), membranes.end(),
     221          12 :               [&](const Membrane& lhs, const Membrane& rhs)
     222             :               {
     223          12 :                   return membraneTypeRegistry_->getByID(lhs.getTypeID()).getRadius() <
     224          12 :                          membraneTypeRegistry_->getByID(rhs.getTypeID()).getRadius();
     225             :               });
     226             : 
     227           9 :     std::vector<Compartment*> compartments({&cell});
     228          19 :     while (!membranes.empty())
     229             :     {
     230          10 :         auto membrane = std::move(membranes.back());
     231          10 :         membranes.pop_back();
     232             : 
     233          10 :         const auto& M = membrane.getPosition();
     234          10 :         const auto& R = membraneTypeRegistry_->getByID(membrane.getTypeID()).getRadius();
     235             : 
     236          10 :         bool parentFound = false;
     237          11 :         for (auto iter = compartments.rbegin(); !parentFound && iter != compartments.rend(); ++iter)
     238             :         {
     239          11 :             const auto& Mo = (*iter)->getMembrane().getPosition();
     240          11 :             const auto& Ro = membraneTypeRegistry_->getByID((*iter)->getMembrane().getTypeID()).getRadius();
     241             : 
     242          11 :             if (!mathutils::circleIsFullyContainedByCircle(M, R, Mo, Ro))
     243           1 :                 continue;
     244             : 
     245          10 :             auto compartment = (*iter)->createSubCompartment(std::move(membrane));
     246          10 :             compartments.push_back(compartment);
     247          10 :             parentFound = true;
     248          10 :             break;
     249             :         }
     250             : 
     251          10 :         if (!parentFound)
     252           0 :             throw ExceptionWithLocation("Couldn't find a compartment that fully contains the membrane at (" +
     253           0 :                                         std::to_string(M.x) + "," + std::to_string(M.y) + ")");
     254             :     }
     255             : 
     256             :     // We do it here to include the cell itself in the check
     257           9 :     throwIfCompartmentsIntersect(compartments);
     258           9 : }
     259             : 
     260           9 : void SimulationFactory::throwIfCompartmentsIntersect(const std::vector<Compartment*>& compartments) const
     261             : {
     262             :     // This is done once, so no worries about O(n^2)
     263          25 :     for (std::size_t i = 0; i < compartments.size(); ++i)
     264             :     {
     265          17 :         const auto R1 = membraneTypeRegistry_->getByID(compartments[i]->getMembrane().getTypeID()).getRadius();
     266          17 :         const auto M1 = compartments[i]->getMembrane().getPosition();
     267             : 
     268          32 :         for (std::size_t j = i + 1; j < compartments.size(); ++j)
     269             :         {
     270          16 :             const auto R2 = membraneTypeRegistry_->getByID(compartments[j]->getMembrane().getTypeID()).getRadius();
     271          16 :             const auto M2 = compartments[j]->getMembrane().getPosition();
     272             : 
     273          16 :             if (mathutils::circlesIntersect(M1, R1, M2, R2))
     274           3 :                 throw ExceptionWithLocation("Overlapping membranes at " + stringutils::toString(M1) + " and " +
     275           4 :                                             stringutils::toString(M2) + " not allowed");
     276             :         }
     277             :     }
     278           8 : }
     279             : 
     280          10 : void SimulationFactory::throwIfDiscsCanBeLargerThanMembranes(const SimulationConfig& config) const
     281             : {
     282          10 :     if (config.discTypes.empty())
     283           0 :         return;
     284             : 
     285          10 :     const auto maxDiscRadius = std::max_element(config.discTypes.begin(), config.discTypes.end(),
     286          23 :                                                 [](const auto& t1, const auto& t2) { return t1.radius < t2.radius; })
     287          10 :                                    ->radius;
     288             : 
     289          10 :     auto minMembraneRadius = config.cellMembraneType.radius;
     290          22 :     for (const auto& membraneType : config.membraneTypes)
     291          12 :         minMembraneRadius = std::min(minMembraneRadius, membraneType.radius);
     292             : 
     293          10 :     if (maxDiscRadius >= minMembraneRadius)
     294           1 :         throw ExceptionWithLocation("At least one disc type has a radius larger than at least one membrane type.");
     295             : }
     296             : 
     297             : } // namespace cell

Generated by: LCOV version 1.14