def initialize_time_series_data(variables, time, t0=None): """ Initializes time series data from initial conditions """ # TODO: What is the right API for this function? if t0 is None: # time could be a list, OrderedSet, or ContinuousSet t0 = next(iter(time)) variable_data = {} for var in variables: if var.is_reference(): try: variable_data[str(ComponentUID( var.referent))] = [value(var[t0])] except ValueError as err: if "No value for uninitialized NumericValue" in str(err): variable_data[str(ComponentUID(var.referent))] = [None] else: raise err else: try: variable_data[str(ComponentUID(var))] = [value(var[t0])] except ValueError as err: if "No value for uninitialized NumericValue" in str(err): variable_data[str(ComponentUID(var))] = [None] else: raise err return ([t0], variable_data)
def get_time_indexed_cuid(var, sets=None, dereference=None): """ Arguments --------- var: Object to process time: Set Set to use if slicing a vardata object dereference: None or int Number of times we may access referent attribute to recover a "base component" from a reference. """ # TODO: Does this function have a good name? # Should this function be generalized beyond a single indexing set? # Is allowing dereference to be an integer worth the confusion it might # add? if dereference is None: # Does this branch make sense? If given an unattached component, # we dereference, otherwise we don't dereference. remaining_dereferences = int(var.parent_block() is None) else: remaining_dereferences = int(dereference) if var.is_indexed(): if var.is_reference() and remaining_dereferences: remaining_dereferences -= 1 referent = var.referent if isinstance(referent, IndexedComponent_slice): return ComponentUID(referent) else: # If dereference is None, we propagate None, dereferencing # until we either reach a component attached to a block # or reach a non-reference component. dereference = dereference if dereference is None else\ remaining_dereferences # NOTE: Calling this function recursively return get_time_indexed_cuid(referent, time, dereference=dereference) else: # Assume that var is indexed only by time # TODO: Get appropriate slice for indexing set. # TODO: Should we call slice_component_along_sets here as well? # To cover the case of b[t0].var, where var is indexed # by a set we care about, and we also care about time... # But then maybe we should slice only the sets we care about... # Don't want to do anything with these sets unless we're # presented with a vardata... #index = tuple( # get_slice_for_set(s) for s in var.index_set().subsets() #) index = get_slice_for_set(var.index_set()) return ComponentUID(var[:]) else: if sets is None: raise ValueError( "A ComponentData %s was provided but no set. We need to know\n" "what set this component should be indexed by." % var.name) slice_ = slice_component_along_sets(var, sets) return ComponentUID(slice_)
def extend_time_series_data( data, variables, time, offset=None, include_first=False, include_last=True, ): """ data: ( [t0, t1, ...], { str(cuid): [value0, value1, ...], }, ) """ existing_time, existing_name_map = data time = list(time) start = None if include_first else 1 stop = None if include_last else -1 slice_idx = slice(start, stop, None) time = time[slice_idx] variable_data = {} for var in variables: if var.is_reference(): variable_data[str(ComponentUID( var.referent))] = [value(var[t]) for t in time] else: variable_data[str( ComponentUID(var))] = [value(var[t]) for t in time] if not set(variable_data.keys()) == set(existing_name_map.keys()): raise ValueError( "Trying to extend time series with different variables " "than were originally present.") new_name_map = { name: existing_name_map[name] + variable_data[name] for name in existing_name_map } if offset is not None: time = [t + offset for t in time] if existing_time[-1] >= time[0]: raise ValueError( "Cannot extend a data series with duplicated or " "unsorted time entries. Trying to place %s after %s." % (time[0], existing_time[-1])) new_time = existing_time + time return (new_time, new_name_map)
def expand_data_over_space(m, time, space, data): """ data maps time/space cuids to values. We get a data object from the cuid, then generate slices over time only at every point in space. """ sets = (time, ) t0 = next(iter(time)) new_data = {} for name, value in data.items(): var = m.find_component(name) if var is None: # Steady model won't have accumulation variables assert "accumulation" in name continue factor_sets = list(var.index_set().subsets()) if var.is_indexed() and len(factor_sets) == 2: # Assume our var is indexed by time, then space. for x in space: cuid = ComponentUID( slice_component_along_sets(var[t0, x], sets)) new_data[str(cuid)] = value else: new_data[name] = value return new_data
def _get_structured_variable_data(sets, comps, dereference=False): mesh = np.meshgrid(*sets, indexing="ij") # Without "ij", for two dimensions, meshgrid returns what # I would consider the transpose of the proper mesh. # TODO: references get tricky. If we have flattened # "base components" using references, we would like # to deference to get the underlying slices. # If we have flattened references, however, we may # or may not want to get the underlying slice. # # For now, we only dereference once, to undo the layer # of references added by the flattener. The ability # to completely dereference may be valuable in the future... set_cuids = [str(ComponentUID(s)) for s in sets] indices = [[val for val in s] for s in sets] cuid_buffer = {} if dereference: # TODO: Should we branch on whether each component is a reference # rather than use some "dereference" flag? variables = { str(ComponentUID(comp.referent, cuid_buffer=cuid_buffer)): apply_function_elementwise( lambda *ind: comp[ind].value, # This function, which gives us the data to be stored, # will vary with component type, and could possibly # by user provided/configurable. *mesh, ) for comp in comps } else: variables = { str(ComponentUID(comp, cuid_buffer=cuid_buffer)): apply_function_elementwise( lambda *ind: comp[ind].value, *mesh, ) for comp in comps } return { "sets": set_cuids, "indices": indices, "variables": variables, }
def _get_unstructured_variable_data(comps): # TODO: If we decide to support "complete dereferencing," # we will need a dereference flag here as well. cuid_buffer = {} return { "sets": None, "indices": None, "variables": {str(ComponentUID(comp, cuid_buffer)): comp.value for comp in comps}, }
def get_tracking_cost_expression( variables, time, setpoint_data, weight_data=None, dereference=True, ): """ Arguments --------- variables: list List of time-indexed variables to include in the tracking cost expression. setpoint_data: dict Maps variable names to setpoint values weight_data: dict Maps variable names to tracking cost weights Returns ------- Pyomo expression. Sum of weighted squared difference between variables and setpoint values. """ # TODO: Should setpoints be mutable params? Indexed by variable names? # This is tempting as it makes changing the setpoint easy. variable_names = [ # NOTE: passing strings through ComponentUID. # This may break things that rely on a particular form of the string. # I believe this approach is more consistent, however. # This is a temporary measure before I switch to using CUIDs as keys. str(ComponentUID(str(get_time_indexed_cuid(var)))) for var in variables ] # str(ComponentUID(var)) # if not var.is_reference() or not dereference # else str(ComponentUID(var.referent)) # for var in variables #] if weight_data is None: weight_data = {name: 1.0 for name in variable_names} def tracking_rule(m, t): return sum(weight_data[name] * (var[t] - setpoint_data[name])**2 for name, var in zip(variable_names, variables)) # Note that I don't enforce that variables are actually indexed # by time. "time" could just be a list of sample points... tracking_expr = Expression(time, rule=tracking_rule) return tracking_expr
def _get_structured_variable_data_from_dict(sets, indices, comps_dict): """ Arguments --------- sets: Tuple of Pyomo Sets Sets by which the components are indexed. comps_dict: Dict of dicts. Maps ComponentUID to a dict mapping indices to values. """ mesh = np.meshgrid(*indices, indexing="ij") return { # TODO: Why am I storing sets and indices separately instead of one # "sets" field that contains a list of (name, indices) tuples? "sets": [str(ComponentUID(s)) for s in sets], "indices": [list(s) for s in indices], "variables": { name: apply_function_elementwise( lambda *idx: data[idx], *mesh, ) for name, data in comps_dict.items() }, }
def add_solution(self, solution, smap_id, delete_symbol_map=True, cache=None, ignore_invalid_labels=False, ignore_missing_symbols=True, default_variable_value=None): instance = self._instance() soln = ModelSolution() soln._metadata['status'] = solution.status if not type(solution.message) is UndefinedData: soln._metadata['message'] = solution.message if not type(solution.gap) is UndefinedData: soln._metadata['gap'] = solution.gap if smap_id is None: # # Cache symbol names, which might be re-used in subsequent # calls to add_solution() # if cache is None: cache = {} if solution._cuid: # # Loading a solution with CUID keys # if len(cache) == 0: for obj in instance.component_data_objects(Var): cache[ComponentUID(obj)] = obj for obj in instance.component_data_objects(Objective, active=True): cache[ComponentUID(obj)] = obj for obj in instance.component_data_objects(Constraint, active=True): cache[ComponentUID(obj)] = obj for name in ['problem', 'objective', 'variable', 'constraint']: tmp = soln._entry[name] for cuid, val in getattr(solution, name).items(): obj = cache.get(cuid, None) if obj is None: if ignore_invalid_labels: continue raise RuntimeError( "CUID %s is missing from model %s" % (str(cuid), instance.name)) tmp[id(obj)] = (weakref_ref(obj), val) else: # # Loading a solution with string keys # if len(cache) == 0: for obj in instance.component_data_objects(Var): cache[obj.name] = obj for obj in instance.component_data_objects(Objective, active=True): cache[obj.name] = obj for obj in instance.component_data_objects(Constraint, active=True): cache[obj.name] = obj for name in ['problem', 'objective', 'variable', 'constraint']: tmp = soln._entry[name] for symb, val in getattr(solution, name).items(): obj = cache.get(symb, None) if obj is None: if ignore_invalid_labels: continue raise RuntimeError( "Symbol %s is missing from model %s" % (symb, instance.name)) tmp[id(obj)] = (weakref_ref(obj), val) else: # # Map solution # smap = self.symbol_map[smap_id] for name in ['problem', 'objective', 'variable', 'constraint']: tmp = soln._entry[name] for symb, val in getattr(solution, name).items(): if symb in smap.bySymbol: obj = smap.bySymbol[symb] elif symb in smap.aliases: obj = smap.aliases[symb] elif ignore_missing_symbols: continue else: #pragma:nocover # # This should never happen ... # raise RuntimeError( "ERROR: Symbol %s is missing from " "model %s when loading with a symbol map!" % (symb, instance.name)) tmp[id(obj())] = (obj, val) # # Wrap up # if delete_symbol_map: self.delete_symbol_map(smap_id) # # Collect fixed variables # tmp = soln._entry['variable'] for vdata in instance.component_data_objects(Var): id_ = id(vdata) if vdata.fixed: tmp[id_] = (weakref_ref(vdata), {'Value': value(vdata)}) elif (default_variable_value is not None) and \ (smap_id is not None) and \ (id_ in smap.byObject) and \ (id_ not in tmp): tmp[id_] = (weakref_ref(vdata), { 'Value': default_variable_value }) self.solutions.append(soln) return len(self.solutions) - 1
def __call__(self, obj=None): return ComponentUID(obj)