-
Notifications
You must be signed in to change notification settings - Fork 0
/
trianglesolver.py
235 lines (196 loc) · 8.66 KB
/
trianglesolver.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
"""
Written by Steven Byrnes, http://sjbyrnes.com/
Download: https://pypi.python.org/pypi/trianglesolver/
Source code repository: https://github.com/sbyrnes321/trianglesolver
This little package applies the law of sines or cosines to find all the
sides and angles of a triangle, if you know some of the sides and/or
angles.
The main function defined by this package is solve(...). Simple example::
from math import pi
from trianglesolver import solve
a,b,c,A,B,C = solve(b=7.6, c=8.3, A=pi/3)
Following the usual convention, lower-case letters are side lengths and
capital letters are angles. Corresponding letters are opposite each other,
e.g. side b is opposite angle B.
All angles are in radians! However, you can use the degree constant to
convert::
from trianglesolver import solve, degree
a,b,c,A,B,C = solve(b=7, A=5*degree, B=70*degree)
print(C / degree)
"""
from __future__ import division, print_function
from math import sin, cos, pi, sqrt, acos, asin, isclose
# 5 * degree is 5 degrees in radians
# X / degree is the angle X expressed in degrees
degree = pi/180
# ---------- Helper functions -------------------------------------- #
def aaas(D, E, F, f):
""" This function solves the triangle and returns (d,e,f,D,E,F) """
d = f * sin(D) / sin(F)
e = f * sin(E) / sin(F)
return (d,e,f,D,E,F)
def sss(d,e,f):
""" This function solves the triangle and returns (d,e,f,D,E,F) """
assert d + e > f and e + f > d and f + d > e
F = acos((d**2 + e**2 - f**2) / (2 * d * e))
E = acos((d**2 + f**2 - e**2) / (2 * d * f))
D = pi - F - E
return (d,e,f,D,E,F)
def sas(d,e,F):
""" This function solves the triangle and returns (d,e,f,D,E,F) """
f = sqrt(d**2 + e**2 - 2 * d * e * cos(F))
return sss(d,e,f)
def ssa(d, e, D, ssa_flag):
""" This function solves the triangle and returns (d,e,f,D,E,F)
See docstring for solve() for definition of ssa_flag"""
assert ssa_flag in ('acute', 'obtuse', 'forbid'), 'Invalid value of ssa_flag'
sinE = sin(D) * e / d
# This whole part is to calculate E
if isclose(sinE, 1):
# Right triangle, where the solution is unique
E = pi/2
else:
assert sinE < 1, 'No such triangle'
E_acute = asin(sinE)
E_obtuse = pi - E_acute
acute_is_valid = (0 < (pi - D - E_acute) < pi)
obtuse_is_valid = (0 < (pi - D - E_obtuse) < pi)
if ssa_flag == 'acute':
assert acute_is_valid, 'No such triangle'
E = E_acute
elif ssa_flag == 'obtuse':
assert obtuse_is_valid, 'No such triangle'
E = E_obtuse
else:
assert ssa_flag == 'forbid'
if acute_is_valid and obtuse_is_valid:
raise ValueError('Two different triangles fit this description')
if (not acute_is_valid) and (not obtuse_is_valid):
raise ValueError('No such triangle')
E = E_acute if acute_is_valid else E_obtuse
# Now that we know E, the rest is straightforward
F = pi - D - E
e,f,d,E,F,D = aaas(E,F,D,d)
return (d,e,f,D,E,F)
# ---------- Main function you should use ------------------------- #
def solve(a=None, b=None, c=None, A=None, B=None, C=None, ssa_flag='forbid'):
"""
Solve to find all the information about a triangle, given partial
information.
a, b, c, A, B, and C are the three sides and angles. (e.g. A is the angle
opposite the side of length a.) Out of these six possibilities, you need
to tell the program exactly three. Then the program will tell you all six.
It returns a tuple (a, b, c, A, B, C).
"ssa" is the situation when you give two sides and an angle which is not
between them. This is usually not enough information to specify a unique
triangle. Usually there are two possible triangles—except for a special
case with right triangles where the two possible triangles are the same
(the equation has a "double root"), and some cases where one of the two
possible triangles has a negative angle.
Therefore there is an 'ssa_flag'. You can set it to'forbid' (raise an error
if the answer is not unique - the default setting), or 'acute' (where the
unknown angle across from the known side is chosen to be acute) or 'obtuse'
(similarly).
"""
if sum(x is not None for x in (a,b,c,A,B,C)) != 3:
raise ValueError('Must provide exactly 3 inputs')
if sum(x is None for x in (a,b,c)) == 3:
raise ValueError('Must provide at least 1 side length')
assert all(x > 0 for x in (a,b,c,A,B,C) if x is not None)
assert all(x < pi for x in (A,B,C) if x is not None)
assert ssa_flag in ('forbid', 'acute', 'obtuse')
# If three sides are known...
if sum(x is not None for x in (a,b,c)) == 3:
a,b,c,A,B,C = sss(a,b,c)
return (a,b,c,A,B,C)
# If two sides and one angle are known...
if sum(x is not None for x in (a,b,c)) == 2:
# ssa case
if all(x is not None for x in (a, A, b)):
a,b,c,A,B,C = ssa(a, b, A, ssa_flag)
elif all(x is not None for x in (a, A, c)):
a,c,b,A,C,B = ssa(a, c, A, ssa_flag)
elif all(x is not None for x in (b, B, a)):
b,a,c,B,A,C = ssa(b, a, B, ssa_flag)
elif all(x is not None for x in (b, B, c)):
b,c,a,B,C,A = ssa(b, c, B, ssa_flag)
elif all(x is not None for x in (c, C, a)):
c,a,b,C,A,B = ssa(c, a, C, ssa_flag)
elif all(x is not None for x in (c, C, b)):
c,b,a,C,B,A = ssa(c, b, C, ssa_flag)
# sas case
elif all(x is not None for x in (a, b, C)):
a,b,c,A,B,C = sas(a, b, C)
elif all(x is not None for x in (b, c, A)):
b,c,a,B,C,A = sas(b, c, A)
elif all(x is not None for x in (c, a, B)):
c,a,b,C,A,B = sas(c, a, B)
else:
raise ValueError('Oops, this code should never run')
return (a,b,c,A,B,C)
# If one side and two angles are known...
if sum(x is not None for x in (a,b,c)) == 1:
# Find the third angle...
if A is None:
A = pi - B - C
elif B is None:
B = pi - A - C
else:
C = pi - A - B
assert A > 0 and B > 0 and C > 0
# Then solve the triangle...
if c is not None:
a,b,c,A,B,C = aaas(A,B,C,c)
elif a is not None:
b,c,a,B,C,A = aaas(B,C,A,a)
else:
c,a,b,C,A,B = aaas(C,A,B,b)
return (a,b,c,A,B,C)
raise ValueError('Oops, this code should never run')
#---------------- Tests ------------------------------------------------- #
def tuple_isclose(x,y):
return all(isclose(x[i], y[i]) for i in range(len(x)))
def test_triangle(a,b,c,A,B,C):
""" Check that the triangle satisfies the law of cosines and law of
sines"""
assert isclose(a/sin(A), b/sin(B))
assert isclose(a/sin(A), c/sin(C))
assert isclose(c**2, a**2 + b**2 - 2 * a * b * cos(C))
assert isclose(a**2, b**2 + c**2 - 2 * b * c * cos(A))
assert isclose(b**2, c**2 + a**2 - 2 * c * a * cos(B))
def test_solver(a,b,c):
""" Test that the program works, for a triangle of sides a,b,c."""
# first find the angles, testing SSS
a1,b1,c1,A,B,C = solve(a=a,b=b,c=c)
assert tuple_isclose((a,b,c), (a1,b1,c1))
test_triangle(a,b,c,A,B,C)
tri = (a,b,c,A,B,C)
# SAS tests
assert tuple_isclose(tri, solve(a=a,b=b,C=C))
assert tuple_isclose(tri, solve(a=a,c=c,B=B))
assert tuple_isclose(tri, solve(b=b,c=c,A=A))
# SAA / ASA tests
assert tuple_isclose(tri, solve(a=a, A=A, B=B))
assert tuple_isclose(tri, solve(a=a, A=A, C=C))
assert tuple_isclose(tri, solve(a=a, B=B, C=C))
assert tuple_isclose(tri, solve(b=b, A=A, B=B))
assert tuple_isclose(tri, solve(b=b, A=A, C=C))
assert tuple_isclose(tri, solve(b=b, B=B, C=C))
assert tuple_isclose(tri, solve(c=c, A=A, B=B))
assert tuple_isclose(tri, solve(c=c, A=A, C=C))
assert tuple_isclose(tri, solve(c=c, B=B, C=C))
Atype = 'acute' if A < pi/2 else 'obtuse'
Btype = 'acute' if B < pi/2 else 'obtuse'
Ctype = 'acute' if C < pi/2 else 'obtuse'
# SSA tests
assert tuple_isclose(tri, solve(a=a, b=b, A=A, ssa_flag=Btype))
assert tuple_isclose(tri, solve(a=a, b=b, B=B, ssa_flag=Atype))
assert tuple_isclose(tri, solve(a=a, c=c, A=A, ssa_flag=Ctype))
assert tuple_isclose(tri, solve(a=a, c=c, C=C, ssa_flag=Atype))
assert tuple_isclose(tri, solve(b=b, c=c, B=B, ssa_flag=Ctype))
assert tuple_isclose(tri, solve(b=b, c=c, C=C, ssa_flag=Btype))
def run_lots_of_tests():
for a,b,c in ((7,8,9), (5,6,10), (5,10,6), (10,5,6), (5,12,13),
(12,5,13), (12,13,5)):
test_solver(a,b,c)
print('All tests pass!')