Path: blob/master/src/sage/plot/plot3d/implicit_surface.pyx
8815 views
r"""1Graphics 3D object for representing and triangulating isosurfaces.23AUTHORS:45- Robert Hanson (2007): initial Java version, in Jmol.6- Carl Witty (2009-01): first Cython version.7- Bill Cauchois (2009): improvements for inclusion into Sage.8"""910#*****************************************************************************11# Copyright (C) 2009 Carl Witty <[email protected]>12#13# Distributed under the terms of the GNU General Public License (GPL)14#15# This code is distributed in the hope that it will be useful,16# but WITHOUT ANY WARRANTY; without even the implied warranty of17# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU18# General Public License for more details.19#20# The full text of the GPL is available at:21#22# http://www.gnu.org/licenses/23#*****************************************************************************2425# Pieces of this file are strongly based on the marching cubes26# implementation in Jmol located at src/org/jmol/jvxl/calc/MarchingCubes.java.27# The data tables are in fact directly copied from there.2829#*****************************************************************************30# This copyright is inherited from MarchingCubes.java in the Jmol source code.31#32# * Copyright (C) 2007 Miguel, Bob, Jmol Development33# *34# * Contact: [email protected]35# *36# * This library is free software; you can redistribute it and/or37# * modify it under the terms of the GNU Lesser General Public38# * License as published by the Free Software Foundation; either39# * version 2.1 of the License, or (at your option) any later version.40# *41# * This library is distributed in the hope that it will be useful,42# * but WITHOUT ANY WARRANTY; without even the implied warranty of43# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU44# * Lesser General License for more details.45# *46# * You should have received a copy of the GNU Lesser General Public47# * License along with this library; if not, write to the Free Software48# * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.49#*****************************************************************************505152# There's a framework in here for computing multiple isosurfaces of a53# single function. Currently, it's only used for a single54# implicit_plot3d where contour=... is given a list, but it's ready to55# be extended. I think the best UI would be if animate(...) and56# show(...) had a prepass where they went through their rendering57# trees, found ImplicitSurface objects with the same function,58# bounding box, and plot_points (other parameters, such as contours,59# hole, jmol_color, vertex_color, would be allowed to be different),60# and arranged to have them all plotted simultaneously. These61# prepasses have not been written yet. Combining multiple62# ImplicitSurface plots would be particularly advantageous for animate(...),63# but for a big animation, watch out for memory usage.6465# If you have a reasonably simple surface (not a space-filling fractal),66# then if n is your resolution, we have n^3 evaluations of the main67# function, about n^2 evaluations of auxiliary functions (hole, gradient,68# vertex_color/jmol_color), and output of size about n^2.6970# With this in mind, we pay particular attention to optimizing the n^371# function evaluations. (But keep in mind that n may be as small as 2072# or so, so we shouldn't ignore the efficiency of the n^2 parts.)7374# Also, we make sure not to ever allocate O(n^3) memory; we do the75# computation a slice at a time to avoid this. (Jmol always allocates76# n^3 memory when it reads the JVXL file, but that might be on a different77# computer; Tachyon would only allocate memory proportional to the78# output size.)7980from cStringIO import StringIO8182cimport numpy as np83import numpy as np8485from sage.plot.plot3d.transform cimport point_c, face_c, Transformation86from sage.plot.plot3d.base cimport PrimitiveObject87from sage.plot.plot3d.base import RenderParams, default_texture88from sage.plot.plot3d.index_face_set cimport IndexFaceSet89from sage.rings.all import RDF90from sage.plot.misc import setup_for_eval_on_grid9192include 'sage/ext/cdefs.pxi'93include 'sage/ext/stdsage.pxi'94include 'sage/gsl/gsl.pxi'95from cpython.string cimport *9697include "point_c.pxi"9899# The default value for plot_points (a keyword argument to implicit_plot3d()),100# assumed when plot_points is set to "automatic".101DEFAULT_PLOT_POINTS = 40102103cdef double nan = float(RDF('NaN'))104105cdef inline bint marching_has_edge(double a, double b, double contour, double *f, bint *has_nan):106# XXX Would be nicer to use isnan(), because it's inlined.107# Is it portable enough?108if gsl_isnan(a) or gsl_isnan(b):109has_nan[0] = True110return False111112has_nan[0] = False113114if (a >= contour) == (b >= contour):115return False116117f[0] = (contour - a) / (b - a)118return True119120# Returns 0 or 1121cdef inline int marching_is_inside(double v, double contour):122return gsl_isnan(v) or v < contour123124cdef void interpolate_point_c(point_c *result, double frac, point_c *inputs):125result[0].x = inputs[0].x + frac*(inputs[1].x - inputs[0].x)126result[0].y = inputs[0].y + frac*(inputs[1].y - inputs[0].y)127result[0].z = inputs[0].z + frac*(inputs[1].z - inputs[0].z)128129cdef class VertexInfo:130# The point in "integer space"131cdef point_c pt132133# The gradient at this point in "evaluation space"134cdef point_c gradient135136# (R,G,B), so not really a point at all137cdef point_c color138139# This point in "evaluation space"140cdef point_c eval_pt141142cdef void update_eval_pt(self, point_c *eval_min, point_c *eval_scale):143"""144Use eval_min and eval_scale to transform self.pt into evaluation space145and store the result in self.eval_pt.146"""147self.eval_pt.x = eval_min[0].x + eval_scale[0].x*self.pt.x148self.eval_pt.y = eval_min[0].y + eval_scale[0].y*self.pt.y149self.eval_pt.z = eval_min[0].z + eval_scale[0].z*self.pt.z150151def __repr__(self):152"""153TESTS::154155sage: from sage.plot.plot3d.implicit_surface import VertexInfo156sage: VertexInfo()157<0.0, 0.0, 0.0>158"""159return '<%s, %s, %s>' % (self.pt.x, self.pt.y, self.pt.z)160161cdef mk_VertexInfo(double x, double y, double z, point_c *eval_min, point_c *eval_scale):162cdef VertexInfo v163v = PY_NEW(VertexInfo)164v.pt.x = x165v.pt.y = y166v.pt.z = z167168v.update_eval_pt(eval_min, eval_scale)169170return v171172cdef class MarchingCubes:173r"""174Handles marching cube rendering.175176Protocol:1771781. Create the class.1792. Call process_slice once for each X slice, from self.nx > x >= 0.1803. Call finish(), which returns a list of strings.181182Note: Actually, only 4 slices ever exist; the caller will re-use old183storage.184"""185186cdef readonly object xrange187cdef readonly object yrange188cdef readonly object zrange189cdef readonly double contour190cdef readonly int nx191cdef readonly int ny192cdef readonly int nz193cdef readonly Transformation transform194cdef readonly object region195cdef readonly object gradient196cdef readonly bint smooth197cdef readonly object vertex_color198cdef readonly object results199200# We deal with three coordinate systems. We do most of our work201# in an integral coordinate system where x ranges from202# 0 <= x < self.nx, etc.; we do function evaluations where203# self.xrange[0] <= x <= self.xrange[1], etc.; and output204# is in a third coordinate system.205# (Note that in "integer space", function evaluations of the main206# function happen on integer coordinates; but function evaluations207# of the auxiliary functions will have one non-integer coordinate.)208209# These parameters convert from integer space to function-evaluation210# space: eval_coord = eval_min + int_coord * eval_scale211cdef point_c eval_min212cdef point_c eval_scale213214# The componentwise reciprocal of eval_scale; just used to change215# some divisions into multiplications216cdef point_c eval_scale_inv217218cdef point_c out_origin, out_plus_x, out_plus_y, out_plus_z219220def __init__(self, xrange, yrange, zrange, contour, plot_points,221transform=None, region=None, gradient=None, smooth=True, vertex_color=None):222"""223TESTS:224225Marching cubes is an abstract base class, you can't do anything with it::226227sage: from sage.plot.plot3d.implicit_surface import MarchingCubes228sage: cube_marcher = MarchingCubes((0, 1), (0, 1), (0, 1), 1, (10, 10, 10))229"""230231self.xrange = xrange232self.yrange = yrange233self.zrange = zrange234self.contour = contour235self.nx = plot_points[0]236self.ny = plot_points[1]237self.nz = plot_points[2]238self.transform = transform239self.region = region240self.gradient = gradient241self.smooth = smooth242self.vertex_color = vertex_color243244self.eval_min.x = xrange[0]245self.eval_scale.x = (xrange[1] - xrange[0]) / (self.nx - 1)246self.eval_min.y = yrange[0]247self.eval_scale.y = (yrange[1] - yrange[0]) / (self.ny - 1)248self.eval_min.z = zrange[0]249self.eval_scale.z = (zrange[1] - zrange[0]) / (self.nz - 1)250self.eval_scale_inv.x = 1/self.eval_scale.x251self.eval_scale_inv.y = 1/self.eval_scale.y252self.eval_scale_inv.z = 1/self.eval_scale.z253254cdef point_c zero_pt, origin, plus_x, plus_y, plus_z255zero_pt.x = 0; zero_pt.y = 0; zero_pt.z = 0256origin = self.eval_min257plus_x = zero_pt; plus_x.x = self.eval_scale.x258plus_y = zero_pt; plus_y.y = self.eval_scale.y259plus_z = zero_pt; plus_z.z = self.eval_scale.z260if self.transform is not None:261self.transform.transform_point_c(&self.out_origin, origin)262self.transform.transform_point_c(&self.out_plus_x, plus_x)263self.transform.transform_point_c(&self.out_plus_y, plus_y)264self.transform.transform_point_c(&self.out_plus_z, plus_z)265else:266self.out_origin = origin267self.out_plus_x = plus_x268self.out_plus_y = plus_y269self.out_plus_z = plus_z270271self.results = []272273def finish(self):274"""275Returns the results of the marching cubes algorithm as a list. The format276is specific to the subclass implementing this method.277278TESTS:279280By default, it returns an empty list::281282sage: from sage.plot.plot3d.implicit_surface import MarchingCubes283sage: cube_marcher = MarchingCubes((0, 1), (0, 1), (0, 1), 1, (10, 10, 10), None)284sage: cube_marcher.finish()285[]286"""287return self.results288289cdef class MarchingCubesTriangles(MarchingCubes):290"""291A subclass of MarchingCubes that returns its results as a list of triangles,292including their vertices and normals (if smooth=True).293"""294295cdef readonly np.ndarray x_vertices296cdef readonly np.ndarray y_vertices297cdef readonly np.ndarray z_vertices298299cdef readonly np.ndarray y_vertices_swapped300cdef readonly np.ndarray z_vertices_swapped301302cdef readonly slices303304def __init__(self, *args, **kwargs):305"""306TESTS::307308sage: from sage.plot.plot3d.implicit_surface import MarchingCubesTriangles309sage: cube_marcher = MarchingCubesTriangles((0, 1), (0, 1), (0, 1), 1, (10, 10, 10))310"""311312MarchingCubes.__init__(self, *args, **kwargs)313314self.x_vertices = np.empty((self.ny, self.nz), dtype=object)315self.y_vertices = np.empty((2, self.ny-1, self.nz), dtype=object)316self.z_vertices = np.empty((2, self.ny, self.nz-1), dtype=object)317318# Create new arrays that share the same underlying data, but319# have 0 and 1 reversed for the first coordinate.320self.y_vertices_swapped = self.y_vertices[::-1, ...]321self.z_vertices_swapped = self.z_vertices[::-1, ...]322323self.slices = []324325def process_slice(self, unsigned int x, np.ndarray slice):326"""327Process a single slice of function evaluations at the specified x coordinate.328329EXAMPLES::330331sage: from sage.plot.plot3d.implicit_surface import MarchingCubesTriangles332sage: import numpy as np333sage: cube_marcher = MarchingCubesTriangles((-2, 2), (-2, 2), (-2, 2), 4, (10,)*3, smooth=False)334sage: f = lambda x, y, z: x^2 + y^2 + z^2335sage: slices = np.zeros((10, 10, 10), dtype=np.double)336sage: for x in reversed(xrange(0, 10)):337... for y in xrange(0, 10):338... for z in xrange(0, 10):339... slices[x, y, z] = f(*[a * (4 / 9) -2 for a in (x, y, z)])340... cube_marcher.process_slice(x, slices[x, :, :])341sage: faces = cube_marcher.finish()342sage: faces[0][0]343{'y': -1.1..., 'x': 1.5..., 'z': -0.5...}344345We render the isosurface using IndexFaceSet::346347sage: from sage.plot.plot3d.index_face_set import IndexFaceSet348sage: IndexFaceSet([tuple((p['x'], p['y'], p['z']) for p in face) for face in faces])349"""350# We go to a lot of effort to avoid repeating computations.351# (I hope that the related bookkeeping is actually faster352# than repeating the computations, but I haven't tested.)353354# We get evaluation points, one slice at a time. But we355# don't want to process slices of evaluation points;356# we want to process slices of cubes. (There is one fewer slice357# of cubes than there is of evaluation points.)358359# Without any caching, we would repeat this for each cube:360# Find which vertices are inside/outside the surface.361# For edges which cross the surface, interpolate to362# find the exact crossing location; these will be the vertices363# of triangles.364# Compute the color and surface normal for each of these vertices.365# Assemble the vertices into triangles, using the "marching cubes"366# tables.367368# The problem with this is that each vertex is actually shared369# among four neighbor cubes (except on the edge of the array),370# so we would be repeating work (potentially a lot of work, if371# the user-specified gradient or color routines are expensive)372# four times.373374# So we cache the information associated with each vertex.375# Let's call an edge an X, Y, or Z edge depending on which376# axis it is parallel to; and a vertex is an X, Y, or Z377# vertex depending on which kind of edge it lies on.378379# X vertices are shared between cubes that are all within a380# single cube slice. However, Y and Z vertices are shared381# between adjacent slices, so we need to keep those caches382# around. But we still need only two slices of vertex cache383# at a time (the two that are adjacent to the current cube slice).384385# To reduce memory allocations, we allocate these caches at the386# beginning of the run (they are ndarrays of objects). We387# set a VertexInfo object in the cache when we first compute it;388# then we look in the cache when we need it for the other three389# cubes.390391# The X vertex cache is a 2-D ndarray. The Y and Z vertex caches392# are 3-D, where the first (x) dimension is indexed by 0 or 1.393394# The Cython buffer interface (that we use for fast access to395# ndarray's) has to reinitialize itself separately in each396# function. For this reason, we use fewer, bigger functions;397# in particular, we don't call any functions per-cube that398# require buffer access.399400# To compute interpolated gradients using central differencing,401# we need up to 4 slices. Consider a gradient computed at402# an X vertex, between slices 1 and 2. This is interpolated403# between the gradient at slice 1 and the gradient at slice 2.404# Computing the gradient at slice 1 requires slices 0 and 2;405# computing the gradient at slice 2 requires slices 1 and 3.406407# This means we need to queue up slices and process them408# in a somewhat strange order. This function only does409# the queuing, and then passes all the real work off to410# other functions.411412self.slices = ([slice] + self.slices)[:4]413414if len(self.slices) >= 2:415self._update_yz_vertices(x+1,416self.slices[0],417self.slices[1],418self.slices[2] if len(self.slices) >= 3 else None)419420if len(self.slices) >= 3:421self._update_x_vertices(x+1,422self.slices[0],423self.slices[1],424self.slices[2],425self.slices[3] if len(self.slices) >= 4 else None)426self.process_cubes(self.slices[1], self.slices[2])427428if x == 0:429self._update_yz_vertices(x,430None,431self.slices[0],432self.slices[1])433434self._update_x_vertices(x,435None,436self.slices[0],437self.slices[1],438self.slices[2] if len(self.slices) >= 3 else None)439440self.process_cubes(self.slices[0], self.slices[1])441442cpdef _update_yz_vertices(self, int x, np.ndarray _prev, np.ndarray _cur, np.ndarray _next):443"""444TESTS::445446sage: from sage.plot.plot3d.implicit_surface import MarchingCubesTriangles447sage: import numpy as np448sage: cube_marcher = MarchingCubesTriangles((0, 1), (0, 1), (0, 1), 0, (2,)*3, smooth=False)449sage: prev_slice = next_slice = np.ones((2, 2), dtype=np.double)450sage: cur_slice = prev_slice.copy()451sage: cur_slice[0, 0] = -1 # Seed the slice data with an "interesting" value.452sage: cube_marcher._update_yz_vertices(1, prev_slice, cur_slice, next_slice)453sage: cube_marcher.z_vertices.tolist()454[[[<1.0, 0.0, 0.5>], [None]], [[None], [None]]]455sage: cube_marcher.y_vertices.tolist()456[[[<1.0, 0.5, 0.0>, None]], [[None, None]]]457sage: cube_marcher.x_vertices.any() # This shouldn't affect the X vertices.458"""459(self.y_vertices, self.y_vertices_swapped) = \460(self.y_vertices_swapped, self.y_vertices)461(self.z_vertices, self.z_vertices_swapped) = \462(self.z_vertices_swapped, self.z_vertices)463464cdef bint has_prev = (_prev is not None)465cdef bint has_next = (_next is not None)466467cdef np.ndarray[double, ndim=2] prev = _prev468cdef np.ndarray[double, ndim=2] cur = _cur469cdef np.ndarray[double, ndim=2] next = _next470471cdef np.ndarray[object, ndim=2] y_vertices = self.y_vertices[0,...]472cdef np.ndarray[object, ndim=2] z_vertices = self.z_vertices[0,...]473474cdef int ny = self.ny475cdef int nz = self.nz476477cdef int y478cdef int z479cdef VertexInfo v480cdef double frac481cdef bint has_nan482cdef point_c gradients[2]483cdef int i484for y from 0 <= y < ny - 1:485for z from 0 <= z < nz:486if marching_has_edge(cur[y,z], cur[y+1,z], self.contour, &frac, &has_nan):487v = mk_VertexInfo(x, y+frac, z, &self.eval_min, &self.eval_scale)488if self.region is not None:489if not self.in_region(v):490y_vertices[y,z] = None491continue492if self.smooth:493# We must compute a gradient.494if self.gradient is not None:495self.apply_point_func(&v.gradient, self.gradient, v)496else:497# Use central differencing.498for i from 0 <= i < 2:499self.get_gradient(&gradients[i],500x, y+i, z,501cur[y+i,z],502prev[y+i,z] if has_prev else 0,503next[y+i,z] if has_next else 0,504cur[y+i-1,z] if y+i>0 else 0,505cur[y+i+1,z] if y+i<ny-1 else 0,506cur[y+i,z-1] if z>0 else 0,507cur[y+i,z+1] if z<nz-1 else 0)508interpolate_point_c(&v.gradient, frac, gradients)509if self.vertex_color:510self.apply_point_func(&v.color, self.vertex_color, v)511y_vertices[y,z] = v512else:513y_vertices[y,z] = None514515# OK, that updated the Y vertices. Now we do almost exactly516# the same thing to update Z vertices.517for y from 0 <= y < ny:518for z from 0 <= z < nz - 1:519if marching_has_edge(cur[y,z], cur[y,z+1], self.contour, &frac, &has_nan):520v = mk_VertexInfo(x, y, z+frac, &self.eval_min, &self.eval_scale)521if self.region is not None:522if not self.in_region(v):523z_vertices[y,z] = None524continue525if self.smooth:526# We must compute a gradient.527if self.gradient is not None:528self.apply_point_func(&v.gradient, self.gradient, v)529else:530# Use central differencing.531for i from 0 <= i < 2:532self.get_gradient(&gradients[i],533x, y, z+i,534cur[y,z+i],535prev[y,z+i] if has_prev else 0,536next[y,z+i] if has_next else 0,537cur[y-1,z+i] if y>0 else 0,538cur[y+1,z+i] if y<ny-1 else 0,539cur[y,z+i-1] if z+i>0 else 0,540cur[y,z+i+1] if z+i<nz-1 else 0)541interpolate_point_c(&v.gradient, frac, gradients)542if self.vertex_color:543self.apply_point_func(&v.color, self.vertex_color, v)544z_vertices[y,z] = v545else:546z_vertices[y,z] = None547548cpdef _update_x_vertices(self, int x, np.ndarray _prev, np.ndarray _left, np.ndarray _right, np.ndarray _next):549"""550TESTS::551552sage: from sage.plot.plot3d.implicit_surface import MarchingCubesTriangles553sage: import numpy as np554sage: cube_marcher = MarchingCubesTriangles((0, 1), (0, 1), (0, 1), 0, (4, 2, 2), smooth=False)555sage: prev_slice = right_slice = next_slice = np.ones((2, 2), dtype=np.double)556sage: left_slice = prev_slice.copy()557sage: left_slice[1, 1] = -1558sage: cube_marcher._update_x_vertices(1, prev_slice, left_slice, right_slice, next_slice)559sage: cube_marcher.x_vertices.tolist()560[[None, None], [None, <1.5, 1.0, 1.0>]]561sage: cube_marcher.y_vertices.any() or cube_marcher.z_vertices.any() # This shouldn't affect the Y or Z vertices.562"""563cdef bint has_prev = (_prev is not None)564cdef bint has_next = (_next is not None)565566cdef np.ndarray[double, ndim=2] prev = _prev567cdef np.ndarray[double, ndim=2] left = _left568cdef np.ndarray[double, ndim=2] right = _right569cdef np.ndarray[double, ndim=2] next = _next570571cdef np.ndarray[object, ndim=2] x_vertices = self.x_vertices572573cdef int ny = self.ny574cdef int nz = self.nz575576cdef int y577cdef int z578579cdef VertexInfo v580cdef double frac581cdef bint has_nan582cdef point_c gradients[2]583for y from 0 <= y < ny:584for z from 0 <= z < nz:585if marching_has_edge(left[y,z], right[y,z], self.contour, &frac, &has_nan):586v = mk_VertexInfo(x+frac, y, z, &self.eval_min, &self.eval_scale)587if self.region is not None:588if not self.in_region(v):589x_vertices[y,z] = None590continue591if self.smooth:592# We must compute a gradient.593if self.gradient is not None:594self.apply_point_func(&v.gradient, self.gradient, v)595else:596# Use central differencing.597self.get_gradient(&gradients[0],598x, y, z,599left[y,z],600prev[y,z] if has_prev else 0,601right[y,z],602left[y-1,z] if y>0 else 0,603left[y+1,z] if y<ny-1 else 0,604left[y,z-1] if z>0 else 0,605left[y,z+1] if z<nz-1 else 0)606self.get_gradient(&gradients[1],607x, y, z,608right[y,z],609left[y,z],610next[y,z] if has_next else 0,611right[y-1,z] if y>0 else 0,612right[y+1,z] if y<ny-1 else 0,613right[y,z-1] if z>0 else 0,614right[y,z+1] if z<nz-1 else 0)615interpolate_point_c(&v.gradient, frac, gradients)616if self.vertex_color:617self.apply_point_func(&v.color, self.vertex_color, v)618x_vertices[y,z] = v619else:620x_vertices[y,z] = None621622cdef bint in_region(self, VertexInfo v):623return (self.region(v.eval_pt.x, v.eval_pt.y, v.eval_pt.z) > 0)624625cdef apply_point_func(self, point_c *pt, fn, VertexInfo v):626if isinstance(fn, tuple):627pt[0].x = fn[0](v.eval_pt.x, v.eval_pt.y, v.eval_pt.z)628pt[0].y = fn[1](v.eval_pt.x, v.eval_pt.y, v.eval_pt.z)629pt[0].z = fn[2](v.eval_pt.x, v.eval_pt.y, v.eval_pt.z)630else:631t = fn(v.eval_pt.x, v.eval_pt.y, v.eval_pt.z)632pt[0].x = t[0]633pt[0].y = t[1]634pt[0].z = t[2]635636cdef get_gradient(self,637point_c *g,638int x, int y, int z,639double center,640double lx, double ux,641double ly, double uy,642double lz, double uz):643# What a mess! It would be much nicer-looking code to pass slices644# in here and do the subscripting in here. Unfortunately,645# that would also be much slower, because we'd have to re-initialize646# the Cython buffer interface on each call.647648cdef double dx = ux - lx649cdef double dy = uy - ly650cdef double dz = uz - lz651652cdef double gx = dx * self.eval_scale_inv.x653cdef double gy = dy * self.eval_scale_inv.y654cdef double gz = dz * self.eval_scale_inv.z655656if x > 0 and x < self.nx - 1: gx *= 0.5657if y > 0 and y < self.ny - 1: gy *= 0.5658if z > 0 and z < self.nz - 1: gz *= 0.5659660g[0].x = gx661g[0].y = gy662g[0].z = gz663664cpdef process_cubes(self, np.ndarray _left, np.ndarray _right):665"""666TESTS::667668sage: from sage.plot.plot3d.implicit_surface import MarchingCubesTriangles669sage: import numpy as np670sage: cube_marcher = MarchingCubesTriangles((0, 1), (0, 1), (0, 1), 0, (3, 2, 2), smooth=False)671sage: slices = [np.ones((2, 2), dtype=np.double) for i in xrange(0, 3)]672sage: slices[0][1, 1] = -1673sage: cube_marcher._update_yz_vertices(0, None, slices[0], slices[1])674sage: cube_marcher._update_x_vertices(0, None, slices[0], slices[1], slices[2])675sage: cube_marcher.process_cubes(slices[0], slices[1])676sage: cube_marcher.finish()677[({'y': 1.0, 'x': 0.0, 'z': 0.5}, {'y': 1.0, 'x': 0.25, 'z': 1.0}, {'y': 0.5, 'x': 0.0, 'z': 1.0})]678"""679cdef np.ndarray[double, ndim=2] left = _left680cdef np.ndarray[double, ndim=2] right = _right681682cdef np.ndarray[object, ndim=2] x_vertices = self.x_vertices683cdef np.ndarray[object, ndim=3] y_vertices = self.y_vertices684cdef np.ndarray[object, ndim=3] z_vertices = self.z_vertices685686cdef int ny = self.ny687cdef int nz = self.nz688689cdef int y690cdef int z691692# based on generateSurfaceData in MarchingCubes.java693cdef int insideMask694695# Cool ASCII art from MarchingCubes.java:696# * Y697# * 4 --------4--------- 5698# * /| /|699# * / | / |700# * / | / |701# * 7 8 5 |702# * / | / 9703# * / | / |704# * 7 --------6--------- 6 |705# * | | | |706# * | 0 ---------0--|----- 1 X707# * | / | /708# * 11 / 10 /709# * | 3 | 1710# * | / | /711# * | / | /712# * 3 ---------2-------- 2713# * Z714715# We see from the above that vertices are labeled 0 to 7, and716# edges are labeled 0 to 11.717718cdef list all_vertex_info = [None] * 12719cdef tuple my_triangles720721cdef int i722723for y from 0 <= y < ny-1:724for z from 0 <= z < nz-1:725# For each vertex (0 to 7), set the corresponding bit726# of insideMask iff the vertex is inside the surface.727insideMask = 0728insideMask |= marching_is_inside(left[y,z], self.contour)<<0729insideMask |= marching_is_inside(right[y,z], self.contour)<<1730insideMask |= marching_is_inside(right[y,z+1], self.contour)<<2731insideMask |= marching_is_inside(left[y,z+1], self.contour)<<3732insideMask |= marching_is_inside(left[y+1,z], self.contour)<<4733insideMask |= marching_is_inside(right[y+1,z], self.contour)<<5734insideMask |= marching_is_inside(right[y+1,z+1], self.contour)<<6735insideMask |= marching_is_inside(left[y+1,z+1], self.contour)<<7736737if insideMask == 0 or insideMask == 255: continue738739# OK, we have a cube on the surface. Copy all of the vertex740# info into an array for easier reference.741742all_vertex_info[0] = x_vertices[y,z]743all_vertex_info[1] = z_vertices[1,y,z]744all_vertex_info[2] = x_vertices[y,z+1]745all_vertex_info[3] = z_vertices[0,y,z]746all_vertex_info[4] = x_vertices[y+1,z]747all_vertex_info[5] = z_vertices[1,y+1,z]748all_vertex_info[6] = x_vertices[y+1,z+1]749all_vertex_info[7] = z_vertices[0,y+1,z]750all_vertex_info[8] = y_vertices[0,y,z]751all_vertex_info[9] = y_vertices[1,y,z]752all_vertex_info[10]= y_vertices[1,y,z+1]753all_vertex_info[11]= y_vertices[0,y,z+1]754755my_triangles = triangle_table2[insideMask]756757for i in range(0, len(my_triangles), 4):758# In wireframe mode, my_triangles[i+3] specifies759# whether or not to draw the corresponding edge.760# See MarchingCubes.java for details.761self.add_triangle(all_vertex_info[my_triangles[i]],762all_vertex_info[my_triangles[i+1]],763all_vertex_info[my_triangles[i+2]])764765cpdef add_triangle(self, VertexInfo v1, VertexInfo v2, VertexInfo v3):766"""767Called when a new triangle is generated by the marching cubes algorithm768to update the results array.769770TESTS::771772sage: from sage.plot.plot3d.implicit_surface import MarchingCubesTriangles, VertexInfo773sage: cube_marcher = MarchingCubesTriangles((0, 1), (0, 1), (0, 1), 0, (10,)*3, smooth=False)774sage: cube_marcher.add_triangle(VertexInfo(), VertexInfo(), VertexInfo())775sage: cube_marcher.finish()776[({'y': 0.0, 'x': 0.0, 'z': 0.0}, {'y': 0.0, 'x': 0.0, 'z': 0.0}, {'y': 0.0, 'x': 0.0, 'z': 0.0})]777"""778779if v1 is None or v2 is None or v3 is None:780# This happens if there is a NaN nearby, or if a hole was specified here.781return782783cdef:784point_c v1_ev_pt, v2_ev_pt, v3_ev_pt785point_c n1_ev_vec, n2_ev_vec, n3_ev_vec786787if self.transform is not None:788self.transform.transform_point_c(&v1_ev_pt, v1.eval_pt)789self.transform.transform_point_c(&v2_ev_pt, v2.eval_pt)790self.transform.transform_point_c(&v3_ev_pt, v3.eval_pt)791else:792v1_ev_pt = v1.eval_pt793v2_ev_pt = v2.eval_pt794v3_ev_pt = v3.eval_pt795face = (v1_ev_pt, v2_ev_pt, v3_ev_pt)796797if self.smooth:798# XXX I believe this is wrong for non-uniform transforms799if self.transform is not None:800self.transform.transform_vector_c(&n1_ev_vec, v1.gradient)801self.transform.transform_vector_c(&n2_ev_vec, v2.gradient)802self.transform.transform_vector_c(&n3_ev_vec, v3.gradient)803else:804n1_ev_vec = v1.gradient805n2_ev_vec = v2.gradient806n3_ev_vec = v3.gradient807face += (n1_ev_vec, n2_ev_vec, n3_ev_vec)808809self.results.append(face)810811cpdef render_implicit(f, xrange, yrange, zrange, plot_points, cube_marchers):812"""813INPUT:814815- ``f`` - a (fast!) callable function816817- ``xrange`` - a 2-tuple (x_min, x_max)818819- ``yrange`` - a 2-tuple (y_min, y_may)820821- ``zrange`` - a 2-tuple (z_min, z_maz)822823- ``plot_points`` - a triple of integers indicating the number of824function evaluations in each direction.825826- ``cube_marchers`` - a list of cube marchers, one for each contour.827828829OUTPUT:830831A representation of the isosurface, in the format specified by the individual832cube marchers.833834TESTS::835836sage: from sage.plot.plot3d.implicit_surface import render_implicit, MarchingCubesTriangles837sage: plot_points, f = (40,)*3, lambda x, y, z: x + y + z838sage: cube_marcher = MarchingCubesTriangles((0, 1), (0, 1), (0, 1), 1, (10,)*3)839sage: results = render_implicit(lambda x, y, z: x + y + z, \840... (0, 1), (0, 1), (0, 1), (10,)*3, [cube_marcher])841sage: results[0][0]842{'y': 0.0, 'x': 1.0, 'z': 0.0}843"""844845cdef int nx = plot_points[0]846cdef int ny = plot_points[1]847cdef int nz = plot_points[2]848849cdef point_c eval_min, eval_scale850851eval_min.x = xrange[0]852eval_scale.x = (xrange[1] - xrange[0]) / (nx - 1)853eval_min.y = yrange[0]854eval_scale.y = (yrange[1] - yrange[0]) / (ny - 1)855eval_min.z = zrange[0]856eval_scale.z = (zrange[1] - zrange[0]) / (nz - 1)857858# A possible future extension would be to allow passing in "f"859# as a numpy ndarray. If that were done, we could slightly modify860# the following code to just pass slices of f to the renderers861# (no copying of the data would be required).862863# The current marching cube renderers need only at most four slices at864# a time.865866cdef np.ndarray[double, ndim=3] data = np.zeros((4, ny, nz), dtype=np.double)867cdef np.ndarray[double, ndim=2] slice868869cdef unsigned int x, y, z870cdef int n871872cdef double eval_x, eval_y, eval_z873874cdef MarchingCubes marcher875876for n from 0 <= n < nx:877x = nx-1-n878eval_x = eval_min.x + eval_scale.x * x879slice = data[n % 4, :, :]880for y from 0 <= y < ny:881eval_y = eval_min.y + eval_scale.y * y882for z from 0 <= z < nz:883eval_z = eval_min.z + eval_scale.z * z884slice[y, z] = f(eval_x, eval_y, eval_z)885886for marcher in cube_marchers:887marcher.process_slice(x, slice)888889results = []890891for marcher in cube_marchers:892results.extend(marcher.finish())893894return results895896cdef class ImplicitSurface(IndexFaceSet):897cdef readonly object f898cdef readonly object vars899cdef readonly tuple xrange900cdef readonly tuple yrange901cdef readonly tuple zrange902cdef readonly list contours903cdef readonly object region904cdef readonly bint smooth905cdef readonly object gradient906cdef readonly object vertex_color907cdef readonly tuple plot_points908909def __init__(self, f, xrange, yrange, zrange,910contour=0, plot_points="automatic",911region=None, smooth=True, gradient=None, vertex_color=None,912**kwds):913"""914TESTS::915916sage: from sage.plot.plot3d.implicit_surface import ImplicitSurface917sage: var('x,y,z')918(x, y, z)919sage: G = ImplicitSurface(x^2 + y^2 + z^2, (x,-2, 2), (y,-2, 2), (z,-2, 2), contour=4)920sage: show(G)921"""922IndexFaceSet.__init__(self, [], [], **kwds)923from sage.ext.fast_eval import fast_float924925orig_f = f926self.f, ranges, self.vars = setup_for_eval_on_grid(f, [xrange, yrange, zrange], return_vars=True)927self.xrange = ranges[0][:2]928self.yrange = ranges[1][:2]929self.zrange = ranges[2][:2]930if isinstance(contour, (list, tuple)):931contours = contour932else:933contours = [contour]934self.contours = [float(c) for c in contours]935if region is not None:936self.region = fast_float(region, *self.vars)937938# Comments from Carl Witty, who first wrote this some of this code939# See Trac 9483940# When I first wrote the code, I had the idea to create a941# direct-to-tachyon backend that would use vertex normals942# to create much nicer-looking plots with smaller numbers943# of plot_points, and laid the groundwork for this backend944# with the gradient and smooth arguments. But I abandoned the945# code without writing this backend (and leaving many other parts946# of the code unfinished).947# When William Cauchois took over and finished the code (thank you,948# William!), he only wrote an IndexFaceSet backend, that can't949# (currently) make use of vertex normals. So the gradient code is950# currently useless.951# But it's still open for somebody to write a direct-to-tachyon backend,952# or to extend IndexFaceSet to support vertex normals.953954# Since IndexFaceSet doesn't even support smooth shading, we overwrite955# the passed-in smooth parameter.956smooth=False957958959self.smooth = smooth960if smooth and gradient is None:961try:962gradient = (orig_f.diff(self.vars[0]),963orig_f.diff(self.vars[1]),964orig_f.diff(self.vars[2]))965except Exception:966# Would be nice to have more nuanced error handling here.967968# If anything goes wrong, we'll just use central differencing.969pass970if gradient is not None:971self.gradient = fast_float(gradient, *self.vars)972if vertex_color is not None:973self.vertex_color = fast_float(vertex_color, *self.vars)974if plot_points == "automatic":975plot_points = DEFAULT_PLOT_POINTS976my_plot_points = []977for i in range(3):978if isinstance(plot_points, (list, tuple)):979n = int(plot_points[i])980else:981n = int(plot_points)982if n < 2:983raise ValueError984my_plot_points.append(n)985self.plot_points = tuple(my_plot_points)986987def bounding_box(self):988"""989Return a bounding box for the ``ImplicitSurface``, as a tuple of two9903-dimensional points.991992EXAMPLES:993994Note that the bounding box corresponds exactly to the x-, y-, and z- range::995996sage: from sage.plot.plot3d.implicit_surface import ImplicitSurface997sage: G = ImplicitSurface(0, (0, 1), (0, 1), (0, 1))998sage: G.bounding_box()999((0.0, 0.0, 0.0), (1.0, 1.0, 1.0))1000"""1001return ((self.xrange[0], self.yrange[0], self.zrange[0]),1002(self.xrange[1], self.yrange[1], self.zrange[1]))10031004def obj_repr(self, render_params):1005"""1006Return a representation of this object in the .obj format.10071008TESTS::10091010We graph a simple plane::10111012sage: from sage.plot.plot3d.implicit_surface import ImplicitSurface1013sage: var('x,y,z')1014(x, y, z)1015sage: G = ImplicitSurface(x + y + z, (x,-1, 1), (y,-1, 1), (z,-1, 1))1016sage: obj = G.obj_repr(G.default_render_params())1017sage: vertices = obj[2]10181019The number of vertices in the OBJ representation should equal the number1020of vertices in the face set::10211022sage: len(vertices) == len(G.vertex_list())1023True10241025The vertices in the OBJ representation should also be approximately equal1026to the vertices in the face set -- the small error is due to rounding1027which occurs during output (we test only the first 20 points for the1028sake of speed)::10291030sage: def points_equal(a, b, epsilon=(1e-5)):1031... return all(abs(x0-x1) < epsilon for x0, x1 in zip(a, b))1032sage: list = []1033sage: assert len(vertices) >= 20 # I should hope so, we're rendering at the default resolution!1034sage: for vertex, surf_vertex in zip(vertices, G.vertex_list())[0:20]:1035... list.append(points_equal(map(float, vertex.split(' ')[1:]), surf_vertex))1036sage: all(list)1037True1038"""1039self.triangulate()1040return IndexFaceSet.obj_repr(self, render_params)10411042def tachyon_repr(self, render_params):1043"""1044Return a representation of this object suitable for use with the Tachyon1045renderer.10461047TESTS::10481049sage: from sage.plot.plot3d.implicit_surface import ImplicitSurface1050sage: var('x,y,z')1051(x, y, z)1052sage: G = ImplicitSurface(x + y + z, (x,-1, 1), (y,-1, 1), (z,-1, 1))1053sage: G.tachyon_repr(G.default_render_params())[0].startswith('TRI')1054True1055"""1056self.triangulate()1057return IndexFaceSet.tachyon_repr(self, render_params)10581059def jmol_repr(self, render_params):1060"""1061Return a representation of this object suitable for use with the Jmol1062renderer.10631064TESTS::10651066sage: from sage.plot.plot3d.implicit_surface import ImplicitSurface1067sage: var('x,y,z')1068(x, y, z)1069sage: G = ImplicitSurface(x + y + z, (x,-1, 1), (y,-1, 1), (z,-1, 1))1070sage: show(G, viewer='jmol')1071"""1072self.triangulate()1073return IndexFaceSet.jmol_repr(self, render_params)10741075def json_repr(self, render_params):1076"""1077Return a representation of this object in JavaScript Object Notation (JSON).10781079TESTS::10801081sage: from sage.plot.plot3d.implicit_surface import ImplicitSurface1082sage: var('x,y,z')1083(x, y, z)1084sage: G = ImplicitSurface(x + y + z, (x,-1, 1), (y,-1, 1), (z,-1, 1))1085sage: G.json_repr(G.default_render_params())[0].startswith('{vertices:')1086True1087"""1088self.triangulate()1089return IndexFaceSet.json_repr(self, render_params)10901091def triangulate(self, force=False):1092"""1093The IndexFaceSet will be empty until you call this method, which generates1094the faces and vertices according to the parameters specified in the1095constructor for ImplicitSurface. Note that if you call this method more1096than once, subsequent invocations will have no effect (this is an1097optimization to avoid repeated work) unless you specify force=True in the1098keywords.10991100EXAMPLES::11011102sage: from sage.plot.plot3d.implicit_surface import ImplicitSurface1103sage: var('x,y,z')1104(x, y, z)1105sage: G = ImplicitSurface(x + y + z, (x,-1, 1), (y,-1, 1), (z,-1, 1))1106sage: len(G.vertex_list()), len(G.face_list())1107(0, 0)1108sage: G.triangulate()1109sage: len(G.vertex_list()) > 0, len(G.face_list()) > 01110(True, True)1111sage: G.show() # This should be fast, since the mesh is already triangulated.1112"""1113if self.fcount != 0 and not force:1114# The mesh is already triangulated1115return11161117options = dict(xrange=self.xrange, yrange=self.yrange, zrange=self.zrange,1118region=self.region, smooth=self.smooth,1119gradient=self.gradient,1120vertex_color=self.vertex_color,1121plot_points=self.plot_points)1122cube_marchers = [MarchingCubesTriangles(contour=x, **options) for x in self.contours]1123results = render_implicit(self.f, self.xrange, self.yrange, self.zrange,1124self.plot_points, cube_marchers)1125cdef:1126face_c* dest_face1127point_c* dest_vertex1128int fcount = len(results)11291130self.realloc(fcount * 3, fcount, fcount * 3)1131for i from 0 <= i < fcount:1132dest_face = &self._faces[i]1133src_face = results[i]11341135dest_face.n = 31136dest_face.vertices = &self.face_indices[3 * i]11371138for j from 0 <= j < 3:1139dest_face.vertices[j] = (3 * i) + j1140dest_vertex = &self.vs[(3 * i) + j]1141dest_vertex.x = src_face[j]['x']1142dest_vertex.y = src_face[j]['y']1143dest_vertex.z = src_face[j]['z']11441145self.vcount = fcount * 31146self.fcount = fcount1147self.icount = fcount * 311481149# Data table (courtesy of MarchingCubes.java)1150triangle_table2 = ( None,1151( 0, 8, 3, 7 ),1152( 0, 1, 9, 7 ), ( 1, 8, 3, 6, 9, 8, 1, 5 ), ( 1, 2, 10, 7 ),1153( 0, 8, 3, 7, 1, 2, 10, 7 ), ( 9, 2, 10, 6, 0, 2, 9, 5 ),1154( 2, 8, 3, 6, 2, 10, 8, 1, 10, 9, 8, 3 ), ( 3, 11, 2, 7 ),1155( 0, 11, 2, 6, 8, 11, 0, 5 ), ( 1, 9, 0, 7, 2, 3, 11, 7 ),1156( 1, 11, 2, 6, 1, 9, 11, 1, 9, 8, 11, 3 ), ( 3, 10, 1, 6, 11, 10, 3, 5 ),1157( 0, 10, 1, 6, 0, 8, 10, 1, 8, 11, 10, 3 ),1158( 3, 9, 0, 6, 3, 11, 9, 1, 11, 10, 9, 3 ), ( 9, 8, 10, 5, 10, 8, 11, 6 ),1159( 4, 7, 8, 7 ), ( 4, 3, 0, 6, 7, 3, 4, 5 ), ( 0, 1, 9, 7, 8, 4, 7, 7 ),1160( 4, 1, 9, 6, 4, 7, 1, 1, 7, 3, 1, 3 ), ( 1, 2, 10, 7, 8, 4, 7, 7 ),1161( 3, 4, 7, 6, 3, 0, 4, 3, 1, 2, 10, 7 ),1162( 9, 2, 10, 6, 9, 0, 2, 3, 8, 4, 7, 7 ),1163( 2, 10, 9, 3, 2, 9, 7, 0, 2, 7, 3, 6, 7, 9, 4, 6 ),1164( 8, 4, 7, 7, 3, 11, 2, 7 ), ( 11, 4, 7, 6, 11, 2, 4, 1, 2, 0, 4, 3 ),1165( 9, 0, 1, 7, 8, 4, 7, 7, 2, 3, 11, 7 ),1166( 4, 7, 11, 3, 9, 4, 11, 1, 9, 11, 2, 2, 9, 2, 1, 6 ),1167( 3, 10, 1, 6, 3, 11, 10, 3, 7, 8, 4, 7 ),1168( 1, 11, 10, 6, 1, 4, 11, 0, 1, 0, 4, 3, 7, 11, 4, 5 ),1169( 4, 7, 8, 7, 9, 0, 11, 1, 9, 11, 10, 6, 11, 0, 3, 6 ),1170( 4, 7, 11, 3, 4, 11, 9, 4, 9, 11, 10, 6 ), ( 9, 5, 4, 7 ),1171( 9, 5, 4, 7, 0, 8, 3, 7 ), ( 0, 5, 4, 6, 1, 5, 0, 5 ),1172( 8, 5, 4, 6, 8, 3, 5, 1, 3, 1, 5, 3 ), ( 1, 2, 10, 7, 9, 5, 4, 7 ),1173( 3, 0, 8, 7, 1, 2, 10, 7, 4, 9, 5, 7 ),1174( 5, 2, 10, 6, 5, 4, 2, 1, 4, 0, 2, 3 ),1175( 2, 10, 5, 3, 3, 2, 5, 1, 3, 5, 4, 2, 3, 4, 8, 6 ),1176( 9, 5, 4, 7, 2, 3, 11, 7 ), ( 0, 11, 2, 6, 0, 8, 11, 3, 4, 9, 5, 7 ),1177( 0, 5, 4, 6, 0, 1, 5, 3, 2, 3, 11, 7 ),1178( 2, 1, 5, 3, 2, 5, 8, 0, 2, 8, 11, 6, 4, 8, 5, 5 ),1179( 10, 3, 11, 6, 10, 1, 3, 3, 9, 5, 4, 7 ),1180( 4, 9, 5, 7, 0, 8, 1, 5, 8, 10, 1, 2, 8, 11, 10, 3 ),1181( 5, 4, 0, 3, 5, 0, 11, 0, 5, 11, 10, 6, 11, 0, 3, 6 ),1182( 5, 4, 8, 3, 5, 8, 10, 4, 10, 8, 11, 6 ), ( 9, 7, 8, 6, 5, 7, 9, 5 ),1183( 9, 3, 0, 6, 9, 5, 3, 1, 5, 7, 3, 3 ),1184( 0, 7, 8, 6, 0, 1, 7, 1, 1, 5, 7, 3 ), ( 1, 5, 3, 5, 3, 5, 7, 6 ),1185( 9, 7, 8, 6, 9, 5, 7, 3, 10, 1, 2, 7 ),1186( 10, 1, 2, 7, 9, 5, 0, 5, 5, 3, 0, 2, 5, 7, 3, 3 ),1187( 8, 0, 2, 3, 8, 2, 5, 0, 8, 5, 7, 6, 10, 5, 2, 5 ),1188( 2, 10, 5, 3, 2, 5, 3, 4, 3, 5, 7, 6 ),1189( 7, 9, 5, 6, 7, 8, 9, 3, 3, 11, 2, 7 ),1190( 9, 5, 7, 3, 9, 7, 2, 0, 9, 2, 0, 6, 2, 7, 11, 6 ),1191( 2, 3, 11, 7, 0, 1, 8, 5, 1, 7, 8, 2, 1, 5, 7, 3 ),1192( 11, 2, 1, 3, 11, 1, 7, 4, 7, 1, 5, 6 ),1193( 9, 5, 8, 5, 8, 5, 7, 6, 10, 1, 3, 3, 10, 3, 11, 6 ),1194( 5, 7, 0, 1, 5, 0, 9, 6, 7, 11, 0, 1, 1, 0, 10, 5, 11, 10, 0, 1 ),1195( 11, 10, 0, 1, 11, 0, 3, 6, 10, 5, 0, 1, 8, 0, 7, 5, 5, 7, 0, 1 ),1196( 11, 10, 5, 3, 7, 11, 5, 5 ), ( 10, 6, 5, 7 ),1197( 0, 8, 3, 7, 5, 10, 6, 7 ), ( 9, 0, 1, 7, 5, 10, 6, 7 ),1198( 1, 8, 3, 6, 1, 9, 8, 3, 5, 10, 6, 7 ), ( 1, 6, 5, 6, 2, 6, 1, 5 ),1199( 1, 6, 5, 6, 1, 2, 6, 3, 3, 0, 8, 7 ),1200( 9, 6, 5, 6, 9, 0, 6, 1, 0, 2, 6, 3 ),1201( 5, 9, 8, 3, 5, 8, 2, 0, 5, 2, 6, 6, 3, 2, 8, 5 ),1202( 2, 3, 11, 7, 10, 6, 5, 7 ), ( 11, 0, 8, 6, 11, 2, 0, 3, 10, 6, 5, 7 ),1203( 0, 1, 9, 7, 2, 3, 11, 7, 5, 10, 6, 7 ),1204( 5, 10, 6, 7, 1, 9, 2, 5, 9, 11, 2, 2, 9, 8, 11, 3 ),1205( 6, 3, 11, 6, 6, 5, 3, 1, 5, 1, 3, 3 ),1206( 0, 8, 11, 3, 0, 11, 5, 0, 0, 5, 1, 6, 5, 11, 6, 6 ),1207( 3, 11, 6, 3, 0, 3, 6, 1, 0, 6, 5, 2, 0, 5, 9, 6 ),1208( 6, 5, 9, 3, 6, 9, 11, 4, 11, 9, 8, 6 ), ( 5, 10, 6, 7, 4, 7, 8, 7 ),1209( 4, 3, 0, 6, 4, 7, 3, 3, 6, 5, 10, 7 ),1210( 1, 9, 0, 7, 5, 10, 6, 7, 8, 4, 7, 7 ),1211( 10, 6, 5, 7, 1, 9, 7, 1, 1, 7, 3, 6, 7, 9, 4, 6 ),1212( 6, 1, 2, 6, 6, 5, 1, 3, 4, 7, 8, 7 ),1213( 1, 2, 5, 5, 5, 2, 6, 6, 3, 0, 4, 3, 3, 4, 7, 6 ),1214( 8, 4, 7, 7, 9, 0, 5, 5, 0, 6, 5, 2, 0, 2, 6, 3 ),1215( 7, 3, 9, 1, 7, 9, 4, 6, 3, 2, 9, 1, 5, 9, 6, 5, 2, 6, 9, 1 ),1216( 3, 11, 2, 7, 7, 8, 4, 7, 10, 6, 5, 7 ),1217( 5, 10, 6, 7, 4, 7, 2, 1, 4, 2, 0, 6, 2, 7, 11, 6 ),1218( 0, 1, 9, 7, 4, 7, 8, 7, 2, 3, 11, 7, 5, 10, 6, 7 ),1219( 9, 2, 1, 6, 9, 11, 2, 2, 9, 4, 11, 1, 7, 11, 4, 5, 5, 10, 6, 7 ),1220( 8, 4, 7, 7, 3, 11, 5, 1, 3, 5, 1, 6, 5, 11, 6, 6 ),1221( 5, 1, 11, 1, 5, 11, 6, 6, 1, 0, 11, 1, 7, 11, 4, 5, 0, 4, 11, 1 ),1222( 0, 5, 9, 6, 0, 6, 5, 2, 0, 3, 6, 1, 11, 6, 3, 5, 8, 4, 7, 7 ),1223( 6, 5, 9, 3, 6, 9, 11, 4, 4, 7, 9, 5, 7, 11, 9, 1 ),1224( 10, 4, 9, 6, 6, 4, 10, 5 ), ( 4, 10, 6, 6, 4, 9, 10, 3, 0, 8, 3, 7 ),1225( 10, 0, 1, 6, 10, 6, 0, 1, 6, 4, 0, 3 ),1226( 8, 3, 1, 3, 8, 1, 6, 0, 8, 6, 4, 6, 6, 1, 10, 6 ),1227( 1, 4, 9, 6, 1, 2, 4, 1, 2, 6, 4, 3 ),1228( 3, 0, 8, 7, 1, 2, 9, 5, 2, 4, 9, 2, 2, 6, 4, 3 ),1229( 0, 2, 4, 5, 4, 2, 6, 6 ), ( 8, 3, 2, 3, 8, 2, 4, 4, 4, 2, 6, 6 ),1230( 10, 4, 9, 6, 10, 6, 4, 3, 11, 2, 3, 7 ),1231( 0, 8, 2, 5, 2, 8, 11, 6, 4, 9, 10, 3, 4, 10, 6, 6 ),1232( 3, 11, 2, 7, 0, 1, 6, 1, 0, 6, 4, 6, 6, 1, 10, 6 ),1233( 6, 4, 1, 1, 6, 1, 10, 6, 4, 8, 1, 1, 2, 1, 11, 5, 8, 11, 1, 1 ),1234( 9, 6, 4, 6, 9, 3, 6, 0, 9, 1, 3, 3, 11, 6, 3, 5 ),1235( 8, 11, 1, 1, 8, 1, 0, 6, 11, 6, 1, 1, 9, 1, 4, 5, 6, 4, 1, 1 ),1236( 3, 11, 6, 3, 3, 6, 0, 4, 0, 6, 4, 6 ), ( 6, 4, 8, 3, 11, 6, 8, 5 ),1237( 7, 10, 6, 6, 7, 8, 10, 1, 8, 9, 10, 3 ),1238( 0, 7, 3, 6, 0, 10, 7, 0, 0, 9, 10, 3, 6, 7, 10, 5 ),1239( 10, 6, 7, 3, 1, 10, 7, 1, 1, 7, 8, 2, 1, 8, 0, 6 ),1240( 10, 6, 7, 3, 10, 7, 1, 4, 1, 7, 3, 6 ),1241( 1, 2, 6, 3, 1, 6, 8, 0, 1, 8, 9, 6, 8, 6, 7, 6 ),1242( 2, 6, 9, 1, 2, 9, 1, 6, 6, 7, 9, 1, 0, 9, 3, 5, 7, 3, 9, 1 ),1243( 7, 8, 0, 3, 7, 0, 6, 4, 6, 0, 2, 6 ), ( 7, 3, 2, 3, 6, 7, 2, 5 ),1244( 2, 3, 11, 7, 10, 6, 8, 1, 10, 8, 9, 6, 8, 6, 7, 6 ),1245( 2, 0, 7, 1, 2, 7, 11, 6, 0, 9, 7, 1, 6, 7, 10, 5, 9, 10, 7, 1 ),1246( 1, 8, 0, 6, 1, 7, 8, 2, 1, 10, 7, 1, 6, 7, 10, 5, 2, 3, 11, 7 ),1247( 11, 2, 1, 3, 11, 1, 7, 4, 10, 6, 1, 5, 6, 7, 1, 1 ),1248( 8, 9, 6, 1, 8, 6, 7, 6, 9, 1, 6, 1, 11, 6, 3, 5, 1, 3, 6, 1 ),1249( 0, 9, 1, 7, 11, 6, 7, 7 ),1250( 7, 8, 0, 3, 7, 0, 6, 4, 3, 11, 0, 5, 11, 6, 0, 1 ), ( 7, 11, 6, 7 ),1251( 7, 6, 11, 7 ), ( 3, 0, 8, 7, 11, 7, 6, 7 ),1252( 0, 1, 9, 7, 11, 7, 6, 7 ), ( 8, 1, 9, 6, 8, 3, 1, 3, 11, 7, 6, 7 ),1253( 10, 1, 2, 7, 6, 11, 7, 7 ), ( 1, 2, 10, 7, 3, 0, 8, 7, 6, 11, 7, 7 ),1254( 2, 9, 0, 6, 2, 10, 9, 3, 6, 11, 7, 7 ),1255( 6, 11, 7, 7, 2, 10, 3, 5, 10, 8, 3, 2, 10, 9, 8, 3 ),1256( 7, 2, 3, 6, 6, 2, 7, 5 ), ( 7, 0, 8, 6, 7, 6, 0, 1, 6, 2, 0, 3 ),1257( 2, 7, 6, 6, 2, 3, 7, 3, 0, 1, 9, 7 ),1258( 1, 6, 2, 6, 1, 8, 6, 0, 1, 9, 8, 3, 8, 7, 6, 3 ),1259( 10, 7, 6, 6, 10, 1, 7, 1, 1, 3, 7, 3 ),1260( 10, 7, 6, 6, 1, 7, 10, 4, 1, 8, 7, 2, 1, 0, 8, 3 ),1261( 0, 3, 7, 3, 0, 7, 10, 0, 0, 10, 9, 6, 6, 10, 7, 5 ),1262( 7, 6, 10, 3, 7, 10, 8, 4, 8, 10, 9, 6 ), ( 6, 8, 4, 6, 11, 8, 6, 5 ),1263( 3, 6, 11, 6, 3, 0, 6, 1, 0, 4, 6, 3 ),1264( 8, 6, 11, 6, 8, 4, 6, 3, 9, 0, 1, 7 ),1265( 9, 4, 6, 3, 9, 6, 3, 0, 9, 3, 1, 6, 11, 3, 6, 5 ),1266( 6, 8, 4, 6, 6, 11, 8, 3, 2, 10, 1, 7 ),1267( 1, 2, 10, 7, 3, 0, 11, 5, 0, 6, 11, 2, 0, 4, 6, 3 ),1268( 4, 11, 8, 6, 4, 6, 11, 3, 0, 2, 9, 5, 2, 10, 9, 3 ),1269( 10, 9, 3, 1, 10, 3, 2, 6, 9, 4, 3, 1, 11, 3, 6, 5, 4, 6, 3, 1 ),1270( 8, 2, 3, 6, 8, 4, 2, 1, 4, 6, 2, 3 ), ( 0, 4, 2, 5, 4, 6, 2, 3 ),1271( 1, 9, 0, 7, 2, 3, 4, 1, 2, 4, 6, 6, 4, 3, 8, 6 ),1272( 1, 9, 4, 3, 1, 4, 2, 4, 2, 4, 6, 6 ),1273( 8, 1, 3, 6, 8, 6, 1, 0, 8, 4, 6, 3, 6, 10, 1, 3 ),1274( 10, 1, 0, 3, 10, 0, 6, 4, 6, 0, 4, 6 ),1275( 4, 6, 3, 1, 4, 3, 8, 6, 6, 10, 3, 1, 0, 3, 9, 5, 10, 9, 3, 1 ),1276( 10, 9, 4, 3, 6, 10, 4, 5 ), ( 4, 9, 5, 7, 7, 6, 11, 7 ),1277( 0, 8, 3, 7, 4, 9, 5, 7, 11, 7, 6, 7 ),1278( 5, 0, 1, 6, 5, 4, 0, 3, 7, 6, 11, 7 ),1279( 11, 7, 6, 7, 8, 3, 4, 5, 3, 5, 4, 2, 3, 1, 5, 3 ),1280( 9, 5, 4, 7, 10, 1, 2, 7, 7, 6, 11, 7 ),1281( 6, 11, 7, 7, 1, 2, 10, 7, 0, 8, 3, 7, 4, 9, 5, 7 ),1282( 7, 6, 11, 7, 5, 4, 10, 5, 4, 2, 10, 2, 4, 0, 2, 3 ),1283( 3, 4, 8, 6, 3, 5, 4, 2, 3, 2, 5, 1, 10, 5, 2, 5, 11, 7, 6, 7 ),1284( 7, 2, 3, 6, 7, 6, 2, 3, 5, 4, 9, 7 ),1285( 9, 5, 4, 7, 0, 8, 6, 1, 0, 6, 2, 6, 6, 8, 7, 6 ),1286( 3, 6, 2, 6, 3, 7, 6, 3, 1, 5, 0, 5, 5, 4, 0, 3 ),1287( 6, 2, 8, 1, 6, 8, 7, 6, 2, 1, 8, 1, 4, 8, 5, 5, 1, 5, 8, 1 ),1288( 9, 5, 4, 7, 10, 1, 6, 5, 1, 7, 6, 2, 1, 3, 7, 3 ),1289( 1, 6, 10, 6, 1, 7, 6, 2, 1, 0, 7, 1, 8, 7, 0, 5, 9, 5, 4, 7 ),1290( 4, 0, 10, 1, 4, 10, 5, 6, 0, 3, 10, 1, 6, 10, 7, 5, 3, 7, 10, 1 ),1291( 7, 6, 10, 3, 7, 10, 8, 4, 5, 4, 10, 5, 4, 8, 10, 1 ),1292( 6, 9, 5, 6, 6, 11, 9, 1, 11, 8, 9, 3 ),1293( 3, 6, 11, 6, 0, 6, 3, 4, 0, 5, 6, 2, 0, 9, 5, 3 ),1294( 0, 11, 8, 6, 0, 5, 11, 0, 0, 1, 5, 3, 5, 6, 11, 3 ),1295( 6, 11, 3, 3, 6, 3, 5, 4, 5, 3, 1, 6 ),1296( 1, 2, 10, 7, 9, 5, 11, 1, 9, 11, 8, 6, 11, 5, 6, 6 ),1297( 0, 11, 3, 6, 0, 6, 11, 2, 0, 9, 6, 1, 5, 6, 9, 5, 1, 2, 10, 7 ),1298( 11, 8, 5, 1, 11, 5, 6, 6, 8, 0, 5, 1, 10, 5, 2, 5, 0, 2, 5, 1 ),1299( 6, 11, 3, 3, 6, 3, 5, 4, 2, 10, 3, 5, 10, 5, 3, 1 ),1300( 5, 8, 9, 6, 5, 2, 8, 0, 5, 6, 2, 3, 3, 8, 2, 5 ),1301( 9, 5, 6, 3, 9, 6, 0, 4, 0, 6, 2, 6 ),1302( 1, 5, 8, 1, 1, 8, 0, 6, 5, 6, 8, 1, 3, 8, 2, 5, 6, 2, 8, 1 ),1303( 1, 5, 6, 3, 2, 1, 6, 5 ),1304( 1, 3, 6, 1, 1, 6, 10, 6, 3, 8, 6, 1, 5, 6, 9, 5, 8, 9, 6, 1 ),1305( 10, 1, 0, 3, 10, 0, 6, 4, 9, 5, 0, 5, 5, 6, 0, 1 ),1306( 0, 3, 8, 7, 5, 6, 10, 7 ), ( 10, 5, 6, 7 ),1307( 11, 5, 10, 6, 7, 5, 11, 5 ), ( 11, 5, 10, 6, 11, 7, 5, 3, 8, 3, 0, 7 ),1308( 5, 11, 7, 6, 5, 10, 11, 3, 1, 9, 0, 7 ),1309( 10, 7, 5, 6, 10, 11, 7, 3, 9, 8, 1, 5, 8, 3, 1, 3 ),1310( 11, 1, 2, 6, 11, 7, 1, 1, 7, 5, 1, 3 ),1311( 0, 8, 3, 7, 1, 2, 7, 1, 1, 7, 5, 6, 7, 2, 11, 6 ),1312( 9, 7, 5, 6, 9, 2, 7, 0, 9, 0, 2, 3, 2, 11, 7, 3 ),1313( 7, 5, 2, 1, 7, 2, 11, 6, 5, 9, 2, 1, 3, 2, 8, 5, 9, 8, 2, 1 ),1314( 2, 5, 10, 6, 2, 3, 5, 1, 3, 7, 5, 3 ),1315( 8, 2, 0, 6, 8, 5, 2, 0, 8, 7, 5, 3, 10, 2, 5, 5 ),1316( 9, 0, 1, 7, 5, 10, 3, 1, 5, 3, 7, 6, 3, 10, 2, 6 ),1317( 9, 8, 2, 1, 9, 2, 1, 6, 8, 7, 2, 1, 10, 2, 5, 5, 7, 5, 2, 1 ),1318( 1, 3, 5, 5, 3, 7, 5, 3 ), ( 0, 8, 7, 3, 0, 7, 1, 4, 1, 7, 5, 6 ),1319( 9, 0, 3, 3, 9, 3, 5, 4, 5, 3, 7, 6 ), ( 9, 8, 7, 3, 5, 9, 7, 5 ),1320( 5, 8, 4, 6, 5, 10, 8, 1, 10, 11, 8, 3 ),1321( 5, 0, 4, 6, 5, 11, 0, 0, 5, 10, 11, 3, 11, 3, 0, 3 ),1322( 0, 1, 9, 7, 8, 4, 10, 1, 8, 10, 11, 6, 10, 4, 5, 6 ),1323( 10, 11, 4, 1, 10, 4, 5, 6, 11, 3, 4, 1, 9, 4, 1, 5, 3, 1, 4, 1 ),1324( 2, 5, 1, 6, 2, 8, 5, 0, 2, 11, 8, 3, 4, 5, 8, 5 ),1325( 0, 4, 11, 1, 0, 11, 3, 6, 4, 5, 11, 1, 2, 11, 1, 5, 5, 1, 11, 1 ),1326( 0, 2, 5, 1, 0, 5, 9, 6, 2, 11, 5, 1, 4, 5, 8, 5, 11, 8, 5, 1 ),1327( 9, 4, 5, 7, 2, 11, 3, 7 ),1328( 2, 5, 10, 6, 3, 5, 2, 4, 3, 4, 5, 2, 3, 8, 4, 3 ),1329( 5, 10, 2, 3, 5, 2, 4, 4, 4, 2, 0, 6 ),1330( 3, 10, 2, 6, 3, 5, 10, 2, 3, 8, 5, 1, 4, 5, 8, 5, 0, 1, 9, 7 ),1331( 5, 10, 2, 3, 5, 2, 4, 4, 1, 9, 2, 5, 9, 4, 2, 1 ),1332( 8, 4, 5, 3, 8, 5, 3, 4, 3, 5, 1, 6 ), ( 0, 4, 5, 3, 1, 0, 5, 5 ),1333( 8, 4, 5, 3, 8, 5, 3, 4, 9, 0, 5, 5, 0, 3, 5, 1 ), ( 9, 4, 5, 7 ),1334( 4, 11, 7, 6, 4, 9, 11, 1, 9, 10, 11, 3 ),1335( 0, 8, 3, 7, 4, 9, 7, 5, 9, 11, 7, 2, 9, 10, 11, 3 ),1336( 1, 10, 11, 3, 1, 11, 4, 0, 1, 4, 0, 6, 7, 4, 11, 5 ),1337( 3, 1, 4, 1, 3, 4, 8, 6, 1, 10, 4, 1, 7, 4, 11, 5, 10, 11, 4, 1 ),1338( 4, 11, 7, 6, 9, 11, 4, 4, 9, 2, 11, 2, 9, 1, 2, 3 ),1339( 9, 7, 4, 6, 9, 11, 7, 2, 9, 1, 11, 1, 2, 11, 1, 5, 0, 8, 3, 7 ),1340( 11, 7, 4, 3, 11, 4, 2, 4, 2, 4, 0, 6 ),1341( 11, 7, 4, 3, 11, 4, 2, 4, 8, 3, 4, 5, 3, 2, 4, 1 ),1342( 2, 9, 10, 6, 2, 7, 9, 0, 2, 3, 7, 3, 7, 4, 9, 3 ),1343( 9, 10, 7, 1, 9, 7, 4, 6, 10, 2, 7, 1, 8, 7, 0, 5, 2, 0, 7, 1 ),1344( 3, 7, 10, 1, 3, 10, 2, 6, 7, 4, 10, 1, 1, 10, 0, 5, 4, 0, 10, 1 ),1345( 1, 10, 2, 7, 8, 7, 4, 7 ), ( 4, 9, 1, 3, 4, 1, 7, 4, 7, 1, 3, 6 ),1346( 4, 9, 1, 3, 4, 1, 7, 4, 0, 8, 1, 5, 8, 7, 1, 1 ),1347( 4, 0, 3, 3, 7, 4, 3, 5 ), ( 4, 8, 7, 7 ),1348( 9, 10, 8, 5, 10, 11, 8, 3 ), ( 3, 0, 9, 3, 3, 9, 11, 4, 11, 9, 10, 6 ),1349( 0, 1, 10, 3, 0, 10, 8, 4, 8, 10, 11, 6 ),1350( 3, 1, 10, 3, 11, 3, 10, 5 ), ( 1, 2, 11, 3, 1, 11, 9, 4, 9, 11, 8, 6 ),1351( 3, 0, 9, 3, 3, 9, 11, 4, 1, 2, 9, 5, 2, 11, 9, 1 ),1352( 0, 2, 11, 3, 8, 0, 11, 5 ), ( 3, 2, 11, 7 ),1353( 2, 3, 8, 3, 2, 8, 10, 4, 10, 8, 9, 6 ), ( 9, 10, 2, 3, 0, 9, 2, 5 ),1354( 2, 3, 8, 3, 2, 8, 10, 4, 0, 1, 8, 5, 1, 10, 8, 1 ), ( 1, 10, 2, 7 ),1355( 1, 3, 8, 3, 9, 1, 8, 5 ), ( 0, 9, 1, 7 ), ( 0, 3, 8, 7 ), None )135613571358