LCOV - code coverage report
Current view: top level - cell - CellPopulator.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 101 117 86.3 %
Date: 2025-12-06 00:15:40 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           8 : CellPopulator::CellPopulator(Cell& cell, SimulationConfig simulationConfig, SimulationContext simulationContext)
      17           8 :     : cell_(cell)
      18           8 :     , simulationConfig_(std::move(simulationConfig))
      19           8 :     , simulationContext_(std::move(simulationContext))
      20             : {
      21           8 : }
      22             : 
      23           8 : void CellPopulator::populateCell()
      24             : {
      25           8 :     if (simulationConfig_.useDistribution)
      26           1 :         populateWithDistributions();
      27             :     else
      28           7 :         populateDirectly();
      29           8 : }
      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           7 : void CellPopulator::populateDirectly()
      46             : {
      47          26 :     for (const auto& disc : simulationConfig_.discs)
      48             :     {
      49          19 :         Disc newDisc(simulationContext_.discTypeRegistry.getIDFor(disc.discTypeName));
      50          19 :         newDisc.setPosition({disc.x, disc.y});
      51          19 :         newDisc.setVelocity({disc.vx, disc.vy});
      52             : 
      53          19 :         auto& compartment = findDeepestContainingCompartment(newDisc);
      54          19 :         compartment.addDisc(std::move(newDisc));
      55             :     }
      56           7 : }
      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<sf::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 - sf::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             : 
     121           3 :     const auto& distribution = membraneType.discTypeDistribution;
     122             : 
     123           3 :     if (membraneType.discCount < 0)
     124           0 :         throw ExceptionWithLocation("Disc count for membrane type \"" + membraneTypeName + "\" is negative (" +
     125           0 :                                     std::to_string(membraneType.discCount) + ")");
     126             : 
     127           3 :     auto gridPoints = calculateCompartmentGridPoints(compartment, maxRadius, membraneType.discCount);
     128           3 :     const auto discCount = std::min(static_cast<std::size_t>(membraneType.discCount), gridPoints.size());
     129             : 
     130           3 :     if (!distribution.empty())
     131             :     {
     132           3 :         if (double sum = calculateValueSum(distribution) * 100; std::abs(sum - 100) > 1e-1)
     133           0 :             throw ExceptionWithLocation("Distribution for membrane type \"" + membraneTypeName +
     134           0 :                                         "\" doesn't add up to 100%, it adds up to " + std::to_string(sum) + "%");
     135             :     }
     136             : 
     137           9 :     for (const auto& [discTypeName, frequency] : distribution)
     138             :     {
     139           6 :         const auto count = static_cast<int>(std::round(frequency * static_cast<double>(discCount)));
     140           6 :         const auto discTypeID = simulationContext_.discTypeRegistry.getIDFor(discTypeName);
     141             : 
     142         306 :         for (int i = 0; i < count && !gridPoints.empty(); ++i)
     143             :         {
     144         300 :             Disc newDisc(discTypeID);
     145         300 :             newDisc.setPosition(gridPoints.back());
     146         300 :             newDisc.setVelocity(sampleVelocityFromDistribution());
     147             : 
     148         300 :             compartment.addDisc(std::move(newDisc));
     149         300 :             gridPoints.pop_back();
     150             :         }
     151             :     }
     152             : 
     153           5 :     for (auto& subCompartment : compartment.getCompartments())
     154           2 :         populateCompartmentWithDistribution(*subCompartment, maxRadius);
     155           3 : }
     156             : 
     157         300 : sf::Vector2d CellPopulator::sampleVelocityFromDistribution() const
     158             : {
     159         300 :     static thread_local std::random_device rd;
     160         300 :     static thread_local std::mt19937 gen(rd());
     161             : 
     162         300 :     const auto sigma = simulationConfig_.maxVelocity;
     163             : 
     164             :     // 2D maxwell distribution is a rayleigh distribution
     165             :     // weibull distribution with k=2 and lambda = sigma*sqrt(2) is rayleigh distribution
     166             : 
     167         300 :     std::uniform_real_distribution<double> angleDistribution(0, 2 * std::numbers::pi);
     168         300 :     std::weibull_distribution<double> speedDistribution(2, std::numbers::sqrt2 * sigma);
     169             : 
     170         300 :     const auto angle = angleDistribution(gen);
     171         300 :     const auto speed = speedDistribution(gen);
     172             : 
     173         300 :     return sf::Vector2d{speed * std::cos(angle), speed * std::sin(angle)};
     174             : }
     175             : 
     176          19 : Compartment& CellPopulator::findDeepestContainingCompartment(const Disc& disc)
     177             : {
     178          19 :     const auto& M = disc.getPosition();
     179          19 :     const auto& R = simulationContext_.discTypeRegistry.getByID(disc.getTypeID()).getRadius();
     180             : 
     181          31 :     auto isFullyContainedIn = [&](const Compartment& compartment)
     182             :     {
     183          31 :         const auto& membrane = compartment.getMembrane();
     184          31 :         const auto& membraneType = simulationContext_.membraneTypeRegistry.getByID(membrane.getTypeID());
     185             : 
     186          31 :         return mathutils::circleIsFullyContainedByCircle(M, R, membrane.getPosition(), membraneType.getRadius());
     187          19 :     };
     188             : 
     189          19 :     if (!isFullyContainedIn(cell_))
     190             :     {
     191           0 :         LOG(WARNING) << "Disc at (" + std::to_string(M.x) + ", " + std::to_string(M.y) +
     192           0 :                             ") is not contained by the cell";
     193           0 :         return cell_;
     194             :     }
     195             : 
     196          19 :     Compartment* compartment = &cell_;
     197          19 :     bool continueSearch = true;
     198             : 
     199          42 :     while (continueSearch)
     200             :     {
     201          23 :         continueSearch = false;
     202          35 :         for (auto& subCompartment : compartment->getCompartments())
     203             :         {
     204          12 :             if (isFullyContainedIn(*subCompartment))
     205             :             {
     206           4 :                 compartment = subCompartment.get();
     207           4 :                 continueSearch = true;
     208             :             }
     209             :         }
     210             :     }
     211             : 
     212          19 :     return *compartment;
     213             : }
     214             : 
     215           3 : double CellPopulator::calculateValueSum(const std::unordered_map<std::string, double>& distribution) const
     216             : {
     217           3 :     return std::accumulate(distribution.begin(), distribution.end(), 0.0,
     218           9 :                            [](double partialSum, const auto& entry) { return partialSum + entry.second; });
     219             : }
     220             : 
     221             : } // namespace cell

Generated by: LCOV version 1.14