-
Notifications
You must be signed in to change notification settings - Fork 1
/
main.py
219 lines (183 loc) · 8.55 KB
/
main.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
import streamlit
import websocket
import ast
import streamlit as st
import pandas as pd
import matplotlib.pyplot as plt
def on_error(wsapp, message):
""" A function to print any error messages """
print(message)
pit_volume = 0 # initializing some variables
incrementalRevenue = []
deltathingy = 0
currentPitVolume = []
flowRateIn = 0
names = []
flowRateOut = []
def on_message(wsapp, message):
""" A function called for every message received.
Stores data and sends back the results of the optimization algorithm. """
global pit_volume
global incrementalRevenue
global deltathingy
global currentPitVolume
global flowRateIn
global names
global flowRateOut
# stores the data as a dictionary
try:
data = ast.literal_eval(message)
except:
print("Invalid data type.")
print(f"{message}\n\n")
return 0
# tests whether the message sent is the data from the operations, or the results of the flow allocation
if data["type"] == "CURRENT_STATE":
# displays the data
print(f"data = {message}")
names = [data["operations"][i]["name"] for i in range(len(data["operations"]))]
# creates the suitable framework for the flow allocation
output = "["
for i in range(0, len(data["operations"])):
output = output + "{\"operationId\":\"" + data["operations"][i]["id"] + "\",\"flowRate\":" + str(
allocate_flow(data)[i]) + "},"
output = output[:-1] + "]"
flowRateOut = [allocate_flow(data)[i] for i in range(len(data["operations"]))]
# prints and sends the flow allocation
print(f"ouput = {output}")
wsapp.send(output)
else:
print(f"response = {message}\n\n")
# storing the data we need to graph
pit_volume = data["currentPitVolume"]
incrementalRevenue.append(int(data["incrementalRevenue"]))
deltathingy = incrementalRevenue[-1] - incrementalRevenue[-2]
currentPitVolume.append(data["currentPitVolume"])
flowRateIn = data["flowRateIn"]
app()
def allocate_flow(data):
""" Stores data and runs the optimization algorithm to determine flow allocation. """
global pit_volume
global incrementalRevenue
global deltathingy
flowRateIn = data["flowRateIn"] + pit_volume
operations = data["operations"]
names = []
points = []
for operation in operations:
# makes a 2d array of the revenue points organized by their operation and then location
points_row = []
names.append(operation["name"])
for i in range(21):
# the index implies the flow rate of the point because flow=index*10000
points_row.append(operation["revenueStructure"][i]["dollarsPerDay"])
points.append(points_row)
slopes = []
for row in points:
# 2d array of the slopes between each point
# linear interpolation, baby
slopes_row = []
for i in range(1, 21):
dy = row[i] - row[i - 1]
dx = 10000
slope = dy / dx
slopes_row.append(slope)
slopes.append(slopes_row)
maxindeces = []
for row in points:
# initializing our output as the flow rates that maximize the profit for each operation without considering the limit on inflow
maxindeces.append(row.index(max(row)))
if sum(maxindeces) * 10000 > flowRateIn: # continue only if the current water use is out of bounds
moves = []
while sum(maxindeces) * 10000 - flowRateIn > 10000:
new = []
for row in range(len(maxindeces)): # the maximum revenues that cost less water than the current maxes
new.append((max(points[row][0:maxindeces[row]])) if maxindeces[
row] != 0 else -99999999) # hoping -99999999 is low enough to keep dif so high it's out of competition
workingRow = 0
workingDif = points[0][maxindeces[0]] - new[0]
for row in range(len(maxindeces)):
dif = points[row][maxindeces[row]] - new[row]
if dif < workingDif:
workingRow = row # finding the minimum difference and corresponding row
workingDif = dif
moves.append(
[workingDif, maxindeces[workingRow], maxindeces[workingRow] - points[workingRow].index(new[workingRow]),
workingRow]) # keeping track of the jumps to cheaper peaks
maxindeces[workingRow] = points[workingRow].index(new[workingRow]) # updating the maxindeces solution set
if sum(maxindeces) * 10000 > flowRateIn: # checking if sliding down from one of the current maxes will make it cross the water threshhold
maxesofeach = []
for row in range(len(points)):
newint = max(points[row][0:maxindeces[
row]]) # here we consider the next left peaks (newint) and the points where the slide would bring us (ylimit)
ylimit = (points[row][maxindeces[row]] - slopes[row][maxindeces[row] - 1] * (
sum(maxindeces) * 10000 - flowRateIn)) if maxindeces[row] != 0 else 0
maxesofeach.append([max([newint, ylimit]),
ylimit > newint]) # array shenanigans to help compare everything and keep track of where it's from
workingRow = maxesofeach.index(max(maxesofeach))
if not maxesofeach[workingRow][1]:
moves.append([points[workingRow][maxindeces[workingRow]] - max(maxesofeach)[0], maxindeces[workingRow],
maxindeces[workingRow] - points[workingRow].index(max(maxesofeach)[0]), workingRow])
maxindeces[workingRow] = points[workingRow].index(maxesofeach[workingRow][0])
while True: # undoing sacrifices that we got enough water to undo in the last jump
for move in moves:
if move[2] > flowRateIn / 10000 - sum(maxindeces):
moves.remove(move)
if len(moves) == 0:
break
undoing = max(moves)
maxindeces[undoing[3]] = undoing[1]
moves.remove(
undoing) # it removes moves it can't afford and moves it does until the moveset is empty
else:
maxindeces[workingRow] = maxindeces[workingRow] - sum(
maxindeces) + flowRateIn / 10000 # the fated slide
for i in range(len(maxindeces)):
maxindeces[i] = maxindeces[i] * 10000 # converts from indeces to actual flow rates
return maxindeces
def on_open(wsapp):
wsapp.send("{\"setPitCapacity\": 100000}")
#app()
def app():
global incrementalRevenue
global deltathingy
global currentPitVolume
global flowRateIn
global names
global flowRateOut
st.set_page_config(layout="wide")
# formatting function for pie chart
def my_fmt(x):
return '{:.1f}%\n({:.0f})'.format(x, totalWater * x / 100)
# Title
st.title("EOG Optimization Dashboard")
# Columns setup
col1, col2 = st.columns(2)
with col1:
# Revenue total
revenue = sum(incrementalRevenue)
# deltathingy = 4000
st.metric(label="Cumulative Revenue", value="$" + str(revenue), delta=deltathingy)
st.subheader("Revenue in last 5 seconds (dollars)")
# Line Chart for Revenue
chart_data = pd.DataFrame(incrementalRevenue[-12:] if len(incrementalRevenue) > 11 else incrementalRevenue, columns=[''])
st.line_chart(chart_data)
st.subheader("Volume of Water in the Pit (bbls)")
maxPit = 100000
chart_data = pd.DataFrame(currentPitVolume[-12:] if len(currentPitVolume) > 11 else currentPitVolume, columns=[''])
st.line_chart(chart_data)
with col2:
# Pie chart
totalWater = flowRateIn
labels = names
sizes = flowRateOut
# explode = (0, 0.1, 0, 0) # only "explode" the 2nd slice
fig1, ax1 = plt.subplots()
# explode=explode for the thing below
plt.title("Current Distribution of Water between Operations")
ax1.pie(sizes, labels=labels, autopct=my_fmt, startangle=90)
ax1.axis('equal') # Equal aspect ratio ensures that pie is drawn as a circle.
st.pyplot(fig1) # pie chart drawing
wsapp = websocket.WebSocketApp("wss://2021-utd-hackathon.azurewebsites.net", on_message=on_message, on_error=on_error,
on_open=on_open)
wsapp.run_forever()