nbs-bl 0.2.0__py3-none-any.whl
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.
- nbs_bl/__init__.py +15 -0
- nbs_bl/beamline.py +450 -0
- nbs_bl/configuration.py +838 -0
- nbs_bl/detectors.py +89 -0
- nbs_bl/devices/__init__.py +12 -0
- nbs_bl/devices/detectors.py +154 -0
- nbs_bl/devices/motors.py +242 -0
- nbs_bl/devices/sampleholders.py +360 -0
- nbs_bl/devices/shutters.py +120 -0
- nbs_bl/devices/slits.py +51 -0
- nbs_bl/gGrEqns.py +171 -0
- nbs_bl/geometry/__init__.py +0 -0
- nbs_bl/geometry/affine.py +197 -0
- nbs_bl/geometry/bars.py +189 -0
- nbs_bl/geometry/frames.py +534 -0
- nbs_bl/geometry/linalg.py +138 -0
- nbs_bl/geometry/polygons.py +56 -0
- nbs_bl/help.py +126 -0
- nbs_bl/hw.py +270 -0
- nbs_bl/load.py +113 -0
- nbs_bl/motors.py +19 -0
- nbs_bl/planStatus.py +5 -0
- nbs_bl/plans/__init__.py +8 -0
- nbs_bl/plans/batches.py +174 -0
- nbs_bl/plans/conditions.py +77 -0
- nbs_bl/plans/flyscan_base.py +180 -0
- nbs_bl/plans/groups.py +55 -0
- nbs_bl/plans/maximizers.py +423 -0
- nbs_bl/plans/metaplans.py +179 -0
- nbs_bl/plans/plan_stubs.py +246 -0
- nbs_bl/plans/preprocessors.py +160 -0
- nbs_bl/plans/scan_base.py +58 -0
- nbs_bl/plans/scan_decorators.py +524 -0
- nbs_bl/plans/scans.py +145 -0
- nbs_bl/plans/suspenders.py +87 -0
- nbs_bl/plans/time_estimation.py +168 -0
- nbs_bl/plans/xas.py +123 -0
- nbs_bl/printing.py +221 -0
- nbs_bl/qt/models/beamline.py +11 -0
- nbs_bl/qt/models/energy.py +53 -0
- nbs_bl/qt/widgets/energy.py +225 -0
- nbs_bl/queueserver.py +249 -0
- nbs_bl/redisDevice.py +96 -0
- nbs_bl/run_engine.py +63 -0
- nbs_bl/samples.py +130 -0
- nbs_bl/settings.py +68 -0
- nbs_bl/shutters.py +39 -0
- nbs_bl/sim/__init__.py +2 -0
- nbs_bl/sim/config/polphase.nc +0 -0
- nbs_bl/sim/energy.py +403 -0
- nbs_bl/sim/manipulator.py +14 -0
- nbs_bl/sim/utils.py +36 -0
- nbs_bl/startup.py +27 -0
- nbs_bl/status.py +114 -0
- nbs_bl/tests/__init__.py +0 -0
- nbs_bl/tests/modify_regions.py +160 -0
- nbs_bl/tests/test_frames.py +99 -0
- nbs_bl/tests/test_panels.py +69 -0
- nbs_bl/utils.py +235 -0
- nbs_bl-0.2.0.dist-info/METADATA +71 -0
- nbs_bl-0.2.0.dist-info/RECORD +64 -0
- nbs_bl-0.2.0.dist-info/WHEEL +4 -0
- nbs_bl-0.2.0.dist-info/entry_points.txt +2 -0
- nbs_bl-0.2.0.dist-info/licenses/LICENSE +13 -0
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
from ..beamline import GLOBAL_BEAMLINE
|
|
2
|
+
from ..help import add_to_plan_list, add_to_condition_list
|
|
3
|
+
import warnings
|
|
4
|
+
from bluesky import Msg
|
|
5
|
+
from bluesky.plan_stubs import rd, sleep, mv
|
|
6
|
+
import time
|
|
7
|
+
from typing import Optional
|
|
8
|
+
from bluesky.protocols import Readable
|
|
9
|
+
import typing
|
|
10
|
+
from .conditions import is_signal_below, is_signal_equals, is_signal_above
|
|
11
|
+
|
|
12
|
+
GLOBAL_EXPOSURE_TIME = 1.0
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def update_plan_status(status):
|
|
16
|
+
ret = yield Msg("update_plan_status", None, status)
|
|
17
|
+
return ret
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def clear_plan_status():
|
|
21
|
+
ret = yield Msg("clear_plan_status")
|
|
22
|
+
return ret
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def call_obj(obj, method, *args, **kwargs):
|
|
26
|
+
ret = yield Msg("call_obj", obj, *args, method=method, **kwargs)
|
|
27
|
+
return ret
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def sampleholder_move_sample_old(sampleholder, sample_id=None, **position):
|
|
31
|
+
"""
|
|
32
|
+
Set and move a sample. Dangerous! Sample moves really need to go through
|
|
33
|
+
Bluesky's mv plan, or else they won't be properly pause-able
|
|
34
|
+
"""
|
|
35
|
+
yield from call_obj(sampleholder, "move_sample", sample_id, **position)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def sampleholder_move_sample(sampleholder, sample_id=None, **position):
|
|
39
|
+
"""
|
|
40
|
+
Set and move a sample.
|
|
41
|
+
"""
|
|
42
|
+
positions = yield from call_obj(
|
|
43
|
+
sampleholder, "get_sample_position", sample_id, **position
|
|
44
|
+
)
|
|
45
|
+
print(f"Moving {sampleholder.name} to {positions}")
|
|
46
|
+
yield from mv(sampleholder, positions)
|
|
47
|
+
print("Done Moving ", sampleholder.name)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def sampleholder_set_sample(sampleholder, sample_id):
|
|
51
|
+
"""
|
|
52
|
+
Set a sample without moving it
|
|
53
|
+
"""
|
|
54
|
+
yield from call_obj(sampleholder, "set_sample", sample_id)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@add_to_plan_list
|
|
58
|
+
def set_exposure(time: Optional[float] = None, extra_dets=[]):
|
|
59
|
+
"""Sets the exposure time for all active detectors"""
|
|
60
|
+
global GLOBAL_EXPOSURE_TIME
|
|
61
|
+
if time is not None:
|
|
62
|
+
GLOBAL_EXPOSURE_TIME = time
|
|
63
|
+
|
|
64
|
+
all_dets = GLOBAL_BEAMLINE.detectors.active + extra_dets
|
|
65
|
+
for d in all_dets:
|
|
66
|
+
try:
|
|
67
|
+
if hasattr(d, "set_exposure"):
|
|
68
|
+
yield from call_obj(d, "set_exposure", GLOBAL_EXPOSURE_TIME)
|
|
69
|
+
except RuntimeError as ex:
|
|
70
|
+
warnings.warn(repr(ex), RuntimeWarning)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@add_to_plan_list
|
|
74
|
+
def set_roi(label, llim, ulim):
|
|
75
|
+
for d in GLOBAL_BEAMLINE.detectors.active:
|
|
76
|
+
try:
|
|
77
|
+
if hasattr(d, "set_roi"):
|
|
78
|
+
yield from call_obj(d, "set_roi", label, llim, ulim)
|
|
79
|
+
except RuntimeError as ex:
|
|
80
|
+
warnings.warn(repr(ex), RuntimeWarning)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@add_to_plan_list
|
|
84
|
+
def clear_all_rois():
|
|
85
|
+
for d in GLOBAL_BEAMLINE.detectors.active:
|
|
86
|
+
try:
|
|
87
|
+
if hasattr(d, "clear_all_rois"):
|
|
88
|
+
yield from call_obj(d, "clear_all_rois")
|
|
89
|
+
except RuntimeError as ex:
|
|
90
|
+
warnings.warn(repr(ex), RuntimeWarning)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@add_to_plan_list
|
|
94
|
+
def clear_one_roi(label):
|
|
95
|
+
for d in GLOBAL_BEAMLINE.detectors.active:
|
|
96
|
+
try:
|
|
97
|
+
if hasattr(d, "clear_roi"):
|
|
98
|
+
yield from call_obj(d, "clear_roi", label)
|
|
99
|
+
except RuntimeError as ex:
|
|
100
|
+
warnings.warn(repr(ex), RuntimeWarning)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@add_to_plan_list
|
|
104
|
+
def wait_for_condition(
|
|
105
|
+
condition,
|
|
106
|
+
condition_args: typing.List = None,
|
|
107
|
+
condition_kwargs: typing.Dict = None,
|
|
108
|
+
timeout: Optional[float] = None,
|
|
109
|
+
sleep_time: float = 10,
|
|
110
|
+
):
|
|
111
|
+
"""
|
|
112
|
+
Wait for a condition function to return True.
|
|
113
|
+
|
|
114
|
+
Parameters
|
|
115
|
+
----------
|
|
116
|
+
condition : str or callable
|
|
117
|
+
A condition function that returns a boolean.
|
|
118
|
+
condition_args : list, optional
|
|
119
|
+
Arguments for the condition function.
|
|
120
|
+
condition_kwargs : dict, optional
|
|
121
|
+
Keyword arguments for the condition function.
|
|
122
|
+
timeout : Optional[float], optional
|
|
123
|
+
Maximum time to wait in seconds. If None, wait indefinitely.
|
|
124
|
+
sleep_time : float, optional
|
|
125
|
+
Time to sleep between checks in seconds. Default is 10.
|
|
126
|
+
|
|
127
|
+
Returns
|
|
128
|
+
-------
|
|
129
|
+
bool
|
|
130
|
+
True when condition returns True.
|
|
131
|
+
|
|
132
|
+
Raises
|
|
133
|
+
------
|
|
134
|
+
TimeoutError
|
|
135
|
+
If timeout is reached before condition is met.
|
|
136
|
+
"""
|
|
137
|
+
if condition_args is None:
|
|
138
|
+
condition_args = []
|
|
139
|
+
if condition_kwargs is None:
|
|
140
|
+
condition_kwargs = {}
|
|
141
|
+
|
|
142
|
+
start_time = time.time()
|
|
143
|
+
while True:
|
|
144
|
+
if timeout is not None and (time.time() - start_time > timeout):
|
|
145
|
+
raise TimeoutError
|
|
146
|
+
result = yield from condition(*condition_args, **condition_kwargs)
|
|
147
|
+
if result:
|
|
148
|
+
return True
|
|
149
|
+
else:
|
|
150
|
+
yield from sleep(sleep_time)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def wait_for_signal_below(
|
|
154
|
+
sig: Readable, val: float, timeout: Optional[float] = None, sleep_time: float = 10
|
|
155
|
+
):
|
|
156
|
+
"""
|
|
157
|
+
Wait for a readable object to go below a threshold value.
|
|
158
|
+
|
|
159
|
+
Parameters
|
|
160
|
+
----------
|
|
161
|
+
sig : Readable
|
|
162
|
+
Any object that implements the Readable protocol (can be read with rd()).
|
|
163
|
+
val : float
|
|
164
|
+
The target value to wait for.
|
|
165
|
+
timeout : Optional[float], optional
|
|
166
|
+
Maximum time to wait in seconds. If None, wait indefinitely.
|
|
167
|
+
sleep_time : float, optional
|
|
168
|
+
Time to sleep between checks in seconds. Default is 10.
|
|
169
|
+
|
|
170
|
+
Returns
|
|
171
|
+
-------
|
|
172
|
+
bool
|
|
173
|
+
True when signal goes below threshold.
|
|
174
|
+
|
|
175
|
+
Raises
|
|
176
|
+
------
|
|
177
|
+
TimeoutError
|
|
178
|
+
If timeout is reached before condition is met.
|
|
179
|
+
"""
|
|
180
|
+
return wait_for_condition(
|
|
181
|
+
is_signal_below, [sig, val], timeout=timeout, sleep_time=sleep_time
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def wait_for_signal_equals(
|
|
186
|
+
sig: Readable, val: float, timeout: Optional[float] = None, sleep_time: float = 10
|
|
187
|
+
):
|
|
188
|
+
"""
|
|
189
|
+
Wait for a readable object to equal a target value.
|
|
190
|
+
|
|
191
|
+
Parameters
|
|
192
|
+
----------
|
|
193
|
+
sig : Readable
|
|
194
|
+
Any object that implements the Readable protocol (can be read with rd()).
|
|
195
|
+
val : float
|
|
196
|
+
The target value to wait for.
|
|
197
|
+
timeout : Optional[float], optional
|
|
198
|
+
Maximum time to wait in seconds. If None, wait indefinitely.
|
|
199
|
+
sleep_time : float, optional
|
|
200
|
+
Time to sleep between checks in seconds. Default is 10.
|
|
201
|
+
|
|
202
|
+
Returns
|
|
203
|
+
-------
|
|
204
|
+
bool
|
|
205
|
+
True when signal equals target value.
|
|
206
|
+
|
|
207
|
+
Raises
|
|
208
|
+
------
|
|
209
|
+
TimeoutError
|
|
210
|
+
If timeout is reached before condition is met.
|
|
211
|
+
"""
|
|
212
|
+
return wait_for_condition(
|
|
213
|
+
is_signal_equals, [sig, val], timeout=timeout, sleep_time=sleep_time
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def wait_for_signal_above(
|
|
218
|
+
sig: Readable, val: float, timeout: Optional[float] = None, sleep_time: float = 10
|
|
219
|
+
):
|
|
220
|
+
"""
|
|
221
|
+
Wait for a readable object to go above a threshold value.
|
|
222
|
+
|
|
223
|
+
Parameters
|
|
224
|
+
----------
|
|
225
|
+
sig : Readable
|
|
226
|
+
Any object that implements the Readable protocol (can be read with rd()).
|
|
227
|
+
val : float
|
|
228
|
+
The target value to wait for.
|
|
229
|
+
timeout : Optional[float], optional
|
|
230
|
+
Maximum time to wait in seconds. If None, wait indefinitely.
|
|
231
|
+
sleep_time : float, optional
|
|
232
|
+
Time to sleep between checks in seconds. Default is 10.
|
|
233
|
+
|
|
234
|
+
Returns
|
|
235
|
+
-------
|
|
236
|
+
bool
|
|
237
|
+
True when signal goes above threshold.
|
|
238
|
+
|
|
239
|
+
Raises
|
|
240
|
+
------
|
|
241
|
+
TimeoutError
|
|
242
|
+
If timeout is reached before condition is met.
|
|
243
|
+
"""
|
|
244
|
+
return wait_for_condition(
|
|
245
|
+
is_signal_above, [sig, val], timeout=timeout, sleep_time=sleep_time
|
|
246
|
+
)
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
from bluesky.plan_stubs import open_run, close_run
|
|
2
|
+
from bluesky.utils import RunEngineControlException, make_decorator
|
|
3
|
+
from bluesky.preprocessors import contingency_wrapper
|
|
4
|
+
from bluesky.preprocessors import plan_mutator, ensure_generator, single_gen
|
|
5
|
+
from bluesky import Msg
|
|
6
|
+
from functools import wraps
|
|
7
|
+
from ..utils import merge_func
|
|
8
|
+
from copy import deepcopy
|
|
9
|
+
import inspect
|
|
10
|
+
import uuid
|
|
11
|
+
from typing import Optional
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def sanitize(arg):
|
|
15
|
+
# storable_types = (int, float, str, bool)
|
|
16
|
+
if type(arg) == list:
|
|
17
|
+
_arg = []
|
|
18
|
+
for thing in arg:
|
|
19
|
+
try:
|
|
20
|
+
_arg.append(thing.name)
|
|
21
|
+
except Exception:
|
|
22
|
+
_arg.append(thing)
|
|
23
|
+
return _arg
|
|
24
|
+
else:
|
|
25
|
+
if hasattr(arg, "name"):
|
|
26
|
+
return arg.name
|
|
27
|
+
else:
|
|
28
|
+
return arg
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def plan_md_decorator(plan_function):
|
|
32
|
+
@wraps(plan_function)
|
|
33
|
+
def _inner(*args, md: Optional[dict] = None, plan_level=0, **kwargs):
|
|
34
|
+
plan_name = plan_function.__name__
|
|
35
|
+
md = md or {}
|
|
36
|
+
_md = {}
|
|
37
|
+
arguments = {}
|
|
38
|
+
s = inspect.signature(plan_function)
|
|
39
|
+
for p in s.parameters.values():
|
|
40
|
+
if p.default is not s.empty:
|
|
41
|
+
arguments[p.name] = p.default
|
|
42
|
+
|
|
43
|
+
for a, n in zip(args, plan_function.__code__.co_varnames):
|
|
44
|
+
arguments[n] = sanitize(a)
|
|
45
|
+
for k, v in kwargs.items():
|
|
46
|
+
arguments[k] = sanitize(v)
|
|
47
|
+
if "md" in arguments:
|
|
48
|
+
del arguments["md"]
|
|
49
|
+
|
|
50
|
+
_md["plan_history"] = []
|
|
51
|
+
if plan_level == 0:
|
|
52
|
+
_md["master_plan"] = plan_name
|
|
53
|
+
_md["batch_uid"] = str(uuid.uuid4())
|
|
54
|
+
_md.update(deepcopy(md))
|
|
55
|
+
_md["plan_history"].append(
|
|
56
|
+
{"plan_name": plan_name, "arguments": arguments, "plan_level": plan_level}
|
|
57
|
+
)
|
|
58
|
+
return plan_function(*args, md=_md, plan_level=plan_level + 1, **kwargs)
|
|
59
|
+
|
|
60
|
+
return _inner
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def wrap_metadata(param):
|
|
64
|
+
def decorator(func):
|
|
65
|
+
@merge_func(func)
|
|
66
|
+
def inner(*args, md: Optional[dict] = None, **kwargs):
|
|
67
|
+
md = md or {}
|
|
68
|
+
_md = {}
|
|
69
|
+
_md.update(param)
|
|
70
|
+
_md.update(md)
|
|
71
|
+
return (yield from func(*args, md=_md, **kwargs))
|
|
72
|
+
|
|
73
|
+
return inner
|
|
74
|
+
|
|
75
|
+
return decorator
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def run_return_wrapper(plan, *, md: Optional[dict] = None):
|
|
79
|
+
"""Enclose in 'open_run' and 'close_run' messages.
|
|
80
|
+
|
|
81
|
+
Parameters
|
|
82
|
+
----------
|
|
83
|
+
plan : iterable or iterator
|
|
84
|
+
a generator, list, or similar containing `Msg` objects
|
|
85
|
+
md : dict, optional
|
|
86
|
+
metadata to be passed into the 'open_run' message
|
|
87
|
+
"""
|
|
88
|
+
yield from open_run(md)
|
|
89
|
+
|
|
90
|
+
def except_plan(e):
|
|
91
|
+
if isinstance(e, RunEngineControlException):
|
|
92
|
+
yield from close_run(exit_status=e.exit_status)
|
|
93
|
+
else:
|
|
94
|
+
yield from close_run(exit_status="fail", reason=str(e))
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
yield from contingency_wrapper(
|
|
98
|
+
plan, except_plan=except_plan, else_plan=close_run
|
|
99
|
+
)
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
run_return_decorator = make_decorator(run_return_wrapper)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def plan_status_decorator(func):
|
|
107
|
+
"""
|
|
108
|
+
Update plan status, and clear after plan completion
|
|
109
|
+
|
|
110
|
+
Parameters
|
|
111
|
+
----------
|
|
112
|
+
plan : iterable or iterator
|
|
113
|
+
a generator, list, or similar containing `Msg` objects
|
|
114
|
+
status : str
|
|
115
|
+
the status to update to
|
|
116
|
+
|
|
117
|
+
Yields
|
|
118
|
+
------
|
|
119
|
+
msg : Msg
|
|
120
|
+
messages from plan with 'update_plan_status', and 'clear_plan_status' messages inserted
|
|
121
|
+
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
@merge_func(func)
|
|
125
|
+
def _inner(*args, md: Optional[dict] = None, **kwargs):
|
|
126
|
+
# Get plan name from metadata if available
|
|
127
|
+
md = md or {}
|
|
128
|
+
plan_name = md.get("plan_name", func.__name__) if md else func.__name__
|
|
129
|
+
status = "Running " + plan_name
|
|
130
|
+
print(f"Plan status decorator: {status}")
|
|
131
|
+
update_msgs = [Msg("update_plan_status", None, status)]
|
|
132
|
+
clear_msgs = [Msg("clear_plan_status")]
|
|
133
|
+
|
|
134
|
+
def insert_after_open(msg):
|
|
135
|
+
if msg.command == "open_run":
|
|
136
|
+
|
|
137
|
+
def new_gen():
|
|
138
|
+
yield from ensure_generator(update_msgs)
|
|
139
|
+
|
|
140
|
+
return single_gen(msg), new_gen()
|
|
141
|
+
else:
|
|
142
|
+
return None, None
|
|
143
|
+
|
|
144
|
+
def insert_before_close(msg):
|
|
145
|
+
if msg.command == "close_run":
|
|
146
|
+
|
|
147
|
+
def new_gen():
|
|
148
|
+
yield from ensure_generator(clear_msgs)
|
|
149
|
+
yield msg
|
|
150
|
+
|
|
151
|
+
return new_gen(), None
|
|
152
|
+
else:
|
|
153
|
+
return None, None
|
|
154
|
+
|
|
155
|
+
# Apply nested mutations.
|
|
156
|
+
plan1 = plan_mutator(func(*args, md=md, **kwargs), insert_after_open)
|
|
157
|
+
plan2 = plan_mutator(plan1, insert_before_close)
|
|
158
|
+
return (yield from plan2)
|
|
159
|
+
|
|
160
|
+
return _inner
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
def _make_gscan_points(*args, shift: float = 0):
|
|
2
|
+
"""
|
|
3
|
+
Generate a sequence of energy scan points from a variable-length parameter list.
|
|
4
|
+
|
|
5
|
+
Parameters should be passed in the following format:
|
|
6
|
+
(estart1, delta1, estop1, delta2, estop2, ...)
|
|
7
|
+
|
|
8
|
+
- Each segment defines a range starting from the given estart and ending at the estop,
|
|
9
|
+
using the specified delta (step size).
|
|
10
|
+
- Delta values must be positive. The function automatically determines the direction
|
|
11
|
+
(increasing or decreasing) based on the estart and estop values.
|
|
12
|
+
- A single energy value can be passed to return a single-point list.
|
|
13
|
+
- Segments can be non-monotonic — i.e., you can mix increasing and decreasing ranges.
|
|
14
|
+
- An optional `shift` parameter can be used to apply a constant offset to all values.
|
|
15
|
+
|
|
16
|
+
Examples:
|
|
17
|
+
_make_gscan_points(250)
|
|
18
|
+
➜ [250.0]
|
|
19
|
+
|
|
20
|
+
_make_gscan_points(250, 5, 260)
|
|
21
|
+
➜ [250.0, 255.0, 260.0]
|
|
22
|
+
|
|
23
|
+
_make_gscan_points(264, 2, 260, 5, 250)
|
|
24
|
+
➜ [264.0, 262.0, 260.0, 255.0, 250.0]
|
|
25
|
+
|
|
26
|
+
Raises:
|
|
27
|
+
TypeError if the number of arguments is incorrect
|
|
28
|
+
ValueError if any delta is zero
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
if len(args) == 1:
|
|
32
|
+
return [float(args[0]) + shift]
|
|
33
|
+
if (len(args) - 1) % 2 != 0:
|
|
34
|
+
raise TypeError(
|
|
35
|
+
"gscan received an even number of arguments. Either a stop or a step-size is missing. Expected format: (estart1, delta1, estop1, delta2, estop2, ...)"
|
|
36
|
+
)
|
|
37
|
+
points = []
|
|
38
|
+
for i in range(1, len(args) - 1, 2):
|
|
39
|
+
estart = float(args[i - 1]) + shift
|
|
40
|
+
delta = abs(args[i]) ## Ensures delta is positive in case users input negative delta for reverse energy list.
|
|
41
|
+
estop = float(args[i + 1]) + shift
|
|
42
|
+
|
|
43
|
+
if delta == 0:
|
|
44
|
+
raise ValueError("Step size (delta) cannot be zero.")
|
|
45
|
+
|
|
46
|
+
step = delta if estop > estart else -delta
|
|
47
|
+
|
|
48
|
+
if not points or points[-1] != estart:
|
|
49
|
+
points.append(estart)
|
|
50
|
+
|
|
51
|
+
next_point = estart + step
|
|
52
|
+
while (step > 0 and next_point < estop - step / 2.0) or (step < 0 and next_point > estop - step / 2.0):
|
|
53
|
+
points.append(next_point)
|
|
54
|
+
next_point += step
|
|
55
|
+
|
|
56
|
+
points.append(estop)
|
|
57
|
+
|
|
58
|
+
return points
|