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

Generated by: LCOV version 1.14