def compute_terms(self, control_allocation, execution_id=None): '''Calculate interaction terms. Results are returned in a dict; entries are keyed using names of terms listed in the `PV` Enum. Values of entries are nd arrays. ''' # FIX: 11/9/19 LOCALLY MANAGE STATEFULNESS OF ControlSignals AND costs # ref_variables = ref_variables or self.reference_variable # self.reference_variable = ref_variables terms = self.specified_terms computed_terms = {} # No need to calculate features, so just get values computed_terms[PV.F] = f = self.terms[PV.F.value] # Compute value of each control_signal from its variable c = [None] * len(control_allocation) for i, var in enumerate(control_allocation): c[i] = self.control_signal_functions[i]( var, execution_id=execution_id) computed_terms[PV.C] = c = np.array(c) # Compute costs for new control_signal values if PV.COST in terms: # computed_terms[PV.COST] = -(np.exp(0.25*c-3)) # computed_terms[PV.COST] = -(np.exp(0.25*c-3) + (np.exp(0.25*np.abs(c-self.control_signal_change)-3))) costs = [None] * len(c) for i, val in enumerate(c): # MODIFIED 11/9/18 OLD: costs[i] = -(self._compute_costs[i]( val, execution_id=execution_id)) # # MODIFIED 11/9/18 NEW: [JDC] # costs[i] = -(self._compute_costs[i](val, ref_variables[i])) # MODIFIED 11/9/18 END computed_terms[PV.COST] = np.array(costs) # Compute terms interaction that are used if any(term in terms for term in [PV.FF, PV.FFC, PV.FFCC]): computed_terms[PV.FF] = ff = np.array( tensor_power(f, range(2, self.num[PV.F.value] + 1))) if any(term in terms for term in [PV.CC, PV.FCC, PV.FFCC]): computed_terms[PV.CC] = cc = np.array( tensor_power(c, range(2, self.num[PV.C.value] + 1))) if any(term in terms for term in [PV.FC, PV.FCC, PV.FFCC]): computed_terms[PV.FC] = np.tensordot(f, c, axes=0) if any(term in terms for term in [PV.FFC, PV.FFCC]): computed_terms[PV.FFC] = np.tensordot(ff, c, axes=0) if any(term in terms for term in [PV.FCC, PV.FFCC]): computed_terms[PV.FCC] = np.tensordot(f, cc, axes=0) if PV.FFCC in terms: computed_terms[PV.FFCC] = np.tensordot(ff, cc, axes=0) return computed_terms
def __init__(self, feature_values, control_signals, specified_terms): # Get variable for control_signals specified in constructor control_allocation = [] for c in control_signals: if isinstance(c, ControlSignal): try: v = c.variable except: v = c.defaults.variable elif isinstance(c, type): if issubclass(c, ControlSignal): v = c.class_defaults.variable else: # If a class other than ControlSignal was specified, typecheck should have found it assert False, "PROGRAM ERROR: unrecognized specification for {} arg of {}: {}".\ format(repr(CONTROL_SIGNALS), self.name, c) else: port_spec_dict = _parse_port_spec(port_type=ControlSignal, owner=self, port_spec=c) v = port_spec_dict[VARIABLE] v = v or ControlSignal.defaults.variable control_allocation.append(v) # Get primary function and compute_costs function for each ControlSignal (called in compute_terms) self.control_signal_functions = [ c.function for c in control_signals ] self._compute_costs = [c.compute_costs for c in control_signals] def get_intrxn_labels(x): return list([s for s in powerset(x) if len(s) > 1]) def error_for_too_few_terms(term): spec_type = {'FF': 'feature_values', 'CC': 'control_signals'} raise RegressionCFAError( "Specification of {} for {} arg of {} " "requires at least two {} be specified".format( 'PV.' + term, repr(PREDICTION_TERMS), self.name, spec_type(term))) F = PV.F.value C = PV.C.value FF = PV.FF.value CC = PV.CC.value FC = PV.FC.value FFC = PV.FFC.value FCC = PV.FCC.value FFCC = PV.FFCC.value COST = PV.COST.value # RENAME THIS AS SPECIFIED_TERMS self.specified_terms = specified_terms self.terms = [None] * len(PV) self.idx = [None] * len(PV) self.num = [None] * len(PV) self.num_elems = [None] * len(PV) self.labels = [None] * len(PV) # MAIN EFFECT TERMS (unflattened) # Feature_values self.terms[F] = f = feature_values self.num[F] = len(f) # feature_values are arrays self.num_elems[F] = len( f.reshape(-1)) # num of total elements assigned to vector self.labels[F] = ['f' + str(i) for i in range(0, len(f))] # Placemarker until control_signals are instantiated self.terms[C] = c = np.array([[0]] * len(control_allocation)) self.num[C] = len(c) self.num_elems[C] = len(c.reshape(-1)) self.labels[C] = [ 'c' + str(i) for i in range(0, len(control_allocation)) ] # Costs # Placemarker until control_signals are instantiated self.terms[COST] = cst = np.array([[0]] * len(control_allocation)) self.num[COST] = self.num[C] self.num_elems[COST] = len(cst.reshape(-1)) self.labels[COST] = [ 'cst' + str(i) for i in range(0, self.num[COST]) ] # INTERACTION TERMS (unflattened) # Interactions among feature vectors if any(term in specified_terms for term in [PV.FF, PV.FFC, PV.FFCC]): if len(f) < 2: self.error_for_too_few_terms('FF') self.terms[FF] = ff = np.array( tensor_power(f, levels=range(2, len(f) + 1))) self.num[FF] = len(ff) self.num_elems[FF] = len(ff.reshape(-1)) self.labels[FF] = get_intrxn_labels(self.labels[F]) # Interactions among values of control_signals if any(term in specified_terms for term in [PV.CC, PV.FCC, PV.FFCC]): if len(c) < 2: self.error_for_too_few_terms('CC') self.terms[CC] = cc = np.array( tensor_power(c, levels=range(2, len(c) + 1))) self.num[CC] = len(cc) self.num_elems[CC] = len(cc.reshape(-1)) self.labels[CC] = get_intrxn_labels(self.labels[C]) # feature-control interactions if any(term in specified_terms for term in [PV.FC, PV.FCC, PV.FFCC]): self.terms[FC] = fc = np.tensordot(f, c, axes=0) self.num[FC] = len(fc.reshape(-1)) self.num_elems[FC] = len(fc.reshape(-1)) self.labels[FC] = list(product(self.labels[F], self.labels[C])) # feature-feature-control interactions if any(term in specified_terms for term in [PV.FFC, PV.FFCC]): if len(f) < 2: self.error_for_too_few_terms('FF') self.terms[FFC] = ffc = np.tensordot(ff, c, axes=0) self.num[FFC] = len(ffc.reshape(-1)) self.num_elems[FFC] = len(ffc.reshape(-1)) self.labels[FFC] = list( product(self.labels[FF], self.labels[C])) # feature-control-control interactions if any(term in specified_terms for term in [PV.FCC, PV.FFCC]): if len(c) < 2: self.error_for_too_few_terms('CC') self.terms[FCC] = fcc = np.tensordot(f, cc, axes=0) self.num[FCC] = len(fcc.reshape(-1)) self.num_elems[FCC] = len(fcc.reshape(-1)) self.labels[FCC] = list( product(self.labels[F], self.labels[CC])) # feature-feature-control-control interactions if PV.FFCC in specified_terms: if len(f) < 2: self.error_for_too_few_terms('FF') if len(c) < 2: self.error_for_too_few_terms('CC') self.terms[FFCC] = ffcc = np.tensordot(ff, cc, axes=0) self.num[FFCC] = len(ffcc.reshape(-1)) self.num_elems[FFCC] = len(ffcc.reshape(-1)) self.labels[FFCC] = list( product(self.labels[FF], self.labels[CC])) # Construct "flattened" vector based on specified terms, and assign indices (as slices) i = 0 for t in range(len(PV)): if t in [t.value for t in specified_terms]: self.idx[t] = slice(i, i + self.num_elems[t]) i += self.num_elems[t] self.vector = np.zeros(i)