compiler-config 0.1.0__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.
@@ -0,0 +1,28 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2024, oqc-community
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ 3. Neither the name of the copyright holder nor the names of its
16
+ contributors may be used to endorse or promote products derived from
17
+ this software without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,23 @@
1
+ Metadata-Version: 2.1
2
+ Name: compiler-config
3
+ Version: 0.1.0
4
+ Summary:
5
+ Author: jamie
6
+ Author-email: jfriel@oxfordquantumcircuits.com
7
+ Requires-Python: >=3.8.1,<3.13
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.9
10
+ Classifier: Programming Language :: Python :: 3.10
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Description-Content-Type: text/markdown
14
+
15
+ # compiler-config
16
+
17
+ OQC's task level compiler configuration. The compiler-config package is an internal dependency of [QAT](https://github.com/oqc-community/qat)
18
+ (OQC's compiler and runtime) and will soon be adopted as a dependency of our QCaaS (Quantum Computing as a Service) client. *End users should not need to interact with this package except via QAT or QCAAS client.*
19
+
20
+ #### Licence
21
+
22
+ This code in this repository is licensed under the BSD 3-Clause Licence.
23
+ Please see [LICENSE](LICENSE) for more information.
@@ -0,0 +1,9 @@
1
+ # compiler-config
2
+
3
+ OQC's task level compiler configuration. The compiler-config package is an internal dependency of [QAT](https://github.com/oqc-community/qat)
4
+ (OQC's compiler and runtime) and will soon be adopted as a dependency of our QCaaS (Quantum Computing as a Service) client. *End users should not need to interact with this package except via QAT or QCAAS client.*
5
+
6
+ #### Licence
7
+
8
+ This code in this repository is licensed under the BSD 3-Clause Licence.
9
+ Please see [LICENSE](LICENSE) for more information.
File without changes
@@ -0,0 +1,447 @@
1
+ # SPDX-License-Identifier: BSD-3-Clause
2
+ # Copyright (c) 2023 Oxford Quantum Circuits Ltd
3
+ from __future__ import annotations
4
+
5
+ import inspect
6
+ import re
7
+ import sys
8
+ from enum import Enum, Flag, IntEnum, auto
9
+ from typing import List, Optional
10
+
11
+ from compiler_config.serialiser import json_dumps, json_loads
12
+
13
+
14
+ class InlineResultsProcessing(Flag):
15
+ """
16
+ Results transforms applied directly to the read-out value on the QPU. In most
17
+ situations applied post-execution, but can also be interwoven.
18
+ """
19
+
20
+ # Raw readout from the QPU. Normally Z-axis values for each shot.
21
+ Raw = auto()
22
+
23
+ # Shot results averaged out to get a single 0/1 value for each qubit.
24
+ Binary = auto()
25
+
26
+ # Return the values in numpy arrays not Python lists.
27
+ NumpyArrays = auto()
28
+
29
+ Experiment = Raw | NumpyArrays
30
+ Program = Binary
31
+
32
+ def __repr__(self):
33
+ return self.name
34
+
35
+
36
+ class ResultsFormatting(Flag):
37
+ # Transforms each shot into binary then counts the instances.
38
+ # Example for two qubits: { '00': 15, '01': 2524, '10': 250, '11': 730 }
39
+ BinaryCount = auto()
40
+
41
+ # Change results value based on conditions for ease-of-use. Set as a flag because
42
+ # it means that return values from execution may change format unexpectedly, so
43
+ # should have a way to disable it for certain uses.
44
+ DynamicStructureReturn = auto()
45
+
46
+ # If your qubit results are lists of binary, squash to one string representation.
47
+ # Changes 1: [1, 0, 0, 1] to 1: '1001'.
48
+ # Only works when Binary format is returned.
49
+ SquashBinaryResultArrays = auto()
50
+
51
+ def __repr__(self):
52
+ return self.name
53
+
54
+
55
+ class QuantumResultsFormat:
56
+ def __init__(self):
57
+ self.format: Optional[InlineResultsProcessing] = None
58
+ self.transforms: Optional[ResultsFormatting] = (
59
+ ResultsFormatting.DynamicStructureReturn
60
+ )
61
+
62
+ def raw(self) -> QuantumResultsFormat:
63
+ self.format = InlineResultsProcessing.Raw
64
+ return self
65
+
66
+ def binary(self) -> QuantumResultsFormat:
67
+ self.format = InlineResultsProcessing.Binary
68
+ return self
69
+
70
+ def binary_count(self):
71
+ """
72
+ Returns a count of each instance of measured qubit registers.
73
+ Switches result format to raw.
74
+ """
75
+ self.transforms = (
76
+ ResultsFormatting.BinaryCount | ResultsFormatting.DynamicStructureReturn
77
+ )
78
+ self.format = InlineResultsProcessing.Raw
79
+ return self
80
+
81
+ def squash_binary_result_arrays(self):
82
+ """
83
+ Squashes binary result list into a singular bit string. Switches results to
84
+ binary.
85
+ """
86
+ self.transforms = (
87
+ ResultsFormatting.SquashBinaryResultArrays
88
+ | ResultsFormatting.DynamicStructureReturn
89
+ )
90
+ self.format = InlineResultsProcessing.Binary
91
+ return self
92
+
93
+ def __contains__(self, other):
94
+ if isinstance(other, ResultsFormatting):
95
+ return self.transforms.__contains__(other)
96
+ elif isinstance(other, InlineResultsProcessing):
97
+ return self.format.__contains__(other)
98
+ return False
99
+
100
+ def __or__(self, other):
101
+ if isinstance(other, ResultsFormatting):
102
+ self.transforms = self.transforms.__or__(other)
103
+ elif isinstance(other, InlineResultsProcessing):
104
+ self.format = self.format.__or__(other)
105
+ return self
106
+
107
+ def __and__(self, other):
108
+ if isinstance(other, ResultsFormatting):
109
+ self.transforms = self.transforms.__and__(other)
110
+ elif isinstance(other, InlineResultsProcessing):
111
+ self.format = self.format.__and__(other)
112
+ return self
113
+
114
+ def __xor__(self, other):
115
+ if isinstance(other, ResultsFormatting):
116
+ self.transforms = self.transforms.__xor__(other)
117
+ elif isinstance(other, InlineResultsProcessing):
118
+ self.format = self.format.__xor__(other)
119
+ return self
120
+
121
+ def __repr__(self):
122
+ return f"Format: {str(self.format)}. Transforms: {str(self.transforms)}."
123
+
124
+ def __eq__(self, other):
125
+ return self.format == other.format and self.transforms == other.transforms
126
+
127
+
128
+ class TketOptimizations(Flag):
129
+ """Flags for the various Tket optimizations we can apply."""
130
+
131
+ Empty = auto()
132
+ DefaultMappingPass = auto()
133
+ FullPeepholeOptimise = auto()
134
+ ContextSimp = auto()
135
+ DirectionalCXGates = auto()
136
+ CliffordSimp = auto()
137
+ DecomposeArbitrarilyControlledGates = auto()
138
+ # EulerAngleReduction = auto()
139
+ GlobalisePhasedX = auto()
140
+ # GuidedPauliSimp = auto()
141
+ KAKDecomposition = auto()
142
+ # OptimisePhaseGadgets = auto()
143
+ # PauliSimp = auto()
144
+ # PauliSquash = auto()
145
+ PeepholeOptimise2Q = auto()
146
+ RemoveDiscarded = auto()
147
+ RemoveBarriers = auto()
148
+ RemoveRedundancies = auto()
149
+ ThreeQubitSquash = auto()
150
+ SimplifyMeasured = auto()
151
+
152
+ One = DefaultMappingPass | DirectionalCXGates
153
+ Two = One | FullPeepholeOptimise | ContextSimp
154
+
155
+
156
+ class QiskitOptimizations(Flag):
157
+ """Flags for the various Qiskit optimizations we can apply."""
158
+
159
+ Empty = auto()
160
+
161
+
162
+ class QatOptimizations(Flag):
163
+ """Flags for the various Qat optimizations we can apply."""
164
+
165
+ Empty = auto()
166
+
167
+
168
+ class MetricsType(Flag):
169
+ Empty = auto()
170
+
171
+ # Returns circuit after optimizations have been run.
172
+ OptimizedCircuit = auto()
173
+
174
+ # Count of transformed instructions after all forms of optimizations have
175
+ # been performed.
176
+ OptimizedInstructionCount = auto()
177
+
178
+ # Set of basic metrics that should be returned at all times.
179
+ Default = OptimizedCircuit | OptimizedInstructionCount
180
+
181
+ def is_composite(self):
182
+ """
183
+ Any flags that are only composed of other ones should be signaled here. This
184
+ is used for automatic metric generation and whether to build/validate this
185
+ particular value.
186
+ """
187
+ return self == self.Default or self == self.Empty
188
+
189
+ def snake_case_name(self):
190
+ """
191
+ Generate the Python field name that'll be used to hold the results of this
192
+ metric.
193
+ """
194
+ name = self.name
195
+ name = re.sub("(.)([A-Z][a-z]+)", r"\1_\2", name)
196
+ return re.sub("([a-z0-9])([A-Z])", r"\1_\2", name).lower()
197
+
198
+
199
+ class ErrorMitigationConfig(Flag):
200
+ Empty = auto()
201
+ MatrixMitigation = auto()
202
+ LinearMitigation = auto()
203
+
204
+
205
+ class ExperimentalFeatures:
206
+ error_mitigation = ErrorMitigationConfig.Empty
207
+
208
+
209
+ class CompilerConfig:
210
+ """
211
+ Full settings for the compiler. All values are defaulted on initialization.
212
+
213
+ If no explicit optimizations are passed then the default set of optimization for the
214
+ language you're attempting to compile will be applied.
215
+ """
216
+
217
+ def __init__(
218
+ self,
219
+ repeats=None,
220
+ repetition_period=None,
221
+ results_format: QuantumResultsFormat = None,
222
+ metrics=MetricsType.Default,
223
+ active_calibrations=None,
224
+ optimizations: "OptimizationConfig" = None,
225
+ error_mitigation: ErrorMitigationConfig = None,
226
+ ):
227
+ self.repeats: Optional[int] = repeats
228
+ self.repetition_period: Optional[float] = repetition_period
229
+ self.results_format: QuantumResultsFormat = results_format or QuantumResultsFormat()
230
+ self.metrics: MetricsType = metrics
231
+ self.active_calibrations: List[CalibrationArguments] = active_calibrations or []
232
+ self.optimizations: Optional[OptimizationConfig] = optimizations
233
+ self.error_mitigation: Optional[ErrorMitigationConfig] = error_mitigation
234
+
235
+ def to_json(self):
236
+ return json_dumps(self, serializable_types=get_serializable_types())
237
+
238
+ def from_json(self, json: str):
239
+ vars(self).update(
240
+ vars(json_loads(json, serializable_types=get_serializable_types()))
241
+ )
242
+ return self
243
+
244
+ @classmethod
245
+ def create_from_json(cls, json: str):
246
+ return CompilerConfig().from_json(json)
247
+
248
+ def validate(self, hardware):
249
+
250
+ if (
251
+ self.error_mitigation is not None
252
+ and self.error_mitigation != ErrorMitigationConfig.Empty
253
+ ):
254
+ if (
255
+ hardware.error_mitigation is None
256
+ or not hardware.error_mitigation.readout_mitigation
257
+ ):
258
+ raise ValueError("Error mitigation not calibrated on this device.")
259
+ if ResultsFormatting.BinaryCount not in self.results_format:
260
+ raise ValueError(
261
+ "Binary Count format required for readout error mitigation"
262
+ )
263
+
264
+
265
+ class CalibrationArguments:
266
+ """Base class for individual calibration arguments."""
267
+
268
+ def to_json(self):
269
+ return json_dumps(self)
270
+
271
+ def from_json(self, json: str):
272
+ self.from_dict(json_loads(json))
273
+
274
+ def _get_field_names(self):
275
+ """Get existing field names for all attributes and properties"""
276
+ existing_field_names = set(vars(self).keys())
277
+ # Include class properties
278
+ class_type = type(self)
279
+ for prop in dir(class_type):
280
+ if isinstance(getattr(class_type, prop), property):
281
+ existing_field_names.add(prop)
282
+
283
+ return existing_field_names
284
+
285
+ def from_dict(self, dict_values):
286
+ """
287
+ Loads this dictionary into the arguments. Throws if key dosen't exist on the
288
+ object.
289
+ """
290
+ valid_names = self._get_field_names()
291
+ invalid_fields = [val for val in dict_values.keys() if val not in valid_names]
292
+ if any(invalid_fields):
293
+ raise ValueError(
294
+ f"Field(s) {','.join(invalid_fields)} are not valid for "
295
+ f"{self.__class__.__name__}."
296
+ )
297
+
298
+ vars(self).update(dict_values)
299
+
300
+
301
+ class Languages(IntEnum):
302
+ Empty, Qasm2, Qasm3, QIR = range(4)
303
+
304
+ def __repr__(self):
305
+ return self.name
306
+
307
+
308
+ class OptimizationConfig:
309
+ """
310
+ Base class for instantiated optimizations as well as mix-in classes. Built this way
311
+ so we can mix and match optimization objects across multiple setups and languages
312
+ without duplication.
313
+ """
314
+
315
+ def __init__(self):
316
+ super().__init__()
317
+
318
+ def default(self):
319
+ """Apply default set of optimizations to the current set."""
320
+ return self
321
+
322
+ def disable(self):
323
+ """Disable all optimizations."""
324
+ return self
325
+
326
+ def minimum(self):
327
+ """
328
+ Apply minimum working set for current optimizations.
329
+ """
330
+ return self
331
+
332
+ def __contains__(self, item):
333
+ return False
334
+
335
+
336
+ class Tket(OptimizationConfig):
337
+ def __init__(self, tket_optimization=None):
338
+ super().__init__()
339
+ self.tket_optimizations: TketOptimizations = TketOptimizations.Empty
340
+ if tket_optimization is not None:
341
+ self.tket_optimizations = tket_optimization
342
+
343
+ def default(self):
344
+ self.tket_optimizations = TketOptimizations.One
345
+ return self
346
+
347
+ def disable(self):
348
+ self.tket_optimizations = TketOptimizations.Empty
349
+ return self
350
+
351
+ def minimum(self):
352
+ self.tket_optimizations = TketOptimizations.DefaultMappingPass
353
+ return self
354
+
355
+ def __contains__(self, item):
356
+ if isinstance(item, TketOptimizations) and item in self.tket_optimizations:
357
+ return True
358
+ return super().__contains__(item)
359
+
360
+
361
+ class Qiskit(OptimizationConfig):
362
+ def __init__(self):
363
+ super().__init__()
364
+ self.qiskit_optimizations: QiskitOptimizations = QiskitOptimizations.Empty
365
+
366
+ def default(self):
367
+ self.qiskit_optimizations = QiskitOptimizations.Empty
368
+ return self
369
+
370
+ def __contains__(self, item):
371
+ if isinstance(item, QiskitOptimizations) and item in self.qiskit_optimizations:
372
+ return True
373
+ return super().__contains__(item)
374
+
375
+
376
+ class Qasm2Optimizations(Tket, Qiskit):
377
+ def __init__(self):
378
+ super().__init__()
379
+ self.default()
380
+
381
+ def __repr__(self):
382
+ return f"Qiskit: {self.qiskit_optimizations}. Tket: {self.tket_optimizations}."
383
+
384
+
385
+ class Qasm3Optimizations(OptimizationConfig):
386
+ pass
387
+
388
+
389
+ class QIROptimizations(OptimizationConfig):
390
+ pass
391
+
392
+
393
+ def get_optimizer_config(lang: Languages) -> Optional[OptimizationConfig]:
394
+ """
395
+ Returns the optimization config for this particular language. None if no valid ones
396
+ found.
397
+ """
398
+ if lang == Languages.Qasm2:
399
+ return Qasm2Optimizations()
400
+ elif lang == Languages.Qasm3:
401
+ return Qasm3Optimizations()
402
+ elif lang == Languages.QIR:
403
+ return QIROptimizations()
404
+ return None
405
+
406
+
407
+ def get_config(lang: Languages, **kwargs):
408
+ """
409
+ Helper method to build a compiler config for a particular language. Forwards
410
+ keywords to the CompilerConfig constructor.
411
+ """
412
+ config = CompilerConfig(**kwargs)
413
+ config.optimizations = get_optimizer_config(lang)
414
+ return config
415
+
416
+
417
+ serializable_types_dict = None
418
+
419
+
420
+ def get_serializable_types():
421
+ global serializable_types_dict
422
+
423
+ if serializable_types_dict is not None:
424
+ return serializable_types_dict
425
+
426
+ serializable_types = {}
427
+
428
+ def update_dict(type):
429
+ if issubclass(type, Enum):
430
+ serializable_types.update({f"<enum '{type.__module__}.{type.__name__}'>": type})
431
+ else:
432
+ serializable_types.update({str(type): type})
433
+
434
+ def get_serializable_types_dict(type):
435
+ update_dict(type)
436
+
437
+ for subclass in type.__subclasses__():
438
+ get_serializable_types_dict(subclass)
439
+
440
+ types_in_module = inspect.getmembers(sys.modules[__name__], inspect.isclass)
441
+
442
+ for name, typ in types_in_module:
443
+ get_serializable_types_dict(typ)
444
+
445
+ serializable_types_dict = serializable_types
446
+
447
+ return serializable_types_dict
@@ -0,0 +1,181 @@
1
+ # SPDX-License-Identifier: BSD-3-Clause
2
+ # Copyright (c) 2023 Oxford Quantum Circuits Ltd
3
+ import json
4
+ import re
5
+ import sys
6
+ from dataclasses import asdict, is_dataclass
7
+ from enum import Enum
8
+ from importlib import import_module
9
+ from json import JSONDecoder, JSONEncoder
10
+
11
+
12
+ # Set of common methods so we don't have to add/remove the custom serializer.
13
+ def json_dumps(*args, serializable_types=None, **kwargs):
14
+ kwargs.setdefault("cls", CustomJSONEncoder)
15
+ return json.dumps(*args, serializable_types=serializable_types, **kwargs)
16
+
17
+
18
+ def json_loads(*args, serializable_types=None, **kwargs):
19
+ kwargs.setdefault("cls", CustomJsonDecoder)
20
+ return json.loads(*args, serializable_types=serializable_types, **kwargs)
21
+
22
+
23
+ def json_dump(*args, serializable_types=None, **kwargs):
24
+ kwargs.setdefault("cls", CustomJSONEncoder)
25
+ return json.dump(*args, serializable_types=serializable_types, **kwargs)
26
+
27
+
28
+ def json_load(*args, serializable_types=None, **kwargs):
29
+ kwargs.setdefault("cls", CustomJsonDecoder)
30
+ return json.load(*args, serializable_types=serializable_types, **kwargs)
31
+
32
+
33
+ class CustomJsonDecoder(JSONDecoder):
34
+ def __init__(self, *args, serializable_types=None, model=None, **kwargs):
35
+ self.serializable_types = serializable_types
36
+ super().__init__(object_hook=self.default, *args, **kwargs)
37
+
38
+ def default(self, obj):
39
+ if not isinstance(obj, dict):
40
+ return obj
41
+
42
+ # Components are objects directly related to hardware and you never want to serialize them, so we re-link
43
+ # upon deserialization.
44
+
45
+ obj_type = obj.get("$type")
46
+ if obj_type is None:
47
+ return obj
48
+
49
+ if obj_type == "<class 'tuple'>":
50
+ return tuple(obj["$data"])
51
+
52
+ if self.serializable_types is not None:
53
+ old_paths = ["scc.compiler.config", "qat.purr.compiler.config"]
54
+ for old_path in old_paths:
55
+ if old_path in obj_type:
56
+ obj_type = obj_type.replace(old_path, "compiler_config.config")
57
+ break
58
+
59
+ typ = self.serializable_types.get(obj_type)
60
+ if typ is None:
61
+ raise ValueError(f"Invalid type attempted to be serialized: {obj_type}.")
62
+ else:
63
+ typ = _get_type(obj_type)
64
+
65
+ if issubclass(typ, Enum):
66
+ return typ(obj["$value"])
67
+
68
+ if "$data" in obj:
69
+ data = obj["$data"]
70
+ if obj.get("$dataclass", False):
71
+ fields = self.default(data)
72
+ return typ(**fields)
73
+ elif isinstance(data, dict):
74
+ new_obj = object.__new__(typ)
75
+ new_obj.__dict__ = {key: self.default(value) for key, value in data.items()}
76
+ return new_obj
77
+ elif isinstance(data, str):
78
+ return typ(data)
79
+ else:
80
+ return data
81
+
82
+ return obj
83
+
84
+
85
+ class CustomJSONEncoder(JSONEncoder):
86
+ """
87
+ It is a customised JSON encoder, which allows the serialization of the more complex
88
+ objects.
89
+
90
+ There are four major cases, based on the provided object to be serialized:
91
+
92
+ - if the type of the object is supported by the default :class:`JSONEncoder`, than
93
+ the default method is used.
94
+ - if the class of the object is a :class:`dataclass`, then the serialization will
95
+ contain the name of the type, ``dataclass`` flag in order to help at the
96
+ deserialization, and the dictionary of the fields.
97
+ - if the object is none from the above, then the type name is saved, and the
98
+ interior data using ``__dict__``.
99
+ - if an exception is encountered from any cases from above (e.g. ``__dict__`` is not
100
+ available in case of complex numbers), then the type name is saved, and the data
101
+ is the string representation of the object.
102
+ """
103
+
104
+ def __init__(self, *args, serializable_types=None, **kwargs):
105
+ self.serializable_types = serializable_types
106
+ super().__init__(*args, **kwargs)
107
+
108
+ def default(self, obj):
109
+ typ = type(obj)
110
+ if issubclass(typ, Enum):
111
+ typ_str = f"<enum '{obj.__module__}.{typ.__name__}'>"
112
+ else:
113
+ typ_str = str(typ)
114
+
115
+ if self.serializable_types is not None:
116
+ if (
117
+ typ_str not in self.serializable_types
118
+ and type(obj).__module__ != "builtins"
119
+ ):
120
+ raise ValueError(f"Invalid type attempted to be serialized: {(type(obj))}.")
121
+
122
+ try:
123
+
124
+ # TODO: Acquire is a special wrapper component, not an actual component. Have a few too many special-cases
125
+ # for it now, think about reverting its special status.
126
+ if is_dataclass(obj):
127
+ return {
128
+ "$type": typ_str,
129
+ "$dataclass": True,
130
+ "$data": self.default(asdict(obj)),
131
+ }
132
+ elif isinstance(obj, Enum):
133
+ return {"$type": typ_str, "$value": obj.value}
134
+ elif isinstance(obj, complex):
135
+ return {"$type": typ_str, "$data": str(obj)}
136
+ elif isinstance(obj, tuple):
137
+ return {
138
+ "$type": typ_str,
139
+ "$data": tuple(self.default(val) for val in obj),
140
+ }
141
+
142
+ if hasattr(obj, "__dict__"):
143
+ return {
144
+ "$type": typ_str,
145
+ "$data": {
146
+ key: self.default(value) for key, value in obj.__dict__.items()
147
+ },
148
+ }
149
+ except (TypeError, AttributeError):
150
+ pass
151
+
152
+ return obj
153
+
154
+
155
+ _type_name_matcher = r"<(?:class|enum) '((?:\w+\.)*)(\w+)'>"
156
+
157
+
158
+ def _get_type(s: str):
159
+ match = re.match(_type_name_matcher, s)
160
+ if match:
161
+ namespace = match.group(1)
162
+ if namespace != "":
163
+ expanded_namespace = namespace[:-1].split(".")
164
+ module_name = expanded_namespace.pop(0)
165
+ imported_module = import_module(module_name)
166
+ for item in expanded_namespace:
167
+ module_name = f"{module_name}.{item}"
168
+ try:
169
+ imported_module = getattr(imported_module, item)
170
+ except AttributeError:
171
+ import_module(module_name)
172
+ imported_module = getattr(imported_module, item)
173
+ else:
174
+ imported_module = sys.modules["builtins"]
175
+ try:
176
+ return getattr(imported_module, match.group(2))
177
+ except AttributeError:
178
+ raise AttributeError(
179
+ f"Class {s} not found in built-in modules or namespace '{namespace}'"
180
+ )
181
+ return None
@@ -0,0 +1,48 @@
1
+ [tool.poetry]
2
+ name = "compiler-config"
3
+ version = "0.1.0"
4
+ description = ""
5
+ authors = ["jamie <jfriel@oxfordquantumcircuits.com>"]
6
+ readme = "README.md"
7
+
8
+ packages = [
9
+ { include = "compiler_config" }
10
+ ]
11
+
12
+
13
+ [tool.poetry.dependencies]
14
+ python = ">=3.8.1,<3.13"
15
+
16
+
17
+ [tool.poetry.group.dev.dependencies]
18
+ pytest = "^8.3.2"
19
+ isort = "^5.13.2"
20
+ black = "^24.4.2"
21
+ autoflake = "^2.3.1"
22
+ pip-audit = "^2.7.3"
23
+
24
+
25
+ [tool.autoflake]
26
+ remove-all-unused-imports = true
27
+ ignore-init-module-imports = true
28
+ ignore-pass-after-docstring = true
29
+ in-place = true
30
+ recursive = true
31
+
32
+ [tool.isort]
33
+ py_version = 39
34
+ profile = "black"
35
+ line_length = 92
36
+ known_first_party = ["qat"]
37
+
38
+ [tool.black]
39
+ line-length = 92
40
+ target-version = ["py38", "py39", "py310"]
41
+
42
+ [tool.poetry.scripts]
43
+ format-code = "poetry_scripts:format_code"
44
+
45
+
46
+ [build-system]
47
+ requires = ["poetry-core"]
48
+ build-backend = "poetry.core.masonry.api"