Multiphysics Engine
A fluent simulation engine for student-friendly PDE simulations on simple geometries, designed for visualization in Unity or web platforms. Built on existing finite difference infrastructure (Grid2D, GridOperators, ITimeStepper) with engineering materials, binary/JSON export, and optional ML/Monte Carlo integration.
Namespace: CSharpNumerics.Engines.Multiphysics
โ๏ธ Pipeline Overviewโ
Material โ Geometry โ Boundary โ Source โ Solve โ Result โ Export / ML
The engine builds on existing library capabilities:
| Step | Uses |
|---|---|
| Materials | EngineeringMaterial, EngineeringLibrary (Physics) |
| FD Spatial | Grid2D, GridOperators.Laplacian2D, Advection2D, Divergence2D, SolvePoisson2D (Numerics) |
| Time Integration | Forward Euler (internal), ITimeStepper compatible |
| Beam Theory | SolidExtensions - Euler-Bernoulli, Hooke's law (Physics) |
| Monte Carlo | IMonteCarloModel, MonteCarloSimulator (Statistics) |
| ML Clustering | ClusteringExperiment, KMeans, evaluators (ML) |
| ML Surrogate | IModel, Ridge, MLPRegressor (ML) |
๐งช Simulation Typesโ
| Type | PDE | Method | Dimension |
|---|---|---|---|
HeatPlate | FD (Grid2D + Laplacian2D + forward Euler) | 2D | |
PipeFlow | Hagen-Poiseuille (cylindrical Laplacian) | FD (1D transient) | 1D |
ElectricField | Poisson solver (Gauss-Seidel) | 2D | |
BeamStress | Analytical (SolidExtensions) | 1D | |
CylinderFlow | Incompressible Navier-Stokes | Chorin projection (FD + Poisson) | 2D |
FluidFlow2D | Incompressible Navier-Stokes (rectangular) | Chorin projection (FD + Poisson) | 2D |
MagneticField | (magnetostatics) | Poisson solver (Gauss-Seidel) | 2D |
PlaneStress | Navier-Cauchy (plane stress elasticity) | FD (SOR iterative) | 2D |
๐ Fluent API - SimulationType -> SimulationBuilder -> SimulationResultโ
All simulations use the same fluent entry point:
using CSharpNumerics.Engines.Multiphysics;
using CSharpNumerics.Engines.Multiphysics.Enums;
using CSharpNumerics.Physics.Materials.Engineering;
var result = SimulationType.Create(MultiphysicsType.HeatPlate)
.WithMaterial(EngineeringLibrary.Aluminum)
.WithGeometry(width: 0.1, height: 0.1, nx: 20, ny: 20)
.WithBoundary(top: 100.0, bottom: 0.0, left: 0.0, right: 0.0)
.WithInitialCondition(0.0)
.AddSource(10, 10, 1e6)
.Solve(dt: 0.0001, steps: 500);
double[,] finalField = result.Field; // final temperature field
List<double[,]> timeline = result.Timeline; // per-step snapshots
double peak = result.MaxValue; // 100.0
โจ๏ธ HeatPlate - 2D Transient Heat Equationโ
Thermal diffusivity is automatically derived from the EngineeringMaterial.
var result = SimulationType.Create(MultiphysicsType.HeatPlate)
.WithMaterial(EngineeringLibrary.Copper) // ฮฑ = k/(ฯยทcp) โ 1.17e-4
.WithGeometry(width: 0.1, height: 0.1, nx: 50, ny: 50)
.WithBoundary(top: 200.0, bottom: 0.0, left: 0.0, right: 0.0)
.WithInitialCondition((x, y) => 20.0) // function-based IC
.AddSource(25, 25, 5e5) // point heat source
.Solve(dt: 0.00005, steps: 1000);
// Access timeline for animation
for (int step = 0; step < result.Timeline.Count; step++)
double[,] frame = result.Timeline[step];
๐ฐ PipeFlow - 1D Hagen-Poiseuilleโ
Transient cylindrical channel flow converging to the parabolic velocity profile:
var result = SimulationType.Create(MultiphysicsType.PipeFlow)
.WithMaterial(EngineeringLibrary.Water) // uses KinematicViscosity
.WithGeometry(length: 1.0, radius: 0.005, nodes: 21)
.WithBoundary(pressureGradient: -100.0)
.Solve(dt: 0.02, steps: 3000);
double[] velocity = result.Values; // radial velocity profile
double[] positions = result.Positions; // radial positions [0..R]
double vCenter = result.Values[0]; // peak centreline velocity
โก ElectricField - 2D Poisson/Laplaceโ
Solves with Dirichlet BCs, then computes :
var result = SimulationType.Create(MultiphysicsType.ElectricField)
.WithMaterial(EngineeringLibrary.Air) // uses ElectricPermittivity
.WithGeometry(width: 1.0, height: 1.0, nx: 40, ny: 40)
.WithBoundary(top: 0.0, bottom: 0.0, left: 0.0, right: 100.0)
.AddSource(20, 20, 1e-6) // point charge
.Solve(maxIterations: 20000, tolerance: 1e-8);
double[,] potential = result.Field; // ฯ(x, y)
double[,] Ex = result.Ex; // -โฯ/โx
double[,] Ey = result.Ey; // -โฯ/โy
๐๏ธ BeamStress - 1D Euler-Bernoulliโ
Analytical beam solutions with multiple support types and load combinations:
var result = SimulationType.Create(MultiphysicsType.BeamStress)
.WithMaterial(EngineeringLibrary.Steel) // uses YoungsModulus
.WithGeometry(length: 2.0, nodes: 201)
.WithCrossSection(width: 0.05, height: 0.1) // rectangular
// .WithCrossSection(radius: 0.02) // circular
// .WithSecondMoment(1.5e-6) // custom I
.WithBoundary(BeamSupport.Cantilever) // or SimplySupported, FixedFixed
.AddSource(position: 2.0, value: 1000) // point load at tip
.WithSource(500) // uniform distributed load
.Solve();
double[] deflection = result.Values; // ฮด(x)
double[] moment = result.BendingMoment; // M(x)
double[] shear = result.ShearForce; // V(x)
double[] stress = result.Stress; // ฯ(x) = Mยทy_max/I
double[] x = result.Positions; // node positions
Analytical reference
| Support | Point Load P at tip | Uniform Load q |
|---|---|---|
| Cantilever | ||
| Simply Supported | - | |
| Fixed-Fixed | - |
๐ต CylinderFlow โ 2D Incompressible Navier-Stokesโ
Simulates viscous flow around a circular cylinder using Chorin's projection (fractional-step) method. Classic benchmark for observing the Kรกrmรกn vortex street at moderate Reynolds numbers.
var result = SimulationType.Create(MultiphysicsType.CylinderFlow)
.WithMaterial(EngineeringLibrary.Water) // uses KinematicViscosity
.WithGeometry(width: 2.0, height: 0.8, nx: 200, ny: 80)
.WithCylinder(centerX: 0.4, centerY: 0.4, radius: 0.05)
.WithInletVelocity(0.1) // Uโ = 0.1 m/s
.Solve(dt: 0.001, steps: 5000);
double[,] vx = result.Vx; // x-velocity field
double[,] vy = result.Vy; // y-velocity field
double[,] p = result.Pressure; // pressure field
double[,] w = result.Vorticity; // vorticity ฯ = โvy/โx โ โvx/โy
bool[,] mask = result.CylinderMask; // obstacle cells
double cd = result.DragCoefficient; // โ 1.3โ1.5 at Re โ 100
double cl = result.LiftCoefficient; // oscillates for vortex shedding
double st = result.StrouhalNumber; // โ 0.2 at Re โ 100โ200
Algorithm
Each time step applies Chorin's fractional-step method:
- Predict โ advection (
Advection2D, upwind) + diffusion (Laplacian2D) - Pressure Poisson โ solve (
SolvePoisson2D) - Correct โ (
Gradient2D) - Enforce BCs โ inlet (Dirichlet), outlet (Neumann), walls (free-slip), cylinder (no-slip mask)
CFL-based adaptive time-step clamping ensures stability: .
Boundary Conditions
| Boundary | Type |
|---|---|
| Inlet (left) | Uniform velocity (Dirichlet) |
| Outlet (right) | Zero-gradient (Neumann) |
| Top / Bottom | Free-slip () |
| Cylinder | No-slip (, immersed boundary mask) |
Post-Processing
| Output | Description |
|---|---|
Vorticity | โ ideal for visualization |
DragCoefficient | Pressure-based integrated around cylinder boundary |
LiftCoefficient | Pressure-based โ oscillates during vortex shedding |
StrouhalNumber | estimated from lift time series zero-crossings |
Timeline | Vorticity snapshots every 10 steps for animation |
๐จ FluidFlow2D โ 2D Transient Navier-Stokesโ
Solves incompressible Navier-Stokes on a rectangular domain using the Chorin projection method. Unlike CylinderFlow, this type operates on an open domain without an embedded obstacle โ ideal for lid-driven cavity, channel flow, or general 2D flow simulations.
var result = SimulationType.Create(MultiphysicsType.FluidFlow2D)
.WithMaterial(EngineeringLibrary.Water)
.WithGeometry(width: 0.1, height: 0.1, nx: 40, ny: 40)
.WithBoundary(top: 0.1, bottom: 0, left: 0, right: 0) // lid-driven cavity
.WithInletVelocity(0.01)
.Solve(dt: 0.001, steps: 500);
double[,] vx = result.Vx; // x-velocity field
double[,] vy = result.Vy; // y-velocity field
double[,] p = result.Pressure; // pressure field
double[,] mag = result.Field; // velocity magnitude |v|
List<double[,]> timeline = result.Timeline; // per-step snapshots
Boundary Conditions
| Boundary | Type |
|---|---|
| Inlet (left) | Uniform velocity (Dirichlet) |
| Outlet (right) | Zero-gradient (Neumann) |
| Top / Bottom | No-slip (), or driven (set via WithBoundary) |
Algorithm
Same Chorin fractional-step as CylinderFlow:
- Predict โ advection (upwind) + diffusion (Laplacian2D) โ
- Pressure Poisson โ
- Correct โ
CFL-limited adaptive time-stepping for stability.
๐งฒ MagneticField โ 2D Magnetostaticsโ
Solves the magnetic vector potential Poisson equation with material permeability support:
where is the z-component of the vector potential and is the current density. After solving, computes :
var result = SimulationType.Create(MultiphysicsType.MagneticField)
.WithMaterial(EngineeringLibrary.Steel) // ฮผ_r = 100 (ferromagnetic)
.WithGeometry(width: 0.2, height: 0.2, nx: 50, ny: 50)
.WithBoundary(top: 0, bottom: 0, left: 0, right: 0) // A = 0 at infinity
.AddSource(25, 25, 1e6) // J = 1 MA/mยฒ
.Solve(maxIterations: 10000, tolerance: 1e-7);
double[,] A = result.VectorPotential; // magnetic vector potential A(x,y)
double[,] bx = result.Bx; // B_x = โA/โy
double[,] by = result.By; // B_y = โโA/โx
double[,] bMag = result.Field; // |B| = โ(Bxยฒ + Byยฒ)
The solver uses the material's MagneticPermeability () โ ferromagnetic materials like Steel () produce significantly stronger fields than non-magnetic materials.
๐ง PlaneStress โ 2D Plane Stress Elasticityโ
Solves the 2D Navier-Cauchy equilibrium equations for plane stress using iterative SOR (Successive Over-Relaxation). Produces displacement and stress tensor fields.
with plane-stress constitutive law: , , .
var result = SimulationType.Create(MultiphysicsType.PlaneStress)
.WithMaterial(EngineeringLibrary.Steel)
.WithGeometry(width: 1.0, height: 0.1, nx: 50, ny: 10)
.AddSource(40, 5, 1e6) // point force Fx at interior node
.WithSource(500) // uniform distributed load (Fy)
.Solve(maxIterations: 10000, tolerance: 1e-9);
double[,] ux = result.Ux; // x-displacement
double[,] uy = result.Uy; // y-displacement
double[,] sxx = result.StressXX; // normal stress ฯxx
double[,] syy = result.StressYY; // normal stress ฯyy
double[,] sxy = result.StressXY; // shear stress ฯxy
double[,] vonMises = result.Field; // von Mises stress field
Boundary Conditions
| Boundary | Type |
|---|---|
| Left | Fixed (Dirichlet: ) |
| Right | Free (Neumann) |
| Bottom | Roller (, free in ) |
| Top | Free (Neumann) |
Output Fields
| Field | Description |
|---|---|
Ux, Uy | Displacement components (m) |
StressXX, StressYY | Normal stress (Pa) |
StressXY | Shear stress (Pa) |
Field | Von Mises equivalent stress |
๐ง HeatBlock3D โ 3D Transient Heat Equationโ
Forward Euler diffusion in a solid block with six-face Dirichlet boundary conditions:
var result = SimulationType.Create(MultiphysicsType.HeatBlock3D)
.WithMaterial(EngineeringLibrary.Steel)
.WithGeometry3D(width: 1.0, height: 1.0, depth: 0.5, nx: 20, ny: 20, nz: 10)
.WithBoundary3D(top: 100, bottom: 0, left: 0, right: 0, front: 0, back: 0)
.Solve(dt: 0.0001, steps: 5000);
double[,,] T = result.Field3D; // temperature at final step
double[,] sliceXY = result.SliceXY(5); // horizontal cross-section at iz=5
๐ซ๏ธ FluidDiffusion3D โ 3D Advection-Diffusionโ
Scalar transport in a prescribed 3D velocity field:
var result = SimulationType.Create(MultiphysicsType.FluidDiffusion3D)
.WithGeometry3D(1.0, 1.0, 1.0, 20, 20, 20)
.WithDiffusionCoefficient(0.01)
.WithVelocityField3D((x, y, z) => (0.1, 0, 0))
.AddSource3D(10, 10, 10, 100.0)
.WithBoundary3D(0, 0, 0, 0, 0, 0)
.Solve(dt: 0.001, steps: 2000);
double[,,] c = result.Field3D;
๐ต CylinderFlow3D โ 3D Incompressible Navier-Stokesโ
Extension of CylinderFlow with axial (z) variation. Uses Chorin projection on Grid3D:
var result = SimulationType.Create(MultiphysicsType.CylinderFlow3D)
.WithMaterial(EngineeringLibrary.Water)
.WithGeometry3D(2.0, 0.8, 0.4, 40, 16, 8)
.WithCylinder(0.4, 0.4, 0.05)
.WithInletVelocity(0.1)
.Solve(dt: 0.001, steps: 3000);
double[,,] vx = result.Vx3D;
double[,,] p = result.Pressure3D;
double cd = result.DragCoefficient;
๐๏ธ Snapshots & Timelineโ
Time-stamped field data for animation and export:
using CSharpNumerics.Engines.Multiphysics.Snapshots;
// Build timeline from simulation result
var timeline = SimulationTimeline.FromResult(result, dt: 0.0001, dx: 0.005, dy: 0.005);
int frames = timeline.Count; // initial + N steps
double t0 = timeline.StartTime;
double tEnd = timeline.EndTime;
FieldSnapshot first = timeline[0]; // access by index
// Interpolate between frames (linear blend)
double[,] interp = timeline.InterpolateAt(0.00015);
// Individual snapshots
FieldSnapshot snap = timeline[5];
double val = snap[10, 15]; // indexing
double max = snap.Max();
double min = snap.Min();
// Beam snapshot
var beamSnap = BeamSnapshot.FromResult(result, BeamSupport.Cantilever, length: 2.0);
double maxDef = beamSnap.MaxDeflection;
double maxStress = beamSnap.MaxStress;
๐พ Binary Export (MPHY Format)โ
Unity-compatible binary format with magic bytes, header, and float layers:
using CSharpNumerics.Engines.Multiphysics.Export;
// Save 2D timeline
MultiphysicsBinaryExporter.Save(timeline, "heat_sim.mphy");
// Save beam snapshot
MultiphysicsBinaryExporter.Save(beamSnap, "beam.mphy");
// Read back
var header = MultiphysicsBinaryExporter.ReadHeader("heat_sim.mphy");
// header.Type, header.Nx, header.Ny, header.TimeStepCount, header.LayerCount
var data = MultiphysicsBinaryExporter.Read("heat_sim.mphy");
float[] frame0 = data.Layers[0]; // first time step, flattened
๐ JSON Exportโ
Zero-dependency JSON for web visualization:
// Direct result export
string json = MultiphysicsJsonExporter.ToJson(result);
MultiphysicsJsonExporter.Save(result, "simulation.json");
// Timeline export (all frames)
string timelineJson = MultiphysicsJsonExporter.ToJson(timeline);
// Single snapshot
string snapJson = MultiphysicsJsonExporter.ToJson(snapshot);
// Beam snapshot with metadata
string beamJson = MultiphysicsJsonExporter.ToJson(beamSnap);
๐ฒ Monte Carlo Integrationโ
Stochastic parameter variation for uncertainty quantification:
using CSharpNumerics.Engines.Multiphysics.MonteCarlo;
// Define parameter variation ranges
var variation = new ParameterVariation()
.ThermalConductivity(40, 60) // k โ [40, 60] W/(mยทK)
.BoundaryTemperature(90, 110) // T_top โ [90, 110] ยฐC
.SourceIntensity(8e5, 1.2e6); // S โ [8e5, 1.2e6] W/mยณ
// Build MC model from a baseline simulation
var baseline = SimulationType.Create(MultiphysicsType.HeatPlate)
.WithMaterial(EngineeringLibrary.Steel)
.WithGeometry(width: 0.1, height: 0.1, nx: 10, ny: 10)
.WithBoundary(top: 100.0, bottom: 0, left: 0, right: 0)
.WithInitialCondition(0.0);
var mc = new MultiphysicsMonteCarloModel(baseline, variation);
// Full batch: scenario matrix + per-iteration results
MultiphysicsScenarioResult mcResult = mc.RunBatch(iterations: 100, seed: 42);
Matrix scenarioMatrix = mcResult.ScenarioMatrix; // 100 ร (NxยทNy)
int features = mcResult.FeatureCount; // NxยทNy
// Per-cell distribution across scenarios
double[] dist = mcResult.GetFeatureDistribution(featureIndex: 50);
// Percentile maps
double[] p95 = mcResult.ComputePercentile(95);
double[] p50 = mcResult.ComputePercentile(50);
Compatible with MonteCarloSimulator for scalar summary:
using CSharpNumerics.Statistics.MonteCarlo;
var sim = new MonteCarloSimulator(seed: 42);
MonteCarloResult stats = sim.Run(mc, iterations: 1000);
double meanPeak = stats.Mean;
double p95scalar = stats.Percentile(95);
๐ง Surrogate Modelโ
Train a regression model on MC data for fast prediction without re-running the solver:
var surrogate = new SurrogateTrainer(mcResult, new Ridge());
surrogate.Train(r => r.MaxValue); // target = peak temperature
// Fast prediction without running the solver
VectorN predictions = surrogate.Predict(mcResult.ScenarioMatrix);
double single = surrogate.PredictOne(mcResult.GetScenarioVector(0));
๐งฉ Cluster Analysisโ
Identify representative scenario groups from Monte Carlo ensembles:
using CSharpNumerics.ML.Clustering.Algorithms;
using CSharpNumerics.ML.Clustering.Evaluators;
var analysis = MultiphysicsClusterAnalyzer
.For(mcResult)
.WithAlgorithm(new KMeans { K = 3 })
.TryClusterCounts(2, 5)
.WithEvaluator(new SilhouetteEvaluator())
.Run();
int bestK = analysis.BestClusterCount;
int dominant = analysis.DominantCluster; // most frequent group
int[] iterations = analysis.GetClusterIterations(dominant);
double[] meanOutput = analysis.GetClusterMeanOutput(dominant);