cxroots.contours.AnnulusSector — cxroots 1.1.0 documentation

# Source code for cxroots.contours.AnnulusSector

    import numpy as np
    from scipy import pi, exp

    from ..Contour import Contour
    from ..Paths import ComplexLine, ComplexArc

    class AnnulusSector(Contour):
    	"""
    	A sector of an annulus in the complex plane.

    	Parameters
    	----------
    	center : complex
    		The center of the annulus sector.
    	radii : tuple
    		Tuple of length two of the form (inner_radius, outer_radius)
    	phiRange : tuple
    		Tuple of length two of the form (phi0, phi1).
    		The segment of the contour containing inner and outer circular
    		arcs will be joined, counter clockwise from phi0 to phi1.

    	Examples
    	--------
    	.. plot::
    		:include-source:

    		from numpy import pi
    		from cxroots import AnnulusSector
    		annulusSector = AnnulusSector(center=0.2, radii=(0.5, 1.25), phiRange=(-pi/4, pi/4))
    		annulusSector.show()

    	.. plot::
    		:include-source:

    		from numpy import pi
    		from cxroots import AnnulusSector
    		annulusSector = AnnulusSector(center=0.2, radii=(0.5, 1.25), phiRange=(pi/4, -pi/4))
    		annulusSector.show()
    	"""
    	def __init__(self, center, radii, phiRange):
    		self.center = center
    		self.axisName = ('r', 'phi')

    		if phiRange[0] > phiRange[1]:
    			phiRange = (phiRange[0], phiRange[1]+2*pi)

    		phi0, phi1 = self.phiRange = phiRange

    		# r > 0
    		r0, r1 = self.radii = radii
    		if r0 < 0 or r1 <= 0:
    			raise ValueError('Radius > 0')

    		# verticies [[radius0,phi0],[radius0,phi1],[radius1,phi1],[radius0,phi1]]
    		self.z1 = z1 = center + r0*exp(1j*phi0)
    		self.z2 = z2 = center + r1*exp(1j*phi0)
    		self.z3 = z3 = center + r1*exp(1j*phi1)
    		self.z4 = z4 = center + r0*exp(1j*phi1)

    		segments = [ComplexLine(z1,z2),
    					ComplexArc(center,r1,phi0,phi1-phi0),
    					ComplexLine(z3,z4),
    					ComplexArc(center,r0,phi1,phi0-phi1)]

    		super(AnnulusSector, self).__init__(segments)

    	def __str__(self):
    		return 'Annulus sector: center={center.real:.3f}{center.imag:+.3f}i, r0={radii[0]:.3f}, r1={radii[1]:.3f}, phi0={phiRange[0]:.3f}, phi1={phiRange[1]:.3f}'.format(center=self.center, radii=self.radii, phiRange=self.phiRange)

    	@property
    	def centralPoint(self):
    		# get the central point within the contour
    		r = (self.radii[0] + self.radii[1])/2
    		phi = (self.phiRange[0] + self.phiRange[1])/2
    		return r*exp(1j*phi)

    	@property
    	def area(self):
    		return (self.radii[1]**2 - self.radii[0]**2)*abs(self.phiRange[1] - self.phiRange[0])%(2*pi)/2

    	def contains(self, z):
    		""" Returns True if the point z lies within the contour, False if otherwise """
    		angle = np.angle(z - self.center)%(2*pi) # np.angle maps to [-pi,pi]
    		radiusCorrect = self.radii[0] < abs(z - self.center) < self.radii[1]

    		phi = np.mod(self.phiRange, 2*pi)
    		if phi[0] > phi[1]:
    			angleCorrect = phi[0] < angle <= 2*pi or 0 <= angle < phi[1]
    		else:
    			angleCorrect = phi[0] < angle < phi[1]

    		return radiusCorrect and angleCorrect

    	def subdivide(self, axis, divisionFactor=0.5):
    		"""
    		Subdivide the contour

    		Parameters
    		----------
    		axis : str, can be either 'r' or 'phi'
    			The axis along which the line subdividing the contour is a constant.
    		divisionFactor : float in range (0,1), optional
    			Determines the point along 'axis' at which the line dividing the box is placed

    		Returns
    		-------
    		box1 : AnnulusSector
    			If axis is 'r' then phiRange and the inner radius is the same as original AnnulusSector
    			with the outer radius determined by the divisionFactor.
    			If axis is 'phi' then the radii and phiRange[0] is the same as the original AnnulusSector
    			with phiRange[1] determined by the divisionFactor.
    		box2 : AnnulusSector
    			If axis is 'r' then phiRange and the outer radius is the same as original AnnulusSector
    			with the inner radius determined equal to the outer radius of box1.
    			If axis is 'phi' then the radii and phiRange[1] is the same as the original AnnulusSector
    			with phiRange[0] equal to phiRange[1] of box1.
    		"""
    		r0, r1 = self.radii
    		phi0, phi1 = self.phiRange
    		if axis == 0 or axis == self.axisName[0]:
    			divisionPoint = r0 + divisionFactor*(r1-r0)
    			box1 = AnnulusSector(self.center, [r0, divisionPoint], self.phiRange)
    			box2 = AnnulusSector(self.center, [divisionPoint, r1], self.phiRange)

    			# reuse line segments from original box where possible
    			# this allows the cached integrals to be used
    			box1.segments[3] = self.segments[3]
    			box2.segments[1] = self.segments[1]
    			box1.segments[1]._reversePath = box2.segments[3]
    			box2.segments[3]._reversePath = box1.segments[1]

    		elif axis == 1 or axis == self.axisName[1]:
    			divisionPoint = phi0 + divisionFactor*(phi1-phi0)
    			box1 = AnnulusSector(self.center, self.radii, [phi0, divisionPoint])
    			box2 = AnnulusSector(self.center, self.radii, [divisionPoint, phi1])

    			box1.segments[0] = self.segments[0]
    			box2.segments[2] = self.segments[2]
    			box1.segments[2]._reversePath = box2.segments[0]
    			box2.segments[0]._reversePath = box1.segments[2]

    		for box in [box1, box2]:
    			box._createdBySubdivisionAxis = axis
    			box._parentBox = self
    		self._childBoxes = [box1, box2]

    		return box1, box2

    	def randomPoint(self):
    		"""Returns a random point inside the contour of the AnnulusSector."""
    		r = np.random.uniform(*self.radii)
    		phiRange = np.mod(self.phiRange, 2*pi)
    		if phiRange[0] > phiRange[1]:
    			phi = random.choice([np.random.uniform(phiRange[0], 2*pi),
    								 np.random.uniform(0, phiRange[1])])
    		else:
    			phi = np.random.uniform(*phiRange)

    		return r*exp(1j*phi) + self.center