admin管理员组文章数量:1122832
I'm trying to create a script that will minimize the maximum distance between the ends of two lines at three different orientations. The first line, A, has a fixed origin and length, while line B's origin and length are the parameters I'm trying to optimize to minimize the maximum distance between the end points. I have two constraints, the Euclidean distance between the origins must be greater than 0.1, and the endpoints of line B for each rotation have to be at least 0.1 units further away from the origin of line A than the endpoints of line A for that rotation. When I run the script, the constraints are ignored.
Here is my code to create the optimization problem. Constraint 1 and 2 are both ignored.
# Define the length of line A
length_A = 0.5
# Define the endpoint positions of line A after each rotation
A_origin = np.array([0, 0])
A_ends = np.array([
A_origin + [0, -length_A], # Initial position
A_origin + [-length_A, 0], # After 90-degree rotation
A_origin + [0, length_A]
])
# Display results
print("Origin of line A:", A_origin)
print("Ends of line A:", A_ends)
# Objective function: Minimize the maximum distance between endpoints of A and B
def max_distance_to_A(params, A_ends):
B_origin = np.array(params[:2])
length_B = params[2]
# Define rotations for line B endpoint based on origin and length
B_ends = np.array([
B_origin + [0, -length_B], # Initial position
B_origin + [-length_B, 0], # After 90-degree rotation
B_origin + [0, length_B] # After 180-degree rotation
])
# Calculate distances between each corresponding end of lines A and B
distances = [np.linalg.norm(B_end - A_end) for B_end, A_end in zip(B_ends, A_ends)]
# Objective: Minimize the maximum distance across all rotations
return max(distances)
# Constraint function: Ensures all constraints are met
def distance_constraints(params, A_ends):
B_origin = np.array(params[:2])
length_B = params[2]
# Constraint 1: Origin of line B must be at least 0.1 away from origin of A
constraint1 = (np.linalg.norm(B_origin) - 0.1)
# Constraint 2: End of line B should not be closer to origin [0,0] than the end of line A
B_ends = np.array([
B_origin + [0, -length_B],
B_origin + [-length_B, 0],
B_origin + [0, length_B]
])
constraint2 = []
for B_end, A_end in zip(B_ends, A_ends):
dist_B_end_to_origin = np.linalg.norm(B_end)
dist_A_end_to_origin = np.linalg.norm(A_end)
constraint2.append(dist_B_end_to_origin - dist_A_end_to_origin - 0.1)
all_constraints = [constraint1] + constraint2
# Combine all constraints
return all_constraints
# Wrapper for constraints in SciPy format
def constraint_func(params, A_ends):
constraints = distance_constraints(params, A_ends)
return [{'type': 'ineq', 'fun': lambda params, c=c:c} for c in constraints]
And then here's my code to run Scipy's optimize function
def debug_callback(params):
print(f"Current parameters: {params}")
print(f"Constraint values: {distance_constraints(params, A_ends)}")
print(f"Objective value: {max_distance_to_A(params, A_ends)}")
initial_guess = [0.5, 0.5, 0.6] # [x, y, length_B]
# Optimize
result = minimize(
fun=max_distance_to_A,
x0=initial_guess,
args=(A_ends,),
constraints=constraint_func(initial_guess, A_ends),
method='SLSQP',
callback=debug_callback,
options={'disp': True, 'ftol': 1e-5}
)
# Extract optimal origin and length of line B
B_origin_opt = result.x[:2]
length_B_opt = result.x[2]
# Display results
print("Optimal origin of line B:", B_origin_opt)
print("Optimal length of line B:", length_B_opt)
print("Minimum of maximum distance:", result.fun)
print(f"Constraint values: {distance_constraints(result.x, A_ends)}")
When I run the code, scipy minimizes the objective function while ignoring the constraints, and then runs until the iteration limit is reached since the constraints are not respected but doesn't try to change the control variables to fix this.
Objective value: 1.2382318026714528e-08
Iteration limit reached (Exit mode 9)
Current function value: 1.2382318026714528e-08
Iterations: 100
Function evaluations: 1088
Gradient evaluations: 100
Optimal origin of line B: [-1.22005976e-09 -5.21614408e-09]
Optimal length of line B: 0.49999999289408037
Minimum of maximum distance: 1.2382318026714528e-08
Constraint values: [-0.09999999464306945, -0.10000000188977556, -0.10000000588585986, -0.10000001232206371]
I'm trying to create a script that will minimize the maximum distance between the ends of two lines at three different orientations. The first line, A, has a fixed origin and length, while line B's origin and length are the parameters I'm trying to optimize to minimize the maximum distance between the end points. I have two constraints, the Euclidean distance between the origins must be greater than 0.1, and the endpoints of line B for each rotation have to be at least 0.1 units further away from the origin of line A than the endpoints of line A for that rotation. When I run the script, the constraints are ignored.
Here is my code to create the optimization problem. Constraint 1 and 2 are both ignored.
# Define the length of line A
length_A = 0.5
# Define the endpoint positions of line A after each rotation
A_origin = np.array([0, 0])
A_ends = np.array([
A_origin + [0, -length_A], # Initial position
A_origin + [-length_A, 0], # After 90-degree rotation
A_origin + [0, length_A]
])
# Display results
print("Origin of line A:", A_origin)
print("Ends of line A:", A_ends)
# Objective function: Minimize the maximum distance between endpoints of A and B
def max_distance_to_A(params, A_ends):
B_origin = np.array(params[:2])
length_B = params[2]
# Define rotations for line B endpoint based on origin and length
B_ends = np.array([
B_origin + [0, -length_B], # Initial position
B_origin + [-length_B, 0], # After 90-degree rotation
B_origin + [0, length_B] # After 180-degree rotation
])
# Calculate distances between each corresponding end of lines A and B
distances = [np.linalg.norm(B_end - A_end) for B_end, A_end in zip(B_ends, A_ends)]
# Objective: Minimize the maximum distance across all rotations
return max(distances)
# Constraint function: Ensures all constraints are met
def distance_constraints(params, A_ends):
B_origin = np.array(params[:2])
length_B = params[2]
# Constraint 1: Origin of line B must be at least 0.1 away from origin of A
constraint1 = (np.linalg.norm(B_origin) - 0.1)
# Constraint 2: End of line B should not be closer to origin [0,0] than the end of line A
B_ends = np.array([
B_origin + [0, -length_B],
B_origin + [-length_B, 0],
B_origin + [0, length_B]
])
constraint2 = []
for B_end, A_end in zip(B_ends, A_ends):
dist_B_end_to_origin = np.linalg.norm(B_end)
dist_A_end_to_origin = np.linalg.norm(A_end)
constraint2.append(dist_B_end_to_origin - dist_A_end_to_origin - 0.1)
all_constraints = [constraint1] + constraint2
# Combine all constraints
return all_constraints
# Wrapper for constraints in SciPy format
def constraint_func(params, A_ends):
constraints = distance_constraints(params, A_ends)
return [{'type': 'ineq', 'fun': lambda params, c=c:c} for c in constraints]
And then here's my code to run Scipy's optimize function
def debug_callback(params):
print(f"Current parameters: {params}")
print(f"Constraint values: {distance_constraints(params, A_ends)}")
print(f"Objective value: {max_distance_to_A(params, A_ends)}")
initial_guess = [0.5, 0.5, 0.6] # [x, y, length_B]
# Optimize
result = minimize(
fun=max_distance_to_A,
x0=initial_guess,
args=(A_ends,),
constraints=constraint_func(initial_guess, A_ends),
method='SLSQP',
callback=debug_callback,
options={'disp': True, 'ftol': 1e-5}
)
# Extract optimal origin and length of line B
B_origin_opt = result.x[:2]
length_B_opt = result.x[2]
# Display results
print("Optimal origin of line B:", B_origin_opt)
print("Optimal length of line B:", length_B_opt)
print("Minimum of maximum distance:", result.fun)
print(f"Constraint values: {distance_constraints(result.x, A_ends)}")
When I run the code, scipy minimizes the objective function while ignoring the constraints, and then runs until the iteration limit is reached since the constraints are not respected but doesn't try to change the control variables to fix this.
Objective value: 1.2382318026714528e-08
Iteration limit reached (Exit mode 9)
Current function value: 1.2382318026714528e-08
Iterations: 100
Function evaluations: 1088
Gradient evaluations: 100
Optimal origin of line B: [-1.22005976e-09 -5.21614408e-09]
Optimal length of line B: 0.49999999289408037
Minimum of maximum distance: 1.2382318026714528e-08
Constraint values: [-0.09999999464306945, -0.10000000188977556, -0.10000000588585986, -0.10000001232206371]
Share
Improve this question
edited Nov 27, 2024 at 21:59
Conor Carson
asked Nov 21, 2024 at 15:55
Conor CarsonConor Carson
113 bronze badges
5
|
1 Answer
Reset to default 0The very simplest fix here is to change your constraints so that they directly reference distance_constraints
.
constraints=[{'type': 'ineq', 'fun': distance_constraints, 'args': (A_ends,)}],
(Note: although distance_constraints()
is returning multiple values, it is fine to put this in a single constraint, as SciPy supports vector-valued constraints.)
Full corrected code
import numpy as np
from scipy.optimize import minimize
import matplotlib.pyplot as plt
# Define the length of line A
length_A = 0.5
# Define the endpoint positions of line A after each rotation
A_origin = np.array([0, 0])
A_ends = np.array([
A_origin + [0, -length_A], # Initial position
A_origin + [-length_A, 0], # After 90-degree rotation
A_origin + [0, length_A]
])
# Display results
print("Origin of line A:", A_origin)
print("Ends of line A:", A_ends)
# Objective function: Minimize the maximum distance between endpoints of A and B
def max_distance_to_A(params, A_ends):
B_origin = np.array(params[:2])
length_B = params[2]
# Define rotations for line B endpoint based on origin and length
B_ends = np.array([
B_origin + [0, -length_B], # Initial position
B_origin + [-length_B, 0], # After 90-degree rotation
B_origin + [0, length_B] # After 180-degree rotation
])
# Calculate distances between each corresponding end of lines A and B
distances = [np.linalg.norm(B_end - A_end) for B_end, A_end in zip(B_ends, A_ends)]
# Objective: Minimize the maximum distance across all rotations
return max(distances)
# Constraint function: Ensures all constraints are met
def distance_constraints(params, A_ends):
B_origin = np.array(params[:2])
length_B = params[2]
# Constraint 1: Origin of line B must be at least 0.1 away from origin of A
constraint1 = (np.linalg.norm(B_origin) - 0.1)
# Constraint 2: End of line B should not be closer to origin [0,0] than the end of line A
B_ends = np.array([
B_origin + [0, -length_B],
B_origin + [-length_B, 0],
B_origin + [0, length_B]
])
constraint2 = []
for B_end, A_end in zip(B_ends, A_ends):
dist_B_end_to_origin = np.linalg.norm(B_end)
dist_A_end_to_origin = np.linalg.norm(A_end)
constraint2.append(dist_B_end_to_origin - dist_A_end_to_origin - 0.1)
all_constraints = [constraint1] + constraint2
# Combine all constraints
return all_constraints
def debug_callback(params):
print(f"Current parameters: {params}")
print(f"Constraint values: {distance_constraints(params, A_ends)}")
print(f"Objective value: {max_distance_to_A(params, A_ends)}")
initial_guess = [0.5, 0.5, 0.6] # [x, y, length_B]
# Optimize
result = minimize(
fun=max_distance_to_A,
x0=initial_guess,
args=(A_ends,),
constraints=[{'type': 'ineq', 'fun': distance_constraints, 'args': (A_ends,)}],
method='SLSQP',
callback=debug_callback,
options={'disp': True, 'ftol': 1e-5}
)
# Extract optimal origin and length of line B
B_origin_opt = result.x[:2]
length_B_opt = result.x[2]
# Display results
print("Optimal origin of line B:", B_origin_opt)
print("Optimal length of line B:", length_B_opt)
print("Minimum of maximum distance:", result.fun)
print(f"Constraint values: {distance_constraints(result.x, A_ends)}")
From the output, we can tell that the constraints are all non-negative.
Plotting the result
But do the constraints/objective match the problem we mean to solve? For this kind of thing, I like to write some plotting code, to visually represent the problem at hand.
def get_B_ends(params):
B_origin = np.array(params[:2])
length_B = params[2]
# Constraint 1: Origin of line B must be at least 0.1 away from origin of A
constraint1 = (np.linalg.norm(B_origin) - 0.1)
# Constraint 2: End of line B should not be closer to origin [0,0] than the end of line A
B_ends = np.array([
B_origin + [0, -length_B],
B_origin + [-length_B, 0],
B_origin + [0, length_B]
])
return B_origin, B_ends
def plot_optimization(result, initial_guess):
for pt in A_ends:
plt.plot([A_origin[0], pt[0]], [A_origin[1], pt[1]], 'ro-', label='A')
B_origin, B_ends = get_B_ends(initial_guess)
for pt in B_ends:
plt.plot([B_origin[0], pt[0]], [B_origin[1], pt[1]], 'bo-', label='initial guess')
B_origin, B_ends = get_B_ends(result.x)
# print(A_origin, A_ends)
# print(B_origin, B_ends)
for pt in B_ends:
plt.plot([B_origin[0], pt[0]], [B_origin[1], pt[1]], 'go-', label='final optimized')
plt.axis('equal')
handles, labels = plt.gca().get_legend_handles_labels()
by_label = dict(zip(labels, handles))
plt.legend(by_label.values(), by_label.keys())
plt.show()
plot_optimization(result, initial_guess)
Output:
Comparing your goals, code comments, and the output of this optimization process, I see three things to comment on.
- In constraint 1, you want the origin of line B to be at least 0.1 away from origin of A. However, it is actually checking that it is at least 0.1 away from (0, 0). This will only be the same thing if A_origin is at (0, 0).
- In constraint 2, you want the end of line B to not be closer to origin [0,0] than the end of line A. However, it is actually checking that it is not closer than the end of line A plus 0.1.
- It's not doing a very good job of finding a point which minimizes the maximum distance between corresponding rotated A and B points.
Issues 1 and 2 are not something I can solve - you might have intended the things I'm pointing out. Issue 3 is solvable, however.
To me, this looks like a local minima problem: the optimization problem contains multiple local minima, and the local optimizer is not capable of telling the difference between a local minima and the global minimum.
To check this, a simple way is to vary the initial value, and see if it can find a better result.
initial_guess = [-0.01, 0.0, 0.6]
With this initial guess, it finds a better result. This shows that global optimization is required.
Basinhopping
I usually use basinhopping for a problem like this. Basinhopping is a two-phase approach that alternates between random hops and local minimization.
However, in order to respect your constraints, it requires that you define an acceptance function. This function detects if the random hopping step has changed the parameters in a way that is forbidden by your constraints.
Example:
from scipy.optimize import basinhopping
initial_guess = [0.5, 0.5, 0.6] # [x, y, length_B]
ftol = 1e-5
def accept_test(f_new=None, x_new=None, f_old=None, x_old=None):
cons = distance_constraints(x_new, A_ends)
# SLSQP allows constraint violations of up to ftol
# Allow that here too
accept = (np.array(cons) >= -ftol).all()
return accept
result = basinhopping(
max_distance_to_A,
x0=initial_guess,
minimizer_kwargs=dict(
args=(A_ends,),
constraints=[{'type': 'ineq', 'fun': distance_constraints, 'args': (A_ends,)}],
method='SLSQP',
callback=debug_callback,
options={'disp': True, 'ftol': ftol}
),
accept_test=accept_test,
niter=100,
)
# Extract optimal origin and length of line B
B_origin_opt = result.x[:2]
length_B_opt = result.x[2]
# Display results
print("Optimal origin of line B:", B_origin_opt)
print("Optimal length of line B:", length_B_opt)
print("Minimum of maximum distance:", result.fun)
print(f"Constraint values: {distance_constraints(result.x, A_ends)}")
plot_optimization(result, initial_guess)
print(result)
Output:
本文标签: python 3xScipyoptimize not respecting constraintsStack Overflow
版权声明:本文标题:python 3.x - Scipy.optimize not respecting constraints - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1736309216a1933936.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
A_ends
andA_origin
variables are not defined. – Nick ODell Commented Nov 21, 2024 at 16:57return [{'type': 'ineq', 'fun': lambda params: c} for c in constraints]
This lambda uses a variable not passed as an argument. This means that it uses the value of the variable at the time that the lambda is called, not the value at the time it is defined. This is called late binding, and it would cause this code to only respect the last constraint defined. – Nick ODell Commented Nov 21, 2024 at 17:00return [{'type': 'ineq', 'fun': lambda params, c=c: c} for c in constraints]
(assuming the rest of that code makes sense). – Matt Haberland Commented Nov 22, 2024 at 0:08params
; they just returnc
. So if they are not satisfied initially, they will never be satisfied. I'd suggest defining your constraint functions separately, without a loop. They should acceptparams
and return a non-negative values whenparams
is acceptable. – Matt Haberland Commented Nov 28, 2024 at 23:48