quantum-pipeline 1.0.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.
- quantum_pipeline-1.0.2/LICENSE +21 -0
- quantum_pipeline-1.0.2/PKG-INFO +61 -0
- quantum_pipeline-1.0.2/pyproject.toml +161 -0
- quantum_pipeline-1.0.2/quantum_pipeline/__init__.py +1 -0
- quantum_pipeline-1.0.2/quantum_pipeline/configs/defaults.py +26 -0
- quantum_pipeline-1.0.2/quantum_pipeline/configs/parsing/argparser.py +258 -0
- quantum_pipeline-1.0.2/quantum_pipeline/configs/parsing/backend_config.py +38 -0
- quantum_pipeline-1.0.2/quantum_pipeline/configs/parsing/configuration_manager.py +124 -0
- quantum_pipeline-1.0.2/quantum_pipeline/configs/parsing/producer_config.py +33 -0
- quantum_pipeline-1.0.2/quantum_pipeline/configs/settings.py +50 -0
- quantum_pipeline-1.0.2/quantum_pipeline/drivers/__init__.py +0 -0
- quantum_pipeline-1.0.2/quantum_pipeline/drivers/basis_sets.py +23 -0
- quantum_pipeline-1.0.2/quantum_pipeline/drivers/molecule_loader.py +53 -0
- quantum_pipeline-1.0.2/quantum_pipeline/features/circuit.py +26 -0
- quantum_pipeline-1.0.2/quantum_pipeline/mappers/__init__.py +0 -0
- quantum_pipeline-1.0.2/quantum_pipeline/mappers/jordan_winger_mapper.py +37 -0
- quantum_pipeline-1.0.2/quantum_pipeline/mappers/mapper.py +5 -0
- quantum_pipeline-1.0.2/quantum_pipeline/report/configuration.py +45 -0
- quantum_pipeline-1.0.2/quantum_pipeline/report/content_builder.py +170 -0
- quantum_pipeline-1.0.2/quantum_pipeline/report/renderer.py +136 -0
- quantum_pipeline-1.0.2/quantum_pipeline/report/report_generator.py +91 -0
- quantum_pipeline-1.0.2/quantum_pipeline/runners/runner.py +6 -0
- quantum_pipeline-1.0.2/quantum_pipeline/runners/vqe_runner.py +265 -0
- quantum_pipeline-1.0.2/quantum_pipeline/solvers/__init__.py +0 -0
- quantum_pipeline-1.0.2/quantum_pipeline/solvers/solver.py +102 -0
- quantum_pipeline-1.0.2/quantum_pipeline/solvers/vqe_solver.py +221 -0
- quantum_pipeline-1.0.2/quantum_pipeline/stream/kafka_interface.py +115 -0
- quantum_pipeline-1.0.2/quantum_pipeline/stream/serialization/interfaces/vqe.py +293 -0
- quantum_pipeline-1.0.2/quantum_pipeline/stream/serialization/schemas/vqe_initial.avsc +37 -0
- quantum_pipeline-1.0.2/quantum_pipeline/stream/serialization/schemas/vqe_molecule.avsc +32 -0
- quantum_pipeline-1.0.2/quantum_pipeline/stream/serialization/schemas/vqe_process.avsc +10 -0
- quantum_pipeline-1.0.2/quantum_pipeline/structures/vqe_observation.py +54 -0
- quantum_pipeline-1.0.2/quantum_pipeline/utils/__init__.py +0 -0
- quantum_pipeline-1.0.2/quantum_pipeline/utils/dir.py +44 -0
- quantum_pipeline-1.0.2/quantum_pipeline/utils/logger.py +17 -0
- quantum_pipeline-1.0.2/quantum_pipeline/utils/schema_registry.py +54 -0
- quantum_pipeline-1.0.2/quantum_pipeline/utils/visualizer.py +12 -0
- quantum_pipeline-1.0.2/quantum_pipeline/visual/__init__.py +0 -0
- quantum_pipeline-1.0.2/quantum_pipeline/visual/ansatz.py +59 -0
- quantum_pipeline-1.0.2/quantum_pipeline/visual/energy.py +94 -0
- quantum_pipeline-1.0.2/quantum_pipeline/visual/molecule.py +200 -0
- quantum_pipeline-1.0.2/quantum_pipeline/visual/operator.py +219 -0
- quantum_pipeline-1.0.2/tests/configs/test_argparser.py +154 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Piotr Krzysztof Lis
|
|
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,61 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: quantum-pipeline
|
|
3
|
+
Version: 1.0.2
|
|
4
|
+
Summary: Framework for running, monitoring and analysing quantum algorithms.
|
|
5
|
+
Author-Email: Piotr Krzysztof <piotrlis555@gmail.com>
|
|
6
|
+
Maintainer-Email: Piotr Krzysztof <piotrlis555@gmail.com>
|
|
7
|
+
License: MIT
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Intended Audience :: Science/Research
|
|
10
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Project-URL: Homepage, https://github.com/straightchlorine/quantum-pipeline
|
|
17
|
+
Requires-Python: <3.13,>=3.10
|
|
18
|
+
Requires-Dist: qiskit>=1.0.0
|
|
19
|
+
Requires-Dist: qiskit-ibm-runtime>=0.20.0
|
|
20
|
+
Requires-Dist: qiskit-aer>=0.15.1
|
|
21
|
+
Requires-Dist: qiskit-nature>=0.7.0
|
|
22
|
+
Requires-Dist: qiskit-qasm3-import>=0.5.1
|
|
23
|
+
Requires-Dist: openqasm3>=1.0.0
|
|
24
|
+
Requires-Dist: rustworkx>=0.13.0
|
|
25
|
+
Requires-Dist: pyscf>=2.7.0
|
|
26
|
+
Requires-Dist: numpy>=1.22.0
|
|
27
|
+
Requires-Dist: scipy>=1.10.0
|
|
28
|
+
Requires-Dist: pandas>=2.0.0
|
|
29
|
+
Requires-Dist: matplotlib>=3.7.0
|
|
30
|
+
Requires-Dist: reportlab>=4.2.5
|
|
31
|
+
Requires-Dist: h5py>=3.8.0
|
|
32
|
+
Requires-Dist: sympy>=1.11.0
|
|
33
|
+
Requires-Dist: kafka-python-ng>=2.2.3
|
|
34
|
+
Requires-Dist: avro>=1.12.0
|
|
35
|
+
Requires-Dist: pydantic>=2.0.0
|
|
36
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
37
|
+
Provides-Extra: dev
|
|
38
|
+
Requires-Dist: debugpy; extra == "dev"
|
|
39
|
+
Requires-Dist: ipython; extra == "dev"
|
|
40
|
+
Requires-Dist: ipdb; extra == "dev"
|
|
41
|
+
Requires-Dist: pytest>=7.3.0; extra == "dev"
|
|
42
|
+
Requires-Dist: pytest-cov; extra == "dev"
|
|
43
|
+
Requires-Dist: pytest-mock; extra == "dev"
|
|
44
|
+
Requires-Dist: hypothesis; extra == "dev"
|
|
45
|
+
Requires-Dist: mypy; extra == "dev"
|
|
46
|
+
Requires-Dist: types-all; extra == "dev"
|
|
47
|
+
Requires-Dist: ruff; extra == "dev"
|
|
48
|
+
Requires-Dist: black; extra == "dev"
|
|
49
|
+
Requires-Dist: isort; extra == "dev"
|
|
50
|
+
Requires-Dist: py-spy; extra == "dev"
|
|
51
|
+
Requires-Dist: memory-profiler; extra == "dev"
|
|
52
|
+
Requires-Dist: line-profiler; extra == "dev"
|
|
53
|
+
Requires-Dist: jupyter; extra == "dev"
|
|
54
|
+
Requires-Dist: notebook; extra == "dev"
|
|
55
|
+
Requires-Dist: jupyterlab; extra == "dev"
|
|
56
|
+
Requires-Dist: ipykernel; extra == "dev"
|
|
57
|
+
Requires-Dist: sphinx; extra == "dev"
|
|
58
|
+
Requires-Dist: pdoc3; extra == "dev"
|
|
59
|
+
Requires-Dist: bandit; extra == "dev"
|
|
60
|
+
Requires-Dist: safety; extra == "dev"
|
|
61
|
+
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = [
|
|
3
|
+
"pdm-backend",
|
|
4
|
+
]
|
|
5
|
+
build-backend = "pdm.backend"
|
|
6
|
+
|
|
7
|
+
[project]
|
|
8
|
+
name = "quantum-pipeline"
|
|
9
|
+
description = "Framework for running, monitoring and analysing quantum algorithms."
|
|
10
|
+
authors = [
|
|
11
|
+
{ name = "Piotr Krzysztof", email = "piotrlis555@gmail.com" },
|
|
12
|
+
]
|
|
13
|
+
maintainers = [
|
|
14
|
+
{ name = "Piotr Krzysztof", email = "piotrlis555@gmail.com" },
|
|
15
|
+
]
|
|
16
|
+
version = "1.0.2"
|
|
17
|
+
requires-python = ">=3.10,<3.13"
|
|
18
|
+
classifiers = [
|
|
19
|
+
"Development Status :: 3 - Alpha",
|
|
20
|
+
"Intended Audience :: Science/Research",
|
|
21
|
+
"Topic :: Scientific/Engineering :: Physics",
|
|
22
|
+
"License :: OSI Approved :: MIT License",
|
|
23
|
+
"Programming Language :: Python :: 3.10",
|
|
24
|
+
"Programming Language :: Python :: 3.11",
|
|
25
|
+
"Programming Language :: Python :: 3.12",
|
|
26
|
+
"Operating System :: OS Independent",
|
|
27
|
+
]
|
|
28
|
+
dependencies = [
|
|
29
|
+
"qiskit>=1.0.0",
|
|
30
|
+
"qiskit-ibm-runtime>=0.20.0",
|
|
31
|
+
"qiskit-aer>=0.15.1",
|
|
32
|
+
"qiskit-nature>=0.7.0",
|
|
33
|
+
"qiskit-qasm3-import>=0.5.1",
|
|
34
|
+
"openqasm3>=1.0.0",
|
|
35
|
+
"rustworkx>=0.13.0",
|
|
36
|
+
"pyscf>=2.7.0",
|
|
37
|
+
"numpy>=1.22.0",
|
|
38
|
+
"scipy>=1.10.0",
|
|
39
|
+
"pandas>=2.0.0",
|
|
40
|
+
"matplotlib>=3.7.0",
|
|
41
|
+
"reportlab>=4.2.5",
|
|
42
|
+
"h5py>=3.8.0",
|
|
43
|
+
"sympy>=1.11.0",
|
|
44
|
+
"kafka-python-ng>=2.2.3",
|
|
45
|
+
"avro>=1.12.0",
|
|
46
|
+
"pydantic>=2.0.0",
|
|
47
|
+
"python-dotenv>=1.0.0",
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
[project.license]
|
|
51
|
+
text = "MIT"
|
|
52
|
+
|
|
53
|
+
[project.optional-dependencies]
|
|
54
|
+
dev = [
|
|
55
|
+
"debugpy",
|
|
56
|
+
"ipython",
|
|
57
|
+
"ipdb",
|
|
58
|
+
"pytest>=7.3.0",
|
|
59
|
+
"pytest-cov",
|
|
60
|
+
"pytest-mock",
|
|
61
|
+
"hypothesis",
|
|
62
|
+
"mypy",
|
|
63
|
+
"types-all",
|
|
64
|
+
"ruff",
|
|
65
|
+
"black",
|
|
66
|
+
"isort",
|
|
67
|
+
"py-spy",
|
|
68
|
+
"memory-profiler",
|
|
69
|
+
"line-profiler",
|
|
70
|
+
"jupyter",
|
|
71
|
+
"notebook",
|
|
72
|
+
"jupyterlab",
|
|
73
|
+
"ipykernel",
|
|
74
|
+
"sphinx",
|
|
75
|
+
"pdoc3",
|
|
76
|
+
"bandit",
|
|
77
|
+
"safety",
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
[project.urls]
|
|
81
|
+
Homepage = "https://github.com/straightchlorine/quantum-pipeline"
|
|
82
|
+
|
|
83
|
+
[project.scripts]
|
|
84
|
+
quantum-pipeline = "quantum_pipeline.cli:main"
|
|
85
|
+
|
|
86
|
+
[tool.ruff]
|
|
87
|
+
line-length = 99
|
|
88
|
+
target-version = "py310"
|
|
89
|
+
|
|
90
|
+
[tool.ruff.lint]
|
|
91
|
+
select = [
|
|
92
|
+
"E",
|
|
93
|
+
"F",
|
|
94
|
+
"W",
|
|
95
|
+
"I",
|
|
96
|
+
"N",
|
|
97
|
+
"UP",
|
|
98
|
+
"ASYNC",
|
|
99
|
+
"S",
|
|
100
|
+
"C4",
|
|
101
|
+
]
|
|
102
|
+
ignore = [
|
|
103
|
+
"E501",
|
|
104
|
+
]
|
|
105
|
+
|
|
106
|
+
[tool.ruff.format]
|
|
107
|
+
quote-style = "single"
|
|
108
|
+
indent-style = "space"
|
|
109
|
+
docstring-code-format = true
|
|
110
|
+
|
|
111
|
+
[tool.black]
|
|
112
|
+
line-length = 99
|
|
113
|
+
target-version = [
|
|
114
|
+
"py310",
|
|
115
|
+
]
|
|
116
|
+
include = "\\.pyi?$"
|
|
117
|
+
extend-exclude = "/(\n # directories\n \\.eggs\n | \\.git\n | \\.hg\n | \\.mypy_cache\n | \\.tox\n | \\.venv\n | build\n | dist\n)/\n"
|
|
118
|
+
|
|
119
|
+
[tool.mypy]
|
|
120
|
+
python_version = "3.10"
|
|
121
|
+
warn_return_any = true
|
|
122
|
+
warn_unused_configs = true
|
|
123
|
+
ignore_missing_imports = true
|
|
124
|
+
|
|
125
|
+
[tool.pytest.ini_options]
|
|
126
|
+
minversion = "7.0"
|
|
127
|
+
addopts = "-ra -q"
|
|
128
|
+
testpaths = [
|
|
129
|
+
"tests",
|
|
130
|
+
]
|
|
131
|
+
pythonpath = [
|
|
132
|
+
".",
|
|
133
|
+
]
|
|
134
|
+
|
|
135
|
+
[tool.coverage.run]
|
|
136
|
+
source = [
|
|
137
|
+
"quantum_pipeline",
|
|
138
|
+
]
|
|
139
|
+
omit = [
|
|
140
|
+
"tests/*",
|
|
141
|
+
]
|
|
142
|
+
|
|
143
|
+
[tool.flake8]
|
|
144
|
+
max-line-length = 127
|
|
145
|
+
max-complexity = 10
|
|
146
|
+
ignore = [
|
|
147
|
+
"C901",
|
|
148
|
+
]
|
|
149
|
+
select = [
|
|
150
|
+
"E9",
|
|
151
|
+
"F63",
|
|
152
|
+
"F7",
|
|
153
|
+
"F82",
|
|
154
|
+
]
|
|
155
|
+
exclude = [
|
|
156
|
+
".git",
|
|
157
|
+
"__pycache__",
|
|
158
|
+
"venv",
|
|
159
|
+
"build",
|
|
160
|
+
"dist",
|
|
161
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '1.0.2'
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
DEFAULTS = {
|
|
2
|
+
'basis_set': 'sto3g',
|
|
3
|
+
'ansatz_reps': 2,
|
|
4
|
+
'local': True,
|
|
5
|
+
'ibm_quantum': False,
|
|
6
|
+
'max_iterations': 100,
|
|
7
|
+
'convergence_threshold_enable': False,
|
|
8
|
+
'convergence_threshold': 1e-6,
|
|
9
|
+
'optimizer': 'COBYLA',
|
|
10
|
+
'shots': 1024,
|
|
11
|
+
'backend': {
|
|
12
|
+
'local': True,
|
|
13
|
+
'min_qubits': None,
|
|
14
|
+
'optimization_level': 3,
|
|
15
|
+
'filters': None,
|
|
16
|
+
},
|
|
17
|
+
'kafka': {
|
|
18
|
+
'servers': 'localhost:9092',
|
|
19
|
+
'topic': 'vqe_results',
|
|
20
|
+
'retries': 3,
|
|
21
|
+
'internal_retries': 0,
|
|
22
|
+
'acks': 'all',
|
|
23
|
+
'timeout': 10,
|
|
24
|
+
'retry_delay': 2,
|
|
25
|
+
},
|
|
26
|
+
}
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import logging
|
|
3
|
+
import os
|
|
4
|
+
from typing import Any, Dict
|
|
5
|
+
|
|
6
|
+
from quantum_pipeline.configs import settings
|
|
7
|
+
from quantum_pipeline.configs.defaults import DEFAULTS
|
|
8
|
+
from quantum_pipeline.configs.parsing.configuration_manager import ConfigurationManager
|
|
9
|
+
from quantum_pipeline.utils.dir import ensureDirExists
|
|
10
|
+
from quantum_pipeline.utils.logger import get_logger
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class QuantumPipelineArgParser:
|
|
14
|
+
"""Class for handling command line argument parsing for quantum pipeline."""
|
|
15
|
+
|
|
16
|
+
def __init__(self):
|
|
17
|
+
"""Initialize the parser with default configuration."""
|
|
18
|
+
self.logger = get_logger('Argparser')
|
|
19
|
+
|
|
20
|
+
self.initialize_simulation_environment()
|
|
21
|
+
self.parser = argparse.ArgumentParser(
|
|
22
|
+
description='Quantum Circuit Simulation and Execution',
|
|
23
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
self._add_arguments()
|
|
27
|
+
|
|
28
|
+
def initialize_simulation_environment(self):
|
|
29
|
+
"""Ensure required directories and configurations are in place."""
|
|
30
|
+
ensureDirExists(settings.GEN_DIR)
|
|
31
|
+
ensureDirExists(settings.GRAPH_DIR)
|
|
32
|
+
ensureDirExists(settings.REPORT_DIR)
|
|
33
|
+
ensureDirExists(settings.RUN_CONFIGS)
|
|
34
|
+
|
|
35
|
+
def _create_optimizer_help_text(self) -> str:
|
|
36
|
+
"""Create detailed help text for optimizer options."""
|
|
37
|
+
help_text = 'Available optimizers:\n'
|
|
38
|
+
for optimizer, description in settings.SUPPORTED_OPTIMIZERS.items():
|
|
39
|
+
help_text += f'| {optimizer}: {description} |'
|
|
40
|
+
return help_text
|
|
41
|
+
|
|
42
|
+
def _add_arguments(self):
|
|
43
|
+
"""Add all argument groups and their arguments to the parser."""
|
|
44
|
+
self._add_required_arguments()
|
|
45
|
+
self._add_simulation_config()
|
|
46
|
+
self._add_vqe_parameters()
|
|
47
|
+
self._add_output_logging()
|
|
48
|
+
self._add_backend_options()
|
|
49
|
+
self._add_kafka_config()
|
|
50
|
+
self._add_additional_features()
|
|
51
|
+
|
|
52
|
+
def _add_required_arguments(self):
|
|
53
|
+
"""Add required arguments to the parser."""
|
|
54
|
+
self.parser.add_argument(
|
|
55
|
+
'-f', '--file', required=True, help='Path to molecule data file (JSON)'
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
def _add_simulation_config(self):
|
|
59
|
+
"""Add simulation configuration arguments."""
|
|
60
|
+
sim_group = self.parser.add_argument_group('Simulation Configuration')
|
|
61
|
+
sim_group.add_argument(
|
|
62
|
+
'-b', '--basis', default=DEFAULTS['basis_set'], help='Basis set for the simulation'
|
|
63
|
+
)
|
|
64
|
+
sim_group.add_argument(
|
|
65
|
+
'-ar',
|
|
66
|
+
'--ansatz-reps',
|
|
67
|
+
default=DEFAULTS['ansatz_reps'],
|
|
68
|
+
help='Amount of reps for the ansatz',
|
|
69
|
+
)
|
|
70
|
+
sim_group.add_argument(
|
|
71
|
+
'--ibm',
|
|
72
|
+
action='store_false',
|
|
73
|
+
default=DEFAULTS['backend']['local'],
|
|
74
|
+
help='Using IBM Quantum backend for simulation (otherwise local Aer simulator is used.)',
|
|
75
|
+
)
|
|
76
|
+
sim_group.add_argument(
|
|
77
|
+
'--min-qubits',
|
|
78
|
+
type=int,
|
|
79
|
+
default=DEFAULTS['backend']['min_qubits'],
|
|
80
|
+
help='Minimum number of qubits required for the backend',
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
def _add_kafka_config(self):
|
|
84
|
+
"""Add simulation configuration arguments."""
|
|
85
|
+
kafka_group = self.parser.add_argument_group('Kafka Configuration')
|
|
86
|
+
kafka_group.add_argument(
|
|
87
|
+
'--kafka', action='store_true', help='Enable streaming results to Apache Kafka'
|
|
88
|
+
)
|
|
89
|
+
kafka_group.add_argument(
|
|
90
|
+
'--servers',
|
|
91
|
+
type=str,
|
|
92
|
+
default=DEFAULTS['kafka']['servers'],
|
|
93
|
+
help='Apache Kafka instance address',
|
|
94
|
+
)
|
|
95
|
+
kafka_group.add_argument(
|
|
96
|
+
'--topic',
|
|
97
|
+
type=str,
|
|
98
|
+
default=DEFAULTS['kafka']['topic'],
|
|
99
|
+
help='Name of the topic, with which message will be categorised with',
|
|
100
|
+
)
|
|
101
|
+
kafka_group.add_argument(
|
|
102
|
+
'--retries',
|
|
103
|
+
type=str,
|
|
104
|
+
default=DEFAULTS['kafka']['retries'],
|
|
105
|
+
help='Number of attempts to send message to kafka',
|
|
106
|
+
)
|
|
107
|
+
kafka_group.add_argument(
|
|
108
|
+
'--retry-delay',
|
|
109
|
+
type=str,
|
|
110
|
+
default=DEFAULTS['kafka']['retries'],
|
|
111
|
+
help='Number of attempts to send message to kafka',
|
|
112
|
+
)
|
|
113
|
+
kafka_group.add_argument(
|
|
114
|
+
'--internal-retries',
|
|
115
|
+
type=int,
|
|
116
|
+
default=DEFAULTS['kafka']['internal_retries'],
|
|
117
|
+
help='Number of attempts kafka should attempt automatically (introduces risk of duplicate entries)',
|
|
118
|
+
)
|
|
119
|
+
kafka_group.add_argument(
|
|
120
|
+
'--acks',
|
|
121
|
+
default=DEFAULTS['kafka']['acks'],
|
|
122
|
+
choices=['0', '1', 'all'],
|
|
123
|
+
help='Number of acknowledgments producer requires to receive before a request is complete.',
|
|
124
|
+
)
|
|
125
|
+
kafka_group.add_argument(
|
|
126
|
+
'--timeout',
|
|
127
|
+
type=int,
|
|
128
|
+
default=DEFAULTS['kafka']['timeout'],
|
|
129
|
+
help='Number of seconds required for producer to consider request as failed',
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
def _add_vqe_parameters(self):
|
|
133
|
+
"""Add VQE-specific parameters."""
|
|
134
|
+
vqe_group = self.parser.add_argument_group('VQE Parameters')
|
|
135
|
+
vqe_group.add_argument(
|
|
136
|
+
'--max-iterations',
|
|
137
|
+
type=int,
|
|
138
|
+
default=DEFAULTS['max_iterations'],
|
|
139
|
+
help='Maximum number of VQE iterations',
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
vqe_group.add_argument(
|
|
143
|
+
'--convergence',
|
|
144
|
+
action='store_true',
|
|
145
|
+
default=DEFAULTS['convergence_threshold_enable'],
|
|
146
|
+
help='Enable convergence threshold during minimization',
|
|
147
|
+
)
|
|
148
|
+
vqe_group.add_argument(
|
|
149
|
+
'--threshold',
|
|
150
|
+
type=float,
|
|
151
|
+
default=DEFAULTS['convergence_threshold'],
|
|
152
|
+
help='Set convergence threshold for VQE optimization',
|
|
153
|
+
)
|
|
154
|
+
vqe_group.add_argument(
|
|
155
|
+
'--optimizer',
|
|
156
|
+
choices=list(settings.SUPPORTED_OPTIMIZERS.keys()),
|
|
157
|
+
default=DEFAULTS['optimizer'],
|
|
158
|
+
help=self._create_optimizer_help_text(),
|
|
159
|
+
metavar='OPTIMIZER',
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
def _add_output_logging(self):
|
|
163
|
+
"""Add output and logging related arguments."""
|
|
164
|
+
output_group = self.parser.add_argument_group('Output and Logging')
|
|
165
|
+
output_group.add_argument(
|
|
166
|
+
'--output-dir', default=settings.GEN_DIR, help='Directory to store output files'
|
|
167
|
+
)
|
|
168
|
+
output_group.add_argument(
|
|
169
|
+
'--log-level',
|
|
170
|
+
choices=['DEBUG', 'INFO', 'WARNING', 'ERROR'],
|
|
171
|
+
default='INFO',
|
|
172
|
+
help='Set the logging level',
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
def shots(self, shots):
|
|
176
|
+
if int(shots) <= 0:
|
|
177
|
+
raise argparse.ArgumentTypeError(f'shots:{shots} is not a valid number')
|
|
178
|
+
return shots
|
|
179
|
+
|
|
180
|
+
def _add_backend_options(self):
|
|
181
|
+
"""Add advanced backend configuration options."""
|
|
182
|
+
backend_group = self.parser.add_argument_group('Advanced Backend Options')
|
|
183
|
+
backend_group.add_argument(
|
|
184
|
+
'--shots',
|
|
185
|
+
type=self.shots,
|
|
186
|
+
default=DEFAULTS['shots'],
|
|
187
|
+
help='Number of shots for quantum circuit execution',
|
|
188
|
+
)
|
|
189
|
+
backend_group.add_argument(
|
|
190
|
+
'--optimization-level',
|
|
191
|
+
type=int,
|
|
192
|
+
choices=[0, 1, 2, 3],
|
|
193
|
+
default=DEFAULTS['backend']['optimization_level'],
|
|
194
|
+
help='Circuit optimization level',
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
def dir_path(self, path):
|
|
198
|
+
if os.path.isfile(path):
|
|
199
|
+
return path
|
|
200
|
+
else:
|
|
201
|
+
raise argparse.ArgumentTypeError(f'readable_dir:{path} is not a valid path')
|
|
202
|
+
|
|
203
|
+
def _add_additional_features(self):
|
|
204
|
+
"""Add additional feature arguments."""
|
|
205
|
+
additional_group = self.parser.add_argument_group('Additional Features')
|
|
206
|
+
additional_group.add_argument(
|
|
207
|
+
'--report', action='store_true', help='Generate a PDF report after simulation'
|
|
208
|
+
)
|
|
209
|
+
additional_group.add_argument(
|
|
210
|
+
'--dump',
|
|
211
|
+
action='store_true',
|
|
212
|
+
help='Dump configuration generated by the parameters into JSON file',
|
|
213
|
+
)
|
|
214
|
+
additional_group.add_argument(
|
|
215
|
+
'--load',
|
|
216
|
+
type=self.dir_path,
|
|
217
|
+
help='Path to a JSON configuration file to load parameters from',
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
def kafka_params_set(self, args: argparse.Namespace):
|
|
221
|
+
if (
|
|
222
|
+
args.servers != DEFAULTS['kafka']['servers']
|
|
223
|
+
or args.topic != DEFAULTS['kafka']['topic']
|
|
224
|
+
or args.retries != DEFAULTS['kafka']['retries']
|
|
225
|
+
or args.internal_retries != DEFAULTS['kafka']['internal_retries']
|
|
226
|
+
or args.acks != DEFAULTS['kafka']['acks']
|
|
227
|
+
or args.timeout != DEFAULTS['kafka']['timeout']
|
|
228
|
+
):
|
|
229
|
+
return True
|
|
230
|
+
return False
|
|
231
|
+
|
|
232
|
+
def _validate_args(self, args: argparse.Namespace) -> None:
|
|
233
|
+
"""Validate parsed arguments."""
|
|
234
|
+
settings.LOG_LEVEL = getattr(logging, args.log_level)
|
|
235
|
+
|
|
236
|
+
if args.dump and args.load:
|
|
237
|
+
self.parser.error('--dump and --load cannot be used together.')
|
|
238
|
+
|
|
239
|
+
if args.min_qubits is not None and not args.ibm_quantum:
|
|
240
|
+
self.parser.error('--min-qubits can only be used if --ibm-quantum is selected.')
|
|
241
|
+
|
|
242
|
+
if args.convergence and args.threshold is None:
|
|
243
|
+
self.parser.error('--threshold must be set if --convergence is enabled.')
|
|
244
|
+
|
|
245
|
+
if self.kafka_params_set(args) and not args.kafka:
|
|
246
|
+
self.parser.error('--kafka must be set for the options to take effect.')
|
|
247
|
+
|
|
248
|
+
def parse_args(self) -> argparse.Namespace:
|
|
249
|
+
"""Parse and validate command line arguments."""
|
|
250
|
+
args = self.parser.parse_args()
|
|
251
|
+
self._validate_args(args)
|
|
252
|
+
return args
|
|
253
|
+
|
|
254
|
+
def get_config(self) -> Dict[str, Any]:
|
|
255
|
+
"""Get the configuration from the parsed arguments."""
|
|
256
|
+
config_manager = ConfigurationManager()
|
|
257
|
+
args = self.parse_args()
|
|
258
|
+
return config_manager.get_config(args)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from dataclasses import asdict, dataclass
|
|
2
|
+
from typing import Any, Callable, Dict
|
|
3
|
+
|
|
4
|
+
from qiskit_ibm_runtime import IBMBackend
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class BackendConfig:
|
|
9
|
+
"""Dataclass for storing backend filter."""
|
|
10
|
+
|
|
11
|
+
local: bool | None
|
|
12
|
+
optimization_level: int | None
|
|
13
|
+
min_num_qubits: int | None
|
|
14
|
+
filters: Callable[[IBMBackend], bool] | None
|
|
15
|
+
|
|
16
|
+
def to_dict(self):
|
|
17
|
+
"""Convert the dataclass to a dictionary."""
|
|
18
|
+
return asdict(self)
|
|
19
|
+
|
|
20
|
+
@classmethod
|
|
21
|
+
def from_dict(cls, data: Dict[str, Any]) -> 'BackendConfig':
|
|
22
|
+
"""Create a BackendConfig instance from a dictionary."""
|
|
23
|
+
return cls(
|
|
24
|
+
local=data.get('local'),
|
|
25
|
+
optimization_level=data.get('optimization_level'),
|
|
26
|
+
min_num_qubits=data.get('min_num_qubits'),
|
|
27
|
+
filters=None,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
@classmethod
|
|
31
|
+
def default_backend_config(cls) -> 'BackendConfig':
|
|
32
|
+
"""Return the default backend configuration."""
|
|
33
|
+
return cls(
|
|
34
|
+
local=True,
|
|
35
|
+
optimization_level=3,
|
|
36
|
+
min_num_qubits=None,
|
|
37
|
+
filters=None,
|
|
38
|
+
)
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
from copy import deepcopy
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
import json
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any, Dict
|
|
7
|
+
|
|
8
|
+
from quantum_pipeline.configs import settings
|
|
9
|
+
from quantum_pipeline.configs.parsing.backend_config import BackendConfig
|
|
10
|
+
from quantum_pipeline.configs.parsing.producer_config import ProducerConfig
|
|
11
|
+
from quantum_pipeline.utils.logger import get_logger
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ConfigurationManager:
|
|
15
|
+
"""Class for managing application configurations."""
|
|
16
|
+
|
|
17
|
+
def __init__(self):
|
|
18
|
+
self.logger = get_logger(self.__class__.__name__)
|
|
19
|
+
|
|
20
|
+
def create_kafka_config(self, args: argparse.Namespace) -> ProducerConfig:
|
|
21
|
+
"""Create Kafka configuration from arguments."""
|
|
22
|
+
return ProducerConfig.from_dict(
|
|
23
|
+
{
|
|
24
|
+
'servers': args.servers,
|
|
25
|
+
'topic': args.topic,
|
|
26
|
+
'retries': args.retries,
|
|
27
|
+
'retry_delay': args.retry_delay,
|
|
28
|
+
'kafka_retries': args.internal_retries,
|
|
29
|
+
'acks': args.acks,
|
|
30
|
+
'timeout': args.timeout,
|
|
31
|
+
}
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
def create_backend_config(self, args: argparse.Namespace) -> BackendConfig:
|
|
35
|
+
"""Create Backend configuration from arguments."""
|
|
36
|
+
return BackendConfig.from_dict(
|
|
37
|
+
{
|
|
38
|
+
'local': args.ibm,
|
|
39
|
+
'min_num_qubits': args.min_qubits,
|
|
40
|
+
'optimization_level': args.optimization_level,
|
|
41
|
+
'filters': None,
|
|
42
|
+
}
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
def dump(self, args: argparse.Namespace, config: Dict[str, Any]):
|
|
46
|
+
"""Dump the configurations for debugging or logging."""
|
|
47
|
+
|
|
48
|
+
self.logger.debug('Dumping configuration into the file...')
|
|
49
|
+
|
|
50
|
+
# create a copy thats JSON serializable
|
|
51
|
+
config_dict = deepcopy(config)
|
|
52
|
+
config_dict['backend_config'] = self.create_backend_config(args)
|
|
53
|
+
config_dict['kafka_config'] = self.create_kafka_config(args)
|
|
54
|
+
config_dict['backend_config'] = config_dict['backend_config'].to_dict()
|
|
55
|
+
config_dict['kafka_config'] = config_dict['kafka_config'].to_dict()
|
|
56
|
+
|
|
57
|
+
for k in list(config_dict.keys()):
|
|
58
|
+
if k in config_dict.get('backend_config', {}):
|
|
59
|
+
config_dict.pop(k)
|
|
60
|
+
if k in config_dict.get('kafka_config', {}):
|
|
61
|
+
config_dict.pop(k)
|
|
62
|
+
|
|
63
|
+
# create a filename
|
|
64
|
+
file_name = Path(args.file).stem
|
|
65
|
+
basis_set = args.basis
|
|
66
|
+
optimizer = args.optimizer
|
|
67
|
+
backend_local = 'api' if args.ibm else 'local'
|
|
68
|
+
current_date = datetime.now().strftime('%Y%m%d')
|
|
69
|
+
|
|
70
|
+
file_path = f'{file_name}-{basis_set}-{optimizer}-{backend_local}-{current_date}.json'
|
|
71
|
+
self.config_path = Path(settings.RUN_CONFIGS, file_path)
|
|
72
|
+
|
|
73
|
+
try:
|
|
74
|
+
with open(self.config_path, 'w') as file:
|
|
75
|
+
json.dump(config_dict, file, indent=4)
|
|
76
|
+
except Exception as e:
|
|
77
|
+
self.logger.error(f'Failed to save configuration: {e}')
|
|
78
|
+
raise
|
|
79
|
+
|
|
80
|
+
self.logger.info(f'Configuration saved to:\n\n{file_path}\n')
|
|
81
|
+
self.logger.debug(f'Arguments:\n\n{vars(args)}\n')
|
|
82
|
+
self.logger.debug(f'Configurations:\n\n{config_dict}\n')
|
|
83
|
+
|
|
84
|
+
def load(self, file_path: str) -> Dict[str, Any]:
|
|
85
|
+
"""Load the configurations from a JSON file."""
|
|
86
|
+
try:
|
|
87
|
+
self.logger.debug(f'Loading configuration from:\n\n{file_path}\n')
|
|
88
|
+
with open(file_path, 'r') as file:
|
|
89
|
+
config_dict = json.load(file)
|
|
90
|
+
|
|
91
|
+
# reconstruct config objects
|
|
92
|
+
backend_config = BackendConfig.from_dict(config_dict['backend_config'])
|
|
93
|
+
kafka_config = ProducerConfig.from_dict(config_dict['kafka_config'])
|
|
94
|
+
|
|
95
|
+
# build full dictionary
|
|
96
|
+
config_dict['backend_config'] = backend_config
|
|
97
|
+
config_dict['kafka_config'] = kafka_config
|
|
98
|
+
|
|
99
|
+
self.logger.info(f'Configuration loaded from:\n\n{file_path}\n')
|
|
100
|
+
self.logger.debug(f'Loaded configurations:\n\n{config_dict}\n')
|
|
101
|
+
return config_dict
|
|
102
|
+
|
|
103
|
+
except FileNotFoundError:
|
|
104
|
+
self.logger.error(f'Configuration file not found: {file_path}')
|
|
105
|
+
raise
|
|
106
|
+
except json.JSONDecodeError as e:
|
|
107
|
+
self.logger.error(f'Failed to parse JSON configuration: {e}')
|
|
108
|
+
raise
|
|
109
|
+
|
|
110
|
+
def get_config(self, args: argparse.Namespace) -> Dict[str, Any]:
|
|
111
|
+
"""Convert parsed arguments dynamically into a dictionary."""
|
|
112
|
+
|
|
113
|
+
if args.load:
|
|
114
|
+
self.load(args.load)
|
|
115
|
+
|
|
116
|
+
# create a dictionary from the parsed arguments
|
|
117
|
+
config_dict = {key: value for key, value in vars(args).items() if key != 'local'}
|
|
118
|
+
config_dict['kafka_config'] = self.create_kafka_config(args)
|
|
119
|
+
config_dict['backend_config'] = self.create_backend_config(args)
|
|
120
|
+
|
|
121
|
+
if args.dump:
|
|
122
|
+
self.dump(args, config_dict)
|
|
123
|
+
|
|
124
|
+
return config_dict
|