quapp-merlin 0.0.1.dev1__tar.gz → 0.0.1.dev2__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 (41) hide show
  1. {quapp_merlin-0.0.1.dev1/quapp_merlin.egg-info → quapp_merlin-0.0.1.dev2}/PKG-INFO +1 -1
  2. {quapp_merlin-0.0.1.dev1 → quapp_merlin-0.0.1.dev2}/pyproject.toml +1 -1
  3. {quapp_merlin-0.0.1.dev1 → quapp_merlin-0.0.1.dev2}/quapp_merlin/model/device/merlin_device.py +34 -0
  4. {quapp_merlin-0.0.1.dev1 → quapp_merlin-0.0.1.dev2}/quapp_merlin/model/device/merlin_local_device.py +0 -19
  5. {quapp_merlin-0.0.1.dev1 → quapp_merlin-0.0.1.dev2}/quapp_merlin/model/device/merlin_remote_device.py +1 -16
  6. {quapp_merlin-0.0.1.dev1 → quapp_merlin-0.0.1.dev2/quapp_merlin.egg-info}/PKG-INFO +1 -1
  7. {quapp_merlin-0.0.1.dev1 → quapp_merlin-0.0.1.dev2}/quapp_merlin.egg-info/SOURCES.txt +1 -0
  8. quapp_merlin-0.0.1.dev2/tests/test_measurement_kind.py +114 -0
  9. {quapp_merlin-0.0.1.dev1 → quapp_merlin-0.0.1.dev2}/LICENSE +0 -0
  10. {quapp_merlin-0.0.1.dev1 → quapp_merlin-0.0.1.dev2}/README.md +0 -0
  11. {quapp_merlin-0.0.1.dev1 → quapp_merlin-0.0.1.dev2}/quapp_merlin/__init__.py +0 -0
  12. {quapp_merlin-0.0.1.dev1 → quapp_merlin-0.0.1.dev2}/quapp_merlin/component/__init__.py +0 -0
  13. {quapp_merlin-0.0.1.dev1 → quapp_merlin-0.0.1.dev2}/quapp_merlin/component/backend/__init__.py +0 -0
  14. {quapp_merlin-0.0.1.dev1 → quapp_merlin-0.0.1.dev2}/quapp_merlin/component/backend/merlin_invocation.py +0 -0
  15. {quapp_merlin-0.0.1.dev1 → quapp_merlin-0.0.1.dev2}/quapp_merlin/data/__init__.py +0 -0
  16. {quapp_merlin-0.0.1.dev1 → quapp_merlin-0.0.1.dev2}/quapp_merlin/data/merlin_job_spec.py +0 -0
  17. {quapp_merlin-0.0.1.dev1 → quapp_merlin-0.0.1.dev2}/quapp_merlin/error_rules.py +0 -0
  18. {quapp_merlin-0.0.1.dev1 → quapp_merlin-0.0.1.dev2}/quapp_merlin/factory/__init__.py +0 -0
  19. {quapp_merlin-0.0.1.dev1 → quapp_merlin-0.0.1.dev2}/quapp_merlin/factory/merlin_device_factory.py +0 -0
  20. {quapp_merlin-0.0.1.dev1 → quapp_merlin-0.0.1.dev2}/quapp_merlin/factory/merlin_handler_factory.py +0 -0
  21. {quapp_merlin-0.0.1.dev1 → quapp_merlin-0.0.1.dev2}/quapp_merlin/factory/merlin_provider_factory.py +0 -0
  22. {quapp_merlin-0.0.1.dev1 → quapp_merlin-0.0.1.dev2}/quapp_merlin/handler/__init__.py +0 -0
  23. {quapp_merlin-0.0.1.dev1 → quapp_merlin-0.0.1.dev2}/quapp_merlin/handler/invocation_handler.py +0 -0
  24. {quapp_merlin-0.0.1.dev1 → quapp_merlin-0.0.1.dev2}/quapp_merlin/model/__init__.py +0 -0
  25. {quapp_merlin-0.0.1.dev1 → quapp_merlin-0.0.1.dev2}/quapp_merlin/model/device/__init__.py +0 -0
  26. {quapp_merlin-0.0.1.dev1 → quapp_merlin-0.0.1.dev2}/quapp_merlin/model/provider/__init__.py +0 -0
  27. {quapp_merlin-0.0.1.dev1 → quapp_merlin-0.0.1.dev2}/quapp_merlin/model/provider/quandela_merlin_provider.py +0 -0
  28. {quapp_merlin-0.0.1.dev1 → quapp_merlin-0.0.1.dev2}/quapp_merlin/model/provider/quapp_merlin_provider.py +0 -0
  29. {quapp_merlin-0.0.1.dev1 → quapp_merlin-0.0.1.dev2}/quapp_merlin/util/__init__.py +0 -0
  30. {quapp_merlin-0.0.1.dev1 → quapp_merlin-0.0.1.dev2}/quapp_merlin/util/single_machine.py +0 -0
  31. {quapp_merlin-0.0.1.dev1 → quapp_merlin-0.0.1.dev2}/quapp_merlin/util/tensors.py +0 -0
  32. {quapp_merlin-0.0.1.dev1 → quapp_merlin-0.0.1.dev2}/quapp_merlin.egg-info/dependency_links.txt +0 -0
  33. {quapp_merlin-0.0.1.dev1 → quapp_merlin-0.0.1.dev2}/quapp_merlin.egg-info/requires.txt +0 -0
  34. {quapp_merlin-0.0.1.dev1 → quapp_merlin-0.0.1.dev2}/quapp_merlin.egg-info/top_level.txt +0 -0
  35. {quapp_merlin-0.0.1.dev1 → quapp_merlin-0.0.1.dev2}/setup.cfg +0 -0
  36. {quapp_merlin-0.0.1.dev1 → quapp_merlin-0.0.1.dev2}/tests/test_error_logging.py +0 -0
  37. {quapp_merlin-0.0.1.dev1 → quapp_merlin-0.0.1.dev2}/tests/test_error_rules.py +0 -0
  38. {quapp_merlin-0.0.1.dev1 → quapp_merlin-0.0.1.dev2}/tests/test_job_spec.py +0 -0
  39. {quapp_merlin-0.0.1.dev1 → quapp_merlin-0.0.1.dev2}/tests/test_lazy_sdk_imports.py +0 -0
  40. {quapp_merlin-0.0.1.dev1 → quapp_merlin-0.0.1.dev2}/tests/test_single_machine.py +0 -0
  41. {quapp_merlin-0.0.1.dev1 → quapp_merlin-0.0.1.dev2}/tests/test_tensors.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: quapp-merlin
3
- Version: 0.0.1.dev1
3
+ Version: 0.0.1.dev2
4
4
  Summary: Quapp common library supporting Quapp Platform for Quantum Computing
5
5
  Author-email: "CITYNOW Co. Ltd. " <corp@citynow.vn>
6
6
  License: The MIT License (MIT)
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "quapp-merlin"
7
- version = "0.0.1.dev1"
7
+ version = "0.0.1.dev2"
8
8
  description = "Quapp common library supporting Quapp Platform for Quantum Computing"
9
9
  readme = "README.md"
10
10
  authors = [{ name = "CITYNOW Co. Ltd. ", email = "corp@citynow.vn" }]
@@ -110,6 +110,40 @@ class MerlinDevice(CustomDevice, ABC):
110
110
 
111
111
  # ------------------------------------------------------------- helpers
112
112
 
113
+ # A MeasurementStrategy keeps its kind in `.type`, a MeasurementKind enum
114
+ # (PROBABILITIES / MODE_EXPECTATIONS / AMPLITUDES / PARTIAL). It has no
115
+ # `.name` of its own -- the first version of this read
116
+ # type(strategy).__name__ instead, which is always 'MeasurementStrategy',
117
+ # so nothing ever matched 'probs' and no job ever produced a histogram.
118
+ MEASUREMENT_KINDS = {'PROBABILITIES' : 'probs',
119
+ 'MODE_EXPECTATIONS' : 'mode_expectations',
120
+ 'AMPLITUDES' : 'amplitudes',
121
+ 'PARTIAL' : 'partial'}
122
+
123
+ def _measurement_name(self, model) -> str | None:
124
+ """Which measurement the quantum layer uses, as a short string.
125
+
126
+ Only used to decide whether a histogram makes sense, so anything
127
+ unrecognised degrades to None rather than raising.
128
+ """
129
+ modules = model.modules() if hasattr(model, 'modules') else [model]
130
+
131
+ for module in modules:
132
+ strategy = getattr(module, 'measurement_strategy', None)
133
+ if strategy is None:
134
+ continue
135
+
136
+ kind = getattr(strategy, 'type', None)
137
+ name = (getattr(kind, 'name', None) or getattr(kind, 'value', None)
138
+ or kind)
139
+ if name is None:
140
+ return None
141
+
142
+ name = str(name).upper()
143
+ return self.MEASUREMENT_KINDS.get(name, name.lower())
144
+
145
+ return None
146
+
113
147
  def _histogram_from(self, output, shots, batch_size, measurement) -> dict | None:
114
148
  """Bin a single-row probability output into a state -> count histogram.
115
149
 
@@ -174,22 +174,3 @@ class MerlinLocalDevice(MerlinDevice):
174
174
 
175
175
  return output, {'epochs': epochs, 'loss': tensors.plain(history),
176
176
  'final_loss': history[-1] if history else None}
177
-
178
- @staticmethod
179
- def _measurement_name(model) -> str | None:
180
- """Which MeasurementStrategy the quantum layer uses, as a short string.
181
-
182
- Only used to decide whether a histogram makes sense, so an unknown shape
183
- degrades to None instead of raising.
184
- """
185
- for module in (model.modules() if hasattr(model, 'modules') else [model]):
186
- strategy = getattr(module, 'measurement_strategy', None)
187
- if strategy is None:
188
- continue
189
- name = getattr(strategy, 'name', None) or type(strategy).__name__
190
- name = str(name).lower()
191
- for known in ('probs', 'probabilit', 'expectation', 'amplitude'):
192
- if known in name:
193
- return 'probs' if known.startswith('prob') else known
194
- return name
195
- return None
@@ -29,8 +29,7 @@ from quapp_common.model.provider.provider import Provider
29
29
 
30
30
  from .merlin_device import MerlinDevice
31
31
  from ...util import tensors
32
- from ...util.single_machine import (DEFAULT_MAX_BATCH, batch_size_of,
33
- quantum_leaves, reject_foreign_backend,
32
+ from ...util.single_machine import (DEFAULT_MAX_BATCH, reject_foreign_backend,
34
33
  require_batch_fits, require_single_leaf)
35
34
 
36
35
  # How long we are willing to hold the invocation open waiting for the cloud.
@@ -148,17 +147,3 @@ class MerlinRemoteDevice(MerlinDevice):
148
147
  return ','.join(str(job_id) for job_id in job_ids)
149
148
 
150
149
  return str(job_ids[0])
151
-
152
- @staticmethod
153
- def _measurement_name(model) -> str | None:
154
- leaves = quantum_leaves(model)
155
- for _, leaf in leaves:
156
- strategy = getattr(leaf, 'measurement_strategy', None)
157
- if strategy is None:
158
- continue
159
- name = str(getattr(strategy, 'name', None)
160
- or type(strategy).__name__).lower()
161
- if name.startswith('prob') or 'probabilit' in name:
162
- return 'probs'
163
- return name
164
- return None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: quapp-merlin
3
- Version: 0.0.1.dev1
3
+ Version: 0.0.1.dev2
4
4
  Summary: Quapp common library supporting Quapp Platform for Quantum Computing
5
5
  Author-email: "CITYNOW Co. Ltd. " <corp@citynow.vn>
6
6
  License: The MIT License (MIT)
@@ -34,5 +34,6 @@ tests/test_error_logging.py
34
34
  tests/test_error_rules.py
35
35
  tests/test_job_spec.py
36
36
  tests/test_lazy_sdk_imports.py
37
+ tests/test_measurement_kind.py
37
38
  tests/test_single_machine.py
38
39
  tests/test_tensors.py
@@ -0,0 +1,114 @@
1
+ # Quapp Platform Project
2
+ # test_measurement_kind.py
3
+ # Copyright © CITYNOW Co. Ltd. All rights reserved.
4
+
5
+ """Reading the measurement kind off a MerLin strategy.
6
+
7
+ This decides whether a job gets a histogram, and the first version got it
8
+ wrong in a way nothing would have noticed: MeasurementStrategy has no `.name`,
9
+ so `type(strategy).__name__` was read instead -- always the string
10
+ 'MeasurementStrategy' -- nothing ever matched 'probs', and `_histogram_from`
11
+ returned None for every job ever run.
12
+
13
+ The stand-ins mirror merlinquantum 0.4.1 exactly: `MeasurementStrategy.type` is
14
+ a `MeasurementKind` enum whose members are PROBABILITIES, MODE_EXPECTATIONS,
15
+ AMPLITUDES and PARTIAL (merlin/measurement/strategies.py:244).
16
+ """
17
+
18
+ import enum
19
+
20
+ import pytest
21
+
22
+ from quapp_merlin.model.device.merlin_device import MerlinDevice
23
+
24
+
25
+ class MeasurementKind(enum.Enum):
26
+ PROBABILITIES = 'PROBABILITIES'
27
+ MODE_EXPECTATIONS = 'MODE_EXPECTATIONS'
28
+ AMPLITUDES = 'AMPLITUDES'
29
+ PARTIAL = 'PARTIAL'
30
+
31
+
32
+ class MeasurementStrategy:
33
+ """No `.name` -- exactly like the real one. The kind lives in `.type`."""
34
+
35
+ def __init__(self, kind):
36
+ self.type = kind
37
+
38
+
39
+ class Module:
40
+ def __init__(self, children=None, **attributes):
41
+ self._children = children or {}
42
+ self.__dict__.update(attributes)
43
+
44
+ def modules(self):
45
+ yield self
46
+ for child in self._children.values():
47
+ yield from child.modules()
48
+
49
+
50
+ def read(model):
51
+ """_measurement_name does not touch instance state, so call it unbound."""
52
+ return MerlinDevice._measurement_name(MerlinDevice, model)
53
+
54
+
55
+ @pytest.mark.parametrize('kind, expected', [
56
+ (MeasurementKind.PROBABILITIES, 'probs'),
57
+ (MeasurementKind.MODE_EXPECTATIONS, 'mode_expectations'),
58
+ (MeasurementKind.AMPLITUDES, 'amplitudes'),
59
+ (MeasurementKind.PARTIAL, 'partial'),
60
+ ])
61
+ def test_every_kind_is_recognised(kind, expected):
62
+ model = Module(measurement_strategy=MeasurementStrategy(kind))
63
+
64
+ assert read(model) == expected
65
+
66
+
67
+ def test_probabilities_is_what_enables_a_histogram():
68
+ """The whole point: 'probs' is the only value _histogram_from accepts."""
69
+ model = Module(measurement_strategy=MeasurementStrategy(
70
+ MeasurementKind.PROBABILITIES))
71
+
72
+ assert read(model) == 'probs'
73
+
74
+
75
+ def test_the_strategy_is_found_on_a_nested_layer():
76
+ """simple() returns a SimpleSequential wrapper; the strategy sits on the
77
+ QuantumLayer inside it."""
78
+ layer = Module(measurement_strategy=MeasurementStrategy(
79
+ MeasurementKind.PROBABILITIES))
80
+ model = Module(children={'quantum_layer': layer})
81
+
82
+ assert read(model) == 'probs'
83
+
84
+
85
+ def test_class_name_is_never_used_as_the_kind():
86
+ """The regression: reading type(strategy).__name__ gave
87
+ 'measurementstrategy', which matches no kind and silently killed the
88
+ histogram."""
89
+ model = Module(measurement_strategy=MeasurementStrategy(
90
+ MeasurementKind.PROBABILITIES))
91
+
92
+ assert read(model) != 'measurementstrategy'
93
+
94
+
95
+ def test_no_strategy_at_all():
96
+ assert read(Module()) is None
97
+
98
+
99
+ def test_unknown_kind_degrades_instead_of_raising():
100
+ class Odd(enum.Enum):
101
+ SOMETHING_NEW = 'SOMETHING_NEW'
102
+
103
+ model = Module(measurement_strategy=MeasurementStrategy(Odd.SOMETHING_NEW))
104
+
105
+ assert read(model) == 'something_new'
106
+
107
+
108
+ def test_a_bare_module_without_modules_still_works():
109
+ """A handler may return the layer itself rather than a wrapper."""
110
+
111
+ class Bare:
112
+ measurement_strategy = MeasurementStrategy(MeasurementKind.AMPLITUDES)
113
+
114
+ assert read(Bare()) == 'amplitudes'