Skip to content

Building physics: catalogue

Multi-zone airflow, heat and contaminants (CONTAM's physics; CONTAM .prj/.wth, EPW and Modelica Buildings Library readers). The airflow constitutive laws and drives a building uses come from the core library and are listed here too. How the pieces fit together: Building physics. Every application is set up the same way; see Using noodl.

Import from noodl.apps.building_physics and the modules named in each table. The full reference, with every parameter, is in the API reference.

Network attributes

What the builders, constitutive laws and drives read off net.add_node(...) and net.add_edge(...), by name. Defined in noodl.apps.building_physics.attributes.

Nodes

Attribute Meaning Read by
volume air volume of the zone (m3); 0 for a wall node thermal_layer, species_layer
T0 initial temperature (K), also the boundary temperature of a boundary node initial_state, initial_drivers, project_to_model
z_ref height of the node's reference pressure (m) Stack.from_network
position absolute (x, y, z) of the node's component (m), set by Component.flatten; for drawing only noodl.components
heat_capacity extra lumped heat capacity of the node (J/K); a wall node's capacity thermal_layer
in_building_volume 1 if the zone counts in the building's conditioned space, 0 if not (CONTAM's zone flag 16, set by the CONTAM reader); absent means 1 building_air_change_rate

Edges of kind airpath

Attribute Meaning Read by
z_path height of the opening (m) Stack.from_network
Cd discharge coefficient (dimensionless) orifice_law_from_edges
area opening area (m2) orifice_law_from_edges
name label of the edge in model.refs and in named results (optional) noodl.refs

Edges of kind airpath, to or from the ambient node

Attribute Meaning Read by
azimuth facade direction, in the unit of the theta_w driver (degrees from CONTAM) Wind.from_network
Cp constant wind pressure coefficient when the edge has no profile Wind.from_network
facade key of the building's inner_table('facades', ...); flatten writes the facade's azimuth Component.flatten
Ch wind speed modifier (dimensionless, 1 by default) Wind.from_network
profile number of the WindProfile to use; 0 means the constant Cp Wind.from_network

Edges of kind duct, a segment between two junctions (made by DuctNetwork)

Attribute Meaning Read by
length length of the segment (m) duct_laws_from_edges
diameter hydraulic diameter of the duct (m) duct_laws_from_edges
roughness roughness of the duct wall (m) duct_laws_from_edges
loss sum of the segment's dynamic loss coefficients (dimensionless) duct_laws_from_edges
area cross-section of the duct (m2); the circle of the diameter by default duct_laws_from_edges

Edges of kind duct_terminal, a junction to a zone (made by DuctNetwork)

Attribute Meaning Read by
area area of the duct at the terminal (m2) duct_laws_from_edges
loss loss coefficient of the terminal (dimensionless) duct_laws_from_edges
z_path height of the terminal, the junction's height (m) Stack.from_network
profile number of the WindProfile on a terminal in the ambient; absent or 0 for none duct_laws_from_edges, Wind.from_network
Cp constant wind pressure coefficient of a terminal in the ambient without a profile duct_laws_from_edges, Wind.from_network
Ch wind speed modifier (dimensionless, 1 by default) Wind.from_network
azimuth wall direction of a terminal with a profile (degrees from CONTAM) Wind.from_network

Edges of kind air_handler, an air handler's edges (made by add_air_handler)

Attribute Meaning Read by
role what the edge is: 0 supply point (plenum -> zone), 1 return point (zone -> plenum), 2 recirculation, 3 outdoor air, 4 exhaust air_handler_from_edges
design_flow design mass flow of a supply or return point (kg/s); 0 on the other edges air_handler_from_edges
outdoor_air_fraction on the recirculation edge: the outdoor-air fraction of the supply when no input sets it (dimensionless, 1 by default) air_handler_from_edges
min_outdoor_air on the recirculation edge: the minimum outdoor-air flow (kg/s, 0 by default) air_handler_from_edges

Edges of kind fan, a constant-mass-flow fan (made by the fan component)

Attribute Meaning Read by
flow mass flow delivered from source to target whatever the pressures (kg/s) fan_law_from_edges

Edges of kind wall

Attribute Meaning Read by
ua conductance zone -> wall or wall -> ambient (W/K) thermal_layer

Set-up

Build the model and its starting state and inputs.

Name What it does How to call it
build_model
noodl.apps.building_physics
Layers "air" (+ "thermal", + "species"), the density closure, one Model. build_model(net, *, air_laws, drives, ambient='ambient', thermal=True, species=0, density='ideal_gas', density_kwargs=None, coupling='pingpong', iterate_tol=None, iterate_max=20, thermal_scheme=None, species_scheme='implicit', flow_kinds=None, closures=(), flow_balanced=(), storage_free=(), controls=None, return_inputs=False)
initial_drivers
noodl.apps.building_physics
A complete driver set: zero boundary pressures, boundary temperatures from each boundary node's T0 attribute (T_REF where it has none), zero boundary mass fractions; then values, by name (see noodl.refs.drivers_from). initial_drivers(model, *, values=None)
initial_state
noodl.apps.building_physics
"<layer>.x" for every transport layer: temperatures from the node attribute T0 (interior order), mass fractions zero; then values. initial_state(model, *, values=None)
project_to_model
noodl.apps.building_physics
Air layer + species layer (no thermal layer: a .prj carries no thermal data), the initial state and the drivers for the project's ambient conditions (or ambient). project_to_model(project, *, ambient=None, species=True, scheme='implicit')

Network components

Descriptions of the physical objects a network is built from.

Name What it does How to call it
AirThroughFilter
noodl.apps.building_physics
The air passing a filter: what a flow-dependent filter's efficiency reads. AirThroughFilter(mass_flow, temperature, density, time=0.0)
AmbientConcentrations
noodl.apps.building_physics
Ambient (outdoor) mass fractions over time: t (s from 00:00 of start_day, a day of the year), x (len(t), K) (kg/kg) for the species named, in file order. AmbientConcentrations(t, x, species, start_day=1)
Actuator
noodl.apps.building_physics
Writes block block's output into driver key at positions index on its last axis (the whole driver when None); mode="add" adds it to those entries instead (a heat flow into "thermal.sources", W). Actuator(block, key, index=None, mode='set', name='')
Evaluation
noodl.apps.building_physics
What a block sees: the state the evaluation reads, the drivers, and the step length in s (None: an algebraic evaluation that advances nothing). Evaluation(state, drivers, dt)
Project
noodl.apps.building_physics
A CONTAM project as read by read_prj: the network, its airflow laws and drives, zones and their temperatures and volumes, species, initial mass fractions, sources and ambient conditions, the run control, and the schedules as inputs. project_to_model builds the model from it. Project(net, laws, drives, zones, ambient, paths, species, T_zone, zone_volumes, g, ambient_conditions, profiles, x0, sources, kinds, zone_nr_to_name, density_uses_zone_pressure=False, run_control=None, schedules=<factory>, inputs=<factory>, ambient_mass_fractions=None, multiplier_edges=<factory>, speed_edges=<factory>, constant_wind_pressure=<factory>, zone_flags=<factory>, source_path=None, species_records=<factory>, zone_reactions=<factory>, edge_filters=<factory>, junctions=<factory>, junction_x0=None, terminals=<factory>, segments=<factory>, schedule_units=<factory>, terminal_openings=0, flow_balanced=<factory>, storage_free=<factory>, closures=<factory>, air_handler_edges=<factory>, junction_nr_to_name=<factory>, control_nodes=<factory>, control_plan=None, occupants=<factory>)
RunControl
noodl.apps.building_physics
A project's run control: what to simulate and when. RunControl(transient_airflow, contaminants, integration, integration_weight, steady_day, steady_time, start_day, start_time, end_day, end_time, time_step, output_step, day_type, weather_file, ambient_file, zone_mass_change)
Species
noodl.apps.building_physics
One contaminant species: CONTAM's species record. Species(name, molar_mass=0.0, mean_diameter=0.0, effective_density=0.0, decay=0.0, diffusivity=0.0, default_mass_fraction=0.0, non_trace=False, uv_susceptibility=0.0, description='')
WallMass
noodl.apps.building_physics
A lumped wall node: capacity [J/K], conductances to the zone and to ambient [W/K]. WallMass(name, capacity, ua_zone, ua_ambient)
Weather
noodl.apps.building_physics
A weather series: time t (s from 00:00 of the file's start day start_day, a day of the year), ambient temperature Ta (K), barometric pressure Pb (Pa), wind speed Ws (m/s) and direction Wd (degrees from north, blowing from), and optionally the humidity ratio humidity_ratio (kg water / kg dry air) and each day's CONTAM day type day_types {day of year: 1-12}. at(t) interpolates; signal(name) and inputs(model) feed simulate. Weather(t, Ta, Pb, Ws, Wd, start_day=1, day_types=<factory>, humidity_ratio=None)
Zone
noodl.apps.building_physics
A room: its air volume (m3), initial temperature T0 (K), reference height z_ref (m) and an optional lumped wall (WallMass). Add it with add_zone. Zone(name, volume, T0=293.15, z_ref=0.0, wall=None)

Constitutive laws

The relation between the flow through an edge and the potential difference across it.

Name What it does How to call it
AffinityFan
noodl.elements
A fan curve run at another speed and air density, by the fan laws (ContamX's performance-curve fan, measured against ContamX 3.4.1.7). AffinityFan(curve, *, src, tgt, rho_ref, speed_key=None, off=0.0, multiplier=1.0, shutoff=None, rho_key='rho', kind=None)
AirDuct
noodl.elements
A duct segment, F (kg/s) from dp (Pa): Darcy-Weisbach with ContamX's friction factor. AirDuct(length, diameter, roughness, loss=0.0, *, area=None, src, tgt, rho_key='rho', temperature_key=None, kind='duct', learnable=False)
Conductance
noodl.elements
q = g dp, the linear branch law used for thermal conduction and passive exchange; g is in the flow unit per unit of potential (W/K for heat, kg/s per Pa for air). Conductance(g, *, kind='conduction', learnable=False)
CubicSplineFlow
noodl.elements
An airflow element given by measured points, fitted as ContamX fits them (SplineCurve): CONTAM's cubic-spline elements. CubicSplineFlow(x, y, *, independent='pressure', volume=False, multiplier=1.0, src, tgt, rho_key='rho', kind='airpath')
Damper
noodl.elements
Separate power-law coefficient and exponent for each flow direction: C_pos/n_pos for dp > 0 and C_neg/n_neg for dp < 0, each as in PowerLaw (C in the flow unit per Pa^n, n dimensionless). Damper(C_pos, n_pos, C_neg, n_neg, *, dp_transition=0.001, src=None, tgt=None, m=0.0, rho_ref=1.2041, rho_key='rho', kind='damper', learnable=False)
DoorCompartment
also MBLDoorCompartment
noodl.elements
One compartment of Buildings.Airflow.Multizone.DoorDiscretizedOpen per edge. DoorCompartment(*, src, tgt, medium, dA, CD=0.65, m=0.5, dp_turbulent=0.01, vZer=0.001, T_key='T', Xw_key='X_w', p_key='p_abs', kind='door', learnable=False)
DoorPortStream
also MBLDoorPortStream
noodl.elements
One of the two port streams of a whole discretised door, as ONE edge from side A to side B: direction="ab" returns port_a1.m_flow = mAB_flow, "ba" returns port_b2.m_flow = -port_a2.m_flow = -mBA_flow (kg/s, both positive from A to B), from the compartment law comp (:class:DoorCompartment or :class:OperableDoorCompartment, one door) at the compartment pressure differences dp + head(drivers) (:class:DoorCompartmentHead). The edge's own dp is the zones' potential difference phi_A - phi_B. DoorPortStream(comp, head, direction, kind)
Duct
noodl.elements
Colebrook duct: F(dp) with friction from the Colebrook equation, laminar below Re_t. Length L, diameter D and roughness eps (m); sum_C the dimensionless sum of dynamic loss coefficients; A (m2) defaults to the circular area; rho (kg/m3) and mu (Pa s); the flow is a mass flow in kg/s. Duct(L, D, eps, sum_C=0.0, *, A=None, Re_t=2000.0, rho=1.2041, mu=1.81625e-05, n_iter=4, kind='duct', learnable=False)
DuctTerminal
noodl.elements
A duct terminal, the zero-length duct between a junction and its zone, F (kg/s) from dp (Pa), as ContamX 3.4.1.7 computes it (measured). DuctTerminal(area, loss, *, src, tgt, rho_key='rho', temperature_key=None, opening_key=None, kind='duct_terminal', learnable=False)
FanCurve
noodl.elements
Cubic pressure-flow curve inverted for q given dp on [0, q_max]: coeffs are (a0, a1, a2, a3) of P(q) = a0 + a1 q + a2 q^2 + a3 q^3 in Pa for q in the flow unit, and q_max is the largest flow the curve covers, in that unit. FanCurve(coeffs, q_max, *, kind='airpath', learnable=False)
FixedFlow
noodl.elements
q = q0 regardless of dp (q0 in the flow unit); dflow = 0; linear_init = (q0, 0). FixedFlow(q0, *, kind='airpath', learnable=False)
FixedVolumeFlow
noodl.elements
F = Q rho_up whatever dp: a constant volume flow volume_flow Q (m3/s, one per edge) delivered as mass flow at the density of the node it draws from (src when Q >= 0, tgt otherwise), read from drivers[rho_key] (full node order). ContamX's constant-volume fan in a duct (measured). FixedVolumeFlow(volume_flow, *, src, tgt, rho_key='rho', kind='fan', learnable=False)
OpenDoor
also MBLDoorOpen
noodl.elements
Buildings.Airflow.Multizone.DoorOpen: one direction of the always-open door. OpenDoor(*, direction, src, tgt, medium, wOpe=0.9, hOpe=2.1, CD=0.65, m=0.5, dp_turbulent=0.01, T_key='T', kind='door', learnable=False)
OperableDoor
also MBLDoorOperable
noodl.elements
Buildings.Airflow.Multizone.DoorOperable: the door blended between open and closed by the opening signal y read from drivers[y_key]. OperableDoor(*, direction, src, tgt, medium, y_key, LClo, wOpe=0.9, hOpe=2.1, CDOpe=0.65, mOpe=0.5, mClo=0.65, dpCloRat=4.0, CDCloRat=1.0, dp_turbulent=0.01, T_key='T', kind='door', learnable=False)
OperableDoorCompartment
also MBLDoorCompartmentOperable
noodl.elements
One compartment of Buildings.Airflow.Multizone.DoorDiscretizedOperable per edge, blended between the open door and the closed crack by y = drivers[y_key]. OperableDoorCompartment(*, src, tgt, medium, y_key, AOpe, LClo, nCom, CDOpe=0.65, CDClo=0.65, CDCloRat=1.0, dpCloRat=4.0, mOpe=0.5, mClo=0.65, dp_turbulent=0.01, vZer=0.001, T_key='T', Xw_key='X_w', p_key='p_abs', kind='door', learnable=False)
PowerLaw
noodl.elements
q = C sign(dp) |dp|^n, laminar-blended or smoothly regularised near dp = 0. dp is in Pa; C is in the flow unit per Pa^n (kg/s for a mass-flow coefficient, m3/s for a volume-flow one), so the flow comes out in whatever unit C carries; n is dimensionless (0.5 for an orifice, towards 1 for a laminar crack). PowerLaw(C, n, *, dp_transition=0.001, regularised=None, kind='airpath', learnable=False)
PrescribedFlow
noodl.elements
F = drivers[key] whatever dp: a flow (kg/s, one value per edge of the kind) that a closure or an input prescribes every step, e.g. an air handler's supply flows. dtype is the layer's: linear_init, which has no dp to take it from, returns it. PrescribedFlow(key, *, kind, dtype=torch.float64)
Quadratic
noodl.elements
q = sign(dp) * 2|dp| / (sqrt(a^2 + 4 b |dp|) + a): the inverse of dp = a q + b |q| q, with dp in Pa, a in Pa per flow unit and b in Pa per flow unit squared. Quadratic(a, b, *, kind='airpath', learnable=False)
RegularizedPowerLaw
also MBLPowerLaw
noodl.elements
MBL's regularised power law, q = C sign(dp) \|dp\|^m, volume or mass form. RegularizedPowerLaw(C, m, *, dp_turbulent=0.1, form, rho_default, kind='airpath', learnable=False)
Scaled
noodl.elements
F = s F_law: law's flow times the dimensionless multiplier drivers[key]. Scaled(law, key)
SplineFlowTable
also MBLTable
noodl.elements
MBL's tabulated flow law: a monotone cubic Hermite spline through (dp, flow) knots, linearly extrapolated outside them; flow(dp) always returns MASS flow in kg/s. SplineFlowTable(dp_points, flow_points, *, form, rho_default=None, kind='airpath')
UpstreamDensityPowerLaw
noodl.elements
PowerLaw with C scaled by (rho_upstream / rho_ref) ** m, upstream by sign(dp). UpstreamDensityPowerLaw(C, n, *, src, tgt, m, rho_ref=1.2041, rho_key='rho', dp_transition=0.001, regularised=None, kind='airpath', learnable=False)

Drives

Terms added to an edge's potential difference (stack, wind, fans).

Name What it does How to call it
DoorCompartmentHead
noodl.elements
The hydrostatic part of DoorDiscretized.mo:64-66, as a noodl drive: rho_A hAg[i] - rho_B hBg[i] per compartment edge, shape (..., n_edges). DoorCompartmentHead(kind, *, src, tgt, hAg, hBg, medium, T_key='T', Xw_key='X_w', p_key='p_abs')
ConstantDrive
noodl.drives
A drive that reads a pre-computed, already batched value from drivers. ConstantDrive(kind, key)
Stack
noodl.drives
Hydrostatic stack term (CONTAM TN 1887r1 eq. 17 with eq. 64-65 at the path elevation). Stack(kind, *, src, tgt, z_path, z_ref, rho_key='rho', g=9.80665)
DuctStack
noodl.drives
Hydrostatic term of a duct segment between two junctions: the air column at the density of the junctions' MEAN TEMPERATURE (ContamX 3.4.1.7, measured), which at one pressure is the harmonic mean of the two node densities. DuctStack(kind, *, src, tgt, z_ref, rho_key='rho', g=9.80665)
WindProfile
noodl.drives
CONTAM wind pressure profile: Cp versus relative wind angle, periodic in the angle. WindProfile(angles_deg, cp, *, curve='linear')
Wind
noodl.drives
Wind pressure on envelope paths (CONTAM TN 1887r1 section 3.15). Drivers: rho_amb in kg/m3, V_met in m/s and theta_w in the unit the azimuth edge attribute uses (degrees for networks read from CONTAM). Wind(kind, *, sign, envelope, azimuth, ch, cp_const, profile_index, profiles=(), rho_key='rho_amb', speed_key='V_met', direction_key='theta_w')

Closures

Functions of the state that compute drivers each step (densities, flows, levels).

Name What it does How to call it
AirHandlerFlows
noodl.apps.building_physics
The closure that prescribes every flow of one air handler each step, by CONTAM's algorithm (see the module) from its design flows and its outdoor-air fraction. AirHandlerFlows(kind, *, roles, design_flows, outdoor_air_fraction=1.0, min_outdoor_air=0.0, name='')
ControlNetwork
noodl.apps.building_physics
Blocks evaluated in order and the actuators that write their outputs into drivers: a model closure that keeps the block outputs in the state. Blocks without a name are named "block <i>". ControlNetwork(blocks, actuators=(), *, sampled=True, name='controls')
DriverReading
noodl.apps.building_physics
factor * drivers[key][..., *index]. In a sampled network, the value the previous step used (ContamX's sensors see a scheduled temperature one step late); the first evaluation reads the current value. DriverReading(key, index, *, factor=1.0)
FunctionReading
noodl.apps.building_physics
fn(state, drivers), for any other reading (the Modelica route's zone temperature). key names the state key it reads, when it reads one (see ControlNetwork.reads_state). FunctionReading(fn, *, previous_step=False, key=None)
StateReading
noodl.apps.building_physics
factor * state[key][..., *index], or default while the state has no key (a flow or a pressure before the first airflow solve). StateReading(key, index, *, factor=1.0, default=0.0)
FilterTransmission
noodl.apps.building_physics
Closure: the species layer's per-step transmission "<layer>.transmission", (..., b_flow, K), for filters that include flow-dependent ones (UVGIFilter). FilterTransmission(net, flow_kinds, species, filters, *, airflow, layer='species', on_reverse=None)
IdealGasDensity
noodl.apps.building_physics
rho = P_ref / (R T); P_ref from drivers["P_ref"] if present (CONTAM section 3.18). IdealGasDensity(thermal, *, ambient_index, P_ref=101325.0, R=287.055)
LinearDensity
noodl.apps.building_physics
Boussinesq: rho = rho_0 (1 - (T - T_0) / T_0), the form the analytical natural- ventilation solutions (Li and Delsante 2001) assume. LinearDensity(thermal, *, ambient_index, rho_0=1.2041, T_0=293.15)
OccupantGeneration
noodl.apps.building_physics
Closure: the occupants' generation added to "<layer>.sources" (kg/s, full node order) of the species layer (a TransportLayer). OccupantGeneration(occupants, *, layer, species=None)
SpeciesSources
noodl.apps.building_physics
Closure: every source's generation summed into "<layer>.sources" (kg/s, full node order), added to what an earlier closure or a driver put there, and every removal coefficient into "<layer>.removal" (1/s, active interior order), each times its entry of "<layer>.source_multiplier", on its own species. SpeciesSources(sources, *, species, layer, names=None, heights=None, g=9.80665, pressure_key='air.phi')

Sources

Nodal sources and withdrawals.

Name What it does How to call it
BurstSource
noodl.apps.building_physics
mass delivered over the step(s) covering [t_burst, t_burst + dt). BurstSource(node, mass, t_burst, dt)
ConstantSource
noodl.apps.building_physics
S = G - R x (constant coefficient model): G kg/s, R kg/s per unit mass fraction (CONTAM's kg_air/s). Under SpeciesSources the term R x is implicit in the species step, as ContamX holds it; called directly the source is evaluated at the x given. ConstantSource(node, G, R=0.0)
CutoffSource
noodl.apps.building_physics
S = G (1 - x / x_cut) (cutoff concentration model): G kg/s, x_cut kg/kg. CutoffSource(node, G, x_cut)
DecayingSource
noodl.apps.building_physics
S = G0 exp(-(t - t0) / tau) for t >= t0, else 0. DecayingSource(node, G0, tau, t0=0.0)
DepositionRateSink
noodl.apps.building_physics
First-order removal at a deposition rate (CONTAM's deposition rate sink): R = k_d rho V, the rate k_d (1/s) on the zone's mass fraction; rate a float or tensor. It is also how to give a species a first-order decay in a zone. DepositionRateSink(node, rate)
DepositionVelocitySink
noodl.apps.building_physics
Deposition onto a surface at a deposition velocity (CONTAM's deposition velocity sink): R = v_d A rho, a first-order rate v_d A / V on the zone's mass fraction. velocity v_d in m/s, area A in m2 (floats or tensors). Its node needs a positive volume: the density is the zone's air mass over it. DepositionVelocitySink(node, velocity, area)
PeakEmissionSource
noodl.apps.building_physics
Emission rising to a peak and decaying (the NRCC peak model): S = a exp(-(ln(t / t_p) / b)^2 / 2), t the time since the start (s), nothing at t <= 0. peak a kg/s, width b, t_peak t_p s. PeakEmissionSource(node, peak, width, t_peak)
PowerLawDecaySource
noodl.apps.building_physics
Emission decaying as a power of time (the NRCC power-law model): S = a t_h^(-b) after t_p and a t_ph^(-b) before it, t_h being the time since the start in HOURS (measured: ContamX takes the power of the time in hours, although its file stores t_p in s). coefficient a kg/s, exponent b, t_p s (> 0: the power has no value at t = 0). PowerLawDecaySource(node, coefficient, exponent, t_p)
PressureDrivenSource
noodl.apps.building_physics
Entry driven by the pressure difference across the zone's envelope (CONTAM's pressure-driven model: radon, soil gas): S = G max(dP, 0)^n, dP = P_ambient - P_zone at the zone's reference height (Pa). coefficient G in kg/(s Pa^n), exponent n. Nothing enters while the zone is at or above the ambient pressure (measured against ContamX 3.4.1.7, which evaluates it at the step's own pressures). PressureDrivenSource(node, coefficient, exponent)

Files

Readers and writers of other tools' file formats.

Name What it does How to call it
read_ctm
noodl.apps.building_physics
A CONTAM .ctm ambient-concentration file as AmbientConcentrations. read_ctm(path)
read_epw
noodl.apps.building_physics
An EnergyPlus .epw weather file as a Weather series (ambient temperature, pressure, wind speed and direction), from start_day for n_hours hours. read_epw(path, *, start_day=1, n_hours=None)
read_modelica
noodl.apps.building_physics
Read one noodl-modelica/1 JSON file into (model, state, drivers). read_modelica(path, *, return_names=False, substeps=1, mass_storage=None)
read_prj
noodl.apps.building_physics
A CONTAM .prj project as a Project: zones, airflow paths and their elements, species and sources; project_to_model turns it into a model. read_prj(path)
read_wth
noodl.apps.building_physics
A CONTAM .wth weather file as a Weather series (ambient temperature, pressure, wind speed and direction, humidity ratio, and each day's day type). read_wth(path)
write_wth
noodl.apps.building_physics
WeatherFile ContamW 2.0, one row per sample, the columns read_wth reads. write_wth(weather, path, *, start_date='1/1')

Other classes

Supporting types.

Name What it does How to call it
ContamCompatibilityWarning
noodl.apps.building_physics
The reader follows ContamX where ContamX ignores data the file carries. ContamCompatibilityWarning(...)
AboveLimit
noodl.apps.building_physics
1 where u > limit (strict), else 0. AboveLimit(a, limit, *, name='')
Absolute
noodl.apps.building_physics
\|u\|. Absolute(a, *, name='')
Affine
noodl.apps.building_physics
(u - offset) * gain: a unit conversion or a gain. Affine(a, *, offset=0.0, gain=1.0, name='')
And
noodl.apps.building_physics
1 where both inputs are > 0. And(a, b, *, name='')
Average
noodl.apps.building_physics
The mean of its inputs. Average(*inputs, name='')
BelowLimit
noodl.apps.building_physics
1 where u < limit (strict), else 0. BelowLimit(a, limit, *, name='')
Block
noodl.apps.building_physics
One block. inputs are the positions, in the network's block list, of the blocks whose outputs it reads. step returns its output and its new memory, (..., memory_size). A sampled_only block keeps a previous step's value; a continuous_only block integrates in continuous time (rate). Block(...)
Complement
noodl.apps.building_physics
1 - u (CONTAM's "inverse" node; not a logical negation). Complement(a, *, name='')
Constant
noodl.apps.building_physics
A constant value; a block earlier in the order reads it before its first evaluation too (ContamX presets its constant nodes, measured). Constant(value, *, name='')
Divide
noodl.apps.building_physics
numerator / denominator; a zero denominator raises naming the block (ContamX stops on it too). Divide(numerator, denominator, *, name='')
DriverValue
noodl.apps.building_physics
drivers[key][..., index] at this evaluation: a value an Input writes (a schedule into "controls.input"), or a driver such as "time". DriverValue(key, index=None, *, name='')
Excess
noodl.apps.building_physics
max(0, u - limit): how far the input is above the limit. Excess(a, limit, *, name='')
ExclusiveOr
noodl.apps.building_physics
1 where exactly one input is > 0. ExclusiveOr(a, b, *, name='')
Hysteresis
noodl.apps.building_physics
ContamX's hysteresis: with x the input clipped to 0..1, the output follows the rising line max(0, (x - slack) slope) and the falling line min(1, x slope) and stays between them: y = min(falling, max(rising, y_prev)). The previous output starts at initial * slope. ContamX's files give the slope; the default 1 / (1 - slack), the slope that makes the falling line reach 1 where the rising line does, is a convenience for networks written in Python. Hysteresis(a, *, slack, slope=None, initial=0.0, name='')
Identity
noodl.apps.building_physics
Passes its input on (a signal split, a reported value). Identity(a, *, name='')
IncrementalPI
noodl.apps.building_physics
ContamX's PI controller on an error e, evaluated once per step of length dt (s). It is incremental: the output changes by a proportional part on the change of the error and an integral part on the trapezoid of the error over the step, y = clip(y_prev + kp (e - e_prev) + ki dt (e + e_prev) / 2, 0, 1), ki in 1/s. The stored previous output is the clipped one (which limits windup); the first evaluation updates from initial_output and initial_error. IncrementalPI(a, *, kp, ki, initial_output=0.0, initial_error=0.0, name='')
LimitedPID
noodl.apps.building_physics
A P or PI controller with a limited output and anti-windup, in continuous time, as MSL 4.1.0's LimPID without its derivative part (Modelica/Blocks/Continuous.mo, LimPID): v = gain (setpoint_weight u_s - u_m + xi), y = clamp(v, y_min, y_max) and, with an integral_time Ti (s), d xi/dt = (u_s - u_m + (y - v)/(gain windup_ratio)) / Ti, xi(0) = initial_integral; without one, xi = 0 (a P controller). y_min defaults to -y_max. Its switching values, (v - y_max)/w and (v - y_min)/w with w = y_max - y_min, cross zero at the limiter's corners. LimitedPID(setpoint, measurement, *, gain, y_max, y_min=None, integral_time=None, setpoint_weight=1.0, windup_ratio=0.9, initial_integral=0.0, name='')
LowerBandSwitch
noodl.apps.building_physics
On (1) once u > limit + band, off (0) once u < limit, unchanged in between; off at the start (CONTAM's lower band switch). LowerBandSwitch(a, limit, *, band, name='')
Maximum
noodl.apps.building_physics
The largest of its inputs. Maximum(*inputs, name='')
Minimum
noodl.apps.building_physics
The smallest of its inputs. Minimum(*inputs, name='')
Or
noodl.apps.building_physics
1 where either input is > 0. Or(a, b, *, name='')
Positive
noodl.apps.building_physics
1 where u > 0, else 0. Positive(a, *, name='')
Product
noodl.apps.building_physics
The product of its inputs. Product(*inputs, name='')
ProportionalControl
noodl.apps.building_physics
clip(gain u, 0, 1) (CONTAM's proportional controller: the input is an error). ProportionalControl(a, *, gain, name='')
Sensor
noodl.apps.building_physics
A reading converted to x = (value - offset) * scale and, with a time_constant (s), lagged: y = x + (y_prev - x) exp(-dt / time_constant), y = x at the first evaluation. A reading with previous_step gives, in a sampled network, the value the previous evaluation's step used (after stores it once the actuators have written). Sensor(reading, *, offset=0.0, scale=1.0, time_constant=0.0, name='')
Shortfall
noodl.apps.building_physics
max(0, limit - u): how far the input is below the limit. Shortfall(a, limit, *, name='')
Sum
noodl.apps.building_physics
sum_i w_i u_i (weights default 1): CONTAM's add, subtract and sum nodes, and Modelica's Add, Add3, Sum, MultiSum and Feedback. Sum(*inputs, weights=None, name='')
UpperBandSwitch
noodl.apps.building_physics
On (1) once u < limit - band, off (0) once u > limit, unchanged in between; off at the start (CONTAM's upper band switch). UpperBandSwitch(a, limit, *, band, name='')
Calendar
noodl.apps.building_physics
Where t = 0 sits in the year, and the CONTAM day type (1-12) of every day. Calendar(*, start_day=1, start_time=0.0, start_day_type=1, day_types=None)
ConstantEfficiencyFilter
noodl.apps.building_physics
Removes a fixed fraction of each species it lists, nothing of the others. ConstantEfficiencyFilter(efficiencies, *, name='')
DaySchedule
noodl.apps.building_physics
One day of values at times of day: CONTAM's day schedule. DaySchedule(times, values, *, shape='rectangular', name='')
DuctNetwork
noodl.apps.building_physics
A duct system, described first and then added to a Network or a Component. DuctNetwork(*, kind='duct', terminal_kind='duct_terminal', ambient='ambient')
FilterSeries
noodl.apps.building_physics
Filters in series (CONTAM's super filter): the air passes each stage in turn, so the pass fraction is the product of the stages' pass fractions. FilterSeries(stages, *, name='')
Input
noodl.apps.building_physics
signal's value written into drivers[key] at each step. Input(signal, key, *, index=None, at=None, name='')
KineticReaction
noodl.apps.building_physics
First-order reactions between species: dx_p/dt += k[p, r] x_r, x in kg/kg. KineticReaction(coefficients, *, name='')
Occupancy
noodl.apps.building_physics
An occupant's movements: one OccupancyDay per CONTAM day type (1-12). Occupancy(day_schedules, day_types=(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1), *, name='')
OccupancyDay
noodl.apps.building_physics
An occupant's zone over one day: zones[i] from times[i] (s after midnight) until the next time. times run from 0 (00:00:00) to 86400 (24:00:00), strictly increasing. A zone is a node name, or None for out of the building. ContamX needs the 24:00:00 point too: without it the run stops. OccupancyDay(times, zones, *, name='')
Occupant
noodl.apps.building_physics
A person (or multiplier people) following an Occupancy, generating each species in generation (kg/s per person; quanta/s for a quanta species) while in the building. Occupant(occupancy, *, generation=None, multiplier=1.0, schedules=None, name='occupant', description='')
ParticleFilter
noodl.apps.building_physics
Efficiency as a function of particle diameter (CONTAM's simple particle filter, e.g. a MERV curve); zero for a gas. ParticleFilter(diameters, efficiencies, *, name='')
UVGIFilter
noodl.apps.building_physics
Ultraviolet germicidal irradiation (CONTAM's Penn State UVGI filter element, TN 1887r1 equations uv-1 to uv-9): each species is inactivated with the single-pass efficiency UVGIFilter(*, design_survival, design_velocity, design_mass_flow, design_rate_constant, output_coefficients, age_coefficients=None, name='')
Presence
noodl.apps.building_physics
An occupant's location as a signal over net's nodes (full node order, float64, dimensionless). Presence(occupancy, net, *, name='')
Signal
noodl.apps.building_physics
A time-varying input: value(t, calendar), a float64 tensor at t s since the simulation start (left limit at a jump for t > 0, right value at t == 0), and breakpoints(lo, hi, calendar), the times in (lo, hi) where it jumps or kinks. Signal(...)
TimeSeries
noodl.apps.building_physics
Values at times since the simulation start (s): a measured or computed input. TimeSeries(times, values, *, interpolation='linear', name='')
WeekSchedule
noodl.apps.building_physics
CONTAM's week schedule: one DaySchedule per day type (1-12). WeekSchedule(day_schedules, day_types=(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1), *, name='')
AirMedium
also MBLMedium
noodl.elements
An air medium's default state and the two densities its flow elements need. AirMedium(name, p_default, T_default, X_default, rho_default, has_moisture, _buoyancy, _density=None, _cp=None)
LawContext
noodl.elements
What a dynamic or implicit constitutive law needs besides its own edge values. LawContext(mode, dt=None, t=None, integration='trapezoidal', dp_prev=None, q_prev=None, omega=None)
SplineCurve
noodl.elements
A measured curve y(x) fitted as ContamX fits it (see the module docstring). SplineCurve(x, y, *, name='')

Functions

Calculations and helpers.

Name What it does How to call it
add_air_handler
noodl.apps.building_physics
Add an air handler to target (a Network or a Component): its two plenums (volumes in m3, temperature T0 in K) and its edges of kind. supplies maps each supplied zone to its design flow (kg/s), returns each zone it draws from. outdoor_air_fraction and min_outdoor_air (kg/s) are the defaults the closure uses when no input sets them. In a component the outdoor air and the exhaust reach the shared ambient through outer(ambient). Give each air handler of a building its own kind. add_air_handler(target, *, supplies, returns, kind='air_handler', supply_plenum='ahs_supply', return_plenum='ahs_return', outdoor_air_fraction=1.0, min_outdoor_air=0.0, supply_volume=0.0, return_volume=0.0, T0=293.15, ambient='ambient')
add_large_opening
noodl.apps.building_physics
CONTAM's two-opening doorway (DR_PL2, TN 1887r1 eq. 69-70): two orifices of area W H / 2 at z_mid -/+ 2H/9, both oriented a -> b. Exact for a mid-height neutral plane. Returns the two edge keys (low, high). Edge attributes: z_path, area, Cd, opening=1. names gives the two edges a name (as component factories do). net may be a Network or a noodl.components.Component. add_large_opening(net, a, b, *, H, W, z_mid, Cd=0.78, kind='airpath', names=None)
add_zone
noodl.apps.building_physics
Add the zone's air node (attributes volume, T0, z_ref, heat_capacity=0) and, if it has a wall, the wall node (heat_capacity) with 'wall' edges zone -> wall -> ambient (ua). add_zone(net, zone, *, ambient='ambient')
air_handler
noodl.apps.building_physics
CONTAM's simple air-handling system: plenums supply and return (volumes in m3, temperature T0 in K), supply points to the ports supplies[i] and return points from returns[j] (design flows in kg/s), the recirculation, and the outdoor air and exhaust to the shared ambient; outdoor_air_fraction and min_outdoor_air (kg/s) as in add_air_handler. After flatten, air_handler_from_edges(net, kind) gives its law and closure, and air_handler_plenums(net, kind) the nodes for build_model(flow_balanced=...). Zero plenum volumes (CONTAM's default) also need storage_free=... and an implicit heat layer; the 1 m3 default here needs neither. Give each air handler of a building its own kind. Ports: supplies[i], returns[j]. air_handler(name, *, supplies, returns, outdoor_air_fraction=1.0, min_outdoor_air=0.0, supply_volume=1.0, return_volume=1.0, T0=293.15, kind='air_handler')
air_handler_from_edges
noodl.apps.building_physics
The law and the closure of the air handler whose edges are of kind (as add_air_handler, or the .prj reader, made them). air_handler_from_edges(net, kind)
air_handler_plenums
noodl.apps.building_physics
(supply plenum, return plenum) of the air handler of kind: the recirculation edge's target and source. air_handler_plenums(net, kind)
apply_inputs
noodl.apps.building_physics
New drivers with every input's value at t written in (inputs bound first). apply_inputs(drivers, inputs, t, calendar, *, t_start=None)
assemble_sources
noodl.apps.building_physics
Sum of every source's explicit rate source(t, x_full) (kg/s), full-node order, shape like x_full; there is no separate node-count argument. assemble_sources(sources, t, x_full)
bind_inputs
noodl.apps.building_physics
inputs with every at resolved to positions through model.refs, checked for overlap: two inputs may not write the same entry of the same driver (raises naming both). A key given with at must be one the model declares (model.refs.field(key)). bind_inputs(model, inputs)
building_air_change_rate
noodl.apps.building_physics
The building air change rate (1/h), as ContamX writes it to its .ach file: every flow entering the conditioned space from outside it, from the ambient or from a zone left out of it, over density (1.2041 kg/m3, ContamX's) times the space's volume (the nodes' "volume"), times 3600. Shape: the history's leading dimensions. building_air_change_rate(history, model, *, conditioned=None, layer='species', density=1.2041)
crack
noodl.apps.building_physics
A small orifice (m2, m). Interior: between terminals a and b. Exterior: from the shared ambient to a, carrying the wind attributes Wind.from_network reads; give the facade direction as azimuth (degrees from north) or as facade, a key of the building's inner_table("facades", ...). Edge: path. crack(name, *, area, z_path, Cd=0.6, exterior=False, azimuth=None, facade=None, Cp=None, Ch=None, profile=None)
cumulative_exposure
noodl.apps.building_physics
The time integral of occupant_exposure from the start: shape (N, ..., K), row 0 zero, in the exposure's unit times s (kg/kg s for mass fractions). cumulative_exposure(exposure, times)
duct_laws_from_edges
noodl.apps.building_physics
The laws and drives of the duct edges DuctNetwork.add_to made: an AirDuct and a DuctStack for the segments, a DuctTerminal, a Stack (at the junction's height) and, when a terminal has a wind profile or a nonzero Cp, a Wind for the terminals (profiles numbers the WindProfiles from 1, as for Wind.from_network; an edge with neither feels no wind). Returns (laws, drives); a kind the network lacks contributes nothing. temperature_key names the full-node temperature driver for the viscosity (the viscosity at 293.15 K when None); opening_key the terminals' opening driver. duct_laws_from_edges(net, *, kind='duct', terminal_kind='duct_terminal', temperature_key=None, opening_key=None, g=9.80665, ambient='ambient', profiles=())
door
noodl.apps.building_physics
A doorway between two zones (CONTAM's two-opening DR_PL2): terminals a, b and edges low, high. z_mid (m) is relative to the door's placement. Ports: a, b. door(name, *, H, W, z_mid, Cd=0.78)
duct
noodl.apps.building_physics
A duct run with a terminal at each end: junctions ends[0] at height z_a and ends[1] at z_b (m, relative to the placement), each holding half the duct's air, joined by the segment segment (length, hydraulic diameter and roughness in m, loss its dynamic loss coefficients), with terminals terminal_a and terminal_b (area terminal_area in m2, the duct's own by default; loss coefficient terminal_loss). Interior: from port a to port b. Exterior: terminal_a opens to the shared ambient and takes the wind from the WindProfile numbered profile (with azimuth or facade, and Ch); azimuth, facade or Ch without a profile are refused (a constant Cp is offered by DuctNetwork.terminal). Invalid lengths, diameters or losses are refused naming the duct. Build it with duct_laws_from_edges. Ports: a, b (only b when exterior). duct(name, *, length, diameter, roughness=9e-05, loss=0.0, terminal_area=None, terminal_loss=0.125, z_a=0.0, z_b=0.0, T0=293.15, exterior=False, azimuth=None, facade=None, Ch=None, profile=None)
edge_flow
noodl.apps.building_physics
The mass flow (kg/s) of edge position of kind, along the edge. edge_flow(kind, position=0)
edge_pressure_difference
noodl.apps.building_physics
The driving pressure difference (Pa) of an edge, from-node minus to-node with the edge's drives (KirchhoffLayer.dp), which simulate records as "air.dp". edge_pressure_difference(kind, position=0)
fan
noodl.apps.building_physics
A fan delivering the mass flow flow (kg/s, finite, not negative) whatever the pressures and the height (CONTAM's constant-mass-flow fan): from terminal a to b, or with exterior="supply" from the shared ambient into a, or with exterior="exhaust" from a to the ambient. Edge: fan, kind "fan"; build it with fan_law_from_edges. Ports: a, b (only a when exterior). fan(name, *, flow, exterior=None)
fan_law_from_edges
noodl.apps.building_physics
One FixedFlow over every edge of kind, each edge's mass flow from its flow attribute (kg/s), as the fan component sets it. fan_law_from_edges(net, kind='fan')
filter_transmission
noodl.apps.building_physics
The species layer's transmission, (b_flow, K): 1 - efficiency on each filtered edge, 1 elsewhere. Rows follow the layer's flow order: the edges of each kind in flow_kinds, kind by kind, each kind in network order; pass the layer's own flow_kinds. An edge is named by the (source, target, key) add_edge returned. A filter whose efficiency depends on the air passing (UVGIFilter, or a FilterSeries holding one) has no fixed transmission and is refused by name: FilterTransmission writes it every step. filter_transmission(net, flow_kinds, species, filters)
infection_probability
noodl.apps.building_physics
Wells-Riley: P = 1 - exp(-dose), the dose in quanta (accurate for small doses); dimensionless. infection_probability(dose)
inhaled_dose
noodl.apps.building_physics
What was breathed in: breathing rate (m3/s) x air density (kg/m3) x the time integral of the mass fraction (kg/kg s; cumulative_exposure, not the per-step occupant_exposure), in kg, or quanta for a quanta species. The density defaults to 1.2041 kg/m3, CONTAM's standard air density; pass the density the species layer's mass fractions were computed with (its capacity over the zone volume) to turn them back into the inhaled amount exactly. inhaled_dose(integrated_exposure, breathing_rate, *, density=1.2041)
local_mean_age
noodl.apps.building_physics
The local mean age of air of every interior row of the layer (s; ContamX's .age file gives it in hours): the steady age equation sum_in q tau_upstream - sum_out q tau + M = 0 at every row, tau = 0 at the boundary (the outdoor air), with M the row's air mass: a transport problem with a uniform source of one second per second in every kilogram of air (Sandberg's definition; the method of AIVC Technical Note 34, which ContamX cites). Shape (..., n_i) over the history's leading dimensions, in the layer's interior order (model.transport[layer].interior_idx); evaluated at every row, with that row's flows and air mass. local_mean_age(history, model, *, layer='species', capacity=None)
mass_orifice
noodl.apps.building_physics
F = Cd A sqrt(2 rho dp) [kg/s]: PowerLaw(C = Cd A sqrt(2 rho), n = 0.5). mass_orifice(Cd, A, *, rho=1.2041, dp_transition=0.001, kind='airpath', learnable=False)
node_mass_fraction
noodl.apps.building_physics
A zone's mass fraction (kg/kg) of species (position or name) in the species layer's state, read through the layer's own layout ((..., n_interior) for one species, (..., n_interior, K) for more). node_mass_fraction(node, species=0)
node_pressure
noodl.apps.building_physics
A node's gauge pressure (Pa): the air layer's potential. node_pressure(node)
node_temperature
noodl.apps.building_physics
A node's temperature (K). With a thermal layer: its state at an interior node, its boundary temperature (the driver "thermal.x_boundary") at a boundary node. Without one: the prescribed temperature "T", which the model must declare. A driver is read one step late in a sampled network. node_temperature(node)
occupant_exposure
noodl.apps.building_physics
The occupant's exposure over each step of history (from simulate): the step mean of the occupied zone's "<layer>.x", linearly interpolated within the step and split at every move; zero out of the building. Shape (N - 1, ..., K), in the layer's unit (kg/kg for mass fractions), row k for the step from history["time"][k] to [k + 1]. occupant_exposure(history, occupant, model, *, calendar, layer='species')
species_history
noodl.apps.building_physics
x (a history "<layer>.x" from simulate) in the stacked layout (N, ..., n_i, K), and whether it came in reduced. species_history(x, layer, name='x')
occupant_inputs
noodl.apps.building_physics
The inputs simulate needs for the occupants that generate something: each one's presence on "presence.<name>" (a step mean, the fraction of the step at each node of net) and, when it has schedules, the schedule values (1 for an unscheduled species, in the order of its generation) on "generation_multiplier.<name>". Occupants whose names repeat are refused: their drivers would collide. occupant_inputs(occupants, net)
orifice_law_from_edges
noodl.apps.building_physics
One PowerLaw over every edge of kind, C_e = Cd_e A_e sqrt(2 rho), from the edge attributes Cd and area (KeyError names a missing one). orifice_law_from_edges(net, kind, *, rho=1.2041, dp_transition=0.001, learnable=False)
project_calendar
noodl.apps.building_physics
The calendar of a project's transient run: its run control's start day, time and day type, and the day types of its weather file when it has one. weather (from read_wth) overrides that file; by default the project's own is read, next to its .prj, and a weather file that cannot be found there is refused by name. simulate_project runs on this calendar; pass it where a function places times in the year (occupant_exposure). project_calendar(project, weather=None)
reaction_kinetics
noodl.apps.building_physics
The species layer's kinetics, (n_i, K, K): each node's reaction matrix in the layer's active interior order (active_interior(net, flow_kinds, [ambient])), zero where a node has none. Pass the same ambient and flow_kinds as to species_layer. A node that is not an active interior node of that layer is refused by name: the layer holds no concentration there to react. reaction_kinetics(net, *, species, reactions, ambient='ambient', flow_kinds=('airpath',))
room
noodl.apps.building_physics
A zone: air node air (m3, K, m) and, with wall, a lumped wall node conducting to the shared ambient. Port: air. room(name, *, volume, T0=293.15, z_ref=0.0, wall=None)
run_project
noodl.apps.building_physics
Run the project AS WRITTEN: its own run control (start, end, time step), weather and ambient-concentration files and schedules; no ambient value is set through the API. Returns the initial state and every step: time (N,) s since the start, flow (N, n_paths) kg/s, mf (N, n_zones, K), terminal_flow (N, n_terminals): flow out of each duct terminal into its zone, kg/s, in junction-number order, dt, and the path and zone numbering of run_transient, and control (N, n_reports): the project's named pass (split) nodes, in node order, as getOutputControlValue reports them: the value the network computed for that step ((N, 0) when the project has none). airflow_convergence as in run_steady. When the project's output flags ask for them, the occupants' exposure, the age of air and the building air change rate ContamX writes are returned too, one row per step (_result_files: exposure, age, air_change_rate, building_volume); a key is absent when its file is. run_project(prj_path, *, airflow_convergence=None)
run_steady
noodl.apps.building_physics
Path net flows [kg/s], zone mass fractions and duct terminal flows (terminal_flow, kg/s, out of each terminal into its zone, in junction-number order) after the initial steady-state solve. run_steady(prj_path, *, ambient, airflow_convergence=None)
run_transient
noodl.apps.building_physics
steps steps of the project's own time step; results stacked with the initial state first: flow (steps+1, n_paths), mf (steps+1, n_zones, K), terminal_flow (steps+1, n_terminals). airflow_convergence as in run_steady. run_transient(prj_path, *, steps, ambient, airflow_convergence=None)
scatter
noodl.apps.building_physics
A new tensor: base with value written at index on the last axis (all of it when index is None), batch dimensions broadcast. Differentiable in value; base is never modified. The result has base's floating dtype (so a model moved to float32 by Model.to stays float32); with base None, index must be None and value is returned as float64. scatter(base, index, value)
shaft
noodl.apps.building_physics
A tall space (stairwell, atrium, lift shaft) as a stack of zones, one per level, joined by openings (how stairwells are usually represented in multizone models): node levels[i] at z_ref = i * level_height, joined by an opening slab[i] (area m2) at the slab between levels i and i+1. Ports: levels[0], levels[1], ... shaft(name, *, levels, level_height, volume, area, T0=293.15, Cd=0.6)
simulate
noodl.apps.building_physics
Time histories at times (s since the start, increasing, times[0] == 0; the calendar places that start in the year). simulate(model, state, drivers, times, *, inputs=(), calendar=None, air_mass_change=None, land_on_breakpoints=True, zone_pressure_rtol=1e-12, zone_pressure_max_iter=10, diagnostics=None, **step_kwargs)
simulate_project
noodl.apps.building_physics
Run a .prj project the way ContamX does: its run control (start, end, time step), its schedules, its weather and ambient-concentration files (resolved next to the .prj), with the time step fixed (no step split at a schedule's jump, as in ContamX) and the zone air-mass change when the run control asks for it. inputs are extra inputs of your own. Returns simulate's histories plus "path_flow" (N, n_paths) in path-number order and, for a project with a duct network, "terminal_flow" (N, n_terminals): the flow out of each duct terminal into its zone, in junction-number order, and, for a project with control references, "control" (N, n_reports): the named report nodes' values in node order (ContamX's control outputs; "controls.outputs" holds every block's), and, for a project with occupants, "exposure" (N - 1, ..., n_occupants, K): each occupant's exposure over each step, in project.occupants order, as ContamX's exposure file (occupant_exposure on the run's project_calendar; zero while out of the building). The species histories carry the zones' rows first, then the junctions' (project.zone_mass_fractions selects the zones). diagnostics is simulate's run-level dict, not Model.step's: "airflow_retries", the steps whose airflow solve was retried from the linear initial guess (none for a steady run). simulate_project(project, *, inputs=(), diagnostics=None, **step_kwargs)
sources_from_project
noodl.apps.building_physics
The project's PrjSource records (from read_prj) -> source objects. sources_from_project(project, *, dt=60.0)
species_layer
noodl.apps.building_physics
Species as mass fractions with zone air mass rho V as capacity (CONTAM convention). species_layer(net, *, ambient='ambient', name='species', flow_kinds=('airpath',), rho=1.2041, n_species=1, scheme='implicit', species_names=None, transmission=None, kinetics=None, storage_free=())
thermal_layer
noodl.apps.building_physics
The heat layer: capacity rho c_p V + heat_capacity per active interior node, carrier c_p on the flow kinds, conduction UA on conduction_kind edges (if any exist). thermal_layer(net, *, ambient='ambient', name='thermal', flow_kinds=('airpath',), conduction_kind='wall', c_p=1005.0, rho=1.2041, scheme=None, fixed_temperature=(), storage_free=())
window
noodl.apps.building_physics
An opening in the envelope: a crack that is exterior unless told otherwise. window(name, *, area, z_path, Cd=0.6, exterior=True, azimuth=None, facade=None, Cp=None, Ch=None, profile=None)
wells_riley
noodl.apps.building_physics
The classic steady single-zone form P = 1 - exp(-I q p t / Q): infectors I, quanta_rate q (quanta/s per infector), breathing_rate p (m3/s), ventilation_rate Q (m3/s of clean air), duration t (s); dimensionless. wells_riley(infectors, quanta_rate, breathing_rate, ventilation_rate, duration)
zone_air_change_rate
noodl.apps.building_physics
Each zone's air change rate (1/h): all the air flowing into it, from the ambient and from other zones alike, over its air mass, times 3600; the inverse of its nominal time constant, not an outdoor air change rate. An addition of noodl: ContamX writes no zone rate. Returns (rate, zones): rate (..., n_z) over the history's leading dimensions, and zones the node names of its last axis, the layer's interior rows in their order without the storage-free ones (a zero-volume plenum holds no air to change). A duct junction with volume is a row like a zone. zone_air_change_rate(history, model, *, layer='species', capacity=None)
Orifice
noodl.elements
PowerLaw(C = Cd * A * sqrt(2 / rho), n = 0.5): the sharp-edged orifice equation, as a VOLUME flow in m3/s from Cd (dimensionless discharge coefficient), A (m2) and rho (kg/m3). Orifice(Cd, A, *, rho=1.2, dp_transition=0.001, regularised=None, kind='airpath', learnable=False)
air_viscosity
noodl.elements
Dynamic viscosity of air (Pa s) at temperature T (K): ContamX's linear law, 1.81625e-5 + 4.92859e-8 (T - 293.15), measured between 253 and 333 K. air_viscosity(T)
discretized_door
also mbl_discretized_door
noodl.elements
One DoorDiscretizedOpen as nCom edges of kind: the compartment element and its head drive. Defaults are MBL's (DoorDiscretized.mo:6-11, TwoWayFlowElementBuoyancy.mo:6-14, DoorDiscretizedOpen.mo:6, TwoWayFlowElement.mo:19). dA = wOpe hOpe / nCom on every edge (DoorDiscretizedOpen.mo:23, DoorDiscretized.mo:60). discretized_door(*, src, tgt, medium, nCom=10, wOpe=0.9, hOpe=2.1, hA=1.35, hB=1.35, CD=0.65, m=0.5, dp_turbulent=0.01, vZer=0.001, kind='door', T_key='T', Xw_key='X_w', p_key='p_abs')
discretized_operable_door
also mbl_discretized_operable_door
noodl.elements
One DoorDiscretizedOperable as nCom edges of kind; see :func:discretized_door. AOpe = wOpe hOpe (DoorDiscretizedOperable.mo:35); LClo has no MBL default. discretized_operable_door(*, src, tgt, medium, y_key, LClo, nCom=10, wOpe=0.9, hOpe=2.1, hA=1.35, hB=1.35, CDOpe=0.65, CDClo=0.65, CDCloRat=1.0, dpCloRat=4.0, mOpe=0.5, mClo=0.65, dp_turbulent=0.01, vZer=0.001, kind='door', T_key='T', Xw_key='X_w', p_key='p_abs')
effective_leakage_area
also mbl_ela
noodl.elements
EffectiveAirLeakageArea.mo:3-5,7-14,16: C = L * CDRat * sqrt(2 / rho_default) * dpRat ** (0.5 - m), m = 0.65 by default (:4), dpRat = 4 Pa (:7-9), CDRat = 1 (:11-14). effective_leakage_area(L, dpRat=4.0, CDRat=1.0, m=0.65, dp_turbulent=0.1, *, rho_default, kind='airpath', learnable=False)
medium
noodl.elements
Look up one of the three air media, by physical name or by Modelica class path. medium(name)
open_door_pair
also mbl_door_pair
noodl.elements
Both edges of one DoorOpen (or of several, with per-door src/tgt): the "ab" edge (path 1) with kind f"{kind}_ab" and the "ba" edge (path 2) with kind f"{kind}_ba", sharing every keyword argument of :class:OpenDoor. open_door_pair(*, kind='door', **kwargs)
operable_door_pair
also mbl_operable_door_pair
noodl.elements
Both edges of one DoorOperable; see :func:open_door_pair. operable_door_pair(*, kind='door', **kwargs)
power_law_coefficient
also mbl_coefficient
noodl.elements
Coefficient_V_flow.mo/Coefficient_m_flow.mo: C/k and m given directly, with no further formula -- a direct pass-through to :class:RegularizedPowerLaw. power_law_coefficient(C, m, form, dp_turbulent=0.1, *, rho_default, kind='airpath', learnable=False)
power_law_from_point
also mbl_point
noodl.elements
Point_m_flow.mo:4-5: k = mMea_flow_nominal / dpMea_nominal ** m, fit from one measured (dp, m_flow) pair; m stays a free parameter (default 0.5, inherited from Coefficient_m_flow's PowerLawResistanceParameters(m=0.5), Coefficient_m_flow.mo:27-28). power_law_from_point(dpMea, mMea_flow, m=0.5, dp_turbulent=0.1, *, rho_default, kind='airpath', learnable=False)
power_law_from_points
also mbl_points
noodl.elements
Points_m_flow.mo:4-6,15-16: the flow exponent itself is derived from two measured (dp, m_flow) pairs, m = (ln(mMea[0]) - ln(mMea[1])) / (ln(dpMea[0]) - ln(dpMea[1])), then k = mMea[0] / dpMea[0] ** m. dpMea/mMea_flow are each a 2-element sequence. power_law_from_points(dpMea, mMea_flow, dp_turbulent=0.1, *, rho_default, kind='airpath', learnable=False)
regularized_orifice
also mbl_orifice
noodl.elements
Orifice.mo:3-5,9: C = CD * A * sqrt(2 / rho_default), m = 0.5 by default (overridable, per Orifice.mo:3's non-final m=0.5 modifier); C itself is final in MBL (does not depend on m). CD defaults to 0.65 (Orifice.mo:9, the sharp-edged-orifice discharge coefficient). regularized_orifice(A, CD=0.65, m=0.5, dp_turbulent=0.1, *, rho_default, kind='airpath', learnable=False)
check_drive_signature
noodl.drives
Raise TypeError unless drive is callable as drive(drivers). check_drive_signature(drive, *, where)

Constants and tables

Named values and option tables.

Name Value
DEFAULT_ROUGHNESS 9e-05
DEFAULT_TERMINAL_LOSS 0.125
EXHAUST 4
OUTDOOR_AIR 3
RECIRCULATION 2
RETURN 1
SUPPLY 0

Base classes (for extending)

Subclass these to add a constitutive law, drive, closure, reaction or node source; see Extending noodl.

Name What it does How to call it
ConstitutiveLaw
noodl.elements
Abstract branch law: flow as a function of potential difference. ConstitutiveLaw(kind)
ImplicitLaw
noodl.elements
A constitutive law written as a relation residual(dp, q, drivers, ctx) = 0. ImplicitLaw(kind)
Drive
noodl.drives
A drive must read every differentiable quantity from drivers, never own one. Drive(...)