pytweezer.coordinators.rearrangement module¶
Atom-rearrangement coordinator.
Ports the standalone rearrangement_node_server.py (a ZMQ REP server that held
its own camera and talked to the SLM over another socket) into a composite-device
Coordinator. The camera and SLM are now
sub-devices of the same process, so this coordinator drives them with direct
in-process calls: the GPU-computed phase sequence goes straight to
slm.update_mask with no frame ever serialized onto a socket.
Roles in the composite’s "devices" block:
slm— apytweezer.drivers.slm.SLM(update_mask). Required.camera— an ImagEM-X2-style camera (setup_acquisition, set_roi, enable_em_gain, acquire_n_frames, …). Optional: without it the coordinator constructs SLM-only andinitialise()raises, but the phase sequence generation and upload path is fully usable. Benchmarks that only time frame delivery run this way.
Lifecycle over RPC (all synchronous; each stalls the server for its duration, per the coordinator contract):
initialise()— build the GPU phasemask generator, configure the camera, precompute the initial array. Takes the two trap parameter sets (data1/data2, shape(4, N): w, phi, x, y).arm_rearrangement()— grab an image, extract the occupancy mask, then generate the interpolated phase sequence (OptimisationBasedPhasemaskGeneratorGPU.iter_rearrangement_sequence) and upload it to the SLM concurrently, then grab a reset image. Returns the before/after images.test()/status()/shutdown().
Generation and upload are pipelined: the GPU loop pushes each finished frame onto a
bounded queue and a writer thread copies it to the host and DMAs it to the board, so
synthesis of frame n+1 overlaps the upload of frame n. The queue depth
(UPLOAD_QUEUE_DEPTH) bounds how far the GPU may run ahead.
cupy/lap and the heavy GPU math are imported lazily, so this module imports
and status() works on any machine; initialise()/arm_rearrangement()
raise a clear error where the GPU stack is absent.
The remaining timing optimisation — preloading frames into the SLM’s on-board memory
and clocking them out with a hardware trigger (preload_sequence /
start_auto_increment) instead of per-frame software writes — is not wired up:
arm_rearrangement() still plays the sequence software-timed through
run_sequence, and nothing yet arms the SLM’s external trigger or clocks the
frames.
- pytweezer.coordinators.rearrangement.DEFAULT_PHASEMASK = {'blaze_dx_dy_um': (48, -4), 'focal_length_mm': 17.3, 'fresnel_f_mm': 1072, 'input_beam_waist_mm': 16, 'slm_pitch_um': 17, 'slm_res': (1024, 1024), 'wavelength_um': 0.852, 'zernike_coeff_dict': {5: 1.195, 6: 0.725, 7: 0.97, 8: 0.478, 9: -1.091, 10: 0.303, 11: 0.021, 12: 0.072, 13: 0.049}}¶
Default phasemask-generator geometry (the lab’s Rb SLM); overridable via config.
- pytweezer.coordinators.rearrangement.DEFAULT_ROI = [50, 70, 384, 384]¶
Default camera ROI (x0, y0, width, height) if
initialiseisn’t given one.
- class pytweezer.coordinators.rearrangement.Rearrangement(targets, conf)[source]¶
Bases:
CoordinatorCamera + SLM rearrangement loop, run entirely in one process.
- arm_rearrangement()[source]¶
Run one rearrangement and return the
(before, after)camera images.Loads the initial array, grabs an occupancy image, then generates the interpolated phase sequence and uploads it to the SLM concurrently - each frame goes to the board as soon as the GPU produces it - and finally grabs a reset image. Timing breakdown is logged.
Generation and upload overlap, so they are timed together; splitting them would only measure where the pipeline happened to stall.
- camera: ImagEMX2Camera | None¶
- camera_role = 'camera'¶
- initialise(data1, data2, array_shape1, array_shape2, d0, fps, threshold, grid_positions, roi=None, profile='minimum_jerk')[source]¶
Build the phasemask generator, configure the camera, precompute masks.
data1/data2are(4, N)arrays of trap parameters (w, phi, x, y) for the initial and target arrays. Everything GPU-side is kept on the device between here andarm_rearrangement().profilepicks the transport trajectory used by every subsequentarm_rearrangement():"minimum_jerk"(smoother on the atoms) or"linear", which needs 1.875x fewer frames for the samed0and so completes the move in proportionally less time.
- last_first_frame_at¶
time.perf_counter()at which the most recent_play_sequence_pipelined()finished displaying its first frame, orNoneif it never did. Frame 0 must be synthesised, copied to the host and DMA’d before anything reaches the panel, so this splits a pipelined run into time-to-first-frame and the move proper.
- shutdown()[source]¶
Release rearrangement state. The camera/SLM backends close themselves.
- Return type:
None
- slm_role = 'slm'¶
- pytweezer.coordinators.rearrangement.UPLOAD_QUEUE_DEPTH = 5¶
How many generated frames may queue ahead of the SLM before the GPU loop blocks.
- pytweezer.coordinators.rearrangement.USE_SUM_CPP = True¶
Set
Falseto force the numpy occupancy path instead of the C++ extension.