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)
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]])
dot(a, b, out=None)
Dot product of two arrays. Specifically,
If both
aandbare 1-D arrays, it is inner product of vectors (without complex conjugation).If both
aandbare 2-D arrays, it is matrix multiplication, but usingmatmul()ora @ bis preferred.If either
aorbis 0-D (scalar), it is equivalent tomultiply()and usingnumpy.multiply(a, b)ora * bis preferred.If
ais an N-D array andbis a 1-D array, it is a sum product over the last axis ofaandb.If
ais an N-D array andbis an M-D array (whereM>=2), it is a sum product over the last axis ofaand the second-to-last axis ofb::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
A simple circle class.
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.
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.
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.
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.
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.
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.
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.
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.