Skip to main content

sva_formula/
refusal.rs

1// Concern: declares this crate's refusal codes and Left, the atom that left A | Non-concern: rendering a diagnostic block (sva-engine) | IO: none
2
3use crate::origin::Origin;
4use crate::ty::{Mismatch, Ty};
5
6#[derive(Clone, Copy, Debug, PartialEq, Eq)]
7pub enum Code {
8    DomainMismatch,
9    SamplesInClosedForm,
10    RateConflict,
11    FrameMismatch,
12    WidthMismatch,
13    NonAffineSingular,
14    SeriesNotSummable,
15    LeftAlgebra,
16}
17
18impl Code {
19    pub fn as_str(self) -> &'static str {
20        match self {
21            Code::DomainMismatch => "type.domain_mismatch",
22            Code::SamplesInClosedForm => "type.samples_in_closed_form",
23            Code::RateConflict => "type.rate_conflict",
24            Code::FrameMismatch => "type.frame_mismatch",
25            Code::WidthMismatch => "type.width_mismatch",
26            Code::NonAffineSingular => "type.non_affine_singular",
27            Code::SeriesNotSummable => "type.series_not_summable",
28            Code::LeftAlgebra => "cast.left_algebra",
29        }
30    }
31}
32
33#[derive(Clone, Debug, PartialEq, Eq)]
34pub struct Refusal {
35    pub code: Code,
36    pub origin: Origin,
37    pub message: String,
38}
39
40impl Refusal {
41    pub fn new(code: Code, origin: Origin, message: impl Into<String>) -> Refusal {
42        Refusal {
43            code,
44            origin,
45            message: message.into(),
46        }
47    }
48}
49
50impl Refusal {
51    /// The text for two operand types that have no common type, one repair per side.
52    pub fn of_mismatch(m: Mismatch, origin: Origin, a: Ty, b: Ty) -> Refusal {
53        let (code, message) = match m {
54            Mismatch::Domain => (
55                Code::DomainMismatch,
56                "a closed form in t meets one in f. write fourier on the t side, or ifourier on \
57                 the f side"
58                    .to_string(),
59            ),
60            Mismatch::SamplesInClosedForm => (
61                Code::SamplesInClosedForm,
62                "this mixes a closed form and samples. write sample(...) on the closed form"
63                    .to_string(),
64            ),
65            Mismatch::Rate => (
66                Code::RateConflict,
67                format!(
68                    "{:?} Hz meets {:?} Hz; one rate per expression",
69                    a.rate, b.rate
70                ),
71            ),
72            Mismatch::Frames => (
73                Code::FrameMismatch,
74                "two frame layouts meet in one expression".to_string(),
75            ),
76            Mismatch::Width => (
77                Code::WidthMismatch,
78                format!(
79                    "{} components meet {}; an elementwise operation needs equal widths, or one \
80                     side mono",
81                    a.width, b.width
82                ),
83            ),
84        };
85        Refusal::new(code, origin, message)
86    }
87}
88
89/// One atom factor, so a refusal can name the pair that met rather than the whole product,
90/// or `Value` where no factor is to blame.
91#[derive(Clone, Copy, Debug, PartialEq, Eq)]
92pub enum Factor {
93    Value,
94    Amplitude,
95    Polynomial,
96    Exponential,
97    Gaussian,
98    Indicator,
99    Pole,
100    PrincipalValue,
101    Delta,
102}
103
104impl Factor {
105    pub fn as_str(self) -> &'static str {
106        match self {
107            Factor::Value => "this subterm",
108            Factor::Amplitude => "an amplitude",
109            Factor::Polynomial => "a polynomial",
110            Factor::Exponential => "an exponential",
111            Factor::Gaussian => "a Gaussian",
112            Factor::Indicator => "an indicator",
113            Factor::Pole => "a pole",
114            Factor::PrincipalValue => "a principal value",
115            Factor::Delta => "a delta",
116        }
117    }
118}
119
120#[derive(Clone, Copy, Debug, PartialEq, Eq)]
121pub struct AtomSketch {
122    pub first: Factor,
123    pub second: Option<Factor>,
124}
125
126impl AtomSketch {
127    pub fn of(first: Factor) -> AtomSketch {
128        AtomSketch {
129            first,
130            second: None,
131        }
132    }
133
134    pub fn pair(first: Factor, second: Factor) -> AtomSketch {
135        AtomSketch {
136            first,
137            second: Some(second),
138        }
139    }
140
141    pub fn describe(self) -> String {
142        match self.second {
143            Some(second) => format!("{} times {}", self.first.as_str(), second.as_str()),
144            None => self.first.as_str().to_string(),
145        }
146    }
147}
148
149#[derive(Clone, Copy, Debug, PartialEq, Eq)]
150pub enum LeftReason {
151    NotTempered,
152    GaussianTimesIndicator,
153    GaussianTimesPole,
154    PoleTimesIndicator,
155    PoleOrder(u16),
156    NonAffineArgument,
157    KeyedOnASignal,
158    Nonlinearity,
159    Reciprocal,
160    SeriesNotSummable,
161    SeriesNonUniform,
162    SeriesTimesSeries,
163    HilbertOfPolynomial,
164    NotInTable,
165    NoValue,
166    NotFinite,
167    Overflow,
168    Unsubstituted,
169}
170
171impl LeftReason {
172    /// The refusal's second line. Every one names what the dual would have to be, so "left A"
173    /// never reads as "no transform exists".
174    pub fn clause(self) -> &'static str {
175        match self {
176            LeftReason::NotTempered => {
177                "an exponential growing on an unbounded side is not a tempered distribution"
178            }
179            LeftReason::GaussianTimesIndicator => {
180                "a Gaussian times an indicator leaves A: its dual is an error function"
181            }
182            LeftReason::GaussianTimesPole => {
183                "a Gaussian times a pole leaves A: its dual is a Faddeeva function"
184            }
185            LeftReason::PoleTimesIndicator => {
186                "a pole times an indicator leaves A: its dual is an exponential integral"
187            }
188            LeftReason::PoleOrder(_) => {
189                "A holds residues to total pole order 12, and a principal value only at order one"
190            }
191            LeftReason::NonAffineArgument => {
192                "an argument that is not affine in the free variable spreads to infinitely \
193                 many lines"
194            }
195            LeftReason::KeyedOnASignal => {
196                "a hash keyed on a moving value is piecewise constant, and a piecewise \
197                 constant has no finite atom sum"
198            }
199            LeftReason::Nonlinearity => "a bounded nonlinearity has no finite atom sum",
200            LeftReason::Reciprocal => {
201                "a reciprocal of a non-constant closed form is not an atom sum"
202            }
203            LeftReason::SeriesNotSummable => {
204                "a series converges in A only with polynomially bounded coefficients"
205            }
206            LeftReason::SeriesNonUniform => {
207                "a series index entering a Gaussian width or an indicator bound has no \
208                 termwise dual"
209            }
210            LeftReason::SeriesTimesSeries => {
211                "two series multiply term by term only where one of them is a finite atom sum"
212            }
213            LeftReason::HilbertOfPolynomial => {
214                "the Hilbert transform of a polynomial has no tempered value"
215            }
216            LeftReason::NotInTable => {
217                "no rule of this Fourier dual rules version holds this atom; a later version may"
218            }
219            LeftReason::NoValue => {
220                "a division or a remainder by zero names no number, and no atom holds one"
221            }
222            LeftReason::NotFinite => {
223                "a rate, a width or an instant that is not a finite double names no number: \
224                 it was divided by zero or it passed the largest double, and no atom holds one"
225            }
226            LeftReason::Overflow => {
227                "a growing exponential passes the largest double inside its window, so its \
228                 transform has no finite weight; a decay read before its onset grows there, \
229                 and a crop at the onset keeps it finite"
230            }
231            LeftReason::Unsubstituted => {
232                "a node or a parameter reaches the closed form unsubstituted; the graph resolves one \
233                 before it normalizes"
234            }
235        }
236    }
237}
238
239/// The atom that left A, the token naming where it was written, and the factor pair that
240/// did it.
241#[derive(Clone, Copy, Debug, PartialEq, Eq)]
242pub struct Left {
243    pub origin: Origin,
244    pub sketch: AtomSketch,
245    pub reason: LeftReason,
246}
247
248impl Left {
249    pub fn new(origin: Origin, sketch: AtomSketch, reason: LeftReason) -> Left {
250        Left {
251            origin,
252            sketch,
253            reason,
254        }
255    }
256
257    pub fn refusal(self) -> Refusal {
258        Refusal::new(
259            Code::LeftAlgebra,
260            self.origin,
261            format!(
262                "{} left A. {}",
263                self.sketch.describe(),
264                self.reason.clause()
265            ),
266        )
267    }
268}