Skip to content

Tutorial 4: Gridding and phantom rotation

Learn computational mesh strategies and advanced near-field placement techniques.

What you'll learn

  • Automatic vs manual gridding
  • Subgridding for antenna components
  • Per-frequency grid settings
  • By_cheek placement with phantom rotation
  • Binary search for touching angle detection
  • Scene alignment optimization

Related documentation: Configuration (gridding parameters), Advanced features (phantom rotation)

Prerequisites

  • Tutorial 3 completed (near-field basics)
  • Understanding of FDTD grid concepts
  • Antenna models available

Bash setup

Run this once per notebook session:

from pathlib import Path
import importlib.util

p = Path.cwd()
while not (p / "scripts" / "notebook_helpers.py").exists():
    p = p.parent
spec = importlib.util.spec_from_file_location("_", p / "scripts" / "notebook_helpers.py")
m = importlib.util.module_from_spec(spec)
spec.loader.exec_module(m)
run_bash = m.get_run_bash()

import IPython

IPython.core.display.max_output_size = None

This helper function lets you run bash commands from Python cells using run_bash('command').

If you're using bash directly (recommended), ignore the Python code blocks and just run the commands directly. Make sure to always run source .bashrc first.


Computational gridding basics

The FDTD solver discretizes space into a rectangular mesh. Cell size affects both accuracy and computation time.

Key tradeoffs:

  • Smaller cells: More accurate, longer simulation time
  • Larger cells: Faster simulation, potential artifacts
  • Optimal size: 10-20 cells per wavelength minimum

For a 700 MHz signal: - Wavelength in tissue: ~40-60 mm (depends on tissue type) - Recommended cell size: 2-3 mm (GOLIAT enforces a 3 mm maximum for manual grids)

GOLIAT supports three gridding strategies:

Strategy When to use Configuration
Automatic Quick tests, uniform materials grid_mode: "automatic"
Manual Production runs, precise control grid_mode: "manual" + per-frequency settings
Subgridding Fine details (antenna parts) Defined in antenna_config

The GOLIAT EU Project uses manual gridding with per-frequency settings for all production simulations.


The configuration file

This tutorial uses the Thelonious phantom with two frequencies (700 MHz and 835 MHz) to demonstrate gridding configurations. The 700 MHz antenna uses subgridding, while 835 MHz does not.

run_bash("cat configs/tutorial_4_advanced.json")

Gridding parameters

The gridding_parameters section defines global mesh settings.

"gridding_parameters": {
  "global_gridding": {
    "grid_mode": "manual",
    "manual_fallback_max_step_mm": 5.0
  },
  "global_gridding_per_frequency": {
    "700": 2.5,
    "835": 2.5
  },
  "padding": {
    "padding_mode": "manual",
    "manual_bottom_padding_mm": [0, 0, 0],
    "manual_top_padding_mm": [0, 0, 0]
  }
}

Key parameters:

  • grid_mode: Use "manual" for explicit control, "automatic" for Sim4Life defaults
  • manual_fallback_max_step_mm: Maximum cell size if no per-frequency value is defined
  • global_gridding_per_frequency: Cell size in mm for each frequency
  • padding: Extra space around simulation boundaries

The per-frequency settings let you use finer grids for higher frequencies where wavelengths are shorter.

Grid visualization

Left: Coarse 5 mm grid. Right: Fine 2.5 mm grid. Finer grids resolve more details.

Subgridding for antennas

Subgridding applies a finer mesh to specific components without affecting the global grid. This is useful for small antenna features that need high resolution.

Here's the 700 MHz antenna config with subgridding:

"700": {
  "center_frequency": 700,
  "target_power_mW": 267,
  "model_type": "PIFA",
  "source_name": "Lines 1",
  "materials": {...},
  "gridding": {
    "automatic": ["component1:Substrate", "Lines 1"],
    "subgridding": {
      "components": ["component1:Battery", "component1:Patch", "Extrude 1", "component1:ShortingPin"],
      "SubGridMode": "Box",
      "SubGridLevel": "x9",
      "AutoRefinement": "AutoRefinementVeryFine"
    }
  }
}

Subgridding parameters:

  • components: List of CAD entities to apply subgrid to
  • SubGridMode: Subgrid shape ("Box" is standard)
  • SubGridLevel: Resolution multiplier relative to global grid
  • "x3": 3x finer (cell size divided by 3)
  • "x9": 9x finer (cell size divided by 9)
  • Higher values give better accuracy but increase computation time
  • AutoRefinement: Additional refinement level
  • "AutoRefinementVeryFine": Highest quality
  • "AutoRefinementFine": Moderate quality
  • "AutoRefinementDefault": Standard quality

For 700 MHz with global grid 2.5 mm and SubGridLevel x9: - Global cell size: 2.5 mm - Subgrid cell size: 2.5 / 9 ≈ 0.28 mm

This resolves fine antenna features without making the entire domain expensive.

Subgrid visualization

Subgrid region around antenna components (shown in red). Global grid is coarser (blue).

Comparing with and without subgridding

The 835 MHz antenna does not use subgridding:

"835": {
  "center_frequency": 835,
  "target_power_mW": 228,
  "model_type": "PIFA",
  "source_name": "Lines 1",
  "materials": {...},
  "gridding": {
    "automatic": ["component1:Substrate", "Lines 1"]
  }
}

This antenna uses only the global 2.5 mm grid. You can compare results to see if subgridding makes a difference for your specific antenna geometry.


By_cheek placement

The by_cheek placement positions the phone at the ear, using the tragus (ear cartilage protrusion) as an anatomical landmark.

Configuration

"placement_scenarios": {
  "by_cheek": {
    "bounding_box": "default",
    "positions": {
      "tragus": [0, 0, 0]
    },
    "orientations": {
      "cheek_base": {
        "rotate_phantom_to_cheek": true,
        "angle_offset_deg": 0
      },
      "tilt_base": []
    },
    "antenna_reference": {
      "distance_from_top": 10
    },
    "phantom_reference": "tragus"
  }
}

Key differences from front_of_eyes:

  • Uses tragus instead of nasion as reference point
  • Distance from cheek is 8 mm (vs 200 mm from eyes)
  • Includes base alignment calculation (ear-to-mouth vector)
  • Supports phantom rotation option

Tragus landmark

The phantom_reference: "tragus" parameter points to coordinates defined in phantom_definitions:

"phantom_definitions": {
  "thelonious": {
    "tragus": [0, 7, -5],
    "lips": [0, 122, 31],
    ...
    "placements": {
      "do_by_cheek": true,
      "distance_from_cheek": 8
    }
  }
}

GOLIAT finds the ear skin entity, calculates its center, applies the tragus offset, then positions the phone 8 mm away.

Tragus landmark

Tragus anatomical landmark used as reference for by_cheek placement.


Phantom rotation

For some by_cheek orientations, the phantom needs to rotate toward the phone to create realistic contact. This is configured using a dictionary format in the orientations section.

Configuration

"orientations": {
  "cheek_base": {
    "rotate_phantom_to_cheek": true,
    "angle_offset_deg": 0
  }
}

Parameters:

  • rotate_phantom_to_cheek: Enable automatic rotation (boolean)
  • angle_offset_deg: Additional angle to rotate phantom away from phone after contact (number)

When this is enabled, GOLIAT: 1. Places phone and phantom in initial positions 2. Runs binary search to find touching angle 3. Applies the angle + offset 4. Rotates phantom, bounding boxes, and sensors 5. Continues with scene alignment

Binary search for touching angle

GOLIAT uses binary search to find the exact angle where the phantom's skin touches the phone's ground plane.

The search: - Searches between 0 and 30 degrees rotation around Z axis - Uses 0.5 degree precision - Checks for overlap at each iteration - Stops when touching distance is minimized

Watch the console output during setup to see the search progress:

  - Handling phantom rotation...
  - Finding touching angle via binary search...
    - Testing angle: 15.00 deg (distance: 2.35 mm)
    - Testing angle: 7.50 deg (distance: 5.82 mm)
    - Testing angle: 11.25 deg (distance: 3.47 mm)
    - Testing angle: 13.12 deg (distance: 2.89 mm)
    ...
    - Determined touching angle: 13.50 deg
    - Applying offset: 0.00 deg
    - Final rotation: 13.50 deg
    - Done in 4.8s

Binary search log

Console output showing binary search iterations converging to optimal angle.

Before and after rotation

Before rotation

Before rotation: Gap between phantom and phone.

After rotation

After rotation: Phantom skin touches phone ground plane.

Angle offset

The angle_offset_deg parameter lets you fine-tune the final position. Positive values rotate the phantom away from the phone (increasing gap), negative values rotate toward it (increasing overlap).

Examples: - angle_offset_deg: 0: Phantom just touches phone (default) - angle_offset_deg: 2: 2 degree separation (small gap) - angle_offset_deg: -1: 1 degree overlap (slight compression)


Scene alignment

For by_cheek placements, GOLIAT automatically aligns the entire scene with the phone's upright orientation. This optimizes the computational grid and can reduce simulation time.

How it works

After phantom rotation (if enabled), GOLIAT:

  1. Identifies reference entities on phone:
  2. PIFA: component1:Substrate and component1:Battery
  3. IFA: Ground and Battery
  4. Calculates transformation matrix to make phone upright
  5. Applies transformation to all scene entities:
  6. Phantom group
  7. Antenna group
  8. Simulation bounding box
  9. Antenna bounding box
  10. Head and trunk bounding boxes
  11. Point sensors

Only parent groups and bounding boxes are transformed, not individual tissues. This keeps relative geometry correct while aligning the grid with the phone.

Scene alignment

Scene after alignment: Phone is upright, grid aligns with phone orientation.

Why this matters

FDTD grids are always axis-aligned rectangular meshes. When the phone is tilted relative to the axes, the grid must use smaller cells to resolve edges accurately. Aligning the phone with the axes lets the grid use larger cells while maintaining accuracy.

This can reduce: - Number of grid cells (by 10-30%) - Memory usage - Simulation time

The alignment happens automatically for all by_cheek placements. No configuration is needed.


Running the simulations

Run the study:

run_bash("goliat study tutorial_4_advanced")

This runs 4 simulations: 1. 700 MHz, by_cheek, tragus position, cheek_base orientation (with rotation, with subgrid) 2. 700 MHz, by_cheek, tragus position, tilt_base orientation (no rotation, with subgrid) 3. 835 MHz, by_cheek, tragus position, cheek_base orientation (with rotation, no subgrid) 4. 835 MHz, by_cheek, tragus position, tilt_base orientation (no rotation, no subgrid)

Watch for these key steps in the logs:

For cheek_base orientation:

  - Calculating placement details...
  - Calculated base rotation for cheek alignment: 84.23 degrees around X-axis.
  - Handling phantom rotation...
  - Finding touching angle via binary search...
  - Determined touching angle: 13.50 deg
  - Aligning simulation scene with phone...

For tilt_base orientation:

  - Calculating placement details...
  - Calculated base rotation for cheek alignment: 84.23 degrees around X-axis.
  - Handling phantom rotation...
  - Phantom rotation not enabled for this orientation.
  - Aligning simulation scene with phone...

The setup phase takes longer for cheek_base due to the binary search (extra 5-8 seconds).


Comparing results

After simulations complete, compare the four cases:

Subgridding comparison: - 700 MHz (with subgrid) vs 835 MHz (no subgrid) - Check if SAR hotspot patterns differ - Compare peak 10g SAR values

Rotation comparison: - cheek_base (with rotation) vs tilt_base (no rotation) - Check contact region SAR values - Compare field penetration depth

TODO: Analysis and visualization tools will be covered in a future tutorial.

Find results in:

results/near_field/thelonious/700MHz/
  by_cheek_tragus_cheek_base/     # With rotation, with subgrid
  by_cheek_tragus_tilt_base/      # No rotation, with subgrid

results/near_field/thelonious/835MHz/
  by_cheek_tragus_cheek_base/     # With rotation, no subgrid
  by_cheek_tragus_tilt_base/      # No rotation, no subgrid

Gridding recommendations

Based on GOLIAT EU Project experience:

For production simulations: - Use manual gridding with per-frequency settings - 2.5 mm cells for frequencies below 1000 MHz - 1.5-2.0 mm cells for frequencies 1000-3000 MHz - 1.0 mm cells for frequencies above 3000 MHz

For subgridding: - Apply to small antenna components (patches, pins, batteries) - Use SubGridLevel x9 for fine metallic features - Use AutoRefinementVeryFine for best accuracy - Skip subgridding for quick test runs

For phantom rotation: - Enable for by_cheek placements where contact matters - Use angle_offset_deg: 0 for realistic contact - Test without rotation first to verify basic setup


What's next

You've learned advanced near-field techniques. Next steps:

Tutorial 5: Parallel and cloud execution - Running multiple simulations simultaneously - Config splitting strategies - Using oSPARC for cloud compute


Summary

You learned: - FDTD grids discretize space into rectangular meshes with tradeoffs between accuracy and speed - Manual gridding with per-frequency settings provides precise control - Subgridding applies finer resolution to antenna components without affecting global grid - By_cheek placement uses tragus landmark and includes automatic base alignment - Phantom rotation uses binary search to find optimal touching angle - Scene alignment optimizes grid orientation for by_cheek placements automatically - The GOLIAT EU Project uses manual 2.5 mm grids with x9 subgridding for production

In Tutorial 5, we'll explore running multiple simulations in parallel to use multi-core systems and cloud compute resources.