Quickstart
This page walks through a complete hvacpy workflow — from defining the building envelope to selecting equipment.
1"""hvacpy quickstart — v0.4 verification script.
2
3Covers all four modules: Assembly (v0.1), Psychrometrics (v0.2),
4Heat Loads (v0.3), and Equipment Sizing (v0.4).
5Run with: python quickstart.py
6"""
7
8from hvacpy import (
9 Q_, Assembly, db,
10 Room, Zone, WallComponent, WindowComponent, InternalGain,
11 CoolingLoad, HeatingLoad, Orientation,
12 SplitSystem, PackagedRTU, AirSourceHeatPump,
13 DuctSizer, VentilationCheck,
14)
15
16# ── v0.1 — Assembly (envelope) ──────────────────────────────────────
17
18wall_assy = Assembly('Office South Wall')
19wall_assy.add_layer('brick_common', Q_(110, 'mm'))
20wall_assy.add_layer('mineral_wool_batt', Q_(75, 'mm'))
21wall_assy.add_layer('plasterboard_std', Q_(12.5,'mm'))
22
23roof_assy = Assembly('Flat Roof', orientation='roof')
24roof_assy.add_layer('concrete_dense', Q_(150, 'mm'))
25roof_assy.add_layer('xps_insulation', Q_(100, 'mm'))
26roof_assy.add_layer('bitumen_felt', Q_(5, 'mm'))
27
28assert wall_assy.u_value.magnitude > 0
29
30# ── v0.3 — Heat Loads ────────────────────────────────────────────────
31
32room = Room(
33 name='Open Plan Office',
34 floor_area=Q_(100, 'm**2'),
35 ceiling_height=Q_(3, 'm'),
36 ach_infiltration=0.5,
37)
38room.walls.append(WallComponent(
39 name='South Wall', assembly=wall_assy,
40 area=Q_(30, 'm**2'), orientation=Orientation.S,
41))
42room.walls.append(WallComponent(
43 name='Roof', assembly=roof_assy,
44 area=Q_(100, 'm**2'), orientation=Orientation.HORIZONTAL, is_roof=True,
45))
46room.windows.append(WindowComponent(
47 name='South Glazing', area=Q_(15, 'm**2'),
48 orientation=Orientation.S, u_factor=Q_(2.8, 'W/(m**2*K)'),
49 shgc=0.40,
50))
51room.internal_gains.append(InternalGain(gain_type='people', count=20, activity='office_work'))
52room.internal_gains.append(InternalGain(gain_type='lighting', watts_per_m2=12))
53room.internal_gains.append(InternalGain(gain_type='equipment', watts_per_m2=15))
54
55cooling = CoolingLoad(room, city='london')
56print(f"Peak cooling: {cooling.peak_total.to('kW'):.2f}")
57print(f"Peak hour: {cooling.peak_hour}:00")
58print(f"Sensible: {cooling.peak_sensible.to('kW'):.2f}")
59print(f"Latent: {cooling.peak_latent.to('kW'):.2f}")
60print(f"SHR: {cooling.sensible_heat_ratio:.3f}")
61print(cooling.breakdown())
62
63heating = HeatingLoad(room, city='london')
64print(f"Peak heating: {heating.total.to('kW'):.2f}")
65print(heating.breakdown())
66
67# ── v0.4 — Equipment Sizing ─────────────────────────────────────────
68
69print("\n" + "─" * 57)
70print("EQUIPMENT SIZING (v0.4)")
71print("─" * 57)
72
73# Size a split system from the calculated cooling load
74ss = SplitSystem(cooling, cop_rated=3.5)
75print(f"\nSplit System:")
76print(f" Required: {cooling.peak_total.to('kW'):.2f}")
77print(f" Selected nominal: {ss.nominal_capacity:.2f}")
78print(f" Oversizing ratio: {ss.oversizing_ratio:.3f}")
79print(f" Supply airflow: {ss.supply_airflow.to('m**3/s'):.3f}")
80print(f" Warning: {ss.oversizing_warning or 'None (within 25%)'}")
81print(ss.summary())
82
83# Size an air-source heat pump covering both modes
84hp = AirSourceHeatPump(
85 cooling, heating,
86 t_outdoor_cooling=Q_(33, 'degC'),
87 t_outdoor_heating=Q_(2, 'degC'),
88)
89print(f"\nAir-Source Heat Pump:")
90print(f" Nominal: {hp.nominal_capacity_kw:.2f}")
91print(f" Binding mode: {hp.binding_mode}")
92print(f" COP (cooling): {hp.cop_at_design_cooling:.2f}")
93print(f" COP (heating): {hp.cop_at_design_heating:.2f}")
94print(f" Heating coverage: {hp.heating_coverage:.1%}")
95print(f" Supplemental heat: {hp.supplemental_heat_kw:.2f}" if hp.needs_supplemental_heat else " Supplemental heat: Not required")
96print(hp.summary())
97
98# Size a supply duct with the equal friction method
99supply_flow = ss.supply_airflow
100ds = DuctSizer(supply_flow, method='equal_friction', section_type='main_supply')
101print(f"\nDuct Sizing (equal friction, main supply):")
102print(f" Diameter: {ds.diameter}")
103print(f" Velocity: {ds.velocity:.2f}")
104print(f" Friction loss: {ds.friction_loss:.3f}")
105print(f" Velocity OK: {ds.velocity_ok}")
106print(f" {ds.summary()}")
107
108# Ventilation compliance (ASHRAE 62.1-2022)
109vc = VentilationCheck(room, supply_airflow=supply_flow, space_type='office')
110print(f"\nVentilation Compliance (ASHRAE 62.1-2022):")
111print(f" Required OA: {vc.required_oa_flow:.2f}")
112print(f" Actual OA: {vc.actual_oa_flow:.2f}")
113print(f" Compliant: {vc.compliant}")
114print(f" {vc.summary()}")
115
116# ── Assertions ───────────────────────────────────────────────────────
117
118assert cooling.peak_total.magnitude > 0
119assert 12 <= cooling.peak_hour <= 18, f"peak_hour {cooling.peak_hour} not in [12,18]"
120assert 0.60 <= cooling.sensible_heat_ratio <= 0.85
121assert heating.total.magnitude > 0
122assert ss.nominal_capacity.magnitude > 0
123assert ss.oversizing_ratio >= 1.0
124assert ds.diameter.magnitude > 0
125assert ds.velocity.magnitude > 0
126assert ds.friction_loss.magnitude > 0
127assert hp.nominal_capacity_kw.magnitude > 0
128assert hp.heating_coverage > 0
129assert vc.required_oa_flow.magnitude > 0
130assert vc.actual_oa_flow.magnitude > 0
131
132print("\n✓ All quickstart assertions passed!")