Module conduction.solver.conductionNd
Copyright 2017 Ben Mather
This file is part of Conduction https://git.dias.ie/itherc/conduction/
Conduction is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or any later version.
Conduction 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 Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along with Conduction. If not, see http://www.gnu.org/licenses/.
Expand source code
"""
Copyright 2017 Ben Mather
This file is part of Conduction <https://git.dias.ie/itherc/conduction/>
Conduction is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or any later version.
Conduction 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 Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with Conduction. If not, see <http://www.gnu.org/licenses/>.
"""
try: range = xrange
except: pass
import numpy as np
from petsc4py import PETSc
from mpi4py import MPI
comm = MPI.COMM_WORLD
from ..tools import sum_duplicates
from ..mesh import MeshVariable
class ConductionND(object):
"""
Implicit N-dimensional solver for the steady-state heat equation
over a structured grid using PETSc data structures.
Parameters
----------
minCoord : tuple, minimum Cartesian coordinates at edge of domain
maxCoord : tuple, maximum Cartesian coordinates at edge of domain
res : tuple, resolution in each dimension
kwargs : dict, keyword arguments to pass to KSP method and preconditioner
see PETSc documentaion for KSPType and PCType options...
http://www.mcs.anl.gov/petsc/petsc-current/docs/manualpages/KSP/KSPType.html
http://www.mcs.anl.gov/petsc/petsc-current/docs/manualpages/PC/PCType.html
"""
def __init__(self, minCoord, maxCoord, res, **kwargs):
dim = len(res)
extent = np.zeros(dim*2)
index = 0
for i in range(0, dim):
extent[index] = minCoord[i]
extent[index+1] = maxCoord[i]
index += 2
width = kwargs.pop('stencil_width', 1)
dm = PETSc.DMDA().create(dim=dim, sizes=res, stencil_width=width, comm=comm)
dm.setUniformCoordinates(*extent)
self.dm = dm
self.lgmap = dm.getLGMap()
self.lvec = dm.createLocalVector()
self.gvec = dm.createGlobalVector()
# Setup matrix sizes
self.sizes = self.gvec.getSizes(), self.gvec.getSizes()
self.dim = dim
self.extent = extent
# include ghost nodes in local domain
# (minI, maxI), (minJ, maxJ), (minK, maxK) = dm.getGhostRanges()
ghost_ranges = dm.getGhostRanges()
n = np.zeros(dim, dtype=PETSc.IntType)
nn = 1
for i, (gs, ge) in enumerate(ghost_ranges):
n[i] = ge - gs
nn *= n[i]
self.n = n[::-1]
self.nn = nn
self.npoints = nn
# stencil size
self.width = width
self.stencil_width = 2*dim*width + 1
# create closure array
closure = []
for w in range(width, 0, -1):
closure_array = self._get_closure_array(dim, w, width)
closure.extend(closure_array[:-1])
closure.append(closure_array[-1]) # centre node at last
# create closure object
self.closure = self._create_closure_object(closure, width)
# local numbering
self.nodes = np.arange(0, nn, dtype=PETSc.IntType)
# set matrix and vector types
self.MatType = kwargs.pop('MatType', 'aij') # cuda, seqaij, mpiaij, etc.
self.VecType = kwargs.pop('VecType', 'standard')
self._initialise_mesh_variables()
self._initialise_boundary_dictionary()
self.mat = self._initialise_matrix()
self._initialise_COO_vectors(width)
self.ksp = self._initialise_ksp(**kwargs)
# thermal properties
self.diffusivity = MeshVariable('diffusivity', dm)
self.heat_sources = MeshVariable('heat_sources', dm)
self.temperature = MeshVariable('temperature', dm)
# right hand side vector
self.rhs = MeshVariable('rhs', dm)
def __delete__(self):
del self.rhs, self.diffusivity, self.heat_sources, self.temperature
self.mat.destroy()
self.dm.destroy()
self.lvec.destroy()
self.gvec.destroy()
self.lgmap.destroy()
def _initialise_ksp(self, matrix=None, atol=1e-10, rtol=1e-50, **kwargs):
"""
Initialise linear solver object
"""
if matrix is None:
matrix = self.mat
solver = kwargs.pop('solver', 'gmres')
precon = kwargs.pop('pc', None)
ksp = PETSc.KSP().create(comm)
ksp.setType(solver)
ksp.setOperators(matrix)
ksp.setTolerances(atol, rtol)
if precon is not None:
pc = ksp.getPC()
pc.setType(precon)
ksp.setFromOptions()
return ksp
def _initialise_COO_vectors(self, pad=1):
nn = self.nn
n = self.n
self.index = np.pad(self.nodes.reshape(n), pad, 'constant', constant_values=-1)
self.rows = np.empty((self.stencil_width, nn), dtype=PETSc.IntType)
self.cols = np.empty((self.stencil_width, nn), dtype=PETSc.IntType)
self.vals = np.empty((self.stencil_width, nn))
def _initialise_mesh_variables(self):
dim = self.dim
bbox = self.dm.getBoundingBox()
extent = np.zeros(dim*2)
index = 0
for bs, be in bbox:
extent[index] = bs
extent[index+1] = be
index += 2
self.extent = extent
# local coordinates
self.coords = self.dm.getCoordinatesLocal().array.reshape(-1, dim)
grid_coords = [None]*dim
for i in range(0, dim):
grid_coords[i] = np.unique(self.coords[:,i])
self.grid_coords = grid_coords
def _initialise_boundary_dictionary(self):
coords = self.coords
grid_coords = self.grid_coords
dim = self.dim
minCoords = coords.min(axis=0)
maxCoords = coords.max(axis=0)
bbox = self.dm.getBoundingBox()
sizes = self.dm.getSizes()
# Setup boundary dictionary
bc = dict()
wall = [("minX", "maxX"), ("minY", "maxY"), ("minZ", "maxZ")]
for i in range(0, dim):
w0, w1 = wall[i]
c0, c1 = bbox[i]
m0, m1 = self.coords[:,i] == c0, self.coords[:,i] == c1
d0 = d1 = (c1 - c0)/(sizes[i] - 1)
bc[w0] = {"val": 0.0, "delta": d0, "flux": True, "mask": m0}
bc[w1] = {"val": 0.0, "delta": d1, "flux": True, "mask": m1}
self.bc = bc
self.dirichlet_mask = np.zeros(self.nn, dtype=bool)
def _initialise_matrix(self, nnz=None):
"""
There should be no mallocs but we turn off the error just to be sure.
If there is it will be from users adjusting the BCs.
Could push zeros into the matrix to allocate all potential entries
but that would lengthen the build stage.
"""
if nnz is None:
nnz = (self.stencil_width, self.dim*2)
mat = PETSc.Mat().create(comm=comm)
mat.setType(self.MatType)
mat.setSizes(self.sizes)
mat.setLGMap(self.lgmap)
mat.setPreallocationNNZ(nnz)
mat.setOption(PETSc.Mat.Option.NEW_NONZERO_ALLOCATION_ERR, 0)
mat.setFromOptions()
return mat
def _initialise_vector(self, sizes):
vec = PETSc.Vec().create(comm=comm)
vec.setType(self.VecType)
vec.setSizes(self.sizes[0])
vec.setLGMap(self.lgmap)
vec.setFromOptions()
return vec
def _create_closure_object(self, closure, pad=1):
nc = len(closure)
n = self.n
p2 = 2*pad
obj = [[0] * self.dim for i in range(nc)]
for i in range(0, nc):
# construct slicing object
for j in range(0, self.dim):
start, end = closure[i-j]
obj[i][j] = slice(start, n[j]+end+p2)
return obj
def _get_closure_array(self, dim, width=1, pad=1):
w, p = width, pad
if w > p:
raise ValueError('width exceeds padding')
if dim == 1:
closure = [(p-w,-p-w), (p+w,-(p-w)), (p,-p)]
elif dim == 2:
closure = [(p-w,-p-w), (p,-p), (p+w,-(p-w)), (p,-p), (p,-p)]
elif dim == 3:
closure = [(p-w,-p-w), (p,-p), (p,-p), (p+w,-(p-w)), (p,-p), (p,-p), (p,-p)]
else:
raise ValueError('{} is an invalid number of dimensions'.format(dim))
return closure
def refine(self, fn, axis):
"""
Pass a function to apply to the x,y,z coordinates on the mesh.
The domain will be redefined accordingly.
Notes
-----
We do it this way to make sure the domain is balanced across
processors. Adding new nodes would imbalance the matrix.
"""
v = self.dm.getCoordinatesLocal()
coords = v.array.reshape(-1, self.dim)
coords[:,axis] = fn(coords[:,axis])
if not np.isfinite(coords).all():
raise ValueError('This function has created NaNs or Inf numbers')
v.setArray(coords.ravel())
self.dm.setCoordinatesLocal(v)
self._initialise_mesh_variables()
self._initialise_boundary_dictionary()
self.mat = self._initialise_matrix()
def create_meshVariable(self, name):
return MeshVariable(name, self.dm)
def update_properties(self, diffusivity, heat_sources):
"""
Update diffusivity and heat sources
"""
self.diffusivity[:] = diffusivity
self.heat_sources[:] = heat_sources
def boundary_condition(self, wall, val, flux=True):
"""
Set the boundary conditions on each wall of the domain.
By default each wall is a Neumann (flux) condition.
If flux=True, positive val indicates a flux vector towards the centre
of the domain.
val can be a vector with the same number of elements as the wall
"""
wall = str(wall)
if wall in self.bc:
self.bc[wall]["val"] = np.array(val, copy=True)
self.bc[wall]["flux"] = bool(flux)
d = self.bc[wall]
mask = d['mask']
if flux:
self.dirichlet_mask[mask] = False
self.bc[wall]["val"] /= -d['delta']
else:
self.dirichlet_mask[mask] = True
else:
raise ValueError("Wall should be one of {}".format(self.bc.keys()))
def find_neighbours(self, width=1):
"""
Find node neighbours for each point in the domain
Returns a point cloud of neighbours shape(n,nneighbours)
-1 indicates a null value
"""
nodes = self.nodes
dim = self.dim
n = self.n
# setup new stencil
stencil_width = 2*self.dim*width + 1
neighbours = np.empty((stencil_width, self.nn), dtype=PETSc.IntType)
index = np.pad(nodes.reshape(n), width, 'constant', constant_values=-1)
closure = []
for w in range(width, 0, -1):
closure_array = self._get_closure_array(dim, w, width)
closure.extend(closure_array[:-1])
closure.append(closure_array[-1]) # centre node at last
# create closure object
closure = self._create_closure_object(closure, width)
for i in range(0, stencil_width):
obj = closure[i]
neighbours[i] = index[obj].ravel()
return neighbours.T
def construct_matrix(self, in_place=True, derivative=False):
"""
Construct the coefficient matrix
i.e. matrix A in Ax = b
We vectorise the 7-point stencil for fast matrix insertion.
An extra border of dummy values around the domain allows for automatic
Neumann (flux) boundary creation.
These are stomped on if there are any Dirichlet conditions.
"""
if in_place:
mat = self.mat
else:
mat = self._initialise_matrix()
nodes = self.nodes
nn = self.nn
n = self.n
dim = self.dim
index = self.index
rows = self.rows
cols = self.cols
vals = self.vals
dirichlet_mask = self.dirichlet_mask
u = self.diffusivity[:].reshape(n)
k = np.pad(u, self.width, 'constant', constant_values=0)
for i in range(0, self.stencil_width):
obj = tuple(self.closure[i])
rows[i] = nodes
cols[i] = index[obj].ravel()
distance = np.linalg.norm(self.coords[cols[i]] - self.coords, axis=1)
distance[distance==0] = 1e-12 # protect against dividing by zero
delta = 1.0/(2.0*distance**2)
vals[i] = delta*(k[obj] + u).ravel()
# Dirichlet boundary conditions (duplicates are summed)
cols[:,dirichlet_mask] = nodes[dirichlet_mask]
vals[:,dirichlet_mask] = 0.0
# zero off-grid coordinates
vals[cols < 0] = 0.0
# centre point
vals[-1] = 0.0
if derivative:
vals[-1][dirichlet_mask] = 0.
else:
vals[-1][dirichlet_mask] = -1.0
row = rows.ravel()
col = cols.ravel()
val = vals.ravel()
# mask off-grid entries and sum duplicates
mask = col >= 0
row, col, val = sum_duplicates(row[mask], col[mask], val[mask])
# indptr, col, val = coo_tocsr(row, col, val)
nnz = np.bincount(row)
indptr = np.insert(np.cumsum(nnz),0,0)
mat.assemblyBegin()
mat.setValuesLocalCSR(indptr.astype(PETSc.IntType), col, val)
mat.assemblyEnd()
# set diagonal vector
diag = mat.getRowSum()
diag.scale(-1.0)
mat.setDiagonal(diag)
return mat
def construct_rhs(self, in_place=True):
"""
Construct the right-hand-side vector
i.e. vector b in Ax = b
Boundary conditions are grabbed from the dictionary and
summed to the rhs.
Be careful of duplicate entries on the corners!!
"""
if in_place:
rhs = self.rhs
else:
rhs = MeshVariable('rhs', self.dm)
vec = -1.0*self.heat_sources[:]
for wall in self.bc:
val = self.bc[wall]['val']
flux = self.bc[wall]['flux']
mask = self.bc[wall]['mask']
if flux:
vec[mask] += val
else:
vec[mask] = val
rhs[:] = vec
return rhs
def solve(self, matrix=None, rhs=None):
"""
Construct the matrix A and vector b in Ax = b
and solve for x
GMRES method is default
"""
if matrix is None:
matrix = self.construct_matrix()
if rhs is None:
rhs = self.construct_rhs()
res = self.temperature
ksp = self.ksp
ksp.setOperators(matrix)
ksp.solve(rhs._gdata, res._gdata)
# We should hand this back to local vectors
return res[:]
def sync(self, vector):
"""
Synchronise a vector field across all processors
"""
self.lvec.setArray(vector)
self.dm.localToGlobal(self.lvec, self.gvec)
self.dm.globalToLocal(self.gvec, self.lvec)
return self.lvec.array.copy()
def gradient(self, vector, **kwargs):
"""
Computes the gradient of a vector field
Parameters
----------
vector : array shape(n,) size of the mesh
Returns
-------
grad : array shape(dim,n) gradient in each direction
"""
return np.gradient(vector.reshape(self.n), *self.grid_coords[::-1], **kwargs)
def heatflux(self):
"""
Compute the heat flux based on stored vector fields
- temperature
- diffusivity
Returns
-------
Q : array shape(dim,n) heat flux in each direction
"""
T = self.temperature[:]
k = self.diffusivity[:] * -1
divT = np.array(self.gradient(T))
q = []
for i in range(0, self.dim):
div = k*divT[i].ravel()
q.append(div)
return np.array(q)
def isosurface(self, vector, isoval, axis=0, interp='nearest', return_indices=False):
"""
Calculate an isosurface along a given axis
(So far this is only working for axis=0 and in serial)
Parameters
----------
vector : array, the same size as the mesh (n,)
isoval : float, isosurface value
axis : int, axis to generate the isosurface
interp : str, method can be either
'nearest' - nearest neighbour interpolation
'linear' - linear interpolation
return_indices : bool (default=False)
return the coordinate index
Returns
-------
z_interp : isosurface the same size as the specified axis
indices : int, same size as axis (if return_indices is True)
"""
Vcube = vector.reshape(self.n)
Zcube = self.coords[:,::-1][:,axis].reshape(self.n)
sort_idx = ((Vcube - isoval)**2).argsort(axis=axis)
i0 = sort_idx[0]
# z0 = Zcube.take(i0)
obj = []
for d in range(0, self.dim):
obj.append( slice(0, self.n[d]) )
obj.pop(axis)
idx = list(np.mgrid[obj])
idx.insert(axis, i0)
z0 = Zcube[idx]
z_interp = z0
if interp == 'linear':
v0 = Vcube[idx]
# identify next nearest node
i1 = sort_idx[1]
idx[axis] = i1
z1 = Zcube[idx]
v1 = Vcube[idx]
vmin = np.minimum(v0, v1)
vmax = np.maximum(v0, v1)
ratio = np.vstack([np.ones_like(vmax)*isoval, vmin, vmax])
ratio -= ratio.min(axis=0)
ratio /= ratio.max(axis=0)
z_interp = ratio[0]*z1 + (1.0 - ratio[0])*z0
if return_indices:
Bcube = np.zeros_like(Vcube, dtype=bool)
idx[axis] = i0
Bcube[idx] = True
indices = np.where(Bcube.ravel())[0]
return z_interp, indices
else:
return z_interp
def save_mesh_to_hdf5(self, filename):
"""
Save important mesh information to an HDF5 file
including bounding box and resolution.
These are saved under the topology group
Parameters
----------
filename : file to put .h5 file
"""
import h5py
filename = str(filename)
if not filename.endswith('.h5'):
filename += '.h5'
ViewHDF5 = PETSc.Viewer()
ViewHDF5.createHDF5(filename, mode='w')
ViewHDF5.view(obj=self.dm)
ViewHDF5.destroy()
if comm.rank == 0:
# Every processor is writing the same thing
f = h5py.File(filename, 'r+')
f.create_group('topology')
topo = f['topology']
# create attributes
extent = self.extent.reshape(self.dim,-1)
minCoord = extent[:,0]
maxCoord = extent[:,1]
shape = self.dm.getSizes()
topo.attrs.create('minCoord', minCoord[::-1])
topo.attrs.create('maxCoord', maxCoord[::-1])
topo.attrs.create('shape', np.array(shape)[::-1])
f.close()
def save_field_to_hdf5(self, filename, *args, **kwargs):
"""
Saves data on the mesh to an HDF5 file
e.g. height, rainfall, sea level, etc.
Pass these as arguments or keyword arguments for
their names to be saved to the hdf5 file
"""
import os.path
filename = str(filename)
if not filename.endswith('.h5'):
filename += '.h5'
# write mesh if it doesn't exist
# if not os.path.isfile(file):
# self.save_mesh_to_hdf5(file)
kwdict = kwargs
for i, arg in enumerate(args):
key = "arr_{}".format(i)
if key in kwdict.keys():
raise ValueError("Cannot use un-named variables\
and keyword: {}".format(key))
kwdict[key] = arg
vec = self.gvec.duplicate()
# change mode to append if file already exists
# set mode to "a" after first write
if os.path.isfile(filename):
mode = 'a'
else:
mode = 'w'
for key in kwdict:
val = kwdict[key]
try:
vec.setArray(val)
except:
self.lvec.setArray(val)
self.dm.localToGlobal(self.lvec, vec)
vec.setName(key)
ViewHDF5 = PETSc.Viewer()
ViewHDF5.createHDF5(filename, mode=mode)
ViewHDF5.view(obj=vec)
ViewHDF5.destroy()
mode = "a"
vec.destroy()
def save_vector_to_hdf5(self, filename, *args, **kwargs):
"""
Saves vector on the mesh to an HDF5 file
e.g. heat flux field.
Pass these as arguments or keyword arguments for
their names to be saved to the hdf5 file
Each argument with x,y,z direction tuple
e.g. Q=(Qx, Qy, Qz)
"""
import os.path
filename = str(filename)
if not filename.endswith('.h5'):
filename += '.h5'
kwdict = kwargs
for i, arg in enumerate(args):
key = "arr_{}".format(i)
if key in kwdict.keys():
raise ValueError("Cannot use un-named variables\
and keyword: {}".format(key))
kwdict[key] = arg
# change mode to append if file already exists
# set mode to "a" after first write
if os.path.isfile(filename):
mode = 'a'
else:
mode = 'w'
# This is a flattened dim x n global vector
gvec = self.dm.getCoordinates().duplicate()
for key in kwdict:
val = np.array(kwdict[key]).T.ravel()
# vx, vy, vz = kwdict[key]
# val = np.column_stack([vx, vy, vz]).ravel()
gvec.assemblyBegin()
gvec.setValuesLocal(np.arange(val.size, dtype=PETSc.IntType), val)
gvec.assemblyEnd()
gvec.setName(key)
ViewHDF5 = PETSc.Viewer()
ViewHDF5.createHDF5(filename, mode=mode)
ViewHDF5.view(obj=gvec)
ViewHDF5.destroy()
mode = "a"
gvec.destroy()
Classes
class ConductionND (minCoord, maxCoord, res, **kwargs)
-
Implicit N-dimensional solver for the steady-state heat equation over a structured grid using PETSc data structures.
Parameters
minCoord : tuple, minimum Cartesian coordinates at edge of domain maxCoord : tuple, maximum Cartesian coordinates at edge of domain res : tuple, resolution in each dimension kwargs : dict, keyword arguments to pass to KSP method and preconditioner see PETSc documentaion for KSPType and PCType options… http://www.mcs.anl.gov/petsc/petsc-current/docs/manualpages/KSP/KSPType.html http://www.mcs.anl.gov/petsc/petsc-current/docs/manualpages/PC/PCType.html
Expand source code
class ConductionND(object): """ Implicit N-dimensional solver for the steady-state heat equation over a structured grid using PETSc data structures. Parameters ---------- minCoord : tuple, minimum Cartesian coordinates at edge of domain maxCoord : tuple, maximum Cartesian coordinates at edge of domain res : tuple, resolution in each dimension kwargs : dict, keyword arguments to pass to KSP method and preconditioner see PETSc documentaion for KSPType and PCType options... http://www.mcs.anl.gov/petsc/petsc-current/docs/manualpages/KSP/KSPType.html http://www.mcs.anl.gov/petsc/petsc-current/docs/manualpages/PC/PCType.html """ def __init__(self, minCoord, maxCoord, res, **kwargs): dim = len(res) extent = np.zeros(dim*2) index = 0 for i in range(0, dim): extent[index] = minCoord[i] extent[index+1] = maxCoord[i] index += 2 width = kwargs.pop('stencil_width', 1) dm = PETSc.DMDA().create(dim=dim, sizes=res, stencil_width=width, comm=comm) dm.setUniformCoordinates(*extent) self.dm = dm self.lgmap = dm.getLGMap() self.lvec = dm.createLocalVector() self.gvec = dm.createGlobalVector() # Setup matrix sizes self.sizes = self.gvec.getSizes(), self.gvec.getSizes() self.dim = dim self.extent = extent # include ghost nodes in local domain # (minI, maxI), (minJ, maxJ), (minK, maxK) = dm.getGhostRanges() ghost_ranges = dm.getGhostRanges() n = np.zeros(dim, dtype=PETSc.IntType) nn = 1 for i, (gs, ge) in enumerate(ghost_ranges): n[i] = ge - gs nn *= n[i] self.n = n[::-1] self.nn = nn self.npoints = nn # stencil size self.width = width self.stencil_width = 2*dim*width + 1 # create closure array closure = [] for w in range(width, 0, -1): closure_array = self._get_closure_array(dim, w, width) closure.extend(closure_array[:-1]) closure.append(closure_array[-1]) # centre node at last # create closure object self.closure = self._create_closure_object(closure, width) # local numbering self.nodes = np.arange(0, nn, dtype=PETSc.IntType) # set matrix and vector types self.MatType = kwargs.pop('MatType', 'aij') # cuda, seqaij, mpiaij, etc. self.VecType = kwargs.pop('VecType', 'standard') self._initialise_mesh_variables() self._initialise_boundary_dictionary() self.mat = self._initialise_matrix() self._initialise_COO_vectors(width) self.ksp = self._initialise_ksp(**kwargs) # thermal properties self.diffusivity = MeshVariable('diffusivity', dm) self.heat_sources = MeshVariable('heat_sources', dm) self.temperature = MeshVariable('temperature', dm) # right hand side vector self.rhs = MeshVariable('rhs', dm) def __delete__(self): del self.rhs, self.diffusivity, self.heat_sources, self.temperature self.mat.destroy() self.dm.destroy() self.lvec.destroy() self.gvec.destroy() self.lgmap.destroy() def _initialise_ksp(self, matrix=None, atol=1e-10, rtol=1e-50, **kwargs): """ Initialise linear solver object """ if matrix is None: matrix = self.mat solver = kwargs.pop('solver', 'gmres') precon = kwargs.pop('pc', None) ksp = PETSc.KSP().create(comm) ksp.setType(solver) ksp.setOperators(matrix) ksp.setTolerances(atol, rtol) if precon is not None: pc = ksp.getPC() pc.setType(precon) ksp.setFromOptions() return ksp def _initialise_COO_vectors(self, pad=1): nn = self.nn n = self.n self.index = np.pad(self.nodes.reshape(n), pad, 'constant', constant_values=-1) self.rows = np.empty((self.stencil_width, nn), dtype=PETSc.IntType) self.cols = np.empty((self.stencil_width, nn), dtype=PETSc.IntType) self.vals = np.empty((self.stencil_width, nn)) def _initialise_mesh_variables(self): dim = self.dim bbox = self.dm.getBoundingBox() extent = np.zeros(dim*2) index = 0 for bs, be in bbox: extent[index] = bs extent[index+1] = be index += 2 self.extent = extent # local coordinates self.coords = self.dm.getCoordinatesLocal().array.reshape(-1, dim) grid_coords = [None]*dim for i in range(0, dim): grid_coords[i] = np.unique(self.coords[:,i]) self.grid_coords = grid_coords def _initialise_boundary_dictionary(self): coords = self.coords grid_coords = self.grid_coords dim = self.dim minCoords = coords.min(axis=0) maxCoords = coords.max(axis=0) bbox = self.dm.getBoundingBox() sizes = self.dm.getSizes() # Setup boundary dictionary bc = dict() wall = [("minX", "maxX"), ("minY", "maxY"), ("minZ", "maxZ")] for i in range(0, dim): w0, w1 = wall[i] c0, c1 = bbox[i] m0, m1 = self.coords[:,i] == c0, self.coords[:,i] == c1 d0 = d1 = (c1 - c0)/(sizes[i] - 1) bc[w0] = {"val": 0.0, "delta": d0, "flux": True, "mask": m0} bc[w1] = {"val": 0.0, "delta": d1, "flux": True, "mask": m1} self.bc = bc self.dirichlet_mask = np.zeros(self.nn, dtype=bool) def _initialise_matrix(self, nnz=None): """ There should be no mallocs but we turn off the error just to be sure. If there is it will be from users adjusting the BCs. Could push zeros into the matrix to allocate all potential entries but that would lengthen the build stage. """ if nnz is None: nnz = (self.stencil_width, self.dim*2) mat = PETSc.Mat().create(comm=comm) mat.setType(self.MatType) mat.setSizes(self.sizes) mat.setLGMap(self.lgmap) mat.setPreallocationNNZ(nnz) mat.setOption(PETSc.Mat.Option.NEW_NONZERO_ALLOCATION_ERR, 0) mat.setFromOptions() return mat def _initialise_vector(self, sizes): vec = PETSc.Vec().create(comm=comm) vec.setType(self.VecType) vec.setSizes(self.sizes[0]) vec.setLGMap(self.lgmap) vec.setFromOptions() return vec def _create_closure_object(self, closure, pad=1): nc = len(closure) n = self.n p2 = 2*pad obj = [[0] * self.dim for i in range(nc)] for i in range(0, nc): # construct slicing object for j in range(0, self.dim): start, end = closure[i-j] obj[i][j] = slice(start, n[j]+end+p2) return obj def _get_closure_array(self, dim, width=1, pad=1): w, p = width, pad if w > p: raise ValueError('width exceeds padding') if dim == 1: closure = [(p-w,-p-w), (p+w,-(p-w)), (p,-p)] elif dim == 2: closure = [(p-w,-p-w), (p,-p), (p+w,-(p-w)), (p,-p), (p,-p)] elif dim == 3: closure = [(p-w,-p-w), (p,-p), (p,-p), (p+w,-(p-w)), (p,-p), (p,-p), (p,-p)] else: raise ValueError('{} is an invalid number of dimensions'.format(dim)) return closure def refine(self, fn, axis): """ Pass a function to apply to the x,y,z coordinates on the mesh. The domain will be redefined accordingly. Notes ----- We do it this way to make sure the domain is balanced across processors. Adding new nodes would imbalance the matrix. """ v = self.dm.getCoordinatesLocal() coords = v.array.reshape(-1, self.dim) coords[:,axis] = fn(coords[:,axis]) if not np.isfinite(coords).all(): raise ValueError('This function has created NaNs or Inf numbers') v.setArray(coords.ravel()) self.dm.setCoordinatesLocal(v) self._initialise_mesh_variables() self._initialise_boundary_dictionary() self.mat = self._initialise_matrix() def create_meshVariable(self, name): return MeshVariable(name, self.dm) def update_properties(self, diffusivity, heat_sources): """ Update diffusivity and heat sources """ self.diffusivity[:] = diffusivity self.heat_sources[:] = heat_sources def boundary_condition(self, wall, val, flux=True): """ Set the boundary conditions on each wall of the domain. By default each wall is a Neumann (flux) condition. If flux=True, positive val indicates a flux vector towards the centre of the domain. val can be a vector with the same number of elements as the wall """ wall = str(wall) if wall in self.bc: self.bc[wall]["val"] = np.array(val, copy=True) self.bc[wall]["flux"] = bool(flux) d = self.bc[wall] mask = d['mask'] if flux: self.dirichlet_mask[mask] = False self.bc[wall]["val"] /= -d['delta'] else: self.dirichlet_mask[mask] = True else: raise ValueError("Wall should be one of {}".format(self.bc.keys())) def find_neighbours(self, width=1): """ Find node neighbours for each point in the domain Returns a point cloud of neighbours shape(n,nneighbours) -1 indicates a null value """ nodes = self.nodes dim = self.dim n = self.n # setup new stencil stencil_width = 2*self.dim*width + 1 neighbours = np.empty((stencil_width, self.nn), dtype=PETSc.IntType) index = np.pad(nodes.reshape(n), width, 'constant', constant_values=-1) closure = [] for w in range(width, 0, -1): closure_array = self._get_closure_array(dim, w, width) closure.extend(closure_array[:-1]) closure.append(closure_array[-1]) # centre node at last # create closure object closure = self._create_closure_object(closure, width) for i in range(0, stencil_width): obj = closure[i] neighbours[i] = index[obj].ravel() return neighbours.T def construct_matrix(self, in_place=True, derivative=False): """ Construct the coefficient matrix i.e. matrix A in Ax = b We vectorise the 7-point stencil for fast matrix insertion. An extra border of dummy values around the domain allows for automatic Neumann (flux) boundary creation. These are stomped on if there are any Dirichlet conditions. """ if in_place: mat = self.mat else: mat = self._initialise_matrix() nodes = self.nodes nn = self.nn n = self.n dim = self.dim index = self.index rows = self.rows cols = self.cols vals = self.vals dirichlet_mask = self.dirichlet_mask u = self.diffusivity[:].reshape(n) k = np.pad(u, self.width, 'constant', constant_values=0) for i in range(0, self.stencil_width): obj = tuple(self.closure[i]) rows[i] = nodes cols[i] = index[obj].ravel() distance = np.linalg.norm(self.coords[cols[i]] - self.coords, axis=1) distance[distance==0] = 1e-12 # protect against dividing by zero delta = 1.0/(2.0*distance**2) vals[i] = delta*(k[obj] + u).ravel() # Dirichlet boundary conditions (duplicates are summed) cols[:,dirichlet_mask] = nodes[dirichlet_mask] vals[:,dirichlet_mask] = 0.0 # zero off-grid coordinates vals[cols < 0] = 0.0 # centre point vals[-1] = 0.0 if derivative: vals[-1][dirichlet_mask] = 0. else: vals[-1][dirichlet_mask] = -1.0 row = rows.ravel() col = cols.ravel() val = vals.ravel() # mask off-grid entries and sum duplicates mask = col >= 0 row, col, val = sum_duplicates(row[mask], col[mask], val[mask]) # indptr, col, val = coo_tocsr(row, col, val) nnz = np.bincount(row) indptr = np.insert(np.cumsum(nnz),0,0) mat.assemblyBegin() mat.setValuesLocalCSR(indptr.astype(PETSc.IntType), col, val) mat.assemblyEnd() # set diagonal vector diag = mat.getRowSum() diag.scale(-1.0) mat.setDiagonal(diag) return mat def construct_rhs(self, in_place=True): """ Construct the right-hand-side vector i.e. vector b in Ax = b Boundary conditions are grabbed from the dictionary and summed to the rhs. Be careful of duplicate entries on the corners!! """ if in_place: rhs = self.rhs else: rhs = MeshVariable('rhs', self.dm) vec = -1.0*self.heat_sources[:] for wall in self.bc: val = self.bc[wall]['val'] flux = self.bc[wall]['flux'] mask = self.bc[wall]['mask'] if flux: vec[mask] += val else: vec[mask] = val rhs[:] = vec return rhs def solve(self, matrix=None, rhs=None): """ Construct the matrix A and vector b in Ax = b and solve for x GMRES method is default """ if matrix is None: matrix = self.construct_matrix() if rhs is None: rhs = self.construct_rhs() res = self.temperature ksp = self.ksp ksp.setOperators(matrix) ksp.solve(rhs._gdata, res._gdata) # We should hand this back to local vectors return res[:] def sync(self, vector): """ Synchronise a vector field across all processors """ self.lvec.setArray(vector) self.dm.localToGlobal(self.lvec, self.gvec) self.dm.globalToLocal(self.gvec, self.lvec) return self.lvec.array.copy() def gradient(self, vector, **kwargs): """ Computes the gradient of a vector field Parameters ---------- vector : array shape(n,) size of the mesh Returns ------- grad : array shape(dim,n) gradient in each direction """ return np.gradient(vector.reshape(self.n), *self.grid_coords[::-1], **kwargs) def heatflux(self): """ Compute the heat flux based on stored vector fields - temperature - diffusivity Returns ------- Q : array shape(dim,n) heat flux in each direction """ T = self.temperature[:] k = self.diffusivity[:] * -1 divT = np.array(self.gradient(T)) q = [] for i in range(0, self.dim): div = k*divT[i].ravel() q.append(div) return np.array(q) def isosurface(self, vector, isoval, axis=0, interp='nearest', return_indices=False): """ Calculate an isosurface along a given axis (So far this is only working for axis=0 and in serial) Parameters ---------- vector : array, the same size as the mesh (n,) isoval : float, isosurface value axis : int, axis to generate the isosurface interp : str, method can be either 'nearest' - nearest neighbour interpolation 'linear' - linear interpolation return_indices : bool (default=False) return the coordinate index Returns ------- z_interp : isosurface the same size as the specified axis indices : int, same size as axis (if return_indices is True) """ Vcube = vector.reshape(self.n) Zcube = self.coords[:,::-1][:,axis].reshape(self.n) sort_idx = ((Vcube - isoval)**2).argsort(axis=axis) i0 = sort_idx[0] # z0 = Zcube.take(i0) obj = [] for d in range(0, self.dim): obj.append( slice(0, self.n[d]) ) obj.pop(axis) idx = list(np.mgrid[obj]) idx.insert(axis, i0) z0 = Zcube[idx] z_interp = z0 if interp == 'linear': v0 = Vcube[idx] # identify next nearest node i1 = sort_idx[1] idx[axis] = i1 z1 = Zcube[idx] v1 = Vcube[idx] vmin = np.minimum(v0, v1) vmax = np.maximum(v0, v1) ratio = np.vstack([np.ones_like(vmax)*isoval, vmin, vmax]) ratio -= ratio.min(axis=0) ratio /= ratio.max(axis=0) z_interp = ratio[0]*z1 + (1.0 - ratio[0])*z0 if return_indices: Bcube = np.zeros_like(Vcube, dtype=bool) idx[axis] = i0 Bcube[idx] = True indices = np.where(Bcube.ravel())[0] return z_interp, indices else: return z_interp def save_mesh_to_hdf5(self, filename): """ Save important mesh information to an HDF5 file including bounding box and resolution. These are saved under the topology group Parameters ---------- filename : file to put .h5 file """ import h5py filename = str(filename) if not filename.endswith('.h5'): filename += '.h5' ViewHDF5 = PETSc.Viewer() ViewHDF5.createHDF5(filename, mode='w') ViewHDF5.view(obj=self.dm) ViewHDF5.destroy() if comm.rank == 0: # Every processor is writing the same thing f = h5py.File(filename, 'r+') f.create_group('topology') topo = f['topology'] # create attributes extent = self.extent.reshape(self.dim,-1) minCoord = extent[:,0] maxCoord = extent[:,1] shape = self.dm.getSizes() topo.attrs.create('minCoord', minCoord[::-1]) topo.attrs.create('maxCoord', maxCoord[::-1]) topo.attrs.create('shape', np.array(shape)[::-1]) f.close() def save_field_to_hdf5(self, filename, *args, **kwargs): """ Saves data on the mesh to an HDF5 file e.g. height, rainfall, sea level, etc. Pass these as arguments or keyword arguments for their names to be saved to the hdf5 file """ import os.path filename = str(filename) if not filename.endswith('.h5'): filename += '.h5' # write mesh if it doesn't exist # if not os.path.isfile(file): # self.save_mesh_to_hdf5(file) kwdict = kwargs for i, arg in enumerate(args): key = "arr_{}".format(i) if key in kwdict.keys(): raise ValueError("Cannot use un-named variables\ and keyword: {}".format(key)) kwdict[key] = arg vec = self.gvec.duplicate() # change mode to append if file already exists # set mode to "a" after first write if os.path.isfile(filename): mode = 'a' else: mode = 'w' for key in kwdict: val = kwdict[key] try: vec.setArray(val) except: self.lvec.setArray(val) self.dm.localToGlobal(self.lvec, vec) vec.setName(key) ViewHDF5 = PETSc.Viewer() ViewHDF5.createHDF5(filename, mode=mode) ViewHDF5.view(obj=vec) ViewHDF5.destroy() mode = "a" vec.destroy() def save_vector_to_hdf5(self, filename, *args, **kwargs): """ Saves vector on the mesh to an HDF5 file e.g. heat flux field. Pass these as arguments or keyword arguments for their names to be saved to the hdf5 file Each argument with x,y,z direction tuple e.g. Q=(Qx, Qy, Qz) """ import os.path filename = str(filename) if not filename.endswith('.h5'): filename += '.h5' kwdict = kwargs for i, arg in enumerate(args): key = "arr_{}".format(i) if key in kwdict.keys(): raise ValueError("Cannot use un-named variables\ and keyword: {}".format(key)) kwdict[key] = arg # change mode to append if file already exists # set mode to "a" after first write if os.path.isfile(filename): mode = 'a' else: mode = 'w' # This is a flattened dim x n global vector gvec = self.dm.getCoordinates().duplicate() for key in kwdict: val = np.array(kwdict[key]).T.ravel() # vx, vy, vz = kwdict[key] # val = np.column_stack([vx, vy, vz]).ravel() gvec.assemblyBegin() gvec.setValuesLocal(np.arange(val.size, dtype=PETSc.IntType), val) gvec.assemblyEnd() gvec.setName(key) ViewHDF5 = PETSc.Viewer() ViewHDF5.createHDF5(filename, mode=mode) ViewHDF5.view(obj=gvec) ViewHDF5.destroy() mode = "a" gvec.destroy()
Subclasses
Methods
def boundary_condition(self, wall, val, flux=True)
-
Set the boundary conditions on each wall of the domain. By default each wall is a Neumann (flux) condition. If flux=True, positive val indicates a flux vector towards the centre of the domain.
val can be a vector with the same number of elements as the wall
Expand source code
def boundary_condition(self, wall, val, flux=True): """ Set the boundary conditions on each wall of the domain. By default each wall is a Neumann (flux) condition. If flux=True, positive val indicates a flux vector towards the centre of the domain. val can be a vector with the same number of elements as the wall """ wall = str(wall) if wall in self.bc: self.bc[wall]["val"] = np.array(val, copy=True) self.bc[wall]["flux"] = bool(flux) d = self.bc[wall] mask = d['mask'] if flux: self.dirichlet_mask[mask] = False self.bc[wall]["val"] /= -d['delta'] else: self.dirichlet_mask[mask] = True else: raise ValueError("Wall should be one of {}".format(self.bc.keys()))
def construct_matrix(self, in_place=True, derivative=False)
-
Construct the coefficient matrix i.e. matrix A in Ax = b
We vectorise the 7-point stencil for fast matrix insertion. An extra border of dummy values around the domain allows for automatic Neumann (flux) boundary creation. These are stomped on if there are any Dirichlet conditions.
Expand source code
def construct_matrix(self, in_place=True, derivative=False): """ Construct the coefficient matrix i.e. matrix A in Ax = b We vectorise the 7-point stencil for fast matrix insertion. An extra border of dummy values around the domain allows for automatic Neumann (flux) boundary creation. These are stomped on if there are any Dirichlet conditions. """ if in_place: mat = self.mat else: mat = self._initialise_matrix() nodes = self.nodes nn = self.nn n = self.n dim = self.dim index = self.index rows = self.rows cols = self.cols vals = self.vals dirichlet_mask = self.dirichlet_mask u = self.diffusivity[:].reshape(n) k = np.pad(u, self.width, 'constant', constant_values=0) for i in range(0, self.stencil_width): obj = tuple(self.closure[i]) rows[i] = nodes cols[i] = index[obj].ravel() distance = np.linalg.norm(self.coords[cols[i]] - self.coords, axis=1) distance[distance==0] = 1e-12 # protect against dividing by zero delta = 1.0/(2.0*distance**2) vals[i] = delta*(k[obj] + u).ravel() # Dirichlet boundary conditions (duplicates are summed) cols[:,dirichlet_mask] = nodes[dirichlet_mask] vals[:,dirichlet_mask] = 0.0 # zero off-grid coordinates vals[cols < 0] = 0.0 # centre point vals[-1] = 0.0 if derivative: vals[-1][dirichlet_mask] = 0. else: vals[-1][dirichlet_mask] = -1.0 row = rows.ravel() col = cols.ravel() val = vals.ravel() # mask off-grid entries and sum duplicates mask = col >= 0 row, col, val = sum_duplicates(row[mask], col[mask], val[mask]) # indptr, col, val = coo_tocsr(row, col, val) nnz = np.bincount(row) indptr = np.insert(np.cumsum(nnz),0,0) mat.assemblyBegin() mat.setValuesLocalCSR(indptr.astype(PETSc.IntType), col, val) mat.assemblyEnd() # set diagonal vector diag = mat.getRowSum() diag.scale(-1.0) mat.setDiagonal(diag) return mat
def construct_rhs(self, in_place=True)
-
Construct the right-hand-side vector i.e. vector b in Ax = b
Boundary conditions are grabbed from the dictionary and summed to the rhs. Be careful of duplicate entries on the corners!!
Expand source code
def construct_rhs(self, in_place=True): """ Construct the right-hand-side vector i.e. vector b in Ax = b Boundary conditions are grabbed from the dictionary and summed to the rhs. Be careful of duplicate entries on the corners!! """ if in_place: rhs = self.rhs else: rhs = MeshVariable('rhs', self.dm) vec = -1.0*self.heat_sources[:] for wall in self.bc: val = self.bc[wall]['val'] flux = self.bc[wall]['flux'] mask = self.bc[wall]['mask'] if flux: vec[mask] += val else: vec[mask] = val rhs[:] = vec return rhs
def create_meshVariable(self, name)
-
Expand source code
def create_meshVariable(self, name): return MeshVariable(name, self.dm)
def find_neighbours(self, width=1)
-
Find node neighbours for each point in the domain
Returns a point cloud of neighbours shape(n,nneighbours) -1 indicates a null value
Expand source code
def find_neighbours(self, width=1): """ Find node neighbours for each point in the domain Returns a point cloud of neighbours shape(n,nneighbours) -1 indicates a null value """ nodes = self.nodes dim = self.dim n = self.n # setup new stencil stencil_width = 2*self.dim*width + 1 neighbours = np.empty((stencil_width, self.nn), dtype=PETSc.IntType) index = np.pad(nodes.reshape(n), width, 'constant', constant_values=-1) closure = [] for w in range(width, 0, -1): closure_array = self._get_closure_array(dim, w, width) closure.extend(closure_array[:-1]) closure.append(closure_array[-1]) # centre node at last # create closure object closure = self._create_closure_object(closure, width) for i in range(0, stencil_width): obj = closure[i] neighbours[i] = index[obj].ravel() return neighbours.T
def gradient(self, vector, **kwargs)
-
Computes the gradient of a vector field
Parameters
vector : array shape(n,) size of the mesh
Returns
grad : array shape(dim,n) gradient in each direction
Expand source code
def gradient(self, vector, **kwargs): """ Computes the gradient of a vector field Parameters ---------- vector : array shape(n,) size of the mesh Returns ------- grad : array shape(dim,n) gradient in each direction """ return np.gradient(vector.reshape(self.n), *self.grid_coords[::-1], **kwargs)
def heatflux(self)
-
Compute the heat flux based on stored vector fields - temperature - diffusivity
Returns
Q : array shape(dim,n) heat flux in each direction
Expand source code
def heatflux(self): """ Compute the heat flux based on stored vector fields - temperature - diffusivity Returns ------- Q : array shape(dim,n) heat flux in each direction """ T = self.temperature[:] k = self.diffusivity[:] * -1 divT = np.array(self.gradient(T)) q = [] for i in range(0, self.dim): div = k*divT[i].ravel() q.append(div) return np.array(q)
def isosurface(self, vector, isoval, axis=0, interp='nearest', return_indices=False)
-
Calculate an isosurface along a given axis (So far this is only working for axis=0 and in serial)
Parameters
vector : array, the same size as the mesh (n,) isoval : float, isosurface value axis : int, axis to generate the isosurface interp : str, method can be either 'nearest' - nearest neighbour interpolation 'linear' - linear interpolation return_indices : bool (default=False) return the coordinate index
Returns
z_interp : isosurface the same size as the specified axis indices : int, same size as axis (if return_indices is True)
Expand source code
def isosurface(self, vector, isoval, axis=0, interp='nearest', return_indices=False): """ Calculate an isosurface along a given axis (So far this is only working for axis=0 and in serial) Parameters ---------- vector : array, the same size as the mesh (n,) isoval : float, isosurface value axis : int, axis to generate the isosurface interp : str, method can be either 'nearest' - nearest neighbour interpolation 'linear' - linear interpolation return_indices : bool (default=False) return the coordinate index Returns ------- z_interp : isosurface the same size as the specified axis indices : int, same size as axis (if return_indices is True) """ Vcube = vector.reshape(self.n) Zcube = self.coords[:,::-1][:,axis].reshape(self.n) sort_idx = ((Vcube - isoval)**2).argsort(axis=axis) i0 = sort_idx[0] # z0 = Zcube.take(i0) obj = [] for d in range(0, self.dim): obj.append( slice(0, self.n[d]) ) obj.pop(axis) idx = list(np.mgrid[obj]) idx.insert(axis, i0) z0 = Zcube[idx] z_interp = z0 if interp == 'linear': v0 = Vcube[idx] # identify next nearest node i1 = sort_idx[1] idx[axis] = i1 z1 = Zcube[idx] v1 = Vcube[idx] vmin = np.minimum(v0, v1) vmax = np.maximum(v0, v1) ratio = np.vstack([np.ones_like(vmax)*isoval, vmin, vmax]) ratio -= ratio.min(axis=0) ratio /= ratio.max(axis=0) z_interp = ratio[0]*z1 + (1.0 - ratio[0])*z0 if return_indices: Bcube = np.zeros_like(Vcube, dtype=bool) idx[axis] = i0 Bcube[idx] = True indices = np.where(Bcube.ravel())[0] return z_interp, indices else: return z_interp
def refine(self, fn, axis)
-
Pass a function to apply to the x,y,z coordinates on the mesh. The domain will be redefined accordingly.
Notes
We do it this way to make sure the domain is balanced across processors. Adding new nodes would imbalance the matrix.
Expand source code
def refine(self, fn, axis): """ Pass a function to apply to the x,y,z coordinates on the mesh. The domain will be redefined accordingly. Notes ----- We do it this way to make sure the domain is balanced across processors. Adding new nodes would imbalance the matrix. """ v = self.dm.getCoordinatesLocal() coords = v.array.reshape(-1, self.dim) coords[:,axis] = fn(coords[:,axis]) if not np.isfinite(coords).all(): raise ValueError('This function has created NaNs or Inf numbers') v.setArray(coords.ravel()) self.dm.setCoordinatesLocal(v) self._initialise_mesh_variables() self._initialise_boundary_dictionary() self.mat = self._initialise_matrix()
def save_field_to_hdf5(self, filename, *args, **kwargs)
-
Saves data on the mesh to an HDF5 file e.g. height, rainfall, sea level, etc.
Pass these as arguments or keyword arguments for their names to be saved to the hdf5 file
Expand source code
def save_field_to_hdf5(self, filename, *args, **kwargs): """ Saves data on the mesh to an HDF5 file e.g. height, rainfall, sea level, etc. Pass these as arguments or keyword arguments for their names to be saved to the hdf5 file """ import os.path filename = str(filename) if not filename.endswith('.h5'): filename += '.h5' # write mesh if it doesn't exist # if not os.path.isfile(file): # self.save_mesh_to_hdf5(file) kwdict = kwargs for i, arg in enumerate(args): key = "arr_{}".format(i) if key in kwdict.keys(): raise ValueError("Cannot use un-named variables\ and keyword: {}".format(key)) kwdict[key] = arg vec = self.gvec.duplicate() # change mode to append if file already exists # set mode to "a" after first write if os.path.isfile(filename): mode = 'a' else: mode = 'w' for key in kwdict: val = kwdict[key] try: vec.setArray(val) except: self.lvec.setArray(val) self.dm.localToGlobal(self.lvec, vec) vec.setName(key) ViewHDF5 = PETSc.Viewer() ViewHDF5.createHDF5(filename, mode=mode) ViewHDF5.view(obj=vec) ViewHDF5.destroy() mode = "a" vec.destroy()
def save_mesh_to_hdf5(self, filename)
-
Save important mesh information to an HDF5 file including bounding box and resolution.
These are saved under the topology group
Parameters
filename : file to put .h5 file
Expand source code
def save_mesh_to_hdf5(self, filename): """ Save important mesh information to an HDF5 file including bounding box and resolution. These are saved under the topology group Parameters ---------- filename : file to put .h5 file """ import h5py filename = str(filename) if not filename.endswith('.h5'): filename += '.h5' ViewHDF5 = PETSc.Viewer() ViewHDF5.createHDF5(filename, mode='w') ViewHDF5.view(obj=self.dm) ViewHDF5.destroy() if comm.rank == 0: # Every processor is writing the same thing f = h5py.File(filename, 'r+') f.create_group('topology') topo = f['topology'] # create attributes extent = self.extent.reshape(self.dim,-1) minCoord = extent[:,0] maxCoord = extent[:,1] shape = self.dm.getSizes() topo.attrs.create('minCoord', minCoord[::-1]) topo.attrs.create('maxCoord', maxCoord[::-1]) topo.attrs.create('shape', np.array(shape)[::-1]) f.close()
def save_vector_to_hdf5(self, filename, *args, **kwargs)
-
Saves vector on the mesh to an HDF5 file e.g. heat flux field.
Pass these as arguments or keyword arguments for their names to be saved to the hdf5 file
Each argument with x,y,z direction tuple e.g. Q=(Qx, Qy, Qz)
Expand source code
def save_vector_to_hdf5(self, filename, *args, **kwargs): """ Saves vector on the mesh to an HDF5 file e.g. heat flux field. Pass these as arguments or keyword arguments for their names to be saved to the hdf5 file Each argument with x,y,z direction tuple e.g. Q=(Qx, Qy, Qz) """ import os.path filename = str(filename) if not filename.endswith('.h5'): filename += '.h5' kwdict = kwargs for i, arg in enumerate(args): key = "arr_{}".format(i) if key in kwdict.keys(): raise ValueError("Cannot use un-named variables\ and keyword: {}".format(key)) kwdict[key] = arg # change mode to append if file already exists # set mode to "a" after first write if os.path.isfile(filename): mode = 'a' else: mode = 'w' # This is a flattened dim x n global vector gvec = self.dm.getCoordinates().duplicate() for key in kwdict: val = np.array(kwdict[key]).T.ravel() # vx, vy, vz = kwdict[key] # val = np.column_stack([vx, vy, vz]).ravel() gvec.assemblyBegin() gvec.setValuesLocal(np.arange(val.size, dtype=PETSc.IntType), val) gvec.assemblyEnd() gvec.setName(key) ViewHDF5 = PETSc.Viewer() ViewHDF5.createHDF5(filename, mode=mode) ViewHDF5.view(obj=gvec) ViewHDF5.destroy() mode = "a" gvec.destroy()
def solve(self, matrix=None, rhs=None)
-
Construct the matrix A and vector b in Ax = b and solve for x
GMRES method is default
Expand source code
def solve(self, matrix=None, rhs=None): """ Construct the matrix A and vector b in Ax = b and solve for x GMRES method is default """ if matrix is None: matrix = self.construct_matrix() if rhs is None: rhs = self.construct_rhs() res = self.temperature ksp = self.ksp ksp.setOperators(matrix) ksp.solve(rhs._gdata, res._gdata) # We should hand this back to local vectors return res[:]
def sync(self, vector)
-
Synchronise a vector field across all processors
Expand source code
def sync(self, vector): """ Synchronise a vector field across all processors """ self.lvec.setArray(vector) self.dm.localToGlobal(self.lvec, self.gvec) self.dm.globalToLocal(self.gvec, self.lvec) return self.lvec.array.copy()
def update_properties(self, diffusivity, heat_sources)
-
Update diffusivity and heat sources
Expand source code
def update_properties(self, diffusivity, heat_sources): """ Update diffusivity and heat sources """ self.diffusivity[:] = diffusivity self.heat_sources[:] = heat_sources