Created
December 15, 2011 18:25
-
-
Save cvr/1482210 to your computer and use it in GitHub Desktop.
Recursive bisection method for 2d convex quadrilaterals
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python | |
# vim: set fileencoding=utf-8 : | |
# -*- coding: utf-8 -*- | |
######################################################################## | |
# Copyright (C) 2011 by Carlos Veiga Rodrigues. All rights reserved. | |
# author: Carlos Veiga Rodrigues <[email protected]> | |
# | |
# This program can be redistribuited and modified | |
# under the terms of the GNU Lesser General Public License | |
# as published by the Free Software Foundation, | |
# either version 3 of the License or any later version. | |
# This program is distributed WITHOUT ANY WARRANTY, | |
# without even the implied warranty of MERCHANTABILITY | |
# or FITNESS FOR A PARTICULAR PURPOSE. | |
# For more details consult the GNU Lesser General Public License | |
# at http://www.gnu.org/licenses/lgpl.html. | |
# | |
# ChangeLog (date - version - author): | |
# * December 2011 - 1.0 - Carlos Veiga Rodrigues | |
# | |
# Summary: | |
# Recursive bisection method for 2d convex quadrilaterals, which allows | |
# to capture more than one zero of the intersection of two functions, | |
# u(x,y) and v(x,y). Instead of the traditional bisection method, | |
# which refines the search of an zero and allows only to find one, | |
# this creates a recursive tree from the division of the original 2d element | |
# into 4, progressively eliminating form the search sub-elements that do not | |
# meet the requirements for housing a zero. | |
# This is based on the work of A. Globus, C. Levit, and T. Lasinski | |
# on finding critical points in flow topology, although it looks like | |
# they use a traditional bisection method for the 3D case only. | |
# (see http://alglobus.net/NASAwork/topology) | |
# | |
# Advantages: | |
# * More than one zero can be found with this method. | |
# Disadvantages: | |
# * There is no way to discriminate from a case with infinite solutions. | |
# * If a zero lies on the boundary between 2 or more elements, both will | |
# return the same zero. | |
######################################################################## | |
import sys | |
sys.setrecursionlimit (10000) | |
import numpy as np | |
######################################################################## | |
## FUNCTIONS u (x, y) AND v (x, y) | |
######################################################################## | |
def ufun (x, y): | |
return x | |
def vfun (x, y): | |
return y | |
######################################################################## | |
## BILINEAR INTERPOLATION OF SPATIAL COORDINATES AND ELEMENT DIVISION | |
######################################################################## | |
def bilin_interp (xcv, c): | |
xp = (1-c[1])*( xcv[0,0]*(1-c[0]) + xcv[1,0]*c[0] )\ | |
+c[1]*( xcv[0,1]*(1-c[0]) + xcv[1,1]*c[0] ) | |
return xp | |
def divide_cv_in_4 (cv): | |
## INTERPOLATE NEW VERTEXES | |
#fs = bilin_interp (cv, [.5, 0.]) | |
#fn = bilin_interp (cv, [.5, 1.]) | |
#fw = bilin_interp (cv, [0., .5]) | |
#fe = bilin_interp (cv, [1., .5]) | |
#fp = bilin_interp (cv, [.5, .5]) | |
fs = np.mean(cv[:,0]) | |
fn = np.mean(cv[:,1]) | |
fw = np.mean(cv[0,:]) | |
fe = np.mean(cv[1,:]) | |
fp = np.mean(cv) | |
## DEFINE NEW CV'S | |
cvsw = np.array([[cv[0,0], fw], [fs, fp]], dtype=np.float64) | |
cvse = np.array([[fs, fp], [cv[1,0], fe]], dtype=np.float64) | |
cvnw = np.array([[fw, cv[0,1]], [fp, fn]], dtype=np.float64) | |
cvne = np.array([[fp, fn], [fe, cv[1,1]]], dtype=np.float64) | |
return cvsw, cvse, cvnw, cvne | |
######################################################################## | |
## RECURSIVE TREE BISECTION 2D ALGORITHM | |
######################################################################## | |
def rectree_bisection2d (xcv, ycv, ufun, vfun,\ | |
tol=1.E-15, maxiter=None): | |
if (2,2)!=np.shape(xcv) or (2,2)!=np.shape(ycv): | |
raise RuntimeError ("element shape != (2,2). Aborting") | |
## TEST 1 | |
def test_point_out (ucv, vcv): | |
isout = (0>ucv).all() or (0<ucv).all()\ | |
or (0>vcv).all() or (0<vcv).all() | |
return isout | |
## TEST 2 | |
def test_point_vertex (ucv, vcv): | |
return ((0==ucv) & (0==vcv)).any() | |
## TEST 3 | |
#def test_inf_sol (ucv, vcv): | |
# return (ucv/np.max(np.abs(ucv))==vcv/np.max(np.abs(vcv))).all() | |
## GET U, V | |
ucv, vcv = ufun (xcv, ycv), vfun (xcv, ycv) | |
## TEST IF POINT IS INSIDE INITIAL CV | |
if test_point_out (ucv, vcv): | |
raise RuntimeError ("Bad initial condition. Point outside CV.") | |
## DEFINE RECURSIVE FUNCTION | |
def recursive_testing (xcv, ycv, ucv, vcv, qsi, eta,\ | |
tol=1.E-15, iter=0, maxiter=None): | |
iter+= 1 | |
found, ii, jj = [], [], [] | |
## FUNCTION FOR LIST UPDATE | |
def update_list (ii, i): | |
if type(i)==list: | |
ii.extend(i) | |
else: | |
ii.append(i) | |
return ii | |
## GET U, V | |
ucv, vcv = ufun (xcv, ycv), vfun (xcv, ycv) | |
## TEST IF 0 IS IN VERTEXES | |
if test_point_vertex (ucv, vcv): | |
if 0==ucv[0,0] and 0==vcv[0,0]: | |
ii = update_list (ii, qsi[0]) | |
jj = update_list (jj, eta[0]) | |
if 0==ucv[1,0] and 0==vcv[1,0]: | |
ii = update_list (ii, qsi[1]) | |
jj = update_list (jj, eta[0]) | |
if 0==ucv[0,1] and 0==vcv[0,1]: | |
ii = update_list (ii, qsi[0]) | |
jj = update_list (jj, eta[1]) | |
if 0==ucv[1,1] and 0==vcv[1,1]: | |
ii = update_list (ii, qsi[1]) | |
jj = update_list (jj, eta[1]) | |
return ii, jj, [3], iter | |
## TEST IF I REACHED MY LIMIT | |
if None!=tol \ | |
and abs(np.diff(qsi))<=tol \ | |
and abs(np.diff(eta))<=tol: | |
ii = update_list (ii, np.mean(qsi)) | |
jj = update_list (jj, np.mean(eta)) | |
return ii, jj, [2], iter | |
## TEST IF MAXIMUM RECURSION LEVELS ARE REACHED | |
if None!=maxiter and iter>=maxiter: | |
ii = update_list (ii, np.mean(qsi)) | |
jj = update_list (jj, np.mean(eta)) | |
return ii, jj, [1], iter | |
## ARRAYS WHERE TO STORE STUFF | |
iiter = np.zeros([4], dtype=np.int) | |
## DIVIDE INTO 4 ELEMENTS | |
xsw, xse, xnw, xne = divide_cv_in_4 (xcv) | |
ysw, yse, ynw, yne = divide_cv_in_4 (ycv) | |
## GET U V IN EACH OF THE 4 ELEMENTS | |
usw, vsw = ufun (xsw, ysw), vfun (xsw, ysw) | |
use, vse = ufun (xse, yse), vfun (xse, yse) | |
unw, vnw = ufun (xnw, ynw), vfun (xnw, ynw) | |
une, vne = ufun (xne, yne), vfun (xne, yne) | |
## LAUNCH EACH OF MY ELEMENTS | |
if not test_point_out (usw, vsw): | |
qsw = np.array([qsi[0], np.mean(qsi)]) | |
esw = np.array([eta[0], np.mean(eta)]) | |
i, j, ifound, iiter[0] = recursive_testing (\ | |
xsw, ysw, usw, vsw, qsw, esw,\ | |
tol, iter, maxiter) | |
if []!=ifound and np.max(ifound)>0: | |
ii = update_list (ii, i) | |
jj = update_list (jj, j) | |
found = update_list (found, ifound) | |
if not test_point_out (use, vse): | |
qse = np.array([np.mean(qsi), qsi[1]]) | |
ese = np.array([eta[0], np.mean(eta)]) | |
i, j, ifound, iiter[1] = recursive_testing (\ | |
xse, yse, use, vse, qse, ese,\ | |
tol, iter, maxiter) | |
if []!=ifound and np.max(ifound)>0: | |
ii = update_list (ii, i) | |
jj = update_list (jj, j) | |
found = update_list (found, ifound) | |
if not test_point_out (unw, vnw): | |
qnw = np.array([qsi[0], np.mean(qsi)]) | |
enw = np.array([np.mean(eta), eta[1]]) | |
i, j, ifound, iiter[2] = recursive_testing (\ | |
xnw, ynw, unw, vnw, qnw, enw,\ | |
tol, iter, maxiter) | |
if []!=ifound and np.max(ifound)>0: | |
ii = update_list (ii, i) | |
jj = update_list (jj, j) | |
found = update_list (found, ifound) | |
if not test_point_out (une, vne): | |
qne = np.array([np.mean(qsi), qsi[1]]) | |
ene = np.array([np.mean(eta), eta[1]]) | |
i, j, ifound, iiter[3] = recursive_testing (\ | |
xne, yne, une, vne, qne, ene,\ | |
tol, iter, maxiter) | |
if []!=ifound and np.max(ifound)>0: | |
ii = update_list (ii, i) | |
jj = update_list (jj, j) | |
found = update_list (found, ifound) | |
## PROCESS ITERATIONS | |
iter = np.maximum(iter, np.max(iiter)) | |
if []!=found and np.max(found)>0: | |
return ii, jj, found, iter | |
else: | |
return [], [], [], iter | |
## RUN MY RECURSIVE FUNCTION | |
qsi = np.array([0., 1.]) | |
eta = np.array([0., 1.]) | |
ii, jj, found, iter = recursive_testing (\ | |
xcv, ycv, ucv, vcv, qsi, eta, tol=tol, maxiter=maxiter) | |
print "Iterations = %d" % iter | |
if []!=found: | |
found = np.array(found) | |
print "zeros exact = %d" % np.size(np.flatnonzero(found==3)) | |
print "zeros tol = %d" % np.size(np.flatnonzero(found==2)) | |
print "zeros iter = %d" % np.size(np.flatnonzero(found==1)) | |
for (i, j, k) in zip(ii, jj, found): | |
xx = bilin_interp (xcv, [i, j]) | |
yy = bilin_interp (ycv, [i, j]) | |
print "%d : (%.4f , %.4f) -> (%.4f, %.4f)" % \ | |
(k, i, j, xx, yy) | |
return | |
######################################################################## | |
## CODE TO TEST THE METHOD | |
######################################################################## | |
def test_method (xcv, ycv, ufun, vfun, c, msg=None, maxiter=None): | |
print "\n" + 50*"=" | |
if None!=msg: | |
print msg | |
if None!=c: | |
xp = bilin_interp (xcv, c) | |
yp = bilin_interp (ycv, c) | |
else: | |
xp, yp = 0, 0 | |
rectree_bisection2d (xcv-xp, ycv-yp,\ | |
ufun=ufun, vfun=vfun, maxiter=maxiter) | |
if None!=c: | |
print 33*"-" | |
print "True=(%.4f , %.4f)" % tuple(c) | |
return | |
######################################################################## | |
## EXECUTE | |
######################################################################## | |
if __name__ == '__main__': | |
xcv = np.array([[0., 1.], [0., 1.]]) | |
ycv = np.array([[0., 0.], [1., 1.]]) | |
c = [0.11, 0.62] | |
test_method (xcv, ycv, ufun, vfun, c, msg="POINT IN POLYGON TEST") | |
# | |
xcv = np.array([[0., 1.], [0.1, 1.2]]) | |
ycv = np.array([[0., 0.], [1., 1.]]) | |
c = [0.11, 0.62] | |
test_method (xcv, ycv, ufun, vfun, c, msg="POINT IN POLYGON TEST") | |
# | |
xcv = np.array([[0., 1.], [0.1, 1.2]]) | |
ycv = np.array([[0., 0.], [1., 0.9]]) | |
c = [0.11, 0.62] | |
test_method (xcv, ycv, ufun, vfun, c, msg="POINT IN POLYGON TEST") | |
# | |
xcv = np.array([[0., 1.], [0., 1.]]) | |
ycv = np.array([[0., 0.], [1., 1.]]) | |
c = [0.5, 0.5] | |
test_method (xcv, ycv, ufun, vfun, c, msg="POINT IN POLYGON TEST") | |
# | |
ucv = np.array([[10., 10.], [-2., 10.]]) | |
vcv = np.array([[10., 10.], [-1., 10.]]) | |
test_method (ucv, vcv, ufun, vfun, None,\ | |
msg="NO ROOTS SHOULD EVER BE FOUND") | |
# | |
ucv = np.array([[6., -2.], [ 6., 6.]]) | |
vcv = np.array([[8., -2.], [ 8., -2.]]) | |
test_method (ucv, vcv, ufun, vfun, None, msg="ONE SOLUTION") | |
# | |
ucv = np.array([[-2., 2.], [ 2., 2.]]) | |
vcv = np.array([[-1., -5.], [-5., 50.]]) | |
test_method (ucv, vcv, ufun, vfun, None, msg="TWO VALID SOLUTIONS") | |
# | |
ucv = np.array([[-2., 2.], [ 2., 2.]]) | |
vcv = np.array([[-1., -5.], [-5., 10.]]) | |
test_method (ucv, vcv, ufun, vfun, None, msg="TWO VALID SOLUTIONS") | |
# | |
ucv = np.array([[10., 10.], [-2., 10.]]) | |
vcv = np.array([[10., 10.], [-2., 10.]]) | |
test_method (ucv, vcv, ufun, vfun, None, msg="INFINITE SOLUTIONS",\ | |
maxiter=10) | |
# | |
ucv = np.array([[2., -2.], [ 6., 2.]]) | |
vcv = np.array([[4., -4.], [12., 4.]]) | |
test_method (ucv, vcv, ufun, vfun, None, msg="INFINITE SOLUTIONS",\ | |
maxiter=10) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment