Skip to content

Commit

Permalink
Merge branch 'JBjoernskov:main' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
SebsCubs authored Nov 27, 2024
2 parents 8031f37 + e9170b9 commit 48601d5
Show file tree
Hide file tree
Showing 2 changed files with 66 additions and 10 deletions.
52 changes: 48 additions & 4 deletions twin4build/estimator/estimator.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import pygad
import functools
from scipy._lib._array_api import atleast_nd, array_namespace
from typing import Union, List, Dict, Optional, Callable, TYPE_CHECKING
from typing import Union, List, Dict, Optional, Callable, TYPE_CHECKING, Any
if TYPE_CHECKING:
import twin4build.model.model as model

Expand Down Expand Up @@ -64,6 +64,12 @@ def __init__(self,
n_par=n_par,
n_par_map=n_par_map)

def __copy__(self):
return MCMCEstimationResult(**self)

def copy(self):
return self.__copy__()

class LSEstimationResult(dict):
def __init__(self,
result_x: np.array=None,
Expand All @@ -81,6 +87,13 @@ def __init__(self,
endTime_train=endTime_train,
stepSize_train=stepSize_train)

def __copy__(self):
return LSEstimationResult(**self)


def copy(self):
return self.__copy__()

class Estimator():
"""
A class for parameter estimation in the twin4build framework.
Expand Down Expand Up @@ -1500,8 +1513,21 @@ def _eps_for_method(x0_dtype, f0_dtype, method):



def ls(self,
def ls(self,
n_cores=multiprocessing.cpu_count(),
method: str="trf",
ftol: float = 1e-8,
xtol: float = 1e-8,
gtol: float = 1e-8,
x_scale: float = 1,
loss: str = 'linear',
f_scale: float = 1,
diff_step: Any | None = None,
tr_solver: Any | None = None,
tr_options: Any = {},
jac_sparsity: Any | None = None,
max_nfev: Any | None = None,
verbose: int = 0,
**kwargs) -> LSEstimationResult:
"""
Run least squares estimation.
Expand Down Expand Up @@ -1529,8 +1555,26 @@ def ls(self,
self.jac_chunksize = 1
self.model.make_pickable()

self.bounds = (self._lb, self._ub) #, loss="soft_l1"
ls_result = least_squares(self._res_fun_ls_separate_process, self._x0, bounds=(self._lb, self._ub), verbose=2, xtol=1e-14, ftol=1e-8, x_scale=self._x0, jac=self.numerical_jac) #Change verbose to 2 to see the optimization progress
self.bounds = (self._lb, self._ub)

ls_result = least_squares(self._res_fun_ls_separate_process,
self._x0, jac=self.numerical_jac,
bounds=self.bounds,
method=method,
ftol=ftol,
xtol=xtol,
gtol=gtol,
x_scale=x_scale,
loss=loss,
f_scale=f_scale,
diff_step=diff_step,
tr_solver=tr_solver,
tr_options=tr_options,
jac_sparsity=jac_sparsity,
max_nfev=max_nfev,
verbose=verbose) #Change verbose to 2 to see the optimization progress


ls_result = LSEstimationResult(result_x=ls_result.x,
component_id=[com.id for com in self.flat_component_list],
component_attr=[attr for attr in self.flat_attr_list],
Expand Down
24 changes: 18 additions & 6 deletions twin4build/model/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ def draw_graph(self, filename: str, graph: pydot.Dot, args: Optional[List[str]]
args (Optional[List[str]]): Additional arguments for the graph drawing command.
"""
fontpath, fontname = self._get_font()

beige = "#F2EADD"
light_grey = "#71797E"
graph_filename = os.path.join(self.graph_path, f"{filename}.png")
graph.write(f'{filename}.dot', prog="dot")
Expand Down Expand Up @@ -130,6 +130,7 @@ def draw_graph(self, filename: str, graph: pydot.Dot, args: Optional[List[str]]
"-Grepulsiveforce=0.5",
"-Gremincross=true",
"-Gstart=1",
"-Gbgcolor=transparent",
"-q",
f"-o{graph_filename}",
f"{filename}.dot"] #__unflatten
Expand Down Expand Up @@ -541,22 +542,33 @@ def add_connection(self, sender_component: System, receiver_component: System,
sender_obj_connection.connectsSystemAt.append(receiver_component_connection_point)
receiver_component_connection_point.connectsSystemThrough.append(sender_obj_connection)# if sender_obj_connection not in receiver_component_connection_point.connectsSystemThrough else None


# Inputs and outputs of these classes can be set dynamically. Inputs and outputs of classes not in this tuple are set as part of their class definition.
exception_classes = (systems.TimeSeriesInputSystem,
systems.PiecewiseLinearSystem,
systems.PiecewiseLinearSupplyWaterTemperatureSystem,
systems.PiecewiseLinearScheduleSystem,
base.Sensor,
base.Meter,
systems.MaxSystem,
systems.NeuralPolicyControllerSystem) # These classes are exceptions because their inputs and outputs can take any form
systems.NeuralPolicyControllerSystem)

if isinstance(sender_component, exception_classes):
sender_component.output.update({sender_property_name: tps.Scalar()})
if sender_property_name not in sender_component.output:
# If the property is not already an output, we assume it is a Scalar
sender_component.output.update({sender_property_name: tps.Scalar()})
else:
pass
else:
message = f"The property \"{sender_property_name}\" is not a valid output for the component \"{sender_component.id}\" of type \"{type(sender_component)}\".\nThe valid output properties are: {','.join(list(sender_component.output.keys()))}"
assert sender_property_name in (set(sender_component.input.keys()) | set(sender_component.output.keys())), message

if isinstance(receiver_component, exception_classes):
receiver_component.input.update({receiver_property_name: tps.Scalar()})
if receiver_property_name not in receiver_component.input:
# If the property is not already an input, we assume it is a Scalar
receiver_component.input.update({receiver_property_name: tps.Scalar()})
else:
assert isinstance(receiver_component.input[receiver_property_name], tps.Vector), f"The input property \"{receiver_property_name}\" for the component \"{receiver_component.id}\" of type \"{type(receiver_component)}\" is already set as a Scalar input."
else:
message = f"The property \"{receiver_property_name}\" is not a valid input for the component \"{receiver_component.id}\" of type \"{type(receiver_component)}\".\nThe valid input properties are: {','.join(list(receiver_component.input.keys()))}"
assert receiver_property_name in receiver_component.input.keys(), message
Expand Down Expand Up @@ -3609,6 +3621,7 @@ def draw_graph(self, filename: str, graph: pydot.Dot, args: Optional[List[str]]
"-Grepulsiveforce=0.5",
"-Gremincross=true",
"-Gstart=1",
"-Gbgcolor=transparent",
"-q",
f"-o{graph_filename}",
f"{filename}.dot"] #__unflatten
Expand Down Expand Up @@ -3860,7 +3873,6 @@ def load_estimation_result(self, filename: Optional[str] = None, result: Optiona
Raises:
AssertionError: If invalid arguments are provided.
"""

if result is not None:
assert isinstance(result, dict), "Argument d must be a dictionary"
cls_ = result.__class__
Expand Down Expand Up @@ -3888,7 +3900,7 @@ def load_estimation_result(self, filename: Optional[str] = None, result: Optiona
self.result = estimator.MCMCEstimationResult(**d)
else:
raise Exception(f"The estimation result file is not of a supported type. The file must be a .pickle, .npz file with the name containing \"_ls\" or \"_mcmc\".")


for key, value in self.result.items():
self.result[key] = 1/self.result["chain_betas"] if key=="chain_T" else value
Expand Down

0 comments on commit 48601d5

Please sign in to comment.