quantum-random 1.4.0__tar.gz → 1.4.2__tar.gz

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.
Files changed (28) hide show
  1. quantum_random-1.4.2/.github/workflows/publish.yml +24 -0
  2. quantum_random-1.4.2/.github/workflows/tests.yml +51 -0
  3. quantum_random-1.4.2/.gitignore +3 -0
  4. quantum_random-1.4.2/.python-version +1 -0
  5. {quantum_random-1.4.0 → quantum_random-1.4.2}/PKG-INFO +14 -22
  6. {quantum_random-1.4.0 → quantum_random-1.4.2}/README.md +1 -2
  7. quantum_random-1.4.2/analysis/README.md +15 -0
  8. quantum_random-1.4.2/analysis/generate_plot.py +53 -0
  9. quantum_random-1.4.2/analysis/random.png +0 -0
  10. quantum_random-1.4.2/analysis/requirements.txt +3 -0
  11. quantum_random-1.4.2/analysis/uniform.md +21 -0
  12. quantum_random-1.4.2/pyproject.toml +79 -0
  13. quantum_random-1.4.2/tests/conftest.py +120 -0
  14. quantum_random-1.4.2/tests/data/fetch_data.py +56 -0
  15. quantum_random-1.4.2/tests/data/responses.json +1 -0
  16. quantum_random-1.4.2/tests/test_api.py +184 -0
  17. quantum_random-1.4.2/tests/test_cli.py +100 -0
  18. quantum_random-1.4.2/tests/test_generator.py +89 -0
  19. quantum_random-1.4.2/tests/test_numpy_support.py +11 -0
  20. quantum_random-1.4.2/tests/test_qrandom.py +5 -0
  21. quantum_random-1.4.2/uv.lock +612 -0
  22. quantum_random-1.4.0/pyproject.toml +0 -69
  23. {quantum_random-1.4.0 → quantum_random-1.4.2}/LICENSE +0 -0
  24. {quantum_random-1.4.0 → quantum_random-1.4.2}/src/qrandom/__init__.py +0 -0
  25. {quantum_random-1.4.0 → quantum_random-1.4.2}/src/qrandom/_api.py +0 -0
  26. {quantum_random-1.4.0 → quantum_random-1.4.2}/src/qrandom/_cli.py +0 -0
  27. {quantum_random-1.4.0 → quantum_random-1.4.2}/src/qrandom/_generator.py +0 -0
  28. {quantum_random-1.4.0 → quantum_random-1.4.2}/src/qrandom/numpy.py +0 -0
@@ -0,0 +1,24 @@
1
+ name: Publish
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - '*'
7
+
8
+ jobs:
9
+ release:
10
+ permissions:
11
+ id-token: write
12
+ name: Release
13
+ runs-on: ubuntu-latest
14
+ steps:
15
+ - name: Checkout repo
16
+ uses: actions/checkout@v4
17
+ - name: Install uv
18
+ uses: astral-sh/setup-uv@v3
19
+ with:
20
+ version: "0.5.4"
21
+ - name: Build
22
+ run: uv build
23
+ - name: Publish
24
+ run: uv publish
@@ -0,0 +1,51 @@
1
+ name: Tests
2
+
3
+ on: push
4
+
5
+ jobs:
6
+ test:
7
+ name: Tests and type checks
8
+ runs-on: ${{ matrix.os }}
9
+ strategy:
10
+ matrix:
11
+ os: [ubuntu-latest, macos-latest, windows-latest]
12
+ python-version: ['3.9', '3.10', '3.11', '3.12', '3.13']
13
+ defaults:
14
+ run:
15
+ shell: bash
16
+ steps:
17
+ - name: Checkout repo
18
+ uses: actions/checkout@v4
19
+ - name: Install uv
20
+ uses: astral-sh/setup-uv@v3
21
+ with:
22
+ version: "0.5.4"
23
+ - name: Set up Python ${{ matrix.python-version }}
24
+ run: uv python install ${{ matrix.python-version }}
25
+ - name: Install the project
26
+ run: uv sync --all-extras --dev
27
+ - name: Run pytest
28
+ run: QRANDOM_API_KEY=key uv run pytest
29
+ - name: Run mypy
30
+ run: uv run mypy --install-types --non-interactive .
31
+
32
+ lint:
33
+ name: Lint
34
+ runs-on: ubuntu-latest
35
+ steps:
36
+ - name: Checkout repo
37
+ uses: actions/checkout@v4
38
+ - name: Install uv
39
+ uses: astral-sh/setup-uv@v3
40
+ with:
41
+ version: "0.5.4"
42
+ - name: Set up Python
43
+ run: uv python install
44
+ - name: Install the project
45
+ run: uv sync --all-extras --dev
46
+ - name: Run black
47
+ run: uv run black --check .
48
+ - name: Run isort
49
+ run: uv run isort --check .
50
+ - name: Run flake8
51
+ run: uv run flake8 --extend-exclude .venv
@@ -0,0 +1,3 @@
1
+ *.pyc
2
+ dist/
3
+ *.egg-info/
@@ -0,0 +1 @@
1
+ 3.12
@@ -1,15 +1,13 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: quantum-random
3
- Version: 1.4.0
3
+ Version: 1.4.2
4
4
  Summary: Quantum random numbers
5
- Home-page: https://github.com/sbalian/quantum-random/
6
- License: MIT
5
+ Project-URL: Homepage, https://github.com/sbalian/quantum-random
6
+ Project-URL: Repository, https://github.com/sbalian/quantum-random
7
+ Author-email: Seto Balian <seto.balian@gmail.com>
8
+ Maintainer-email: Seto Balian <seto.balian@gmail.com>
9
+ License: MIT License
7
10
  Keywords: quantum,random,statistics
8
- Author: Seto Balian
9
- Author-email: seto.balian@gmail.com
10
- Maintainer: Seto Balian
11
- Maintainer-email: seto.balian@gmail.com
12
- Requires-Python: >=3.9,<4.0
13
11
  Classifier: Intended Audience :: Developers
14
12
  Classifier: Intended Audience :: Science/Research
15
13
  Classifier: License :: OSI Approved :: MIT License
@@ -18,20 +16,16 @@ Classifier: Operating System :: Microsoft :: Windows
18
16
  Classifier: Operating System :: Unix
19
17
  Classifier: Programming Language :: Python
20
18
  Classifier: Programming Language :: Python :: 3
21
- Classifier: Programming Language :: Python :: 3.9
22
- Classifier: Programming Language :: Python :: 3.10
23
- Classifier: Programming Language :: Python :: 3.11
24
- Classifier: Programming Language :: Python :: 3.12
25
19
  Classifier: Topic :: Scientific/Engineering
26
20
  Classifier: Topic :: Software Development
27
21
  Classifier: Typing :: Typed
22
+ Requires-Python: >=3.9
23
+ Requires-Dist: click>=8.1.7
24
+ Requires-Dist: requests>=2.32.3
25
+ Requires-Dist: xdg>=6.0.0
28
26
  Provides-Extra: numpy
29
- Requires-Dist: click (>=8.1.7,<9.0.0)
30
- Requires-Dist: numpy (>=1.26.4,<2.0.0) ; extra == "numpy"
31
- Requires-Dist: randomgen (>=1.26.1,<2.0.0) ; extra == "numpy"
32
- Requires-Dist: requests (>=2.31.0,<3.0.0)
33
- Requires-Dist: xdg (>=6.0.0,<7.0.0)
34
- Project-URL: Repository, https://github.com/sbalian/quantum-random/
27
+ Requires-Dist: numpy>=2.0.2; extra == 'numpy'
28
+ Requires-Dist: randomgen>=2.1.1; extra == 'numpy'
35
29
  Description-Content-Type: text/markdown
36
30
 
37
31
  # Quantum random numbers in Python
@@ -125,7 +119,7 @@ if you need to pre-fetch at the start of your computation.
125
119
  ## Tests
126
120
 
127
121
  The tests run for Python 3.9 - 3.12 on the latest Windows,
128
- macOS and Ubuntu runner images. Use [tox][tox] to run the tests locally.
122
+ macOS and Ubuntu runner images.
129
123
 
130
124
  See [here](./analysis/uniform.md) for a visualisation and a Kolmogorov–Smirnov
131
125
  test.
@@ -150,7 +144,5 @@ See [LICENCE](./LICENSE).
150
144
  [anu]: https://quantumnumbers.anu.edu.au
151
145
  [anupricing]: https://quantumnumbers.anu.edu.au/pricing
152
146
  [pyrandom]: https://docs.python.org/3/library/random.html
153
- [tox]: https://tox.wiki/en/latest/
154
147
  [numpy]: https://numpy.org
155
148
  [randomgen]: https://github.com/bashtage/randomgen
156
-
@@ -89,7 +89,7 @@ if you need to pre-fetch at the start of your computation.
89
89
  ## Tests
90
90
 
91
91
  The tests run for Python 3.9 - 3.12 on the latest Windows,
92
- macOS and Ubuntu runner images. Use [tox][tox] to run the tests locally.
92
+ macOS and Ubuntu runner images.
93
93
 
94
94
  See [here](./analysis/uniform.md) for a visualisation and a Kolmogorov–Smirnov
95
95
  test.
@@ -114,6 +114,5 @@ See [LICENCE](./LICENSE).
114
114
  [anu]: https://quantumnumbers.anu.edu.au
115
115
  [anupricing]: https://quantumnumbers.anu.edu.au/pricing
116
116
  [pyrandom]: https://docs.python.org/3/library/random.html
117
- [tox]: https://tox.wiki/en/latest/
118
117
  [numpy]: https://numpy.org
119
118
  [randomgen]: https://github.com/bashtage/randomgen
@@ -0,0 +1,15 @@
1
+ # Analysis
2
+
3
+ To run the analysis, first install the requirements (includes `quantum-random`):
4
+
5
+ ```bash
6
+ pip install -r requirements.txt
7
+ ```
8
+
9
+ Then run the script to generate the plot in `uniform.md`:
10
+
11
+ ```bash
12
+ python generate_plot.py
13
+ ```
14
+
15
+ The output is written to `random.png`.
@@ -0,0 +1,53 @@
1
+ import random
2
+ from typing import List, Tuple
3
+
4
+ import matplotlib.pyplot as plt
5
+ from scipy import stats
6
+
7
+ import qrandom
8
+
9
+ DataType = Tuple[Tuple[List[float], str], Tuple[List[float], str]]
10
+
11
+
12
+ def generate_data() -> DataType:
13
+ numbers = [qrandom.random() for _ in range(10000)]
14
+ py_numbers = [random.random() for _ in range(10000)]
15
+ return (numbers, "Quantum random"), (py_numbers, "Python pseudo-random")
16
+
17
+
18
+ def plot(data: DataType) -> None:
19
+ quantum, python = data
20
+ numbers, quantum_title = quantum
21
+ py_numbers, python_title = python
22
+
23
+ plt.figure(figsize=(8, 3))
24
+ plt.subplot(1, 2, 1)
25
+ plt.hist(numbers, edgecolor="black")
26
+ plt.title(quantum_title)
27
+ plt.subplot(1, 2, 2)
28
+ plt.hist(py_numbers, edgecolor="black")
29
+ plt.title(python_title)
30
+ plt.tight_layout()
31
+ plt.savefig("random.png", dpi=120, bbox_inches="tight")
32
+ return
33
+
34
+
35
+ def kstest(data: DataType) -> None:
36
+ quantum, python = data
37
+ numbers, quantum_title = quantum
38
+ py_numbers, python_title = python
39
+
40
+ print(quantum_title, stats.kstest(numbers, "uniform"))
41
+ print(python_title, stats.kstest(py_numbers, "uniform"))
42
+ return
43
+
44
+
45
+ def main() -> None:
46
+ data = generate_data()
47
+ plot(data)
48
+ kstest(data)
49
+ return
50
+
51
+
52
+ if __name__ == "__main__":
53
+ main()
Binary file
@@ -0,0 +1,3 @@
1
+ scipy
2
+ matplotlib
3
+ quantum-random
@@ -0,0 +1,21 @@
1
+ # Visualise and test if the quantum randoms are uniformly distributed in [0.0, 1)
2
+
3
+ Calling `qrandom.random()` 10,000 times, and comparing it to `random.random()`:
4
+
5
+ ![Random](./random.png)
6
+
7
+ The [Kolmogorov–Smirnov statistics][kstest] with the reference distribution
8
+ `scipy.uniform` gives the following results.
9
+
10
+ | Trial | Quantum statistic | Quantum p-value | Std. statistic | Std. p-value |
11
+ | ----- | ----------------- | --------------- | ---------------| -------------|
12
+ | 1 | 0.01 | 0.2 | 0.005 | 0.9 |
13
+ | 2 | 0.007 | 0.7 | 0.005 | 1.0 |
14
+ | 3 | 0.006 | 0.9 | 0.008 | 0.6 |
15
+ | 4 | 0.01 | 0.3 | 0.008 | 0.5 |
16
+
17
+ Each trial is a different run of [`scipy.stats.kstest`][scipy-kstest] with
18
+ 10,000 numbers. The plot shows the numbers for the last trial.
19
+
20
+ [kstest]: https://en.wikipedia.org/wiki/Kolmogorov%E2%80%93Smirnov_test
21
+ [scipy-kstest]: https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.kstest.html
@@ -0,0 +1,79 @@
1
+ [project]
2
+ name = "quantum-random"
3
+ version = "1.4.2"
4
+ description = "Quantum random numbers"
5
+ readme = "README.md"
6
+ license = {text = "MIT License"}
7
+ authors = [
8
+ { name = "Seto Balian", email = "seto.balian@gmail.com" }
9
+ ]
10
+ maintainers = [
11
+ { name = "Seto Balian", email = "seto.balian@gmail.com" }
12
+ ]
13
+ keywords = ["quantum", "random", "statistics"]
14
+ classifiers = [
15
+ "Intended Audience :: Science/Research",
16
+ "Intended Audience :: Developers",
17
+ "Programming Language :: Python",
18
+ "Programming Language :: Python :: 3",
19
+ "Topic :: Software Development",
20
+ "Topic :: Scientific/Engineering",
21
+ "Typing :: Typed",
22
+ "Operating System :: Microsoft :: Windows",
23
+ "Operating System :: Unix",
24
+ "Operating System :: MacOS",
25
+ "License :: OSI Approved :: MIT License",
26
+ ]
27
+ requires-python = ">=3.9"
28
+ dependencies = [
29
+ "click>=8.1.7",
30
+ "requests>=2.32.3",
31
+ "xdg>=6.0.0",
32
+ ]
33
+
34
+ [project.optional-dependencies]
35
+ numpy = [
36
+ "numpy>=2.0.2",
37
+ "randomgen>=2.1.1",
38
+ ]
39
+
40
+ [dependency-groups]
41
+ dev = [
42
+ "pytest-mock>=3.14.0",
43
+ "pytest>=8.3.3",
44
+ "responses>=0.25.3",
45
+ "mypy>=1.13.0",
46
+ "flake8>=7.1.1",
47
+ "isort>=5.13.2",
48
+ "black>=24.10.0",
49
+ "pip>=24.3.1",
50
+ ]
51
+
52
+ [project.scripts]
53
+ qrandom-init = "qrandom._cli:main"
54
+
55
+ [project.urls]
56
+ Homepage = "https://github.com/sbalian/quantum-random"
57
+ Repository = "https://github.com/sbalian/quantum-random"
58
+
59
+ [build-system]
60
+ requires = ["hatchling"]
61
+ build-backend = "hatchling.build"
62
+
63
+ [tool.hatch.build.targets.wheel]
64
+ packages = ["src/qrandom"]
65
+
66
+ [tool.pytest.ini_options]
67
+ addopts = "--verbose"
68
+ testpaths = "tests"
69
+
70
+ [tool.mypy]
71
+ ignore_missing_imports = true
72
+
73
+ [tool.black]
74
+ line-length = 79
75
+
76
+ [tool.isort]
77
+ profile = "black"
78
+ line_length = 79
79
+ known_first_party = ["qrandom"]
@@ -0,0 +1,120 @@
1
+ import json
2
+
3
+ import pytest
4
+ import responses
5
+
6
+ from qrandom import _api, _generator
7
+
8
+
9
+ @pytest.fixture
10
+ def mocked_responses():
11
+ with responses.RequestsMock() as rsps:
12
+ yield rsps
13
+
14
+
15
+ @pytest.fixture
16
+ def test_responses():
17
+ with open("tests/data/responses.json") as f:
18
+ return json.load(f)
19
+
20
+
21
+ @pytest.fixture
22
+ def anu_url():
23
+ return "https://api.quantumnumbers.anu.edu.au"
24
+
25
+
26
+ @pytest.fixture
27
+ def api_client_with_successful_api_call(
28
+ mocked_responses, anu_url, test_responses
29
+ ):
30
+ mocked_responses.get(
31
+ anu_url,
32
+ json={"data": test_responses[0]["data"], "success": True},
33
+ status=200,
34
+ )
35
+ return _api.Client("key")
36
+
37
+
38
+ @pytest.fixture
39
+ def api_client_with_failed_api_call(mocked_responses, anu_url):
40
+ mocked_responses.get(
41
+ anu_url,
42
+ json={"success": False},
43
+ status=400,
44
+ )
45
+ return _api.Client("key")
46
+
47
+
48
+ @pytest.fixture
49
+ def api_client_with_failed_api_call_and_no_json_response(
50
+ mocked_responses, anu_url
51
+ ):
52
+ mocked_responses.get(
53
+ anu_url,
54
+ status=400,
55
+ )
56
+ return _api.Client("key")
57
+
58
+
59
+ @pytest.fixture
60
+ def api_client_with_failed_api_call_with_200_status(mocked_responses, anu_url):
61
+ mocked_responses.get(
62
+ anu_url,
63
+ json={"success": False},
64
+ status=200,
65
+ )
66
+ return _api.Client("key")
67
+
68
+
69
+ @pytest.fixture
70
+ def api_client_with_mocked_fetch_hex_raw(mocker, test_responses):
71
+ mocker.patch(
72
+ "qrandom._api.Client.fetch_hex_raw",
73
+ return_value=test_responses[0],
74
+ )
75
+ return _api.Client("key")
76
+
77
+
78
+ @pytest.fixture
79
+ def quantum_random_with_no_api_calls():
80
+ return _generator.QuantumRandom()
81
+
82
+
83
+ @pytest.fixture
84
+ def quantum_random_with_mocked_fetch_hex_raw(mocker, test_responses):
85
+ mocker.patch(
86
+ "qrandom._api.Client.fetch_hex_raw",
87
+ return_value=test_responses[0],
88
+ )
89
+ return _generator.QuantumRandom()
90
+
91
+
92
+ @pytest.fixture
93
+ def quantum_random_with_mocked_fetch_hex_raw_twice(mocker, test_responses):
94
+ mocker.patch(
95
+ "qrandom._api.Client.fetch_hex_raw",
96
+ side_effect=[test_responses[0], test_responses[1]],
97
+ )
98
+ return _generator.QuantumRandom()
99
+
100
+
101
+ @pytest.fixture
102
+ def quantum_random_with_mocked_fetch_hex_raw_five_times(
103
+ mocker, test_responses
104
+ ):
105
+ mocker.patch(
106
+ "qrandom._api.Client.fetch_hex_raw",
107
+ side_effect=[test_responses[i] for i in range(5)],
108
+ )
109
+ return _generator.QuantumRandom()
110
+
111
+
112
+ @pytest.fixture
113
+ def quantum_random_with_mocked_fetch_hex_raw_for_all_data(
114
+ mocker, test_responses
115
+ ):
116
+ mocker.patch(
117
+ "qrandom._api.Client.fetch_hex_raw",
118
+ side_effect=test_responses,
119
+ )
120
+ return _generator.QuantumRandom()
@@ -0,0 +1,56 @@
1
+ import json
2
+ import pathlib
3
+
4
+ import click
5
+
6
+ from qrandom import _api
7
+
8
+
9
+ @click.command()
10
+ @click.option(
11
+ "--num-hits",
12
+ "-n",
13
+ type=int,
14
+ default=10,
15
+ show_default=True,
16
+ help="Number of API calls.",
17
+ )
18
+ @click.option(
19
+ "--output-directory",
20
+ "-o",
21
+ type=click.Path(
22
+ exists=True,
23
+ file_okay=False,
24
+ resolve_path=True,
25
+ path_type=pathlib.Path,
26
+ ),
27
+ default=pathlib.Path("."),
28
+ help="Output directory.",
29
+ show_default=True,
30
+ )
31
+ def main(num_hits, output_directory) -> None:
32
+ """Write a JSON file of responses from the ANU API for testing purposes."""
33
+
34
+ output_path = output_directory / "responses.json"
35
+ if output_path.exists():
36
+ click.confirm(
37
+ f"Would you like to overwrite {output_path}?",
38
+ abort=True,
39
+ )
40
+
41
+ key = _api.find_api_key()
42
+ client = _api.Client(key)
43
+
44
+ click.echo("Fetching data ...")
45
+ with click.progressbar(range(num_hits)) as bar:
46
+ responses = [client.fetch_hex_raw() for _ in bar]
47
+ click.echo("Writing responses ...")
48
+ with open(pathlib.Path(output_path), "w") as f:
49
+ json.dump(responses, f)
50
+ click.echo(f"Wrote responses to {output_path} .")
51
+
52
+ return
53
+
54
+
55
+ if __name__ == "__main__":
56
+ main()