"Balancing Time, Distance, and Production: A Simple Python Approach to Well Route Optimization"
The code aims to optimize routes for well operators, maximizing oil production while respecting distance and time constraints. It uses real-world parameters like office location, maximum distance per operator, maximum time that the operator can work in a day, and average speed travelling from well to well. The approach can be easily scaled to handle larger datasets
Optimization Technique
The input dataframe contains
Columns:
Well: API or Name of the well
Lat: Latitude
Long: Longitude
Route; Usually Each well is already assigned a pre determined Route and an operator.
Production : In this case is loss of production due to a well issue.
领英推荐
ServiceTime: Many operators use downtime coding and each code may have times to fix issues. if not you one can manually add the time one would take to tackle the issue.
User inputs:
office_location = (35.09, -95.97)
max_distance = 100 # Maximum distance per operator (in miles)
max_time = 480 # Maximum time per operator (in minutes, e.g., 8 hours)
speed = 50 # Average speed (in mph)
Here is the code.
import pulp
import pandas as pd
from geopy.distance import geodesic
import folium
# Calculate distances and travel times
def calculate_distance(loc1, loc2):
return geodesic(loc1, loc2).miles
def calculate_travel_time(distance, speed):
return (distance / speed) 60 # Convert hours to minutes
# Precompute distances and travel times
distances = {}
travel_times = {}
for , well in wells.iterrows():
welllocation = (well['Lat'], well['Long'])
distance = calculate_distance(office_location, well_location)
distances[well['Well']] = distance
travel_times[well['Well']] = calculate_travel_time(distance, speed)
# Function to solve the optimization problem for a single operator
def optimize_operator_route(operator_wells, office_location, max_distance, max_time):
# Calculate distances and travel times from office to each well
distances = {}
travel_times = {}
for , well in operatorwells.iterrows():
well_location = (well['Lat'], well['Long'])
distance = calculate_distance(office_location, well_location)
distances[well['Well']] = distance
travel_times[well['Well']] = calculate_travel_time(distance, speed)
# Define the problem
prob = pulp.LpProblem(f"{operator}_Well_Selection", pulp.LpMaximize)
# Decision variables
x = pulp.LpVariable.dicts("x", operator_wells['Well'], cat='Binary')
# Objective function: Maximize oil production
prob += pulp.lpSum([x[well] operator_wells.loc[operator_wells['Well'] == well, 'Production'].values[0] for well in operator_wells['Well']])
# Constraint 1: Total distance must not exceed max_distance
prob += pulp.lpSum([x[well] distances[well] for well in operator_wells['Well']]) <= max_distance
# Constraint 2: Total time (travel + service) must not exceed max_time
prob += pulp.lpSum([x[well] (travel_times[well] + operator_wells.loc[operator_wells['Well'] == well, 'ServiceTime'].values[0]) for well in operator_wells['Well']]) <= max_time
# Solve
prob.solve()
# Extract selected wells
selected_wells = []
for well in operator_wells['Well']:
if pulp.value(x[well]) == 1:
selected_wells.append(well)
return operator_wells[operator_wells['Well'].isin(selected_wells)]
# Process each operator
results = []
for operator in wells['Route'].unique():
operator_wells = wells[wells['Route'] == operator]
optimized_wells = optimize_operator_route(operator_wells, office_location, max_distance, max_time)
results.append(optimized_wells)
# Combine results into a single dataframe
results_df = pd.concat(results)
wells['Attended'] = wells['Well'].isin(results_df['Well']).map({True: 'Yes', False: 'No'})
wells=wells.sort_values(by=['Route','Attended'],ascending=[True, False])
print("Optimized Wells for Each Operator:")
print(wells)
# Visualize the routes in Folium
m = folium.Map(location=office_location, zoom_start=10)
# Add the office location
folium.Marker(
location=office_location,
popup="Office",
icon=folium.Icon(color="green", icon="home")
).add_to(m)
# Add wells and routes for each operator
colors = ['blue', 'red'] # Different colors for each operator
for i, operator in enumerate(wells['Route'].unique()):
operator_wells = results_df[results_df['Route'] == operator]
if not operator_wells.empty:
# Create a list of coordinates for the route
route_coords = [office_location] # Start at the office
for order, (_, row) in enumerate(operator_wells.iterrows(), start=1):
well_coords = (row['Lat'], row['Long'])
route_coords.append(well_coords)
folium.Marker(
location=well_coords,
popup=f"Well {row['Well']}",
icon=folium.Icon(color=colors[i], icon="tint")
).add_to(m)
# Add label next to the marker
folium.map.Marker(
location=well_coords,
icon=folium.DivIcon(
icon_size=(150, 36),
icon_anchor=(0, 0),
html=f'<div style="font-size: 12px; color: black;">{row["Well"]} - {order}</div>'
)
).add_to(m)
route_coords.append(office_location) # Return to the office
# Draw the route
folium.PolyLine(
locations=route_coords,
color=colors[i],
weight=2.5,
opacity=1,
popup=f"{operator}'s Route"
).add_to(m)
# Save and display the map
m.save("optimized_routes.html")
Resulting Data Table:
Business Advisor at National Business Capital
4 周Fantastic insights, Dicman! Your approach with Python and open-source tools makes complex route optimization accessible and efficient. Love the humor about driving over water—keeps it light-hearted! ?? Cheers to innovation!