1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
// SPDX-License-Identifier: Apache-2.0
//! Homogeneous sums and quotient derivatives with an extended exponent range.
use super::admission::EvaluationRefusal;
use super::decode;
use crate::features::FinitePoint3;
use crate::math::sum::{ExactSignedSum, LaneProductSums, LaneSum, ScaledValue};
use crate::scalar::FiniteReal;
/// Cloneable pole traversal. Admission precedes each input-dependent advance.
#[derive(Clone)]
struct SumTerms<'scratch, 'ctx, 'arena, I> {
terms: I,
scratch: &'scratch decode::Scratch<'ctx, 'arena>,
input_sized: bool,
/// The work steps this traversal has charged.
charged: usize,
}
impl<I: Iterator> Iterator for SumTerms<'_, '_, '_, I> {
type Item = I::Item;
fn next(&mut self) -> Option<Self::Item> {
if self.terms.size_hint().1 == Some(0) {
return None;
}
if self.input_sized {
self.scratch.work(1)?;
self.charged += 1;
}
self.terms.next()
}
}
/// Charge a traversal of the terms that a shared traversal has already made:
/// `work` steps, the count one traversal charged.
fn charge_traversal(
scratch: &decode::Scratch<'_, '_>,
work: usize,
) -> Result<(), EvaluationRefusal> {
if work > 0 {
// A refused charge is recorded in `scratch`, which `unless_refused`
// states.
scratch.work(work);
}
scratch.unless_refused()
}
#[derive(Clone, Copy)]
pub(super) struct Homogeneous {
values: [Option<ScaledValue>; 4],
constant: [Option<FiniteReal>; 3],
}
impl Homogeneous {
/// The four lanes are summed as `product_sum` sums each, and a constant
/// coordinate is one every term with a nonzero blend shares.
///
/// One traversal forms the f64 pass of every lane and the constant
/// coordinates; a lane that `product_sum` would replay is replayed through
/// the exact accumulator. Work is charged as for one traversal per lane,
/// one per replay and one for the constant coordinates, in that order, so
/// the budget and the point of any refusal do not depend on how the
/// traversals are shared.
pub(super) fn sum(
scratch: &decode::Scratch<'_, '_>,
terms: impl Iterator<Item = Option<([f64; 2], f64, FinitePoint3)>> + Clone,
) -> Result<Option<Self>, EvaluationRefusal> {
let input_sized = terms.size_hint().1.is_none_or(|count| count > 2);
let terms = SumTerms {
terms,
scratch,
input_sized,
charged: 0,
};
let mut traversal = terms.clone();
let mut lanes = LaneProductSums::<4>::default();
let mut constant = [None; 3];
let mut first = true;
for term in traversal.by_ref() {
// A missing pole or a factor that is not finite leaves the first
// lane undefined, and `product_sum` stops there.
let Some((basis, weight, point)) = term else {
scratch.unless_refused()?;
return Ok(None);
};
let shared = [basis[0], basis[1], weight];
if lanes
.add(shared, [point.x, point.y, point.z, 1.0])
.is_none()
{
scratch.unless_refused()?;
return Ok(None);
}
if basis.contains(&0.0) || weight == 0.0 {
continue;
}
for (axis, coordinate) in point.coordinates().into_iter().enumerate() {
if first {
constant[axis] = Some(coordinate);
} else if constant[axis] != Some(coordinate) {
constant[axis] = None;
}
}
first = false;
}
scratch.unless_refused()?;
let traversal_work = traversal.charged;
let mut values = [None; 4];
for (axis, (value, lane)) in values.iter_mut().zip(lanes.finish()).enumerate() {
if axis > 0 {
charge_traversal(scratch, traversal_work)?;
}
// `values` carries the same exact zero that `ExactSignedSum::finish`
// and `add_scaled_product` state as `None`.
*value = match lane {
LaneSum::Settled(value) => value,
LaneSum::Replay => {
let mut sum = ExactSignedSum::default();
for term in terms.clone() {
let Some((basis, weight, point)) = term else {
scratch.unless_refused()?;
return Ok(None);
};
sum.add_factors([
basis[0],
basis[1],
weight,
[point.x, point.y, point.z, 1.0][axis],
]);
}
scratch.unless_refused()?;
sum.finish()
}
};
}
charge_traversal(scratch, traversal_work)?;
Ok(Some(Self { values, constant }))
}
/// Keep source weights when they remain normal. Otherwise choose one
/// binary scale for the complete output net, preserving relative weights.
pub(super) fn weights(
scratch: &decode::Scratch<'_, '_>,
values: &[Self],
) -> Result<Option<Vec<f64>>, EvaluationRefusal> {
let result = (|| {
scratch.work(0)?;
for value in values {
scratch.work(1)?;
value.values[3]?;
}
let mut output = Vec::new();
for value in values {
scratch.work(1)?;
let Some(weight) = value.values[3].and_then(|weight| weight.finite().ok()) else {
break;
};
scratch.work(std::mem::size_of::<f64>())?;
output.push(weight.get());
}
if output.len() == values.len() {
let mut normal = true;
for value in &output {
scratch.work(1)?;
if !value.is_normal() {
normal = false;
break;
}
}
if normal {
return Some(output);
}
}
let mut bounds: Option<(i32, i32)> = None;
for value in values {
scratch.work(1)?;
let exponent = value.values[3]?.exponent();
bounds = Some(match bounds {
Some((minimum, maximum)) => (minimum.min(exponent), maximum.max(exponent)),
None => (exponent, exponent),
});
}
let (minimum, maximum) = bounds?;
let normal_range = (maximum - 1023, minimum + 1021);
let full_range = (maximum - 1024, minimum + 1073);
let (lower, upper) = if normal_range.0 <= normal_range.1 {
normal_range
} else {
full_range
};
if lower > upper {
return None;
}
// Select the nearest exponent to zero that preserves the complete net.
let exponent = if 0 < lower {
lower
} else if 0 > upper {
upper
} else {
0
};
output.clear();
for value in values {
scratch.work(1)?;
let weight = value.values[3]
.and_then(|weight| weight.rescale(exponent))
.map(FiniteReal::get)
.filter(|weight| *weight != 0.0)?;
scratch.work(std::mem::size_of::<f64>())?;
output.push(weight);
}
Some(output)
})();
scratch.unless_refused()?;
Ok(result)
}
/// Subtract the specified weight derivatives, then divide by the base
/// weight. Repeated corrections express second derivatives without first
/// multiplying a possibly large first derivative by two.
///
/// A zero base weight, and a correction outside the accumulator's range,
/// leave no value. Each lane is its coordinate, or the signed infinity of
/// a quotient that overflows.
pub(super) fn project(
self,
base: Self,
subtract: &[(Self, [FiniteReal; 3])],
) -> Option<[Result<FiniteReal, f64>; 3]> {
let denominator = base.values[3]?;
let mut lanes = [Ok(FiniteReal::ZERO); 3];
for (axis, lane) in lanes.iter_mut().enumerate() {
// A constant coordinate divides out exactly, including at f64::MAX.
if subtract.is_empty() {
if let Some(value) = self.constant[axis] {
*lane = Ok(value);
continue;
}
}
let numerator = if subtract.is_empty() {
self.values[axis]
} else {
let mut sum = ExactSignedSum::default();
sum.add_scaled_product(self.values[axis], FiniteReal::ONE)?;
for (weight, factor) in subtract {
sum.add_scaled_product(weight.values[3], factor[axis].negated())?;
}
sum.finish()
};
*lane = numerator.map_or(Ok(FiniteReal::ZERO), |value| value.quotient(denominator));
}
Some(lanes)
}
}
/// The coordinates when every lane is finite. Otherwise the value of every
/// lane: the coordinate, or the signed infinity of a lane that overflows.
pub(super) fn finite_lanes<const N: usize>(
lanes: [Result<FiniteReal, f64>; N],
) -> Result<[FiniteReal; N], [f64; N]> {
let mut coordinates = [FiniteReal::ZERO; N];
for (coordinate, lane) in coordinates.iter_mut().zip(lanes) {
let Ok(value) = lane else {
return Err(lanes.map(|lane| lane.map_or_else(|raw| raw, FiniteReal::get)));
};
*coordinate = value;
}
Ok(coordinates)
}
#[cfg(test)]
mod tests;