Source code for rtc_tools_meta.meta_mixin

import logging
from typing import Dict, List, Set, Tuple, Union

import casadi as ca
import numpy as np

from rtctools.optimization.optimization_problem import OptimizationProblem
from rtctools.optimization.timeseries import Timeseries

logger = logging.getLogger("rtctools")


[docs]class MetaMixin(OptimizationProblem): """Adds Compatibility with MetaOptimizationProblem models This mixin allows MetaOptimizationProblem models to assimilate your RTC-Tools model into its meta problem and impose constraints back onto your model. *Resistance is futile.* """ def __init__(self, **kwargs): super().__init__(**kwargs) # TODO: categorize fixed variables by run level? self.__fixed_interface_variables = {} self.__meta_solver_output = {} self.meta_level = None def _update_meta_level(self, meta_level: str): """Set meta run level. :param meta_level: String identifying meta run level. """ logger.debug( f"MetaOptimizationProblem: {self.default_meta_level} activating meta level {meta_level}" ) self.meta_level = meta_level @property def solver_output(self): if self.meta_level == self.default_meta_level: return super().solver_output else: try: return self.__meta_solver_output[self.meta_level] except KeyError: raise Exception(f"No results for meta level {self.meta_level}") def bounds(self): # Call parent class first for default values. bounds = super().bounds() for variable, timeseries in self.__fixed_interface_variables.items(): bounds[variable] = (timeseries, timeseries) return bounds def _update_meta_solver_output(self, meta_solver_output: np.ndarray): """Record results from a meta optimization Takes the solver output from a meta optimization and redistributes it to the correct submodels. :param meta_solver_output: 1D numpy array of results calculated by ``self.optimize()`` """ self.__meta_solver_output[self.meta_level] = meta_solver_output def _fix_interface_variables(self, variables: Tuple[str]): """Fix interface variables of submodels Force subproblems to take the meta-optimal values of boundary conditions between submodels so that the submodels can be run independently :param variables: Tuple of variable names to be fixed """ results = self.extract_results() for variable in variables: timeseries = Timeseries(self.times(variable), results[variable]) self.__fixed_interface_variables[variable] = timeseries def optimize(self, *args, **kwargs): self._update_meta_level(self.default_meta_level) return super().optimize(*args, **kwargs) @property def default_meta_level(self): """Meta level corresponding to no meta modeling. Default is class name.""" return self.__class__.__name__