Files
FluidSim/Core/Solver.cs

172 lines
7.0 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Collections.Generic;
using FluidSim.Components;
using FluidSim.Interfaces;
namespace FluidSim.Core
{
public class Solver
{
private readonly List<Volume0D> _volumes = new();
private readonly List<Pipe1D> _pipes = new();
private readonly List<Connection> _connections = new();
private double _dt; // global time step
public void AddVolume(Volume0D v) => _volumes.Add(v);
public void AddPipe(Pipe1D p) => _pipes.Add(p);
public void AddConnection(Connection c) => _connections.Add(c);
/// <summary>Set the global time step (called from Simulation).</summary>
public void SetTimeStep(double dt) => _dt = dt;
/// <summary>
/// Convenient method to set the boundary type of a pipe end.
/// </summary>
public void SetPipeBoundary(Pipe1D pipe, bool isLeft, BoundaryType type, double ambientPressure = 101325.0)
{
if (isLeft)
{
pipe.SetLeftBoundaryType(type);
if (type == BoundaryType.OpenEnd)
pipe.SetLeftAmbientPressure(ambientPressure);
}
else
{
pipe.SetRightBoundaryType(type);
if (type == BoundaryType.OpenEnd)
pipe.SetRightAmbientPressure(ambientPressure);
}
}
public float Step()
{
// 1. Volumes publish state to ports (only needed if any volume exists)
foreach (var v in _volumes)
v.PushStateToPort();
// 2. Set initial pipe boundary conditions ONLY for volumecoupled ends
foreach (var conn in _connections)
{
if (IsPipePort(conn.PortA) && IsVolumePort(conn.PortB))
{
var pipe = GetPipe(conn.PortA);
bool isLeft = pipe.PortA == conn.PortA;
BoundaryType bc = isLeft ? pipe.LeftBCType : pipe.RightBCType;
if (bc == BoundaryType.VolumeCoupling)
SetVolumeBC(conn.PortA, conn.PortB);
}
else if (IsVolumePort(conn.PortA) && IsPipePort(conn.PortB))
{
var pipe = GetPipe(conn.PortB);
bool isLeft = pipe.PortB == conn.PortB;
BoundaryType bc = isLeft ? pipe.LeftBCType : pipe.RightBCType;
if (bc == BoundaryType.VolumeCoupling)
SetVolumeBC(conn.PortB, conn.PortA);
}
}
// 3. Determine number of substeps
int nSub = 1;
foreach (var p in _pipes)
nSub = Math.Max(nSub, p.GetRequiredSubSteps(_dt));
double dtSub = _dt / nSub;
// 4. Substep loop
for (int sub = 0; sub < nSub; sub++)
{
foreach (var p in _pipes)
p.SimulateSingleStep(dtSub);
// Transfer flows only for volumecoupled connections
foreach (var conn in _connections)
{
if (IsPipePort(conn.PortA) && IsVolumePort(conn.PortB))
{
var pipe = GetPipe(conn.PortA);
bool isLeft = pipe.PortA == conn.PortA;
if (pipe.LeftBCType == BoundaryType.VolumeCoupling || pipe.RightBCType == BoundaryType.VolumeCoupling)
TransferAndIntegrate(conn.PortA, conn.PortB, dtSub);
}
else if (IsVolumePort(conn.PortA) && IsPipePort(conn.PortB))
{
var pipe = GetPipe(conn.PortB);
bool isLeft = pipe.PortB == conn.PortB;
if (pipe.LeftBCType == BoundaryType.VolumeCoupling || pipe.RightBCType == BoundaryType.VolumeCoupling)
TransferAndIntegrate(conn.PortB, conn.PortA, dtSub);
}
}
// Update BCs for volumecoupled ends between substeps
if (sub < nSub - 1)
{
foreach (var v in _volumes)
v.PushStateToPort();
foreach (var conn in _connections)
{
if (IsPipePort(conn.PortA) && IsVolumePort(conn.PortB))
{
var pipe = GetPipe(conn.PortA);
bool isLeft = pipe.PortA == conn.PortA;
if ((isLeft && pipe.LeftBCType == BoundaryType.VolumeCoupling) ||
(!isLeft && pipe.RightBCType == BoundaryType.VolumeCoupling))
SetVolumeBC(conn.PortA, conn.PortB);
}
else if (IsVolumePort(conn.PortA) && IsPipePort(conn.PortB))
{
var pipe = GetPipe(conn.PortB);
bool isLeft = pipe.PortB == conn.PortB;
if ((isLeft && pipe.LeftBCType == BoundaryType.VolumeCoupling) ||
(!isLeft && pipe.RightBCType == BoundaryType.VolumeCoupling))
SetVolumeBC(conn.PortB, conn.PortA);
}
}
}
}
// 5. Audio samples from SoundConnections (if any)
var audioSamples = new List<float>();
foreach (var conn in _connections)
{
if (conn is SoundConnection sc)
audioSamples.Add(sc.GetAudioSample());
}
// 6. Clear volume BC flags
foreach (var p in _pipes)
p.ClearBC();
return SoundProcessor.MixAndClip(audioSamples.ToArray());
}
private bool IsVolumePort(Port p) => _volumes.Exists(v => v.Port == p);
private bool IsPipePort(Port p) => _pipes.Exists(pp => pp.PortA == p || pp.PortB == p);
private Pipe1D GetPipe(Port p) => _pipes.Find(pp => pp.PortA == p || pp.PortB == p);
private Volume0D GetVolume(Port p) => _volumes.Find(v => v.Port == p);
private void SetVolumeBC(Port pipePort, Port volPort)
{
var pipe = GetPipe(pipePort);
if (pipe == null) return;
bool isLeft = pipe.PortA == pipePort;
if (isLeft)
pipe.SetLeftVolumeState(volPort.Density, volPort.Pressure);
else
pipe.SetRightVolumeState(volPort.Density, volPort.Pressure);
}
private void TransferAndIntegrate(Port pipePort, Port volPort, double dtSub)
{
double mdot = pipePort.MassFlowRate;
volPort.MassFlowRate = -mdot;
if (mdot < 0) // pipe → volume
{
volPort.SpecificEnthalpy = pipePort.SpecificEnthalpy;
}
// else: volumes own enthalpy (set by PushStateToPort) is used
GetVolume(volPort)?.Integrate(dtSub);
}
}
}