3

I have a black box function baseball that determines the magnitude of the distance that a baseball travels when launched. It takes the initial speed, the angle of launch and the initial spin. Assuming that the spin remains the same, but varying the launch angle, I want to numerically determine the minimum speed that produces a given distance. i.e.

d = baseball(given_speed, given_angle, given_spin)
speed_minimum, angle = find_speed(d, given_spin)
baseball(speed_minimum, angle, given_spin) == d
> true

I've considered partially differentiating baseball to find the minimum of speed but that wouldn't constrain the distance. 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.

3 Answers 3

2

partially differentiating baseball() ... [or] Lagrange multipliers

Those are both good mathematical techniques. But they're incompatible with your claim that baseball() is "a black box". Describing it in that way means we only get to make calls and observe its reported distance. At best that would give us a numeric partial differential, rather than an analytic one. We will make such numeric calculations as outlined below.

Hold given_spin constant, throughout.

Ignoring spin and interaction with an atmosphere, we know that ballistic flight in a constant G-field will look like a parabola, with max distance achieved at θ = 45° or π/4 radians. So use that as initial guess for angle.

Now we have a binary search problem, bounded between low (zero) and high speeds. Choose high to be roughly infinite, big enough that baseball() returns something greater than the target distance. The distance is monotonic with speed.

Do binary search in the usual way, until high - low < epsilon for some suitably small epsilon threshold. Now we know approximately the correct speed at which to throw, and we will hold it constant. But what is the correct angle?

Let's maximize the distance. Presumably air resistance is going to force us to throw at a slightly higher angle than 45°. Verify that baseball() gives a slightly worse distance for angle - epsilon, and a slightly better distance for angle + epsilon.

Good, now we have another search problem, though not monotonic. Choose interval to be "small". Keep incrementing angle by interval, and see the distance get slightly larger each time. Of course at some point the distance will begin to shrink slightly (a negative delta). So back away from that, cut interval in half, and note the sign of the delta. Keep cutting interval in half while the delta is negative. Continue probing the baseball() black box once you see a positive delta, advancing the angle by interval. Terminate the search once interval is smaller than a set threshold.

By now we have the correct angle, and we are throwing the ball slightly faster than necessary, so the reported distance is slightly greater than our original target. So repeat the binary search for speed using fixed angle, with bounds of low = 0 and high of the previously computed speed.

At this point you could in principle lather rinse repeat, refining the estimate of the angle, and then of the speed. But you already have probably arrived at the required accuracy.

Sign up to request clarification or add additional context in comments.

1 Comment

This was great! I used scipy for root finding instead of binary search for the distances and maximisation for the angle but it's still the exact same logic. Thank you!
2

Your problem suits the Lagrange multipliers cuas it seeks to enhance a function with constraints on its variables.

In math:

Here you need to minimize the speed while meet the condition,

baseball(speed, angle, spin) = target_distance.

The Lagrwnge equations are.

∂L/∂speed = 1 + λ × (∂baseball/∂speed)

∂L/∂angle = 0 + λ × (∂baseball/∂angle)

So we need to increase the speed and angle by a value, such as 0.1 ( it's a hyperparameter).

In python:

There is a library scipy which has the fsolve( ) function to deal with Lagrange,

It follows Newton method:

  1. Take an initial guess.

  2. Calculate the diversity around it.

  3. Move to the solution.

  4. Keep doing that to reach the solution.

So we will use a popular initialization suits thr baseball , speed = 30, angle =45, the spin =0.1

Here you go the code:

import numpy as np
from scipy.optimize import fsolve

def lagrange_system(x, target_d, spin):
    speed, angle, lambda_val = x
    
    d_current = baseball(speed, angle, spin)
    d_speed = baseball(speed + 0.1, angle, spin)
    d_angle = baseball(speed, angle + 0.1, spin)
    
    eq1 = 1 + lambda_val * (d_speed - d_current) / 0.1
    eq2 = lambda_val * (d_angle - d_current) / 0.1
    eq3 = d_current - target_d
    
    return [eq1, eq2, eq3]

def find_min_speed(target_d, spin):
    initial_guess = [30.0, 45.0, 1.0]
    
    solution = fsolve(lagrange_system, initial_guess, args= 
               (target_d, spin))
    speed_min, best_angle, lambda_opt = solution
    
    return speed_min, best_angle

You could learn more about Lagrange multipliers following this link

  1. https://www.geeksforgeeks.org/engineering-mathematics/lagrange-multipliers/

  2. https://heathhenley.dev/posts/numerical-methods/

Comments

2

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.