Introduction to VectorField Class

This tutorial introduces the VectorField class in gwexpy, which represents vector-valued fields in 4D spacetime.

What is VectorField?

VectorField is a specialized container that manages multiple ScalarField components as a single vector-valued physical field. Each component (e.g., x, y, z) is a full ScalarField with its own spacetime structure, but the VectorField ensures all components share identical axes and metadata.

Key Features:

  • Component-wise operations: Apply FFT, filtering, and signal processing to each vector component independently

  • Geometrical consistency: All components guaranteed to have the same axis structure

  • Vector algebra: Compute norm, dot products, and other vector operations

  • Flexible basis: Support for Cartesian and custom coordinate systems

When to use VectorField:

  • Electromagnetic fields (E-field, B-field)

  • Velocity or acceleration fields

  • Force or displacement fields

  • Any physical quantity with directional components in spacetime

Basic Usage

Creating a VectorField

A VectorField is constructed from a dictionary of ScalarField components:

import numpy as np
from astropy import units as u
from gwexpy.fields import ScalarField, VectorField

# Create sample 4D data for each component
nt, nx, ny, nz = 100, 8, 8, 8
t = np.arange(nt) * 0.01 * u.s
x = np.arange(nx) * 0.5 * u.m
y = np.arange(ny) * 0.5 * u.m
z = np.arange(nz) * 0.5 * u.m

# X component: wave propagating in +x direction
data_x = np.sin(2 * np.pi * (5 * t.value[:, None, None, None] - x.value[None, :, None, None]))
field_x = ScalarField(
    data_x,
    unit=u.V/u.m,
    axis0=t, axis1=x, axis2=y, axis3=z,
    axis_names=['t', 'x', 'y', 'z'],
    axis0_domain='time',
    space_domain='real'
)

# Y component: constant background
data_y = np.ones((nt, nx, ny, nz)) * 0.1
field_y = ScalarField(
    data_y,
    unit=u.V/u.m,
    axis0=t, axis1=x, axis2=y, axis3=z,
    axis_names=['t', 'x', 'y', 'z'],
    axis0_domain='time',
    space_domain='real'
)

# Z component: small noise
data_z = np.random.randn(nt, nx, ny, nz) * 0.05
field_z = ScalarField(
    data_z,
    unit=u.V/u.m,
    axis0=t, axis1=x, axis2=y, axis3=z,
    axis_names=['t', 'x', 'y', 'z'],
    axis0_domain='time',
    space_domain='real'
)

# Create VectorField
vec_field = VectorField({'x': field_x, 'y': field_y, 'z': field_z})

print(f"VectorField components: {list(vec_field.keys())}")
print(f"Basis: {vec_field.basis}")

Accessing Components

Individual components can be accessed like a dictionary:

# Access individual components
Ex = vec_field['x']
Ey = vec_field['y']
Ez = vec_field['z']

print(f"X component shape: {Ex.shape}")
print(f"Y component shape: {Ey.shape}")

Computing Vector Magnitude

The norm() method computes the L2 norm (magnitude) at each spacetime point:

# Compute magnitude: |E| = √(Ex² + Ey² + Ez²)
magnitude = vec_field.norm()

print(f"Magnitude type: {type(magnitude)}")  # Returns a ScalarField
print(f"Magnitude shape: {magnitude.shape}")
print(f"Magnitude unit: {magnitude.unit}")

Transformations

Since each component is a ScalarField, you can apply FFT and other transformations to the entire vector field:

Time-Frequency Transform

# Transform all components to frequency domain
vec_field_freq = VectorField({
    key: field.fft_time()
    for key, field in vec_field.items()
})

print(f"Frequency domain: {vec_field_freq['x'].axis0_domain}")

Spatial FFT (Real → K-space)

# Transform all components to wavenumber space
vec_field_k = VectorField({
    key: field.fft_space()
    for key, field in vec_field.items()
})

print(f"K-space domains: {vec_field_k['x'].space_domains}")

Converting to Array

For numerical analysis, you can convert the VectorField to a single 5D NumPy array:

# Convert to 5D array: (time, x, y, z, components)
array_5d = vec_field.to_array()

print(f"Array shape: {array_5d.shape}")
# Expected: (100, 8, 8, 8, 3) for 3 components

The last axis (axis 4) contains the vector components in the order they were defined.

Practical Example: Electromagnetic Field

Here’s a complete example simulating a simple electromagnetic field:

import matplotlib.pyplot as plt

# Create a localized wave packet in E-field
nt, nx, ny, nz = 200, 16, 16, 16
t = np.linspace(0, 1, nt) * u.s
x = np.linspace(-5, 5, nx) * u.m
y = np.linspace(-5, 5, ny) * u.m
z = np.linspace(-5, 5, nz) * u.m

# Gaussian envelope in space, oscillating in time
X, Y, Z = np.meshgrid(x.value, y.value, z.value, indexing='ij')
r = np.sqrt(X**2 + Y**2 + Z**2)
envelope = np.exp(-r**2 / 2)

# Oscillating E-field components
omega = 2 * np.pi * 10  # 10 Hz
Ex_data = envelope[None, :, :, :] * np.sin(omega * t.value[:, None, None, None])
Ey_data = envelope[None, :, :, :] * np.cos(omega * t.value[:, None, None, None]) * 0.5
Ez_data = np.zeros((nt, nx, ny, nz))

# Create ScalarField components
Ex_field = ScalarField(Ex_data, unit=u.V/u.m, axis0=t, axis1=x, axis2=y, axis3=z,
                       axis_names=['t','x','y','z'], axis0_domain='time', space_domain='real')
Ey_field = ScalarField(Ey_data, unit=u.V/u.m, axis0=t, axis1=x, axis2=y, axis3=z,
                       axis_names=['t','x','y','z'], axis0_domain='time', space_domain='real')
Ez_field = ScalarField(Ez_data, unit=u.V/u.m, axis0=t, axis1=x, axis2=y, axis3=z,
                       axis_names=['t','x','y','z'], axis0_domain='time', space_domain='real')

# Create E-field vector
E_field = VectorField({'Ex': Ex_field, 'Ey': Ey_field, 'Ez': Ez_field})

# Compute field magnitude
E_magnitude = E_field.norm()

# Visualize magnitude at a specific time slice (t=0.5s)
time_idx = 100  # Middle of time range
magnitude_slice = E_magnitude[time_idx, :, :, nz//2].value.squeeze()

plt.figure(figsize=(8, 6))
plt.imshow(magnitude_slice.T, origin='lower', extent=[-5, 5, -5, 5], cmap='viridis')
plt.colorbar(label='|E| [V/m]')
plt.xlabel('X [m]')
plt.ylabel('Y [m]')
plt.title('Electric Field Magnitude (t=0.5s, z=0 plane)')
plt.show()

Summary

VectorField provides a powerful abstraction for vector-valued physical fields:

  • Creation: Build from ScalarField components

  • Access: Dictionary-like component access

  • Operations: Norm, component-wise FFT, etc.

  • Consistency: Automatic validation of axes and metadata

  • Flexibility: Works with any coordinate basis

Next Steps