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(|r| { r.send(Pattern::new(&phases, Intensity::MAX))?; r.tick(Duration::from_millis(1))?; Ok(())})?;emulator = Emulator(geometry)
def record_fn(r: Recorder) -> None: r.send(Pattern(phases, Intensity.MAX)) 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 set of named columns.
Rust returns a RawFrame (the number of rows rows and the list of name and value pairs columns), and Python returns a Polars DataFrame.
The output examples below show the Python DataFrame.
Transducer Table
Section titled “Transducer Table”Emulator::transducer_table returns a list of all transducers as a table.
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 table 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.rows, voltage.columns.len());
let ultrasound = record.output_ultrasound();dbg!(ultrasound.rows, ultrasound.columns.len());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).
| Option | Field | Default | Description |
|---|---|---|---|
RmsRecordOption |
sound_speed |
340 m/s | Sound speed |
InstantRecordOption |
sound_speed |
340 m/s | Sound speed |
time_step |
1 µs | Time interval of the instantaneous sound pressure | |
memory_limits_hint_mb |
128 | Rough memory budget that decides how much is computed at once [MB] |
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 table returned by observe_points is an observation point and corresponds to a row of the sound field table.
let rms = record.sound_field(range, RmsRecordOption::default())?;let observe_points = rms.observe_points();dbg!(observe_points);rms = record.sound_field(rng, RmsRecordOption())observe_points = rms.observe_points()print(observe_points)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 table returned by next stores the RMS sound pressure per observation point every 25 μs (column name rms[Pa]@<time>[ns]).
let range = Grid { x: (target.x - 20.0)..=(target.x + 20.0), y: (target.y - 20.0)..=(target.y + 20.0), z: target.z..=target.z, resolution: 1.0, order: AxisOrder::XYZ,};let mut rms = record.sound_field(range.clone(), RmsRecordOption::default())?;let field = rms.next(Duration::from_micros(25))?;dbg!(field);rng = Grid( 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, order="xyz",)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 table 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.
Use Grid for points on a regular grid: x / y / z give the range of each axis and resolution the step width (mm).
order is the enumeration order of the observation points; they are scanned starting from the first axis. It cannot be omitted.
It is AxisOrder::XYZ / XZY / YXZ / YZX / ZXY / ZYX in Rust, and a string such as order="xyz" in Python.
In Rust, a Point3 or a Vec<Point3> can also be passed directly.