class HasCollections(properties.HasProperties): tuple_unobs = properties.Tuple('') dict_unobs = properties.Dictionary('') dict_obs = properties.Dictionary('', observe_mutations=True) list_unobs = properties.List('') list_obs = properties.List('', observe_mutations=True) set_unobs = properties.Set('') set_obs = properties.Set('', observe_mutations=True)
class HasCoercedIntTuple(properties.HasProperties): aaa = properties.Tuple('tuple of ints', properties.Integer(''), coerce=True)
class HasIntTuple(properties.HasProperties): aaa = properties.Tuple('tuple of ints', properties.Integer(''), default=tuple)
class HasDummyTuple(properties.HasProperties): mytuple = properties.Tuple('dummy has properties tuple', prop=HasPropsDummy)
class UntypedTuple(properties.HasProperties): mytuple = properties.Tuple('no type')
class HasOptPropTuple(properties.HasProperties): mytuple = properties.Tuple( doc='', prop=properties.Bool('', required=False), default=properties.undefined, )
class HasOptionalTuple(properties.HasProperties): mytuple = properties.Tuple('', properties.Bool(''), required=False)
class HasIntATuple(properties.HasProperties): mytuple = properties.Tuple('tuple of HasIntA', HasIntA)
def test_tuple(self): with self.assertRaises(TypeError): properties.Tuple('bad string tuple', prop=str) with self.assertRaises(TypeError): properties.Tuple('bad max', properties.Integer(''), max_length=-10) with self.assertRaises(TypeError): properties.Tuple('bad max', properties.Integer(''), max_length='ten') with self.assertRaises(TypeError): mytuple = properties.Tuple('bad max', properties.Integer(''), min_length=20) mytuple.max_length = 10 with self.assertRaises(TypeError): properties.Tuple('bad min', properties.Integer(''), min_length=-10) with self.assertRaises(TypeError): properties.Tuple('bad min', properties.Integer(''), min_length='ten') with self.assertRaises(TypeError): mytuple = properties.Tuple('bad min', properties.Integer(''), max_length=10) mytuple.min_length = 20 with self.assertRaises(AttributeError): properties.Tuple('bad observe', properties.Integer(''), observe_mutations=5) with self.assertRaises(TypeError): properties.Tuple('bad coerce', properties.Integer(''), coerce=5) class HasPropsDummy(properties.HasProperties): pass mytuple = properties.Tuple('dummy has properties tuple', prop=HasPropsDummy) assert isinstance(mytuple.prop, properties.Instance) assert mytuple.prop.instance_class is HasPropsDummy class HasDummyTuple(properties.HasProperties): mytuple = properties.Tuple('dummy has properties tuple', prop=HasPropsDummy) assert HasDummyTuple()._props['mytuple'].name == 'mytuple' assert HasDummyTuple()._props['mytuple'].prop.name == 'mytuple' class HasIntTuple(properties.HasProperties): aaa = properties.Tuple('tuple of ints', properties.Integer(''), default=tuple) li = HasIntTuple() li.aaa = (1, 2, 3) with self.assertRaises(ValueError): li.aaa = [1, 2, 3] li.aaa = (1., 2., 3.) with self.assertRaises(ValueError): li.aaa = 4 with self.assertRaises(ValueError): li.aaa = ('a', 'b', 'c') li1 = HasIntTuple() li2 = HasIntTuple() assert li1.aaa == li2.aaa assert li1.aaa is li2.aaa li1.aaa += (1, ) assert li1.aaa is not li2.aaa class HasCoercedIntTuple(properties.HasProperties): aaa = properties.Tuple('tuple of ints', properties.Integer(''), coerce=True) li = HasCoercedIntTuple() li.aaa = 1 assert li.aaa == (1, ) li.aaa = [1, 2, 3] assert li.aaa == (1, 2, 3) li.aaa = {1, 2, 3} assert isinstance(li.aaa, tuple) assert all(val in li.aaa for val in [1, 2, 3]) li.aaa = np.array([3, 2, 1]) assert li.aaa == (3, 2, 1) class HasConstrianedTuple(properties.HasProperties): aaa = properties.Tuple('tuple of ints', properties.Integer(''), min_length=2) li = HasConstrianedTuple() li.aaa = (1, 2, 3) li.validate() li.aaa = (1, ) with self.assertRaises(ValueError): li.validate() class HasConstrianedTuple(properties.HasProperties): aaa = properties.Tuple('tuple of ints', properties.Integer(''), max_length=2) li = HasConstrianedTuple() li.aaa = (1, 2) li.validate() li.aaa = (1, 2, 3, 4, 5) with self.assertRaises(ValueError): li.validate() class HasColorTuple(properties.HasProperties): ccc = properties.Tuple('tuple of colors', properties.Color(''), min_length=2, max_length=2) li = HasColorTuple() li.ccc = ('red', '#00FF00') assert li.ccc[0] == (255, 0, 0) assert li.ccc[1] == (0, 255, 0) numtuple = (1, 2, 3, 4) assert properties.Tuple.to_json(numtuple) == list(numtuple) assert properties.Tuple.from_json(list(numtuple)) == numtuple class HasIntA(properties.HasProperties): a = properties.Integer('int a', required=True) assert properties.Tuple.to_json((HasIntA(a=5), HasIntA(a=10))) == [{ '__class__': 'HasIntA', 'a': 5 }, { '__class__': 'HasIntA', 'a': 10 }] assert li.serialize(include_class=False) == { 'ccc': [[255, 0, 0], [0, 255, 0]] } class HasIntATuple(properties.HasProperties): mytuple = properties.Tuple('tuple of HasIntA', HasIntA) deser_tuple = HasIntATuple.deserialize({ 'mytuple': [{ 'a': 0 }, { 'a': 10 }, { 'a': 100 }] }).mytuple assert isinstance(deser_tuple, tuple) assert len(deser_tuple) == 3 assert isinstance(deser_tuple[0], HasIntA) and deser_tuple[0].a == 0 assert isinstance(deser_tuple[1], HasIntA) and deser_tuple[1].a == 10 assert isinstance(deser_tuple[2], HasIntA) and deser_tuple[2].a == 100 class HasOptionalTuple(properties.HasProperties): mytuple = properties.Tuple('', properties.Bool(''), required=False) hol = HasOptionalTuple() hol.validate() assert HasIntATuple._props['mytuple'].deserialize(None) is None assert properties.Tuple('', properties.Instance('', HasIntA)).equal( (HasIntA(a=1), HasIntA(a=2)), (HasIntA(a=1), HasIntA(a=2))) assert not properties.Tuple('', properties.Instance( '', HasIntA)).equal((HasIntA(a=1), HasIntA(a=2)), (HasIntA(a=1), HasIntA(a=2), HasIntA(a=3))) assert not properties.Tuple('', properties.Instance( '', HasIntA)).equal((HasIntA(a=1), HasIntA(a=2)), (HasIntA(a=1), HasIntA(a=3))) assert not properties.Tuple('', properties.Integer('')).equal(5, 5) class HasOptPropTuple(properties.HasProperties): mytuple = properties.Tuple( doc='', prop=properties.Bool('', required=False), default=properties.undefined, ) hopt = HasOptPropTuple() with self.assertRaises(ValueError): hopt.validate() with self.assertRaises(ValueError): hopt.mytuple = (None, ) with self.assertRaises(ValueError): hopt.mytuple = (properties.undefined, ) hopt._backend = {'mytuple': (properties.undefined, )} with self.assertRaises(ValueError): hopt.validate() hopt.mytuple = (True, ) del hopt.mytuple with self.assertRaises(ValueError): hopt.validate() class UntypedTuple(properties.HasProperties): mytuple = properties.Tuple('no type') ut = UntypedTuple(mytuple=(1, 'hi', UntypedTuple)) ut.validate()
class HasColorTuple(properties.HasProperties): ccc = properties.Tuple('tuple of colors', properties.Color(''), min_length=2, max_length=2)
class HasConstrianedTuple(properties.HasProperties): aaa = properties.Tuple('tuple of ints', properties.Integer(''), max_length=2)
class BaseMesh(properties.HasProperties, InterfaceMixins): """ BaseMesh does all the counting you don't want to do. BaseMesh should be inherited by meshes with a regular structure. """ _REGISTRY = {} _aliases = { "nC": "n_cells", "nN": "n_nodes", "nEx": "n_edges_x", "nEy": "n_edges_y", "nEz": "n_edges_z", "nE": "n_edges", "vnE": "n_edges_per_direction", "nFx": "n_faces_x", "nFy": "n_faces_y", "nFz": "n_faces_z", "nF": "n_faces", "vnF": "n_faces_per_direction", } # Properties _n = properties.Tuple( "Tuple of number of cells in each direction (dim, )", prop=properties.Integer("Number of cells along a particular direction", cast=True, min=1), min_length=1, max_length=3, coerce=True, required=True, ) origin = properties.Array( "origin of the mesh (dim, )", dtype=(float, int), shape=("*", ), required=True, ) # Instantiate the class def __init__(self, n=None, origin=None, **kwargs): if n is not None: self._n = n # number of dimensions if "x0" in kwargs: origin = kwargs.pop('x0') if origin is None: self.origin = np.zeros(len(self._n)) else: self.origin = origin super(BaseMesh, self).__init__(**kwargs) def __getattr__(self, name): if name == "_aliases": raise AttributeError # http://nedbatchelder.com/blog/201010/surprising_getattr_recursion.html name = self._aliases.get(name, name) return object.__getattribute__(self, name) @property def x0(self): return self.origin @x0.setter def x0(self, val): self.origin = val @classmethod def deserialize(cls, value, **kwargs): if "x0" in value: value["origin"] = value.pop("x0") return super().deserialize(value, **kwargs) # Validators @properties.validator("_n") def _check_n_shape(self, change): if change["previous"] != properties.undefined: # _n can only be set once if change["previous"] != change["value"]: raise AttributeError( "Cannot change n. Instead, create a new mesh") else: # check that if h has been set, sizes still agree if getattr(self, "h", None) is not None and len(self.h) > 0: for i in range(len(change["value"])): if len(self.h[i]) != change["value"][i]: raise properties.ValidationError( "Mismatched shape of n. Expected {}, len(h[{}]), got " "{}".format(len(self.h[i]), i, change["value"][i])) # check that if nodes have been set for curvi mesh, sizes still # agree if getattr(self, "node_list", None) is not None and len(self.node_list) > 0: for i in range(len(change["value"])): if self.node_list[0].shape[i] - 1 != change["value"][i]: raise properties.ValidationError( "Mismatched shape of n. Expected {}, len(node_list[{}]), " "got {}".format(self.node_list[0].shape[i] - 1, i, change["value"][i])) @properties.validator("origin") def _check_origin(self, change): if not (not isinstance(change["value"], properties.utils.Sentinel) and change["value"] is not None): raise Exception("n must be set prior to setting origin") if len(self._n) != len(change["value"]): raise Exception( "Dimension mismatch. origin has length {} != len(n) which is " "{}".format(len(self.origin), len(self._n))) @property def dim(self): """The dimension of the mesh (1, 2, or 3). Returns ------- int dimension of the mesh """ return len(self._n) @property def n_cells(self): """Total number of cells in the mesh. Returns ------- int number of cells in the mesh Notes ----- Also accessible as `nC`. Examples -------- >>> import discretize >>> import numpy as np >>> import matplotlib.pyplot as plt >>> mesh = discretize.TensorMesh([np.ones(n) for n in [2,3]]) >>> mesh.plot_grid(centers=True, show_it=True) >>> print(mesh.n_cells) """ return int(np.prod(self._n)) def __len__(self): """The number of cells on the mesh.""" return self.n_cells @property def n_nodes(self): """Total number of nodes Returns ------- int number of nodes in the mesh Notes ----- Also accessible as `nN`. Examples -------- >>> import discretize >>> import numpy as np >>> import matplotlib.pyplot as plt >>> mesh = discretize.TensorMesh([np.ones(n) for n in [2,3]]) >>> mesh.plot_grid(nodes=True, show_it=True) >>> print(mesh.n_nodes) """ return int(np.prod(x + 1 for x in self._n)) @property def n_edges_x(self): """Number of x-edges Returns ------- int Notes ----- Also accessible as `nEx`. """ return int(np.prod(x + y for x, y in zip(self._n, (0, 1, 1)))) @property def n_edges_y(self): """Number of y-edges Returns ------- int Notes ----- Also accessible as `nEy`. """ if self.dim < 2: return None return int(np.prod(x + y for x, y in zip(self._n, (1, 0, 1)))) @property def n_edges_z(self): """Number of z-edges Returns ------- int Notes ----- Also accessible as `nEz`. """ if self.dim < 3: return None return int(np.prod(x + y for x, y in zip(self._n, (1, 1, 0)))) @property def n_edges_per_direction(self): """The number of edges in each direction Returns ------- n_edges_per_direction : tuple [n_edges_x, n_edges_y, n_edges_z], (dim, ) Notes ----- Also accessible as `vnE`. Examples -------- >>> import discretize >>> import matplotlib.pyplot as plt >>> import numpy as np >>> M = discretize.TensorMesh([np.ones(n) for n in [2,3]]) >>> M.plot_grid(edges=True, show_it=True) """ return tuple(x for x in [self.n_edges_x, self.n_edges_y, self.n_edges_z] if x is not None) @property def n_edges(self): """Total number of edges. Returns ------- int sum([n_edges_x, n_edges_y, n_edges_z]) Notes ----- Also accessible as `nE`. """ n = self.n_edges_x if self.dim > 1: n += self.n_edges_y if self.dim > 2: n += self.n_edges_z return n @property def n_faces_x(self): """Number of x-faces Returns ------- int Notes ----- Also accessible as `nFx`. """ return int(np.prod(x + y for x, y in zip(self._n, (1, 0, 0)))) @property def n_faces_y(self): """Number of y-faces Returns ------- int Notes ----- Also accessible as `nFy`. """ if self.dim < 2: return None return int(np.prod(x + y for x, y in zip(self._n, (0, 1, 0)))) @property def n_faces_z(self): """Number of z-faces Returns ------- int Notes ----- Also accessible as `nFz`. """ if self.dim < 3: return None return int(np.prod(x + y for x, y in zip(self._n, (0, 0, 1)))) @property def n_faces_per_direction(self): """The number of faces in each direction Returns ------- n_faces_per_direction : tuple [n_faces_x, n_faces_y, n_faces_z], (dim, ) Notes ----- Also accessible as `vnF`. Examples -------- >>> import discretize >>> import numpy as np >>> import matplotlib.pyplot as plt >>> M = discretize.TensorMesh([np.ones(n) for n in [2,3]]) >>> M.plot_grid(faces=True, show_it=True) """ return tuple(x for x in [self.n_faces_x, self.n_faces_y, self.n_faces_z] if x is not None) @property def n_faces(self): """Total number of faces. Returns ------- int sum([n_faces_x, n_faces_y, n_faces_z]) Notes ----- Also accessible as `nF`. """ n = self.n_faces_x if self.dim > 1: n += self.n_faces_y if self.dim > 2: n += self.n_faces_z return n @property def face_normals(self): """Face Normals Returns ------- numpy.ndarray normals, (n_faces, dim) """ if self.dim == 2: nX = np.c_[np.ones(self.n_faces_x), np.zeros(self.n_faces_x)] nY = np.c_[np.zeros(self.n_faces_y), np.ones(self.n_faces_y)] return np.r_[nX, nY] elif self.dim == 3: nX = np.c_[np.ones(self.n_faces_x), np.zeros(self.n_faces_x), np.zeros(self.n_faces_x), ] nY = np.c_[np.zeros(self.n_faces_y), np.ones(self.n_faces_y), np.zeros(self.n_faces_y), ] nZ = np.c_[np.zeros(self.n_faces_z), np.zeros(self.n_faces_z), np.ones(self.n_faces_z), ] return np.r_[nX, nY, nZ] @property def edge_tangents(self): """Edge Tangents Returns ------- numpy.ndarray normals, (n_edges, dim) """ if self.dim == 2: tX = np.c_[np.ones(self.n_edges_x), np.zeros(self.n_edges_x)] tY = np.c_[np.zeros(self.n_edges_y), np.ones(self.n_edges_y)] return np.r_[tX, tY] elif self.dim == 3: tX = np.c_[np.ones(self.n_edges_x), np.zeros(self.n_edges_x), np.zeros(self.n_edges_x), ] tY = np.c_[np.zeros(self.n_edges_y), np.ones(self.n_edges_y), np.zeros(self.n_edges_y), ] tZ = np.c_[np.zeros(self.n_edges_z), np.zeros(self.n_edges_z), np.ones(self.n_edges_z), ] return np.r_[tX, tY, tZ] def project_face_vector(self, face_vector): """Project vectors onto the faces of the mesh. Given a vector, face_vector, in cartesian coordinates, this will project it onto the mesh using the normals Parameters ---------- face_vector : numpy.ndarray face vector with shape (n_faces, dim) Returns ------- numpy.ndarray projected face vector, (n_faces, ) """ if not isinstance(face_vector, np.ndarray): raise Exception("face_vector must be an ndarray") if not (len(face_vector.shape) == 2 and face_vector.shape[0] == self.n_faces and face_vector.shape[1] == self.dim): raise Exception( "face_vector must be an ndarray of shape (n_faces x dim)") return np.sum(face_vector * self.face_normals, 1) def project_edge_vector(self, edge_vector): """Project vectors onto the edges of the mesh Given a vector, edge_vector, in cartesian coordinates, this will project it onto the mesh using the tangents Parameters ---------- edge_vector : numpy.ndarray edge vector with shape (n_edges, dim) Returns ------- numpy.ndarray projected edge vector, (n_edges, ) """ if not isinstance(edge_vector, np.ndarray): raise Exception("edge_vector must be an ndarray") if not (len(edge_vector.shape) == 2 and edge_vector.shape[0] == self.n_edges and edge_vector.shape[1] == self.dim): raise Exception( "edge_vector must be an ndarray of shape (nE x dim)") return np.sum(edge_vector * self.edge_tangents, 1) def save(self, file_name="mesh.json", verbose=False, **kwargs): """ Save the mesh to json :param str file: file_name for saving the casing properties :param str directory: working directory for saving the file """ if 'filename' in kwargs: file_name = kwargs['filename'] warnings.warn( "The filename keyword argument has been deprecated, please use file_name. " "This will be removed in discretize 1.0.0", DeprecationWarning, ) f = os.path.abspath( file_name) # make sure we are working with abs path with open(f, "w") as outfile: json.dump(self.serialize(), outfile) if verbose: print("Saved {}".format(f)) return f def copy(self): """ Make a copy of the current mesh """ return properties.copy(self) axis_u = properties.Vector3( "Vector orientation of u-direction. For more details see the docs for the :attr:`~discretize.base.BaseMesh.rotation_matrix` property.", default="X", length=1, ) axis_v = properties.Vector3( "Vector orientation of v-direction. For more details see the docs for the :attr:`~discretize.base.BaseMesh.rotation_matrix` property.", default="Y", length=1, ) axis_w = properties.Vector3( "Vector orientation of w-direction. For more details see the docs for the :attr:`~discretize.base.BaseMesh.rotation_matrix` property.", default="Z", length=1, ) @properties.validator def _validate_orientation(self): """Check if axes are orthogonal""" tol = 1E-6 if not (np.abs(self.axis_u.dot(self.axis_v) < tol) and np.abs(self.axis_v.dot(self.axis_w) < tol) and np.abs(self.axis_w.dot(self.axis_u) < tol)): raise ValueError("axis_u, axis_v, and axis_w must be orthogonal") return True @property def reference_is_rotated(self): """True if the axes are rotated from the traditional <X,Y,Z> system with vectors of :math:`(1,0,0)`, :math:`(0,1,0)`, and :math:`(0,0,1)` """ if (np.allclose(self.axis_u, (1, 0, 0)) and np.allclose(self.axis_v, (0, 1, 0)) and np.allclose(self.axis_w, (0, 0, 1))): return False return True @property def rotation_matrix(self): """Builds a rotation matrix to transform coordinates from their coordinate system into a conventional cartesian system. This is built off of the three `axis_u`, `axis_v`, and `axis_w` properties; these mapping coordinates use the letters U, V, and W (the three letters preceding X, Y, and Z in the alphabet) to define the projection of the X, Y, and Z durections. These UVW vectors describe the placement and transformation of the mesh's coordinate sytem assuming at most 3 directions. Why would you want to use these UVW mapping vectors the this `rotation_matrix` property? They allow us to define the realationship between local and global coordinate systems and provide a tool for switching between the two while still maintaing the connectivity of the mesh's cells. For a visual example of this, please see the figure in the docs for the :class:`~discretize.mixins.vtk_mod.InterfaceVTK`. """ return np.array([self.axis_u, self.axis_v, self.axis_w]) reference_system = properties.String( "The type of coordinate reference frame. Can take on the values " + "cartesian, cylindrical, or spherical. Abbreviations of these are allowed.", default="cartesian", change_case="lower", ) @properties.validator def _validate_reference_system(self): """Check if the reference system is of a known type.""" choices = ["cartesian", "cylindrical", "spherical"] # Here are a few abbreviations that users can harnes abrevs = { "car": choices[0], "cart": choices[0], "cy": choices[1], "cyl": choices[1], "sph": choices[2], } # Get the name and fix it if it is abbreviated self.reference_system = abrevs.get(self.reference_system, self.reference_system) if self.reference_system not in choices: raise ValueError("Coordinate system ({}) unknown.".format( self.reference_system)) return True def _parse_location_type(self, location_type): if len(location_type) == 0: return location_type elif location_type[0] == "F": if len(location_type) > 1: return "faces_" + location_type[-1] else: return "faces" elif location_type[0] == "E": if len(location_type) > 1: return "edges_" + location_type[-1] else: return "edges" elif location_type[0] == "N": return "nodes" elif location_type[0] == "C": if len(location_type) > 2: return "cell_centers_" + location_type[-1] else: return "cell_centers" else: return location_type # DEPRECATED normals = deprecate_property("face_normals", "normals", removal_version="1.0.0") tangents = deprecate_property("edge_tangents", "tangents", removal_version="1.0.0") projectEdgeVector = deprecate_method("project_edge_vector", "projectEdgeVector", removal_version="1.0.0") projectFaceVector = deprecate_method("project_face_vector", "projectFaceVector", removal_version="1.0.0")
class BaseTensorMesh(BaseMesh): """ Base class for tensor-product style meshes This class contains properites and methods that are common to cartesian and cylindrical meshes defined by tensor-produts of vectors describing cell spacings. Do not use this class directly, instead, inherit it if you plan to develop a tensor-style mesh (e.g. a spherical mesh) or use the :meth:`discretize.TensorMesh` class to create a cartesian tensor mesh. """ _meshType = "BASETENSOR" _aliases = { **BaseMesh._aliases, **{ "gridCC": "cell_centers", "gridN": "nodes", "gridFx": "faces_x", "gridFy": "faces_y", "gridFz": "faces_z", "gridEx": "edges_x", "gridEy": "edges_y", "gridEz": "edges_z", }, } _unitDimensions = [1, 1, 1] # properties h = properties.Tuple( "h is a list containing the cell widths of the tensor mesh in each " "dimension.", properties.Array( "widths of the tensor mesh in a single dimension", dtype=float, shape=("*", ), ), min_length=1, max_length=3, coerce=True, required=True, ) def __init__(self, h=None, origin=None, **kwargs): h_in = h if "x0" in kwargs: origin = kwargs.pop('x0') origin_in = origin # Sanity Checks if not isinstance(h_in, (list, tuple)): raise TypeError("h_in must be a list, not {}".format(type(h_in))) if len(h_in) not in [1, 2, 3]: raise ValueError( "h_in must be of dimension 1, 2, or 3 not {}".format( len(h_in))) # build h h = list(range(len(h_in))) for i, h_i in enumerate(h_in): if is_scalar(h_i) and not isinstance(h_i, np.ndarray): # This gives you something over the unit cube. h_i = self._unitDimensions[i] * np.ones(int(h_i)) / int(h_i) elif isinstance(h_i, (list, tuple)): h_i = unpack_widths(h_i) if not isinstance(h_i, np.ndarray): raise TypeError("h[{0:d}] is not a numpy array.".format(i)) if len(h_i.shape) != 1: raise ValueError( "h[{0:d}] must be a 1D numpy array.".format(i)) h[i] = h_i[:] # make a copy. # Origin of the mesh origin = np.zeros(len(h)) if origin_in is not None: if len(h) != len(origin_in): raise ValueError("Dimension mismatch. origin != len(h)") for i in range(len(h)): x_i, h_i = origin_in[i], h[i] if is_scalar(x_i): origin[i] = x_i elif x_i == "0": origin[i] = 0.0 elif x_i == "C": origin[i] = -h_i.sum() * 0.5 elif x_i == "N": origin[i] = -h_i.sum() else: raise Exception( "origin[{0:d}] must be a scalar or '0' to be zero, " "'C' to center, or 'N' to be negative. The input value" " {1} {2} is invalid".format(i, x_i, type(x_i))) if "n" in kwargs.keys(): n = kwargs.pop("n") if np.any(n != np.array([x.size for x in h])): raise ValueError( "Dimension mismatch. The provided n doesn't h") else: n = np.array([x.size for x in h]) super(BaseTensorMesh, self).__init__(n, origin=origin, **kwargs) # Ensure h contains 1D vectors self.h = [mkvc(x.astype(float)) for x in h] @property def nodes_x(self): """Nodal grid vector (1D) in the x direction.""" return np.r_[self.origin[0], self.h[0]].cumsum() @property def nodes_y(self): """Nodal grid vector (1D) in the y direction.""" return None if self.dim < 2 else np.r_[self.origin[1], self.h[1]].cumsum() @property def nodes_z(self): """Nodal grid vector (1D) in the z direction.""" return None if self.dim < 3 else np.r_[self.origin[2], self.h[2]].cumsum() @property def cell_centers_x(self): """Cell-centered grid vector (1D) in the x direction.""" nodes = self.nodes_x return (nodes[1:] + nodes[:-1]) / 2 @property def cell_centers_y(self): """Cell-centered grid vector (1D) in the y direction.""" if self.dim < 2: return None nodes = self.nodes_y return (nodes[1:] + nodes[:-1]) / 2 @property def cell_centers_z(self): """Cell-centered grid vector (1D) in the z direction.""" if self.dim < 3: return None nodes = self.nodes_z return (nodes[1:] + nodes[:-1]) / 2 @property def cell_centers(self): """Cell-centered grid.""" return self._getTensorGrid("cell_centers") @property def nodes(self): """Nodal grid.""" return self._getTensorGrid("nodes") @property def h_gridded(self): """ Returns an (nC, dim) numpy array with the widths of all cells in order """ if self.dim == 1: return self.h[0][:, None] return ndgrid(*self.h) @property def faces_x(self): """Face staggered grid in the x direction.""" if self.nFx == 0: return return self._getTensorGrid("faces_x") @property def faces_y(self): """Face staggered grid in the y direction.""" if self.nFy == 0 or self.dim < 2: return return self._getTensorGrid("faces_y") @property def faces_z(self): """Face staggered grid in the z direction.""" if self.nFz == 0 or self.dim < 3: return return self._getTensorGrid("faces_z") @property def edges_x(self): """Edge staggered grid in the x direction.""" if self.nEx == 0: return return self._getTensorGrid("edges_x") @property def edges_y(self): """Edge staggered grid in the y direction.""" if self.nEy == 0 or self.dim < 2: return return self._getTensorGrid("edges_y") @property def edges_z(self): """Edge staggered grid in the z direction.""" if self.nEz == 0 or self.dim < 3: return return self._getTensorGrid("edges_z") def _getTensorGrid(self, key): if getattr(self, "_" + key, None) is None: setattr(self, "_" + key, ndgrid(self.get_tensor(key))) return getattr(self, "_" + key) def get_tensor(self, key): """Returns a tensor list. Parameters ---------- key : str Which tensor (see below) key can be:: 'CC', 'cell_centers' -> location of cell centers 'N', 'nodes' -> location of nodes 'Fx', 'faces_x' -> location of faces with an x normal 'Fy', 'faces_y' -> location of faces with an y normal 'Fz', 'faces_z' -> location of faces with an z normal 'Ex', 'edges_x' -> location of edges with an x tangent 'Ey', 'edges_y' -> location of edges with an y tangent 'Ez', 'edges_z' -> location of edges with an z tangent Returns ------- list list of the tensors that make up the mesh. """ key = self._parse_location_type(key) if key == "faces_x": ten = [ self.nodes_x, self.cell_centers_y, self.cell_centers_z, ] elif key == "faces_y": ten = [ self.cell_centers_x, self.nodes_y, self.cell_centers_z, ] elif key == "faces_z": ten = [ self.cell_centers_x, self.cell_centers_y, self.nodes_z, ] elif key == "edges_x": ten = [self.cell_centers_x, self.nodes_y, self.nodes_z] elif key == "edges_y": ten = [self.nodes_x, self.cell_centers_y, self.nodes_z] elif key == "edges_z": ten = [self.nodes_x, self.nodes_y, self.cell_centers_z] elif key == "cell_centers": ten = [ self.cell_centers_x, self.cell_centers_y, self.cell_centers_z, ] elif key == "nodes": ten = [self.nodes_x, self.nodes_y, self.nodes_z] else: raise KeyError(r"Unrecognized key {key}") return [t for t in ten if t is not None] # --------------- Methods --------------------- def is_inside(self, pts, location_type="nodes", **kwargs): """ Determines if a set of points are inside a mesh. :param numpy.ndarray pts: Location of points to test :rtype: numpy.ndarray :return: inside, numpy array of booleans """ if "locType" in kwargs: warnings.warn( "The locType keyword argument has been deprecated, please use location_type. " "This will be removed in discretize 1.0.0", DeprecationWarning, ) location_type = kwargs["locType"] pts = as_array_n_by_dim(pts, self.dim) tensors = self.get_tensor(location_type) if location_type[0].lower() == "n" and self._meshType == "CYL": # NOTE: for a CYL mesh we add a node to check if we are inside in # the radial direction! tensors[0] = np.r_[0.0, tensors[0]] tensors[1] = np.r_[tensors[1], 2.0 * np.pi] inside = np.ones(pts.shape[0], dtype=bool) for i, tensor in enumerate(tensors): TOL = np.diff(tensor).min() * 1.0e-10 inside = (inside & (pts[:, i] >= tensor.min() - TOL) & (pts[:, i] <= tensor.max() + TOL)) return inside def _getInterpolationMat(self, loc, location_type="cell_centers", zeros_outside=False): """Produces interpolation matrix Parameters ---------- loc : numpy.ndarray Location of points to interpolate to location_type: stc What to interpolate location_type can be:: 'Ex', 'edges_x' -> x-component of field defined on x edges 'Ey', 'edges_y' -> y-component of field defined on y edges 'Ez', 'edges_z' -> z-component of field defined on z edges 'Fx', 'faces_x' -> x-component of field defined on x faces 'Fy', 'faces_y' -> y-component of field defined on y faces 'Fz', 'faces_z' -> z-component of field defined on z faces 'N', 'nodes' -> scalar field defined on nodes 'CC', 'cell_centers' -> scalar field defined on cell centers 'CCVx', 'cell_centers_x' -> x-component of vector field defined on cell centers 'CCVy', 'cell_centers_y' -> y-component of vector field defined on cell centers 'CCVz', 'cell_centers_z' -> z-component of vector field defined on cell centers Returns ------- scipy.sparse.csr_matrix M, the interpolation matrix """ loc = as_array_n_by_dim(loc, self.dim) if not zeros_outside: if not np.all(self.is_inside(loc)): raise ValueError("Points outside of mesh") else: indZeros = np.logical_not(self.is_inside(loc)) loc[indZeros, :] = np.array( [v.mean() for v in self.get_tensor("CC")]) location_type = self._parse_location_type(location_type) if location_type in [ "faces_x", "faces_y", "faces_z", "edges_x", "edges_y", "edges_z" ]: ind = {"x": 0, "y": 1, "z": 2}[location_type[-1]] if self.dim < ind: raise ValueError("mesh is not high enough dimension.") if "f" in location_type.lower(): items = (self.nFx, self.nFy, self.nFz)[:self.dim] else: items = (self.nEx, self.nEy, self.nEz)[:self.dim] components = [spzeros(loc.shape[0], n) for n in items] components[ind] = interpolation_matrix( loc, *self.get_tensor(location_type)) # remove any zero blocks (hstack complains) components = [comp for comp in components if comp.shape[1] > 0] Q = sp.hstack(components) elif location_type in ["cell_centers", "nodes"]: Q = interpolation_matrix(loc, *self.get_tensor(location_type)) elif location_type in [ "cell_centers_x", "cell_centers_y", "cell_centers_z" ]: Q = interpolation_matrix(loc, *self.get_tensor("CC")) Z = spzeros(loc.shape[0], self.nC) if location_type[-1] == "x": Q = sp.hstack([Q, Z, Z]) elif location_type[-1] == "y": Q = sp.hstack([Z, Q, Z]) elif location_type[-1] == "z": Q = sp.hstack([Z, Z, Q]) else: raise NotImplementedError("getInterpolationMat: location_type==" + location_type + " and mesh.dim==" + str(self.dim)) if zeros_outside: Q[indZeros, :] = 0 return Q.tocsr() def get_interpolation_matrix(self, loc, location_type="cell_centers", zeros_outside=False, **kwargs): """Produces linear interpolation matrix Parameters ---------- loc : numpy.ndarray Location of points to interpolate to location_type : str What to interpolate (see below) location_type can be:: 'Ex', 'edges_x' -> x-component of field defined on x edges 'Ey', 'edges_y' -> y-component of field defined on y edges 'Ez', 'edges_z' -> z-component of field defined on z edges 'Fx', 'faces_x' -> x-component of field defined on x faces 'Fy', 'faces_y' -> y-component of field defined on y faces 'Fz', 'faces_z' -> z-component of field defined on z faces 'N', 'nodes' -> scalar field defined on nodes 'CC', 'cell_centers' -> scalar field defined on cell centers 'CCVx', 'cell_centers_x' -> x-component of vector field defined on cell centers 'CCVy', 'cell_centers_y' -> y-component of vector field defined on cell centers 'CCVz', 'cell_centers_z' -> z-component of vector field defined on cell centers Returns ------- scipy.sparse.csr_matrix M, the interpolation matrix """ if "locType" in kwargs: warnings.warn( "The locType keyword argument has been deprecated, please use location_type. " "This will be removed in discretize 1.0.0", DeprecationWarning, ) location_type = kwargs["locType"] if "zerosOutside" in kwargs: warnings.warn( "The zerosOutside keyword argument has been deprecated, please use zeros_outside. " "This will be removed in discretize 1.0.0", DeprecationWarning, ) zeros_outside = kwargs["zerosOutside"] return self._getInterpolationMat(loc, location_type, zeros_outside) def _fastInnerProduct(self, projection_type, model=None, invert_model=False, invert_matrix=False): """Fast version of getFaceInnerProduct. This does not handle the case of a full tensor property. Parameters ---------- model : numpy.array material property (tensor properties are possible) at each cell center (nC, (1, 3, or 6)) projection_type : str 'edges' or 'faces' returnP : bool returns the projection matrices invert_model : bool inverts the material property invert_matrix : bool inverts the matrix Returns ------- scipy.sparse.csr_matrix M, the inner product matrix (nF, nF) """ projection_type = projection_type[0].upper() if projection_type not in ["F", "E"]: raise ValueError( "projection_type must be 'F' for faces or 'E' for edges") if model is None: model = np.ones(self.nC) if invert_model: model = 1.0 / model if is_scalar(model): model = model * np.ones(self.nC) # number of elements we are averaging (equals dim for regular # meshes, but for cyl, where we use symmetry, it is 1 for edge # variables and 2 for face variables) if self._meshType == "CYL": shape = getattr(self, "vn" + projection_type) n_elements = sum([1 if x != 0 else 0 for x in shape]) else: n_elements = self.dim # Isotropic? or anisotropic? if model.size == self.nC: Av = getattr(self, "ave" + projection_type + "2CC") Vprop = self.cell_volumes * mkvc(model) M = n_elements * sdiag(Av.T * Vprop) elif model.size == self.nC * self.dim: Av = getattr(self, "ave" + projection_type + "2CCV") # if cyl, then only certain components are relevant due to symmetry # for faces, x, z matters, for edges, y (which is theta) matters if self._meshType == "CYL": if projection_type == "E": model = model[:, 1] # this is the action of a projection mat elif projection_type == "F": model = model[:, [0, 2]] V = sp.kron(sp.identity(n_elements), sdiag(self.cell_volumes)) M = sdiag(Av.T * V * mkvc(model)) else: return None if invert_matrix: return sdinv(M) else: return M def _fastInnerProductDeriv(self, projection_type, model, invert_model=False, invert_matrix=False): """ Parameters ---------- projection_type : str 'E' or 'F' tensorType : TensorType type of the tensor invert_model : bool inverts the material property invert_matrix : bool inverts the matrix Returns ------- function dMdmu, the derivative of the inner product matrix """ projection_type = projection_type[0].upper() if projection_type not in ["F", "E"]: raise ValueError( "projection_type must be 'F' for faces or 'E' for edges") tensorType = TensorType(self, model) dMdprop = None if invert_matrix or invert_model: MI = self._fastInnerProduct(projection_type, model, invert_model=invert_model, invert_matrix=invert_matrix) # number of elements we are averaging (equals dim for regular # meshes, but for cyl, where we use symmetry, it is 1 for edge # variables and 2 for face variables) if self._meshType == "CYL": shape = getattr(self, "vn" + projection_type) n_elements = sum([1 if x != 0 else 0 for x in shape]) else: n_elements = self.dim if tensorType == 0: # isotropic, constant Av = getattr(self, "ave" + projection_type + "2CC") V = sdiag(self.cell_volumes) ones = sp.csr_matrix( (np.ones(self.nC), (range(self.nC), np.zeros(self.nC))), shape=(self.nC, 1), ) if not invert_matrix and not invert_model: dMdprop = n_elements * Av.T * V * ones elif invert_matrix and invert_model: dMdprop = n_elements * (sdiag(MI.diagonal()**2) * Av.T * V * ones * sdiag(1.0 / model**2)) elif invert_model: dMdprop = n_elements * Av.T * V * sdiag(-1.0 / model**2) elif invert_matrix: dMdprop = n_elements * (sdiag(-MI.diagonal()**2) * Av.T * V) elif tensorType == 1: # isotropic, variable in space Av = getattr(self, "ave" + projection_type + "2CC") V = sdiag(self.cell_volumes) if not invert_matrix and not invert_model: dMdprop = n_elements * Av.T * V elif invert_matrix and invert_model: dMdprop = n_elements * (sdiag(MI.diagonal()**2) * Av.T * V * sdiag(1.0 / model**2)) elif invert_model: dMdprop = n_elements * Av.T * V * sdiag(-1.0 / model**2) elif invert_matrix: dMdprop = n_elements * (sdiag(-MI.diagonal()**2) * Av.T * V) elif tensorType == 2: # anisotropic Av = getattr(self, "ave" + projection_type + "2CCV") V = sp.kron(sp.identity(self.dim), sdiag(self.cell_volumes)) if self._meshType == "CYL": Zero = sp.csr_matrix((self.nC, self.nC)) Eye = sp.eye(self.nC) if projection_type == "E": P = sp.hstack([Zero, Eye, Zero]) # print(P.todense()) elif projection_type == "F": P = sp.vstack([ sp.hstack([Eye, Zero, Zero]), sp.hstack([Zero, Zero, Eye]) ]) # print(P.todense()) else: P = sp.eye(self.nC * self.dim) if not invert_matrix and not invert_model: dMdprop = Av.T * P * V elif invert_matrix and invert_model: dMdprop = (sdiag(MI.diagonal()**2) * Av.T * P * V * sdiag(1.0 / model**2)) elif invert_model: dMdprop = Av.T * P * V * sdiag(-1.0 / model**2) elif invert_matrix: dMdprop = sdiag(-MI.diagonal()**2) * Av.T * P * V if dMdprop is not None: def innerProductDeriv(v=None): if v is None: warnings.warn( "Depreciation Warning: TensorMesh.innerProductDeriv." " You should be supplying a vector. " "Use: sdiag(u)*dMdprop", DeprecationWarning, ) return dMdprop return sdiag(v) * dMdprop return innerProductDeriv else: return None # DEPRECATED @property def hx(self): """Width of cells in the x direction Returns ------- numpy.ndarray .. deprecated:: 0.5.0 `hx` will be removed in discretize 1.0.0 to reduce namespace clutter, please use `mesh.h[0]`. """ warnings.warn("hx has been deprecated, please access as mesh.h[0]", DeprecationWarning) return self.h[0] @property def hy(self): """Width of cells in the y direction Returns ------- numpy.ndarray or None .. deprecated:: 0.5.0 `hy` will be removed in discretize 1.0.0 to reduce namespace clutter, please use `mesh.h[1]`. """ warnings.warn("hy has been deprecated, please access as mesh.h[1]", DeprecationWarning) return None if self.dim < 2 else self.h[1] @property def hz(self): """Width of cells in the z direction Returns ------- numpy.ndarray or None .. deprecated:: 0.5.0 `hz` will be removed in discretize 1.0.0 to reduce namespace clutter, please use `mesh.h[2]`. """ warnings.warn("hz has been deprecated, please access as mesh.h[2]", DeprecationWarning) return None if self.dim < 3 else self.h[2] vectorNx = deprecate_property("nodes_x", "vectorNx", removal_version="1.0.0") vectorNy = deprecate_property("nodes_y", "vectorNy", removal_version="1.0.0") vectorNz = deprecate_property("nodes_z", "vectorNz", removal_version="1.0.0") vectorCCx = deprecate_property("cell_centers_x", "vectorCCx", removal_version="1.0.0") vectorCCy = deprecate_property("cell_centers_y", "vectorCCy", removal_version="1.0.0") vectorCCz = deprecate_property("cell_centers_z", "vectorCCz", removal_version="1.0.0") getInterpolationMat = deprecate_method("get_interpolation_matrix", "getInterpolationMat", removal_version="1.0.0") isInside = deprecate_method("is_inside", "isInside", removal_version="1.0.0") getTensor = deprecate_method("get_tensor", "getTensor", removal_version="1.0.0")
class HasColorTuple(properties.HasProperties): ccc = properties.Tuple('tuple of colors', properties.Color(''))