LCOV - code coverage report
Current view: top level - cell - Compartment.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 105 124 84.7 %
Date: 2025-12-26 22:55:38 Functions: 19 20 95.0 %

          Line data    Source code
       1             : #include "Compartment.hpp"
       2             : #include "CollisionDetector.hpp"
       3             : #include "CollisionHandler.hpp"
       4             : #include "Disc.hpp"
       5             : #include "MathUtils.hpp"
       6             : #include "ReactionEngine.hpp"
       7             : 
       8             : namespace cell
       9             : {
      10             : 
      11          21 : Compartment::Compartment(Compartment* parent, Membrane membrane, SimulationContext simulationContext)
      12          21 :     : parent_(parent)
      13          21 :     , membrane_(std::move(membrane))
      14          21 :     , simulationContext_(std::move(simulationContext))
      15          42 :     , collisionDetector_(simulationContext_.discTypeRegistry, simulationContext_.membraneTypeRegistry)
      16             : {
      17          21 :     membrane_.setCompartment(this);
      18          21 :     collisionDetector_.setParams(CollisionDetector::Params{.discs = &discs_,
      19          21 :                                                            .membranes = &membranes_,
      20          21 :                                                            .intrudingDiscs = &intrudingDiscs_,
      21          21 :                                                            .containingMembrane = &membrane_});
      22          21 : }
      23             : 
      24          21 : Compartment::~Compartment() = default;
      25             : 
      26      362942 : const Membrane& Compartment::getMembrane() const
      27             : {
      28      362942 :     return membrane_;
      29             : }
      30             : 
      31           0 : void Compartment::setDiscs(std::vector<Disc>&& discs)
      32             : {
      33           0 :     discs_ = std::move(discs);
      34           0 : }
      35             : 
      36         323 : void Compartment::addDisc(Disc disc)
      37             : {
      38         323 :     discs_.push_back(std::move(disc));
      39         323 : }
      40             : 
      41          38 : const std::vector<Disc>& Compartment::getDiscs() const
      42             : {
      43          38 :     return discs_;
      44             : }
      45             : 
      46           5 : void Compartment::addIntrudingDisc(Disc* disc, const Compartment* source, bool shouldBeCaptured)
      47             : {
      48           5 :     intrudingDiscs_.push_back(disc);
      49           5 :     intruderCaptureStatus_.push_back(static_cast<char>(shouldBeCaptured));
      50             : 
      51           5 :     if (compartments_.size() <= 1)
      52           5 :         return;
      53             : 
      54           0 :     const auto discRadius = simulationContext_.discTypeRegistry.getByID(disc->getTypeID()).getRadius();
      55           0 :     for (auto& compartment : compartments_)
      56             :     {
      57           0 :         if (compartment.get() == source)
      58           0 :             continue;
      59             : 
      60             :         // TODO In case of many compartments, make search more efficient
      61             :         const auto* membraneType =
      62           0 :             &simulationContext_.membraneTypeRegistry.getByID(compartment->getMembrane().getTypeID());
      63           0 :         if (mathutils::circlesOverlap(disc->getPosition(), discRadius, compartment->getMembrane().getPosition(),
      64             :                                       membraneType->getRadius()))
      65           0 :             compartment->addIntrudingDisc(disc, source, shouldBeCaptured);
      66             :     }
      67             : }
      68             : 
      69      462366 : std::vector<std::unique_ptr<Compartment>>& Compartment::getCompartments()
      70             : {
      71      462366 :     return compartments_;
      72             : }
      73             : 
      74          14 : const std::vector<std::unique_ptr<Compartment>>& Compartment::getCompartments() const
      75             : {
      76          14 :     return compartments_;
      77             : }
      78             : 
      79           3 : const Compartment* Compartment::getParent() const
      80             : {
      81           3 :     return parent_;
      82             : }
      83             : 
      84           9 : void Compartment::update(double dt)
      85             : {
      86             :     // TODO Remove recursing twice and just accept destroyed discs at the end of update?
      87           9 :     bimolecularUpdate();
      88           9 :     unimolecularUpdate(dt);
      89           9 : }
      90             : 
      91          13 : void Compartment::bimolecularUpdate()
      92             : {
      93          13 :     allocateMemoryForIntruders();
      94          13 :     auto discMembraneCollisions = detectDiscMembraneCollisions();
      95          13 :     registerIntruders(discMembraneCollisions);
      96             : 
      97          17 :     for (auto& compartment : compartments_)
      98           4 :         compartment->bimolecularUpdate();
      99             : 
     100          13 :     auto discDiscCollisions = detectDiscDiscCollisions();
     101          13 :     simulationContext_.collisionHandler.resolveCollisions(discMembraneCollisions);
     102          13 :     simulationContext_.collisionHandler.resolveCollisions(discDiscCollisions);
     103          13 :     simulationContext_.reactionEngine.applyBimolecularReactions(discDiscCollisions, newDiscs_);
     104             : 
     105          13 :     captureIntruders();
     106          13 : }
     107             : 
     108          13 : void Compartment::unimolecularUpdate(double dt)
     109             : {
     110          17 :     for (auto& compartment : compartments_)
     111           4 :         compartment->unimolecularUpdate(dt);
     112             : 
     113          13 :     moveDiscsAndCleanUp(dt);
     114          13 : }
     115             : 
     116          13 : void Compartment::allocateMemoryForIntruders()
     117             : {
     118          13 :     if (intruderAllocationCount_ == 0)
     119          13 :         return;
     120             : 
     121           0 :     discs_.reserve(
     122           0 :         std::max(2 * discs_.capacity(), discs_.capacity() + intruderAllocationCount_ * 2)); // Let's be generous here
     123           0 :     intruderAllocationCount_ = 0;
     124             : }
     125             : 
     126          11 : Compartment* Compartment::createSubCompartment(Membrane membrane)
     127             : {
     128          11 :     auto compartment = std::make_unique<Compartment>(this, std::move(membrane), simulationContext_);
     129          11 :     membranes_.push_back(compartment->getMembrane());
     130          11 :     compartments_.push_back(std::move(compartment));
     131          11 :     collisionDetector_.buildMembraneIndex();
     132             : 
     133          22 :     return compartments_.back().get();
     134          11 : }
     135             : 
     136          13 : std::vector<cell::CollisionDetector::Collision> Compartment::detectDiscMembraneCollisions()
     137             : {
     138          13 :     collisionDetector_.buildDiscIndex();
     139          13 :     return collisionDetector_.detectDiscMembraneCollisions();
     140             : }
     141             : 
     142          13 : std::vector<cell::CollisionDetector::Collision> Compartment::detectDiscDiscCollisions()
     143             : {
     144          13 :     collisionDetector_.addIntrudingDiscsToIndex();
     145          13 :     auto collisions = collisionDetector_.detectDiscDiscCollisions();
     146             : 
     147          13 :     return collisions;
     148             : }
     149             : 
     150          13 : void Compartment::registerIntruders(const std::vector<CollisionDetector::Collision>& discMembraneCollisions)
     151             : {
     152             :     // Assumptions:
     153             :     // - Called directly after collision detection, so no destroyed discs yet
     154             :     // - parent_ can't be nullptr because the outermost membrane shouldn't be permeable for anything so
     155             :     // collision.allowedToPass would always be false
     156             :     // - Also this function only expects DiscContainingMembrane and DiscChildMembrane collisions
     157             : 
     158          21 :     for (const auto& collision : discMembraneCollisions)
     159             :     {
     160           8 :         if (!collision.allowedToPass)
     161           3 :             continue;
     162             : 
     163           5 :         const auto discRadius = simulationContext_.discTypeRegistry.getByID(collision.disc->getTypeID()).getRadius();
     164             : 
     165           5 :         const auto& membranePosition = collision.membrane->getPosition();
     166             :         const auto membraneRadius =
     167           5 :             simulationContext_.membraneTypeRegistry.getByID(collision.membrane->getTypeID()).getRadius();
     168             : 
     169           5 :         if (collision.type == CollisionDetector::CollisionType::DiscContainingMembrane)
     170             :         {
     171             :             // collision.membrane is the membrane of this compartment
     172             :             const bool shouldBeCaptured =
     173           1 :                 !mathutils::circlesOverlap(collision.disc->getPosition(), discRadius, membranePosition, membraneRadius);
     174             : 
     175           1 :             parent_->addIntrudingDisc(collision.disc, this, shouldBeCaptured);
     176             :         }
     177             :         else
     178             :         {
     179             :             // collision.membrane is the child membrane the disc collided with
     180           4 :             const bool shouldBeCaptured = mathutils::circleIsFullyContainedByCircle(
     181           4 :                 collision.disc->getPosition(), discRadius, membranePosition, membraneRadius);
     182             : 
     183           4 :             collision.membrane->getCompartment()->addIntrudingDisc(collision.disc, nullptr, shouldBeCaptured);
     184             :         }
     185             :     }
     186          13 : }
     187             : 
     188          13 : void Compartment::captureIntruders()
     189             : {
     190             :     // Intruders can safely be added without reallocation (which would invalidate any pointers/intruders in other
     191             :     // compartments)
     192          13 :     if (discs_.capacity() >= discs_.size() + intrudingDiscs_.size())
     193             :     {
     194           9 :         for (std::size_t i = 0; i < intrudingDiscs_.size(); ++i)
     195             :         {
     196           0 :             auto& intruder = intrudingDiscs_[i];
     197           0 :             if (intruder->isMarkedDestroyed())
     198           0 :                 continue;
     199             : 
     200           0 :             if (intruderCaptureStatus_[i])
     201             :             {
     202           0 :                 discs_.push_back(*intruder);
     203           0 :                 intruder->markDestroyed();
     204             :             }
     205             :         }
     206             :     }
     207             :     else
     208           4 :         intruderAllocationCount_ = intrudingDiscs_.size();
     209             : 
     210          13 :     intrudingDiscs_.clear();
     211          13 :     intruderCaptureStatus_.clear();
     212          13 : }
     213             : 
     214          13 : void Compartment::moveDiscsAndCleanUp(double dt)
     215             : {
     216          39 :     for (std::size_t i = 0; i < discs_.size(); ++i)
     217             :     {
     218          26 :         auto& disc = discs_[i];
     219          26 :         simulationContext_.reactionEngine.applyUnimolecularReactions(disc, dt, newDiscs_);
     220             : 
     221          26 :         if (disc.isMarkedDestroyed())
     222             :         {
     223          14 :             discs_[i] = std::move(discs_.back());
     224          14 :             discs_.pop_back();
     225          14 :             --i;
     226          14 :             continue;
     227             :         }
     228             : 
     229          12 :         disc.move(disc.getVelocity() * dt);
     230             :     }
     231             : 
     232          23 :     for (auto& disc : newDiscs_)
     233          10 :         disc.move(disc.getVelocity() * dt);
     234             : 
     235          13 :     if (!newDiscs_.empty())
     236             :     {
     237           6 :         discs_.insert(discs_.end(), newDiscs_.begin(), newDiscs_.end());
     238           6 :         newDiscs_.clear();
     239             :     }
     240          13 : }
     241             : 
     242             : } // namespace cell

Generated by: LCOV version 1.14