""" 
This is for 3D point double and 3D doublet line (e.g. used for induction zone).

For 2D Vortex Doublet Panel, look at DoubletPanel2D
"""

import numpy as np
import unittest
try:
    from numpy import trapezoid
except:
    from numpy import trapz as trapezoid
    

# --------------------------------------------------------------------------------}
# --- Point doublet 
# --------------------------------------------------------------------------------{
def doublet_u(Xcp,Ycp,Zcp,m,x0=[0,0,0]): 
    """
    Velocity field induced by one doublet located at x0 with vector intensity m
         u = 1/(4pi)       *( 3* r * m.r/|r|**5 - m/|r|**3)
         u = 1/(4pi |r|**3)*( 3* r * m.r/|r|**2 - m)
    Xcp, Ycp, Zcp: Cartesian coordinates of control points
    """
    
    Xcp=np.asarray(Xcp)
    Ycp=np.asarray(Ycp)
    Zcp=np.asarray(Zcp)
    
    DX = Xcp - x0[0]
    DY = Ycp - x0[1]
    DZ = Zcp - x0[2]

    r_norm3 = (DX**2 + DY**2 + DZ**2 )**(3/2) # |r|**3
    r_norm3=np.asarray(r_norm3)

    # --- Avoiding singularity by introducing a temporarily fake norm
    bSing= r_norm3<1e-8
    r_norm3[bSing]=1 # Temporary hack, replaced at the end

    r_norm5 = (r_norm3 )**(5/3)               # |r|**5
    m_dot_r = DX*m[0] + DY*m[1] + DZ*m[2]     # m . r
    Fact1 = 1./(4*np.pi)*3*m_dot_r/r_norm5
    Fact2 = 1./(4*np.pi)/r_norm3

    ui = np.asarray(Fact1*DX - Fact2*m[0])
    vi = np.asarray(Fact1*DY - Fact2*m[1])
    wi = np.asarray(Fact1*DZ - Fact2*m[2])

    # --- Singularity
    ui[bSing]=0
    vi[bSing]=0
    wi[bSing]=0
    
    return ui,vi,wi

def doublet_u_polar(rcp,zcp,m_z,z0=0): 
    """
    Velocity field induced by one doublet located at z0 on the z axis with vector intensity (0,0,mz)
         ur = 3 m_z /(4pi) r (z-z0) / |r|**5
         uz =   m_z /(4pi) (3 (z-z0)**2 / |r|**5 -1/|r|**3)
    Control points defined by polar coordinates `rcp` and `zcp`.
    """
    if np.any(rcp<0):
        raise Exception('Script meant for positive r')
    rcp=np.asarray(rcp)
    zcp=np.asarray(zcp)

    DZ = zcp-z0

    r_norm3 = (rcp**2 + DZ**2 )**(3/2) # |r|**3
    r_norm3=np.asarray(r_norm3)

    # --- Avoiding singularity by introducing a temporarily fake norm
    bSing= r_norm3<1e-8
    r_norm3[bSing]=1 # Temporary hack, replaced at the end

    r_norm5 = (r_norm3 )**(5/3)               # |r|**5

    ur = np.asarray( 3*rcp*m_z / (4*np.pi) * DZ / r_norm5                      )
    uz = np.asarray(       m_z / (4*np.pi) * (3 * DZ**2 / r_norm5 - 1/r_norm3) )

    # --- Singularity
    ur[bSing]= 0
    uz[bSing]= 0
    
    return ur,uz


# --------------------------------------------------------------------------------}
# --- Line doublet
# --------------------------------------------------------------------------------{
def doublet_line_polar_u_num(rcp,zcp,dmz_dz,z0=0,zmax=1000,nQuad=100):
    """
    Velocity field induced by a doublet line (on the z axis) of intensity `dmz_dz`.
    Control points defined by polar coordinates `rcp` and `zcp`.
    The line goes from `z0` to `zmax`.
    Numerical integration is used with `nQuad` quadrature points.

    """
    rcp=np.asarray(rcp)
    zcp=np.asarray(zcp)

    zq = np.linspace(z0,zmax,nQuad)

    # --- Summation
    dz = zq[1]-zq[0]
    mz = dmz_dz * dz
    ur =  np.zeros(rcp.shape)
    uz =  np.zeros(rcp.shape)
    for z0 in zq: 
        dur, duz = doublet_u_polar(rcp,zcp,mz,z0=z0)
        ur += dur
        uz += duz

    return ur, uz

def doublet_line_polar_u(rcp,zcp,dmz_dz, bSelfInd=False):
    r"""
    Velocity field induced by a semi-infinite doublet line (on the z axis) of intensity `dmz_dz`
    Control points defined by polar coordinates `rcp` and `zcp`.

     \int  1/(r^2 + (z-x)^2 )^(3/2) dx
     \int  1/(r^2 + (z-x)^2 )^(5/2) dx
    """
    if np.any(rcp<0):
        raise Exception('Script meant for positive r')
    r=np.asarray(rcp)
    z=np.asarray(zcp)

    # Vectorial "if" statements to isolate singular regions of the domain
    bZ0    = np.abs(z)<1e-8
    bR0    = np.abs(r)<1e-8
    bZ0R0  = np.logical_and(bZ0,bR0)
    bZ0Rp  = np.logical_and(bZ0, np.abs(r)>1e-8)
    bR0Zp  = np.logical_and(bR0, z>1e-8)
    bR0Zm  = np.logical_and(bR0, z<-1e-8)
    bOK    = np.logical_and(~bZ0,~bR0)

    uz=np.zeros(r.shape)
    ur=np.zeros(r.shape)

    norm2 = r**2+z**2
    uz[bOK]  = dmz_dz/(4*np.pi) * 1/r[bOK]**2 * ( z[bOK]**3/(norm2[bOK])**(3/2) - z[bOK]/(norm2[bOK])**(1/2) )
    uz[bZ0Rp] = 0
    uz[bR0Zm] = dmz_dz/(4*np.pi) * 1/norm2[bR0Zm]
    #uz[bR0Zp] = dmz_dz/(4*np.pi) * 1/norm2[bR0Zp] #<<< No singularity there, but we force it to 0
    ur[bOK]   =-dmz_dz/(4*np.pi) * r[bOK]          *  1/(norm2[bOK]  )**(3/2)
    ur[bZ0Rp] =-dmz_dz/(4*np.pi) * r[bZ0Rp]        *  1/(norm2[bZ0Rp])**(3/2)
    ur[bR0Zm] = 0
    ur[bR0Zp] = 0

    ur[bZ0R0] = 0
    uz[bZ0R0] = 0


    return ur, uz

def doublet_line_u(Xcp,Ycp,Zcp,dmz_dz,m=0,L=np.inf):
    r""" 
    Velocity field induced by a double line, of intensity [0,0,dmz/dz]
    The line is defined by the coordinates (x=mz,y=0,z)
    By default a semi-infinite line is assumed (L=np.inf)
    INPUTS:
       Xcp,Ycp,Zcp: vector or matrix of control points Cartesian Coordinates
       m          : tangent of the skew angle (0 for a straight line along z)
       L          : length of the line (on the z axis!), True length is L*\sqrt{1+m^2}
    """
#     if m==0:
#         vr, vpsi = np.sqrt(Xcp**2+Ycp**2), np.arctan2(Ycp,Xcp) # polar coords
#         ur,uz=doublet_line_polar_u(vr,Zcp,dmz_dz)
#         ux=ur*np.cos(vpsi)
#         uy=ur*np.sin(vpsi)
#     else:
    if True:
        ux =  np.zeros(Xcp.shape)
        uy =  np.zeros(Xcp.shape)
        uz =  np.zeros(Xcp.shape)
        #
        a = np.sqrt(Xcp**2+Ycp**2+Zcp**2)
        b = m*Xcp+Zcp
        c = np.sqrt(m**2+1)

        # On the doublet line, we force the velocity to be zero even if a solution exist
        bOnLine = np.logical_and( np.abs(Ycp)<1e-8, np.abs(Xcp-m*Zcp)<1.e-8)
        bOnLine = np.logical_and(bOnLine, Zcp>=0)
        bOnLine = np.logical_and(bOnLine, Zcp<=L)
        ux[bOnLine]=0
        uy[bOnLine]=0
        uz[bOnLine]=0
        # Singularity condition of the integral:
#         bSing = np.abs(a*c-b)<1e-8
#         ux[bSing]=0
#         uy[bSing]=0
#         uz[bSing]=0
        # Standard formula
#         bOK   = np.logical_not(np.logical_or(bSing,bOnLine))
#         bOK   = np.logical_not(bSing)
        bOK   = np.logical_not(bOnLine)
        a=a[bOK]
        b=b[bOK]
        Xcp=Xcp[bOK]
        Ycp=Ycp[bOK]
        Zcp=Zcp[bOK]

        if L==np.inf:
            i30  = I30 (a,b,c) # NOTE: not scaling
            ti50 = tI50(a,b,c) # = 3 x I50
            ti51 = tI51(a,b,c) # = 3 x I50
            ti52 = tI52(a,b,c) # = 3 x I50
        else:
            i30, ti50, ti51, ti52 = _integrals_DL_finite(a,b,c,L) #= I30, 3*I50, 3*I51, 3*I52

        uz[bOK] = dmz_dz/(4*np.pi)  * (       Zcp**2 * ti50 +   ti52 - 2*Zcp * ti51 - i30 )
        ux[bOK] = dmz_dz/(4*np.pi)  * ( Xcp * Zcp    * ti50 + m*ti52 - (m*Zcp+Xcp) * ti51 )
        uy[bOK] = dmz_dz/(4*np.pi)  * ( Ycp * Zcp    * ti50         -        Ycp   * ti51 )

    return ux,uy,uz 


def doublet_line_u_num(Xcp,Ycp,Zcp,dmz_dz,m=0,zmax=1000,nQuad=100):
    """ 
    Velocity field induced by a double line, of intensity [0,0,dmz/dz]
    The line is defined by the coordinates (x=mz,y=0,z)

    INPUTS:
       Xcp,Ycp,Zcp: vector or matrix of control points Cartesian Coordinates
       m          : tangent of the skew angle (0 for a straight line along z)
    """
    ## --- Summation
    zq = np.linspace(0,zmax,nQuad)
    dz = zq[1]-zq[0]
    mz = dmz_dz * dz
    ux =  np.zeros(Xcp.shape)
    uy =  np.zeros(Xcp.shape)
    uz =  np.zeros(Xcp.shape)
    for z0 in zq: 
        dux, duy, duz = doublet_u(Xcp,Ycp,Zcp,m=[0,0,mz],x0=[m*z0,0,z0]) 
        ux += dux
        uy += duy
        uz += duz
    return ux,uy,uz 

# --------------------------------------------------------------------------------}
# --- Integrals useful for vortex doublet
# --------------------------------------------------------------------------------{
# --- Main Results for semi-infinite line
# a = sqrt(x^2 + y^2 + z^2)
# b = m x + z
# c = sqrt(m^2+1)
#  I30       = 1/(a (a c-b))   
#  I50       = (2 a c - b)/(3 a^3 (a c - b)^2)
#  I51       = 1/ (3 a (ac - b)^2)
#  I52       = 1/(3 c (a c - b)^2)

# --- INTEGRALS WRITTEN ALONG X AND USING PROPER a b c:   a^2 = x^2 + y^2 + z^2, b=my+x , c^2 = (1+m^2)
# II30       = \int  1    / ( a^2  - 2 b x'  + c^2 x'^2 )^(3/2) dx'
# II50       = \int  1    / ( a^2  - 2 b x'  + c^2 x'^2 )^(5/2) dx'
# II51       = \int  x'   / ( a^2  - 2 b x'  + c^2 x'^2 )^(5/2) dx'
# II52       = \int  x'^2 / ( a^2  - 2 b x'  + c^2 x'^2 )^(5/2) dx'

# --- I30
# indef         = (b - c^2 x)/((b^2 - a^2 c^2) sqrt(a^2 + x (-2 b + c^2 x)))
# indef_(b=-ac) = -1/(2 c (a + c x)^2) =  s/(2 c (a -sc x)^2) s==-1
# indef_(b=ac)  =  1/(2 c (a - c x)^2) =  s/(2 c (a -sc x)^2) s==1
#
# --- I50
# Indef  =  ((-b + c^2 x) (-b^2 + 2 c^4 x^2 + c^2 (3 a^2 - 4 b x)))/(3 (-b^2 + a^2 c^2)^2 (a^2 - x (2 b - c^2 x))^(3/2))
# indef_(b=-ac) = -1/(4 c (a + c x)^4) =  s/(4 c (a -sc x)^4) s==-1  b=-ac
# indef_(b= ac) =  1/(4 c (a - c x)^4) =  s/(4 c (a -sc x)^4) s==1


# --- I51
# Indef  =  (-(a^4 c^2) - a^2 b (b - 3 c^2 x) + b x (3 b^2 - 6 b c^2 x + 2 c^4 x^2))/(3 (b^2 - a^2 c^2)^2 (a^2 + x (-2 b + c^2 x))^(3/2)) 
# indef_(b=-ac) =  -(a + 4 c x)/(12 c^2 (a + c x)^4) =  -(a -s 4 c x)/(12 c^2 (a -sc x)^4) s==-1
# indef_(b= ac) =  -(a - 4 c x)/(12 c^2 (a - c x)^4) =  -(a -s 4 c x)/(12 c^2 (a -sc x)^4) s==1

# --- I52
# Indef         = (-2 a^4 b - b^2 x^2 (3 b - c^2 x) + a^2 x (6 b^2 - 3 b c^2 x + c^4 x^2))/(3 (-b^2 + a^2 c^2)^2 (a^2 - x (2 b - c^2 x))^(3/2))
# indef_(b=-ac) = -(a^2 + 4 a c x + 6 c^2 x^2)/(12 c^3 (a + c x)^4)= s(a^2 -s 4 a c x + 6 c^2 x^2)/(12 c^3 (a - s c x)^4) s==-1
# indef_(b= ac) =  (a^2 - 4 a c x + 6 c^2 x^2)/(12 c^3 (a - c x)^4)= s(a^2 -s 4 a c x + 6 c^2 x^2)/(12 c^3 (a - s c x)^4) s==1

def I30(a,b,c):
    # = I30
    # I30 = \int_0^\infty  1    / ( a^2  - 2 b x'  + c^2 x'^2 )^(3/2) dx'
#     if a==b/c:
#         return  -1/(2*c[b0]*a[b0]**2)
    return 1/(a*(a*c-b))

def tI50(a,b,c):
    # return 3 x I50
    # I50 = \int_0^\infty  1    / ( a^2  - 2 b x'  + c^2 x'^2 )^(5/2) dx'
#     if a==b/c:
#         return -3/(4*c*a**4)
    return (2*a*c-b)/(a**3*(a*c-b)**2)

def tI51(a,b,c):
    # return 3 x I51
    # I51 = \int_0^\infty  x'   / ( a^2  - 2 b x'  + c^2 x'^2 )^(5/2) dx'
#     if a==b/c:
#         return 3/(12*c**2*a**3)
    return 1/(a*(a*c-b)**2)

def tI52(a,b,c):
    # return 3 x I52
    # I52 = \int_0^\infty  x'^2 / ( a^2  - 2 b x'  + c^2 x'^2 )^(5/2) dx'
#     if a==b/c:
#         return -3/(12*c**3*a**2)
    return 1/(c*(a*c-b)**2)

def I30_num(x,y,z,m,nQuad=50000,zmax=5000):
    zp = np.linspace(0,zmax,nQuad)
    r2 = (x-m*zp)**2 + y**2 + (z-zp)**2
    dI = 1/ r2**(3/2)
    return trapezoid(dI, zp)

def I50_num(x,y,z,m,nQuad=50000,zmax=5000):
    zp = np.linspace(0,zmax,nQuad)
    r2 = (x-m*zp)**2 + y**2 + (z-zp)**2
    dI = 1/ r2**(5/2)
    return trapezoid(dI, zp)

def I51_num(x,y,z,m,nQuad=50000,zmax=5000):
    zp = np.linspace(0,zmax,nQuad)
    r2 = (x-m*zp)**2 + y**2 + (z-zp)**2
    dI = zp/ r2**(5/2)
    return trapezoid(dI, zp)

def I52_num(x,y,z,m,nQuad=50000,zmax=5000):
    zp = np.linspace(0,zmax,nQuad)
    r2 = (x-m*zp)**2 + y**2 + (z-zp)**2
    dI = zp**2/ r2**(5/2)
    return trapezoid(dI, zp)


# --------------------------------------------------------------------------------}
# --- FINITE LENGTH DOUBLET LINE
# --------------------------------------------------------------------------------{
def _integrals_DL_finite(a,b,c,L):
    """
    Compute integrals values for doublet line of finite length
    returns: I30,  3*I50, 3*I51, 3*I52
    """
    # I30 = \int_0^L  1    / ( a^2  - 2 b x'  + c^2 x'^2 )^(3/2) dx'
    def i30indef_sing(a,b,c,x):
        s=np.sign(b)
        return s/(2*c*(a -s*c*x)**2)
    def i30indef_norm(a,b,c,x):
        return (b - c**2*x)/((b**2 - a**2*c**2)*(a**2 - 2*b*x + c**2 * x**2)**(1/2))
    # I50 = \int_0^L  1    / ( a^2  - 2 b x'  + c^2 x'^2 )^(5/2) dx'
    def i50indef_sing(a,b,c,x):
        s=np.sign(b)
        return 3*s/(4*c*(a -s*c*x)**4)
    def i50indef_norm(a,b,c,x):
        return ( b*(b**2 - 3*a**2*c**2) + 2*c**2*(b**2 + a**2*c**2)*x - 6*b*c**4*x**2 + 2*c**6*x**3 )/ ((b**2 - a**2*c**2)**2*(a**2 - 2*b*x + c**2*x**2)**(3/2) ) 
    # I51 = \int_0^L  x'   / ( a^2  - 2 b x'  + c^2 x'^2 )^(5/2) dx'
    def i51indef_sing(a,b,c,x):
        s=np.sign(b)
        return -3*(a -s*4*c*x)/(12*c**2*(a -s*c*x)**4)
    def i51indef_norm(a,b,c,x):
        return ( -a**2*(b**2 + a**2*c**2) + 3*b*(b**2 + a**2*c**2)*x - 6*b**2*c**2*x**2 + 2*b*c**4*x**3 )/ ( (b**2 - a**2*c**2)**2 *(a**2 -2*b*x + c**2*x**2)**(3/2) ) 
    # I52 = \int  x'^2 / ( a^2  - 2 b x'  + c^2 x'^2 )^(5/2) dx'
    def i52indef_sing(a,b,c,x):
        s=np.sign(b)
        return 3*s*(a**2 -s*4*a*c*x + 6*c**2*x**2)/(12*c**3*(a - s*c*x)**4)
    def i52indef_norm(a,b,c,x):
        return ( -2*a**4*b + 6*a**2*b**2*x - 3*b*(b**2 +  a**2*c**2)*x**2 + c**2*(b**2  + a**2*c**2)*x**3 )/ ( (b**2 - a**2*c**2)**2 *(a**2 - 2*b*x + c**2*x**2)**(3/2) )

    a=np.asarray(a); b=np.asarray(b);
    S = np.abs((b**2 - a**2*c**2))<1e-8 # a== b/c
    N = np.logical_not(S)
    aS=a[S]; bS=b[S];
    aN=a[N]; bN=b[N];

    I30 = np.zeros(a.shape); I50 = np.zeros(a.shape); I51 = np.zeros(a.shape); I52 = np.zeros(a.shape)

    I30[S] = i30indef_sing(aS, bS, c, L)-i30indef_sing(aS, bS, c, 0)
    I30[N] = i30indef_norm(aN, bN, c, L)-i30indef_norm(aN, bN, c, 0)
    I50[S] = i50indef_sing(aS, bS, c, L)-i50indef_sing(aS, bS, c, 0)
    I50[N] = i50indef_norm(aN, bN, c, L)-i50indef_norm(aN, bN, c, 0)
    I51[S] = i51indef_sing(aS, bS, c, L)-i51indef_sing(aS, bS, c, 0)
    I51[N] = i51indef_norm(aN, bN, c, L)-i51indef_norm(aN, bN, c, 0)
    I52[S] = i52indef_sing(aS, bS, c, L)-i52indef_sing(aS, bS, c, 0)
    I52[N] = i52indef_norm(aN, bN, c, L)-i52indef_norm(aN, bN, c, 0)
    return I30, I50, I51, I52

class TestDoublet(unittest.TestCase):

    def test_Integrals(self):
        # --- random point, z>0
        x = 3; y = 1; z = 1.0; m = 0.3
        a = np.sqrt(x**2+y**2+z**2); b = m*x+z ; c = np.sqrt(m**2+1)
        np.testing.assert_almost_equal(I30(a,b,c), I30_num(x,y,z,m), 5)
        np.testing.assert_almost_equal(tI50(a,b,c)/3, I50_num(x,y,z,m), 5)
        np.testing.assert_almost_equal(tI51(a,b,c)/3, I51_num(x,y,z,m), 5)
        np.testing.assert_almost_equal(tI52(a,b,c)/3, I52_num(x,y,z,m), 5)
        # --- random point, z<0
        x = 1; y = 2; z = -0.5; m = 0.3
        a = np.sqrt(x**2+y**2+z**2); b = m*x+z ; c = np.sqrt(m**2+1)
        np.testing.assert_almost_equal(I30(a,b,c), I30_num(x,y,z,m), 5)
        np.testing.assert_almost_equal(tI50(a,b,c)/3, I50_num(x,y,z,m), 5)
        np.testing.assert_almost_equal(tI51(a,b,c)/3, I51_num(x,y,z,m), 5)
        np.testing.assert_almost_equal(tI52(a,b,c)/3, I52_num(x,y,z,m), 5)
        # --- On Axis, z<0 (a==-b/c), no need for singularity
        y = 0; z = -1 ; m = 0.3; x = z*m
        a = np.sqrt(x**2+y**2+z**2); b = m*x+z ; c = np.sqrt(m**2+1)
        np.testing.assert_almost_equal(I30(a,b,c), I30_num(x,y,z,m), 2)
        np.testing.assert_almost_equal(tI50(a,b,c)/3, I50_num(x,y,z,m), 2)
        np.testing.assert_almost_equal(tI51(a,b,c)/3, I51_num(x,y,z,m), 2)
        np.testing.assert_almost_equal(tI52(a,b,c)/3, I52_num(x,y,z,m), 2)
        # --- On Axis, z>0 (a==b/c) Can't match singularity with numerical integration
        y = 0; z = 1 ; m = 0.3; x = z*m
        a = np.sqrt(x**2+y**2+z**2); b = m*x+z ; c = np.sqrt(m**2+1)
        #np.testing.assert_almost_equal(I30(a,b,c), I30_num(x,y,z,m), 2)
        #np.testing.assert_almost_equal(tI50(a,b,c)/3, I50_num(x,y,z,m), 2)
        #np.testing.assert_almost_equal(tI51(a,b,c)/3, I51_num(x,y,z,m), 2)
        #np.testing.assert_almost_equal(tI52(a,b,c)/3, I52_num(x,y,z,m), 2)

    def test_Integrals_Finite(self):
        L = 1e+4
        # --- random point, z>0
        x = 3; y = 1; z = 1.0; m = 0.3
        a = np.sqrt(x**2+y**2+z**2); b = m*x+z ; c = np.sqrt(m**2+1)
        I30L, tI50L, tI51L, tI52L = _integrals_DL_finite(a,b,c,L)
        np.testing.assert_almost_equal(I30(a,b,c) , I30L  , 7)
        np.testing.assert_almost_equal(tI50(a,b,c), tI50L, 7)
        np.testing.assert_almost_equal(tI51(a,b,c), tI51L, 7)
        np.testing.assert_almost_equal(tI52(a,b,c), tI52L, 7)
        # --- random point, z<0
        x = 1; y = 2; z = -0.5; m = 0.3
        a = np.sqrt(x**2+y**2+z**2); b = m*x+z ; c = np.sqrt(m**2+1)
        I30L, tI50L, tI51L, tI52L = _integrals_DL_finite(a,b,c,L)
        np.testing.assert_almost_equal(I30(a,b,c) , I30L  , 7)
        np.testing.assert_almost_equal(tI50(a,b,c), tI50L, 7)
        np.testing.assert_almost_equal(tI51(a,b,c), tI51L, 7)
        np.testing.assert_almost_equal(tI52(a,b,c), tI52L, 7)
        # --- On Axis, z<0 (a==-b/c)
        y = 0; z = -1 ; m = 0.3; x = z*m
        a = np.sqrt(x**2+y**2+z**2); b = m*x+z ; c = np.sqrt(m**2+1)
        I30L, tI50L, tI51L, tI52L = _integrals_DL_finite(a,b,c,L)
        np.testing.assert_almost_equal(I30(a,b,c) , I30L  , 7)
        np.testing.assert_almost_equal(tI50(a,b,c), tI50L, 7)
        np.testing.assert_almost_equal(tI51(a,b,c), tI51L, 7)
        np.testing.assert_almost_equal(tI52(a,b,c), tI52L, 7)
        # --- On Axis, z>0 (a==b/c)
        y = 0; z = 1 ; m = 0.3; x = z*m
        a = np.sqrt(x**2+y**2+z**2); b = m*x+z ; c = np.sqrt(m**2+1)
        a = np.sqrt(x**2+y**2+z**2); b = m*x+z ; c = np.sqrt(m**2+1)
        I30L, tI50L, tI51L, tI52L = _integrals_DL_finite(a,b,c,L)
        #np.testing.assert_almost_equal(I30(a,b,c) , I30L  , 7)
        #np.testing.assert_almost_equal(tI50(a,b,c), tI50L, 7)
        #np.testing.assert_almost_equal(tI51(a,b,c), tI51L, 7)
        #np.testing.assert_almost_equal(tI52(a,b,c), tI52L, 7)


    def test_Doublet_example(self):
        # ---- Singularity check
        u=doublet_u(0,0,0,[0,0,100])
        np.testing.assert_almost_equal(u,(0,0,0))
        # --- Random point check
        u     = doublet_u(1,2,3,[700,-800,900],x0=[-4,-5,6])
        u_ref = (-0.16496, -0.043617, -0.039940)
        np.testing.assert_almost_equal(u,u_ref,5)

    def test_Doublet_polar(self):
        # ---- Singularity check
        u=doublet_u_polar(0,0,0,100)
        np.testing.assert_almost_equal(u,(0,0))
        # ---- Random points check
        u     = doublet_u_polar(1,  3,     100,  z0=6)
        u_ref = doublet_u      (1,0,3,[0,0,100], x0=[0,0,6])
        np.testing.assert_almost_equal(u,(u_ref[0],u_ref[2]))
        u     = doublet_u_polar( 1,  -3,     100,  z0=6)
        u_ref = doublet_u      ( 1,0,-3,[0,0,100], x0=[0,0,6])
        np.testing.assert_almost_equal(u,(u_ref[0],u_ref[2]))

        u     = doublet_u_polar( 0.011,  -0.326,  100    ,  z0=0)
        u_ref = doublet_u      ( 0.011,0,-0.326,[0,0,100], x0=[0,0,0])
        np.testing.assert_almost_equal(u,(u_ref[0],u_ref[2]))


    def test_Doublet_axis(self):
        gamma_t = -10; R = 5
        m       = 2
        dmzdz   = np.pi * R**2 * gamma_t

        #--- Induction zone on axis
        z=np.linspace(-10*R,-1*R,51)
        y=0*z
        x=0*z
        urp,   uzp = doublet_line_polar_u(x, z, dmzdz)
        uri,_ ,uzi = doublet_line_u(x, y, z, dmzdz, m=0, L=np.inf)
        urL,_ ,uzL = doublet_line_u(x, y, z, dmzdz, m=0, L=40*R)
        np.testing.assert_almost_equal(uzp, uzi, 6)
        np.testing.assert_almost_equal(urp, uri, 6)
        np.testing.assert_almost_equal(uzi, uzL, 3)
        np.testing.assert_almost_equal(uri, urL, 3)

        # --- Far field on axis
        z=np.linspace(-100*R,-30*R,51)
        y=0*z
        x=0*z
        uzc = gamma_t/2*(1+z/np.sqrt(z**2 + R**2))
        _,_ ,uzi = doublet_line_u(x, y, z, dmzdz, m=0, L=np.inf)
        _,_ ,uzL = doublet_line_u(x, y, z, dmzdz, m=0, L=100*R)
        np.testing.assert_almost_equal(uzi, uzc, 5)
        np.testing.assert_almost_equal(uzL, uzc, 4)

        #--- Skewed line - on axis - comparison finite infinite 
        # Compare axisformula of a doublet with the one of a cylinder (should match at infinite)
        # TODO axis formula : v_z=-Gamma/(2) *( 1 + z / sqrt(R^2+z^2))
        # gamma_t/2 * (1 + z / np.sqrt(z** 2 + R**2))
        z=np.linspace(-10*R,10*R,31)
        x=m*z
        y=0*z
        uri,_ ,uzi = doublet_line_u(x, y, z, dmzdz, m=m, L=np.inf)
        urL,_ ,uzL = doublet_line_u(x, y, z, dmzdz, m=m, L=100*R)
        np.testing.assert_almost_equal(uzi, uzL, 5)
        np.testing.assert_almost_equal(uri, urL, 4)

        #--- Skewed, on line, enforced to be zero
        z=np.linspace(0*R,10*R,31)
        x=m*z
        y=0*z
        uri,_ ,uzi = doublet_line_u(x, y, z, dmzdz, m=m, L=np.inf)
        urL,_ ,uzL = doublet_line_u(x, y, z, dmzdz, m=m, L=10*R)
        np.testing.assert_almost_equal(uzi, z*0.0, 5)
        np.testing.assert_almost_equal(uri, z*0.0, 5)
        np.testing.assert_almost_equal(uzL, z*0.0, 5)
        np.testing.assert_almost_equal(urL, z*0.0, 5)
#         import matplotlib.pyplot as plt
#         fig,ax = plt.subplots(1,1)
#         ax.plot(z/R, uzL, 'o',label='uzL')
#         ax.plot(z/R, uzi, 'k+',label='uz')
#         ax.plot(z/R, uri,'d', label='uri')
#         ax.plot(z/R, urL,'^', label='urL')
#         ax.set_xlabel('')
#         ax.set_ylabel('')
#         ax.legend()
#         plt.show()

    def test_Doublet_line(self):
        gamma_t = -10
        R       = 10
        dmzdz   = np.pi * R**2 * gamma_t

        # --- Singularity 0,0 (should be infinity)
        u=doublet_line_polar_u  (0,0,dmzdz)
        np.testing.assert_almost_equal(u,(0,0))

        # --- Formula on the z axis from analytical and numerical integration
        zcp = np.linspace(-10,-1,50)*R
        rcp = zcp*0 
        urn,uzn=doublet_line_polar_u_num(rcp,zcp,dmzdz,0,1000,10000)
        urt,uzt=doublet_line_polar_u    (rcp,zcp,dmzdz)

        # --- Enforced 0 on positive z (NOTE: a solution in fact exist
#         u=doublet_line_polar_u  (0,0,dmzdz)
#         np.testing.assert_almost_equal(u,(0,0))


        # ---- Plot
        #uzc = gamma_t/2*(1+zcp/np.sqrt(zcp**2 + R**2))
        #import matplotlib.pyplot as plt
        #fig,ax = plt.subplots(1,1)
        #ax.plot(zcp/R, urt    , label='urt')
        #ax.plot(zcp/R, urn ,'--'   , label='urn')
        #ax.plot(zcp/R, uzt    , label='uzt')
        #ax.plot(zcp/R, uzn ,'--'   , label='uzn')
        #print(zcp)
        #ax.plot(zcp/R, uzc ,'k.'   , label='uzn')
        #ax.set_xlabel('')
        #ax.set_ylabel('')
        #ax.legend()
        #plt.show()

        np.testing.assert_almost_equal(urn,urt,1)
        np.testing.assert_almost_equal(uzn,uzt,1)


if __name__ == "__main__":
#     TestDoublet().test_Doublet_polar()
#     TestDoublet().test_Integrals()
    unittest.main()
