I then considered creating an np.linspace for every angle between -pi and pi and then iterating to find the value of speed that fits using reasonable bounding estimates but that seems like a huge computational effort. I've heard of Lagrange multipliers but don't understand how those constraints would work here.
SciPy has a built in function that can optimize a function subject to a constraint using Lagrange multipliers. (Technically, it uses KKT multipliers.) Specifically, SciPy's minimize(method=’SLSQP’) can do that. It will be more efficient than brute force, as it uses information about local derivatives to determine what direction to move in to try new points.
Presuming your problem only has one local minima, you could use minimize() like this.
import numpy as np
from scipy.optimize import minimize, NonlinearConstraint
def baseball(params):
# print(params)
speed_mps, given_angle_degrees, given_spin = params
# https://physics.stackexchange.com/questions/107933/how-to-calculate-distance-travelled-from-velocity-vector-and-angle
g = 9.8 # m/s**2
given_angle_radians = np.radians(given_angle_degrees)
d = speed_mps ** 2 * np.sin(2 * given_angle_radians) / g
# Ignore spin
_ = given_spin
return d
def loss(params):
speed_mps, given_angle_degrees, given_spin = params
return speed_mps
# Initial guess
x0 = np.array([10, 10, 0])
min_dist = 10 # meters
constraints = NonlinearConstraint(baseball, lb=min_dist, ub=np.inf)
bounds = [(0, np.inf), (0, 90), (-np.inf, np.inf)]
result = minimize(loss, x0, bounds=bounds, constraints=constraints, method='SLSQP', options=dict(disp=True))
print(result)
print("distance of solution", baseball(result.x))
print("speed of solution", result.x[0])
print("angle of solution", result.x[1])
Alternatively, you might try COBYQA, which you can use by replacing method='SLSQP' with method='COBYQA'.
I have made your minimum distance constraint an inequality constraint. In other words, SLSQP is permitted to try speeds that over-shoot your minimum distance. If you prefer that it try to exactly meet this distance, you can replace ub=np.inf with ub=min_dist.
If you have a problem that has multiple local minima, then it is possible for a local minimizer such as SLSQP to get stuck in a local minima that is not globally optimal. In that situation, you might want to try basinhopping(), which uses random search to find plausible points and calls a local minimizer to optimize the points and see which of them are truly better.