Skip to content

Conversation

jbangelo
Copy link
Collaborator

@jbangelo jbangelo commented Sep 4, 2025

This PR updates the Reference frame module to use the nalgebra crate for more of the math.

@jbangelo jbangelo marked this pull request as ready for review September 8, 2025 20:48
@jbangelo jbangelo requested a review from a team as a code owner September 8, 2025 20:48
assert!(
s >= 0.0 && s <= 1.0,
"Can only compute square root of numbers between 0 and 1"
);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was about to say we shouldn't do this but realized this is a const fn, nice job! This is a great use of that.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand this comment, but I am an absolute Rust noob. I take it to mean we shouldn't assert! in the code (vs debug_assert! presumably), but since the function is const the assert is expected to happen at compile-time? can't a const fn also be called at runtime?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@peddie You've got the right idea. Asserts are generally considered an anti-pattern in Rust, except for in test code where asserts are the primary means of implementing checks (a failed assert causes the test to fail). const functions are also a bit of a special case. When a const function is evaluated at compile time a failed assert will cause a compilation error (like the one below). You're also right that a const function can be called at runtime, and I don't think there's a Rust equivalent to C++'s consteval to prevent it from being invoked at runtime. My hope is that a big ugly name like compile_time_sqrt will make clear my intention to not use this at runtime but it's certainly not foolproof.

error[E0080]: evaluation panicked: Can only compute square root of numbers between 0 and 1
  --> swiftnav/src/coords/ellipsoid.rs:22:16
   |
22 | const X: f64 = compile_time_sqrt(2.0);
   |                ^^^^^^^^^^^^^^^^^^^^^^ evaluation of `coords::ellipsoid::X` failed inside this call
   |
note: inside `compile_time_sqrt`
  --> swiftnav/src/math.rs:40:5
   |
40 | /     assert!(
41 | |         s >= 0.0 && s <= 1.0,
42 | |         "Can only compute square root of numbers between 0 and 1"
43 | |     );
   | |_____^ the failure occurred here

For more information about this error, try `rustc --explain E0080`.
error: could not compile `swiftnav` (lib) due to 1 previous error

I think there's a couple of alternatives if we don't like this, but none are perfect:

  • Could move it into the Ellipsoid trait where it's used to maybe make clear it's not intended for general purpose use (though I think this would make it accessible outside this crate)
  • Could get rid of the compile-time nature at all and simply re-compute the Ellipsoid flattening term as needed at run time

Copy link

@pcrumley pcrumley left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this new code might be a little slower, but i think it is much clearer.

Comment on lines 197 to +204
pub fn transform_position(&self, position: &ECEF, epoch: f64) -> ECEF {
let dt = epoch - self.epoch;
let tx = (self.tx + self.tx_dot * dt) * Self::TRANSLATE_SCALE;
let ty = (self.ty + self.ty_dot * dt) * Self::TRANSLATE_SCALE;
let tz = (self.tz + self.tz_dot * dt) * Self::TRANSLATE_SCALE;
let t = (self.t + self.t_dot * dt) * Self::TRANSLATE_SCALE;
let s = (self.s + self.s_dot * dt) * Self::SCALE_SCALE;
let rx = (self.rx + self.rx_dot * dt) * Self::ROTATE_SCALE;
let ry = (self.ry + self.ry_dot * dt) * Self::ROTATE_SCALE;
let rz = (self.rz + self.rz_dot * dt) * Self::ROTATE_SCALE;
let r = (self.r + self.r_dot * dt) * Self::ROTATE_SCALE;

let x = position.x() + tx + (s * position.x()) + (-rz * position.y()) + (ry * position.z());
let y = position.y() + ty + (rz * position.x()) + (s * position.y()) + (-rx * position.z());
let z = position.z() + tz + (-ry * position.x()) + (rx * position.y()) + (s * position.z());
let m = Self::make_rotation_matrix(s, r);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you added a few allocations to the heap here with after changing this to nalgebra function, and adding the rotation matrix. probably doesn't matter but just making sure.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure? From the nalgebra docs:

Internally, dynamically- and statically-sized matrices do not use the same data storage type. While the former is always allocated on the heap using a Vec, the latter prefers static allocation indirectly using a column-major 2D array [[T; R]; C] for better performances.

My understanding is that fixed size matrices will not allocate memory. There's likely to still be a bit of overhead from operating on objects instead of primitive data types, and likely from the new function call (unless the compiler can inline it, I'm still unclear what/where to to use the #[inline] attribute).

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh dang! sorry for the incorrect info

@jbangelo jbangelo force-pushed the jbangelo/more-nalgebra branch from 91344f0 to 0c2fb40 Compare September 9, 2025 01:19
@jbangelo jbangelo merged commit 7f82a6a into master Sep 9, 2025
6 checks passed
@jbangelo jbangelo deleted the jbangelo/more-nalgebra branch September 9, 2025 01:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants