OpenSimula 0.7.1__tar.gz → 0.8.5__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.
- {opensimula-0.7.1 → opensimula-0.8.5}/LICENSE +1 -1
- {opensimula-0.7.1 → opensimula-0.8.5}/PKG-INFO +3 -5
- {opensimula-0.7.1 → opensimula-0.8.5}/README.md +0 -1
- {opensimula-0.7.1 → opensimula-0.8.5}/pyproject.toml +8 -5
- {opensimula-0.7.1/src/opensimula → opensimula-0.8.5/src/OpenSimula}/Component.py +32 -20
- {opensimula-0.7.1/src/opensimula → opensimula-0.8.5/src/OpenSimula}/Parameters.py +31 -5
- {opensimula-0.7.1/src/opensimula → opensimula-0.8.5/src/OpenSimula}/Project.py +90 -89
- {opensimula-0.7.1/src/opensimula → opensimula-0.8.5/src/OpenSimula}/Simulation.py +17 -72
- {opensimula-0.7.1/src/opensimula → opensimula-0.8.5/src/OpenSimula}/__init__.py +2 -1
- {opensimula-0.7.1/src/opensimula → opensimula-0.8.5/src/OpenSimula}/components/Building_surface.py +41 -1
- opensimula-0.8.5/src/OpenSimula/components/Chiller_heat_pump.py +155 -0
- {opensimula-0.7.1/src/opensimula → opensimula-0.8.5/src/OpenSimula}/components/Construction.py +5 -3
- {opensimula-0.7.1/src/opensimula → opensimula-0.8.5/src/OpenSimula}/components/File_data.py +7 -3
- {opensimula-0.7.1/src/opensimula → opensimula-0.8.5/src/OpenSimula}/components/HVAC_MZW_system.py +39 -18
- {opensimula-0.7.1/src/opensimula → opensimula-0.8.5/src/OpenSimula}/components/HVAC_SZW_system.py +129 -42
- opensimula-0.8.5/src/OpenSimula/components/HVAC_water_system.py +314 -0
- {opensimula-0.7.1/src/opensimula → opensimula-0.8.5/src/OpenSimula}/components/Opening.py +2 -1
- {opensimula-0.7.1/src/opensimula → opensimula-0.8.5/src/OpenSimula}/components/Opening_type.py +3 -3
- opensimula-0.8.5/src/OpenSimula/components/Pump.py +50 -0
- {opensimula-0.7.1/src/opensimula → opensimula-0.8.5/src/OpenSimula}/components/Space.py +294 -127
- {opensimula-0.7.1/src/opensimula → opensimula-0.8.5/src/OpenSimula}/components/Surface.py +14 -0
- {opensimula-0.7.1/src/opensimula → opensimula-0.8.5/src/OpenSimula}/components/Water_coil.py +11 -9
- {opensimula-0.7.1/src/opensimula → opensimula-0.8.5/src/OpenSimula}/components/__init__.py +4 -0
- opensimula-0.8.5/src/OpenSimula/editor/__init__.py +19 -0
- opensimula-0.8.5/src/OpenSimula/editor/schema.py +229 -0
- opensimula-0.8.5/src/OpenSimula/editor/static/editor.css +388 -0
- opensimula-0.8.5/src/OpenSimula/editor/static/editor.js +1047 -0
- opensimula-0.8.5/src/OpenSimula/editor/validation.py +91 -0
- opensimula-0.8.5/src/OpenSimula/editor/widget.py +246 -0
- {opensimula-0.7.1/src/opensimula → opensimula-0.8.5/src/OpenSimula}/visual_3D/Environment_3D.py +113 -28
- opensimula-0.8.5/src/OpenSimula/visual_3D/Polygon_3D.py +370 -0
- {opensimula-0.7.1/src/opensimula.egg-info → opensimula-0.8.5/src/OpenSimula.egg-info}/PKG-INFO +3 -5
- opensimula-0.8.5/src/OpenSimula.egg-info/SOURCES.txt +113 -0
- {opensimula-0.7.1/src/opensimula.egg-info → opensimula-0.8.5/src/OpenSimula.egg-info}/requires.txt +2 -3
- {opensimula-0.7.1 → opensimula-0.8.5}/test/test_Case_600FF.py +4 -3
- {opensimula-0.7.1 → opensimula-0.8.5}/test/test_Case_610.py +5 -4
- {opensimula-0.7.1 → opensimula-0.8.5}/test/test_HVAC_DX_system.py +13 -12
- opensimula-0.8.5/test/test_HVAC_FAN_COIL_water_system.py +401 -0
- opensimula-0.8.5/test/test_HVAC_water_system_process.py +250 -0
- {opensimula-0.7.1 → opensimula-0.8.5}/test/test_Project.py +43 -0
- opensimula-0.8.5/test/test_editor_schema.py +202 -0
- opensimula-0.8.5/test/test_editor_widget.py +513 -0
- opensimula-0.8.5/test/test_underground_repeated_simulation.py +202 -0
- opensimula-0.7.1/src/opensimula/visual_3D/Polygon_3D.py +0 -257
- opensimula-0.7.1/src/opensimula.egg-info/SOURCES.txt +0 -58
- {opensimula-0.7.1 → opensimula-0.8.5}/setup.cfg +0 -0
- {opensimula-0.7.1/src/opensimula → opensimula-0.8.5/src/OpenSimula}/Child.py +0 -0
- {opensimula-0.7.1/src/opensimula → opensimula-0.8.5/src/OpenSimula}/Iterative_process.py +0 -0
- {opensimula-0.7.1/src/opensimula → opensimula-0.8.5/src/OpenSimula}/Message.py +0 -0
- {opensimula-0.7.1/src/opensimula → opensimula-0.8.5/src/OpenSimula}/Parameter_container.py +0 -0
- {opensimula-0.7.1/src/opensimula → opensimula-0.8.5/src/OpenSimula}/Variable.py +0 -0
- {opensimula-0.7.1/src/opensimula → opensimula-0.8.5/src/OpenSimula}/components/Building.py +0 -0
- {opensimula-0.7.1/src/opensimula → opensimula-0.8.5/src/OpenSimula}/components/Calculator.py +0 -0
- {opensimula-0.7.1/src/opensimula → opensimula-0.8.5/src/OpenSimula}/components/DX_unit.py +0 -0
- {opensimula-0.7.1/src/opensimula → opensimula-0.8.5/src/OpenSimula}/components/Day_schedule.py +0 -0
- {opensimula-0.7.1/src/opensimula → opensimula-0.8.5/src/OpenSimula}/components/Fan.py +0 -0
- {opensimula-0.7.1/src/opensimula → opensimula-0.8.5/src/OpenSimula}/components/File_met.py +0 -0
- {opensimula-0.7.1/src/opensimula → opensimula-0.8.5/src/OpenSimula}/components/Frame.py +0 -0
- {opensimula-0.7.1/src/opensimula → opensimula-0.8.5/src/OpenSimula}/components/Glazing.py +0 -0
- {opensimula-0.7.1/src/opensimula → opensimula-0.8.5/src/OpenSimula}/components/HVAC_DX_system.py +0 -0
- {opensimula-0.7.1/src/opensimula → opensimula-0.8.5/src/OpenSimula}/components/HVAC_perfect_system.py +0 -0
- {opensimula-0.7.1/src/opensimula → opensimula-0.8.5/src/OpenSimula}/components/Material.py +0 -0
- {opensimula-0.7.1/src/opensimula → opensimula-0.8.5/src/OpenSimula}/components/Solar_surface.py +0 -0
- {opensimula-0.7.1/src/opensimula → opensimula-0.8.5/src/OpenSimula}/components/Space_type.py +0 -0
- {opensimula-0.7.1/src/opensimula → opensimula-0.8.5/src/OpenSimula}/components/Test_component.py +0 -0
- {opensimula-0.7.1/src/opensimula → opensimula-0.8.5/src/OpenSimula}/components/Week_schedule.py +0 -0
- {opensimula-0.7.1/src/opensimula → opensimula-0.8.5/src/OpenSimula}/components/Year_schedule.py +0 -0
- {opensimula-0.7.1/src/opensimula.egg-info → opensimula-0.8.5/src/OpenSimula.egg-info}/dependency_links.txt +0 -0
- {opensimula-0.7.1/src/opensimula.egg-info → opensimula-0.8.5/src/OpenSimula.egg-info}/top_level.txt +0 -0
- {opensimula-0.7.1 → opensimula-0.8.5}/test/test_Calculator.py +0 -0
- {opensimula-0.7.1 → opensimula-0.8.5}/test/test_Construction.py +0 -0
- {opensimula-0.7.1 → opensimula-0.8.5}/test/test_File_data.py +0 -0
- {opensimula-0.7.1 → opensimula-0.8.5}/test/test_File_met.py +0 -0
- {opensimula-0.7.1 → opensimula-0.8.5}/test/test_HVAC_DX_Equipment.py +0 -0
- {opensimula-0.7.1 → opensimula-0.8.5}/test/test_Iterative_process.py +0 -0
- {opensimula-0.7.1 → opensimula-0.8.5}/test/test_Schedules.py +0 -0
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: opensimula
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.5
|
|
4
4
|
Summary: Component-based time simulation environment in Python
|
|
5
5
|
Author-email: "Juan F. Coronel" <jfc@us.es>
|
|
6
6
|
Requires-Python: >=3.12
|
|
7
7
|
Description-Content-Type: text/markdown
|
|
8
8
|
License-File: LICENSE
|
|
9
|
-
Requires-Dist:
|
|
10
|
-
Requires-Dist:
|
|
11
|
-
Requires-Dist: dash-bootstrap-components>=2.0.4
|
|
9
|
+
Requires-Dist: anywidget>=0.11.0
|
|
10
|
+
Requires-Dist: jsonschema>=4.26.0
|
|
12
11
|
Requires-Dist: nbformat>=5.10.4
|
|
13
12
|
Requires-Dist: numpy>=2.3.3
|
|
14
13
|
Requires-Dist: openpyxl>=3.1.5
|
|
@@ -43,5 +42,4 @@ Component-based time simulation environment in Python
|
|
|
43
42
|
- nbformat
|
|
44
43
|
- openpyxl
|
|
45
44
|
- tqdm
|
|
46
|
-
- triangle
|
|
47
45
|
- vedo
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "opensimula"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.8.5"
|
|
4
4
|
requires-python = ">=3.12"
|
|
5
5
|
authors = [
|
|
6
6
|
{ name="Juan F. Coronel", email="jfc@us.es" },
|
|
@@ -8,9 +8,8 @@ authors = [
|
|
|
8
8
|
description = "Component-based time simulation environment in Python"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
dependencies = [
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"dash-bootstrap-components>=2.0.4",
|
|
11
|
+
"anywidget>=0.11.0",
|
|
12
|
+
"jsonschema>=4.26.0",
|
|
14
13
|
"nbformat>=5.10.4",
|
|
15
14
|
"numpy>=2.3.3",
|
|
16
15
|
"openpyxl>=3.1.5",
|
|
@@ -20,7 +19,7 @@ dependencies = [
|
|
|
20
19
|
"scipy>=1.16.2",
|
|
21
20
|
"shapely>=2.1.1",
|
|
22
21
|
"tqdm>=4.67.1",
|
|
23
|
-
"vedo>=2025.5.4",
|
|
22
|
+
"vedo>=2025.5.4",
|
|
24
23
|
]
|
|
25
24
|
|
|
26
25
|
[dependency-groups]
|
|
@@ -31,6 +30,10 @@ dev = [
|
|
|
31
30
|
"marimo>=0.19.0",
|
|
32
31
|
]
|
|
33
32
|
|
|
33
|
+
[tool.setuptools.package-data]
|
|
34
|
+
# The widget loads these at runtime; without this they are left out of the wheel.
|
|
35
|
+
"opensimula.editor" = ["static/*.js", "static/*.css"]
|
|
36
|
+
|
|
34
37
|
[build-system]
|
|
35
38
|
requires = ["setuptools>=61.2", "wheel"]
|
|
36
39
|
|
|
@@ -38,7 +38,7 @@ class Component(Parameter_container):
|
|
|
38
38
|
|
|
39
39
|
Args:
|
|
40
40
|
units (bool, optional): Includes unit in the name of the variable. Defaults to True.
|
|
41
|
-
frequency (None or str, optional): frequency of the values: None, "
|
|
41
|
+
frequency (None or str, optional): frequency of the values: None, "hourly", "daily", "monthly", "yearly". Defaults to None.
|
|
42
42
|
value (str, optional): "mean", "sum", "sum_pos", "sum_neg", "max" or "min". Defaults to "mean".
|
|
43
43
|
interval (None or list of two dates): List with the start and end dates of the period to be included in the dataframe, if the value is None all values are included.
|
|
44
44
|
pos_neg_columns (list of str, optional): List of variables that will be included in separate columns positive and negative values. Defaults to [].
|
|
@@ -62,14 +62,19 @@ class Component(Parameter_container):
|
|
|
62
62
|
data[col + "_pos"] = data[col].apply(lambda x: x if x > 0 else 0)
|
|
63
63
|
data[col + "_neg"] = data[col].apply(lambda x: x if x < 0 else 0)
|
|
64
64
|
if frequency is not None:
|
|
65
|
+
freq={"hourly": "h", "daily": "D", "monthly": "ME", "yearly": "YE"}
|
|
65
66
|
if value == "mean":
|
|
66
|
-
data = data.resample(frequency, on='date').mean()
|
|
67
|
+
data = data.resample(freq[frequency], on='date').mean()
|
|
67
68
|
elif value == "sum":
|
|
68
|
-
data = data.resample(frequency, on='date').sum()
|
|
69
|
+
data = data.resample(freq[frequency], on='date').sum()
|
|
70
|
+
elif value == "sum_pos":
|
|
71
|
+
data = data.resample(freq[frequency], on='date').apply(lambda x: x.clip(lower=0).sum())
|
|
72
|
+
elif value == "sum_neg":
|
|
73
|
+
data = data.resample(freq[frequency], on='date').apply(lambda x: x.clip(upper=0).sum())
|
|
69
74
|
elif value == "max":
|
|
70
|
-
data = data.resample(frequency, on='date').max()
|
|
75
|
+
data = data.resample(freq[frequency], on='date').max()
|
|
71
76
|
elif value == "min":
|
|
72
|
-
data = data.resample(frequency, on='date').min()
|
|
77
|
+
data = data.resample(freq[frequency], on='date').min()
|
|
73
78
|
if interval is not None:
|
|
74
79
|
data = data[(data['date'] > interval[0]) &
|
|
75
80
|
(data['date'] < interval[1])]
|
|
@@ -77,36 +82,43 @@ class Component(Parameter_container):
|
|
|
77
82
|
|
|
78
83
|
# ____________ Functions that must be overwriten for time simulation _________________
|
|
79
84
|
|
|
80
|
-
def get_all_referenced_components(self):
|
|
85
|
+
def get_all_referenced_components(self, _seen=None):
|
|
81
86
|
"""Get list of all referenced components, first itself. Look recursively at the referenced components
|
|
82
87
|
|
|
88
|
+
Args:
|
|
89
|
+
_seen (set, optional): components already walked, used internally.
|
|
90
|
+
References can form a cycle, if only because a wrong name gets
|
|
91
|
+
typed into a reference, and without remembering where it has
|
|
92
|
+
been the walk follows it until the stack runs out.
|
|
93
|
+
|
|
83
94
|
Returns:
|
|
84
95
|
component_list (component[])
|
|
85
96
|
"""
|
|
97
|
+
if _seen is None:
|
|
98
|
+
_seen = set()
|
|
99
|
+
if id(self) in _seen:
|
|
100
|
+
return []
|
|
101
|
+
_seen.add(id(self))
|
|
102
|
+
|
|
86
103
|
comp_list = []
|
|
104
|
+
|
|
105
|
+
def walk(component):
|
|
106
|
+
if component is not None:
|
|
107
|
+
comp_list.extend(component.get_all_referenced_components(_seen))
|
|
108
|
+
|
|
87
109
|
for key, value in self.parameter_dict().items():
|
|
88
110
|
if value.type == "Parameter_component":
|
|
89
|
-
|
|
90
|
-
sublist = value.component.get_all_referenced_components()
|
|
91
|
-
for subcomp in sublist:
|
|
92
|
-
comp_list.append(subcomp)
|
|
111
|
+
walk(value.component)
|
|
93
112
|
elif value.type == "Parameter_component_list":
|
|
94
113
|
for comp in value.component:
|
|
95
|
-
|
|
96
|
-
sublist = comp.get_all_referenced_components()
|
|
97
|
-
for subcomp in sublist:
|
|
98
|
-
comp_list.append(subcomp)
|
|
114
|
+
walk(comp)
|
|
99
115
|
if value.type == "Parameter_variable":
|
|
100
116
|
if value.variable is not None:
|
|
101
|
-
|
|
102
|
-
for subcomp in sublist:
|
|
103
|
-
comp_list.append(subcomp)
|
|
117
|
+
walk(value.variable.parent)
|
|
104
118
|
elif value.type == "Parameter_variable_list":
|
|
105
119
|
for var in value.variable:
|
|
106
120
|
if var is not None:
|
|
107
|
-
|
|
108
|
-
for subcomp in sublist:
|
|
109
|
-
comp_list.append(subcomp)
|
|
121
|
+
walk(var.parent)
|
|
110
122
|
comp_list.append(self)
|
|
111
123
|
return comp_list
|
|
112
124
|
|
|
@@ -184,7 +184,9 @@ class Parameter_string_list(Parameter):
|
|
|
184
184
|
else:
|
|
185
185
|
for el in value:
|
|
186
186
|
el = str(el)
|
|
187
|
-
|
|
187
|
+
# Own copy: keeping the caller list would let it be mutated from
|
|
188
|
+
# outside, and would be shared by every parameter assigned from it
|
|
189
|
+
self._value_ = list(value)
|
|
188
190
|
|
|
189
191
|
|
|
190
192
|
# _____________ Parameter_int ___________________________
|
|
@@ -201,6 +203,14 @@ class Parameter_int(Parameter):
|
|
|
201
203
|
def unit(self):
|
|
202
204
|
return self._unit_
|
|
203
205
|
|
|
206
|
+
@property
|
|
207
|
+
def min(self):
|
|
208
|
+
return self._min_
|
|
209
|
+
|
|
210
|
+
@property
|
|
211
|
+
def max(self):
|
|
212
|
+
return self._max_
|
|
213
|
+
|
|
204
214
|
@property
|
|
205
215
|
def value(self):
|
|
206
216
|
return self._value_
|
|
@@ -235,6 +245,14 @@ class Parameter_int_list(Parameter):
|
|
|
235
245
|
def unit(self):
|
|
236
246
|
return self._unit_
|
|
237
247
|
|
|
248
|
+
@property
|
|
249
|
+
def min(self):
|
|
250
|
+
return self._min_
|
|
251
|
+
|
|
252
|
+
@property
|
|
253
|
+
def max(self):
|
|
254
|
+
return self._max_
|
|
255
|
+
|
|
238
256
|
@property
|
|
239
257
|
def value(self):
|
|
240
258
|
return self._value_
|
|
@@ -389,7 +407,9 @@ class Parameter_options_list(Parameter):
|
|
|
389
407
|
else:
|
|
390
408
|
for el in value:
|
|
391
409
|
el = str(el)
|
|
392
|
-
|
|
410
|
+
# Own copy: keeping the caller list would let it be mutated from
|
|
411
|
+
# outside, and would be shared by every parameter assigned from it
|
|
412
|
+
self._value_ = list(value)
|
|
393
413
|
|
|
394
414
|
@property
|
|
395
415
|
def options(self):
|
|
@@ -484,7 +504,9 @@ class Parameter_component_list(Parameter):
|
|
|
484
504
|
# Is a list
|
|
485
505
|
for el in value:
|
|
486
506
|
el = str(el)
|
|
487
|
-
|
|
507
|
+
# Own copy: keeping the caller list would let it be mutated from
|
|
508
|
+
# outside, and would be shared by every parameter assigned from it
|
|
509
|
+
self._value_ = list(value)
|
|
488
510
|
|
|
489
511
|
self._external_ = []
|
|
490
512
|
for el in self.value:
|
|
@@ -622,7 +644,9 @@ class Parameter_variable_list(Parameter):
|
|
|
622
644
|
# Is a list
|
|
623
645
|
for el in value:
|
|
624
646
|
el = str(el)
|
|
625
|
-
|
|
647
|
+
# Own copy: keeping the caller list would let it be mutated from
|
|
648
|
+
# outside, and would be shared by every parameter assigned from it
|
|
649
|
+
self._value_ = list(value)
|
|
626
650
|
|
|
627
651
|
format_error = False
|
|
628
652
|
self._symbol_ = []
|
|
@@ -744,7 +768,9 @@ class Parameter_math_exp_list(Parameter):
|
|
|
744
768
|
# Is a list
|
|
745
769
|
for el in value:
|
|
746
770
|
el = str(el)
|
|
747
|
-
|
|
771
|
+
# Own copy: keeping the caller list would let it be mutated from
|
|
772
|
+
# outside, and would be shared by every parameter assigned from it
|
|
773
|
+
self._value_ = list(value)
|
|
748
774
|
|
|
749
775
|
def evaluate(self, i, values_dic):
|
|
750
776
|
try:
|
|
@@ -2,9 +2,6 @@ import json
|
|
|
2
2
|
import datetime as dt
|
|
3
3
|
import numpy as np
|
|
4
4
|
import pandas as pd
|
|
5
|
-
from dash import Dash, callback, Input, Output, html, State
|
|
6
|
-
import dash_ag_grid as dag
|
|
7
|
-
import dash_bootstrap_components as dbc
|
|
8
5
|
import psychrolib as sicro
|
|
9
6
|
from tqdm import trange
|
|
10
7
|
from opensimula.Message import Message
|
|
@@ -59,13 +56,14 @@ class Project(Parameter_container):
|
|
|
59
56
|
)
|
|
60
57
|
self.add_parameter(
|
|
61
58
|
Parameter_options(
|
|
62
|
-
"shadow_calculation", "
|
|
59
|
+
"shadow_calculation", "INTERPOLATION", ["NO", "INSTANT", "INTERPOLATION"]
|
|
63
60
|
)
|
|
64
61
|
)
|
|
65
62
|
self.add_parameter(Parameter_float("albedo", 0.2, "frac", min=0, max=1))
|
|
66
63
|
|
|
67
64
|
self._sim_ = sim
|
|
68
65
|
self._components_ = []
|
|
66
|
+
self._components_dict_ = {} # name -> component index, see component()
|
|
69
67
|
sicro.SetUnitSystem(sicro.SI)
|
|
70
68
|
atm_p = sicro.GetStandardAtmPressure(0)
|
|
71
69
|
w_50 = sicro.GetHumRatioFromRelHum(22.5, 0.5, atm_p)
|
|
@@ -93,6 +91,18 @@ class Project(Parameter_container):
|
|
|
93
91
|
"RHOCP_W": rhocp_water,
|
|
94
92
|
}
|
|
95
93
|
|
|
94
|
+
def clear(self):
|
|
95
|
+
"""Remove every component from the Project
|
|
96
|
+
|
|
97
|
+
The parameters of the project itself keep their values; only its
|
|
98
|
+
components are dropped. Needed before reloading a definition into a
|
|
99
|
+
project that already holds one, because read_dict() and read_json()
|
|
100
|
+
add components, they do not replace them.
|
|
101
|
+
"""
|
|
102
|
+
self._components_ = []
|
|
103
|
+
self._components_dict_ = {}
|
|
104
|
+
self._ordered_component_list_ = []
|
|
105
|
+
|
|
96
106
|
def del_component(self, component):
|
|
97
107
|
"""Delete component from Project
|
|
98
108
|
|
|
@@ -100,6 +110,13 @@ class Project(Parameter_container):
|
|
|
100
110
|
component (Component): Component to be removed from the project
|
|
101
111
|
"""
|
|
102
112
|
self._components_.remove(component)
|
|
113
|
+
# Drop every index entry pointing at it, so a later component reusing
|
|
114
|
+
# its name cannot resolve to the deleted one.
|
|
115
|
+
self._components_dict_ = {
|
|
116
|
+
key: comp
|
|
117
|
+
for key, comp in self._components_dict_.items()
|
|
118
|
+
if comp is not component
|
|
119
|
+
}
|
|
103
120
|
|
|
104
121
|
def component(self, name):
|
|
105
122
|
"""Find and return component with its name
|
|
@@ -110,8 +127,16 @@ class Project(Parameter_container):
|
|
|
110
127
|
Returns:
|
|
111
128
|
component (Component): component found, None if not found.
|
|
112
129
|
"""
|
|
130
|
+
# Components are looked up by name millions of times during a
|
|
131
|
+
# simulation, so hits are served from an index. The cached component is
|
|
132
|
+
# revalidated against its current name because components can be
|
|
133
|
+
# renamed after being indexed; a stale entry falls back to the scan.
|
|
134
|
+
comp = self._components_dict_.get(name)
|
|
135
|
+
if comp is not None and comp.parameter("name").value == name:
|
|
136
|
+
return comp
|
|
113
137
|
for comp in self._components_:
|
|
114
138
|
if comp.parameter("name").value == name:
|
|
139
|
+
self._components_dict_[name] = comp
|
|
115
140
|
return comp
|
|
116
141
|
return None
|
|
117
142
|
|
|
@@ -586,100 +611,76 @@ class Project(Parameter_container):
|
|
|
586
611
|
def simulation_dataframe(self):
|
|
587
612
|
return self._sim_df
|
|
588
613
|
|
|
589
|
-
def
|
|
590
|
-
editor
|
|
591
|
-
df = self.component_dataframe(comp_type=comp_type, string_format=True)
|
|
592
|
-
self._n_clicks_new_comp_ = 0
|
|
593
|
-
self._n_clicks_del_comp_ = 0
|
|
594
|
-
disabled_new = False
|
|
595
|
-
if comp_type == "all":
|
|
596
|
-
disabled_new = True
|
|
597
|
-
|
|
598
|
-
column_definition = [
|
|
599
|
-
{
|
|
600
|
-
"field": "name",
|
|
601
|
-
"checkboxSelection": True,
|
|
602
|
-
"headerCheckboxSelection": True,
|
|
603
|
-
}
|
|
604
|
-
]
|
|
605
|
-
for i in df.columns:
|
|
606
|
-
if i != "name":
|
|
607
|
-
column_definition.append({"field": i})
|
|
608
|
-
|
|
609
|
-
editor.layout = html.Div(
|
|
610
|
-
[
|
|
611
|
-
dbc.Label("Components editor:"),
|
|
612
|
-
html.Br(),
|
|
613
|
-
dbc.Button(
|
|
614
|
-
"New component",
|
|
615
|
-
id="btn-new-comp",
|
|
616
|
-
disabled=disabled_new,
|
|
617
|
-
n_clicks=0,
|
|
618
|
-
),
|
|
619
|
-
dbc.Button(
|
|
620
|
-
"Delete selected components",
|
|
621
|
-
id="btn-del-comp",
|
|
622
|
-
n_clicks=0,
|
|
623
|
-
style={"margin-left": "15px"},
|
|
624
|
-
),
|
|
625
|
-
html.Br(),
|
|
626
|
-
html.Br(),
|
|
627
|
-
dag.AgGrid(
|
|
628
|
-
id="comp-table",
|
|
629
|
-
rowData=df.to_dict("records"),
|
|
630
|
-
columnDefs=column_definition,
|
|
631
|
-
columnSize="sizeToFit",
|
|
632
|
-
defaultColDef={"filter": True, "editable": True},
|
|
633
|
-
style={"height": "500px"},
|
|
634
|
-
dashGridOptions={
|
|
635
|
-
"rowSelection": "multiple",
|
|
636
|
-
"suppressRowClickSelection": True,
|
|
637
|
-
"pagination": True,
|
|
638
|
-
},
|
|
639
|
-
),
|
|
640
|
-
]
|
|
641
|
-
)
|
|
614
|
+
def editor(self):
|
|
615
|
+
"""Interactive editor for the whole project definition.
|
|
642
616
|
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
Input("btn-new-comp", "n_clicks"),
|
|
647
|
-
Input("btn-del-comp", "n_clicks"),
|
|
648
|
-
State("comp-table", "selectedRows"),
|
|
649
|
-
prevent_initial_call=True,
|
|
650
|
-
)
|
|
651
|
-
def update_data(changed, n_clicks_new, n_clicks_del, selectedRows):
|
|
652
|
-
if self._n_clicks_new_comp_ < n_clicks_new:
|
|
653
|
-
self.new_component(comp_type, "new_comp_" + str(n_clicks_new))
|
|
654
|
-
self._n_clicks_new_comp_ = n_clicks_new
|
|
655
|
-
elif self._n_clicks_del_comp_ < n_clicks_del:
|
|
656
|
-
for row in selectedRows:
|
|
657
|
-
self.del_component(self.component(row["name"]))
|
|
658
|
-
self._n_clicks_del_comp_ = n_clicks_del
|
|
659
|
-
else:
|
|
660
|
-
if changed is not None:
|
|
661
|
-
if changed[0]["colId"] == "name":
|
|
662
|
-
self.component(changed[0]["oldValue"]).parameter(
|
|
663
|
-
"name"
|
|
664
|
-
).value = changed[0]["value"]
|
|
665
|
-
else:
|
|
666
|
-
self.component(changed[0]["data"]["name"]).parameter(
|
|
667
|
-
changed[0]["colId"]
|
|
668
|
-
).value = changed[0]["value"]
|
|
669
|
-
df_end = self.component_dataframe(comp_type=comp_type, string_format=True)
|
|
670
|
-
return df_end.to_dict("records")
|
|
617
|
+
Returns an anywidget that works in Jupyter and, wrapped in
|
|
618
|
+
mo.ui.anywidget(), in Marimo. The document is read with write_dict();
|
|
619
|
+
the widget does not write back to this project.
|
|
671
620
|
|
|
672
|
-
|
|
621
|
+
Returns:
|
|
622
|
+
ProjectEditor: widget holding the project document in .value
|
|
623
|
+
"""
|
|
624
|
+
# Imported here: opensimula.editor reaches Project through Simulation,
|
|
625
|
+
# so a module level import would close the cycle.
|
|
626
|
+
from opensimula.editor import ProjectEditor
|
|
627
|
+
|
|
628
|
+
return ProjectEditor(self)
|
|
673
629
|
|
|
674
630
|
def create_3D_environment(self, env_3D):
|
|
675
631
|
for component in self.component_list("all"):
|
|
676
|
-
if hasattr(component, "get_polygon_3D"):
|
|
677
|
-
|
|
632
|
+
if not hasattr(component, "get_polygon_3D"):
|
|
633
|
+
continue
|
|
634
|
+
try:
|
|
635
|
+
polygon = component.get_polygon_3D()
|
|
636
|
+
except (AttributeError, IndexError, TypeError):
|
|
637
|
+
# A surface whose space, or an opening whose surface, does not
|
|
638
|
+
# resolve has no place to be. It is left out rather than
|
|
639
|
+
# bringing the whole view down; check() names the reference.
|
|
640
|
+
continue
|
|
641
|
+
self._set_polygon_origin_(polygon, component)
|
|
642
|
+
env_3D.add_polygon_3D(polygon)
|
|
643
|
+
|
|
644
|
+
def _set_polygon_origin_(self, polygon, component):
|
|
645
|
+
"""Record which component a polygon came from, and which spaces it bounds.
|
|
646
|
+
|
|
647
|
+
Polygon_3D is geometry and knows nothing of the model, so the link is
|
|
648
|
+
made here, where the component is still at hand. It is what lets a
|
|
649
|
+
viewer show one space at a time; an interior partition belongs to the
|
|
650
|
+
two spaces on either side of it.
|
|
651
|
+
"""
|
|
652
|
+
polygon.component = component.parameter("name").value
|
|
653
|
+
polygon.component_type = component.parameter("type").value
|
|
654
|
+
|
|
655
|
+
surface = component
|
|
656
|
+
if polygon.component_type == "Opening":
|
|
657
|
+
surface = component.get_surface()
|
|
658
|
+
if surface is None:
|
|
659
|
+
return
|
|
660
|
+
|
|
661
|
+
parameters = surface.parameter_dict()
|
|
662
|
+
if "surface_type" in parameters:
|
|
663
|
+
polygon.surface_type = parameters["surface_type"].value
|
|
664
|
+
if "spaces" in parameters:
|
|
665
|
+
polygon.spaces = [
|
|
666
|
+
name for name in parameters["spaces"].value if name != "not_defined"
|
|
667
|
+
]
|
|
678
668
|
|
|
669
|
+
def geometry_dict(self):
|
|
670
|
+
"""Geometry of the project as plain data, for a 3D viewer
|
|
671
|
+
|
|
672
|
+
Returns:
|
|
673
|
+
dict: {"meshes": [...], "spaces": [...]}, one mesh per polygon with
|
|
674
|
+
its triangles and the component and spaces it belongs to
|
|
675
|
+
"""
|
|
676
|
+
env_3D = Environment_3D()
|
|
677
|
+
self.create_3D_environment(env_3D)
|
|
678
|
+
return env_3D.geometry_dict()
|
|
679
|
+
|
|
679
680
|
def show_3D(self, jupyter=False):
|
|
680
681
|
env_3D = Environment_3D()
|
|
681
682
|
self.create_3D_environment(env_3D)
|
|
682
|
-
env_3D.show(polygons_type="initial", jupyter=jupyter)
|
|
683
|
+
return env_3D.show(polygons_type="initial", jupyter=jupyter)
|
|
683
684
|
|
|
684
685
|
def show_3D_shadows(self, date, jupyter=False):
|
|
685
686
|
env_3D = Environment_3D()
|
|
@@ -2,9 +2,6 @@ from opensimula.Project import Project
|
|
|
2
2
|
import pandas as pd
|
|
3
3
|
import plotly.express as px
|
|
4
4
|
from plotly.subplots import make_subplots
|
|
5
|
-
from dash import Dash, callback, Input, Output, html, State
|
|
6
|
-
import dash_ag_grid as dag
|
|
7
|
-
import dash_bootstrap_components as dbc
|
|
8
5
|
|
|
9
6
|
|
|
10
7
|
class Simulation:
|
|
@@ -96,18 +93,17 @@ class Simulation:
|
|
|
96
93
|
"""Return the list of messages"""
|
|
97
94
|
return self._messages_
|
|
98
95
|
|
|
99
|
-
def plot(self, dates, variables, names=[], axis=[], frequency=None, value="mean"):
|
|
96
|
+
def plot(self, dates, variables, names=[], axis=[], frequency=None, value="mean",interval=None):
|
|
100
97
|
"""_summary_
|
|
101
98
|
Draw variables graph (using plotly)
|
|
102
99
|
|
|
103
100
|
Args:
|
|
104
101
|
variables: List of hourly variables
|
|
105
102
|
axis: list of axis y 1 or 2 to use for each variable, empty all in first axis
|
|
106
|
-
frequency (None or str, optional): frequency of the values: None, "
|
|
107
|
-
value (str, optional): "mean", "sum", "max" or "min". Defaults to "mean".
|
|
108
|
-
|
|
103
|
+
frequency (None or str, optional): frequency of the values: None, "hourly", "daily", "monthly", "yearly". Defaults to None.
|
|
104
|
+
value (str, optional): "mean", "sum", "sum_pos", "sum_neg", "max" or "min". Defaults to "mean".
|
|
105
|
+
interval (None or list of two dates): List with the start and end dates of the period to be included in the dataframe, if the value is None all values are included.
|
|
109
106
|
"""
|
|
110
|
-
|
|
111
107
|
series = {}
|
|
112
108
|
series["date"] = dates
|
|
113
109
|
for i in range(len(variables)):
|
|
@@ -121,15 +117,24 @@ class Simulation:
|
|
|
121
117
|
series[variables[i].key] = variables[i].values
|
|
122
118
|
data = pd.DataFrame(series)
|
|
123
119
|
if frequency is not None:
|
|
120
|
+
freq={"hourly": "h", "daily": "D", "monthly": "ME", "yearly": "YE"}
|
|
124
121
|
if value == "mean":
|
|
125
|
-
data = data.resample(frequency, on='date').mean()
|
|
122
|
+
data = data.resample(freq[frequency], on='date').mean()
|
|
126
123
|
elif value == "sum":
|
|
127
|
-
data = data.resample(frequency, on='date').sum()
|
|
124
|
+
data = data.resample(freq[frequency], on='date').sum()
|
|
125
|
+
elif value == "sum_pos":
|
|
126
|
+
data = data.resample(freq[frequency], on='date').apply(lambda x: x.clip(lower=0).sum())
|
|
127
|
+
elif value == "sum_neg":
|
|
128
|
+
data = data.resample(freq[frequency], on='date').apply(lambda x: x.clip(upper=0).sum())
|
|
128
129
|
elif value == "max":
|
|
129
|
-
data = data.resample(frequency, on='date').max()
|
|
130
|
+
data = data.resample(freq[frequency], on='date').max()
|
|
130
131
|
elif value == "min":
|
|
131
|
-
data = data.resample(frequency, on='date').min()
|
|
132
|
+
data = data.resample(freq[frequency], on='date').min()
|
|
132
133
|
data["date"] = data.index
|
|
134
|
+
if interval is not None:
|
|
135
|
+
data = data[(data['date'] > interval[0]) &
|
|
136
|
+
(data['date'] < interval[1])]
|
|
137
|
+
|
|
133
138
|
|
|
134
139
|
subfig = make_subplots(specs=[[{"secondary_y": True}]])
|
|
135
140
|
|
|
@@ -155,63 +160,3 @@ class Simulation:
|
|
|
155
160
|
# fig.update_traces(showlegend=True)
|
|
156
161
|
subfig.show()
|
|
157
162
|
|
|
158
|
-
def project_editor(self):
|
|
159
|
-
editor = Dash(external_stylesheets=[dbc.themes.BOOTSTRAP])
|
|
160
|
-
df = self.project_dataframe(string_format=True)
|
|
161
|
-
self._n_clicks_new_project_ = 0
|
|
162
|
-
self._n_clicks_del_project_ = 0
|
|
163
|
-
column_definition = [
|
|
164
|
-
{"field": "name", "checkboxSelection": True, "headerCheckboxSelection": True}]
|
|
165
|
-
for i in df.columns:
|
|
166
|
-
if i != "name":
|
|
167
|
-
column_definition.append({"field": i})
|
|
168
|
-
|
|
169
|
-
editor.layout = html.Div([
|
|
170
|
-
dbc.Label("Projects editor: "),
|
|
171
|
-
html.Br(),
|
|
172
|
-
dbc.Button('New project', id='btn-new-project', n_clicks=0),
|
|
173
|
-
dbc.Button('Delete selected projects',
|
|
174
|
-
id='btn-del-project', n_clicks=0, style={"margin-left": "15px"}),
|
|
175
|
-
html.Br(),
|
|
176
|
-
html.Br(),
|
|
177
|
-
dag.AgGrid(
|
|
178
|
-
id="project-table",
|
|
179
|
-
rowData=df.to_dict("records"),
|
|
180
|
-
columnDefs=column_definition,
|
|
181
|
-
columnSize="sizeToFit",
|
|
182
|
-
defaultColDef={"filter": True, "editable": True},
|
|
183
|
-
style={"height": '250px'},
|
|
184
|
-
dashGridOptions={
|
|
185
|
-
"rowSelection": "multiple",
|
|
186
|
-
"suppressRowClickSelection": True,
|
|
187
|
-
"pagination": True,
|
|
188
|
-
})
|
|
189
|
-
])
|
|
190
|
-
|
|
191
|
-
@callback(
|
|
192
|
-
Output('project-table', 'rowData'),
|
|
193
|
-
Input('project-table', 'cellValueChanged'),
|
|
194
|
-
Input('btn-new-project', 'n_clicks'),
|
|
195
|
-
Input('btn-del-project', 'n_clicks'),
|
|
196
|
-
State('project-table', 'selectedRows'),
|
|
197
|
-
prevent_initial_call=True)
|
|
198
|
-
def update_data(changed, n_clicks_new, n_clicks_del, selectedRows):
|
|
199
|
-
if (self._n_clicks_new_project_ < n_clicks_new):
|
|
200
|
-
self.new_project("new_project_"+str(n_clicks_new))
|
|
201
|
-
self._n_clicks_new_project_ = n_clicks_new
|
|
202
|
-
elif (self._n_clicks_del_project_ < n_clicks_del):
|
|
203
|
-
for row in selectedRows:
|
|
204
|
-
self.del_project(self.project(row["name"]))
|
|
205
|
-
self._n_clicks_del_project_ = n_clicks_del
|
|
206
|
-
else:
|
|
207
|
-
if changed is not None:
|
|
208
|
-
if changed[0]["colId"] == "name":
|
|
209
|
-
self.project(changed[0]['oldValue']).parameter(
|
|
210
|
-
"name").value = changed[0]["value"]
|
|
211
|
-
else:
|
|
212
|
-
self.project(changed[0]['data']["name"]).parameter(
|
|
213
|
-
changed[0]["colId"]).value = changed[0]["value"]
|
|
214
|
-
df_end = self.project_dataframe(string_format=True)
|
|
215
|
-
return df_end.to_dict("records")
|
|
216
|
-
|
|
217
|
-
editor.run(jupyter_height=350)
|