Skip to content

Commit 9851a51

Browse files
committed
experiment.subscan: Flip run_once() @kernel default; add core device tests
The flipped default make multiple subscans in the same kernel compile, as run_once() then gets monomorphised in each class.
1 parent 020c1b1 commit 9851a51

File tree

2 files changed

+215
-6
lines changed

2 files changed

+215
-6
lines changed

ndscan/experiment/subscan.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -544,8 +544,8 @@ def build_fragment(
544544
save_results_by_default,
545545
expose_analysis_results,
546546
)
547-
if is_kernel(scanned_fragment.run_once):
548-
self.run_once = self._kernel_run_once
547+
if not is_kernel(scanned_fragment.run_once):
548+
self.run_once = self._subscan.acquire
549549

550550
def configure(
551551
self,
@@ -583,6 +583,7 @@ def host_cleanup(self):
583583
self._scanned_fragment.host_cleanup()
584584
super().host_cleanup()
585585

586+
@kernel
586587
def run_once(self) -> None:
587588
"""Execute the subscan as previously configured.
588589
@@ -593,7 +594,3 @@ def run_once(self) -> None:
593594
well.
594595
"""
595596
self._subscan.acquire()
596-
597-
@kernel
598-
def _kernel_run_once(self):
599-
self._subscan.acquire()

test/test_experiment_kernel.py

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from dataclasses import dataclass
1111
from enum import Enum, unique
1212

13+
import numpy as np
1314
from artiq.language import kernel
1415
from emulator_environment import KernelEmulatorCase
1516
from fixtures import TrivialKernelFragment
@@ -24,6 +25,8 @@
2425
StringParam,
2526
)
2627
from ndscan.experiment.result_channels import FloatChannel, IntChannel, OpaqueChannel
28+
from ndscan.experiment.scan_generator import LinearGenerator, ListGenerator
29+
from ndscan.experiment.subscan import SubscanExpFragment, setattr_subscan
2730
from ndscan.utils import SCHEMA_REVISION, SCHEMA_REVISION_KEY
2831

2932

@@ -164,3 +167,212 @@ def d(key):
164167
Counter(d(f"points.channel_{scan_def.param_name}_result")),
165168
Counter({k: num_repeats for k in scan_def.result_values}),
166169
)
170+
171+
172+
# # #
173+
174+
175+
class Inner(ExpFragment):
176+
def build_fragment(self) -> None:
177+
self.setattr_device("core")
178+
self.setattr_param("param_float", FloatParam, "Float param", default=0.0)
179+
self.setattr_param("param_int", IntParam, "Int param", default=0.0)
180+
self.setattr_result("result", FloatChannel)
181+
182+
@kernel
183+
def run_once(self) -> None:
184+
self.result.push(self.param_float.get() + self.param_int.get())
185+
186+
187+
INT_GEN = ListGenerator([-1, 0, 1], randomise_order=False)
188+
FLOAT_GEN = LinearGenerator(-1.0, 1.0, 11, randomise_order=False)
189+
190+
191+
class FloatSetattrSubscan(ExpFragment):
192+
def build_fragment(self):
193+
self.setattr_fragment("inner", Inner)
194+
setattr_subscan(self, "float_scan", self.inner, [(self.inner, "param_float")])
195+
196+
def host_setup(self):
197+
self.float_scan.set_scan_spec([(self.inner.param_float, FLOAT_GEN)])
198+
super().host_setup()
199+
200+
@kernel
201+
def run_once(self) -> None:
202+
self.float_scan.acquire()
203+
204+
205+
class IntSetattrSubscan(ExpFragment):
206+
def build_fragment(self):
207+
self.setattr_fragment("inner", Inner)
208+
setattr_subscan(self, "int_scan", self.inner, [(self.inner, "param_int")])
209+
210+
def host_setup(self):
211+
self.int_scan.set_scan_spec([(self.inner.param_int, INT_GEN)])
212+
213+
@kernel
214+
def run_once(self) -> None:
215+
self.int_scan.acquire()
216+
217+
218+
class SetattrParent(ExpFragment):
219+
def build_fragment(self) -> None:
220+
self.setattr_fragment("int_frag", IntSetattrSubscan)
221+
self.setattr_fragment("float_frag", FloatSetattrSubscan)
222+
223+
@kernel
224+
def run_once(self):
225+
self.int_frag.run_once()
226+
self.float_frag.run_once()
227+
228+
229+
SetattrParentScan = make_fragment_scan_exp(SetattrParent)
230+
231+
232+
class FloatSubscanExpFragment(SubscanExpFragment):
233+
pass
234+
235+
236+
class FloatFragmentSubscan(ExpFragment):
237+
def build_fragment(self):
238+
self.setattr_fragment("inner", Inner)
239+
self.setattr_fragment(
240+
"scan",
241+
FloatSubscanExpFragment,
242+
self,
243+
self.inner,
244+
[(self.inner, "param_float")],
245+
)
246+
247+
def host_setup(self):
248+
self.scan.configure([(self.inner.param_float, FLOAT_GEN)])
249+
super().host_setup()
250+
251+
@kernel
252+
def run_once(self) -> None:
253+
self.scan.run_once()
254+
255+
256+
class IntSubscanExpFragment(SubscanExpFragment):
257+
pass
258+
259+
260+
class IntFragmentSubscan(ExpFragment):
261+
def build_fragment(self):
262+
self.setattr_fragment("inner", Inner)
263+
self.setattr_fragment(
264+
"scan",
265+
IntSubscanExpFragment,
266+
self,
267+
self.inner,
268+
[(self.inner, "param_int")],
269+
)
270+
271+
def host_setup(self):
272+
self.scan.configure([(self.inner.param_int, INT_GEN)])
273+
274+
@kernel
275+
def run_once(self) -> None:
276+
self.scan.run_once()
277+
278+
279+
class FragmentSubscanParent(ExpFragment):
280+
def build_fragment(self) -> None:
281+
self.setattr_fragment("int_frag", IntFragmentSubscan)
282+
self.setattr_fragment("float_frag", FloatFragmentSubscan)
283+
284+
@kernel
285+
def run_once(self):
286+
self.int_frag.run_once()
287+
self.float_frag.run_once()
288+
289+
290+
FragmentSubscanParentScan = make_fragment_scan_exp(FragmentSubscanParent)
291+
292+
293+
class FloatSubclassSubscan(SubscanExpFragment):
294+
def build_fragment(self):
295+
self.setattr_fragment("inner", Inner)
296+
super().build_fragment(
297+
self,
298+
self.inner,
299+
[(self.inner, "param_float")],
300+
)
301+
302+
def host_setup(self):
303+
self.configure([(self.inner.param_float, FLOAT_GEN)])
304+
super().host_setup()
305+
306+
307+
class IntSubclassSubscan(SubscanExpFragment):
308+
def build_fragment(self):
309+
self.setattr_fragment("inner", Inner)
310+
super().build_fragment(
311+
self,
312+
self.inner,
313+
[(self.inner, "param_int")],
314+
)
315+
316+
def host_setup(self):
317+
self.configure([(self.inner.param_int, INT_GEN)])
318+
super().host_setup()
319+
320+
321+
class SubclassSubscanParent(ExpFragment):
322+
def build_fragment(self) -> None:
323+
self.setattr_fragment("int_frag", IntSubclassSubscan)
324+
self.setattr_fragment("float_frag", FloatSubclassSubscan)
325+
326+
@kernel
327+
def run_once(self):
328+
self.int_frag.run_once()
329+
self.float_frag.run_once()
330+
331+
332+
SubclassSubscanParentScan = make_fragment_scan_exp(SubclassSubscanParent)
333+
334+
335+
class TestSubscanKernelCase(KernelEmulatorCase):
336+
def _test_subscan(self, cls, fragment_fqn, float_channel_name, int_channel_name):
337+
exp = self.create(cls)
338+
exp.prepare()
339+
exp.run()
340+
341+
def d(key):
342+
return self.dataset_db.get("ndscan.rid_0." + key)
343+
344+
self.assertEqual(d(SCHEMA_REVISION_KEY), SCHEMA_REVISION)
345+
self.assertEqual(d("completed"), True)
346+
self.assertEqual(d("fragment_fqn"), fragment_fqn)
347+
self.assertEqual(d("source_id"), "rid_0")
348+
349+
np.testing.assert_array_max_ulp(
350+
d(f"point.{float_channel_name}"), FLOAT_GEN.points_for_level(0)
351+
)
352+
np.testing.assert_array_max_ulp(
353+
d(f"point.{int_channel_name}"), INT_GEN.points_for_level(0)
354+
)
355+
356+
def test_setattr_subscan(self):
357+
self._test_subscan(
358+
SetattrParentScan,
359+
"test_experiment_kernel.SetattrParent",
360+
"float_scan_channel_result",
361+
"int_scan_channel_result",
362+
)
363+
364+
def test_fragment_subscan(self):
365+
self._test_subscan(
366+
FragmentSubscanParentScan,
367+
"test_experiment_kernel.FragmentSubscanParent",
368+
"float_frag_scan__channel_result",
369+
"int_frag_scan__channel_result",
370+
)
371+
372+
def test_subclass_subscan(self):
373+
self._test_subscan(
374+
SubclassSubscanParentScan,
375+
"test_experiment_kernel.SubclassSubscanParent",
376+
"float_frag__channel_result",
377+
"int_frag__channel_result",
378+
)

0 commit comments

Comments
 (0)