epinterface 1.0.3__py3-none-any.whl → 1.0.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,18 +2,20 @@
2
2
 
3
3
  import asyncio
4
4
  import gc
5
+ import logging
5
6
  import shutil
6
7
  import tempfile
7
8
  from collections.abc import Callable
8
9
  from dataclasses import dataclass
9
10
  from pathlib import Path
10
- from typing import Literal, cast
11
+ from typing import Literal, cast, get_args
11
12
  from uuid import uuid4
12
13
 
13
14
  import numpy as np
14
15
  import pandas as pd
15
16
  from archetypal.idfclass import IDF
16
17
  from archetypal.idfclass.sql import Sql
18
+ from ladybug.epw import EPW
17
19
  from pydantic import BaseModel, Field, field_validator, model_validator
18
20
 
19
21
  from epinterface.constants import assumed_constants, physical_constants
@@ -37,6 +39,8 @@ from epinterface.sbem.components.zones import ZoneComponent
37
39
  from epinterface.sbem.exceptions import NotImplementedParameter
38
40
  from epinterface.weather import BaseWeather
39
41
 
42
+ logger = logging.getLogger(__name__)
43
+
40
44
  DESIRED_METERS = (
41
45
  "InteriorEquipment:Electricity",
42
46
  "InteriorLights:Electricity",
@@ -46,11 +50,13 @@ DESIRED_METERS = (
46
50
  )
47
51
 
48
52
  # TODO: add the meters for HVAC systems
49
- DESIRED_VARIABLES = (
53
+ AvailableHourlyVariables = Literal[
50
54
  "Zone Mean Air Temperature",
51
55
  "Zone Air Relative Humidity",
52
56
  "Site Outdoor Air Drybulb Temperature",
53
- )
57
+ ]
58
+
59
+ AVAILABLE_HOURLY_VARIABLES = get_args(AvailableHourlyVariables)
54
60
 
55
61
 
56
62
  class SimulationPathConfig(BaseModel):
@@ -803,7 +809,7 @@ class Model(BaseWeather, validate_assignment=True):
803
809
 
804
810
  return idf
805
811
 
806
- def build(
812
+ def build( # noqa: C901
807
813
  self,
808
814
  config: SimulationPathConfig,
809
815
  post_geometry_callback: Callable[[IDF], IDF] | None = None,
@@ -846,7 +852,7 @@ class Model(BaseWeather, validate_assignment=True):
846
852
  "Variable_Name": variable,
847
853
  "Reporting_Frequency": "Hourly",
848
854
  }
849
- for variable in DESIRED_VARIABLES
855
+ for variable in AVAILABLE_HOURLY_VARIABLES
850
856
  ]
851
857
  )
852
858
  idf = IDF(
@@ -870,10 +876,6 @@ class Model(BaseWeather, validate_assignment=True):
870
876
  idf = add_default_sim_controls(idf)
871
877
  idf, _scheds = add_default_schedules(idf)
872
878
 
873
- idf = SiteGroundTemperature.FromValues(
874
- assumed_constants.SiteGroundTemperature_degC
875
- ).add(idf)
876
-
877
879
  idf = self.geometry.add(idf)
878
880
  if post_geometry_callback is not None:
879
881
  idf = post_geometry_callback(idf)
@@ -885,6 +887,44 @@ class Model(BaseWeather, validate_assignment=True):
885
887
  for zone in added_zone_lists.main_zone_list.Names:
886
888
  self.Zone.add_to_idf_zone(idf, zone)
887
889
 
890
+ # Handle setting ground temperature
891
+ subtractor = (
892
+ 4 if (self.geometry.basement and not self.Basement.Conditioned) else 2
893
+ )
894
+ has_heating = self.Zone.Operations.HVAC.ConditioningSystems.Heating is not None
895
+ has_cooling = self.Zone.Operations.HVAC.ConditioningSystems.Cooling is not None
896
+ hsp = self.Zone.Operations.SpaceUse.Thermostat.HeatingSchedule
897
+ csp = self.Zone.Operations.SpaceUse.Thermostat.CoolingSchedule
898
+ epw = EPW(epw_path.as_posix())
899
+ epw_ground_vals_all = epw.monthly_ground_temperature
900
+ if self.geometry.basement:
901
+ # if there is a basement, we use the 2m depth to account for the basement depth.
902
+ epw_ground_vals = epw_ground_vals_all[4].values
903
+ else:
904
+ # if there is no basement, we use the 0.5m depth to account for the ground temperature.
905
+ epw_ground_vals = epw_ground_vals_all[0.5].values
906
+ low_ground_val = min(epw_ground_vals)
907
+ high_ground_val = max(epw_ground_vals)
908
+ phase = (np.array(epw_ground_vals) - low_ground_val) / (
909
+ high_ground_val - low_ground_val
910
+ )
911
+ if has_heating and has_cooling:
912
+ winter_line = np.array(hsp.MonthlyAverageValues) - subtractor
913
+ summer_line = np.array(csp.MonthlyAverageValues) - subtractor
914
+ elif has_heating:
915
+ winter_line = np.array(hsp.MonthlyAverageValues) - subtractor
916
+ summer_line = np.array(hsp.MonthlyAverageValues)
917
+ elif has_cooling:
918
+ winter_line = np.array(csp.MonthlyAverageValues) - subtractor
919
+ summer_line = np.array(csp.MonthlyAverageValues) - subtractor
920
+ else:
921
+ # No heating or cooling, so we use the default ground temperature, should not matter much.
922
+ winter_line = np.array(assumed_constants.SiteGroundTemperature_degC)
923
+ summer_line = np.array(assumed_constants.SiteGroundTemperature_degC)
924
+ interp_temp = phase * np.abs(summer_line - winter_line) + winter_line
925
+ ground_vals = [max(epw_ground_vals[i], interp_temp[i]) for i in range(12)]
926
+ idf = SiteGroundTemperature.FromValues(ground_vals).add(idf)
927
+
888
928
  # handle basements
889
929
  if self.Basement.UseFraction or self.Basement.Conditioned:
890
930
  new_zone_def = self.Zone.model_copy(deep=True)
@@ -1310,9 +1350,15 @@ if __name__ == "__main__":
1310
1350
  from epinterface.sbem.prisma.client import PrismaSettings
1311
1351
 
1312
1352
  with tempfile.TemporaryDirectory() as temp_dir:
1313
- database_path = Path("/Users/daryaguettler/globi/data/Brazil/components-lib.db")
1353
+ # database_path = Path("/Users/daryaguettler/globi/data/Brazil/components-lib.db")
1354
+ # component_map_path = Path(
1355
+ # "/Users/daryaguettler/globi/data/Brazil/component-map.yaml"
1356
+ # )
1357
+ database_path = Path(
1358
+ "/Users/daryaguettler/globi/data/Portugal/components-lib.db"
1359
+ )
1314
1360
  component_map_path = Path(
1315
- "/Users/daryaguettler/globi/data/Brazil/component-map.yaml"
1361
+ "/Users/daryaguettler/globi/data/Portugal/component-map.yaml"
1316
1362
  )
1317
1363
  settings = PrismaSettings(
1318
1364
  database_path=database_path,
@@ -1329,18 +1375,26 @@ if __name__ == "__main__":
1329
1375
  component_map_yaml = yaml.safe_load(f)
1330
1376
  selector = SelectorModel.model_validate(component_map_yaml)
1331
1377
  db = settings.db
1378
+ # context = {
1379
+ # "region": "SP",
1380
+ # "income": "Low",
1381
+ # "typology": "Residential",
1382
+ # "scenario": "withAC",
1383
+ # }
1332
1384
  context = {
1333
- "region": "SP",
1334
- "income": "Low",
1335
- "typology": "Residential",
1336
- "scenario": "withAC",
1385
+ "Region": "I1_V2",
1386
+ "City": "LS",
1387
+ "Typology": "Single_Family_Residential",
1388
+ "Age_buckets": "1971_1980",
1389
+ "scenario": "Baseline",
1337
1390
  }
1338
1391
  with settings.db:
1339
1392
  zone = cast(ZoneComponent, selector.get_component(context=context, db=db))
1340
1393
 
1341
1394
  model = Model(
1342
1395
  Weather=(
1343
- "https://climate.onebuilding.org/WMO_Region_3_South_America/BRA_Brazil/SP_Sao_Paulo/BRA_SP_Guaratingueta.AP.837080_TMYx.2009-2023.zip"
1396
+ # "https://climate.onebuilding.org/WMO_Region_3_South_America/BRA_Brazil/SP_Sao_Paulo/BRA_SP_Guaratingueta.AP.837080_TMYx.2009-2023.zip"
1397
+ "https://climate.onebuilding.org/WMO_Region_6_Europe/PRT_Portugal/LB_Lisboa/PRT_LB_Lisboa.Portela.AP.085360_TMYx.2009-2023.zip"
1344
1398
  ), # pyright: ignore [reportArgumentType]
1345
1399
  Zone=zone,
1346
1400
  Attic=AtticAssumptions(
@@ -1358,7 +1412,7 @@ if __name__ == "__main__":
1358
1412
  d=20,
1359
1413
  h=3,
1360
1414
  wwr=0.3,
1361
- num_stories=3,
1415
+ num_stories=1,
1362
1416
  basement=False,
1363
1417
  zoning="by_storey",
1364
1418
  roof_height=None,
@@ -70,6 +70,11 @@ class DayComponent(NamedObject, extra="forbid"):
70
70
  Hour_22: float
71
71
  Hour_23: float
72
72
 
73
+ @property
74
+ def AverageValue(self) -> float:
75
+ """Get the average value of the day."""
76
+ return sum(self.Values) / len(self.Values)
77
+
73
78
  @property
74
79
  def bounds(self) -> tuple[float, float]:
75
80
  """Get the bounds of the day."""
@@ -200,6 +205,11 @@ class WeekComponent(NamedObject, extra="forbid"):
200
205
  highs = [day.bounds[1] for day in self.Days]
201
206
  return min(lows), max(highs)
202
207
 
208
+ @property
209
+ def AverageValue(self) -> float:
210
+ """Get the average value of the week."""
211
+ return sum(day.AverageValue for day in self.Days) / len(self.Days)
212
+
203
213
  @property
204
214
  def Days(self) -> list[DayComponent]:
205
215
  """Get the days of the week as a list."""
@@ -338,6 +348,16 @@ class YearComponent(NamedObject, extra="forbid"):
338
348
  November: WeekComponent
339
349
  December: WeekComponent
340
350
 
351
+ @property
352
+ def AverageValue(self) -> float:
353
+ """Get the average value of the year."""
354
+ return sum(week.AverageValue for week in self.Weeks) / len(self.Weeks)
355
+
356
+ @property
357
+ def MonthlyAverageValues(self) -> list[float]:
358
+ """Get the average values of the year."""
359
+ return [week.AverageValue for week in self.Weeks]
360
+
341
361
  @property
342
362
  def bounds(self) -> tuple[float, float]:
343
363
  """Get the bounds of the year."""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: epinterface
3
- Version: 1.0.3
3
+ Version: 1.0.5
4
4
  Summary: This is a repository for dynamically generating energy models within Python, relying on Archetypal and Eppy for most of its functionality.
5
5
  Project-URL: Homepage, https://github.com/szvsw/epinterface
6
6
  Project-URL: Documentation, https://szvsw.github.io/epinterface
@@ -13,6 +13,7 @@ Requires-Dist: archetypal
13
13
  Requires-Dist: click==8.1.7
14
14
  Requires-Dist: geopandas~=1.0.1
15
15
  Requires-Dist: httpx~=0.27.2
16
+ Requires-Dist: ladybug-core>=0.44.30
16
17
  Requires-Dist: openpyxl~=3.1.5
17
18
  Requires-Dist: pandas<2.3,>=2.2
18
19
  Requires-Dist: prisma~=0.15.0
@@ -20,7 +20,7 @@ epinterface/data/__init__.py,sha256=r6Uju05rbG3hVVAvCuqJS5wI_zLwK3fU_M8nKMC5DEA,
20
20
  epinterface/data/res_schedules.parquet,sha256=GjMaqb8uywr7FwR3rAByL72BKGfoZiVuSdAH2sAPFvw,4295
21
21
  epinterface/sbem/__init__.py,sha256=aOEtaivujVzFDMFyLrLRx_Xmwv7_Y60MYqObBsn5OR8,48
22
22
  epinterface/sbem/annotations.py,sha256=qnN0z7Suri5eHHPJNvXWUorYbEMHGajX2DPezCYcCSQ,1302
23
- epinterface/sbem/builder.py,sha256=Vu-UsvzxOGIJWPkA81goKtT8_uf3YJ3h9yUHJn2RFws,55328
23
+ epinterface/sbem/builder.py,sha256=hgXHtILjpT921uAaw5Ictv_wChKVYoFsrPljJtDERTg,58198
24
24
  epinterface/sbem/common.py,sha256=s7DekSlosfM2mQUZGiIGEbbgiJfF7h6QsTvYbAPP3Ac,1317
25
25
  epinterface/sbem/exceptions.py,sha256=4uOXlZgJyvhhRG2cbSua2InxxXHanGSe74h2ioiNEkw,3332
26
26
  epinterface/sbem/flat_model.py,sha256=q-G23Rvw3GBDYRm7Oa6PItI8_Xp-FYieLZmLDR4mNyM,84602
@@ -32,7 +32,7 @@ epinterface/sbem/components/composer.py,sha256=Gs4v8-x8iqfCjaD28TREmqX0pHiRWwyoO
32
32
  epinterface/sbem/components/envelope.py,sha256=ccmLr2dajgA8s2qBwZxMaj3leu4DETROm6t4N6Q9oSo,12950
33
33
  epinterface/sbem/components/materials.py,sha256=MKewPewQEMim9kbLDdgbnW9mXkJh9Juxarf2O-jcW3I,3340
34
34
  epinterface/sbem/components/operations.py,sha256=G8GedBR6evNq8ZQ0Ff004Av2Bcz-afCjVmJMC17r6mI,14924
35
- epinterface/sbem/components/schedules.py,sha256=AQWHkoBN2apbAVYSN_URe836LSGeEcBZ457WCJTUhOw,24449
35
+ epinterface/sbem/components/schedules.py,sha256=G6QTdM7hngDhtNygKipqVS_CXiXB5jXSk1Kr7D7pMlQ,25130
36
36
  epinterface/sbem/components/space_use.py,sha256=CEWXZ47Etbn06zga88ypmlqwhXEIDSF6Wf4HpW8RFJM,10125
37
37
  epinterface/sbem/components/systems.py,sha256=02C_HZd38mbE_4i0etxyS-py5y0UiZ9oXkkz_5hmqZ8,8945
38
38
  epinterface/sbem/components/zones.py,sha256=ivilWtXkDqWPej4JJho-XAu5aW4NrRqeR7mZDek2yQ0,1050
@@ -50,8 +50,8 @@ epinterface/sbem/prisma/migrations/20250325185158_add_mutually_exclusive_ventila
50
50
  epinterface/sbem/prisma/migrations/20250326141941_reduce_naming_complexity_ventilation/migration.sql,sha256=tslwxYOfpbfWCUPqnOadTGk-BzluLcly27MV-mZy2Sc,2289
51
51
  epinterface/sbem/prisma/migrations/20250331141910_add_support_for_attic_and_basement_constructions/migration.sql,sha256=fvAzKga8qsMmVM3jLFDAlSbBkWbqRGrHu5Mf2doIEgs,6690
52
52
  epinterface/sbem/prisma/migrations/20250919152559_decouple_basement_infiltration/migration.sql,sha256=YrQJxHcU1jrKb6AlBSdfuJKETKkkyxqdKb-X8krBH-8,1876
53
- epinterface-1.0.3.dist-info/METADATA,sha256=V_hpPgy5kYUvOK7qMtdHPRSc8GW-iasrfQtxf_CM5mk,3426
54
- epinterface-1.0.3.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
55
- epinterface-1.0.3.dist-info/entry_points.txt,sha256=bjjYRuHWvWV0d-QUesH6prRqHqcppTJANSuAfg3h9j8,78
56
- epinterface-1.0.3.dist-info/licenses/LICENSE,sha256=hNp6DmbGMuUcwlnpYS8E-ZHYU7kxfmRUP8pLGQaCnu8,1066
57
- epinterface-1.0.3.dist-info/RECORD,,
53
+ epinterface-1.0.5.dist-info/METADATA,sha256=2t5ecDAFk_97jAnOAvlvFwHDarCGPXZIAmxJnkpObHA,3463
54
+ epinterface-1.0.5.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
55
+ epinterface-1.0.5.dist-info/entry_points.txt,sha256=bjjYRuHWvWV0d-QUesH6prRqHqcppTJANSuAfg3h9j8,78
56
+ epinterface-1.0.5.dist-info/licenses/LICENSE,sha256=hNp6DmbGMuUcwlnpYS8E-ZHYU7kxfmRUP8pLGQaCnu8,1066
57
+ epinterface-1.0.5.dist-info/RECORD,,