I am trying to figure out if it is possible to configure a specific problem as a mixed-integer program. I think I am able to structure it as a continuous non-linear optimization problem, but would like to see if a MIP works better.
The basic idea is that there are several systems comprised of multiple elements. Over time, the elements degrade and certain actions/decisions can be applied at periodic intervals. Each decision choice has a different cost and impact on element condition. The goal is to define the optimal set of decisions for each element at periodic intervals. However, the decisions must not allow an overall system to degrade below a minimally acceptable level, or to cost more than a budgeted amount for each period.
Below is an example of how an example could be set up and how a single decision matrix would be evaluated:
# 1. define parameters like # of systes, decision options, decision periods, types etc.
# 2. define constraints
# 3. model how the elements degrade over time, and costs for decisions
# 4. model how elements will impact system metrics
# 5. structure as MIP <- HOW TO DO THIS??:
import random
from random import choices
import pandas as pd
# define parameters
#
decision_options = list(range(0,4)) # defines number of decision options
periods = list(range(0,6)) # defines number of decision periods
system_types = ['A', 'B', 'C'] # type used for condition constraints
# say we have 100 elements, each assigned to a system
element_ids = list(range(0, 100))
# create initialized element condition values
condition = choices(range(60,101), k=len(element_ids))
# assign types for each item
system_type = choices(system_types, k=len(element_ids))
# assign value for each group
value = random.sample(range(500, 20000), len(element_ids))
# create a dataframe with element, system type, condition, and element value information
df = pd.DataFrame({'Element_ID': element_ids, 'System_Type': system_type, 'Condition': condition, "Value": value})
df
# 2. define constraints
#
# create a dict where each type has a minimum allowable condition
vals= [60, 50, 40] # System = A has a limit of 60, System B has a limit of 50...
min_condition = dict(zip(system_types, vals))
# budget constraint for each period
max_budget = [200000] * len(periods)
# 3. model costs and degradation over time
# create a function that sets a element cost based on decision
def decision_cost(decision, value):
cost = 0
match decision:
case 0:
cost = 0 # do nothing
case 1:
cost = value / 10 # do a little
case 2:
cost = value / 5 # do a little more
case 3:
cost = value / 2 # do a lot
return(cost)
# create a function that sets a element condition based on decision
def decision_result(decision, condition):
match decision:
case 0:
condition = condition # no improvement
case 1:
condition = min(condition*1.1, 100) # a little improvement
case 2:
condition = min(condition*1.2, 100) # a little more improvement
case 3:
condition = min(condition*1.5, 100) # a lot of improvement
return(condition)
# model element degradation
# element loses 10% at each period
def degrade_element(condition):
new_condition = round(condition *0.9,0)
return(new_condition)
# 4. model how elements will impact system metrics
# these are to be compared to the min_condition constraints
#
# system condition is the weighted-and-summed condition of the constituent elements
def system_condition(df):
system_types = sorted(df['System_Type'].unique())
system_condition = [0] * len(system_types)
for i in range(len(system_types)):
system_data = df[df['System_Type'] == system_types[i]]
system_condition[i] = (system_data['Condition'] * system_data['Value']).sum() / system_data['Value'].sum()
return(system_condition)
def period_costs(new_df, periods):
column_names = [f'Period_{p}' for p in periods]
period_sums = []
for col in column_names:
period_sums.append(new_df[col].sum())
return(period_sums)
system_condition(df)
# create a sample decision matrix:
# row = element
# column = period
# cell value = decision
import numpy as np
# randomly initialize a decision matrix
decision_matrix = np.random.randint(0, len(decision_options), size=(len(element_ids), max(periods)+1))
# example evaluation of a decision matrix
# get cost and result dataframes for system/element results
system_cost_df = system_result_df = pd.DataFrame(index=range(len(system_types)), columns=range(0, len(periods)))
element_cost_df = element_result_df = pd.DataFrame(index=range(len(element_ids)), columns=range(0, len(periods)))
def evaluate_decision_matrix(df, decision_matrix, periods):
new_df = df
#new_df['Cost'] = 0
# create a column to collect the cost for each period
column_names = [f'Period_{p}' for p in periods]
new_df = new_df.assign(**{col: 0 for col in column_names})
# for each period
for i in range(0, len(periods)):
# for each element
for j in range(0, len(element_ids)):
# execute decision
decision = decision_matrix[j,i]
element_value = new_df.iloc[j]['Value']
#element_cost_df.loc[j,i] = decision_cost(decision, element_value)
#cost = decision_cost(decision, element_value)
new_df.loc[j, column_names[i]] = decision_cost(decision, element_value)
# impact condition with decision
current_condition = new_df.iloc[j]['Condition']
updated_condition = decision_result(decision, current_condition)
new_df.loc[j, 'Condition'] = updated_condition
# degrade element for next period
degraded_condition = degrade_element(new_df.iloc[j]['Condition'])
new_df.loc[j, 'Condition'] = degraded_condition
return(new_df)
# evaluate decision matrix
new_df = evaluate_decision_matrix(df, decision_matrix, periods)
# calculate system condition
system_condition_values = system_condition(new_df)
# check if system condition constraints are met
# returns true if system condition constraints are met
condition_check = [x > y for x, y in zip(system_condition_values, vals)]
print(condition_check)
# check if budget constaint is met
costs = period_costs(new_df, periods)
budget_check = [x < y for x, y in zip(costs, max_budget)]
print(budget_check)
As an example, the print(condition_check) command displays [True, True, True] indicating the system condition constraints are satisfied. But print(budget_check) shows [True, False, False, False, True, False] indicated 4 of the period budget constraints have been broken.
Is it possible to structure this as an MIP in python and, if so, how would that be done?
budgetconstraint; are you sure that your current code is doing what you intend before you move on (or, ask others to) to something else?