"""
Classes for generating geometric surfaces:
===========================================================================
===========================================================================
Each class inherits functionality from the _AnalyticalSurface class but changes the
__init__, _height and __repr__ functions
===========================================================================
===========================================================================
FlatSurface:
For generating flat surfaces, slopes are allowed in both directions
PyramidSurface:
For generating square pyramid surfaces like indenters
RoundSurface:
For generating ball type surfaces different radii allowed in each
direction
===========================================================================
===========================================================================
"""
__all__ = ['FlatSurface', 'RoundSurface', 'PyramidSurface']
import collections.abc
import typing
import warnings
from numbers import Number
import numpy as np
from .Surface_class import _AnalyticalSurface
[docs]class FlatSurface(_AnalyticalSurface):
""" Flat surface can be angled in any direction by changing slope
Parameters
----------
slope : {tuple, float}, optional (0,0)
The gradient of the surface in the x and y directions
rotation: float, optional (None)
If set the surface will be rotated by this number of radians
shift: tuple, optional (None)
If set the surface will be shifted by this distance in the and y directions, tuple should be length 2, if not
set the default is to shift by half the extent, meaning that the origin becomes the centre.
generate: bool, optional (False)
If True the surface profile is discretised on instantiation.
grid_spacing: float, optional (None)
The distance between grid points on the surface profile
extent: tuple, optional (None)
The overall size of the surface
shape: tuple, optional (None)
The number of grid points in each direction on the surface
Attributes
----------
See Also
--------
Surface
Notes
-----
This is a subclass of Surface all functionality and attributes are available
within this class.
All keyword arguments allowed for Surface are also
allowed on instantiation of this class apart from the profile key word.
If the extent is findable on instantiation the surface will be shifted so the natural origin is in the centre
of the extent. If the extent is not findable the natural origin will be at (0,0)
Examples
--------
Make an analytically defined flat surface with no slope:
>>> import slippy.surface as s
>>> my_surface = s.FlatSurface()
Make a discretely defined flat surface with no slope:
>>> my_surface = s.FlatSurface(grid_spacing = 1e-6, shape = (256, 256), generate=True)
Alternatively, any two of: shape, extent and grid_spacing can be set, eg:
>>> my_surface = s.FlatSurface(extent = (1e-5, 1e-5), shape = (256, 256), generate=True)
"""
surface_type = 'flat'
analytic = True
[docs] def __init__(self, slope: typing.Union[tuple, float] = (0, 0), rotation: float = None,
shift: typing.Optional[tuple] = None,
generate: bool = False, grid_spacing: float = None,
extent: tuple = None, shape: tuple = None):
if isinstance(slope, collections.abc.Sequence):
self._slope = slope
elif isinstance(slope, Number):
# noinspection PyTypeChecker
slope = float(slope)
self._slope = [slope, 0]
if self.dimensions == 2:
warnings.warn("Assumed 0 slope in Y direction for"
" analytical flat surface")
super().__init__(generate=generate, rotation=rotation, shift=shift,
grid_spacing=grid_spacing, extent=extent, shape=shape)
def _height(self, x_mesh, y_mesh):
"""Analytically determined height of the surface at specified points
Parameters
----------
x_mesh, y_mesh : array-like
Arrays of X and Y points, must be the same shape
Returns
-------
Array of surface heights, with the same shape as the input arrays
Notes
-----
This is an alternative to discretise which may be more
appropriate for some applications
Examples
--------
>>> import numpy as np
>>> my_surface=FlatSurface(slope=(1,1))
>>> x, y = np.arange(10), np.arange(10)
>>> X, Y = np.meshgrid(x,y)
>>> Z=my_surface.height(X, Y)
"""
return x_mesh * self._slope[0] + y_mesh * self._slope[1]
def __repr__(self):
string = self._repr_helper()
return 'FlatSurface(slope=' + repr(self._slope) + string + ')'
[docs]class RoundSurface(_AnalyticalSurface):
""" Round surfaces with any radii
Parameters
----------
radius : Sequence
The radius of the surface in the X Y and Z directions, or in all
directions if a float is given
rotation: float, optional (None)
If set the surface will be rotated by this number of radians
shift: tuple, optional (None)
If set the surface will be shifted by this distance in the and y directions, tuple should be length 2, if not
set the default is to shift by half the extent, meaning that the origin becomes the centre.
generate: bool, optional (False)
If True the surface profile is discretised on instantiation.
grid_spacing: float, optional (None)
The distance between grid points on the surface profile
extent: tuple, optional (None)
The overall size of the surface
shape: tuple, optional (None)
The number of grid points in each direction on the surface
See Also
--------
Surface
Notes
-----
This is a subclass of Surface all functionality and attributes are available
within this class.
All keyword arguments allowed for Surface are also
allowed on instantiation of this class apart from the profile key word.
If the extent is findable on instantiation the surface will be shifted so the natural origin is in the centre
of the extent. If the extent is not findable the natural origin will be at (0,0).
For a round surface the natural origin is the centre of the ball.
Examples
--------
Making an analytically defined spherical surface with radius 1:
>>> import slippy.surface as s
>>> my_surface = s.RoundSurface((1,1,1), extent=(0.5,0.5))
Making a discretely defined cylindrical surface:
>>> my_surface = s.RoundSurface((1,float('inf'),1), extent=(0.5,0.5), grid_spacing=0.001, generate = True)
Making a discretely defined cylindrical surface which is rotated 45 degrees:
>>> my_surface = s.RoundSurface((1,float('inf'),1), extent=(0.5, 0.5), shape=(512, 512), generate = True,
>>> rotation=3.14/4)
"""
radius: tuple
[docs] def __init__(self, radius: typing.Sequence, rotation: float = None,
shift: typing.Optional[tuple] = None,
generate: bool = False, grid_spacing: float = None,
extent: tuple = None, shape: tuple = None):
if isinstance(radius, Number):
radius = (radius,)*3
radius = [r if r else np.inf for r in radius]
if np.isinf(radius[-1]):
raise ValueError('Radius in the z direction must be set')
if isinstance(radius, collections.abc.Sequence) and len(radius) == 3:
self._radius = radius
else:
msg = ('Radius must be either scalar or list of radii equal in '
'length to number of dimensions of the surface +1')
raise ValueError(msg)
super().__init__(generate=generate, rotation=rotation, shift=shift,
grid_spacing=grid_spacing, extent=extent, shape=shape)
def _height(self, x_mesh, y_mesh):
"""Analytically determined height of the surface at specified points
Parameters
----------
x_mesh, y_mesh : array-like
Arrays of x and y points, must be the same shape
Returns
-------
Array of surface heights, with the same shape as the input arrays
Notes
-----
This is an alternative to discretise the surface which may be more
appropriate for some applications
Examples
--------
>>> import numpy as np
>>> my_surface=RoundSurface(radius=(1,1,1))
>>> x, y = np.arange(10), np.arange(10)
>>> x_mesh, y_mesh = np.meshgrid(x,y)
>>> Z=my_surface.height(x_mesh, y_mesh)
"""
# noinspection PyTypeChecker
z = ((1 - (x_mesh / self._radius[0]) ** 2 - (y_mesh / self._radius[1]) ** 2) *
self._radius[-1]**2)**0.5 - self._radius[-1]
return np.nan_to_num(z, False)
def __repr__(self):
string = self._repr_helper()
return 'RoundSurface(radius=' + repr(self._radius) + string + ')'
[docs]class PyramidSurface(_AnalyticalSurface):
""" Pyramid surface with any slopes
Parameters
----------
lengths : {Sequence, float}
The characteristic lengths of the pyramid in each direction, if a scalar is given the results is a square based
pyramid with 45 degree sides
rotation: float, optional (None)
If set the surface will be rotated by this number of radians
shift: tuple, optional (None)
If set the surface will be shifted by this distance in the and y directions, tuple should be length 2, if not
set the default is to shift by half the extent, meaning that the origin becomes the centre.
generate: bool, optional (False)
If True the surface profile is discretised on instantiation.
grid_spacing: float, optional (None)
The distance between grid points on the surface profile
extent: tuple, optional (None)
The overall size of the surface
shape: tuple, optional (None)
The number of grid points in each direction on the surface
See Also
--------
Surface
Notes
-----
This is a subclass of Surface all functionality and attributes are available
within this class.
All keyword arguments allowed for Surface are also
allowed on instantiation of this class apart from the profile key word.
If the extent is findable on instantiation the surface will be shifted so the natural origin is in the centre
of the extent. If the extent is not findable the natural origin will be at (0,0).
For a round surface the natural origin is the centre of the ball.
Examples
--------
Making an analytically defined square based pyramid:
>>> import slippy.surface as s
>>> my_surface = s.PyramidSurface((1,1,1), extent=(0.5,0.5))
Making a discretely defined square based pyramid:
>>> my_surface = s.PyramidSurface((1, 1, 3), extent=(0.5,0.5), grid_spacing=0.001, generate = True)
"""
surface_type = 'pyramid'
[docs] def __init__(self, lengths: typing.Union[typing.Sequence], rotation: float = None,
shift: typing.Optional[tuple] = None,
generate: bool = False, grid_spacing: float = None,
extent: tuple = None, shape: tuple = None):
if isinstance(lengths, Number):
lengths = (lengths, ) * 3
if type(lengths) is tuple:
if len(lengths) == (self.dimensions + 1):
self._lengths = lengths
else:
msg = ('Lengths must be either scalar or list of Lengths equal'
' in length to number of dimensions of the surface +1')
raise ValueError(msg)
super().__init__(generate=generate, rotation=rotation, shift=shift,
grid_spacing=grid_spacing, extent=extent, shape=shape)
def _height(self, x_mesh, y_mesh):
"""Analytically determined height of the surface at specified points
Parameters
----------
x_mesh, y_mesh : array-like
Arrays of X and Y points, must be the same shape
Returns
-------
Array of surface heights, with the same shape as the input arrays
Notes
-----
This is an alternative to discretise the surface which may be more
appropriate for some applications
Examples
--------
>>> import numpy as np
>>> my_surface=PyramidSurface(lengths=[20,20,20])
>>> x, y = np.arange(10), np.arange(10)
>>> X, Y = np.meshgrid(x,y)
>>> Z=my_surface.height(X, Y)
"""
return (0 - np.abs(x_mesh) / self._lengths[0] - np.abs(y_mesh) / self._lengths[1]) * self._lengths[-1]
def __repr__(self):
string = self._repr_helper()
return 'PyramidSurface(lengths=' + repr(self._lengths) + string + ')'