ecoengine 3.2.4__tar.gz → 3.2.7__tar.gz

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.
Files changed (80) hide show
  1. {ecoengine-3.2.4 → ecoengine-3.2.7}/PKG-INFO +1 -1
  2. {ecoengine-3.2.4 → ecoengine-3.2.7}/pyproject.toml +1 -1
  3. {ecoengine-3.2.4 → ecoengine-3.2.7}/src/ecoengine/objects/components/heating/PerformanceMap.py +64 -34
  4. {ecoengine-3.2.4 → ecoengine-3.2.7}/src/ecoengine/objects/components/heating/WaterHeater.py +8 -0
  5. {ecoengine-3.2.4 → ecoengine-3.2.7}/src/ecoengine.egg-info/PKG-INFO +1 -1
  6. {ecoengine-3.2.4 → ecoengine-3.2.7}/README.md +0 -0
  7. {ecoengine-3.2.4 → ecoengine-3.2.7}/setup.cfg +0 -0
  8. {ecoengine-3.2.4 → ecoengine-3.2.7}/src/ecoengine/__init__.py +0 -0
  9. {ecoengine-3.2.4 → ecoengine-3.2.7}/src/ecoengine/constants/__init__.py +0 -0
  10. {ecoengine-3.2.4 → ecoengine-3.2.7}/src/ecoengine/constants/constants.py +0 -0
  11. {ecoengine-3.2.4 → ecoengine-3.2.7}/src/ecoengine/data/__init__.py +0 -0
  12. {ecoengine-3.2.4 → ecoengine-3.2.7}/src/ecoengine/data/climate_data/DryBulbTemperatures_ByClimateZone.csv +0 -0
  13. {ecoengine-3.2.4 → ecoengine-3.2.7}/src/ecoengine/data/climate_data/InletWaterTemperatures_ByClimateZone.csv +0 -0
  14. {ecoengine-3.2.4 → ecoengine-3.2.7}/src/ecoengine/data/climate_data/WeatherStation_ClimateZone_Lookup.csv +0 -0
  15. {ecoengine-3.2.4 → ecoengine-3.2.7}/src/ecoengine/data/climate_data/ZipCode_ClimateZone_Lookup.csv +0 -0
  16. {ecoengine-3.2.4 → ecoengine-3.2.7}/src/ecoengine/data/climate_data/__init__.py +0 -0
  17. {ecoengine-3.2.4 → ecoengine-3.2.7}/src/ecoengine/data/climate_data/kGperkWh_ByClimateZone.csv +0 -0
  18. {ecoengine-3.2.4 → ecoengine-3.2.7}/src/ecoengine/data/load_shapes/__init__.py +0 -0
  19. {ecoengine-3.2.4 → ecoengine-3.2.7}/src/ecoengine/data/load_shapes/apartment.json +0 -0
  20. {ecoengine-3.2.4 → ecoengine-3.2.7}/src/ecoengine/data/load_shapes/elementary_school.json +0 -0
  21. {ecoengine-3.2.4 → ecoengine-3.2.7}/src/ecoengine/data/load_shapes/food_service_a.json +0 -0
  22. {ecoengine-3.2.4 → ecoengine-3.2.7}/src/ecoengine/data/load_shapes/food_service_b.json +0 -0
  23. {ecoengine-3.2.4 → ecoengine-3.2.7}/src/ecoengine/data/load_shapes/junior_high.json +0 -0
  24. {ecoengine-3.2.4 → ecoengine-3.2.7}/src/ecoengine/data/load_shapes/mens_dorm.json +0 -0
  25. {ecoengine-3.2.4 → ecoengine-3.2.7}/src/ecoengine/data/load_shapes/motel.json +0 -0
  26. {ecoengine-3.2.4 → ecoengine-3.2.7}/src/ecoengine/data/load_shapes/multi_family.json +0 -0
  27. {ecoengine-3.2.4 → ecoengine-3.2.7}/src/ecoengine/data/load_shapes/nursing_home.json +0 -0
  28. {ecoengine-3.2.4 → ecoengine-3.2.7}/src/ecoengine/data/load_shapes/office_building.json +0 -0
  29. {ecoengine-3.2.4 → ecoengine-3.2.7}/src/ecoengine/data/load_shapes/senior_high.json +0 -0
  30. {ecoengine-3.2.4 → ecoengine-3.2.7}/src/ecoengine/data/load_shapes/womens_dorm.json +0 -0
  31. {ecoengine-3.2.4 → ecoengine-3.2.7}/src/ecoengine/data/preformanceMaps/__init__.py +0 -0
  32. {ecoengine-3.2.4 → ecoengine-3.2.7}/src/ecoengine/data/preformanceMaps/maps.json +0 -0
  33. {ecoengine-3.2.4 → ecoengine-3.2.7}/src/ecoengine/data/preformanceMaps/pkls/GenericHPWH3_SP_bounds.pkl +0 -0
  34. {ecoengine-3.2.4 → ecoengine-3.2.7}/src/ecoengine/data/preformanceMaps/pkls/GenericHPWH3_SP_capacity_interpolator.pkl +0 -0
  35. {ecoengine-3.2.4 → ecoengine-3.2.7}/src/ecoengine/data/preformanceMaps/pkls/GenericHPWH3_SP_power_in_interpolator.pkl +0 -0
  36. {ecoengine-3.2.4 → ecoengine-3.2.7}/src/ecoengine/data/preformanceMaps/pkls/GenericHPWH4_MP_bounds.pkl +0 -0
  37. {ecoengine-3.2.4 → ecoengine-3.2.7}/src/ecoengine/data/preformanceMaps/pkls/GenericHPWH4_MP_capacity_interpolator.pkl +0 -0
  38. {ecoengine-3.2.4 → ecoengine-3.2.7}/src/ecoengine/data/preformanceMaps/pkls/GenericHPWH4_MP_power_in_interpolator.pkl +0 -0
  39. {ecoengine-3.2.4 → ecoengine-3.2.7}/src/ecoengine/data/preformanceMaps/pkls/__init__.py +0 -0
  40. {ecoengine-3.2.4 → ecoengine-3.2.7}/src/ecoengine/interfaces/EcosizerEngine.py +0 -0
  41. {ecoengine-3.2.4 → ecoengine-3.2.7}/src/ecoengine/interfaces/Simulator.py +0 -0
  42. {ecoengine-3.2.4 → ecoengine-3.2.7}/src/ecoengine/interfaces/__init__.py +0 -0
  43. {ecoengine-3.2.4 → ecoengine-3.2.7}/src/ecoengine/objects/__init__.py +0 -0
  44. {ecoengine-3.2.4 → ecoengine-3.2.7}/src/ecoengine/objects/building/Building.py +0 -0
  45. {ecoengine-3.2.4 → ecoengine-3.2.7}/src/ecoengine/objects/building/ClimateZone.py +0 -0
  46. {ecoengine-3.2.4 → ecoengine-3.2.7}/src/ecoengine/objects/building/UtilityCostTracker.py +0 -0
  47. {ecoengine-3.2.4 → ecoengine-3.2.7}/src/ecoengine/objects/building/__init__.py +0 -0
  48. {ecoengine-3.2.4 → ecoengine-3.2.7}/src/ecoengine/objects/components/__init__.py +0 -0
  49. {ecoengine-3.2.4 → ecoengine-3.2.7}/src/ecoengine/objects/components/heating/Controls.py +0 -0
  50. {ecoengine-3.2.4 → ecoengine-3.2.7}/src/ecoengine/objects/components/heating/__init__.py +0 -0
  51. {ecoengine-3.2.4 → ecoengine-3.2.7}/src/ecoengine/objects/components/storage/EnergyTank.py +0 -0
  52. {ecoengine-3.2.4 → ecoengine-3.2.7}/src/ecoengine/objects/components/storage/MixedStorageTank.py +0 -0
  53. {ecoengine-3.2.4 → ecoengine-3.2.7}/src/ecoengine/objects/components/storage/SlugOverlayTank.py +0 -0
  54. {ecoengine-3.2.4 → ecoengine-3.2.7}/src/ecoengine/objects/components/storage/StorageTank.py +0 -0
  55. {ecoengine-3.2.4 → ecoengine-3.2.7}/src/ecoengine/objects/components/storage/StratifiedTank.py +0 -0
  56. {ecoengine-3.2.4 → ecoengine-3.2.7}/src/ecoengine/objects/components/storage/__init__.py +0 -0
  57. {ecoengine-3.2.4 → ecoengine-3.2.7}/src/ecoengine/objects/dhwsystems/DHWSystem.py +0 -0
  58. {ecoengine-3.2.4 → ecoengine-3.2.7}/src/ecoengine/objects/dhwsystems/InstantWHSystem.py +0 -0
  59. {ecoengine-3.2.4 → ecoengine-3.2.7}/src/ecoengine/objects/dhwsystems/MPNoRecircSystem.py +0 -0
  60. {ecoengine-3.2.4 → ecoengine-3.2.7}/src/ecoengine/objects/dhwsystems/__init__.py +0 -0
  61. {ecoengine-3.2.4 → ecoengine-3.2.7}/src/ecoengine/objects/dhwsystems/recirc_systems/ParallelLoopSystem.py +0 -0
  62. {ecoengine-3.2.4 → ecoengine-3.2.7}/src/ecoengine/objects/dhwsystems/recirc_systems/RecircSystem.py +0 -0
  63. {ecoengine-3.2.4 → ecoengine-3.2.7}/src/ecoengine/objects/dhwsystems/recirc_systems/SwingDualFuelSystem.py +0 -0
  64. {ecoengine-3.2.4 → ecoengine-3.2.7}/src/ecoengine/objects/dhwsystems/recirc_systems/SwingERTrdOffSystem.py +0 -0
  65. {ecoengine-3.2.4 → ecoengine-3.2.7}/src/ecoengine/objects/dhwsystems/recirc_systems/SwingSystem.py +0 -0
  66. {ecoengine-3.2.4 → ecoengine-3.2.7}/src/ecoengine/objects/dhwsystems/recirc_systems/__init__.py +0 -0
  67. {ecoengine-3.2.4 → ecoengine-3.2.7}/src/ecoengine/objects/dhwsystems/rtp_systems/MP_RTPInSeriesSystem.py +0 -0
  68. {ecoengine-3.2.4 → ecoengine-3.2.7}/src/ecoengine/objects/dhwsystems/rtp_systems/MultiPassRTPSystem.py +0 -0
  69. {ecoengine-3.2.4 → ecoengine-3.2.7}/src/ecoengine/objects/dhwsystems/rtp_systems/RTPSystem.py +0 -0
  70. {ecoengine-3.2.4 → ecoengine-3.2.7}/src/ecoengine/objects/dhwsystems/rtp_systems/SP_RTPInParallelSystem.py +0 -0
  71. {ecoengine-3.2.4 → ecoengine-3.2.7}/src/ecoengine/objects/dhwsystems/rtp_systems/SP_RTPInSeriesSystem.py +0 -0
  72. {ecoengine-3.2.4 → ecoengine-3.2.7}/src/ecoengine/objects/dhwsystems/rtp_systems/SinglePassRTPSystem.py +0 -0
  73. {ecoengine-3.2.4 → ecoengine-3.2.7}/src/ecoengine/objects/dhwsystems/rtp_systems/__init__.py +0 -0
  74. {ecoengine-3.2.4 → ecoengine-3.2.7}/src/ecoengine/objects/dhwsystems/utils.py +0 -0
  75. {ecoengine-3.2.4 → ecoengine-3.2.7}/src/ecoengine/objects/simulation/SimulationRun.py +0 -0
  76. {ecoengine-3.2.4 → ecoengine-3.2.7}/src/ecoengine/objects/simulation/__init__.py +0 -0
  77. {ecoengine-3.2.4 → ecoengine-3.2.7}/src/ecoengine.egg-info/SOURCES.txt +0 -0
  78. {ecoengine-3.2.4 → ecoengine-3.2.7}/src/ecoengine.egg-info/dependency_links.txt +0 -0
  79. {ecoengine-3.2.4 → ecoengine-3.2.7}/src/ecoengine.egg-info/requires.txt +0 -0
  80. {ecoengine-3.2.4 → ecoengine-3.2.7}/src/ecoengine.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ecoengine
3
- Version: 3.2.4
3
+ Version: 3.2.7
4
4
  Requires-Python: >=3.11
5
5
  Requires-Dist: numpy<3.0,>=2.4
6
6
  Requires-Dist: scipy<2.0,>=1.17
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ecoengine"
7
- version = "3.2.4"
7
+ version = "3.2.7"
8
8
  requires-python = ">=3.11"
9
9
 
10
10
  # Ranges are bounded on purpose, and the shared ones are kept in sync with
@@ -25,6 +25,13 @@ _TWO_INPUT_PKL_NAMES = frozenset({
25
25
  "MODELS_Droplet_C_SP",
26
26
  })
27
27
 
28
+ # Snap tolerances for PklPerformanceMap._force_closest [°F]. A grid point in the
29
+ # conservative direction (lower OAT, higher inlet, higher outlet) is chosen over
30
+ # the overall nearest point if it is no more than this much further away.
31
+ _SNAP_TOL_OAT_F = 10.0
32
+ _SNAP_TOL_INLET_F = 15.0
33
+ _SNAP_TOL_OUTLET_F = 20.0
34
+
28
35
  # Per-directory cache so that different perf_map_dir values can coexist in one process.
29
36
  _maps_json_cache: dict[str, dict] = {}
30
37
 
@@ -276,8 +283,10 @@ class PklPerformanceMap(PerformanceMap):
276
283
  * OAT ≥ oat_max → use stored default high-OAT capacity/power values.
277
284
  * OAT < oat_min → Electric Resistance fallback: output = input =
278
285
  ``nominal_capacity_kbtuh / num_units`` (COP = 1).
279
- * NaN in range → attempt to snap inlet/outlet to nearest valid grid
280
- point (``_force_closest``); if still NaN, assume COP = 1.5.
286
+ * NaN in range → snap to a nearby valid grid point, preferring the
287
+ conservative direction (lower OAT, higher inlet, higher outlet) within
288
+ ``_SNAP_TOL_*`` of the nearest point (``_force_closest``); if still
289
+ NaN, assume COP = 1.5.
281
290
  """
282
291
 
283
292
  def __init__(
@@ -339,6 +348,8 @@ class PklPerformanceMap(PerformanceMap):
339
348
  if self._secondary_hx:
340
349
  inlet += self._hx_increase
341
350
  outlet += self._hx_increase
351
+ if inlet > self._inlet_max:
352
+ print("Warning: Inlet water temperature simulated exceeds performance map boundaries. Simulation accuracy may be impacted.")
342
353
  inlet = min(inlet, self._inlet_max)
343
354
  return inlet, outlet
344
355
 
@@ -355,51 +366,68 @@ class PklPerformanceMap(PerformanceMap):
355
366
  float(self._input_interp(arr)[0][0]),
356
367
  )
357
368
 
369
+ @staticmethod
370
+ def _pick_biased(
371
+ values: list[float], target: float, prefer_above: bool, tol: float
372
+ ) -> int:
373
+ """
374
+ Return the index of the grid value to snap ``target`` to.
375
+
376
+ An exact match wins. Otherwise the nearest value in the preferred
377
+ direction (above when ``prefer_above``, else below) is chosen if it is
378
+ no more than ``tol`` further away than the overall nearest value;
379
+ failing that, the overall nearest value is chosen.
380
+ """
381
+ nearest_i = min(range(len(values)), key=lambda i: abs(values[i] - target))
382
+ if values[nearest_i] == target:
383
+ return nearest_i
384
+
385
+ if prefer_above:
386
+ preferred = [i for i, v in enumerate(values) if v > target]
387
+ else:
388
+ preferred = [i for i, v in enumerate(values) if v < target]
389
+ if not preferred:
390
+ return nearest_i
391
+
392
+ pref_i = min(preferred, key=lambda i: abs(values[i] - target))
393
+ if abs(values[pref_i] - target) <= abs(values[nearest_i] - target) + tol:
394
+ return pref_i
395
+ return nearest_i
396
+
358
397
  def _idx_nearest_oat(self, oat_f: float) -> int:
359
- best, idx = abs(self._unique_oats[0] - oat_f), 0
360
- for i in range(1, len(self._unique_oats)):
361
- d = abs(self._unique_oats[i] - oat_f)
362
- if d < best:
363
- best, idx = d, i
364
- return idx
398
+ """Index of the OAT to snap to, preferring lower OATs."""
399
+ return self._pick_biased(
400
+ self._unique_oats, oat_f, prefer_above=False, tol=_SNAP_TOL_OAT_F
401
+ )
365
402
 
366
403
  def _idx_nearest_inlet(self, oat_idx: int, inlet_t: float) -> int:
367
- """
368
- Find the index of the nearest available inlet temp that is >= inlet_t.
369
- Returns -1 (last element) when none qualifies.
370
- """
371
- entries = self._inTs_outTs[oat_idx]
372
- best = abs(entries[-1][0] - inlet_t)
373
- closest = -1
374
- for i, (t_in, _) in enumerate(entries):
375
- d = abs(t_in - inlet_t)
376
- if d < best and t_in >= inlet_t:
377
- best, closest = d, i
378
- return closest
404
+ """Index of the inlet temp (at oat_idx) to snap to, preferring higher inlets."""
405
+ inlets = [t_in for t_in, _ in self._inTs_outTs[oat_idx]]
406
+ return self._pick_biased(
407
+ inlets, inlet_t, prefer_above=True, tol=_SNAP_TOL_INLET_F
408
+ )
379
409
 
380
410
  def _nearest_outlet(self, oat_idx: int, inlet_idx: int, outlet_t: float) -> float:
381
- """Return the outlet temp in the bounds grid nearest to outlet_t."""
411
+ """Outlet temp (at oat_idx, inlet_idx) to snap to, preferring higher outlets."""
382
412
  outlets = self._inTs_outTs[oat_idx][inlet_idx][1]
383
- best = abs(self._unique_oats[0] - outlet_t) # mirrors original init
384
- best_i = 0
385
- for i, o in enumerate(outlets):
386
- d = abs(o - outlet_t)
387
- if d < best:
388
- best, best_i = d, i
389
- return outlets[best_i]
413
+ return outlets[self._pick_biased(
414
+ outlets, outlet_t, prefer_above=True, tol=_SNAP_TOL_OUTLET_F
415
+ )]
390
416
 
391
417
  def _force_closest(
392
418
  self, inlet_t: float, outlet_t: float, oat_f: float
393
419
  ) -> tuple[float, float] | None:
394
420
  """
395
- Attempt to recover a valid interpolation result by snapping inlet and
396
- outlet to the nearest valid grid points. Returns (out_kw, inp_kw) per
397
- unit, or None when the inputs are too far outside the map.
421
+ Attempt to recover a valid interpolation result by snapping OAT, inlet
422
+ and outlet to nearby valid grid points, preferring the conservative
423
+ direction (see ``_pick_biased``). Returns (out_kw, inp_kw) per unit,
424
+ or None when the inputs are too far outside the map.
398
425
  """
399
426
  oat_idx = self._idx_nearest_oat(oat_f)
400
427
  try:
401
428
  inlet_idx = self._idx_nearest_inlet(oat_idx, inlet_t)
402
- except Exception:
429
+ except Exception as e:
430
+ print(e)
403
431
  return None
404
432
 
405
433
  snapped_oat = self._unique_oats[oat_idx]
@@ -414,12 +442,14 @@ class PklPerformanceMap(PerformanceMap):
414
442
  out_kw, inp_kw = self._raw_query(snapped_inlet, snapped_outlet, snapped_oat)
415
443
  if not (math.isnan(out_kw) or math.isnan(inp_kw)):
416
444
  if snapped_outlet < outlet_t and outlet_t > snapped_inlet:
417
- # Proportional capacity adjustment for lower outlet temperature
445
+ # Proportional capacity adjustment for lower outlet temperature,
446
+ # holding the snapped point's COP constant
418
447
  adj = out_kw * (
419
448
  (outlet_t - snapped_inlet) - (snapped_outlet - snapped_inlet)
420
449
  ) / (outlet_t - snapped_inlet)
450
+ if out_kw > 0:
451
+ inp_kw *= (out_kw + adj) / out_kw
421
452
  out_kw += adj
422
- inp_kw += adj
423
453
  return out_kw, inp_kw
424
454
 
425
455
  return None
@@ -330,6 +330,14 @@ class WaterHeater:
330
330
  controls = self.get_controls_for_hour(hour_of_day)
331
331
  if controls is None:
332
332
  raise ValueError(f"No Controls configured for hour {hour_of_day}.")
333
+
334
+ # QPL Alignment block : QPL states Upon change of load shift operation, disable all heaters if “on sensor” is warmer than “on temperature”.
335
+ # To do this in EcosizerEngine, we simply turn off water heaters at the beginning of a mode change
336
+ previous_hour = 23 if hour_of_day == 0 else hour_of_day - 1
337
+ if self.control_schedule[hour_of_day] != self.control_schedule[previous_hour]:
338
+ self.turn_off()
339
+
340
+ # control logic
333
341
  if self._active:
334
342
  if controls.should_turn_off(storage_tank):
335
343
  self.turn_off()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ecoengine
3
- Version: 3.2.4
3
+ Version: 3.2.7
4
4
  Requires-Python: >=3.11
5
5
  Requires-Dist: numpy<3.0,>=2.4
6
6
  Requires-Dist: scipy<2.0,>=1.17
File without changes
File without changes