Emulator
Emulator is a tool that records AUTD3 emission data without real hardware and computes the sound field offline from it (not provided for C#).
Recording
Section titled “Recording”By passing a Geometry to Emulator::new, sending data inside record, and advancing time with tick, a Record with the sent data recorded is obtained.
let emulator = Emulator::new(geometry);let record = emulator.record(async move |r| { let mut builder = r.datagram_builder(); builder.push(Pattern::new(&patterns)); let datagrams = builder.build()?; for frame in &datagrams { r.send_checked(frame).await?; } r.tick(Duration::from_millis(1))?; Ok(())})?;emulator = Emulator(geometry)
def record_fn(r: Recorder) -> None: builder = r.datagram_builder() builder.push(Pattern(patterns)) for frame in builder.build(): r.send_checked(frame) r.tick(Duration.from_millis(1))
record = emulator.record(record_fn)The time passed to tick must be an integer multiple of the ultrasound period (25 us).
The output data is a Polars DataFrame.
For details, refer to the Polars documentation.
Transducer Table
Section titled “Transducer Table”Emulator::transducer_table returns a list of all transducers as a DataFrame.
Each row is one transducer, storing the device index (dev_idx), the local index within the device (tr_idx), the position (x/y/z[mm]), and the normal direction (nx/ny/nz).
Each row of the emission data and output signal described later corresponds one-to-one with a row of this table.
let table = emulator.transducer_table();dbg!(table);table = emulator.transducer_table()print(table)shape: (249, 8)┌─────────┬────────┬────────────┬────────────┬───────┬─────┬─────┬─────┐│ dev_idx ┆ tr_idx ┆ x[mm] ┆ y[mm] ┆ z[mm] ┆ nx ┆ ny ┆ nz ││ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ││ u16 ┆ u8 ┆ f32 ┆ f32 ┆ f32 ┆ f32 ┆ f32 ┆ f32 │╞═════════╪════════╪════════════╪════════════╪═══════╪═════╪═════╪═════╡│ 0 ┆ 0 ┆ 0.0 ┆ 0.0 ┆ 0.0 ┆ 0.0 ┆ 0.0 ┆ 1.0 ││ 0 ┆ 1 ┆ 10.16 ┆ 0.0 ┆ 0.0 ┆ 0.0 ┆ 0.0 ┆ 1.0 ││ 0 ┆ 2 ┆ 20.32 ┆ 0.0 ┆ 0.0 ┆ 0.0 ┆ 0.0 ┆ 1.0 ││ 0 ┆ 3 ┆ 30.48 ┆ 0.0 ┆ 0.0 ┆ 0.0 ┆ 0.0 ┆ 1.0 ││ 0 ┆ 4 ┆ 40.639999 ┆ 0.0 ┆ 0.0 ┆ 0.0 ┆ 0.0 ┆ 1.0 ││ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … ││ 0 ┆ 244 ┆ 132.080002 ┆ 132.080002 ┆ 0.0 ┆ 0.0 ┆ 0.0 ┆ 1.0 ││ 0 ┆ 245 ┆ 142.23999 ┆ 132.080002 ┆ 0.0 ┆ 0.0 ┆ 0.0 ┆ 1.0 ││ 0 ┆ 246 ┆ 152.399994 ┆ 132.080002 ┆ 0.0 ┆ 0.0 ┆ 0.0 ┆ 1.0 ││ 0 ┆ 247 ┆ 162.559998 ┆ 132.080002 ┆ 0.0 ┆ 0.0 ┆ 0.0 ┆ 1.0 ││ 0 ┆ 248 ┆ 172.720001 ┆ 132.080002 ┆ 0.0 ┆ 0.0 ┆ 0.0 ┆ 1.0 │└─────────┴────────┴────────────┴────────────┴───────┴─────┴─────┴─────┘Obtaining Emission Data
Section titled “Obtaining Emission Data”From the Record returned by record, the recorded emission data can be obtained.
phase / pulse_width return a DataFrame whose columns are times.
The column names are phase@<time>[ns] / pulse_width@<time>[ns], and the time is in increments of the ultrasound period (25 us).
let phase = record.phase();dbg!(phase);
let pulse_width = record.pulse_width();dbg!(pulse_width);phase = record.phase()print(phase)
pulse_width = record.pulse_width()print(pulse_width)shape: (249, 40)┌─────────────┬─────────────────┬─────────────────┬─────────────────┬───┬──────────────────┬──────────────────┬──────────────────┬──────────────────┐│ phase@0[ns] ┆ phase@25000[ns] ┆ phase@50000[ns] ┆ phase@75000[ns] ┆ … ┆ phase@900000[ns] ┆ phase@925000[ns] ┆ phase@950000[ns] ┆ phase@975000[ns] ││ --- ┆ --- ┆ --- ┆ --- ┆ ┆ --- ┆ --- ┆ --- ┆ --- ││ u8 ┆ u8 ┆ u8 ┆ u8 ┆ ┆ u8 ┆ u8 ┆ u8 ┆ u8 │╞═════════════╪═════════════════╪═════════════════╪═════════════════╪═══╪══════════════════╪══════════════════╪══════════════════╪══════════════════╡│ 1 ┆ 2 ┆ 3 ┆ 4 ┆ … ┆ 38 ┆ 39 ┆ 40 ┆ 42 ││ 254 ┆ 252 ┆ 250 ┆ 248 ┆ … ┆ 183 ┆ 181 ┆ 179 ┆ 178 ││ 1 ┆ 2 ┆ 3 ┆ 4 ┆ … ┆ 40 ┆ 41 ┆ 42 ┆ 44 ││ 253 ┆ 250 ┆ 248 ┆ 245 ┆ … ┆ 158 ┆ 156 ┆ 153 ┆ 151 ││ 255 ┆ 255 ┆ 254 ┆ 254 ┆ … ┆ 242 ┆ 241 ┆ 241 ┆ 241 ││ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … ││ 0 ┆ 0 ┆ 0 ┆ 0 ┆ … ┆ 4 ┆ 4 ┆ 4 ┆ 5 ││ 253 ┆ 251 ┆ 249 ┆ 247 ┆ … ┆ 177 ┆ 175 ┆ 173 ┆ 171 ││ 1 ┆ 3 ┆ 4 ┆ 6 ┆ … ┆ 60 ┆ 61 ┆ 63 ┆ 65 ││ 254 ┆ 253 ┆ 251 ┆ 250 ┆ … ┆ 203 ┆ 201 ┆ 200 ┆ 199 ││ 1 ┆ 3 ┆ 4 ┆ 6 ┆ … ┆ 58 ┆ 59 ┆ 61 ┆ 63 │└─────────────┴─────────────────┴─────────────────┴─────────────────┴───┴──────────────────┴──────────────────┴──────────────────┴──────────────────┘shape: (249, 40)┌───────────────────┬───────────────────────┬───────────────────────┬───────────────────────┬───┬────────────────────────┬────────────────────────┬────────────────────────┬────────────────────────┐│ pulse_width@0[ns] ┆ pulse_width@25000[ns] ┆ pulse_width@50000[ns] ┆ pulse_width@75000[ns] ┆ … ┆ pulse_width@900000[ns] ┆ pulse_width@925000[ns] ┆ pulse_width@950000[ns] ┆ pulse_width@975000[ns] ││ --- ┆ --- ┆ --- ┆ --- ┆ ┆ --- ┆ --- ┆ --- ┆ --- ││ u16 ┆ u16 ┆ u16 ┆ u16 ┆ ┆ u16 ┆ u16 ┆ u16 ┆ u16 │╞═══════════════════╪═══════════════════════╪═══════════════════════╪═══════════════════════╪═══╪════════════════════════╪════════════════════════╪════════════════════════╪════════════════════════╡│ 16 ┆ 33 ┆ 49 ┆ 67 ┆ … ┆ 256 ┆ 256 ┆ 256 ┆ 256 ││ 16 ┆ 33 ┆ 49 ┆ 67 ┆ … ┆ 256 ┆ 256 ┆ 256 ┆ 256 ││ 16 ┆ 33 ┆ 49 ┆ 67 ┆ … ┆ 256 ┆ 256 ┆ 256 ┆ 256 ││ 16 ┆ 33 ┆ 49 ┆ 67 ┆ … ┆ 256 ┆ 256 ┆ 256 ┆ 256 ││ 16 ┆ 33 ┆ 49 ┆ 67 ┆ … ┆ 256 ┆ 256 ┆ 256 ┆ 256 ││ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … ││ 16 ┆ 33 ┆ 49 ┆ 67 ┆ … ┆ 256 ┆ 256 ┆ 256 ┆ 256 ││ 16 ┆ 33 ┆ 49 ┆ 67 ┆ … ┆ 256 ┆ 256 ┆ 256 ┆ 256 ││ 16 ┆ 33 ┆ 49 ┆ 67 ┆ … ┆ 256 ┆ 256 ┆ 256 ┆ 256 ││ 16 ┆ 33 ┆ 49 ┆ 67 ┆ … ┆ 256 ┆ 256 ┆ 256 ┆ 256 ││ 16 ┆ 33 ┆ 49 ┆ 67 ┆ … ┆ 256 ┆ 256 ┆ 256 ┆ 256 │└───────────────────┴───────────────────────┴───────────────────────┴───────────────────────┴───┴────────────────────────┴────────────────────────┴────────────────────────┴────────────────────────┘Obtaining Output Signals
Section titled “Obtaining Output Signals”The voltage applied to the transducer and the ultrasound output from the transducer are computed with a BVD (Butterworth-Van Dyke) model. Both have a time resolution of 25 us / 512.
let voltage = record.output_voltage();dbg!(voltage);
let ultrasound = record.output_ultrasound();dbg!(ultrasound);voltage = record.output_voltage()print(voltage)
ultrasound = record.output_ultrasound()print(ultrasound)shape: (249, 20_480)┌────────────────────────┬────────────────────────┬────────────────────────┬────────────────────────┬───┬────────────────────────────┬────────────────────────────┬────────────────────────────┬────────────────────────────┐│ voltage[V]@0[25us/512] ┆ voltage[V]@1[25us/512] ┆ voltage[V]@2[25us/512] ┆ voltage[V]@3[25us/512] ┆ … ┆ voltage[V]@20476[25us/512] ┆ voltage[V]@20477[25us/512] ┆ voltage[V]@20478[25us/512] ┆ voltage[V]@20479[25us/512] ││ --- ┆ --- ┆ --- ┆ --- ┆ ┆ --- ┆ --- ┆ --- ┆ --- ││ f32 ┆ f32 ┆ f32 ┆ f32 ┆ ┆ f32 ┆ f32 ┆ f32 ┆ f32 │╞════════════════════════╪════════════════════════╪════════════════════════╪════════════════════════╪═══╪════════════════════════════╪════════════════════════════╪════════════════════════════╪════════════════════════════╡│ 12.0 ┆ 12.0 ┆ 12.0 ┆ 12.0 ┆ … ┆ 12.0 ┆ 12.0 ┆ 12.0 ┆ 12.0 ││ 12.0 ┆ 12.0 ┆ 12.0 ┆ 12.0 ┆ … ┆ -12.0 ┆ -12.0 ┆ -12.0 ┆ -12.0 ││ 12.0 ┆ 12.0 ┆ 12.0 ┆ 12.0 ┆ … ┆ 12.0 ┆ 12.0 ┆ 12.0 ┆ 12.0 ││ 12.0 ┆ 12.0 ┆ -12.0 ┆ -12.0 ┆ … ┆ -12.0 ┆ -12.0 ┆ -12.0 ┆ -12.0 ││ 12.0 ┆ 12.0 ┆ 12.0 ┆ 12.0 ┆ … ┆ 12.0 ┆ 12.0 ┆ 12.0 ┆ 12.0 ││ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … ││ 12.0 ┆ 12.0 ┆ 12.0 ┆ 12.0 ┆ … ┆ 12.0 ┆ 12.0 ┆ 12.0 ┆ 12.0 ││ 12.0 ┆ 12.0 ┆ -12.0 ┆ -12.0 ┆ … ┆ -12.0 ┆ -12.0 ┆ -12.0 ┆ -12.0 ││ 12.0 ┆ 12.0 ┆ 12.0 ┆ 12.0 ┆ … ┆ -12.0 ┆ -12.0 ┆ -12.0 ┆ -12.0 ││ 12.0 ┆ 12.0 ┆ 12.0 ┆ 12.0 ┆ … ┆ 12.0 ┆ 12.0 ┆ 12.0 ┆ 12.0 ││ 12.0 ┆ 12.0 ┆ 12.0 ┆ 12.0 ┆ … ┆ -12.0 ┆ -12.0 ┆ 12.0 ┆ 12.0 │└────────────────────────┴────────────────────────┴────────────────────────┴────────────────────────┴───┴────────────────────────────┴────────────────────────────┴────────────────────────────┴────────────────────────────┘shape: (249, 20_480)┌─────────────────────┬─────────────────────┬─────────────────────┬─────────────────────┬───┬─────────────────────────┬─────────────────────────┬─────────────────────────┬─────────────────────────┐│ p[a.u.]@0[25us/512] ┆ p[a.u.]@1[25us/512] ┆ p[a.u.]@2[25us/512] ┆ p[a.u.]@3[25us/512] ┆ … ┆ p[a.u.]@20476[25us/512] ┆ p[a.u.]@20477[25us/512] ┆ p[a.u.]@20478[25us/512] ┆ p[a.u.]@20479[25us/512] ││ --- ┆ --- ┆ --- ┆ --- ┆ ┆ --- ┆ --- ┆ --- ┆ --- ││ f32 ┆ f32 ┆ f32 ┆ f32 ┆ ┆ f32 ┆ f32 ┆ f32 ┆ f32 │╞═════════════════════╪═════════════════════╪═════════════════════╪═════════════════════╪═══╪═════════════════════════╪═════════════════════════╪═════════════════════════╪═════════════════════════╡│ 0.0 ┆ -0.000272 ┆ -0.000481 ┆ -0.000618 ┆ … ┆ 0.367795 ┆ 0.379597 ┆ 0.391337 ┆ 0.403013 ││ 0.0 ┆ -0.000272 ┆ -0.000481 ┆ -0.000618 ┆ … ┆ 0.261409 ┆ 0.250499 ┆ 0.239553 ┆ 0.228573 ││ 0.0 ┆ -0.000272 ┆ -0.000481 ┆ -0.000618 ┆ … ┆ 0.325682 ┆ 0.337693 ┆ 0.349649 ┆ 0.361547 ││ 0.0 ┆ -0.000272 ┆ -0.000481 ┆ -0.000766 ┆ … ┆ -0.237067 ┆ -0.247149 ┆ -0.257188 ┆ -0.267184 ││ 0.0 ┆ -0.000272 ┆ -0.000481 ┆ -0.000618 ┆ … ┆ 0.980448 ┆ 0.98075 ┆ 0.980904 ┆ 0.980913 ││ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … ││ 0.0 ┆ -0.000272 ┆ -0.000481 ┆ -0.000618 ┆ … ┆ 0.904004 ┆ 0.909175 ┆ 0.914209 ┆ 0.919103 ││ 0.0 ┆ -0.000272 ┆ -0.000481 ┆ -0.000766 ┆ … ┆ 0.13367 ┆ 0.122602 ┆ 0.111521 ┆ 0.100428 ││ 0.0 ┆ -0.000272 ┆ -0.000481 ┆ -0.000618 ┆ … ┆ -0.108901 ┆ -0.096903 ┆ -0.084897 ┆ -0.072884 ││ 0.0 ┆ -0.000272 ┆ -0.000481 ┆ -0.000618 ┆ … ┆ 0.620835 ┆ 0.612537 ┆ 0.604152 ┆ 0.595681 ││ 0.0 ┆ -0.000272 ┆ -0.000481 ┆ -0.000618 ┆ … ┆ -0.066326 ┆ -0.054301 ┆ -0.042275 ┆ -0.0301 │└─────────────────────┴─────────────────────┴─────────────────────┴─────────────────────┴───┴─────────────────────────┴─────────────────────────┴─────────────────────────┴─────────────────────────┘Sound Field Computation
Section titled “Sound Field Computation”Passing an observation range (Range) and options to Record::sound_field yields a sound field computation object.
There are two kinds of options: RmsRecordOption (RMS sound pressure) and InstantRecordOption (instantaneous sound pressure).
For both, observe_points gives the observation point coordinates, and next gives the sound field for the specified duration.
skip advances time without computing.
Each row of the DataFrame returned by observe_points is an observation point and corresponds to a row of the sound field DataFrame.
let rms = record.sound_field(range, RmsRecordOption::default())?;let observe_points = rms.observe_points();dbg!(observe_points);rng = RangeXY( x=(target[0] - 20.0, target[0] + 20.0), y=(target[1] - 20.0, target[1] + 20.0), z=float(target[2]), resolution=1.0,)rms = record.sound_field(rng, RmsRecordOption())field = rms.next(Duration.from_micros(25))print(field)shape: (1_681, 3)┌────────────┬───────────┬───────┐│ x[mm] ┆ y[mm] ┆ z[mm] ││ --- ┆ --- ┆ --- ││ f32 ┆ f32 ┆ f32 │╞════════════╪═══════════╪═══════╡│ 66.625267 ┆ 46.713196 ┆ 150.0 ││ 67.625267 ┆ 46.713196 ┆ 150.0 ││ 68.625267 ┆ 46.713196 ┆ 150.0 ││ 69.625267 ┆ 46.713196 ┆ 150.0 ││ 70.625267 ┆ 46.713196 ┆ 150.0 ││ … ┆ … ┆ … ││ 102.625267 ┆ 86.713196 ┆ 150.0 ││ 103.625267 ┆ 86.713196 ┆ 150.0 ││ 104.625267 ┆ 86.713196 ┆ 150.0 ││ 105.625267 ┆ 86.713196 ┆ 150.0 ││ 106.625267 ┆ 86.713196 ┆ 150.0 │└────────────┴───────────┴───────┘RMS Sound Pressure
Section titled “RMS Sound Pressure”Each column of the DataFrame returned by next stores the RMS sound pressure per observation point every 25 μs (column name rms[Pa]@<time>[ns]).
let range = RangeXY { x: (target.x - 20.0)..=(target.x + 20.0), y: (target.y - 20.0)..=(target.y + 20.0), z: target.z, resolution: 1.0,};let mut rms = record.sound_field(range.clone(), RmsRecordOption::default())?;let field = rms.next(Duration::from_micros(25))?;dbg!(field);rng = RangeXY( x=(target[0] - 20.0, target[0] + 20.0), y=(target[1] - 20.0, target[1] + 20.0), z=float(target[2]), resolution=1.0,)rms = record.sound_field(rng, RmsRecordOption())field = rms.next(Duration.from_micros(25))print(field)shape: (1_681, 1)┌───────────────┐│ rms[Pa]@0[ns] ││ --- ││ f32 │╞═══════════════╡│ 22.213818 ││ 22.964384 ││ 23.199003 ││ 22.829695 ││ 21.915253 ││ … ││ 19.375198 ││ 19.254133 ││ 18.922194 ││ 18.434885 ││ 17.942478 │└───────────────┘Instantaneous Sound Pressure
Section titled “Instantaneous Sound Pressure”For the instantaneous sound pressure, the time resolution is specified with time_step.
Each column of the DataFrame returned by next stores the instantaneous sound pressure per observation point in increments of time_step (column name p[Pa]@<time>[ns]).
let mut instant = record.sound_field( range, InstantRecordOption { time_step: Duration::from_micros(1), ..Default::default() },)?;instant.skip(Duration::from_micros(500))?;let field = instant.next(Duration::from_micros(25))?;dbg!(field);instant = record.sound_field( rng, InstantRecordOption(time_step=Duration.from_micros(1)),)instant.skip(Duration.from_micros(500))field = instant.next(Duration.from_micros(25))print(field)shape: (1_681, 25)┌──────────────────┬──────────────────┬──────────────────┬──────────────────┬───┬──────────────────┬──────────────────┬──────────────────┬──────────────────┐│ p[Pa]@500000[ns] ┆ p[Pa]@501000[ns] ┆ p[Pa]@502000[ns] ┆ p[Pa]@503000[ns] ┆ … ┆ p[Pa]@521000[ns] ┆ p[Pa]@522000[ns] ┆ p[Pa]@523000[ns] ┆ p[Pa]@524000[ns] ││ --- ┆ --- ┆ --- ┆ --- ┆ ┆ --- ┆ --- ┆ --- ┆ --- ││ f32 ┆ f32 ┆ f32 ┆ f32 ┆ ┆ f32 ┆ f32 ┆ f32 ┆ f32 │╞══════════════════╪══════════════════╪══════════════════╪══════════════════╪═══╪══════════════════╪══════════════════╪══════════════════╪══════════════════╡│ 10.003653 ┆ 3.894438 ┆ -6.252444 ┆ -16.506908 ┆ … ┆ 28.38765 ┆ 28.567314 ┆ 29.98049 ┆ 32.387314 ││ 7.126377 ┆ 2.020372 ┆ -8.165694 ┆ -17.129423 ┆ … ┆ 31.707983 ┆ 31.654594 ┆ 30.74332 ┆ 31.946955 ││ 3.92967 ┆ -1.315457 ┆ -9.487965 ┆ -16.549021 ┆ … ┆ 35.146038 ┆ 35.977501 ┆ 33.133389 ┆ 30.673653 ││ 0.343901 ┆ -5.408858 ┆ -10.659369 ┆ -15.897531 ┆ … ┆ 38.936932 ┆ 39.213017 ┆ 35.64106 ┆ 29.435026 ││ -1.625009 ┆ -8.594726 ┆ -11.886508 ┆ -15.217589 ┆ … ┆ 41.400124 ┆ 41.093994 ┆ 36.455021 ┆ 29.054434 ││ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … ││ 1.06873 ┆ -4.236325 ┆ -7.555728 ┆ -12.076913 ┆ … ┆ 33.985756 ┆ 36.456608 ┆ 34.53088 ┆ 29.42366 ││ 4.188988 ┆ -0.0129 ┆ -5.104079 ┆ -12.541022 ┆ … ┆ 30.883274 ┆ 34.034138 ┆ 33.920597 ┆ 29.746302 ││ 8.367446 ┆ 4.674797 ┆ -3.678846 ┆ -13.309359 ┆ … ┆ 28.576004 ┆ 30.555122 ┆ 31.229137 ┆ 31.220989 ││ 12.870935 ┆ 7.263134 ┆ -2.312043 ┆ -13.103703 ┆ … ┆ 26.725754 ┆ 27.065441 ┆ 29.68734 ┆ 32.745209 ││ 15.355244 ┆ 8.761391 ┆ -1.179362 ┆ -12.026244 ┆ … ┆ 24.756136 ┆ 25.81175 ┆ 29.540882 ┆ 33.987869 │└──────────────────┴──────────────────┴──────────────────┴──────────────────┴───┴──────────────────┴──────────────────┴──────────────────┴──────────────────┘Observation Range
Section titled “Observation Range”Range represents a set of observation points.
Besides planes such as RangeXY, there are single-axis variants such as RangeX and all-three-axis variants such as RangeXYZ.
The order of the axes (RangeXY vs. RangeYX, etc.) determines the enumeration order of the observation points.
resolution is the step width of each axis (mm).
GPU Backend (Rust version only)
Section titled “GPU Backend (Rust version only)”Enabling the gpu feature allows the sound field computation to be done on the GPU.
Only when enabled does each option gain a gpu: bool field, and setting it to true uses the GPU (default is false).
let mut rms = record.sound_field( range, RmsRecordOption { gpu: true, ..Default::default() },)?;