From Fast to Detailed: Smarter Engineering with Hybrid Models
Engineering teams face the same dilemma over and over:
The real answer? A hybrid workflow: start with system models, add detail only when it matters and keep everything connected.
Case Study: Cooling a Room with HVAC
We set out to answer a simple question: How should an HVAC system be configured to keep a room comfortable while minimizing energy use?
At the system level, Modelica (using the IBPSA library) makes it easy to capture control strategy and system behavior.
CloudImport[CloudObject["https://www.wolframcloud.com/obj/b9ea8653-2418-4297-a5e8-1f7bcefb18d6"]];model = SystemModel["RoomHeating"];
model[{"Diagram", Frame -> False}]sim = SystemModelSimulate[model, 120];
SystemModelPlot[sim, PlotRange -> All, TargetUnits -> {"Seconds", Automatic}]These quick insights already help test strategies without heavy computation. But they don’t tell us why one corner of the room feels stale or how inlet and outlet duct placement affects airflow.
Adding Detail with FEM
This is where finite element modeling (FEM) adds value.
By extending the workflow, we can see:
- Velocity fields – stream plots reveal how air jets circulate, pinpointing dead zones with poor mixing.
- Pressure distribution – contour plots expose back pressure at outlets, highlighting inefficiencies in fan energy use.
For this demonstration, I combined System Modeler (Modelica) for system behavior with the Wolfram Language FEM framework for spatial detail.
Needs["NDSolve`FEM`"]region = Polygon[{{0, 0}, {6, 0}, {6, 3}, {5.1, 3}, {4.9, 2.8}, {4.1, 2.8}, {3.9, 3}, {2.1, 3}, {1.9, 2.8}, {1.1, 2.8}, {0.9, 3}, {0, 3}}];
RegionPlot[region, AspectRatio -> Automatic]tpars = <|"ReynoldsNumber" -> 1000, "Material" -> Entity["Chemical", "Air"]|>;Get the boundary conditions from the model. In this case, we get the inlet velocity to the room from the system model.
data = Interpolation[{#, sim["inletVel.v"][#]}& /@ Range[0, 120, 1], InterpolationOrder -> 2];
Plot[data[t], {t, 0, 20}]bcs = {
DirichletCondition[
{u[t, x, y] == 0, v[t, x, y] == -data[t]}, 1.1 < x < 1.9 && y == 2.8],
DirichletCondition[{u[t, x, y] == 0, v[t, x, y] == 0}, 0 <= x <= 6 && y < 2.8],
DirichletCondition[{u[t, x, y] == 0, v[t, x, y] == 0}, 0 <= x <= 6 && y > 2.8],
DirichletCondition[p[t, x, y] == 0, 4.1 < x < 4.9 && y == 2.8]
};ic = {u[0, x, y] == 0, v[0, x, y] == 0, p[0, x, y] == 0};mesh = ToElementMesh[region, MaxCellMeasure -> 0.01];
mesh["Wireframe"]tvars = {{u[t, x, y], v[t, x, y], p[t, x, y]}, t, {x, y}};op = FluidFlowPDEComponent[tvars, tpars];
MatrixForm[op]tEnd = 20;
AbsoluteTiming[Monitor[{uVel, vVel, pressure} = NDSolveValue[{op == {0, 0, 0}, bcs, ic}, {u, v, p}, Element[{x, y}, mesh], {t, 0, tEnd}, Method -> {"PDEDiscretization" -> {"MethodOfLines", "SpatialDiscretization" -> {"FiniteElement", "InterpolationOrder" -> {u -> 2, v -> 2, p -> 1}}}},
EvaluationMonitor :> (monitor = Row[{"t = ", CForm[t]}])];, monitor]]frames = StreamPlot[
{uVel[#, x, y], vVel[#, x, y]},
{x, y}∈region,
PlotLabel -> "Time: " <> ToString[#] <> " s",
Frame -> False,
AspectRatio -> Automatic]& /@ Range[0, tEnd, 5];FrameListVideo[frames]framesPressure = ContourPlot[
pressure[#, x, y],
{x, y}∈region,
ColorFunction -> "BlueGreenYellow",
PlotLabel -> "Time: " <> ToString[#] <> " s",
Frame -> False, AspectRatio -> Automatic]& /@ Range[0, tEnd, 5];FrameListVideo[framesPressure]Instead of a single airflow number, we now see exactly how air moves, corner by corner, second by second.
Why This Matters
This integrated workflow eliminates two big pain points:
Final Thought
As engineers, our job isn’t to simulate everything—it’s to simulate what matters.
The future lies in modular, hybrid modeling pipelines like this one, where Modelica provides system context and FEM provides spatial detail.