Skip to content

Commit 3a603ef

Browse files
Merge pull request #30 from Simon-McIntosh/main
Carry the steradian in the name, not only in the unit
2 parents 488c917 + fbd86ca commit 3a603ef

6 files changed

Lines changed: 78 additions & 0 deletions

File tree

imas_standard_names/grammar/constants.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,7 @@ class SegmentRule:
391391
"fourier_coefficient",
392392
"gyroaveraged",
393393
"moment",
394+
"per_solid_angle",
394395
"reference_waveform",
395396
"waveform",
396397
)
@@ -446,6 +447,7 @@ class SegmentRule:
446447
"fourier_coefficient",
447448
"gyroaveraged",
448449
"moment",
450+
"per_solid_angle",
449451
"reference_waveform",
450452
"waveform",
451453
)

imas_standard_names/grammar/model_types.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -687,6 +687,7 @@ class Transformation(StrEnum):
687687
FOURIER_COEFFICIENT = "fourier_coefficient"
688688
GYROAVERAGED = "gyroaveraged"
689689
MOMENT = "moment"
690+
PER_SOLID_ANGLE = "per_solid_angle"
690691
REFERENCE_WAVEFORM = "reference_waveform"
691692
WAVEFORM = "waveform"
692693

@@ -737,6 +738,7 @@ class Decomposition(StrEnum):
737738
FOURIER_COEFFICIENT = "fourier_coefficient"
738739
GYROAVERAGED = "gyroaveraged"
739740
MOMENT = "moment"
741+
PER_SOLID_ANGLE = "per_solid_angle"
740742
REFERENCE_WAVEFORM = "reference_waveform"
741743
WAVEFORM = "waveform"
742744

imas_standard_names/grammar/vocabularies/advisory_aliases.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ segments:
1717
reason: >-
1818
Retired duplicate spelling of the per-time derivative; the registered
1919
operator says what it does without a meteorological reading.
20+
per_steradian:
21+
canonical: per_solid_angle
22+
reason: >-
23+
Names the steradian rather than the quantity it measures; the
24+
registered operator reads as the physical division it performs.
2025
physical_base:
2126
strain_tensor:
2227
canonical: strain

imas_standard_names/grammar/vocabularies/operators.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,17 @@ operators:
307307
precedence: 10
308308
returns: scalar
309309

310+
# Division by the solid angle the quantity is emitted into, adding sr^-1 to
311+
# the unit (a volumetric source rate m^-3.s^-1 becomes m^-3.s^-1.sr^-1).
312+
# Postfix so the per-unit normalization reads after the quantity it divides,
313+
# as it does in physics prose ("source rate per solid angle"), and one token
314+
# serves every quantity rather than a base per emitted species.
315+
per_solid_angle:
316+
kind: unary_postfix
317+
precedence: 15
318+
returns: scalar_or_vector
319+
dimension_transforming: true
320+
310321
reference_waveform:
311322
kind: unary_postfix
312323
precedence: 5

tests/grammar/test_advisory_alias_contract.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
("position", "annulus_centre"): "annulus_center",
1616
("physical_base", "strain_tensor"): "strain",
1717
("transformation", "tendency"): "time_derivative",
18+
("transformation", "per_steradian"): "per_solid_angle",
1819
}
1920

2021
CONTEXTUAL_REWRITES = {
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
"""The per-solid-angle spelling: one operator token, every quantity.
2+
3+
A volumetric emission rate resolved into direction carries sr^-1 in its unit
4+
(m^-3.s^-1.sr^-1). ``per_solid_angle`` is what carries that steradian in the
5+
name, so the unit string is not the only place the per-solid-angle character
6+
survives, and it composes onto any quantity rather than needing a base per
7+
emitted species.
8+
"""
9+
10+
import pytest
11+
12+
from imas_standard_names import compose, parse
13+
from imas_standard_names.grammar import parse_name
14+
from imas_standard_names.grammar.vocab_loaders import load_operators
15+
16+
_PER_SOLID_ANGLE_NAMES = [
17+
"source_rate_per_solid_angle",
18+
"hard_xray_source_rate_per_solid_angle",
19+
"electron_energy_source_rate_per_solid_angle",
20+
"radiance_per_solid_angle",
21+
]
22+
23+
24+
@pytest.mark.parametrize("name", _PER_SOLID_ANGLE_NAMES)
25+
def test_per_solid_angle_names_parse_strictly(name: str) -> None:
26+
parse(name, strict=True)
27+
28+
29+
@pytest.mark.parametrize("name", _PER_SOLID_ANGLE_NAMES)
30+
def test_per_solid_angle_names_round_trip(name: str) -> None:
31+
assert compose(parse(name, strict=True).ir) == name
32+
33+
34+
@pytest.mark.parametrize("name", _PER_SOLID_ANGLE_NAMES)
35+
def test_the_operator_leaves_the_base_intact(name: str) -> None:
36+
"""The token normalizes the quantity; it never becomes part of the base."""
37+
without = name.removesuffix("_per_solid_angle")
38+
assert parse_name(name).physical_base == parse_name(without).physical_base
39+
40+
41+
def test_the_token_is_a_dimension_transforming_postfix_operator() -> None:
42+
"""sr^-1 joins the unit, so base-implies-unit inference must be suppressed."""
43+
operator = load_operators().operators["per_solid_angle"]
44+
45+
assert operator.kind == "unary_postfix"
46+
assert operator.dimension_transforming is True
47+
48+
49+
def test_one_token_serves_every_emitted_quantity() -> None:
50+
"""No base spelled '<base>_per_solid_angle' — the operator replaces them."""
51+
from imas_standard_names.grammar.vocab_loaders import load_physical_bases
52+
53+
assert not [
54+
token
55+
for token in load_physical_bases().bases
56+
if token.endswith("_per_solid_angle") or token.endswith("_per_steradian")
57+
]

0 commit comments

Comments
 (0)