|
| 1 | +"""Simple plane wave transmit example using a velocity (u0) source. |
| 2 | +
|
| 3 | +A velocity source drives the particle-velocity equation directly, as opposed to |
| 4 | +the hard pressure source (p0) which drives the pressure equation. For a plane |
| 5 | +wave propagating in the depth (x) direction the relevant component is u0. |
| 6 | +
|
| 7 | +The acoustic impedance relation p = rho · c · u links the two formulations, so |
| 8 | +the velocity amplitude is scaled as |
| 9 | +
|
| 10 | + u_amp = p_amp / (ρ₀ · c₀) |
| 11 | +
|
| 12 | +Everything else (grid, medium, sensor, solver) is identical to |
| 13 | +simple_plane_wave.py so the two examples can be run side-by-side for comparison. |
| 14 | +""" |
| 15 | + |
| 16 | +import logging |
| 17 | +from pathlib import Path |
| 18 | + |
| 19 | +import numpy as np |
| 20 | + |
| 21 | +import fullwave |
| 22 | +from fullwave.utils import plot_utils, signal_process |
| 23 | +from fullwave.utils.coordinates import map_to_coords |
| 24 | + |
| 25 | + |
| 26 | +def main() -> None: |
| 27 | + """Run simple plane wave transmit example with a velocity source.""" |
| 28 | + logging.getLogger("__main__").setLevel(logging.INFO) |
| 29 | + |
| 30 | + # |
| 31 | + # --- working directory --- |
| 32 | + # |
| 33 | + work_dir = Path("./outputs/") / "simple_plane_wave_velocity_source" |
| 34 | + work_dir.mkdir(parents=True, exist_ok=True) |
| 35 | + |
| 36 | + # |
| 37 | + # --- computational grid --- |
| 38 | + # |
| 39 | + domain_size = (3e-2, 2e-2) # (depth, lateral) in metres |
| 40 | + f0 = 3e6 # centre frequency [Hz] |
| 41 | + c0 = 1540.0 # background sound speed [m/s] |
| 42 | + rho0 = 1000.0 # background density [kg/m³] |
| 43 | + duration = domain_size[0] / c0 * 2 # two-way travel time across depth |
| 44 | + grid = fullwave.Grid( |
| 45 | + domain_size=domain_size, |
| 46 | + f0=f0, |
| 47 | + duration=duration, |
| 48 | + c0=c0, |
| 49 | + ) |
| 50 | + |
| 51 | + # |
| 52 | + # --- acoustic medium --- |
| 53 | + # |
| 54 | + sound_speed_map = c0 * np.ones((grid.nx, grid.ny)) # m/s |
| 55 | + density_map = rho0 * np.ones((grid.nx, grid.ny)) # kg/m³ |
| 56 | + alpha_coeff_map = 0.5 * np.ones((grid.nx, grid.ny)) # dB/(MHz^y cm) |
| 57 | + alpha_power_map = 1.0 * np.ones((grid.nx, grid.ny)) # power-law exponent |
| 58 | + beta_map = 0.0 * np.ones((grid.nx, grid.ny)) # nonlinearity coefficient |
| 59 | + |
| 60 | + # embed a scatterer with different acoustic properties |
| 61 | + obj_x_start = grid.nx // 3 |
| 62 | + obj_x_end = 2 * grid.nx // 3 |
| 63 | + obj_y_start = grid.ny // 3 |
| 64 | + obj_y_end = 2 * grid.ny // 3 |
| 65 | + |
| 66 | + sound_speed_map[obj_x_start:obj_x_end, obj_y_start:obj_y_end] = 1600 |
| 67 | + density_map[obj_x_start:obj_x_end, obj_y_start:obj_y_end] = 1100 |
| 68 | + alpha_coeff_map[obj_x_start:obj_x_end, obj_y_start:obj_y_end] = 0.75 |
| 69 | + alpha_power_map[obj_x_start:obj_x_end, obj_y_start:obj_y_end] = 1.1 |
| 70 | + |
| 71 | + medium = fullwave.Medium( |
| 72 | + grid=grid, |
| 73 | + sound_speed=sound_speed_map, |
| 74 | + density=density_map, |
| 75 | + alpha_coeff=alpha_coeff_map, |
| 76 | + alpha_power=alpha_power_map, |
| 77 | + beta=beta_map, |
| 78 | + ) |
| 79 | + medium.plot(export_path=work_dir / "medium.png") |
| 80 | + |
| 81 | + # |
| 82 | + # --- velocity source --- |
| 83 | + # |
| 84 | + # Use the u-component (depth / x direction) to drive a downward-travelling |
| 85 | + # plane wave. The source occupies the top `element_thickness_px` rows of the |
| 86 | + # grid, matching the pressure-source layout in simple_plane_wave.py. |
| 87 | + # |
| 88 | + # Velocity amplitude is derived from the target pressure amplitude via the |
| 89 | + # plane-wave impedance relation: u_amp = p_amp / (ρ₀ · c₀) |
| 90 | + # |
| 91 | + p_amp = 1e5 # target pressure amplitude [Pa] |
| 92 | + u_amp = p_amp / (rho0 * c0) # corresponding velocity amplitude [m/s] |
| 93 | + |
| 94 | + ncycles = 2 |
| 95 | + drop_off = 2 |
| 96 | + # element_thickness_px = 3 |
| 97 | + |
| 98 | + # Build the coordinate array for the velocity source layer |
| 99 | + # small velocity source at the center of the domain |
| 100 | + |
| 101 | + source_width_px_x = 2 |
| 102 | + source_width_px_y = 2 |
| 103 | + u_mask = np.zeros((grid.nx, grid.ny), dtype=bool) |
| 104 | + u_mask[ |
| 105 | + grid.nx // 2 - source_width_px_x // 2 : grid.nx // 2 + source_width_px_x // 2, |
| 106 | + grid.ny // 2 - source_width_px_y // 2 : grid.ny // 2 + source_width_px_y // 2, |
| 107 | + ] = True |
| 108 | + |
| 109 | + coords_u = map_to_coords(u_mask) # shape [n_sources_u, 2] |
| 110 | + |
| 111 | + # Build the u0 signal matrix [n_sources_u, nt] |
| 112 | + u0 = np.zeros((coords_u.shape[0], grid.nt)) |
| 113 | + |
| 114 | + u0_vec = fullwave.utils.pulse.gaussian_modulated_sinusoidal_signal( |
| 115 | + nt=grid.nt, |
| 116 | + f0=f0, |
| 117 | + duration=duration, |
| 118 | + ncycles=ncycles, |
| 119 | + drop_off=drop_off, |
| 120 | + p0=u_amp, # amplitude in m/s |
| 121 | + ) |
| 122 | + u0[:, :] = u0_vec |
| 123 | + |
| 124 | + # for i_layer in range(element_thickness_px): |
| 125 | + # u0_vec = fullwave.utils.pulse.gaussian_modulated_sinusoidal_signal( |
| 126 | + # nt=grid.nt, |
| 127 | + # f0=f0, |
| 128 | + # duration=duration, |
| 129 | + # ncycles=ncycles, |
| 130 | + # drop_off=drop_off, |
| 131 | + # p0=u_amp, # amplitude in m/s |
| 132 | + # i_layer=i_layer, |
| 133 | + # dt_for_layer_delay=grid.dt, |
| 134 | + # cfl_for_layer_delay=grid.cfl, |
| 135 | + # ) |
| 136 | + # n_y = coords_u.shape[0] // element_thickness_px |
| 137 | + # u0[n_y * i_layer : n_y * (i_layer + 1), :] = u0_vec |
| 138 | + |
| 139 | + source = fullwave.Source( |
| 140 | + grid_shape=grid.shape, |
| 141 | + u0=u0, |
| 142 | + coords_u=coords_u, |
| 143 | + ) |
| 144 | + |
| 145 | + # |
| 146 | + # --- sensor --- |
| 147 | + # |
| 148 | + sensor_mask = np.ones((grid.nx, grid.ny), dtype=bool) |
| 149 | + sensor = fullwave.Sensor(mask=sensor_mask, sampling_modulus_time=7) |
| 150 | + |
| 151 | + # |
| 152 | + # --- solver --- |
| 153 | + # |
| 154 | + fw_solver = fullwave.Solver( |
| 155 | + work_dir=work_dir, |
| 156 | + grid=grid, |
| 157 | + medium=medium, |
| 158 | + source=source, |
| 159 | + sensor=sensor, |
| 160 | + run_on_memory=False, |
| 161 | + use_exponential_attenuation=True, |
| 162 | + save_gpu_memory=True, |
| 163 | + path_fullwave_simulation_bin=Path( |
| 164 | + "/home/msode/workspace/lab_repos/fullwave-python-public/debug_solver_bin/fullwave2_2d_exponential_attenuation_multi_gpu", |
| 165 | + ), |
| 166 | + ) |
| 167 | + sensor_output = fw_solver.run() |
| 168 | + |
| 169 | + # |
| 170 | + # --- visualisation --- |
| 171 | + # |
| 172 | + propagation_map = signal_process.reshape_whole_sensor_to_nt_nx_ny( |
| 173 | + sensor_output, |
| 174 | + grid, |
| 175 | + ) |
| 176 | + p_max_plot = np.abs(propagation_map).max().item() / 4 |
| 177 | + time_step = propagation_map.shape[0] // 3 |
| 178 | + plot_utils.plot_array( |
| 179 | + propagation_map[time_step, :, :], |
| 180 | + aspect=propagation_map.shape[2] / propagation_map.shape[1], |
| 181 | + export_path=work_dir / "wave_propagation_snapshot.png", |
| 182 | + vmax=p_max_plot, |
| 183 | + vmin=-p_max_plot, |
| 184 | + ) |
| 185 | + plot_utils.plot_wave_propagation_with_map( |
| 186 | + propagation_map=propagation_map, |
| 187 | + c_map=medium.sound_speed, |
| 188 | + rho_map=medium.density, |
| 189 | + export_name=work_dir / "wave_propagation_animation.mp4", |
| 190 | + vmax=p_max_plot, |
| 191 | + vmin=-p_max_plot, |
| 192 | + figsize=(4, 6), |
| 193 | + ) |
| 194 | + |
| 195 | + |
| 196 | +if __name__ == "__main__": |
| 197 | + main() |
0 commit comments