complex-range 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- complex_range/__init__.py +25 -0
- complex_range/core.py +549 -0
- complex_range/dev.py +41 -0
- complex_range/farey.py +84 -0
- complex_range/py.typed +0 -0
- complex_range-1.0.0.dist-info/METADATA +330 -0
- complex_range-1.0.0.dist-info/RECORD +10 -0
- complex_range-1.0.0.dist-info/WHEEL +5 -0
- complex_range-1.0.0.dist-info/licenses/LICENSE +21 -0
- complex_range-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""
|
|
2
|
+
complex-range - Generate ranges of complex numbers.
|
|
3
|
+
|
|
4
|
+
A Python implementation of the Wolfram Language ComplexRange resource function,
|
|
5
|
+
originally created by Daniele Gregori.
|
|
6
|
+
|
|
7
|
+
Generates rectangular and linear ranges of complex numbers in the complex plane.
|
|
8
|
+
|
|
9
|
+
Example usage:
|
|
10
|
+
>>> from complex_range import complex_range
|
|
11
|
+
>>> list(complex_range(0, 2+2j))
|
|
12
|
+
[0j, 1j, 2j, (1+0j), (1+1j), (1+2j), (2+0j), (2+1j), (2+2j)]
|
|
13
|
+
|
|
14
|
+
>>> list(complex_range([0, 1+1j])) # Linear range
|
|
15
|
+
[0j, (1+1j)]
|
|
16
|
+
|
|
17
|
+
Author: Daniele Gregori
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from .core import complex_range, complex_range_iter, ComplexRangeError
|
|
21
|
+
from .farey import farey_sequence
|
|
22
|
+
|
|
23
|
+
__version__ = "1.0.0"
|
|
24
|
+
__author__ = "Daniele Gregori"
|
|
25
|
+
__all__ = ["complex_range", "complex_range_iter", "ComplexRangeError", "farey_sequence"]
|
complex_range/core.py
ADDED
|
@@ -0,0 +1,549 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Core implementation of the ComplexRange function.
|
|
3
|
+
|
|
4
|
+
This module provides the complex_range function which generates ranges of
|
|
5
|
+
complex numbers in either rectangular (grid) or linear (diagonal) patterns.
|
|
6
|
+
|
|
7
|
+
Author: Daniele Gregori
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from fractions import Fraction
|
|
11
|
+
from typing import List, Tuple, Union, Iterator, Optional, Literal
|
|
12
|
+
from numbers import Number
|
|
13
|
+
|
|
14
|
+
from .farey import farey_sequence
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ComplexRangeError(Exception):
|
|
18
|
+
"""Exception raised for ComplexRange-specific errors."""
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _farey_range_values(start: float, end: float, order: int) -> List[Union[int, float, Fraction]]:
|
|
23
|
+
"""
|
|
24
|
+
Generate values for a Farey range.
|
|
25
|
+
|
|
26
|
+
The Farey sequence of the given order is applied to each unit interval
|
|
27
|
+
in the range, and all points are combined.
|
|
28
|
+
|
|
29
|
+
Parameters
|
|
30
|
+
----------
|
|
31
|
+
start : float
|
|
32
|
+
Start of the range.
|
|
33
|
+
end : float
|
|
34
|
+
End of the range.
|
|
35
|
+
order : int
|
|
36
|
+
Order of the Farey sequence to use.
|
|
37
|
+
|
|
38
|
+
Returns
|
|
39
|
+
-------
|
|
40
|
+
List
|
|
41
|
+
Sorted list of values in the range.
|
|
42
|
+
"""
|
|
43
|
+
if order < 1:
|
|
44
|
+
raise ComplexRangeError(f"Farey order must be positive, got {order}")
|
|
45
|
+
|
|
46
|
+
# Get the Farey sequence for this order
|
|
47
|
+
farey = farey_sequence(order)
|
|
48
|
+
|
|
49
|
+
# Determine the number of unit intervals
|
|
50
|
+
span = end - start
|
|
51
|
+
num_units = int(span)
|
|
52
|
+
|
|
53
|
+
# If span is not an integer, we need to handle fractional part
|
|
54
|
+
if span != num_units:
|
|
55
|
+
# For non-integer spans, just scale the Farey sequence
|
|
56
|
+
result = sorted(set(float(start + f * span) for f in farey))
|
|
57
|
+
return result
|
|
58
|
+
|
|
59
|
+
# For integer spans, apply Farey to each unit interval
|
|
60
|
+
all_values = set()
|
|
61
|
+
|
|
62
|
+
for unit in range(num_units):
|
|
63
|
+
unit_start = start + unit
|
|
64
|
+
for f in farey:
|
|
65
|
+
all_values.add(unit_start + float(f))
|
|
66
|
+
|
|
67
|
+
# Convert to sorted list
|
|
68
|
+
result = sorted(all_values)
|
|
69
|
+
|
|
70
|
+
# Try to keep as fractions if possible
|
|
71
|
+
if all(isinstance(x, int) or (isinstance(x, float) and x == int(x)) for x in [start, end]):
|
|
72
|
+
frac_result = []
|
|
73
|
+
for val in result:
|
|
74
|
+
try:
|
|
75
|
+
frac = Fraction(val).limit_denominator(1000)
|
|
76
|
+
if abs(float(frac) - val) < 1e-10:
|
|
77
|
+
frac_result.append(frac)
|
|
78
|
+
else:
|
|
79
|
+
frac_result.append(val)
|
|
80
|
+
except (ValueError, OverflowError):
|
|
81
|
+
frac_result.append(val)
|
|
82
|
+
return frac_result
|
|
83
|
+
|
|
84
|
+
return result
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _to_number(z: Union[complex, float, int, Fraction]) -> Union[complex, Fraction, int, float]:
|
|
88
|
+
"""Convert input to appropriate numeric type, preserving exactness where possible."""
|
|
89
|
+
if isinstance(z, (int, Fraction)):
|
|
90
|
+
return z
|
|
91
|
+
if isinstance(z, float):
|
|
92
|
+
# Try to convert to Fraction if it's a "nice" number
|
|
93
|
+
try:
|
|
94
|
+
frac = Fraction(z).limit_denominator(10000)
|
|
95
|
+
if abs(float(frac) - z) < 1e-10:
|
|
96
|
+
return frac
|
|
97
|
+
except (ValueError, OverflowError):
|
|
98
|
+
pass
|
|
99
|
+
return z
|
|
100
|
+
if isinstance(z, complex):
|
|
101
|
+
return z
|
|
102
|
+
return z
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _arange_inclusive(start: Union[int, float, Fraction],
|
|
106
|
+
end: Union[int, float, Fraction],
|
|
107
|
+
step: Union[int, float, Fraction]) -> List[Union[int, float, Fraction]]:
|
|
108
|
+
"""
|
|
109
|
+
Generate a range from start to end (inclusive) with given step.
|
|
110
|
+
|
|
111
|
+
Similar to numpy.arange but includes the endpoint if it's exactly reachable.
|
|
112
|
+
Handles both positive and negative steps.
|
|
113
|
+
"""
|
|
114
|
+
if step == 0:
|
|
115
|
+
raise ComplexRangeError("Step cannot be zero")
|
|
116
|
+
|
|
117
|
+
# Handle mismatched direction (positive step but going down, or negative step but going up)
|
|
118
|
+
if (step > 0 and start > end) or (step < 0 and start < end):
|
|
119
|
+
return []
|
|
120
|
+
|
|
121
|
+
result = []
|
|
122
|
+
current = start
|
|
123
|
+
|
|
124
|
+
# Use Fraction for exact arithmetic when possible
|
|
125
|
+
if isinstance(start, (int, Fraction)) and isinstance(end, (int, Fraction)) and isinstance(step, (int, Fraction)):
|
|
126
|
+
start = Fraction(start)
|
|
127
|
+
end = Fraction(end)
|
|
128
|
+
step = Fraction(step)
|
|
129
|
+
current = start
|
|
130
|
+
|
|
131
|
+
if step > 0:
|
|
132
|
+
while current <= end:
|
|
133
|
+
result.append(current)
|
|
134
|
+
current = current + step
|
|
135
|
+
else:
|
|
136
|
+
# Negative step: go downward
|
|
137
|
+
while current >= end:
|
|
138
|
+
result.append(current)
|
|
139
|
+
current = current + step
|
|
140
|
+
else:
|
|
141
|
+
# Floating point version with tolerance
|
|
142
|
+
eps = abs(step) * 1e-14
|
|
143
|
+
if step > 0:
|
|
144
|
+
while current <= end + eps:
|
|
145
|
+
result.append(current)
|
|
146
|
+
current = current + step
|
|
147
|
+
else:
|
|
148
|
+
# Negative step: go downward
|
|
149
|
+
while current >= end - eps:
|
|
150
|
+
result.append(current)
|
|
151
|
+
current = current + step
|
|
152
|
+
|
|
153
|
+
return result
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _rectangular_range(
|
|
157
|
+
z1: complex,
|
|
158
|
+
z2: complex,
|
|
159
|
+
step: Union[complex, Tuple[Number, Number], None] = None,
|
|
160
|
+
increment_first: Literal['im', 're'] = 'im',
|
|
161
|
+
farey_range: bool = False
|
|
162
|
+
) -> List[complex]:
|
|
163
|
+
"""
|
|
164
|
+
Generate a rectangular grid of complex numbers.
|
|
165
|
+
|
|
166
|
+
Parameters
|
|
167
|
+
----------
|
|
168
|
+
z1 : complex
|
|
169
|
+
First corner of the rectangle (minimum real and imaginary parts).
|
|
170
|
+
z2 : complex
|
|
171
|
+
Second corner of the rectangle (maximum real and imaginary parts).
|
|
172
|
+
step : complex or tuple, optional
|
|
173
|
+
Step size. Can be a complex number (real part = real step, imag part = imag step)
|
|
174
|
+
or a tuple (real_step, imag_step). Default is 1+1j.
|
|
175
|
+
increment_first : {'im', 're'}
|
|
176
|
+
Which component to increment first in the iteration. Default is 'im'.
|
|
177
|
+
farey_range : bool
|
|
178
|
+
If True, use Farey sequence to generate intermediate points.
|
|
179
|
+
|
|
180
|
+
Returns
|
|
181
|
+
-------
|
|
182
|
+
List[complex]
|
|
183
|
+
List of complex numbers forming a rectangular grid.
|
|
184
|
+
"""
|
|
185
|
+
|
|
186
|
+
# Extract real and imaginary bounds
|
|
187
|
+
re_min = z1.real
|
|
188
|
+
re_max = z2.real
|
|
189
|
+
im_min = z1.imag
|
|
190
|
+
im_max = z2.imag
|
|
191
|
+
|
|
192
|
+
# Determine step sizes
|
|
193
|
+
if step is None:
|
|
194
|
+
re_step = 1
|
|
195
|
+
im_step = 1
|
|
196
|
+
elif isinstance(step, (list, tuple)):
|
|
197
|
+
re_step, im_step = step
|
|
198
|
+
else:
|
|
199
|
+
# Complex number: real part is real step, imag part is imag step
|
|
200
|
+
re_step = step.real if hasattr(step, 'real') else step
|
|
201
|
+
im_step = step.imag if hasattr(step, 'imag') else step
|
|
202
|
+
|
|
203
|
+
# Check for reversed range - return empty in case
|
|
204
|
+
if ((z1.real > z2.real and (re_step > 0)) and (z1.imag > z2.imag and (im_step > 0))): ###
|
|
205
|
+
return []
|
|
206
|
+
elif ((z1.real < z2.real and (re_step < 0)) and (z1.imag < z2.imag and (im_step < 0))): ###
|
|
207
|
+
return []
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
# Handle Farey range
|
|
211
|
+
if farey_range:
|
|
212
|
+
# Farey range requires integer step
|
|
213
|
+
if not (isinstance(re_step, int) or (isinstance(re_step, float) and re_step.is_integer())):
|
|
214
|
+
raise ComplexRangeError(
|
|
215
|
+
f"No Farey range with non-integer third argument {re_step} is allowed."
|
|
216
|
+
)
|
|
217
|
+
if not (isinstance(im_step, int) or (isinstance(im_step, float) and im_step.is_integer())):
|
|
218
|
+
raise ComplexRangeError(
|
|
219
|
+
f"No Farey range with non-integer third argument {im_step} is allowed."
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
re_order = int(re_step)
|
|
223
|
+
im_order = int(im_step)
|
|
224
|
+
|
|
225
|
+
# Generate Farey-based ranges
|
|
226
|
+
# The Farey sequence is applied to each unit interval, then combined
|
|
227
|
+
re_values = _farey_range_values(re_min, re_max, re_order)
|
|
228
|
+
im_values = _farey_range_values(im_min, im_max, im_order)
|
|
229
|
+
else:
|
|
230
|
+
# Regular range
|
|
231
|
+
re_values = _arange_inclusive(re_min, re_max, re_step)
|
|
232
|
+
im_values = _arange_inclusive(im_min, im_max, im_step)
|
|
233
|
+
|
|
234
|
+
# Handle empty ranges
|
|
235
|
+
if not re_values or not im_values:
|
|
236
|
+
return []
|
|
237
|
+
|
|
238
|
+
# Generate the grid
|
|
239
|
+
result = []
|
|
240
|
+
if increment_first == 'im':
|
|
241
|
+
# Increment imaginary first (default): for each real, iterate through imaginaries
|
|
242
|
+
for re in re_values:
|
|
243
|
+
for im in im_values:
|
|
244
|
+
result.append(_make_complex(re, im))
|
|
245
|
+
else:
|
|
246
|
+
# Increment real first: for each imaginary, iterate through reals
|
|
247
|
+
for im in im_values:
|
|
248
|
+
for re in re_values:
|
|
249
|
+
result.append(_make_complex(re, im))
|
|
250
|
+
|
|
251
|
+
return result
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def _make_complex(re: Union[int, float, Fraction], im: Union[int, float, Fraction]) -> complex:
|
|
255
|
+
"""Create a complex number from real and imaginary parts."""
|
|
256
|
+
# Convert Fractions to float for complex number creation
|
|
257
|
+
re_val = float(re) if isinstance(re, Fraction) else re
|
|
258
|
+
im_val = float(im) if isinstance(im, Fraction) else im
|
|
259
|
+
|
|
260
|
+
# Simplify representation
|
|
261
|
+
if im_val == 0:
|
|
262
|
+
if isinstance(re_val, float) and re_val.is_integer():
|
|
263
|
+
return complex(int(re_val), 0)
|
|
264
|
+
return complex(re_val, 0)
|
|
265
|
+
if re_val == 0:
|
|
266
|
+
if isinstance(im_val, float) and im_val.is_integer():
|
|
267
|
+
return complex(0, int(im_val))
|
|
268
|
+
return complex(0, im_val)
|
|
269
|
+
|
|
270
|
+
return complex(re_val, im_val)
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def _linear_range(
|
|
274
|
+
z1: complex,
|
|
275
|
+
z2: complex,
|
|
276
|
+
step: Optional[Union[complex, Tuple[Number, Number]]] = None,
|
|
277
|
+
farey_range: bool = False
|
|
278
|
+
) -> List[complex]:
|
|
279
|
+
"""
|
|
280
|
+
Generate a linear (diagonal) range of complex numbers.
|
|
281
|
+
|
|
282
|
+
Points lie on a line from z1 to z2, with both real and imaginary
|
|
283
|
+
components incrementing together.
|
|
284
|
+
|
|
285
|
+
Parameters
|
|
286
|
+
----------
|
|
287
|
+
z1 : complex
|
|
288
|
+
Starting point.
|
|
289
|
+
z2 : complex
|
|
290
|
+
Ending point.
|
|
291
|
+
step : complex or tuple, optional
|
|
292
|
+
Step sizes. Can be a complex number (real part = real step, imag part = imag step)
|
|
293
|
+
or a tuple (real_step, imag_step). Default is (1, 1).
|
|
294
|
+
farey_range : bool
|
|
295
|
+
If True, use Farey sequence to generate intermediate points.
|
|
296
|
+
|
|
297
|
+
Returns
|
|
298
|
+
-------
|
|
299
|
+
List[complex]
|
|
300
|
+
List of complex numbers along the line from z1 to z2.
|
|
301
|
+
"""
|
|
302
|
+
if step is None:
|
|
303
|
+
re_step = 1
|
|
304
|
+
im_step = 1
|
|
305
|
+
elif isinstance(step, (list, tuple)):
|
|
306
|
+
re_step, im_step = step
|
|
307
|
+
elif isinstance(step, complex):
|
|
308
|
+
re_step = step.real
|
|
309
|
+
im_step = step.imag
|
|
310
|
+
else:
|
|
311
|
+
# Single number: use as both steps
|
|
312
|
+
re_step = step
|
|
313
|
+
im_step = step
|
|
314
|
+
|
|
315
|
+
# Get component ranges
|
|
316
|
+
re_start, re_end = z1.real, z2.real
|
|
317
|
+
im_start, im_end = z1.imag, z2.imag
|
|
318
|
+
|
|
319
|
+
# Handle Farey range
|
|
320
|
+
if farey_range:
|
|
321
|
+
# Farey range requires integer step
|
|
322
|
+
if not (isinstance(re_step, int) or (isinstance(re_step, float) and re_step.is_integer())):
|
|
323
|
+
raise ComplexRangeError(
|
|
324
|
+
f"No Farey range with non-integer third argument {re_step} is allowed."
|
|
325
|
+
)
|
|
326
|
+
if not (isinstance(im_step, int) or (isinstance(im_step, float) and im_step.is_integer())):
|
|
327
|
+
raise ComplexRangeError(
|
|
328
|
+
f"No Farey range with non-integer third argument {im_step} is allowed."
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
re_order = int(re_step)
|
|
332
|
+
im_order = int(im_step)
|
|
333
|
+
|
|
334
|
+
# Generate Farey-based ranges
|
|
335
|
+
re_values = _farey_range_values(re_start, re_end, re_order)
|
|
336
|
+
im_values = _farey_range_values(im_start, im_end, im_order)
|
|
337
|
+
else:
|
|
338
|
+
# Generate ranges for each component
|
|
339
|
+
re_values = _arange_inclusive(re_start, re_end, re_step)
|
|
340
|
+
im_values = _arange_inclusive(im_start, im_end, im_step)
|
|
341
|
+
|
|
342
|
+
# For linear range, we need to pair up values
|
|
343
|
+
# Take the minimum length (they should increment together)
|
|
344
|
+
n = min(len(re_values), len(im_values))
|
|
345
|
+
|
|
346
|
+
if n == 0:
|
|
347
|
+
return []
|
|
348
|
+
|
|
349
|
+
return [_make_complex(re_values[i], im_values[i]) for i in range(n)]
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def complex_range(
|
|
353
|
+
z1: Union[complex, float, int, List, Tuple],
|
|
354
|
+
z2: Optional[Union[complex, float, int]] = None,
|
|
355
|
+
step: Optional[Union[complex, float, int, List, Tuple]] = None,
|
|
356
|
+
*,
|
|
357
|
+
increment_first: Literal['im', 're'] = 'im',
|
|
358
|
+
farey_range: bool = False
|
|
359
|
+
) -> List[complex]:
|
|
360
|
+
"""
|
|
361
|
+
Generate a range of complex numbers.
|
|
362
|
+
|
|
363
|
+
This function can generate either rectangular (grid) or linear (diagonal)
|
|
364
|
+
ranges of complex numbers in the complex plane.
|
|
365
|
+
|
|
366
|
+
Parameters
|
|
367
|
+
----------
|
|
368
|
+
z1 : complex, number, or list
|
|
369
|
+
For rectangular range: the first corner (or single endpoint if z2 is None).
|
|
370
|
+
For linear range: a list [z] or [z_start, z_end] specifying endpoints.
|
|
371
|
+
z2 : complex or number, optional
|
|
372
|
+
For rectangular range: the second corner.
|
|
373
|
+
step : complex, number, list, or tuple, optional
|
|
374
|
+
Step size(s) for the range.
|
|
375
|
+
- For rectangular range with complex step: real part = real step, imag = imag step
|
|
376
|
+
- For rectangular/linear range with list/tuple: [real_step, imag_step]
|
|
377
|
+
Default step is 1+1j for rectangular, (1, 1) for linear.
|
|
378
|
+
increment_first : {'im', 're'}, keyword-only
|
|
379
|
+
For rectangular range: which component to increment first.
|
|
380
|
+
'im' (default): increment imaginary first (column-major order)
|
|
381
|
+
're': increment real first (row-major order)
|
|
382
|
+
farey_range : bool, keyword-only
|
|
383
|
+
If True, use Farey sequence to generate intermediate points.
|
|
384
|
+
Only valid for rectangular ranges with integer step.
|
|
385
|
+
Creates a finer grid based on the Farey sequence of the given order.
|
|
386
|
+
|
|
387
|
+
Returns
|
|
388
|
+
-------
|
|
389
|
+
List[complex]
|
|
390
|
+
A list of complex numbers.
|
|
391
|
+
|
|
392
|
+
Raises
|
|
393
|
+
------
|
|
394
|
+
ComplexRangeError
|
|
395
|
+
If farey_range is True but step is non-integer.
|
|
396
|
+
|
|
397
|
+
Examples
|
|
398
|
+
--------
|
|
399
|
+
Rectangular range from origin:
|
|
400
|
+
|
|
401
|
+
>>> complex_range(2+2j)
|
|
402
|
+
[(0+0j), 1j, 2j, (1+0j), (1+1j), (1+2j), (2+0j), (2+1j), (2+2j)]
|
|
403
|
+
|
|
404
|
+
Rectangular range between two points:
|
|
405
|
+
|
|
406
|
+
>>> complex_range(1+1j, 2+2j)
|
|
407
|
+
[(1+1j), (1+2j), (2+1j), (2+2j)]
|
|
408
|
+
|
|
409
|
+
Rectangular range with step:
|
|
410
|
+
|
|
411
|
+
>>> complex_range(0, 2+2j, 0.5+0.5j)
|
|
412
|
+
[(0+0j), 0.5j, 1j, 1.5j, 2j, (0.5+0j), ...]
|
|
413
|
+
|
|
414
|
+
Linear range (points on a diagonal):
|
|
415
|
+
|
|
416
|
+
>>> complex_range([0, 2+2j])
|
|
417
|
+
[(0+0j), (1+1j), (2+2j)]
|
|
418
|
+
|
|
419
|
+
Linear range with step:
|
|
420
|
+
|
|
421
|
+
>>> complex_range([0, 4+4j], (2, 2))
|
|
422
|
+
[(0+0j), (2+2j), (4+4j)]
|
|
423
|
+
|
|
424
|
+
Linear range with complex step:
|
|
425
|
+
|
|
426
|
+
>>> complex_range([0, 4+4j], 2+2j)
|
|
427
|
+
[(0+0j), (2+2j), (4+4j)]
|
|
428
|
+
|
|
429
|
+
Descending linear range:
|
|
430
|
+
|
|
431
|
+
>>> complex_range([2+2j, 0], -1-1j)
|
|
432
|
+
[(2+2j), (1+1j), 0j]
|
|
433
|
+
|
|
434
|
+
Rectangular range with Farey subdivision:
|
|
435
|
+
|
|
436
|
+
>>> complex_range(0, 1+1j, 2, farey_range=True)
|
|
437
|
+
[(0+0j), 0.5j, 1j, (0.5+0j), (0.5+0.5j), (0.5+1j), (1+0j), (1+0.5j), (1+1j)]
|
|
438
|
+
|
|
439
|
+
See Also
|
|
440
|
+
--------
|
|
441
|
+
farey_sequence : Generate Farey sequence of a given order.
|
|
442
|
+
|
|
443
|
+
Notes
|
|
444
|
+
-----
|
|
445
|
+
The function distinguishes between rectangular and linear ranges based on
|
|
446
|
+
the type of the first argument:
|
|
447
|
+
|
|
448
|
+
- If z1 is a list or tuple, a linear range is generated
|
|
449
|
+
- Otherwise, a rectangular range is generated
|
|
450
|
+
|
|
451
|
+
For rectangular ranges, the default iteration order is "imaginary first",
|
|
452
|
+
meaning for each real value, all imaginary values are enumerated before
|
|
453
|
+
moving to the next real value.
|
|
454
|
+
|
|
455
|
+
Author: Daniele Gregori
|
|
456
|
+
"""
|
|
457
|
+
# Check if this is a linear range (first argument is a list/tuple)
|
|
458
|
+
if isinstance(z1, (list, tuple)):
|
|
459
|
+
return _handle_linear_range(z1, z2, step, farey_range)
|
|
460
|
+
|
|
461
|
+
# Rectangular range
|
|
462
|
+
return _handle_rectangular_range(z1, z2, step, increment_first, farey_range)
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
def _handle_linear_range(
|
|
466
|
+
z_spec: Union[List, Tuple],
|
|
467
|
+
step_or_none: Optional[Union[complex, float, int, List, Tuple]],
|
|
468
|
+
step: Optional[Union[complex, float, int, List, Tuple]],
|
|
469
|
+
farey_range: bool = False
|
|
470
|
+
) -> List[complex]:
|
|
471
|
+
"""Handle linear range cases."""
|
|
472
|
+
# Parse the specification
|
|
473
|
+
if len(z_spec) == 1:
|
|
474
|
+
# {z} - linear from 0 to z
|
|
475
|
+
z_start = complex(0, 0)
|
|
476
|
+
z_end = complex(z_spec[0])
|
|
477
|
+
elif len(z_spec) == 2:
|
|
478
|
+
# {z1, z2} - linear from z1 to z2
|
|
479
|
+
z_start = complex(z_spec[0])
|
|
480
|
+
z_end = complex(z_spec[1])
|
|
481
|
+
else:
|
|
482
|
+
raise ComplexRangeError(f"Linear range specification must have 1 or 2 elements, got {len(z_spec)}")
|
|
483
|
+
|
|
484
|
+
# Determine step (step_or_none is actually the step for linear ranges)
|
|
485
|
+
if step_or_none is not None:
|
|
486
|
+
if isinstance(step_or_none, (list, tuple)):
|
|
487
|
+
linear_step = tuple(step_or_none)
|
|
488
|
+
elif isinstance(step_or_none, complex):
|
|
489
|
+
# Pass complex step directly - _linear_range will handle it
|
|
490
|
+
linear_step = step_or_none
|
|
491
|
+
else:
|
|
492
|
+
raise ComplexRangeError(f"Invalid step specification: {step_or_none}")
|
|
493
|
+
else:
|
|
494
|
+
linear_step = None
|
|
495
|
+
|
|
496
|
+
return _linear_range(z_start, z_end, linear_step, farey_range)
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
def _handle_rectangular_range(
|
|
500
|
+
z1: Union[complex, float, int],
|
|
501
|
+
z2: Optional[Union[complex, float, int]],
|
|
502
|
+
step: Optional[Union[complex, float, int, List, Tuple]],
|
|
503
|
+
increment_first: Literal['im', 're'],
|
|
504
|
+
farey_range: bool
|
|
505
|
+
) -> List[complex]:
|
|
506
|
+
"""Handle rectangular range cases."""
|
|
507
|
+
# Convert to complex
|
|
508
|
+
z1_complex = complex(z1)
|
|
509
|
+
|
|
510
|
+
if z2 is None:
|
|
511
|
+
# Single argument: range from 0 to z1
|
|
512
|
+
z2_complex = z1_complex
|
|
513
|
+
z1_complex = complex(0, 0)
|
|
514
|
+
else:
|
|
515
|
+
z2_complex = complex(z2)
|
|
516
|
+
|
|
517
|
+
# Handle step
|
|
518
|
+
if step is not None:
|
|
519
|
+
if isinstance(step, (list, tuple)):
|
|
520
|
+
step_tuple = tuple(step)
|
|
521
|
+
else:
|
|
522
|
+
step_val = complex(step) if not isinstance(step, complex) else step
|
|
523
|
+
step_tuple = step_val
|
|
524
|
+
else:
|
|
525
|
+
step_tuple = None
|
|
526
|
+
|
|
527
|
+
return _rectangular_range(z1_complex, z2_complex, step_tuple, increment_first, farey_range)
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
# Convenience function for iterator version
|
|
531
|
+
def complex_range_iter(
|
|
532
|
+
z1: Union[complex, float, int, List, Tuple],
|
|
533
|
+
z2: Optional[Union[complex, float, int]] = None,
|
|
534
|
+
step: Optional[Union[complex, float, int, List, Tuple]] = None,
|
|
535
|
+
*,
|
|
536
|
+
increment_first: Literal['im', 're'] = 'im',
|
|
537
|
+
farey_range: bool = False
|
|
538
|
+
) -> Iterator[complex]:
|
|
539
|
+
"""
|
|
540
|
+
Iterator version of complex_range.
|
|
541
|
+
|
|
542
|
+
Same as complex_range but returns an iterator instead of a list.
|
|
543
|
+
Useful for memory efficiency with large ranges.
|
|
544
|
+
|
|
545
|
+
See complex_range for full documentation.
|
|
546
|
+
|
|
547
|
+
Author: Daniele Gregori
|
|
548
|
+
"""
|
|
549
|
+
return iter(complex_range(z1, z2, step, increment_first=increment_first, farey_range=farey_range))
|
complex_range/dev.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Development/testing utilities for complex-range.
|
|
3
|
+
|
|
4
|
+
This module exposes internal functions for interactive testing and debugging.
|
|
5
|
+
Import with: from complex_range.dev import *
|
|
6
|
+
|
|
7
|
+
Author: Daniele Gregori
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from .core import (
|
|
11
|
+
complex_range,
|
|
12
|
+
complex_range_iter,
|
|
13
|
+
ComplexRangeError,
|
|
14
|
+
_rectangular_range,
|
|
15
|
+
_linear_range,
|
|
16
|
+
_arange_inclusive,
|
|
17
|
+
_make_complex,
|
|
18
|
+
_farey_range_values,
|
|
19
|
+
_handle_linear_range,
|
|
20
|
+
_handle_rectangular_range,
|
|
21
|
+
_to_number,
|
|
22
|
+
)
|
|
23
|
+
from .farey import farey_sequence, scaled_farey_sequence
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
# Public API
|
|
27
|
+
"complex_range",
|
|
28
|
+
"complex_range_iter",
|
|
29
|
+
"ComplexRangeError",
|
|
30
|
+
"farey_sequence",
|
|
31
|
+
# Internal functions (for testing)
|
|
32
|
+
"_rectangular_range",
|
|
33
|
+
"_linear_range",
|
|
34
|
+
"_arange_inclusive",
|
|
35
|
+
"_make_complex",
|
|
36
|
+
"_farey_range_values",
|
|
37
|
+
"_handle_linear_range",
|
|
38
|
+
"_handle_rectangular_range",
|
|
39
|
+
"_to_number",
|
|
40
|
+
"scaled_farey_sequence",
|
|
41
|
+
]
|
complex_range/farey.py
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Farey sequence implementation for complex-range.
|
|
3
|
+
|
|
4
|
+
The Farey sequence F_n is the sequence of completely reduced fractions,
|
|
5
|
+
between 0 and 1, which have denominators less than or equal to n,
|
|
6
|
+
arranged in order of increasing size.
|
|
7
|
+
|
|
8
|
+
Author: Daniele Gregori
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from fractions import Fraction
|
|
12
|
+
from typing import List
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def farey_sequence(n: int) -> List[Fraction]:
|
|
16
|
+
"""
|
|
17
|
+
Generate the Farey sequence of order n.
|
|
18
|
+
|
|
19
|
+
The Farey sequence F_n is the sequence of completely reduced fractions
|
|
20
|
+
between 0 and 1, with denominators less than or equal to n, arranged
|
|
21
|
+
in increasing order.
|
|
22
|
+
|
|
23
|
+
Parameters
|
|
24
|
+
----------
|
|
25
|
+
n : int
|
|
26
|
+
The order of the Farey sequence. Must be a positive integer.
|
|
27
|
+
|
|
28
|
+
Returns
|
|
29
|
+
-------
|
|
30
|
+
List[Fraction]
|
|
31
|
+
A list of Fraction objects representing the Farey sequence.
|
|
32
|
+
|
|
33
|
+
Raises
|
|
34
|
+
------
|
|
35
|
+
ValueError
|
|
36
|
+
If n is not a positive integer.
|
|
37
|
+
|
|
38
|
+
Examples
|
|
39
|
+
--------
|
|
40
|
+
>>> farey_sequence(1)
|
|
41
|
+
[Fraction(0, 1), Fraction(1, 1)]
|
|
42
|
+
|
|
43
|
+
>>> farey_sequence(3)
|
|
44
|
+
[Fraction(0, 1), Fraction(1, 3), Fraction(1, 2), Fraction(2, 3), Fraction(1, 1)]
|
|
45
|
+
|
|
46
|
+
>>> farey_sequence(5)
|
|
47
|
+
[Fraction(0, 1), Fraction(1, 5), Fraction(1, 4), Fraction(1, 3),
|
|
48
|
+
Fraction(2, 5), Fraction(1, 2), Fraction(3, 5), Fraction(2, 3),
|
|
49
|
+
Fraction(3, 4), Fraction(4, 5), Fraction(1, 1)]
|
|
50
|
+
"""
|
|
51
|
+
if not isinstance(n, int) or n < 1:
|
|
52
|
+
raise ValueError(f"Order n must be a positive integer, got {n}")
|
|
53
|
+
|
|
54
|
+
# Generate all fractions with denominator <= n
|
|
55
|
+
fractions_set = set()
|
|
56
|
+
for denom in range(1, n + 1):
|
|
57
|
+
for numer in range(0, denom + 1):
|
|
58
|
+
fractions_set.add(Fraction(numer, denom))
|
|
59
|
+
|
|
60
|
+
# Sort and return
|
|
61
|
+
return sorted(fractions_set)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def scaled_farey_sequence(n: int, start: float, end: float) -> List[Fraction]:
|
|
65
|
+
"""
|
|
66
|
+
Generate a scaled Farey sequence between start and end.
|
|
67
|
+
|
|
68
|
+
Parameters
|
|
69
|
+
----------
|
|
70
|
+
n : int
|
|
71
|
+
The order of the Farey sequence.
|
|
72
|
+
start : float
|
|
73
|
+
The starting value.
|
|
74
|
+
end : float
|
|
75
|
+
The ending value.
|
|
76
|
+
|
|
77
|
+
Returns
|
|
78
|
+
-------
|
|
79
|
+
List[Fraction]
|
|
80
|
+
Farey sequence scaled to [start, end] interval.
|
|
81
|
+
"""
|
|
82
|
+
farey = farey_sequence(n)
|
|
83
|
+
span = end - start
|
|
84
|
+
return [Fraction(start) + f * Fraction(span) for f in farey]
|
complex_range/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: complex-range
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Generate ranges of complex numbers - rectangular grids and linear sequences in the complex plane
|
|
5
|
+
Author-email: Daniele Gregori <dangregori@gmail.com>
|
|
6
|
+
Maintainer-email: Daniele Gregori <dangregori@gmail.com>
|
|
7
|
+
License: MIT
|
|
8
|
+
Project-URL: Homepage, https://pypi.org/project/complex-range
|
|
9
|
+
Project-URL: Documentation, https://resources.wolframcloud.com/FunctionRepository/resources/ComplexRange/
|
|
10
|
+
Project-URL: Repository, https://github.com/Daniele-Gregori/PyPI-packages
|
|
11
|
+
Project-URL: Issues, https://github.com/Daniele-Gregori/PyPI-packages/issues
|
|
12
|
+
Project-URL: Changelog, https://github.com/Daniele-Gregori/PyPI-packages/blob/main/CHANGELOG.md
|
|
13
|
+
Keywords: complex,numbers,range,grid,mathematics,farey,sequence,complex-plane
|
|
14
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Intended Audience :: Science/Research
|
|
17
|
+
Classifier: Intended Audience :: Education
|
|
18
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
19
|
+
Classifier: Operating System :: OS Independent
|
|
20
|
+
Classifier: Programming Language :: Python :: 3
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
25
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
26
|
+
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
27
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
28
|
+
Classifier: Typing :: Typed
|
|
29
|
+
Requires-Python: >=3.8
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
License-File: LICENSE
|
|
32
|
+
Provides-Extra: dev
|
|
33
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
34
|
+
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
35
|
+
Requires-Dist: black>=23.0; extra == "dev"
|
|
36
|
+
Requires-Dist: isort>=5.0; extra == "dev"
|
|
37
|
+
Requires-Dist: mypy>=1.0; extra == "dev"
|
|
38
|
+
Requires-Dist: flake8>=6.0; extra == "dev"
|
|
39
|
+
Provides-Extra: docs
|
|
40
|
+
Requires-Dist: sphinx>=6.0; extra == "docs"
|
|
41
|
+
Requires-Dist: sphinx-rtd-theme>=1.0; extra == "docs"
|
|
42
|
+
Dynamic: license-file
|
|
43
|
+
|
|
44
|
+
# complex-range
|
|
45
|
+
|
|
46
|
+
[](https://badge.fury.io/py/complex-range)
|
|
47
|
+
[](https://pypi.org/project/complex-range/)
|
|
48
|
+
[](https://opensource.org/licenses/MIT)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
Generate ranges of complex numbers in Python - rectangular grids and linear sequences in the complex plane.
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
## Installation
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pip install complex-range
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Quick Start
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
from complex_range import complex_range
|
|
64
|
+
|
|
65
|
+
# Rectangular grid from origin to 2+2j
|
|
66
|
+
grid = complex_range(0, 2+2j)
|
|
67
|
+
# [0j, 1j, 2j, (1+0j), (1+1j), (1+2j), (2+0j), (2+1j), (2+2j)]
|
|
68
|
+
|
|
69
|
+
# Linear range along a diagonal
|
|
70
|
+
line = complex_range([0, 3+3j])
|
|
71
|
+
# [0j, (1+1j), (2+2j), (3+3j)]
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Features
|
|
75
|
+
|
|
76
|
+
- **Rectangular ranges**: Generate 2D grids of complex numbers
|
|
77
|
+
- **Linear ranges**: Generate points along a line in the complex plane
|
|
78
|
+
- **Flexible step sizes**: Control spacing independently for real and imaginary axes
|
|
79
|
+
- **Negative steps**: Support for descending ranges with negative step values
|
|
80
|
+
- **Farey sequence subdivision**: Create refined grids using number-theoretic sequences
|
|
81
|
+
- **Iteration order control**: Choose whether to iterate real or imaginary component first
|
|
82
|
+
- **Pure Python**: No dependencies required
|
|
83
|
+
|
|
84
|
+
## Usage
|
|
85
|
+
|
|
86
|
+
### Rectangular Ranges
|
|
87
|
+
|
|
88
|
+
Generate a grid of complex numbers between two corners:
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
from complex_range import complex_range
|
|
92
|
+
|
|
93
|
+
# Basic rectangular range
|
|
94
|
+
complex_range(0, 2+2j)
|
|
95
|
+
# Returns: [0j, 1j, 2j, (1+0j), (1+1j), (1+2j), (2+0j), (2+1j), (2+2j)]
|
|
96
|
+
|
|
97
|
+
# Single argument: range from 0 to z
|
|
98
|
+
complex_range(2+3j)
|
|
99
|
+
# Returns grid from 0 to 2+3j (3×4 = 12 points)
|
|
100
|
+
|
|
101
|
+
# With custom step size (complex number)
|
|
102
|
+
complex_range(0, 2+2j, 0.5+0.5j)
|
|
103
|
+
# Real step = 0.5, Imaginary step = 0.5
|
|
104
|
+
|
|
105
|
+
# With separate real and imaginary steps
|
|
106
|
+
complex_range(0, 4+6j, [2, 3])
|
|
107
|
+
# Real step = 2, Imaginary step = 3
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Linear Ranges
|
|
111
|
+
|
|
112
|
+
Generate points along a line (diagonal) in the complex plane:
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
# Linear from 0 to endpoint
|
|
116
|
+
complex_range([3+3j])
|
|
117
|
+
# Returns: [0j, (1+1j), (2+2j), (3+3j)]
|
|
118
|
+
|
|
119
|
+
# Linear between two points
|
|
120
|
+
complex_range([-1-1j, 2+2j])
|
|
121
|
+
# Returns: [(-1-1j), 0j, (1+1j), (2+2j)]
|
|
122
|
+
|
|
123
|
+
# Linear with custom step
|
|
124
|
+
complex_range([0, 4+4j], [2, 2])
|
|
125
|
+
# Returns: [0j, (2+2j), (4+4j)]
|
|
126
|
+
|
|
127
|
+
# Linear with complex step
|
|
128
|
+
complex_range([0, 4+4j], 2+2j)
|
|
129
|
+
# Returns: [0j, (2+2j), (4+4j)]
|
|
130
|
+
|
|
131
|
+
# Descending linear range with negative step
|
|
132
|
+
complex_range([2+2j, 0], -1-1j)
|
|
133
|
+
# Returns: [(2+2j), (1+1j), 0j]
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Options
|
|
137
|
+
|
|
138
|
+
#### `increment_first`
|
|
139
|
+
|
|
140
|
+
Control the iteration order for rectangular ranges:
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
# Default: increment imaginary first
|
|
144
|
+
complex_range(0, 2+2j, 1+1j, increment_first='im')
|
|
145
|
+
# [0j, 1j, 2j, (1+0j), (1+1j), (1+2j), (2+0j), (2+1j), (2+2j)]
|
|
146
|
+
|
|
147
|
+
# Increment real first
|
|
148
|
+
complex_range(0, 2+2j, 1+1j, increment_first='re')
|
|
149
|
+
# [0j, (1+0j), (2+0j), 1j, (1+1j), (2+1j), 2j, (1+2j), (2+2j)]
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
#### `farey_range`
|
|
153
|
+
|
|
154
|
+
Use Farey sequence to create a finer subdivision of the grid:
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
# Regular grid
|
|
158
|
+
regular = complex_range(0, 4+4j, 2+2j)
|
|
159
|
+
# Returns 9 points
|
|
160
|
+
|
|
161
|
+
# Farey subdivision (step must be integer)
|
|
162
|
+
farey = complex_range(0, 4+4j, 2+2j, farey_range=True)
|
|
163
|
+
# Returns 81 points using Farey sequence F_2
|
|
164
|
+
|
|
165
|
+
# Farey sequence of order n creates fractions 0, 1/n, ..., 1
|
|
166
|
+
from complex_range import farey_sequence
|
|
167
|
+
farey_sequence(3)
|
|
168
|
+
# [Fraction(0,1), Fraction(1,3), Fraction(1,2), Fraction(2,3), Fraction(1,1)]
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## API Reference
|
|
172
|
+
|
|
173
|
+
### `complex_range(z1, z2=None, step=None, *, increment_first='im', farey_range=False)`
|
|
174
|
+
|
|
175
|
+
Generate a range of complex numbers.
|
|
176
|
+
|
|
177
|
+
**Parameters:**
|
|
178
|
+
|
|
179
|
+
| Parameter | Type | Description |
|
|
180
|
+
|-----------|------|-------------|
|
|
181
|
+
| `z1` | `complex`, `list`, or `tuple` | First corner (rectangular) or endpoint specification (linear) |
|
|
182
|
+
| `z2` | `complex`, optional | Second corner for rectangular range |
|
|
183
|
+
| `step` | `complex`, `list`, or `tuple`, optional | Step size(s). Default: `1+1j` |
|
|
184
|
+
| `increment_first` | `'im'` or `'re'` | Which component to iterate first. Default: `'im'` |
|
|
185
|
+
| `farey_range` | `bool` | Use Farey sequence subdivision. Default: `False` |
|
|
186
|
+
|
|
187
|
+
**Returns:** `List[complex]` - List of complex numbers
|
|
188
|
+
|
|
189
|
+
**Raises:** `ComplexRangeError` - If `farey_range=True` with non-integer step
|
|
190
|
+
|
|
191
|
+
### `farey_sequence(n)`
|
|
192
|
+
|
|
193
|
+
Generate the Farey sequence of order n.
|
|
194
|
+
|
|
195
|
+
**Parameters:**
|
|
196
|
+
|
|
197
|
+
| Parameter | Type | Description |
|
|
198
|
+
|-----------|------|-------------|
|
|
199
|
+
| `n` | `int` | Order of the Farey sequence (must be positive) |
|
|
200
|
+
|
|
201
|
+
**Returns:** `List[Fraction]` - Sorted list of fractions in [0, 1]
|
|
202
|
+
|
|
203
|
+
## Understanding the Ranges
|
|
204
|
+
|
|
205
|
+
### Rectangular Range
|
|
206
|
+
|
|
207
|
+
A rectangular range generates all points on a 2D grid:
|
|
208
|
+
|
|
209
|
+
```
|
|
210
|
+
Im ↑
|
|
211
|
+
3 │ · · ·
|
|
212
|
+
2 │ · · ·
|
|
213
|
+
1 │ · · ·
|
|
214
|
+
0 │ · · ·
|
|
215
|
+
└──────────→ Re
|
|
216
|
+
0 1 2
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
With `increment_first='im'` (default), points are generated column by column:
|
|
220
|
+
`(0,0), (0,1), (0,2), (0,3), (1,0), (1,1), ...`
|
|
221
|
+
|
|
222
|
+
### Linear Range
|
|
223
|
+
|
|
224
|
+
A linear range generates points along a diagonal:
|
|
225
|
+
|
|
226
|
+
```
|
|
227
|
+
Im ↑
|
|
228
|
+
3 │ ·
|
|
229
|
+
2 │ ·
|
|
230
|
+
1 │ ·
|
|
231
|
+
0 │·
|
|
232
|
+
└──────────→ Re
|
|
233
|
+
0 1 2 3
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Points increment along both axes simultaneously.
|
|
237
|
+
|
|
238
|
+
## Examples
|
|
239
|
+
|
|
240
|
+
### Visualizing a Rectangular Grid
|
|
241
|
+
|
|
242
|
+
```python
|
|
243
|
+
import matplotlib.pyplot as plt
|
|
244
|
+
from complex_range import complex_range
|
|
245
|
+
|
|
246
|
+
points = complex_range(0, 3+3j)
|
|
247
|
+
x = [p.real for p in points]
|
|
248
|
+
y = [p.imag for p in points]
|
|
249
|
+
|
|
250
|
+
plt.scatter(x, y)
|
|
251
|
+
plt.xlabel('Real')
|
|
252
|
+
plt.ylabel('Imaginary')
|
|
253
|
+
plt.title('Rectangular Complex Range')
|
|
254
|
+
plt.grid(True)
|
|
255
|
+
plt.show()
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### Creating a Refined Grid with Farey Sequence
|
|
259
|
+
|
|
260
|
+
```python
|
|
261
|
+
from complex_range import complex_range
|
|
262
|
+
|
|
263
|
+
# Coarse grid: 3×3 = 9 points
|
|
264
|
+
coarse = complex_range(0, 2+2j, 1+1j)
|
|
265
|
+
|
|
266
|
+
# Refined grid using Farey sequence
|
|
267
|
+
refined = complex_range(0, 2+2j, 2+2j, farey_range=True)
|
|
268
|
+
print(f"Coarse: {len(coarse)} points, Refined: {len(refined)} points")
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### Iterating Over Complex Regions
|
|
272
|
+
|
|
273
|
+
```python
|
|
274
|
+
from complex_range import complex_range
|
|
275
|
+
|
|
276
|
+
# Evaluate a function over a region of the complex plane
|
|
277
|
+
def mandelbrot_escape(c, max_iter=100):
|
|
278
|
+
z = 0
|
|
279
|
+
for i in range(max_iter):
|
|
280
|
+
z = z*z + c
|
|
281
|
+
if abs(z) > 2:
|
|
282
|
+
return i
|
|
283
|
+
return max_iter
|
|
284
|
+
|
|
285
|
+
# Create a grid and evaluate
|
|
286
|
+
grid = complex_range(-2-1.5j, 1+1.5j, [0.1, 0.1])
|
|
287
|
+
escapes = [mandelbrot_escape(c) for c in grid]
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
## Author
|
|
291
|
+
|
|
292
|
+
**Daniele Gregori**
|
|
293
|
+
|
|
294
|
+
- Original [Wolfram Language ComplexRange](https://resources.wolframcloud.com/FunctionRepository/resources/ComplexRange/) function
|
|
295
|
+
- Python implementation
|
|
296
|
+
|
|
297
|
+
## Contributing
|
|
298
|
+
|
|
299
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
300
|
+
|
|
301
|
+
```bash
|
|
302
|
+
# Clone the repository
|
|
303
|
+
git clone https://github.com/Daniele-Gregori/PyPI-packages.git
|
|
304
|
+
cd PyPI-packages/packages/complex-range
|
|
305
|
+
|
|
306
|
+
# Install development dependencies
|
|
307
|
+
pip install -e ".[dev]"
|
|
308
|
+
|
|
309
|
+
# Run tests
|
|
310
|
+
pytest
|
|
311
|
+
|
|
312
|
+
# Run type checking
|
|
313
|
+
mypy src/complex_range
|
|
314
|
+
|
|
315
|
+
# Format code
|
|
316
|
+
black src tests
|
|
317
|
+
isort src tests
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
## License
|
|
321
|
+
|
|
322
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
323
|
+
|
|
324
|
+
## Acknowledgments
|
|
325
|
+
|
|
326
|
+
Original [Wolfram Language ComplexRange](https://resources.wolframcloud.com/FunctionRepository/resources/ComplexRange/) resource function contributed by Daniele Gregori and reviewed by the Wolfram Review Team.
|
|
327
|
+
|
|
328
|
+
## Changelog
|
|
329
|
+
|
|
330
|
+
See [CHANGELOG.md](CHANGELOG.md) for a list of changes.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
complex_range/__init__.py,sha256=hZC54Rv4f2Zvha7IDM-DngPqqwDYFugErkzGSLir7-Y,779
|
|
2
|
+
complex_range/core.py,sha256=pgq185F5KX3hjju7k4bGJoeMegarncoAHx2-Y20wd-Y,18196
|
|
3
|
+
complex_range/dev.py,sha256=I2uaDDWVMJoDfdRPZin-0XTKElRz3ianknT_7oOrPws,929
|
|
4
|
+
complex_range/farey.py,sha256=xDcZSJi1h2vMtghkP8TbaEc2dqHbGc4_C7UaD1H6qS0,2305
|
|
5
|
+
complex_range/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
complex_range-1.0.0.dist-info/licenses/LICENSE,sha256=S2_QCHbwpFs9uShfKzzbIYGKLwPhrlcUUVZnpieCdDU,1072
|
|
7
|
+
complex_range-1.0.0.dist-info/METADATA,sha256=zcL9a78XhglVg2qIR1emviUJr7h8ey1tJFxtM-Kh-b4,9311
|
|
8
|
+
complex_range-1.0.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
9
|
+
complex_range-1.0.0.dist-info/top_level.txt,sha256=AjyB3ZnFm0s9ReE4FvZzVKfwOJ62TI7NU_ieImIlutk,14
|
|
10
|
+
complex_range-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Daniele Gregori
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
complex_range
|