# Source code for stride.problem.domain

```
import numpy as np
from cached_property import cached_property

__all__ = ['Space', 'Time', 'SlowTime', 'Grid']

[docs]
class Space:
"""
This defines the spatial grid over which the problem is defined.

The spatial grid consists of an inner domain defined by ``shape`` and
an external padding defined by ``extra``. Within this extra region, a
further sub-region is defined as absorbing for boundary purposes as defined
by ``absorbing``.

The ``spacing`` defines the axis-wise spacing of the grid.

Parameters
----------
shape : tuple
Shape of the inner domain.
spacing : tuple or float
Axis-wise spacing of the grid, in metres.
extra : tuple
Amount of axis-wise extra space around the inner domain.
absorbing : tuple
Portion of the extra space that corresponds to absorbing boundaries.

"""

def __init__(self, shape=None, spacing=None, extra=None, absorbing=None):
if isinstance(spacing, float):
spacing = (spacing,)*len(shape)

extra = extra or (0,)*len(shape)
absorbing = absorbing or (0,)*len(shape)

self.dim = len(shape)
self.shape = tuple(shape)
self.spacing = tuple(spacing)
self.extra = tuple(extra)
self.absorbing = tuple(absorbing)

origin = (0,) * self.dim
pml_origin = tuple([each_origin - each_spacing * each_extra for each_origin, each_spacing, each_extra in
zip(origin, spacing, extra)])

size = tuple(np.array(spacing) * (np.array(shape) - 1))
extended_size = tuple([each_origin + each_spacing * (each_shape + each_extra - 1)
for each_origin, each_spacing, each_shape, each_extra in zip(origin, spacing, shape, extra)])

self.origin = origin
self.pml_origin = pml_origin
self.extended_shape = extended_shape
self.limit = size
self.extended_limit = extended_size

@property
def size(self):
"""
Alias for the domain limit.

"""
return self.limit

@property
def extended_size(self):
"""
Alias for the extended domain limit.

"""
return self.extended_limit

[docs]
def resample(self, spacing, extra=None, absorbing=None):
if isinstance(spacing, float):
spacing = (spacing,)*self.dim

shape = tuple((np.array(self.size) / np.array(spacing) + 1).astype(int))

if extra is None:
extra = tuple((np.array(self.spacing) * (np.array(self.extra) - 1) /
np.array(spacing) + 1).astype(int))

if absorbing is None:
absorbing = tuple((np.array(self.spacing) * (np.array(self.absorbing) - 1) /
np.array(spacing) + 1).astype(int))

return Space(shape=shape, spacing=spacing, extra=extra, absorbing=absorbing)

@property
def inner(self):
"""
Slices defining the inner domain, as a tuple of slices.

"""
return tuple([slice(extra, extra + shape) for shape, extra in zip(self.shape, self.extra)])

@property
"""
Tensor of the shape of the space grid with gridpoints wihtin inner domain set to 1
and those outside set to 0, as an ndarray.

"""
pml_slices = self.inner

@cached_property
def mesh_indices(self):
"""
Create the mesh of indices in the inner domain, as a tuple
of ndarray.

"""
grid = [np.arange(0, shape) for shape in self.shape]
return np.meshgrid(*grid)

@cached_property
def extended_mesh_indices(self):
"""
Create the mesh of indices in the extended domain, as a tuple
of ndarray.

"""
grid = [np.arange(0, extended_shape) for extended_shape in self.extended_shape]
return np.meshgrid(*grid)

@cached_property
def mesh(self):
"""
Create the mesh of spatial locations in the inner domain, as a tuple
of ndarray.

"""
grid = self.grid
return np.meshgrid(*grid, indexing='ij')

@cached_property
def extended_mesh(self):
"""
Create the mesh of spatial locations the full, extended domain, as a tuple
of ndarray.

"""
grid = self.extended_grid
return np.meshgrid(*grid, indexing='ij')

@cached_property
def indices(self):
"""
Indices corresponding to the grid of the inner domain, as a tuple of 1d-arrays.

"""
axes = [np.arange(0, shape) for shape in self.shape]
return tuple(axes)

@cached_property
def extended_indices(self):
"""
Indices corresponding to the grid of the extended domain, as a tuple of 1d-arrays.

"""
axes = [np.arange(0, extended_shape) for extended_shape in self.extended_shape]
return tuple(axes)

@cached_property
def grid(self):
"""
Spatial points corresponding to the grid of the inner domain, as a tuple of 1d-arrays.

"""
axes = [np.linspace(self.origin[dim], self.limit[dim], self.shape[dim],
endpoint=True, dtype=np.float32)
for dim in range(self.dim)]
return tuple(axes)

@cached_property
def extended_grid(self):
"""
Spatial points corresponding to the grid of the extended domain, as a tuple of 1d-arrays.

"""
axes = [np.linspace(self.pml_origin[dim], self.extended_limit[dim], self.extended_shape[dim],
endpoint=True, dtype=np.float32)
for dim in range(self.dim)]
return tuple(axes)

[docs]
class Time:
"""
This defines the temporal grid over which the problem is defined

A time grid is fully defined by three of its arguments: start, stop, step or num.

The time grid can be extended with a certain amount of padding, generating an
inner domain and an extended domain, similar to that seen in the Space.

Parameters
----------
start : float, optional
Point at which time starts, in seconds.
step : float, optional
Step between time points, in seconds.
num : int, optional
Number of time points in the grid.
stop : float, optional
Point at which time ends, in seconds.

"""

def __init__(self, start=None, step=None, num=None, stop=None):
self._set_properties(start, step, num, stop)

def _set_properties(self, start=None, step=None, num=None, stop=None):
try:
if start is None:
start = stop - step*(num - 1)
elif step is None:
step = (stop - start)/(num - 1)
elif num is None:
num = int(np.ceil((stop - start)/step + 1))
stop = step*(num - 1) + start
elif stop is None:
stop = start + step*(num - 1)

except:
raise ValueError('Three of args start, step, num and stop may be set')

if not isinstance(num, int):
raise TypeError('"input" argument must be of type int')

self.start = start
self.stop = stop
self.step = step
self.num = num

self.extra = 0
self.extended_start = start
self.extended_stop = stop
self.extended_num = num

[docs]
def extend(self, extra):
self.extra = extra
self.extended_start = self.start - (self.extra - 1)*self.step
self.extended_stop = self.stop + (self.extra - 1)*self.step
self.extended_num = self.num + self.extra + self.extra

[docs]
def resample(self, new_step, new_num):
"""
Resample a trace.

Parameters
----------
new_step : float
The time spacing for the interpolated grid
new_num : int
The number of time-points, default is calculated to match input pulse
length in [s]

Returns
-------
"""

dt_in = self.step  # Extract current parameters
start = self.start
stop = self.stop
num = self.num

new_start = 0.  # Calculate new parameters

interp_num = int((num)*(dt_in/new_step))
interp_stop = new_start + new_step*(interp_num - 1)

if new_num is not None:  # Do we need to pad the array or not?
new_stop = new_start + new_step*(new_num - 1)
else:
new_num = interp_num
new_stop = interp_stop

self._set_properties(start=new_start, step=new_step, num=new_num)  # Update time
try:
del self.__dict__['grid']
del self.__dict__['extended_grid']
except:
pass

@property
def inner(self):
"""
Slice defining the inner domain.

"""
return slice(self.extra, self.extra + self.num)

@cached_property
def grid(self):
"""
Time points corresponding to the grid of the inner domain, as a 1d-array.

"""
return np.linspace(self.start, self.stop, self.num, endpoint=True, dtype=np.float32)

@cached_property
def extended_grid(self):
"""
Time points corresponding to the grid of the extended domain, as a 1d-array.

"""
return np.linspace(self.extended_start, self.extended_stop, self.extended_num, endpoint=True, dtype=np.float32)

[docs]
class SlowTime:
"""
This defines the slow temporal grid over which the problem is defined

Parameters
----------
frame_rate : float, optional
Sampling frequency between frames, in Hz.
acq_rate : float, optional
Sampling frequency between acquisitions, in Hz.
frame_step : float, optional
Time step between frames, in seconds.
acq_step : float, optional
Time step between frames, in seconds.
num_frame : int, optional
Number of frames in the grid.
num_acq : int, optional
Number of acquisitions per frame.

"""

def __init__(self, frame_rate=None, acq_rate=None,
frame_step=None, acq_step=None,
num_frame=None, num_acq=None):
try:
if frame_step is None:
frame_step = 1/frame_rate
else:
frame_rate = 1/frame_step

except:
raise ValueError('Either freq or step has to be defined')

if not isinstance(num_frame, int):
raise TypeError('num_frames must be of type int')

if acq_step is None and acq_rate is None:
acq_step = 0
acq_rate = -1
num_acq = 1
else:
if not isinstance(num_acq, int):
raise TypeError('num_acq must be of type int')

if acq_step is None:
acq_step = 1/acq_rate
elif acq_rate is None:
acq_rate = 1/acq_step

if num_acq*acq_step > frame_step:
raise ValueError('Acquisition step (%e s) too large for frame step (%e s).'
% (num_acq*acq_step, frame_step))

start = 0.
stop = start + frame_step * (num_frame - 1)

self.start = start
self.stop = stop
self.frame_step = frame_step
self.frame_rate = frame_rate
self.num_frame = num_frame
self.acq_step = acq_step
self.acq_rate = acq_rate
self.num_acq = num_acq

[docs]
def resample(self):
raise NotImplementedError('Resampling has not been implemented yet')

@property
def num(self):
"""
Total number of steps.

"""
return self.num_frame*self.num_acq

@property
def extended_num(self):
"""
Total number of steps.

"""
return self.num

@property
def inner(self):
"""
Slice defining the inner domain.

"""
return slice(0, None)

@cached_property
def grid(self):
"""
Time points corresponding to the grid, as a 1d-array.

"""
if self.acq_rate > 0:
start = 0.
stop = start + self.acq_step * (self.num_acq - 1)

grid = [np.linspace(start + self.frame_step*acq, stop + self.frame_step*acq,
self.num_acq, endpoint=True, dtype=np.float32)
for acq in range(self.num_frame)]

return np.concatenate(grid)
else:
return np.linspace(self.start, self.stop, self.num_frame, endpoint=True, dtype=np.float32)

class Grid:
"""
The grid is a container for the spatial and temporal grids.

Parameters
----------
space : Space
time : Time
slow_time : SlowTime
"""

def __init__(self, space=None, time=None, slow_time=None):
self.space = space
self.time = time
self.slow_time = slow_time
```