ecoengine 3.1.2__tar.gz → 3.1.4__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 (90) hide show
  1. {ecoengine-3.1.2 → ecoengine-3.1.4}/PKG-INFO +1 -1
  2. {ecoengine-3.1.2 → ecoengine-3.1.4}/pyproject.toml +1 -1
  3. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/objects/components/storage/StratifiedTank.py +2 -2
  4. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/objects/dhwsystems/DHWSystem.py +10 -98
  5. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/objects/dhwsystems/recirc_systems/SwingSystem.py +10 -4
  6. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/objects/simulation/SimulationRun.py +41 -10
  7. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine.egg-info/PKG-INFO +1 -1
  8. {ecoengine-3.1.2 → ecoengine-3.1.4}/README.md +0 -0
  9. {ecoengine-3.1.2 → ecoengine-3.1.4}/setup.cfg +0 -0
  10. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/__init__.py +0 -0
  11. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/constants/__init__.py +0 -0
  12. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/constants/constants.py +0 -0
  13. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/data/__init__.py +0 -0
  14. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/data/climate_data/DryBulbTemperatures_ByClimateZone.csv +0 -0
  15. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/data/climate_data/InletWaterTemperatures_ByClimateZone.csv +0 -0
  16. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/data/climate_data/WeatherStation_ClimateZone_Lookup.csv +0 -0
  17. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/data/climate_data/ZipCode_ClimateZone_Lookup.csv +0 -0
  18. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/data/climate_data/__init__.py +0 -0
  19. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/data/climate_data/kGperkWh_ByClimateZone.csv +0 -0
  20. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/data/load_shapes/__init__.py +0 -0
  21. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/data/load_shapes/apartment.json +0 -0
  22. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/data/load_shapes/elementary_school.json +0 -0
  23. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/data/load_shapes/food_service_a.json +0 -0
  24. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/data/load_shapes/food_service_b.json +0 -0
  25. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/data/load_shapes/junior_high.json +0 -0
  26. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/data/load_shapes/mens_dorm.json +0 -0
  27. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/data/load_shapes/motel.json +0 -0
  28. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/data/load_shapes/multi_family.json +0 -0
  29. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/data/load_shapes/nursing_home.json +0 -0
  30. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/data/load_shapes/office_building.json +0 -0
  31. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/data/load_shapes/senior_high.json +0 -0
  32. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/data/load_shapes/womens_dorm.json +0 -0
  33. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/data/preformanceMaps/__init__.py +0 -0
  34. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/data/preformanceMaps/maps.json +0 -0
  35. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/data/preformanceMaps/pkls/GenericHPWH3_SP_bounds.pkl +0 -0
  36. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/data/preformanceMaps/pkls/GenericHPWH3_SP_capacity_interpolator.pkl +0 -0
  37. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/data/preformanceMaps/pkls/GenericHPWH3_SP_power_in_interpolator.pkl +0 -0
  38. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/data/preformanceMaps/pkls/GenericHPWH4_MP_bounds.pkl +0 -0
  39. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/data/preformanceMaps/pkls/GenericHPWH4_MP_capacity_interpolator.pkl +0 -0
  40. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/data/preformanceMaps/pkls/GenericHPWH4_MP_power_in_interpolator.pkl +0 -0
  41. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/data/preformanceMaps/pkls/__init__.py +0 -0
  42. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/interfaces/EcosizerEngine.py +0 -0
  43. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/interfaces/Simulator.py +0 -0
  44. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/interfaces/__init__.py +0 -0
  45. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/objects/__init__.py +0 -0
  46. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/objects/building/Building.py +0 -0
  47. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/objects/building/ClimateZone.py +0 -0
  48. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/objects/building/UtilityCostTracker.py +0 -0
  49. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/objects/building/__init__.py +0 -0
  50. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/objects/components/__init__.py +0 -0
  51. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/objects/components/heating/Controls.py +0 -0
  52. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/objects/components/heating/PerformanceMap.py +0 -0
  53. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/objects/components/heating/WaterHeater.py +0 -0
  54. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/objects/components/heating/__init__.py +0 -0
  55. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/objects/components/storage/EnergyTank.py +0 -0
  56. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/objects/components/storage/MixedStorageTank.py +0 -0
  57. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/objects/components/storage/SlugOverlayTank.py +0 -0
  58. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/objects/components/storage/StorageTank.py +0 -0
  59. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/objects/components/storage/__init__.py +0 -0
  60. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/objects/dhwsystems/InstantWHSystem.py +0 -0
  61. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/objects/dhwsystems/MPNoRecircSystem.py +0 -0
  62. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/objects/dhwsystems/__init__.py +0 -0
  63. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/objects/dhwsystems/recirc_systems/ParallelLoopSystem.py +0 -0
  64. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/objects/dhwsystems/recirc_systems/RecircSystem.py +0 -0
  65. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/objects/dhwsystems/recirc_systems/SwingDualFuelSystem.py +0 -0
  66. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/objects/dhwsystems/recirc_systems/SwingERTrdOffSystem.py +0 -0
  67. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/objects/dhwsystems/recirc_systems/__init__.py +0 -0
  68. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/objects/dhwsystems/rtp_systems/MP_RTPInSeriesSystem.py +0 -0
  69. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/objects/dhwsystems/rtp_systems/MultiPassRTPSystem.py +0 -0
  70. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/objects/dhwsystems/rtp_systems/RTPSystem.py +0 -0
  71. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/objects/dhwsystems/rtp_systems/SP_RTPInParallelSystem.py +0 -0
  72. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/objects/dhwsystems/rtp_systems/SP_RTPInSeriesSystem.py +0 -0
  73. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/objects/dhwsystems/rtp_systems/SinglePassRTPSystem.py +0 -0
  74. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/objects/dhwsystems/rtp_systems/__init__.py +0 -0
  75. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/objects/dhwsystems/utils.py +0 -0
  76. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/objects/simulation/__init__.py +0 -0
  77. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/tests/__init__.py +0 -0
  78. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/tests/test_buildings.py +0 -0
  79. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/tests/test_dhw_system.py +0 -0
  80. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/tests/test_ecosizer_engine_dual_fuel.py +0 -0
  81. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/tests/test_mixing_valve.py +0 -0
  82. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/tests/test_performance_map.py +0 -0
  83. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/tests/test_sp_rtp_in_series.py +0 -0
  84. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/tests/test_sprtp_simulation.py +0 -0
  85. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/tests/test_storage_tanks.py +0 -0
  86. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine/tests/test_utility_cost_tracker.py +0 -0
  87. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine.egg-info/SOURCES.txt +0 -0
  88. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine.egg-info/dependency_links.txt +0 -0
  89. {ecoengine-3.1.2 → ecoengine-3.1.4}/src/ecoengine.egg-info/requires.txt +0 -0
  90. {ecoengine-3.1.2 → ecoengine-3.1.4}/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.1.2
3
+ Version: 3.1.4
4
4
  Requires-Python: >=3.11
5
5
  Requires-Dist: numpy
6
6
  Requires-Dist: scipy
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ecoengine"
7
- version = "3.1.2"
7
+ version = "3.1.4"
8
8
  requires-python = ">=3.11"
9
9
  dependencies = ["numpy", "scipy", "plotly>=4.12.0"]
10
10
 
@@ -264,8 +264,8 @@ class StratifiedTank(StorageTank):
264
264
  hi = mid
265
265
  if hi - lo < 1e-6:
266
266
  break
267
-
268
- if yield_mid < volume_supplyT_gal - 1e-6:
267
+
268
+ if yield_mid < volume_supplyT_gal - 1e-6 and hi == self.total_volume_gal and hi - lo < 1e-6:
269
269
  # Even the largest physical draw the search explored (bounded at
270
270
  # total_volume_gal) falls short of demand -- a true outage. The
271
271
  # binary search's upper bound assumes one tank-volume of draw is
@@ -1188,95 +1188,6 @@ class DHWSystem:
1188
1188
 
1189
1189
  return (transition_temp_sum + fully_hot_temp_sum) / total_phys_gal
1190
1190
 
1191
- def _strat_factor_for_on_params(
1192
- self,
1193
- on_fract: float,
1194
- on_temp_f: float,
1195
- strat_slope: float,
1196
- ) -> float:
1197
- """
1198
- Compute the stratification factor for a single (on_fract, on_temp_f) pair.
1199
-
1200
- The tank temperature profile is modeled as a linear gradient in the
1201
- transition zone between cold and fully-hot layers. The slope of that
1202
- gradient is strat_slope [°F / %-height].
1203
-
1204
- Parameters
1205
- ----------
1206
- on_fract : float
1207
- Fractional height of the aquastat ON sensor (0 = bottom, 1 = top).
1208
- on_temp_f : float
1209
- Temperature at the aquastat sensor when the heater turns on [°F].
1210
- strat_slope : float
1211
- Temperature gradient [°F per percentage-point of tank height].
1212
-
1213
- Returns
1214
- -------
1215
- float
1216
- Stratification factor (0-1). A value of 1.0 means all storage
1217
- above the aquastat is at full storage temperature (ideal).
1218
- """
1219
- on_pct = on_fract * 100
1220
- delta_t = self.storage_temp_f - self.supply_temp_f
1221
-
1222
- # Tank height (%) where temperature profile crosses supply and storage setpoints
1223
- supply_height_pct = on_pct + (self.supply_temp_f - on_temp_f) / strat_slope
1224
- storage_height_pct = on_pct + (self.storage_temp_f - on_temp_f) / strat_slope
1225
- storage_height_pct = min(storage_height_pct, 100.0)
1226
- # TODO : if storage_height_pct is 100, shouldn't we realculate
1227
- # delta_t based off of what temperature is actually at the top
1228
-
1229
- # Clamp supply_height_pct so the transition zone stays within [on_pct, 100].
1230
- # If on_temp_f >= storage_temp_f the whole tank above the aquastat is fully
1231
- # hot, which gives a factor of 1.0 — the clamp produces that naturally.
1232
- supply_height_pct = max(supply_height_pct, on_pct)
1233
- # TODO: shouldn't supply_height_pct always just be where supply temp percentage height is on the tank?
1234
- # If there is some water that is supply temp under on_pct, it is still usable water
1235
-
1236
- # Volume above supply temp = fully-hot zone + trapezoidal transition zone
1237
- vol_fully_hot = (100 - storage_height_pct) * delta_t
1238
- vol_transition = (storage_height_pct - supply_height_pct) * delta_t / 2
1239
- # TODO : delta T in the above two lines should, I am pretty sure, be the difference between supply temp (or lowest emp in tank if whole tank is above supply)
1240
- # and true top of tank temperature
1241
- vol_above_supply = vol_fully_hot + vol_transition
1242
-
1243
- # Ideal case: all volume above the aquastat is at full storage temp
1244
- ideal_vol = (100 - on_pct) * delta_t
1245
- # TODO : delta_t in this case is actually the difference between storage and supply as this is a perfectly stratified tank
1246
-
1247
- return vol_above_supply / ideal_vol
1248
-
1249
- def _strat_pct_of_tank(
1250
- self,
1251
- on_fract: float,
1252
- on_temp_f: float,
1253
- strat_slope: float,
1254
- ) -> float:
1255
- """
1256
- Return the fraction of the *total* tank volume that is at or above
1257
- supply temperature when the ON sensor is at on_fract and reads
1258
- on_temp_f.
1259
-
1260
- This is the "percent of whole tank" form of the stratification factor:
1261
-
1262
- strat_pct = strat_factor * (1 - on_fract)
1263
-
1264
- Used in load-shift sizing where the effective storage band is the
1265
- difference between two aquastat levels expressed as fractions of the
1266
- entire tank volume, not just the volume above one aquastat.
1267
-
1268
- Parameters
1269
- ----------
1270
- on_fract : float
1271
- on_temp_f : float
1272
- strat_slope : float
1273
-
1274
- Returns
1275
- -------
1276
- float
1277
- """
1278
- return self._strat_factor_for_on_params(on_fract, on_temp_f, strat_slope) * (1.0 - on_fract)
1279
-
1280
1191
  @staticmethod
1281
1192
  def _is_load_shifting(control_map: dict[str, Controls] | None) -> bool:
1282
1193
  """Return True if the control_map has a ``"shed"`` key."""
@@ -1398,6 +1309,7 @@ class DHWSystem:
1398
1309
  Generation rate [gal/hr at supplyT].
1399
1310
  """
1400
1311
  daily_gal = building.daily_dhw_use_supplyT_gal
1312
+ inlet_temp_f = self._require_design_inlet_temp(building)
1401
1313
  normal_gen_rate_gph = daily_gal / self.max_daily_run_hr #TODO min of this and number of non-shed hours?
1402
1314
 
1403
1315
  _, load_up_hours = self._get_first_shed_block_and_load_up_hours(control_schedule)
@@ -1412,15 +1324,15 @@ class DHWSystem:
1412
1324
  lu_ctrl = control_map.get("loadUp", normal_ctrl)
1413
1325
  shed_ctrl = control_map["shed"]
1414
1326
 
1415
- normal_strat_pct = self._strat_pct_of_tank(
1416
- normal_ctrl.on_sensor_fract, normal_ctrl.on_trigger_t_f, strat_slope
1417
- )
1418
- lu_strat_pct = self._strat_pct_of_tank(
1419
- lu_ctrl.on_sensor_fract, lu_ctrl.on_trigger_t_f, strat_slope
1420
- )
1421
- shed_strat_pct = self._strat_pct_of_tank(
1422
- shed_ctrl.on_sensor_fract, shed_ctrl.on_trigger_t_f, strat_slope
1423
- )
1327
+ normal_strat_pct = self._calc_supply_temp_gal_from_100gal_tank(
1328
+ normal_ctrl.on_sensor_fract, normal_ctrl.on_trigger_t_f, strat_slope, inlet_temp_f
1329
+ ) / 100.0
1330
+ lu_strat_pct = self._calc_supply_temp_gal_from_100gal_tank(
1331
+ lu_ctrl.off_sensor_fract, lu_ctrl.off_trigger_t_f, strat_slope, inlet_temp_f
1332
+ ) / 100.0
1333
+ shed_strat_pct = self._calc_supply_temp_gal_from_100gal_tank(
1334
+ shed_ctrl.on_sensor_fract, shed_ctrl.on_trigger_t_f, strat_slope, inlet_temp_f
1335
+ ) / 100.0
1424
1336
 
1425
1337
  ls_band = lu_strat_pct - shed_strat_pct
1426
1338
  if ls_band <= 0:
@@ -585,6 +585,7 @@ class SwingSystem(RecircSystem):
585
585
  Returns the maximum.
586
586
  """
587
587
  daily_gal = building.daily_dhw_use_supplyT_gal
588
+ inlet_temp_f = self._require_design_inlet_temp(building)
588
589
  normal_gen_rate_gph = daily_gal * self._eff_mix_fraction / self.max_daily_run_hr
589
590
 
590
591
  _, load_up_hours = self._get_first_shed_block_and_load_up_hours(control_schedule)
@@ -598,10 +599,15 @@ class SwingSystem(RecircSystem):
598
599
  normal_ctrl = control_map["normal"]
599
600
  lu_ctrl = control_map.get("loadUp", normal_ctrl)
600
601
  shed_ctrl = control_map["shed"]
601
-
602
- lu_strat = self._strat_pct_of_tank(lu_ctrl.on_sensor_fract, lu_ctrl.on_trigger_t_f, strat_slope)
603
- normal_strat = self._strat_pct_of_tank(normal_ctrl.on_sensor_fract, normal_ctrl.on_trigger_t_f, strat_slope)
604
- shed_strat = self._strat_pct_of_tank(shed_ctrl.on_sensor_fract, shed_ctrl.on_trigger_t_f, strat_slope)
602
+ normal_strat = self._calc_supply_temp_gal_from_100gal_tank(
603
+ normal_ctrl.on_sensor_fract, normal_ctrl.on_trigger_t_f, strat_slope, inlet_temp_f
604
+ ) / 100.0
605
+ lu_strat = self._calc_supply_temp_gal_from_100gal_tank(
606
+ lu_ctrl.off_sensor_fract, lu_ctrl.off_trigger_t_f, strat_slope, inlet_temp_f
607
+ ) / 100.0
608
+ shed_strat = self._calc_supply_temp_gal_from_100gal_tank(
609
+ shed_ctrl.on_sensor_fract, shed_ctrl.on_trigger_t_f, strat_slope, inlet_temp_f
610
+ ) / 100.0
605
611
 
606
612
  ls_band = lu_strat - shed_strat
607
613
  if ls_band <= 0:
@@ -332,7 +332,16 @@ class SimulationRun:
332
332
 
333
333
  Columns: timestep, time_min, dhw_demand_supplyT_gal,
334
334
  usable_volume_supplyT_gal, heater_output_kbtuh, heater_power_in_kw,
335
- oat_f, inlet_water_temp_f, tank_temp_0pct, tank_temp_20pct, ..., tank_temp_100pct.
335
+ oat_f, inlet_water_temp_f, tank_temp_0pct, tank_temp_20pct, ...,
336
+ tank_temp_100pct, mode.
337
+
338
+ When applicable (non-empty for this run), also includes
339
+ tm_tank_temp_f, tm_heater_output_kbtuh, tm_heater_input_kw. These
340
+ columns cover the swing/TM tank for SwingSystem-family schematics,
341
+ the gas storage tank + gas heater for the in-series dual-fuel
342
+ schematics (SP_RTPInSeriesSystem, MP_RTPInSeriesSystem), and the gas
343
+ heater's output/input power for SP_RTPInParallelSystem (which has no
344
+ separate tank, so tm_tank_temp_f is omitted for that schematic).
336
345
 
337
346
  Parameters
338
347
  ----------
@@ -342,17 +351,31 @@ class SimulationRun:
342
351
  import csv as _csv
343
352
  n = len(self.dhw_demand_supplyT_gal)
344
353
  tank_col_names = [f"tank_temp_{int(f*100)}pct" for f in _TANK_NODE_FRACTS]
354
+
355
+ has_tm_temp = len(self.tm_tank_temp_f) == n
356
+ has_tm_output = len(self.tm_heater_output_kbtuh) == n
357
+ has_tm_kw = len(self.tm_heater_input_kw) == n
358
+
359
+ header = [
360
+ "timestep", "time_min",
361
+ "dhw_demand_supplyT_gal", "usable_volume_supplyT_gal",
362
+ "heater_output_kbtuh", "heater_power_in_kw",
363
+ "oat_f", "inlet_water_temp_f",
364
+ *tank_col_names,
365
+ "mode",
366
+ ]
367
+ if has_tm_temp:
368
+ header.append("tm_tank_temp_f")
369
+ if has_tm_output:
370
+ header.append("tm_heater_output_kbtuh")
371
+ if has_tm_kw:
372
+ header.append("tm_heater_input_kw")
373
+
345
374
  with open(filepath, "w", newline="") as f:
346
375
  writer = _csv.writer(f)
347
- writer.writerow([
348
- "timestep", "time_min",
349
- "dhw_demand_supplyT_gal", "usable_volume_supplyT_gal",
350
- "heater_output_kbtuh", "heater_power_in_kw",
351
- "oat_f", "inlet_water_temp_f",
352
- *tank_col_names,
353
- ])
376
+ writer.writerow(header)
354
377
  for i in range(n):
355
- writer.writerow([
378
+ row = [
356
379
  i,
357
380
  i * self.timestep_min,
358
381
  self.dhw_demand_supplyT_gal[i],
@@ -362,7 +385,15 @@ class SimulationRun:
362
385
  self.oat_f[i],
363
386
  self.inlet_water_temp_f[i],
364
387
  *(self.tank_temps_f[node][i] for node in range(len(_TANK_NODE_FRACTS))),
365
- ])
388
+ self.heater_mode[i],
389
+ ]
390
+ if has_tm_temp:
391
+ row.append(self.tm_tank_temp_f[i])
392
+ if has_tm_output:
393
+ row.append(self.tm_heater_output_kbtuh[i])
394
+ if has_tm_kw:
395
+ row.append(self.tm_heater_input_kw[i])
396
+ writer.writerow(row)
366
397
 
367
398
  def to_plotly(
368
399
  self,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ecoengine
3
- Version: 3.1.2
3
+ Version: 3.1.4
4
4
  Requires-Python: >=3.11
5
5
  Requires-Dist: numpy
6
6
  Requires-Dist: scipy
File without changes
File without changes