Beispiel #1
0
def hadrian_min(vectorized_f, xbnds, ybnds, xtol, ytol, swarm=8, mx_iters=5,
        inc=False):
    """
    hadrian_min is a stochastic, hill climbing minimization algorithm.  It
    uses a stratified sampling technique (Latin Hypercube) to get good
    coverage of potential new points.  It also uses vectorized function
    evaluations to drive concurrent function evaluations.

    It is named after the Roman Emperor Hadrian, the most famous Latin hill
    mountain climber of ancient times.
    """
    assert xbnds[1] > xbnds[0]
    assert ybnds[1] > ybnds[0]
    assert xtol > 0
    assert ytol > 0
    # simplexes are simplex indexes
    # vertexes are vertex indexes
    # points are spatial coordinates

    bnds = np.vstack((xbnds, ybnds))
    points = latin_sample(np.vstack((xbnds, ybnds)), swarm)
    z = vectorized_f(points)

    # exclude corners from possibilities, but add them to the triangulation
    # this bounds the domain, but ensures they don't get picked
    points = np.append(points, list(product(xbnds, ybnds)), axis=0)
    z = np.append(z, 4*[z.max()])

    tri = Delaunay(points, incremental=inc)
    del points

    for step in range(1, mx_iters+1):
        i, vertexes = get_minimum_neighbors(tri, z)
        disp = tri.points[i] - tri.points[np.unique(vertexes)]
        disp /= np.array([xtol, ytol])
        err = err_mean(disp)

        if err < 1.:
            return tri.points[i], z[i], tri.points, z, 1

        tri_points = tri.points[vertexes]
        bnds = np.cumsum(area(tri_points))
        bnds /= bnds[-1]
        indx = np.searchsorted(bnds, np.random.rand(swarm))

        new_pts = sample_triangle(tri_points[indx])
        new_z = vectorized_f(new_pts)

        if inc:
            tri.add_points(new_pts)
        else:
            # make a new triangulation if I can't append points
            points = np.append(tri.points, new_pts, axis=0)
            tri = Delaunay(points)
        z = np.append(z, new_z)

    return None, None, tri.points, z, step
Beispiel #2
0
    def run(self):
        self.uniform_refine_boundary(n=5)

        node = self.mesh.entity('node')

        tri = Delaunay(node, incremental=True)

        fnode = self.advance()

        tri.add_points(fnode)

        return tri, fnode
class DelaunayChooser:
    def __init__(self, points):
        self.points = points
        self.tri = Delaunay(self.points, incremental=True)

        self.data = Data(5)

    def add_points(self, pnt):
        self.points = np.append(self.points, pnt, axis=0)
        self.tri.add_points(pnt)
        self.update_data()

    def next_point(self, eps):
        if (random.random() < eps):
            loc = np.where(self.data.areas == np.amax(self.data.areas))
            r = [0]
            if (len(loc[0]) > 1):
                r = np.random.randint(0, len(loc[0]) - 1, 1)
            return (self.points[loc[0][r]] + self.points[loc[1][r]] +
                    self.points[loc[2][r]]) / 3
        else:
            loc = np.where(self.data.lengths == np.amax(self.data.lengths))
            r = [0]
            if (len(loc[0]) > 1):
                r = np.random.randint(0, len(loc[0]) - 1, 1)
            return (self.points[loc[0][r]] + self.points[loc[1][r]]) / 2

    def update_data(self):
        if (len(self.points) >= self.data.size):
            self.data.grow(2 * len(self.points))
        else:
            self.data.grow(len(self.points))
        for s in self.tri.simplices:
            a = self.length(s[0], s[1])
            b = self.length(s[1], s[2])
            c = self.length(s[0], s[2])
            self.data.lengths[s[0]][s[1]] = a
            self.data.lengths[s[1]][s[2]] = b
            self.data.lengths[s[0]][s[2]] = c
            self.data.areas[s[0]][s[1]][
                s[2]] = (a + b + c) * (-a + b + c) * (a - b + c) * (a + b - c)
            #print(self.data.areas[s[0]][s[1]][s[2]])

    def length(self, p1, p2):
        return np.sqrt(np.sum((self.points[p1] - self.points[p2])**2))

    def plot(self, ax):
        ax.triplot(self.points[:, 0], self.points[:, 1],
                   self.tri.simplices.copy())
Beispiel #4
0
def in_hull(p, hull, extendy=False):
    """
	Test if points in `p` are in `hull`

	`p` should be a `NxK` coordinates of `N` points in `K` dimensions
	`hull` is either a scipy.spatial.Delaunay object or the `MxK` array of the 
	coordinates of `M` points in `K`dimensions for which Delaunay triangulation
	will be computed
	"""
    from scipy.spatial import Delaunay
    if not isinstance(hull, Delaunay):
        hull = Delaunay(hull, incremental=True)
        if extendy:
            pts = hull.points
            minx = np.min(pts[:, 0])
            maxx = np.max(pts[:, 0])
            new_pts = [[minx, 0], [maxx, 0]]
            hull.add_points(new_pts)

    return hull.find_simplex(p) >= 0
class plasma_mesh(object):
    def __init__(self, Lx=0., Ly=0., N_init=0, config=0, q_0=0.):
        self.Lx = Lx
        self.Ly = Ly
        self.N = N_init
        self.Grid = False
        #boundary conditions
        if config == BC_config.closed:
            self.q_bc = 0
        elif config == BC_config.constant_flux:
            self.q_bc = q_0
        else:
            raise Exception('Not done yet')

    def create_grid(self, config=0):
        if config == Bfield_config.uniform:  #create uniform grid of points, then triangulate
            nrows = int(np.sqrt(self.N) * float(self.Ly) / float(self.Lx))
            ncols = int(np.sqrt(self.N) * float(self.Lx) / float(self.Ly))
            ax = float(self.Lx) / float(ncols)
            ay = float(self.Ly) / float(nrows)
            for row, col in itool.product(range(nrows + 1), range(ncols + 1)):
                if row == 0 and col == 0:
                    self.nodes = np.array([[0, 0]])
                else:
                    self.nodes = np.append(self.nodes, [[ax * col, ay * row]],
                                           axis=0)
            self.Grid = Delaunay(
                self.nodes,
                incremental=True)  #allow incrementing to add points
            self.N = np.shape(
                self.nodes)[0]  #fix N to match actual number of vertices
        else:
            raise Exception('Define a vaild magnetic configuration')

    def create_cell_values(self, config=0, T_0=0., n_0=0):
        self.ncells = np.shape(self.Grid.simplices)[0]
        if config == profile_config.random:  #random starting T and n profile
            self.T = list(T_0 * np.random.random(self.ncells))
            self.n = list(T_0 * np.random.random(self.ncells))
        elif config == T_config.maxwellian:  #2D maxwellian starting profile w/ randomization
            raise Exception('I\'m not done with this yet')
        else:
            raise Exception('Define a valid temperature initialization')


#    def conduction_radiation(self,grad_Tx,grad_Ty,radiation_func):

    def cell_solver(self):
        q_checker = np.zeros((3, self.ncells))
        for cell in range(self.ncells + 1):
            q_nn = []
            for neighbor in range(3):
                if self.Grid.neighbors[cell][
                        neighbor] == -1:  #if one of the "neighbors" is the boundary
                    q_nn.append(self.q_bc)
                else:
                    boundary_pts = []
                    for pts in range(3):
                        if pts != neighbor:
                            boundary_pts.append(
                                self.nodes[self.Grid.simplices[cell][pts]])

    def update_grid_points(self):
        if not self.Grid:
            raise Exception(
                'Grid must be initialized first, use method "create_grid"')
        self.Grid.add_points([[0.45, 0.05], [0.45, 0.15]])
        self.nodes = np.append(self.nodes, [[0.45, 0.05], [0.45, 0.15]],
                               axis=0)
Beispiel #6
0
    def __init__(self, cat, viz=False, periodic=False, buff=5.):
        """Initialize tesselation.

        Parameters
        ----------
        cat : Catalog
            Catalog of objects used to compute the Voronoi tesselation.
        viz : bool
            Compute visualization.
        periodic : bool
            Use periodic boundary conditions.
        buff : float
            Width of incremental buffer shells for periodic computation.
        """
        coords = cat.coord[cat.nnls == np.arange(len(cat.nnls))]
        if periodic:
            print("Triangulating...")
            Del = Delaunay(coords, incremental=True, qhull_options='QJ')
            sim = Del.simplices
            simlen = len(sim)
            cids = np.arange(len(coords))
            print("Finding periodic neighbors...")
            n = 0
            coords2, cids = getBuff(coords, cids, cat.cmin, cat.cmax, buff, n)
            coords3 = coords.tolist()
            coords3.extend(coords2)
            Del.add_points(coords2)
            sim = Del.simplices
            while np.amin(sim[simlen:]) < len(coords):
                n = n + 1
                simlen = len(sim)
                coords2, cids = getBuff(coords, cids, cat.cmin, cat.cmax, buff,
                                        n)
                coords3.extend(coords2)
                Del.add_points(coords2)
                sim = Del.simplices
            for i in range(len(sim)):
                sim[i] = cids[sim[i]]
            print("Tesselating...")
            Vor = Voronoi(coords3)
            ver = Vor.vertices
            reg = np.array(Vor.regions)[Vor.point_region]
            del Vor
            print("Computing volumes...")
            vol = np.zeros(len(reg))
            cut = np.arange(len(coords))
            hul = []
            for r in reg[cut]:
                try:
                    ch = ConvexHull(ver[r])
                except:
                    ch = ConvexHull(ver[r], qhull_options='QJ')
                hul.append(ch)
            #hul = [ConvexHull(ver[r]) for r in reg[cut]]
            vol[cut] = np.array([h.volume for h in hul])
            self.volumes = vol
        else:
            print("Tesselating...")
            Vor = Voronoi(coords)
            ver = Vor.vertices
            reg = np.array(Vor.regions)[Vor.point_region]
            del Vor
            ve2 = ver.T
            vth = np.arctan2(np.sqrt(ve2[0]**2. + ve2[1]**2.), ve2[2])
            vph = np.arctan2(ve2[1], ve2[0])
            vrh = np.array([np.sqrt((v**2.).sum()) for v in ver])
            crh = np.array([np.sqrt((c**2.).sum()) for c in coords])
            rmx = np.amax(crh)
            rmn = np.amin(crh)
            print("Computing volumes...")
            vol = np.zeros(len(reg))
            cu1 = np.array([-1 not in r for r in reg])
            cu2 = np.array([
                np.product(np.logical_and(vrh[r] > rmn, vrh[r] < rmx),
                           dtype=bool) for r in reg[cu1]
            ]).astype(bool)
            msk = cat.mask
            nsd = hp.npix2nside(len(msk))
            pid = hp.ang2pix(nsd, vth, vph)
            imk = msk[pid]
            cu3 = np.array([
                np.product(imk[r], dtype=bool) for r in reg[cu1][cu2]
            ]).astype(bool)
            cut = np.arange(len(vol))
            cut = cut[cu1][cu2][cu3]
            hul = []
            for r in reg[cut]:
                try:
                    ch = ConvexHull(ver[r])
                except:
                    ch = ConvexHull(ver[r], qhull_options='QJ')
                hul.append(ch)
            #hul = [ConvexHull(ver[r]) for r in reg[cut]]
            vol[cut] = np.array([h.volume for h in hul])
            self.volumes = vol
            if viz:
                self.vertIDs = reg
                vecut = np.zeros(len(vol), dtype=bool)
                vecut[cut] = True
                self.vecut = vecut
                self.verts = ver
            print("Triangulating...")
            Del = Delaunay(coords, qhull_options='QJ')
            sim = Del.simplices
        nei = []
        lut = [[] for _ in range(len(vol))]
        print("Consolidating neighbors...")
        for i in range(len(sim)):
            for j in sim[i]:
                lut[j].append(i)
        for i in range(len(vol)):
            cut = np.array(lut[i])
            nei.append(np.unique(sim[cut]))
        self.neighbors = np.array(nei)
Beispiel #7
0
def create_Delaunay(images_train, reference):
    centers = np.array(list(map(lambda x: x['center'], images_train)))[:, :, 0]
    pca = PCA(.95)
    pca.fit(centers)
    train_c = pca.transform(centers)
    #print(reference)
    ref = np.array([train_c[i] for i in reference])
    #ref = [train_c[i] for i in reference]
    indices = list(range(len(ref)))
    tri = Delaunay(ref, incremental=True)

    def orderConvexHull(points, ch):
        mp = {}
        for c in ch:
            if c[0] not in mp:
                mp[c[0]] = []
            if c[1] not in mp:
                mp[c[1]] = []

            mp[c[0]].append(c[1])
            mp[c[1]].append(c[0])
        #print()

        ls = [ch[0][0], ch[0][1]]
        while ls[0] != ls[-1]:
            two = mp[ls[-1]]
            if two[0] == ls[-2]:
                ls.append(two[1])
            else:
                ls.append(two[0])

        # Find top point, opengl convention. I.e., max y
        maxy = -1e10
        maxyi = -1
        for i, p in enumerate(ls):
            if points[p][1] > maxy:
                maxy = points[p][1]
                maxyi = i

        if points[ls[(maxyi + 1) % len(ls)]][0] > points[ls[maxyi]][0]:
            return ls
        else:
            return ls[::-1]

    convexList = orderConvexHull(tri.points, tri.convex_hull)
    convexList = convexList[:-1]
    pp = tri.points
    #print(tri.points)
    plt.plot(train_c[:, 0], train_c[:, 1], 'o')
    plt.triplot(pp[:, 0], pp[:, 1], tri.simplices.copy())
    plt.plot(pp[:, 0], pp[:, 1], 'rx')
    plt.savefig('output/xx.png')
    newpoints = []

    def func(v1):
        #print("v1", v1)
        v1 = np.array([v1[0], v1[1], 0])
        v2 = np.array([0, 0, -1])
        v3 = np.cross(v1, v2)
        #print("v3", v3)
        v3 = v3 / np.linalg.norm(v3)
        return v3

    for i in range(len(convexList)):
        p0 = convexList[i]
        #if indices[p0] == 255: continue

        pa = convexList[(i + 1) % len(convexList)]
        pb = convexList[(len(convexList) + i - 1) % len(convexList)]
        va = func(tri.points[pa] - tri.points[p0])
        vb = -func(tri.points[pb] - tri.points[p0])
        vs = (va + vb) * 0.5
        vs = vs / np.linalg.norm(vs)
        newpoints.append(tri.points[p0] + vs[:2] * 0.8)
        indices += [indices[p0]]

    print(indices)
    tri.add_points(newpoints)

    pp = tri.points
    plt.triplot(pp[:, 0], pp[:, 1], tri.simplices.copy())
    plt.plot(pp[:, 0], pp[:, 1], 'o')
    plt.savefig('output/xxx.png')

    size = 512
    # Create empty black canvas
    imBary = Image.new('RGB', (size, size))
    drawBary = ImageDraw.Draw(imBary)

    imInd = Image.new('RGB', (size, size))
    drawInd = ImageDraw.Draw(imInd)

    imIndVis = Image.new('RGB', (size, size))
    drawIndVis = ImageDraw.Draw(imIndVis)

    maxcoordinate = np.max(np.abs(tri.points))
    scaler = size * 0.5 * 0.98 / maxcoordinate
    shifter = size / 2

    vlist = []
    for i in range(size):
        for j in range(size):
            vx = i
            vy = j
            vx = (vx - shifter) / scaler
            vy = (shifter - vy) / scaler
            vlist.append((vx, vy))

    ind = tri.find_simplex(vlist)

    simscaler = int(math.floor(255 / (len(tri.simplices) - 1)))
    for i in range(size):
        for j in range(size):
            val = ind[i * size + j]
            if val > -1:
                #print(tri.simplices[val])
                ids = [indices[tri.simplices[val][x]] for x in range(3)]
                #print(ids)
                drawInd.point([(i, j)], fill=(ids[0], ids[1], ids[2]))
                drawIndVis.point([(i, j)],
                                 fill=(ids[0] * simscaler, ids[1] * simscaler,
                                       ids[2] * simscaler))

                bary = tri.transform[val, :2, :2] * np.matrix(vlist[
                    i * size + j] - tri.transform[val, 2, :]).transpose()
                drawBary.point([(i, j)],
                               fill=(bary[0] * 255, bary[1] * 255,
                                     (1 - bary[0] - bary[1]) * 255))

    imBary.save('output/bary.png')
    imInd.save('output/indices.png')
    imIndVis.save('output/indicesVis.png')

    exit()
Beispiel #8
0
class Delaunay_d(object):

	def __init__(self, d, iteration):
		self.datafile = 'go_data_trail'
		self.file1 = "trail1.gif"
		self.file2 = "trail2.gif"
		self.img0 = Image.open(self.file1).convert('L')
		self.img1 = Image.open(self.file2).convert('L')
		self.width, self.height = self.img0.size
		self.globalbestPoint = [] # 0.3922227 , 0.19259966 , 0.23557716 , 0.43344293

		self.d, self.iteration = d, iteration
		self.Mn, self.gn, self.vn, self.bestRho = np.inf, np.inf, np.inf, np.inf
		self.bestPoint = []
		self.vertex_values = defaultdict(list)
		self.volumes = defaultdict(list)
		self.vertices = [[0, 0, 0, 0],
						[0, 0, 0, 1],
						[0, 0, 1, 0],
						[0, 1, 0, 0],
						[1, 0, 0, 0],
						[0, 0, 1, 1],
						[0, 1, 1, 0],
						[1, 1, 0, 0],
						[0, 1, 0, 1],
						[1, 0, 1, 0],
						[1, 0, 0, 1],
						[0, 1, 1, 1],
						[1, 1, 1, 0],
						[1, 0, 1, 1],
						[1, 1, 0, 1],
						[1, 1, 1, 1],
						[0.3, 0.5, 0.6, 0.9]]
		self.T = Delaunay(self.vertices, incremental=True, qhull_options='Qt Fv')
		self.T.coplanar
		self.simplices = np.array(self.vertices)[self.T.simplices]
		for vertex in self.vertices:
			# print self.func(vertex), vertex
			if self.Mn > self.func(vertex):
				self.globalbestPoint = vertex
			self.Mn = min(self.Mn, self.func(vertex))
		self.error = [0 for _ in xrange(self.iteration)]

	def ScaleRotateTranslate(self, image, angle, center=None, new_center=None, scale=None):
		if center is None:
			return image.rotate(angle)
		# angle = angle / 180.0 * math.pi
		nx, ny = x, y = center
		sx = sy = 1.0
		if new_center:
			nx, ny = new_center
		if scale:
			sx, sy = scale
		cosine = np.cos(np.deg2rad(angle))
		sine = np.sin(np.deg2rad(angle))
		a = cosine / sx
		b = sine / sx
		c = x-nx*a-ny*b
		d = -sine / sy
		e = cosine / sy
		f = y - nx*d - ny*e
		return image.transform(image.size, Image.AFFINE, (a,b,c,d,e,f), resample=Image.BICUBIC)

	def MSE(self, imageA, imageB):
		return MI.mutual_information(imageA, imageB)

	def func(self, p):
		# print tuple(p), self.vertex_values
		idx = tuple(p)
		if not self.vertex_values[idx]:
			x = self.width / 2.0 + (40 * p[0] - 20.0)
			y = self.height / 2.0 + (40 * p[1] - 20.0)
			# r = 0.24273422 * 40.0 - 20.0
			# s = 0.42331118 * 0.4 + 0.8
			r = 0.5 * 40.0 - 20.0
			s = 0.5 * 0.4 + 0.8
			x0, y0, x1, y1 = math.floor(x), math.floor(y), math.ceil(x), math.ceil(y)

			# clockwise
			if x0 == x1 and y0 != y1:
				v0 = self.MSE(self.img0, self.ScaleRotateTranslate(self.img1, r, (self.width / 2.0, self.height/2.0), (x0, y0), (s, s)))
				v1 = self.MSE(self.img0, self.ScaleRotateTranslate(self.img1, r, (self.width / 2.0, self.height/2.0), (x0, y1), (s, s)))
				rs = (y1-y)/(y1-y0)*v0 + (y-y0)/(y1-y0)*v1
			elif x0 != x1 and y0 == y1:
				v0 = self.MSE(self.img0, self.ScaleRotateTranslate(self.img1, r, (self.width / 2.0, self.height/2.0), (x0, y0), (s, s)))
				v1 = self.MSE(self.img0, self.ScaleRotateTranslate(self.img1, r, (self.width / 2.0, self.height/2.0), (x1, y0), (s, s)))
				rs = (x1-x)/(x1-x0)*v0 + (x-x0)/(x1-x0)*v1
			elif x0 == x1 and y0 == y1:
				rs = self.MSE(self.img0, self.ScaleRotateTranslate(self.img1, r, (self.width / 2.0, self.height/2.0), (x, y), (s, s)))
			else:
				v0 = self.MSE(self.img0, self.ScaleRotateTranslate(self.img1, r, (self.width / 2.0, self.height/2.0), (x0, y0), (s, s)))
				v1 = self.MSE(self.img0, self.ScaleRotateTranslate(self.img1, r, (self.width / 2.0, self.height/2.0), (x1, y0), (s, s)))
				v2 = self.MSE(self.img0, self.ScaleRotateTranslate(self.img1, r, (self.width / 2.0, self.height/2.0), (x0, y1), (s, s)))
				v3 = self.MSE(self.img0, self.ScaleRotateTranslate(self.img1, r, (self.width / 2.0, self.height/2.0), (x1, y1), (s, s)))
				l1 = (x1-x)/(x1-x0)*v0 + (x-x0)/(x1-x0)*v1
				l2 = (x1-x)/(x1-x0)*v2 + (x-x0)/(x1-x0)*v3
				rs = (y1-y)/(y1-y0)*l1 + (y-y0)/(y1-y0)*l2
			# trans_B = self.ScaleRotateTranslate(self.img1, r, (self.width / 2.0, self.height/2.0), (x, y), (s, s))
			# rs = self.MSE(self.img0, trans_B)
			self.vertex_values[idx] = 1.0 / rs
		else:
			return self.vertex_values[idx]

		return 1.0 / rs

	def volume(self, simplex):
		idx = tuple(map(tuple, simplex))
		temp = self.volumes[idx]
		if temp:
			return temp
		matrix = np.copy(simplex)
		matrix = np.insert(matrix, 0, 1, axis=1)
		rs = 1.0/2/3/4 * abs(np.linalg.det(matrix))
		self.volumes[idx] = rs
		return rs

	def calRho(self):
		# print "In this iteration has: ", len(np.array(self.vertices)[self.T.simplices])
		for simplex in np.array(self.vertices)[self.T.simplices]:
			# if simplex visited before, then skip
			# idx = tuple(map(tuple, simplex))
			# if self.volumes[idx]:
			# 	continue
			v = self.volume(simplex)
			if v == 0.0 or np.isinf(v):
				continue
			if v < self.vn:
				self.vn = v
				# q = 34.346004
				q = 27.152900397563425
				# print 'hahahahahahahahahaahahahaha'
				self.gn = np.sqrt(q * self.vn * np.log(1.0 / self.vn))
			ff = np.sum( self.func(s) for s in simplex ) / (self.d+1)
			rho = v / (ff - self.Mn + self.gn)**2 
			if rho > self.bestRho:
				self.bestPoint = np.sum(simplex, axis=0) / (self.d + 1)
				# print 'Best Point this time is: ', self.bestPoint
				self.bestRho = rho
		return ff

	def iter(self):
		for n in range(self.iteration):
			self.bestRho = 0.0
			self.calRho()
			l = len(self.simplices)
			print "###################", n, "###################", "simplices: ", l
			if self.Mn > self.func(self.bestPoint):
				self.globalbestPoint = self.bestPoint
				# print "error: ", self.func(self.bestPoint), "Best Point: ", self.bestPoint, " Value: ", self.func(self.bestPoint)
			self.Mn = min(self.Mn, self.func(self.bestPoint))
			self.error[n] += self.Mn
			self.vertices.append(self.bestPoint)
			self.T.add_points([self.bestPoint])
			self.simplices = np.array(self.vertices)[self.T.simplices]

			# p = self.globalbestPoint
			# x = self.width / 2.0 + (100 * p[0] - 50.0)
			# y = self.height / 2.0 + (100 * p[1] - 50.0)
			# r = p[2] * 40.0 - 20.0
			# s = p[3] * 0.4 + 0.8
			# print "error: ", self.error[n],"Best Global: ", self.globalbestPoint,"\nBest Point: ", (x,y,r,s), " Value: ", self.func(self.bestPoint)
			print "error: ", self.error[n],"Best Global: ", self.globalbestPoint,"\nBest Point: ", self.bestPoint, " Value: ", self.func(self.bestPoint)
		np.savetxt(self.datafile, self.vertices)

	def plot(self):
		self.vertices = np.array(self.vertices)
		plt.triplot(self.vertices[:,0], self.vertices[:,1])
		# plt.plot(self.vertices[:,0], self.vertices[:,1], '.')
		plt.plot(self.globalbestPoint[0], self.globalbestPoint[1], 'o')
		plt.show()

	def show(self):
		img0 = Image.open(self.file1)
		img1 = Image.open(self.file2)
		p = self.globalbestPoint
		# p = [  0.42237259 , 0.22260757 , 0.24273422,  0.42331118]
		x = self.width / 2.0 + (40 * p[0] - 20.0)
		y = self.height / 2.0 + (40 * p[1] - 20.0)
		# r = 0.24273422 * 40.0 - 20.0
		# s = 0.42331118 * 0.4 + 0.8
		r = 0.5 * 40.0 - 20.0
		s = 0.5 * 0.4 + 0.8
		print (x,y,r,s)
		img1 = self.ScaleRotateTranslate(img1, r, (self.width / 2.0, self.height/2.0), (x, y), (s, s))
		# img1 = self.ScaleRotateTranslate(self.img1, r, None, (x, self.height-y), (s, s))
		img0.show()
		img1.show()
		img1.save('rs-trail1.png')
		rs = (Image.blend(img1, img0, 0.5))
		rs.save('rs-trail2.png')
		rs.show()

	def test2(self):
		file = open('go_data2')
		vertices = []
		for line in file.readlines():
			temp = []
			for i in line.split(' '):
				temp.append(float(i))
			vertices.append(temp)
		m = np.inf
		for i, arr in enumerate(vertices):
			arr = np.array(arr)
			val = self.func(arr)
			if m > val:
				m = val
				print i, arr, val

	def test3(self):
		fig = plt.figure()
		ax = fig.gca(projection='3d')
		X = np.arange(-1, 1, 0.1)
		Y = np.arange(-1, 1, 0.1)
		X, Y = np.meshgrid(X, Y)
		zs = np.array( [self.func([x,y]) for x,y in zip(np.ravel(X), np.ravel(Y))])
		Z = zs.reshape(X.shape)
		surf = ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap=cm.coolwarm,
		                       linewidth=0, antialiased=False)
		ax.zaxis.set_major_locator(LinearLocator(10))
		ax.zaxis.set_major_formatter(FormatStrFormatter('%.02f'))
		fig.colorbar(surf, shrink=0.5, aspect=5)
		plt.show()
Beispiel #9
0
class VoronoiStrata(Strata):
    @beartype
    def __init__(
            self,
            seeds: np.ndarray = None,
            seeds_number: PositiveInteger = None,
            dimension: PositiveInteger = None,
            decomposition_iterations: PositiveInteger = 1,
            random_state: RandomStateType = None
    ):
        """
        Define a geometric decomposition of the n-dimensional unit hypercube into disjoint and space-filling
        Voronoi strata.

        :param seeds: An array of dimension :math:`N * n` specifying the seeds of all strata. The seeds of the strata
         are the coordinates of the point inside each stratum that defines the stratum. The user must provide `seeds` or
         `seeds_number` and `dimension`
        :param seeds_number: The number of seeds to randomly generate. Seeds are generated by random sampling on the
         unit hypercube. The user must provide `seeds` or `seeds_number` and `dimension`
        :param dimension: The dimension of the unit hypercube in which to generate random seeds. Used only if
         `seeds_number` is provided. The user must provide `seeds` or `seeds_number` and `dimension`
        :param decomposition_iterations: Number of iterations to perform to create a Centroidal Voronoi decomposition.
         If :code:`decomposition_iterations = 0`, the Voronoi decomposition is based on the provided or generated seeds.
         If :code:`decomposition_iterations >= 1`, the seed points are moved to the centroids of the Voronoi cells
         in each iteration and a new Voronoi decomposition is performed. This process is repeated
         `decomposition_iterations` times to create a Centroidal Voronoi decomposition.
        """
        super().__init__(seeds=seeds, random_state=random_state)

        self.logger = logging.getLogger(__name__)
        self.seeds_number = seeds_number
        self.dimension = dimension
        self.decomposition_iterations = decomposition_iterations
        self.voronoi: Voronoi = None
        """
        Defines a Voronoi decomposition of the set of reflected points. When creating the Voronoi decomposition on
        the unit hypercube, the code reflects the points on the unit hypercube across all faces of the unit hypercube.
        This causes the Voronoi decomposition to create edges along the faces of the hypercube.
        
        This object is not the Voronoi decomposition of the unit hypercube. It is the Voronoi decomposition of all
        points and their reflections from which the unit hypercube is extracted.
        
        To access the vertices in the unit hypercube, see the attribute :py:attr:`vertices`."""
        self.vertices: list = []
        """A list of the vertices for each Voronoi stratum on the unit hypercube."""

        if self.seeds is not None:
            if self.seeds_number is not None or self.dimension is not None:
                self.logger.info(
                    "UQpy: Ignoring 'nseeds' and 'dimension' attributes because 'seeds' are provided"
                )
            self.dimension = self.seeds.shape[1]

        self.stratify()

    def stratify(self):
        """
        Performs the Voronoi stratification.
        """
        self.logger.info("UQpy: Creating Voronoi stratification ...")

        initial_seeds = self.seeds
        if self.seeds is None:
            initial_seeds = stats.uniform.rvs(size=[self.seeds_number, self.dimension], random_state=self.random_state)

        if self.decomposition_iterations == 0:
            cent, vol = self.create_volume(initial_seeds)
            self.volume = np.asarray(vol)
        else:
            for i in range(self.decomposition_iterations):
                cent, vol = self.create_volume(initial_seeds)
                initial_seeds = np.asarray(cent)
                self.volume = np.asarray(vol)

        self.seeds = initial_seeds
        self.logger.info("UQpy: Voronoi stratification created.")

    def create_volume(self, initial_seeds):
        self.voronoi, bounded_regions = self.voronoi_unit_hypercube(initial_seeds)
        cent, vol = [], []
        for region in bounded_regions:
            vertices = self.voronoi.vertices[region + [region[0]], :]
            centroid, volume = self.compute_voronoi_centroid_volume(vertices)
            self.vertices.append(vertices)
            cent.append(centroid[0, :])
            vol.append(volume)
        return cent, vol

    @staticmethod
    def voronoi_unit_hypercube(seeds):
        from scipy.spatial import Voronoi

        # Mirror the seeds in both low and high directions for each dimension
        bounded_points = seeds
        dimension = seeds.shape[1]
        for i in range(dimension):
            seeds_del = np.delete(bounded_points, i, 1)
            if i == 0:
                points_temp1 = np.hstack([np.atleast_2d(-bounded_points[:, i]).T, seeds_del])
                points_temp2 = np.hstack([np.atleast_2d(2 - bounded_points[:, i]).T, seeds_del])
            elif i == dimension - 1:
                points_temp1 = np.hstack([seeds_del, np.atleast_2d(-bounded_points[:, i]).T])
                points_temp2 = np.hstack([seeds_del, np.atleast_2d(2 - bounded_points[:, i]).T])
            else:
                points_temp1 = np.hstack([seeds_del[:, :i],
                                          np.atleast_2d(-bounded_points[:, i]).T,
                                          seeds_del[:, i:], ])
                points_temp2 = np.hstack([seeds_del[:, :i],
                                          np.atleast_2d(2 - bounded_points[:, i]).T,
                                          seeds_del[:, i:],])
            seeds = np.append(seeds, points_temp1, axis=0)
            seeds = np.append(seeds, points_temp2, axis=0)

        vor = Voronoi(seeds, incremental=True)

        regions = [None] * bounded_points.shape[0]

        for i in range(bounded_points.shape[0]):
            regions[i] = vor.regions[vor.point_region[i]]

        bounded_regions = regions

        return vor, bounded_regions

    @staticmethod
    def compute_voronoi_centroid_volume(vertices):
        """
        This function computes the centroid and volume of a Voronoi cell from its vertices.

        :param vertices: Coordinates of the vertices that define the Voronoi cell.
        :return: Centroid and Volume of the Voronoi cell
        """
        from scipy.spatial import Delaunay, ConvexHull

        tess = Delaunay(vertices)
        dimension = np.shape(vertices)[1]

        w = np.zeros((tess.nsimplex, 1))
        cent = np.zeros((tess.nsimplex, dimension))
        for i in range(tess.nsimplex):
            # pylint: disable=E1136
            ch = ConvexHull(tess.points[tess.simplices[i]])
            w[i] = ch.volume
            cent[i, :] = np.mean(tess.points[tess.simplices[i]], axis=0)

        volume = np.sum(w)
        centroid = np.matmul(np.divide(w, volume).T, cent)

        return centroid, volume

    def sample_strata(self, nsamples_per_stratum, random_state):
        from scipy.spatial import Delaunay, ConvexHull

        samples_in_strata, weights = list(), list()
        for j in range(
                len(self.vertices)
        ):  # For each bounded region (Voronoi stratification)
            vertices = self.vertices[j][:-1, :]
            seed = self.seeds[j, :].reshape(1, -1)
            seed_and_vertices = np.concatenate([vertices, seed])

            # Create Dealunay Triangulation using seed and vertices of each stratum
            delaunay_obj = Delaunay(seed_and_vertices)

            # Compute volume of each delaunay
            volume = list()
            for i in range(len(delaunay_obj.vertices)):
                vert = delaunay_obj.vertices[i]
                ch = ConvexHull(seed_and_vertices[vert])
                volume.append(ch.volume)

            temp_prob = np.array(volume) / sum(volume)
            a = list(range(len(delaunay_obj.vertices)))
            for k in range(int(nsamples_per_stratum[j])):
                simplex = random_state.choice(a, p=temp_prob)

                new_samples = SimplexSampling(
                    nodes=seed_and_vertices[delaunay_obj.vertices[simplex]],
                    nsamples=1,
                    random_state=self.random_state,
                ).samples
                samples_in_strata.append(new_samples)

            self.extend_weights(nsamples_per_stratum, j, weights)
        return samples_in_strata, weights


    def compute_centroids(self):
        # if self.mesh is None:
        #     self.add_boundary_points_and_construct_delaunay()
        self.mesh.centroids = np.zeros([self.mesh.nsimplex, self.dimension])
        self.mesh.volumes = np.zeros([self.mesh.nsimplex, 1])
        from scipy.spatial import qhull, ConvexHull

        for j in range(self.mesh.nsimplex):
            try:
                ConvexHull(self.points[self.mesh.vertices[j]])
                self.mesh.centroids[j, :], self.mesh.volumes[j] = \
                    DelaunayStrata.compute_delaunay_centroid_volume(self.points[self.mesh.vertices[j]])
            except qhull.QhullError:
                self.mesh.centroids[j, :], self.mesh.volumes[j] = (np.mean(self.points[self.mesh.vertices[j]]), 0,)

    def initialize(self, samples_number, training_points):
        self.add_boundary_points_and_construct_delaunay(samples_number, training_points)
        self.mesh.old_vertices = self.mesh.vertices.copy()

    def add_boundary_points_and_construct_delaunay(
            self, samples_number, training_points
    ):
        """
        This method add the corners of :math:`[0, 1]^n` hypercube to the existing samples, which are used to construct a
        Delaunay Triangulation.
        """
        self.mesh_vertices = training_points.copy()
        self.points_to_samplesU01 = np.arange(0, training_points.shape[0])
        for i in range(np.shape(self.voronoi.vertices)[0]):
            if any(np.logical_and(self.voronoi.vertices[i, :] >= -1e-10, self.voronoi.vertices[i, :] <= 1e-10,)) or \
               any(np.logical_and(self.voronoi.vertices[i, :] >= 1 - 1e-10, self.voronoi.vertices[i, :] <= 1 + 1e-10,)):
                self.mesh_vertices = np.vstack([self.mesh_vertices, self.voronoi.vertices[i, :]])
                self.points_to_samplesU01 = np.hstack([np.array([-1]), self.points_to_samplesU01, ])
        from scipy.spatial.qhull import Delaunay

        # Define the simplex mesh to be used for gradient estimation and sampling
        self.mesh = Delaunay(
            self.mesh_vertices,
            furthest_site=False,
            incremental=True,
            qhull_options=None,)
        self.points = getattr(self.mesh, "points")

    def calculate_strata_metrics(self, index):
        self.compute_centroids()
        s = np.zeros(self.mesh.nsimplex)
        for j in range(self.mesh.nsimplex):
            s[j] = self.mesh.volumes[j] ** 2
        return s

    def update_strata_and_generate_samples(
            self, dimension, points_to_add, bins2break, samples_u01, random_state
    ):
        new_points = np.zeros([points_to_add, dimension])
        for j in range(points_to_add):
            new_points[j, :] = self._generate_sample(
                bins2break[j], random_state=random_state
            )
        self._update_strata(new_point=new_points, samples_u01=samples_u01)
        return new_points

    def calculate_gradient_strata_metrics(self, index):
        # Estimate the variance over each simplex by Delta Method. Moments of the simplices are computed using
        # Eq. (19) from the following reference:
        # Good, I.J. and Gaskins, R.A. (1971). The Centroid Method of Numerical Integration. Numerische
        #       Mathematik. 16: 343--359.
        var = np.zeros((self.mesh.nsimplex, self.dimension))
        s = np.zeros(self.mesh.nsimplex)
        for j in range(self.mesh.nsimplex):
            for k in range(self.dimension):
                std = np.std(self.points[self.mesh.vertices[j]][:, k])
                var[j, k] = (
                                    self.mesh.volumes[j]
                                    * math.factorial(self.dimension)
                                    / math.factorial(self.dimension + 2)
                            ) * (self.dimension * std ** 2)
            s[j] = np.sum(self.dy_dx[j, :] * var[j, :] * self.dy_dx[j, :]) * (
                    self.mesh.volumes[j] ** 2
            )
        self.dy_dx_old = self.dy_dx
        return s

    def estimate_gradient(
            self,
            surrogate,
            step_size,
            samples_number,
            index,
            samples_u01,
            training_points,
            qoi,
            max_train_size=None,
    ):
        self.mesh.centroids = np.zeros([self.mesh.nsimplex, self.dimension])
        self.mesh.volumes = np.zeros([self.mesh.nsimplex, 1])
        from scipy.spatial import qhull, ConvexHull

        for j in range(self.mesh.nsimplex):
            try:
                ConvexHull(self.points[self.mesh.vertices[j]])
                self.mesh.centroids[j, :], self.mesh.volumes[j] = DelaunayStrata.compute_delaunay_centroid_volume(
                    self.points[self.mesh.vertices[j]])
            except qhull.QhullError:
                self.mesh.centroids[j, :], self.mesh.volumes[j] = (np.mean(self.points[self.mesh.vertices[j]]), 0,)

        if max_train_size is None or len(training_points) <= max_train_size or index == training_points.shape[0]:
            from UQpy.utilities.Utilities import calculate_gradient
            # Use the entire sample set to train the surrogate model (more expensive option)
            self.dy_dx = calculate_gradient(
                surrogate,
                step_size,
                np.atleast_2d(training_points),
                np.atleast_2d(np.array(qoi)).T,
                self.mesh.centroids,)
            # dy_dx = self.calculate_gradient(
            #     np.atleast_2d(training_points), qoi, self.mesh.centroids, surrogate)
        else:
            # Use only max_train_size points to train the surrogate model (more economical option)
            # Build a mapping from the new vertex indices to the old vertex indices.
            self.mesh.new_vertices, self.mesh.new_indices = [], []
            self.mesh.new_to_old = np.zeros([self.mesh.vertices.shape[0], ]) * np.nan
            j, k = 0, 0
            while (j < self.mesh.vertices.shape[0] and k < self.mesh.old_vertices.shape[0]):

                if np.all(self.mesh.vertices[j, :] == self.mesh.old_vertices[k, :]):
                    self.mesh.new_to_old[j] = int(k)
                    j += 1
                    k = 0
                else:
                    k += 1
                    if k == self.mesh.old_vertices.shape[0]:
                        self.mesh.new_vertices.append(self.mesh.vertices[j])
                        self.mesh.new_indices.append(j)
                        j += 1
                        k = 0

            # Find the nearest neighbors to the most recently added point
            from sklearn.neighbors import NearestNeighbors

            knn = NearestNeighbors(n_neighbors=max_train_size)
            knn.fit(np.atleast_2d(samples_u01))
            neighbors = knn.kneighbors(np.atleast_2d(samples_u01[-1]), return_distance=False)

            # For every simplex, check if at least dimension-1 vertices are in the neighbor set.
            # Only update the gradient in simplices that meet this criterion.
            update_list = []
            for j in range(self.mesh.vertices.shape[0]):
                self.vertices_in_U01 = self.points_to_samplesU01[self.mesh.vertices[j]]
                self.vertices_in_U01[np.isnan(self.vertices_in_U01)] = 10 ** 18
                v_set = set(self.vertices_in_U01)
                v_list = list(self.vertices_in_U01)
                if len(v_set) != len(v_list):
                    continue
                else:
                    if all(np.isin(self.vertices_in_U01, np.hstack([neighbors, np.atleast_2d(10 ** 18)]),)):
                        update_list.append(j)

            update_array = np.asarray(update_list)

            # Initialize the gradient vector
            self.dy_dx = np.zeros((self.mesh.new_to_old.shape[0], self.dimension))

            # For those simplices that will not be updated, use the previous gradient
            for j in range(self.dy_dx.shape[0]):
                if np.isnan(self.mesh.new_to_old[j]):
                    continue
                else:
                    self.dy_dx[j, :] = self.dy_dx_old[int(self.mesh.new_to_old[j]), :]

            # For those simplices that will be updated, compute the new gradient
            from UQpy.utilities.Utilities import calculate_gradient
            self.dy_dx[update_array, :] = calculate_gradient(surrogate, step_size,
                                                             np.atleast_2d(training_points)[neighbors],
                                                             np.atleast_2d(np.array(qoi)[neighbors]).T,
                                                             self.mesh.centroids[update_array])

    def _update_strata(self, new_point, samples_u01):
        i_ = samples_u01.shape[0]
        p_ = new_point.shape[0]
        # Update the matrices to have recognize the new point
        self.points_to_samplesU01 = np.hstack([self.points_to_samplesU01, np.arange(i_, i_ + p_)])
        self.mesh.old_vertices = self.mesh.vertices

        # Update the Delaunay triangulation mesh to include the new point.
        self.mesh.add_points(new_point)
        self.points = getattr(self.mesh, "points")
        self.mesh_vertices = np.vstack([self.mesh_vertices, new_point])

        # Compute the strata weights.
        self.voronoi, bounded_regions = VoronoiStrata.voronoi_unit_hypercube(samples_u01)

        self.centroids = []
        self.volume = []
        for region in bounded_regions:
            vertices = self.voronoi.vertices[region + [region[0]]]
            centroid, volume = VoronoiStrata.compute_voronoi_centroid_volume(vertices)
            self.centroids.append(centroid[0, :])
            self.volume.append(volume)

    def _generate_sample(self, bin_, random_state):
        import itertools

        tmp_vertices = self.points[self.mesh.simplices[int(bin_), :]]
        col_one = np.array(list(itertools.combinations(np.arange(self.dimension + 1), self.dimension)))
        self.mesh.sub_simplex = np.zeros_like(tmp_vertices)  # node: an array containing mid-point of edges
        for m in range(self.dimension + 1):
            self.mesh.sub_simplex[m, :] = (
                    np.sum(tmp_vertices[col_one[m] - 1, :], 0) / self.dimension)

        # Using the Simplex class to generate a new sample in the sub-simplex
        new = SimplexSampling(nodes=self.mesh.sub_simplex, nsamples=1, random_state=random_state).samples
        return new
Beispiel #10
0
class Delaunay(Sampler):
    """
    Delaunay Class.

    Inherits from the Sampler class and augments pick and update with the
    mechanics of the Delanauy triangulation method

    Attributes
    ----------
    triangulation : scipy.spatial.qhull.Delaunay
        The Delaunay triangulation model object
    simplex_cache : dict
        Cached values of simplices for Delaunay triangulation
    explore_priority : float
        The priority of exploration against exploitation

    See Also
    --------
    Sampler : Base Class
    """

    name = 'Delaunay'

    def __init__(self, lower, upper, explore_priority=0.0001):
        """
        Initialise the Delaunay class.

        .. note ::

            Currently only supports rectangular type restrictions on the
            parameter space

        Parameters
        ----------
        lower : array_like
            Lower or minimum bounds for the parameter space
        upper : array_like
            Upper or maximum bounds for the parameter space
        explore_priority : float, optional
            The priority of exploration against exploitation
        """
        Sampler.__init__(self, lower, upper)
        self.triangulation = None  # Delaunay model
        self.simplex_cache = {}  # Pre-computed values of simplices
        self.explore_priority = explore_priority

    def update(self, uid, y_true):
        """
        Update a job with its observed value.

        Parameters
        ----------
        uid : str
            A hexadecimal ID that identifies the job to be updated
        y_true : float
            The observed value corresponding to the job identified by 'uid'

        Returns
        -------
        int
            Index location in the data lists 'Delaunay.X' and
            'Delaunay.y' corresponding to the job being updated
        """
        return self._update(uid, y_true)

    def pick(self):
        """
        Pick the next feature location for the next observation to be taken.

        This uses the recursive Delaunay subdivision algorithm.

        Returns
        -------
        numpy.ndarray
            Location in the parameter space for the next observation to be
            taken
        str
            A random hexadecimal ID to identify the corresponding job
        """
        n = len(self.X)

        # -- note that we are assuming the points in X are not reordered by
        # the scipy Delaunay implementation

        n_corners = 2 ** self.dims
        if n < n_corners + 1:

            # Bootstrap with a regular sampling strategy to get it started
            xq = grid_sample(self.lower, self.upper, n)
            yq_exp = [0.]
        else:

            X = self.X()  # calling returns the value as an array
            y = self.y()
            virtual = self.virtual_flag()

            # Otherwise, recursive subdivide the edges with the Delaunay model
            if not self.triangulation:
                self.triangulation = ScipyDelaunay(X, incremental=True)

            # Weight by hyper-volume
            simplices = [tuple(s) for s in self.triangulation.vertices]
            cache = self.simplex_cache

            def get_value(s):

                # Computes the sample value as:
                #   hyper-volume of simplex * variance of values in simplex
                ind = list(s)
                value = (np.var(y[ind]) + self.explore_priority) * \
                    np.linalg.det((X[ind] - X[ind[0]])[1:])
                if not np.max(virtual[ind]):
                    cache[s] = value
                return value

            # Mostly the simplices won't change from call to call - cache!
            sample_value = [cache[s] if s in cache else get_value(s)
                            for s in simplices]

            # alternatively, a nicely vectorised computation might work here
            # profile and check what the bottleneck is

            # Extract the points in the highest value simplex
            simplex_indices = list(simplices[np.argmax(sample_value)])
            simplex = X[simplex_indices]
            simplex_v = y[simplex_indices]

            # Weight the position in this simplex based on value deviation
            eps = 1e-3
            weight = eps + np.abs(simplex_v - np.mean(simplex_v))
            weight /= np.sum(weight)
            xq = np.sum(weight * simplex, axis=0)  # dot
            yq_exp = np.sum(weight * simplex_v, axis=0)

            self.triangulation.add_points(xq[np.newaxis, :])  # incremental

        uid = Sampler._assign(self, xq, yq_exp)
        return xq, uid
class AlphaShape(object):
    """
    Compute the alpha shape (or concave hull) of points.

    Parameters
    ----------
    points : (Mx2) array
        The coordinates of the points.
    alpha : float
        Influences the shape of the alpha shape. Higher values lead to more
        edges being deleted.
    inc : bool
        If True adding and deleting points is allowed (at the cost of some
        efficiency).

    Attributes
    ----------
    points : (Mx2) array
        The coordinates of the points used to compute the alpha shape.
    alpha : float
        The alpha used to compute the alpha shape.
    tri : SciPy Delaunay object
        The Delaunay triangulation of the points.
    edges : set
        The edges between the points resulting from the Delaunay
        triangulation.
    edge_points : list of arrays
        Each array contains the points which are connected by the edges
        resulting from the Delaunay triangulation.
    m : Shapely MultiLineString
        The Delaunay triangulation edges converted to a Shapely
        MultiLineString.
    triangles : array of Shapely polygons
        The Delaunay triangles converted to Shapely polygons.
    shape : Shapely polygon
        The resulting alpha shape of the to_shape method.

    Methods
    -------
    add_points(points)
        Adds points to the alpha shape. Can only be used if 'inc' has been set
        to True.
    remove_last_added()
        Removes the last added points by the 'add_points' method and
        recreates the alpha shape.
    """
    def __init__(self, points, alpha, inc=False):
        if len(points) < 4:
            raise ValueError('Not enough points to compute an alpha shape.')
        self.saved = None
        self.area = 0
        self.alpha = alpha
        self.points = points
        self.tri = Delaunay(points, incremental=inc)
        self.edges = set()
        self.edge_points = []
        self._add_edges(self.tri.vertices)

    @staticmethod
    def _find_outliers(data, m):
        """
        Find outliers by Median Absolute Deviation (MAD).

        Parameters
        ----------
        data : array
            Data to find outliers of.
        m : float or int
            Threshold value of the median deviation of a data entry
            to be considered an outlier.

        Returns
        -------
        outliers : array
            Boolean array where outliers are marked as False.
        """
        d = np.abs(data - np.median(data))
        mdev = np.median(d)
        s = d/mdev if mdev else 0.
        return s < m

    @staticmethod
    def remove_array_from_list(L, arr):
        """
        Removes an array from a list.

        Parameters
        ----------
        L : list
            A list with arrays of which one has to be removed.
        arr : array
            The array which needs to be removed from the list.
        """
        i = 0
        size = len(L)
        while i != size and not np.array_equal(L[i], arr):
            i += 1
        if i != size:
            L.pop(i)
        else:
            raise ValueError('array not found in list.')

    def _triangle_geometry(self, triangle):
        """
        Compute the area and circumradius of a triangle.

        Parameters
        ----------
        triangle : (1x3) array-like
            The indices of the points which form the triangle.

        Returns
        -------
        area : float
            The area of the triangle
        circum_r : float
            The circumradius of the triangle
        """
        pa = self.points[triangle[0]]
        pb = self.points[triangle[1]]
        pc = self.points[triangle[2]]
        # Lengths of sides of triangle
        a = math.hypot((pa[0]-pb[0]), (pa[1]-pb[1]))
        b = math.hypot((pb[0]-pc[0]), (pb[1]-pc[1]))
        c = math.hypot((pc[0]-pa[0]), (pc[1]-pa[1]))
        # Semiperimeter of triangle
        s = (a + b + c)/2.0
        # Area of triangle by Heron's formula
        area = math.sqrt(s*(s-a)*(s-b)*(s-c))
        if area != 0:
            circum_r = a*b*c/(4.0*area)
        else:
            circum_r = 0
        return area, circum_r

    def _add_edge(self, i, j):
        """
        Add an edge (line) between two points.

        Parameters
        ----------
        i : int
            Index of a point.
        j : int
            Index of a point.
        """
        if (i, j) in self.edges or (j, i) in self.edges:
            return
        self.edges.add((i, j))
        self.edge_points.append(self.points[[i, j]])

    def _add_edges(self, vertices):
        """
        Add the edges between the given vertices if the circumradius of the
        triangle is bigger than 1/alpha.

        Parameters
        ----------
        vertices : (Mx3) array
            Indices of the points forming the vertices of a triangulation.
        """
        for ia, ib, ic in vertices:
            area, circum_r = self._triangle_geometry((ia, ib, ic))
            if area != 0:
                if circum_r < 1.0/self.alpha:
                    self._add_edge(ia, ib)
                    self._add_edge(ib, ic)
                    self._add_edge(ic, ia)
                    self.area += area

    def _remove_edge(self, i, j):
        """
        Remove an edge (line) between two points.

        Parameters
        ----------
        i : int
            Index of a point.
        j : int
            Index of a point.
        """
        if (i, j) not in self.edges and (j, i) not in self.edges:
            return
        if (i, j) in self.edges:
            self.edges.remove((i, j))
            self.remove_array_from_list(self.edge_points, self.points[[i, j]])
        if (j, i) in self.edges:
            self.edges.remove((j, i))
            self.remove_array_from_list(self.edge_points, self.points[[j, i]])

    def _remove_edges(self, vertices):
        """
        Remove the edges between the given vertices if the circumradius of the
        triangle is bigger than 1/alpha.

        Parameters
        ----------
        vertices : (Mx3) array
            Indices of the points forming the vertices of a triangulation.
        """
        for ia, ib, ic in vertices:
            area, circum_r = self._triangle_geometry((ia, ib, ic))
            if area != 0:
                if circum_r < 1.0/self.alpha:
                    self.area -= area
                    self._remove_edge(ia, ib)
                    self._remove_edge(ib, ic)
                    self._remove_edge(ic, ia)

    def to_shape(self, remove_interior=None):
        """
        Convert the alpha shape to a Shapely polygon object.

        Parameters
        ----------
        remove_interior : float or int
            If remove_interior parameter is set it will attempt to remove a
            possible interior of a alpha shape by detecting outlying polygons
            in 'triangles', since interiors are often way bigger than the
            actual triangles. Outliers detected using a Median Absolute
            Deviation (MAD).
        """
        self.m = geometry.MultiLineString(self.edge_points)
        self.triangles = np.array(list(polygonize(self.m)))
        if remove_interior is not None:
            tri_area = np.array([t.area for t in self.triangles])
            outliers = self._find_outliers(tri_area, m=remove_interior)
            if type(outliers) is bool:
                outliers = np.array([outliers])
            self.triangles = self.triangles[outliers]

        self.shape = cascaded_union(self.triangles)

    def add_points(self, points):
        """
        Adds points to the alpha shape.

        Parameters
        ----------
        points : (Mx2) array
            The coordinates of the points.
        """
        # Save current properties
        self.saved = {'points': self.points.copy(),
                      'edge_points': self.edge_points[:],
                      'edges': self.edges.copy(),
                      'area': self.area}
        # Determine old and new triangles
        old = self.tri.vertices.copy()
        old_rows = old.view(np.dtype((np.void,
                                      old.dtype.itemsize*old.shape[1])))
        self.tri.add_points(points)
        new = self.tri.vertices
        new_rows = new.view(np.dtype((np.void,
                                      new.dtype.itemsize*new.shape[1])))
        old_tri = np.setdiff1d(old_rows,
                               new_rows).view(new.dtype).reshape(-1,
                                                                 new.shape[1])
        new_tri = np.setdiff1d(new_rows,
                               old_rows).view(new.dtype).reshape(-1,
                                                                 new.shape[1])
        # Remove old triangles and add new
        self._remove_edges(old_tri)
        self.points = np.vstack((self.points, points))
        self._add_edges(new_tri)

    def remove_last_added(self):
        """
        Removes the last added points.
        """
        if self.saved is None:
            raise RuntimeError('No points were added.')
        self.points = self.saved['points']
        self.edge_points = self.saved['edge_points']
        self.edges = self.saved['edges']
        self.area = self.saved['area']
        self.tri = Delaunay(self.points, incremental=True)
        self.saved = None
Beispiel #12
0
class AdaptiveMesh(object):
  """
  Implementation of an adaptive mesh for a given mapping f:domain->image.
  We start from a Delaunay triangulation in the domain of f. This grid
  will be distorted in the image space. We refine the mesh by subdividing
  large or broken triangles. This process can be iterated, e.g. wehn a 
  triangle is cut multiple times (use threshold for minimal size of triangle
  in domain space). Points outside of the domain (e.g. raytrace fails) 
  should be mapped to image point (np.nan,np.nan) and are handled separately.
  
  ToDo: add unit tests
  """
  
  def __init__(self,initial_domain,mapping):
    """
    Initialize mesh, mapping and image points.
      initial_domain ... 2d array of shape (nPoints,2)
      mapping        ... function image=mapping(domain) that accepts a list of  
                           domain points and returns corresponding image points
    """
    from scipy.spatial import Delaunay
     
    assert( initial_domain.ndim==2 and initial_domain.shape[1] == 2)
    self.initial_domain = initial_domain;
    self.mapping = mapping;
    # triangulation of initial domain
    self.__tri = Delaunay(initial_domain,incremental=True);
    self.simplices = self.__tri.simplices;
    # calculate distorted grid
    self.initial_image = self.mapping(self.initial_domain);
    assert( self.initial_image.ndim==2)
    assert( self.initial_image.shape==(self.initial_domain.shape[0],2))
    # current domain and image during refinement and for plotting
    self.domain = self.initial_domain;    
    self.image  = self.initial_image;   
    # initial domain area
    self.initial_domain_area = np.sum(self.get_area_in_domain());
    
          
  def get_mesh(self):
    """ 
    return triangles and points in domain and image space
       domain,image:  coordinate array of shape (nPoints,2)
       simplices:     index array for vertices of each triangle, shape (nTriangles,3)
    Returns: (domain,image,simplices)
    """
    return self.domain,self.image,self.simplices;

  
  def plot_triangulation(self,skip_triangle=None):
    """
    plot current triangulation of adaptive mesh in domain and image space
    
    Parameters
    ----------
      skip_triangle : function mask=skip_triangle(simplices), optional
        function that accepts a ndarray of simplices of shape (nTriangles, 3) 
        and returns a flag for each triangle indicating that it should not be drawn
        
    Return
    ------
      handle to new matplotlib figure
    """ 
    simplices = self.simplices.copy();
    if skip_triangle is not None:
      skip = skip_triangle(simplices);
      skipped_simplices=simplices[skip];
      simplices=simplices[~skip];
          
    fig,(ax1,ax2)= plt.subplots(2);
    ax1.set_title("Sampling + Triangulation in Domain");
    if skip_triangle is not None and np.sum(skip)>0:
      ax1.triplot(self.domain[:,0], self.domain[:,1], skipped_simplices,'k:');
    if len(simplices)>0:
      ax1.triplot(self.domain[:,0], self.domain[:,1], simplices,'b-');    
    ax1.plot(self.initial_domain[:,0],self.initial_domain[:,1],'r.')
    
    ax2.set_title("Sampling + Triangulation in Image")
    if len(simplices)>0:
      ax2.triplot(self.image[:,0], self.image[:,1], simplices,'b-');
    ax2.plot(self.initial_image[:,0],self.initial_image[:,1],'r.')

    # if aspect is close to 1 force aspect to be 1
    if 1./4 < ax1.get_data_ratio() < 4: ax1.set_aspect('equal'); 
    if 1./4 < ax2.get_data_ratio() < 4: ax2.set_aspect('equal');
    return fig;


  def get_area_in_domain(self,simplices=None):
    """
    calculate signed area of given simplices in domain space
      simplices ... (opt) list of simplices, shape (nTriangles,3)
    Returns:
      1d vector of size nTriangles containing the signed area of each triangle
      (positive: ccw orientation, negative: cw orientation of vertices)
    """
    if simplices is None: simplices = self.simplices;    
    x,y = self.domain[simplices].T;
    # See http://geomalgorithms.com/a01-_area.html#2D%20Polygons
    return 0.5 * ( (x[1]-x[0])*(y[2]-y[0]) - (x[2]-x[0])*(y[1]-y[0]) );

  def get_area_in_image(self,simplices=None):
    """
    calculate signed area of given simplices in image space
    (see get_area_in_domain())
    """
    if simplices is None: simplices = self.simplices;    
    x,y = self.image[simplices].T;
    # See http://geomalgorithms.com/a01-_area.html#2D%20Polygons
    return 0.5 * ( (x[1]-x[0])*(y[2]-y[0]) - (x[2]-x[0])*(y[1]-y[0]) );
  
  
  def find_broken_triangles(self,simplices=None,lthresh=None,):
    """
    identify triangles that are cut in image space or include invalid vertices
    
    Parameters
    ----------
      simplices : ndarray of ints, shape (nTriangles,3), optional
         Indices of the points forming the simplices in the triangulation. 
      lthresh : float, optional
         absolute threshold for longest side of broken triangle 
         
    Returns
    -------
      bBroken: boolean vector of length nTriangles
         indicates, if triangle is broken
    """
    if simplices is None: simplices = self.simplices;    
    # x and y coordinates for each vertex in each triangle 
    triangles = self.image[simplices]    
    # calculate maximum of (squared) length of two sides of each triangle 
    # (X[0]-X[1])**2 + (Y[0]-Y[1])**2; (X[1]-X[2])**2 + (Y[1]-Y[2])**2 
    max_lensq = np.max(np.sum(np.diff(triangles,axis=1)**2,axis=2),axis=1);
    # default: mark triangle as broken, if max side is 3 times larger than median value
    if lthresh is None: lthresh = 3*np.sqrt(np.nanmedian(max_lensq));
    # valid triangles: all sides smaller than lthresh, none of its vertices invalid (np.nan)
    bValid = max_lensq < lthresh**2; 
    return ~bValid;   # Note: differs from (max_lensq >= lthresh**2), if some vertices are invalid!

  def find_skinny_triangles(self,simplices=None,rthresh=5,Athresh=1e-10):
    """
    Identify triangles that deviate strongly from regular triangle (have very small angles)
    
    Parameters
    ----------
      simplices : ndarray of ints, shape (nTriangles,3), optional
         Indices of the points forming the simplices in the triangulation. 
      rthresh : float, optional
         relative threshold, specifies, how much smaller (area) a triangle
         can be compared to the corresponding regular triangle
      Athresh : float, optional
         area in domain threshold (relative to total domain area)      
      
    Returns
    -------
      bSkinny: boolean vector of length nTriangles
         indicates, if triangle is skinny
    """
    if simplices is None: simplices = self.simplices;    
    # x and y coordinates for each vertex in each triangle, shape (nTriangles,3,2)
    triangles = self.image[simplices];
    # calculate (squared) length of each edge in triangle, shape (nTriangles,3)
    # (X[0]-X[1])**2 + (Y[0]-Y[1])**2; (X[1]-X[2])**2 + (Y[1]-Y[2])**2, (X[2]-X[0])**2 + (Y[2]-Y[0])**2 
    lensq = np.sum(np.diff(triangles[:,[0,1,2,0]],axis=1)**2,axis=2)
    # calculate (signed) area of triangles
    x,y = triangles.T    
    area = 0.5 * ( (x[1]-x[0])*(y[2]-y[0]) - (x[2]-x[0])*(y[1]-y[0]) );   
    # skinny triangles: area of triangle is much smaller (by factor rthresh) 
    # than the area of regular triangle sqrt(3)/4*maxlensq ~ 0.433*maxlensq
    bSkinny = np.abs(area) < (0.433/rthresh) * np.nanmax(lensq,axis=1);
      # note: nan's in area are not catched so far
    bSkinny&= np.abs(area)/self.initial_domain_area > Athresh # triangle is large
      
    return bSkinny    
   

   
   
   
  def refine_skinny_triangles_complicated(self,skip_triangle=None,rthresh=5,scale_sampling=0.5,bPlot=False):
    """
    subdivide skinny triangles in the image mesh (have very small angles)
    
    Parameters
    ----------
      skip_triangle: function mask=skip_triangle(simplices), optional
         function, that accepts a list of simplices of shape (nTriangles, 3) 
         and returns a flag for each triangle indicating if it is ignored
      rthresh : float, optional
         relative threshold, specifies, how much smaller (area) a triangle
         can be compared to the corresponding regular triangle 
      scale_sampling : float, optional
         increasing scale_sampling will increase the number of subdivisions,
         a typical range is between 0.5 (default) and 1
      bPlot : boolean
         if True, the triangulation including the skinny triangles is shown
    
    Returns
    --------
      number of points added to the triangulation
    
    Note
    ----
      Artefacts occur at the boundary of the split triangles, if the neighboring
      triangle is not split (very thin, overlapping triangles). Separation should
      maybe be done differently (using Delaunay triangulation, just splitting largest side by two☺)
    
      See Shewchuk, Delaunay Refinement Algorithms for Triangular Mesh Generation
      http://www.cs.berkeley.edu/~jrs/papers/2dj.pdf
    
    """     
    bSkinny = self.find_skinny_triangles(self.simplices,rthresh=rthresh);
    if skip_triangle is not None:
      bSkinny &= ~skip_triangle(self.simplices);
    if np.sum(bSkinny)==0: return; # nothing to do
    
    simplices = self.simplices[bSkinny];                   # shape (nTriangles,3)
    triangles = self.image[simplices];                     # shape (nTriangles,3,2)
    nPointsOrigMesh = self.image.shape[0];  

    # find largest side of triangle in image space -> named as CA
    len_edge = np.sqrt( np.sum(np.diff(triangles[:,[0,1,2,0]],axis=1)**2,axis=2) ); # shape (nTriangles,3)
    indC = np.argmax(len_edge,axis=1);
    area = np.abs(self.get_area_in_image(simplices));

    # iterate over all triangles and subdivide them
    #
    #   notation for skinny triangle (oriented ccw)
    #             C .         
    #              /|           CA: longest side of triangle
    #             / |           
    #            /  |
    #        A  /___| B
    #      
    #   subdivision of skinny triangle: 
    #   we add nCB points along CB, nBA points along BA
    #   and nCB+nBA+1 points along CA and create new triangles
    #   as indicated below for nCB=3, nBA=1.
    #      
    #          p0     p1     p2     p3     p4     p5
    #          .______.______.______B______.______A 
    #         / \    / \    / \    / \    / \    /
    #        /   \  /   \  /   \  /   \  /   \  /
    #       C_____\/_____\/_____\/_____\/_____\/  
    #      q0     q1     q2     q3     q4     q5
    #      
    #   New triangles (all oriented ccw): 
    #     lower triangles: (q_i, q_{i+1}, p_i)     for i=0,...,nCB+nBA
    #     upper triangles: (p_i, q_{i+1}, p_{i+1}) for i=0,...,nCB+nBA
    #
    #   Special case: nCB=0=nBA
    #     one lower and one upper triangle is created

    new_domain_points=[];
    new_simplices=[];
    for k,simplex in enumerate(simplices):    
      # vertices of simplex in domain space, ordered such that 
      # edge CA is largest side of triangle in image space
      vertices = self.domain[simplex];      # shape (3,2);
      ind_sort = (indC[k]+np.arange(3))%3;  # index array for sorting vertices as C,A,B
      C,A,B = vertices[ind_sort];
      ca,ba,cb = len_edge[k,ind_sort];
      assert ca>=ba and ca>=cb, "unexpected error in naming of skinny triangle"
      # estimate number of subdivisions (from ratio of heigt h_CA of triangle ABC vs CB and CA)      
      hCA = 2*area[k]/ca;  
      nCB = int(np.floor(cb/hCA*0.86*scale_sampling)); # Note: cb>h_CA, i.e. nCB would be always >1 without factor 0.8
      nBA = int(np.floor(ba/hCA*0.86*scale_sampling)); #       0.86 ~ sqrt(3)/2, height in regular triangle
      nCA = nCB+nBA+1;
      #print k,nCB,nBA      
      # offset for indices of points p and q in list of domain_points
      p=nPointsOrigMesh + len(new_domain_points);
      q=nPointsOrigMesh + len(new_domain_points)+nCA+1;         
      # create new sampling points 
      for x in np.arange(1,nCB+2)/(nCB+1.):         # [1/n, 2/n, ..., 1], at least [1,]
        new_domain_points.append((1-x)*C + x*B);    # p0, ..., p_nCB
      for x in np.arange(1,nBA+2)/(nBA+1.):         # [1/n, 2/n, ..., 1], at least [1,]
        new_domain_points.append((1-x)*B + x*A);    # p_{nCB+1}, ..., p_nCA
      for x in np.arange(0,nCA+1)/(nCA+1.):         # [0, 1/n, ..., (n-1)/n], at least [0,0.5]
        new_domain_points.append((1-x)*C + x*A);    # q_0, ..., q_nCA;
      # create new simplices (see figure above)
      new_simplices.extend( [(q+i, q+i+1, p+i  ) for i in range(0,nCA)] ); # lower triangles
      new_simplices.extend( [(p+i, q+i+1, p+i+1) for i in range(0,nCA)] ); # upper triangles
         
    # update points in mesh (points are no longer unique!)
    logging.debug("refining_skinny_triangles(): adding %d points"%len(new_domain_points));
    new_domain_points=np.asarray(new_domain_points);  
    new_image_points=self.mapping(new_domain_points);
    self.domain= np.vstack((self.domain,new_domain_points));    
    self.image = np.vstack((self.image, new_image_points)); 
    
   
    if bPlot:   
      from matplotlib.collections import PolyCollection
      fig = self.plot_triangulation();
      fig.suptitle("DEBUG: refine_skinny_triangles()");
      ax1,ax2 = fig.axes;
      params = dict(facecolors='r', edgecolors='none', alpha=0.3);      
      ax1.add_collection(PolyCollection(self.domain[simplices],**params));      
      
      ax2.add_collection(PolyCollection(self.image[simplices],**params));
      ax2.plot(new_image_points[:,0],new_image_points[:,1],'k.')
      
    # sanity check that total area did not change after segmentation
    old = np.sum(np.abs(self.get_area_in_domain(simplices)));
    new = np.sum(np.abs(self.get_area_in_domain(new_simplices)));
    assert(abs((old-new)/old)<1e-10) # segmentation of triangle has no holes/overlaps

    # update list of simplices
    return self.__add_new_simplices(np.asarray(new_simplices),bSkinny);  
  
  
  def refine_skinny_triangles(self,skip_triangle=None,rthresh=5,Athresh=1e-10,scale_sampling=0.5,bPlot=False):
    """
    subdivide skinny triangles in the image mesh (have very small angles)
    
    Parameters
    ----------
      skip_triangle: function mask=skip_triangle(simplices), optional
         function, that accepts a list of simplices of shape (nTriangles, 3) 
         and returns a flag for each triangle indicating if it is ignored
      rthresh : float, optional
         relative threshold, specifies, how much smaller (area) a triangle
         can be compared to the corresponding regular triangle 
      Athresh : float, optional
         area in domain threshold (relative to total domain area)    
      scale_sampling : float, optional
         increasing scale_sampling will increase the number of subdivisions,
         a typical range is between 0.5 (default) and 1
      bPlot : boolean
         if True, the triangulation including the skinny triangles is shown
    
    Returns
    --------
      number of points added to the triangulation
    
    Note
    ----
      ToDo: remove duplicate points !!
      
      See Shewchuk, Delaunay Refinement Algorithms for Triangular Mesh Generation
      http://www.cs.berkeley.edu/~jrs/papers/2dj.pdf
    
    """     
    # check if mesh is still a Delaunay mesh
    if self.__tri is None:
      raise RuntimeError('Mesh is no longer a Delaunay mesh. Subdivision not implemented for this case.');
      
    bSkinny = self.find_skinny_triangles(self.simplices,rthresh=rthresh,Athresh=Athresh);
    if skip_triangle is not None:
      bSkinny &= ~skip_triangle(self.simplices);
    if np.sum(bSkinny)==0: return; # nothing to do
    
    simplices = self.simplices[bSkinny];                   # shape (nTriangles,3)
    triangles = self.image[simplices];                     # shape (nTriangles,3,2)
    nTriangles= triangles.shape[0];

    # identify the shortest edge of the triangle in image space (not cut)
    lensq = np.sum( np.diff(triangles[:,[0,1,2,0]],axis=1)**2, axis=2); # shape (nTriangles,3)
    min_edge = np.argmin( lensq,axis=1);                                # shape (nTriangles)
 
    # find point as C (opposit to min_edge) and calculate midpoint on CA and CB
    indC = min_edge-1;
    A,B,C,new_domain_points,new_image_points = \
                self.__resample_edges_of_triangle(simplices,indC,x=(0.5,));
    # unique domain_points
    new_domain_points = np.vstack(set(map(tuple, new_domain_points.reshape(2*nTriangles,2))))
    new_image_points = self.mapping(new_domain_points);
    
    if bPlot:   
      from matplotlib.collections import PolyCollection
      fig = self.plot_triangulation();
      fig.suptitle("DEBUG: refine_skinny_triangles()");
      ax1,ax2 = fig.axes;
      params = dict(facecolors='r', edgecolors='none', alpha=0.3);      
      ax1.add_collection(PolyCollection(self.domain[simplices],**params));      
      ax1.plot(new_domain_points[:,0],new_domain_points[:,1],'k.')      
      ax2.add_collection(PolyCollection(self.image[simplices],**params));
      ax2.plot(new_image_points[:,0],new_image_points[:,1],'k.')
        
    
    # update triangulation  
    logging.debug("refining_skinny_triangles(): adding %d points"% (new_domain_points.shape[0]));
    self.image = np.vstack((self.image,new_image_points)); 
    self.domain= np.vstack((self.domain,new_domain_points));   
    self.__tri.add_points(new_domain_points);
    self.simplices = self.__tri.simplices;

    return 2*nTriangles;     


      
  def refine_large_triangles(self,is_large):
    """
    subdivide large triangles in the image mesh
    
    Parameters
    ----------
      is_large : function, mask=is_large(triangles)
        function, which accepts a list of simplices of shape (nTriangles, 3) 
        and returns a flag for each triangle indicating if it should be subdivided
    
    Returns
    --------
      number of points added to the triangulation                 
    
    Note
    ----
      Additional points are added at the center of gravity of large triangles
      and the Delaunay triangulation is recalculated. Edge flips can occur.
      This procedure is suboptimal, as it produces skinny triangles.
    """
    # check if mesh is still a Delaunay mesh
    if self.__tri is None:
      raise RuntimeError('Mesh is no longer a Delaunay mesh. Subdivision not implemented for this case.');
    
    ind = is_large(self.simplices);
    if np.sum(ind)==0: return; # nothing to do
    
    # add center of gravity for critical triangles
    new_domain_points = np.sum(self.domain[self.simplices[ind]],axis=1)/3; # shape (nTriangles,2)
    # remove invalid points (coordinates are nan)    
    # new_domain_points = new_domain_points[~np.any(np.isnan(new_domain_points),axis=1)]
    # update triangulation    
    self.__tri.add_points(new_domain_points);
    logging.debug("refining_large_triangles(): adding %d points"%(new_domain_points.shape[0]))
    
    # calculate image points and update data
    new_image_points = self.mapping(new_domain_points);
    self.image = np.vstack((self.image,new_image_points));
    self.domain= np.vstack((self.domain,new_domain_points));
    # remove degenerated triangles (p1,p2 identical to A or B) => area is 0 
    simplices = self.__tri.simplices;
    area = self.get_area_in_domain(simplices);    
    degenerated = np.abs(area/self.initial_domain_area)<1e-10;
    assert(np.all(area[~degenerated]>0));               # by construction all triangles are oriented ccw
    self.simplices = simplices[~degenerated];        # remove degenerate triangles
    
    return new_domain_points.shape[0];


  def refine_broken_triangles(self,is_broken,nDivide=10,bPlot=False,bPlotTriangles=[0]):
    """
    subdivide triangles which contain discontinuities in the image mesh or invalid vertices
      is_broken  ... function mask=is_broken(triangles) that accepts a list of 
                      simplices of shape (nTriangles, 3) and returns a flag 
                      for each triangle indicating if it should be subdivided
      nDivide    ... (opt) number of subdivisions of each side of broken triangle
      bPlot      ... (opt) plot sampling and selected points for debugging 
      bPlotTriangles (opt) list of triangle indices for which segmentation should be shown

    returns: number of new triangles
    Note: The resulting mesh will be no longer a Delaunay mesh (identical points 
          might be present, circumference rule not guaranteed). Mesh functions, 
          that need this property (like refine_large_triangles()) will not work
          after calling this function.
    """
    broken = is_broken(self.simplices);                    # shape (nSimplices)
    simplices = self.simplices[broken];                    # shape (nTriangles,3)
    triangles = self.image[simplices];                     # shape (nTriangles,3,2)
    
    # check if any of the triangles has an invalid vertex (x or y coordinate is np.nan)
    bInvalidVertex = np.any(np.isnan(triangles),axis=2);   # shape (nTriangles,3)
    if np.sum(bInvalidVertex)>0:
      raise RuntimeError("Mesh contains invalid points. Call Mesh.refine_invalid_triangles() first.");
      
    # check if subdivision is needed at all    
    nTriangles = np.sum(broken)
    if nTriangles==0: return 0;                 # noting to do!
    nPointsOrigMesh = self.image.shape[0];  
    
    # add new simplices:
    # segmentation of each broken triangle is generated in a cyclic manner,
    # starting with isolated point C and the two closest new sampling points
    # in image space, p1 + p2), continues with p3,p4,A,B.
    #    
    #             C
    #             /\
    #            /  \              \\\ largest segments of triangle in image space
    #        p1 *    *  p2          *  new sampling points
    #     ....///....\\\.............. discontinuity
    #      p3 *        * p4
    #        /          \          new triangles:
    #       /____________\           (C,p1,p2),             isolated point + closest two new points  
    #      A              B          (p1,p3,p2),(p2,p3,p4)  new broken triangles, only between new sampling points
    #                                (p4,p3,A), (p4,A,B):   rest
    # 
    # Note: one has to pay attention, that C,p1,p3,A are located on same side
    #       of the triangle, otherwise the partition will fail!     

    # identify the shortest edge of the triangle in image space (not cut)
    lensq = np.sum( np.diff(triangles[:,[0,1,2,0]],axis=1)**2, axis=2); # shape (nTriangles,3)
    min_edge = np.argmin( lensq,axis=1);                                # shape (nTriangles)
 
    # find point as C (opposit to min_edge) and resample CA and CB
    indC = min_edge-1;
    A,B,C,domain_points,image_points = self.__resample_edges_of_triangle(simplices,indC,nDivide=nDivide);
                                                           # shape (nDivide,2,nTriangles,2)
    # determine indices of broken segments (largest elements in CA and CB)
    len_segments = np.sum(np.diff(image_points,axis=0)**2,axis=-1); 
                                                    # shape (nDivide-1,2,nTriangle)
    largest_segments = np.argmax(len_segments,axis=0); # shape (2,nTriangle) 
    edge_points = np.asarray((largest_segments,largest_segments+1));
                                                    # shape (2,2,nTriangle)
 
    # set points p1 ... p4 for segmentation of triangle
    # see http://stackoverflow.com/questions/15660885/correctly-indexing-a-multidimensional-numpy-array-with-another-array-of-indices
    idx_tuple = (edge_points[...,np.newaxis],) + tuple(np.ogrid[:2,:nTriangles,:2]);
    new_domain_points = domain_points[idx_tuple];
    new_image_points  = image_points[idx_tuple];    
              # shape (2,2,nTriangle,2), indicating iDistance,iEdge,iTriangle,(x/y)
 
    # update points in mesh (points are no longer unique!)
    logging.debug("refining_broken_triangles(): adding %d points"%(4*nTriangles));
    self.image = np.vstack((self.image,new_image_points.reshape(-1,2))); 
    self.domain= np.vstack((self.domain,new_domain_points.reshape(-1,2)));    
   
    if bPlot:   
      from matplotlib.collections import PolyCollection
      fig = self.plot_triangulation(skip_triangle=is_broken);
      fig.suptitle("DEBUG: refine_broken_triangles()");
      ax1,ax2 = fig.axes;
      #params = dict(facecolors='r', edgecolors='none', alpha=0.3);      
      #ax1.add_collection(PolyCollection(self.domain[simplices],**params));   
      ax1.plot(domain_points[...,0].flat,domain_points[...,1].flat,'k.',label='test points');
      ax1.plot(new_domain_points[...,0].flat,new_domain_points[...,1].flat,'g.',label='selected points');
      ax1.legend(loc=0);      
      ax2.plot(image_points[...,0].flat,image_points[...,1].flat,'k.')
      ax2.plot(new_image_points[...,0].flat,new_image_points[...,1].flat,'g.',label='selected points');   
    
    # indices for points p1 ... p4 in new list of points self.domain 
    # (offset by number of points in the original mesh!)
    # Note: by construction, the order of p1 ... p4 corresponds exactly to the order
    #       shown above (first tuple contains points closest to C,
    #       first on CA, then on CB, second tuple beyond the discontinuity)
    (p1,p2),(p3,p4) = np.arange(4*nTriangles).reshape(2,2,nTriangles) + nPointsOrigMesh;
                                                    # shape (nTriangles,)
    # construct the five triangles from points
    t1=np.vstack((C,p1,p2));                        # shape (3,nTriangles)
    t2=np.vstack((p1,p3,p2));
    t3=np.vstack((p2,p3,p4));
    t4=np.vstack((p3,A,p4));
    t5=np.vstack((p4,A,B));
    new_simplices = np.hstack((t1,t2,t3,t4,t5)).T;  
       # shape (5*nTriangles,3), reshape as (5,nTriangles,3) to obtain subdivision of each triangle  

    # DEBUG subdivision of triangles
    if bPlot:
      ax1.add_collection(PolyCollection(self.domain[t1.T],edgecolor='none',facecolor='b',alpha=0.3));   
      ax1.add_collection(PolyCollection(self.domain[t2.T],edgecolor='none',facecolor='r',alpha=0.3));   
      ax1.add_collection(PolyCollection(self.domain[t3.T],edgecolor='none',facecolor='y',alpha=0.3));   
      ax1.add_collection(PolyCollection(self.domain[t4.T],edgecolor='none',facecolor='g',alpha=0.3));   
      ax1.add_collection(PolyCollection(self.domain[t5.T],edgecolor='none',facecolor='k',alpha=0.3));   
      for t in bPlotTriangles: # select index of triangle to look at
        BCA=[B[t],C[t],A[t]]; subdiv=[ C[t],p1[t],p2[t],p3[t],p4[t],A[t] ];
        pt=self.domain[BCA]; ax1.plot(pt[...,0],pt[...,1],'g')
        pt=self.image[BCA];  ax2.plot(pt[...,0],pt[...,1],'g')
        pt=self.domain[subdiv]; ax1.plot(pt[...,0],pt[...,1],'r')
        pt=self.image[subdiv];  ax2.plot(pt[...,0],pt[...,1],'r')

    # sanity check that total area did not change after segmentation
    old = np.sum(np.abs(self.get_area_in_domain(simplices)));
    new = np.sum(np.abs(self.get_area_in_domain(new_simplices)));
    assert(abs((old-new)/old)<1e-10) # segmentation of triangle has no holes/overlaps

    # update list of simplices
    return self.__add_new_simplices(new_simplices,broken);  
    
    
  def refine_invalid_triangles(self,nDivide=10,bPlot=False,bPlotTriangles=[0]):
    """
    subdivide triangles which have one or two invalid vertices (x or y coordinate are np.nan)
      nDivide    ... (opt) number of subdivisions of each side of triangle
      bPlot      ... (opt) plot sampling and selected points for debugging 
      bPlotTriangles (opt) list of triangle indices for which segmentation should be shown

    returns: number of new triangles
    
    Note: This function might also reuse refine_broken_triangles(), if we 
          replace NaN's by a very large but finit number. However it might
          be less clean.
    Note: The resulting mesh will be no longer a Delaunay mesh (identical 
          points might be present, circumference rule n ot guaranteed) 
          and the total area in domain is reduced.
    """
    vertices = self.image[self.simplices];                 # shape (nSimplices,3,2)    
    bInvalidVertex = np.any(np.isnan(vertices),axis=2);    # shape (nSimplices,3)   
    if ~np.any(bInvalidVertex): return 0;                  # all valid: nothing to do
    if np.all(bInvalidVertex):                             # all invalid: can do nothing
      logging.warning('all rays are invalid');
      return 0;
    
    # we consider three cases: 
    #  1. one vertex is invalid (generate two new triangles)
    #  2. two vertices are invalid (generate one new triangle)
    #  3. all vertices are invalid (triangle is skipped)
    # all triangles with only valid vertices are unchanged
    nInvalidVertices = np.sum(bInvalidVertex,axis=1);      # shape (nSimplices)
    new_simplices=[]; 
    ind_case1 = nInvalidVertices==1;
    new_simplices.extend(self.__subdivide_triangles_with_one_invalid_vertex(ind_case1,nDivide=nDivide));
    ind_case2 = nInvalidVertices==2;
    new_simplices.extend(self.__subdivide_triangles_with_two_invalid_vertices(ind_case2,nDivide=nDivide));
    new_simplices=np.reshape(new_simplices,(-1,3));    
    
    # update list of simplices
    bReplace=nInvalidVertices>0;   # includes case 1, 2 and 3
    return self.__add_new_simplices(new_simplices,bReplace);
    
    
  def __subdivide_triangles_with_one_invalid_vertex(self,bInvalid,nDivide=10):
    """
    case 1: one point is invalid (chosen as point C)
    adds new points p1 and p2 to mesh and returns new simplices
                 C
                 x
                x x              x invalid points 
               x   x             o new triangle vertices (first valid from C)
              x     x
          p1 o       o p2
            /         \          new triangles:
           /___________\           (p1,A,p2),(A,B,p2)
          A              B 
    """
    if ~np.any(bInvalid): return [];                       # nothing to do
    simplices = self.simplices[bInvalid];                  # shape (nTriangles,3)
    triangles = self.image[simplices];                     # shape (nTriangles,3,2)
    nTriangles= triangles.shape[0];
    nPointsOrigMesh = self.image.shape[0];  
    
    # find invalid point as C (index on first axis) and resample CA and CB
    indC = np.where(np.any(np.isnan(triangles),axis=-1))[1];
    A,B,C,domain_points,image_points = self.__resample_edges_of_triangle(simplices,indC,nDivide=nDivide);
                                                           # shape (nDivide,2,nTriangles,2)
    assert(np.all(np.any(np.isnan(self.image[C]),axis=-1)));  # all points C should be invalid

    # iterate over all triangles and subdivide them
    new_domain_points=[];
    new_image_points=[];
    new_simplices=[];
    for k in range(nTriangles):    
      # find index of first valid point p1 on CA and p2 on CB
      ind = np.any(np.isnan(image_points[:,:,k]),axis=-1);  # shape (nDivide,2)
      p1=np.where(~ind[:,0])[0][0];                   # first valid point on CA
      p2=np.where(~ind[:,1])[0][0];                   # first valid ponit on CB
      new_domain_points.extend((domain_points[p1,0,k,:], domain_points[p2,1,k,:]));
      new_image_points.extend( ( image_points[p1,0,k,:],  image_points[p2,1,k,:]));
      # calculate index for points p1 and p2 in self.domain = [self.domain, new_domain_points]
      P1 = nPointsOrigMesh+2*k; P2=P1+1;
      new_simplices.extend(((P1,A[k],P2), (A[k],B[k],P2)));

    # update points in mesh (points are no longer unique!)
    logging.debug("refine_invalid_triangles(case1): adding %d points"%(2*nTriangles));
    self.image = np.vstack((self.image,np.reshape(new_image_points,(2*nTriangles,2)))); 
    self.domain= np.vstack((self.domain,np.reshape(new_domain_points,(2*nTriangles,2))));  

    return np.reshape(new_simplices,(2*nTriangles,3));
    

  def __subdivide_triangles_with_two_invalid_vertices(self,bInvalid,nDivide=10):
    """
    case 2: two points are invalid (chosen as points A and B)
                 C
                 /\
                /  \              x invalid points 
               /    \             o new triangle vertices (last valid from C)
              /      \
          p1 o        o p2
            x          x          new triangle:
           xxxxxxxxxxxxxx           (p1,p2,C)
          A              B 
    """
    if ~np.any(bInvalid): return [];                       # nothing to do
    simplices = self.simplices[bInvalid];                  # shape (nTriangles,3)
    triangles = self.image[simplices];                     # shape (nTriangles,3,2)
    nTriangles= triangles.shape[0];
    nPointsOrigMesh = self.image.shape[0];  

    # find valid point as C (index on first axis) and resample CA and CB
    indC = np.where(~np.any(np.isnan(triangles),axis=-1))[1];
    A,B,C,domain_points,image_points = self.__resample_edges_of_triangle(simplices,indC,nDivide=nDivide);
                                                           # shape (nDivide,2,nTriangles,2)
    assert(np.all(np.any(np.isnan(self.image[A]),axis=-1)));  # all points A should be invalid
    assert(np.all(np.any(np.isnan(self.image[B]),axis=-1)));  # all points B should be invalid
    
    # iterate over all triangles and subdivide them
    new_domain_points=[];
    new_image_points=[];
    new_simplices=[];
    for k in range(nTriangles):    
      # find index of first valid point p1 on CA and p2 on CB
      ind = np.any(np.isnan(image_points[:,:,k]),axis=-1);  # shape (nDivide,2)
      p1=np.where(~ind[:,0])[0][-1];                   # last valid point on CA
      p2=np.where(~ind[:,1])[0][-1];                   # last valid ponit on CB
      new_domain_points.extend((domain_points[p1,0,k,:], domain_points[p2,1,k,:]));
      new_image_points.extend( ( image_points[p1,0,k,:],  image_points[p2,1,k,:]));
      # calculate index for points p1 and p2 in self.domain = [self.domain, new_domain_points]
      P1 = nPointsOrigMesh+2*k; P2=P1+1;
      new_simplices.append((P1,P2,C[k]));
 
    # update points in mesh (points are no longer unique!)
    logging.debug("refine_invalid_triangles(case2): adding %d points"%(2*nTriangles));
    self.image = np.vstack((self.image,np.reshape(new_image_points,(2*nTriangles,2)))); 
    self.domain= np.vstack((self.domain,np.reshape(new_domain_points,(2*nTriangles,2))));  

    return np.reshape(new_simplices,(nTriangles,3));
        


  def __resample_edges_of_triangle(self,simplices,indC,x=None,nDivide=10):
    """
    generate dense sampling on edges CA and CB on given simplices
    
    Parameters
    ----------
      simplices : ndarray of shape (nTriangles,3)
        vertex indices of triangles that should be resampled
      indC : vector of length nTriangles
        vertex number (mod 3) that should be used as point C
      x : vector of floats, optional
        indicates position of sampling points
      nDivide : integer, optional (only active if x=None)
        number of sampling points on CA and CB
      
    Returns
    -------
      A,B,C : vector of ints, length (nTriangles)
        indices of points A,B,C
      domain_points : ndarray of shape (nDivide,2,nTriangle,2)
        sampling points along CA,CB in domain, indices are (iPoint,iSide,iTriangle,xy)
      image_points : ndarray of shape (nDivide,2,nTriangle,2)
        sampling points along CA,CB in image
    """    
    # handle optional arguments (sampling along CA and CB)
    if x is None:
      x = np.linspace(0,1,nDivide,endpoint=True)
    else:
      x = np.asarray(x); 
      nDivide=x.size;
    # get indices of points ABC as shown above (C is isolated point)
    nTriangles = simplices.shape[0];    
    ind_triangle = np.arange(nTriangles)
    C = simplices[ind_triangle,(indC)%3];
    A = simplices[ind_triangle,(indC+1)%3];
    B = simplices[ind_triangle,(indC-1)%3];
    # create dense sampling along C->B and C->A in domain space
    CA = np.outer(1-x,self.domain[C]) + np.outer(x,self.domain[A]);
    CB = np.outer(1-x,self.domain[C]) + np.outer(x,self.domain[B]);
    # map sampling on CA and CB to image space 
    domain_points= np.hstack((CA,CB)).reshape(nDivide,2,nTriangles,2);
    image_points = self.mapping(domain_points.reshape(-1,2)).reshape(nDivide,2,nTriangles,2);
    return A,B,C,domain_points,image_points;       


  def  __add_new_simplices(self,new_simplices,bReplace):
    """
      add list of new simplices to Mesh and Replace old simplices indicated by boolean array
      perform sanity checks beforehand
        new_simplices ... shape(nTriangles,3)
        bReplace      ... shape(self.simplices.shape[0])
      returns: number of added triangles
    """
    # remove degenerated triangles (p1,p2 identical to A or B) => area is 0 
    area = self.get_area_in_domain(new_simplices);    
    degenerated = np.abs(area/self.initial_domain_area)<1e-10;
    new_simplices = new_simplices[~degenerated];        # remove degenerate triangles
    assert(np.all(area[~degenerated]>0));               # by construction all triangles are oriented ccw
    # update simplices in mesh    
    self.__tri = None; # delete initial Delaunay triangulation        
    self.simplices=np.vstack((self.simplices[~bReplace], new_simplices)); # no longer Delaunay
    return new_simplices.shape[0];
class AdaptiveParametricSpaceMapper:
    def __init__(self, parameters, target, error_norm):
        self.parameters = parameters
        self.target = target
        self.vertices = np.empty((0, len(parameters)), dtype=float)
        self.funvals = []
        self.error_norm = error_norm
        self.create_initial_mesh()
        self.iteration_times = []

    def rescale_parameters(self, values):
        rescaled = np.ndarray(values.shape)
        for parameter_id in range(len(self.parameters)):
            pmin = self.parameters[parameter_id][1]
            pmax = self.parameters[parameter_id][2]
            rescaled[parameter_id] = pmin + (pmax -
                                             pmin) * values[parameter_id]
        return rescaled

    def rescale_parameters_multiple(self, values):
        rescaled = np.ndarray(values.shape)
        for parameter_id in range(len(self.parameters)):
            pmin = self.parameters[parameter_id][1]
            pmax = self.parameters[parameter_id][2]
            rescaled[:, parameter_id] = pmin + (pmax -
                                                pmin) * values[:, parameter_id]
        return rescaled

    def inverse_rescale_parameters_multiple(self, values):
        rescaled = np.ndarray(values.shape)
        for parameter_id in range(len(self.parameters)):
            pmin = self.parameters[parameter_id][1]
            pmax = self.parameters[parameter_id][2]
            rescaled[:, parameter_id] = (values[:, parameter_id] -
                                         pmin) / (pmax - pmin)
        return rescaled

    # initial mesh is a n-parallelipiped
    def create_initial_mesh(self):
        self.vertices = np.empty(
            (2**len(self.parameters) + 1, len(self.parameters)),
            dtype=np.float)
        for vertex_id in range(2**len(self.parameters)):
            for parameter_id in range(len(self.parameters)):
                if vertex_id % (2**(parameter_id + 1)) >= 2**parameter_id:
                    self.vertices[vertex_id, parameter_id] = 1
                else:
                    self.vertices[vertex_id, parameter_id] = 0
        self.vertices[2**len(self.parameters), :] = np.mean(
            self.vertices[:2**len(self.parameters), :], axis=0)
        #grids1d = []
        #for parameter_id in range(len(self.parameters)):
        #    grids1d.append(np.linspace(0, 1, 3))
        #gridsnd = np.asarray(np.meshgrid(*(tuple(grids1d))))
        #gridsnd = np.reshape(gridsnd, (len(self.parameters), 3**len(self.parameters)))
        #self.vertices = gridsnd.T
        self.triangulation = Delaunay(np.asarray(self.vertices),
                                      incremental=True)
        self.funvals = []

    def error_estimator(self):
        nsimplex = self.triangulation.simplices.shape[0]
        simplex_error = np.empty((nsimplex, ))
        for simplex_id in range(nsimplex):
            simplex_points = self.triangulation.simplices[simplex_id, :]
            simplex_funvals = []
            for simplex_point in simplex_points:
                simplex_funvals.append(self.funvals[simplex_point])
            simplex_mean_funval = np.mean(simplex_funvals, axis=0)
            simplex_dfs = simplex_mean_funval - simplex_funvals
            print(simplex_dfs)
            simplex_dfs_norms = self.error_norm(simplex_dfs)
            simplex_error[simplex_id] = max(simplex_dfs_norms)
        return simplex_error

    def add_vertex(self, vertex):
        self.vertices.resize(
            (self.vertices.shape[0] + 1, self.vertices.shape[1]))
        self.vertices[self.vertices.shape[0] - 1, :] = vertex
        self.triangulation.add_points(np.reshape(vertex, (1, len(vertex))))

    def run(self, max_vertices=sys.maxsize):
        while len(self.vertices) < max_vertices:
            iteration_begin = time.time()
            if (self.vertices.shape[0] != len(self.funvals)):
                self.funvals.append(
                    self.target(
                        self.rescale_parameters(
                            self.vertices[len(self.funvals) - 1, :])))
                iteration_end = time.time()
                self.iteration_times.append(
                    {'target_evaluation': iteration_end - iteration_begin})
            else:
                dfs = self.error_estimator()
                error_estimator_time = time.time()
                max_simplices_ids = np.argwhere(dfs == np.max(dfs))
                max_simplices_measures = np.empty(max_simplices_ids.shape)
                print((np.argmax(dfs), np.max(dfs)))
                #max_simplices_centroids = np.empty((max_simplices_ids.size, len(self.parameters)))
                for max_simplex_id, simplex_id in enumerate(max_simplices_ids):
                    simplex_vertices = self.triangulation.simplices[
                        simplex_id, :]
                    simplex_vertices_coordinates = self.triangulation.points[
                        simplex_vertices, :]
                    max_simplices_measures[max_simplex_id] = np.linalg.det(
                        simplex_vertices_coordinates[0, 1:, :] -
                        simplex_vertices_coordinates[0, 0, :]
                    ) / math.factorial(len(self.parameters))
                max_simplex_vertices = self.triangulation.simplices[
                    max_simplices_ids[np.argmax(max_simplices_measures
                                                )], :].ravel()
                simplex_edge_lengths = np.empty(
                    (len(max_simplex_vertices), len(max_simplex_vertices)))
                for simplex_vertex1_id, simplex_vertex1 in enumerate(
                        max_simplex_vertices):
                    for simplex_vertex2_id, simplex_vertex2 in enumerate(
                            max_simplex_vertices):
                        simplex_edge_lengths[
                            simplex_vertex1_id,
                            simplex_vertex2_id] = np.linalg.norm(
                                self.triangulation.points[simplex_vertex1, :] -
                                self.triangulation.points[simplex_vertex2, :])
                    print(
                        self.rescale_parameters(
                            self.triangulation.points[simplex_vertex1]))
                    print(
                        np.real((self.funvals[simplex_vertex1][1] -
                                 self.funvals[simplex_vertex1][0])))
                simplex_vertex1_id, simplex_vertex2_id = np.unravel_index(
                    [np.argmax(simplex_edge_lengths.ravel())],
                    simplex_edge_lengths.shape)
                simplex_vertex1 = max_simplex_vertices[simplex_vertex1_id]
                simplex_vertex2 = max_simplex_vertices[simplex_vertex2_id]
                simplex_chooser_time = time.time()
                #print ((simplex_vertex1[0], simplex_vertex2[0], simplex_edge_lengths[simplex_vertex1_id, simplex_vertex2_id], np.max(dfs)))
                median = 0.5 * (
                    self.triangulation.points[simplex_vertex1[0], :] +
                    self.triangulation.points[simplex_vertex2[0], :])
                self.add_vertex(median)

                self.funvals.append(
                    self.target(self.rescale_parameters(median)))
                iteration_end_time = time.time()
                centroid = np.mean(
                    self.triangulation.points[max_simplex_vertices, :], axis=0)
                self.add_vertex(centroid)
                self.funvals.append(
                    self.target(self.rescale_parameters(centroid)))
                self.iteration_times.append({
                    'error_estimator':
                    -iteration_begin + error_estimator_time,
                    'simplex_chooser':
                    -error_estimator_time + simplex_chooser_time,
                    'target_evaluation':
                    iteration_end_time - error_estimator_time
                })
Beispiel #14
0
def triangulatePoly(coords,addPoints=False,iterations=2,debug=False):
	
	"""Triangulates a polygon with given coords using a Delaunay triangulation.
	
	Uses http://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.Delaunay.html#scipy.spatial.Delaunay to 
	calculate triangulation, then filters the triangles actually lying within the polygon.
	
	Args:
		coords (list): List of (x,y)-coordinates of corners.
	
	Keyword Args:
		addPoints (bool): Allow incremental addition of points.
		iterations (int): Number of iterations of additional point adding.
		debug (boo): Print debugging messages.
		
	Returns:
		tuple: Tuple containing:
		
			* triFinal (list): List of found triangles.
			* coordsTri (list): List of vertex coordinates.
	"""
	
	#Bookkeeping list
	triFinal=[]
		
	#Triangulate
	tri=Delaunay(coords,incremental=addPoints)
	
	#Backup original coordinates
	coordsOrg=list(coords)
	
	if debug:
		print "Found ", len(tri.simplices.copy()), "triangles in initial call."

	#Incrementally refine triangulation
	if addPoints:
		for i in range(iterations):
			
			mids=[]
			for j in range(len(tri.simplices)):
				mid=getCenterOfMass(coords[tri.simplices.copy()[j]])
				mids.append(mid)
				
			coords=np.asarray(list(coords)+mids)
			tri.add_points(mids,restart=True)
				
		if debug:
			print "Found ", len(tri.simplices.copy()), "triangles after iterations."
		
			
	#Remember assigment of points by traingulation function
	coordsTri=tri.points
	
	midsIn=[]
	midsOut=[]
	triOutIdx=[]
	triInIdx=[]
	
	for i in range(len(tri.simplices)):
	
		# Get COM of triangle
		mid=getCenterOfMass(coordsTri[tri.simplices.copy()[i]])
		
		#Check if triangle is inside original polygon
		if checkInsidePolyVec(mid[0],mid[1],coordsOrg):
			triFinal.append(tri.simplices.copy()[i])
			midsIn.append(mid)
			triInIdx.append(i)		
		else:
			triOutIdx.append(i)
			midsOut.append(mid)
	
	if debug:
		print "Removed ", len(tri.simplices.copy())-len(triFinal), "triangles through COM criteria."	
		print "Returning ", len(triFinal), "triangles."	
		
		
	return triFinal,coordsTri
Beispiel #15
0
class Delaunay(Sampler):
    """
    Delaunay Class.

    Inherits from the Sampler class and augments pick and update with the
    mechanics of the Delanauy triangulation method

    Attributes
    ----------
    triangulation : scipy.spatial.qhull.Delaunay
        The Delaunay triangulation model object
    simplex_cache : dict
        Cached values of simplices for Delaunay triangulation
    explore_priority : float
        The priority of exploration against exploitation

    See Also
    --------
    Sampler : Base Class
    """

    name = 'Delaunay'

    def __init__(self, lower, upper, explore_priority=0.0001):
        """
        Initialise the Delaunay class.

        .. note ::

            Currently only supports rectangular type restrictions on the
            parameter space

        Parameters
        ----------
        lower : array_like
            Lower or minimum bounds for the parameter space
        upper : array_like
            Upper or maximum bounds for the parameter space
        explore_priority : float, optional
            The priority of exploration against exploitation
        """
        Sampler.__init__(self, lower, upper)
        self.triangulation = None  # Delaunay model
        self.simplex_cache = {}  # Pre-computed values of simplices
        self.explore_priority = explore_priority

    def update(self, uid, y_true):
        """
        Update a job with its observed value.

        Parameters
        ----------
        uid : str
            A hexadecimal ID that identifies the job to be updated
        y_true : float
            The observed value corresponding to the job identified by 'uid'

        Returns
        -------
        int
            Index location in the data lists 'Delaunay.X' and
            'Delaunay.y' corresponding to the job being updated
        """
        return self._update(uid, y_true)

    def pick(self):
        """
        Pick the next feature location for the next observation to be taken.

        This uses the recursive Delaunay subdivision algorithm.

        Returns
        -------
        numpy.ndarray
            Location in the parameter space for the next observation to be
            taken
        str
            A random hexadecimal ID to identify the corresponding job
        """
        n = len(self.X)

        # -- note that we are assuming the points in X are not reordered by
        # the scipy Delaunay implementation

        n_corners = 2**self.dims
        if n < n_corners + 1:

            # Bootstrap with a regular sampling strategy to get it started
            xq = grid_sample(self.lower, self.upper, n)
            yq_exp = [0.]
        else:

            X = self.X()  # calling returns the value as an array
            y = self.y()
            virtual = self.virtual_flag()

            # Otherwise, recursive subdivide the edges with the Delaunay model
            if not self.triangulation:
                self.triangulation = ScipyDelaunay(X, incremental=True)

            # Weight by hyper-volume
            simplices = [tuple(s) for s in self.triangulation.vertices]
            cache = self.simplex_cache

            def get_value(s):

                # Computes the sample value as:
                #   hyper-volume of simplex * variance of values in simplex
                ind = list(s)
                value = (np.var(y[ind]) + self.explore_priority) * \
                    np.linalg.det((X[ind] - X[ind[0]])[1:])
                if not np.max(virtual[ind]):
                    cache[s] = value
                return value

            # Mostly the simplices won't change from call to call - cache!
            sample_value = [
                cache[s] if s in cache else get_value(s) for s in simplices
            ]

            # alternatively, a nicely vectorised computation might work here
            # profile and check what the bottleneck is

            # Extract the points in the highest value simplex
            simplex_indices = list(simplices[np.argmax(sample_value)])
            simplex = X[simplex_indices]
            simplex_v = y[simplex_indices]

            # Weight the position in this simplex based on value deviation
            eps = 1e-3
            weight = eps + np.abs(simplex_v - np.mean(simplex_v))
            weight /= np.sum(weight)
            xq = np.sum(weight * simplex, axis=0)  # dot
            yq_exp = np.sum(weight * simplex_v, axis=0)

            self.triangulation.add_points(xq[np.newaxis, :])  # incremental

        uid = Sampler._assign(self, xq, yq_exp)
        return xq, uid
class AdaptiveParametricSpaceMapper:
    def __init__(self, parameters, target, error_norm):
        self.parameters = parameters
        self.target = target
        self.vertices = np.empty((0, len(parameters)), dtype=float)
        self.funvals = []
        self.error_norm = error_norm
        self.create_initial_mesh()
        self.iteration_times = []
    
    def rescale_parameters(self, values):
        rescaled = np.ndarray(values.shape)
        for parameter_id in range(len(self.parameters)):
            pmin = self.parameters[parameter_id][1]
            pmax = self.parameters[parameter_id][2]
            rescaled[parameter_id] = pmin+(pmax-pmin)*values[parameter_id]
        return rescaled
    
    def rescale_parameters_multiple(self, values):
        rescaled = np.ndarray(values.shape)
        for parameter_id in range(len(self.parameters)):
            pmin = self.parameters[parameter_id][1]
            pmax = self.parameters[parameter_id][2]
            rescaled[:,parameter_id] = pmin+(pmax-pmin)*values[:,parameter_id]
        return rescaled
    
    def inverse_rescale_parameters_multiple(self, values):
        rescaled = np.ndarray(values.shape)
        for parameter_id in range(len(self.parameters)):
            pmin = self.parameters[parameter_id][1]
            pmax = self.parameters[parameter_id][2]
            rescaled[:,parameter_id] = (values[:,parameter_id]-pmin)/(pmax-pmin)
        return rescaled
    
    # initial mesh is a n-parallelipiped
    def create_initial_mesh(self):
        self.vertices = np.empty((2**len(self.parameters)+1,len(self.parameters)), dtype=np.float)
        for vertex_id in range(2**len(self.parameters)):
            for parameter_id in range(len(self.parameters)):
                if vertex_id % (2**(parameter_id+1)) >= 2**parameter_id:
                    self.vertices[vertex_id, parameter_id] = 1
                else:
                    self.vertices[vertex_id, parameter_id] = 0
        self.vertices[2**len(self.parameters), :] = np.mean(self.vertices[:2**len(self.parameters),:], axis=0)
        #grids1d = []
        #for parameter_id in range(len(self.parameters)):
        #    grids1d.append(np.linspace(0, 1, 3))
        #gridsnd = np.asarray(np.meshgrid(*(tuple(grids1d))))
        #gridsnd = np.reshape(gridsnd, (len(self.parameters), 3**len(self.parameters)))
        #self.vertices = gridsnd.T
        self.triangulation = Delaunay(np.asarray(self.vertices), incremental=True)
        self.funvals = []
    
    def error_estimator(self):
        nsimplex = self.triangulation.simplices.shape[0]
        simplex_error = np.empty((nsimplex,))
        for simplex_id in range(nsimplex):
            simplex_points = self.triangulation.simplices[simplex_id, :]
            simplex_funvals = []
            for simplex_point in simplex_points:
                simplex_funvals.append(self.funvals[simplex_point])
            simplex_mean_funval = np.mean(simplex_funvals, axis=0)
            simplex_dfs = simplex_mean_funval - simplex_funvals
            print(simplex_dfs)
            simplex_dfs_norms = self.error_norm(simplex_dfs)
            simplex_error[simplex_id] = max(simplex_dfs_norms)
        return simplex_error
        
    def add_vertex(self, vertex):
        self.vertices.resize((self.vertices.shape[0]+1, self.vertices.shape[1]))
        self.vertices[self.vertices.shape[0]-1, :] = vertex
        self.triangulation.add_points(np.reshape(vertex, (1, len(vertex))))
    
    def run(self, max_vertices=sys.maxsize):
        while len(self.vertices)<max_vertices:
            iteration_begin = time.time()
            if (self.vertices.shape[0] != len(self.funvals)):
                self.funvals.append(self.target(self.rescale_parameters(self.vertices[len(self.funvals)-1,:])))
                iteration_end = time.time()
                self.iteration_times.append({'target_evaluation': iteration_end-iteration_begin})
            else:
                dfs = self.error_estimator()
                error_estimator_time = time.time()
                max_simplices_ids = np.argwhere(dfs==np.max(dfs))
                max_simplices_measures = np.empty(max_simplices_ids.shape)
                print((np.argmax(dfs),np.max(dfs)))
                #max_simplices_centroids = np.empty((max_simplices_ids.size, len(self.parameters)))
                for max_simplex_id, simplex_id in enumerate(max_simplices_ids):
                    simplex_vertices = self.triangulation.simplices[simplex_id, :]
                    simplex_vertices_coordinates = self.triangulation.points[simplex_vertices,:]
                    max_simplices_measures[max_simplex_id] = np.linalg.det(simplex_vertices_coordinates[0,1:,:]-simplex_vertices_coordinates[0,0,:])/math.factorial(len(self.parameters))
                max_simplex_vertices = self.triangulation.simplices[max_simplices_ids[np.argmax(max_simplices_measures)],:].ravel()
                simplex_edge_lengths = np.empty((len(max_simplex_vertices), len(max_simplex_vertices)))
                for simplex_vertex1_id, simplex_vertex1 in enumerate(max_simplex_vertices):
                    for simplex_vertex2_id, simplex_vertex2 in enumerate(max_simplex_vertices):
                        simplex_edge_lengths[simplex_vertex1_id, simplex_vertex2_id] = np.linalg.norm(self.triangulation.points[simplex_vertex1,:] - self.triangulation.points[simplex_vertex2,:])
                    print (self.rescale_parameters(self.triangulation.points[simplex_vertex1]))
                    print (np.real((self.funvals[simplex_vertex1][1]-self.funvals[simplex_vertex1][0])))
                simplex_vertex1_id, simplex_vertex2_id = np.unravel_index([np.argmax(simplex_edge_lengths.ravel())], simplex_edge_lengths.shape)
                simplex_vertex1 = max_simplex_vertices[simplex_vertex1_id]
                simplex_vertex2 = max_simplex_vertices[simplex_vertex2_id]
                simplex_chooser_time = time.time()
                #print ((simplex_vertex1[0], simplex_vertex2[0], simplex_edge_lengths[simplex_vertex1_id, simplex_vertex2_id], np.max(dfs)))
                median = 0.5*(self.triangulation.points[simplex_vertex1[0],:] + self.triangulation.points[simplex_vertex2[0],:])
                self.add_vertex(median)

                self.funvals.append(self.target(self.rescale_parameters(median)))
                iteration_end_time = time.time()
                centroid = np.mean(self.triangulation.points[max_simplex_vertices,:], axis=0)
                self.add_vertex(centroid)
                self.funvals.append(self.target(self.rescale_parameters(centroid)))
                self.iteration_times.append({'error_estimator': -iteration_begin + error_estimator_time,
                                             'simplex_chooser': -error_estimator_time + simplex_chooser_time,
                                             'target_evaluation': iteration_end_time - error_estimator_time})
Beispiel #17
0
                    max_p2 = e[1]
                    max_v = v

        #sample center of this edge
        center = edge_center(max_p1, max_p2)
        P = dataGrid.grid_num(int(center[0]), int(center[1]))
        if P in S or P < 1 or P >= dataGrid.size:
            cell = np.random.choice(range(1, dataGrid.size + 1), 1)
            while cell[0] in S:
                cell = np.random.choice(range(1, dataGrid.size + 1), 1)
            P = cell[0]

    M[P - 1] = dataGrid.data_at_loc(P)[:, 1]
    S.add(P)
    x, y = dataGrid.coord(P)
    tri.add_points([(x - 1, y - 1)])
    update_edge_values()

    #Plotting
    full_data = interpolateData(M, 3, dataGrid)
    exp_data = clipSimilarityMatrix(getSimilarityMatrix(full_data, dataGrid))

    ax[0, 1].imshow(exp_data)
    ax[1, 1].imshow(true_data)

    measured_points = np.zeros(dataGrid.dims)
    for s in S:
        x, y = dataGrid.coord(s)
        measured_points[x - 1, y - 1] = 1
    ax[1, 0].imshow(measured_points)
Beispiel #18
0
class AdaptiveMesh(object):
    """
  Implementation of an adaptive mesh for a given mapping f:domain->image.
  We start from a Delaunay triangulation in the domain of f. This grid
  will be distorted in the image space. We refine the mesh by subdividing
  large or broken triangles. This process can be iterated, e.g. wehn a 
  triangle is cut multiple times (use threshold for minimal size of triangle
  in domain space). Points outside of the domain (e.g. raytrace fails) 
  should be mapped to image point (np.nan,np.nan) and are handled separately.
  
  ToDo: add unit tests
  """
    def __init__(self, initial_domain, mapping):
        """
    Initialize mesh, mapping and image points.
      initial_domain ... 2d array of shape (nPoints,2)
      mapping        ... function image=mapping(domain) that accepts a list of  
                           domain points and returns corresponding image points
    """
        from scipy.spatial import Delaunay

        assert (initial_domain.ndim == 2 and initial_domain.shape[1] == 2)
        self.initial_domain = initial_domain
        self.mapping = mapping
        # triangulation of initial domain
        self.__tri = Delaunay(initial_domain, incremental=True)
        self.simplices = self.__tri.simplices
        # calculate distorted grid
        self.initial_image = self.mapping(self.initial_domain)
        assert (self.initial_image.ndim == 2)
        assert (self.initial_image.shape == (self.initial_domain.shape[0], 2))
        # current domain and image during refinement and for plotting
        self.domain = self.initial_domain
        self.image = self.initial_image
        # initial domain area
        self.initial_domain_area = np.sum(self.get_area_in_domain())

    def get_mesh(self):
        """ 
    return triangles and points in domain and image space
       domain,image:  coordinate array of shape (nPoints,2)
       simplices:     index array for vertices of each triangle, shape (nTriangles,3)
    Returns: (domain,image,simplices)
    """
        return self.domain, self.image, self.simplices

    def plot_triangulation(self, skip_triangle=None):
        """
    plot current triangulation of adaptive mesh in domain and image space
    
    Parameters
    ----------
      skip_triangle : function mask=skip_triangle(simplices), optional
        function that accepts a ndarray of simplices of shape (nTriangles, 3) 
        and returns a flag for each triangle indicating that it should not be drawn
        
    Return
    ------
      handle to new matplotlib figure
    """
        simplices = self.simplices.copy()
        if skip_triangle is not None:
            skip = skip_triangle(simplices)
            skipped_simplices = simplices[skip]
            simplices = simplices[~skip]

        fig, (ax1, ax2) = plt.subplots(2)
        ax1.set_title("Sampling + Triangulation in Domain")
        if skip_triangle is not None and np.sum(skip) > 0:
            ax1.triplot(self.domain[:, 0], self.domain[:, 1],
                        skipped_simplices, 'k:')
        if len(simplices) > 0:
            ax1.triplot(self.domain[:, 0], self.domain[:, 1], simplices, 'b-')
        ax1.plot(self.initial_domain[:, 0], self.initial_domain[:, 1], 'r.')

        ax2.set_title("Sampling + Triangulation in Image")
        if len(simplices) > 0:
            ax2.triplot(self.image[:, 0], self.image[:, 1], simplices, 'b-')
        ax2.plot(self.initial_image[:, 0], self.initial_image[:, 1], 'r.')

        # if aspect is close to 1 force aspect to be 1
        if 1. / 4 < ax1.get_data_ratio() < 4: ax1.set_aspect('equal')
        if 1. / 4 < ax2.get_data_ratio() < 4: ax2.set_aspect('equal')
        return fig

    def get_area_in_domain(self, simplices=None):
        """
    calculate signed area of given simplices in domain space
      simplices ... (opt) list of simplices, shape (nTriangles,3)
    Returns:
      1d vector of size nTriangles containing the signed area of each triangle
      (positive: ccw orientation, negative: cw orientation of vertices)
    """
        if simplices is None: simplices = self.simplices
        x, y = self.domain[simplices].T
        # See http://geomalgorithms.com/a01-_area.html#2D%20Polygons
        return 0.5 * ((x[1] - x[0]) * (y[2] - y[0]) - (x[2] - x[0]) *
                      (y[1] - y[0]))

    def get_area_in_image(self, simplices=None):
        """
    calculate signed area of given simplices in image space
    (see get_area_in_domain())
    """
        if simplices is None: simplices = self.simplices
        x, y = self.image[simplices].T
        # See http://geomalgorithms.com/a01-_area.html#2D%20Polygons
        return 0.5 * ((x[1] - x[0]) * (y[2] - y[0]) - (x[2] - x[0]) *
                      (y[1] - y[0]))

    def find_broken_triangles(
        self,
        simplices=None,
        lthresh=None,
    ):
        """
    identify triangles that are cut in image space or include invalid vertices
    
    Parameters
    ----------
      simplices : ndarray of ints, shape (nTriangles,3), optional
         Indices of the points forming the simplices in the triangulation. 
      lthresh : float, optional
         absolute threshold for longest side of broken triangle 
         
    Returns
    -------
      bBroken: boolean vector of length nTriangles
         indicates, if triangle is broken
    """
        if simplices is None: simplices = self.simplices
        # x and y coordinates for each vertex in each triangle
        triangles = self.image[simplices]
        # calculate maximum of (squared) length of two sides of each triangle
        # (X[0]-X[1])**2 + (Y[0]-Y[1])**2; (X[1]-X[2])**2 + (Y[1]-Y[2])**2
        max_lensq = np.max(np.sum(np.diff(triangles, axis=1)**2, axis=2),
                           axis=1)
        # default: mark triangle as broken, if max side is 3 times larger than median value
        if lthresh is None: lthresh = 3 * np.sqrt(np.nanmedian(max_lensq))
        # valid triangles: all sides smaller than lthresh, none of its vertices invalid (np.nan)
        bValid = max_lensq < lthresh**2
        return ~bValid
        # Note: differs from (max_lensq >= lthresh**2), if some vertices are invalid!

    def find_skinny_triangles(self, simplices=None, rthresh=5, Athresh=1e-10):
        """
    Identify triangles that deviate strongly from regular triangle (have very small angles)
    
    Parameters
    ----------
      simplices : ndarray of ints, shape (nTriangles,3), optional
         Indices of the points forming the simplices in the triangulation. 
      rthresh : float, optional
         relative threshold, specifies, how much smaller (area) a triangle
         can be compared to the corresponding regular triangle
      Athresh : float, optional
         area in domain threshold (relative to total domain area)      
      
    Returns
    -------
      bSkinny: boolean vector of length nTriangles
         indicates, if triangle is skinny
    """
        if simplices is None: simplices = self.simplices
        # x and y coordinates for each vertex in each triangle, shape (nTriangles,3,2)
        triangles = self.image[simplices]
        # calculate (squared) length of each edge in triangle, shape (nTriangles,3)
        # (X[0]-X[1])**2 + (Y[0]-Y[1])**2; (X[1]-X[2])**2 + (Y[1]-Y[2])**2, (X[2]-X[0])**2 + (Y[2]-Y[0])**2
        lensq = np.sum(np.diff(triangles[:, [0, 1, 2, 0]], axis=1)**2, axis=2)
        # calculate (signed) area of triangles
        x, y = triangles.T
        area = 0.5 * ((x[1] - x[0]) * (y[2] - y[0]) - (x[2] - x[0]) *
                      (y[1] - y[0]))
        # skinny triangles: area of triangle is much smaller (by factor rthresh)
        # than the area of regular triangle sqrt(3)/4*maxlensq ~ 0.433*maxlensq
        bSkinny = np.abs(area) < (0.433 / rthresh) * np.nanmax(lensq, axis=1)
        # note: nan's in area are not catched so far
        bSkinny &= np.abs(
            area) / self.initial_domain_area > Athresh  # triangle is large

        return bSkinny

    def refine_skinny_triangles_complicated(self,
                                            skip_triangle=None,
                                            rthresh=5,
                                            scale_sampling=0.5,
                                            bPlot=False):
        """
    subdivide skinny triangles in the image mesh (have very small angles)
    
    Parameters
    ----------
      skip_triangle: function mask=skip_triangle(simplices), optional
         function, that accepts a list of simplices of shape (nTriangles, 3) 
         and returns a flag for each triangle indicating if it is ignored
      rthresh : float, optional
         relative threshold, specifies, how much smaller (area) a triangle
         can be compared to the corresponding regular triangle 
      scale_sampling : float, optional
         increasing scale_sampling will increase the number of subdivisions,
         a typical range is between 0.5 (default) and 1
      bPlot : boolean
         if True, the triangulation including the skinny triangles is shown
    
    Returns
    --------
      number of points added to the triangulation
    
    Note
    ----
      Artefacts occur at the boundary of the split triangles, if the neighboring
      triangle is not split (very thin, overlapping triangles). Separation should
      maybe be done differently (using Delaunay triangulation, just splitting largest side by two☺)
    
      See Shewchuk, Delaunay Refinement Algorithms for Triangular Mesh Generation
      http://www.cs.berkeley.edu/~jrs/papers/2dj.pdf
    
    """
        bSkinny = self.find_skinny_triangles(self.simplices, rthresh=rthresh)
        if skip_triangle is not None:
            bSkinny &= ~skip_triangle(self.simplices)
        if np.sum(bSkinny) == 0:
            return
            # nothing to do

        simplices = self.simplices[bSkinny]
        # shape (nTriangles,3)
        triangles = self.image[simplices]
        # shape (nTriangles,3,2)
        nPointsOrigMesh = self.image.shape[0]

        # find largest side of triangle in image space -> named as CA
        len_edge = np.sqrt(
            np.sum(np.diff(triangles[:, [0, 1, 2, 0]], axis=1)**2, axis=2))
        # shape (nTriangles,3)
        indC = np.argmax(len_edge, axis=1)
        area = np.abs(self.get_area_in_image(simplices))

        # iterate over all triangles and subdivide them
        #
        #   notation for skinny triangle (oriented ccw)
        #             C .
        #              /|           CA: longest side of triangle
        #             / |
        #            /  |
        #        A  /___| B
        #
        #   subdivision of skinny triangle:
        #   we add nCB points along CB, nBA points along BA
        #   and nCB+nBA+1 points along CA and create new triangles
        #   as indicated below for nCB=3, nBA=1.
        #
        #          p0     p1     p2     p3     p4     p5
        #          .______.______.______B______.______A
        #         / \    / \    / \    / \    / \    /
        #        /   \  /   \  /   \  /   \  /   \  /
        #       C_____\/_____\/_____\/_____\/_____\/
        #      q0     q1     q2     q3     q4     q5
        #
        #   New triangles (all oriented ccw):
        #     lower triangles: (q_i, q_{i+1}, p_i)     for i=0,...,nCB+nBA
        #     upper triangles: (p_i, q_{i+1}, p_{i+1}) for i=0,...,nCB+nBA
        #
        #   Special case: nCB=0=nBA
        #     one lower and one upper triangle is created

        new_domain_points = []
        new_simplices = []
        for k, simplex in enumerate(simplices):
            # vertices of simplex in domain space, ordered such that
            # edge CA is largest side of triangle in image space
            vertices = self.domain[simplex]
            # shape (3,2);
            ind_sort = (indC[k] + np.arange(3)) % 3
            # index array for sorting vertices as C,A,B
            C, A, B = vertices[ind_sort]
            ca, ba, cb = len_edge[k, ind_sort]
            assert ca >= ba and ca >= cb, "unexpected error in naming of skinny triangle"
            # estimate number of subdivisions (from ratio of heigt h_CA of triangle ABC vs CB and CA)
            hCA = 2 * area[k] / ca
            nCB = int(np.floor(cb / hCA * 0.86 * scale_sampling))
            # Note: cb>h_CA, i.e. nCB would be always >1 without factor 0.8
            nBA = int(np.floor(ba / hCA * 0.86 * scale_sampling))
            #       0.86 ~ sqrt(3)/2, height in regular triangle
            nCA = nCB + nBA + 1
            #print k,nCB,nBA
            # offset for indices of points p and q in list of domain_points
            p = nPointsOrigMesh + len(new_domain_points)
            q = nPointsOrigMesh + len(new_domain_points) + nCA + 1
            # create new sampling points
            for x in np.arange(1, nCB + 2) / (
                    nCB + 1.):  # [1/n, 2/n, ..., 1], at least [1,]
                new_domain_points.append((1 - x) * C + x * B)
                # p0, ..., p_nCB
            for x in np.arange(1, nBA + 2) / (
                    nBA + 1.):  # [1/n, 2/n, ..., 1], at least [1,]
                new_domain_points.append((1 - x) * B + x * A)
                # p_{nCB+1}, ..., p_nCA
            for x in np.arange(0, nCA + 1) / (
                    nCA + 1.):  # [0, 1/n, ..., (n-1)/n], at least [0,0.5]
                new_domain_points.append((1 - x) * C + x * A)
                # q_0, ..., q_nCA;
            # create new simplices (see figure above)
            new_simplices.extend([(q + i, q + i + 1, p + i)
                                  for i in range(0, nCA)])
            # lower triangles
            new_simplices.extend([(p + i, q + i + 1, p + i + 1)
                                  for i in range(0, nCA)])
            # upper triangles

        # update points in mesh (points are no longer unique!)
        logging.debug("refining_skinny_triangles(): adding %d points" %
                      len(new_domain_points))
        new_domain_points = np.asarray(new_domain_points)
        new_image_points = self.mapping(new_domain_points)
        self.domain = np.vstack((self.domain, new_domain_points))
        self.image = np.vstack((self.image, new_image_points))

        if bPlot:
            from matplotlib.collections import PolyCollection
            fig = self.plot_triangulation()
            fig.suptitle("DEBUG: refine_skinny_triangles()")
            ax1, ax2 = fig.axes
            params = dict(facecolors='r', edgecolors='none', alpha=0.3)
            ax1.add_collection(PolyCollection(self.domain[simplices],
                                              **params))

            ax2.add_collection(PolyCollection(self.image[simplices], **params))
            ax2.plot(new_image_points[:, 0], new_image_points[:, 1], 'k.')

        # sanity check that total area did not change after segmentation
        old = np.sum(np.abs(self.get_area_in_domain(simplices)))
        new = np.sum(np.abs(self.get_area_in_domain(new_simplices)))
        assert (abs((old - new) / old) < 1e-10
                )  # segmentation of triangle has no holes/overlaps

        # update list of simplices
        return self.__add_new_simplices(np.asarray(new_simplices), bSkinny)

    def refine_skinny_triangles(self,
                                skip_triangle=None,
                                rthresh=5,
                                Athresh=1e-10,
                                scale_sampling=0.5,
                                bPlot=False):
        """
    subdivide skinny triangles in the image mesh (have very small angles)
    
    Parameters
    ----------
      skip_triangle: function mask=skip_triangle(simplices), optional
         function, that accepts a list of simplices of shape (nTriangles, 3) 
         and returns a flag for each triangle indicating if it is ignored
      rthresh : float, optional
         relative threshold, specifies, how much smaller (area) a triangle
         can be compared to the corresponding regular triangle 
      Athresh : float, optional
         area in domain threshold (relative to total domain area)    
      scale_sampling : float, optional
         increasing scale_sampling will increase the number of subdivisions,
         a typical range is between 0.5 (default) and 1
      bPlot : boolean
         if True, the triangulation including the skinny triangles is shown
    
    Returns
    --------
      number of points added to the triangulation
    
    Note
    ----
      ToDo: remove duplicate points !!
      
      See Shewchuk, Delaunay Refinement Algorithms for Triangular Mesh Generation
      http://www.cs.berkeley.edu/~jrs/papers/2dj.pdf
    
    """
        # check if mesh is still a Delaunay mesh
        if self.__tri is None:
            raise RuntimeError(
                'Mesh is no longer a Delaunay mesh. Subdivision not implemented for this case.'
            )

        bSkinny = self.find_skinny_triangles(self.simplices,
                                             rthresh=rthresh,
                                             Athresh=Athresh)
        if skip_triangle is not None:
            bSkinny &= ~skip_triangle(self.simplices)
        if np.sum(bSkinny) == 0:
            return
            # nothing to do

        simplices = self.simplices[bSkinny]
        # shape (nTriangles,3)
        triangles = self.image[simplices]
        # shape (nTriangles,3,2)
        nTriangles = triangles.shape[0]

        # identify the shortest edge of the triangle in image space (not cut)
        lensq = np.sum(np.diff(triangles[:, [0, 1, 2, 0]], axis=1)**2, axis=2)
        # shape (nTriangles,3)
        min_edge = np.argmin(lensq, axis=1)
        # shape (nTriangles)

        # find point as C (opposit to min_edge) and calculate midpoint on CA and CB
        indC = min_edge - 1
        A,B,C,new_domain_points,new_image_points = \
                    self.__resample_edges_of_triangle(simplices,indC,x=(0.5,))
        # unique domain_points
        new_domain_points = np.vstack(
            set(map(tuple, new_domain_points.reshape(2 * nTriangles, 2))))
        new_image_points = self.mapping(new_domain_points)

        if bPlot:
            from matplotlib.collections import PolyCollection
            fig = self.plot_triangulation()
            fig.suptitle("DEBUG: refine_skinny_triangles()")
            ax1, ax2 = fig.axes
            params = dict(facecolors='r', edgecolors='none', alpha=0.3)
            ax1.add_collection(PolyCollection(self.domain[simplices],
                                              **params))
            ax1.plot(new_domain_points[:, 0], new_domain_points[:, 1], 'k.')
            ax2.add_collection(PolyCollection(self.image[simplices], **params))
            ax2.plot(new_image_points[:, 0], new_image_points[:, 1], 'k.')

        # update triangulation
        logging.debug("refining_skinny_triangles(): adding %d points" %
                      (new_domain_points.shape[0]))
        self.image = np.vstack((self.image, new_image_points))
        self.domain = np.vstack((self.domain, new_domain_points))
        self.__tri.add_points(new_domain_points)
        self.simplices = self.__tri.simplices

        return 2 * nTriangles

    def refine_large_triangles(self, is_large):
        """
    subdivide large triangles in the image mesh
    
    Parameters
    ----------
      is_large : function, mask=is_large(triangles)
        function, which accepts a list of simplices of shape (nTriangles, 3) 
        and returns a flag for each triangle indicating if it should be subdivided
    
    Returns
    --------
      number of points added to the triangulation                 
    
    Note
    ----
      Additional points are added at the center of gravity of large triangles
      and the Delaunay triangulation is recalculated. Edge flips can occur.
      This procedure is suboptimal, as it produces skinny triangles.
    """
        # check if mesh is still a Delaunay mesh
        if self.__tri is None:
            raise RuntimeError(
                'Mesh is no longer a Delaunay mesh. Subdivision not implemented for this case.'
            )

        ind = is_large(self.simplices)
        if np.sum(ind) == 0:
            return
            # nothing to do

        # add center of gravity for critical triangles
        new_domain_points = np.sum(self.domain[self.simplices[ind]],
                                   axis=1) / 3
        # shape (nTriangles,2)
        # remove invalid points (coordinates are nan)
        # new_domain_points = new_domain_points[~np.any(np.isnan(new_domain_points),axis=1)]
        # update triangulation
        self.__tri.add_points(new_domain_points)
        logging.debug("refining_large_triangles(): adding %d points" %
                      (new_domain_points.shape[0]))

        # calculate image points and update data
        new_image_points = self.mapping(new_domain_points)
        self.image = np.vstack((self.image, new_image_points))
        self.domain = np.vstack((self.domain, new_domain_points))
        # remove degenerated triangles (p1,p2 identical to A or B) => area is 0
        simplices = self.__tri.simplices
        area = self.get_area_in_domain(simplices)
        degenerated = np.abs(area / self.initial_domain_area) < 1e-10
        assert (np.all(area[~degenerated] > 0))
        # by construction all triangles are oriented ccw
        self.simplices = simplices[~degenerated]
        # remove degenerate triangles

        return new_domain_points.shape[0]

    def refine_broken_triangles(self,
                                is_broken,
                                nDivide=10,
                                bPlot=False,
                                bPlotTriangles=[0]):
        """
    subdivide triangles which contain discontinuities in the image mesh or invalid vertices
      is_broken  ... function mask=is_broken(triangles) that accepts a list of 
                      simplices of shape (nTriangles, 3) and returns a flag 
                      for each triangle indicating if it should be subdivided
      nDivide    ... (opt) number of subdivisions of each side of broken triangle
      bPlot      ... (opt) plot sampling and selected points for debugging 
      bPlotTriangles (opt) list of triangle indices for which segmentation should be shown

    returns: number of new triangles
    Note: The resulting mesh will be no longer a Delaunay mesh (identical points 
          might be present, circumference rule not guaranteed). Mesh functions, 
          that need this property (like refine_large_triangles()) will not work
          after calling this function.
    """
        broken = is_broken(self.simplices)
        # shape (nSimplices)
        simplices = self.simplices[broken]
        # shape (nTriangles,3)
        triangles = self.image[simplices]
        # shape (nTriangles,3,2)

        # check if any of the triangles has an invalid vertex (x or y coordinate is np.nan)
        bInvalidVertex = np.any(np.isnan(triangles), axis=2)
        # shape (nTriangles,3)
        if np.sum(bInvalidVertex) > 0:
            raise RuntimeError(
                "Mesh contains invalid points. Call Mesh.refine_invalid_triangles() first."
            )

        # check if subdivision is needed at all
        nTriangles = np.sum(broken)
        if nTriangles == 0:
            return 0
            # noting to do!
        nPointsOrigMesh = self.image.shape[0]

        # add new simplices:
        # segmentation of each broken triangle is generated in a cyclic manner,
        # starting with isolated point C and the two closest new sampling points
        # in image space, p1 + p2), continues with p3,p4,A,B.
        #
        #             C
        #             /\
        #            /  \              \\\ largest segments of triangle in image space
        #        p1 *    *  p2          *  new sampling points
        #     ....///....\\\.............. discontinuity
        #      p3 *        * p4
        #        /          \          new triangles:
        #       /____________\           (C,p1,p2),             isolated point + closest two new points
        #      A              B          (p1,p3,p2),(p2,p3,p4)  new broken triangles, only between new sampling points
        #                                (p4,p3,A), (p4,A,B):   rest
        #
        # Note: one has to pay attention, that C,p1,p3,A are located on same side
        #       of the triangle, otherwise the partition will fail!

        # identify the shortest edge of the triangle in image space (not cut)
        lensq = np.sum(np.diff(triangles[:, [0, 1, 2, 0]], axis=1)**2, axis=2)
        # shape (nTriangles,3)
        min_edge = np.argmin(lensq, axis=1)
        # shape (nTriangles)

        # find point as C (opposit to min_edge) and resample CA and CB
        indC = min_edge - 1
        A, B, C, domain_points, image_points = self.__resample_edges_of_triangle(
            simplices, indC, nDivide=nDivide)
        # shape (nDivide,2,nTriangles,2)
        # determine indices of broken segments (largest elements in CA and CB)
        len_segments = np.sum(np.diff(image_points, axis=0)**2, axis=-1)
        # shape (nDivide-1,2,nTriangle)
        largest_segments = np.argmax(len_segments, axis=0)
        # shape (2,nTriangle)
        edge_points = np.asarray((largest_segments, largest_segments + 1))
        # shape (2,2,nTriangle)

        # set points p1 ... p4 for segmentation of triangle
        # see http://stackoverflow.com/questions/15660885/correctly-indexing-a-multidimensional-numpy-array-with-another-array-of-indices
        idx_tuple = (edge_points[..., np.newaxis], ) + tuple(
            np.ogrid[:2, :nTriangles, :2])
        new_domain_points = domain_points[idx_tuple]
        new_image_points = image_points[idx_tuple]
        # shape (2,2,nTriangle,2), indicating iDistance,iEdge,iTriangle,(x/y)

        # update points in mesh (points are no longer unique!)
        logging.debug("refining_broken_triangles(): adding %d points" %
                      (4 * nTriangles))
        self.image = np.vstack((self.image, new_image_points.reshape(-1, 2)))
        self.domain = np.vstack((self.domain, new_domain_points.reshape(-1,
                                                                        2)))

        if bPlot:
            from matplotlib.collections import PolyCollection
            fig = self.plot_triangulation(skip_triangle=is_broken)
            fig.suptitle("DEBUG: refine_broken_triangles()")
            ax1, ax2 = fig.axes
            #params = dict(facecolors='r', edgecolors='none', alpha=0.3);
            #ax1.add_collection(PolyCollection(self.domain[simplices],**params));
            ax1.plot(domain_points[..., 0].flat,
                     domain_points[..., 1].flat,
                     'k.',
                     label='test points')
            ax1.plot(new_domain_points[..., 0].flat,
                     new_domain_points[..., 1].flat,
                     'g.',
                     label='selected points')
            ax1.legend(loc=0)
            ax2.plot(image_points[..., 0].flat, image_points[..., 1].flat,
                     'k.')
            ax2.plot(new_image_points[..., 0].flat,
                     new_image_points[..., 1].flat,
                     'g.',
                     label='selected points')

        # indices for points p1 ... p4 in new list of points self.domain
        # (offset by number of points in the original mesh!)
        # Note: by construction, the order of p1 ... p4 corresponds exactly to the order
        #       shown above (first tuple contains points closest to C,
        #       first on CA, then on CB, second tuple beyond the discontinuity)
        (p1, p2), (p3, p4) = np.arange(4 * nTriangles).reshape(
            2, 2, nTriangles) + nPointsOrigMesh
        # shape (nTriangles,)
        # construct the five triangles from points
        t1 = np.vstack((C, p1, p2))
        # shape (3,nTriangles)
        t2 = np.vstack((p1, p3, p2))
        t3 = np.vstack((p2, p3, p4))
        t4 = np.vstack((p3, A, p4))
        t5 = np.vstack((p4, A, B))
        new_simplices = np.hstack((t1, t2, t3, t4, t5)).T
        # shape (5*nTriangles,3), reshape as (5,nTriangles,3) to obtain subdivision of each triangle

        # DEBUG subdivision of triangles
        if bPlot:
            ax1.add_collection(
                PolyCollection(self.domain[t1.T],
                               edgecolor='none',
                               facecolor='b',
                               alpha=0.3))
            ax1.add_collection(
                PolyCollection(self.domain[t2.T],
                               edgecolor='none',
                               facecolor='r',
                               alpha=0.3))
            ax1.add_collection(
                PolyCollection(self.domain[t3.T],
                               edgecolor='none',
                               facecolor='y',
                               alpha=0.3))
            ax1.add_collection(
                PolyCollection(self.domain[t4.T],
                               edgecolor='none',
                               facecolor='g',
                               alpha=0.3))
            ax1.add_collection(
                PolyCollection(self.domain[t5.T],
                               edgecolor='none',
                               facecolor='k',
                               alpha=0.3))
            for t in bPlotTriangles:  # select index of triangle to look at
                BCA = [B[t], C[t], A[t]]
                subdiv = [C[t], p1[t], p2[t], p3[t], p4[t], A[t]]
                pt = self.domain[BCA]
                ax1.plot(pt[..., 0], pt[..., 1], 'g')
                pt = self.image[BCA]
                ax2.plot(pt[..., 0], pt[..., 1], 'g')
                pt = self.domain[subdiv]
                ax1.plot(pt[..., 0], pt[..., 1], 'r')
                pt = self.image[subdiv]
                ax2.plot(pt[..., 0], pt[..., 1], 'r')

        # sanity check that total area did not change after segmentation
        old = np.sum(np.abs(self.get_area_in_domain(simplices)))
        new = np.sum(np.abs(self.get_area_in_domain(new_simplices)))
        assert (abs((old - new) / old) < 1e-10
                )  # segmentation of triangle has no holes/overlaps

        # update list of simplices
        return self.__add_new_simplices(new_simplices, broken)

    def refine_invalid_triangles(self,
                                 nDivide=10,
                                 bPlot=False,
                                 bPlotTriangles=[0]):
        """
    subdivide triangles which have one or two invalid vertices (x or y coordinate are np.nan)
      nDivide    ... (opt) number of subdivisions of each side of triangle
      bPlot      ... (opt) plot sampling and selected points for debugging 
      bPlotTriangles (opt) list of triangle indices for which segmentation should be shown

    returns: number of new triangles
    
    Note: This function might also reuse refine_broken_triangles(), if we 
          replace NaN's by a very large but finit number. However it might
          be less clean.
    Note: The resulting mesh will be no longer a Delaunay mesh (identical 
          points might be present, circumference rule n ot guaranteed) 
          and the total area in domain is reduced.
    """
        vertices = self.image[self.simplices]
        # shape (nSimplices,3,2)
        bInvalidVertex = np.any(np.isnan(vertices), axis=2)
        # shape (nSimplices,3)
        if ~np.any(bInvalidVertex):
            return 0
            # all valid: nothing to do
        if np.all(bInvalidVertex):  # all invalid: can do nothing
            logging.warning('all rays are invalid')
            return 0

        # we consider three cases:
        #  1. one vertex is invalid (generate two new triangles)
        #  2. two vertices are invalid (generate one new triangle)
        #  3. all vertices are invalid (triangle is skipped)
        # all triangles with only valid vertices are unchanged
        nInvalidVertices = np.sum(bInvalidVertex, axis=1)
        # shape (nSimplices)
        new_simplices = []
        ind_case1 = nInvalidVertices == 1
        new_simplices.extend(
            self.__subdivide_triangles_with_one_invalid_vertex(
                ind_case1, nDivide=nDivide))
        ind_case2 = nInvalidVertices == 2
        new_simplices.extend(
            self.__subdivide_triangles_with_two_invalid_vertices(
                ind_case2, nDivide=nDivide))
        new_simplices = np.reshape(new_simplices, (-1, 3))

        # update list of simplices
        bReplace = nInvalidVertices > 0
        # includes case 1, 2 and 3
        return self.__add_new_simplices(new_simplices, bReplace)

    def __subdivide_triangles_with_one_invalid_vertex(self,
                                                      bInvalid,
                                                      nDivide=10):
        """
    case 1: one point is invalid (chosen as point C)
    adds new points p1 and p2 to mesh and returns new simplices
                 C
                 x
                x x              x invalid points 
               x   x             o new triangle vertices (first valid from C)
              x     x
          p1 o       o p2
            /         \          new triangles:
           /___________\           (p1,A,p2),(A,B,p2)
          A              B 
    """
        if ~np.any(bInvalid):
            return []
            # nothing to do
        simplices = self.simplices[bInvalid]
        # shape (nTriangles,3)
        triangles = self.image[simplices]
        # shape (nTriangles,3,2)
        nTriangles = triangles.shape[0]
        nPointsOrigMesh = self.image.shape[0]

        # find invalid point as C (index on first axis) and resample CA and CB
        indC = np.where(np.any(np.isnan(triangles), axis=-1))[1]
        A, B, C, domain_points, image_points = self.__resample_edges_of_triangle(
            simplices, indC, nDivide=nDivide)
        # shape (nDivide,2,nTriangles,2)
        assert (np.all(np.any(np.isnan(self.image[C]), axis=-1)))
        # all points C should be invalid

        # iterate over all triangles and subdivide them
        new_domain_points = []
        new_image_points = []
        new_simplices = []
        for k in range(nTriangles):
            # find index of first valid point p1 on CA and p2 on CB
            ind = np.any(np.isnan(image_points[:, :, k]), axis=-1)
            # shape (nDivide,2)
            p1 = np.where(~ind[:, 0])[0][0]
            # first valid point on CA
            p2 = np.where(~ind[:, 1])[0][0]
            # first valid ponit on CB
            new_domain_points.extend(
                (domain_points[p1, 0, k, :], domain_points[p2, 1, k, :]))
            new_image_points.extend(
                (image_points[p1, 0, k, :], image_points[p2, 1, k, :]))
            # calculate index for points p1 and p2 in self.domain = [self.domain, new_domain_points]
            P1 = nPointsOrigMesh + 2 * k
            P2 = P1 + 1
            new_simplices.extend(((P1, A[k], P2), (A[k], B[k], P2)))

        # update points in mesh (points are no longer unique!)
        logging.debug("refine_invalid_triangles(case1): adding %d points" %
                      (2 * nTriangles))
        self.image = np.vstack(
            (self.image, np.reshape(new_image_points, (2 * nTriangles, 2))))
        self.domain = np.vstack(
            (self.domain, np.reshape(new_domain_points, (2 * nTriangles, 2))))

        return np.reshape(new_simplices, (2 * nTriangles, 3))

    def __subdivide_triangles_with_two_invalid_vertices(
            self, bInvalid, nDivide=10):
        """
    case 2: two points are invalid (chosen as points A and B)
                 C
                 /\
                /  \              x invalid points 
               /    \             o new triangle vertices (last valid from C)
              /      \
          p1 o        o p2
            x          x          new triangle:
           xxxxxxxxxxxxxx           (p1,p2,C)
          A              B 
    """
        if ~np.any(bInvalid):
            return []
            # nothing to do
        simplices = self.simplices[bInvalid]
        # shape (nTriangles,3)
        triangles = self.image[simplices]
        # shape (nTriangles,3,2)
        nTriangles = triangles.shape[0]
        nPointsOrigMesh = self.image.shape[0]

        # find valid point as C (index on first axis) and resample CA and CB
        indC = np.where(~np.any(np.isnan(triangles), axis=-1))[1]
        A, B, C, domain_points, image_points = self.__resample_edges_of_triangle(
            simplices, indC, nDivide=nDivide)
        # shape (nDivide,2,nTriangles,2)
        assert (np.all(np.any(np.isnan(self.image[A]), axis=-1)))
        # all points A should be invalid
        assert (np.all(np.any(np.isnan(self.image[B]), axis=-1)))
        # all points B should be invalid

        # iterate over all triangles and subdivide them
        new_domain_points = []
        new_image_points = []
        new_simplices = []
        for k in range(nTriangles):
            # find index of first valid point p1 on CA and p2 on CB
            ind = np.any(np.isnan(image_points[:, :, k]), axis=-1)
            # shape (nDivide,2)
            p1 = np.where(~ind[:, 0])[0][-1]
            # last valid point on CA
            p2 = np.where(~ind[:, 1])[0][-1]
            # last valid ponit on CB
            new_domain_points.extend(
                (domain_points[p1, 0, k, :], domain_points[p2, 1, k, :]))
            new_image_points.extend(
                (image_points[p1, 0, k, :], image_points[p2, 1, k, :]))
            # calculate index for points p1 and p2 in self.domain = [self.domain, new_domain_points]
            P1 = nPointsOrigMesh + 2 * k
            P2 = P1 + 1
            new_simplices.append((P1, P2, C[k]))

        # update points in mesh (points are no longer unique!)
        logging.debug("refine_invalid_triangles(case2): adding %d points" %
                      (2 * nTriangles))
        self.image = np.vstack(
            (self.image, np.reshape(new_image_points, (2 * nTriangles, 2))))
        self.domain = np.vstack(
            (self.domain, np.reshape(new_domain_points, (2 * nTriangles, 2))))

        return np.reshape(new_simplices, (nTriangles, 3))

    def __resample_edges_of_triangle(self,
                                     simplices,
                                     indC,
                                     x=None,
                                     nDivide=10):
        """
    generate dense sampling on edges CA and CB on given simplices
    
    Parameters
    ----------
      simplices : ndarray of shape (nTriangles,3)
        vertex indices of triangles that should be resampled
      indC : vector of length nTriangles
        vertex number (mod 3) that should be used as point C
      x : vector of floats, optional
        indicates position of sampling points
      nDivide : integer, optional (only active if x=None)
        number of sampling points on CA and CB
      
    Returns
    -------
      A,B,C : vector of ints, length (nTriangles)
        indices of points A,B,C
      domain_points : ndarray of shape (nDivide,2,nTriangle,2)
        sampling points along CA,CB in domain, indices are (iPoint,iSide,iTriangle,xy)
      image_points : ndarray of shape (nDivide,2,nTriangle,2)
        sampling points along CA,CB in image
    """
        # handle optional arguments (sampling along CA and CB)
        if x is None:
            x = np.linspace(0, 1, nDivide, endpoint=True)
        else:
            x = np.asarray(x)
            nDivide = x.size
        # get indices of points ABC as shown above (C is isolated point)
        nTriangles = simplices.shape[0]
        ind_triangle = np.arange(nTriangles)
        C = simplices[ind_triangle, (indC) % 3]
        A = simplices[ind_triangle, (indC + 1) % 3]
        B = simplices[ind_triangle, (indC - 1) % 3]
        # create dense sampling along C->B and C->A in domain space
        CA = np.outer(1 - x, self.domain[C]) + np.outer(x, self.domain[A])
        CB = np.outer(1 - x, self.domain[C]) + np.outer(x, self.domain[B])
        # map sampling on CA and CB to image space
        domain_points = np.hstack((CA, CB)).reshape(nDivide, 2, nTriangles, 2)
        image_points = self.mapping(domain_points.reshape(-1, 2)).reshape(
            nDivide, 2, nTriangles, 2)
        return A, B, C, domain_points, image_points

    def __add_new_simplices(self, new_simplices, bReplace):
        """
      add list of new simplices to Mesh and Replace old simplices indicated by boolean array
      perform sanity checks beforehand
        new_simplices ... shape(nTriangles,3)
        bReplace      ... shape(self.simplices.shape[0])
      returns: number of added triangles
    """
        # remove degenerated triangles (p1,p2 identical to A or B) => area is 0
        area = self.get_area_in_domain(new_simplices)
        degenerated = np.abs(area / self.initial_domain_area) < 1e-10
        new_simplices = new_simplices[~degenerated]
        # remove degenerate triangles
        assert (np.all(area[~degenerated] > 0))
        # by construction all triangles are oriented ccw
        # update simplices in mesh
        self.__tri = None
        # delete initial Delaunay triangulation
        self.simplices = np.vstack((self.simplices[~bReplace], new_simplices))
        # no longer Delaunay
        return new_simplices.shape[0]