diff --git a/contrib/vcpkg b/contrib/vcpkg index b8927a1f..940f5877 160000 --- a/contrib/vcpkg +++ b/contrib/vcpkg @@ -1 +1 @@ -Subproject commit b8927a1f9dd394496414668e1d2f94b2559d45a8 +Subproject commit 940f58770cee8e2011bfb4847fb2bd70057301a8 diff --git a/scripts/unix/clang-format.sh b/scripts/unix/clang-format.sh old mode 100644 new mode 100755 index 4d1467e7..8e78deca --- a/scripts/unix/clang-format.sh +++ b/scripts/unix/clang-format.sh @@ -5,8 +5,8 @@ cd ../.. if [[ "$1" == "-f" ]]; then echo "Formatting files..." - find src/ include/ test/ -name '*.h' -o -name '*.hpp' -o -name '*.inl' -o -name '*.cpp' | xargs clang-format -i + find src/ test/ -name '*.h' -o -name '*.hpp' -o -name '*.inl' -o -name '*.cpp' | xargs clang-format -i else echo "Checking with clang-format..." - find src/ include/ test/ -name '*.h' -o -name '*.hpp' -o -name '*.inl' -o -name '*.cpp' | xargs clang-format --dry-run --Werror + find src/ test/ -name '*.h' -o -name '*.hpp' -o -name '*.inl' -o -name '*.cpp' | xargs clang-format --dry-run --Werror fi \ No newline at end of file diff --git a/src/apps/cell-cli/Main.cpp b/src/apps/cell-cli/Main.cpp index 810145e8..c69b1dd8 100644 --- a/src/apps/cell-cli/Main.cpp +++ b/src/apps/cell-cli/Main.cpp @@ -14,7 +14,7 @@ using namespace std::chrono_literals; int main(int argc, char** argv) { - CLI::App app{"Command line interface for the cell simulation"}; + CLI::App app{"Cell 1.1.1\nCommand line interface for the cell simulation"}; fs::path configFile; fs::path outFile; diff --git a/src/lib/cell/DataPoint.cpp b/src/lib/cell/DataPoint.cpp index 28dbfc5c..9d40f8a8 100644 --- a/src/lib/cell/DataPoint.cpp +++ b/src/lib/cell/DataPoint.cpp @@ -60,7 +60,7 @@ void DataPoint::initializeHistograms(const std::vector& discTypeIDs, bh::axis::regular<>(20, -3 * vSigma, 3 * vSigma, "v_y")); data_.vHistogram = bh::make_histogram(bh::axis::category(discTypeIDs, "Disc type"), - bh::axis::regular<>(20, -3 * vSigma, 3 * vSigma, "v")); + bh::axis::regular<>(20, 0, 4 * vSigma, "v")); } void DataPoint::addSimulationData(Cell& cell, const ch::duration& elapsedTime, diff --git a/src/lib/cell/Reaction.cpp b/src/lib/cell/Reaction.cpp index f087d521..3406a6b7 100644 --- a/src/lib/cell/Reaction.cpp +++ b/src/lib/cell/Reaction.cpp @@ -2,6 +2,8 @@ #include "ExceptionWithLocation.hpp" #include "Hashing.hpp" +#include + namespace cell { @@ -66,12 +68,12 @@ void Reaction::setProbability(double probability) void Reaction::validate(const DiscTypeRegistry& discTypeRegistry) const { - auto getMass = [&](DiscTypeID discTypeID) { return discTypeRegistry.getByID(discTypeID).getMass(); }; + const auto getMass = [&](DiscTypeID discTypeID) { return discTypeRegistry.getByID(discTypeID).getMass(); }; const auto eductMassSum = getMass(educt1_) + (educt2_ ? getMass(*educt2_) : 0); const auto productMassSum = getMass(product1_) + (product2_ ? getMass(*product2_) : 0); - if (eductMassSum != productMassSum) + if (std::abs(eductMassSum - productMassSum) > 1e-6) throw ExceptionWithLocation(toString(*this, discTypeRegistry) + ": Product- and educt masses need to be identical"); @@ -79,6 +81,20 @@ void Reaction::validate(const DiscTypeRegistry& discTypeRegistry) const throw ExceptionWithLocation(toString(*this, discTypeRegistry) + ": Educt 1 and product 1 are identical"); } +void Reaction::validateAreaConservation(const DiscTypeRegistry& discTypeRegistry) const +{ + const auto getArea = [&](DiscTypeID discTypeID) + { + const auto R = discTypeRegistry.getByID(discTypeID).getRadius(); + return std::numbers::pi * R * R; + }; + const auto eductAreaSum = getArea(educt1_) + (educt2_ ? getArea(*educt2_) : 0); + const auto productAreaSum = getArea(product1_) + (product2_ ? getArea(*product2_) : 0); + + if (std::abs(eductAreaSum - productAreaSum) > 1e-6) + throw ExceptionWithLocation(toString(*this, discTypeRegistry) + ": Area not conserved"); +} + std::string Reaction::getTypeString() const { switch (type_) diff --git a/src/lib/cell/Reaction.hpp b/src/lib/cell/Reaction.hpp index 96ed320f..4ffd679c 100644 --- a/src/lib/cell/Reaction.hpp +++ b/src/lib/cell/Reaction.hpp @@ -157,6 +157,8 @@ class Reaction */ void validate(const DiscTypeRegistry& discTypeRegistry) const; + void validateAreaConservation(const DiscTypeRegistry& discTypeRegistry) const; + private: std::string getTypeString() const; diff --git a/src/lib/cell/SimulationConfig.hpp b/src/lib/cell/SimulationConfig.hpp index 3a37cbba..318f3c36 100644 --- a/src/lib/cell/SimulationConfig.hpp +++ b/src/lib/cell/SimulationConfig.hpp @@ -91,12 +91,11 @@ struct SimulationConfig */ double simulationTimeScale = 1; double mostProbableSpeed = 600; - bool useDistribution = true; + bool reactionsConserveArea = false; // In case of no distribution, these are used std::vector discs; - // These never use a distribution std::vector membranes; @@ -114,9 +113,9 @@ NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Reaction, educt1, educt2, product1, product2, } // namespace config -NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(SimulationConfig, discTypes, membraneTypes, reactions, cellMembraneType, - simulationTimeStep, simulationTimeScale, mostProbableSpeed, useDistribution, discs, - membranes) +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(SimulationConfig, discTypes, membraneTypes, reactions, cellMembraneType, + simulationTimeStep, simulationTimeScale, mostProbableSpeed, + useDistribution, reactionsConserveArea, discs, membranes) cell::config::MembraneType& findMembraneTypeByName(cell::SimulationConfig& simulationConfig, std::string membraneTypeName); diff --git a/src/lib/cell/SimulationConfigBuilder.cpp b/src/lib/cell/SimulationConfigBuilder.cpp index e754cb59..eede4bd8 100644 --- a/src/lib/cell/SimulationConfigBuilder.cpp +++ b/src/lib/cell/SimulationConfigBuilder.cpp @@ -80,6 +80,11 @@ void SimulationConfigBuilder::setMostProbableSpeed(double mostProbableSpeed) simulationConfig_.mostProbableSpeed = mostProbableSpeed; } +void SimulationConfigBuilder::setReactionsConserveArea(bool value) +{ + simulationConfig_.reactionsConserveArea = value; +} + const SimulationConfig& SimulationConfigBuilder::getSimulationConfig() const { return simulationConfig_; diff --git a/src/lib/cell/SimulationConfigBuilder.hpp b/src/lib/cell/SimulationConfigBuilder.hpp index 1867d025..91a9a7df 100644 --- a/src/lib/cell/SimulationConfigBuilder.hpp +++ b/src/lib/cell/SimulationConfigBuilder.hpp @@ -31,6 +31,7 @@ class SimulationConfigBuilder void setTimeStep(double simulationTimeStep); void setTimeScale(double simulationTimeScale); void setMostProbableSpeed(double mostProbableSpeed); + void setReactionsConserveArea(bool value); const SimulationConfig& getSimulationConfig() const; diff --git a/src/lib/cell/SimulationFactory.cpp b/src/lib/cell/SimulationFactory.cpp index cee20434..d611d4ad 100644 --- a/src/lib/cell/SimulationFactory.cpp +++ b/src/lib/cell/SimulationFactory.cpp @@ -136,6 +136,9 @@ ReactionTable SimulationFactory::buildReactionTable(const SimulationConfig& simu reaction.product2.empty() ? std::nullopt : std::make_optional(discTypeRegistry.getIDFor(reaction.product2)); Reaction newReaction(educt1, educt2, product1, product2, reaction.probability); + if (simulationConfig.reactionsConserveArea) + newReaction.validateAreaConservation(discTypeRegistry); + reactionTable.addReaction(newReaction); } diff --git a/src/lib/cell/SimulationRecordSerializer.cpp b/src/lib/cell/SimulationRecordSerializer.cpp index 3474bc70..2cede060 100644 --- a/src/lib/cell/SimulationRecordSerializer.cpp +++ b/src/lib/cell/SimulationRecordSerializer.cpp @@ -6,7 +6,7 @@ namespace cell { -void SimulationRecordSerializer::writeTypeCountsToCsv(const std::vector& dataPoints, +void SimulationRecordSerializer::writeTypeCountsToCsv(const std::deque& dataPoints, const DiscTypeRegistry& discTypeRegistry, const fs::path& outFile) { std::ofstream file(outFile); diff --git a/src/lib/cell/SimulationRecordSerializer.hpp b/src/lib/cell/SimulationRecordSerializer.hpp index 97de1449..34b094a3 100644 --- a/src/lib/cell/SimulationRecordSerializer.hpp +++ b/src/lib/cell/SimulationRecordSerializer.hpp @@ -4,13 +4,15 @@ #include "DataPoint.hpp" #include "Types.hpp" +#include + namespace cell { class SimulationRecordSerializer { public: - void writeTypeCountsToCsv(const std::vector& dataPoints, const DiscTypeRegistry& discTypeRegistry, + void writeTypeCountsToCsv(const std::deque& dataPoints, const DiscTypeRegistry& discTypeRegistry, const fs::path& outFile); }; diff --git a/src/lib/cell/SimulationRecorder.cpp b/src/lib/cell/SimulationRecorder.cpp index b622d9a0..480b0d09 100644 --- a/src/lib/cell/SimulationRecorder.cpp +++ b/src/lib/cell/SimulationRecorder.cpp @@ -11,11 +11,6 @@ namespace cell SimulationRecorder::SimulationRecorder(const DiscTypeRegistry& discTypeRegistry, double vSigma) : discTypeRegistry_(discTypeRegistry) { - // With a simulation time step of 1ms, we get 1000 data points each second - // With an averaging time of 10ms, we save 100 datapoints for 1 second - // We'll reserve enough space for 5 minutes, 5*60*100 - dataPoints_.reserve(30000); - std::vector discTypeIDs = discTypeRegistry.getIDs(); currentDataPoint_.initializeHistograms(discTypeIDs, vSigma); } @@ -54,7 +49,7 @@ void SimulationRecorder::storeRemainingData() storeDataPoint(); } -const std::vector& SimulationRecorder::getDataPoints() const +const std::deque& SimulationRecorder::getDataPoints() const { return dataPoints_; } diff --git a/src/lib/cell/SimulationRecorder.hpp b/src/lib/cell/SimulationRecorder.hpp index f90de416..64c6d9ef 100644 --- a/src/lib/cell/SimulationRecorder.hpp +++ b/src/lib/cell/SimulationRecorder.hpp @@ -8,6 +8,8 @@ #include +#include + namespace cell { @@ -33,7 +35,7 @@ class SimulationRecorder void processInitialSimulationData(Cell& cell); void processSimulationData(Cell& cell, const ch::duration& elapsedTime); void storeRemainingData(); - const std::vector& getDataPoints() const; + const std::deque& getDataPoints() const; void clear(); const DataPoint& getCurrentDataPoint() const; void setRecordLastFrame(bool value); @@ -48,7 +50,7 @@ class SimulationRecorder private: ch::duration storageInterval_ = ch::milliseconds{100}; DataPoint currentDataPoint_; - std::vector dataPoints_; + std::deque dataPoints_; const DiscTypeRegistry& discTypeRegistry_; bool recordLastFrame_ = false; Frame lastFrame_; diff --git a/src/lib/gui/core/FrameBuffer.cpp b/src/lib/gui/core/FrameBuffer.cpp deleted file mode 100644 index 723f5526..00000000 --- a/src/lib/gui/core/FrameBuffer.cpp +++ /dev/null @@ -1,19 +0,0 @@ -#include "core/FrameBuffer.hpp" - -void FrameBuffer::pushFrame(Frame frame) -{ - QMutexLocker lock(&mutex_); - latestFrame_ = std::move(frame); -} - -std::optional FrameBuffer::takeLatest() -{ - QMutexLocker lock(&mutex_); - if (!latestFrame_) - return std::nullopt; - - auto frame = std::move(latestFrame_); - latestFrame_.reset(); - - return frame; -} diff --git a/src/lib/gui/core/FrameBuffer.hpp b/src/lib/gui/core/FrameBuffer.hpp deleted file mode 100644 index 9873a7db..00000000 --- a/src/lib/gui/core/FrameBuffer.hpp +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef D8F4E8FD_F1C9_47D8_B57A_6AC979ABC03A_HPP -#define D8F4E8FD_F1C9_47D8_B57A_6AC979ABC03A_HPP - -#include "cell/SimulationRecorder.hpp" - -#include -#include - -#include - -using Frame = cell::SimulationRecorder::Frame; - -class FrameBuffer : public QObject -{ - Q_OBJECT -public: - void pushFrame(Frame frame); - std::optional takeLatest(); - -private: - QMutex mutex_; - std::optional latestFrame_; -}; - -#endif /* D8F4E8FD_F1C9_47D8_B57A_6AC979ABC03A_HPP */ diff --git a/src/lib/gui/core/MainWindow.cpp b/src/lib/gui/core/MainWindow.cpp index 13f7e197..dbee7c5f 100644 --- a/src/lib/gui/core/MainWindow.cpp +++ b/src/lib/gui/core/MainWindow.cpp @@ -74,7 +74,7 @@ MainWindow::MainWindow(QWidget* parent) connect(ui->simulationControlWidget, &SimulationControlWidget::fitIntoViewRequested, ui->simulationWidget, &SimulationWidget::fitSimulationIntoView); - connect(simulation_.get(), &Simulation::initialFrame, ui->simulationWidget, + connect(simulation_.get(), &Simulation::drawFrameImmediately, ui->simulationWidget, &SimulationWidget::renderFrameImmediately); connect(simulation_.get(), &Simulation::frame, ui->simulationWidget, &SimulationWidget::queueFrameForRendering); connect(simulation_.get(), &Simulation::performanceData, ui->simulationInfoWidget, @@ -192,7 +192,7 @@ void MainWindow::showAboutDialog() -

This is Cell, version 1.1.0. Build time: %1 %2.

+

This is Cell, version 1.1.1. Build time: %1 %2.

Application for simulating reaction networks.

Developed by: Yannik Schroeder

For additional information, see: https://github.com/yannik131/Cell

diff --git a/src/lib/gui/core/PlotCategories.cpp b/src/lib/gui/core/PlotCategories.cpp index 85730616..6a0f5566 100644 --- a/src/lib/gui/core/PlotCategories.cpp +++ b/src/lib/gui/core/PlotCategories.cpp @@ -12,7 +12,9 @@ const std::vector PlotCategoryPairs{ {PlotCategory::CollisionCounts, "Collision counts"}, {PlotCategory::AbsoluteMomentum, "Absolute momentum"}, {PlotCategory::KineticEnergy, "Kinetic energy"}, - {PlotCategory::VelocityDistribution, "Velocity distribution"}, + {PlotCategory::XVelocityDistribution, "X Velocity distribution"}, + {PlotCategory::YVelocityDistribution, "Y Velocity distribution"}, + {PlotCategory::AbsoluteVelocityDistribution, "|Velocity| distribution"}, {PlotCategory::VelocityColorMap, "Velocity color map"}, }; diff --git a/src/lib/gui/core/PlotCategories.hpp b/src/lib/gui/core/PlotCategories.hpp index 97366b89..05c83fc9 100644 --- a/src/lib/gui/core/PlotCategories.hpp +++ b/src/lib/gui/core/PlotCategories.hpp @@ -13,7 +13,9 @@ enum class PlotCategory CollisionCounts, AbsoluteMomentum, KineticEnergy, - VelocityDistribution, + XVelocityDistribution, + YVelocityDistribution, + AbsoluteVelocityDistribution, VelocityColorMap }; diff --git a/src/lib/gui/core/Simulation.cpp b/src/lib/gui/core/Simulation.cpp index 055dc08e..b66874bd 100644 --- a/src/lib/gui/core/Simulation.cpp +++ b/src/lib/gui/core/Simulation.cpp @@ -54,7 +54,7 @@ void Simulation::emitLastFrame() if (!simulationRecorder_) return; - emit initialFrame(simulationRecorder_->getLastFrame()); + emit drawFrameImmediately(simulationRecorder_->getLastFrame()); } SimulationConfigUpdater& Simulation::getSimulationConfigUpdater() diff --git a/src/lib/gui/core/Simulation.hpp b/src/lib/gui/core/Simulation.hpp index fa711c6e..525b11de 100644 --- a/src/lib/gui/core/Simulation.hpp +++ b/src/lib/gui/core/Simulation.hpp @@ -45,7 +45,7 @@ class Simulation : public QObject signals: void started(); void stopped(); - void initialFrame(Frame frame); + void drawFrameImmediately(Frame frame); void frame(Frame frame); void performanceData(const cell::SimulationRunner::PerformanceData& performanceData); void dataPoint(const cell::DataPoint& dataPoint); diff --git a/src/lib/gui/dialogs/SetupDialog.cpp b/src/lib/gui/dialogs/SetupDialog.cpp index fcb3d2a7..a78e15fb 100644 --- a/src/lib/gui/dialogs/SetupDialog.cpp +++ b/src/lib/gui/dialogs/SetupDialog.cpp @@ -3,6 +3,8 @@ #include "models/SetupModel.hpp" #include "ui_SetupDialog.h" +#include + SetupDialog::SetupDialog(QWidget* parent, SimulationConfigUpdater* simulationConfigUpdater) : QDialog(parent) , ui(new Ui::SetupDialog) @@ -21,6 +23,8 @@ SetupDialog::SetupDialog(QWidget* parent, SimulationConfigUpdater* simulationCon connect(ui->cancelPushButton, &QPushButton::clicked, this, &QDialog::reject); connect(ui->useDistributionRadioButton, &QRadioButton::toggled, setupModel_, &SetupModel::setUseDistribution); + connect(ui->conserveAreaCheckbox, &QCheckBox::checkStateChanged, setupModel_, + &SetupModel::setReactionsConserveArea); connect(ui->timeStepSpinBox, &QSpinBox::valueChanged, setupModel_, &SetupModel::setTimeStepUs); connect(ui->timeScaleDoubleSpinBox, &QDoubleSpinBox::valueChanged, setupModel_, &SetupModel::setTimeScale); connect(ui->mostProbableSpeedSpinBox, &QDoubleSpinBox::valueChanged, setupModel_, @@ -48,6 +52,7 @@ void SetupDialog::displayCurrentConfig() { const auto& config = simulationConfigUpdater_->getSimulationConfig(); ui->useDistributionRadioButton->setChecked(config.useDistribution); + ui->conserveAreaCheckbox->setChecked(config.reactionsConserveArea); ui->manualPositionsRadioButton->setChecked(!config.useDistribution); ui->timeStepSpinBox->setValue(static_cast(std::round(config.simulationTimeStep * 1e6))); ui->timeScaleDoubleSpinBox->setValue(config.simulationTimeScale); diff --git a/src/lib/gui/dialogs/SetupDialog.ui b/src/lib/gui/dialogs/SetupDialog.ui index 7a3856a4..72d63888 100644 --- a/src/lib/gui/dialogs/SetupDialog.ui +++ b/src/lib/gui/dialogs/SetupDialog.ui @@ -10,7 +10,7 @@ 0 0 517 - 325 + 331 @@ -183,6 +183,26 @@ + + + + + + + Conserve area in reactions + + + + + + + + + + + + + diff --git a/src/lib/gui/models/PlotModel.cpp b/src/lib/gui/models/PlotModel.cpp index b205afdd..5de3c208 100644 --- a/src/lib/gui/models/PlotModel.cpp +++ b/src/lib/gui/models/PlotModel.cpp @@ -16,10 +16,14 @@ PlotModel::PlotModel(QObject* parent, Simulation* simulation) void PlotModel::setPlotCategory(PlotCategory plotCategory) { - static const std::unordered_map graphType{ - {PlotCategory::AbsoluteMomentum, 1}, {PlotCategory::CollisionCounts, 1}, - {PlotCategory::KineticEnergy, 1}, {PlotCategory::TypeCounts, 1}, - {PlotCategory::VelocityDistribution, 2}, {PlotCategory::VelocityColorMap, 3}}; + static const std::unordered_map graphType{{PlotCategory::AbsoluteMomentum, 1}, + {PlotCategory::CollisionCounts, 1}, + {PlotCategory::KineticEnergy, 1}, + {PlotCategory::TypeCounts, 1}, + {PlotCategory::XVelocityDistribution, 2}, + {PlotCategory::YVelocityDistribution, 2}, + {PlotCategory::AbsoluteVelocityDistribution, 2}, + {PlotCategory::VelocityColorMap, 3}}; plotCategory_ = plotCategory; setPlot(); @@ -94,7 +98,9 @@ void PlotModel::setPlot() case PlotCategory::AbsoluteMomentum: case PlotCategory::CollisionCounts: case PlotCategory::KineticEnergy: setLinePlot(); break; - case PlotCategory::VelocityDistribution: setHistogramPlot(); break; + case PlotCategory::XVelocityDistribution: + case PlotCategory::YVelocityDistribution: + case PlotCategory::AbsoluteVelocityDistribution: setHistogramPlot(); break; case PlotCategory::VelocityColorMap: setColorMapPlot(); break; default: throw ExceptionWithLocation("Invalid plot category"); } @@ -176,7 +182,7 @@ void PlotModel::setColorMapPlot() std::vector histograms; const auto& simulationRecorder = simulation_->getSimulationRecorder(); const auto& dataPoints = simulationRecorder.getDataPoints().empty() - ? std::vector({simulationRecorder.getCurrentDataPoint()}) + ? std::deque({simulationRecorder.getCurrentDataPoint()}) : simulationRecorder.getDataPoints(); histograms.reserve(dataPoints.size()); @@ -216,7 +222,9 @@ void PlotModel::updatePlot(const DataPoint& dataPoint) case PlotCategory::AbsoluteMomentum: case PlotCategory::CollisionCounts: case PlotCategory::KineticEnergy: updateLinePlot(); break; - case PlotCategory::VelocityDistribution: updateHistogramPlot(); break; + case PlotCategory::XVelocityDistribution: + case PlotCategory::YVelocityDistribution: + case PlotCategory::AbsoluteVelocityDistribution: updateHistogramPlot(); break; case PlotCategory::VelocityColorMap: updateColorMapPlot(); break; default: throw ExceptionWithLocation("Invalid plot category"); } @@ -379,7 +387,17 @@ Histogram PlotModel::discardInactiveDiscTypes(const Histogram& histogram) Histogram PlotModel::getVelocityHistogramFromDataPoint(const DataPoint& dataPoint, CalculateSum calculateSum) { - auto h = discardInactiveDiscTypes(dataPoint.getData().vxHistogram); + cell::Histogram velocityHistogram; + switch (plotCategory_) + { + case PlotCategory::XVelocityDistribution: velocityHistogram = dataPoint.getData().vxHistogram; break; + case PlotCategory::YVelocityDistribution: velocityHistogram = dataPoint.getData().vyHistogram; break; + case PlotCategory::AbsoluteVelocityDistribution: + case PlotCategory::VelocityColorMap: velocityHistogram = dataPoint.getData().vHistogram; break; + default: throw ExceptionWithLocation("Invalid plot category"); + } + + auto h = discardInactiveDiscTypes(velocityHistogram); if (calculateSum.value) h = sumHistogramStacks(h); diff --git a/src/lib/gui/models/SetupModel.cpp b/src/lib/gui/models/SetupModel.cpp index 81510d36..933a5f06 100644 --- a/src/lib/gui/models/SetupModel.cpp +++ b/src/lib/gui/models/SetupModel.cpp @@ -14,6 +14,11 @@ void SetupModel::setUseDistribution(bool useDistribution) simulationConfig_.useDistribution = useDistribution; } +void SetupModel::setReactionsConserveArea(bool value) +{ + simulationConfig_.reactionsConserveArea = value; +} + void SetupModel::setTimeStepUs(int timeStepUs) { simulationConfig_.simulationTimeStep = timeStepUs / 1e6; diff --git a/src/lib/gui/models/SetupModel.hpp b/src/lib/gui/models/SetupModel.hpp index 09780b9c..0b38c4a0 100644 --- a/src/lib/gui/models/SetupModel.hpp +++ b/src/lib/gui/models/SetupModel.hpp @@ -15,6 +15,7 @@ class SetupModel : public QObject, public AbstractConfigChanger SetupModel(QObject* parent, SimulationConfigUpdater* simulationConfigUpdater); void setUseDistribution(bool useDistribution); + void setReactionsConserveArea(bool value); void setTimeStepUs(int timeStepUs); void setTimeScale(double timeScale); void setMostProbableSpeed(double mostProbableSpeed); diff --git a/src/lib/gui/widgets/PlotWidget.cpp b/src/lib/gui/widgets/PlotWidget.cpp index 846f318b..a3fd3767 100644 --- a/src/lib/gui/widgets/PlotWidget.cpp +++ b/src/lib/gui/widgets/PlotWidget.cpp @@ -338,8 +338,10 @@ QString PlotWidget::getYAxisLabelFromPlotCategory(const PlotCategory& plotCatego case PlotCategory::CollisionCounts: return "Collision count"; case PlotCategory::KineticEnergy: return "E_kin"; case PlotCategory::TypeCounts: return "Number of discs"; - case PlotCategory::VelocityColorMap: return "v_x"; - case PlotCategory::VelocityDistribution: return "Count"; + case PlotCategory::VelocityColorMap: return "|v|"; + case PlotCategory::XVelocityDistribution: + case PlotCategory::YVelocityDistribution: + case PlotCategory::AbsoluteVelocityDistribution: return "Count"; default: throw ExceptionWithLocation("Invalid plot category"); } } diff --git a/src/lib/gui/widgets/SimulationWidget.cpp b/src/lib/gui/widgets/SimulationWidget.cpp index 469dc57e..d2065ef7 100644 --- a/src/lib/gui/widgets/SimulationWidget.cpp +++ b/src/lib/gui/widgets/SimulationWidget.cpp @@ -165,12 +165,12 @@ void SimulationWidget::rebuildTypeShapes(const cell::DiscTypeRegistry& discTypeR void SimulationWidget::queueFrameForRendering(Frame frame) { - frameBuffer_.pushFrame(std::move(frame)); + frame_ = std::move(frame); } void SimulationWidget::renderFrameImmediately(Frame frame) { - frameBuffer_.pushFrame(std::move(frame)); + queueFrameForRendering(std::move(frame)); // Use the next draw event, otherwise we'll get a huge FPS increase that could crash the GUI if the user // drags the view and causes continuous redraws while the simulation is running @@ -183,21 +183,16 @@ void SimulationWidget::renderFrameImmediately(Frame frame) void SimulationWidget::drawFrame() { using namespace std::chrono; - auto frame = frameBuffer_.takeLatest(); - if (!frame) - return; - const auto start = myClock::now(); - sf::RenderWindow::clear(sf::Color::Black); - for (const auto& disc : frame->discs) + for (const auto& disc : frame_.discs) { discTypeShapes_[disc.getTypeID()].setPosition(utility::toVector2f(disc.getPosition())); sf::RenderWindow::draw(discTypeShapes_[disc.getTypeID()]); } - for (const auto& membrane : frame->membranes) + for (const auto& membrane : frame_.membranes) { auto& membraneTypeShape = membraneTypeShapes_[membrane.getTypeID()]; membraneTypeShape.setOutlineThickness(static_cast(QSFMLWidget::getCurrentZoom())); diff --git a/src/lib/gui/widgets/SimulationWidget.hpp b/src/lib/gui/widgets/SimulationWidget.hpp index 3c8ecf26..7ee7f93d 100644 --- a/src/lib/gui/widgets/SimulationWidget.hpp +++ b/src/lib/gui/widgets/SimulationWidget.hpp @@ -1,7 +1,6 @@ #ifndef F8B0BFE1_0E51_424A_A3DE_69E0B57425D7_HPP #define F8B0BFE1_0E51_424A_A3DE_69E0B57425D7_HPP -#include "core/FrameBuffer.hpp" #include "core/Types.hpp" #include "widgets/QSFMLWidget.hpp" @@ -67,7 +66,7 @@ public slots: myClock::time_point currentRenderInterval_{}; myClock::duration elapsedRenderTime_{}; int renderedFrames_ = 0; - FrameBuffer frameBuffer_; + Frame frame_; QTimer renderingTimer_; }; diff --git a/test/libcell/integration/CellTest.cpp b/test/libcell/integration/CellTest.cpp index c6f09193..894008fc 100644 --- a/test/libcell/integration/CellTest.cpp +++ b/test/libcell/integration/CellTest.cpp @@ -254,3 +254,12 @@ TEST_F(ACell, SimulatesDiscsPassingThroughMembranesInBothDirections) ASSERT_TRUE(counts.contains("C")); EXPECT_EQ(counts["C"], 2); } + +TEST_F(ACell, EnforcesAreaConservationOfReactions) +{ + builder.addReaction("C", "", "A", "B", Probability{1}); + EXPECT_NO_THROW(createAndUpdateCell()); + + builder.setReactionsConserveArea(true); + EXPECT_THROW(createAndUpdateCell(), InvalidReactionsException); +} \ No newline at end of file diff --git a/test/libcell/integration/SerializationTest.cpp b/test/libcell/integration/SerializationTest.cpp index f7f58515..4140183f 100644 --- a/test/libcell/integration/SerializationTest.cpp +++ b/test/libcell/integration/SerializationTest.cpp @@ -25,6 +25,7 @@ TEST(ASimulationConfig, CanBeSerialized) builder.addMembrane("Large", Position{.x = 500, .y = 500}); builder.useDistribution(true); + builder.setReactionsConserveArea(true); builder.setDistribution("", {{"A", 0.5}, {"B", 0.5}}); builder.setDistribution("Large", {{"A", 1}, {"B", 0}}); builder.addDisc("C", Position{.x = 50, .y = 50}, Velocity{.x = 50, .y = 50}); diff --git a/test/resources/water.json b/test/resources/water.json new file mode 100644 index 00000000..6747f825 --- /dev/null +++ b/test/resources/water.json @@ -0,0 +1,207 @@ +{ + "config": { + "cellMembraneType": { + "discCount": 10000, + "discTypeDistribution": { + "CO₂ (g)": 0.1, + "Ca²⁺": 0.1, + "H₂O": 0.8 + }, + "name": "Cell membrane", + "permeabilityMap": {}, + "radius": 500.0 + }, + "discTypes": [ + { + "mass": 1.0, + "name": "H⁺", + "radius": 1.13 + }, + { + "mass": 18.0, + "name": "H₂O", + "radius": 4.79 + }, + { + "mass": 40.0, + "name": "Ca²⁺", + "radius": 7.14 + }, + { + "mass": 44.0, + "name": "CO₂ (g)", + "radius": 7.48 + }, + { + "mass": 44.0, + "name": "CO₂ (aq)", + "radius": 7.48 + }, + { + "mass": 60.0, + "name": "CO₃²⁻", + "radius": 8.74 + }, + { + "mass": 61.0, + "name": "HCO₃⁻", + "radius": 8.81 + }, + { + "mass": 62.0, + "name": "H₂CO₃", + "radius": 8.88 + }, + { + "mass": 100.0, + "name": "CaCO₃ (s)", + "radius": 11.28 + } + ], + "discs": [], + "membraneTypes": [], + "membranes": [], + "mostProbableSpeed": 200.0, + "reactions": [ + { + "educt1": "CO₂ (g)", + "educt2": "", + "probability": 0.05, + "product1": "CO₂ (aq)", + "product2": "" + }, + { + "educt1": "CO₂ (aq)", + "educt2": "", + "probability": 0.01, + "product1": "CO₂ (g)", + "product2": "" + }, + { + "educt1": "CO₂ (aq)", + "educt2": "H₂O", + "probability": 0.02, + "product1": "H₂CO₃", + "product2": "" + }, + { + "educt1": "H₂CO₃", + "educt2": "", + "probability": 0.2, + "product1": "CO₂ (aq)", + "product2": "H₂O" + }, + { + "educt1": "H₂CO₃", + "educt2": "", + "probability": 0.865, + "product1": "H⁺", + "product2": "HCO₃⁻" + }, + { + "educt1": "H⁺", + "educt2": "HCO₃⁻", + "probability": 0.12, + "product1": "H₂CO₃", + "product2": "" + }, + { + "educt1": "HCO₃⁻", + "educt2": "", + "probability": 0.03, + "product1": "H⁺", + "product2": "CO₃²⁻" + }, + { + "educt1": "H⁺", + "educt2": "CO₃²⁻", + "probability": 0.3, + "product1": "HCO₃⁻", + "product2": "" + }, + { + "educt1": "CaCO₃ (s)", + "educt2": "", + "probability": 0.002, + "product1": "Ca²⁺", + "product2": "CO₃²⁻" + }, + { + "educt1": "Ca²⁺", + "educt2": "CO₃²⁻", + "probability": 0.015, + "product1": "CaCO₃ (s)", + "product2": "" + } + ], + "reactionsConserveArea": false, + "simulationTimeScale": 15.0, + "simulationTimeStep": 0.003, + "useDistribution": true + }, + "configVersion": "1", + "discTypeColorMap": { + "CO₂ (aq)": [ + 255, + 255, + 0, + 255 + ], + "CO₂ (g)": [ + 0, + 0, + 128, + 255 + ], + "CO₃²⁻": [ + 128, + 0, + 128, + 255 + ], + "CaCO₃ (s)": [ + 0, + 0, + 255, + 255 + ], + "Ca²⁺": [ + 255, + 165, + 0, + 255 + ], + "HCO₃⁻": [ + 0, + 255, + 0, + 255 + ], + "H⁺": [ + 255, + 0, + 0, + 255 + ], + "H₂CO₃": [ + 255, + 192, + 203, + 255 + ], + "H₂O": [ + 0, + 128, + 128, + 255 + ] + }, + "membraneTypeColorMap": { + "Cell membrane": [ + 255, + 255, + 0, + 255 + ] + } +} \ No newline at end of file