def test_df_nans_not_allowed(self): df = pd.DataFrame.from_dict(sample_data) df2 = pd.DataFrame.from_dict(sample_data2) cu._df_nans_not_allowed('sample_data', df) with self.assertRaises(TypeError): cu._df_nans_not_allowed('sample_data2', df2)
def create_pyomo_model(self, impact=None, sensor=None, scenario=None, sensor_budget=None, use_sensor_cost=False, use_scenario_probability=False, impact_col_name='Impact'): """ Returns the Pyomo model. See :py:meth:`ImpactFormulation.solve` for more information on parameters. Returns ------- Pyomo ConcreteModel ready to be solved """ # reset the internal model and data attributes # BLN: Why do we reset these when they will be overwritten at the # end of this method anyway? self._model = None self._impact = None self._sensor = None self._scenario = None # validate the pandas dataframe input cu._df_columns_required( 'impact', impact, { 'Scenario': np.object, 'Sensor': np.object, impact_col_name: [np.float64, np.int64] }) cu._df_nans_not_allowed('impact', impact) if sensor is not None: cu._df_columns_required('sensor', sensor, {'Sensor': np.object}) cu._df_nans_not_allowed('sensor', sensor) sensor = sensor.set_index('Sensor') assert (sensor.index.names[0] == 'Sensor') cu._df_columns_required('scenario', scenario, { 'Scenario': np.object, 'Undetected Impact': [np.float64, np.int64] }) cu._df_nans_not_allowed('scenario', scenario) # validate optional columns in pandas dataframe input if use_scenario_probability: cu._df_columns_required('scenario', scenario, {'Probability': np.float64}) if use_sensor_cost: if sensor is None: raise ValueError( 'ImpactFormulation: use_sensor_cost cannot be ' 'True if "sensor" DataFrame is not provided.') cu._df_columns_required('sensor', sensor, {'Cost': [np.float64, np.int64]}) # Notice, setting the index here impact = impact.set_index(['Scenario', 'Sensor']) assert (impact.index.names[0] == 'Scenario') assert (impact.index.names[1] == 'Sensor') # Python set will extract the unique Scenario and Sensor values scenario_list = sorted(scenario['Scenario'].unique()) # Always get sensor list from impact DataFrame in case there are # sensors in the sensor DataFrame that didn't detect anything and # therefore do not appear in the impact DataFrame sensor_list = sorted(set(impact.index.get_level_values('Sensor'))) if use_sensor_cost: sensor_cost = sensor['Cost'] else: sensor_cost = pd.Series(data=[1.0] * len(sensor_list), index=sensor_list) # Add in the data for the dummy sensor to account for a scenario that # is undetected sensor_list.append(dummy_sensor_name) df_dummy = pd.DataFrame(scenario_list, columns=['Scenario']) df_dummy = df_dummy.set_index(['Scenario']) scenario = scenario.set_index(['Scenario']) df_dummy[impact_col_name] = scenario['Undetected Impact'] scenario.reset_index(level=[0], inplace=True) df_dummy['Sensor'] = dummy_sensor_name df_dummy = df_dummy.reset_index().set_index(['Scenario', 'Sensor']) impact = impact.append(df_dummy) sensor_cost[dummy_sensor_name] = 0.0 # Create a list of tuples for all the scenario/sensor pairs where # detection has occurred scenario_sensor_pairs = impact.index.tolist() # Create the (jagged) index set of sensors that were able to detect a # particular scenario scenario_sensors = dict() for (a, i) in scenario_sensor_pairs: if a not in scenario_sensors: scenario_sensors[a] = list() scenario_sensors[a].append(i) # create the model container model = pe.ConcreteModel() model.scenario_sensors = scenario_sensors # Pyomo does not create an ordered dummy set when passed a list - do # this for now as a workaround model.scenario_set = pe.Set(initialize=scenario_list, ordered=True) model.sensor_set = pe.Set(initialize=sensor_list, ordered=True) model.scenario_sensor_pairs_set = \ pe.Set(initialize=scenario_sensor_pairs, ordered=True) # create mutable parameter that may be changed model.sensor_budget = pe.Param(initialize=sensor_budget, mutable=True) # x_{a,i} variable indicates which sensor is the first to detect # scenario a model.x = pe.Var(model.scenario_sensor_pairs_set, bounds=(0, 1)) # y_i variable indicates if a sensor is installed or not model.y = pe.Var(model.sensor_set, within=pe.Binary) # objective function minimize the sum impact across all scenarios if use_scenario_probability: scenario.set_index(['Scenario'], inplace=True) model.obj = pe.Objective(expr= \ sum(float(scenario.at[a, 'Probability']) * float(impact[impact_col_name].loc[a, i]) * model.x[a, i] for (a, i) in scenario_sensor_pairs)) else: model.obj = pe.Objective(expr= \ 1.0 / float(len(scenario_list)) * sum(float(impact[impact_col_name].loc[a, i]) * model.x[a, i] for (a, i) in scenario_sensor_pairs)) # constrain the problem to have only one x value for each scenario def limit_x_rule(m, a): return sum(m.x[a, i] for i in scenario_sensors[a]) == 1 model.limit_x = pe.Constraint(model.scenario_set, rule=limit_x_rule) # can only detect scenario a with location i if location i is selected def detect_only_if_sensor_rule(m, a, i): return m.x[a, i] <= model.y[i] model.detect_only_if_sensor = \ pe.Constraint(model.scenario_sensor_pairs_set, rule=detect_only_if_sensor_rule) # limit the number of sensors model.total_sensor_cost = pe.Expression(expr=sum( float(sensor_cost[i]) * model.y[i] for i in sensor_list)) model.sensor_budget_con = pe.Constraint( expr=model.total_sensor_cost <= model.sensor_budget) self._model = model impact.reset_index(inplace=True) self._impact = impact self._sensor = sensor scenario.reset_index(inplace=True) self._scenario = scenario self._impact_col_name = impact_col_name self._use_sensor_cost = use_sensor_cost self._use_scenario_probability = use_scenario_probability # Any changes to the model require re-solving self._solved = False return model
def create_pyomo_model(self, impact, sensor_budget, sensor, scenario): """ Returns the Pyomo model. Parameters ---------- impact : pandas DataFrame Impact assessment sensor_budget : float Sensor budget sensor : pandas DataFrame Sensor characteristics scenario : pandas DataFrame Scenario characteristics Returns ------- Pyomo ConcreteModel ready to be solved """ # validate the pandas dataframe input cu._df_columns_required('sensor', sensor, { 'Sensor': np.object, 'Cost': [np.float64, np.int64] }) cu._df_nans_not_allowed('sensor', sensor) cu._df_columns_required('scenario', scenario, { 'Scenario': np.object, 'Undetected Impact': [np.float64, np.int64] }) cu._df_nans_not_allowed('scenario', scenario) cu._df_columns_required( 'impact', impact, { 'Scenario': np.object, 'Sensor': np.object, 'Impact': [np.float64, np.int64] }) cu._df_nans_not_allowed('impact', impact) # validate optional columns in pandas dataframe input if self.use_scenario_probability: cu._df_columns_required('scenario', scenario, {'Probability': np.float64}) self._sensor_df = sensor self._scenario_df = scenario self._impact_df = impact impact = impact.set_index(['Scenario', 'Sensor']) assert (impact.index.names[0] == 'Scenario') assert (impact.index.names[1] == 'Sensor') sensor = sensor.set_index('Sensor') assert (sensor.index.names[0] == 'Sensor') # Python set will extract the unique Scenario and Sensor values scenario_list = \ sorted(set(impact.index.get_level_values('Scenario'))) sensor_list = sorted(set(impact.index.get_level_values('Sensor'))) if self.use_sensor_cost: sensor_cost = sensor['Cost'] else: sensor['Cost'] = 1 sensor_cost = sensor['Cost'] # Add in the data for the dummy sensor to account for a scenario that # is undetected sensor_list.append(dummy_sensor_name) df_dummy = pd.DataFrame(scenario_list, columns=['Scenario']) df_dummy = df_dummy.set_index(['Scenario']) scenario = scenario.set_index(['Scenario']) df_dummy['Impact'] = scenario['Undetected Impact'] scenario.reset_index(level=[0], inplace=True) df_dummy['Sensor'] = dummy_sensor_name df_dummy = df_dummy.reset_index().set_index(['Scenario', 'Sensor']) impact = impact.append(df_dummy) sensor_cost[dummy_sensor_name] = 0.0 # create a list of tuples for all the scenario/sensor pairs where # detection has occurred scenario_sensor_pairs = impact.index.tolist() # create the (jagged) index set of sensors that were able to detect a # particular scenario scenario_sensors = dict() for (a, i) in scenario_sensor_pairs: if a not in scenario_sensors: scenario_sensors[a] = list() scenario_sensors[a].append(i) # create the model container model = pe.ConcreteModel() model._scenario_sensors = scenario_sensors # Pyomo does not create an ordered dummy set when passed a list - do # this for now as a workaround model.scenario_set = pe.Set(initialize=scenario_list, ordered=True) model.sensor_set = pe.Set(initialize=sensor_list, ordered=True) model.scenario_sensor_pairs_set = \ pe.Set(initialize=scenario_sensor_pairs, ordered=True) # x_{a,i} variable indicates which sensor is the first to detect # scenario a model.x = pe.Var(model.scenario_sensor_pairs_set, bounds=(0, 1)) # y_i variable indicates if a sensor is installed or not model.y = pe.Var(model.sensor_set, within=pe.Binary) # objective function minimize the sum impact across all scenarios # in current formulation all scenarios are equally probable def obj_rule(m): return 1.0 / float(len(scenario_list)) * \ sum(float(impact.loc[a, i]) * m.x[a, i] for (a, i) in scenario_sensor_pairs) # Modify the objective function to include scenario probabilities if self.use_scenario_probability: scenario.set_index(['Scenario'], inplace=True) def obj_rule(m): return sum( float(scenario.loc[a, 'Probability']) * float(impact.loc[a, i]) * m.x[a, i] for (a, i) in scenario_sensor_pairs) model.obj = pe.Objective(rule=obj_rule) # constrain the problem to have only one x value for each scenario def limit_x_rule(m, a): return sum(m.x[a, i] for i in scenario_sensors[a]) == 1 model.limit_x = pe.Constraint(model.scenario_set, rule=limit_x_rule) def detect_only_if_sensor_rule(m, a, i): return m.x[a, i] <= model.y[i] model.detect_only_if_sensor = \ pe.Constraint(model.scenario_sensor_pairs_set, rule=detect_only_if_sensor_rule) model.sensor_budget = \ pe.Constraint(expr=sum(float(sensor_cost[i]) * model.y[i] for i in sensor_list) <= sensor_budget) self._model = model return model