import numpy as np
from scipy import special

def t96(parmod,ps,x,y,z):
    """
    Release date of this version: June 22, 1996.

    Data-based model calibrated by (1) solar wind pressure pdyn (nanopascals),
        (2) dst (nanotesla),  (3) byimf, and (4) bzimf (nanotesla).
    :param parmod: 10-element array, but only the first 4 elements are used
        (1) solar wind pressure pdyn (nanopascals)
        (2) dst (nanotesla)
        (3) byimf (nanotesla)
        (4) bzimf (nanotesla)
	:param ps: geo-dipole tilt angle in radius.
    :param x,y,z: GSM coordinates in Re (1 Re = 6371.2 km)
    :return: bx,by,bz. Field components in GSM system, in nT.
        Computed as a sum of contributions from principal field sources.
    """

    pdyn0,eps10 = [2.,3630.7]
    a = np.array([1.162,22.344,18.50,2.602,6.903,5.287,0.5790,0.4462,0.7850])
    am0,s0,x00,dsig = [70.,1.08,5.48,0.005]
    delimfx,delimfy = [20.,10.]
    pdyn,dst,byimf,bzimf =parmod[0:4]

    sps=np.sin(ps)
    # depr is an estimate of total near-earth depression, based on dst and pdyn (usually, depr &lt 0 )
    depr=0.8*dst-13.*np.sqrt(pdyn)

    # calculate the imf-related quantities:
    bt=np.sqrt(byimf**2+bzimf**2)

    if (byimf == 0) & (bzimf == 0):
        theta = 0
    else:
        theta=np.arctan2(byimf,bzimf)
        if theta < 0: theta += 2*np.pi
    ct=np.cos(theta)
    st=np.sin(theta)
    eps=718.5*np.sqrt(pdyn)*bt*np.sin(theta/2.)

    facteps=eps/eps10-1.
    factpd=np.sqrt(pdyn/pdyn0)-1.
    #  rcampl is the amplitude of the ring current (positive and equal to abs.value of rc depression at origin)
    rcampl=-a[0]*depr

    tampl2=a[1]+a[2]*factpd+a[3]*facteps
    tampl3=a[4]+a[5]*factpd
    b1ampl=a[6]+a[7]*facteps
    b2ampl=20.*b1ampl  # it is equivalent to assuming that the total current in the region 2 system is 40% of that in region 1
    reconn=a[8]

    xappa=(pdyn/pdyn0)**0.14
    xappa3=xappa**3
    ys=y*ct-z*st
    zs=z*ct+y*st

    factimf=np.exp(x/delimfx-(ys/delimfy)**2)

    # calculate the "imf" components outside the layer  (hence begin with "o")
    oimfx=0.
    oimfy=reconn*byimf*factimf
    oimfz=reconn*bzimf*factimf

    rimfampl=reconn*bt

    pps=ps
    xx=x*xappa
    yy=y*xappa
    zz=z*xappa

    # Scale and calculate the magnetopause parameters for the interpolation across
    # the boundary layer (the coordinates xx,yy,zz  are already scaled)
    x0=x00/xappa
    am=am0/xappa
    rho2=y**2+z**2
    asq=am**2
    xmxm=am+x-x0
    if xmxm < 0: xmxm = 0   # the boundary is a cylinder tailward of x=x0-am
    axx0=xmxm**2
    aro=asq+rho2
    sigma=np.sqrt((aro+axx0+np.sqrt((aro+axx0)**2-4.*asq*axx0))/(2.*asq))

    # Now, there are three possible cases:
    #    (1) inside the magnetosphere
    #    (2) in the boundary layer
    #    (3) outside the magnetosphere and b.layer

    # First of all, consider the cases (1) and (2):
    if sigma < (s0+dsig):     # cases (1) or (2); calculate the model field (with the potential "penetrated" interconnection field):
        cfx,cfy,cfz = dipshld(pps,xx,yy,zz)
        bxrc,byrc,bzrc,bxt2,byt2,bzt2,bxt3,byt3,bzt3 = tailrc96(sps,xx,yy,zz)
        r1x,r1y,r1z = birk1tot_02(pps,xx,yy,zz)
        r2x,r2y,r2z = birk2tot_02(pps,xx,yy,zz)
        rimfx,rimfys,rimfzs = intercon(xx,ys*xappa,zs*xappa)
        rimfy=rimfys*ct+rimfzs*st
        rimfz=rimfzs*ct-rimfys*st

        fx=cfx*xappa3 + rcampl*bxrc+tampl2*bxt2+tampl3*bxt3+ b1ampl*r1x +b2ampl*r2x +rimfampl*rimfx
        fy=cfy*xappa3 + rcampl*byrc+tampl2*byt2+tampl3*byt3+ b1ampl*r1y +b2ampl*r2y +rimfampl*rimfy
        fz=cfz*xappa3 + rcampl*bzrc+tampl2*bzt2+tampl3*bzt3+ b1ampl*r1z +b2ampl*r2z +rimfampl*rimfz

        #  Now, let us check whether we have the case (1). if yes - we are done:
        if sigma < (s0-dsig):   # (x,y,z) is inside the magnetosphere
            bx,by,bz = [fx,fy,fz]
        else:                   # this is the most complex case: we are inside the interpolation region
            fint=0.5*(1.-(sigma-s0)/dsig)
            fext=0.5*(1.+(sigma-s0)/dsig)

            qx,qy,qz = dipole(ps,x,y,z)
            bx=(fx+qx)*fint+oimfx*fext -qx
            by=(fy+qy)*fint+oimfy*fext -qy
            bz=(fz+qz)*fint+oimfz*fext -qz
    # The cases (1) and (2) are exhausted; the only remaining possibility is now the case (3):
    else:
        qx,qy,qz = dipole(ps,x,y,z)
        bx=oimfx-qx
        by=oimfy-qy
        bz=oimfz-qz

    return bx,by,bz

def dipshld(ps,x,y,z):
    """
    Calculates gsm components of the external magnetic field due to shielding of the earth's dipole only
    """

    cps=np.cos(ps)
    sps=np.sin(ps)

    a1 = np.array([.24777,-27.003,-.46815,7.0637,-1.5918,-.90317E-01,57.522,
        13.757,2.0100,10.458,4.5798,2.1695])
    a2 = np.array([-.65385,-18.061,-.40457,-5.0995,1.2846,.78231e-01,39.592,
        13.291,1.9970,10.062,4.5140,2.1558])

    hx,hy,hz = cylharm(a1, x,y,z)
    fx,fy,fz = cylhar1(a2, x,y,z)

    bx=hx*cps+fx*sps
    by=hy*cps+fy*sps
    bz=hz*cps+fz*sps

    return bx,by,bz

def cylharm(a, x,y,z):
    """
    This code yields the shielding field for the perpendicular dipole.
    An approximation for the Chapman-Ferraro field by a sum of 6 cylindrical harmonics (see pp. 97-113 in the brown GSFC notebook #1)

    The 6 linear parameters a[0]-a[5] are amplitudes of the cylindrical harmonic terms.
    The 6 nonlinear parameters a[6]-a[11] are the corresponding scale lengths for each term (see GSFC brown notebook).
    """

    rho=np.sqrt(y**2+z**2)
    if rho < 1e-8:
        sinfi=1.
        cosfi=0.
        rho=1e-8
    else:
        sinfi=z/rho
        cosfi=y/rho

    sinfi2=sinfi**2
    si2co2=sinfi2-cosfi**2

    bx,by,bz =[0.]*3

    for i in range(3):
        dzeta=rho/a[i+6]
        xksi=x/a[i+6]
        xj0=special.j0(dzeta)
        xj1=special.j1(dzeta)
        xexp=np.exp(xksi)
        bx=bx-a[i]*xj1*xexp*sinfi
        by=by+a[i]*(2*xj1/dzeta-xj0)*xexp*sinfi*cosfi
        bz=bz+a[i]*(xj1/dzeta*si2co2-xj0*sinfi2)*xexp

    for i in range(3,6):
        dzeta=rho/a[i+6]
        xksi=x/a[i+6]
        xj0=special.j0(dzeta)
        xj1=special.j1(dzeta)
        xexp=np.exp(xksi)
        brho=(xksi*xj0-(dzeta**2+xksi-1)*xj1/dzeta)*xexp*sinfi
        bphi=(xj0+xj1/dzeta*(xksi-1))*xexp*cosfi
        bx=bx+a[i]*(dzeta*xj0+xksi*xj1)*xexp*sinfi
        by=by+a[i]*(brho*cosfi-bphi*sinfi)
        bz=bz+a[i]*(brho*sinfi+bphi*cosfi)

    return bx,by,bz

def cylhar1(a, x,y,z):
    """
    This code yields the shielding field for the parallel dipole.
    An approximation for the Chapman-Ferraro field by a sum of 6 cylindrical harmonics (see pp. 97-113 in the brown GSFC notebook #1)

    The 6 linear parameters a[0]-a[5] are amplitudes of the cylindrical harmonic terms.
    The 6 nonlinear parameters a[6]-a[11] are the corresponding scale lengths for each term (see GSFC brown notebook).
    """

    rho=np.sqrt(y**2+z**2)
    if rho < 1e-8:
        sinfi=1.
        cosfi=0.
        rho=1e-8
    else:
        sinfi=z/rho
        cosfi=y/rho

    bx,by,bz =[0.]*3

    for i in range(3):
        dzeta=rho/a[i+6]
        xksi=x/a[i+6]
        xj0=special.j0(dzeta)
        xj1=special.j1(dzeta)
        xexp=np.exp(xksi)
        brho=xj1*xexp
        bx=bx-a[i]*xj0*xexp
        by=by+a[i]*brho*cosfi
        bz=bz+a[i]*brho*sinfi

    for i in range(3,6):
        dzeta=rho/a[i+6]
        xksi=x/a[i+6]
        xj0=special.j0(dzeta)
        xj1=special.j1(dzeta)
        xexp=np.exp(xksi)
        brho=(dzeta*xj0+xksi*xj1)*xexp
        bx=bx+a[i]*(dzeta*xj1-xj0*(xksi+1))*xexp
        by=by+a[i]*brho*cosfi
        bz=bz+a[i]*brho*sinfi

    return bx,by,bz

def tailrc96(sps, x,y,z):
    """
    Computes the components of the field of the model ring current and three tail modes with unit amplitudes
    For the ring current, it means the disturbance of bz=-1nT at origin, and
    for the tail modes it means maximal bx just above the sheet equal 1 nT.

    """

    # common /warp/ cpss,spss,dpsrr,rps,warp,d,xs,zs,dxsx,dxsy,dxsz,dzsx,dzsy,dzsz,dzetas,ddzetadx,ddzetady,ddzetadz,zsww
    global cpss,spss,dpsrr,rps,warp,d,xs,zs,dxsx,dxsy,dxsz,dzsx,dzsy,dzsz,dzetas,ddzetadx,ddzetady,ddzetadz,zsww

    arc = np.array([
        -3.087699646,3.516259114,18.81380577,-13.95772338,-5.497076303,0.1712890838,
        2.392629189,-2.728020808,-14.79349936,11.08738083,4.388174084,0.2492163197E-01,
        0.7030375685,-.7966023165,-3.835041334,2.642228681,-0.2405352424,-0.7297705678,
        -0.3680255045,0.1333685557,2.795140897,-1.078379954,0.8014028630,0.1245825565,
        0.6149982835,-0.2207267314,-4.424578723,1.730471572,-1.716313926,-0.2306302941,
        -0.2450342688,0.8617173961E-01,1.54697858,-0.6569391113,-0.6537525353,0.2079417515,
        12.75434981,11.37659788,636.4346279,1.752483754,3.604231143,12.83078674,
        7.412066636,9.434625736,676.7557193,1.701162737,3.580307144,14.64298662])
    atail2 = np.array([
        .8747515218,-.9116821411,2.209365387,-2.159059518,-7.059828867,5.924671028,
        -1.916935691,1.996707344,-3.877101873,3.947666061,11.38715899,-8.343210833,
        1.194109867,-1.244316975,3.73895491,-4.406522465,-20.66884863,3.020952989,
        .2189908481,-.09942543549,-.927225562,.1555224669,.6994137909,-.08111721003,
        -.7565493881,.4686588792,4.266058082,-.3717470262,-3.920787807,.02298569870,
        .7039506341,-.5498352719,-6.675140817,.8279283559,-2.234773608,-1.622656137,
        5.187666221,6.802472048,39.13543412,2.784722096,6.979576616,25.71716760,
        4.495005873,8.068408272,93.47887103,4.158030104,9.313492566,57.18240483])
    atail3 = np.array([
        -19091.95061,-3011.613928,20582.16203,4242.918430,-2377.091102,-1504.820043,
        19884.04650,2725.150544,-21389.04845,-3990.475093,2401.610097,1548.171792,
        -946.5493963,490.1528941,986.9156625,-489.3265930,-67.99278499,8.711175710,
        -45.15734260,-10.76106500,210.7927312,11.41764141,-178.0262808,.7558830028,
        339.3806753,9.904695974,69.50583193,-118.0271581,22.85935896,45.91014857,
        -425.6607164,15.47250738,118.2988915,65.58594397,-201.4478068,-14.57062940,
        19.69877970,20.30095680,86.45407420,22.50403727,23.41617329,48.48140573,
        24.61031329,123.5395974,223.5367692,39.50824342,65.83385762,266.2948657])

    rh,dr,g,d0,deltady = [9.,4.,10.,2.,10.]

    # To economize the code, we first calculate common variables, which are
    # the same for all modes, and put them in the common-block /warp/
    dr2=dr*dr
    c11=np.sqrt((1+rh)**2+dr2)
    c12=np.sqrt((1-rh)**2+dr2)
    c1=c11-c12
    spsc1=sps/c1
    rps=0.5*(c11+c12)*sps   # this is the shift of the sheet with respect to gsm eq.plane for the 3rd (asymptotic) tail mode

    r=np.sqrt(x*x+y*y+z*z)
    sq1=np.sqrt((r+rh)**2+dr2)
    sq2=np.sqrt((r-rh)**2+dr2)
    c=sq1-sq2
    cs=(r+rh)/sq1-(r-rh)/sq2
    spss=spsc1/r*c
    cpss=np.sqrt(1-spss**2)
    dpsrr=sps/(r*r)*(cs*r-c)/np.sqrt((r*c1)**2-(c*sps)**2)

    wfac=y/(y**4+1e4)   # warping
    w=wfac*y**3
    ws=4e4*y*wfac**2
    warp=g*sps*w
    xs=x*cpss-z*spss
    zsww=z*cpss+x*spss  # "ww" means "without y-z warping" (in x-z only)
    zs=zsww +warp

    dxsx=cpss-x*zsww*dpsrr
    dxsy=-y*zsww*dpsrr
    dxsz=-spss-z*zsww*dpsrr
    dzsx=spss+x*xs*dpsrr
    dzsy=xs*y*dpsrr  +g*sps*ws  # the last term is for the y-z warp
    dzsz=cpss+xs*z*dpsrr        # (tail modes only)

    d=d0+deltady*(y/20)**2      # sheet half-thickness for the tail modes
    dddy=deltady*y*0.005        # (thickens to flanks, but no variation along x, in contrast to ring current)

    dzetas=np.sqrt(zs**2+d**2)  # this is the same simple way to spread out the sheet, as that used in t89
    ddzetadx=zs*dzsx/dzetas
    ddzetady=(zs*dzsy+d*dddy)/dzetas
    ddzetadz=zs*dzsz/dzetas

    wx,wy,wz = shlcar3x3(arc,x,y,z,sps)
    hx,hy,hz = ringcurr96(x,y,z)
    bxrc=wx+hx
    byrc=wy+hy
    bzrc=wz+hz

    wx,wy,wz = shlcar3x3(atail2,x,y,z,sps)
    hx,hy,hz = taildisk(x,y,z)
    bxt2=wx+hx
    byt2=wy+hy
    bzt2=wz+hz

    wx,wy,wz = shlcar3x3(atail3,x,y,z,sps)
    hx,hz = tail87(x,z)
    bxt3=wx+hx
    byt3=wy
    bzt3=wz+hz

    return bxrc,byrc,bzrc, bxt2,byt2,bzt2, bxt3,byt3,bzt3


def shlcar3x3(a, x,y,z, sps):
    """
    This code returns the shielding field represented by  2x3x3=18 "cartesian" harmonics

    The 36 coefficients enter in pairs in the amplitudes of the "cartesian" harmonics a[0]-a[35].
    The 12 nonlinear parameters a[36]-a[47] are the scales Pi,Ri,Qi,and Si entering the
    arguments of exponents, sines, and cosines in each of the 18 "Cartesian" harmonics
    """

    cps=np.sqrt(1-sps**2)
    s3ps=4*cps**2-1     # this is sin(3*ps)/sin(ps)

    hx,hy,hz= [0.]*3

    l=0
    for m in range(2):  # m=1 for 1st sum (perp symmetry), m=2 for 2nd sum (parallel symmetry)
        for i in range(3):
            p=a[36+i]
            q=a[42+i]
            cypi=np.cos(y/p)
            cyqi=np.cos(y/q)
            sypi=np.sin(y/p)
            syqi=np.sin(y/q)
            for k in range(3):
                r=a[39+k]
                s=a[45+k]
                szrk=np.sin(z/r)
                czsk=np.cos(z/s)
                czrk=np.cos(z/r)
                szsk=np.sin(z/s)
                sqpr=np.sqrt(1/p**2+1/r**2)
                sqqs=np.sqrt(1/q**2+1/s**2)
                epr=np.exp(x*sqpr)
                eqs=np.exp(x*sqqs)
                for n in range(2):  # n=1 for the 1st part of each coefficient, n=2 for 2nd
                    if m == 0:
                        if n == 0:
                            dx=-sqpr*epr*cypi*szrk
                            dy= epr/p*sypi*szrk
                            dz=-epr/r*cypi*czrk
                            hx=hx+a[l]*dx
                            hy=hy+a[l]*dy
                            hz=hz+a[l]*dz
                        else:
                            dx=dx*cps
                            dy=dy*cps
                            dz=dz*cps
                            hx=hx+a[l]*dx
                            hy=hy+a[l]*dy
                            hz=hz+a[l]*dz
                    else:
                        if n == 0:
                            dx=-sps*sqqs*eqs*cyqi*czsk
                            dy= sps*eqs/q*syqi*czsk
                            dz= sps*eqs/s*cyqi*szsk
                            hx=hx+a[l]*dx
                            hy=hy+a[l]*dy
                            hz=hz+a[l]*dz
                        else:
                            dx=dx*s3ps
                            dy=dy*s3ps
                            dz=dz*s3ps
                            hx=hx+a[l]*dx
                            hy=hy+a[l]*dy
                            hz=hz+a[l]*dz
                    l += 1

    return hx,hy,hz

def ringcurr96(x,y,z):
    """
    This subroutine computes the components of the ring current field, similar to
    that described by Tsyganenko and Peredo (1994). The difference is that now
    we use spacewarping, as described in the paper on modeling Birkeland currents
    (Tsyganenko and Stern, 1996), instead of shearing it in the spirit of the T89 tail model.

    In addition, instead of 7 terms for the ring current model, we use now only 2 terms;
    This simplification also gives rise to an eastward ring current located earthward
    from the main one, in line with what is actually observed.

    For details, see NB #3, pages 70-73
    """

    # common /warp/ cpss,spss,dpsrr, xnext(3),xs,zswarped,dxsx,dxsy, dxsz,dzsx,dzsywarped,dzsz,other(4),zs
    # zs here is without y-z warp
    global cpss,spss,dpsrr, xnext,xs,zswarped,dxsx,dxsy, dxsz,dzsx,dzsywarped,dzsz,other,zs
    d0,deltadx,xd,xldx = [2.,0.,0.,4.]  # the rc is now completely symmetric (deltadx=0)

    # the original values of f[i] were multiplied by beta[i] (to reduce the number of
    # multiplications below) and by the factor -0.43, normalizing the disturbance at origin to b=-1nT
    f = np.array([569.895366,-1603.386993])
    beta = np.array([2.722188,3.766875])

    # no warping in the y-z plane (along x only), and this is why we do not use  dzsy from the common-block
    dzsy=xs*y*dpsrr
    xxd=x-xd
    fdx=0.5*(1+xxd/np.sqrt(xxd**2+xldx**2))
    dddx=deltadx*0.5*xldx**2/np.sqrt(xxd**2+xldx**2)**3
    d=d0+deltadx*fdx

    # this is the same simple way to spread out the sheet, as that used in t89
    dzetas=np.sqrt(zs**2+d**2)
    rhos=  np.sqrt(xs**2+y**2)
    ddzetadx=(zs*dzsx+d*dddx)/dzetas
    ddzetady=zs*dzsy/dzetas
    ddzetadz=zs*dzsz/dzetas

    if rhos < 1e-5:
        drhosdx=0
        drhosdy=np.sign(y)
        drhosdz=0
    else:
        drhosdx=xs*dxsx/rhos
        drhosdy=(xs*dxsy+y)/rhos
        drhosdz=xs*dxsz/rhos

    [bx,by,bz] = [0.]*3

    for i in range(2):
        bi = beta[i]
        s1=np.sqrt((dzetas+bi)**2+(rhos+bi)**2)
        s2=np.sqrt((dzetas+bi)**2+(rhos-bi)**2)
        ds1ddz=(dzetas+bi)/s1
        ds2ddz=(dzetas+bi)/s2
        ds1drhos=(rhos+bi)/s1
        ds2drhos=(rhos-bi)/s2

        ds1dx=ds1ddz*ddzetadx+ds1drhos*drhosdx
        ds1dy=ds1ddz*ddzetady+ds1drhos*drhosdy
        ds1dz=ds1ddz*ddzetadz+ds1drhos*drhosdz

        ds2dx=ds2ddz*ddzetadx+ds2drhos*drhosdx
        ds2dy=ds2ddz*ddzetady+ds2drhos*drhosdy
        ds2dz=ds2ddz*ddzetadz+ds2drhos*drhosdz

        s1ts2=s1*s2
        s1ps2=s1+s2
        s1ps2sq=s1ps2**2
        fac1=np.sqrt(s1ps2sq-(2*bi)**2)
        as0=fac1/(s1ts2*s1ps2sq)
        term1=1/(s1ts2*s1ps2*fac1)
        fac2=as0/s1ps2sq
        dasds1=term1-fac2/s1*(s2*s2+s1*(3*s1+4*s2))
        dasds2=term1-fac2/s2*(s1*s1+s2*(3*s2+4*s1))

        dasdx=dasds1*ds1dx+dasds2*ds2dx
        dasdy=dasds1*ds1dy+dasds2*ds2dy
        dasdz=dasds1*ds1dz+dasds2*ds2dz

        bx=bx+f[i]*((2*as0+y*dasdy)*spss-xs*dasdz+as0*dpsrr*(y**2*cpss+z*zs))
        by=by-f[i]*y*(as0*dpsrr*xs+dasdz*cpss+dasdx*spss)
        bz=bz+f[i]*((2*as0+y*dasdy)*cpss+xs*dasdx-as0*dpsrr*(x*zs+y**2*spss))

    return bx,by,bz

def taildisk(x,y,z):
    """
    This subroutine computes the components of the ring current field, similar to
    that described by Tsyganenko and Peredo (1994). The difference is that now
    we use spacewarping, as described in the paper on modeling Birkeland currents
    (Tsyganenko and Stern, 1996), instead of shearing it in the spirit of the T89 tail model.

    In addition, instead of 8 terms for the tail current model, we use now only 4 terms

    For details, see NB #3, pages 74
    """

    # common /warp/ cpss,spss,dpsrr,xnext(3),xs,zs,dxsx,dxsy,dxsz,other(3),dzetas,ddzetadx,ddzetady,ddzetadz,zsww
    global cpss,spss,dpsrr,xnext,xs,zs,dxsx,dxsy,dxsz,other,dzetas,ddzetadx,ddzetady,ddzetadz,zsww
    xshift = 4.5
    # here original F(I) are multiplied by BETA(I), to economize calculations
    f = np.array([-745796.7338,1176470.141,-444610.529,-57508.01028])
    beta = np.array([7.9250000,8.0850000,8.4712500,27.89500])

    rhos=np.sqrt((xs-xshift)**2+y**2)
    if rhos < 1e-5:
       drhosdx=0
       drhosdy=np.sign(y)
       drhosdz=0
    else:
       drhosdx=(xs-xshift)*dxsx/rhos
       drhosdy=((xs-xshift)*dxsy+y)/rhos
       drhosdz=(xs-xshift)*dxsz/rhos

    bx,by,bz = [0.]*3

    for i in range(4):
        bi=beta[i]

        s1=np.sqrt((dzetas+bi)**2+(rhos+bi)**2)
        s2=np.sqrt((dzetas+bi)**2+(rhos-bi)**2)
        ds1ddz=(dzetas+bi)/s1
        ds2ddz=(dzetas+bi)/s2
        ds1drhos=(rhos+bi)/s1
        ds2drhos=(rhos-bi)/s2

        ds1dx=ds1ddz*ddzetadx+ds1drhos*drhosdx
        ds1dy=ds1ddz*ddzetady+ds1drhos*drhosdy
        ds1dz=ds1ddz*ddzetadz+ds1drhos*drhosdz

        ds2dx=ds2ddz*ddzetadx+ds2drhos*drhosdx
        ds2dy=ds2ddz*ddzetady+ds2drhos*drhosdy
        ds2dz=ds2ddz*ddzetadz+ds2drhos*drhosdz

        s1ts2=s1*s2
        s1ps2=s1+s2
        s1ps2sq=s1ps2**2
        fac1=np.sqrt(s1ps2sq-(2*bi)**2)
        as0=fac1/(s1ts2*s1ps2sq)
        term1=1/(s1ts2*s1ps2*fac1)
        fac2=as0/s1ps2sq
        dasds1=term1-fac2/s1*(s2*s2+s1*(3*s1+4*s2))
        dasds2=term1-fac2/s2*(s1*s1+s2*(3*s2+4*s1))

        dasdx=dasds1*ds1dx+dasds2*ds2dx
        dasdy=dasds1*ds1dy+dasds2*ds2dy
        dasdz=dasds1*ds1dz+dasds2*ds2dz

        bx=bx+f[i]*((2*as0+y*dasdy)*spss-(xs-xshift)*dasdz+as0*dpsrr*(y**2*cpss+z*zsww))
        by=by-f[i]*y*(as0*dpsrr*xs+dasdz*cpss+dasdx*spss)
        bz=bz+f[i]*((2*as0+y*dasdy)*cpss+(xs-xshift)*dasdx-as0*dpsrr*(x*zsww+y**2*spss))

    return bx,by,bz

def tail87(x,z):
    """
    Long version of the 1987 tail magnetic field model (N.A.Tsyganenko, Planet. Space Sci., v.35, p.1347, 1987)
    """
    # common /warp/ first(3), rps,warp,d, other(13)
    # d. The y-dependent sheet half-thickness (increasing towards flanks)
    # rps. The tilt-dependent shift of the sheet in the z-direction, corresponding to
    #     the asymptotic hinging distance, defined in the main subroutine (tailrc96)
    #     from the parameters rh and dr of the T96-type module, and
    # warp. The bending of the sheet flanks in the z-direction, directed
    #      opposite to rps, and increasing with dipole tilt and |y|
    global first, rps,warp,d, other
    dd = 3.
    # These are new values of x1, x2, b0, b1, b2, corresponding to tscale=1, instead of tscale=0.6
    hpi,rt,xn,x1,x2,b0,b1,b2,xn21,xnr,adln = [1.5707963,40.,-10.,
        -1.261,-0.663,0.391734,5.89715,24.6833,76.37,-0.1071,0.13238005]
    # rt=40. z-position of upper and lower additional sheets
    # xn=-10. Inner edge position

    # Scaling factor, defining the rate of increase of the current density tailwards
    tscale=1.

    b0=0.391734
    b1=5.89715 *tscale
    b2=24.6833 *tscale**2

    # Here original values of the mode amplitudes (p.77, nb#3) were normalized so that asymptotic bx=1nT at x=-200Re
    #   x1=(4.589  -5.85) *tscale -(tscale-1.)*xn ! nonlinear parameters of the current function
    #   x2=(5.187  -5.85) *tscale -(tscale-1.)*xn
    xn21=(xn-x1)**2
    xnr=1./(xn-x2)
    adln=-np.log(xnr**2*xn21)

    zs=z-rps+warp
    zp=z-rt
    zm=z+rt

    xnx=xn-x
    xnx2=xnx**2
    xc1=x-x1
    xc2=x-x2
    xc22=xc2**2
    xr2=xc2*xnr
    xc12=xc1**2
    d2=dd**2        # square of the total half-thickness (dd=3Re for this mode)
    b20=zs**2+d2
    b2p=zp**2+d2
    b2m=zm**2+d2
    b= np.sqrt(b20)
    bp=np.sqrt(b2p)
    bm=np.sqrt(b2m)
    xa1=xc12+b20
    xap1=xc12+b2p
    xam1=xc12+b2m
    xa2=1./(xc22+b20)
    xap2=1./(xc22+b2p)
    xam2=1./(xc22+b2m)
    xna=xnx2+b20
    xnap=xnx2+b2p
    xnam=xnx2+b2m
    f=b20-xc22
    fp=b2p-xc22
    fm=b2m-xc22
    xln1= np.log(xn21/xna)
    xlnp1=np.log(xn21/xnap)
    xlnm1=np.log(xn21/xnam)
    xln2=xln1+adln
    xlnp2=xlnp1+adln
    xlnm2=xlnm1+adln
    aln=0.25*(xlnp1+xlnm1-2.*xln1)
    s0= (np.arctan(xnx/b)+hpi)/b
    s0p=(np.arctan(xnx/bp)+hpi)/bp
    s0m=(np.arctan(xnx/bm)+hpi)/bm
    s1=(xln1*.5+xc1*s0)/xa1
    s1p=(xlnp1*.5+xc1*s0p)/xap1
    s1m=(xlnm1*.5+xc1*s0m)/xam1
    s2=(xc2*xa2*xln2-xnr-f*xa2*s0)*xa2
    s2p=(xc2*xap2*xlnp2-xnr-fp*xap2*s0p)*xap2
    s2m=(xc2*xam2*xlnm2-xnr-fm*xam2*s0m)*xam2
    g1=(b20*s0-0.5*xc1*xln1)/xa1
    g1p=(b2p*s0p-0.5*xc1*xlnp1)/xap1
    g1m=(b2m*s0m-0.5*xc1*xlnm1)/xam1
    g2=((0.5*f*xln2+2.*s0*b20*xc2)*xa2+xr2)*xa2
    g2p=((0.5*fp*xlnp2+2.*s0p*b2p*xc2)*xap2+xr2)*xap2
    g2m=((0.5*fm*xlnm2+2.*s0m*b2m*xc2)*xam2+xr2)*xam2
    bx=b0*(zs*s0-0.5*(zp*s0p+zm*s0m))+b1*(zs*s1-0.5*(zp*s1p+zm*s1m))+b2*(zs*s2-0.5*(zp*s2p+zm*s2m))
    bz=b0*aln+b1*(g1-0.5*(g1p+g1m))+b2*(g2-0.5*(g2p+g2m))

    return bx,bz


def birk1tot_02(ps, x,y,z):
    """
    This is the second version of the analytical model of the region I field based on a separate
    representation of the potential field in the inner and outer space, mapped by means of a
    sphero-dipolar coordinate system (NB #3, p.91). The difference from the first one is that
    instead of octagonal current loops, circular ones are used in this version for approximating the
    field in the outer region, which is faster.
    """

    # common /coord11/ xx1(12),yy1(12)
    # common /rhdr/ rh,dr
    # common /loopdip1/ tilt,xcentre(2),radius(2), dipx,dipy
    # common /coord21/ xx2(14),yy2(14),zz2(14)
    # common /dx1/ dx,scalein,scaleout
    global xx1,yy1, rh,dr, tilt, xcentre,radius, dipx,dipy, xx2,yy2,zz2, dx,scalein,scaleout

    c1 = np.array([
        -0.911582e-03,-0.376654e-02,-0.727423e-02,-0.270084e-02,-0.123899E-02,
        -0.154387E-02,-0.340040E-02,-0.191858E-01,-0.518979E-01,0.635061E-01,
        0.440680,-0.396570,0.561238E-02,0.160938E-02,-0.451229E-02,
        -0.251810E-02,-0.151599E-02,-0.133665E-02,-0.962089E-03,-0.272085E-01,
        -0.524319E-01,0.717024E-01,0.523439,-0.405015,-89.5587,23.2806])
    c2 = np.array([
        6.04133,.305415,.606066e-02,.128379e-03,-.179406e-04,
        1.41714,-27.2586,-4.28833,-1.30675,35.5607,8.95792,.961617E-03,
        -.801477E-03,-.782795E-03,-1.65242,-16.5242,-5.33798,.424878E-03,
        .331787E-03,-.704305E-03,.844342E-03,.953682E-04,.886271E-03,
        25.1120,20.9299,5.14569,-44.1670,-51.0672,-1.87725,20.2998,
        48.7505,-2.97415,3.35184,-54.2921,-.838712,-10.5123,70.7594,
        -4.94104,.106166E-03,.465791E-03,-.193719E-03,10.8439,-29.7968,
         8.08068,.463507E-03,-.224475E-04,.177035E-03,-.317581E-03,
        -.264487E-03,.102075E-03,7.71390,10.1915,-4.99797,-23.1114,
        -29.2043,12.2928,10.9542,33.6671,-9.3851,.174615E-03,-.789777E-06,
        .686047E-03,.460104E-04,-.345216E-02,.221871E-02,.110078E-01,
        -.661373E-02,.249201E-02,.343978E-01,-.193145E-05,.493963E-05,
        -.535748E-04,.191833E-04,-.100496E-03,-.210103E-03,-.232195E-02,
        .315335E-02,-.134320E-01,-.263222E-01])
    tilt,xcentre,radius,dipx,dipy = [1.00891,[2.28397,-5.60831],[1.86106,7.83281],1.12541,0.945719]
    dx,scalein,scaleout = [-0.16,0.08,0.4]
    xx1 = np.array([-11.,-7,-7,-3,-3,1,1,1,5,5,9,9])
    yy1 = np.array([2.,0,4,2,6,0,4,8,2,6,0,4])
    xx2 = np.array([-10.,-7,-4,-4,0,4,4,7,10,0,0,0,0,0])
    yy2 = np.array([3.,6,3,9,6,3,9,6,3,0,0,0,0,0])
    zz2 = np.array([20.,20,4,20,4,4,20,20,20,2,3,4.5,7,10])

    # rh is the "hinging distance" and dr is the transition scale length, defining the curvature of the warping (see p.89, NB #2)
    rh,dr = [9.,4.]

    # these are latitudes of the r-1 oval at noon and at midnight
    xltday,xltnght = [78.,70.]

    # this is the latitudinal half-thickness of the r-1 oval (the interpolation region between the high-lat. and the plasma sheet)
    dtet0 = 0.034906

    # Here we assume that the positions of the northern and southern r-1 ovals are symmetric in the sm-coordinates
    tnoonn=(90-xltday)*0.01745329
    tnoons=np.pi-tnoonn
    dtetdn=(xltday-xltnght)*0.01745329
    dr2=dr**2

    sps=np.sin(ps)
    r2=x**2+y**2+z**2
    r=np.sqrt(r2)
    r3=r*r2

    rmrh=r-rh
    rprh=r+rh
    sqm=np.sqrt(rmrh**2+dr2)
    sqp=np.sqrt(rprh**2+dr2)
    c=sqp-sqm
    q=np.sqrt((rh+1)**2+dr2)-np.sqrt((rh-1)**2+dr2)
    spsas=sps/r*c/q
    cpsas=np.sqrt(1-spsas**2)
    xas = x*cpsas-z*spsas
    zas = x*spsas+z*cpsas
    pas = 0.
    if (xas != 0) & (y != 0):
        pas = np.arctan2(y,xas)
    tas=np.arctan2(np.sqrt(xas**2+y**2),zas)
    stas=np.sin(tas)
    f=stas/(stas**6*(1-r3)+r3)**0.1666666667

    tet0=np.arcsin(f)
    if tas > 1.5707963: tet0=np.pi-tet0
    dtet=dtetdn*np.sin(pas*0.5)**2
    tetr1n=tnoonn+dtet
    tetr1s=tnoons-dtet

    # Now let's define which of the four regions (high-lat., northern psbl,
    # plasma sheet, southern psbl) does the point (x,y,z) belong to:
    # tetr1s is greater than tetr1n. That's why high lat is when tetr1n<tetr1n-dtet0. -Sheng.
    loc = 0
    if (tet0 < tetr1n-dtet0) | (tet0 > tetr1s+dtet0):   loc = 1   # high-lat
    if (tet0 > tetr1n+dtet0) & (tet0 < tetr1s-dtet0):   loc = 2   # pl.sheet
    if (tet0 >= tetr1n-dtet0) & (tet0 <= tetr1n+dtet0): loc = 3 # north psbl
    if (tet0 >= tetr1s-dtet0) & (tet0 <= tetr1s+dtet0): loc = 4 # south psbl

    bx,by,bz = [0.]*3
    # in the high-lat. region use the subroutine dipoct
    if loc == 1:
        xi = [x,y,z,ps]
        d1 = diploop1(xi)
        for i in range(26):
            bx=bx+c1[i]*d1[0,i]
            by=by+c1[i]*d1[1,i]
            bz=bz+c1[i]*d1[2,i]
    elif loc == 2:
        xi = [x,y,z,ps]
        d2 = condip1(xi)
        for i in range(79):
            bx=bx+c2[i]*d2[0,i]
            by=by+c2[i]*d2[1,i]
            bz=bz+c2[i]*d2[2,i]
    elif loc == 3:
        t01=tetr1n-dtet0
        t02=tetr1n+dtet0
        sqr=np.sqrt(r)
        st01as=sqr/(r3+1/np.sin(t01)**6-1)**0.1666666667
        st02as=sqr/(r3+1/np.sin(t02)**6-1)**0.1666666667
        ct01as=np.sqrt(1-st01as**2)
        ct02as=np.sqrt(1-st02as**2)

        # x1,y1,z1 are coords of the northern boundary point
        xas1=r*st01as*np.cos(pas)
        y1=  r*st01as*np.sin(pas)
        zas1=r*ct01as
        x1= xas1*cpsas+zas1*spsas
        z1=-xas1*spsas+zas1*cpsas
        xi = [x1,y1,z1,ps]
        d1 = diploop1(xi)
        # bx1,by1,bz1 are field components in the northern boundary point
        bx1,by1,bz1 = [0.]*3
        for i in range(26):
            bx1=bx1+c1[i]*d1[0,i]
            by1=by1+c1[i]*d1[1,i]
            bz1=bz1+c1[i]*d1[2,i]

        # x2,y2,z2 are coords of the southern boundary point
        xas2=r*st02as*np.cos(pas)
        y2=  r*st02as*np.sin(pas)
        zas2=r*ct02as
        x2= xas2*cpsas+zas2*spsas
        z2=-xas2*spsas+zas2*cpsas
        xi = [x2,y2,z2,ps]
        d2 = condip1(xi)
        # bx2,by2,bz2 are field components in the southern boundary point
        bx2,by2,bz2 = [0.]*3
        for i in range(79):
            bx2=bx2+c2[i]*d2[0,i]
            by2=by2+c2[i]*d2[1,i]
            bz2=bz2+c2[i]*d2[2,i]

        # now interpolate:
        ss=np.sqrt((x2-x1)**2+(y2-y1)**2+(z2-z1)**2)
        ds=np.sqrt((x-x1)**2+(y-y1)**2+(z-z1)**2)
        frac=ds/ss
        bx=bx1*(1-frac)+bx2*frac
        by=by1*(1-frac)+by2*frac
        bz=bz1*(1-frac)+bz2*frac
    elif loc == 4:
        t01=tetr1s-dtet0
        t02=tetr1s+dtet0
        sqr=np.sqrt(r)
        st01as=sqr/(r3+1/np.sin(t01)**6-1)**0.1666666667
        st02as=sqr/(r3+1/np.sin(t02)**6-1)**0.1666666667
        ct01as=-np.sqrt(1-st01as**2)
        ct02as=-np.sqrt(1-st02as**2)

        # x1,y1,z1 are coords of the northern boundary point
        xas1=r*st01as*np.cos(pas)
        y1=  r*st01as*np.sin(pas)
        zas1=r*ct01as
        x1= xas1*cpsas+zas1*spsas
        z1=-xas1*spsas+zas1*cpsas
        xi = [x1,y1,z1,ps]
        d2 = condip1(xi)
        # bx1,by1,bz1 are field components in the northern boundary point
        bx1,by1,bz1 = [0.]*3
        for i in range(79):
            bx1=bx1+c2[i]*d2[0,i]
            by1=by1+c2[i]*d2[1,i]
            bz1=bz1+c2[i]*d2[2,i]

        # x2,y2,z2 are coords of the southern boundary point
        xas2=r*st02as*np.cos(pas)
        y2=  r*st02as*np.sin(pas)
        zas2=r*ct02as
        x2= xas2*cpsas+zas2*spsas
        z2=-xas2*spsas+zas2*cpsas
        xi = [x1,y1,z1,ps]
        d1 = diploop1(xi)
        # bx2,by2,bz2 are field components in the southern boundary point
        bx2,by2,bz2 = [0.]*3
        for i in range(26):
            bx2=bx2+c1[i]*d1[0,i]
            by2=by2+c1[i]*d1[1,i]
            bz2=bz2+c1[i]*d1[2,i]

        # now interpolate:
        ss=np.sqrt((x2-x1)**2+(y2-y1)**2+(z2-z1)**2)
        ds=np.sqrt((x-x1)**2+(y-y1)**2+(z-z1)**2)
        frac=ds/ss
        bx=bx1*(1-frac)+bx2*frac
        by=by1*(1-frac)+by2*frac
        bz=bz1*(1-frac)+bz2*frac
    else: raise ValueError

    # Now, let us add the shielding field
    bsx,bsy,bsz = birk1shld(ps, x,y,z)

    bx=bx+bsx
    by=by+bsy
    bz=bz+bsz

    return bx,by,bz

def diploop1(xi):
    """
    Calculates dependent model variables and their derivatives for given independent variables
    and model parameters. Specifies model functions with free parameters which must be determined
    by means of least squares fits (RMS minimization procedure).

    The 26 coefficients are moments (Z- and X-components) of 12 dipoles placed inside the
    R1-shell, plus amplitudes of two octagonal double loops. The dipoles with nonzero Yi
    appear in pairs with equal moments. (see the notebook #2, pp.102-103, for details)

    :param xi: input vector containing independent variables
    :return: d. Output double precision vector containing calculated values for derivatives of
        dependent variables with respect to LINEAR model parameters.
    """

    # common /coord11/ xx1(12),yy1(12)
    # common /loopdip1/ tilt,xcentre(2),radius(2),  dipx,dipy
    # common /rhdr/rh,dr
    global xx1,yy1, tilt,xcentre,radius, dipx,dipy, rh,dr

    x,y,z, ps = xi
    sps=np.sin(ps)
    d = np.empty((3,26))

    for i in range(12):
        r2=(xx1[i]*dipx)**2+(yy1[i]*dipy)**2
        r=np.sqrt(r2)
        rmrh=r-rh
        rprh=r+rh
        dr2=dr**2
        sqm=np.sqrt(rmrh**2+dr2)
        sqp=np.sqrt(rprh**2+dr2)
        c=sqp-sqm
        q=np.sqrt((rh+1)**2+dr2)-np.sqrt((rh-1)**2+dr2)
        spsas=sps/r*c/q
        cpsas=np.sqrt(1-spsas**2)
        xd= (xx1[i]*dipx)*cpsas
        yd= (yy1[i]*dipy)
        zd=-(xx1[i]*dipx)*spsas
        bx1x,by1x,bz1x,bx1y,by1y,bz1y,bx1z,by1z,bz1z = dipxyz(x-xd,y-yd,z-zd)
        if np.abs(yd) > 1e-10:
            bx2x,by2x,bz2x,bx2y,by2y,bz2y,bx2z,by2z,bz2z = dipxyz(x-xd,y+yd,z-zd)
        else:
            bx2x,by2x,bz2x = [0.]*3
            bx2z,by2z,bz2z = [0.]*3

        d[0,i]=bx1z+bx2z
        d[1,i]=by1z+by2z
        d[2,i]=bz1z+bz2z
        d[0,i+12]=(bx1x+bx2x)*sps
        d[1,i+12]=(by1x+by2x)*sps
        d[2,i+12]=(bz1x+bz2x)*sps

    r2=(xcentre[0]+radius[0])**2
    r=np.sqrt(r2)
    rmrh=r-rh
    rprh=r+rh
    dr2=dr**2
    sqm=np.sqrt(rmrh**2+dr2)
    sqp=np.sqrt(rprh**2+dr2)
    c=sqp-sqm
    q=np.sqrt((rh+1)**2+dr2)-np.sqrt((rh-1)**2+dr2)
    spsas=sps/r*c/q
    cpsas=np.sqrt(1-spsas**2)
    xoct1= x*cpsas-z*spsas
    yoct1= y
    zoct1= x*spsas+z*cpsas

    bxoct1,byoct1,bzoct1 = crosslp(xoct1,yoct1,zoct1,xcentre[0],radius[0],tilt)
    d[0,24]= bxoct1*cpsas+bzoct1*spsas
    d[1,24]= byoct1
    d[2,24]=-bxoct1*spsas+bzoct1*cpsas

    r2=(radius[1]-xcentre[1])**2
    r=np.sqrt(r2)
    rmrh=r-rh
    rprh=r+rh
    dr2=dr**2
    sqm=np.sqrt(rmrh**2+dr2)
    sqp=np.sqrt(rprh**2+dr2)
    c=sqp-sqm
    q=np.sqrt((rh+1)**2+dr2)-np.sqrt((rh-1)**2+dr2)
    spsas=sps/r*c/q
    cpsas=np.sqrt(1-spsas**2)
    xoct2= x*cpsas-z*spsas -xcentre[1]
    yoct2= y
    zoct2= x*spsas+z*cpsas
    bx,by,bz = circle(xoct2,yoct2,zoct2,radius[1])
    d[0,25] =  bx*cpsas+bz*spsas
    d[1,25] =  by
    d[2,25] = -bx*spsas+bz*cpsas

    return d

def dipxyz(x,y,z):
    """
    Returns the field components produced by three dipoles, each having M=Me
    and oriented parallel to x,y, and z axis, respectively.

    :param x,y,z:
    :return: bxx,byx,bzx,bxy,byy,bzy,bxz,byz,bzz
    """

    x2=x**2
    y2=y**2
    z2=z**2
    r2=x2+y2+z2

    xmr5=30574/(r2*r2*np.sqrt(r2))
    xmr53=3*xmr5
    bxx=xmr5*(3*x2-r2)
    byx=xmr53*x*y
    bzx=xmr53*x*z

    bxy=byx
    byy=xmr5*(3*y2-r2)
    bzy=xmr53*y*z

    bxz=bzx
    byz=bzy
    bzz=xmr5*(3*z2-r2)

    return bxx,byx,bzx,bxy,byy,bzy,bxz,byz,bzz


def crosslp(x,y,z,xc,rl,al):
    """
    Returns field components of a pair of loops with a common center and diameter,
    coinciding with the x axis. The loops are inclined to the equatorial plane by
    the angle al (radians) and shifted in the positive x-direction by the distance xc.

    :param x,y,z:
    :param xc:
    :param rl:
    :param al:
    :return:
    """

    cal=np.cos(al)
    sal=np.sin(al)

    y1=y*cal-z*sal
    z1=y*sal+z*cal
    y2=y*cal+z*sal
    z2=-y*sal+z*cal
    bx1,by1,bz1 = circle(x-xc,y1,z1,rl)
    bx2,by2,bz2 = circle(x-xc,y2,z2,rl)
    bx=bx1+bx2
    by= (by1+by2)*cal+(bz1-bz2)*sal
    bz=-(by1-by2)*sal+(bz1+bz2)*cal

    return bx,by,bz

def circle(x,y,z,rl):
    """
    Returns components of the field from a circular current loop of radius rl.
    Uses the second (more accurate) approximation given in Abramowitz and Stegun

    :param x,y,z:
    :param rl:
    :return:
    """

    rho2=x*x+y*y
    rho=np.sqrt(rho2)
    r22=z*z+(rho+rl)**2
    r2=np.sqrt(r22)
    r12=r22-4*rho*rl
    r32=0.5*(r12+r22)
    xk2=1-r12/r22
    xk2s=1-xk2
    dl=np.log(1/xk2s)
    k=1.38629436112+xk2s*(0.09666344259+xk2s*(0.03590092383+xk2s*(0.03742563713+xk2s*0.01451196212)))+\
      dl*(0.5+xk2s*(0.12498593597+xk2s*(0.06880248576+xk2s*(0.03328355346+xk2s*0.00441787012))))
    e=1+xk2s*(0.44325141463+xk2s*(0.0626060122+xk2s*(0.04757383546+xk2s*0.01736506451)))+\
      dl*xk2s*(0.2499836831+xk2s*(0.09200180037+xk2s*(0.04069697526+xk2s*0.00526449639)))

    if rho > 1e-6:
        brho=z/(rho2*r2)*(r32/r12*e-k) # this is not exactly the b-rho component - note the additional division by rho
    else:
        brho=np.pi*rl/r2*(rl-rho)/r12*z/(r32-rho2)

    bx=brho*x
    by=brho*y
    bz=(k-e*(r32-2*rl*rl)/r12)/r2

    return bx,by,bz


def condip1(xi):
    """
    Calculates dependent model variables and their derivatives for given independent variables
    and model parameters. Specifies model functions with free parameters which must be determined
    by means of least squares fits (RMS minimization procedure).

    The 79 coefficients are (1) 5 amplitudes of the conical harmonics, plus
    (2) (9x3+5x2)x2=74 components of the dipole moments (see the notebook #2, pp.113-..., for details)

    :param xi: input vector containing independent variables
    :return: d. Output double precision vector containing calculated values for derivatives of
        dependent variables with respect to LINEAR model parameters.
    """

    # common /dx1/ dx,scalein,scaleout
    # common /coord21/ xx(14),yy(14),zz(14)
    global xx2,yy2,zz2, dx,scalein,scaleout

    x,y,z, ps = xi
    sps=np.sin(ps)
    cps=np.cos(ps)
    d = np.empty((3,79))
    cf = np.empty(5)
    sf = np.empty(5)

    xsm=x*cps-z*sps-dx
    zsm=z*cps+x*sps
    ro2=xsm**2+y**2
    ro=np.sqrt(ro2)

    cf[0]=xsm/ro
    sf[0]=y/ro
    cf[1]=cf[0]**2-sf[0]**2
    sf[1]=2*sf[0]*cf[0]
    cf[2]=cf[1]*cf[0]-sf[1]*sf[0]
    sf[2]=sf[1]*cf[0]+cf[1]*sf[0]
    cf[3]=cf[2]*cf[0]-sf[2]*sf[0]
    sf[3]=sf[2]*cf[0]+cf[2]*sf[0]
    cf[4]=cf[3]*cf[0]-sf[3]*sf[0]
    sf[4]=sf[3]*cf[0]+cf[3]*sf[0]

    r2=ro2+zsm**2
    r=np.sqrt(r2)
    c=zsm/r
    s=ro/r
    ch=np.sqrt(0.5*(1+c))
    sh=np.sqrt(0.5*(1-c))
    tnh=sh/ch
    cnh=1/tnh

    # init d[:,0:5]
    for m in range(5):
        m1 = m+1
        bt=m1*cf[m]/(r*s)*(tnh**m1+cnh**m1)
        bf=-0.5*m1*sf[m]/r*(tnh**m/ch**2-cnh**m/sh**2)
        bxsm= bt*c*cf[0]-bf*sf[0]
        by  = bt*c*sf[0]+bf*cf[0]
        bzsm=-bt*s

        d[0,m]= bxsm*cps+bzsm*sps
        d[1,m]= by
        d[2,m]=-bxsm*sps+bzsm*cps

    xsm = x*cps-z*sps
    zsm = z*cps+x*sps

    # init d[:,5:32] and d[:,32:59]
    for i in range(9):
        if (i == 2) | (i == 4) | (i == 5):
            xd = xx2[i]*scalein
            yd = yy2[i]*scalein
        else:
            xd = xx2[i]*scaleout
            yd = yy2[i]*scaleout

        zd = zz2[i]
        bx1x,by1x,bz1x,bx1y,by1y,bz1y,bx1z,by1z,bz1z = dipxyz(xsm-xd,y-yd,zsm-zd)
        bx2x,by2x,bz2x,bx2y,by2y,bz2y,bx2z,by2z,bz2z = dipxyz(xsm-xd,y+yd,zsm-zd)
        bx3x,by3x,bz3x,bx3y,by3y,bz3y,bx3z,by3z,bz3z = dipxyz(xsm-xd,y-yd,zsm+zd)
        bx4x,by4x,bz4x,bx4y,by4y,bz4y,bx4z,by4z,bz4z = dipxyz(xsm-xd,y+yd,zsm+zd)

        ix=i*3+5
        iy=ix+1
        iz=iy+1

        d[0,ix]=(bx1x+bx2x-bx3x-bx4x)*cps+(bz1x+bz2x-bz3x-bz4x)*sps
        d[1,ix]= by1x+by2x-by3x-by4x
        d[2,ix]=(bz1x+bz2x-bz3x-bz4x)*cps-(bx1x+bx2x-bx3x-bx4x)*sps

        d[0,iy]=(bx1y-bx2y-bx3y+bx4y)*cps+(bz1y-bz2y-bz3y+bz4y)*sps
        d[1,iy]= by1y-by2y-by3y+by4y
        d[2,iy]=(bz1y-bz2y-bz3y+bz4y)*cps-(bx1y-bx2y-bx3y+bx4y)*sps

        d[0,iz]=(bx1z+bx2z+bx3z+bx4z)*cps+(bz1z+bz2z+bz3z+bz4z)*sps
        d[1,iz]= by1z+by2z+by3z+by4z
        d[2,iz]=(bz1z+bz2z+bz3z+bz4z)*cps-(bx1z+bx2z+bx3z+bx4z)*sps

        ix=ix+27
        iy=iy+27
        iz=iz+27

        d[0,ix]=sps*((bx1x+bx2x+bx3x+bx4x)*cps+(bz1x+bz2x+bz3x+bz4x)*sps)
        d[1,ix]=sps*(by1x+by2x+by3x+by4x)
        d[2,ix]=sps*((bz1x+bz2x+bz3x+bz4x)*cps-(bx1x+bx2x+bx3x+bx4x)*sps)

        d[0,iy]=sps*((bx1y-bx2y+bx3y-bx4y)*cps+(bz1y-bz2y+bz3y-bz4y)*sps)
        d[1,iy]=sps*(by1y-by2y+by3y-by4y)
        d[2,iy]=sps*((bz1y-bz2y+bz3y-bz4y)*cps-(bx1y-bx2y+bx3y-bx4y)*sps)

        d[0,iz]=sps*((bx1z+bx2z-bx3z-bx4z)*cps+(bz1z+bz2z-bz3z-bz4z)*sps)
        d[1,iz]=sps*(by1z+by2z-by3z-by4z)
        d[2,iz]=sps*((bz1z+bz2z-bz3z-bz4z)*cps-(bx1z+bx2z-bx3z-bx4z)*sps)

    # init d[59:69] and d[69:79]
    for i in range(5):
        zd=zz2[i+9]
        bx1x,by1x,bz1x,bx1y,by1y,bz1y,bx1z,by1z,bz1z = dipxyz(xsm,y,zsm-zd)
        bx2x,by2x,bz2x,bx2y,by2y,bz2y,bx2z,by2z,bz2z = dipxyz(xsm,y,zsm+zd)
        ix=59+i*2
        iz=ix+1
        d[0,ix]=(bx1x-bx2x)*cps+(bz1x-bz2x)*sps
        d[1,ix]= by1x-by2x
        d[2,ix]=(bz1x-bz2x)*cps-(bx1x-bx2x)*sps

        d[0,iz]=(bx1z+bx2z)*cps+(bz1z+bz2z)*sps
        d[1,iz]= by1z+by2z
        d[2,iz]=(bz1z+bz2z)*cps-(bx1z+bx2z)*sps

        ix=ix+10
        iz=iz+10
        d[0,ix]=sps*((bx1x+bx2x)*cps+(bz1x+bz2x)*sps)
        d[1,ix]=sps* (by1x+by2x)
        d[2,ix]=sps*((bz1x+bz2x)*cps-(bx1x+bx2x)*sps)

        d[0,iz]=sps*((bx1z-bx2z)*cps+(bz1z-bz2z)*sps)
        d[1,iz]=sps* (by1z-by2z)
        d[2,iz]=sps*((bz1z-bz2z)*cps-(bx1z-bx2z)*sps)

    return d

def birk1shld(ps, x,y,z):
    """
    The 64 linear parameters are amplitudes of the "box" harmonics. The 16 nonlinear parametersare the scales Pi,
    and Qk entering the arguments of sines/cosines and exponents in each of 32 cartesian harmonics

    :param ps:
    :param x,y,z:
    :return:
    """

    a = np.array([
        1.174198045,-1.463820502,4.840161537,-3.674506864,82.18368896,
        -94.94071588,-4122.331796,4670.278676,-21.54975037,26.72661293,
        -72.81365728,44.09887902,40.08073706,-51.23563510,1955.348537,
        -1940.971550,794.0496433,-982.2441344,1889.837171,-558.9779727,
        -1260.543238,1260.063802,-293.5942373,344.7250789,-773.7002492,
        957.0094135,-1824.143669,520.7994379,1192.484774,-1192.184565,
        89.15537624,-98.52042999,-0.8168777675E-01,0.4255969908E-01,0.3155237661,
        -0.3841755213,2.494553332,-0.6571440817E-01,-2.765661310,0.4331001908,
        0.1099181537,-0.6154126980E-01,-0.3258649260,0.6698439193,-5.542735524,
        0.1604203535,5.854456934,-0.8323632049,3.732608869,-3.130002153,
        107.0972607,-32.28483411,-115.2389298,54.45064360,-0.5826853320,
        -3.582482231,-4.046544561,3.311978102,-104.0839563,30.26401293,
        97.29109008,-50.62370872,-296.3734955,127.7872523,5.303648988,
        10.40368955,69.65230348,466.5099509,1.645049286,3.825838190,
        11.66675599,558.9781177,1.826531343,2.066018073,25.40971369,
        990.2795225,2.319489258,4.555148484,9.691185703,591.8280358])

    p1 = a[64:68]
    r1 = a[68:72]
    q1 = a[72:76]
    s1 = a[76:80]
    rp = 1/p1
    rr = 1/r1
    rq = 1/q1
    rs = 1/s1


    bx,by,bz = [0.]*3
    cps=np.cos(ps)
    sps=np.sin(ps)
    s3ps=4*cps**2-1

    l = 0
    for m in range(2):  # m=1 for 1st sum (perp symmetry), m=2 for 2nd sum (parallel symmetry)
        for i in range(4):
            cypi=np.cos(y*rp[i])
            cyqi=np.cos(y*rq[i])
            sypi=np.sin(y*rp[i])
            syqi=np.sin(y*rq[i])
            for k in range(4):
                szrk=np.sin(z*rr[k])
                czsk=np.cos(z*rs[k])
                czrk=np.cos(z*rr[k])
                szsk=np.sin(z*rs[k])
                sqpr=np.sqrt(rp[i]**2+rr[k]**2)
                sqqs=np.sqrt(rq[i]**2+rs[k]**2)
                epr= np.exp(x*sqpr)
                eqs= np.exp(x*sqqs)
                for n in range(2):  # n=1 for the 1st part of each coefficient, n=2 for 2nd
                    if m == 0:
                        if n == 0:
                            hx=-sqpr*epr*cypi*szrk
                            hy= rp[i]*epr*sypi*szrk
                            hz=-rr[k]*epr*cypi*czrk
                        else:
                            hx=hx*cps
                            hy=hy*cps
                            hz=hz*cps
                    else:
                        if n == 0:
                            hx=-sps*sqqs*eqs*cyqi*czsk
                            hy= sps*rq[i]*eqs*syqi*czsk
                            hz= sps*rs[k]*eqs*cyqi*szsk
                        else:
                            hx=hx*s3ps
                            hy=hy*s3ps
                            hz=hz*s3ps
                    bx=bx+a[l]*hx
                    by=by+a[l]*hy
                    bz=bz+a[l]*hz
                    l=l+1

    return bx,by,bz

def birk2tot_02(ps, x,y,z):
    """

    :param ps:
    :param x,y,z:
    :return:
    """

    wx,wy,wz= birk2shl(x,y,z,ps)
    hx,hy,hz = r2_birk(x,y,z,ps)
    bx=wx+hx
    by=wy+hy
    bz=wz+hz

    return bx,by,bz

def birk2shl(x,y,z, ps):
    """
    The model parameters are provided to this module via common-block /A/.
    The 16 linear parameters enter in pairs in the amplitudes of the "cartesian" harmonics.
    The 8 nonlinear parameters are the scales Pi,Ri,Qi,and Si entering the
    arguments of exponents, sines, and cosines in each of the 8 "Cartesian" harmonics

    :param x,y,z:
    :param ps:
    :return:
    """

    a = np.array([
        -111.6371348,124.5402702,110.3735178,-122.0095905,111.9448247,-129.1957743,
        -110.7586562,126.5649012,-0.7865034384,-0.2483462721,0.8026023894,0.2531397188,
        10.72890902,0.8483902118,-10.96884315,-0.8583297219,13.85650567,14.90554500,
        10.21914434,10.09021632,6.340382460,14.40432686,12.71023437,12.83966657])
    p = a[16:18]
    r = a[18:20]
    q = a[20:22]
    s = a[22:24]

    cps=np.cos(ps)
    sps=np.sin(ps)
    s3ps=4*cps**2-1 # this is sin(3*ps)/sin(ps)

    hx,hy,hz = [0.]*3

    l = 0
    for m in range(2):  # m=1 for 1st sum (perp symmetry), m=2 for 2nd sum (parallel symmetry)
        for i in range(2):
            cypi=np.cos(y/p[i])
            cyqi=np.cos(y/q[i])
            sypi=np.sin(y/p[i])
            syqi=np.sin(y/q[i])
            for k in range(2):
                szrk=np.sin(z/r[k])
                czsk=np.cos(z/s[k])
                czrk=np.cos(z/r[k])
                szsk=np.sin(z/s[k])
                sqpr=np.sqrt(1/p[i]**2+1/r[k]**2)
                sqqs=np.sqrt(1/q[i]**2+1/s[k]**2)
                epr= np.exp(x*sqpr)
                eqs= np.exp(x*sqqs)
                for n in range(2):  # n=1 for the 1st part of each coefficient, n=2 for 2nd
                    if m == 0:
                        if n == 0:
                            dx=-sqpr*epr*cypi*szrk
                            dy= epr/p[i]*sypi*szrk
                            dz=-epr/r[k]*cypi*czrk
                            hx=hx+a[l]*dx
                            hy=hy+a[l]*dy
                            hz=hz+a[l]*dz
                        else:
                            dx=dx*cps
                            dy=dy*cps
                            dz=dz*cps
                            hx=hx+a[l]*dx
                            hy=hy+a[l]*dy
                            hz=hz+a[l]*dz
                    else:
                        if n == 0:
                            dx=-sps*sqqs*eqs*cyqi*czsk
                            dy= sps*eqs/q[i]*syqi*czsk
                            dz= sps*eqs/s[k]*cyqi*szsk
                            hx=hx+a[l]*dx
                            hy=hy+a[l]*dy
                            hz=hz+a[l]*dz
                        else:
                            dx=dx*s3ps
                            dy=dy*s3ps
                            dz=dz*s3ps
                            hx=hx+a[l]*dx
                            hy=hy+a[l]*dy
                            hz=hz+a[l]*dz
                    l += 1
    return hx,hy,hz

def r2_birk(x,y,z, ps):
    """
    Returns the model field for the region II birkeland current/partial rc (without shielding field)

    :param x,y,z:
    :param ps:
    :return:
    """

    delarg,delarg1= [0.03,0.015]

    psi=ps
    cps=np.cos(ps)
    sps=np.sin(ps)

    xsm=x*cps-z*sps
    zsm=z*cps+x*sps

    xks=xksi(xsm,y,zsm)
    if xks < -(delarg+delarg1):
        # all components are multiplied by the factor -0.02, so that bz=-1 nt at x=-5.3 re, y=z=0
        bxsm,by,bzsm = np.dot(r2outer(xsm,y,zsm),-0.02)
    elif (xks < -delarg+delarg1):
        f2=-0.02*tksi(xks,-delarg,delarg1)
        f1=-0.02-f2
        bxsm1,by1,bzsm1 = np.dot(r2outer(xsm,y,zsm),f1)
        bxsm2,by2,bzsm2 = np.dot(r2sheet(xsm,y,zsm),f2)
        bxsm=bxsm1+bxsm2
        by  =by1+by2
        bzsm=bzsm1+bzsm2
    elif (xks < delarg-delarg1):
        bxsm,by,bzsm = np.dot(r2outer(xsm,y,zsm),-0.02)
    elif (xks < delarg+delarg1):
        f1=-0.02*tksi(xks,delarg,delarg1)
        f2=-0.02-f1
        bxsm1,by1,bzsm1 = np.dot(r2inner(xsm,y,zsm),f1)
        bxsm2,by2,bzsm2 = np.dot(r2sheet(xsm,y,zsm),f2)
        bxsm=bxsm1+bxsm2
        by  =by1+by2
        bzsm=bzsm1+bzsm2
    else:
        bxsm,by,bzsm = np.dot(r2inner(xsm,y,zsm),-0.02)

    bx=bxsm*cps+bzsm*sps
    bz=bzsm*cps-bxsm*sps

    return bx,by,bz

def xksi(x,y,z):
    """
    :param x,y,z:
    :return:
    """
    # a11 - c72, r0, and dr below are stretch parameters (p.26-27, NB# 3),
    a11a12,a21a22,a41a42,a51a52,a61a62,b11b12,b21b22,c61c62,c71c72,r0,dr =\
        [0.305662,-0.383593,0.2677733,-0.097656,-0.636034,-0.359862,0.424706,-0.126366,0.292578,1.21563,7.50937]

    # correspond to noon and midnight latitudes 69 and 63.5 degs, resp.
    tnoon,dteta = [0.3665191,0.09599309]

    dr2=dr*dr
    x2=x*x
    y2=y*y
    z2=z*z
    xy=x*y
    r2=x2+y2+z2
    r=np.sqrt(r2)
    xr=x/r
    yr=y/r
    zr=z/r

    if r < r0: pr=0
    else: pr=np.sqrt((r-r0)**2+dr2)-dr

    f=x+pr*(a11a12+a21a22*xr+a41a42*xr*xr+a51a52*yr*yr+a61a62*zr*zr)
    g=y+pr*(b11b12*yr+b21b22*xr*yr)
    h=z+pr*(c61c62*zr+c71c72*xr*zr)
    g2=g*g

    fgh=f**2+g2+h**2
    fgh32=np.sqrt(fgh)**3
    fchsg2=f**2+g2

    if fchsg2 < 1e-5:
        # this is just for eliminating problems on the z-axis
        xksi = -1.
    else:
        sqfchsg2=np.sqrt(fchsg2)
        alpha=fchsg2/fgh32
        theta=tnoon+0.5*dteta*(1-f/sqfchsg2)
        phi=np.sin(theta)**2
        xksi=alpha-phi

    return xksi

def tksi(xksi,xks0,dxksi):

    tdz3=2.*dxksi**3
    if xksi-xks0 < -dxksi:
        tksii=0.
    elif xksi < xks0:
        br3=(xksi-xks0+dxksi)**3
        tksii=1.5*br3/(tdz3+br3)
    elif xksi-xks0 < dxksi:
        br3=(xksi-xks0-dxksi)**3
        tksii=1.+1.5*br3/(tdz3-br3)
    elif xksi-xks0 >= dxksi:
        tksii=1.
    else:
        raise ValueError

    return tksii


def fexp(s,a):
    # TODO the function is not continuous in a???
    if a < 0:
        return np.sqrt(-2*a*np.e)*s*np.exp(a*s*s)
    else:
        return s*np.exp(a*(s*s-1))

def fexp1(s,a):
    # TODO the function is not continuous in a???
    if a <= 0:
        return np.exp(a*s*s)
    else:
        return np.exp(a*(s*s-1))


def r2outer(x,y,z):
    """

    :param x,y,z:
    :return:
    """

    pl1,pl2,pl3,pl4,pl5 = [-34.105,-2.00019,628.639,73.4847,12.5162]
    pn1,pn2,pn3,pn4,pn5,pn6,pn7,pn8,pn9,pn10,pn11,pn12,pn13,pn14,pn15,pn16,pn17 = \
        [.55,.694,.0031,1.55,2.8,.1375,-.7,.2,.9625,-2.994,2.925,-1.775,4.3,-.275,2.7,.4312,1.55]

    # three pairs of crossed loops:
    dbx1,dby1,dbz1 = crosslp(x,y,z,pn1,pn2,pn3)
    dbx2,dby2,dbz2 = crosslp(x,y,z,pn4,pn5,pn6)
    dbx3,dby3,dbz3 = crosslp(x,y,z,pn7,pn8,pn9)

    # now an equatorial loop on the nightside
    dbx4,dby4,dbz4 = circle(x-pn10,y,z,pn11)

    # now a 4-loop system on the nightside
    dbx5,dby5,dbz5 = loops4(x,y,z,pn12,pn13,pn14,pn15,pn16,pn17)

    # now compute the field components:
    bx=pl1*dbx1+pl2*dbx2+pl3*dbx3+pl4*dbx4+pl5*dbx5
    by=pl1*dby1+pl2*dby2+pl3*dby3+pl4*dby4+pl5*dby5
    bz=pl1*dbz1+pl2*dbz2+pl3*dbz3+pl4*dbz4+pl5*dbz5

    return bx,by,bz

def loops4(x,y,z,xc,yc,zc,r,theta,phi):
    """
    Returns field components from a system of 4 current loops, positioned symmetrically
    with respect to noon-midnight meridian and equatorial planes.

    :param x,y,z: a point of space
    :param xc,yc,zc: position of the center of the 1st-quadrant loop, (yc>0 and zc>0)
    :param r: loop radius (the same for all four)
    :param theta,phi: specify the orientation of the normal of the 1st loop
    :return:
    """
    ct=np.cos(theta)
    st=np.sin(theta)
    cp=np.cos(phi)
    sp=np.sin(phi)

    # 1st quadrant:
    xs= (x-xc)*cp+(y-yc)*sp
    yss=(y-yc)*cp-(x-xc)*sp
    zs= z-zc
    xss=xs*ct-zs*st
    zss=zs*ct+xs*st

    bxss,bys,bzss = circle(xss,yss,zss,r)
    bxs= bxss*ct+bzss*st
    bz1= bzss*ct-bxss*st
    bx1= bxs*cp-bys*sp
    by1= bxs*sp+bys*cp

    # 2nd quadrant:
    xs= (x-xc)*cp-(y+yc)*sp
    yss=(y+yc)*cp+(x-xc)*sp
    zs= z-zc
    xss=xs*ct-zs*st
    zss=zs*ct+xs*st

    bxss,bys,bzss = circle(xss,yss,zss,r)
    bxs= bxss*ct+bzss*st
    bz2= bzss*ct-bxss*st
    bx2= bxs*cp+bys*sp
    by2=-bxs*sp+bys*cp

    # 3rd quadrant:
    xs= -(x-xc)*cp+(y+yc)*sp
    yss=-(y+yc)*cp-(x-xc)*sp
    zs= z+zc
    xss=xs*ct-zs*st
    zss=zs*ct+xs*st

    bxss,bys,bzss = circle(xss,yss,zss,r)
    bxs= bxss*ct+bzss*st
    bz3= bzss*ct-bxss*st
    bx3=-bxs*cp-bys*sp
    by3= bxs*sp-bys*cp

    # 4th quadrant:
    xs= -(x-xc)*cp-(y-yc)*sp
    yss=-(y-yc)*cp+(x-xc)*sp
    zs= z+zc
    xss=xs*ct-zs*st
    zss=zs*ct+xs*st

    bxss,bys,bzss = circle(xss,yss,zss,r)
    bxs= bxss*ct+bzss*st
    bz4= bzss*ct-bxss*st
    bx4=-bxs*cp+bys*sp
    by4=-bxs*sp-bys*cp

    bx=bx1+bx2+bx3+bx4
    by=by1+by2+by3+by4
    bz=bz1+bz2+bz3+bz4

    return bx,by,bz

def r2sheet(x,y,z):
    """

    :param x,y,z:
    :return:
    """

    pnonx1,pnonx2,pnonx3,pnonx4,pnonx5,pnonx6,pnonx7,pnonx8, \
    pnony1,pnony2,pnony3,pnony4,pnony5,pnony6,pnony7,pnony8, \
    pnonz1,pnonz2,pnonz3,pnonz4,pnonz5,pnonz6,pnonz7,pnonz8 = [
        -19.0969,-9.28828,-0.129687,5.58594,22.5055,0.0483750,0.0396953,0.0579023,
        -13.6750,-6.70625,2.31875,11.4062,20.4562,0.0478750,0.0363750,0.0567500,
        -16.7125,-16.4625,-0.1625,5.1,23.7125,0.0355625,0.0318750,0.0538750]
    a1, a2, a3, a4, a5, a6, a7, a8, a9, a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20, \
    a21,a22,a23,a24,a25,a26,a27,a28,a29,a30,a31,a32,a33,a34,a35,a36,a37,a38,a39,a40, \
    a41,a42,a43,a44,a45,a46,a47,a48,a49,a50,a51,a52,a53,a54,a55,a56,a57,a58,a59,a60, \
    a61,a62,a63,a64,a65,a66,a67,a68,a69,a70,a71,a72,a73,a74,a75,a76,a77,a78,a79,a80 = [
        8.07190,-7.39582,-7.62341,0.684671,-13.5672,11.6681,13.1154,-0.890217,7.78726,-5.38346,
        -8.08738,0.609385,-2.70410, 3.53741,3.15549,-1.11069,-8.47555,0.278122,2.73514,4.55625,
        13.1134,1.15848,-3.52648,-8.24698,-6.85710,-2.81369,2.03795,4.64383,2.49309,-1.22041,
        -1.67432,-0.422526,-5.39796,7.10326,5.53730,-13.1918,4.67853,-7.60329,-2.53066,7.76338,
        5.60165,5.34816,-4.56441,7.05976,-2.62723,-0.529078,1.42019,-2.93919,55.6338,-1.55181,
        39.8311,-80.6561,-46.9655,32.8925,-6.32296,19.7841,124.731,10.4347,-30.7581,102.680,
        -47.4037,-3.31278,9.37141,-50.0268,-533.319,110.426,1000.20,-1051.40,1619.48,589.855,
        -1462.73,1087.10,-1994.73,-1654.12,1263.33,-260.210,1424.84,1255.71,-956.733,219.946]
    b1, b2, b3, b4, b5, b6, b7, b8, b9, b10,b11,b12,b13,b14,b15,b16,b17,b18,b19,b20, \
    b21,b22,b23,b24,b25,b26,b27,b28,b29,b30,b31,b32,b33,b34,b35,b36,b37,b38,b39,b40, \
    b41,b42,b43,b44,b45,b46,b47,b48,b49,b50,b51,b52,b53,b54,b55,b56,b57,b58,b59,b60, \
    b61,b62,b63,b64,b65,b66,b67,b68,b69,b70,b71,b72,b73,b74,b75,b76,b77,b78,b79,b80 = [
        -9.08427,10.6777,10.3288,-0.969987,6.45257,-8.42508,-7.97464,1.41996,-1.92490,3.93575,
        2.83283,-1.48621,0.244033,-0.757941,-0.386557,0.344566,9.56674,-2.5365,-3.32916,-5.86712,
        -6.19625,1.83879,2.52772,4.34417,1.87268,-2.13213,-1.69134,-.176379,-.261359,.566419,
        0.3138,-0.134699,-3.83086,-8.4154,4.77005,-9.31479,37.5715,19.3992,-17.9582,36.4604,
        -14.9993,-3.1442,6.17409,-15.5519,2.28621,-0.891549e-2,-.462912,2.47314,41.7555,208.614,
        -45.7861,-77.8687,239.357,-67.9226,66.8743,238.534,-112.136,16.2069,-40.4706,-134.328,
        21.56,-0.201725,2.21,32.5855,-108.217,-1005.98,585.753,323.668,-817.056,235.750,
        -560.965,-576.892,684.193,85.0275,168.394,477.776,-289.253,-123.216,75.6501,-178.605]
    c1, c2, c3, c4, c5, c6, c7, c8, c9, c10,c11,c12,c13,c14,c15,c16,c17,c18,c19,c20, \
    c21,c22,c23,c24,c25,c26,c27,c28,c29,c30,c31,c32,c33,c34,c35,c36,c37,c38,c39,c40, \
    c41,c42,c43,c44,c45,c46,c47,c48,c49,c50,c51,c52,c53,c54,c55,c56,c57,c58,c59,c60, \
    c61,c62,c63,c64,c65,c66,c67,c68,c69,c70,c71,c72,c73,c74,c75,c76,c77,c78,c79,c80 = [
        1167.61,-917.782,-1253.2,-274.128,-1538.75,1257.62,1745.07,113.479,393.326,-426.858,
        -641.1,190.833,-29.9435,-1.04881,117.125,-25.7663,-1168.16,910.247,1239.31,289.515,
        1540.56,-1248.29,-1727.61,-131.785,-394.577,426.163,637.422,-187.965,30.0348,0.221898,
        -116.68,26.0291,12.6804,4.84091,1.18166,-2.75946,-17.9822,-6.80357,-1.47134,3.02266,
        4.79648,0.665255,-0.256229,-0.857282e-1,-0.588997,0.634812e-1,0.164303,-0.15285,22.2524,-22.4376,
        -3.85595,6.07625,-105.959,-41.6698,0.378615,1.55958,44.3981,18.8521,3.19466,5.89142,
        -8.63227,-2.36418,-1.027,-2.31515,1035.38,2040.66,-131.881,-744.533,-3274.93,-4845.61,
        482.438,1567.43,1354.02,2040.47,-151.653,-845.012,-111.723,-265.343,-26.1171,216.632]

    xks=xksi(x,y,z) # variation across the current sheet
    t1x=xks/np.sqrt(xks**2+pnonx6**2)
    t2x=pnonx7**3/np.sqrt(xks**2+pnonx7**2)**3
    t3x=xks/np.sqrt(xks**2+pnonx8**2)**5 *3.493856*pnonx8**4

    t1y=xks/np.sqrt(xks**2+pnony6**2)
    t2y=pnony7**3/np.sqrt(xks**2+pnony7**2)**3
    t3y=xks/np.sqrt(xks**2+pnony8**2)**5 *3.493856*pnony8**4

    t1z=xks/np.sqrt(xks**2+pnonz6**2)
    t2z=pnonz7**3/np.sqrt(xks**2+pnonz7**2)**3
    t3z=xks/np.sqrt(xks**2+pnonz8**2)**5 *3.493856*pnonz8**4

    rho2=x*x+y*y
    r=np.sqrt(rho2+z*z)
    rho=np.sqrt(rho2)

    c1p=x/rho
    s1p=y/rho
    s2p=2*s1p*c1p
    c2p=c1p*c1p-s1p*s1p
    s3p=s2p*c1p+c2p*s1p
    c3p=c2p*c1p-s2p*s1p
    s4p=s3p*c1p+c3p*s1p
    ct=z/r
    st=rho/r

    # now compute the gsm field components:
    s1=fexp(ct,pnonx1)
    s2=fexp(ct,pnonx2)
    s3=fexp(ct,pnonx3)
    s4=fexp(ct,pnonx4)
    s5=fexp(ct,pnonx5)
    bx = s1*((a1 +a2 *t1x+a3 *t2x+a4 *t3x)+c1p*(a5 +a6 *t1x+a7 *t2x+a8 *t3x)+c2p*(a9 +a10*t1x+a11*t2x+a12*t3x)+c3p*(a13+a14*t1x+a15*t2x+a16*t3x)) \
        +s2*((a17+a18*t1x+a19*t2x+a20*t3x)+c1p*(a21+a22*t1x+a23*t2x+a24*t3x)+c2p*(a25+a26*t1x+a27*t2x+a28*t3x)+c3p*(a29+a30*t1x+a31*t2x+a32*t3x)) \
        +s3*((a33+a34*t1x+a35*t2x+a36*t3x)+c1p*(a37+a38*t1x+a39*t2x+a40*t3x)+c2p*(a41+a42*t1x+a43*t2x+a44*t3x)+c3p*(a45+a46*t1x+a47*t2x+a48*t3x)) \
        +s4*((a49+a50*t1x+a51*t2x+a52*t3x)+c1p*(a53+a54*t1x+a55*t2x+a56*t3x)+c2p*(a57+a58*t1x+a59*t2x+a60*t3x)+c3p*(a61+a62*t1x+a63*t2x+a64*t3x)) \
        +s5*((a65+a66*t1x+a67*t2x+a68*t3x)+c1p*(a69+a70*t1x+a71*t2x+a72*t3x)+c2p*(a73+a74*t1x+a75*t2x+a76*t3x)+c3p*(a77+a78*t1x+a79*t2x+a80*t3x))

    s1=fexp(ct,pnony1)
    s2=fexp(ct,pnony2)
    s3=fexp(ct,pnony3)
    s4=fexp(ct,pnony4)
    s5=fexp(ct,pnony5)
    by = s1*(s1p*(b1 +b2 *t1y+b3 *t2y+b4 *t3y)+s2p*(b5 +b6 *t1y+b7 *t2y+b8 *t3y)+s3p*(b9 +b10*t1y+b11*t2y+b12*t3y)+s4p*(b13+b14*t1y+b15*t2y+b16*t3y)) \
        +s2*(s1p*(b17+b18*t1y+b19*t2y+b20*t3y)+s2p*(b21+b22*t1y+b23*t2y+b24*t3y)+s3p*(b25+b26*t1y+b27*t2y+b28*t3y)+s4p*(b29+b30*t1y+b31*t2y+b32*t3y)) \
        +s3*(s1p*(b33+b34*t1y+b35*t2y+b36*t3y)+s2p*(b37+b38*t1y+b39*t2y+b40*t3y)+s3p*(b41+b42*t1y+b43*t2y+b44*t3y)+s4p*(b45+b46*t1y+b47*t2y+b48*t3y)) \
        +s4*(s1p*(b49+b50*t1y+b51*t2y+b52*t3y)+s2p*(b53+b54*t1y+b55*t2y+b56*t3y)+s3p*(b57+b58*t1y+b59*t2y+b60*t3y)+s4p*(b61+b62*t1y+b63*t2y+b64*t3y)) \
        +s5*(s1p*(b65+b66*t1y+b67*t2y+b68*t3y)+s2p*(b69+b70*t1y+b71*t2y+b72*t3y)+s3p*(b73+b74*t1y+b75*t2y+b76*t3y)+s4p*(b77+b78*t1y+b79*t2y+b80*t3y))

    s1=fexp1(ct,pnonz1)
    s2=fexp1(ct,pnonz2)
    s3=fexp1(ct,pnonz3)
    s4=fexp1(ct,pnonz4)
    s5=fexp1(ct,pnonz5)
    bz = s1*((c1 +c2 *t1z+c3 *t2z+c4 *t3z)+c1p*(c5 +c6 *t1z+c7 *t2z+c8 *t3z)+c2p*(c9 +c10*t1z+c11*t2z+c12*t3z)+c3p*(c13+c14*t1z+c15*t2z+c16*t3z)) \
        +s2*((c17+c18*t1z+c19*t2z+c20*t3z)+c1p*(c21+c22*t1z+c23*t2z+c24*t3z)+c2p*(c25+c26*t1z+c27*t2z+c28*t3z)+c3p*(c29+c30*t1z+c31*t2z+c32*t3z)) \
        +s3*((c33+c34*t1z+c35*t2z+c36*t3z)+c1p*(c37+c38*t1z+c39*t2z+c40*t3z)+c2p*(c41+c42*t1z+c43*t2z+c44*t3z)+c3p*(c45+c46*t1z+c47*t2z+c48*t3z)) \
        +s4*((c49+c50*t1z+c51*t2z+c52*t3z)+c1p*(c53+c54*t1z+c55*t2z+c56*t3z)+c2p*(c57+c58*t1z+c59*t2z+c60*t3z)+c3p*(c61+c62*t1z+c63*t2z+c64*t3z)) \
        +s5*((c65+c66*t1z+c67*t2z+c68*t3z)+c1p*(c69+c70*t1z+c71*t2z+c72*t3z)+c2p*(c73+c74*t1z+c75*t2z+c76*t3z)+c3p*(c77+c78*t1z+c79*t2z+c80*t3z))

    return bx,by,bz


def r2inner (x,y,z):

    pl1,pl2,pl3,pl4,pl5,pl6,pl7,pl8 = [154.185,-2.12446,.601735e-01,-.153954e-02,.355077e-04,29.9996,262.886,99.9132]
    pn1,pn2,pn3,pn4,pn5,pn6,pn7,pn8 = [-8.1902,6.5239,5.504,7.7815,.8573,3.0986,.0774,-.038]

    cbx,cby,cbz = bconic(x,y,z,5)

    # now introduce one 4-loop system:
    dbx8,dby8,dbz8 = loops4(x,y,z,pn1,pn2,pn3,pn4,pn5,pn6)
    dbx6,dby6,dbz6 = dipdistr(x-pn7,y,z,0)
    dbx7,dby7,dbz7 = dipdistr(x-pn8,y,z,1)

    # now compute the field components:
    bx=pl1*cbx[0]+pl2*cbx[1]+pl3*cbx[2]+pl4*cbx[3]+pl5*cbx[4]+pl6*dbx6+pl7*dbx7+pl8*dbx8
    by=pl1*cby[0]+pl2*cby[1]+pl3*cby[2]+pl4*cby[3]+pl5*cby[4]+pl6*dby6+pl7*dby7+pl8*dby8
    bz=pl1*cbz[0]+pl2*cbz[1]+pl3*cbz[2]+pl4*cbz[3]+pl5*cbz[4]+pl6*dbz6+pl7*dbz7+pl8*dbz8

    return bx,by,bz


def bconic(x,y,z,nmax):
    """
    # Conical harmonics

    :param x,y,z:
    :param nmax:
    :return:
    """
    cbx = np.empty(nmax)
    cby = np.empty(nmax)
    cbz = np.empty(nmax)

    ro2=x**2+y**2
    ro=np.sqrt(ro2)

    cf=x/ro
    sf=y/ro
    cfm1=1
    sfm1=0

    r2=ro2+z**2
    r=np.sqrt(r2)
    c=z/r
    s=ro/r
    ch=np.sqrt(0.5*(1+c))
    sh=np.sqrt(0.5*(1-c))
    tnhm1=1
    cnhm1=1
    tnh=sh/ch
    cnh=1/tnh

    for m in range(nmax):
        m1 = m+1
        cfm=cfm1*cf-sfm1*sf
        sfm=cfm1*sf+sfm1*cf
        cfm1=cfm
        sfm1=sfm
        tnhm=tnhm1*tnh
        cnhm=cnhm1*cnh
        bt=m1*cfm/(r*s)*(tnhm+cnhm)
        bf=-0.5*m1*sfm/r*(tnhm1/ch**2-cnhm1/sh**2)
        tnhm1=tnhm
        cnhm1=cnhm
        cbx[m]= bt*c*cf-bf*sf
        cby[m]= bt*c*sf+bf*cf
        cbz[m]=-bt*s

    return cbx,cby,cbz


def dipdistr(x,y,z,mode):
    """
    Returns field components from a linear distribution of dipolar sources on the z-axis.
    The parameter mode defines how the dipole strength varies along the z-axis:
        mode=0 is for a step-function (mx=const and >0 for z>0, and mx=-const and <0 for z<0)
        mode=1 is for a linear variation of the dipole moment density
    See NB#3, page 53 for details.

    :param x,y,z: a point of space
    :param mode: the mode
    :return:
    """

    x2=x*x
    rho2=x2+y*y
    r2=rho2+z*z
    r3=r2*np.sqrt(r2)

    if mode == 0:
        bx=z/rho2**2*(r2*(y*y-x2)-rho2*x2)/r3
        by=-x*y*z/rho2**2*(2*r2+rho2)/r3
        bz=x/r3
    else:
        bx=z/rho2**2*(y*y-x2)
        by=-2*x*y*z/rho2**2
        bz=x/rho2

    return bx,by,bz

def intercon(x,y,z):
    """
    Calculates the potential interconnection field inside the magnetosphere, corresponding to
    DELTA_X = 20Re and DELTA_Y = 10Re (NB#3, p.90, 6/6/1996).

    The position (X,Y,Z) and field components BX,BY,BZ are given in the rotated coordinate system,
    in which the Z-axis is always directed along the BzIMF (i.e. rotated by the IMF clock angle Theta)

    It is also assumed that the IMF Bt=1 nT, so that the components should be
       (i) multiplied by the actual Bt, and
       (ii) transformed to standard GSM coords by rotating back around X axis by the angle -Theta.

    :param x,y,z: GSM position
    :return: bx,by,bz. Interconnection field components inside the magnetosphere of a standard size
        (to take into account effects of pressure changes, apply the scaling transformation)
    """

    # The 9 linear parameters are amplitudes of the "cartesian" harmonics
    # The 6 nonlinear parameters are the scales Pi and Ri entering the arguments of exponents, sines, and cosines in the 9 "Cartesian" harmonics (3+3)

    a = np.array([
        -8.411078731,5932254.951,-9073284.93,-11.68794634,6027598.824,
        -9218378.368,-6.508798398,-11824.42793,18015.66212,7.99754043,
        13.9669886,90.24475036,16.75728834,1015.645781,1553.493216])

    p = a[9 :12]
    r = a[12:15]
    rp = 1/p
    rr = 1/r

    l = 0
    bx,by,bz = [0.]*3
    # "perpendicular" kind of symmetry only
    for i in range(3):
        cypi=np.cos(y*rp[i])
        sypi=np.sin(y*rp[i])
        for k in range(3):
            szrk=np.sin(z*rr[k])
            czrk=np.cos(z*rr[k])
            sqpr=np.sqrt(rp[i]**2+rr[k]**2)
            epr= np.exp(x*sqpr)

            hx=-sqpr*epr*cypi*szrk
            hy= rp[i]*epr*sypi*szrk
            hz=-rr[k]*epr*cypi*czrk

            bx=bx+a[l]*hx
            by=by+a[l]*hy
            bz=bz+a[l]*hz
            l += 1

    return bx,by,bz


def dipole(ps, x,y,z):
    """
    same as t89
    """
    sps = np.sin(ps)
    cps = np.cos(ps)

    p = x**2
    u = z**2
    v = 3*z*x
    t = y**2
    q = 30574./np.sqrt(p+t+u)**5
    bx = q*((t+u-2*p)*sps-v*cps)
    by = -3*y*q*(x*sps+z*cps)
    bz = q*((p+t-2*u)*cps-v*sps)

    return bx,by,bz
