Created
October 14, 2018 01:58
-
-
Save caudamus/041a4b7ee590b8ae004989a656f1bf53 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
from math import atan2, pi, sqrt | |
''' | |
Our goal is to figure out an analytic formulation of the area the goat can reach. | |
*Note:* We'll take advantage of symmetry to only solve the top half of the area. | |
We have two circles which intersect: | |
- Goat circle, with radius G, centered at (0, 0) | |
- Field circle, with radius R, centered at (0, R) | |
The formulas for these two circles: | |
- x**2 + y**2 = G**2 | |
- (x - R)**2 + y**2 = R**2 which simplifies to x**2 + y**2 = 2yR | |
Setting them equal to each other we get: | |
- x = G**2 / 2R | |
- y = sqrt(G**2 - x**2) | |
Now we need to figure out the area that the goat can reach, given this point. | |
We do this adding taking the sections of both circles, | |
and then subtracting the overlapping triangle. | |
The angles of the two circle sections are: | |
- Goat section, angle alpha = arctan(y, x) | |
- Field section, angle beta = pi - 2alpha | |
So the area sections are: | |
- Goat section area A = alpha G**2 / 2 | |
- Field section area B = beta R**2 / 2 | |
- Overlapping section O = y * R / 2 | |
So we can now calculate the total area (in the top half) reachable by the goat, | |
- reachable_area = A + B - O | |
We want to compare to the total area in the top half field | |
- field_area = pi * R**2 / 2 | |
Diving this reachable area by the field area gives us the reachable fraction | |
- reachable_fraction = reachable_area / field_area | |
We'll solve this by binary search for a ratio which gives us reachable_fraction = 1/2. | |
- Start with the widest possible range: 0 <= ratio <= 2 | |
- Check the fraction reachable | |
- if it's greater than the target, lower the upper end of the range | |
- if it's less than the target, raise the lower end of the range | |
We want to stop at some, point, so we'll stop when the range is < 1e-10. | |
(If the field is a mile wide, our tether is accurate to ~3 millionths of an inch) | |
''' | |
def get_reachable_fraction(ratio): | |
''' | |
Input: ratio of goat tether length / field radius | |
Output: fraction of the field reachable by goat | |
''' | |
# NOTE: all of the areas are just considering the top half of the field! | |
R = 1 # Field radius | |
G = ratio * R # Goat tether length | |
x = G**2 / (2 * R) # Intersection point | |
y = sqrt(G**2 - x**2) # Intersection point | |
alpha = atan2(y, x) # Goat section angle | |
beta = pi - 2 * alpha # Field section angle | |
A = alpha * G**2 / 2 # Goat section area | |
B = beta * R**2 / 2 # Field section area | |
overlap = y * R / 2 # Overlapping area | |
reachable_area = A + B - overlap # This is the top area the goat can reach | |
field_area = pi * R**2 / 2 # This is the whole (top half) field | |
return reachable_area / field_area # This is the reachable fraction | |
# Start with a widest possible range, and binary search! | |
target_fraction = 0.5 # This is our goal fraction of the field | |
low_guess, high_guess = 0, 2 # Our initial guesses | |
allowed_error = 1e-10 # This is how we know we're close enough to stop | |
# Binary search down to a very narrow range for the correct value | |
steps = 0 | |
while high_guess - low_guess > allowed_error: # This converges very quickly! | |
steps += 1 | |
# We'll check the middle of the range and choose the top or bottom half | |
mid_guess = (high_guess + low_guess) / 2 | |
# If this new guess is too high, bring down the upper bound | |
if get_reachable_fraction(mid_guess) > target_fraction: | |
high_guess = mid_guess | |
else: # Otherwise if it's too low, bring up the lower bound | |
low_guess = mid_guess | |
print('Answer: {:.9f}'.format((high_guess + low_guess) / 2)) # 1.158728473 | |
print('Converged after', steps, 'steps') # 35 steps |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment