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!")