1import pytest
2
3from pint import DimensionalityError
4from pint.testsuite import QuantityTestCase, helpers
5
6
7@helpers.requires_not_uncertainties()
8class TestNotMeasurement(QuantityTestCase):
9    def test_instantiate(self):
10        M_ = self.ureg.Measurement
11        with pytest.raises(RuntimeError):
12            M_(4.0, 0.1, "s")
13
14
15@helpers.requires_uncertainties()
16class TestMeasurement(QuantityTestCase):
17    def test_simple(self):
18        M_ = self.ureg.Measurement
19        M_(4.0, 0.1, "s")
20
21    def test_build(self):
22        M_ = self.ureg.Measurement
23        v, u = self.Q_(4.0, "s"), self.Q_(0.1, "s")
24        M_(v.magnitude, u.magnitude, "s")
25        ms = (
26            M_(v.magnitude, u.magnitude, "s"),
27            M_(v, u.magnitude),
28            M_(v, u),
29            v.plus_minus(0.1),
30            v.plus_minus(0.025, True),
31            v.plus_minus(u),
32        )
33
34        for m in ms:
35            assert m.value == v
36            assert m.error == u
37            assert m.rel == m.error / abs(m.value)
38
39    def test_format(self, subtests):
40        v, u = self.Q_(4.0, "s ** 2"), self.Q_(0.1, "s ** 2")
41        m = self.ureg.Measurement(v, u)
42
43        for spec, result in (
44            ("{}", "(4.00 +/- 0.10) second ** 2"),
45            ("{!r}", "<Measurement(4.0, 0.1, second ** 2)>"),
46            ("{:P}", "(4.00 ± 0.10) second²"),
47            ("{:L}", r"\left(4.00 \pm 0.10\right)\ \mathrm{second}^{2}"),
48            ("{:H}", "(4.00 &plusmn; 0.10) second<sup>2</sup>"),
49            ("{:C}", "(4.00+/-0.10) second**2"),
50            ("{:Lx}", r"\SI{4.00 +- 0.10}{\second\squared}"),
51            ("{:.1f}", "(4.0 +/- 0.1) second ** 2"),
52            ("{:.1fP}", "(4.0 ± 0.1) second²"),
53            ("{:.1fL}", r"\left(4.0 \pm 0.1\right)\ \mathrm{second}^{2}"),
54            ("{:.1fH}", "(4.0 &plusmn; 0.1) second<sup>2</sup>"),
55            ("{:.1fC}", "(4.0+/-0.1) second**2"),
56            ("{:.1fLx}", r"\SI{4.0 +- 0.1}{\second\squared}"),
57        ):
58            with subtests.test(spec):
59                assert spec.format(m) == result
60
61    def test_format_paru(self, subtests):
62        v, u = self.Q_(0.20, "s ** 2"), self.Q_(0.01, "s ** 2")
63        m = self.ureg.Measurement(v, u)
64
65        for spec, result in (
66            ("{:uS}", "0.200(10) second ** 2"),
67            ("{:.3uS}", "0.2000(100) second ** 2"),
68            ("{:.3uSP}", "0.2000(100) second²"),
69            ("{:.3uSL}", r"0.2000\left(100\right)\ \mathrm{second}^{2}"),
70            ("{:.3uSH}", "0.2000(100) second<sup>2</sup>"),
71            ("{:.3uSC}", "0.2000(100) second**2"),
72        ):
73            with subtests.test(spec):
74                assert spec.format(m) == result
75
76    def test_format_u(self, subtests):
77        v, u = self.Q_(0.20, "s ** 2"), self.Q_(0.01, "s ** 2")
78        m = self.ureg.Measurement(v, u)
79
80        for spec, result in (
81            ("{:.3u}", "(0.2000 +/- 0.0100) second ** 2"),
82            ("{:.3uP}", "(0.2000 ± 0.0100) second²"),
83            ("{:.3uL}", r"\left(0.2000 \pm 0.0100\right)\ \mathrm{second}^{2}"),
84            ("{:.3uH}", "(0.2000 &plusmn; 0.0100) second<sup>2</sup>"),
85            ("{:.3uC}", "(0.2000+/-0.0100) second**2"),
86            (
87                "{:.3uLx}",
88                r"\SI{0.2000 +- 0.0100}{\second\squared}",
89            ),
90            ("{:.1uLx}", r"\SI{0.20 +- 0.01}{\second\squared}"),
91        ):
92            with subtests.test(spec):
93                assert spec.format(m) == result
94
95    def test_format_percu(self, subtests):
96        self.test_format_perce(subtests)
97        v, u = self.Q_(0.20, "s ** 2"), self.Q_(0.01, "s ** 2")
98        m = self.ureg.Measurement(v, u)
99
100        for spec, result in (
101            ("{:.1u%}", "(20 +/- 1)% second ** 2"),
102            ("{:.1u%P}", "(20 ± 1)% second²"),
103            ("{:.1u%L}", r"\left(20 \pm 1\right) \%\ \mathrm{second}^{2}"),
104            ("{:.1u%H}", "(20 &plusmn; 1)% second<sup>2</sup>"),
105            ("{:.1u%C}", "(20+/-1)% second**2"),
106        ):
107            with subtests.test(spec):
108                assert spec.format(m) == result
109
110    def test_format_perce(self, subtests):
111        v, u = self.Q_(0.20, "s ** 2"), self.Q_(0.01, "s ** 2")
112        m = self.ureg.Measurement(v, u)
113        for spec, result in (
114            ("{:.1ue}", "(2.0 +/- 0.1)e-01 second ** 2"),
115            ("{:.1ueP}", "(2.0 ± 0.1)×10⁻¹ second²"),
116            (
117                "{:.1ueL}",
118                r"\left(2.0 \pm 0.1\right) \times 10^{-1}\ \mathrm{second}^{2}",
119            ),
120            ("{:.1ueH}", "(2.0 &plusmn; 0.1)×10<sup>-1</sup> second<sup>2</sup>"),
121            ("{:.1ueC}", "(2.0+/-0.1)e-01 second**2"),
122        ):
123            with subtests.test(spec):
124                assert spec.format(m) == result
125
126    def test_format_exponential_pos(self, subtests):
127        # Quantities in exponential format come with their own parenthesis, don't wrap
128        # them twice
129        m = self.ureg.Quantity(4e20, "s^2").plus_minus(1e19)
130        for spec, result in (
131            ("{}", "(4.00 +/- 0.10)e+20 second ** 2"),
132            ("{!r}", "<Measurement(4e+20, 1e+19, second ** 2)>"),
133            ("{:P}", "(4.00 ± 0.10)×10²⁰ second²"),
134            ("{:L}", r"\left(4.00 \pm 0.10\right) \times 10^{20}\ \mathrm{second}^{2}"),
135            ("{:H}", "(4.00 &plusmn; 0.10)×10<sup>20</sup> second<sup>2</sup>"),
136            ("{:C}", "(4.00+/-0.10)e+20 second**2"),
137            ("{:Lx}", r"\SI{4.00 +- 0.10 e+20}{\second\squared}"),
138        ):
139            with subtests.test(spec):
140                assert spec.format(m) == result
141
142    def test_format_exponential_neg(self, subtests):
143        m = self.ureg.Quantity(4e-20, "s^2").plus_minus(1e-21)
144        for spec, result in (
145            ("{}", "(4.00 +/- 0.10)e-20 second ** 2"),
146            ("{!r}", "<Measurement(4e-20, 1e-21, second ** 2)>"),
147            ("{:P}", "(4.00 ± 0.10)×10⁻²⁰ second²"),
148            (
149                "{:L}",
150                r"\left(4.00 \pm 0.10\right) \times 10^{-20}\ \mathrm{second}^{2}",
151            ),
152            ("{:H}", "(4.00 &plusmn; 0.10)×10<sup>-20</sup> second<sup>2</sup>"),
153            ("{:C}", "(4.00+/-0.10)e-20 second**2"),
154            ("{:Lx}", r"\SI{4.00 +- 0.10 e-20}{\second\squared}"),
155        ):
156            with subtests.test(spec):
157                assert spec.format(m) == result
158
159    def test_raise_build(self):
160        v, u = self.Q_(1.0, "s"), self.Q_(0.1, "s")
161        o = self.Q_(0.1, "m")
162
163        M_ = self.ureg.Measurement
164        with pytest.raises(DimensionalityError):
165            M_(v, o)
166        with pytest.raises(DimensionalityError):
167            v.plus_minus(o)
168        with pytest.raises(ValueError):
169            v.plus_minus(u, relative=True)
170
171    def test_propagate_linear(self):
172
173        v1, u1 = self.Q_(8.0, "s"), self.Q_(0.7, "s")
174        v2, u2 = self.Q_(5.0, "s"), self.Q_(0.6, "s")
175        v2, u3 = self.Q_(-5.0, "s"), self.Q_(0.6, "s")
176
177        m1 = v1.plus_minus(u1)
178        m2 = v2.plus_minus(u2)
179        m3 = v2.plus_minus(u3)
180
181        for factor, m in zip((3, -3, 3, -3), (m1, m3, m1, m3)):
182            r = factor * m
183            helpers.assert_quantity_almost_equal(
184                r.value.magnitude, factor * m.value.magnitude
185            )
186            helpers.assert_quantity_almost_equal(
187                r.error.magnitude, abs(factor * m.error.magnitude)
188            )
189            assert r.value.units == m.value.units
190
191        for ml, mr in zip((m1, m1, m1, m3), (m1, m2, m3, m3)):
192            r = ml + mr
193            helpers.assert_quantity_almost_equal(
194                r.value.magnitude, ml.value.magnitude + mr.value.magnitude
195            )
196            helpers.assert_quantity_almost_equal(
197                r.error.magnitude,
198                (
199                    ml.error.magnitude + mr.error.magnitude
200                    if ml is mr
201                    else (ml.error.magnitude ** 2 + mr.error.magnitude ** 2) ** 0.5
202                ),
203            )
204            assert r.value.units == ml.value.units
205
206        for ml, mr in zip((m1, m1, m1, m3), (m1, m2, m3, m3)):
207            r = ml - mr
208            helpers.assert_quantity_almost_equal(
209                r.value.magnitude, ml.value.magnitude - mr.value.magnitude
210            )
211            helpers.assert_quantity_almost_equal(
212                r.error.magnitude,
213                0
214                if ml is mr
215                else (ml.error.magnitude ** 2 + mr.error.magnitude ** 2) ** 0.5,
216            )
217            assert r.value.units == ml.value.units
218
219    def test_propagate_product(self):
220
221        v1, u1 = self.Q_(8.0, "s"), self.Q_(0.7, "s")
222        v2, u2 = self.Q_(5.0, "s"), self.Q_(0.6, "s")
223        v2, u3 = self.Q_(-5.0, "s"), self.Q_(0.6, "s")
224
225        m1 = v1.plus_minus(u1)
226        m2 = v2.plus_minus(u2)
227        m3 = v2.plus_minus(u3)
228
229        m4 = (2.3 * self.ureg.meter).plus_minus(0.1)
230        m5 = (1.4 * self.ureg.meter).plus_minus(0.2)
231
232        for ml, mr in zip((m1, m1, m1, m3, m4), (m1, m2, m3, m3, m5)):
233            r = ml * mr
234            helpers.assert_quantity_almost_equal(
235                r.value.magnitude, ml.value.magnitude * mr.value.magnitude
236            )
237            assert r.value.units == ml.value.units * mr.value.units
238
239        for ml, mr in zip((m1, m1, m1, m3, m4), (m1, m2, m3, m3, m5)):
240            r = ml / mr
241            helpers.assert_quantity_almost_equal(
242                r.value.magnitude, ml.value.magnitude / mr.value.magnitude
243            )
244            assert r.value.units == ml.value.units / mr.value.units
245
246    def test_measurement_comparison(self):
247        x = self.Q_(4.2, "meter")
248        y = self.Q_(5.0, "meter").plus_minus(0.1)
249        assert x <= y
250        assert not (x >= y)
251