simetri.geometry.circle

  1from math import pi, cos, sqrt, acos, atan
  2from dataclasses import dataclass
  3import cmath
  4
  5import numpy as np
  6
  7from .geometry import distance, homogenize, side_len_to_radius, angle_between_lines2
  8from ..graphics.affine import rotate, scale_matrix, rotation_matrix
  9from ..graphics.shapes import Circle
 10
 11array = np.array
 12dot = np.dot
 13linalg = np.linalg
 14
 15@dataclass
 16class Circle_:
 17    """A simple circle class."""
 18
 19    center: tuple
 20    radius: float
 21
 22def circle_tangent_to_3_circles(c1, r1, c2, r2, c3, r3, s1=-1, s2=-1, s3=-1):
 23    """Given the centers and radii of 3 circles, return the center and radius
 24    of a circle that is tangent to all 3 circles.
 25
 26    Args:
 27        c1 (tuple): Center of the first circle.
 28        r1 (float): Radius of the first circle.
 29        c2 (tuple): Center of the second circle.
 30        r2 (float): Radius of the second circle.
 31        c3 (tuple): Center of the third circle.
 32        r3 (float): Radius of the third circle.
 33        s1 (int, optional): Sign for the first circle. Defaults to -1.
 34        s2 (int, optional): Sign for the second circle. Defaults to -1.
 35        s3 (int, optional): Sign for the third circle. Defaults to -1.
 36
 37    Returns:
 38        tuple: Center (x, y) and radius of the tangent circle.
 39    """
 40
 41    x1, y1 = c1
 42    x2, y2 = c2
 43    x3, y3 = c3
 44
 45    v11 = 2 * x2 - 2 * x1
 46    v12 = 2 * y2 - 2 * y1
 47    v13 = x1 * x1 - x2 * x2 + y1 * y1 - y2 * y2 - r1 * r1 + r2 * r2
 48    v14 = 2 * s2 * r2 - 2 * s1 * r1
 49
 50    v21 = 2 * x3 - 2 * x2
 51    v22 = 2 * y3 - 2 * y2
 52    v23 = x2 * x2 - x3 * x3 + y2 * y2 - y3 * y3 - r2 * r2 + r3 * r3
 53    v24 = 2 * s3 * r3 - 2 * s3 * r2
 54
 55    w12 = v12 / v11
 56    w13 = v13 / v11
 57    w14 = v14 / v11
 58
 59    w22 = v22 / v21 - w12
 60    w23 = v23 / v21 - w13
 61    w24 = v24 / v21 - w14
 62
 63    P = -w23 / w22
 64    Q = w24 / w22
 65    M = -w12 * P - w13
 66    N = w14 - w12 * Q
 67
 68    a = N*N + Q*Q - 1
 69    b = 2 * M * N - 2 * N * x1 + 2 * P * Q - 2 * Q * y1 + 2 * s1 * r1
 70    c = x1 * x1 + M * M - 2 * M * x1 + P * P + y1 * y1 - 2 * P * y1 - r1 * r1
 71
 72    # Find a root of a quadratic equation.
 73    # This requires the circle centers not to be collinear
 74    D = b * b - 4 * a * c
 75    rs = (-b - sqrt(D)) / (2 * a)
 76
 77    xs = M + N * rs
 78    ys = P + Q * rs
 79
 80    return (xs, ys, rs)
 81
 82def apollonius(r1, r2, r3, z1, z2, z3, plus_minus=1):
 83    """Solves the Problem of Apollonius using Descartes' Theorem.
 84
 85    Args:
 86        r1 (float): Radius of the first circle.
 87        r2 (float): Radius of the second circle.
 88        r3 (float): Radius of the third circle.
 89        z1 (complex): Center of the first circle.
 90        z2 (complex): Center of the second circle.
 91        z3 (complex): Center of the third circle.
 92        plus_minus (int, optional): +1 for outer tangent circle, -1 for inner tangent circle. Defaults to 1.
 93
 94    Returns:
 95        tuple: Radius and center coordinates (x, y) of the tangent circle, or None if no solution is found.
 96    """
 97    k1, k2, k3 = 1/r1, 1/r2, 1/r3
 98
 99    # Applying Descartes' Theorem
100    k4_values = (k1 + k2 + k3) + plus_minus * 2 * sqrt(k1*k2 + k2*k3 + k3*k1)
101
102    # Handle cases where no solution exists (e.g., division by zero)
103    if k4_values == 0:
104        return None
105
106    r4 = 1/k4_values
107    z4 = ((k1*z1 + k2*z2 + k3*z3 + plus_minus * 2 *
108           cmath.sqrt(k1*k2*z1*z2 + k2*k3*z2*z3 + k3*k1*z3*z1)) / k4_values)
109
110    return r4, z4
111
112
113
114def circle_tangent_to_2_circles(c1, r1, c2, r2, r):
115    """Given the centers and radii of 2 circles, return the center
116    of a circle with radius r that is tangent to both circles.
117
118    Args:
119        c1 (tuple): Center of the first circle.
120        r1 (float): Radius of the first circle.
121        c2 (tuple): Center of the second circle.
122        r2 (float): Radius of the second circle.
123        r (float): Radius of the tangent circle.
124
125    Returns:
126        tuple: Centers (x1, y1) and (x2, y2) of the tangent circle.
127    """
128    x1, y1 = c1
129    x2, y2 = c2
130
131    r12 = r1**2
132    r12y1 = r12 * y1
133    r12y2 = r12 * y2
134    r1r2 = r1 * r2
135    r22 = r2**2
136    r22y2 = r22 * y2
137    r_2 = r**2
138    rr1 = r * r1
139    rr1y1 = rr1 * y1
140    rr1y2 = rr1 * y2
141    rr2 = r * r2
142    rr2y1 = rr2 * y1
143    rr2y2 = rr2 * y2
144    x12 = x1**2
145    x12y1 = x12 * y1
146    x12y2 = x12 * y2
147    x1_x2 = x1 - x2
148    x1x2 = x1 * x2
149    x1x2y1 = x1x2 * y1
150    x1x2y2 = x1x2 * y2
151    x22 = x2**2
152    x22y1 = x22 * y1
153    x22y2 = x22 * y2
154    y12 = y1**2
155    y12y2 = y12 * y2
156    y13 = y1**3
157    y1y2 = y1 * y2
158    y22 = y2**2
159    y1y22 = y1 * y22
160    y23 = y2**3
161
162    x_1 = (
163        -(y1 - y2)
164        * (
165            -2 * rr1y1
166            + 2 * rr1y2
167            + 2 * rr2y1
168            - 2 * rr2y2
169            - r12y1
170            + r12y2
171            + r22 * y1
172            - r22y2
173            + x12y1
174            + x12y2
175            - 2 * x1x2y1
176            - 2 * x1x2y2
177            + x22y1
178            + x22y2
179            + y13
180            - y12y2
181            - y1y22
182            + y23
183            + sqrt(
184                (-r12 + 2 * r1r2 - r22 + x12 - 2 * x1x2 + x22 + y12 - 2 * y1y2 + y22)
185                * (
186                    4 * r_2
187                    + 4 * rr1
188                    + 4 * rr2
189                    + r12
190                    + 2 * r1r2
191                    + r22
192                    - x12
193                    + 2 * x1x2
194                    - x22
195                    - y12
196                    + 2 * y1y2
197                    - y22
198                )
199            )
200            * (-x1 + x2)
201        )
202        - (x12 - 2 * x1x2 + x22 + y12 - 2 * y1y2 + y22)
203        * (2 * rr1 - 2 * rr2 + r12 - r22 - x12 + x22 - y12 + y22)
204    ) / (2 * (x1_x2) * (x12 - 2 * x1x2 + x22 + y12 - 2 * y1y2 + y22))
205
206    y_1 = (
207        -2 * rr1y1
208        + 2 * rr1y2
209        + 2 * rr2y1
210        - 2 * rr2y2
211        - r12y1
212        + r12y2
213        + r22 * y1
214        - r22y2
215        + x12y1
216        + x12y2
217        - 2 * x1x2y1
218        - 2 * x1x2y2
219        + x22y1
220        + x22y2
221        + y13
222        - y12y2
223        - y1y22
224        + y23
225        + sqrt(
226            (-r12 + 2 * r1r2 - r22 + x12 - 2 * x1x2 + x22 + y12 - 2 * y1y2 + y22)
227            * (
228                4 * r_2
229                + 4 * rr1
230                + 4 * rr2
231                + r12
232                + 2 * r1r2
233                + r22
234                - x12
235                + 2 * x1x2
236                - x22
237                - y12
238                + 2 * y1y2
239                - y22
240            )
241        )
242        * (-x1 + x2)
243    ) / (2 * (x12 - 2 * x1x2 + x22 + y12 - 2 * y1y2 + y22))
244
245    x_2 = (
246        -(y1 - y2)
247        * (
248            -2 * rr1y1
249            + 2 * rr1y2
250            + 2 * rr2y1
251            - 2 * rr2y2
252            - r12y1
253            + r12y2
254            + r22 * y1
255            - r22y2
256            + x12y1
257            + x12y2
258            - 2 * x1x2y1
259            - 2 * x1x2y2
260            + x22y1
261            + x22y2
262            + y13
263            - y12y2
264            - y1y22
265            + y23
266            + sqrt(
267                (-r12 + 2 * r1r2 - r22 + x12 - 2 * x1x2 + x22 + y12 - 2 * y1y2 + y22)
268                * (
269                    4 * r_2
270                    + 4 * rr1
271                    + 4 * rr2
272                    + r12
273                    + 2 * r1r2
274                    + r22
275                    - x12
276                    + 2 * x1x2
277                    - x22
278                    - y12
279                    + 2 * y1y2
280                    - y22
281                )
282            )
283            * (x1_x2)
284        )
285        - (x12 - 2 * x1x2 + x22 + y12 - 2 * y1y2 + y22)
286        * (2 * rr1 - 2 * rr2 + r12 - r22 - x12 + x22 - y12 + y22)
287    ) / (2 * (x1_x2) * (x12 - 2 * x1x2 + x22 + y12 - 2 * y1y2 + y22))
288
289    y_2 = (
290        -2 * rr1y1
291        + 2 * rr1y2
292        + 2 * rr2y1
293        - 2 * rr2y2
294        - r12y1
295        + r12y2
296        + r22 * y1
297        - r22y2
298        + x12y1
299        + x12y2
300        - 2 * x1x2y1
301        - 2 * x1x2y2
302        + x22y1
303        + x22y2
304        + y13
305        - y12y2
306        - y1y22
307        + y23
308        + sqrt(
309            (-r12 + 2 * r1r2 - r22 + x12 - 2 * x1x2 + x22 + y12 - 2 * y1y2 + y22)
310            * (
311                4 * r_2
312                + 4 * rr1
313                + 4 * rr2
314                + r12
315                + 2 * r1r2
316                + r22
317                - x12
318                + 2 * x1x2
319                - x22
320                - y12
321                + 2 * y1y2
322                - y22
323            )
324        )
325        * (x1_x2)
326    ) / (2 * (x12 - 2 * x1x2 + x22 + y12 - 2 * y1y2 + y22))
327
328    return ((x_1, y_1), (x_2, y_2))
329
330
331def tangent_points(center1, radius, center2, radius2, cross=False):
332    """Returns the tangent points (p1, p2, p3, p4) in world coordinates.
333
334    Args:
335        center1 (tuple): Center of the first circle.
336        radius (float): Radius of the first circle.
337        center2 (tuple): Center of the second circle.
338        radius2 (float): Radius of the second circle.
339        cross (bool, optional): Whether to calculate crossing tangents. Defaults to False.
340
341    Returns:
342        tuple: Tangent points (p1, p2, p3, p4) in world coordinates.
343    """
344    c1 = Circle_(center1, radius)
345    c2 = Circle_(center2, radius2)
346    if radius < radius2:
347        c1, c2 = c2, c1
348    pos = c1.center
349    dist = distance(pos, c2.center)
350    r1 = c1.radius
351    r2 = c2.radius
352
353    if cross:
354        dr = r1 + r2
355    else:
356        dr = r1 - r2
357
358    x = sqrt(dist**2 - dr**2)
359    y = pos[1] + r1
360    p1 = [pos[0], y]
361    p2 = [pos[0] + x, y]
362    points = homogenize([p1, p2])
363    alpha = angle_between_lines2((pos[0] + x, pos[1] + dr), pos, c2.center)
364    tp1w, tp2w = rotate(points, alpha, pos)
365
366    if x == 0:
367        beta = 0
368    else:
369        beta = pi / 2 - atan(dr / x)
370    tp3w = rotate([tp1w], -2 * beta, pos)[0]
371    tp4w = rotate([tp2w], -2 * beta, c2.center)[0]
372
373    return (tp1w, tp2w, tp3w, tp4w)
374
375
376def circle_area(rad):
377    """Given the radius of a circle, return the area of the circle.
378
379    Args:
380        rad (float): Radius of the circle.
381
382    Returns:
383        float: Area of the circle.
384    """
385    return pi * rad**2
386
387
388def circle_circumference(rad):
389    """Given the radius of a circle, return the circumference of the circle.
390
391    Args:
392        rad (float): Radius of the circle.
393
394    Returns:
395        float: Circumference of the circle.
396    """
397    return 2 * pi * rad
398
399
400def flower_angle(r1, r2, r3):
401    """Given the radii of 3 circles forming an interstice, return the angle between
402    the lines connecting circles' centers to center of the circle with r1 radius.
403
404    Args:
405        r1 (float): Radius of the first circle.
406        r2 (float): Radius of the second circle.
407        r3 (float): Radius of the third circle.
408
409    Returns:
410        float: Angle between the lines connecting circles' centers.
411    """
412    angle = acos(
413        ((r1 + r2) ** 2 + (r1 + r3) ** 2 - (r2 + r3) ** 2) / (2 * (r1 + r2) * (r1 + r3))
414    )
415
416    return angle
417
418
419
420ratios = {8: 0.4974, 9: 0.5394, 10: 0.575, 11: 0.6056,
421        12: 0.6321, 13: 0.6553, 14: 0.6757, 15: 0.6939, 16: 0.7101,
422        17: 0.7248, 18: 0.738, 19: 0.75, 20: 0.7609}
423
424def circle_flower(n, radius=25, layers=6, ratio = None):
425    """Steiner chain. Return a list of circles that form a flower-like pattern.
426
427    Args:
428        n (int): Number of circles.
429        radius (float, optional): Radius of the circles. Defaults to 25.
430        layers (int, optional): Number of layers. Defaults to 6.
431        ratio (float, optional): Ratio for scaling. Defaults to None.
432
433    Returns:
434        list: List of circles forming a flower-like pattern.
435
436    Raises:
437        ValueError: If n is less than 8.
438    """
439    if n<8:
440        raise ValueError('n must be greater than 7')
441    if ratio is None:
442        if n<21:
443            ratio = ratios[n]
444        else:
445            ratio = (-0.000000089767 * n**4 + 0.000015821834 * n**3 +
446                    -0.001100867708 * n **2+ 0.038096046379 * n + 0.327363569038)
447
448    r1 = side_len_to_radius(n, 2*radius)
449    circles = Circle((r1, 0), radius).rotate(pi/(n/2), (0, 0), reps=n-1)
450    xform = scale_matrix(ratio) @ rotation_matrix(pi/n)
451
452    return circles.transform(xform_matrix=xform, reps=layers)
def array(unknown):

array(object, dtype=None, *, copy=True, order='K', subok=False, ndmin=0, like=None)

Create an array.

Parameters

object : array_like An array, any object exposing the array interface, an object whose __array__ method returns an array, or any (nested) sequence. If object is a scalar, a 0-dimensional array containing object is returned. dtype : data-type, optional The desired data-type for the array. If not given, NumPy will try to use a default dtype that can represent the values (by applying promotion rules when necessary.) copy : bool, optional If True (default), then the array data is copied. If None, a copy will only be made if __array__ returns a copy, if obj is a nested sequence, or if a copy is needed to satisfy any of the other requirements (dtype, order, etc.). Note that any copy of the data is shallow, i.e., for arrays with object dtype, the new array will point to the same objects. See Examples for ndarray.copy. For False it raises a ValueError if a copy cannot be avoided. Default: True. order : {'K', 'A', 'C', 'F'}, optional Specify the memory layout of the array. If object is not an array, the newly created array will be in C order (row major) unless 'F' is specified, in which case it will be in Fortran order (column major). If object is an array the following holds.

===== ========= ===================================================
order  no copy                     copy=True
===== ========= ===================================================
'K'   unchanged F & C order preserved, otherwise most similar order
'A'   unchanged F order if input is F and not C, otherwise C order
'C'   C order   C order
'F'   F order   F order
===== ========= ===================================================

When ``copy=None`` and a copy is made for other reasons, the result is
the same as if ``copy=True``, with some exceptions for 'A', see the
Notes section. The default order is 'K'.

subok : bool, optional If True, then sub-classes will be passed-through, otherwise the returned array will be forced to be a base-class array (default). ndmin : int, optional Specifies the minimum number of dimensions that the resulting array should have. Ones will be prepended to the shape as needed to meet this requirement. like : array_like, optional Reference object to allow the creation of arrays which are not NumPy arrays. If an array-like passed in as like supports the __array_function__ protocol, the result will be defined by it. In this case, it ensures the creation of an array object compatible with that passed in via this argument.

*New in version 1.20.0.*

Returns

out : ndarray An array object satisfying the specified requirements.

See Also

empty_like : Return an empty array with shape and type of input. ones_like : Return an array of ones with shape and type of input. zeros_like : Return an array of zeros with shape and type of input. full_like : Return a new array with shape of input filled with value. empty : Return a new uninitialized array. ones : Return a new array setting values to one. zeros : Return a new array setting values to zero. full : Return a new array of given shape filled with value. copy: Return an array copy of the given object.

Notes

When order is 'A' and object is an array in neither 'C' nor 'F' order, and a copy is forced by a change in dtype, then the order of the result is not necessarily 'C' as expected. This is likely a bug.

Examples

>>> np.array([1, 2, 3])
array([1, 2, 3])

Upcasting:

>>> np.array([1, 2, 3.0])
array([ 1.,  2.,  3.])

More than one dimension:

>>> np.array([[1, 2], [3, 4]])
array([[1, 2],
       [3, 4]])

Minimum dimensions 2:

>>> np.array([1, 2, 3], ndmin=2)
array([[1, 2, 3]])

Type provided:

>>> np.array([1, 2, 3], dtype=complex)
array([ 1.+0.j,  2.+0.j,  3.+0.j])

Data-type consisting of more than one element:

>>> x = np.array([(1,2),(3,4)],dtype=[('a','<i4'),('b','<i4')])
>>> x['a']
array([1, 3])

Creating an array from sub-classes:

>>> np.array(np.asmatrix('1 2; 3 4'))
array([[1, 2],
       [3, 4]])
>>> np.array(np.asmatrix('1 2; 3 4'), subok=True)
matrix([[1, 2],
        [3, 4]])
def dot(unknown):

dot(a, b, out=None)

Dot product of two arrays. Specifically,

  • If both a and b are 1-D arrays, it is inner product of vectors (without complex conjugation).

  • If both a and b are 2-D arrays, it is matrix multiplication, but using matmul() or a @ b is preferred.

  • If either a or b is 0-D (scalar), it is equivalent to multiply() and using numpy.multiply(a, b) or a * b is preferred.

  • If a is an N-D array and b is a 1-D array, it is a sum product over the last axis of a and b.

  • If a is an N-D array and b is an M-D array (where M>=2), it is a sum product over the last axis of a and the second-to-last axis of b::

    dot(a, b)[i,j,k,m] = sum(a[i,j,:] * b[k,:,m])

It uses an optimized BLAS library when possible (see numpy.linalg).

Parameters

a : array_like First argument. b : array_like Second argument. out : ndarray, optional Output argument. This must have the exact kind that would be returned if it was not used. In particular, it must have the right type, must be C-contiguous, and its dtype must be the dtype that would be returned for dot(a,b). This is a performance feature. Therefore, if these conditions are not met, an exception is raised, instead of attempting to be flexible.

Returns

output : ndarray Returns the dot product of a and b. If a and b are both scalars or both 1-D arrays then a scalar is returned; otherwise an array is returned. If out is given, then it is returned.

Raises

ValueError If the last dimension of a is not the same size as the second-to-last dimension of b.

See Also

vdot : Complex-conjugating dot product. tensordot : Sum products over arbitrary axes. einsum : Einstein summation convention. matmul : '@' operator as method with out parameter. linalg.multi_dot : Chained dot product.

Examples

>>> np.dot(3, 4)
12

Neither argument is complex-conjugated:

>>> np.dot([2j, 3j], [2j, 3j])
(-13+0j)

For 2-D arrays it is the matrix product:

>>> a = [[1, 0], [0, 1]]
>>> b = [[4, 1], [2, 2]]
>>> np.dot(a, b)
array([[4, 1],
       [2, 2]])
>>> a = np.arange(3*4*5*6).reshape((3,4,5,6))
>>> b = np.arange(3*4*5*6)[::-1].reshape((5,4,6,3))
>>> np.dot(a, b)[2,3,2,1,2,2]
499128
>>> sum(a[2,3,2,:] * b[1,2,:,2])
499128
@dataclass
class Circle_:
16@dataclass
17class Circle_:
18    """A simple circle class."""
19
20    center: tuple
21    radius: float

A simple circle class.

Circle_(center: tuple, radius: float)
center: tuple
radius: float
def circle_tangent_to_3_circles(c1, r1, c2, r2, c3, r3, s1=-1, s2=-1, s3=-1):
23def circle_tangent_to_3_circles(c1, r1, c2, r2, c3, r3, s1=-1, s2=-1, s3=-1):
24    """Given the centers and radii of 3 circles, return the center and radius
25    of a circle that is tangent to all 3 circles.
26
27    Args:
28        c1 (tuple): Center of the first circle.
29        r1 (float): Radius of the first circle.
30        c2 (tuple): Center of the second circle.
31        r2 (float): Radius of the second circle.
32        c3 (tuple): Center of the third circle.
33        r3 (float): Radius of the third circle.
34        s1 (int, optional): Sign for the first circle. Defaults to -1.
35        s2 (int, optional): Sign for the second circle. Defaults to -1.
36        s3 (int, optional): Sign for the third circle. Defaults to -1.
37
38    Returns:
39        tuple: Center (x, y) and radius of the tangent circle.
40    """
41
42    x1, y1 = c1
43    x2, y2 = c2
44    x3, y3 = c3
45
46    v11 = 2 * x2 - 2 * x1
47    v12 = 2 * y2 - 2 * y1
48    v13 = x1 * x1 - x2 * x2 + y1 * y1 - y2 * y2 - r1 * r1 + r2 * r2
49    v14 = 2 * s2 * r2 - 2 * s1 * r1
50
51    v21 = 2 * x3 - 2 * x2
52    v22 = 2 * y3 - 2 * y2
53    v23 = x2 * x2 - x3 * x3 + y2 * y2 - y3 * y3 - r2 * r2 + r3 * r3
54    v24 = 2 * s3 * r3 - 2 * s3 * r2
55
56    w12 = v12 / v11
57    w13 = v13 / v11
58    w14 = v14 / v11
59
60    w22 = v22 / v21 - w12
61    w23 = v23 / v21 - w13
62    w24 = v24 / v21 - w14
63
64    P = -w23 / w22
65    Q = w24 / w22
66    M = -w12 * P - w13
67    N = w14 - w12 * Q
68
69    a = N*N + Q*Q - 1
70    b = 2 * M * N - 2 * N * x1 + 2 * P * Q - 2 * Q * y1 + 2 * s1 * r1
71    c = x1 * x1 + M * M - 2 * M * x1 + P * P + y1 * y1 - 2 * P * y1 - r1 * r1
72
73    # Find a root of a quadratic equation.
74    # This requires the circle centers not to be collinear
75    D = b * b - 4 * a * c
76    rs = (-b - sqrt(D)) / (2 * a)
77
78    xs = M + N * rs
79    ys = P + Q * rs
80
81    return (xs, ys, rs)

Given the centers and radii of 3 circles, return the center and radius of a circle that is tangent to all 3 circles.

Arguments:
  • c1 (tuple): Center of the first circle.
  • r1 (float): Radius of the first circle.
  • c2 (tuple): Center of the second circle.
  • r2 (float): Radius of the second circle.
  • c3 (tuple): Center of the third circle.
  • r3 (float): Radius of the third circle.
  • s1 (int, optional): Sign for the first circle. Defaults to -1.
  • s2 (int, optional): Sign for the second circle. Defaults to -1.
  • s3 (int, optional): Sign for the third circle. Defaults to -1.
Returns:

tuple: Center (x, y) and radius of the tangent circle.

def apollonius(r1, r2, r3, z1, z2, z3, plus_minus=1):
 83def apollonius(r1, r2, r3, z1, z2, z3, plus_minus=1):
 84    """Solves the Problem of Apollonius using Descartes' Theorem.
 85
 86    Args:
 87        r1 (float): Radius of the first circle.
 88        r2 (float): Radius of the second circle.
 89        r3 (float): Radius of the third circle.
 90        z1 (complex): Center of the first circle.
 91        z2 (complex): Center of the second circle.
 92        z3 (complex): Center of the third circle.
 93        plus_minus (int, optional): +1 for outer tangent circle, -1 for inner tangent circle. Defaults to 1.
 94
 95    Returns:
 96        tuple: Radius and center coordinates (x, y) of the tangent circle, or None if no solution is found.
 97    """
 98    k1, k2, k3 = 1/r1, 1/r2, 1/r3
 99
100    # Applying Descartes' Theorem
101    k4_values = (k1 + k2 + k3) + plus_minus * 2 * sqrt(k1*k2 + k2*k3 + k3*k1)
102
103    # Handle cases where no solution exists (e.g., division by zero)
104    if k4_values == 0:
105        return None
106
107    r4 = 1/k4_values
108    z4 = ((k1*z1 + k2*z2 + k3*z3 + plus_minus * 2 *
109           cmath.sqrt(k1*k2*z1*z2 + k2*k3*z2*z3 + k3*k1*z3*z1)) / k4_values)
110
111    return r4, z4

Solves the Problem of Apollonius using Descartes' Theorem.

Arguments:
  • r1 (float): Radius of the first circle.
  • r2 (float): Radius of the second circle.
  • r3 (float): Radius of the third circle.
  • z1 (complex): Center of the first circle.
  • z2 (complex): Center of the second circle.
  • z3 (complex): Center of the third circle.
  • plus_minus (int, optional): +1 for outer tangent circle, -1 for inner tangent circle. Defaults to 1.
Returns:

tuple: Radius and center coordinates (x, y) of the tangent circle, or None if no solution is found.

def circle_tangent_to_2_circles(c1, r1, c2, r2, r):
115def circle_tangent_to_2_circles(c1, r1, c2, r2, r):
116    """Given the centers and radii of 2 circles, return the center
117    of a circle with radius r that is tangent to both circles.
118
119    Args:
120        c1 (tuple): Center of the first circle.
121        r1 (float): Radius of the first circle.
122        c2 (tuple): Center of the second circle.
123        r2 (float): Radius of the second circle.
124        r (float): Radius of the tangent circle.
125
126    Returns:
127        tuple: Centers (x1, y1) and (x2, y2) of the tangent circle.
128    """
129    x1, y1 = c1
130    x2, y2 = c2
131
132    r12 = r1**2
133    r12y1 = r12 * y1
134    r12y2 = r12 * y2
135    r1r2 = r1 * r2
136    r22 = r2**2
137    r22y2 = r22 * y2
138    r_2 = r**2
139    rr1 = r * r1
140    rr1y1 = rr1 * y1
141    rr1y2 = rr1 * y2
142    rr2 = r * r2
143    rr2y1 = rr2 * y1
144    rr2y2 = rr2 * y2
145    x12 = x1**2
146    x12y1 = x12 * y1
147    x12y2 = x12 * y2
148    x1_x2 = x1 - x2
149    x1x2 = x1 * x2
150    x1x2y1 = x1x2 * y1
151    x1x2y2 = x1x2 * y2
152    x22 = x2**2
153    x22y1 = x22 * y1
154    x22y2 = x22 * y2
155    y12 = y1**2
156    y12y2 = y12 * y2
157    y13 = y1**3
158    y1y2 = y1 * y2
159    y22 = y2**2
160    y1y22 = y1 * y22
161    y23 = y2**3
162
163    x_1 = (
164        -(y1 - y2)
165        * (
166            -2 * rr1y1
167            + 2 * rr1y2
168            + 2 * rr2y1
169            - 2 * rr2y2
170            - r12y1
171            + r12y2
172            + r22 * y1
173            - r22y2
174            + x12y1
175            + x12y2
176            - 2 * x1x2y1
177            - 2 * x1x2y2
178            + x22y1
179            + x22y2
180            + y13
181            - y12y2
182            - y1y22
183            + y23
184            + sqrt(
185                (-r12 + 2 * r1r2 - r22 + x12 - 2 * x1x2 + x22 + y12 - 2 * y1y2 + y22)
186                * (
187                    4 * r_2
188                    + 4 * rr1
189                    + 4 * rr2
190                    + r12
191                    + 2 * r1r2
192                    + r22
193                    - x12
194                    + 2 * x1x2
195                    - x22
196                    - y12
197                    + 2 * y1y2
198                    - y22
199                )
200            )
201            * (-x1 + x2)
202        )
203        - (x12 - 2 * x1x2 + x22 + y12 - 2 * y1y2 + y22)
204        * (2 * rr1 - 2 * rr2 + r12 - r22 - x12 + x22 - y12 + y22)
205    ) / (2 * (x1_x2) * (x12 - 2 * x1x2 + x22 + y12 - 2 * y1y2 + y22))
206
207    y_1 = (
208        -2 * rr1y1
209        + 2 * rr1y2
210        + 2 * rr2y1
211        - 2 * rr2y2
212        - r12y1
213        + r12y2
214        + r22 * y1
215        - r22y2
216        + x12y1
217        + x12y2
218        - 2 * x1x2y1
219        - 2 * x1x2y2
220        + x22y1
221        + x22y2
222        + y13
223        - y12y2
224        - y1y22
225        + y23
226        + sqrt(
227            (-r12 + 2 * r1r2 - r22 + x12 - 2 * x1x2 + x22 + y12 - 2 * y1y2 + y22)
228            * (
229                4 * r_2
230                + 4 * rr1
231                + 4 * rr2
232                + r12
233                + 2 * r1r2
234                + r22
235                - x12
236                + 2 * x1x2
237                - x22
238                - y12
239                + 2 * y1y2
240                - y22
241            )
242        )
243        * (-x1 + x2)
244    ) / (2 * (x12 - 2 * x1x2 + x22 + y12 - 2 * y1y2 + y22))
245
246    x_2 = (
247        -(y1 - y2)
248        * (
249            -2 * rr1y1
250            + 2 * rr1y2
251            + 2 * rr2y1
252            - 2 * rr2y2
253            - r12y1
254            + r12y2
255            + r22 * y1
256            - r22y2
257            + x12y1
258            + x12y2
259            - 2 * x1x2y1
260            - 2 * x1x2y2
261            + x22y1
262            + x22y2
263            + y13
264            - y12y2
265            - y1y22
266            + y23
267            + sqrt(
268                (-r12 + 2 * r1r2 - r22 + x12 - 2 * x1x2 + x22 + y12 - 2 * y1y2 + y22)
269                * (
270                    4 * r_2
271                    + 4 * rr1
272                    + 4 * rr2
273                    + r12
274                    + 2 * r1r2
275                    + r22
276                    - x12
277                    + 2 * x1x2
278                    - x22
279                    - y12
280                    + 2 * y1y2
281                    - y22
282                )
283            )
284            * (x1_x2)
285        )
286        - (x12 - 2 * x1x2 + x22 + y12 - 2 * y1y2 + y22)
287        * (2 * rr1 - 2 * rr2 + r12 - r22 - x12 + x22 - y12 + y22)
288    ) / (2 * (x1_x2) * (x12 - 2 * x1x2 + x22 + y12 - 2 * y1y2 + y22))
289
290    y_2 = (
291        -2 * rr1y1
292        + 2 * rr1y2
293        + 2 * rr2y1
294        - 2 * rr2y2
295        - r12y1
296        + r12y2
297        + r22 * y1
298        - r22y2
299        + x12y1
300        + x12y2
301        - 2 * x1x2y1
302        - 2 * x1x2y2
303        + x22y1
304        + x22y2
305        + y13
306        - y12y2
307        - y1y22
308        + y23
309        + sqrt(
310            (-r12 + 2 * r1r2 - r22 + x12 - 2 * x1x2 + x22 + y12 - 2 * y1y2 + y22)
311            * (
312                4 * r_2
313                + 4 * rr1
314                + 4 * rr2
315                + r12
316                + 2 * r1r2
317                + r22
318                - x12
319                + 2 * x1x2
320                - x22
321                - y12
322                + 2 * y1y2
323                - y22
324            )
325        )
326        * (x1_x2)
327    ) / (2 * (x12 - 2 * x1x2 + x22 + y12 - 2 * y1y2 + y22))
328
329    return ((x_1, y_1), (x_2, y_2))

Given the centers and radii of 2 circles, return the center of a circle with radius r that is tangent to both circles.

Arguments:
  • c1 (tuple): Center of the first circle.
  • r1 (float): Radius of the first circle.
  • c2 (tuple): Center of the second circle.
  • r2 (float): Radius of the second circle.
  • r (float): Radius of the tangent circle.
Returns:

tuple: Centers (x1, y1) and (x2, y2) of the tangent circle.

def tangent_points(center1, radius, center2, radius2, cross=False):
332def tangent_points(center1, radius, center2, radius2, cross=False):
333    """Returns the tangent points (p1, p2, p3, p4) in world coordinates.
334
335    Args:
336        center1 (tuple): Center of the first circle.
337        radius (float): Radius of the first circle.
338        center2 (tuple): Center of the second circle.
339        radius2 (float): Radius of the second circle.
340        cross (bool, optional): Whether to calculate crossing tangents. Defaults to False.
341
342    Returns:
343        tuple: Tangent points (p1, p2, p3, p4) in world coordinates.
344    """
345    c1 = Circle_(center1, radius)
346    c2 = Circle_(center2, radius2)
347    if radius < radius2:
348        c1, c2 = c2, c1
349    pos = c1.center
350    dist = distance(pos, c2.center)
351    r1 = c1.radius
352    r2 = c2.radius
353
354    if cross:
355        dr = r1 + r2
356    else:
357        dr = r1 - r2
358
359    x = sqrt(dist**2 - dr**2)
360    y = pos[1] + r1
361    p1 = [pos[0], y]
362    p2 = [pos[0] + x, y]
363    points = homogenize([p1, p2])
364    alpha = angle_between_lines2((pos[0] + x, pos[1] + dr), pos, c2.center)
365    tp1w, tp2w = rotate(points, alpha, pos)
366
367    if x == 0:
368        beta = 0
369    else:
370        beta = pi / 2 - atan(dr / x)
371    tp3w = rotate([tp1w], -2 * beta, pos)[0]
372    tp4w = rotate([tp2w], -2 * beta, c2.center)[0]
373
374    return (tp1w, tp2w, tp3w, tp4w)

Returns the tangent points (p1, p2, p3, p4) in world coordinates.

Arguments:
  • center1 (tuple): Center of the first circle.
  • radius (float): Radius of the first circle.
  • center2 (tuple): Center of the second circle.
  • radius2 (float): Radius of the second circle.
  • cross (bool, optional): Whether to calculate crossing tangents. Defaults to False.
Returns:

tuple: Tangent points (p1, p2, p3, p4) in world coordinates.

def circle_area(rad):
377def circle_area(rad):
378    """Given the radius of a circle, return the area of the circle.
379
380    Args:
381        rad (float): Radius of the circle.
382
383    Returns:
384        float: Area of the circle.
385    """
386    return pi * rad**2

Given the radius of a circle, return the area of the circle.

Arguments:
  • rad (float): Radius of the circle.
Returns:

float: Area of the circle.

def circle_circumference(rad):
389def circle_circumference(rad):
390    """Given the radius of a circle, return the circumference of the circle.
391
392    Args:
393        rad (float): Radius of the circle.
394
395    Returns:
396        float: Circumference of the circle.
397    """
398    return 2 * pi * rad

Given the radius of a circle, return the circumference of the circle.

Arguments:
  • rad (float): Radius of the circle.
Returns:

float: Circumference of the circle.

def flower_angle(r1, r2, r3):
401def flower_angle(r1, r2, r3):
402    """Given the radii of 3 circles forming an interstice, return the angle between
403    the lines connecting circles' centers to center of the circle with r1 radius.
404
405    Args:
406        r1 (float): Radius of the first circle.
407        r2 (float): Radius of the second circle.
408        r3 (float): Radius of the third circle.
409
410    Returns:
411        float: Angle between the lines connecting circles' centers.
412    """
413    angle = acos(
414        ((r1 + r2) ** 2 + (r1 + r3) ** 2 - (r2 + r3) ** 2) / (2 * (r1 + r2) * (r1 + r3))
415    )
416
417    return angle

Given the radii of 3 circles forming an interstice, return the angle between the lines connecting circles' centers to center of the circle with r1 radius.

Arguments:
  • r1 (float): Radius of the first circle.
  • r2 (float): Radius of the second circle.
  • r3 (float): Radius of the third circle.
Returns:

float: Angle between the lines connecting circles' centers.

ratios = {8: 0.4974, 9: 0.5394, 10: 0.575, 11: 0.6056, 12: 0.6321, 13: 0.6553, 14: 0.6757, 15: 0.6939, 16: 0.7101, 17: 0.7248, 18: 0.738, 19: 0.75, 20: 0.7609}
def circle_flower(n, radius=25, layers=6, ratio=None):
425def circle_flower(n, radius=25, layers=6, ratio = None):
426    """Steiner chain. Return a list of circles that form a flower-like pattern.
427
428    Args:
429        n (int): Number of circles.
430        radius (float, optional): Radius of the circles. Defaults to 25.
431        layers (int, optional): Number of layers. Defaults to 6.
432        ratio (float, optional): Ratio for scaling. Defaults to None.
433
434    Returns:
435        list: List of circles forming a flower-like pattern.
436
437    Raises:
438        ValueError: If n is less than 8.
439    """
440    if n<8:
441        raise ValueError('n must be greater than 7')
442    if ratio is None:
443        if n<21:
444            ratio = ratios[n]
445        else:
446            ratio = (-0.000000089767 * n**4 + 0.000015821834 * n**3 +
447                    -0.001100867708 * n **2+ 0.038096046379 * n + 0.327363569038)
448
449    r1 = side_len_to_radius(n, 2*radius)
450    circles = Circle((r1, 0), radius).rotate(pi/(n/2), (0, 0), reps=n-1)
451    xform = scale_matrix(ratio) @ rotation_matrix(pi/n)
452
453    return circles.transform(xform_matrix=xform, reps=layers)

Steiner chain. Return a list of circles that form a flower-like pattern.

Arguments:
  • n (int): Number of circles.
  • radius (float, optional): Radius of the circles. Defaults to 25.
  • layers (int, optional): Number of layers. Defaults to 6.
  • ratio (float, optional): Ratio for scaling. Defaults to None.
Returns:

list: List of circles forming a flower-like pattern.

Raises:
  • ValueError: If n is less than 8.