using System.Collections.Generic; using FluidSim.Components; using FluidSim.Interfaces; namespace FluidSim.Core { public class Solver { private readonly List _volumes = new(); private readonly List _pipes = new(); private readonly List _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); /// Set the global time step (called from Simulation). public void SetTimeStep(double dt) => _dt = dt; /// /// Convenient method to set the boundary type of a pipe end. /// 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 volume‑coupled 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 sub‑steps int nSub = 1; foreach (var p in _pipes) nSub = Math.Max(nSub, p.GetRequiredSubSteps(_dt)); double dtSub = _dt / nSub; // 4. Sub‑step loop for (int sub = 0; sub < nSub; sub++) { foreach (var p in _pipes) p.SimulateSingleStep(dtSub); // Transfer flows only for volume‑coupled 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 volume‑coupled ends between sub‑steps 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(); 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: volume’s own enthalpy (set by PushStateToPort) is used GetVolume(volPort)?.Integrate(dtSub); } } }