Source code for cxroots.contours.annulus

from typing import Literal, Tuple, Union, overload

from numpy import pi

from ..contour import Contour
from ..paths import ComplexArc
from .annulus_sector import AnnulusSector


[docs] class Annulus(Contour): """ An annulus in the complex plane with the outer circle positively oriented and the inner circle negatively oriented. Parameters ---------- center : complex The center of the annulus in the complex plane. radii : tuple A tuple of length two of the form (inner_radius, outer_radius). Examples -------- .. plot:: :include-source: from cxroots import Annulus annulus = Annulus(center=0, radii=(0.5,0.75)) annulus.show() """ axis_names = ("r", "phi") def __init__(self, center: complex, radii: Tuple[float, float]): self.center = center self.radii = radii segments = [ ComplexArc(center, radii[1], 0, 2 * pi), ComplexArc(center, radii[0], 0, -2 * pi), ] super(Annulus, self).__init__(segments) def __str__(self): return ( f"Annulus: center={self.center.real:.3f}{self.center.imag:+.3f}i, " f"inner radius={self.radii[0]:.3f}, outer radius={self.radii[1]:.3f}" ) @property def central_point(self) -> complex: # get a central point within the contour r = (self.radii[0] + self.radii[1]) / 2 return r @property def area(self) -> float: return pi * (self.radii[1] ** 2 - self.radii[0] ** 2) def contains(self, z: complex) -> bool: """Returns True if the point z lies within the contour, False if otherwise""" return self.radii[0] < abs(z - self.center) < self.radii[1] @overload def subdivide( self, axis: Literal["r"], division_factor: float ) -> Tuple["Annulus", "Annulus"]: ... @overload def subdivide( self, axis: Literal["phi"], division_factor: float ) -> Tuple[AnnulusSector, AnnulusSector]: ... def subdivide( self, axis: Literal["r", "phi"], division_factor: float = 0.5 ) -> Union[Tuple[AnnulusSector, AnnulusSector], Tuple["Annulus", "Annulus"]]: """ 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 ------- boxes : list of contours Two annuluses if axis is 'r'. Two half-annuluses oriented according to division_factor if axis is 'phi'. """ if axis == "r": midpoint = self.radii[0] + division_factor * (self.radii[1] - self.radii[0]) boxes = ( Annulus(self.center, (self.radii[0], midpoint)), Annulus(self.center, (midpoint, self.radii[1])), ) boxes[0].segments[1] = self.segments[1] boxes[1].segments[0] = self.segments[0] boxes[0].segments[0]._reverse_path = boxes[1].segments[1] boxes[1].segments[1]._reverse_path = boxes[0].segments[0] elif axis == "phi": # Subdividing into two radial boxes rather than one to # ensure that an error is raised if one of the new paths # is too close to a root # XXX: introduce another parameter for phi1 phi0 = 2 * pi * division_factor phi1 = phi0 + pi boxes = ( AnnulusSector(self.center, self.radii, (phi0, phi1)), AnnulusSector(self.center, self.radii, (phi1, phi0)), ) boxes[0].segments[0]._reverse_path = boxes[1].segments[2] boxes[1].segments[2]._reverse_path = boxes[0].segments[0] boxes[0].segments[2]._reverse_path = boxes[1].segments[0] boxes[1].segments[0]._reverse_path = boxes[0].segments[2] else: raise ValueError("axis must be 'r' or 'phi'") for box in boxes: box._created_by_subdivision_axis = axis box._parent = self self._children = boxes return boxes