Skip to content

Commit 0546a6a

Browse files
Merge branch 'master' into fix-join
2 parents 4f795cb + 2d68bb5 commit 0546a6a

29 files changed

+1703
-254
lines changed

.github/workflows/build.yml

+6-9
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,18 @@ jobs:
1010
runs-on: ubuntu-latest
1111
steps:
1212
- uses: actions/checkout@v4
13+
- uses: astral-sh/setup-uv@v5
14+
with:
15+
enable-cache: true
16+
cache-dependency-glob: uv.lock
1317
- uses: actions/setup-python@v5
1418
with:
1519
python-version: 3.13
1620
allow-prereleases: true
17-
- uses: actions/cache@v4
18-
with:
19-
path: ~/.cache/pip
20-
key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }}
21-
- name: Install dependencies
22-
run: |
23-
python -m pip install --upgrade pip setuptools wheel
24-
python -m pip install pytest-cov -r requirements.txt
21+
- run: uv sync --group=test
2522
- name: Run tests
2623
# TODO: #8818 Re-enable quantum tests
27-
run: pytest
24+
run: uv run pytest
2825
--ignore=computer_vision/cnn_classification.py
2926
--ignore=docs/conf.py
3027
--ignore=dynamic_programming/k_means_clustering_tensorflow.py

.github/workflows/project_euler.yml

+6-10
Original file line numberDiff line numberDiff line change
@@ -15,25 +15,21 @@ jobs:
1515
runs-on: ubuntu-latest
1616
steps:
1717
- uses: actions/checkout@v4
18+
- uses: astral-sh/setup-uv@v5
1819
- uses: actions/setup-python@v5
1920
with:
2021
python-version: 3.x
21-
- name: Install pytest and pytest-cov
22-
run: |
23-
python -m pip install --upgrade pip
24-
python -m pip install --upgrade numpy pytest pytest-cov
25-
- run: pytest --doctest-modules --cov-report=term-missing:skip-covered --cov=project_euler/ project_euler/
22+
- run: uv sync --group=euler-validate --group=test
23+
- run: uv run pytest --doctest-modules --cov-report=term-missing:skip-covered --cov=project_euler/ project_euler/
2624
validate-solutions:
2725
runs-on: ubuntu-latest
2826
steps:
2927
- uses: actions/checkout@v4
28+
- uses: astral-sh/setup-uv@v5
3029
- uses: actions/setup-python@v5
3130
with:
3231
python-version: 3.x
33-
- name: Install pytest and requests
34-
run: |
35-
python -m pip install --upgrade pip
36-
python -m pip install --upgrade numpy pytest requests
37-
- run: pytest scripts/validate_solutions.py
32+
- run: uv sync --group=euler-validate --group=test
33+
- run: uv run pytest scripts/validate_solutions.py
3834
env:
3935
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

.github/workflows/ruff.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,5 @@ jobs:
1212
runs-on: ubuntu-latest
1313
steps:
1414
- uses: actions/checkout@v4
15-
- run: pip install --user ruff
16-
- run: ruff check --output-format=github .
15+
- uses: astral-sh/setup-uv@v5
16+
- run: uvx ruff check --output-format=github .

.github/workflows/sphinx.yml

+3-3
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,14 @@ jobs:
2626
runs-on: ubuntu-latest
2727
steps:
2828
- uses: actions/checkout@v4
29+
- uses: astral-sh/setup-uv@v5
2930
- uses: actions/setup-python@v5
3031
with:
3132
python-version: 3.13
3233
allow-prereleases: true
33-
- run: pip install --upgrade pip
34-
- run: pip install myst-parser sphinx-autoapi sphinx-pyproject
34+
- run: uv sync --group=docs
3535
- uses: actions/configure-pages@v5
36-
- run: sphinx-build -c docs . docs/_build/html
36+
- run: uv run sphinx-build -c docs . docs/_build/html
3737
- uses: actions/upload-pages-artifact@v3
3838
with:
3939
path: docs/_build/html

.pre-commit-config.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ repos:
1616
- id: auto-walrus
1717

1818
- repo: https://github.com/astral-sh/ruff-pre-commit
19-
rev: v0.8.1
19+
rev: v0.8.3
2020
hooks:
2121
- id: ruff
2222
- id: ruff-format

audio_filters/iir_filter.py

+19-13
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,17 @@ class IIRFilter:
1010
1111
Implementation details:
1212
Based on the 2nd-order function from
13-
https://en.wikipedia.org/wiki/Digital_biquad_filter,
13+
https://en.wikipedia.org/wiki/Digital_biquad_filter,
1414
this generalized N-order function was made.
1515
1616
Using the following transfer function
17-
H(z)=\frac{b_{0}+b_{1}z^{-1}+b_{2}z^{-2}+...+b_{k}z^{-k}}{a_{0}+a_{1}z^{-1}+a_{2}z^{-2}+...+a_{k}z^{-k}}
17+
.. math:: H(z)=\frac{b_{0}+b_{1}z^{-1}+b_{2}z^{-2}+...+b_{k}z^{-k}}
18+
{a_{0}+a_{1}z^{-1}+a_{2}z^{-2}+...+a_{k}z^{-k}}
19+
1820
we can rewrite this to
19-
y[n]={\frac{1}{a_{0}}}\left(\left(b_{0}x[n]+b_{1}x[n-1]+b_{2}x[n-2]+...+b_{k}x[n-k]\right)-\left(a_{1}y[n-1]+a_{2}y[n-2]+...+a_{k}y[n-k]\right)\right)
21+
.. math:: y[n]={\frac{1}{a_{0}}}
22+
\left(\left(b_{0}x[n]+b_{1}x[n-1]+b_{2}x[n-2]+...+b_{k}x[n-k]\right)-
23+
\left(a_{1}y[n-1]+a_{2}y[n-2]+...+a_{k}y[n-k]\right)\right)
2024
"""
2125

2226
def __init__(self, order: int) -> None:
@@ -34,17 +38,19 @@ def __init__(self, order: int) -> None:
3438

3539
def set_coefficients(self, a_coeffs: list[float], b_coeffs: list[float]) -> None:
3640
"""
37-
Set the coefficients for the IIR filter. These should both be of size order + 1.
38-
a_0 may be left out, and it will use 1.0 as default value.
41+
Set the coefficients for the IIR filter.
42+
These should both be of size `order` + 1.
43+
:math:`a_0` may be left out, and it will use 1.0 as default value.
3944
4045
This method works well with scipy's filter design functions
41-
>>> # Make a 2nd-order 1000Hz butterworth lowpass filter
42-
>>> import scipy.signal
43-
>>> b_coeffs, a_coeffs = scipy.signal.butter(2, 1000,
44-
... btype='lowpass',
45-
... fs=48000)
46-
>>> filt = IIRFilter(2)
47-
>>> filt.set_coefficients(a_coeffs, b_coeffs)
46+
47+
>>> # Make a 2nd-order 1000Hz butterworth lowpass filter
48+
>>> import scipy.signal
49+
>>> b_coeffs, a_coeffs = scipy.signal.butter(2, 1000,
50+
... btype='lowpass',
51+
... fs=48000)
52+
>>> filt = IIRFilter(2)
53+
>>> filt.set_coefficients(a_coeffs, b_coeffs)
4854
"""
4955
if len(a_coeffs) < self.order:
5056
a_coeffs = [1.0, *a_coeffs]
@@ -68,7 +74,7 @@ def set_coefficients(self, a_coeffs: list[float], b_coeffs: list[float]) -> None
6874

6975
def process(self, sample: float) -> float:
7076
"""
71-
Calculate y[n]
77+
Calculate :math:`y[n]`
7278
7379
>>> filt = IIRFilter(2)
7480
>>> filt.process(0)

cellular_automata/wa_tor.py

+26-28
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
"""
22
Wa-Tor algorithm (1984)
33
4-
@ https://en.wikipedia.org/wiki/Wa-Tor
5-
@ https://beltoforion.de/en/wator/
6-
@ https://beltoforion.de/en/wator/images/wator_medium.webm
4+
| @ https://en.wikipedia.org/wiki/Wa-Tor
5+
| @ https://beltoforion.de/en/wator/
6+
| @ https://beltoforion.de/en/wator/images/wator_medium.webm
77
88
This solution aims to completely remove any systematic approach
99
to the Wa-Tor planet, and utilise fully random methods.
@@ -97,8 +97,8 @@ class WaTor:
9797
9898
:attr time_passed: A function that is called every time
9999
time passes (a chronon) in order to visually display
100-
the new Wa-Tor planet. The time_passed function can block
101-
using time.sleep to slow the algorithm progression.
100+
the new Wa-Tor planet. The `time_passed` function can block
101+
using ``time.sleep`` to slow the algorithm progression.
102102
103103
>>> wt = WaTor(10, 15)
104104
>>> wt.width
@@ -216,7 +216,7 @@ def get_surrounding_prey(self, entity: Entity) -> list[Entity]:
216216
"""
217217
Returns all the prey entities around (N, S, E, W) a predator entity.
218218
219-
Subtly different to the try_to_move_to_unoccupied square.
219+
Subtly different to the `move_and_reproduce`.
220220
221221
>>> wt = WaTor(WIDTH, HEIGHT)
222222
>>> wt.set_planet([
@@ -260,7 +260,7 @@ def move_and_reproduce(
260260
"""
261261
Attempts to move to an unoccupied neighbouring square
262262
in either of the four directions (North, South, East, West).
263-
If the move was successful and the remaining_reproduction time is
263+
If the move was successful and the `remaining_reproduction_time` is
264264
equal to 0, then a new prey or predator can also be created
265265
in the previous square.
266266
@@ -351,12 +351,12 @@ def perform_prey_actions(
351351
Performs the actions for a prey entity
352352
353353
For prey the rules are:
354-
1. At each chronon, a prey moves randomly to one of the adjacent unoccupied
355-
squares. If there are no free squares, no movement takes place.
356-
2. Once a prey has survived a certain number of chronons it may reproduce.
357-
This is done as it moves to a neighbouring square,
358-
leaving behind a new prey in its old position.
359-
Its reproduction time is also reset to zero.
354+
1. At each chronon, a prey moves randomly to one of the adjacent unoccupied
355+
squares. If there are no free squares, no movement takes place.
356+
2. Once a prey has survived a certain number of chronons it may reproduce.
357+
This is done as it moves to a neighbouring square,
358+
leaving behind a new prey in its old position.
359+
Its reproduction time is also reset to zero.
360360
361361
>>> wt = WaTor(WIDTH, HEIGHT)
362362
>>> reproducable_entity = Entity(True, coords=(0, 1))
@@ -382,15 +382,15 @@ def perform_predator_actions(
382382
:param occupied_by_prey_coords: Move to this location if there is prey there
383383
384384
For predators the rules are:
385-
1. At each chronon, a predator moves randomly to an adjacent square occupied
386-
by a prey. If there is none, the predator moves to a random adjacent
387-
unoccupied square. If there are no free squares, no movement takes place.
388-
2. At each chronon, each predator is deprived of a unit of energy.
389-
3. Upon reaching zero energy, a predator dies.
390-
4. If a predator moves to a square occupied by a prey,
391-
it eats the prey and earns a certain amount of energy.
392-
5. Once a predator has survived a certain number of chronons
393-
it may reproduce in exactly the same way as the prey.
385+
1. At each chronon, a predator moves randomly to an adjacent square occupied
386+
by a prey. If there is none, the predator moves to a random adjacent
387+
unoccupied square. If there are no free squares, no movement takes place.
388+
2. At each chronon, each predator is deprived of a unit of energy.
389+
3. Upon reaching zero energy, a predator dies.
390+
4. If a predator moves to a square occupied by a prey,
391+
it eats the prey and earns a certain amount of energy.
392+
5. Once a predator has survived a certain number of chronons
393+
it may reproduce in exactly the same way as the prey.
394394
395395
>>> wt = WaTor(WIDTH, HEIGHT)
396396
>>> wt.set_planet([[Entity(True, coords=(0, 0)), Entity(False, coords=(0, 1))]])
@@ -430,7 +430,7 @@ def perform_predator_actions(
430430

431431
def run(self, *, iteration_count: int) -> None:
432432
"""
433-
Emulate time passing by looping iteration_count times
433+
Emulate time passing by looping `iteration_count` times
434434
435435
>>> wt = WaTor(WIDTH, HEIGHT)
436436
>>> wt.run(iteration_count=PREDATOR_INITIAL_ENERGY_VALUE - 1)
@@ -484,11 +484,9 @@ def visualise(wt: WaTor, iter_number: int, *, colour: bool = True) -> None:
484484
an ascii code in terminal to clear and re-print
485485
the Wa-Tor planet at intervals.
486486
487-
Uses ascii colour codes to colourfully display
488-
the predators and prey.
489-
490-
(0x60f197) Prey = #
491-
(0xfffff) Predator = x
487+
Uses ascii colour codes to colourfully display the predators and prey:
488+
* (0x60f197) Prey = ``#``
489+
* (0xfffff) Predator = ``x``
492490
493491
>>> wt = WaTor(30, 30)
494492
>>> wt.set_planet([

computer_vision/README.md

-1
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,3 @@ Image processing and computer vision are a little different from each other. Ima
88
While computer vision comes from modelling image processing using the techniques of machine learning, computer vision applies machine learning to recognize patterns for interpretation of images (much like the process of visual reasoning of human vision).
99

1010
* <https://en.wikipedia.org/wiki/Computer_vision>
11-
* <https://www.datarobot.com/blog/introduction-to-computer-vision-what-it-is-and-how-it-works/>

data_structures/binary_tree/mirror_binary_tree.py

+29-23
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ def mirror(self) -> Node:
5656
def make_tree_seven() -> Node:
5757
r"""
5858
Return a binary tree with 7 nodes that looks like this:
59+
::
60+
5961
1
6062
/ \
6163
2 3
@@ -81,13 +83,15 @@ def make_tree_seven() -> Node:
8183
def make_tree_nine() -> Node:
8284
r"""
8385
Return a binary tree with 9 nodes that looks like this:
84-
1
85-
/ \
86-
2 3
87-
/ \ \
88-
4 5 6
89-
/ \ \
90-
7 8 9
86+
::
87+
88+
1
89+
/ \
90+
2 3
91+
/ \ \
92+
4 5 6
93+
/ \ \
94+
7 8 9
9195
9296
>>> tree_nine = make_tree_nine()
9397
>>> len(tree_nine)
@@ -117,23 +121,25 @@ def main() -> None:
117121
>>> tuple(tree.mirror())
118122
(6, 3, 1, 9, 5, 2, 8, 4, 7)
119123
120-
nine_tree:
121-
1
122-
/ \
123-
2 3
124-
/ \ \
125-
4 5 6
126-
/ \ \
127-
7 8 9
128-
129-
The mirrored tree looks like this:
124+
nine_tree::
125+
126+
1
127+
/ \
128+
2 3
129+
/ \ \
130+
4 5 6
131+
/ \ \
132+
7 8 9
133+
134+
The mirrored tree looks like this::
135+
130136
1
131-
/ \
132-
3 2
133-
/ / \
134-
6 5 4
135-
/ / \
136-
9 8 7
137+
/ \
138+
3 2
139+
/ / \
140+
6 5 4
141+
/ / \
142+
9 8 7
137143
"""
138144
trees = {"zero": Node(0), "seven": make_tree_seven(), "nine": make_tree_nine()}
139145
for name, tree in trees.items():

electronics/electric_power.py

+6-4
Original file line numberDiff line numberDiff line change
@@ -23,20 +23,22 @@ def electric_power(voltage: float, current: float, power: float) -> tuple:
2323
>>> electric_power(voltage=2, current=4, power=2)
2424
Traceback (most recent call last):
2525
...
26-
ValueError: Only one argument must be 0
26+
ValueError: Exactly one argument must be 0
2727
>>> electric_power(voltage=0, current=0, power=2)
2828
Traceback (most recent call last):
2929
...
30-
ValueError: Only one argument must be 0
30+
ValueError: Exactly one argument must be 0
3131
>>> electric_power(voltage=0, current=2, power=-4)
3232
Traceback (most recent call last):
3333
...
3434
ValueError: Power cannot be negative in any electrical/electronics system
3535
>>> electric_power(voltage=2.2, current=2.2, power=0)
3636
Result(name='power', value=4.84)
37+
>>> electric_power(current=0, power=6, voltage=2)
38+
Result(name='current', value=3.0)
3739
"""
3840
if (voltage, current, power).count(0) != 1:
39-
raise ValueError("Only one argument must be 0")
41+
raise ValueError("Exactly one argument must be 0")
4042
elif power < 0:
4143
raise ValueError(
4244
"Power cannot be negative in any electrical/electronics system"
@@ -48,7 +50,7 @@ def electric_power(voltage: float, current: float, power: float) -> tuple:
4850
elif power == 0:
4951
return Result("power", float(round(abs(voltage * current), 2)))
5052
else:
51-
raise ValueError("Exactly one argument must be 0")
53+
raise AssertionError
5254

5355

5456
if __name__ == "__main__":

geodesy/haversine_distance.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,11 @@ def haversine_distance(lat1: float, lon1: float, lat2: float, lon2: float) -> fl
2121
computation like Haversine can be handy for shorter range distances.
2222
2323
Args:
24-
lat1, lon1: latitude and longitude of coordinate 1
25-
lat2, lon2: latitude and longitude of coordinate 2
24+
* `lat1`, `lon1`: latitude and longitude of coordinate 1
25+
* `lat2`, `lon2`: latitude and longitude of coordinate 2
2626
Returns:
2727
geographical distance between two points in metres
28+
2829
>>> from collections import namedtuple
2930
>>> point_2d = namedtuple("point_2d", "lat lon")
3031
>>> SAN_FRANCISCO = point_2d(37.774856, -122.424227)

0 commit comments

Comments
 (0)