# -*- coding: utf-8 -*-
r"""Code for extracting properties of two-body systems.
Phase Shifts
============
We consider here short-range potentials such that for $V(r>R) = 0$.
In this case, the potential may be described in terms of the phase
shift of the scattering states.
"""
from copy import copy
from functools import wraps
import numpy
import numpy as np
import scipy as sp
from numpy import pi, exp, sqrt, sinh, cosh, tanh, tan
from scipy.integrate import ode
from scipy.special import gamma
from mmf.math.integrate.odepack import odeint
import scipy.optimize
import mmf.math.interp
from mmf.math.differentiate import differentiate
# Don't set too large or you get a crash!  Try
# SoonYongPotential.are_(-2.9) a couple of times with these set to 1e-14.
_rel_tol = 1e-12
_abs_tol = 1e-12
[docs]class Potential(object):
    r"""Instances are callable and return compact potentials V(r).
    The class allows one to specify parameters.  The important
    property is that when parameters are modified, the class ensures
    that the radius returned by R = get_R() is such that V(R) <
    abs_tol.
    The parameter M should be the reduced mass of the system.
    """
[docs]    def __init__(self,abs_tol=_abs_tol,rel_tol=_rel_tol,Rmin=None):
        r"""Construct the potential object."""
        self._modified = True
        self.rel_tol = rel_tol
        self.abs_tol = abs_tol
        self.Rmin = Rmin
        self.tabulate_options = mmf.math.interp.Options()
        self.tabulate_options.plot_spline = False
        self.tabulate_options.plot = True
        self.verbose=False
 
    def __call__(self,r):
        r"""Return V(r)."""
        raise Exception("Potential not defined!")
    def __repr__(self):
        return self.__class__.__name__+":"+str(self.get_ar())
[docs]    def dVdV(self,r):
        r"""Return V'(r)/V(r).  Should be overloaded for speed and/or
        accuracy.
        """
        h0 = self.R/100.0
        dVr = differentiate(self,r,h0=h0)
        Vr = self(r)
        return dVr/Vr
 
[docs]    def compute_R(self):
        r"""Return `R >= self.Rmin` such that `abs(self(R)) <
        self.abs_tol`.
        """
        if self.Rmin is not None:
            R = self.Rmin
        else:
            R = 0.1
        while self.abs_tol <= abs(self(R)):
            R *= 2
        return R
 
    def _set_R(self, R):
        self._R = R
    def _get_R(self):
        r"""Return a radius for which self(R) <= abs_tol."""
        if (not '_R' in self.__dict__ or
            self._R < self.Rmin or
            abs(self(self._R)) > self.abs_tol):
            self._R = self.compute_R()
        return self._R
    R = property(_get_R, _set_R)
[docs]    def get_deq(self,f,jac,U0,method='odeint'):
        r"""Return a "deq" object capable of integrating the system
        defined by `U' = f(r,U)` and `diff(U',U) = jac(r,U)` with initial
        condition `U(0.0) = U0`.
        """
        if 'ode' == method:
            deq = ode(f,jac)
            deq.set_integrator('vode',
                               rtol=self.rel_tol,
                               atol=self.abs_tol,
                               nsteps=1000)
            deq.set_initial_value(U0)
        elif 'odeint' == method:
            atol = self.abs_tol
            rtol = self.rel_tol
            class DEQ:
                def __init__(self,f=f,jac=jac):
                    def f0(y,t):
                        return f(t,y)
                    def df0(y,t):
                        return jac(t,y)
                    self.f = f0
                    self.Dfun = df0
                    self.y0 = U0
                def integrate(self,R,atol=atol,rtol=rtol,
                              verbose=self.verbose):
                    infodict = {'istate': -1}
                    y0 = self.y0
                    r0 = 0.0
                    while infodict['istate'] == -1:
                        y,infodict = odeint(self.f,
                                            y0,
                                            [r0,R],
                                            (),
                                            self.Dfun,
                                            rtol=rtol,
                                            atol=atol,
                                            mxstep=500,
                                            full_output=True,
                                            printmessg=verbose)
                        y0 = y[1,:]
                        r0 = infodict['tcur'][0]
                        if verbose and r0 < R:
                            print "Integrating interval " + \
                                
`[r0,R]`
                    return y[1,:]
                def set_initial_value(self,y0):
                    self.y0 = copy(y0)
            deq = DEQ()
        return deq
 
[docs]    def get_deq_ar(self,method='odeint'):
        r"""Return the differential equation (u,du,uu,duu) for finding
        the s-wave scattering length and the s-wave effective range.
        Uses the following system:
        u'(r) = du(r)
        du'(r) = 2*m/hbar**2*V(r)*u(r)
        uu'(r) = duu(r)
        duu'(r) = 2*m/hbar**2*(V(r)*uu(r)-u(r))
        with initial conditions 
        
        u(0) = 0.0
        du(0) = 1.0
        uu(0) = 0.0
        duu(0) = 0.0
        Call (u,du,uu,duu) = deq.integrate(R) to integrate to R.  Note
        that deq maintains state, so subsequent calls must have
        increasing R.
        """
        const = 2.0*self.M/self.hbar/self.hbar
        def f(r,U,V=self,const=const):
            r"""Return vector U' where U = [u,du,uu,duu]:
            u' = du
            du' = const*V*u
            uu' = duu
            duu' = const*(V*uu - u)
            """
            u,du,uu,duu = U
            Vr = V(r)
            dU = numpy.array([du,const*Vr*u,duu,const*Vr*uu-u])
            return dU
        def jac(r,U,V=self,const=const):
            r"""Return jacobian for problem."""
            u,du,uu,duu = U
            Vr = V(r)
            return numpy.array([[0.0,1.0,0.0,0.0],
                                [const*Vr,0.0,0.0,0.0],
                                [0.0,0.0,0.0,1.0],
                                [-1.0,0.0,const*Vr,0.0]])
        U0 = numpy.array([0.0,1.0,0.0,0.0])
        return self.get_deq(f,jac,U0,method=method)
 
[docs]    def get_deq_E(self,E,method='odeint'):
        r"""Return solution to energy dependent differential equation
        for (u,du) for finding the s-wave phase shift.
        Uses the following system:
        du'(r) = 2*m/hbar**2*(V(r)-E)*u(r)
        u'(r) = du(r)
        with initial conditions 
        
        u(0) = 0.0
        du(0) = 1.0
        Call (u,du) = deq.integrate(R) to integrate to R.  Note
        that deq maintains state, so subsequent calls must have
        increasing R.
        
        This is to be used by calling the method integrate(R).
        """
        const = 2.0*self.M/self.hbar/self.hbar
        def f(r,U,E=E,V=self,const=const):
            r"""Return vector U' where U = [u,u']:
            
            du = u'
            du' = const*(V(r) - E)*u
            """
            dU = numpy.array([U[1],const*(V(r) - E)*U[0]])
            return dU
        
        def  jac(r,U,E=E,V=self,const=const):
            r"""Return jacobian for problem."""
            return numpy.array([[0.0,1.0],[const*(V(r) - E),0.0]])
        U0 = numpy.array([0.0,1.0])
        return self.get_deq(f,jac,U0,method=method)
 
[docs]    def get_deq_gE(self, E, method='odeint'):
        r"""Return solution to energy dependent differential equation
        for `(Ap, Am)` for finding the s-wave phase shift which is
        
        .. math::
           g(E) = \kappa \frac{A_+ - A_-}{A_+ + A_-}
        where $E = -\kappa^2$ and the s-wave radial wavefunction has the
        asymptotic form
        
        .. math::
           u(r>R) = A_+ e^{\kappa r} + A_-e^{-\kappa r}.
        
        We solve the radial Schrödinger equation
        .. math::
           u''(r) = [\tilde{V}(r) - \tilde{E}]u(r)
        where $\tilde{V} = 2mV(r)/\hbar^2$ and $\tilde{E} = 2mE(r)/\hbar^2$
        with initial conditions $u(0)=0$, and $u'(0)=1$ using the following
        transformation
       
        .. math::
           A_{\pm}(r) = \frac{1}{2}\left(u(r) \pm \frac{u'(r)}{\kappa}\right)
                        e^{\mp\kappa r}
  
        These should be constant at the asymptotic values once $r>R$ is larger
        than the support of the potential.  These functions satisfy the
        following set of coupled first-order equations:
        .. math::
           \diff{}{r}
           \begin{pmatrix}
             A_+(r)\\
             A_-(r)
           \end{pmatrix} = \frac{\tilde{V}(r)}{2\kappa}\begin{pmatrix}
             1 & e^{-2\kappa r}\\
             -e^{2\kappa r} & -1
           \end{pmatrix}
           \begin{pmatrix}
             A_+(r)\\
             A_-(r)
           \end{pmatrix}
        with initial conditions $A_{\pm}(0) = \pm\frac{1}{2\kappa}$.
        """
        const = 2.0*self.M/self.hbar/self.hbar
        kappa2 = -const*E
        kappa = sqrt(kappa2)
        def f(r, U, E=E, V=self,
              kappa2=kappa2, kappa=kappa, const=const):
            r"""Return vector `U'` where `U = [Ap, Am]`:
            Ap' =  ((2*kappa**2 + VV)*fp + VV*fm)/2/kappa
            Am' = -((2*kappa**2 + VV)*fm + VV*fp)/2/kappa
            fp(0) = 1/2/kappa
            fm(0) = -1/2/kappa
            """
            Ap, Am = U
            Vr = V(r)
            VV = const*Vr
            k2V = 2*kappa2 + VV 
            exp2kr = np.exp(2*kappa*r)
            dU = VV*numpy.array([Ap + Am/exp2kr,
                                 -Ap*exp2kr - Am])/2/kappa
            return dU
        def jac(r,U,E=E,V=self,
                kappa2=kappa2,kappa=kappa,const=const):
            r"""Return jacobian for problem."""
            Ap, Am = U
            Vr = V(r)
            VV = const*Vr
            k2V = 2*kappa2 + VV
            exp2kr = np.exp(2*kappa*r)
            return VV*numpy.array([[1, 1.0/exp2kr],
                                   [-exp2kr, -1.0]])/2/kappa
        U0 = numpy.array([0.5/kappa, -0.5/kappa])
        return self.get_deq(f,jac,U0,method=method)
 
[docs]    def get_deq_gE_old(self, E, method='odeint',
                       version=1):
        r"""Old version in terms of fp and fm.
        
        `version=0` ::
           fp' = dfp'
           dfp' = (kappa**2 + VV)*fp - V'/V*(kappa*fp - dfp)
           fm' = dfm
           dfm' = (kappa**2 + VV)*fm + V'/V*(kappa*fm + dfm)
           fp(0) = 1/2/kappa
           dfp(0) = 1/2
           fm(0) = -1/2/kappa
           dfm(0) = 1/2
        `version=1` ::
           fp' =  ((2*kappa**2 + VV)*fp + VV*fm)/2/kappa
           fm' = -((2*kappa**2 + VV)*fm + VV*fp)/2/kappa
           fp(0) = 1/2/kappa
           fm(0) = -1/2/kappa
        Call (fp,dfp,fm,dfm) = deq.integrate(R) to integrate to R.  Note
        that deq maintains state, so subsequent calls must have
        increasing R.
        
        This is to be used by calling the method integrate(R).
        Examples
        --------
        >>> V = DoubleGaussian(f_v=0.0)
        >>> V._gE_neg(E=-1.0, version=0)
        -1.874095805...
        >>> V._gE_neg(E=-1.0, version=1)
        -1.874095805...
        >>> V._gE_neg(E=-1.0, version=2)
        -1.874095805...
        """
        const = 2.0*self.M/self.hbar/self.hbar
        kappa2 = -const*E
        kappa = sqrt(kappa2)
        if 0 == version:
            def f(r,U,E=E,V=self,dVdV=self.dVdV,
                  kappa2=kappa2,kappa=kappa,const=const):
                r"""Return vector U' where U = [fp,dfp,fm,dfm]:
                fp' = dfp
                dfp' = (kappa**2 + const*V)*fp - V'/V*(kappa*fp - dfp)
                fm' = dfm
                dfm' = (kappa**2 + const*V)*fm + V'/V*(kappa*fm + dfm)
                """
                fp,dfp,fm,dfm = U
                Vr = V(r)
                dVdVr = dVdV(r)
                k2V = kappa2+const*Vr
                dU = numpy.array([dfp,
                                  k2V*fp - dVdVr*(kappa*fp - dfp),
                                  dfm,
                                  k2V*fm + dVdVr*(kappa*fm + dfm)])
                return dU
            def  jac(r,U,E=E,V=self,dVdV=self.dVdV,
                     kappa2=kappa2,kappa=kappa,const=const):
                r"""Return jacobian for problem."""
                fp,dfp,fm,dfm = U
                Vr = V(r)
                dVdVr = dVdV(r)
                k2V = kappa2+const*Vr
                return numpy.array([[0.0,1.0,0.0,0.0],
                                    [k2V-dVdVr*kappa,dVdVr,0.0,0.0],
                                    [0.0,0.0,0.0,1.0],
                                    [0.0,0.0,k2V+dVdVr*kappa,dVdVr]])
            U0 = numpy.array([0.5/kappa,0.5,-0.5/kappa,0.5])
        else:
            def f(r, U, E=E, V=self,
                  kappa2=kappa2, kappa=kappa,const=const):
                r"""Return vector `U'` where `U = [fp, fm]`:
                fp' =  ((2*kappa**2 + VV)*fp + VV*fm)/2/kappa
                fm' = -((2*kappa**2 + VV)*fm + VV*fp)/2/kappa
                fp(0) = 1/2/kappa
                fm(0) = -1/2/kappa
                """
                fp, fm = U
                Vr = V(r)
                VV = const*Vr
                k2V = 2*kappa2 + VV 
                dU = numpy.array([(k2V*fp + VV*fm)/2.0/kappa,
                                  -(k2V*fm + VV*fp)/2.0/kappa])
                return dU
            def  jac(r,U,E=E,V=self,
                     kappa2=kappa2,kappa=kappa,const=const):
                r"""Return jacobian for problem."""
                fp, fm = U
                Vr = V(r)
                VV = const*Vr
                k2V = 2*kappa2 + VV
                return numpy.array([[k2V/2.0/kappa, VV/2.0/kappa],
                                    [-VV/2.0/kappa, -k2V/2.0/kappa]])
            U0 = numpy.array([0.5/kappa, -0.5/kappa])
        return self.get_deq(f,jac,U0,method=method)
 
[docs]    def get_ar(self,R=None,method='odeint'):
        r"""Return (a,re) where a is the s-wave scattering length and
        re is the s-wave effective range.  Integrates the wavefunction
        to radius R.  Answer is independent of R if V(r>R) = 0.
        """
        if R is None:
            R = self.R
        deq = self.get_deq_ar(method)
        u,du,uu,duu = deq.integrate(R)
        R2 = R*R
        R3 = R*R2
        #A = u - du*R
        #AA = du*R3/6.0 - R2*u/2.0 - R*duu + uu
        a = R - u/du
        a2 = a*a
        re = 2.0*R\
            
+2.0/3.0*R3/a2\
            
-2.0*R2/a\
            
-2.0*uu/du/a2\
            
+2.0*(R/a - 1.0)*duu/du/a
        return (a,re)
 
    def _gE(self,E,R=None,method='odeint'):
        r"""Return k*cot(delta) as a function of energy E for the specified
        scattering problem by matching at a specified radius R."""
        if R is None:
            R = self.R
        deq = self.get_deq_E(E)
        u,du = deq.integrate(R)
        
        kappa = sqrt(2.0*self.M*abs(E))/self.hbar
        if 0.0 == kappa:
            return du/(u-du*R)
        elif E >= 0:
            k = kappa
            x = du/u
            t = tan(k*R)
            return (x + k*t)/(1.0-x*t/k)
        else:
            return (du/u - kappa*tanh(kappa*R))/(1.0-du/u*tanh(kappa*R)/kappa)
    def _gE_neg(self, E, R=None, method='odeint', version=2):
        r"""Return a k*cot(delta) as a function of energy E for the specified
        scattering problem by matching at a specified radius R.  Use this with
        `E < 0`.
        """
        if R is None:
            R = self.R
        assert E < 0.0
        kappa = sqrt(2.0*self.M*abs(E))/self.hbar
        if version < 2:
            deq = self.get_deq_gE_old(E, version=version)
            if 0 == version:
                fp, dfp, fm, dfm = deq.integrate(R)
            else:
                fp, fm = deq.integrate(R)
            Ap = fp/exp(kappa*R)
            Am = fm/exp(-kappa*R)
        else:
            deq = self.get_deq_gE(E)
            Ap, Am = deq.integrate(R)
        return kappa*(Ap-Am)/(Ap+Am)
[docs]    def gE(self,E,R=None,method='odeint'):
        r"""Return k*cot(delta) as a function of energy E for the specified
        scattering problem by matching at a specified radius R."""
        if R is None:
            R = self.R
        if E < 0.0:
            return self._gE_neg(E,R=R,method=method)
        else:
            return self._gE(E,R=R,method=method)
 
[docs]    def plot(self,Rmax=None):
        r"""Plot potential."""
        if Rmax is None:
            Rmax = self.R
        Vr = mmf.math.interp.tabulate(self,[0,Rmax],self.tabulate_options)
        from matplotlib import pyplot as plt
        plt.plot(Vr.x,numpy.array(Vr.y)[0,:])
 
[docs]    def plot_gE(self,Erange,Rmax=None):
        r"""Plot potential."""
        if Rmax is None:
            Rmax = self.R
        
        @np.vectorize
        def f(E):
            return self.gE(E)
        gE_interp = mmf.math.interp.tabulate(f,Erange,self.tabulate_options)
        a,re = self.get_ar()
        
        from matplotlib import pyplot as plt        
        E = gE_interp.x
        gE = np.array(gE_interp.y)[0,:]
        gE = np.where(gE > 1000,1000,gE)
        k2 = E/self.hbar/self.hbar*2.0*self.M
        y = -1./a + re*k2/2.0
        y_bound = np.where(k2<0,-sqrt(abs(k2)),0.0)
        plt.plot(E,gE,'-+')
        plt.plot(E,y,':r',E,y_bound,':y')
 
[docs]    def plot_u(self,E,Rmax=None):
        r"""Plot radial wavefunction."""
        if Rmax is None:
            Rmax = self.R
        
        deq = self.get_deq_E(E)
        @np.vectorize
        def f(r,E=E,deq=deq,U0=numpy.array([0.0,1.0])):
            deq.set_initial_value(U0)
            u,du = deq.integrate(r)
            return u
        u_interp = mmf.math.interp.tabulate(f,[0,Rmax],self.tabulate_options)
        
        import pylab
        r = u_interp.x
        u = numpy.array(u_interp.y)[0,:]
        pylab.plot(r,u,'-+')            
  
[docs]class PotentialV0R0(Potential):
    r"""Mixing for two-parameter potentials with a parameter $v_0$ governing the
    overall strength and a parameter $r_0$ governing the effective range.  The
    potential can have additional parameters, but these two will be adjusted to
    set the scattering-length and range."""
[docs]    def set_ainv_re(self, ainv, re):
        r"""Uses a non-linear root-finder to set the parameters `v0` and `r0`
        so that the scattering length is  `1.0/ainv` and the effective range is
        `re`.
        
        >>> V = PoschlTeller()
        >>> V.set_ainv_re(ainv=0.0,re=1.0)
        >>> np.allclose(V.r0, 1.0)
        True
        >>> np.allclose(V.v0, -1.0)
        True
        """
        def f(x,self=self,ainv=ainv,re=re):
            r"""Function to zero: [ainv - 1/a(V),re-re(V)].
            x = [v0, r0]"""
            self.v0 = x[0]
            self.r0 = x[1]
            (a_, re_) = self.get_ar()
            return [ainv - 1.0/a_,re-re_]
        (x,infodict,ier,mesg) = \
            
scipy.optimize.fsolve(func=f,
                                  x0=[self.v0, self.r0],
                                  xtol=1.49012e-08,
                                  maxfev=10000,
                                  full_output=1)
        if (ier != 1):
            print "Error!!!"
            print mesg
            print infodict
            x = [-1.0,0.0]
        self.v0 = x[0]
        self.r0 = x[1]
  
[docs]class PoschlTeller(PotentialV0R0):
    r"""Extended Poschl-Teller (Rosen-Morse) potential used by Soon
    Yong, Alex, Joe, and Stefano:
    .. math::
       V(r) &= v_0 \frac{\hbar^2}{M}\frac{\mu_0^2}{\cosh^2(\mu_0 r)}\\
       V(r) &= v_0 \frac{\hbar^2}{M}\frac{4}{r_0^2}\sech^2\frac{2r}{r_0}\\
    Units are set by $k_F = (3\pi^2N_+)^{1/3}/L$ for a box of length $L$ and
    $\mu = A k_F$.  As a test, at unitarity, $v_0 = 1$ and $r_e = 2/\mu_0$ for
    two-body scattering where $m$ here is the individual mass of the particles
    (`M=0.5` by default).
    >>> V = PoschlTeller(v0=-1.0)
    >>> (a,re) = V.get_ar()
    >>> mu = 2.0/V.r0
    >>> abs(a) > 6e8
    True
    >>> abs(re - 2.0/mu) < 1e-9
    True
    It is resonant $(a=\infty)$ for $v_0 = -n(2n - 1)$ for $n \in [1,2,3,...]$.
    >>> def infty(n):
    ...    return abs(PoschlTeller.a_(-n*(2.0*n-1.0)))
    >>> infty(1) > 1e8
    True
    >>> infty(2) > 1e8
    True
    
    At these resonances one has $r_e/r_0 = H(2n-1)$ where $H(n)$ is the
    Harmonic Number $H(n) = \sum_{j=1}^{n}j^{-1}$.
    >>> def H(n):
    ...     return (1./numpy.arange(1,n+1)).sum()
    >>> def zero(n):
    ...    return abs(PoschlTeller.re_(-n*(2.0*n-1.0))-H(2*n-1))
    >>> zero(1) < 1e-10
    True
    >>> zero(2) < 1e-10
    True
    The scattering length is zero at the following points where the
    effective range diverges.  I am not sure yet what these correspond
    to::
      v0 = [0.0,-4.4438633257625231,-12.913326147200248,
      -25.421577034814653,-41.955714059176017,
      -62.508749555456298,...]
    
      # kF_SY = (3.*pi)**(2./3.)
      # EFG_SY = kF_SY**2/2.0*3.0/5.0
      # EFG = EFG_SY/4.0/pi**2
      # re = 2.0*L/(6.2527*(3.0*pi)**(2./3.))
      #
      # V = -2*hbar^2*mu^2*v0/m/cosh(mu*r)**2
      # v0 = 1.0 => ainv = 0, re = 2.0/mu
      # v0 = 1.30119 => kfa_SY = 1.0
    """
[docs]    def __init__(self,
                 v0=-1.0, r0=2.0/6.2527,
                 hbar=1.0, M=0.5,
                 abs_tol=_abs_tol,
                 rel_tol=_rel_tol,
                 Rmin=0.1):
        Potential.__init__(self,
                           abs_tol=abs_tol,
                           rel_tol=rel_tol,
                           Rmin=Rmin)
        self.v0 = v0
        self.r0 = r0
        self.hbar = hbar
        self.M = M
 
    def __repr__(self):
        return self.__class__.__name__+":"+\
            
str(([self.v0,self.r0],self.get_ar()))
    def __call__(self,r):        
        #kF = (3*pi*pi*N)**(1./3.)/L
        #mu0 = self.A0*kF
        mu0 = 2.0/self.r0
        hbar = self.hbar
        M = self.M
        m = self.M*2.0
        tmp = hbar*hbar/self.M
        v0 = self.v0*mu0*mu0*tmp
        
        return v0/(cosh(mu0*r))**2
    @property
[docs]    def mu0(self):
        r"""Stefano, Alex, etc. use this parameter instead, so we provide an
        alias."""
        return 2.0/self.r0
 
[docs]    def dVdV(self,r):
        r"""Return $V'(r)/V(r)$
        
        .. code-block:: maple
           V:=v0/cosh(mu0*r)^2+V1/cosh(mu1*r)^2;
           with(codegen);
           C(simplify(diff(V,r)/V),optimized);
        """
        mu0 = 2.0/self.r0
        hbar = self.hbar
        M = self.M
        m = self.M*2.0
        tmp = hbar*hbar/M
        v0 = self.v0*mu0*mu0*tmp
        
        return -2.0*mu0*tanh(mu0*r)
 
[docs]    def set_ainv(self,ainv=0.0):
        r"""Set the coefficient `v0` so that the inverse scattering length is
        `ainv`.
        
        >>> V = PoschlTeller()
        >>> V.v0 = -0.5
        >>> V.set_ainv(0.0)
        >>> np.allclose(V.v0, -1.0)
        True
        """
        def f(v0,self=self,ainv=ainv):
            r"""Function to zero: ainv - 1/a(V)."""
            self.v0 = v0
            (a,re) = self.get_ar()
            return ainv - 1.0/a
        (v0_,infodict,ier,mesg) = \
            
scipy.optimize.fsolve(func=f,
                                  x0 = self.v0,
                                  xtol=1.49012e-08,
                                  maxfev=10000,
                                  full_output=1)
        if (ier != 1):
            print "Error!!!"
            print mesg
            print infodict
            v0_ = -1.0
        self.v0 = v0_
 
[docs]    def set_ainv_re(self,ainv=0.0,re=0.07):
        r"""Set the coefficients `v0` and `r0` so that the inverse scattering
        length is `ainv` and the effective range is `re`.
        
        >>> V = PoschlTeller(r0=1.0)
        >>> re = 1.0
        >>> V.set_ainv_re(ainv=0.0,re=re)
        >>> np.allclose(V.r0, re)
        True
        >>> np.allclose(V.v0, -1.0)
        True
        """
        def f(x,self=self,ainv=ainv,re=re):
            r"""Function to zero: [ainv - 1/a(V),re-re(V)].
            x = [v0,r0]"""
            self.v0 = x[0]
            self.r0 = x[1]
            (a_,re_) = self.get_ar()
            return [ainv - 1.0/a_,re-re_]
        (x,infodict,ier,mesg) = \
            
scipy.optimize.fsolve(func=f,
                                  x0=[self.v0,self.r0],
                                  xtol=1.49012e-08,
                                  maxfev=10000,
                                  full_output=1)
        if (ier != 1):
            print "Error!!!"
            print mesg
            print infodict
            x = [-1.0,0.0]
        self.v0 = x[0]
        self.r0 = x[1]
 
    @staticmethod
    @numpy.vectorize
[docs]    def are_(v0,Vtmp=[None]):
        r"""Return the dimensionless function `a(v0,r0)/r0` which is
        characteristic of this potential.
        >>> abs(PoschlTeller.a_(-1.0)) > 1e6
        True
        >>> 
        """
        if Vtmp[0] is None:
            Vtmp[0] = PoschlTeller(r0=1.0)
        Vtmp[0].v0 = v0
        return Vtmp[0].get_ar()
 
    @staticmethod
    @numpy.vectorize
[docs]    def a_(v0):
        r"""Return the dimensionless function `a(v0,r0)/r0` which is
        characteristic of this potential.
        """
        return PoschlTeller.are_(v0)[0]
 
    @staticmethod
    @numpy.vectorize
[docs]    def re_(v0):
        r"""Return the dimensionless function `re(v0,r0)/r0` which is
        characteristic of this potential.
        """
        return PoschlTeller.are_(v0)[1]
 
[docs]    def k_cot_delta_analytic(self, E, l=None):
        r"""This potential has some analytic properties (see Alex's thesis and
        references therein):
        
        .. math::
           k\cot\delta_k &= -k\frac{\Im{Z}}{\Re{Z}},\\
           Z &= \frac{2^{-i\tilde{k}}\Gamma(i\tilde{k})}
                     {\Gamma\left(\frac{1+\lambda + i\tilde{k}}{2}\right)
                      \Gamma\left(\frac{2-\lambda + i\tilde{k}}{2}\right)},\\
           \tilde{k} &= k/\mu, \qquad
           l = \frac{1 + \sqrt{1 - 8v_0}}{2}}
        Examples
        --------
        >>> V = PoschlTeller(v0=-1.0,r0=2.0/6.2527)
        >>> V.gE(E=1.0)
        0.1599309...
        >>> V.k_cot_delta_analytic(E=1.0)
        0.1599309...
        """
        k2 = abs(E*2*self.M)
        if l is None:
            l = (1 + np.sqrt(1 - 4*2*self.v0))/2
        mu = 2/self.r0
        k = sp.sqrt(k2)
        k_mu = k/mu
        ik_mu = 1j*k_mu*np.sign(E)
        z = gamma(ik_mu)*2**(-ik_mu)/gamma((1+l+ik_mu)/2)/gamma((2-l+ik_mu)/2)
        cot_delta = -z.imag/z.real
        return k*cot_delta
  
SoonYongPotential = PoschlTeller
        
[docs]class SoonYongPotential2(Potential):
    r"""Extended Potential used by Soon Yong.
    V(r) = hbar**2/M*(v0*mu0**2/(cosh(mu0*r))**2 \
                      + V1*mu1**2/(cosh(mu1*r))**2)
    His units are set by 
    kF = (3*pi*pi*N)**(1./3.) / L for a box of length L
    mu = A*kF
    
    As a test, at unitarity, v0 = 1.0 and re = 2/mu for two-body
    scattering where m here is the individual mass of the particles.
    >>> V = SoonYongPotential2(v0=-1.0,V1=0.0)
    >>> (a,re) = V.get_ar()
    >>> mu = V.A0*V.kF
    >>> abs(a) > 6e8
    True
    >>> abs(re - 2.0/mu) < 1e-9
    True
    # kF_SY = (3.*pi)**(2./3.)
    # EFG_SY = kF_SY**2/2.0*3.0/5.0
    # EFG = EFG_SY/4.0/pi**2
    # re = 2.0*L/(6.2527*(3.0*pi)**(2./3.))
    #
    # V = -2*hbar^2*mu^2*v0/m/cosh(mu*r)**2
    # v0 = 1.0 => ainv = 0, re = 2.0/mu
    # v0 = 1.30119 => kfa_SY = 1.0
    
    """
[docs]    def __init__(self,
                 v0=-1.0,A0=6.2527,
                 V1=0.0,A1=3.0,
                 hbar=1./2./pi,M=0.5,kF=1.0,
                 abs_tol=_abs_tol,
                 rel_tol=_rel_tol,
                 Rmin=0.1):
        Potential.__init__(self,
                           abs_tol=abs_tol,
                           rel_tol=rel_tol,
                           Rmin=Rmin)
        self.v0 = v0
        self.V1 = V1
        self.A0 = A0
        self.A1 = A1
        self.hbar = hbar
        self.M = M
        self.kF = kF
 
    def __call__(self,r):        
        #kF = (3*pi*pi*N)**(1./3.)/L
        #mu0 = self.A0*kF
        #mu1 = self.A1*kF
        mu0 = self.A0*self.kF
        mu1 = self.A1*self.kF
        hbar = self.hbar
        M = self.M
        m = self.M*2.0
        tmp = hbar*hbar/self.M
        v0 = self.v0*mu0*mu0*tmp
        V1 = self.V1*mu1*mu1*tmp
        
        return v0/(cosh(mu0*r))**2\
            
+ V1/(cosh(mu1*r))**2
[docs]    def dVdV(self,r):
        r"""Return V'(r)/V(r).
        V:=v0/cosh(mu0*r)^2+V1/cosh(mu1*r)^2;
        with(codegen);
        C(simplify(diff(V,r)/V),optimized);
        """
        mu0 = self.A0*self.kF
        mu1 = self.A1*self.kF
        hbar = self.hbar
        M = self.M
        m = self.M*2.0
        tmp = hbar*hbar*2.0/m
        v0 = self.v0*mu0*mu0*tmp
        V1 = self.V1*mu1*mu1*tmp
        
        t1 = mu0*r;
        t2 = sinh(t1);
        t4 = mu1*r;
        t5 = cosh(t4);
        t6 = t5*t5;
        t10 = sinh(t4);
        t12 = cosh(t1);
        t13 = t12*t12;
        t28 = -2.0*(v0*t2*mu0*t6*t5+V1*t10*mu1*t13*t12)/t12/t5/(v0*t6+V1*t13);
        return t28
 
[docs]    def set_ainv(self,ainv=0.0):
        r"""Set the coefficient v0 so that the scattering length is
        a.
        
        >>> V = SoonYongPotential2()
        >>> V.v0 = -0.5
        >>> V.set_ainv(0.0)
        >>> np.allclose(V.v0, -1.0)
        True
        """
        def f(v0,self=self,ainv=ainv):
            r"""Function to zero: ainv - 1/a(V)."""
            self.v0 = v0
            (a,re) = self.get_ar()
            return ainv - 1.0/a
        (v0_,infodict,ier,mesg) = \
            
scipy.optimize.fsolve(func=f,
                                  x0 = self.v0,
                                  xtol=1.49012e-08,
                                  maxfev=10000,
                                  full_output=1)
        if (ier != 1):
            print "Error!!!"
            print mesg
            print infodict
            v0_ = -1.0
        self.v0 = v0_
 
[docs]    def set_ainv_re(self,ainv=0.0,re=0.07):
        r"""Set the coefficients v0 and A0 so that the scattering length is
        1.0/ainv and the effective range is re.
        
        >>> V = SoonYongPotential2(kF=1.0)
        >>> V.V1 = 0.0
        >>> V.set_ainv_re(ainv=0.0,re=1.0)
        >>> np.allclose(V.A0, 2./V.kF)
        True
        >>> np.allclose(V.v0, -1.0)
        True
        """
        def f(x,self=self,ainv=ainv,re=re):
            r"""Function to zero: [ainv - 1/a(V),re-re(V)].
            x = [v0,A0]"""
            self.v0 = x[0]
            self.A0 = x[1]
            (a_,re_) = self.get_ar()
            return [ainv - 1.0/a_,re-re_]
        (x,infodict,ier,mesg) = \
            
scipy.optimize.fsolve(func=f,
                                  x0=[self.v0,self.A0],
                                  xtol=1.49012e-08,
                                  maxfev=10000,
                                  full_output=1)
        if (ier != 1):
            print "Error!!!"
            print mesg
            print infodict
            x = [-1.0,0.0]
        self.v0 = x[0]
        self.A0 = x[1]
 
[docs]    def set_ainv_re_VV(self,ainv=0.0,re=0.07):
        r"""Set the coefficients v0 and V1 so that the scattering length is
        1.0/ainv and the effective range is re.
        
        >>> V = SoonYongPotential2()
        >>> V.A0 = 6.2527
        >>> V.A1 = 3.0
        >>> V.v0 = -1.5
        >>> V.V1 = 0.6
        >>> V.set_ainv_re_VV(ainv=0.0,re=0.0)
        >>> np.allclose(V.v0, -1.4789900537007772)
        True
        >>> np.allclose(V.V1, 0.61932931745837538)
        True
        """
        def f(x,self=self,ainv=ainv,re=re):
            r"""Function to zero: ainv - 1/a(V).  x = [v0,V1]"""
            self.v0 = x[0]
            self.V1 = x[1]
            (a_,re_) = self.get_ar()
            return [ainv - 1.0/a_,re-re_]
        (x,infodict,ier,mesg) = \
            
scipy.optimize.fsolve(func=f,
                                  x0=[self.v0,self.V1],
                                  xtol=1.49012e-08,
                                  maxfev=10000,
                                  full_output=1)
        if (ier != 1):
            print "Error!!!"
            print mesg
            print infodict
            x = [-1.0,0.0]
        self.v0 = x[0]
        self.V1 = x[1]
  
[docs]class DoubleGaussian(PotentialV0R0):
    r"""Double Gaussian potential:
    .. math::
       V(r) &= -v_0 \frac{\hbar^2}{M} \mu_0^2 \left(
         \exp[-(\mu_0 r/2)^{2}] + f_v \exp[-(\mu_0 r f_r/2)^2]
         \right)\\
           &= -v_0 \frac{\hbar^2}{M} \mu_0^2 \left(
         \exp[-(\mu_0 r/2)^{2}] + f_v \exp[-(\mu_0 r f_r/2)^2]
         \right)\\
    Note that $\mu_0 = 4/r_0$ here.
    >>> v = DoubleGaussian(mu0=12.0)
    >>> v.get_ar()              # One of Stefano's values to check.
    (-2887043864..., 0.3293...)
    >>> v.set_ainv_re(ainv=0.0, re=1.0)
    >>> v.v0, v.r0
    (0.786097607..., 1.012180386...)
    
    """
[docs]    def __init__(self, v0=0.7860976072874742,
                 r0=4.0/6.0,
                 mu0=None,
                 f_v=-4.0, f_r=2.0,
                 hbar=1.0, M=0.5,
                 abs_tol=_abs_tol,
                 rel_tol=_rel_tol,
                 Rmin=0.1):
        Potential.__init__(self,
                           abs_tol=abs_tol,
                           rel_tol=rel_tol,
                           Rmin=Rmin)
        self.hbar = hbar
        self.M = M
        self.f_v = f_v
        self.f_r = f_r
        self.v0 = v0
        if mu0 is None:
            self.r0 = r0
        else:
            self.mu0 = mu0
 
[docs]    def set_mu0(self, mu0):
        self.r0 = 4./mu0 
[docs]    def get_mu0(self):
        return 4./self.r0 
    mu0 = property(get_mu0, set_mu0)
    def __call__(self,r):        
        #kF = (3*pi*pi*N)**(1./3.)/L
        #mu0 = self.A0*kF
        mu0 = self.mu0
        hbar = self.hbar
        M = self.M
        m = self.M*2.0
        tmp = hbar*hbar/self.M
        v0 = -self.v0*mu0*mu0*tmp
        return v0*(np.exp(-(mu0*r/2)**2) 
                   + self.f_v*np.exp(-(mu0*r*self.f_r/2)**2))
         
[docs]class DoubleExponential(PotentialV0R0):
    r"""Double Exponential potential:
    .. math::
       V(r) = -v_0 \frac{\hbar^2}{M} \mu_0^2 \left(
         \exp[-\mu_0 r] + f_v \exp[-\mu_0 r f_r]
       \right)
    Note that $\mu_0 = 4/r_0$ here.
    >>> v = DoubleExponential(mu0=14.0)
    >>> v.get_ar()              # One of Stefano's values to check.
    (5852295157..., 0.3099...)
    >>> v.set_ainv_re(ainv=0.0, re=1.0)
    >>> v.v0, v.r0
    (1.190915117966..., 0.9218838916...)
    """
[docs]    def __init__(self, v0=1.1909151179666314,
                 r0=4./6.0,
                 mu0=None,
                 f_v=-2.0, f_r=2.0,
                 hbar=1.0, M=0.5,
                 abs_tol=_abs_tol,
                 rel_tol=_rel_tol,
                 Rmin=0.1):
        Potential.__init__(self,
                           abs_tol=abs_tol,
                           rel_tol=rel_tol,
                           Rmin=Rmin)
        self.hbar = hbar
        self.M = M
        self.f_v = f_v
        self.f_r = f_r
        self.v0 = v0
        if mu0 is None:
            self.r0 = r0
        else:
            self.mu0 = mu0
 
[docs]    def set_mu0(self, mu0):
        self.r0 = 4./mu0 
[docs]    def get_mu0(self):
        return 4./self.r0 
    mu0 = property(get_mu0, set_mu0)
    def __repr__(self):
        return self.__class__.__name__+":"+\
            
str(([self.v0,self.r0],self.get_ar()))
    def __call__(self,r):        
        #kF = (3*pi*pi*N)**(1./3.)/L
        #mu0 = self.A0*kF
        mu0 = self.mu0
        hbar = self.hbar
        M = self.M
        m = self.M*2.0
        tmp = hbar*hbar/self.M
        v0 = -self.v0*mu0*mu0*tmp
        return v0*(np.exp(-mu0*r) + self.f_v*np.exp(-mu0*r*self.f_r))
 
[docs]class BargmannPotential(Potential):
    r"""Bargmann Potential where the effective range expansion is exact
    with only a and r_e.
    >> V = BargmannPotential(a=1.0,re=0.01)
    >> (a,re) = V.get_ar()
    >> (a,re)
    (1.0, 0.1)
    """
[docs]    def __init__(self,
                 a=1.0,
                 re=0.4,
                 hbar=1./2./pi,M=0.5,
                 **kwarg):
        Potential.__init__(self,**kwarg)
        self.a = a
        self.re = re
        self.hbar = hbar
        self.M = M
 
    def __call__(self,r):
        re = self.re
        a = self.a
        hbar = self.hbar
        M = self.M
        m = 2.0*M
        tmp = sqrt(1.0 - 2.0*re/a)
        Ap = tmp + 1.0
        Am = tmp - 1.0
        b = Ap/re
        beta = (Ap-Am)/(Ap+Am)
        v0 = -8.0*hbar*hbar/m*b*b
        
        num = beta*exp(-2.0*b*r)
        den = 1 + num
        den = den*den
        
        return v0*num/den
[docs]    def dVdV(self,r):
        r"""Return V'(r)/V(r).
        V:=v0*beta*exp(-2*b*r)/(1+beta*exp(-2*b*r))^2;
        with(codegen);
        C(simplify(diff(V,r)/V),optimized);
        """
        re = self.re
        a = self.a
        hbar = self.hbar
        M = self.M
        m = 2.0*M
        tmp = sqrt(1.0 - 2.0*re/a)
        Ap = tmp + 1.0
        Am = tmp - 1.0
        b = Ap/re
        beta = (Ap-Am)/(Ap+Am)
        t3 = exp(-2.0*b*r);
        t4 = beta*t3;
        t10 = 2.0*b*(-1.0+t4)/(1.0+t4);
        return t10
  
r"""
# First modify 
from numpy import *
from pylab import *
ion()
import two_body as tb;reload(tb);V=tb.SoonYongPotential2()
deq = V.get_ar()
r = linspace(0.01,1.0,100)
U = []
for r0 in r:
  U.append(deq.integrate(r0))
U = numpy.array(U)
plot(r,U[:,2])
P0 = polyfit(r[50:],U[50:,0],1)
P2 = polyfit(r[50:],U[50:,2],3)
A = P0[-1]
a = -A/P0[-2]
AA = P2[-1]
re = (P2[-2] + AA/a)*2.0/A
print P2[-3],-A/2
print P2[-4],A/6.0/a
"""