Source code for cxroots.contours.annulus_sector

from math import pi
from typing import Literal, Tuple

import numpy as np

from ..contour import Contour
from ..paths import ComplexArc, ComplexLine


[docs] 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) phi_range : 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), phi_range=(-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), phi_range=(pi/4, -pi/4) ) annulusSector.show() """ axis_names = ("r", "phi") def __init__( self, center: complex, radii: Tuple[float, float], phi_range: Tuple[float, float], ): self.center = center if phi_range[0] > phi_range[1]: phi_range = (phi_range[0], phi_range[1] + 2 * pi) phi0, phi1 = self.phi_range = phi_range # 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 * np.exp(1j * phi0) self.z2 = z2 = center + r1 * np.exp(1j * phi0) self.z3 = z3 = center + r1 * np.exp(1j * phi1) self.z4 = z4 = center + r0 * np.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 ( f"Annulus sector: center={self.center.real:.3f}{self.center.imag:+.3f}i, " f"r0={self.radii[0]:.3f}, r1={self.radii[1]:.3f}, " f"phi0={self.phi_range[0]:.3f}, phi1={self.phi_range[1]:.3f}" ) @property def central_point(self) -> complex: # get the central point within the contour r = (self.radii[0] + self.radii[1]) / 2 phi = (self.phi_range[0] + self.phi_range[1]) / 2 return r * np.exp(1j * phi) @property def area(self) -> float: return ( (self.radii[1] ** 2 - self.radii[0] ** 2) * abs(self.phi_range[1] - self.phi_range[0]) % (2 * pi) / 2 ) def contains(self, z: complex) -> bool: """Returns True if the point z lies within the contour, False if otherwise""" angle = float(np.angle(z - self.center)) % (2 * pi) # np.angle maps to [-pi,pi] radius_correct = self.radii[0] < abs(z - self.center) < self.radii[1] phi = np.mod(self.phi_range, 2 * pi) if phi[0] > phi[1]: angle_correct = (phi[0] < angle <= 2 * pi) or (0 <= angle < phi[1]) else: angle_correct = phi[0] < angle < phi[1] return radius_correct and angle_correct def subdivide( self, axis: Literal["r", "phi"], division_factor: float = 0.5 ) -> Tuple["AnnulusSector", "AnnulusSector"]: """ Subdivide the contour Parameters ---------- axis : str, can be either 'r' or 'phi' The axis along which the line subdividing the contour is a constant. division_factor : 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 phi_range and the inner radius is the same as original AnnulusSector with the outer radius determined by the division_factor. If axis is 'phi' then the radii and phi_range[0] is the same as the original AnnulusSector with phi_range[1] determined by the division_factor. box2 : AnnulusSector If axis is 'r' then phi_range 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 phi_range[1] is the same as the original AnnulusSector with phi_range[0] equal to phi_range[1] of box1. """ r0, r1 = self.radii phi0, phi1 = self.phi_range if axis == "r": division_point = r0 + division_factor * (r1 - r0) box1 = AnnulusSector(self.center, (r0, division_point), self.phi_range) box2 = AnnulusSector(self.center, (division_point, r1), self.phi_range) # 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]._reverse_path = box2.segments[3] box2.segments[3]._reverse_path = box1.segments[1] elif axis == "phi": division_point = phi0 + division_factor * (phi1 - phi0) box1 = AnnulusSector(self.center, self.radii, (phi0, division_point)) box2 = AnnulusSector(self.center, self.radii, (division_point, phi1)) box1.segments[0] = self.segments[0] box2.segments[2] = self.segments[2] box1.segments[2]._reverse_path = box2.segments[0] box2.segments[0]._reverse_path = box1.segments[2] else: raise ValueError("axis must be 'r' or 'phi'") for box in [box1, box2]: box._created_by_subdivision_axis = axis box._parent = self self._children = [box1, box2] return box1, box2