{
  "id": 584142,
  "title": "Acoustic Wave Equation Forward Modeling ",
  "url": "/competitions/waveform-inversion/discussion/584142",
  "author_name": "Success Moses",
  "post_date": "2025-06-12T01:43:30.436000",
  "votes": 2,
  "comment_count": 0,
  "views": 0,
  "content": "<p><em>(I am not a physics expert!!!)</em><br>\n I tried to implement forward difference <code>f()</code>. <code>f()</code> takes a v_model array of shape <code>(70, 70)</code> and simulates seismic data <code>(num_sources, time_steps, num_receivers)</code> using the acoustic wave equation via finite differences. <code>f()</code> has applications is data augmentation and self supervised training which could possibly improve LB. I am not a physics expert, I am not sure if what I am doing is correct. I needed feedback.</p>\n<p>This is the wave equation (6), from the <a href=\"https://arxiv.org/pdf/2110.07584\" target=\"_blank\">paper</a></p>\n<p><img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F22353344%2F5cc5da954a8ee3c9667a3efa1f671c92%2FScreenshot%20from%202025-06-12%2002-32-54.png?generation=1749692006975779&amp;alt=media\" alt=\"\"></p>\n<p>I did not find any implementation of the method, so I decide to try to implement it.</p>\n<pre><code>\n () -&gt; np.ndarray:\n    \n    source_grid = np.zeros(grid_shape, dtype=np.float32)\n    h_src, d_src = src_loc\n      &lt;= h_src &lt; grid_shape[]   &lt;= d_src &lt; grid_shape[]:\n        source_grid[h_src, d_src] = current_source_wavelet_value\n     source_grid\n\n () -&gt; np.ndarray:\n\n    pressure_field_next = np.zeros_like(pressure_field_current, dtype=np.float32)\n\n    \n     () -&gt; np.ndarray:\n        \n        laplacian = np.zeros_like(p_field)\n        \n        laplacian[:-, :-] = (\n            (p_field[:, :-] -  * p_field[:-, :-] + p_field[:-, :-]) * one_over_dh_squared +\n            (p_field[:-, :] -  * p_field[:-, :-] + p_field[:-, :-]) * one_over_dh_squared\n        )\n         laplacian\n\n    \n    laplacian_p_current = _calculate_laplacian_2d_internal(pressure_field_current)\n\n    \n    \n    v_squared = vel_model ** \n\n    \n    \n    wave_acceleration_term = laplacian_p_current - source_term_grid\n\n    \n    pressure_field_next = ( * pressure_field_current) - pressure_field_past + \\\n                          (v_squared * dt_squared * wave_acceleration_term)\n\n     pressure_field_next\n\n\n () -&gt; np.ndarray:\n    \n\n    H, D = vel_model.shape  \n    num_time_steps = (source_wavelet)\n    num_receivers = (receiver_locations)\n    num_sources = (source_locations) \n\n    \n    \n    seismic_data = np.zeros((num_sources, num_time_steps, num_receivers), dtype=np.float32)\n\n    \n    dt_squared = dt ** \n    one_over_dh_squared =  / (dh ** )\n\n    \n\n     src_idx, current_source_location  (source_locations):\n        \n        pressure_field_past = np.zeros((H, D), dtype=np.float32)    \n        pressure_field_current = np.zeros((H, D), dtype=np.float32) \n\n        \n         t_step  (num_time_steps):\n\n            \n            current_source_value = source_wavelet[t_step]  t_step &lt; (source_wavelet)  \n\n            \n            source_term_grid = _apply_source_to_grid(current_source_value,\n                                                     current_source_location,\n                                                     (H, D))\n\n            \n            pressure_field_next = _update_wavefield_for_timestep(\n                pressure_field_current,\n                pressure_field_past,\n                vel_model,\n                source_term_grid,\n                dt_squared,\n                one_over_dh_squared,\n                H,\n                D\n            )\n\n            \n            \n            pressure_field_past = pressure_field_current.copy()\n            pressure_field_current = pressure_field_next.copy()\n\n            \n             rec_idx, (h_rec, d_rec)  (receiver_locations):\n                  &lt;= h_rec &lt; H   &lt;= d_rec &lt; D:\n                    seismic_data[src_idx, t_step, rec_idx] = pressure_field_current[h_rec, d_rec]\n                :\n                    seismic_data[src_idx, t_step, rec_idx] =  \n\n\n     seismic_data\n</code></pre>",
  "messages": [
    {
      "id": 3222189,
      "postDate": "2025-06-12T01:43:30.437Z",
      "content": "<p><em>(I am not a physics expert!!!)</em><br>\n I tried to implement forward difference <code>f()</code>. <code>f()</code> takes a v_model array of shape <code>(70, 70)</code> and simulates seismic data <code>(num_sources, time_steps, num_receivers)</code> using the acoustic wave equation via finite differences. <code>f()</code> has applications is data augmentation and self supervised training which could possibly improve LB. I am not a physics expert, I am not sure if what I am doing is correct. I needed feedback.</p>\n<p>This is the wave equation (6), from the <a href=\"https://arxiv.org/pdf/2110.07584\" target=\"_blank\">paper</a></p>\n<p><img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F22353344%2F5cc5da954a8ee3c9667a3efa1f671c92%2FScreenshot%20from%202025-06-12%2002-32-54.png?generation=1749692006975779&amp;alt=media\" alt=\"\"></p>\n<p>I did not find any implementation of the method, so I decide to try to implement it.</p>\n<pre><code>\n () -&gt; np.ndarray:\n    \n    source_grid = np.zeros(grid_shape, dtype=np.float32)\n    h_src, d_src = src_loc\n      &lt;= h_src &lt; grid_shape[]   &lt;= d_src &lt; grid_shape[]:\n        source_grid[h_src, d_src] = current_source_wavelet_value\n     source_grid\n\n () -&gt; np.ndarray:\n\n    pressure_field_next = np.zeros_like(pressure_field_current, dtype=np.float32)\n\n    \n     () -&gt; np.ndarray:\n        \n        laplacian = np.zeros_like(p_field)\n        \n        laplacian[:-, :-] = (\n            (p_field[:, :-] -  * p_field[:-, :-] + p_field[:-, :-]) * one_over_dh_squared +\n            (p_field[:-, :] -  * p_field[:-, :-] + p_field[:-, :-]) * one_over_dh_squared\n        )\n         laplacian\n\n    \n    laplacian_p_current = _calculate_laplacian_2d_internal(pressure_field_current)\n\n    \n    \n    v_squared = vel_model ** \n\n    \n    \n    wave_acceleration_term = laplacian_p_current - source_term_grid\n\n    \n    pressure_field_next = ( * pressure_field_current) - pressure_field_past + \\\n                          (v_squared * dt_squared * wave_acceleration_term)\n\n     pressure_field_next\n\n\n () -&gt; np.ndarray:\n    \n\n    H, D = vel_model.shape  \n    num_time_steps = (source_wavelet)\n    num_receivers = (receiver_locations)\n    num_sources = (source_locations) \n\n    \n    \n    seismic_data = np.zeros((num_sources, num_time_steps, num_receivers), dtype=np.float32)\n\n    \n    dt_squared = dt ** \n    one_over_dh_squared =  / (dh ** )\n\n    \n\n     src_idx, current_source_location  (source_locations):\n        \n        pressure_field_past = np.zeros((H, D), dtype=np.float32)    \n        pressure_field_current = np.zeros((H, D), dtype=np.float32) \n\n        \n         t_step  (num_time_steps):\n\n            \n            current_source_value = source_wavelet[t_step]  t_step &lt; (source_wavelet)  \n\n            \n            source_term_grid = _apply_source_to_grid(current_source_value,\n                                                     current_source_location,\n                                                     (H, D))\n\n            \n            pressure_field_next = _update_wavefield_for_timestep(\n                pressure_field_current,\n                pressure_field_past,\n                vel_model,\n                source_term_grid,\n                dt_squared,\n                one_over_dh_squared,\n                H,\n                D\n            )\n\n            \n            \n            pressure_field_past = pressure_field_current.copy()\n            pressure_field_current = pressure_field_next.copy()\n\n            \n             rec_idx, (h_rec, d_rec)  (receiver_locations):\n                  &lt;= h_rec &lt; H   &lt;= d_rec &lt; D:\n                    seismic_data[src_idx, t_step, rec_idx] = pressure_field_current[h_rec, d_rec]\n                :\n                    seismic_data[src_idx, t_step, rec_idx] =  \n\n\n     seismic_data\n</code></pre>",
      "rawMarkdown": "*(I am not a physics expert!!!)*\n I tried to implement forward difference `f()`. `f()` takes a v_model array of shape `(70, 70)` and simulates seismic data `(num_sources, time_steps, num_receivers)` using the acoustic wave equation via finite differences. `f()` has applications is data augmentation and self supervised training which could possibly improve LB. I am not a physics expert, I am not sure if what I am doing is correct. I needed feedback.\n\nThis is the wave equation (6), from the [paper](https://arxiv.org/pdf/2110.07584)\n\n ![](https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F22353344%2F5cc5da954a8ee3c9667a3efa1f671c92%2FScreenshot%20from%202025-06-12%2002-32-54.png?generation=1749692006975779&alt=media)\n\nI did not find any implementation of the method, so I decide to try to implement it.\n\n```\n# --- Helper function for source application ---\ndef _apply_source_to_grid(current_source_wavelet_value: float,\n                          src_loc: tuple[int, int],\n                          grid_shape: tuple[int, int]) -> np.ndarray:\n    \"\"\"\n    Creates a 2D source term array for the current time step, non-zero only at source location.\n    \"\"\"\n    source_grid = np.zeros(grid_shape, dtype=np.float32)\n    h_src, d_src = src_loc\n    if 0 <= h_src < grid_shape[0] and 0 <= d_src < grid_shape[1]:\n        source_grid[h_src, d_src] = current_source_wavelet_value\n    return source_grid\n\ndef _update_wavefield_for_timestep(\n    pressure_field_current: np.ndarray,\n    pressure_field_past: np.ndarray,\n    vel_model: np.ndarray,\n    source_term_grid: np.ndarray,\n    dt_squared: float,\n    one_over_dh_squared: float,\n    H: int,\n    D: int\n) -> np.ndarray:\n\n    pressure_field_next = np.zeros_like(pressure_field_current, dtype=np.float32)\n\n    # Helper for Laplacian calculation (can be optimized with NumPy array operations)\n    def _calculate_laplacian_2d_internal(p_field: np.ndarray) -> np.ndarray:\n        \"\"\"\n        Internal approximation of the 2D Laplacian operator (nabla^2) for a given pressure field.\n        \"\"\"\n        laplacian = np.zeros_like(p_field)\n        # Apply stencil to interior points\n        laplacian[1:-1, 1:-1] = (\n            (p_field[2:, 1:-1] - 2 * p_field[1:-1, 1:-1] + p_field[:-2, 1:-1]) * one_over_dh_squared +\n            (p_field[1:-1, 2:] - 2 * p_field[1:-1, 1:-1] + p_field[1:-1, :-2]) * one_over_dh_squared\n        )\n        return laplacian\n\n    # Calculate the Laplacian of the current pressure field\n    laplacian_p_current = _calculate_laplacian_2d_internal(pressure_field_current)\n\n    # Apply the core finite difference update equation using vectorized operations for efficiency\n    # This directly implements: p_r^{t+1} = 2*p_r^t - p_r^{t-1} + v_r^2 * dt^2 * (nabla^2 p_r^t - s_r^t)\n    v_squared = vel_model ** 2\n    \n    # Calculate the acceleration term due to the wave equation\n    # (nabla^2 p_r^t - s_r^t)\n    wave_acceleration_term = laplacian_p_current - source_term_grid\n\n    # Full update equation\n    pressure_field_next = (2 * pressure_field_current) - pressure_field_past + \\\n                          (v_squared * dt_squared * wave_acceleration_term)\n\n    return pressure_field_next\n\n\ndef f(vel_model: np.ndarray, #(70, 70)\n      source_wavelet: np.ndarray, # (num_time_steps)\n      source_locations: list[tuple[int, int]], # List of (h, d) grid coordinates for each source.\n      receiver_locations: list[tuple[int, int]], # List of (h, d) grid coordinates for each receiver.\n      dt: float,  # Time step\n      dh: float   # Spatial grid spacing (e.g., dx = dz = dh)\n     ) -> np.ndarray:\n    \"\"\"\n    Simulates seismic data using the acoustic wave equation via finite differences.    \"\"\"\n\n    H, D = vel_model.shape  # Height and Depth dimensions of the velocity model\n    num_time_steps = len(source_wavelet)\n    num_receivers = len(receiver_locations)\n    num_sources = len(source_locations) # Assuming one source_location per simulation based on typical setup\n\n    # Initialize the 3D numpy array to store the simulated seismic data\n    # (num_sources, num_time_steps, num_receivers)\n    seismic_data = np.zeros((num_sources, num_time_steps, num_receivers), dtype=np.float32)\n\n    # Pre-calculate constants for efficiency\n    dt_squared = dt ** 2\n    one_over_dh_squared = 1.0 / (dh ** 2)\n    \n    # --- Main Forward Modeling Loop ---\n\n    for src_idx, current_source_location in enumerate(source_locations):\n        # Initialize pressure fields for the current source simulation\n        pressure_field_past = np.zeros((H, D), dtype=np.float32)    # Corresponds to p_r^{t-1}\n        pressure_field_current = np.zeros((H, D), dtype=np.float32) # Corresponds to p_r^{t}\n\n        # Main time-stepping loop (from t=0 to num_time_steps-1)\n        for t_step in range(num_time_steps):\n\n            # Get the current source term value from the source wavelet\n            current_source_value = source_wavelet[t_step] if t_step < len(source_wavelet) else 0.0\n\n            # Generate the source term grid for the current time step\n            source_term_grid = _apply_source_to_grid(current_source_value,\n                                                     current_source_location,\n                                                     (H, D))\n\n            # Update the entire pressure field for the next time step using the new helper function\n            pressure_field_next = _update_wavefield_for_timestep(\n                pressure_field_current,\n                pressure_field_past,\n                vel_model,\n                source_term_grid,\n                dt_squared,\n                one_over_dh_squared,\n                H,\n                D\n            )\n\n            # After calculating the entire pressure_field_next:\n            # 1. Update the past and current pressure fields for the next time step iteration\n            pressure_field_past = pressure_field_current.copy()\n            pressure_field_current = pressure_field_next.copy()\n\n            # 2. Record the pressure values at receiver locations for the current time step (t_step)\n            for rec_idx, (h_rec, d_rec) in enumerate(receiver_locations):\n                if 0 <= h_rec < H and 0 <= d_rec < D:\n                    seismic_data[src_idx, t_step, rec_idx] = pressure_field_current[h_rec, d_rec]\n                else:\n                    seismic_data[src_idx, t_step, rec_idx] = 0.0 # Receiver outside grid\n\n\n    return seismic_data\n```",
      "votes": 2
    }
  ],
  "comments": [],
  "raw_markdown_by_id": {
    "3222189": "*(I am not a physics expert!!!)*\n I tried to implement forward difference `f()`. `f()` takes a v_model array of shape `(70, 70)` and simulates seismic data `(num_sources, time_steps, num_receivers)` using the acoustic wave equation via finite differences. `f()` has applications is data augmentation and self supervised training which could possibly improve LB. I am not a physics expert, I am not sure if what I am doing is correct. I needed feedback.\n\nThis is the wave equation (6), from the [paper](https://arxiv.org/pdf/2110.07584)\n\n ![](https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F22353344%2F5cc5da954a8ee3c9667a3efa1f671c92%2FScreenshot%20from%202025-06-12%2002-32-54.png?generation=1749692006975779&alt=media)\n\nI did not find any implementation of the method, so I decide to try to implement it.\n\n```\n# --- Helper function for source application ---\ndef _apply_source_to_grid(current_source_wavelet_value: float,\n                          src_loc: tuple[int, int],\n                          grid_shape: tuple[int, int]) -> np.ndarray:\n    \"\"\"\n    Creates a 2D source term array for the current time step, non-zero only at source location.\n    \"\"\"\n    source_grid = np.zeros(grid_shape, dtype=np.float32)\n    h_src, d_src = src_loc\n    if 0 <= h_src < grid_shape[0] and 0 <= d_src < grid_shape[1]:\n        source_grid[h_src, d_src] = current_source_wavelet_value\n    return source_grid\n\ndef _update_wavefield_for_timestep(\n    pressure_field_current: np.ndarray,\n    pressure_field_past: np.ndarray,\n    vel_model: np.ndarray,\n    source_term_grid: np.ndarray,\n    dt_squared: float,\n    one_over_dh_squared: float,\n    H: int,\n    D: int\n) -> np.ndarray:\n\n    pressure_field_next = np.zeros_like(pressure_field_current, dtype=np.float32)\n\n    # Helper for Laplacian calculation (can be optimized with NumPy array operations)\n    def _calculate_laplacian_2d_internal(p_field: np.ndarray) -> np.ndarray:\n        \"\"\"\n        Internal approximation of the 2D Laplacian operator (nabla^2) for a given pressure field.\n        \"\"\"\n        laplacian = np.zeros_like(p_field)\n        # Apply stencil to interior points\n        laplacian[1:-1, 1:-1] = (\n            (p_field[2:, 1:-1] - 2 * p_field[1:-1, 1:-1] + p_field[:-2, 1:-1]) * one_over_dh_squared +\n            (p_field[1:-1, 2:] - 2 * p_field[1:-1, 1:-1] + p_field[1:-1, :-2]) * one_over_dh_squared\n        )\n        return laplacian\n\n    # Calculate the Laplacian of the current pressure field\n    laplacian_p_current = _calculate_laplacian_2d_internal(pressure_field_current)\n\n    # Apply the core finite difference update equation using vectorized operations for efficiency\n    # This directly implements: p_r^{t+1} = 2*p_r^t - p_r^{t-1} + v_r^2 * dt^2 * (nabla^2 p_r^t - s_r^t)\n    v_squared = vel_model ** 2\n    \n    # Calculate the acceleration term due to the wave equation\n    # (nabla^2 p_r^t - s_r^t)\n    wave_acceleration_term = laplacian_p_current - source_term_grid\n\n    # Full update equation\n    pressure_field_next = (2 * pressure_field_current) - pressure_field_past + \\\n                          (v_squared * dt_squared * wave_acceleration_term)\n\n    return pressure_field_next\n\n\ndef f(vel_model: np.ndarray, #(70, 70)\n      source_wavelet: np.ndarray, # (num_time_steps)\n      source_locations: list[tuple[int, int]], # List of (h, d) grid coordinates for each source.\n      receiver_locations: list[tuple[int, int]], # List of (h, d) grid coordinates for each receiver.\n      dt: float,  # Time step\n      dh: float   # Spatial grid spacing (e.g., dx = dz = dh)\n     ) -> np.ndarray:\n    \"\"\"\n    Simulates seismic data using the acoustic wave equation via finite differences.    \"\"\"\n\n    H, D = vel_model.shape  # Height and Depth dimensions of the velocity model\n    num_time_steps = len(source_wavelet)\n    num_receivers = len(receiver_locations)\n    num_sources = len(source_locations) # Assuming one source_location per simulation based on typical setup\n\n    # Initialize the 3D numpy array to store the simulated seismic data\n    # (num_sources, num_time_steps, num_receivers)\n    seismic_data = np.zeros((num_sources, num_time_steps, num_receivers), dtype=np.float32)\n\n    # Pre-calculate constants for efficiency\n    dt_squared = dt ** 2\n    one_over_dh_squared = 1.0 / (dh ** 2)\n    \n    # --- Main Forward Modeling Loop ---\n\n    for src_idx, current_source_location in enumerate(source_locations):\n        # Initialize pressure fields for the current source simulation\n        pressure_field_past = np.zeros((H, D), dtype=np.float32)    # Corresponds to p_r^{t-1}\n        pressure_field_current = np.zeros((H, D), dtype=np.float32) # Corresponds to p_r^{t}\n\n        # Main time-stepping loop (from t=0 to num_time_steps-1)\n        for t_step in range(num_time_steps):\n\n            # Get the current source term value from the source wavelet\n            current_source_value = source_wavelet[t_step] if t_step < len(source_wavelet) else 0.0\n\n            # Generate the source term grid for the current time step\n            source_term_grid = _apply_source_to_grid(current_source_value,\n                                                     current_source_location,\n                                                     (H, D))\n\n            # Update the entire pressure field for the next time step using the new helper function\n            pressure_field_next = _update_wavefield_for_timestep(\n                pressure_field_current,\n                pressure_field_past,\n                vel_model,\n                source_term_grid,\n                dt_squared,\n                one_over_dh_squared,\n                H,\n                D\n            )\n\n            # After calculating the entire pressure_field_next:\n            # 1. Update the past and current pressure fields for the next time step iteration\n            pressure_field_past = pressure_field_current.copy()\n            pressure_field_current = pressure_field_next.copy()\n\n            # 2. Record the pressure values at receiver locations for the current time step (t_step)\n            for rec_idx, (h_rec, d_rec) in enumerate(receiver_locations):\n                if 0 <= h_rec < H and 0 <= d_rec < D:\n                    seismic_data[src_idx, t_step, rec_idx] = pressure_field_current[h_rec, d_rec]\n                else:\n                    seismic_data[src_idx, t_step, rec_idx] = 0.0 # Receiver outside grid\n\n\n    return seismic_data\n```"
  }
}