pytesdaqx 0.2.0__tar.gz → 0.2.3__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.
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/PKG-INFO +1 -1
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/docs/developer/data_io.md +44 -9
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/docs/user/cli/data_inspection.md +2 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/docs/user/configuration/acquisition_config.md +50 -4
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/docs/user/configuration/run_setup_yaml.md +27 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/docs/user/configuration/scans.md +2 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/examples/README.md +15 -0
- pytesdaqx-0.2.3/examples/configuration/blinded/acquisition_blinded_75_25.yaml +117 -0
- pytesdaqx-0.2.3/examples/configuration/blinded/generate_blinded_sequence.py +165 -0
- pytesdaqx-0.2.3/examples/configuration/vibration/hardware_fake.yaml +15 -0
- pytesdaqx-0.2.3/examples/configuration/vibration/run_setup.yaml +45 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pyproject.toml +1 -1
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/cli/view_traces.py +2 -1
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/config/resolver.py +37 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/config/schema.py +32 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/io/hdf5_reader.py +10 -9
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/io/metadata_compat.py +70 -11
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/io/raw_data_writer.py +30 -2
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/io/zarr_reader.py +19 -14
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/naming.py +44 -1
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/recorder/hdf5.py +4 -1
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/recorder/zarr.py +62 -4
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/run/yaml_run.py +214 -16
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/services/tes_bias.py +17 -1
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx.egg-info/PKG-INFO +1 -1
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx.egg-info/SOURCES.txt +9 -0
- pytesdaqx-0.2.3/tests/test_accelerometer_readout.py +175 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/tests/test_feb_signal_generator_didv.py +21 -0
- pytesdaqx-0.2.3/tests/test_finite_record_metadata.py +89 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/tests/test_raw_data_writer.py +30 -0
- pytesdaqx-0.2.3/tests/test_read_block_defaults.py +123 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/tests/test_repository_layout.py +1 -0
- pytesdaqx-0.2.3/tests/test_stream_id_uniqueness.py +167 -0
- pytesdaqx-0.2.3/tests/test_unit_conversion.py +80 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/tests/test_zarr_chunking_and_sharding.py +48 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/MANIFEST.in +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/README.md +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/docs/developer/acquisition_pipeline.md +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/docs/developer/adding_hardware.md +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/docs/developer/architecture.md +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/docs/developer/configuration_pipeline.md +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/docs/developer/hardware_support.md +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/docs/developer/index.md +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/docs/developer/package_structure.md +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/docs/developer/release_process.md +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/docs/developer/testing.md +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/docs/index.md +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/docs/installation/index.md +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/docs/installation/rocky_linux_9.md +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/docs/installation/troubleshooting.md +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/docs/installation/ubuntu.md +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/docs/installation/validation.md +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/docs/installation/visa.md +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/docs/user/cli/acquisition.md +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/docs/user/cli/index.md +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/docs/user/cli/instrument_control.md +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/docs/user/cli/lab_diagnostics.md +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/docs/user/configuration/hardware_yaml.md +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/docs/user/configuration/index.md +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/docs/user/configuration/resolution.md +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/docs/user/configuration/temperature_controllers.md +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/docs/user/data_format.md +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/docs/user/quickstart.md +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/docs/user/terminology.md +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/docs/user/troubleshooting.md +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/docs/user/workflows.md +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/examples/configuration/basic/acquisition_background.yaml +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/examples/configuration/basic/acquisition_didv_background.yaml +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/examples/configuration/basic/acquisition_notebook_demo.yaml +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/examples/configuration/basic/hardware_fake.yaml +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/examples/configuration/basic/hardware_feb_signal_generator.yaml +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/examples/configuration/basic/run_setup.yaml +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/examples/configuration/calibration/acquisition_laser.yaml +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/examples/configuration/pxi_loopback/README.md +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/examples/configuration/pxi_loopback/acquisition_didv.yaml +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/examples/configuration/pxi_loopback/acquisition_iv_restore.yaml +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/examples/configuration/scans/acquisition_didv_ac.yaml +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/examples/configuration/scans/acquisition_didv_frequency_focus.yaml +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/examples/configuration/scans/acquisition_temperature_fake.yaml +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/examples/configuration/scans/acquisition_tes_bias_iv_didv.yaml +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/examples/configuration/scans/acquisition_transducer_vibration_scan.yaml +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/examples/configuration/temperature/hardware_lakeshore372.yaml +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/examples/configuration/temperature/hardware_macrt.yaml +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/examples/configuration/temperature/run_setup_lakeshore372.yaml +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/examples/configuration/temperature/run_setup_macrt.yaml +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/examples/simulation/README.md +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/examples/simulation/write_simulated_zarr.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/mkdocs.yml +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/__init__.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/acquisition/__init__.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/acquisition/fake.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/acquisition/nidaq_ai.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/acquisition/segment.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/acquisition/session.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/acquisition/streaming.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/analysis/__init__.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/analysis/continuity.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/cli/__init__.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/cli/build_manifest.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/cli/display_metadata.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/cli/laser.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/cli/run.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/cli/signal_generator.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/cli/squid.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/cli/temperature.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/cli/tes_bias.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/cli/transducer.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/cli/validate_config.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/config/__init__.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/config/durations.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/config/models.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/config/scan_values.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/config/temperature.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/config/validators.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/control/__init__.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/control/coordinator.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/hardware/__init__.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/hardware/base.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/hardware/factory.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/hardware/legacy.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/hardware/registry.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/instruments/__init__.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/instruments/base.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/instruments/builtins.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/instruments/communication.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/instruments/external.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/instruments/remote.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/instruments/signal_generator/__init__.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/instruments/signal_generator/agilent33500B.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/instruments/signal_generator/keithley2400.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/instruments/signal_generator/keysightDSOX1200.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/instruments/signal_generator/rigoldg800.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/instruments/temperature_controller/__init__.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/instruments/temperature_controller/base.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/instruments/temperature_controller/lakeshore.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/instruments/temperature_controller/macrt.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/instruments/temperature_controller/macrt_module.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/instruments/tes_squid/__init__.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/instruments/tes_squid/feb.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/instruments/tes_squid/magnicon.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/io/__init__.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/io/acquisition_catalog.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/io/catalog.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/io/hdf5_layout.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/io/raw_data_reader.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/io/reader_utils.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/io/stream_reader.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/io/zarr_utils.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/processing/__init__.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/processing/downsample.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/recorder/__init__.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/recorder/async_writer.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/recorder/base.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/run/__init__.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/run/runner.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/sequencer/__init__.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/sequencer/scan.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/sequencer/sequencer.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/services/__init__.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/services/laser.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/services/signal_generator_load.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/services/squid.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/services/temperature.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/services/transducer.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/testing/__init__.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/testing/analyze_continuity.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/testing/lab_check.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/testing/ni_cli_common.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/testing/real_ni_benchmark.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/testing/real_ni_continuity_test.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/testing/real_ni_run_continuous.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/testing/real_ni_smoke.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx/testing/real_ni_trigger_test.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx.egg-info/dependency_links.txt +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx.egg-info/entry_points.txt +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx.egg-info/requires.txt +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/pytesdaqx.egg-info/top_level.txt +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/setup.cfg +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/tests/test_acquisition_catalog_api_cleanup.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/tests/test_acquisition_catalog_naming.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/tests/test_api_cleanup.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/tests/test_cli_contract.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/tests/test_code_cleanup_regressions.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/tests/test_detector_settings_normalization.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/tests/test_developer_documentation.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/tests/test_distribution_readiness.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/tests/test_facility_catalog_metadata.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/tests/test_feb_driver.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/tests/test_io_coordinate_cleanup.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/tests/test_lakeshore372_driver.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/tests/test_laser_configuration_ownership.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/tests/test_macrt_driver.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/tests/test_magnicon_driver.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/tests/test_manifest_and_zarr_metadata.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/tests/test_metadata_canonical_schema.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/tests/test_optional_hdf5_import.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/tests/test_partition_planning.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/tests/test_release_cleanup.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/tests/test_run_cli_cleanup.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/tests/test_scan_documentation.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/tests/test_scan_values.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/tests/test_signal_generator_cli.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/tests/test_signal_generator_drivers.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/tests/test_signal_generator_load_policy.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/tests/test_stream_reader_api_cleanup.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/tests/test_temperature_acquisition_integration.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/tests/test_temperature_cli.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/tests/test_temperature_configuration.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/tests/test_temperature_safety_cleanup.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/tests/test_temperature_service.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/tests/test_transducer_configuration_ownership.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/tests/test_user_documentation.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/tests/test_view_trace_partitions.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/tests/test_zarr_compression.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/tests/test_zarr_default_and_naming.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/tests/test_zarr_manifest_sidecars.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/tests/test_zarr_metadata_layout.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/tests/test_zarr_native_backend.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/tests/test_zarr_read_block_terminology.py +0 -0
- {pytesdaqx-0.2.0 → pytesdaqx-0.2.3}/tests/test_zarr_trace_labels.py +0 -0
|
@@ -25,6 +25,7 @@ Important behavior:
|
|
|
25
25
|
- Zarr v3 group and array metadata.
|
|
26
26
|
- Continuous array shape `[channel, sample]`.
|
|
27
27
|
- Finite array shape `[trace, channel, sample]`.
|
|
28
|
+
- Finite `trace_time[trace]` array of float64 Unix acquisition timestamps.
|
|
28
29
|
- Native zarr-python writing.
|
|
29
30
|
- Optional compression and sharding configuration.
|
|
30
31
|
- Stream metadata updated as read blocks or traces are appended.
|
|
@@ -32,26 +33,60 @@ Important behavior:
|
|
|
32
33
|
|
|
33
34
|
Zarr does not use HDF5 file rotation. Recorder options such as `segments_per_file` are ignored for Zarr by the run controller.
|
|
34
35
|
|
|
36
|
+
Finite-record timestamps are copied from `SegmentMetadata.segment_timestamp`
|
|
37
|
+
to `trace_time`, preserving gaps between triggers. An absent timestamp is NaN;
|
|
38
|
+
the recorder does not substitute its write time. `get_trace()` and
|
|
39
|
+
`StreamReader` return the selected value as `trace_time`. Older stores without
|
|
40
|
+
this array remain readable and omit the unavailable timestamp. The compact
|
|
41
|
+
timestamp array is separate from waveform chunking/sharding and is not a JSONL
|
|
42
|
+
sidecar.
|
|
43
|
+
|
|
44
|
+
The NI run controller records `pretrigger_length_samples: 0` for finite
|
|
45
|
+
acquisitions, whose digital/analog triggers start acquisition rather than
|
|
46
|
+
referencing a pretrigger buffer. Historical pretrigger metadata is preserved
|
|
47
|
+
by the readers; consumers must not assume that an arbitrary finite record is
|
|
48
|
+
centered on its trigger.
|
|
49
|
+
|
|
35
50
|
## Compression and chunking defaults
|
|
36
51
|
|
|
37
52
|
`ZarrRecorder` defaults to zstd compression (level 9) and to sharded arrays for
|
|
38
53
|
both shapes:
|
|
39
54
|
|
|
40
|
-
- **Continuous** (`channel_sample`):
|
|
41
|
-
(
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
count low for filesystems like NERSC/CC-IN2P3 that penalize very large
|
|
45
|
-
numbers of small files.
|
|
55
|
+
- **Continuous** (`channel_sample`): small chunks keep partial reads cheap
|
|
56
|
+
(decompression cost is paid per chunk touched, not per array), while
|
|
57
|
+
sharding keeps the on-disk object count low for filesystems like
|
|
58
|
+
NERSC/CC-IN2P3 that penalize very large numbers of small files.
|
|
46
59
|
- **Finite/trace** (`trace_channel_sample`): one trace per chunk, sharded
|
|
47
60
|
toward a 10s-equivalent target (`trace_sharding=True`,
|
|
48
61
|
`trace_shard_target_duration_s=10.0`).
|
|
49
62
|
|
|
63
|
+
The continuous geometry has two layers, because the useful sizes depend on
|
|
64
|
+
the acquisition and only the run layer knows it:
|
|
65
|
+
|
|
66
|
+
| | `ZarrRecorder` defaults | resolved by `pytesdaqx-run` from the YAML |
|
|
67
|
+
|---|---|---|
|
|
68
|
+
| chunk | `1s` | `0.25s`, grown towards 1 MiB at low data rates (capped 10s), then shortened to divide one read block |
|
|
69
|
+
| shard | `10s` | one read block: the longest block up to 60s that divides the step duration |
|
|
70
|
+
| shard bytes | 128 MiB | 8 GiB, so a whole block is never clamped |
|
|
71
|
+
| chunks per shard | `max_chunks_per_shard=100` | `continuous_max_chunks_per_shard` (high): one block fills a shard in a single write |
|
|
72
|
+
|
|
73
|
+
Constructing `ZarrRecorder` directly (including through `RawDataWriter`
|
|
74
|
+
without recorder options) therefore keeps the older 1s/10s geometry; pass
|
|
75
|
+
the options explicitly to match a run. See
|
|
76
|
+
[acquisition configuration](../user/configuration/acquisition_config.md)
|
|
77
|
+
for the user-facing rules.
|
|
78
|
+
|
|
79
|
+
A shard is rewritten whenever part of it is written, so a read block shorter
|
|
80
|
+
than the shard multiplies disk writes (measured: 5.5x for 1s blocks in 10s
|
|
81
|
+
shards, 30x for 1s blocks in 60s shards, at 3 channels and 1.25 MHz). Keeping
|
|
82
|
+
shard == read block writes each shard exactly once.
|
|
83
|
+
|
|
50
84
|
Both shard-sizing paths (`_continuous_chunking_metadata` and
|
|
51
85
|
`_trace_shard_traces` in `pytesdaqx/recorder/zarr.py`) apply a third
|
|
52
|
-
constraint, `max_chunks_per_shard
|
|
53
|
-
|
|
54
|
-
|
|
86
|
+
constraint, `max_chunks_per_shard`, in addition to the duration and
|
|
87
|
+
byte-size targets: `shard_chunk_count = min(by_duration, by_bytes,
|
|
88
|
+
max_chunks_per_shard)`. A chunk or shard reduced by any of these limits
|
|
89
|
+
emits a warning naming the binding one.
|
|
55
90
|
|
|
56
91
|
This cap exists because of a measured defect in zarr-python 3.1.5: the
|
|
57
92
|
per-write cost of an incremental `resize()` + append into a sharded array
|
|
@@ -110,6 +110,8 @@ pytesdaqx-view-traces acquisition_dir \
|
|
|
110
110
|
|
|
111
111
|
The default display units are detector amps when the required readout-circuit metadata are available. Other supported choices include ADC volts or raw ADC values, depending on stored read mode and CLI options.
|
|
112
112
|
|
|
113
|
+
Accelerometer channels can be displayed in g with `--units g`; this requires `readout_circuit.amplifier_gain` (and optionally `sensitivity_v_per_g`) in the run setup. See [Accelerometer readout](../configuration/run_setup_yaml.md#accelerometer-readout).
|
|
114
|
+
|
|
113
115
|
If amp conversion fails, inspect the channel `readout_circuit` values in stored metadata, especially output gain and resistance/turn-ratio fields.
|
|
114
116
|
|
|
115
117
|
### Plot decimation
|
|
@@ -53,9 +53,36 @@ sequence:
|
|
|
53
53
|
duration: 15m
|
|
54
54
|
```
|
|
55
55
|
|
|
56
|
+
### A step records whole read blocks
|
|
57
|
+
|
|
58
|
+
Acquisition always records complete read blocks, so `sequence[].duration` is
|
|
59
|
+
rounded **up** to a whole number of them. With a 60-second read block a
|
|
60
|
+
20-second step records 60 seconds of data, not 20. Keep the step duration a
|
|
61
|
+
multiple of the read-block duration; pytesdaqx warns when it is not, naming
|
|
62
|
+
the amount of data that will actually be taken.
|
|
63
|
+
|
|
56
64
|
### Automatic read-block duration
|
|
57
65
|
|
|
58
|
-
`read_block_duration` is optional. When omitted, pytesdaqx
|
|
66
|
+
`read_block_duration` is optional. When omitted, pytesdaqx uses the longest
|
|
67
|
+
block up to 60 seconds that divides the step duration exactly, so that a step
|
|
68
|
+
never takes more data than requested:
|
|
69
|
+
|
|
70
|
+
| Step duration | Automatic read block | Blocks recorded |
|
|
71
|
+
|---|---|---|
|
|
72
|
+
| 3 min | 60 s | 3 |
|
|
73
|
+
| 100 s | 50 s | 2 |
|
|
74
|
+
| 20 s | 20 s | 1 |
|
|
75
|
+
| 1 s (scan point) | 1 s | 1 |
|
|
76
|
+
|
|
77
|
+
One read block is written as one shard, so this also sets the file count: a
|
|
78
|
+
three-minute stream becomes three data objects instead of eighteen at the old
|
|
79
|
+
ten-second default. The cost is buffering: one block is held in memory before
|
|
80
|
+
it is written, which is `channels x sample_rate x 2 bytes x block` (1.2 GB for
|
|
81
|
+
8 channels at 1.25 MHz with a 60-second block), and an interrupted acquisition
|
|
82
|
+
loses the block in flight.
|
|
83
|
+
|
|
84
|
+
Steps driven by `n_read_blocks` rather than `duration` keep the historical
|
|
85
|
+
chunk-aligned block.
|
|
59
86
|
|
|
60
87
|
For example:
|
|
61
88
|
|
|
@@ -88,10 +115,29 @@ A **chunk** is a physical Zarr storage-layout unit. It is independent of a read
|
|
|
88
115
|
|
|
89
116
|
| Key | Default | Meaning |
|
|
90
117
|
|---|---:|---|
|
|
91
|
-
| `chunk_duration` | `
|
|
118
|
+
| `chunk_duration` | `0.25s`, grown at low data rates | Continuous chunk-duration target |
|
|
92
119
|
| `chunk_target_mb` | `128` | Maximum target chunk size |
|
|
93
|
-
|
|
94
|
-
|
|
120
|
+
| `shard_duration` | one read block | Continuous shard-duration target |
|
|
121
|
+
| `shard_target_mb` | `8192` (continuous), `128` (traces) | Maximum target shard size |
|
|
122
|
+
| `max_chunks_per_shard` | `100` (traces) | Chunks per trace shard |
|
|
123
|
+
|
|
124
|
+
A chunk is the unit a reader must decompress, so the default keeps it short
|
|
125
|
+
(about 5-8 MB at 20-32 MB/s) to make short random reads such as feature
|
|
126
|
+
windows cheap; at low data rates it grows towards 1 MiB so it stays worth
|
|
127
|
+
compressing, up to 10 seconds. A shard is one file and defaults to one read
|
|
128
|
+
block, which writes each shard exactly once.
|
|
129
|
+
|
|
130
|
+
The resolved chunk may be shorter than `chunk_duration`, and the resolved
|
|
131
|
+
shard shorter than `shard_duration`, when a byte cap or `max_chunks_per_shard`
|
|
132
|
+
is reached. Both cases emit a warning naming the binding limit.
|
|
133
|
+
|
|
134
|
+
### Shards, blocks and write cost
|
|
135
|
+
|
|
136
|
+
A shard is rewritten every time part of it is written, so a read block shorter
|
|
137
|
+
than the shard multiplies disk writes: at 3 channels and 1.25 MHz, one-second
|
|
138
|
+
blocks with 10-second shards write 4.2 GB for 764 MB of data (5.5x), and with
|
|
139
|
+
60-second shards 23 GB (30x). Leaving both at their defaults, or setting
|
|
140
|
+
`read_block_duration` equal to `shard_duration`, writes each shard once.
|
|
95
141
|
|
|
96
142
|
## Finite acquisition: traces
|
|
97
143
|
|
|
@@ -108,6 +108,33 @@ readout_circuit:
|
|
|
108
108
|
|
|
109
109
|
The correct constants depend on the installed readout circuit.
|
|
110
110
|
|
|
111
|
+
### Accelerometer readout
|
|
112
|
+
|
|
113
|
+
Auxiliary sensors such as accelerometers use the same `readout_circuit`
|
|
114
|
+
mapping. The conversion from ADC voltage to acceleration depends on the
|
|
115
|
+
sensor specification and its signal-conditioner gain:
|
|
116
|
+
|
|
117
|
+
```yaml
|
|
118
|
+
channels:
|
|
119
|
+
AccelerometerX:
|
|
120
|
+
adc:
|
|
121
|
+
device: adc1
|
|
122
|
+
channel: 0
|
|
123
|
+
readout_circuit:
|
|
124
|
+
sensor_type: accelerometer
|
|
125
|
+
amplifier_gain: 100 # signal conditioner gain (typically 1, 10 or 100)
|
|
126
|
+
sensitivity_v_per_g: 1.0 # sensor sensitivity in V/g (default 1)
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Acceleration in g is `volts / (amplifier_gain * sensitivity_v_per_g)`.
|
|
130
|
+
`amplifier_gain` is required when `sensor_type: accelerometer`; both values
|
|
131
|
+
must be positive. The constants are saved with the stream metadata, exposed
|
|
132
|
+
by `get_detector_settings()` as `amplifier_gain`, `sensitivity_v_per_g`, and
|
|
133
|
+
`acceleration_norm_v_per_g`, and used when reading data with `units="g"`.
|
|
134
|
+
|
|
135
|
+
A complete example with three accelerometers and a vibration transducer is
|
|
136
|
+
in `examples/configuration/vibration/`.
|
|
137
|
+
|
|
111
138
|
## Other run-level mappings
|
|
112
139
|
|
|
113
140
|
A run setup may also define:
|
|
@@ -430,6 +430,8 @@ Cleanup is not implicit. Without `scan.cleanup.temperature`, the final scan setp
|
|
|
430
430
|
|
|
431
431
|
A vibration transducer is driven by a signal generator; `channels` names logical transducers from `run_setup.transducers`, not detector channels.
|
|
432
432
|
|
|
433
|
+
Accelerometer channels recorded during the scan should define their volts-to-g conversion in `run_setup.yaml` (see [Accelerometer readout](run_setup_yaml.md#accelerometer-readout)); `examples/configuration/vibration/run_setup.yaml` pairs with the vibration scan example below.
|
|
434
|
+
|
|
433
435
|
| Target | Values and units | Behavior |
|
|
434
436
|
|---|---|---|
|
|
435
437
|
| `transducer.frequency_hz` | Numeric drive frequency in Hz | Sets the transducer signal generator's frequency |
|
|
@@ -42,6 +42,10 @@ configuration/
|
|
|
42
42
|
README.md
|
|
43
43
|
acquisition_didv.yaml
|
|
44
44
|
acquisition_iv_restore.yaml
|
|
45
|
+
|
|
46
|
+
vibration/
|
|
47
|
+
hardware_fake.yaml
|
|
48
|
+
run_setup.yaml
|
|
45
49
|
```
|
|
46
50
|
|
|
47
51
|
The files under `basic/` form the common configuration foundation:
|
|
@@ -57,6 +61,17 @@ The files under `basic/` form the common configuration foundation:
|
|
|
57
61
|
|
|
58
62
|
The basic hardware and run-setup examples also include a fake temperature controller using separate `sensors` and `heaters` inventory mappings. The logical `mc` path is suitable for testing `pytesdaqx-temperature`, canonical `temperature.setpoint` scans, stability waiting, and opt-in stream snapshots with `acquisition_temperature_fake.yaml`.
|
|
59
63
|
|
|
64
|
+
The `vibration/` directory maps three accelerometers (`AccelerometerX`, `AccelerometerY`, `AccelerometerZ`) to `adc1` channels 0-2, with their volts-to-g conversion (`readout_circuit.amplifier_gain` and `sensitivity_v_per_g`), plus a vibration transducer. Use it with `scans/acquisition_transducer_vibration_scan.yaml`:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
pytesdaqx-run \
|
|
68
|
+
--hardware examples/configuration/vibration/hardware_fake.yaml \
|
|
69
|
+
--run-setup examples/configuration/vibration/run_setup.yaml \
|
|
70
|
+
--acquisition-config examples/configuration/scans/acquisition_transducer_vibration_scan.yaml \
|
|
71
|
+
--backend fake \
|
|
72
|
+
--no-save-data
|
|
73
|
+
```
|
|
74
|
+
|
|
60
75
|
The `temperature/` directory contains the native MACRT inventory and logical temperature mappings used for real-controller CLI tests.
|
|
61
76
|
|
|
62
77
|
The scan examples cover the currently maintained scan patterns. Less common
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# Blinded physics running: 75% restricted / 25% open, with a grouped
|
|
2
|
+
# IV+dIdV calibration block at the start and end of every cycle.
|
|
3
|
+
#
|
|
4
|
+
# One cycle (repeated by run.n_sequence_repeats):
|
|
5
|
+
#
|
|
6
|
+
# [ IV -> dIdV ] -> 4 data slots -> [ dIdV ]
|
|
7
|
+
# opening calib 3 restricted closing calib
|
|
8
|
+
# + 1 open
|
|
9
|
+
#
|
|
10
|
+
# Why the data block is split into slots instead of one restricted block
|
|
11
|
+
# followed by one open block: with a fixed "restricted then open" order the
|
|
12
|
+
# open sample always sits further from the calibration than the restricted
|
|
13
|
+
# sample does, so bias-point drift biases exactly the 25% used to develop
|
|
14
|
+
# cuts and efficiencies. Splitting into slots and moving the open slot
|
|
15
|
+
# around makes the time-since-dIdV distribution the same for both samples.
|
|
16
|
+
#
|
|
17
|
+
# The open slot's position is rotated by hand here (see the four cycle
|
|
18
|
+
# variants below). For real running, generate this file with a seeded RNG
|
|
19
|
+
# so the position is randomized rather than a fixed rotation, and record the
|
|
20
|
+
# seed -- a fixed rotation can alias with periodic systematics (diurnal
|
|
21
|
+
# temperature, cryocooler, fridge fills).
|
|
22
|
+
#
|
|
23
|
+
# Slot length (5m here) must be short compared to the drift timescale. The
|
|
24
|
+
# two bracketing dIdVs measure that drift directly: if the opening and
|
|
25
|
+
# closing dIdV disagree beyond tolerance, shorten the slots.
|
|
26
|
+
|
|
27
|
+
run:
|
|
28
|
+
file_format: zarr
|
|
29
|
+
save_data: true
|
|
30
|
+
channels: [TES_A, TES_B]
|
|
31
|
+
output_dir: /data/pytesdaqx
|
|
32
|
+
acquisition_label: blinded_physics
|
|
33
|
+
stop_policy: finish_current_step
|
|
34
|
+
# One cycle = 1 IV + 2 dIdV + 4 x 5m data = ~20m of physics data.
|
|
35
|
+
# 72 repeats ~= 24h of running.
|
|
36
|
+
n_sequence_repeats: 72
|
|
37
|
+
|
|
38
|
+
sequence:
|
|
39
|
+
# ---- opening calibration block (IV and dIdV grouped together) ----
|
|
40
|
+
- measurement: iv
|
|
41
|
+
label: iv
|
|
42
|
+
duration: 2m
|
|
43
|
+
|
|
44
|
+
- measurement: didv
|
|
45
|
+
label: didv_open
|
|
46
|
+
duration: 1m
|
|
47
|
+
|
|
48
|
+
# ---- settling window, discarded, applied equally to both samples ----
|
|
49
|
+
# The dIdV square wave injects heat. Taking this as its own labeled step
|
|
50
|
+
# means the transient lands in a stream both samples see identically,
|
|
51
|
+
# instead of being absorbed by whichever slot happens to run first.
|
|
52
|
+
- measurement: background
|
|
53
|
+
label: settle
|
|
54
|
+
duration: 30s
|
|
55
|
+
restricted: 0
|
|
56
|
+
|
|
57
|
+
# ---- data block: 4 slots, 3 restricted + 1 open ----
|
|
58
|
+
# This cycle places the open slot 2nd. Rotate/randomize per cycle.
|
|
59
|
+
- measurement: background
|
|
60
|
+
label: physics
|
|
61
|
+
duration: 5m
|
|
62
|
+
restricted: 1
|
|
63
|
+
|
|
64
|
+
- measurement: background
|
|
65
|
+
label: physics
|
|
66
|
+
duration: 5m
|
|
67
|
+
restricted: 0
|
|
68
|
+
|
|
69
|
+
- measurement: background
|
|
70
|
+
label: physics
|
|
71
|
+
duration: 5m
|
|
72
|
+
restricted: 1
|
|
73
|
+
|
|
74
|
+
- measurement: background
|
|
75
|
+
label: physics
|
|
76
|
+
duration: 5m
|
|
77
|
+
restricted: 1
|
|
78
|
+
|
|
79
|
+
# ---- closing calibration: brackets the block for interpolation ----
|
|
80
|
+
- measurement: didv
|
|
81
|
+
label: didv_close
|
|
82
|
+
duration: 1m
|
|
83
|
+
|
|
84
|
+
measurements:
|
|
85
|
+
iv:
|
|
86
|
+
type: iv
|
|
87
|
+
adc:
|
|
88
|
+
mode: continuous
|
|
89
|
+
sample_rate: 1250000
|
|
90
|
+
read_block_duration: 2s
|
|
91
|
+
adc_input_range_v: [-10, 10]
|
|
92
|
+
read_mode: unscaled_int16
|
|
93
|
+
|
|
94
|
+
didv:
|
|
95
|
+
type: didv
|
|
96
|
+
waveform:
|
|
97
|
+
shape: square
|
|
98
|
+
frequency_hz: 50
|
|
99
|
+
amplitude_vpp: 0.01
|
|
100
|
+
adc:
|
|
101
|
+
mode: continuous
|
|
102
|
+
sample_rate: 1250000
|
|
103
|
+
trace_duration: 2s
|
|
104
|
+
adc_input_range_v: [-10, 10]
|
|
105
|
+
read_mode: unscaled_int16
|
|
106
|
+
|
|
107
|
+
background:
|
|
108
|
+
type: background
|
|
109
|
+
adc:
|
|
110
|
+
mode: continuous
|
|
111
|
+
sample_rate: 1250000
|
|
112
|
+
read_block_duration: 1s
|
|
113
|
+
adc_input_range_v: [-10, 10]
|
|
114
|
+
read_mode: unscaled_int16
|
|
115
|
+
recorder:
|
|
116
|
+
chunk_duration: 1s
|
|
117
|
+
shard_duration: 10s
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"""Generate a blinded physics acquisition YAML with a randomized open slot.
|
|
2
|
+
|
|
3
|
+
Each cycle is:
|
|
4
|
+
|
|
5
|
+
IV -> dIdV -> settle -> [4 data slots: 3 restricted + 1 open] -> dIdV
|
|
6
|
+
|
|
7
|
+
The open slot's position inside the 4-slot unit is drawn with *stratified*
|
|
8
|
+
randomization (permuted blocks): every group of 4 consecutive cycles uses
|
|
9
|
+
each of the 4 positions exactly once, in random order. That gives exactly
|
|
10
|
+
25% open data AND an open/restricted "time since dIdV" distribution that is
|
|
11
|
+
balanced by construction, not just on average -- while staying random, so
|
|
12
|
+
it cannot alias with a periodic systematic the way a fixed rotation can.
|
|
13
|
+
|
|
14
|
+
The seed is recorded in the generated file so the layout is reproducible
|
|
15
|
+
and auditable after unblinding.
|
|
16
|
+
|
|
17
|
+
Usage:
|
|
18
|
+
python generate_blinded_sequence.py --cycles 72 --seed 20260918 \
|
|
19
|
+
--output acquisition_blinded_generated.yaml
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import argparse
|
|
25
|
+
import random
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
|
|
28
|
+
SLOTS_PER_CYCLE = 4 # 3 restricted + 1 open -> 25% open
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def open_slot_positions(n_cycles: int, seed: int, slots: int = SLOTS_PER_CYCLE) -> list[int]:
|
|
32
|
+
"""Open-slot position per cycle, stratified over every block of `slots`."""
|
|
33
|
+
rng = random.Random(seed)
|
|
34
|
+
positions: list[int] = []
|
|
35
|
+
while len(positions) < n_cycles:
|
|
36
|
+
block = list(range(slots))
|
|
37
|
+
rng.shuffle(block)
|
|
38
|
+
positions.extend(block)
|
|
39
|
+
return positions[:n_cycles]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def render_cycle(cycle: int, open_slot: int, slot_duration: str) -> str:
|
|
43
|
+
lines = [
|
|
44
|
+
f" # ---- cycle {cycle}: open slot in position {open_slot + 1} of {SLOTS_PER_CYCLE} ----",
|
|
45
|
+
" - measurement: iv",
|
|
46
|
+
" label: iv",
|
|
47
|
+
" duration: 2m",
|
|
48
|
+
"",
|
|
49
|
+
" - measurement: didv",
|
|
50
|
+
" label: didv_open",
|
|
51
|
+
" duration: 1m",
|
|
52
|
+
"",
|
|
53
|
+
" - measurement: background",
|
|
54
|
+
" label: settle",
|
|
55
|
+
" duration: 30s",
|
|
56
|
+
" restricted: 0",
|
|
57
|
+
"",
|
|
58
|
+
]
|
|
59
|
+
for slot in range(SLOTS_PER_CYCLE):
|
|
60
|
+
restricted = 0 if slot == open_slot else 1
|
|
61
|
+
lines += [
|
|
62
|
+
" - measurement: background",
|
|
63
|
+
" label: physics",
|
|
64
|
+
f" duration: {slot_duration}",
|
|
65
|
+
f" restricted: {restricted}",
|
|
66
|
+
"",
|
|
67
|
+
]
|
|
68
|
+
lines += [
|
|
69
|
+
" - measurement: didv",
|
|
70
|
+
" label: didv_close",
|
|
71
|
+
" duration: 1m",
|
|
72
|
+
"",
|
|
73
|
+
]
|
|
74
|
+
return "\n".join(lines)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
HEADER = """# GENERATED FILE -- do not edit by hand.
|
|
78
|
+
# Regenerate with:
|
|
79
|
+
# python generate_blinded_sequence.py --cycles {cycles} --seed {seed}
|
|
80
|
+
#
|
|
81
|
+
# Blinded physics running, {open_pct}% open / {restricted_pct}% restricted.
|
|
82
|
+
# Open-slot positions (1-based, one per cycle), stratified random with the
|
|
83
|
+
# seed below:
|
|
84
|
+
# {positions}
|
|
85
|
+
#
|
|
86
|
+
# blinding_seed: {seed}
|
|
87
|
+
|
|
88
|
+
run:
|
|
89
|
+
file_format: zarr
|
|
90
|
+
save_data: true
|
|
91
|
+
channels: [TES_A, TES_B]
|
|
92
|
+
output_dir: /data/pytesdaqx
|
|
93
|
+
acquisition_label: blinded_physics
|
|
94
|
+
stop_policy: finish_current_step
|
|
95
|
+
# Cycles are unrolled below, so the sequence runs once.
|
|
96
|
+
n_sequence_repeats: 1
|
|
97
|
+
|
|
98
|
+
sequence:
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
FOOTER = """measurements:
|
|
102
|
+
iv:
|
|
103
|
+
type: iv
|
|
104
|
+
adc:
|
|
105
|
+
mode: continuous
|
|
106
|
+
sample_rate: 1250000
|
|
107
|
+
read_block_duration: 2s
|
|
108
|
+
adc_input_range_v: [-10, 10]
|
|
109
|
+
read_mode: unscaled_int16
|
|
110
|
+
|
|
111
|
+
didv:
|
|
112
|
+
type: didv
|
|
113
|
+
waveform:
|
|
114
|
+
shape: square
|
|
115
|
+
frequency_hz: 50
|
|
116
|
+
amplitude_vpp: 0.01
|
|
117
|
+
adc:
|
|
118
|
+
mode: continuous
|
|
119
|
+
sample_rate: 1250000
|
|
120
|
+
trace_duration: 2s
|
|
121
|
+
adc_input_range_v: [-10, 10]
|
|
122
|
+
read_mode: unscaled_int16
|
|
123
|
+
|
|
124
|
+
background:
|
|
125
|
+
type: background
|
|
126
|
+
adc:
|
|
127
|
+
mode: continuous
|
|
128
|
+
sample_rate: 1250000
|
|
129
|
+
read_block_duration: 1s
|
|
130
|
+
adc_input_range_v: [-10, 10]
|
|
131
|
+
read_mode: unscaled_int16
|
|
132
|
+
recorder:
|
|
133
|
+
chunk_duration: 1s
|
|
134
|
+
shard_duration: 10s
|
|
135
|
+
"""
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def main() -> None:
|
|
139
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
140
|
+
parser.add_argument("--cycles", type=int, default=72)
|
|
141
|
+
parser.add_argument("--seed", type=int, required=True)
|
|
142
|
+
parser.add_argument("--slot-duration", default="5m")
|
|
143
|
+
parser.add_argument("--output", type=Path, default=Path("acquisition_blinded_generated.yaml"))
|
|
144
|
+
args = parser.parse_args()
|
|
145
|
+
|
|
146
|
+
positions = open_slot_positions(args.cycles, args.seed)
|
|
147
|
+
body = "\n".join(
|
|
148
|
+
render_cycle(cycle, slot, args.slot_duration) for cycle, slot in enumerate(positions)
|
|
149
|
+
)
|
|
150
|
+
header = HEADER.format(
|
|
151
|
+
cycles=args.cycles,
|
|
152
|
+
seed=args.seed,
|
|
153
|
+
open_pct=int(100 / SLOTS_PER_CYCLE),
|
|
154
|
+
restricted_pct=int(100 - 100 / SLOTS_PER_CYCLE),
|
|
155
|
+
positions=", ".join(str(p + 1) for p in positions),
|
|
156
|
+
)
|
|
157
|
+
args.output.write_text(header + body + FOOTER, encoding="utf-8")
|
|
158
|
+
|
|
159
|
+
counts = {slot + 1: positions.count(slot) for slot in range(SLOTS_PER_CYCLE)}
|
|
160
|
+
print(f"wrote {args.output}: {args.cycles} cycles, seed {args.seed}")
|
|
161
|
+
print(f"open-slot position counts: {counts}")
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
if __name__ == "__main__":
|
|
165
|
+
main()
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Fake hardware inventory for vibration studies: one ADC reading three
|
|
2
|
+
# accelerometers and one signal generator driving a vibration transducer.
|
|
3
|
+
|
|
4
|
+
instruments:
|
|
5
|
+
adc1:
|
|
6
|
+
driver: fake
|
|
7
|
+
type: pci_adc
|
|
8
|
+
device_alias: Dev1
|
|
9
|
+
model: fake_adc
|
|
10
|
+
channels: [0, 1, 2, 3, 4, 5, 6, 7]
|
|
11
|
+
|
|
12
|
+
agilent1:
|
|
13
|
+
driver: fake
|
|
14
|
+
type: signal_generator
|
|
15
|
+
channels: [1, 2]
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
facility: 2
|
|
2
|
+
fridge_run: 123
|
|
3
|
+
fridge_run_start: "2026-04-12 19:00:00"
|
|
4
|
+
|
|
5
|
+
# Example run setup for vibration studies: three accelerometer axes on adc1
|
|
6
|
+
# channels 0-2 and one vibration transducer.
|
|
7
|
+
#
|
|
8
|
+
# Acceleration in g = volts / (amplifier_gain * sensitivity_v_per_g).
|
|
9
|
+
# amplifier_gain is the signal-conditioner gain (typically 1, 10 or 100);
|
|
10
|
+
# sensitivity_v_per_g is the accelerometer sensitivity (default 1 V/g).
|
|
11
|
+
|
|
12
|
+
channels:
|
|
13
|
+
AccelerometerX:
|
|
14
|
+
adc:
|
|
15
|
+
device: adc1
|
|
16
|
+
channel: 0
|
|
17
|
+
readout_circuit:
|
|
18
|
+
sensor_type: accelerometer
|
|
19
|
+
amplifier_gain: 100
|
|
20
|
+
sensitivity_v_per_g: 1.0
|
|
21
|
+
|
|
22
|
+
AccelerometerY:
|
|
23
|
+
adc:
|
|
24
|
+
device: adc1
|
|
25
|
+
channel: 1
|
|
26
|
+
readout_circuit:
|
|
27
|
+
sensor_type: accelerometer
|
|
28
|
+
amplifier_gain: 100
|
|
29
|
+
sensitivity_v_per_g: 1.0
|
|
30
|
+
|
|
31
|
+
AccelerometerZ:
|
|
32
|
+
adc:
|
|
33
|
+
device: adc1
|
|
34
|
+
channel: 2
|
|
35
|
+
readout_circuit:
|
|
36
|
+
sensor_type: accelerometer
|
|
37
|
+
amplifier_gain: 100
|
|
38
|
+
sensitivity_v_per_g: 1.0
|
|
39
|
+
|
|
40
|
+
# Topology only; drive frequency/amplitude are scan.parameters targets
|
|
41
|
+
# (see examples/configuration/scans/acquisition_transducer_vibration_scan.yaml).
|
|
42
|
+
transducers:
|
|
43
|
+
transducer1:
|
|
44
|
+
device: agilent1
|
|
45
|
+
channel: 2
|
|
@@ -168,7 +168,7 @@ def parse_args(argv: list[str] | None = None):
|
|
|
168
168
|
parser.add_argument(
|
|
169
169
|
"--units",
|
|
170
170
|
default="amps",
|
|
171
|
-
choices=["adc", "volts", "amps"],
|
|
171
|
+
choices=["adc", "volts", "amps", "g"],
|
|
172
172
|
help="Vertical-axis units; default: amps",
|
|
173
173
|
)
|
|
174
174
|
parser.add_argument(
|
|
@@ -736,6 +736,7 @@ def _units_label(units: str) -> str:
|
|
|
736
736
|
"amps": "Current [A]",
|
|
737
737
|
"volts": "Voltage [V]",
|
|
738
738
|
"adc": "ADC counts",
|
|
739
|
+
"g": "Acceleration [g]",
|
|
739
740
|
}.get(key, str(units))
|
|
740
741
|
|
|
741
742
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import warnings as _warnings
|
|
3
4
|
from pathlib import Path
|
|
4
5
|
from typing import Any
|
|
5
6
|
|
|
@@ -68,6 +69,13 @@ class ConfigResolver:
|
|
|
68
69
|
)
|
|
69
70
|
resolved_temperatures = resolve_temperature_paths(run_setup, hardware)
|
|
70
71
|
|
|
72
|
+
# A channel selected for specific measurements is easy to leave out of
|
|
73
|
+
# run.channels by accident, and then it is silently never recorded.
|
|
74
|
+
excluded_warnings = self._record_for_exclusion_warnings(run_setup, selected_channels)
|
|
75
|
+
for message in excluded_warnings:
|
|
76
|
+
_warnings.warn(message, stacklevel=2)
|
|
77
|
+
validation_warnings = list(validation_warnings) + excluded_warnings
|
|
78
|
+
|
|
71
79
|
resolved_channels = self._resolve_channels(run_setup=run_setup, hardware=hardware, selected_channels=selected_channels)
|
|
72
80
|
measurements = require_mapping(acquisition_config.get("measurements", {}), name="measurements")
|
|
73
81
|
sequence = self._resolve_sequence(
|
|
@@ -125,6 +133,35 @@ class ConfigResolver:
|
|
|
125
133
|
if isinstance(cfg, dict)
|
|
126
134
|
}
|
|
127
135
|
|
|
136
|
+
@staticmethod
|
|
137
|
+
def _record_for_exclusion_warnings(
|
|
138
|
+
run_setup: dict[str, Any], selected_channels: list[str] | None
|
|
139
|
+
) -> list[str]:
|
|
140
|
+
"""Warn about channels reserved for a measurement but left out of the run.
|
|
141
|
+
|
|
142
|
+
``record_for`` only narrows the channels of ``run.channels`` (or
|
|
143
|
+
``--channels``) to the measurements that should record them; it cannot
|
|
144
|
+
add a channel that the run never selected.
|
|
145
|
+
"""
|
|
146
|
+
|
|
147
|
+
if not selected_channels:
|
|
148
|
+
return []
|
|
149
|
+
selected = {str(name) for name in selected_channels}
|
|
150
|
+
messages = []
|
|
151
|
+
for name, cfg in (run_setup.get("channels", {}) or {}).items():
|
|
152
|
+
if str(name) in selected or not isinstance(cfg, dict):
|
|
153
|
+
continue
|
|
154
|
+
allowed = cfg.get("record_for", cfg.get("measurements", cfg.get("record_measurements")))
|
|
155
|
+
if allowed is None:
|
|
156
|
+
continue
|
|
157
|
+
allowed_list = [allowed] if isinstance(allowed, str) else list(allowed)
|
|
158
|
+
messages.append(
|
|
159
|
+
f"run_setup.channels.{name} has record_for {allowed_list} but is not "
|
|
160
|
+
f"in run.channels, so it is never recorded. Add {name} to "
|
|
161
|
+
f"run.channels: record_for then limits it to those measurements."
|
|
162
|
+
)
|
|
163
|
+
return messages
|
|
164
|
+
|
|
128
165
|
@staticmethod
|
|
129
166
|
def _resolve_record_channels(acquisition_config: dict[str, Any], cli_channels: list[str] | None) -> list[str] | None:
|
|
130
167
|
if cli_channels is not None:
|