Source code for geotecha.mathematics.geometry

# geotecha - A software suite for geotechncial engineering
# Copyright (C) 2018  Rohan T. Walker (rtrwalker@gmail.com)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see http://www.gnu.org/licenses/gpl.html.
"""Some routines loosely related to geometry."""


from __future__ import division, print_function
import numpy as np
import sympy
import matplotlib
import matplotlib.pyplot as plt

from mpl_toolkits.mplot3d import Axes3D


[docs]def xyz_from_pts(pts, close_polygon = False): """Extract x, y and z values from an (n, 3) or (n, 2) shaped array Parameters ---------- pts : array_like Array of x, y or x, y, z points. close_polygon : boolean, optional If True then 1st point will be repeated i.e. to close the polygon. Default close_polygon=False. Returns ------- x, y, z : 1d ndarrays 1d array of x coords, y coords, z coords. """ pts = np.asarray(pts) if close_polygon: x = np.zeros(len(pts)+1, dtype=float) y = np.zeros(len(pts)+1, dtype=float) z = np.zeros(len(pts)+1, dtype=float) x[:-1] = pts[:,0] x[-1] = x[0] y[:-1] = pts[:,1] y[-1] = y[0] if len(pts[0]) == 3: z[:-1] = pts[:,2] z[-1] = z[0] else: x = pts[:,0] y = pts[:,1] if len(pts[0]) == 3: z = pts[:,2] else: z = np.zeros_like(x) return x, y, z
[docs]def eqn_of_plane(pts): """Equation of plane defined by polygon points a, b, c, d from a*x+b*y+c*z+d = 0 Looking at the plane, counter-clockwise (CCW) points the positive normal is towards you. For clockwise points (CW) the positive normal is away from you. Parameters ---------- pts : array_like aAray of x, y or x, y, z points. Returns ------- [a, b, c] : ndarray with 3 elements Direction cosines of normal to plane. d : float Constant in plane equation. """ pts = np.asarray(pts) x, y, z = xyz_from_pts(pts, True) a = np.sum((y[:-1] - y[1:]) * (z[:-1] + z[1:])) b = np.sum((z[:-1] - z[1:]) * (x[:-1] + x[1:])) c = np.sum((x[:-1] - x[1:]) * (y[:-1] + y[1:])) n = np.array([a, b, c], dtype=float ) n /= np.linalg.norm(n) p = np.array([np.sum(x[:-1]), np.sum(y[:-1]), np.sum(z[:-1])]) / len(pts) d = -np.dot(p,n) return n, d
[docs]def replace_x0_and_x1_with_vect(s,xyz = ['x','y','z']): """Replaces strings x0 with x[:-1], x1 with x[1:], y0 with y[:-1] ... Parameters ---------- s : string, or sympy_expression Expression to make replacements on xyz : list of strings Replacements will be made for each element of xyz. default xyz=['x','y','z']. Returns ------- out : string string with all replacements made """ endings = ['[:-1]', '[1:]'] s = str(s) for x in xyz: for i, ending in enumerate(endings): s = s.replace('{}{:d}'.format(x,i), '{}{}'.format(x,ending)) return s
[docs]def integrate_f_over_polygon_code(f): """Generate code that will integrate a function over a polygon Parameters ---------- f : sympy expression Expression to be integrated over the polygon. Returns ------- out : str Multiline string of function code """ x, y, z=sympy.symbols('x,y,z') x0, x1, y0, y1, z0, z1=sympy.symbols('x0,x1,y0,y1,z0,z1') t = sympy.symbols('t') s2 = [(x, x0+t*(x1-x0)), (y, y0+t*(y1-y0)), (z, z0+t*(z1-z0))] ff = sympy.integrate(f,x) ff = ff.subs(s2) ff = sympy.integrate(ff, (t,0,1)) ff *= (y1 - y0) #if integrating in y direction 1st then *-(x1-x0) ff = replace_x0_and_x1_with_vect(ff) template ="""def ifxy(pts): "Integrate f = {} over polygon" x, y, z = xyz_from_pts(pts, True) return np.sum({})""" return template.format(str(f), ff)
[docs]def integrate_f_over_polyhedra_code(f): """Generate code that will integrate a function over a polyhedra Parameters ---------- f : sympy expression Expression to be integrated over the polyhedron. Returns ------- out : str Multiline string of funciton code. """ x,y,z=sympy.symbols('x,y,z') x0,x1,y0,y1,z0,z1=sympy.symbols('x0,x1,y0,y1,z0,z1') n1,n2,n3,d = sympy.symbols('n1,n2,n3,d') t = sympy.symbols('t') s1 = [(x,-n2/n1*y-n3/n1*z-d/n1)] #for integration order x,y,z *(z1-z0), for x,z,y *-(y1-y0), ignore n1==0 #s1 = [(y,-n1/n2*x-n3/n2*z-d/n2)] #for integration order y,x,z *-(z1-z0), for y,z,x *(x1-x0), ignore n2==0 #s1 = [(z,-n1/n3*x-n2/n3*y-d/n3)] #for integration order z,x,y *(y1-y0), for z,y,x *-(x1-x0), ignore n3==0 s2 = [(x,x0+t*(x1-x0)),(y,y0+t*(y1-y0)),(z,z0+t*(z1-z0))] ff = sympy.integrate(f, x) ff = ff.subs(s1) ff = sympy.integrate(ff, y) ff = ff.subs(s2) ff = sympy.integrate(ff, (t,0,1)) ff *= z1 - z0 ff = replace_x0_and_x1_with_vect(ff) template ="""def ifxyz(faces): "Integrate f = {} over polyhedron" x, y, z = xyz_from_pts(pts,True) igral = 0 for pts in faces: (n1,n2,n3),d = eqn_of_plane(pts) if n1==0: continue igral += np.sum({}) return igral""" return template.format(str(f), ff)
[docs]def polygon_area(pts): """Area of polygon defined by points Parameters ---------- pts : array_like Array of x, y or x, y, z points. Returns ------- a : float Area of polygon. """ pts = np.asarray(pts) x, y, z = xyz_from_pts(pts,True) n, d = eqn_of_plane(pts) i = np.argmax(n) #project polygon onto plane that is perpendicular to this direction, then integrate the area j = list(range(3)) j.pop(i) #remaining directions e = (x, y, z) def if1dxdy(x, y): """integrate f=1 over polygon of x,y points""" #generate code using integrate_f_over_polygon_code(1) return np.sum((x[:-1]/2 + x[1:]/2)*(-y[:-1] + y[1:])) return if1dxdy(e[j[0]], e[j[1]]) / n[i]
[docs]def polygon_centroid(pts): """Centroid of polygon defined by points Parameters ---------- pts : array_like Array of x, y or x, y, z points. Returns ------- [xc, yc, zc] : ndarray of float Coordinates of centroid. """ def ifxdxdy(x,y): """integrate f=x over polygon of x,y points""" #generate code using integrate_f_over_polygon_code(x) return np.sum((-y[:-1] + y[1:])*(x[:-1]**2/6 + x[:-1]*x[1:]/6 + x[1:]**2/6)) def ifydxdy(x,y): """integrate f=y over polygon of x,y points""" #generate code using integrate_f_over_polygon_code(y) return np.sum((-y[:-1] + y[1:])*(x[:-1]*y[:-1]/3 + x[:-1]*y[1:]/6 + x[1:]*y[:-1]/6 + x[1:]*y[1:]/3)) pts = np.asarray(pts) x, y, z = xyz_from_pts(pts, True) n, d = eqn_of_plane(pts) a = polygon_area(pts) xc=0.0 yc=0.0 zc=0.0 calcd = [False,False,False] if n[0]!=0:#project on yz plane if not calcd[1]: yc = ifxdxdy(y, z)/ (n[0] * a) calcd[1]= True if not calcd[2]: zc = ifydxdy(y, z)/ (n[0] * a) calcd[2]= True if n[1]!=0: #project on zx plane if not calcd[2]: zc = ifxdxdy(z, x)/ (n[1] * a) calcd[2]= True if not calcd[0]: xc = ifydxdy(z, x)/ (n[1] * a) calcd[0]= True if n[2]!=0: #project on xy plane if not calcd[0]: xc = ifxdxdy(x, y)/ (n[2] * a) calcd[0]= True if not calcd[1]: yc = ifydxdy(x, y)/ (n[2] * a) calcd[1]= True return np.array([xc, yc, zc])
[docs]def polygon_2nd_moment_of_area(pts): """2nd moment of area of polygon defined by points Parameters ---------- pts : array_like Array of x, y or x, y, z points. Returns ------- [Ixx, Iyy, Izz] : ndarray of float 2nd moment of area about centroidal x, y, and z axes. """ def ifx2dxdy(x,y,xc): """Integrate f = (x - xc)**2 over polygon of x,y points""" #generate code using integrate_f_over_polygon_code((x-xc)**2) return np.sum((-y[:-1] + y[1:])*(x[:-1]**3/12 + x[:-1]**2*x[1:]/12 - x[:-1]**2*xc/3 + x[:-1]*x[1:]**2/12 - x[:-1]*x[1:]*xc/3 + x[:-1]*xc**2/2 + x[1:]**3/12 - x[1:]**2*xc/3 + x[1:]*xc**2/2)) def ify2dxdy(x,y,yc): """integrate f = (y - yc)**2 over polygon of x,y points""" #generate code using integrate_f_over_polygon_code((y-yc)**2) return np.sum((-y[:-1] + y[1:])*(x[:-1]*y[:-1]**2/4 + x[:-1]*y[:-1]*y[1:]/6 - 2*x[:-1]*y[:-1]*yc/3 + x[:-1]*y[1:]**2/12 - x[:-1]*y[1:]*yc/3 + x[:-1]*yc**2/2 + x[1:]*y[:-1]**2/12 + x[1:]*y[:-1]*y[1:]/6 - x[1:]*y[:-1]*yc/3 + x[1:]*y[1:]**2/4 - 2*x[1:]*y[1:]*yc/3 + x[1:]*yc**2/2)) pts = np.asarray(pts) x, y, z = xyz_from_pts(pts, True) n, d = eqn_of_plane(pts) a = polygon_area(pts) xc,yc,zc = polygon_centroid(pts) ixx=0.0 iyy=0.0 izz=0.0 calcd = [False, False, False] if n[0]!=0:#project on yz plane if not calcd[1]: iyy = ifx2dxdy(y, z, yc)/ (n[0] * a) calcd[1]= True if not calcd[2]: izz = ify2dxdy(y, z, zc)/ (n[0] * a) calcd[2]= True if n[1]!=0: #project on zx plane if not calcd[2]: izz = ifx2dxdy(z, x, zc)/ (n[1] * a) calcd[2]= True if not calcd[0]: ixx = ify2dxdy(z, x, xc)/ (n[1] * a) calcd[0]= True if n[2]!=0: #project on xy plane if not calcd[0]: ixx = ifx2dxdy(x, y, xc)/ (n[2] * a) calcd[0]= True if not calcd[1]: iyy = ify2dxdy(x, y, yc)/ (n[2] * a) calcd[1]= True return np.array([ixx, iyy, izz])
[docs]def polyhedron_volume(faces): """Volume of polyhedron defined by faces defined py pts Parameters ---------- faces : list A list of pts arrays defining x,y,z coords of face vertices. Returns ------- v : float Volume of polyhedron. Notes ----- I think points on a face have to be defined in anti clockwise (CCW) order to give a positive volume. No checks are done to check the order. """ v = 0.0 for pts in faces: pts = np.asarray(pts) x, y, z = xyz_from_pts(pts,True) (n1,n2,n3),d = eqn_of_plane(pts) if n1==0: continue #generate code with integrate_f_over_polyhedra_code(1) v += np.sum((-z[:-1] + z[1:])*((-2*d*y[:-1] - n2*y[:-1]**2 - 2*n3*y[:-1]*z[:-1])/(2*n1) + (d*y[:-1] - d*y[1:] + n2*y[:-1]**2 - n2*y[:-1]*y[1:] + 2*n3*y[:-1]*z[:-1] - n3*y[:-1]*z[1:] - n3*y[1:]*z[:-1])/(2*n1) + (-n2*y[:-1]**2 + 2*n2*y[:-1]*y[1:] - n2*y[1:]**2 - 2*n3*y[:-1]*z[:-1] + 2*n3*y[:-1]*z[1:] + 2*n3*y[1:]*z[:-1] - 2*n3*y[1:]*z[1:])/(6*n1))) return v
[docs]def make_hexahedron(coords): """Assemble the face vertices of a hexahedron Parameters ---------- coords : 8 by 3 ndarray x, y, z coords of 8 corner nodes of hexahedron. node numbering is as per Smith and Grifiths. Returns ------- faces : list of pts arrays List of pts arrays. Each list defines vertices of a face. Notes ----- Node numbering: :: x (6)-------(7) ^ / /| | y / / | | / / / | |/ (2)-------(3) (8) |-------->x | | / | | / | | / (1)-------(4) """ coords = np.asarray(coords) face_nodes=np.array([[1,4,3,2],[3,4,8,7],[8,5,6,7],[2,6,5,1],[6,2,3,7],[5,8,4,1]], dtype=int) face_nodes-=1 return [coords[v,:] for v in face_nodes]
if __name__ == '__main__': import nose nose.runmodule(argv=['nose', '--verbosity=3', '--with-doctest']) # nose.runmodule(argv=['nose', '--verbosity=3']) # a = [[-1,-1,-1], # [-1,-1, 1], # [1,-1, 1], # [1,-1,-1], # [-1,1,-1], # [-1,1,1], # [1,1,1], # [1,1,-1]] # # # b = [ # [[0,0,0], # [0,0,1], # [1,0,0], # [0,0,0]], # # [[1,0,0], # [0,0,1], # [0,1,0], # [1,0,0]], # # [[0,1,0], # [0,0,1], # [0,0,0], # [0,1,0]], # # [[0,0,0], # [1,0,0], # [0,1,0], # [0,0,0]] # ] # c = [[[0, 0, 0], [1, 0, 0], [0, 0, 1], [0, 0, 0]], # [[1, 0, 0], [0, 1, 0], [0, 0, 1], [1, 0, 0]], # [[0, 1, 0], [0, 0, 0], [0, 0, 1], [0, 1, 0]], # [[0, 0, 0], [0, 1, 0], [1, 0, 0], [0, 0, 0]]] ## for v in b: ## print(v[::-1]) # c = [v[::-1] for v in b] # print(c) ## b= make_hexahedron(a) ## print(b) # print(polyhedron_volume(c)) # shp = dict() # shp['unit square'] = [[0,0],[1,0],[1,1],[0,1]] # shp['right tri'] = [[0,0],[1,0],[0,1]] # shp['3D tri'] = [[1,-2,0],[3,1,4],[0,-1,2]] # shp['2D tri'] = [[1,0],[3,4],[0,2]] # shp['octahedral tri'] = [[1,0,0],[0,1,0],[0,0,1]] # for shape,pts in shp.iteritems(): # print(shape) # print(eqn_of_plane(pts)) # print(polygon_area(pts)) # print(polygon_centroid(pts)) # print(polygon_2nd_moment_of_area(pts)) # print() # # #print(integrate_f_over_polygon_code(1)) # x,y,z = sympy.symbols('x,y,z') # xc,yc,zc = sympy.symbols('xc,yc,zc') ## print(integrate_f_over_polygon_code((x-xc)**2)) ## print(integrate_f_over_polygon_code((y-yc)**2)) ## print(integrate_f_over_polygon_code((z-zc)**2)) ## # # shp = dict() # vert = dict() # vert['2unit cube'] = [[-1,-1,-1], [-1,-1,1],[1,-1,1],[1,-1,-1], # [-1,1,-1], [-1,1,1],[1,1,1], [1,1,-1]] # shp['2unit cube'] = make_hexahedron(vert['2unit cube']) # for shape,faces in shp.iteritems(): # print(shape) # print(polyhedron_volume(faces)) # print() # fig = plt.figure() # ax = fig.add_subplot(111, projection='3d') # ax.scatter(np.array(vert[shape])[:,0], np.array(vert[shape])[:,1],np.array(vert[shape])[:,2]) # ax.set_xlabel('X') # ax.set_ylabel('Y') # ax.set_zlabel('Z') # plt.show()