1use std::num::NonZeroUsize;
4
5use sim_lib_numbers_tensor_cmplxf::ComplexFTensor;
6use sim_lib_numbers_tensor_f64::F64Tensor;
7
8use crate::SignalError;
9
10#[derive(Clone, Copy, Debug, PartialEq, Eq)]
12pub enum Direction {
13 Forward,
15 Inverse,
17}
18
19#[derive(Clone, Copy, Debug, PartialEq, Eq)]
21pub enum Normalization {
22 None,
24 Forward,
26 Inverse,
28 Orthonormal,
30}
31
32#[derive(Clone, Copy, Debug, PartialEq, Eq)]
34pub enum SignConvention {
35 NegativeForward,
38 PositiveForward,
41}
42
43impl SignConvention {
44 pub fn angle_sign(self, direction: Direction) -> f64 {
46 match (self, direction) {
47 (Self::NegativeForward, Direction::Forward)
48 | (Self::PositiveForward, Direction::Inverse) => -1.0,
49 (Self::PositiveForward, Direction::Forward)
50 | (Self::NegativeForward, Direction::Inverse) => 1.0,
51 }
52 }
53}
54
55#[derive(Clone, Copy, Debug, PartialEq, Eq)]
57pub enum SpectrumPacking {
58 Full,
60 HermitianHalf,
62}
63
64#[derive(Clone, Copy, Debug, PartialEq, Eq)]
66pub enum LengthPolicy {
67 Exact,
69 Pad,
71 Truncate,
73}
74
75#[derive(Clone, Copy, Debug, PartialEq, Eq)]
77pub enum PaddingPolicy {
78 Reject,
80 Zero,
82}
83
84#[derive(Clone, Copy, Debug, PartialEq, Eq)]
86pub struct Stride {
87 offset: usize,
88 step: NonZeroUsize,
89}
90
91impl Stride {
92 pub const fn contiguous() -> Self {
94 Self {
95 offset: 0,
96 step: NonZeroUsize::MIN,
97 }
98 }
99
100 pub fn new(offset: usize, step: usize) -> Result<Self, SignalError> {
102 let step = NonZeroUsize::new(step).ok_or(SignalError::ZeroStride)?;
103 Ok(Self { offset, step })
104 }
105
106 pub const fn offset(self) -> usize {
108 self.offset
109 }
110
111 pub const fn step(self) -> usize {
113 self.step.get()
114 }
115
116 pub fn available(self, len: usize) -> usize {
118 if self.offset >= len {
119 0
120 } else {
121 1 + (len - 1 - self.offset) / self.step()
122 }
123 }
124
125 pub fn physical_index(self, logical: usize) -> Result<usize, SignalError> {
127 logical
128 .checked_mul(self.step())
129 .and_then(|delta| self.offset.checked_add(delta))
130 .ok_or(SignalError::StrideOverflow)
131 }
132}
133
134impl Default for Stride {
135 fn default() -> Self {
136 Self::contiguous()
137 }
138}
139
140#[derive(Clone, Copy, Debug, PartialEq, Eq)]
142pub enum PlacementPolicy {
143 OutOfPlace,
145 InPlace,
147}
148
149#[derive(Clone, Copy, Debug, PartialEq, Eq)]
151pub enum DctType {
152 I,
154 II,
156 III,
158 IV,
160}
161
162#[derive(Clone, Copy, Debug, PartialEq, Eq)]
164pub enum DstType {
165 I,
167 II,
169 III,
171 IV,
173}
174
175#[derive(Clone, Copy, Debug, PartialEq, Eq)]
177pub enum TransformKind {
178 Dft,
180 Fft,
182 RealFft,
184 Dct(DctType),
186 Dst(DstType),
188}
189
190#[derive(Clone, Debug, PartialEq, Eq)]
192pub struct TransformPlan {
193 pub kind: TransformKind,
195 pub len: usize,
197 pub direction: Direction,
199 pub normalization: Normalization,
201 pub sign: SignConvention,
203 pub packing: SpectrumPacking,
205 pub length: LengthPolicy,
207 pub padding: PaddingPolicy,
209 pub stride: Stride,
211 pub placement: PlacementPolicy,
213}
214
215impl TransformPlan {
216 pub fn new(kind: TransformKind, len: usize) -> Self {
218 Self {
219 kind,
220 len,
221 direction: Direction::Forward,
222 normalization: Normalization::Inverse,
223 sign: SignConvention::NegativeForward,
224 packing: SpectrumPacking::Full,
225 length: LengthPolicy::Exact,
226 padding: PaddingPolicy::Reject,
227 stride: Stride::contiguous(),
228 placement: PlacementPolicy::OutOfPlace,
229 }
230 }
231
232 pub fn validate(&self) -> Result<(), SignalError> {
234 if self.len == 0 {
235 return Err(SignalError::InvalidLength {
236 len: self.len,
237 reason: "transforms require at least one value",
238 });
239 }
240 if self.kind == TransformKind::Dct(DctType::I) && self.len < 2 {
241 return Err(SignalError::InvalidLength {
242 len: self.len,
243 reason: "DCT-I requires at least two values",
244 });
245 }
246 if self.packing != SpectrumPacking::Full && self.kind != TransformKind::RealFft {
247 return Err(SignalError::InvalidPolicy {
248 policy: "packing",
249 reason: "Hermitian-half packing is defined only for real FFT",
250 });
251 }
252 match (self.length, self.padding) {
253 (LengthPolicy::Pad, PaddingPolicy::Zero)
254 | (LengthPolicy::Exact | LengthPolicy::Truncate, PaddingPolicy::Reject) => Ok(()),
255 (LengthPolicy::Pad, PaddingPolicy::Reject) => Err(SignalError::InvalidPolicy {
256 policy: "padding",
257 reason: "Pad length policy requires zero padding",
258 }),
259 (LengthPolicy::Exact | LengthPolicy::Truncate, PaddingPolicy::Zero) => {
260 Err(SignalError::InvalidPolicy {
261 policy: "padding",
262 reason: "zero padding requires the Pad length policy",
263 })
264 }
265 }
266 }
267}
268
269#[derive(Clone, Copy, Debug)]
271pub enum SignalView<'a> {
272 Complex(&'a [(f64, f64)]),
274 Real(&'a [f64]),
276}
277
278impl<'a> SignalView<'a> {
279 pub fn from_complex_tensor(tensor: &'a ComplexFTensor) -> Self {
281 Self::Complex(tensor.as_slice())
282 }
283
284 pub fn from_real_tensor(tensor: &'a F64Tensor) -> Self {
286 Self::Real(tensor.as_slice())
287 }
288
289 pub fn physical_len(self) -> usize {
291 match self {
292 Self::Complex(values) => values.len(),
293 Self::Real(values) => values.len(),
294 }
295 }
296}
297
298#[derive(Debug)]
300pub enum SignalViewMut<'a> {
301 Complex(&'a mut [(f64, f64)]),
303 Real(&'a mut [f64]),
305}
306
307#[derive(Clone, Debug, PartialEq)]
309pub enum SignalBuffer {
310 Complex(ComplexFTensor),
312 Real(F64Tensor),
314}
315
316impl SignalBuffer {
317 pub fn len(&self) -> usize {
319 match self {
320 Self::Complex(values) => values.as_slice().len(),
321 Self::Real(values) => values.as_slice().len(),
322 }
323 }
324
325 pub fn is_empty(&self) -> bool {
327 self.len() == 0
328 }
329}