# External modules
import numpy as np
from scipy.sparse import linalg
# Local modules
from . import libspline
from .pyCurve import Curve
from .pySurface import Surface
from .utils import Error, _assembleMatrix, checkInput, closeTecplot, openTecplot, writeTecplot3D
[docs]
class Volume(object):
"""
Create an instance of a b-spline surface. There are two
ways to initialize the class
* **Creation**: Create an instance of the Volume class directly by
supplying the required information: kwargs MUST contain the
following information: ``ku, kv, kw, tu, tv, tw, coef``.
* **LMS/Interpolation**: Create an instance of the Volume class by
using an interpolating spline to given data points or a LMS
spline. The following keyword argument information is required:
1. ``ku`` and ``kv`` and ``kw`` Spline Orders
2. ``X`` real array size (Nu, Nv, Nw, nDim) of data to fit. **OR**
1. ``x`` (3D) and ``y`` (3D) ``z`` (3D) 3D volume interpolation/fitting
3. ``u``, ``v``, ``w`` real array of size (Nu, Nv, Nw). Optional
4. ``nCtlu``, ``nCtlv``, ``nCtlw``. Specify number of control points. Only for
LMS fitting.
Parameters
----------
ku : int
Spline order in u
kv : int
Spline order in v
kw : int
Spline order in w
nCtlu : int
Number of control points in u
nCtlv : int
Number of control points in v
nCtlw : int
Number of control points in w
coef : array, size (nCtlu, nCtl, nDim)
b-spline coefficient array.
tu : array, size(nCtlu + ku)
knot array in u
tv : array, size(nCtlv + kv)
knot array in v
tw : array, size(nCtlw + kw)
knot array in w
X : array, size (Nu, Nv, Nw, ndim)
Full data array to fit
x : array, size (Nu, Nv)
Just x data to fit/interpolate
y : array, size (Nu, Nv, Nw)
Just y data to fit/interpolate
z : array, size (Nu, Nv, Nw)
Just z data to fit/interpolate
u : array, size (Nu, Nv, Nw)
Explicit u parameters to use. Optional.
v : array, size (Nu, Nv, Nw)
Explicit v parameters to use. Optional.
w : array, size (Nu, Nv, Nw)
Explicit w parameters to use. Optional.
nIter : int
Number of Hoscheks parameter corrections to run
recompute : bool
Specifies whether the actual fitting is completed.
Notes
-----
The orientation of the nodes, edges and faces for the volumes is
given below::
NODES | EDGES | FACES
6 7| 5 |
#-----------#| #------------# | #-----------#
/ / | /| /| | /| /|
/ / | / | / | | / | / |
/ / | 6/ | 7/ | | / | 1 / |
/ / | / |10 / |11| / | ---------- 5
/ / | / | 4 / | | / | / |(back)
#-----------# | #------------# | | #-----------# |
4 5 | | | | | | | | | |
| | | | | | | | | | <-3
2 3| | | 1 | | |2-> | | | |
#-----------#| | #------|-----# | | #-----|-----#
/ / | |8 / |9 / |4 ---------- | /
/ / | | / | / | | / | /
/ / | | /2 | /3 | | / 0 | /
/ / | | / | / | | / | /
/ / | |/ |/ | |/ |/
#-----------# | #------------# | #-----------#
0 1 | 0 |
"""
def __init__(self, recompute=True, **kwargs):
self.faceSurfaces = [None, None, None, None, None, None]
self.edgeCurves = [None, None, None, None, None, None, None, None, None, None, None, None]
self.data = None
self.udata = None
self.vdata = None
self.wdata = None
if (
"ku" in kwargs
and "kv" in kwargs
and "kw" in kwargs
and "tu" in kwargs
and "tv" in kwargs
and "tw" in kwargs
and "coef" in kwargs
):
self.X = None
self.u = None
self.v = None
self.w = None
self.U = None
self.V = None
self.W = None
self.interp = False
self.ku = checkInput(kwargs["ku"], "ku", int, 0)
self.kv = checkInput(kwargs["kv"], "kv", int, 0)
self.kw = checkInput(kwargs["kw"], "kw", int, 0)
self.coef = checkInput(kwargs["coef"], "coef", float, 4)
self.nCtlu = self.coef.shape[0]
self.nCtlv = self.coef.shape[1]
self.nCtlw = self.coef.shape[2]
self.nDim = self.coef.shape[3]
self.tu = checkInput(kwargs["tu"], "tu", float, 1, (self.nCtlu + self.ku,))
self.tv = checkInput(kwargs["tv"], "tv", float, 1, (self.nCtlv + self.kv,))
self.tw = checkInput(kwargs["tw"], "tw", float, 1, (self.nCtlw + self.kw,))
self.umin = self.tu[0]
self.umax = self.tu[-1]
self.vmin = self.tv[0]
self.vmax = self.tv[-1]
self.wmin = self.tw[0]
self.wmax = self.tw[-1]
self.origData = False
self.setFaceSurfaces()
self.setEdgeCurves()
else: # We have LMS/Interpolate
# Do some checking on the number of control points
if not (
"ku" in kwargs
and "kv" in kwargs
and "kw" in kwargs
and (
"X" in kwargs
or "x" in kwargs
or ("x" in kwargs and "y" in kwargs)
or ("x" in kwargs and "y" in kwargs and "z" in kwargs)
)
):
raise ValueError(
"ku, kv, and X (or x, or x and y, or x and y and z MUST be defined for task lms or interpolate"
)
if "X" in kwargs:
self.X = np.array(kwargs["X"])
if len(self.X.shape) == 1:
self.nDim = 1
else:
self.nDim = self.X.shape[3]
elif "x" in kwargs and "y" in kwargs and "z" in kwargs:
x = checkInput(kwargs["x"], "x", float, 3)
y = checkInput(kwargs["y"], "y", float, 3)
z = checkInput(kwargs["z"], "z", float, 3)
self.X = np.zeros((x.shape[0], x.shape[1], x.shape[2], 3))
self.X[:, :, :, 0] = x
self.X[:, :, :, 1] = y
self.X[:, :, :, 2] = z
self.nDim = 3
elif "x" in kwargs and "y" in kwargs:
x = checkInput(kwargs["x"], "x", float, 3)
y = checkInput(kwargs["y"], "y", float, 3)
self.X = np.zeros((x.shape[0], x.shape[1], x.shape[3], 3))
self.X[:, :, :, 0] = x
self.X[:, :, :, 1] = y
self.nDim = 2
elif "x" in kwargs:
x = checkInput(kwargs["x"], "x", float, 3)
self.X = np.zeros((x.shape[0], x.shape[1], x.shape[3], 3))
self.X[:, :, :, 0] = kwargs["x"]
self.nDim = 1
# enf if
self.Nu = self.X.shape[0]
self.Nv = self.X.shape[1]
self.Nw = self.X.shape[2]
self.ku = checkInput(kwargs["ku"], "ku", int, 0)
self.kv = checkInput(kwargs["kv"], "kv", int, 0)
self.kw = checkInput(kwargs["kw"], "kw", int, 0)
if "nCtlu" in kwargs and "nCtlv" in kwargs and "nCtlw" in kwargs:
self.nCtlu = checkInput(kwargs["nCtlu"], "nCtlu", int, 0)
self.nCtlv = checkInput(kwargs["nCtlv"], "nCtlv", int, 0)
self.nCtlw = checkInput(kwargs["nCtlw"], "nCtlw", int, 0)
self.interp = False
else:
self.nCtlu = self.Nu
self.nCtlv = self.Nv
self.nCtlw = self.Nw
self.interp = True
self.origData = True
# Sanity Check on Inputs
if self.nCtlu >= self.Nu:
self.nCtlu = self.Nu
if self.nCtlv >= self.Nv:
self.nCtlv = self.Nv
if self.nCtlw >= self.Nw:
self.nCtlw = self.Nw
# Sanity check to make sure k is less than N
if self.Nu < self.ku:
self.ku = self.Nu
if self.Nv < self.kv:
self.kv = self.Nv
if self.Nw < self.kw:
self.kw = self.Nw
if self.nCtlu < self.ku:
self.ku = self.nCtlu
if self.nCtlv < self.kv:
self.kv = self.nCtlv
if self.nCtlw < self.kw:
self.kw = self.nCtlw
if "nIter" in kwargs:
self.nIter = kwargs["nIter"]
else:
self.nIter = 1
if "u" in kwargs and "v" in kwargs and "w" in kwargs:
self.u = checkInput(kwargs["u"], "u", float, 1)
self.v = checkInput(kwargs["v"], "v", float, 1)
self.w = checkInput(kwargs["w"], "w", float, 1)
else:
if self.nDim == 3:
self.calcParameterization()
else:
Error(
"Automatic parameterization of ONLY available for spatial data in 3 dimensions. "
+ "Please supply u and v key word arguments otherwise."
)
self.umin = 0
self.umax = 1
self.vmin = 0
self.vmax = 1
self.wmin = 0
self.wmax = 1
self.calcKnots()
self.setCoefSize()
if recompute:
self.recompute()
# end if (Interpolation type)
[docs]
def recompute(self):
"""Recompute the volume if any driving data has been modified"""
self.setCoefSize()
vals, rowPtr, colInd = libspline.volume_jacobian_wrap(
self.U,
self.V,
self.W,
self.tu,
self.tv,
self.tw,
self.ku,
self.kv,
self.kw,
self.nCtlu,
self.nCtlv,
self.nCtlw,
)
N = _assembleMatrix(vals, colInd, rowPtr, (self.Nu * self.Nv * self.Nw, self.nCtlu * self.nCtlv * self.nCtlw))
if self.interp:
# Factorize once for efficiency
solve = linalg.factorized(N)
for idim in range(self.nDim):
self.coef[:, :, :, idim] = solve(self.X[:, :, :, idim].flatten()).reshape(
[self.nCtlu, self.nCtlv, self.nCtlw]
)
else:
solve = linalg.factorized(N.transpose() * N)
for idim in range(self.nDim):
rhs = N.transpose() * self.X[:, :, :, idim].flatten()
self.coef[:, :, :, idim] = solve(rhs).reshape([self.nCtlu, self.nCtlv, self.nCtlw])
self.setFaceSurfaces()
self.setEdgeCurves()
def setCoefSize(self):
self.coef = np.zeros((self.nCtlu, self.nCtlv, self.nCtlw, self.nDim))
[docs]
def calcParameterization(self):
"""Compute distance based parametrization. Use the fortran
function for this"""
S, u, v, w = libspline.para3d(self.X.T)
S = S.T
self.u = u
self.v = v
self.w = w
self.U = np.asarray(S[:, :, :, 0], order="c")
self.V = np.asarray(S[:, :, :, 1], order="c")
self.W = np.asarray(S[:, :, :, 2], order="c")
return
[docs]
def calcKnots(self):
"""Determine the knots depending on if it is interpolated or
an LMS fit"""
if self.interp:
self.tu = libspline.knots_interp(self.u, np.array([], "d"), self.ku)
self.tv = libspline.knots_interp(self.v, np.array([], "d"), self.kv)
self.tw = libspline.knots_interp(self.w, np.array([], "d"), self.kw)
else:
self.tu = libspline.knots_lms(self.u, self.nCtlu, self.ku)
self.tv = libspline.knots_lms(self.v, self.nCtlv, self.kv)
self.tw = libspline.knots_lms(self.w, self.nCtlw, self.kw)
[docs]
def getValueCorner(self, corner):
"""Get the value of the volume spline on the corner.
Parameters
----------
corner : int
Index of corner, 0<=corner<=7
Returns
-------
value : float
Volume spline evaluation at corner.
"""
if corner not in range(0, 8):
raise Error("Corner must be in range 0..7 inclusive8")
if corner == 0:
val = self.getValue(self.umin, self.vmin, self.wmin)
elif corner == 1:
val = self.getValue(self.umax, self.vmin, self.wmin)
elif corner == 2:
val = self.getValue(self.umin, self.vmax, self.wmin)
elif corner == 3:
val = self.getValue(self.umax, self.vmax, self.wmin)
elif corner == 4:
val = self.getValue(self.umin, self.vmin, self.wmax)
elif corner == 5:
val = self.getValue(self.umax, self.vmin, self.wmax)
elif corner == 6:
val = self.getValue(self.umin, self.vmax, self.wmax)
elif corner == 7:
val = self.getValue(self.umax, self.vmax, self.wmax)
return val
[docs]
def getOrigValueCorner(self, corner):
"""Get the value of the original spline data on the corner if
it exists
Parameters
----------
corner : int
Index of corner, 0<=corner<=7
Returns
-------
value : float
Original data on corner.
"""
if corner not in range(0, 8):
raise Error("Corner must be in range 0..7 inclusive")
if corner == 0:
val = self.X[0, 0, 0]
elif corner == 1:
val = self.X[-1, 0, 0]
elif corner == 2:
val = self.X[0, -1, 0]
elif corner == 3:
val = self.X[-1, -1, 0]
elif corner == 4:
val = self.X[0, 0, -1]
elif corner == 5:
val = self.X[-1, 0, -1]
elif corner == 6:
val = self.X[0, -1, -1]
elif corner == 7:
val = self.X[-1, -1, -1]
return val
[docs]
def getOrigValuesFace(self, face):
"""For a given face index, face, return the 4 corners and the
values of the midpoints of the 4 edges on that face.
Parameters
----------
face : int
Index of face, 0<=face<=5
Returns
-------
coords : array of size (8, ndim)
The first 4 entries are the corner, and the last 4 are the
midpoints.
"""
if face not in range(0, 6):
raise Error("Face must be in range 0..5 inclusive")
if np.mod(self.Nu, 2) == 1:
midu = [(self.Nu - 1) // 2, (self.Nu - 1) // 2]
else:
midu = [self.Nu // 2, self.Nu // 2 - 1]
if np.mod(self.Nv, 2) == 1:
midv = [(self.Nv - 1) // 2, (self.Nv - 1) // 2]
else:
midv = [self.Nv // 2, self.Nv // 2 - 1]
if np.mod(self.Nw, 2) == 1:
midw = [(self.Nw - 1) // 2, (self.Nw - 1) // 2]
else:
midw = [self.Nw // 2, self.Nw // 2 - 1]
if face == 0:
values = [
self.X[0, 0, 0],
self.X[-1, 0, 0],
self.X[0, -1, 0],
self.X[-1, -1, 0],
0.5 * (self.X[midu[0], 0, 0] + self.X[midu[1], 0, 0]),
0.5 * (self.X[midu[0], -1, 0] + self.X[midu[1], -1, 0]),
0.5 * (self.X[0, midv[0], 0] + self.X[0, midv[1], 0]),
0.5 * (self.X[-1, midv[0], 0] + self.X[-1, midv[1], 0]),
]
elif face == 1:
values = [
self.X[0, 0, -1],
self.X[-1, 0, -1],
self.X[0, -1, -1],
self.X[-1, -1, -1],
0.5 * (self.X[midu[0], 0, -1] + self.X[midu[1], 0, -1]),
0.5 * (self.X[midu[0], -1, -1] + self.X[midu[1], -1, -1]),
0.5 * (self.X[0, midv[0], -1] + self.X[0, midv[1], -1]),
0.5 * (self.X[-1, midv[0], -1] + self.X[-1, midv[1], -1]),
]
elif face == 2:
values = [
self.X[0, 0, 0],
self.X[0, -1, 0],
self.X[0, 0, -1],
self.X[0, -1, -1],
0.5 * (self.X[0, midv[0], 0] + self.X[0, midv[1], 0]),
0.5 * (self.X[0, midv[0], -1] + self.X[0, midv[1], -1]),
0.5 * (self.X[0, 0, midw[0]] + self.X[0, 0, midw[1]]),
0.5 * (self.X[0, -1, midw[0]] + self.X[0, -1, midw[1]]),
]
elif face == 3:
values = [
self.X[-1, 0, 0],
self.X[-1, -1, 0],
self.X[-1, 0, -1],
self.X[-1, -1, -1],
0.5 * (self.X[-1, midv[0], 0] + self.X[-1, midv[1], 0]),
0.5 * (self.X[-1, midv[0], -1] + self.X[-1, midv[1], -1]),
0.5 * (self.X[-1, 0, midw[0]] + self.X[-1, 0, midw[1]]),
0.5 * (self.X[-1, -1, midw[0]] + self.X[-1, -1, midw[1]]),
]
elif face == 4:
values = [
self.X[0, 0, 0],
self.X[-1, 0, 0],
self.X[0, 0, -1],
self.X[-1, 0, -1],
0.5 * (self.X[midu[0], 0, 0] + self.X[midu[1], 0, 0]),
0.5 * (self.X[midu[0], 0, -1] + self.X[midu[1], 0, -1]),
0.5 * (self.X[0, 0, midw[0]] + self.X[0, 0, midw[1]]),
0.5 * (self.X[-1, 0, midw[0]] + self.X[-1, 0, midw[1]]),
]
elif face == 5:
values = [
self.X[0, -1, 0],
self.X[-1, -1, 0],
self.X[0, -1, -1],
self.X[-1, -1, -1],
0.5 * (self.X[midu[0], -1, 0] + self.X[midu[1], -1, 0]),
0.5 * (self.X[midu[0], -1, -1] + self.X[midu[1], -1, -1]),
0.5 * (self.X[0, -1, midw[0]] + self.X[0, -1, midw[1]]),
0.5 * (self.X[-1, -1, midw[0]] + self.X[-1, -1, midw[1]]),
]
return np.array(values)
[docs]
def getMidPointEdge(self, edge):
"""Get the midpoint of the edge using the original data.
Parameters
----------
edge : int
Edge index. Must be 0<edge<11.
Returns
-------
midpoint : array of length nDim
Mid point of edge
"""
if np.mod(self.Nu, 2) == 1:
midu = [(self.Nu - 1) // 2, (self.Nu - 1) // 2]
else:
midu = [self.Nu // 2, self.Nu // 2 - 1]
if np.mod(self.Nv, 2) == 1:
midv = [(self.Nv - 1) // 2, (self.Nv - 1) // 2]
else:
midv = [self.Nv // 2, self.Nv // 2 - 1]
if np.mod(self.Nw, 2) == 1:
midw = [(self.Nw - 1) // 2, (self.Nw - 1) // 2]
else:
midw = [self.Nw // 2, self.Nw // 2 - 1]
if edge == 0:
val = self.X[midu[0], 0, 0] + self.X[midu[1], 0, 0]
elif edge == 1:
val = self.X[midu[0], -1, 0] + self.X[midu[1], -1, 0]
elif edge == 2:
val = self.X[0, midv[0], 0] + self.X[0, midv[1], 0]
elif edge == 3:
val = self.X[-1, midv[0], 0] + self.X[-1, midv[1], 0]
elif edge == 4:
val = self.X[midu[0], 0, -1] + self.X[midu[1], 0, -1]
elif edge == 5:
val = self.X[midu[0], -1, -1] + self.X[midu[1], -1, -1]
elif edge == 6:
val = self.X[0, midv[0], -1] + self.X[0, midv[1], -1]
elif edge == 7:
val = self.X[-1, midv[0], -1] + self.X[-1, midv[1], -1]
elif edge == 8:
val = self.X[0, 0, midw[0]] + self.X[0, 0, midw[1]]
elif edge == 9:
val = self.X[-1, 0, midw[0]] + self.X[-1, 0, midw[1]]
elif edge == 10:
val = self.X[0, -1, midw[0]] + self.X[0, -1, midw[1]]
elif edge == 11:
val = self.X[-1, -1, midw[0]] + self.X[-1, -1, midw[1]]
return val
[docs]
def getMidPointFace(self, face):
"""Get the midpoint of the face using the original data.
Parameters
----------
face : int
Face index. Must be 0, 1, 2, 3, 4 or 5
Returns
-------
midpoint : array of length nDim
Mid point of face
"""
if face not in range(0, 6):
raise Error("Face must be in range 0..5 inclusive")
if not self.origData:
raise Error("No original data for this surface")
if np.mod(self.Nu, 2) == 1:
midu = [(self.Nu - 1) // 2, (self.Nu - 1) // 2]
else:
midu = [self.Nu // 2, self.Nu // 2 - 1]
if np.mod(self.Nv, 2) == 1:
midv = [(self.Nv - 1) // 2, (self.Nv - 1) // 2]
else:
midv = [self.Nv // 2, self.Nv // 2 - 1]
if np.mod(self.Nw, 2) == 1:
midw = [(self.Nw - 1) // 2, (self.Nw - 1) // 2]
else:
midw = [self.Nw // 2, self.Nw // 2 - 1]
if face == 0:
val = 0.25 * (
self.X[midu[0], midv[0], 0]
+ self.X[midu[1], midv[0], 0]
+ self.X[midu[0], midv[1], 0]
+ self.X[midu[1], midv[1], 0]
)
elif face == 1:
val = 0.25 * (
self.X[midu[0], midv[0], -1]
+ self.X[midu[1], midv[0], -1]
+ self.X[midu[0], midv[1], -1]
+ self.X[midu[1], midv[1], -1]
)
elif face == 2:
val = 0.25 * (
self.X[0, midv[0], midw[0]]
+ self.X[0, midv[1], midw[0]]
+ self.X[0, midv[0], midw[1]]
+ self.X[0, midv[1], midw[1]]
)
elif face == 3:
val = 0.25 * (
self.X[-1, midv[0], midw[0]]
+ self.X[-1, midv[1], midw[0]]
+ self.X[-1, midv[0], midw[1]]
+ self.X[-1, midv[1], midw[1]]
)
elif face == 4:
val = 0.25 * (
self.X[midu[0], 0, midw[0]]
+ self.X[midu[1], 0, midw[0]]
+ self.X[midu[0], 0, midw[1]]
+ self.X[midu[1], 0, midw[1]]
)
elif face == 5:
val = 0.25 * (
self.X[midu[0], -1, midw[0]]
+ self.X[midu[1], -1, midw[0]]
+ self.X[midu[0], -1, midw[1]]
+ self.X[midu[1], -1, midw[1]]
)
return val
[docs]
def setFaceSurfaces(self):
"""Create face spline objects for each of the faces"""
self.faceSurfaces[0] = Surface(ku=self.ku, kv=self.kv, tu=self.tu, tv=self.tv, coef=self.coef[:, :, 0, :])
self.faceSurfaces[1] = Surface(ku=self.ku, kv=self.kv, tu=self.tu, tv=self.tv, coef=self.coef[:, :, -1, :])
self.faceSurfaces[2] = Surface(ku=self.ku, kv=self.kw, tu=self.tu, tv=self.tw, coef=self.coef[:, 0, :, :])
self.faceSurfaces[3] = Surface(ku=self.ku, kv=self.kw, tu=self.tu, tv=self.tw, coef=self.coef[:, -1, :, :])
self.faceSurfaces[4] = Surface(ku=self.kv, kv=self.kw, tu=self.tv, tv=self.tw, coef=self.coef[0, :, :, :])
self.faceSurfaces[5] = Surface(ku=self.kv, kv=self.kw, tu=self.tv, tv=self.tw, coef=self.coef[-1, :, :, :])
[docs]
def setEdgeCurves(self):
"""Create edge spline objects for each edge"""
self.edgeCurves[0] = Curve(k=self.ku, t=self.tu, coef=self.coef[:, 0, 0])
self.edgeCurves[1] = Curve(k=self.ku, t=self.tu, coef=self.coef[:, -1, 0])
self.edgeCurves[2] = Curve(k=self.kv, t=self.tv, coef=self.coef[0, :, 0])
self.edgeCurves[3] = Curve(k=self.kv, t=self.tv, coef=self.coef[-1, :, 0])
self.edgeCurves[4] = Curve(k=self.ku, t=self.tu, coef=self.coef[:, 0, -1])
self.edgeCurves[5] = Curve(k=self.ku, t=self.tu, coef=self.coef[:, -1, -1])
self.edgeCurves[6] = Curve(k=self.kv, t=self.tv, coef=self.coef[0, :, -1])
self.edgeCurves[7] = Curve(k=self.kv, t=self.tv, coef=self.coef[-1, :, -1])
self.edgeCurves[8] = Curve(k=self.kw, t=self.tw, coef=self.coef[0, 0, :])
self.edgeCurves[9] = Curve(k=self.kw, t=self.tw, coef=self.coef[-1, 0, :])
self.edgeCurves[10] = Curve(k=self.kw, t=self.tw, coef=self.coef[0, -1, :])
self.edgeCurves[11] = Curve(k=self.kw, t=self.tw, coef=self.coef[-1, -1, :])
[docs]
def getBasisPt(self, u, v, w, vals, istart, colInd, lIndex):
"""This function should only be called from pyBlock The purpose
is to compute the basis function for a u, v, w point and add
it to pyBlocks's global dPt/dCoef
matrix. vals, rowPtr, colInd is the CSR data and lIndex in
the local -> global mapping for this volume"""
return libspline.getbasisptvolume(
u, v, w, self.tu, self.tv, self.tw, self.ku, self.kv, self.kw, vals, colInd, istart, lIndex.T
)
def __call__(self, u, v, w):
"""
Equivalent to getValue()
"""
return self.getValue(u, v, w)
[docs]
def getValue(self, u, v, w):
"""Get the value at the volume points(s) u, v, w. This is the
main evaluation routine for the volume object.
Parameters
----------
u : scalar, vector or matrix or tensor of values
u position
v : scalar, vector or matrix or tensor of values
v position
w : scalar, vector or matrix or tensor of values
w position
Returns
-------
values : scalar, vector, matrix or tensor of values
The spline evaluation at (u, v, w)
"""
u = np.atleast_3d(u).T
v = np.atleast_3d(v).T
w = np.atleast_3d(w).T
if not u.shape == v.shape == w.shape:
raise Error("u and v must have the same shape")
vals = libspline.eval_volume(u, v, w, self.tu, self.tv, self.tw, self.ku, self.kv, self.kw, self.coef.T)
return vals.squeeze().T
[docs]
def getValueEdge(self, edge, s):
"""Get the value at the volume points(s) u, v, w
Parameters
----------
edge : int
Index of edge. Must be between 0 and 11.
s : float or array
Parameter position(s) along edge to evaluate.
Returns
-------
values : array
Array of values evaluated along edge.
"""
if edge == 0:
u = s
v = self.vmin
w = self.wmin
elif edge == 1:
u = s
v = self.vmax
w = self.wmin
elif edge == 2:
u = self.umin
v = s
w = self.wmax
elif edge == 3:
u = self.umax
v = s
w = self.umin
elif edge == 4:
u = s
v = self.vmin
w = self.wmax
elif edge == 5:
u = s
v = self.vmax
w = self.wmax
elif edge == 6:
u = self.umin
v = s
w = self.wmax
elif edge == 7:
u = self.umax
v = s
w = self.wmax
elif edge == 8:
u = self.umin
v = self.vmin
w = s
elif edge == 9:
u = self.umax
v = self.vmin
w = s
elif edge == 10:
u = self.umin
v = self.vmax
w = s
elif edge == 11:
u = self.umax
v = self.vmax
w = s
u = np.atleast_3d(u).T
v = np.atleast_3d(v).T
w = np.atleast_3d(w).T
if not u.shape == v.shape == w.shape:
raise Error("u, v, and w must have the same shape")
vals = libspline.eval_volume(u, v, w, self.tu, self.tv, self.tw, self.ku, self.kv, self.kw, self.coef.T)
return vals.squeeze().T
[docs]
def getBounds(self):
"""Determine the extents of the volume
Returns
-------
xMin : array of length 3
Lower corner of the bounding box
xMax : array of length 3
Upper corner of the bounding box
"""
if self.nDim != 3:
raise Error("getBounds is only defined for nDim = 3")
cx = self.coef[:, :, :, 0].flatten()
cy = self.coef[:, :, :, 1].flatten()
cz = self.coef[:, :, :, 2].flatten()
Xmin = np.zeros(self.nDim)
Xmin[0] = min(cx)
Xmin[1] = min(cy)
Xmin[2] = min(cz)
Xmax = np.zeros(self.nDim)
Xmax[0] = max(cx)
Xmax[1] = max(cy)
Xmax[2] = max(cz)
return Xmin, Xmax
[docs]
def projectPoint(self, x0, nIter=25, eps=1e-10, volBounds=None, **kwargs):
"""
Project a point x0 or points x0 onto the volume and return
parametric positions
Parameters
----------
x0 : array of length 3 or array of size (N, 3)
Points to embed in the volume. If the points do not
**actually** lie in the volume, the closest point is
returned
nIter : int
Maximum number of Newton iterations to perform
eps : float
Tolerance for the Newton iteration
volBounds : list of lists
Optional input to prescribe bounds to the parametric coordinates during the projection.
The value is a list that must contain 3 lists with 2 float entries each.
First list contains the min and max bounds for the u parameter, second v, third w.
E.g. ``volBounds = [[0.0, 0.5], [0.5, 1.0], [0.25, 0.75]]``
u : float or array of len(X0)
Optional initial guess for u position.
v : float or array of len(X0)
Optional initial guess for v position.
w : float or array of len(X0)
Optional initial guess for w position.
Returns
-------
u : float or array of length N
u parametric position of closest point
v : float or array of length N
v parametric position of closest point
w : float or array of length N
w parametric position of closest point
D : float or array of length N
Distance between projected point and closest point
If the points are 'inside' the volume, D should
be less than eps.
"""
x0 = np.atleast_2d(x0)
if "u" in kwargs and "v" in kwargs and "w" in kwargs:
u = np.atleast_1d(kwargs["u"])
v = np.atleast_1d(kwargs["v"])
w = np.atleast_1d(kwargs["w"])
else:
u = -1 * np.ones(len(x0))
v = -1 * np.ones(len(x0))
w = -1 * np.ones(len(x0))
if not len(x0) == len(u) == len(v) == len(w):
raise Error("The length of x0 and u, v, w must be the same")
# get the bounds for the projection if we have custom bounds
if volBounds is None:
umin = self.tu[0]
umax = self.tu[-1]
vmin = self.tv[0]
vmax = self.tv[-1]
wmin = self.tw[0]
wmax = self.tw[-1]
else:
umin = volBounds[0][0]
umax = volBounds[0][1]
vmin = volBounds[1][0]
vmax = volBounds[1][1]
wmin = volBounds[2][0]
wmax = volBounds[2][1]
# If necessary get brute-force starting point
if np.any(u < 0) or np.any(u > 1) or np.any(v < 0) or np.any(v > 1):
self.computeData()
u, v, w = libspline.point_volume_start(
x0.real.T, self.udata, self.vdata, self.wdata, self.data.T, umin, umax, vmin, vmax, wmin, wmax
)
D = np.zeros_like(x0)
for i in range(len(x0)):
u[i], v[i], w[i], D[i] = libspline.point_volume(
x0[i].real,
self.tu,
self.tv,
self.tw,
self.ku,
self.kv,
self.kw,
self.coef.T,
nIter,
eps,
umin,
umax,
vmin,
vmax,
wmin,
wmax,
u[i],
v[i],
w[i],
)
return u.squeeze(), v.squeeze(), w.squeeze(), D.squeeze()
[docs]
def computeData(self, recompute=False):
"""
Compute discrete data that is used for the Tecplot
Visualization as well as the data for doing the brute-force
checks
Parameters
----------
recompute : bool
If True, recompute the data even if it has already been computed.
"""
# Only recompute if it doesn't exist already
if self.data is None or recompute:
self.edgeCurves[0].calcInterpolatedGrevillePoints()
self.udata = self.edgeCurves[0].sdata
self.edgeCurves[2].calcInterpolatedGrevillePoints()
self.vdata = self.edgeCurves[2].sdata
self.edgeCurves[8].calcInterpolatedGrevillePoints()
self.wdata = self.edgeCurves[8].sdata
U = np.zeros((len(self.udata), len(self.vdata), len(self.wdata)))
V = np.zeros((len(self.udata), len(self.vdata), len(self.wdata)))
W = np.zeros((len(self.udata), len(self.vdata), len(self.wdata)))
for i in range(len(self.udata)):
for j in range(len(self.vdata)):
for k in range(len(self.wdata)):
U[i, j, k] = self.udata[i]
V[i, j, k] = self.vdata[j]
W[i, j, k] = self.wdata[k]
self.data = self.getValue(U, V, W)
[docs]
def insertKnot(self, direction, s, r):
"""
Insert a knot into the volume along either u, v, w:
Parameters
----------
direction : str
Parameteric direction to insert. Either 'u', 'v', or 'w'
s : float
Parametric position along 'direction' to insert
r : int
Desired number of times to insert.
Returns
-------
r : int
The **actual** number of times the knot was inserted.
"""
if direction not in ["u", "v", "w"]:
raise Error("Direction must be one of 'u' or 'v' or 'w'")
s = checkInput(s, "s", float, 0)
r = checkInput(r, "r", int, 0)
if s <= 0.0:
return
if s >= 1.0:
return
# This is relatively inefficient, but we'll do it for
# simplicity just call insertknot for each slab
if direction == "u":
# Insert once to know how many times it was actually inserted
# so we know how big to make the new coef:
actualR, tNew, coefNew, breakPt = libspline.insertknot(s, r, self.tu, self.ku, self.coef[:, 0, 0].T)
newCoef = np.zeros((self.nCtlu + actualR, self.nCtlv, self.nCtlw, self.nDim))
for k in range(self.nCtlvw):
for j in range(self.nCtlv):
actualR, tNew, coefSlice, breakPt = libspline.insertknot(
s, r, self.tu, self.ku, self.coef[:, j, k].T
)
newCoef[:, j, k] = coefSlice[:, 0 : self.nCtlu + actualR].T
self.tu = tNew[0 : self.nCtlu + self.ku + actualR]
self.nCtlu = self.nCtlu + actualR
elif direction == "v":
actualR, tNew, coefNew, breakPt = libspline.insertknot(s, r, self.tv, self.kv, self.coef[0, :, 0].T)
newCoef = np.zeros((self.nCtlu, self.nCtlv + actualR, self.nCtlw, self.nDim))
for k in range(self.nCtlw):
for i in range(self.nCtlu):
actualR, tNew, coefSlice, breakPt = libspline.insertknot(
s, r, self.tv, self.kv, self.coef[i, :, k].T
)
newCoef[i, :, k] = coefSlice[:, 0 : self.nCtlv + actualR].T
self.tv = tNew[0 : self.nCtlv + self.kv + actualR]
self.nCtlv = self.nCtlv + actualR
elif direction == "w":
actualR, tNew, coefNew, breakPt = libspline.insertknot(s, r, self.tw, self.kw, self.coef[0, 0, :].T)
newCoef = np.zeros((self.nCtlu, self.nCtlv, self.nCtlw + actualR, self.nDim))
for j in range(self.nCtlv):
for i in range(self.nCtlu):
actualR, tNew, coefSlice, breakPt = libspline.insertknot(
s, r, self.tw, self.kw, self.coef[i, j, :].T
)
newCoef[i, j, :] = coefSlice[:, 0 : self.nCtlw + actualR].T
self.tw = tNew[0 : self.nCtlw + self.kw + actualR]
self.nCtlw = self.nCtlw + actualR
self.coef = newCoef
# break_pt is converted to zero based ordering here!!!
return actualR, breakPt - 1
[docs]
def writeTecplot(self, fileName, vols=True, coef=True, orig=False):
"""Write the volume to a tecplot data file.
Parameters
----------
fileName : str
Tecplot filename. Should end in .dat
vols : bool
Flag specifying whether the interpolated volume should
be used. This is usually True if you want to get an
approximation of the entire volume.
coef : bool
Flag specifying if the control points are to be plotted
orig : bool
Flag specifying if original data (used for fitting) is
to be included. If on original data exists, this argument
is ignored.
"""
f = openTecplot(fileName, self.nDim)
if vols:
self.computeData()
writeTecplot3D(f, "interpolated", self.data)
if coef:
writeTecplot3D(f, "control_pts", self.coef)
if orig and self.origData:
writeTecplot3D(f, "orig_data", self.X)
closeTecplot(f)