#! /usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Copyright © 2016-2019 Cyril Desjouy <cyril.desjouy@univ-lemans.fr>
#
# This file is part of fdgrid
#
# fdgrid is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# fdgrid is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with fdgrid. If not, see <http://www.gnu.org/licenses/>.
#
#
# Creation Date : 2019-02-13 - 07:54:54
#
# pylint: disable=too-many-public-methods
# pylint: disable=too-few-public-methods
# pylint: disable=attribute-defined-outside-init
"""
-----------
Module `domains` provides three main objects:
* Subdomain : dataclass for subdivisions of the computational domain
* Obstacle : Subdomain subclass with custom boundary possibilities
* Domain : Container for Subdomain objects
-----------
"""
__all__ = ['Domain', 'Subdomain', 'Obstacle']
import copy as _copy
import itertools as _itertools
import numpy as _np
import scipy.signal as _sps
import matplotlib.pyplot as _plt
import matplotlib.patches as _patches
import dataclasses as _dataclasses
import fdgrid.utils as _utils
[docs]class Domain:
""" Set of Subdomain objects.
Parameters
----------
shape : Size of the domain. Must be a 2 elements tuple
data : list of Subdomain objects or coordinates
"""
def __init__(self, shape, data=None):
self.shape = shape
self.nx, self.nz = shape
self._data = dict({})
if data and isinstance(data, (list, tuple)):
if not isinstance(data[0], Subdomain):
data = self._subdomain_from_list(data)
for sub in data:
if not sub.key:
sub.key = self._key
self._data[sub.key] = sub
# Additional rigid boundaries when bc is periodic
self.additional_rigid_bc = None
# Domain counter
self._n = 0
@staticmethod
def _subdomain_from_list(data):
subs = []
for c in data:
subs.append(Subdomain(c))
return subs
[docs] def show(self, legend=False, fcolor='b', ecolor='y'):
""" Represent the Subdomains. """
_, ax = _plt.subplots(figsize=(9, 9))
for sub in self:
rect = _patches.Rectangle((sub.ix[0], sub.iz[0]),
sub.ix[1] - sub.ix[0],
sub.iz[1] - sub.iz[0],
linewidth=3,
edgecolor=ecolor,
facecolor=fcolor,
alpha=0.5)
ax.add_patch(rect)
ax.set_xlim(0, self.nx)
ax.set_ylim(0, self.nz)
ax.set_aspect('equal')
major_xticks = range(0, self.nx, 10)
major_zticks = range(0, self.nz, 10)
minor_xticks = range(0, self.nx, 2)
minor_zticks = range(0, self.nz, 2)
ax.set_xticks(major_xticks)
ax.set_yticks(major_zticks)
ax.set_xticks(minor_xticks, minor=True)
ax.set_yticks(minor_zticks, minor=True)
ax.grid(which='minor', alpha=0.2)
ax.grid(which='major', alpha=0.5)
for sub in self:
ax.text(*sub.center, f'{sub.key} ({sub.tag})', color='k')
if legend:
print(self)
[docs] def keys(self):
""" Return all keys of Domain instance. """
return _utils.sort(self._data.keys())
[docs] def sort(self, inplace=False):
"""
Sort domains.
If inplace is True, Domain is sorted inplace.
If inplace is False, return a new instance.
"""
tmp_list = []
tmp_dict = {}
for key in self.keys():
tmp_list.append(self._data[key])
tmp_dict[key] = self[key]
if inplace:
self._data = tmp_dict
return None
return Domain(self.shape, data=tmp_list)
[docs] def copy(self):
""" Create copy of the instance. """
return _copy.deepcopy(self)
[docs] def mask(self):
""" Create mask array of the domain. """
mask = _np.zeros(self.shape)
for sub in self:
mask[sub.sx, sub.sz] += 1
return mask
[docs] def append(self, val, key=None):
"""
Append 'val' to the Domain object.
Parameters
----------
val : The object to add to the instance.
Must be a Subdomain object or a list/tuple of Subdomain objects
key : new key for val
"""
if isinstance(val, Subdomain):
if key:
val.key = str(key)
elif not val.key and not key:
val.key = self._key
if val.key in self._data.keys():
raise ValueError(f'Domain {val.key} already exists')
self._data[val.key] = val
self.sort(inplace=True)
elif isinstance(val, (tuple, list)):
for sub in val:
if isinstance(sub, Subdomain):
self.append(sub)
else:
raise ValueError('Not a Subdomain object')
[docs] def pop(self, n):
""" Remove value of index n from the Domain object. """
self._check_key(str(n))
self._data.pop(str(n))
[docs] def steal(self, other, key, newkey=None):
""" Steal key from other. """
if not newkey:
newkey = key
try:
self.append(other[key], key=newkey)
except ValueError:
pass
finally:
other.pop(key)
[docs] def split(self, key, index, nbc=None, axis=None):
""" Split 'key' subdomain.
Parameters
----------
index : int|tuple|list
nbc : Only if index is a sequence. New bc for the domain defined by 'index'
axis : 0 or 1. Along which axis to split
"""
self[key] = self[key].split(index, nbc=nbc, axis=axis)
self._clean_keys()
def _clean_keys(self):
""" Convert 1.1.1 keys to 1.X """
badkeys = {key.split('.')[0] for key in self.keys() if len(key.split('.')) > 2}
if badkeys:
newkeys = self.keys().copy()
for basekey in badkeys:
subkey = (i for i in range(1, len(newkeys)+1))
for idx, key in enumerate(newkeys):
if key.split('.')[0] == basekey:
newkeys[idx] = '{}.{}'.format(basekey, next(subkey))
tmp = {}
for idx, sub in enumerate(self):
sub.key = newkeys[idx]
tmp[sub.key] = sub
self._data = tmp
[docs] def reset_keys(self):
""" Reset all keys then attribute new ordered keys. """
tmp = {}
newkeys = [str(i) for i in range(1, len(self.keys())+1)]
for key, sub in zip(newkeys, self):
sub.key = key
tmp[key] = sub
self._data = tmp
[docs] def join(self, key1, key2, include_split=False, tag=None):
""" Join key1 and key2 Subdomain objects. """
self._check_key(key1)
self._check_key(key2)
sub1 = self[key1]
sub2 = self[key2]
if not tag:
tag = sub1.tag
if not self.isjoinable(sub1, sub2, include_split=include_split):
raise ValueError("domains can't be joined")
if sub1.rx == sub2.rx:
bc = self._join_bc(sub1, sub2, axis=1)
kwargs = {'bc':bc, 'key':sub1.key, 'axis':sub1.axis, 'tag':tag}
iz = sub1.iz + sub2.iz
self[sub1.key] = Subdomain([sub1.ix[0], min(iz), sub1.ix[1], max(iz)], **kwargs)
self.pop(sub2.key)
elif sub1.rz == sub2.rz:
bc = self._join_bc(sub1, sub2, axis=0)
kwargs = {'bc':bc, 'key':sub1.key, 'axis':sub1.axis, 'tag':tag}
ix = sub1.ix + sub2.ix
self[sub1.key] = Subdomain([min(ix), sub1.iz[0], max(ix), sub1.iz[1]], **kwargs)
self.pop(sub2.key)
[docs] def autojoin(self, include_split=False):
""" Automatically join domains that can be joined. """
for sub1, sub2 in _itertools.combinations(self, r=2):
if self.isjoinable(sub1, sub2, include_split=include_split):
self.join(sub1.key, sub2.key, include_split=include_split)
[docs] @staticmethod
def isjoinable(sub1, sub2, include_split=False):
""" Check if sub1 and sub2 can be joined. """
if include_split:
c0 = True
else:
c0 = '.' not in sub1.key and '.' not in sub2.key
c1 = sub1.rz == sub2.rz
c2 = sub1.ix[1] == sub2.ix[0] - 1 or sub2.ix[1] == sub1.ix[0] - 1
c3 = sub1.rx == sub2.rx
c4 = sub1.iz[1] == sub2.iz[0] - 1 or sub2.iz[1] == sub1.iz[0] - 1
c5 = sub1.axis == sub2.axis
condition0 = c0 and c5
condition1 = c1 and c2
condition2 = c3 and c4
if (condition0 and condition1) or (condition0 and condition2):
return True
return False
@staticmethod
def _join_bc(sub1, sub2, axis=None):
""" Determinae bc for the joined Subdomain. """
if axis == 0:
if sub1.axis == 0 and sub1.ix[0] < sub2.ix[0]:
return f'{sub1.bc[0]}.{sub2.bc[2]}.'
if sub1.axis == 0 and sub1.ix[0] > sub2.ix[0]:
return f'{sub2.bc[0]}.{sub1.bc[2]}.'
if axis == 1:
if sub1.axis == 1 and sub1.iz[0] < sub2.iz[0]:
return f'.{sub1.bc[1]}.{sub2.bc[3]}'
if sub1.axis == 1 and sub1.iz[0] > sub2.iz[0]:
return f'.{sub2.bc[1]}.{sub1.bc[3]}'
return sub1.bc
[docs] def periodic_bc(self, mask, axis):
""" Arange subdomains to take into account periodic boundary condtions. """
if axis == 0:
self._xperiod(mask)
self._get_rigid_bc(self.nx, axis=axis)
elif axis == 1:
self._zperiod(mask)
self._get_rigid_bc(self.nz, axis=axis)
else:
raise ValueError('axis must be 0 or 1')
def _xperiod(self, mask):
left = _np.argwhere(mask[0, :] != 0).ravel()
right = _np.argwhere(mask[-1, :] != 0).ravel()
left_rigid, right_rigid = self._periodic_bounds(left, right)
for sub in [s for s in self if s.ix[0] == 0]:
self._periodic_update(sub, left_rigid)
for sub in [s for s in self if s.ix[1] == self.nx-1]:
self._periodic_update(sub, right_rigid)
def _zperiod(self, mask):
bot = _np.argwhere(mask[:, 0] != 0).ravel()
top = _np.argwhere(mask[:, -1] != 0).ravel()
bot_rigid, top_rigid = self._periodic_bounds(bot, top)
for sub in [s for s in self if s.iz[0] == 0]:
self._periodic_update(sub, bot_rigid)
for sub in [s for s in self if s.iz[1] == self.nz-1]:
self._periodic_update(sub, top_rigid)
@staticmethod
def _periodic_bounds(bot, top):
bot_rigid = list(set(top).difference(set(bot)))
top_rigid = list(set(bot).difference(set(top)))
if any(bot_rigid):
bot_rigid = _utils.split_discontinuous(bot_rigid)
else:
bot_rigid = []
if any(top_rigid):
top_rigid = _utils.split_discontinuous(top_rigid)
else:
top_rigid = []
return bot_rigid, top_rigid
def _periodic_update(self, sub, bounds):
if sub.axis == 0:
r = getattr(sub, 'rz')
axis = 0
elif sub.axis == 1:
r = getattr(sub, 'rx')
axis = 1
nbc = sub.bc.replace('P', 'W')
for bound in bounds:
if set(range(bound[0], bound[1]+1)).issuperset(set(r)):
# print(f'Process {sub.key} : update bc')
self[sub.key].bc = nbc
elif set(range(bound[0], bound[1]+1)).issubset(set(r)):
# print(f'Process {sub.key} : split subdomain')
self.split(sub.key, bound, nbc=nbc, axis=axis)
def _get_rigid_bc(self, N, axis=None):
""" Returns a list of rigid boundary conditions. """
self.additional_rigid_bc = []
for sub in self:
if sub.xz[axis] == 0 and sub.bc[axis] == 'W':
if axis == 0:
self.additional_rigid_bc.append((0, sub.sz))
elif axis == 1:
self.additional_rigid_bc.append((sub.sx, 0))
elif sub.xz[axis+2] == N-1 and sub.bc[axis+2] == 'W':
if axis == 0:
self.additional_rigid_bc.append((-1, sub.sz))
elif axis == 1:
self.additional_rigid_bc.append((sub.sx, -1))
@property
def _key(self):
self._n = 1
while str(self._n) in self._data.keys():
self._n += 1
return str(self._n)
@property
def xz(self):
""" Return list of coordinates. """
if self._data:
return [s.xz for s in self]
return None
@property
def sx(self):
""" Return list of slices (over Subdomain x coordinates). """
if self._data:
return [s.sx for s in self]
return None
@property
def sz(self):
""" Return list of slices (over Subdomain z coordinates). """
if self._data:
return [s.sz for s in self]
return None
@property
def rx(self):
""" Return list of ranges (over Subdomain x coordinates). """
if self._data:
return [s.rx for s in self]
return None
@property
def rz(self):
""" Return list of ranges (over Subdomain z coordinates). """
if self._data:
return [s.rz for s in self]
return None
@property
def ix(self):
""" Return list of x coordinates. """
if self._data:
return [s.ix for s in self]
return None
@property
def iz(self):
""" Return list of z coordinates. """
if self._data:
return [s.iz for s in self]
return None
def _check_key(self, n):
""" Check if key n exists. """
if str(n) not in self._data.keys():
raise KeyError('Sudbomain not found')
def __setitem__(self, n, val):
if isinstance(val, (Subdomain, tuple, list)):
self.pop(str(n))
self.append(val, n)
else:
msg = 'Can only assign Subdomain object or sequence of Subdomain objects'
raise ValueError(msg)
def __getitem__(self, n):
self._check_key(n)
return self._data[str(n)]
def __iter__(self):
return iter(self._data.values())
def __add__(self, other):
if not isinstance(other, Domain):
raise TypeError('Can only concatenate Domain objects')
if set(self._data.keys()).intersection(set(other.keys())):
raise ValueError('Duplicate subdomains')
else:
tmp = Domain(self.shape)
for sub in self:
tmp.append(sub)
for sub in other:
tmp.append(sub)
return tmp
def __radd__(self, other):
return self.__add__(other)
def __len__(self):
return len(self._data)
def __str__(self):
s = ''
for sub in self:
s += '\t' + sub.__str__() + '\n'
return s
def __repr__(self):
return self.__str__()
[docs]@_dataclasses.dataclass
class Subdomain:
""" Subdivision of the computation domain.
Parameters
----------
xz : list
Coordinates of the Subdomain : left, bottom, right, top
bc : str
Boundary conditions. Must be a string of 4 characters among 'A|W|Z|P|R'
key : int, optional
Key of the subdomain.
axis : {0, 1}, optional
The direction of the subdomain if relevant.
tag : str, optional
The type of Subdomain.
"""
xz: tuple
bc: str = '....'
key: str = ''
axis: int = -1
tag: str = ''
def __post_init__(self):
self.ix = (self.xz[0], self.xz[2])
self.iz = (self.xz[1], self.xz[3])
self.rx = range(self.xz[0], self.xz[2]+1)
self.rz = range(self.xz[1], self.xz[3]+1)
self.sx = slice(self.xz[0], self.xz[2]+1)
self.sz = slice(self.xz[1], self.xz[3]+1)
@property
def axname(self):
""" Returns "x" or "z" whether axis is 0 or 1. Return 2 if both. """
if self.axis == 0:
return 'x'
if self.axis == 1:
return 'z'
if self.axis == 2:
return 'xz'
return None
@property
def center(self):
""" Returns the center of the Subdomain. """
return [int((self.ix[1]+self.ix[0])/2), int((self.iz[1]+self.iz[0])/2)]
[docs] def intersection(self, other):
""" Return common border with another object. """
edges = self._which_edge(other)
if edges:
if (0, 2) in edges:
return self.xz[0], slice(self.xz[1]+1, self.xz[3])
if (2, 0) in edges:
return self.xz[2], slice(self.xz[1]+1, self.xz[3])
if (1, 3) in edges:
return slice(self.xz[0]+1, self.xz[2]), self.xz[1]
if (3, 1) in edges:
return slice(self.xz[0]+1, self.xz[2]), self.xz[3]
return None
[docs] def touch(self, other):
""" Return borders that touch the domain. """
edges = self._which_edge(other)
coords = []
if edges:
for c in edges:
if c == (0, 0):
coords.append((self.xz[0], slice(self.xz[1]+1, self.xz[3])))
elif c == (1, 1):
coords.append((slice(self.xz[0]+1, self.xz[2]), self.xz[1]))
elif c == (2, 2):
coords.append((self.xz[2], slice(self.xz[1]+1, self.xz[3])))
elif c == (3, 3):
coords.append((slice(self.xz[0]+1, self.xz[2]), self.xz[3]))
return coords
def _which_edge(self, other):
""" Search for common edges. """
edges = []
if self.axis == 0 or other.axis == 0:
indexes = [(0, 2, 1, 3)]
elif self.axis == 1 or other.axis == 1:
indexes = [(1, 3, 0, 2)]
else:
indexes = [(0, 2, 1, 3), (1, 3, 0, 2)]
for a, b, c, d in indexes:
for i, j in _itertools.product([a, b], repeat=2):
c1 = other.xz[j] - 1 <= self.xz[i] <= other.xz[j] + 1
c2 = self.xz[c] >= other.xz[c] and self.xz[d] <= other.xz[d]
if c1 and c2:
edges.append((i, j))
return edges
def __matmul__(self, other):
""" Specify boundary if common edges with "other". """
edges = self._which_edge(other)
bc_lst = list(self.bc)
if not bc_lst:
bc_lst = 4*['.']
for edge in edges:
if other.bc[edge[1]] != '.':
bc_lst[edge[0]] = other.bc[edge[1]]
else:
if other.axis:
bc_lst[edge[0]] = 'X'
self.bc = ''.join(bc_lst)
return self.bc
[docs] def split(self, index, nbc=None, axis=None):
"""
Split the subdomain into several new subdomains.
If index is integer, split into two Subdomain objects at 'index'.
If index is tuple/list (len(index) must be 2), split into 3 (or less)
Subdomain objects.
Parameters
----------
index : {int, tuple, list}
Where to split the subdomain
nbc : str
Only if index is a sequence. New bc for the domain defined by 'index'
axis : {0, 1}
Along which axis to split
"""
subkey = (i for i in range(1, 4))
xz = []
if axis is None:
axis = self.axis
bounds = self._split_indexes(index, axis)
for bound in bounds:
if axis == 0:
xz.append([self.ix[0], bound[0], self.ix[1], bound[1]])
elif axis == 1:
xz.append([bound[0], self.iz[0], bound[1], self.iz[1]])
else:
raise ValueError('axis must be 0 or 1')
bc = self._split_bc(xz, index, nbc, axis)
tag = self._split_tag(xz)
return [Subdomain(xz[i], bc[i], key=f'{self.key}.{next(subkey)}',
axis=self.axis, tag=tag[i]) for i in range(len(xz))]
def _split_indexes(self, index, axis):
if axis == 0:
r = getattr(self, 'rz')
c = getattr(self, 'iz')
elif axis == 1:
r = getattr(self, 'rx')
c = getattr(self, 'ix')
else:
raise ValueError('axis must be 0 or 1')
if isinstance(index, int):
if c[1] == index:
return [(c[0], c[1]), (c[1], c[1])]
if c[0] <= index < c[1]:
return [(c[0], index), (index+1, c[1])]
raise ValueError('index must be in the subdomain')
elif isinstance(index, (tuple, list)):
if len(index) == 2:
index = tuple(sorted(list(index)))
index_set = set(range(index[0], index[1]+1))
domain_set = set(r)
if index_set.issubset(domain_set):
remaining = list(domain_set.difference(index_set))
lst = _utils.split_discontinuous(remaining)
lst.append(index)
lst.sort()
return lst
raise ValueError('indexes must be in the domain')
else:
raise ValueError('index must be of length 2')
else:
raise ValueError('index must be int, list, or tuple')
def _split_bc(self, xz, index, nbc, axis):
""" Determine bc when splitting Subdomain. """
if axis == 0:
cid = 1
elif axis == 1:
cid = 0
if nbc and len(xz) > 1:
bc = [self.bc]*len(xz)
for idx, coord in enumerate(xz):
if set(range(coord[cid], coord[cid+2])).issubset(set(range(*index))):
bc = [self.bc]*len(xz)
bc[idx] = nbc
elif axis != self.axis and self.axis == 0:
bc = ['X.X.']*len(xz)
bc[0] = f'{self.bc[0]}.X.'
bc[-1] = f'X.{self.bc[2]}.'
elif axis != self.axis and self.axis == 1:
bc = ['.X.X']*len(xz)
bc[0] = f'.{self.bc[1]}.X'
bc[-1] = f'.X.{self.bc[3]}'
else:
bc = [self.bc]*len(xz)
return bc
def _split_tag(self, xz):
""" Set right tags to each subdomain. """
tags = [self.tag]*len(xz)
for i, c in enumerate(xz):
if c[0] == c[2] or c[1] == c[3]:
tags[i] = 'W'
return tags
@_dataclasses.dataclass
class Edges:
""" Edges description for moving boundaries. """
func: str = None
wav: str = None
f0: float = 0
phi: float = 0
pn: _np.ndarray = 0 # profile of the wall
vn: _np.ndarray = 0 # time evolution of the wall
vt: _np.ndarray = 0 # time evolution of the wall
sx: slice = 0
sz: slice = 0
axis: int = 0
[docs]class Obstacle(Subdomain):
""" Obstacle object.
Parameters
----------
xz : Coordinates of the Subdomain : left, bottom, right, top
bc : Boundary conditions. Must be a string of 4 characters among 'W|Z|V'
key : Key of the subdomain. Optional
axis : 0 or 1. The direction of the subdomain if relevant. Optional.
tag : str. The type of Subdomain. Optional
"""
def __post_init__(self):
super().__post_init__()
self.edges = []
[docs] def set_moving_bc(self, *args):
"""
Set parameters for moving bc. For each moving bc of the obstacle,
you can specify the parameters as a dictionnary containing the
following optional keys :
Parameters
----------
f : float, str
frequency of the normal velocity.
A : float
Amplitude of the normal velocity
phi : float
Phase of the normal velocity
profile : {'sine', 'tukey'}
Function for normal velocity profile construction
kwargs : dict
optional arguments for profile
Note
----
`profile` argument can be:
* 'sine' : sinus profile. kw: n - wavelength (default: 2 for half period)
* 'tukey' : tapered cosine profile. kw: alpha - edge width (default: 0)
Examples
--------
>>> obs1 = Obstacle([10, 10, 100, 100], 'VVWV')
>>> obs1.set_moving_bc({'f': 100, 'A': 2.1, 'profile': 'sine'},
{'f': 500, 'A': -1.5, 'profile': 'tukey'},
{'f': 500, 'A': 1.5 'profile': 'tukey', 'kwargs':{'alpha': 0.4}})
"""
args = iter(list(args) + (4-len(args))*[{}])
self.setup = [{} if b == 'W' else next(args) for b in self.bc]
def _parse_bc_setup(self, setup):
prof = setup.get('profile', 'tukey')
prof = getattr(self, prof, self.tukey)
wav = setup.get('wav', None)
func = setup.get('func', None)
f = setup.get('f', setup.get('f', 0))
phi = setup.get('phi', setup.get('phi', 0))
A = setup.get('A', setup.get('A', 0))
kwargs = setup.get('kwargs', {})
tag = True if wav or func or f else False
return tag, prof, f, phi, A, kwargs, wav, func
[docs] def make_moving_bc(self, x, z, LOG=False):
""" Construct moving boundaries. """
if hasattr(self, 'setup'):
for i in range(4):
if len(x.shape) == 2:
self._make_moving_bc_curvilinear(i, x, z)
else:
self._make_moving_bc_cartesian(i, x, z)
if LOG:
for e in self.edges:
_, ax = _plt.subplots(1, 2)
ax[0].plot(e.pn)
ax[0].grid()
ax[0].set_title('Numerical domain')
if e.axis == 0:
ax[1].plot(z[e.sx, e.sz], e.pn)
if e.axis == 1:
ax[1].plot(x[e.sx, e.sz], e.pn)
ax[1].set_title('Physical domain')
ax[1].grid()
_plt.tight_layout()
def _make_moving_bc_cartesian(self, i, x, z):
tag, prof, f, phi, A, kw, wav, func = self._parse_bc_setup(self.setup[i])
d = {'f0': f, 'phi': phi, 'axis': i % 2, 'wav': wav, 'func': func}
if i % 2 == 0: # following x
d['sx'] = self.xz[i]
d['sz'] = self.sz
if prof and tag:
d['pn'] = A*prof(z, self.sz, **kw)
else: # following z
d['sx'] = self.sx
d['sz'] = self.xz[i]
if prof and tag:
d['pn'] = A*prof(x, self.sx, **kw)
if prof and tag:
self.edges.append(Edges(**d))
def _make_moving_bc_curvilinear(self, i, x, z):
tag, prof, f, phi, A, kw, wav, func = self._parse_bc_setup(self.setup[i])
d = {'f0': f, 'phi': phi, 'axis': i % 2, 'wav': wav, 'func': func}
if i % 2 == 0: # following x
d['sx'] = self.xz[i]
d['sz'] = self.sz
if prof and tag:
d['pn'] = A*prof(z[self.xz[i], :], self.sz, **kw)
else: # following z
d['sx'] = self.sx
d['sz'] = self.xz[i]
if prof and tag:
d['pn'] = A*prof(x[:, self.xz[i]], self.sx, **kw)
if prof and tag:
self.edges.append(Edges(**d))
[docs] @staticmethod
def sine(c, sc, **kwargs):
""" Sine profile. """
L = c[sc.stop-1] - c[sc.start]
n = kwargs.get('n', 2)
kL = 2*_np.pi/(n*L)
return _np.sin(kL*(c[sc] - c[sc.start]))
[docs] @staticmethod
def tukey(_, sc, **kwargs):
""" Tapered cosine profile. """
N = sc.stop - sc.start
alpha = kwargs.get('alpha', 0)
return _sps.tukey(N, alpha=alpha)