def _get_stream_func_bdy(self): # calculate stream function on boundary # - continuous on boundary (also should be periodic) import numpy as np import geometry_planar as GP # coordinates of polygon x,y = np.array(self.coords).transpose() Nx = len(x) nvec = np.arange(Nx) sfun = 0*x # for each singularity (just outside polygon) perimeter,resolution,\ spacings,tangent_dirn = GP.curve_info(self.singularities) for i,sing in enumerate(self.singularities): an = self.sing_coeffs[i] branch_dir = np.pi/2.+tangent_dirn[i] atan2 = GP.arctan2_branch(y,x=x,branch_point=sing,branch_dir=branch_dir) atan2 = GP.make_arctan2_cts(atan2) sfun = sfun+an*atan2 self.stream_func_bdy = sfun return
def get_contour_lengths(self,bmap=None,pobj=None,show=True,test_function=None,\ func_vals_orig=None): import numpy as np ########################################################################################### class area_info: ######################################################################################## def __init__(self,xy_bdy_coords,xy_conts,\ area,perimeter,lengths,\ spherical_geometry=False,\ ll_bdy_coords=None,ll_contours=None,\ func_vals=None,stream_func=None): import MIZchar as mizc # NB use "1*" to remove pointers to the arrays outside the function # - like copy, but works for lists also self.xy_bdy_coords = 1*xy_bdy_coords # (x,y) coordinates of boundary (ie in projected space) self.xy_contours = 1*xy_conts # (x,y) coordinates of each contour self.lengths = 1*lengths # lengths of each contour self.area = area # area of polygon self.perimeter = perimeter # perimeter of polygon self.FDI = mizc.frac_dim_index([self.area,self.perimeter]) # lon-lat info if present self.spherical_geometry = spherical_geometry if spherical_geometry: if ll_contours is None: raise ValueError('"ll_contours" not given') if ll_bdy_coords is None: raise ValueError('"ll_bdy_coords" not given') self.lonlat_contours = 1*ll_contours # (lon,lat) coordinates of each contour self.ll_bdy_coords = 1*ll_bdy_coords # (lon,lat) coordinates of boundary if func_vals is not None: self.func_vals = 1*func_vals # value of function used by Laplace's equation if stream_func is not None: self.stream_func = 1*stream_func # value of stream function produced by Laplace's equation # some summarising info about "lengths" lens = np.array(lengths) self.length_mean = np.mean(lens) self.length_median = np.median(lens) self.length_percentile05 = np.percentile(lens,5) self.length_percentile95 = np.percentile(lens,95) return ########################################################################################### if bmap is not None: # boundary/area information # - use routines for sphere import geometry_sphere as GS print('getting contour lengths on sphere...\n') x,y = np.array(self.coords).transpose() lons,lats = bmap(x,y,inverse=True) lst = list(np.array([lons,lats]).transpose()) ll_bdy_coords = [(lo,la) for lo,la in lst] area = GS.area_polygon_ellipsoid(lons,lats,radians=False) arclen = GS.arc_length(lons,lats,radians=False,closed=True) perimeter = arclen[-1] # get isolines of function (plot if pobj is not None) contours = self.get_isolines(pobj=pobj,show=show,test_function=test_function,\ func_vals_orig=func_vals_orig) ll_conts = [] lengths = [] for cont in contours: # list of coords (tuples) x,y = np.array(cont).transpose() lons,lats = bmap(x,y,inverse=True) arclen = GS.arc_length(lons,lats,radians=False,closed=False) # lengths.append(arclen[-1]) #perimeter lst = list(np.array([lons,lats]).transpose()) tups = [(lo,la) for lo,la in lst] ll_conts.append(tups) # output object with all the info AI = area_info(self.coords,contours,\ area,perimeter,lengths,\ func_vals=self.func_vals,stream_func=self.stream_func_bdy,\ ll_bdy_coords=ll_bdy_coords,ll_contours=ll_conts,\ spherical_geometry=True) else: # boundary/area information # - just Euclidean routines import geometry_planar as GP print('getting contour lengths in the plane...\n') x,y = np.array(self.coords).transpose() area = self.shapely_polygon.area perimeter = self.shapely_polygon.length # get isolines of function (plot if pobj is not None) contours = self.get_isolines(pobj=pobj,show=show,\ test_function=test_function,\ func_vals_orig=func_vals_orig) lengths = [] for cont in contours: # list of coords (tuples) P = GP.curve_info(cont,closed=False)[0] # perimeter lengths.append(P) # output object with all the info # area_info(xy_bdy_coords,xy_conts,\ # area,perimeter,lengths,\ # ll_bdy_coords=None,ll_conts=None,\ # func_vals=None,stream_func=None,): AI = area_info(self.coords,contours,\ area,perimeter,lengths,\ func_vals=self.func_vals,stream_func=self.stream_func_bdy,\ spherical_geometry=False) return AI
def _check_coords(self,coords,fvals,do_check=True): import rtree.index as Rindex import numpy as np import MIZchar as mizc import geometry_planar as GP import shapely.geometry as shgeom # http://toblerity.org/shapely/manual.html ############################################################# def do_fill(pcoords,res=None,f=None): ############################################################# if res is None: print('Too few points - adding more') else: print('Checking boundary points are close enough together') xc,yc = np.array(pcoords).transpose() xc2,yc2 = mizc.fill_poly(xc,yc,res=res) pc = np.array([xc2,yc2]).transpose() # arrays of arrays: coords = rows pc = [tuple(xy) for xy in pc] # list of tuples ############################################################# ############################################################# if f is not None: fv = [] i0 = -1 for i,cc in enumerate(pc): if cc in pcoords: # NB this includes 1st and last points i0 = i0+1 fv.append(f[i0]) else: x,y = cc x0,y0 = pcoords[i0] x1,y1 = pcoords[i0+1] wt = np.sqrt(pow(x-x0,2)+pow(y-y0,2))/np.sqrt(pow(x1-x0,2)+pow(y1-y0,2)) # f0 = f[i0] f1 = f[i0+1] fv.append(f0+wt*(f1-f0)) ############################################################# return pc,fv ############################################################# pcoords = list(coords) fvals = list(fvals) pcoords = [tuple(cc) for cc in pcoords] ####################################################### # check for periodicity if (pcoords[0]!=pcoords[-1]): # not periodic # - BUT need last and first coordinate the same for shapely polygon print('not periodic') pcoords.append(pcoords[0]) fvals.append(fvals[0]) ####################################################### if do_check: ####################################################### # want to go round curve anti-clockwise x,y = np.array(pcoords).transpose() area = GP.area_polygon_euclidean(x,y) self.area = abs(area) if area<0: print("Curve traversed in clockwise direction - reversing arrays' order") pcoords.reverse() fvals.reverse() ####################################################### ############################################################# #check that there are enough points Nmin = 40 while len(pcoords)<Nmin: #double no of points pcoords,fvals = do_fill(pcoords,f=fvals) ############################################################# ############################################################# #check the points are evenly spaced spc = GP.curve_info(pcoords)[2] res = np.mean(spc) pcoords,fvals = do_fill(pcoords,res=res,f=fvals) ####################################################### ####################################################### # make shapely polygon # (coords now has end-point repeated, # self.coords doesn't) self.shapely_polygon = shgeom.Polygon(pcoords) self.coords = pcoords[:-1] self.func_vals = np.array(fvals[:-1]) ####################################################### ####################################################### # gets spacings,directions between points, and perimeter self.number_of_points = len(self.coords) self.perimeter,self.resolution,\ self.spacings,self.tangent_dirn = GP.curve_info(self.coords) ####################################################### ####################################################### # make rtree index idx = Rindex.Index() for i,(xp,yp) in enumerate(self.coords): idx.insert(i,(xp,yp,xp,yp)) # a point is a rectangle of zero side-length self.coord_index = idx ####################################################### return
def _check_singularities(self): # don't want too many singularities import numpy as np import geometry_planar as GP N0 = self.number_of_points N1 = self.number_of_singularities # set limits for N1 Nthresh = 100 frac = 1.3 # frac = .2 # if self.solve_exactly: # # number of singularities and number of boundary points should be the same # # - this doesn't work too well - need more sing's # Ntarget = N0 if N0 <= Nthresh: # if N0<=Nthresh, try to get N1~N0 Ntarget = N0+30 else: # try to get N1~frac*N0, if frac*N0<Nthresh Ntarget = int(np.max([np.round(frac*N0),Nthresh])) print('\nChecking singularities...\n') print('Number of boundary points : '+str(N0)) print('Number of singularities : '+str(N1)) print('Desired number of singularities : '+str(Ntarget)) check_again = False # NB this applies to the case where N1==Ntarget # NB also if N1>=Ntarget, we can reduce N1 to Ntarget exactly in 1 go if N1>Ntarget: ########################################################################## # reduce the number of sing's by increasing spacing between points coords = self.singularities perimeter,resolution,\ spacings,tangent_dirn = GP.curve_info(coords) # s_target = N1/float(Ntarget)*resolution # increase mean spacing between points new_coords = [coords[0]] # ss = 0 s0 = 0 s1 = s0+s_target for n,c0 in enumerate(coords[1:]): ss = ss+spacings[n] if ss>=s1: new_coords.append(c0) s0 = s1 s1 = s0+s_target ####################################################################### # update list of singularities: N2 = len(new_coords) self.singularities = new_coords self.number_of_singularities = N2 ####################################################################### if N2>Ntarget: ####################################################################### # delete a few points randomly from new_coords to get right number Ndel = N2-Ntarget while Ndel>0: idel = np.random.randint(N2) new_coords.remove(new_coords[idel]) N2 = len(new_coords) Ndel = N2-Ntarget # update list of singularities self.singularities = new_coords self.number_of_singularities = N2 ####################################################################### ########################################################################## elif N2<Ntarget: ####################################################################### # get some more points from self.singularities # *this adds more at start preferentially # but shouldn't matter since it won't be too many Nadd = Ntarget-N2 iadd = 0 for coord in self.singularities: if coord in new_coords: # in there so now need to insert before next element of new_coords iadd = iadd+1 else: # adds coord before new_coords[iadd] new_coords = list(new_coords) new_coords.insert(iadd,coord) N2 = len(new_coords) Nadd = Ntarget-N2 # new element so still need to increase point at which to place iadd = iadd+1 if Nadd==0: break # update list of singularities self.singularities = new_coords self.number_of_singularities = N2 ####################################################################### ########################################################################## print('New number of singularities : '+str(Ntarget)+'\n') ########################################################################## elif N1<Ntarget: ########################################################################## # set up for another iteration if 0: # increase buffer_resolution # - not so good since this only adds more points round corners fac = int(np.ceil(1.2*Ntarget/float(N1))) self.buffer_resolution = fac*self.buffer_resolution else: import MIZchar as mizc xs,ys = np.array(self.singularities).transpose() # double the number of sings # TODO this may not be the best way # - perhaps specify resolution xs,ys = mizc.fill_poly(xs,ys) xys = np.array([xs,ys]).transpose() # self.singularities = [tuple(xyi) for xyi in xys] self.number_of_singularities = len(xys) ########################################################################## # need to call _get_singularities again, # to reset the singularities and check them check_again = True print('Trying again to get singularities (too few)...\n') ########################################################################## return check_again
def __init__(self,coords,func_vals,singularities=None): # initialise object import numpy as np import shapely.geometry as shgeom # http://toblerity.org/shapely/manual.html import geometry_planar as GP # also in py_funs import rtree.index as Rindex ####################################################### self.func_vals = np.array(func_vals) self.coords = 1*coords # no longer pointer to list from outside the function pcoords = 1*coords if (pcoords[0][0]!=pcoords[-1][0]) and (pcoords[0][1]!=pcoords[-1][1]): # not periodic # - BUT need last and first coordinate the same for shapely polygon print('not periodic') pcoords.append(pcoords[0]) ####################################################### ####################################################### # make shapely polygon # (coords now has end-point repeated, # self.coords doesn't) self.shapely_polygon = shgeom.Polygon(pcoords) ####################################################### ####################################################### # want to go round curve anti-clockwise x,y = GP.coords2xy(self.coords) area = GP.area_polygon_euclidean(x,y) self.area = abs(area) if area<0: print("Curve traversed in clockwise direction - reversing arrays' order") self.func_vals = list(self.func_vals) self.func_vals.reverse() self.func_vals = np.array(self.func_vals) # self.coords = list(self.coords) self.coords.reverse() self.coords = self.coords # make rtree index idx = Rindex.Index() for i,(xp,yp) in enumerate(self.coords): idx.insert(i,(xp,yp,xp,yp)) # a point is a rectangle of zero side-length self.coord_index = idx ####################################################### self.number_of_points = len(self.coords) # gets spacings,directions between points, and perimeter self.perimeter,self.resolution,\ self.spacings,self.tangent_dirn = GP.curve_info(self.coords) # # get points around boundary of polygon, then # expand points by an amount related to the spacings of the coords self.buffer_resolution = 16 # default buffer resolution # (number of segments used to approximate a quarter circle around a point.) # self.solve_exactly = solve_exactly # # if True: number of singularities and number of boundary points should be the same if singularities is None: get_sings = True else: get_sings = False self.singularities = singularities self.number_of_singularities = len(self.singularities) print('Number of boundary points : '+str(self.number_of_points)) print('Number of singularities : '+str(self.number_of_singularities)+'\n') while get_sings: # May need a couple of repetitions if too few singularities are found get_sings = self._get_singularities() # solve Laplace's eqn (get a_n) self._solve_laplace_eqn() # # evaluate error on boundary: print('\nCalculating error on the boundary...') self._eval_solution_boundary() print(str(self.boundary_error)+'\n') if 0: # evaluate normal derivative -> stream function on boundary: print('\nCalculating normal derivative -> stream function on the boundary...\n') self._eval_derivs_boundary() elif 1: # use analytical definition of stream function # - for log, this is arctan2 (+const) self._get_stream_func_bdy() return