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.
Files changed (43) hide show
  1. quantum_pipeline-1.0.2/LICENSE +21 -0
  2. quantum_pipeline-1.0.2/PKG-INFO +61 -0
  3. quantum_pipeline-1.0.2/pyproject.toml +161 -0
  4. quantum_pipeline-1.0.2/quantum_pipeline/__init__.py +1 -0
  5. quantum_pipeline-1.0.2/quantum_pipeline/configs/defaults.py +26 -0
  6. quantum_pipeline-1.0.2/quantum_pipeline/configs/parsing/argparser.py +258 -0
  7. quantum_pipeline-1.0.2/quantum_pipeline/configs/parsing/backend_config.py +38 -0
  8. quantum_pipeline-1.0.2/quantum_pipeline/configs/parsing/configuration_manager.py +124 -0
  9. quantum_pipeline-1.0.2/quantum_pipeline/configs/parsing/producer_config.py +33 -0
  10. quantum_pipeline-1.0.2/quantum_pipeline/configs/settings.py +50 -0
  11. quantum_pipeline-1.0.2/quantum_pipeline/drivers/__init__.py +0 -0
  12. quantum_pipeline-1.0.2/quantum_pipeline/drivers/basis_sets.py +23 -0
  13. quantum_pipeline-1.0.2/quantum_pipeline/drivers/molecule_loader.py +53 -0
  14. quantum_pipeline-1.0.2/quantum_pipeline/features/circuit.py +26 -0
  15. quantum_pipeline-1.0.2/quantum_pipeline/mappers/__init__.py +0 -0
  16. quantum_pipeline-1.0.2/quantum_pipeline/mappers/jordan_winger_mapper.py +37 -0
  17. quantum_pipeline-1.0.2/quantum_pipeline/mappers/mapper.py +5 -0
  18. quantum_pipeline-1.0.2/quantum_pipeline/report/configuration.py +45 -0
  19. quantum_pipeline-1.0.2/quantum_pipeline/report/content_builder.py +170 -0
  20. quantum_pipeline-1.0.2/quantum_pipeline/report/renderer.py +136 -0
  21. quantum_pipeline-1.0.2/quantum_pipeline/report/report_generator.py +91 -0
  22. quantum_pipeline-1.0.2/quantum_pipeline/runners/runner.py +6 -0
  23. quantum_pipeline-1.0.2/quantum_pipeline/runners/vqe_runner.py +265 -0
  24. quantum_pipeline-1.0.2/quantum_pipeline/solvers/__init__.py +0 -0
  25. quantum_pipeline-1.0.2/quantum_pipeline/solvers/solver.py +102 -0
  26. quantum_pipeline-1.0.2/quantum_pipeline/solvers/vqe_solver.py +221 -0
  27. quantum_pipeline-1.0.2/quantum_pipeline/stream/kafka_interface.py +115 -0
  28. quantum_pipeline-1.0.2/quantum_pipeline/stream/serialization/interfaces/vqe.py +293 -0
  29. quantum_pipeline-1.0.2/quantum_pipeline/stream/serialization/schemas/vqe_initial.avsc +37 -0
  30. quantum_pipeline-1.0.2/quantum_pipeline/stream/serialization/schemas/vqe_molecule.avsc +32 -0
  31. quantum_pipeline-1.0.2/quantum_pipeline/stream/serialization/schemas/vqe_process.avsc +10 -0
  32. quantum_pipeline-1.0.2/quantum_pipeline/structures/vqe_observation.py +54 -0
  33. quantum_pipeline-1.0.2/quantum_pipeline/utils/__init__.py +0 -0
  34. quantum_pipeline-1.0.2/quantum_pipeline/utils/dir.py +44 -0
  35. quantum_pipeline-1.0.2/quantum_pipeline/utils/logger.py +17 -0
  36. quantum_pipeline-1.0.2/quantum_pipeline/utils/schema_registry.py +54 -0
  37. quantum_pipeline-1.0.2/quantum_pipeline/utils/visualizer.py +12 -0
  38. quantum_pipeline-1.0.2/quantum_pipeline/visual/__init__.py +0 -0
  39. quantum_pipeline-1.0.2/quantum_pipeline/visual/ansatz.py +59 -0
  40. quantum_pipeline-1.0.2/quantum_pipeline/visual/energy.py +94 -0
  41. quantum_pipeline-1.0.2/quantum_pipeline/visual/molecule.py +200 -0
  42. quantum_pipeline-1.0.2/quantum_pipeline/visual/operator.py +219 -0
  43. 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