autd3_core/geometry/
device.rs

1use std::f32::consts::PI;
2
3use bvh::aabb::Aabb;
4use derive_more::{Deref, IntoIterator};
5use getset::Getters;
6
7use crate::common::{METER, ULTRASOUND_FREQ};
8
9use super::{Isometry, Point3, Quaternion, Transducer, UnitQuaternion, UnitVector3, Vector3};
10
11/// An AUTD device unit.
12#[derive(Getters, Deref, IntoIterator)]
13pub struct Device {
14    pub(crate) idx: u16,
15    #[deref]
16    #[into_iterator(ref)]
17    pub(crate) transducers: Vec<Transducer>,
18    /// speed of sound
19    pub sound_speed: f32,
20    #[getset(get = "pub")]
21    /// The rotation of the device.
22    rotation: UnitQuaternion,
23    #[getset(get = "pub")]
24    /// The center of the device.
25    center: Point3,
26    #[getset(get = "pub")]
27    /// The x-direction of the device.
28    x_direction: UnitVector3,
29    #[getset(get = "pub")]
30    /// The y-direction of the device.
31    y_direction: UnitVector3,
32    #[getset(get = "pub")]
33    /// The axial direction of the device.
34    axial_direction: UnitVector3,
35    #[doc(hidden)]
36    #[getset(get = "pub")]
37    inv: Isometry,
38    #[getset(get = "pub")]
39    /// The Axis Aligned Bounding Box of the device.
40    aabb: Aabb<f32, 3>,
41}
42
43impl Device {
44    fn init(&mut self) {
45        self.center = Point3::from(
46            self.transducers
47                .iter()
48                .map(|tr| tr.position().coords)
49                .sum::<Vector3>()
50                / self.transducers.len() as f32,
51        );
52        self.x_direction = Self::get_direction(Vector3::x(), &self.rotation);
53        self.y_direction = Self::get_direction(Vector3::y(), &self.rotation);
54        self.axial_direction = if cfg!(feature = "left_handed") {
55            Self::get_direction(-Vector3::z(), &self.rotation) // GRCOV_EXCL_LINE
56        } else {
57            Self::get_direction(Vector3::z(), &self.rotation)
58        };
59        self.inv = (nalgebra::Translation3::<f32>::from(*self.transducers[0].position())
60            * self.rotation)
61            .inverse();
62        self.aabb = self
63            .transducers
64            .iter()
65            .fold(Aabb::empty(), |aabb, tr| aabb.grow(tr.position()));
66    }
67
68    #[doc(hidden)]
69    #[must_use]
70    pub fn new(rot: UnitQuaternion, transducers: Vec<Transducer>) -> Self {
71        let mut transducers = transducers;
72        transducers.iter_mut().enumerate().for_each(|(tr_idx, tr)| {
73            tr.idx = tr_idx as _;
74        });
75        let mut dev = Self {
76            idx: 0,
77            transducers,
78            sound_speed: 340.0 * METER,
79            rotation: rot,
80            center: Point3::origin(),
81            x_direction: Vector3::x_axis(),
82            y_direction: Vector3::y_axis(),
83            axial_direction: Vector3::z_axis(),
84            inv: nalgebra::Isometry3::identity(),
85            aabb: Aabb::empty(),
86        };
87        dev.init();
88        dev
89    }
90
91    /// Gets the index of the device.
92    #[must_use]
93    pub const fn idx(&self) -> usize {
94        self.idx as _
95    }
96
97    /// Gets the number of transducers of the device.
98    #[must_use]
99    pub const fn num_transducers(&self) -> usize {
100        self.transducers.len()
101    }
102
103    /// Sets the sound speed of devices from the temperature `t`.
104    ///
105    /// This is equivalent to `Self::set_sound_speed_from_temp_with(t, 1.4, 8.314_463, 28.9647e-3)`.
106    pub fn set_sound_speed_from_temp(&mut self, temp: f32) {
107        self.set_sound_speed_from_temp_with(temp, 1.4, 8.314_463, 28.9647e-3);
108    }
109
110    /// Sets the sound speed of devices from the temperature `t`, heat capacity ratio `k`, gas constant `r`, and molar mass `m` [kg/mol].
111    pub fn set_sound_speed_from_temp_with(&mut self, temp: f32, k: f32, r: f32, m: f32) {
112        self.sound_speed = (k * r * (273.15 + temp) / m).sqrt() * METER;
113    }
114
115    /// Gets the wavelength of the ultrasound.
116    #[must_use]
117    pub const fn wavelength(&self) -> f32 {
118        self.sound_speed / ULTRASOUND_FREQ.hz() as f32
119    }
120
121    /// Gets the wavenumber of the ultrasound.
122    #[must_use]
123    pub const fn wavenumber(&self) -> f32 {
124        2.0 * PI * ULTRASOUND_FREQ.hz() as f32 / self.sound_speed
125    }
126
127    #[must_use]
128    fn get_direction(dir: Vector3, rotation: &UnitQuaternion) -> UnitVector3 {
129        let dir: UnitQuaternion = UnitQuaternion::from_quaternion(Quaternion::from_imag(dir));
130        UnitVector3::new_normalize((rotation * dir * rotation.conjugate()).imag())
131    }
132}
133
134#[cfg(test)]
135pub(crate) mod tests {
136    use super::*;
137    use crate::{
138        common::{PI, mm},
139        geometry::tests::{TestDevice, create_device},
140    };
141
142    macro_rules! assert_approx_eq_vec3 {
143        ($a:expr, $b:expr) => {
144            approx::assert_abs_diff_eq!($a.x, $b.x, epsilon = 1e-3);
145            approx::assert_abs_diff_eq!($a.y, $b.y, epsilon = 1e-3);
146            approx::assert_abs_diff_eq!($a.z, $b.z, epsilon = 1e-3);
147        };
148    }
149
150    #[test]
151    fn idx() {
152        assert_eq!(0, create_device(249).idx());
153    }
154
155    #[rstest::rstest]
156    #[test]
157    #[case(1)]
158    #[case(249)]
159    fn num_transducers(#[case] n: u8) {
160        assert_eq!(n, create_device(n).num_transducers() as u8);
161    }
162
163    #[test]
164    fn center() {
165        let device: Device = TestDevice::new_autd3(Point3::origin()).into();
166        let expected =
167            device.iter().map(|t| t.position().coords).sum::<Vector3>() / device.len() as f32;
168        assert_approx_eq_vec3!(expected, device.center());
169    }
170
171    #[rstest::rstest]
172    #[test]
173    #[case(
174        Vector3::new(10., 20., 30.),
175        Vector3::new(10., 20., 30.),
176        Point3::origin(),
177        UnitQuaternion::identity()
178    )]
179    #[case(
180        Vector3::zeros(),
181        Vector3::new(10., 20., 30.),
182        Point3::new(10., 20., 30.),
183        UnitQuaternion::identity()
184    )]
185    #[case(
186        Vector3::new(20., -10., 30.),
187        Vector3::new(10., 20., 30.),
188        Point3::origin(),
189        UnitQuaternion::from_axis_angle(&Vector3::z_axis(), PI / 2.)
190    )]
191    #[case(
192        Vector3::new(30., 30., -30.),
193        Vector3::new(40., 50., 60.),
194        Point3::new(10., 20., 30.),
195        UnitQuaternion::from_axis_angle(&Vector3::x_axis(), PI / 2.)
196    )]
197    fn inv(
198        #[case] expected: Vector3,
199        #[case] target: Vector3,
200        #[case] origin: Point3,
201        #[case] rot: UnitQuaternion,
202    ) {
203        let device: Device = TestDevice::new_autd3_with_rot(origin, rot).into();
204        assert_approx_eq_vec3!(expected, device.inv.transform_point(&Point3::from(target)));
205    }
206
207    #[rstest::rstest]
208    #[test]
209    #[case(340.29525e3, 15.)]
210    #[case(343.23497e3, 20.)]
211    #[case(349.04013e3, 30.)]
212    fn set_sound_speed_from_temp(#[case] expected: f32, #[case] temp: f32) {
213        let mut device = create_device(249);
214        device.set_sound_speed_from_temp(temp);
215        approx::assert_abs_diff_eq!(expected * mm, device.sound_speed, epsilon = 1e-3);
216    }
217
218    #[rstest::rstest]
219    #[test]
220    #[case(8.5, 340e3)]
221    #[case(10., 400e3)]
222    fn wavelength(#[case] expect: f32, #[case] c: f32) {
223        let mut device = create_device(249);
224        device.sound_speed = c;
225        approx::assert_abs_diff_eq!(expect, device.wavelength());
226    }
227
228    #[rstest::rstest]
229    #[test]
230    #[case(0.739_198_27, 340e3)]
231    #[case(0.628_318_55, 400e3)]
232    fn wavenumber(#[case] expect: f32, #[case] c: f32) {
233        let mut device = create_device(249);
234        device.sound_speed = c;
235        approx::assert_abs_diff_eq!(expect, device.wavenumber());
236    }
237}