Coverage for src / hallmd / utils.py: 91%
35 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-19 21:56 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-19 21:56 +0000
1"""Module to provide utilities for the `hallmd` package.
3Includes:
4"""
5import json
6import os
7from pathlib import Path
9import yaml
12def _path_in_dict(value, data: dict) -> list:
13 """Recursively check if a value is in a dictionary and return the list of access keys to the value."""
14 if isinstance(data, dict):
15 for key, v in data.items():
16 path = _path_in_dict(value, v)
17 if path:
18 return [key] + path
19 elif data == value:
20 return [value] # found the value
21 return [] # value not found
24def load_thruster(thruster_dir: str | Path, thruster_filename: str = 'thruster.yml') -> dict:
25 """Load a device configuration from the `device_dir` directory. The `device_file` must be located at
26 `device_dir/device_name/device_file`. All other files in the directory, if referenced in `device_file`, will
27 be converted to an absolute path.
29 !!! Example "Loading a device configuration"
30 Currently, the only provided device configuration is for the SPT-100 thruster.
31 ```python
32 from hallmd.utils import load_thruster
34 device = load_thruster('devices/SPT-100')
35 ```
37 The format of a device file is as follows
38 ```yaml
39 name: MyDevice
40 geometry:
41 channel_length: 1
42 inner_radius: 2
43 outer_radius: 3
44 magnetic_field:
45 file: bfield.csv
46 shielded: false
47 ```
49 :param device_name: name of the device configuration to load
50 :param device_file: name of the device configuration file (default: 'device.yml'). Only supported file types are
51 `.yml` and `.json`.
52 :param device_dir: directory containing the devices. If None, the `hallmd.devices` directory is used.
53 :return: dictionary containing the device configuration
54 """
55 thruster_file = Path(thruster_dir) / thruster_filename
57 with open(thruster_file, 'r', encoding='utf-8') as fd:
58 if thruster_file.suffix == '.yml':
59 config = yaml.safe_load(fd)
60 elif thruster_file.suffix == '.json':
61 config = json.load(fd)
62 else:
63 raise ValueError(
64 f'Unsupported file type "{thruster_file.suffix}". Only .yml and .json files are supported.'
65 )
67 # Convert all relative paths to absolute paths for loading bfields, etc
68 for root, _, files in os.walk(thruster_dir):
69 for file in files:
70 if file != thruster_file:
71 # Check if the posix file path from root is in the config (i.e. "./file.csv")
72 root_path = Path(root) / file # Path like "hallmd/devices/SPT-100/path/to/file.csv"
73 rel_path = root_path.relative_to(thruster_dir) # Just the path/to/file.csv part (relative)
74 dict_path = _path_in_dict(rel_path.as_posix(), config)
75 if len(dict_path) == 0:
76 # Check if the plain filename is in the config (i.e. file.csv); will only pick first match
77 dict_path = _path_in_dict(file, config)
79 if dict_path:
80 d = config # pointer to the nested location in config
81 for key in dict_path[:-2]:
82 d = config[key]
83 d[dict_path[-2]] = root_path.resolve().as_posix()
85 return config