LCOV - code coverage report
Current view: top level - cell - CellPopulator.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 99 116 85.3 %
Date: 2025-12-26 22:55:38 Functions: 12 14 85.7 %

          Line data    Source code
       1             : #include "CellPopulator.hpp"
       2             : #include "Cell.hpp"
       3             : #include "Disc.hpp"
       4             : #include "MathUtils.hpp"
       5             : 
       6             : #include <glog/logging.h>
       7             : 
       8             : #include <algorithm>
       9             : #include <numbers>
      10             : #include <random>
      11             : #include <vector>
      12             : 
      13             : namespace cell
      14             : {
      15             : 
      16           9 : CellPopulator::CellPopulator(Cell& cell, SimulationConfig simulationConfig, SimulationContext simulationContext)
      17           9 :     : cell_(cell)
      18           9 :     , simulationConfig_(std::move(simulationConfig))
      19           9 :     , simulationContext_(std::move(simulationContext))
      20             : {
      21           9 : }
      22             : 
      23           9 : void CellPopulator::populateCell()
      24             : {
      25           9 :     if (simulationConfig_.useDistribution)
      26           1 :         populateWithDistributions();
      27             :     else
      28           8 :         populateDirectly();
      29           9 : }
      30             : 
      31           1 : void CellPopulator::populateWithDistributions()
      32             : {
      33           1 :     const auto& discTypes = simulationConfig_.discTypes;
      34           1 :     if (discTypes.empty())
      35           0 :         return;
      36             : 
      37           1 :     double maxRadius = std::max_element(discTypes.begin(), discTypes.end(),
      38           1 :                                         [](const config::DiscType& lhs, const config::DiscType& rhs)
      39           1 :                                         { return lhs.radius < rhs.radius; })
      40           1 :                            ->radius;
      41             : 
      42           1 :     populateCompartmentWithDistribution(cell_, maxRadius);
      43             : }
      44             : 
      45           8 : void CellPopulator::populateDirectly()
      46             : {
      47          31 :     for (const auto& disc : simulationConfig_.discs)
      48             :     {
      49          23 :         Disc newDisc(simulationContext_.discTypeRegistry.getIDFor(disc.discTypeName));
      50          23 :         newDisc.setPosition({disc.x, disc.y});
      51          23 :         newDisc.setVelocity({disc.vx, disc.vy});
      52             : 
      53          23 :         auto& compartment = findDeepestContainingCompartment(newDisc);
      54          23 :         compartment.addDisc(std::move(newDisc));
      55             :     }
      56           8 : }
      57             : 
      58           0 : double CellPopulator::calculateDistributionSum(const std::map<std::string, double>& distribution) const
      59             : {
      60           0 :     return std::accumulate(distribution.begin(), distribution.end(), 0.0,
      61           0 :                            [](double currentSum, auto& entryPair) { return currentSum + entryPair.second; });
      62             : }
      63             : 
      64           3 : std::vector<Vector2d> CellPopulator::calculateCompartmentGridPoints(Compartment& compartment, double maxRadius,
      65             :                                                                     int discCount) const
      66             : {
      67           3 :     const auto& membraneType = simulationContext_.membraneTypeRegistry.getByID(compartment.getMembrane().getTypeID());
      68           3 :     const auto& membraneCenter = compartment.getMembrane().getPosition();
      69           3 :     const auto& membraneRadius = membraneType.getRadius();
      70             : 
      71           3 :     auto gridPoints = mathutils::calculateGrid(2 * membraneRadius, 2 * membraneRadius, 2 * maxRadius);
      72             : 
      73           3 :     const auto& topLeft = membraneCenter - Vector2d{membraneRadius, membraneRadius};
      74      462337 :     for (size_t i = 0; i < gridPoints.size();)
      75             :     {
      76      462334 :         gridPoints[i] += topLeft;
      77      462334 :         bool valid = true;
      78             : 
      79      462334 :         if (!mathutils::circleIsFullyContainedByCircle(gridPoints[i], maxRadius, membraneCenter, membraneRadius))
      80       99320 :             valid = false;
      81             : 
      82      462334 :         const auto& subCompartments = compartment.getCompartments();
      83      824515 :         for (auto iter = subCompartments.begin(); valid && iter != subCompartments.end(); ++iter)
      84             :         {
      85      362181 :             const auto& membrane = (*iter)->getMembrane();
      86      362181 :             const auto& M = membrane.getPosition();
      87      362181 :             const auto& R = simulationContext_.membraneTypeRegistry.getByID(membrane.getTypeID()).getRadius();
      88             : 
      89      362181 :             if (mathutils::circlesOverlap(gridPoints[i], maxRadius, M, R))
      90       14985 :                 valid = false;
      91             :         }
      92             : 
      93      462334 :         if (!valid)
      94             :         {
      95      114305 :             std::swap(gridPoints[i], gridPoints.back());
      96      114305 :             gridPoints.pop_back();
      97             :         }
      98             :         else
      99      348029 :             ++i;
     100             :     }
     101             : 
     102           3 :     if (static_cast<int>(gridPoints.size()) < discCount)
     103             :     {
     104           0 :         LOG(WARNING) << std::to_string(discCount)
     105           0 :                      << " discs should be created for membrane of type \"" + membraneType.getName() +
     106             :                             "\", but the grid can only fit "
     107           0 :                      << std::to_string(gridPoints.size()) << ". " << std::to_string(discCount - gridPoints.size())
     108           0 :                      << " discs will not be created.";
     109             :     }
     110             : 
     111           6 :     return gridPoints;
     112           0 : }
     113             : 
     114           3 : void CellPopulator::populateCompartmentWithDistribution(Compartment& compartment, double maxRadius)
     115             : {
     116             :     const auto& membraneTypeName =
     117           3 :         simulationContext_.membraneTypeRegistry.getByID(compartment.getMembrane().getTypeID()).getName();
     118             : 
     119           3 :     const auto& membraneType = findMembraneTypeByName(simulationConfig_, membraneTypeName);
     120           3 :     const auto& distribution = membraneType.discTypeDistribution;
     121             : 
     122           3 :     if (membraneType.discCount < 0)
     123           0 :         throw ExceptionWithLocation("Disc count for membrane type \"" + membraneTypeName + "\" is negative (" +
     124           0 :                                     std::to_string(membraneType.discCount) + ")");
     125             : 
     126           3 :     if (membraneType.discCount == 0 || distribution.empty())
     127           0 :         return;
     128             : 
     129           3 :     auto gridPoints = calculateCompartmentGridPoints(compartment, maxRadius, membraneType.discCount);
     130           3 :     const auto discCount = std::min(static_cast<std::size_t>(membraneType.discCount), gridPoints.size());
     131             : 
     132           3 :     if (!distribution.empty())
     133             :     {
     134           3 :         if (double sum = calculateValueSum(distribution) * 100; std::abs(sum - 100) > 1e-1)
     135           0 :             throw ExceptionWithLocation("Distribution for membrane type \"" + membraneTypeName +
     136           0 :                                         "\" doesn't add up to 100%, it adds up to " + std::to_string(sum) + "%");
     137             :     }
     138             : 
     139           9 :     for (const auto& [discTypeName, frequency] : distribution)
     140             :     {
     141           6 :         const auto count = static_cast<int>(std::round(frequency * static_cast<double>(discCount)));
     142           6 :         const auto discTypeID = simulationContext_.discTypeRegistry.getIDFor(discTypeName);
     143           6 :         const auto& discType = simulationContext_.discTypeRegistry.getByID(discTypeID);
     144             : 
     145         306 :         for (int i = 0; i < count && !gridPoints.empty(); ++i)
     146             :         {
     147         300 :             Disc newDisc(discTypeID);
     148         300 :             newDisc.setPosition(gridPoints.back());
     149         300 :             newDisc.setVelocity(
     150         300 :                 sampleVelocityFromDistribution(simulationConfig_.mostProbableSpeed, discType.getMass()));
     151             : 
     152         300 :             compartment.addDisc(std::move(newDisc));
     153         300 :             gridPoints.pop_back();
     154             :         }
     155             :     }
     156             : 
     157           5 :     for (auto& subCompartment : compartment.getCompartments())
     158           2 :         populateCompartmentWithDistribution(*subCompartment, maxRadius);
     159           3 : }
     160             : 
     161         300 : Vector2d CellPopulator::sampleVelocityFromDistribution(double mostProbableSpeed, double m) const
     162             : {
     163         300 :     static thread_local std::mt19937 gen(std::random_device{}());
     164         300 :     std::normal_distribution<double> normalDistribution(0, mostProbableSpeed / std::sqrt(m));
     165         300 :     return Vector2d{normalDistribution(gen), normalDistribution(gen)};
     166             : }
     167             : 
     168          23 : Compartment& CellPopulator::findDeepestContainingCompartment(const Disc& disc)
     169             : {
     170          23 :     const auto& M = disc.getPosition();
     171          23 :     const auto& R = simulationContext_.discTypeRegistry.getByID(disc.getTypeID()).getRadius();
     172             : 
     173          39 :     auto isFullyContainedIn = [&](const Compartment& compartment)
     174             :     {
     175          39 :         const auto& membrane = compartment.getMembrane();
     176          39 :         const auto& membraneType = simulationContext_.membraneTypeRegistry.getByID(membrane.getTypeID());
     177             : 
     178          39 :         return mathutils::circleIsFullyContainedByCircle(M, R, membrane.getPosition(), membraneType.getRadius());
     179          23 :     };
     180             : 
     181          23 :     if (!isFullyContainedIn(cell_))
     182             :     {
     183           0 :         LOG(WARNING) << "Disc at (" + std::to_string(M.x) + ", " + std::to_string(M.y) +
     184           0 :                             ") is not contained by the cell";
     185           0 :         return cell_;
     186             :     }
     187             : 
     188          23 :     Compartment* compartment = &cell_;
     189          23 :     bool continueSearch = true;
     190             : 
     191          52 :     while (continueSearch)
     192             :     {
     193          29 :         continueSearch = false;
     194          45 :         for (auto& subCompartment : compartment->getCompartments())
     195             :         {
     196          16 :             if (isFullyContainedIn(*subCompartment))
     197             :             {
     198           6 :                 compartment = subCompartment.get();
     199           6 :                 continueSearch = true;
     200             :             }
     201             :         }
     202             :     }
     203             : 
     204          23 :     return *compartment;
     205             : }
     206             : 
     207           3 : double CellPopulator::calculateValueSum(const std::unordered_map<std::string, double>& distribution) const
     208             : {
     209           3 :     return std::accumulate(distribution.begin(), distribution.end(), 0.0,
     210           9 :                            [](double partialSum, const auto& entry) { return partialSum + entry.second; });
     211             : }
     212             : 
     213             : } // namespace cell

Generated by: LCOV version 1.14