0
# Units System
1
2
Comprehensive physical unit system with predefined units, unit conversion, arithmetic operations, and alias management. Scipp's unit system provides automatic unit propagation through all operations, ensuring dimensional consistency and preventing common scientific computing errors.
3
4
## Capabilities
5
6
### Unit Class
7
8
Core unit representation with arithmetic operations and string parsing.
9
10
```python { .api }
11
class Unit:
12
"""Physical unit with arithmetic operations and parsing"""
13
14
def __init__(self, unit_string):
15
"""
16
Create Unit from string representation
17
18
Args:
19
unit_string (str): Unit string (e.g., 'm', 'kg*m/s^2', 'mm/s')
20
21
Examples:
22
Unit('m') # meter
23
Unit('kg*m/s^2') # force unit (Newton)
24
Unit('mm') # millimeter
25
Unit('1/s') # frequency unit
26
"""
27
28
def __mul__(self, other):
29
"""Multiply units: m * s -> m*s"""
30
31
def __truediv__(self, other):
32
"""Divide units: m / s -> m/s"""
33
34
def __pow__(self, exponent):
35
"""Raise unit to power: m^2 for area"""
36
37
def __eq__(self, other):
38
"""Check unit equality"""
39
40
def __str__(self):
41
"""String representation of unit"""
42
43
def __repr__(self):
44
"""Detailed string representation"""
45
```
46
47
### Unit Conversion
48
49
Functions for converting between compatible units and checking dimensional consistency.
50
51
```python { .api }
52
def to_unit(x, unit):
53
"""
54
Convert variable to specified unit
55
56
Args:
57
x (Variable or DataArray): Input with units
58
unit (Unit or str): Target unit
59
60
Returns:
61
Variable or DataArray: Converted to target unit
62
63
Raises:
64
UnitError: If units are incompatible
65
"""
66
```
67
68
### Predefined Units
69
70
Common physical units available as module constants.
71
72
```python { .api }
73
# Dimensionless
74
dimensionless: Unit # Dimensionless quantity
75
one: Unit # Alias for dimensionless
76
77
# Length units
78
m: Unit # meter
79
mm: Unit # millimeter
80
angstrom: Unit # angstrom (10^-10 m)
81
82
# Time units
83
s: Unit # second
84
us: Unit # microsecond
85
ns: Unit # nanosecond
86
87
# Mass units
88
kg: Unit # kilogram
89
90
# Temperature units
91
K: Unit # kelvin
92
93
# Angle units
94
rad: Unit # radian
95
deg: Unit # degree
96
97
# Energy units
98
meV: Unit # millielectronvolt
99
100
# Count units
101
counts: Unit # count unit for discrete quantities
102
103
# Special units
104
default_unit: Unit # Marker for default unit behavior
105
```
106
107
### Unit Aliases
108
109
Management system for custom unit aliases and shortcuts.
110
111
```python { .api }
112
class UnitAliases:
113
"""Manager for unit aliases and shortcuts"""
114
115
def __setitem__(self, alias, unit):
116
"""
117
Define a new unit alias
118
119
Args:
120
alias (str): Alias name
121
unit (str or Unit or Variable): Unit definition
122
123
Raises:
124
ValueError: If unit already has an alias
125
"""
126
127
def __delitem__(self, alias):
128
"""Remove an existing alias"""
129
130
def clear(self):
131
"""Remove all aliases"""
132
133
def scoped(self, **kwargs):
134
"""
135
Context manager for temporary aliases
136
137
Args:
138
**kwargs: Mapping from alias names to units
139
140
Returns:
141
Context manager for temporary aliases
142
"""
143
144
def keys(self):
145
"""Iterator over alias names"""
146
147
def values(self):
148
"""Iterator over aliased units"""
149
150
def items(self):
151
"""Iterator over (alias, unit) pairs"""
152
153
# Global alias manager instance
154
aliases: UnitAliases
155
```
156
157
## Usage Examples
158
159
### Basic Unit Operations
160
161
```python
162
import scipp as sc
163
164
# Create variables with units
165
length = sc.scalar(5.0, unit='m')
166
time = sc.scalar(2.0, unit='s')
167
168
# Unit arithmetic through operations
169
velocity = length / time # Automatically gets 'm/s' unit
170
area = length * length # Automatically gets 'm^2' unit
171
172
# Check units
173
print(velocity.unit) # 'm/s'
174
print(area.unit) # 'm^2'
175
176
# Unit compatibility checking
177
try:
178
length + time # Will raise UnitError - incompatible units
179
except sc.UnitError as e:
180
print(f"Cannot add length and time: {e}")
181
```
182
183
### Working with Predefined Units
184
185
```python
186
# Use predefined units
187
distance = sc.array(dims=['x'], values=[1, 2, 3], unit=sc.units.m)
188
temperature = sc.array(dims=['sensor'], values=[273, 298, 373], unit=sc.units.K)
189
190
# Angle calculations with proper units
191
angles = sc.linspace('angle', 0, 2*3.14159, 100, unit=sc.units.rad)
192
angles_deg = sc.linspace('angle', 0, 360, 100, unit=sc.units.deg)
193
194
# Convert between angle units
195
angles_converted = sc.to_unit(angles_deg, sc.units.rad)
196
197
# Energy units for scientific applications
198
energies = sc.array(dims=['detector'], values=[1, 5, 10, 50], unit=sc.units.meV)
199
200
# Counting statistics
201
detector_counts = sc.array(dims=['bin'], values=[100, 150, 80], unit=sc.units.counts)
202
```
203
204
### Unit Conversion
205
206
```python
207
# Convert between compatible units
208
length_m = sc.scalar(1000, unit='mm')
209
length_converted = sc.to_unit(length_m, 'm') # Result: 1.0 m
210
211
# Convert complex units
212
force = sc.scalar(10, unit='kg*m/s^2') # Newton
213
force_in_base = sc.to_unit(force, 'kg*m/s^2') # Same unit
214
215
# Temperature conversion (if supported)
216
temp_celsius = sc.scalar(25, unit='degC')
217
# temp_kelvin = sc.to_unit(temp_celsius, 'K') # Would be 298.15 K
218
```
219
220
### Custom Unit Aliases
221
222
```python
223
# Define custom aliases for convenience
224
sc.units.aliases['speed'] = 'm/s'
225
sc.units.aliases['pressure'] = 'kg/(m*s^2)'
226
227
# Use aliases in calculations
228
velocity = sc.scalar(10, unit='speed')
229
print(velocity.unit) # Will display as 'speed'
230
231
# Temporary aliases with context manager
232
with sc.units.aliases.scoped(temp='K', dist='mm'):
233
temperature = sc.scalar(300, unit='temp')
234
position = sc.scalar(5.2, unit='dist')
235
236
# Aliases are automatically removed after context
237
```
238
239
### Advanced Unit Manipulations
240
241
```python
242
# Create complex derived units
243
acceleration = sc.scalar(9.81, unit='m/s^2')
244
mass = sc.scalar(2.0, unit='kg')
245
force = mass * acceleration # Result: kg*m/s^2 (Newton)
246
247
# Work with dimensionless quantities
248
ratio = sc.scalar(0.75, unit=sc.units.dimensionless)
249
percentage = ratio * 100 # Still dimensionless
250
251
# Unit exponentiation
252
volume = sc.pow(length, 3) # m^3
253
print(volume.unit) # 'm^3'
254
255
# Complex unit expressions
256
power = force * velocity # kg*m^2/s^3 (Watt)
257
energy = power * time # kg*m^2/s^2 (Joule)
258
```
259
260
### Scientific Units in Practice
261
262
```python
263
import numpy as np
264
265
# Neutron scattering data with proper units
266
wavelength = sc.array(dims=['detector'], values=[1.8, 2.0, 2.2], unit=sc.units.angstrom)
267
energy = sc.array(dims=['detector'], values=[25.3, 20.4, 16.7], unit=sc.units.meV)
268
269
# Scattering angle calculation
270
scattering_angles = sc.linspace('angle', 0, 180, 181, unit=sc.units.deg)
271
q_vector = 4 * sc.sin(scattering_angles / 2) / wavelength # Momentum transfer
272
273
# Time-of-flight calculations
274
distance = sc.scalar(10.0, unit=sc.units.m) # Sample-detector distance
275
velocity = sc.sqrt(2 * energy / sc.scalar(1.67e-27, unit='kg')) # Neutron velocity
276
time_of_flight = distance / velocity # Automatically in seconds
277
278
# Statistical analysis with count data
279
raw_counts = sc.array(dims=['bin'], values=[120, 98, 156, 89], unit=sc.units.counts)
280
measurement_time = sc.scalar(300, unit=sc.units.s)
281
count_rate = raw_counts / measurement_time # counts/s
282
283
# Uncertainty propagation with units
284
count_uncertainty = sc.sqrt(raw_counts) # Poisson statistics
285
rate_uncertainty = count_uncertainty / measurement_time # Propagated uncertainty
286
```
287
288
### Unit Error Handling
289
290
```python
291
# Unit compatibility checking
292
try:
293
length = sc.scalar(5, unit='m')
294
time = sc.scalar(2, unit='s')
295
invalid = length + time # Incompatible units
296
except sc.UnitError as e:
297
print(f"Unit error: {e}")
298
299
# Dimensionless function requirements
300
try:
301
angle_with_units = sc.scalar(1.57, unit='rad')
302
result = sc.exp(angle_with_units) # Should fail - exp requires dimensionless
303
except sc.UnitError as e:
304
print(f"Function requires dimensionless input: {e}")
305
306
# Correct usage
307
dimensionless_value = sc.scalar(1.57, unit=sc.units.dimensionless)
308
result = sc.exp(dimensionless_value) # Works correctly
309
```