#!/usr/bin/env python # coding: utf-8 # #
Snub Dodecahedron Volume Calculated Six Ways
# #
Six Python classes, each calculating the volume of the Snub Dodecahedron
of unit edge length in a different way.
by [Mark Adams](https://archimedeansolids.github.io/)
# # ## Way 1, 2, and 3: Coxeter, Kosters, and Adams closed form expressions #
#
# # $$ # Coxeter\,expression\;=\; \frac{12\color{Crimson}{\eta^2}\color{Black}{(3}\color{DarkGoldenrod}{\varphi} # \color{Black}{+1)-}\color{Crimson}{\eta} # \color{Black}{(36}\color{DarkGoldenrod}{\varphi} # \color{Black}{+7)-(53}\color{DarkGoldenrod}{\varphi} # \color{Black}{+6)}}{6\sqrt{(3-\color{Crimson}{\eta^2}\color{black}{)^3}}} # $$ # #
# # $$ # Kosters\,expression\;=\; # \frac{(3\color{DarkGoldenrod}{\varphi}+1)\color{Teal}{\xi^2}+(3\color{DarkGoldenrod}{\varphi}+1)\color{Teal}{\xi}- # \color{DarkGoldenrod}{\varphi}/6-2}{\sqrt{3\color{Teal}{\xi^2}-\color{DarkGoldenrod}{\varphi^2}}} # $$ # #
# # $$ # Adams\,expression\;=\; # \frac{10\color{DarkGoldenrod}{\varphi}}{3}\sqrt{ \color{DarkGoldenrod}{\varphi^2} # \color{Black}{+3}\color{Crimson}{\eta} # \color{Black}{(}\color{DarkGoldenrod}{\varphi} # \color{Black}{+}\color{Crimson}{\eta} # \color{Black}{)}} # \,+\,\frac{\color{DarkGoldenrod}{\varphi^2}}{2}\sqrt{ 5 + 5\sqrt{5}\color{DarkGoldenrod}{\varphi}\color{Crimson}{\eta} # \color{Black}{(}\color{DarkGoldenrod}{\varphi} # \color{Black}{+}\color{Crimson}{\eta} # \color{Black}{)}} # \\ # $$ # #
# # $$ # \text{where}\; # \color{Crimson}{eta}\; # \text{ is defined as} # \quad # \color{Crimson}{\eta\;\equiv\;\sqrt[3]{ \frac{ # \color{DarkGoldenrod}{\varphi} # \color{Crimson}{}}{2} + \frac{1}{2} \sqrt{ # \color{DarkGoldenrod}{\varphi} # \color{Crimson}{-}\frac{5}{27}}}\;+\;} # \color{Crimson}{ # \sqrt[3]{ \frac{ # \color{DarkGoldenrod}{\varphi} # \color{Crimson}{}}{2} - \frac{1}{2} \sqrt{ # \color{DarkGoldenrod}{\varphi} # \color{Crimson}{-}\frac{5}{27}}}} # \quad # \text{ and } # \color{Teal}{xi} # \quad # \color{Teal}{\xi\,}=\, # \frac{ # \color{DarkGoldenrod}{\varphi}}{ # \color{Crimson}{\eta} # } # $$ # # ## Way 4: Coxeter polynomial # Volume is the real root of x: # $$ # 187445810737515625 - # 182124351550575000\,x^2 + # 6152923794150000\,x^4 + # \\ # 1030526618040000\,x^6 + # 162223191936000\,x^8 - # 3195335070720\,x^{10} + # 2176782336\,x^{12} = 0 # $$ # # ## Way 5: Rajpoot convergent # # Iterate to find Circumradius # # # \begin{align*} # \\& # C_{0} = 2.3\quad C_{n+1}=\frac{f(C_{n})}{f\prime (C_{n})} # \\& # f(x)= 256(3-\sqrt{5})x^8 - 128(13-2\sqrt{5})x^6 + 32(35-3\sqrt{5})x^4 - 16(19-\sqrt{5})x^2 +(29-\sqrt{5}) # \\& # f\prime(x)= 2048(3-\sqrt{5})x^7 - 768(13-2\sqrt{5})x^5 + 128(35-3\sqrt{5})x^3 - 32(19-\sqrt{5})x # \\& # Volume = \left( \frac{20\sqrt{3C^2-1}}{3} + # \sqrt{ \frac{10(5+2\sqrt{5})C^2 - 5((7+3\sqrt{5})}{2}} \right) # \end{align*} # # ## Way 6: 3D Numerical # # 3D distances drive a numerical root finder. Two triangle objects are defined as adjacent triangles on a regular icosahedron. The algorithm is applied to one point on the plane of each triangle so that the distance of a side of an inscribed snub triangle is equal to the side of a non inscribed snub triangle. # # ## References # # # [1] [Adams, Mark S.](https://archimedeansolids.github.io/pdf/ArchimedeanPlatonicSolids.pdf)["Archimedean Platonic Solids"](https://archive.org/details/archimedeanplatonicsolids/mode/2up) 1985. # # [2] [Rajpoot, Harish C.](https://notionpress.com/author/HarishChandraRajpoot) [“Optimum Solution of Snub Dodecahedron"](https://www.researchgate.net/publication/335967411_Optimum_Solution_of_Snub_Dodecahedron_an_Archimedean_Solid_by_Using_HCR's_Theory_of_Polygon_Newton-Raphson_Method) 2014. # # [3] Wikipedia [Snub Dodecahedron](https://en.wikipedia.org/wiki/Snub_dodecahedron) # # [4] Coxeter, Harold S. M.; Longuet-Higgins, M. S.; and Miller, J. C. P. ["Uniform Polyhedra."](https://royalsocietypublishing.org/doi/abs/10.1098/rsta.1954.0003) Phil. Trans. Roy. Soc. London, 1954. # # [5] [Weisstein, Eric W.](https://mathworld.wolfram.com/about/author.html) ["Snub Dodecahedron."](https://mathworld.wolfram.com/SnubDodecahedron.html) From MathWorld--A Wolfram Web Resource. # # # # In[1]: ################################################################################################# # Six Python classes, each calculating the volume of the Snub Dodecahedron in a different way. # # # # 3D distances drive a numerical root finder. Two triangle objects are defined as adjacent # # triangles on a regular icosahedron. The algorithm is applied to one point on the plane # # of each triangle so that the distance of a side of an inscribed snub triangle is equal # # to the side of a non inscribed snub triangle. # # # # by Mark Adams, orcID: https://orcid.org/0000-0003-4469-051X # # # ################################################################################################# try: import mpmath as mp except: print("Please wait a minute as mpmath is installed...") cmd = ('python -m pip install -U pip') os.system(cmd) cmd = ('pip install mpmath') os.system(cmd) import mpmath as mp class Way_1_Coxeter_Closed_Form(object): '''Volume of Snub Dodecahedron from https://en.wikipedia.org/wiki/Snub_dodecahedron''' def __init__(self): mp.mp.dps = 55 self.run_calculation() def run_calculation(self): self.digits = 53 self.phi = (mp.sqrt(5) + 1)/2 # Golden Section self.eta = (self.phi/2 + (mp.mpf(1)/2)*mp.sqrt(self.phi - mp.mpf(5)/27)) ** ((mp.mpf(1)/3)) + ( (self.phi/2 - (mp.mpf(1)/2)*mp.sqrt(self.phi - mp.mpf(5)/27)) ** ((mp.mpf(1)/3))) part1 = (12*self.eta ** 2)*(3*self.phi + 1) part2 = self.eta*(36*self.phi + 7) part3 = (53*self.phi + 6) part4 = 4*(part1 - part2 - part3) part5 = 3*((2*mp.sqrt(3 - self.eta*self.eta)) ** 3) self.volume_coxeter_wikipedia = part4/part5 print("") print("Volume calculation of Snub Dodecahedron from") print(" H.S.M. Coxeter's work as shown in Wikipedia") print(" https://en.wikipedia.org/wiki/Snub_dodecahedron") self.print_volume() def print_volume(self): print('Way 1. Coxeter Closed Form Volume = %s'%(mp.nstr(self.volume_coxeter_wikipedia, self.digits))) return class Way_2_Kosters_Closed_Form(object): '''Volume of the Snub Dodecahedron from https://archive.org/details/archimedeanplatonicsolids''' def __init__(self): mp.mp.dps = 55 self.run_calculations() def run_calculations(self): self.digits = 52 self.phi = (mp.sqrt(5) + 1)/2 # Golden Section self.eta = (self.phi/2 + (mp.mpf(1)/2)*mp.sqrt(self.phi - mp.mpf(5)/27)) ** ((mp.mpf(1)/3)) + ( (self.phi/2 - (mp.mpf(1)/2)*mp.sqrt(self.phi - mp.mpf(5)/27)) ** ((mp.mpf(1)/3))) self.xi = self.phi/self.eta self.kosters_volume = ((3*self.phi + 1)*self.xi**2 + (3*self.phi + 1)*self.xi - self.phi/6 - 2) / ( mp.sqrt(3*self.xi**2 - self.phi**2)) print("") print("Volume calculation of the Snub Dodecahedron from Kosters Closed Form") self.print_volume() def print_volume(self): print('Way 2. Kosters Closed Form Volume = %s'%(mp.nstr(self.kosters_volume, self.digits + 1))) return class Way_3_Adams_Closed_Form(object): '''Volume of the Snub Dodecahedron from https://archive.org/details/archimedeanplatonicsolids''' def __init__(self): mp.mp.dps = 55 self.run_calculations() def run_calculations(self): self.digits = 52 self.phi = (mp.sqrt(5) + 1)/2 # Golden Section self.eta = (self.phi/2 + (mp.mpf(1)/2)*mp.sqrt(self.phi - mp.mpf(5)/27)) ** ((mp.mpf(1)/3)) + ( (self.phi/2 - (mp.mpf(1)/2)*mp.sqrt(self.phi - mp.mpf(5)/27)) ** ((mp.mpf(1)/3))) self.radius_triangle = (self.phi/(2*mp.sqrt(3)))*mp.sqrt(self.phi ** 2 + 3*self.eta*(self.phi + self.eta)) self.radius_circumradius = mp.sqrt((self.phi ** 2*(self.phi ** 2 + 3*self.eta*(self.phi + self.eta)) + 4)/12) self.radius_pentagon = (self.phi/2)*mp.sqrt((1/(self.phi*mp.sqrt(5))) + self.eta*(self.phi + self.eta)) self.radius_mid = (mp.mpf(1)/2)*mp.sqrt((self.phi ** 4 + 1 + 3*self.phi ** 2*self.eta*(self.phi + self.eta))/3) self.volume_adams = (10*self.phi/3)*mp.sqrt(self.phi ** 2 + 3*self.eta*(self.phi + self.eta)) + ( (self.phi ** 2/2)*mp.sqrt(5 + 5*mp.sqrt(5)*self.phi*self.eta*(self.phi + self.eta))) print("") print("Volume calculation of the Snub Dodecahedron as shown from") print(" Mark Adams's book 'Archimedean & Platonic Solids'") print(" https://archive.org/details/archimedeanplatonicsolids") print('Eta = %s'%(mp.nstr(self.eta, self.digits))) print('Radius_triangle = %s'%(mp.nstr(self.radius_triangle, self.digits))) print('Radius_circumradius = %s'%(mp.nstr(self.radius_circumradius, self.digits))) print('Radius_pentagon = %s'%(mp.nstr(self.radius_pentagon, self.digits))) print('Radius_mid = %s'%(mp.nstr(self.radius_mid, self.digits))) self.print_volume() def print_volume(self): print('Way 3. Adams Closed Form Volume = %s'%(mp.nstr(self.volume_adams, self.digits + 1))) return class Way_4_Coxeter_Polynomial(object): '''Volume calculation of Snub Dodecahedron from http://mathworld.wolfram.com/SnubDodecahedron.html''' def __init__(self): self.run_calculation() def finder_f(self, f_value): self.f_value = f_value self.f_counter += 1 self.delta = ( mp.mpf(187445810737515625) - mp.mpf(182124351550575000)*self.f_value ** 2 + mp.mpf(6152923794150000)*self.f_value ** 4 + mp.mpf(1030526618040000)*self.f_value ** 6 + mp.mpf(162223191936000)*self.f_value ** 8 - mp.mpf(3195335070720)*self.f_value ** 10 + mp.mpf(2176782336)*self.f_value ** 12) return (self.delta) def run_calculation(self): mp.mp.dps = 60 self.digits = 53 tolerance = mp.mpf('1.0e-55') self.f_counter = 0 mp.findroot(self.finder_f, mp.mpf(36.0), tol=tolerance, solver='halley', maxsteps=2000, verbose=False) self.volume_coxeter_mathworld = self.f_value print("") print("Volume calculation of Snub Dodecahedron (%d iterations) from"%(self.f_counter)) print(" H.S.M. Coxeter's work as shown in Eric Weisstein's MathWorld") print(" http://mathworld.wolfram.com/SnubDodecahedron.html") self.print_volume() return def print_volume(self): print('Way 4. Coxeter Polynomial Volume = %s'%(mp.nstr(self.volume_coxeter_mathworld, self.digits))) return class Way_5_Rajpoot_Convergent(object): '''Volume calculation of the Snub Dodecahedron as shown from https://www.researchgate.net/publication/335967411_Optimum_Solution_of_ Snub_Dodecahedron_an_Archimedean_Solid_by_Using_HCR's_Theory_of_Polygon_Newton-Raphson_Method''' def __init__(self): mp.mp.dps = 55 self.run_calculations() def r_function(self, x): value = 256*(mp.mpf(3) - mp.sqrt(5))*x ** 8 - ( 128*(mp.mpf(13) - 2*mp.sqrt(5))*x ** 6) + ( 32*(mp.mpf(35) - 3*mp.sqrt(5))*x ** 4) - ( 16*(mp.mpf(19) - mp.sqrt(5))*x ** 2) + ( (mp.mpf(29) - mp.sqrt(5))) return (value) def r_function_prime(self, x): value = 2048*(mp.mpf(3) - mp.sqrt(5))*x ** 7 - ( 768*(mp.mpf(13) - 2*mp.sqrt(5))*x ** 5) + ( 128*(mp.mpf(35) - 3*mp.sqrt(5))*x ** 3) - ( 32*(mp.mpf(19) - mp.sqrt(5))*x) return (value) def run_calculations(self): self.digits = 52 self.iterations = 7 self.C = mp.mpf(2.3) # Starting value for i in range(self.iterations): i = i self.C = self.C - self.r_function(self.C)/self.r_function_prime(self.C) self.volume_rajpoot = 20*mp.sqrt(3*self.C ** 2 - 1)/mp.mpf(3) + ( mp.sqrt((10*(5 + 2*mp.sqrt(5))*self.C ** 2 - 5*(7 + 3*mp.sqrt(5)))/mp.mpf(2))) print("") print("Volume calculation of the Snub Dodecahedron from Harish Chandra Rajpoot's paper") print(" 'Optimum Solution of Snub Dodecahedron'") print('Circumradius (%d iterations) = %s'%(self.iterations, mp.nstr(self.C, self.digits))) self.print_volume() def print_volume(self): print('Way 5. Rajpoot Convergent Volume = %s'%(mp.nstr(self.volume_rajpoot, self.digits + 1))) return class Way_6_3D_Numerical(object): '''3D distances drive a numerical root finder''' def __init__(self): mp.mp.dps = 55 self.verbose = True self.snub_dodecahedron_numerical_findroot() class Point(object): def __init__(self, parent, x, y, z): self.parent = parent self.x = x self.y = y self.z = z def make_copy(self): self.mcopy = self.parent.Point(self.parent, self.x, self.y, self.z) return self.mcopy def make_vector_to(self, endpoint): self.vector = self.parent.Point(self.parent, endpoint.x - self.x, endpoint.y - self.y, endpoint.z - self.z) return self.vector def distance_to(self, endpoint): self.distance = mp.sqrt( (endpoint.x - self.x) ** 2 + (endpoint.y - self.y) ** 2 + (endpoint.z - self.z) ** 2) return self.distance def add_vector(self, point): self.x += point[0] self.y += point[1] self.z += point[2] return def scale(self, ratio): self.ratio = ratio self.scaled_xyz = [ self.x*self.ratio, self.y*self.ratio, self.z*self.ratio] return self.scaled_xyz class Triangle(object): def __init__(self, parent, pointA, pointB, pointC): self.parent = parent self.point_A = parent.Point(self.parent, pointA[0], pointA[1], pointA[2]) self.point_B = parent.Point(self.parent, pointB[0], pointB[1], pointB[2]) self.point_C = parent.Point(self.parent, pointC[0], pointC[1], pointC[2]) self.center = parent.Point(self.parent, (self.point_A.x + self.point_B.x + self.point_C.x)/3, (self.point_A.y + self.point_B.y + self.point_C.y)/3, (self.point_A.z + self.point_B.z + self.point_C.z)/3) self.ratio_1 = mp.mp.mpf('.1') self.ratio_2 = mp.mp.mpf('.1') self.vector_to_g_1 = self.center.make_vector_to(self.point_B) self.vector_to_g_2 = self.point_B.make_vector_to(self.point_C) self.vector_to_j_1 = self.center.make_vector_to(self.point_C) self.vector_to_j_2 = self.point_C.make_vector_to(self.point_A) def set_ratio_1(self, ratio_1): self.ratio_1 = ratio_1 return def set_ratio_2(self, ratio_2): self.ratio_2 = ratio_2 return def calculate(self): self.point_g = self.center.make_copy() self.point_g.add_vector(self.vector_to_g_1.scale(self.ratio_1)) self.point_g.add_vector(self.vector_to_g_2.scale(self.ratio_2)) self.point_j = self.center.make_copy() self.point_j.add_vector(self.vector_to_j_1.scale(self.ratio_1)) self.point_j.add_vector(self.vector_to_j_2.scale(self.ratio_2)) # Ratio from center of eq. triangle to vertex by edge length is sqrt(3) self.distance_g_j = self.point_g.distance_to(self.center)*mp.sqrt(3) return def info(self): digits = 5 D1 = mp.nstr(self.point_A.distance_to(self.point_B), digits) D2 = mp.nstr(self.point_B.distance_to(self.point_C), digits) D3 = mp.nstr(self.point_C.distance_to(self.point_A), digits) self.parent.log.info('Base: %s\n %s\n %s'%(D1, D2, D3)) return def snub_dodecahedron_numerical_findroot(self): self.make_icosa() self.triangle_1 = self.Triangle(self, self.icosa[1], self.icosa[0], self.icosa[2]) self.triangle_2 = self.Triangle(self, self.icosa[3], self.icosa[2], self.icosa[0]) self.run_numerical_solution() self.digits = 52 self.D = self.triangle_1.distance_g_j self.phi = (mp.sqrt(5) + 1)/2 # Golden Section self.radius_triangle_unit_icosahedron = self.phi ** 2/(2*mp.sqrt(3)) self.r_tri = self.radius_triangle_unit_icosahedron/self.D self.r_circ = mp.sqrt(self.r_tri ** 2 + (2*mp.sin(mp.pi/3)) ** (-2)) self.r_pent = mp.sqrt(self.r_circ ** 2 - (mp.mpf(2)*mp.sin((mp.pi/5))) ** (-2)) self.r_mid = mp.sqrt(self.r_tri ** 2 + (2*mp.tan(mp.pi/3)) ** (-2)) self.volume_numerical = 80*mp.sqrt(3)/4*(mp.mpf(1)/3)*self.r_tri + ( 12*(mp.mpf(5)/4)*mp.sqrt(self.phi ** 3/mp.sqrt(5))*(mp.mpf(1)/3)*self.r_pent) print("") print("Volume calculation of Snub Dodecahedron from %d 3D numerical iterations"%( self.finder_counter)) print('Radius_triangle = %s'%(mp.nstr(self.r_tri, self.digits))) print('Radius_circumradius = %s'%(mp.nstr(self.r_circ, self.digits))) print('Radius_pentagon = %s'%(mp.nstr(self.r_pent, self.digits))) print('Radius_mid = %s'%(mp.nstr(self.r_mid, self.digits))) self.print_volume() def run_numerical_solution(self): mp.mp.dps = 60 self.verbose = True self.finder_counter = 0 tolerance = mp.mpf('1.0e-55') mp.findroot(self.finder_f, mp.mpc('0.1', '0.1'), tol=tolerance, solver='halley', maxsteps=2000, verbose=False) return def finder_f(self, ratio): 'Called by mp.findroot' self.finder_counter += 1 self.triangle_1.set_ratio_1(ratio.real) self.triangle_2.set_ratio_1(ratio.real) self.triangle_1.set_ratio_2(ratio.imag) self.triangle_2.set_ratio_2(ratio.imag) self.triangle_1.calculate() self.triangle_2.calculate() self.distance_j_gprime = self.triangle_1.point_j.distance_to(self.triangle_2.point_g) self.distance_g_gprime = self.triangle_1.point_g.distance_to(self.triangle_2.point_g) self.delta_1 = self.triangle_1.distance_g_j - self.distance_j_gprime self.delta_2 = self.triangle_1.distance_g_j - self.distance_g_gprime self.delta_distance = mp.mpc(self.delta_1, self.delta_2) return (self.delta_distance) def make_icosa(self, verbose=0, unit_edge_length=1): self.icosa_faces = [ (0, 1, 2), (0, 2, 3), (0, 3, 4), (0, 4, 5), (0, 5, 1), (11, 6, 7), (11, 7, 8), (11, 8, 9), (11, 9, 10), (11, 10, 6), (1, 2, 6), (2, 3, 7), (3, 4, 8), (4, 5, 9), (5, 1, 10), (6, 7, 2), (7, 8, 3), (8, 9, 4), (9, 10, 5), (10, 6, 1)] c63 = 1/mp.sqrt(5) # Cos(a) a = arctan(2) = 63.434... degrees s63 = 2/mp.sqrt(5) # Sin(a) a = arctan(2) = 63.434... degrees c72 = mp.sqrt((3 - mp.sqrt(5))/8) # Cos(72) s72 = mp.sqrt((5 + mp.sqrt(5))/8) # Sin(72) c36 = mp.sqrt((3 + mp.sqrt(5))/8) # Cos(36) s36 = mp.sqrt((5 - mp.sqrt(5))/8) # Sin(36) self.icosa = [ [0, 0, 1], [s63, 0, c63], [s63*c72, s63*s72, c63], [-s63*c36, s63*s36, c63], [-s63*c36, -s63*s36, c63], [s63*c72, -s63*s72, c63], [s63*c36, s63*s36, -c63], [-s63*c72, s63*s72, -c63], [-s63, 0, -c63], [-s63*c72, -s63*s72, -c63], [s63*c36, -s63*s36, -c63], [0, 0, -1]] if unit_edge_length: self.point_A = self.Point(self, self.icosa[0][0], self.icosa[0][1], self.icosa[0][2]) self.point_B = self.Point(self, self.icosa[1][0], self.icosa[1][1], self.icosa[1][2]) adjust = 1/self.point_A.distance_to(self.point_B) for i, points in enumerate(self.icosa): for j, xyz in enumerate(points): self.icosa[i][j] *= adjust if verbose: digits = 3 for i, xyz in enumerate(self.icosa): x = mp.nstr(xyz[0], digits) y = mp.nstr(xyz[1], digits) z = mp.nstr(xyz[2], digits) print('V %d (%s, %s %s)'%(i + 1, x, y, z)) return def print_volume(self): print('Way 6. 3D Numerical Volume = %s'%(mp.nstr(self.volume_numerical, self.digits + 1))) return way_1_coxeter_closed_form = Way_1_Coxeter_Closed_Form() way_2_kosters_closed_form = Way_2_Kosters_Closed_Form() way_3_adams_closed_form = Way_3_Adams_Closed_Form() way_4_coxeter_polynomial = Way_4_Coxeter_Polynomial() way_5_rajpoot_convergent = Way_5_Rajpoot_Convergent() way_6_3d_numerical = Way_6_3D_Numerical() print("") print("Volumes:") way_1_coxeter_closed_form.print_volume() way_2_kosters_closed_form.print_volume() way_3_adams_closed_form.print_volume() way_4_coxeter_polynomial.print_volume() way_5_rajpoot_convergent.print_volume() way_6_3d_numerical.print_volume()