plurimath 0.11.11 → 0.11.13

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.
Files changed (170) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/dependent-tests.yml +7 -0
  3. data/.github/workflows/shared-testsuite.yml +24 -0
  4. data/.gitmodules +3 -0
  5. data/.rspec-opal +3 -0
  6. data/Gemfile +18 -0
  7. data/README.adoc +297 -3
  8. data/docs/catalog.adoc +73 -0
  9. data/docs/uncertainty-asciimath-tree.md +92 -0
  10. data/docs/uncertainty.adoc +522 -0
  11. data/lib/plurimath/asciimath/parse.rb +43 -226
  12. data/lib/plurimath/asciimath/parser.rb +58 -1
  13. data/lib/plurimath/asciimath/slice_adapter.rb +138 -0
  14. data/lib/plurimath/asciimath/transform.rb +218 -0
  15. data/lib/plurimath/asciimath.rb +1 -0
  16. data/lib/plurimath/catalog/entry.rb +109 -0
  17. data/lib/plurimath/catalog/validation.rb +109 -0
  18. data/lib/plurimath/catalog.rb +96 -0
  19. data/lib/plurimath/cli/convert.rb +40 -0
  20. data/lib/plurimath/cli/helpers.rb +49 -0
  21. data/lib/plurimath/cli/render.rb +64 -0
  22. data/lib/plurimath/cli.rb +55 -57
  23. data/lib/plurimath/documentation.rb +99 -0
  24. data/lib/plurimath/errors/ambiguous_decimal_error.rb +19 -0
  25. data/lib/plurimath/errors/invalid_uncertainty.rb +18 -0
  26. data/lib/plurimath/errors/missing_dependency_error.rb +23 -0
  27. data/lib/plurimath/errors/rendering_failed.rb +17 -0
  28. data/lib/plurimath/errors/rendering_unavailable.rb +25 -0
  29. data/lib/plurimath/errors/unsupported_render_format.rb +18 -0
  30. data/lib/plurimath/errors/unsupported_semantic_tree_error.rb +12 -0
  31. data/lib/plurimath/errors.rb +5 -0
  32. data/lib/plurimath/math/evaluation/evaluator.rb +48 -0
  33. data/lib/plurimath/math/evaluation/expression_parser.rb +29 -20
  34. data/lib/plurimath/math/formula/class_tree.rb +92 -0
  35. data/lib/plurimath/math/formula.rb +43 -2
  36. data/lib/plurimath/math/function/abs.rb +6 -0
  37. data/lib/plurimath/math/function/arccos.rb +6 -0
  38. data/lib/plurimath/math/function/arcsin.rb +6 -0
  39. data/lib/plurimath/math/function/arctan.rb +6 -0
  40. data/lib/plurimath/math/function/arg.rb +20 -0
  41. data/lib/plurimath/math/function/bar.rb +6 -0
  42. data/lib/plurimath/math/function/base.rb +6 -0
  43. data/lib/plurimath/math/function/binary_function.rb +4 -0
  44. data/lib/plurimath/math/function/cancel.rb +6 -0
  45. data/lib/plurimath/math/function/ceil.rb +6 -0
  46. data/lib/plurimath/math/function/color.rb +12 -0
  47. data/lib/plurimath/math/function/cos.rb +6 -0
  48. data/lib/plurimath/math/function/cosh.rb +6 -0
  49. data/lib/plurimath/math/function/cot.rb +6 -0
  50. data/lib/plurimath/math/function/coth.rb +6 -0
  51. data/lib/plurimath/math/function/csc.rb +6 -0
  52. data/lib/plurimath/math/function/csch.rb +6 -0
  53. data/lib/plurimath/math/function/ddot.rb +6 -0
  54. data/lib/plurimath/math/function/deg.rb +6 -0
  55. data/lib/plurimath/math/function/det.rb +6 -0
  56. data/lib/plurimath/math/function/dim.rb +6 -0
  57. data/lib/plurimath/math/function/dot.rb +6 -0
  58. data/lib/plurimath/math/function/exp.rb +6 -0
  59. data/lib/plurimath/math/function/fenced.rb +6 -0
  60. data/lib/plurimath/math/function/floor.rb +6 -0
  61. data/lib/plurimath/math/function/font_style/bold-fraktur.rb +6 -0
  62. data/lib/plurimath/math/function/font_style/bold-italic.rb +6 -0
  63. data/lib/plurimath/math/function/font_style/bold-sans-serif.rb +6 -0
  64. data/lib/plurimath/math/function/font_style/bold-script.rb +6 -0
  65. data/lib/plurimath/math/function/font_style/bold.rb +6 -0
  66. data/lib/plurimath/math/function/font_style/double_struck.rb +6 -0
  67. data/lib/plurimath/math/function/font_style/fraktur.rb +6 -0
  68. data/lib/plurimath/math/function/font_style/italic.rb +6 -0
  69. data/lib/plurimath/math/function/font_style/monospace.rb +6 -0
  70. data/lib/plurimath/math/function/font_style/normal.rb +6 -0
  71. data/lib/plurimath/math/function/font_style/sans-serif-bold-italic.rb +6 -0
  72. data/lib/plurimath/math/function/font_style/sans-serif-italic.rb +6 -0
  73. data/lib/plurimath/math/function/font_style/sans-serif.rb +6 -0
  74. data/lib/plurimath/math/function/font_style/script.rb +6 -0
  75. data/lib/plurimath/math/function/font_style.rb +4 -0
  76. data/lib/plurimath/math/function/frac.rb +6 -0
  77. data/lib/plurimath/math/function/gcd.rb +6 -0
  78. data/lib/plurimath/math/function/glb.rb +12 -0
  79. data/lib/plurimath/math/function/hat.rb +6 -0
  80. data/lib/plurimath/math/function/hom.rb +6 -0
  81. data/lib/plurimath/math/function/inf.rb +6 -0
  82. data/lib/plurimath/math/function/int.rb +7 -1
  83. data/lib/plurimath/math/function/intent.rb +35 -0
  84. data/lib/plurimath/math/function/ker.rb +6 -0
  85. data/lib/plurimath/math/function/lcm.rb +12 -0
  86. data/lib/plurimath/math/function/lg.rb +6 -0
  87. data/lib/plurimath/math/function/lim.rb +6 -0
  88. data/lib/plurimath/math/function/liminf.rb +6 -0
  89. data/lib/plurimath/math/function/limits.rb +6 -0
  90. data/lib/plurimath/math/function/limsup.rb +6 -0
  91. data/lib/plurimath/math/function/ln.rb +6 -0
  92. data/lib/plurimath/math/function/log.rb +6 -0
  93. data/lib/plurimath/math/function/longdiv.rb +6 -0
  94. data/lib/plurimath/math/function/lub.rb +6 -0
  95. data/lib/plurimath/math/function/max.rb +6 -0
  96. data/lib/plurimath/math/function/mbox.rb +6 -0
  97. data/lib/plurimath/math/function/menclose.rb +19 -0
  98. data/lib/plurimath/math/function/merror.rb +22 -0
  99. data/lib/plurimath/math/function/min.rb +6 -0
  100. data/lib/plurimath/math/function/mlabeledtr.rb +23 -0
  101. data/lib/plurimath/math/function/mod.rb +6 -0
  102. data/lib/plurimath/math/function/msgroup.rb +20 -0
  103. data/lib/plurimath/math/function/multiscript.rb +6 -0
  104. data/lib/plurimath/math/function/nary.rb +11 -0
  105. data/lib/plurimath/math/function/norm.rb +6 -0
  106. data/lib/plurimath/math/function/obrace.rb +6 -0
  107. data/lib/plurimath/math/function/oint.rb +7 -1
  108. data/lib/plurimath/math/function/over.rb +6 -0
  109. data/lib/plurimath/math/function/overleftrightarrow.rb +6 -0
  110. data/lib/plurimath/math/function/overset.rb +6 -0
  111. data/lib/plurimath/math/function/phantom.rb +6 -0
  112. data/lib/plurimath/math/function/power.rb +6 -0
  113. data/lib/plurimath/math/function/power_base.rb +6 -0
  114. data/lib/plurimath/math/function/prod.rb +7 -1
  115. data/lib/plurimath/math/function/right.rb +1 -1
  116. data/lib/plurimath/math/function/root.rb +6 -0
  117. data/lib/plurimath/math/function/rule.rb +20 -0
  118. data/lib/plurimath/math/function/scarries.rb +20 -0
  119. data/lib/plurimath/math/function/sec.rb +6 -0
  120. data/lib/plurimath/math/function/sech.rb +6 -0
  121. data/lib/plurimath/math/function/semantics.rb +20 -0
  122. data/lib/plurimath/math/function/sin.rb +6 -0
  123. data/lib/plurimath/math/function/sinh.rb +6 -0
  124. data/lib/plurimath/math/function/sqrt.rb +6 -0
  125. data/lib/plurimath/math/function/stackrel.rb +6 -0
  126. data/lib/plurimath/math/function/substack.rb +6 -0
  127. data/lib/plurimath/math/function/sum.rb +7 -1
  128. data/lib/plurimath/math/function/sup.rb +6 -0
  129. data/lib/plurimath/math/function/table/align.rb +6 -0
  130. data/lib/plurimath/math/function/table/array.rb +6 -0
  131. data/lib/plurimath/math/function/table/bmatrix.rb +6 -0
  132. data/lib/plurimath/math/function/table/cases.rb +6 -0
  133. data/lib/plurimath/math/function/table/eqarray.rb +6 -0
  134. data/lib/plurimath/math/function/table/matrix.rb +6 -0
  135. data/lib/plurimath/math/function/table/multline.rb +6 -0
  136. data/lib/plurimath/math/function/table/pmatrix.rb +6 -0
  137. data/lib/plurimath/math/function/table/split.rb +6 -0
  138. data/lib/plurimath/math/function/table/vmatrix.rb +6 -0
  139. data/lib/plurimath/math/function/table.rb +10 -0
  140. data/lib/plurimath/math/function/tan.rb +6 -0
  141. data/lib/plurimath/math/function/tanh.rb +6 -0
  142. data/lib/plurimath/math/function/ternary_function.rb +4 -0
  143. data/lib/plurimath/math/function/text.rb +6 -0
  144. data/lib/plurimath/math/function/tilde.rb +6 -0
  145. data/lib/plurimath/math/function/ubrace.rb +6 -0
  146. data/lib/plurimath/math/function/ul.rb +6 -0
  147. data/lib/plurimath/math/function/unary_function.rb +4 -0
  148. data/lib/plurimath/math/function/uncertainty.rb +655 -0
  149. data/lib/plurimath/math/function/underover.rb +6 -0
  150. data/lib/plurimath/math/function/underset.rb +6 -0
  151. data/lib/plurimath/math/function/vec.rb +6 -0
  152. data/lib/plurimath/math/function.rb +1 -0
  153. data/lib/plurimath/math/renderer.rb +95 -0
  154. data/lib/plurimath/math/semantic_tree.rb +331 -0
  155. data/lib/plurimath/math/symbols/symbol.rb +12 -5
  156. data/lib/plurimath/math.rb +26 -0
  157. data/lib/plurimath/mathml/translator.rb +32 -0
  158. data/lib/plurimath/mathml/uncertainty_marker.rb +118 -0
  159. data/lib/plurimath/omml/translator.rb +68 -0
  160. data/lib/plurimath/omml/utility.rb +9 -1
  161. data/lib/plurimath/symbol_documentation.rb +57 -0
  162. data/lib/plurimath/unicode_math/parsing_rules/common_rules.rb +2 -1
  163. data/lib/plurimath/unicode_math/parsing_rules/masked.rb +3 -0
  164. data/lib/plurimath/unicode_math/transform.rb +64 -3
  165. data/lib/plurimath/unicode_math/utility.rb +38 -0
  166. data/lib/plurimath/version.rb +1 -1
  167. data/lib/plurimath.rb +3 -0
  168. data/plurimath.gemspec +3 -1
  169. data/scripts/replay_asciimath_corpus.rb +611 -0
  170. metadata +30 -4
@@ -0,0 +1,522 @@
1
+ = Measurement uncertainty
2
+
3
+ *Status: proposal, not yet approved.* This page proposes how Plurimath should
4
+ represent a measured value with its uncertainty
5
+ (https://github.com/plurimath/plurimath/issues/493[#493]). Nothing here is
6
+ implemented. Once approved, record the approval link and the approved revision
7
+ here, and build the work on this page and on
8
+ `spec/plurimath/fixtures/uncertainty/contract.yml`, which holds every exact
9
+ string and XML form referred to below.
10
+
11
+ Section numbers refer to JCGM 100:2008 (the GUM) unless marked JCGM 101:2008.
12
+
13
+ == What is supported
14
+
15
+ Plurimath records and converts a reported result such as `100.02147(35)` or
16
+ `(10.0 ± 0.3), k = 2`. It does not calculate anything: no uncertainty
17
+ propagation, no combination, no conversion between `U`, `k`, `p` and `u`,
18
+ no rounding, no unit conversion.
19
+
20
+ Input and output: AsciiMath, MathML, OMML and UnicodeMath. Output only: LaTeX
21
+ and HTML.
22
+
23
+ An ordinary `±` keeps meaning plus-or-minus (`Math::Symbols::Pm`). A measurement
24
+ is only recognised from an explicit form (see <<plain-pm>>).
25
+
26
+ == The three kinds of result
27
+
28
+ Standard uncertainty (§7.2.1, §7.2.2)::
29
+ A value `y` and its standard uncertainty `u`, an absolute, nonnegative number in
30
+ the unit of `y`. It has no coverage factor and no coverage probability.
31
+ Supplying either is an error, because §7.2.2 (note) warns that reading `u` as an
32
+ interval with a level of confidence is usually not justified.
33
+
34
+ Expanded uncertainty, symmetric (§6.2.1, §6.3, §7.2.3, §7.2.4)::
35
+ A value `y`, an expanded uncertainty `U` and the coverage factor `k` that was
36
+ used. `k` is required: §7.2.3 (4) asks for it, and without it the result cannot
37
+ be told apart from an unqualified `±`. Write that as ordinary `±` instead.
38
+ The coverage probability `p` is optional and only recorded if the author gives
39
+ it. Plurimath never derives `p` from `k` or the reverse: §6.3.2 says these
40
+ parameters are not enough to know the level of confidence exactly, and §6.3.3
41
+ gives `k = 2` ≈ 95 % only under stated conditions.
42
+
43
+ Asymmetric: coverage interval (proposal, needs a decision)::
44
+ JCGM 101 §5.3.2 defines a coverage interval by its two endpoints for a stated
45
+ coverage probability `p`. §5.3.3 and §5.3.4 name two choices: the
46
+ probabilistically symmetric interval and the shortest interval. §5.5 asks for
47
+ the estimate `y`, its standard uncertainty, the coverage probability, the
48
+ endpoints and which interval was chosen. (The standard uncertainty can be given
49
+ as a separate standard-uncertainty result.) Its example: `y = 1.024 V`, shortest 95 % coverage interval
50
+ `[0.983, 1.088] V`.
51
+ +
52
+ Proposal: record `y`, the lower and upper *endpoints* (not distances from `y`),
53
+ the required `p`, and the optional interval choice (`shortest`,
54
+ `probabilistically-symmetric`, or not stated). `k` is rejected. Plurimath does not
55
+ require `y` to lie between the endpoints, because JCGM 101 §5.3.4 only says the
56
+ shortest interval contains the mode, not the estimate.
57
+ +
58
+ The common `10.0^{+0.3}_{-0.2}` layout is *not* proposed. Neither JCGM 100 §6–§7
59
+ nor JCGM 101 §5.3/§5.5 defines what its two numbers mean, so supporting it needs
60
+ a maintainer decision about its meaning first. Approving, changing or leaving
61
+ out this variant is a scope decision for #493.
62
+
63
+ == Model node
64
+
65
+ Proposed class `Plurimath::Math::Function::Uncertainty`
66
+ (`lib/plurimath/math/function/uncertainty.rb`), a direct subclass of
67
+ `Math::Core`.
68
+
69
+ [source,ruby]
70
+ ----
71
+ Uncertainty.new(
72
+ value, # Math::Number, the measured value y
73
+ category:, # :standard, :expanded or :coverage_interval
74
+ magnitude: nil, # Math::Number, u or U (standard, expanded)
75
+ coverage_factor: nil, # Math::Number, k (expanded only, required)
76
+ coverage_probability: nil, # Math::Number, p (optional for expanded, required for coverage_interval)
77
+ lower: nil, upper: nil, # Math::Number, interval endpoints (coverage_interval only)
78
+ interval_kind: nil, # :shortest, :probabilistically_symmetric or nil (coverage_interval only)
79
+ notation: :absolute, # :absolute or :concise (standard only)
80
+ unit: nil, # Math::Function::Unitsml, or Math::Formula when read from markup
81
+ )
82
+ ----
83
+
84
+ Validation runs in the constructor and raises a new
85
+ `Plurimath::Errors::InvalidUncertainty < Plurimath::Error`:
86
+
87
+ * every number is a `Math::Number` whose text is a plain decimal,
88
+ `-?[0-9]+(\.[0-9]+)?`; magnitudes, `k`, `p` and endpoints have no sign. No
89
+ exponent, no comma or other locale decimal marker, no `base`, no mini-sized
90
+ digits. Text is kept as given (`1.230` stays `1.230`); no `Float` is used.
91
+ Range checks use exact decimal comparison of the text.
92
+ * `u` and `U` are ≥ 0; `k` is > 0; `p` is strictly between 0 and 1 (`0.95`, not
93
+ `95`); `lower` < `upper`.
94
+ * fields that do not belong to the category must be `nil` (for example `k` on a
95
+ standard result, `notation` other than `:absolute` on an expanded one).
96
+ * `notation: :concise` needs `magnitude` to have exactly as many decimal places
97
+ as `value` (see <<concise>>).
98
+ * `unit` is `nil`, a `Math::Function::Unitsml`, or a `Math::Formula` (only
99
+ produced by the MathML, OMML and UnicodeMath readers, see <<units>>). A
100
+ `Math::Formula` unit must not be empty and must not contain another
101
+ `Uncertainty`; either raises `Errors::InvalidUncertainty`.
102
+
103
+ Equality (`==`) compares class and every field, including `notation` and the
104
+ exact number text. `ustd(1.23(4))` and `ustd(1.23, 0.04)` hold the same
105
+ magnitude but are *not* equal, matching how `Math::Number` already compares text.
106
+
107
+ Cloning: `Core#cloned_objects` builds `self.class.new(nil)` and then copies
108
+ instance variables. A validating constructor would raise on that call, so
109
+ `Uncertainty` overrides `cloned_objects` to rebuild itself through the full
110
+ constructor with cloned children, as `Math::Function::Unitsml#cloned_objects`
111
+ already does. The other `self.class.new(nil)` caller,
112
+ `Core#updated_object_values`, is only reached when a field holds a line break;
113
+ the fields above cannot, so B1 adds a test that line breaking leaves the node
114
+ alone. The clone must be `==` to the original and share no child objects.
115
+
116
+ Numbers are rendered as their raw text. The configured number formatter and
117
+ locale are not applied, so output never re-rounds a value or changes its
118
+ decimal marker. Rounding (§7.2.6) is the author's choice, not Plurimath's.
119
+
120
+ [[plain-pm]]
121
+ == Plain ± and measurements
122
+
123
+ A measurement is recognised only from these explicit forms. Everything else
124
+ keeps its current meaning.
125
+
126
+ [cols="1,3,3"]
127
+ |===
128
+ |Language |Becomes `Uncertainty` |Stays ordinary
129
+
130
+ |AsciiMath
131
+ |`ustd(...)`, `uexp(...)`, `ucov(...)` (added to amath at B4)
132
+ |`+-`, `pm`, `±` are always `Pm`; `1.230(4)` is a number followed by a bracket
133
+
134
+ |MathML
135
+ |an `mrow` whose `intent` starts with `standard-uncertainty`,
136
+ `expanded-uncertainty` or `coverage-interval`
137
+ |`<mo>±</mo>`, any other `intent` (still read as `Function::Intent`)
138
+
139
+ |OMML
140
+ |an `m:box` whose first child is the hidden marker `m:phant` described below
141
+ |`±` runs, `m:d` brackets, any other box or phantom
142
+
143
+ |UnicodeMath
144
+ |`ⓘ("<marker>" ...)` with one of the three marker names
145
+ |`±`, any other `ⓘ(...)`
146
+
147
+ |LaTeX (output only)
148
+ |—
149
+ |`\pm`
150
+ |===
151
+
152
+ A marker that uses one of the three names but is malformed (missing `k`, bad
153
+ number, wrong argument count) raises `InvalidUncertainty`. It is never quietly
154
+ read as ordinary content.
155
+
156
+ AsciiMath has no marker, only the words. A malformed AsciiMath call, for
157
+ example `uexp(10.0, 0.3)` with no `k`, is not an uncertainty and falls back to
158
+ the letter reading it had before the words were added: `u` followed by
159
+ `exp(10.0, 0.3)` (plurimath/amath#9). No error is raised.
160
+
161
+ Word list note for B4: today `uexp(10.0, 0.3, 2)` is read as `u` followed by
162
+ `exp(...)`, and `ustd`, `ucov` as separate letters. Adding the words changes
163
+ how such text is read, so B4 needs ambiguity tests for `uexp` against `exp`.
164
+
165
+ [[concise]]
166
+ == Concise, absolute and ± notation
167
+
168
+ §7.2.2 lists four ways to write a standard uncertainty. Plurimath supports two
169
+ of them as `notation`:
170
+
171
+ * `:concise`, §7.2.2 form 2, `100.02147(35)`: the digits in parentheses apply
172
+ to the last digits of the value.
173
+ * `:absolute`, §7.2.2 form 3, `100.02147(0.00035)`: the number in parentheses
174
+ is the uncertainty itself. This is the default, because it can always be
175
+ written and needs no digit alignment.
176
+
177
+ The model always stores the absolute magnitude. The two notations convert by
178
+ moving the decimal point, never through floating point:
179
+
180
+ * `1.23(4)` means `u = 0.04`; `1.230(4)` means `u = 0.004`.
181
+ * From model to concise: `magnitude` must have the same number of decimal
182
+ places as `value`; the digits are `magnitude` without its decimal point and
183
+ leading zeros (`0.00035` with `100.02147` gives `35`; `2.0` with `1234.5`
184
+ gives `20`).
185
+ * From concise to model: pad the digits with leading zeros and insert the
186
+ decimal point so the result has as many decimal places as `value`.
187
+ * `1.2` with `0.004` cannot be written concisely; asking for that is an error.
188
+
189
+ §7.2.2 form 4, `(y ± u)` with a written caveat, is not offered for standard
190
+ uncertainty: the §7.2.2 note says to avoid it. `±` is used only for expanded
191
+ uncertainty, always together with `k`. Form 1 (`u_c = 0.35 mg` as a separate
192
+ statement) is ordinary text and needs no node.
193
+
194
+ == Exact forms
195
+
196
+ === Visible rendering
197
+
198
+ [cols="1,2,2,2"]
199
+ |===
200
+ | |LaTeX |HTML |Shown in MathML, OMML, UnicodeMath
201
+
202
+ |standard, absolute
203
+ |`1.230(0.004)`
204
+ |`1.230(0.004)`
205
+ |`1.230(0.004)`
206
+
207
+ |standard, concise
208
+ |`1.230(4)`
209
+ |`1.230(4)`
210
+ |`1.230(4)`
211
+
212
+ |expanded
213
+ |`(10.0 \pm 0.3),\ k = 2`
214
+ |`(10.0 &#xb1; 0.3), k = 2`
215
+ |`(10.0 ± 0.3), k = 2`
216
+
217
+ |expanded with `p`
218
+ |`(10.0 \pm 0.3),\ k = 2,\ p = 0.95`
219
+ |`(10.0 &#xb1; 0.3), k = 2, p = 0.95`
220
+ |`(10.0 ± 0.3), k = 2, p = 0.95`
221
+
222
+ |coverage interval (proposal)
223
+ |`1.024,\ [0.983, 1.088],\ p = 0.95`
224
+ |`1.024, [0.983, 1.088], p = 0.95`
225
+ |`1.024, [0.983, 1.088], p = 0.95`
226
+ |===
227
+
228
+ === The marker
229
+
230
+ MathML, OMML and UnicodeMath carry the full meaning in one marker string
231
+ written in MathML intent syntax, with literal numbers:
232
+
233
+ [cols="1,3"]
234
+ |===
235
+ |standard, absolute |`standard-uncertainty(1.230,0.004)`
236
+ |standard, concise |`standard-uncertainty:concise(1.230,0.004)`
237
+ |expanded |`expanded-uncertainty(10.0,0.3,2)`
238
+ |expanded with `p` |`expanded-uncertainty(10.0,0.3,2,0.95)`
239
+ |coverage interval (proposal) |`coverage-interval:shortest(1.024,0.983,1.088,0.95)`;
240
+ `:probabilistically-symmetric`, or no property when the choice is not stated
241
+ |with a unit |a final `$unit` argument, for example `expanded-uncertainty(100.02147,0.00079,2,$unit)`
242
+ |===
243
+
244
+ The concise marker still holds the absolute magnitude, so the meaning never
245
+ depends on digit alignment. When reading, the marker is authoritative and the
246
+ visible content is regenerated on output.
247
+
248
+ === Per language
249
+
250
+ AsciiMath (output at B2, input in amath at B4)::
251
+ +
252
+ [source]
253
+ ----
254
+ ustd(1.230, 0.004) standard, absolute
255
+ ustd(1.230(4)) standard, concise
256
+ uexp(10.0, 0.3, 2) expanded
257
+ uexp(10.0, 0.3, 2, 0.95) expanded with p
258
+ ucov(1.024, 0.983, 1.088, 0.95, shortest) coverage interval (proposal)
259
+ uexp(100.02147, 0.00079, 2, "unitsml(g)") with a unit, always last
260
+ ----
261
+ +
262
+ Arguments always use a full stop as decimal marker and a comma between
263
+ arguments, whatever the parse locale. Under a parse locale whose decimal marker
264
+ is not a full stop (for example `locale: :fr`, decimal `,`, or `:ar`, decimal
265
+ `٫`), a comma between arguments that is directly followed by a digit, as in
266
+ `ustd(1,5)`, `uexp(10,0, 0,3, 2)`, `ustd(1,230, 0,004)` or `ustd(1.230,0.004)`,
267
+ is taken as a likely decimal comma: the AsciiMath parser raises
268
+ `Plurimath::Errors::InvalidUncertainty` instead of guessing, which
269
+ `Plurimath::Math.parse` reports as `Plurimath::Math::ParseError`.
270
+ `ustd(1.230, 0.004)`, with a full stop and a space after each comma, reads the
271
+ same under every locale. Input without `ustd(`, `uexp(` or `ucov(` is not
272
+ checked. A value written with the Arabic decimal mark, such as
273
+ `ustd(1٫230, 0٫004)` under `:ar`, is not an uncertainty form and is still read
274
+ as the letters `u s t d` followed by a bracket.
275
+
276
+ UnicodeMath::
277
+ The existing intent form `ⓘ("marker"content)`:
278
+ +
279
+ [source]
280
+ ----
281
+ ⓘ("standard-uncertainty(1.230,0.004)"1.230(0.004))
282
+ ⓘ("standard-uncertainty:concise(1.230,0.004)"1.230(4))
283
+ ⓘ("expanded-uncertainty(10.0,0.3,2)"(10.0±0.3), k=2)
284
+ ⓘ("expanded-uncertainty(10.0,0.3,2,0.95)"(10.0±0.3), k=2, p=0.95)
285
+ ⓘ("coverage-interval:shortest(1.024,0.983,1.088,0.95)"1.024, [0.983, 1.088], p=0.95)
286
+ ----
287
+ +
288
+ `±` is written without spaces here because today's UnicodeMath reader fails on
289
+ `1.23 ± 0.04` with spaces (see <<baseline>>).
290
+ +
291
+ A negative value that would start the visible content is put in the invisible
292
+ `〖〗` group, because the reader cannot take `"marker"-…`. The marker is
293
+ unchanged:
294
+ +
295
+ ----
296
+ ⓘ("standard-uncertainty(-1.230,0.004)"〖-1.230〗(0.004))
297
+ ⓘ("coverage-interval(-1.024,0.983,1.088,0.95)"〖-1.024〗, [0.983, 1.088], p=0.95)
298
+ ----
299
+ +
300
+ An expanded result already starts with `(`, so it is written as before.
301
+
302
+ MathML::
303
+ An `mrow` carrying the marker as `intent`, written whatever the `intent:`
304
+ option of `to_mathml` is, because it carries data, not just speech hints.
305
+ Plurimath already writes back an `intent` it has read in the same way.
306
+ +
307
+ [source,xml]
308
+ ----
309
+ <mrow intent="standard-uncertainty(1.230,0.004)">
310
+ <mn>1.230</mn>
311
+ <mrow>
312
+ <mo>(</mo>
313
+ <mn>0.004</mn>
314
+ <mo>)</mo>
315
+ </mrow>
316
+ </mrow>
317
+
318
+ <mrow intent="standard-uncertainty:concise(1.230,0.004)">
319
+ <mn>1.230</mn>
320
+ <mrow>
321
+ <mo>(</mo>
322
+ <mn>4</mn>
323
+ <mo>)</mo>
324
+ </mrow>
325
+ </mrow>
326
+
327
+ <mrow intent="expanded-uncertainty(10.0,0.3,2)">
328
+ <mrow>
329
+ <mo>(</mo>
330
+ <mn>10.0</mn>
331
+ <mo>&#xb1;</mo>
332
+ <mn>0.3</mn>
333
+ <mo>)</mo>
334
+ </mrow>
335
+ <mo>,</mo>
336
+ <mi>k</mi>
337
+ <mo>=</mo>
338
+ <mn>2</mn>
339
+ </mrow>
340
+
341
+ <mrow intent="coverage-interval:shortest(1.024,0.983,1.088,0.95)">
342
+ <mn>1.024</mn>
343
+ <mo>,</mo>
344
+ <mrow>
345
+ <mo>[</mo>
346
+ <mn>0.983</mn>
347
+ <mo>,</mo>
348
+ <mn>1.088</mn>
349
+ <mo>]</mo>
350
+ </mrow>
351
+ <mo>,</mo>
352
+ <mi>p</mi>
353
+ <mo>=</mo>
354
+ <mn>0.95</mn>
355
+ </mrow>
356
+ ----
357
+ +
358
+ Expanded with `p` appends `<mo>,</mo><mi>p</mi><mo>=</mo><mn>0.95</mn>`.
359
+ The full `<math>` documents are in the contract. A reader that ignores `intent`
360
+ shows the visible form; a reader that speaks `intent` hears the concept name
361
+ and its numbers.
362
+
363
+ OMML::
364
+ OMML has no attribute for meaning, so the marker is the text of a hidden,
365
+ zero-size phantom placed first inside an `m:box` that holds the whole result:
366
+ +
367
+ [source,xml]
368
+ ----
369
+ <m:box>
370
+ <m:e>
371
+ <m:phant>
372
+ <m:phantPr>
373
+ <m:show m:val="off"/>
374
+ <m:zeroWid m:val="on"/>
375
+ <m:zeroAsc m:val="on"/>
376
+ <m:zeroDesc m:val="on"/>
377
+ </m:phantPr>
378
+ <m:e>
379
+ <m:r>
380
+ <m:rPr>
381
+ <m:sty m:val="p"/>
382
+ </m:rPr>
383
+ <m:t>standard-uncertainty(1.230,0.004)</m:t>
384
+ </m:r>
385
+ </m:e>
386
+ </m:phant>
387
+ <m:r>
388
+ <m:t>1.230</m:t>
389
+ </m:r>
390
+ <m:d>
391
+ <m:dPr>
392
+ <m:begChr m:val="("/>
393
+ <m:sepChr m:val=""/>
394
+ <m:endChr m:val=")"/>
395
+ </m:dPr>
396
+ <m:e>
397
+ <m:r>
398
+ <m:t>0.004</m:t>
399
+ </m:r>
400
+ </m:e>
401
+ </m:d>
402
+ </m:e>
403
+ </m:box>
404
+ ----
405
+ +
406
+ The other variants use the same box and phantom with their own marker, followed
407
+ by the visible content written the way Plurimath writes it today: one `m:r` per
408
+ number, `±`, `,`, `k`, `p` and `=`, and an `m:d` for each bracket (`(` `)` or
409
+ `[` `]`). A unit is wrapped in its own inner `m:box`. Exact XML for every case is
410
+ in the contract.
411
+
412
+ [[units]]
413
+ == Units
414
+
415
+ Recommendation: *allow* an optional unit child, limited to
416
+ `Math::Function::Unitsml`.
417
+
418
+ Why:
419
+
420
+ * §7.2.1 (2) and §7.2.3 (2) say the unit should always be given.
421
+ * §7.2.4 places the unit right after the bracketed result and before the
422
+ coverage statement: `(100.02147 ± 0.00079) g, k = 2`. A unit written as a
423
+ separate expression after the node would land after `k = 2`.
424
+ * No dependency change is needed; the node reuses the existing
425
+ `Math::Function::Unitsml` rendering. No unit conversion is done.
426
+
427
+ What survives a round trip:
428
+
429
+ * AsciiMath: exact, the UnitsML text is written back
430
+ (`uexp(100.02147, 0.00079, 2, "unitsml(g)")`). A unit read from MathML,
431
+ OMML or UnicodeMath is a rendered `Math::Formula`; it is written back as
432
+ `"unitsml(...)"` when that is possible, by this rule:
433
+ ** The formula must be a product of unit symbols separated by `·`
434
+ (`Math::Symbols::Cdot`), each symbol optionally raised to an integer power,
435
+ which may be negative.
436
+ ** A unit symbol is ASCII letters only, upright (`Math::Function::FontStyle::Normal`)
437
+ or plain, held as a symbol or a text. `kg`, `m^2` and `s^-1` qualify; `µm`
438
+ and `°C` do not, because their rendered symbols are not UnitsML input.
439
+ ** The derived text, for example `kg*m^2` or `m*s^-1`, must parse as UnitsML
440
+ and UnitsML must render it back to the same product. A unit written as
441
+ `m/s` therefore comes back as `"unitsml(m*s^-1)"`, which renders the same.
442
+ ** Any other unit cannot be written in the AsciiMath call, whose grammar
443
+ accepts only a final `"unitsml(...)"` argument. It is written after the call
444
+ in its own AsciiMath form, for example `ustd(1.230, 0.004) mathbf(x)`. That
445
+ text still parses and shows the unit, but it reads back as an uncertainty
446
+ without a unit followed by the unit expression.
447
+ * MathML: the unit is written with the usual UnitsML rendering in an
448
+ `mrow arg="unit"`, referred to by `$unit` in the marker. On reading, the unit
449
+ comes back as that rendered content (a `Math::Formula`), not as a `Unitsml`
450
+ node. This is the same loss every UnitsML node has today: Plurimath does not
451
+ turn rendered MathML, OMML, UnicodeMath or AsciiMath back into `Unitsml`.
452
+ * OMML: as MathML, with the unit in an inner `m:box`.
453
+ * UnicodeMath: as MathML, with the unit as `ⓐ(unit \mupg)`. Today's reader
454
+ rejects `ⓐ` in this position, so B3 needs grammar work for it.
455
+
456
+ If this is judged too much for the first release, the fallback is to leave
457
+ units out of the node in B1–B4 and note that expanded results with units then
458
+ render with the unit after `k`.
459
+
460
+ == What each format keeps
461
+
462
+ [cols="2,1,1,1,1,1,1"]
463
+ |===
464
+ | |AsciiMath |MathML |OMML |UnicodeMath |LaTeX |HTML
465
+
466
+ |value, magnitude or endpoints, exact text |yes |yes |yes |yes |shown |shown
467
+ |kind of result |yes |yes |yes |yes |by layout only |by layout only
468
+ |`k`, `p` |yes |yes |yes |yes |shown |shown
469
+ |interval choice (shortest, symmetric) |yes |yes |yes |yes |lost |lost
470
+ |concise or absolute notation |yes |yes |yes |yes |shown |shown
471
+ |unit |exact |rendered only |rendered only |rendered only (B3 grammar) |shown |shown
472
+ |read back as `Uncertainty` |from B4 |yes |yes |yes |no |no
473
+ |===
474
+
475
+ "Shown" means visible in the output but there is no reader. LaTeX and HTML
476
+ never turn an expanded or interval result into a standard one; standard results
477
+ are never written with `±`.
478
+
479
+ Other software: a MathML consumer that ignores `intent` shows the visible form
480
+ correctly. An OMML consumer that ignores phantoms, including Plurimath before
481
+ this work, will show the marker text as ordinary text. Today's Plurimath reads
482
+ these MathML and UnicodeMath forms as a generic `Function::Intent` (except the
483
+ UnicodeMath unit form, which it rejects).
484
+
485
+ == LaTeX input
486
+
487
+ Recommendation: output only for #493. #493 does not require LaTeX input, and
488
+ LaTeX has no single notation for this; the usual one is the `siunitx` package
489
+ (`\num{1.230(4)}`). Reading `siunitx` can be added later as its own task with
490
+ its own tests. The LaTeX output above uses only plain math so that no package is
491
+ needed.
492
+
493
+ == Questions for the maintainer
494
+
495
+ . Approve, change or leave out the coverage-interval variant (and confirm that
496
+ the `+a/−b` layout stays out until its meaning is decided).
497
+ . Units: allow the `Unitsml` child as recommended, or leave units out at first.
498
+ . Marker names and the use of MathML intent syntax with literal numbers. The
499
+ syntax follows our reading of the MathML 4 intent grammar (concept name,
500
+ `:property`, arguments that are numbers or `$references`) and has not been
501
+ checked with a MathML 4 validator or screen reader.
502
+ . OMML hidden phantom marker. Not yet checked in Microsoft Word: whether Word
503
+ keeps the hidden zero-size phantom when the equation is edited, and how it
504
+ displays it.
505
+ . When the visible content in MathML, OMML or UnicodeMath disagrees with the
506
+ marker, the proposal trusts the marker without warning. A stricter option is to
507
+ raise an error.
508
+ . AsciiMath word names `ustd`, `uexp`, `ucov`.
509
+
510
+ [[baseline]]
511
+ == Observations on the current code
512
+
513
+ Measured on revision `d2aa3236` while writing this page; none of these is
514
+ changed by this proposal:
515
+
516
+ * UnicodeMath: `1.23 ± 0.04` (with spaces, as Plurimath itself writes it)
517
+ is read into raw arrays and fails to render; `1.23±0.04` works.
518
+ * OMML input reads `1.23` as `Math::Symbols::Symbol`, not `Math::Number`, so
519
+ MathML written from OMML input uses `<mi>1.23</mi>`.
520
+ * A generic `Function::Intent` written to OMML puts its intent text into the
521
+ visible equation inside nested `m:r` elements.
522
+ * The MathML reader dropped `arg="unit"` on an `mrow` in our test.