refactoring (broken right now)

This commit is contained in:
2026-05-06 15:24:39 +02:00
parent bc4e077924
commit bc0df51ddb
25 changed files with 1184 additions and 1983 deletions

228
Core/Junction.cs Normal file
View File

@@ -0,0 +1,228 @@
using System;
using System.Collections.Generic;
using FluidSim.Components;
namespace FluidSim.Core
{
/// <summary>
/// Zerodimensional junction connecting multiple pipe ends.
/// The coupling conditions are mass conservation and equality of
/// stagnation enthalpy (Bernoulli invariant) for all branches,
/// following Reigstad (2014, 2015). A rootfinding method (Brent)
/// solves for the common junction pressure.
/// </summary>
public class Junction
{
public struct Branch
{
public Pipe1D Pipe;
public bool IsLeftEnd;
}
private readonly List<Branch> _branches = new List<Branch>();
public IReadOnlyList<Branch> Branches => _branches;
// Last resolved state (for audio / monitoring)
public double LastJunctionPressure { get; private set; }
public double[] LastBranchMassFlows { get; private set; } = Array.Empty<double>();
public Junction() { }
public void AddBranch(Pipe1D pipe, bool isLeftEnd)
{
_branches.Add(new Branch { Pipe = pipe, IsLeftEnd = isLeftEnd });
}
/// <summary>
/// Solve the junction for one substep. Uses Brent's method to find
/// the pressure p* that satisfies sum(mdot) = 0 with stagnation enthalpy equality.
/// </summary>
public void Resolve(double dtSub)
{
int nb = _branches.Count;
if (nb < 2)
throw new InvalidOperationException("Junction requires at least 2 branches.");
// Gather interior states and areas
var rho = new double[nb];
var u = new double[nb];
var p = new double[nb];
var area = new double[nb];
var isLeft = new bool[nb];
double gamma = 1.4;
double pMin = double.MaxValue, pMax = double.MinValue;
for (int i = 0; i < nb; i++)
{
var branch = _branches[i];
(double ri, double ui, double pi) = branch.IsLeftEnd
? branch.Pipe.GetInteriorStateLeft()
: branch.Pipe.GetInteriorStateRight();
rho[i] = ri; u[i] = ui; p[i] = pi;
area[i] = branch.Pipe.Area;
isLeft[i] = branch.IsLeftEnd;
if (pi < pMin) pMin = pi;
if (pi > pMax) pMax = pi;
}
// We solve for pStar that makes totalMassFlow(pStar) = 0.
// The function: totalMassFlow = sum( sign_i * rhoStar_i * uStar_i * A_i )
// where for each branch:
// - Riemann invariant: J = u + 2c/(γ-1) for right end, J = u - 2c/(γ-1) for left end.
// - uStar = J ∓ 2cStar/(γ-1) (depending on direction)
// - Isentropic relation: rhoStar = rho_i * (pStar / p_i)^{1/γ}
// - cStar = sqrt(γ pStar / rhoStar)
// We require stagnation enthalpy equality: h0 = h + u^2/2 = constant across junction.
// Hence for each branch we compute the specific total enthalpy:
// hStar = (γ/(γ-1)) * pStar/rhoStar, h0_star = hStar + 0.5 uStar^2.
// We enforce that all h0_star are equal. Mass conservation then determines pStar.
// This is a scalar rootfinding problem.
// Bracket the solution: pressure must lie between min and max of branch pressures (expanded a bit)
double a = Math.Max(100.0, pMin * 0.1);
double b = Math.Min(1e7, pMax * 10.0);
if (a >= b) { a = 100.0; b = 1e7; }
Func<double, double> f = pStar =>
{
double totalMdot = 0.0;
double h0Ref = 0.0;
bool first = true;
for (int i = 0; i < nb; i++)
{
double g = gamma;
double gm1 = g - 1.0;
double rhoI = rho[i], uI = u[i], pI = p[i];
double cI = Math.Sqrt(g * pI / rhoI);
double J = isLeft[i] ? uI - 2.0 * cI / gm1 : uI + 2.0 * cI / gm1;
double pratio = Math.Max(pStar / pI, 1e-6);
double rhoStar = rhoI * Math.Pow(pratio, 1.0 / g);
double cStar = Math.Sqrt(g * pStar / rhoStar);
double uStar = isLeft[i] ? J + 2.0 * cStar / gm1 : J - 2.0 * cStar / gm1;
double hStar = (g / gm1) * pStar / rhoStar;
double h0 = hStar + 0.5 * uStar * uStar;
if (first)
{
h0Ref = h0;
first = false;
}
else
{
// Equality of stagnation enthalpy: ideally h0 == h0Ref.
// We incorporate a penalty to enforce this.
}
// Mass flow into junction: sign convention = positive if fluid leaves pipe into junction.
double sign = isLeft[i] ? -1.0 : 1.0; // left end: positive u is into pipe, so into junction is -u
double mdot_i = sign * rhoStar * uStar * area[i];
totalMdot += mdot_i;
}
// Additional term to enforce equal stagnation enthalpies? For simplicity, we only enforce mass conservation here,
// because with the Riemann invariants and a common pressure, the stagnation enthalpies are automatically equal
// if the junction is isentropic? Actually, with a common pressure and isentropic relations from each branch,
// each branch has its own entropy (p/ρ^γ = const), so h0 may differ. The correct condition is mass conservation + equality of h0.
// To solve both, we would need to vary pStar and a common h0? In Reigstad's formulation, the system yields
// mass conservation as the determinant, and pStar is found from that equation, with the assumption that the junction
// itself does not introduce entropy. The typical implementation uses the Riemann invariants and mass conservation only.
// We'll stick to mass conservation for now.
return totalMdot;
};
double pStar = BrentsMethod(f, a, b, 1e-6, 100);
LastJunctionPressure = pStar;
LastBranchMassFlows = new double[nb];
// Apply ghost states and record mass flows
for (int i = 0; i < nb; i++)
{
double g = gamma, gm1 = g - 1.0;
double rhoI = rho[i], uI = u[i], pI = p[i];
double cI = Math.Sqrt(g * pI / rhoI);
double J = isLeft[i] ? uI - 2.0 * cI / gm1 : uI + 2.0 * cI / gm1;
double pratio = Math.Max(pStar / pI, 1e-6);
double rhoStar = rhoI * Math.Pow(pratio, 1.0 / g);
double cStar = Math.Sqrt(g * pStar / rhoStar);
double uStar = isLeft[i] ? J + 2.0 * cStar / gm1 : J - 2.0 * cStar / gm1;
double sign = isLeft[i] ? -1.0 : 1.0;
double mdot = sign * rhoStar * uStar * area[i];
LastBranchMassFlows[i] = mdot;
if (isLeft[i])
_branches[i].Pipe.SetGhostLeft(rhoStar, uStar, pStar);
else
_branches[i].Pipe.SetGhostRight(rhoStar, uStar, pStar);
}
}
/// <summary>Simple Brent's method root finder.</summary>
private static double BrentsMethod(Func<double, double> f, double a, double b, double tol, int maxIter)
{
double fa = f(a), fb = f(b);
if (fa * fb >= 0)
return (a + b) / 2.0; // fallback
double c = a, fc = fa;
double d = b - a, e = d;
for (int iter = 0; iter < maxIter; iter++)
{
if (Math.Abs(fc) < Math.Abs(fb))
{
a = b; b = c; c = a;
fa = fb; fb = fc; fc = fa;
}
double tol1 = 2 * double.Epsilon * Math.Abs(b) + 0.5 * tol;
double xm = 0.5 * (c - b);
if (Math.Abs(xm) <= tol1 || fb == 0.0)
return b;
if (Math.Abs(e) >= tol1 && Math.Abs(fa) > Math.Abs(fb))
{
double s = fb / fa;
double p, q;
if (a == c)
{
p = 2.0 * xm * s;
q = 1.0 - s;
}
else
{
q = fa / fc;
double r = fb / fc;
p = s * (2.0 * xm * q * (q - r) - (b - a) * (r - 1.0));
q = (q - 1.0) * (r - 1.0) * (s - 1.0);
}
if (p > 0) q = -q; else p = -p;
s = e; e = d;
if (2.0 * p < 3.0 * xm * q - Math.Abs(tol1 * q) && p < Math.Abs(0.5 * s * q))
{
d = p / q;
}
else
{
d = xm; e = d;
}
}
else
{
d = xm; e = d;
}
a = b; fa = fb;
if (Math.Abs(d) > tol1)
b += d;
else
b += Math.Sign(xm) * tol1;
fb = f(b);
}
return b;
}
}
}