Treats each relation network as a conductance network (diameter^4 / length), solves nodal balance per connected component, so flow is conserved at every junction and wider pipes carry proportionally more flow. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
124 lines
5.0 KiB
C++
124 lines
5.0 KiB
C++
#include "TestFixtures.h"
|
|
#include "core/FlowSolver.h"
|
|
#include "core/NetworkModel.h"
|
|
|
|
#include <QSignalSpy>
|
|
#include <QTest>
|
|
|
|
using namespace diag;
|
|
|
|
class TestSolver : public QObject {
|
|
Q_OBJECT
|
|
private slots:
|
|
void init()
|
|
{
|
|
fixtures::fillRegistry(m_reg);
|
|
delete m_model;
|
|
m_model = new NetworkModel(&m_reg, this);
|
|
}
|
|
|
|
void chain_carriesSupplyThrough()
|
|
{
|
|
// source(10) -> pump -> consumer(10)
|
|
const QUuid src = m_model->addNode(QStringLiteral("source"), {0, 0});
|
|
const QUuid pump = m_model->addNode(QStringLiteral("pump"), {200, 0});
|
|
const QUuid c = m_model->addNode(QStringLiteral("consumer"), {400, 0});
|
|
const QUuid e1 = m_model->addEdge(src, "out", pump, "in");
|
|
const QUuid e2 = m_model->addEdge(pump, "out", c, "in");
|
|
|
|
const FlowResult r = FlowSolver::solve(*m_model, QStringLiteral("water"));
|
|
QVERIFY(r.ok);
|
|
QVERIFY(qAbs(r.edgeFlow.value(e1) - 10.0) < 1e-6);
|
|
QVERIFY(qAbs(r.edgeFlow.value(e2) - 10.0) < 1e-6);
|
|
}
|
|
|
|
void junction_conservesFlow()
|
|
{
|
|
// source(10) -> junction -> two consumers (arbitrary demands).
|
|
const QUuid src = m_model->addNode(QStringLiteral("source"), {0, 0});
|
|
const QUuid j = m_model->addNode(QStringLiteral("junction"), {200, 0});
|
|
const QUuid c1 = m_model->addNode(QStringLiteral("consumer"), {400, -100});
|
|
const QUuid c2 = m_model->addNode(QStringLiteral("consumer"), {400, 100});
|
|
m_model->setNodeProperty(c1, "demand", 4.0);
|
|
m_model->setNodeProperty(c2, "demand", 6.0);
|
|
|
|
const QUuid eIn = m_model->addEdge(src, "out", j, "w");
|
|
const QUuid eOut1 = m_model->addEdge(j, "n", c1, "in");
|
|
const QUuid eOut2 = m_model->addEdge(j, "s", c2, "in");
|
|
|
|
const FlowResult r = FlowSolver::solve(*m_model, QStringLiteral("water"));
|
|
QVERIFY(r.ok);
|
|
const double in = r.edgeFlow.value(eIn);
|
|
const double out = r.edgeFlow.value(eOut1) + r.edgeFlow.value(eOut2);
|
|
QVERIFY(qAbs(in - out) < 1e-6); // Kirchhoff at the junction
|
|
QVERIFY(qAbs(in - 10.0) < 1e-6); // all supply enters
|
|
QVERIFY(qAbs(r.edgeFlow.value(eOut1) - 4.0) < 1e-6);
|
|
QVERIFY(qAbs(r.edgeFlow.value(eOut2) - 6.0) < 1e-6);
|
|
}
|
|
|
|
void parallelPipes_widerCarriesMore()
|
|
{
|
|
// Two parallel pipes between two junctions; DN150 vs DN75.
|
|
const QUuid src = m_model->addNode(QStringLiteral("source"), {0, 0});
|
|
const QUuid j1 = m_model->addNode(QStringLiteral("junction"), {200, 0});
|
|
const QUuid j2 = m_model->addNode(QStringLiteral("junction"), {400, 0});
|
|
const QUuid c = m_model->addNode(QStringLiteral("consumer"), {600, 0});
|
|
m_model->addEdge(src, "out", j1, "w");
|
|
const QUuid wide = m_model->addEdge(j1, "n", j2, "n");
|
|
const QUuid narrow = m_model->addEdge(j1, "s", j2, "s");
|
|
m_model->addEdge(j2, "e", c, "in");
|
|
m_model->setEdgeProperty(wide, "diameter", 150.0);
|
|
m_model->setEdgeProperty(narrow, "diameter", 75.0);
|
|
|
|
const FlowResult r = FlowSolver::solve(*m_model, QStringLiteral("water"));
|
|
QVERIFY(r.ok);
|
|
const double fWide = r.edgeFlow.value(wide);
|
|
const double fNarrow = r.edgeFlow.value(narrow);
|
|
QVERIFY(fWide > 0 && fNarrow > 0);
|
|
QVERIFY(qAbs(fWide + fNarrow - 10.0) < 1e-6);
|
|
// Conductance ~ d^4: ratio (150/75)^4 = 16.
|
|
QVERIFY(qAbs(fWide / fNarrow - 16.0) < 1e-6);
|
|
}
|
|
|
|
void unbalancedInjections_areNormalized()
|
|
{
|
|
// Supply 10 vs demand 4: imbalance is spread, but flow stays conserved.
|
|
const QUuid src = m_model->addNode(QStringLiteral("source"), {0, 0});
|
|
const QUuid c = m_model->addNode(QStringLiteral("consumer"), {200, 0});
|
|
m_model->setNodeProperty(c, "demand", 4.0);
|
|
const QUuid e = m_model->addEdge(src, "out", c, "in");
|
|
|
|
const FlowResult r = FlowSolver::solve(*m_model, QStringLiteral("water"));
|
|
QVERIFY(r.ok);
|
|
QVERIFY(qAbs(r.edgeFlow.value(e) - 7.0) < 1e-6); // (10 + 4) / 2
|
|
}
|
|
|
|
void otherRelation_untouched()
|
|
{
|
|
const QUuid src = m_model->addNode(QStringLiteral("source"), {0, 0});
|
|
const QUuid c = m_model->addNode(QStringLiteral("consumer"), {200, 0});
|
|
m_model->addEdge(src, "out", c, "in");
|
|
const FlowResult r = FlowSolver::solve(*m_model, QStringLiteral("power"));
|
|
QVERIFY(r.ok);
|
|
QVERIFY(r.edgeFlow.isEmpty());
|
|
}
|
|
|
|
void apply_writesFlowsAndSignals()
|
|
{
|
|
const QUuid src = m_model->addNode(QStringLiteral("source"), {0, 0});
|
|
const QUuid c = m_model->addNode(QStringLiteral("consumer"), {200, 0});
|
|
const QUuid e = m_model->addEdge(src, "out", c, "in");
|
|
QSignalSpy spy(m_model, &NetworkModel::flowChanged);
|
|
FlowSolver::apply(*m_model, FlowSolver::solve(*m_model, QStringLiteral("water")));
|
|
QCOMPARE(spy.count(), 1);
|
|
QVERIFY(m_model->edge(e)->flowRate > 0);
|
|
}
|
|
|
|
private:
|
|
TypeRegistry m_reg;
|
|
NetworkModel* m_model = nullptr;
|
|
};
|
|
|
|
QTEST_GUILESS_MAIN(TestSolver)
|
|
#include "tst_solver.moc"
|