1use crate::constants::F_L1_HZ;
10use crate::tolerances::DOPPLER_GRID_EDGE_EPS_HZ;
11use crate::validate;
12
13pub const CA_CODE_LENGTH: usize = 1023;
15
16pub const CA_CHIP_RATE_HZ: f64 = 1_023_000.0;
18
19const TWO_PI: f64 = 2.0 * std::f64::consts::PI;
20const DEFAULT_DOPPLER_MIN_HZ: f64 = -2500.0;
21const DEFAULT_DOPPLER_MAX_HZ: f64 = 2500.0;
22const DEFAULT_DOPPLER_STEP_HZ: f64 = 500.0;
23const DEFAULT_SAMPLE_RATE_HZ: f64 = 2.046e6;
24const MAX_DOPPLER_BINS: usize = 4096;
25
26#[derive(Debug, Clone, PartialEq)]
28pub enum SignalError {
29 UnsupportedPrn(i64),
31 InvalidInput {
33 field: &'static str,
35 reason: &'static str,
37 },
38 EmptySamples,
40 TooShort,
42}
43
44impl core::fmt::Display for SignalError {
45 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
46 match self {
47 Self::UnsupportedPrn(prn) => write!(f, "unsupported GPS C/A PRN {prn}"),
48 Self::InvalidInput { field, reason } => {
49 write!(f, "invalid signal input {field}: {reason}")
50 }
51 Self::EmptySamples => write!(f, "empty sample vector"),
52 Self::TooShort => write!(f, "sample vector shorter than one C/A code period"),
53 }
54 }
55}
56
57impl std::error::Error for SignalError {}
58
59#[derive(Debug, Clone, Copy, PartialEq)]
61pub struct IqSample {
62 pub i: f64,
64 pub q: f64,
66}
67
68impl IqSample {
69 pub const fn new(i: f64, q: f64) -> Self {
71 Self { i, q }
72 }
73
74 pub const fn real(i: f64) -> Self {
76 Self { i, q: 0.0 }
77 }
78}
79
80#[derive(Debug, Clone, Copy, PartialEq)]
82pub struct ReplicaOptions {
83 pub sample_rate_hz: f64,
85 pub num_samples: usize,
87 pub code_phase_chips: f64,
89 pub code_doppler_hz: f64,
91}
92
93impl ReplicaOptions {
94 pub fn one_code_period() -> Self {
96 let sample_rate_hz = DEFAULT_SAMPLE_RATE_HZ;
97 let integration_time_s = CA_CODE_LENGTH as f64 / CA_CHIP_RATE_HZ;
98 Self {
99 sample_rate_hz,
100 num_samples: (sample_rate_hz * integration_time_s).round() as usize,
101 code_phase_chips: 0.0,
102 code_doppler_hz: 0.0,
103 }
104 }
105}
106
107#[derive(Debug, Clone, Copy, PartialEq)]
109pub struct CorrelateOptions {
110 pub sample_rate_hz: f64,
112 pub doppler_hz: f64,
114 pub code_phase_chips: f64,
116 pub code_doppler_hz: f64,
118}
119
120impl Default for CorrelateOptions {
121 fn default() -> Self {
122 Self {
123 sample_rate_hz: DEFAULT_SAMPLE_RATE_HZ,
124 doppler_hz: 0.0,
125 code_phase_chips: 0.0,
126 code_doppler_hz: 0.0,
127 }
128 }
129}
130
131#[derive(Debug, Clone, Copy, PartialEq)]
133pub struct CorrelationResult {
134 pub i: f64,
136 pub q: f64,
138 pub power: f64,
140}
141
142#[derive(Debug, Clone, Copy, PartialEq)]
144pub struct AcquisitionOptions {
145 pub sample_rate_hz: f64,
147 pub doppler_min_hz: f64,
149 pub doppler_max_hz: f64,
151 pub doppler_step_hz: f64,
153}
154
155impl Default for AcquisitionOptions {
156 fn default() -> Self {
157 Self {
158 sample_rate_hz: DEFAULT_SAMPLE_RATE_HZ,
159 doppler_min_hz: DEFAULT_DOPPLER_MIN_HZ,
160 doppler_max_hz: DEFAULT_DOPPLER_MAX_HZ,
161 doppler_step_hz: DEFAULT_DOPPLER_STEP_HZ,
162 }
163 }
164}
165
166#[derive(Debug, Clone, PartialEq)]
168pub struct AcquisitionGrid {
169 pub doppler_hz: Vec<f64>,
171 pub code_phase_bins: usize,
173 pub doppler_step_hz: f64,
175 pub samples_per_chip: f64,
177}
178
179#[derive(Debug, Clone, PartialEq)]
181pub struct AcquisitionResult {
182 pub code_phase_chips: f64,
184 pub doppler_hz: f64,
186 pub peak_metric: f64,
188 pub metric: f64,
190 pub peak_power: f64,
192 pub grid: AcquisitionGrid,
194}
195
196pub fn ca_code(prn: i64) -> Result<Vec<i8>, SignalError> {
198 let taps = phase_select(prn)?;
199 let raw = raw_code(taps);
200 Ok(raw.into_iter().map(|bit| 1 - 2 * bit as i8).collect())
201}
202
203pub fn ca_chip(prn: i64, index: i64) -> Result<i8, SignalError> {
205 let code = ca_code(prn)?;
206 let idx = index.rem_euclid(CA_CODE_LENGTH as i64) as usize;
207 Ok(code[idx])
208}
209
210pub fn autocorrelation(code: &[i8]) -> Vec<i32> {
212 (0..code.len())
213 .map(|lag| correlation_at_equal_len(code, code, lag as i64))
214 .collect()
215}
216
217pub fn cross_correlation(code_a: &[i8], code_b: &[i8]) -> Result<Vec<i32>, SignalError> {
219 if code_a.len() != code_b.len() {
220 return Err(invalid_signal_input("code_lengths", "length mismatch"));
221 }
222 Ok((0..code_a.len())
223 .map(|lag| correlation_at_equal_len(code_a, code_b, lag as i64))
224 .collect())
225}
226
227pub fn correlation_at(code_a: &[i8], code_b: &[i8], lag: i64) -> Result<i32, SignalError> {
229 if code_a.len() != code_b.len() {
230 return Err(invalid_signal_input("code_lengths", "length mismatch"));
231 }
232 validate_correlation_lag(code_a.len(), lag)?;
233 Ok(correlation_at_equal_len(code_a, code_b, lag))
234}
235
236fn correlation_at_equal_len(code_a: &[i8], code_b: &[i8], lag: i64) -> i32 {
237 let n = code_a.len() as i64;
238 let mut acc = 0_i32;
239 for (i, &chip_a) in code_a.iter().enumerate() {
240 let j = (i as i64 + lag).rem_euclid(n) as usize;
241 acc += i32::from(chip_a) * i32::from(code_b[j]);
242 }
243 acc
244}
245
246fn validate_correlation_lag(len: usize, lag: i64) -> Result<(), SignalError> {
247 if len == 0 || lag <= 0 {
248 return Ok(());
249 }
250 let max_index =
251 i64::try_from(len - 1).map_err(|_| invalid_signal_input("code_lengths", "out of range"))?;
252 if max_index > i64::MAX - lag {
253 return Err(invalid_signal_input("lag", "out of range"));
254 }
255 Ok(())
256}
257
258pub fn replica(prn: i64, options: ReplicaOptions) -> Result<Vec<i8>, SignalError> {
260 let sample_rate_hz = signal_positive_step(options.sample_rate_hz, "sample_rate_hz")?;
261 let code_phase_chips = signal_finite(options.code_phase_chips, "code_phase_chips")?;
262 let code_doppler_hz = signal_finite(options.code_doppler_hz, "code_doppler_hz")?;
263 let code = ca_code(prn)?;
264 Ok(sample_code(
265 &code,
266 options.num_samples,
267 sample_rate_hz,
268 code_phase_chips,
269 code_doppler_hz,
270 ))
271}
272
273pub fn correlate(
275 iq: &[IqSample],
276 prn: i64,
277 options: CorrelateOptions,
278) -> Result<CorrelationResult, SignalError> {
279 if iq.is_empty() {
280 return Err(SignalError::EmptySamples);
281 }
282 validate_iq_samples(iq, "samples")?;
283 let sample_rate_hz = signal_positive_step(options.sample_rate_hz, "sample_rate_hz")?;
284 let doppler_hz = signal_finite(options.doppler_hz, "doppler_hz")?;
285 let code_phase_chips = signal_finite(options.code_phase_chips, "code_phase_chips")?;
286 let code_doppler_hz = signal_finite(options.code_doppler_hz, "code_doppler_hz")?;
287 let code = ca_code(prn)?;
288 let sampled = sample_code(
289 &code,
290 iq.len(),
291 sample_rate_hz,
292 code_phase_chips,
293 code_doppler_hz,
294 );
295 let (i, q) = correlate_against(iq, &sampled, sample_rate_hz, doppler_hz)?;
296 let power = signal_finite(i * i + q * q, "correlation_power")?;
297 Ok(CorrelationResult { i, q, power })
298}
299
300pub fn correlate_against(
305 iq: &[IqSample],
306 code: &[i8],
307 fs: f64,
308 doppler_hz: f64,
309) -> Result<(f64, f64), SignalError> {
310 if iq.is_empty() {
311 return Err(SignalError::EmptySamples);
312 }
313 validate_iq_samples(iq, "samples")?;
314 if code.is_empty() {
315 return Err(invalid_signal_input("code", "empty"));
316 }
317 let fs = signal_positive_step(fs, "sample_rate_hz")?;
318 let doppler_hz = signal_finite(doppler_hz, "doppler_hz")?;
319 let w = TWO_PI * doppler_hz / fs;
320 let mut acc_i = 0.0;
321 let mut acc_q = 0.0;
322 for (n, (sample, &c)) in iq.iter().zip(code.iter()).enumerate() {
323 let theta = w * n as f64;
324 let cos = theta.cos();
325 let sin = theta.sin();
326 let cc = c as f64;
327 let di = (sample.i * cos + sample.q * sin) * cc;
328 let dq = (sample.q * cos - sample.i * sin) * cc;
329 acc_i += di;
330 acc_q += dq;
331 }
332 Ok((
333 signal_finite(acc_i, "correlation_i")?,
334 signal_finite(acc_q, "correlation_q")?,
335 ))
336}
337
338pub fn acquire(
340 samples: &[IqSample],
341 prn: i64,
342 options: AcquisitionOptions,
343) -> Result<AcquisitionResult, SignalError> {
344 if samples.is_empty() {
345 return Err(SignalError::EmptySamples);
346 }
347 validate_iq_samples(samples, "samples")?;
348 let sample_rate_hz = signal_positive_step(options.sample_rate_hz, "sample_rate_hz")?;
349 let doppler_min_hz = signal_finite(options.doppler_min_hz, "doppler_min_hz")?;
350 let doppler_max_hz = signal_finite(options.doppler_max_hz, "doppler_max_hz")?;
351 let doppler_step_hz = signal_positive_step(options.doppler_step_hz, "doppler_step_hz")?;
352 signal_range_order(doppler_min_hz, doppler_max_hz, "doppler_max_hz")?;
353 let options = AcquisitionOptions {
354 sample_rate_hz,
355 doppler_min_hz,
356 doppler_max_hz,
357 doppler_step_hz,
358 };
359
360 let samples_per_chip = options.sample_rate_hz / CA_CHIP_RATE_HZ;
361 let samples_per_code = (samples_per_chip * CA_CODE_LENGTH as f64).round() as usize;
362 if samples_per_code == 0 {
363 return Err(SignalError::InvalidInput {
364 field: "sample_rate_hz",
365 reason: "out of range",
366 });
367 }
368 if samples.len() < samples_per_code {
369 return Err(SignalError::TooShort);
370 }
371
372 let code = ca_code(prn)?;
373 do_acquire(samples, &code, options, samples_per_chip, samples_per_code)
374}
375
376pub fn coherent_loss(freq_error_hz: f64, integration_time_s: f64) -> Result<f64, SignalError> {
378 let freq_error_hz = signal_finite(freq_error_hz, "freq_error_hz")?;
379 let integration_time_s = signal_positive_step(integration_time_s, "integration_time_s")?;
380 let x = signal_finite(
381 std::f64::consts::PI * freq_error_hz * integration_time_s,
382 "coherent_loss",
383 )?;
384 if x == 0.0 {
385 Ok(1.0)
386 } else {
387 let s = x.sin() / x;
388 signal_finite(s * s, "coherent_loss")
389 }
390}
391
392pub fn coherent_loss_db(freq_error_hz: f64, integration_time_s: f64) -> Result<f64, SignalError> {
394 let loss = coherent_loss(freq_error_hz, integration_time_s)?;
395 if loss <= 0.0 {
396 return Err(invalid_signal_input("coherent_loss_db", "out of range"));
397 }
398 let loss_db = 10.0 * loss.log10();
399 if loss_db.is_finite() {
400 Ok(loss_db)
401 } else {
402 Err(invalid_signal_input("coherent_loss_db", "out of range"))
403 }
404}
405
406pub fn snr_post_db(cn0_dbhz: f64, integration_time_s: f64) -> Result<f64, SignalError> {
408 let cn0_dbhz = signal_finite(cn0_dbhz, "cn0_dbhz")?;
409 let integration_time_s = signal_positive_step(integration_time_s, "integration_time_s")?;
410 signal_finite(cn0_dbhz + 10.0 * integration_time_s.log10(), "snr_post_db")
411}
412
413fn do_acquire(
414 samples: &[IqSample],
415 code: &[i8],
416 options: AcquisitionOptions,
417 samples_per_chip: f64,
418 samples_per_code: usize,
419) -> Result<AcquisitionResult, SignalError> {
420 let doppler_bins = doppler_grid(
421 options.doppler_min_hz,
422 options.doppler_max_hz,
423 options.doppler_step_hz,
424 )?;
425
426 let record = &samples[..samples_per_code];
427 let base_code = sample_code(code, samples_per_code, options.sample_rate_hz, 0.0, 0.0);
428
429 let mut grid = Vec::with_capacity(doppler_bins.len());
430 for &d in &doppler_bins {
431 let wiped = carrier_wipeoff(record, options.sample_rate_hz, d);
432 validate_iq_samples(&wiped, "wiped samples")?;
433 let powers = code_phase_powers(&wiped, &base_code);
434 validate::finite_slice(&powers, "code phase powers").map_err(map_signal_input)?;
435 grid.push((d, powers));
436 }
437
438 let mut peak_power = -1.0;
439 let mut peak_doppler = 0.0;
440 let mut peak_offset = 0_usize;
441 for (d, powers) in &grid {
442 for (off, &p) in powers.iter().enumerate() {
443 if p > peak_power {
444 peak_power = p;
445 peak_doppler = *d;
446 peak_offset = off;
447 }
448 }
449 }
450
451 let metric = peak_to_mean_off_peak(&grid, peak_power, peak_doppler, peak_offset);
452 let code_phase_chips = peak_offset as f64 / samples_per_chip;
453
454 Ok(AcquisitionResult {
455 code_phase_chips,
456 doppler_hz: peak_doppler,
457 peak_metric: metric,
458 metric,
459 peak_power,
460 grid: AcquisitionGrid {
461 doppler_hz: doppler_bins,
462 code_phase_bins: samples_per_code,
463 doppler_step_hz: options.doppler_step_hz,
464 samples_per_chip,
465 },
466 })
467}
468
469fn phase_select(prn: i64) -> Result<(usize, usize), SignalError> {
470 match prn {
471 1 => Ok((2, 6)),
472 2 => Ok((3, 7)),
473 3 => Ok((4, 8)),
474 4 => Ok((5, 9)),
475 5 => Ok((1, 9)),
476 6 => Ok((2, 10)),
477 7 => Ok((1, 8)),
478 8 => Ok((2, 9)),
479 9 => Ok((3, 10)),
480 10 => Ok((2, 3)),
481 11 => Ok((3, 4)),
482 12 => Ok((5, 6)),
483 13 => Ok((6, 7)),
484 14 => Ok((7, 8)),
485 15 => Ok((8, 9)),
486 16 => Ok((9, 10)),
487 17 => Ok((1, 4)),
488 18 => Ok((2, 5)),
489 19 => Ok((3, 6)),
490 20 => Ok((4, 7)),
491 21 => Ok((5, 8)),
492 22 => Ok((6, 9)),
493 23 => Ok((1, 3)),
494 24 => Ok((4, 6)),
495 25 => Ok((5, 7)),
496 26 => Ok((6, 8)),
497 27 => Ok((7, 9)),
498 28 => Ok((8, 10)),
499 29 => Ok((1, 6)),
500 30 => Ok((2, 7)),
501 31 => Ok((3, 8)),
502 32 => Ok((4, 9)),
503 _ => Err(SignalError::UnsupportedPrn(prn)),
504 }
505}
506
507fn raw_code((tap_a, tap_b): (usize, usize)) -> Vec<u8> {
508 let mut g1 = [1_u8; 10];
509 let mut g2 = [1_u8; 10];
510 let mut chips = Vec::with_capacity(CA_CODE_LENGTH);
511
512 for _ in 0..CA_CODE_LENGTH {
513 let g1_out = g1[9];
514 let g2i = g2[tap_a - 1] ^ g2[tap_b - 1];
515 chips.push(g1_out ^ g2i);
516 step_g1(&mut g1);
517 step_g2(&mut g2);
518 }
519
520 chips
521}
522
523fn step_g1(g1: &mut [u8; 10]) {
524 let feedback = g1[2] ^ g1[9];
525 shift(g1, feedback);
526}
527
528fn step_g2(g2: &mut [u8; 10]) {
529 let feedback = g2[1] ^ g2[2] ^ g2[5] ^ g2[7] ^ g2[8] ^ g2[9];
530 shift(g2, feedback);
531}
532
533fn shift(reg: &mut [u8; 10], feedback: u8) {
534 for i in (1..reg.len()).rev() {
535 reg[i] = reg[i - 1];
536 }
537 reg[0] = feedback;
538}
539
540fn sample_code(code: &[i8], n: usize, fs: f64, code_phase: f64, code_doppler: f64) -> Vec<i8> {
541 let code_rate = CA_CHIP_RATE_HZ * (1.0 + code_doppler / F_L1_HZ);
542 let per_sample = code_rate / fs;
543 let len = code.len() as i64;
544
545 (0..n)
546 .map(|k| {
547 let pos = code_phase + k as f64 * per_sample;
548 let idx = (pos.floor() as i64).rem_euclid(len) as usize;
549 code[idx]
550 })
551 .collect()
552}
553
554fn carrier_wipeoff(iq: &[IqSample], fs: f64, doppler_hz: f64) -> Vec<IqSample> {
555 let w = TWO_PI * doppler_hz / fs;
556 iq.iter()
557 .enumerate()
558 .map(|(k, sample)| {
559 let theta = w * k as f64;
560 let cos = theta.cos();
561 let sin = theta.sin();
562 IqSample {
563 i: sample.i * cos + sample.q * sin,
564 q: sample.q * cos - sample.i * sin,
565 }
566 })
567 .collect()
568}
569
570fn code_phase_powers(wiped: &[IqSample], base_code: &[i8]) -> Vec<f64> {
571 let n = wiped.len();
572 (0..n)
573 .map(|offset| {
574 let mut i = 0.0;
575 let mut q = 0.0;
576 for k in 0..n {
577 let sample = wiped[k];
578 let c = base_code[(k + offset) % n] as f64;
579 i += sample.i * c;
580 q += sample.q * c;
581 }
582 i * i + q * q
583 })
584 .collect()
585}
586
587fn peak_to_mean_off_peak(
588 grid: &[(f64, Vec<f64>)],
589 peak_power: f64,
590 peak_doppler: f64,
591 peak_offset: usize,
592) -> f64 {
593 let n = grid.first().map_or(0, |(_, powers)| powers.len());
594 let mut sum = 0.0;
595 let mut count = 0_usize;
596
597 for (d, powers) in grid {
598 for (off, power) in powers.iter().enumerate() {
599 if *d == peak_doppler && abs_circular_diff(off, peak_offset, n) <= 1 {
600 continue;
601 }
602 sum += *power;
603 count += 1;
604 }
605 }
606
607 if count == 0 {
608 0.0
609 } else if sum <= 0.0 && peak_power > 0.0 {
610 1.0e12
611 } else if sum <= 0.0 {
612 0.0
613 } else {
614 peak_power / (sum / count as f64)
615 }
616}
617
618fn doppler_grid(dmin: f64, dmax: f64, dstep: f64) -> Result<Vec<f64>, SignalError> {
619 let dstep = signal_positive_step(dstep, "doppler_step_hz")?;
620 let last_bin_index = doppler_last_bin_index(dmin, dmax, dstep)?;
621 Ok((0..=last_bin_index)
622 .map(|k| dmin + k as f64 * dstep)
623 .filter(|d| *d <= dmax + DOPPLER_GRID_EDGE_EPS_HZ)
624 .collect())
625}
626
627fn doppler_last_bin_index(dmin: f64, dmax: f64, dstep: f64) -> Result<usize, SignalError> {
628 let last_bin_index = ((dmax - dmin) / dstep).round();
629 if !last_bin_index.is_finite() || last_bin_index < 0.0 {
630 return Err(invalid_signal_input("doppler_grid", "out of range"));
631 }
632 let bin_count = last_bin_index + 1.0;
633 if bin_count > MAX_DOPPLER_BINS as f64 {
634 return Err(invalid_signal_input("doppler_grid", "out of range"));
635 }
636 Ok(last_bin_index as usize)
637}
638
639fn signal_positive_step(x: f64, field: &'static str) -> Result<f64, SignalError> {
640 validate::positive_step(x, field).map_err(map_signal_input)
641}
642
643fn signal_finite(x: f64, field: &'static str) -> Result<f64, SignalError> {
644 validate::finite(x, field).map_err(map_signal_input)
645}
646
647fn signal_range_order(lo: f64, hi: f64, field: &'static str) -> Result<(), SignalError> {
648 validate::range_order(lo, hi, field).map_err(map_signal_input)
649}
650
651fn validate_iq_samples(samples: &[IqSample], field: &'static str) -> Result<(), SignalError> {
652 for sample in samples {
653 if !sample.i.is_finite() || !sample.q.is_finite() {
654 return Err(invalid_signal_input(field, "not finite"));
655 }
656 }
657 Ok(())
658}
659
660fn map_signal_input(error: validate::FieldError) -> SignalError {
661 invalid_signal_input(error.field(), error.reason())
662}
663
664fn invalid_signal_input(field: &'static str, reason: &'static str) -> SignalError {
665 SignalError::InvalidInput { field, reason }
666}
667
668fn abs_circular_diff(a: usize, b: usize, n: usize) -> usize {
669 let d = a.abs_diff(b) % n;
670 d.min(n - d)
671}
672
673#[cfg(test)]
674mod tests {
675 use super::*;
676
677 #[test]
678 fn unsupported_prn_is_tagged() {
679 assert_eq!(ca_code(33), Err(SignalError::UnsupportedPrn(33)));
680 assert_eq!(ca_chip(0, 0), Err(SignalError::UnsupportedPrn(0)));
681 }
682
683 #[test]
684 fn code_balance_and_correlation_shape_are_pinned() {
685 for prn in 1..=32 {
686 let code = ca_code(prn).unwrap();
687 assert_eq!(code.len(), CA_CODE_LENGTH);
688 assert_eq!(code.iter().filter(|&&chip| chip == -1).count(), 512);
689 assert_eq!(code.iter().filter(|&&chip| chip == 1).count(), 511);
690 assert_eq!(code.iter().map(|&chip| i32::from(chip)).sum::<i32>(), -1);
691 }
692
693 let code = ca_code(1).unwrap();
694 let corr = autocorrelation(&code);
695 assert_eq!(corr[0], 1023);
696 assert!(!corr[1..].contains(&1023));
697 let mut values = corr[1..].to_vec();
698 values.sort_unstable();
699 values.dedup();
700 assert_eq!(values, vec![-65, -1, 63]);
701 }
702
703 #[test]
704 fn loss_and_snr_primitives_are_deterministic() {
705 assert_eq!(
706 coherent_loss(0.0, 1.0e-3).unwrap().to_bits(),
707 1.0_f64.to_bits()
708 );
709 assert_eq!(
710 snr_post_db(40.0, 1.0e-3).unwrap().to_bits(),
711 10.0_f64.to_bits()
712 );
713 assert_eq!(
714 coherent_loss(f64::MAX, 1.0),
715 Err(invalid_signal_input("coherent_loss", "not finite"))
716 );
717 }
718
719 #[test]
720 fn correlation_rejects_nonfinite_derived_outputs() {
721 let samples = [IqSample::real(f64::MAX), IqSample::real(f64::MAX)];
722 let code = [1_i8, 1_i8];
723
724 assert_eq!(
725 correlate_against(&samples, &code, DEFAULT_SAMPLE_RATE_HZ, 0.0),
726 Err(invalid_signal_input("correlation_i", "not finite"))
727 );
728 assert_eq!(
729 correlate(
730 &samples[..1],
731 1,
732 CorrelateOptions {
733 sample_rate_hz: DEFAULT_SAMPLE_RATE_HZ,
734 doppler_hz: 0.0,
735 code_phase_chips: 0.0,
736 code_doppler_hz: 0.0,
737 }
738 ),
739 Err(invalid_signal_input("correlation_power", "not finite"))
740 );
741 }
742}