Module SWtools_base

SWtools_base

This module implements data structures and algorithms that allow a user to conveniently calculate solitary-wave solutions for a generalized nonlinear-Schrödinger type equation.

Functions

def helper_show_d1(xi, U, it, acc, f_name='none')
Expand source code
def helper_show_d1(xi, U, it, acc, f_name='none'):
    """Helper function for making simple plots in dimension d=1.

    Uses Pythons matplotlibe to prepare a two-panel figure that might be
    captioned as follows:

    Nonlinear bound state of the considered nonlinear eigenvalue problem. Left
    panel: Solution U.  Shown are the real part (Re[U]), imaginary part
    (Im[U]), and modulus (|U|) of the solution.  Right panel: Variation of the
    accuracy upon iteration.

    Parameters
    ----------
    xi : array_like
        Discretized transverse coordinate xi.
    U : array_like
        Solution of the nonlinear eigenvalue problem.
    it : array_like
        List of iteration steps at which results where stored.
    acc : array_like
        List of accuracy values corresponding to the iteration steps in `it`.
    f_name : str, optional
        Figure name. If no name is set, the figure is displayed directly.
    """
    # -- FIGURE LAYOUT 
    fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(6,3))

    # -- LEFT SUBPLOT
    ax1.plot(xi, np.real(U), color='C0', dashes=[], label=r"${\rm{Re}}[U]$")
    ax1.plot(xi, np.imag(U), color='C0', dashes=[2,1], label=r"${\rm{Im}}[U]$")
    ax1.plot(xi, np.abs(U), color='gray', dashes=[1,1], label=r"$|U|$")
    ax1.set_xlabel(r"Coordinate $\xi$")
    ax1.set_ylabel(r"Solution $U$")
    legend = ax1.legend()

    # -- RIGHT SUBPLOT
    ax2.plot(it, np.log10(acc), color='C0')
    ax2.set_xlabel(r"Iteration $n$")
    ax2.set_ylabel(r"log-accuracy $\log(\epsilon_n)$")

    # -- SHOW/SAVE FIGURE
    plt.tight_layout()

    if f_name=='none':
        plt.show()
    else:
        plt.savefig(f_name)

Helper function for making simple plots in dimension d=1.

Uses Pythons matplotlibe to prepare a two-panel figure that might be captioned as follows:

Nonlinear bound state of the considered nonlinear eigenvalue problem. Left panel: Solution U. Shown are the real part (Re[U]), imaginary part (Im[U]), and modulus (|U|) of the solution. Right panel: Variation of the accuracy upon iteration.

Parameters

xi : array_like
Discretized transverse coordinate xi.
U : array_like
Solution of the nonlinear eigenvalue problem.
it : array_like
List of iteration steps at which results where stored.
acc : array_like
List of accuracy values corresponding to the iteration steps in it.
f_name : str, optional
Figure name. If no name is set, the figure is displayed directly.

Classes

class IterBase (xi, tol=1e-12, maxiter=10000, nskip=1, verbose=False)
Expand source code
class IterBase(object):
    """Base class for the implemented iteration methods.

    Attributes
    ----------
    xi : array_like
        Discretized coordinate xi.
    tol : float
        Convergence is assumed if relative error falls below this value
        (defaulf: tol=1e-12).
    maxiter : int
        Maximum number of iterations to perform (default: maxiter=10000).
    nskip : float
        Number of iterations to skip in between storing intermediate results
        (default: 1).
    verbose : bool
        Set to True to print details during iteration (default: False).
    iter_list : array_like
        Sequence of iteration steps at which intermediate results where stored
        (separated by nskip steps).
    U_list : array_like
        List of intermediate solutions U.
    acc_list : array_like
        List of intermediate accuracies (see class method _accuracy() below).
    N_list : array_like
        List of intermediate values of the integral N[U].
    H_list : array_like
        List of intermediate values of the integral H[U].
    K_list : array_like
        List of intermediate values of the eigenvalue estimate K=H[U]/N[U].

    """
    def __init__(self, xi, tol=1e-12, maxiter=10000, nskip=1, verbose=False):
        """Initialization of the iteration base class.

        Parameters
        ----------
        xi : array_like
            Discretized coordinate xi.
        tol : float, optional
            Iteration is stopped if the accuracy falls below this tolerance
            threshold (default: 1e-12).
        maxiter : int, optional
            Maximum number of allowed iterations (default: 10000).
        nskip : 1, optional
            Number of iterations to skip in between storing intermediate results (default: 1)
        verbose : bool
            Set to True to print details during iteration (default: False)
        """
        self.xi = xi
        self.tol = tol
        self.maxiter = maxiter
        self.nskip = nskip
        self.verbose = verbose
        self.iter_list = []
        self.U_list = []
        self.acc_list = []
        self.N_list = []
        self.H_list = []
        self.K_list = []

    @property
    def U(self):
        '''float: Final solution U.'''
        return self.U_list[-1]

    @property
    def kap(self):
        '''float: Value of the eigenvalue.'''
        return self.K_list[-1]

    @property
    def N(self):
        '''float: Value of functional 1.'''
        return self.N_list[-1]

    @property
    def H(self):
        '''float: Value of functional 2.'''
        return self.H_list[-1]

    @property
    def acc(self):
        '''float: Terminal accuracy.'''
        return self.acc_list[-1]

    @property
    def num_iter(self):
        '''int: Number of iterations performed.'''
        return self.iter_list[-1]

    def _clean_up(self):
        '''Cleanup of attributes at beginning of each solution run.'''
        self.U_list = []
        self.acc_list = []
        self.iter_list = []
        self.N_list = []
        self.H_list = []
        self.K_list = []

    def _update_results(self, it, U, N, H, kap, err):
        '''Update attributes using the supplied intermediate results.'''
        self.iter_list.append(it)
        self.N_list.append(N)
        self.H_list.append(H)
        self.K_list.append(kap)
        self.U_list.append(np.copy(U))
        self.acc_list.append(err)
        if self.verbose:
            print(self)

    def solve(self, U, dum, **kwargs):
        '''Performs iteration procedure.

        Parameters
        ----------
        U : array_like
            Initial condition.
        dum : float
            Eigenvalue (in case of SRM) or value of the normalization
            constraint (in case of NSOM).
        ortho_set : array_like, keyword argument
            List of previously found orthogonal solutions.
        acc_fun : function, keyword argument
            Custom accuracy function with call signature `acc_fun(xi, Up, Uc)`,
            where the arguments are the coordinate xi (`xi`), the solution at the
            previous iteration step (`Up`), and the solution at the current
            iteration step (`Uc`).

        Returns
        -------
        U : array_like
            Final solutions.
        acc : float
            Terminal accuracy.
        succ : bool
            Boolean flag indicating if teh iteration procedure exited
            successfully.
        msg : str
            Cause of the termination.
        '''
        # -- USEFUL FUNCTIONS AND ABBREVIATIONS
        # ... STRIP SELF KEYWORD
        xi, tol, maxiter, nskip = self.xi, self.tol, self.maxiter, self.nskip
        # ... LOCAL FUNCTIONS
        acc_fun_def = lambda xi, U, Un: np.sqrt(np.sum(np.abs(Un - U)**2)/np.sum(np.abs(U)**2))
        # -- LOGIC TO MAP OUT KEYWORD ARGUMENTS
        ortho_set = kwargs.get("ortho_set", [])
        acc_fun = kwargs.get("acc_fun", acc_fun_def)
        # -- CLEAN UP
        self._clean_up()

        # -- PREPARE/EVALUATE TRIAL SOLUTION
        U = U.astype(np.complex128)
        N = self.functional_N(U)
        H = self.functional_H(U)
        # -- INITIALIZE ITERATION SCHEME
        it = 1
        acc = np.inf
        # -- KEEP INITIAL RESULTS
        self._update_results(0, U, N, H, H/N, acc)
        # -- ITERATION PROCEDURE
        while acc > tol and it < maxiter:
            # ... ORTHOGONALIZE WRT NOT-LIST 
            if ortho_set:
                U = self.orthogonalize(U, ortho_set)
            # ... UPDATE SOLUTION 
            U_new = self.singleUpdate(U, N, H, dum, **kwargs)
            # ... ROOT-MEAN SQUARED DEVIATION W.R.T. PREVIOUS STEP 
            acc = acc_fun(xi, U, U_new)
            # ... ADVANCE UPDATE TO NEXT ITERATION STEP 
            U = U_new
            # ... RE-EVALUATE THE INTEGRALS FOR THE CURRENT SOLUTION 
            N = self.functional_N(U_new)
            H = self.functional_H(U_new)
            # ... KEEP INTERMEDIATE RESULTS
            if it%nskip==0:
                self._update_results(it, U, N, H, H/N, acc)
            it+=1

        # -- KEEP FINAL SOLUTION 
        # ... ORTHOGONALIZE WRT NOT-LIST 
        if ortho_set:
            U = self.orthogonalize(U, ortho_set)
        self._update_results(it, U, N, H, H/N, acc)

        # -- BASIC STATUS MESSAGE
        if it<maxiter:
            msg = f'Iteration terminated successfully (num_iter = {it}).'
        else:
            if it>=maxiter:
                msg = 'Maximum number of iterations reached.'
            if np.isnan(np.any(U)) or np.isnan(acc):
                msg = 'NaN result encountered.'

        if self.verbose:
            print(f"# {msg}")
            print(f"# Functional 1: N = {N}")
            print(f"# Functional 2: H = {H}")
            print(f"# Eigenvalue: K = {H/N}")
            print(f"# Local error: acc = {acc}")

        return self.U, self.acc, it<maxiter, msg

    def orthogonalize(self, U, ortho_set):
        '''Otrhogonalization procedure.

        Implements a straight forward projection construction that turns the
        solution U into a solution U_ortho, which is orthogonal to any of the
        functions in the set `ortho_set`.

        Parameters
        ----------
        U : array_like
            Current solution.
        ortho_set : array_like
            Orthogonal set of previously found solutions.

        Returns
        -------
        U_ortho : array_like
            New solution which is orthogonal to any previously found solution.
        '''
        xi = self.xi
        # -- PROJECTION CONSTRUCTION 
        for V in ortho_set:
            # ... PROJECT OUT COMPONENT V 
            U -= V*np.sum(np.conj(V)*U)/np.sum(np.abs(V)**2)
        return U

    def functional_H(self, U):
        """Functional number 2.

        Parameters
        ----------
        U : array_like
            Current solution.

        Raises
        ------
        NotImplementedError
            Must be overwritten by subclass.
        """
        raise NotImplementedError

    def functional_N(self, U):
        """Functional number 1.

        Parameters
        ----------
        U : array_like
            Current solution.

        Raises
        ------
        NotImplementedError
            Must be overwritten by subclass.
        """
        raise NotImplementedError

    def singleUpdate(self, U, N, H, dum, **kwargs):
        """Single update of the solution.

        Parameters
        ----------
        U : array_like
            Current solution.
        N : float
            Current value of functional 1.
        H : float
            Current value of functional 2.
        dum : float
            Eigenvalue (in case of SRM) or value of the normalization
            constraint (in case of NSOM).

        Raises
        ------
        NotImplementedError
            Must be overwritten by subclass.
        """
        raise NotImplementedError

    def __str__(self):
        """str: String representation of the current solution."""
        myStr = f"Iter {self.num_iter:06d}: H = {self.H:7.6F}, N = {self.N:7.6F}, K = {self.kap:7.6F}, acc = {self.acc:4.3E}"
        return myStr

Base class for the implemented iteration methods.

Attributes

xi : array_like
Discretized coordinate xi.
tol : float
Convergence is assumed if relative error falls below this value (defaulf: tol=1e-12).
maxiter : int
Maximum number of iterations to perform (default: maxiter=10000).
nskip : float
Number of iterations to skip in between storing intermediate results (default: 1).
verbose : bool
Set to True to print details during iteration (default: False).
iter_list : array_like
Sequence of iteration steps at which intermediate results where stored (separated by nskip steps).
U_list : array_like
List of intermediate solutions U.
acc_list : array_like
List of intermediate accuracies (see class method _accuracy() below).
N_list : array_like
List of intermediate values of the integral N[U].
H_list : array_like
List of intermediate values of the integral H[U].
K_list : array_like
List of intermediate values of the eigenvalue estimate K=H[U]/N[U].

Initialization of the iteration base class.

Parameters

xi : array_like
Discretized coordinate xi.
tol : float, optional
Iteration is stopped if the accuracy falls below this tolerance threshold (default: 1e-12).
maxiter : int, optional
Maximum number of allowed iterations (default: 10000).
nskip : 1, optional
Number of iterations to skip in between storing intermediate results (default: 1)
verbose : bool
Set to True to print details during iteration (default: False)

Subclasses

Instance variables

prop H
Expand source code
@property
def H(self):
    '''float: Value of functional 2.'''
    return self.H_list[-1]

float: Value of functional 2.

prop N
Expand source code
@property
def N(self):
    '''float: Value of functional 1.'''
    return self.N_list[-1]

float: Value of functional 1.

prop U
Expand source code
@property
def U(self):
    '''float: Final solution U.'''
    return self.U_list[-1]

float: Final solution U.

prop acc
Expand source code
@property
def acc(self):
    '''float: Terminal accuracy.'''
    return self.acc_list[-1]

float: Terminal accuracy.

prop kap
Expand source code
@property
def kap(self):
    '''float: Value of the eigenvalue.'''
    return self.K_list[-1]

float: Value of the eigenvalue.

prop num_iter
Expand source code
@property
def num_iter(self):
    '''int: Number of iterations performed.'''
    return self.iter_list[-1]

int: Number of iterations performed.

Methods

def functional_H(self, U)
Expand source code
def functional_H(self, U):
    """Functional number 2.

    Parameters
    ----------
    U : array_like
        Current solution.

    Raises
    ------
    NotImplementedError
        Must be overwritten by subclass.
    """
    raise NotImplementedError

Functional number 2.

Parameters

U : array_like
Current solution.

Raises

NotImplementedError
Must be overwritten by subclass.
def functional_N(self, U)
Expand source code
def functional_N(self, U):
    """Functional number 1.

    Parameters
    ----------
    U : array_like
        Current solution.

    Raises
    ------
    NotImplementedError
        Must be overwritten by subclass.
    """
    raise NotImplementedError

Functional number 1.

Parameters

U : array_like
Current solution.

Raises

NotImplementedError
Must be overwritten by subclass.
def orthogonalize(self, U, ortho_set)
Expand source code
def orthogonalize(self, U, ortho_set):
    '''Otrhogonalization procedure.

    Implements a straight forward projection construction that turns the
    solution U into a solution U_ortho, which is orthogonal to any of the
    functions in the set `ortho_set`.

    Parameters
    ----------
    U : array_like
        Current solution.
    ortho_set : array_like
        Orthogonal set of previously found solutions.

    Returns
    -------
    U_ortho : array_like
        New solution which is orthogonal to any previously found solution.
    '''
    xi = self.xi
    # -- PROJECTION CONSTRUCTION 
    for V in ortho_set:
        # ... PROJECT OUT COMPONENT V 
        U -= V*np.sum(np.conj(V)*U)/np.sum(np.abs(V)**2)
    return U

Otrhogonalization procedure.

Implements a straight forward projection construction that turns the solution U into a solution U_ortho, which is orthogonal to any of the functions in the set ortho_set.

Parameters

U : array_like
Current solution.
ortho_set : array_like
Orthogonal set of previously found solutions.

Returns

U_ortho : array_like
New solution which is orthogonal to any previously found solution.
def singleUpdate(self, U, N, H, dum, **kwargs)
Expand source code
def singleUpdate(self, U, N, H, dum, **kwargs):
    """Single update of the solution.

    Parameters
    ----------
    U : array_like
        Current solution.
    N : float
        Current value of functional 1.
    H : float
        Current value of functional 2.
    dum : float
        Eigenvalue (in case of SRM) or value of the normalization
        constraint (in case of NSOM).

    Raises
    ------
    NotImplementedError
        Must be overwritten by subclass.
    """
    raise NotImplementedError

Single update of the solution.

Parameters

U : array_like
Current solution.
N : float
Current value of functional 1.
H : float
Current value of functional 2.
dum : float
Eigenvalue (in case of SRM) or value of the normalization constraint (in case of NSOM).

Raises

NotImplementedError
Must be overwritten by subclass.
def solve(self, U, dum, **kwargs)
Expand source code
def solve(self, U, dum, **kwargs):
    '''Performs iteration procedure.

    Parameters
    ----------
    U : array_like
        Initial condition.
    dum : float
        Eigenvalue (in case of SRM) or value of the normalization
        constraint (in case of NSOM).
    ortho_set : array_like, keyword argument
        List of previously found orthogonal solutions.
    acc_fun : function, keyword argument
        Custom accuracy function with call signature `acc_fun(xi, Up, Uc)`,
        where the arguments are the coordinate xi (`xi`), the solution at the
        previous iteration step (`Up`), and the solution at the current
        iteration step (`Uc`).

    Returns
    -------
    U : array_like
        Final solutions.
    acc : float
        Terminal accuracy.
    succ : bool
        Boolean flag indicating if teh iteration procedure exited
        successfully.
    msg : str
        Cause of the termination.
    '''
    # -- USEFUL FUNCTIONS AND ABBREVIATIONS
    # ... STRIP SELF KEYWORD
    xi, tol, maxiter, nskip = self.xi, self.tol, self.maxiter, self.nskip
    # ... LOCAL FUNCTIONS
    acc_fun_def = lambda xi, U, Un: np.sqrt(np.sum(np.abs(Un - U)**2)/np.sum(np.abs(U)**2))
    # -- LOGIC TO MAP OUT KEYWORD ARGUMENTS
    ortho_set = kwargs.get("ortho_set", [])
    acc_fun = kwargs.get("acc_fun", acc_fun_def)
    # -- CLEAN UP
    self._clean_up()

    # -- PREPARE/EVALUATE TRIAL SOLUTION
    U = U.astype(np.complex128)
    N = self.functional_N(U)
    H = self.functional_H(U)
    # -- INITIALIZE ITERATION SCHEME
    it = 1
    acc = np.inf
    # -- KEEP INITIAL RESULTS
    self._update_results(0, U, N, H, H/N, acc)
    # -- ITERATION PROCEDURE
    while acc > tol and it < maxiter:
        # ... ORTHOGONALIZE WRT NOT-LIST 
        if ortho_set:
            U = self.orthogonalize(U, ortho_set)
        # ... UPDATE SOLUTION 
        U_new = self.singleUpdate(U, N, H, dum, **kwargs)
        # ... ROOT-MEAN SQUARED DEVIATION W.R.T. PREVIOUS STEP 
        acc = acc_fun(xi, U, U_new)
        # ... ADVANCE UPDATE TO NEXT ITERATION STEP 
        U = U_new
        # ... RE-EVALUATE THE INTEGRALS FOR THE CURRENT SOLUTION 
        N = self.functional_N(U_new)
        H = self.functional_H(U_new)
        # ... KEEP INTERMEDIATE RESULTS
        if it%nskip==0:
            self._update_results(it, U, N, H, H/N, acc)
        it+=1

    # -- KEEP FINAL SOLUTION 
    # ... ORTHOGONALIZE WRT NOT-LIST 
    if ortho_set:
        U = self.orthogonalize(U, ortho_set)
    self._update_results(it, U, N, H, H/N, acc)

    # -- BASIC STATUS MESSAGE
    if it<maxiter:
        msg = f'Iteration terminated successfully (num_iter = {it}).'
    else:
        if it>=maxiter:
            msg = 'Maximum number of iterations reached.'
        if np.isnan(np.any(U)) or np.isnan(acc):
            msg = 'NaN result encountered.'

    if self.verbose:
        print(f"# {msg}")
        print(f"# Functional 1: N = {N}")
        print(f"# Functional 2: H = {H}")
        print(f"# Eigenvalue: K = {H/N}")
        print(f"# Local error: acc = {acc}")

    return self.U, self.acc, it<maxiter, msg

Performs iteration procedure.

Parameters

U : array_like
Initial condition.
dum : float
Eigenvalue (in case of SRM) or value of the normalization constraint (in case of NSOM).
ortho_set : array_like, keyword argument
List of previously found orthogonal solutions.
acc_fun : function, keyword argument
Custom accuracy function with call signature acc_fun(xi, Up, Uc), where the arguments are the coordinate xi (xi), the solution at the previous iteration step (Up), and the solution at the current iteration step (Uc).

Returns

U : array_like
Final solutions.
acc : float
Terminal accuracy.
succ : bool
Boolean flag indicating if teh iteration procedure exited successfully.
msg : str
Cause of the termination.
class NSOM (xi, cL, F, ORP=1.0, tol=1e-12, maxiter=10000, nskip=1, verbose=False)
Expand source code
class NSOM(IterBase):
    """Nonlinear Successive Overrelaxation Method (NSOM).

    Note
    ----
    This implementation addresses the case of a one-dimensional (d=1)
    transverse coordinate xi.
    """
    def __init__(self, xi, cL, F, ORP=1.0, tol=1e-12, maxiter=10000, nskip=1, verbose=False):
        """Initialization of the iteration base class.

        Parameters
        ----------
        xi : array_like
            Discretized coordinate xi.
        cL : array_like
            Coefficients c_L = (c_1, c_2, c_3, c_4) defining the linera operator L.
        F : function
            Nonlinear functional with call signature `F(I, xi)`, where the
            arguments are the squared magnitute of U (`I`), and the transverse
            coordinate xi (`xi`).
        ORP : float, optional
            Overrelaxation parameter (default: 1). Needs to satisfy 0 < ORP <
            2. If ORP<1, the method works by underrelaxation.
        tol : float, optional
            Iteration is stopped if the accuracy falls below this tolerance
            threshold (default: 1e-12).
        maxiter : int, optional
            Maximum number of allowed iterations (default: 10000).
        nskip : 1, optional
            Number of iterations to skip in between storing intermediate results (default: 1)
        verbose : bool
            Set to True to print details during iteration (default: False)
        """
        IterBase.__init__(self, xi, tol=tol, maxiter=maxiter, nskip=nskip, verbose=verbose)
        self.dxi = xi[1]-xi[0]
        self.F = F
        self.coeffs = self._set_coeffs(cL)
        self.ORP = ORP

    def _set_coeffs(self,coeffs):
        dxi = self.dxi
        dxi2 = dxi**2
        c1, c2, c3, c4 = coeffs
        return [ 1j*dxi*c1/12 + c2/12 + 0.5j*c3/dxi + c4/dxi2,\
                -2j*dxi*c1/3 -c2*4/3 - 1j*c3/dxi - c4*4/dxi2,\
                -c2*5/2 - c4*6/dxi2,\
                 2j*dxi*c1/3 -c2*4/3 + 1j*c3/dxi - c4*4/dxi2,\
                -1j*dxi*c1/12 + c2/12 - 0.5j*c3/dxi + c4/dxi2]

    def functional_N(self, U):
        """Functional number 1.

        Parameters
        ----------
        U : array_like
            Current solution.

        Returns
        -------
        N : float
            Value of the functional N at the current iteration step.
        """
        return np.trapz(np.abs(U)**2, x=self.xi)

    def functional_H(self, U):
        """Functional number 2.

        Implements functional H by employing a discretized linear operator L in
        which all derivatives are replaced by their corresponding five-point
        finite-difference approximations.

        Parameters
        ----------
        U : array_like
            Current solution.

        Returns
        -------
        H : float
            Value of the functional H at the current iteration step.
        """
        xi, dxi, F = self.xi, self.dxi, self.F
        am2, am1, a0, ap1, ap2 = self.coeffs
        HL = np.trapz( (am2*U[:-4] + am1*U[1:-3] - a0*U[2:-2] + ap1*U[3:-1] + ap2*U[4:] )*np.conj(U[2:-2]) , dx=1)/dxi
        HN = np.trapz(F(np.abs(U)**2, xi)*np.abs(U)**2, x=xi)
        return np.real(HL + HN)

    def singleUpdate(self, U, N, H, N0, **kwargs):
        """Single iteration step of the NSOM.

        Implements a single step of a nonlinear successive overrelaxation
        method [LM2019] for a generalized nonlinear Schrödinger equation. The
        procedure uses a Gauss-Seidel method with overrelaxation to iteratively
        updata a user-provided initial condition in place [NR2007,LL2017].

        References
        ----------
        [NR2007] W. Press, S. Teukolsky, W. Vetterling, B. Flannery, Numerical
        Recipes: The Art of Scientific Computing, Cambridge University Press
        (2007).

        [LL2017] H. P. Langtangen, S. Linge, Finite-Difference Computing with PDEs,
        Springer (2017).

        [LM2019] H. P. Langtangen, K.-A. Mardal, Introduction to Numerical Methods
        for Variational Problems, Springer (2019).

        Parameters
        ----------
        U : array_like
            Current solution.
        N : float
            Current value of functional 1.
        H : float
            Current value of functional 2.
        dum : float
            Eigenvalue (in case of SRM) or value of the normalization
            constraint (in case of NSOM).

        Returns
        -------
        U : array_like
            Updated solution.
        """
        # -- STRIP SELF KEYWORD
        xi, dxi, F, ORP = self.xi, self.dxi, self.F, self.ORP
        am2, am1, a0, ap1, ap2 = self.coeffs
        # -- USEFUL FUNCTIONS AND ABBREVIATIONS 
        _norm = lambda U, N0: U*np.sqrt(N0/np.trapz(np.abs(U)**2,x=xi))

        # -- GAUSS-SEIDEL RELAXATION PROCEDURE
        for i in range(2,xi.size-2):
            # ... KEEP OLD VALUE FOR REFERENCE
            Ui = U[i]
            xii = xi[i]
            # ... AVERAGE VALUE ACCROSS LOCAL NEIGHBORHOOD
            Ui_nbs_wgt = am2*U[i-2] + am1*U[i-1] + ap1*U[i+1] + ap2*U[i+2]
            # ... LOCAL POTENTIAL
            Vi = -F(np.abs(Ui)**2, xii)
            # ... UPDATE PIVOT
            num = Ui_nbs_wgt
            den =  a0 + (H/N + Vi)*dxi**2
            Ui_new = num/den
            # ... UPDATE SOLUTION USING OVERRELAXATION 
            U[i] += ORP*(Ui_new-Ui)

        # -- SCALE SOLUTION TO DESIRED ENERGY 
        U = _norm(U,N0)

        return U

    def show(self, f_name='none'):
        """Prepare simple plot showing the results.

        Uses Pythons matplotlibe to prepare a two-panel figure that might be
        captioned as follows:

        Nonlinear bound state of the considered nonlinear eigenvalue problem. Left
        panel: Solution U.  Shown are the real part (Re[U]), imaginary part
        (Im[U]), and modulus (|U|) of the solution.  Right panel: Variation of the
        accuracy upon iteration.

        Parameters
        ----------
        f_name : str, optional
            Figure name. If no name is set, the figure is displayed directly.
        """
        xi, U, it, acc = self.xi, self.U, self.iter_list, self.acc_list
        helper_show_d1(xi, U, it, acc, f_name=f_name)

Nonlinear Successive Overrelaxation Method (NSOM).

Note

This implementation addresses the case of a one-dimensional (d=1) transverse coordinate xi.

Initialization of the iteration base class.

Parameters

xi : array_like
Discretized coordinate xi.
cL : array_like
Coefficients c_L = (c_1, c_2, c_3, c_4) defining the linera operator L.
F : function
Nonlinear functional with call signature F(I, xi), where the arguments are the squared magnitute of U (I), and the transverse coordinate xi (xi).
ORP : float, optional
Overrelaxation parameter (default: 1). Needs to satisfy 0 < ORP < 2. If ORP<1, the method works by underrelaxation.
tol : float, optional
Iteration is stopped if the accuracy falls below this tolerance threshold (default: 1e-12).
maxiter : int, optional
Maximum number of allowed iterations (default: 10000).
nskip : 1, optional
Number of iterations to skip in between storing intermediate results (default: 1)
verbose : bool
Set to True to print details during iteration (default: False)

Ancestors

Methods

def functional_H(self, U)
Expand source code
def functional_H(self, U):
    """Functional number 2.

    Implements functional H by employing a discretized linear operator L in
    which all derivatives are replaced by their corresponding five-point
    finite-difference approximations.

    Parameters
    ----------
    U : array_like
        Current solution.

    Returns
    -------
    H : float
        Value of the functional H at the current iteration step.
    """
    xi, dxi, F = self.xi, self.dxi, self.F
    am2, am1, a0, ap1, ap2 = self.coeffs
    HL = np.trapz( (am2*U[:-4] + am1*U[1:-3] - a0*U[2:-2] + ap1*U[3:-1] + ap2*U[4:] )*np.conj(U[2:-2]) , dx=1)/dxi
    HN = np.trapz(F(np.abs(U)**2, xi)*np.abs(U)**2, x=xi)
    return np.real(HL + HN)

Functional number 2.

Implements functional H by employing a discretized linear operator L in which all derivatives are replaced by their corresponding five-point finite-difference approximations.

Parameters

U : array_like
Current solution.

Returns

H : float
Value of the functional H at the current iteration step.
def functional_N(self, U)
Expand source code
def functional_N(self, U):
    """Functional number 1.

    Parameters
    ----------
    U : array_like
        Current solution.

    Returns
    -------
    N : float
        Value of the functional N at the current iteration step.
    """
    return np.trapz(np.abs(U)**2, x=self.xi)

Functional number 1.

Parameters

U : array_like
Current solution.

Returns

N : float
Value of the functional N at the current iteration step.
def show(self, f_name='none')
Expand source code
def show(self, f_name='none'):
    """Prepare simple plot showing the results.

    Uses Pythons matplotlibe to prepare a two-panel figure that might be
    captioned as follows:

    Nonlinear bound state of the considered nonlinear eigenvalue problem. Left
    panel: Solution U.  Shown are the real part (Re[U]), imaginary part
    (Im[U]), and modulus (|U|) of the solution.  Right panel: Variation of the
    accuracy upon iteration.

    Parameters
    ----------
    f_name : str, optional
        Figure name. If no name is set, the figure is displayed directly.
    """
    xi, U, it, acc = self.xi, self.U, self.iter_list, self.acc_list
    helper_show_d1(xi, U, it, acc, f_name=f_name)

Prepare simple plot showing the results.

Uses Pythons matplotlibe to prepare a two-panel figure that might be captioned as follows:

Nonlinear bound state of the considered nonlinear eigenvalue problem. Left panel: Solution U. Shown are the real part (Re[U]), imaginary part (Im[U]), and modulus (|U|) of the solution. Right panel: Variation of the accuracy upon iteration.

Parameters

f_name : str, optional
Figure name. If no name is set, the figure is displayed directly.
def singleUpdate(self, U, N, H, N0, **kwargs)
Expand source code
def singleUpdate(self, U, N, H, N0, **kwargs):
    """Single iteration step of the NSOM.

    Implements a single step of a nonlinear successive overrelaxation
    method [LM2019] for a generalized nonlinear Schrödinger equation. The
    procedure uses a Gauss-Seidel method with overrelaxation to iteratively
    updata a user-provided initial condition in place [NR2007,LL2017].

    References
    ----------
    [NR2007] W. Press, S. Teukolsky, W. Vetterling, B. Flannery, Numerical
    Recipes: The Art of Scientific Computing, Cambridge University Press
    (2007).

    [LL2017] H. P. Langtangen, S. Linge, Finite-Difference Computing with PDEs,
    Springer (2017).

    [LM2019] H. P. Langtangen, K.-A. Mardal, Introduction to Numerical Methods
    for Variational Problems, Springer (2019).

    Parameters
    ----------
    U : array_like
        Current solution.
    N : float
        Current value of functional 1.
    H : float
        Current value of functional 2.
    dum : float
        Eigenvalue (in case of SRM) or value of the normalization
        constraint (in case of NSOM).

    Returns
    -------
    U : array_like
        Updated solution.
    """
    # -- STRIP SELF KEYWORD
    xi, dxi, F, ORP = self.xi, self.dxi, self.F, self.ORP
    am2, am1, a0, ap1, ap2 = self.coeffs
    # -- USEFUL FUNCTIONS AND ABBREVIATIONS 
    _norm = lambda U, N0: U*np.sqrt(N0/np.trapz(np.abs(U)**2,x=xi))

    # -- GAUSS-SEIDEL RELAXATION PROCEDURE
    for i in range(2,xi.size-2):
        # ... KEEP OLD VALUE FOR REFERENCE
        Ui = U[i]
        xii = xi[i]
        # ... AVERAGE VALUE ACCROSS LOCAL NEIGHBORHOOD
        Ui_nbs_wgt = am2*U[i-2] + am1*U[i-1] + ap1*U[i+1] + ap2*U[i+2]
        # ... LOCAL POTENTIAL
        Vi = -F(np.abs(Ui)**2, xii)
        # ... UPDATE PIVOT
        num = Ui_nbs_wgt
        den =  a0 + (H/N + Vi)*dxi**2
        Ui_new = num/den
        # ... UPDATE SOLUTION USING OVERRELAXATION 
        U[i] += ORP*(Ui_new-Ui)

    # -- SCALE SOLUTION TO DESIRED ENERGY 
    U = _norm(U,N0)

    return U

Single iteration step of the NSOM.

Implements a single step of a nonlinear successive overrelaxation method [LM2019] for a generalized nonlinear Schrödinger equation. The procedure uses a Gauss-Seidel method with overrelaxation to iteratively updata a user-provided initial condition in place [NR2007,LL2017].

References

[NR2007] W. Press, S. Teukolsky, W. Vetterling, B. Flannery, Numerical Recipes: The Art of Scientific Computing, Cambridge University Press (2007).

[LL2017] H. P. Langtangen, S. Linge, Finite-Difference Computing with PDEs, Springer (2017).

[LM2019] H. P. Langtangen, K.-A. Mardal, Introduction to Numerical Methods for Variational Problems, Springer (2019).

Parameters

U : array_like
Current solution.
N : float
Current value of functional 1.
H : float
Current value of functional 2.
dum : float
Eigenvalue (in case of SRM) or value of the normalization constraint (in case of NSOM).

Returns

U : array_like
Updated solution.

Inherited members

class SRM (xi, cL, F, tol=1e-12, maxiter=10000, nskip=1, verbose=False, spm=False)
Expand source code
class SRM(IterBase):
    """Spectral renormalization method (SRM).

    Note
    ----
    This implementation addresses the case of a one-dimensional (d=1)
    transverse coordinate xi and nonlinear functional quadratic in U.
    """
    def __init__(self, xi, cL, F, tol=1e-12, maxiter=10000, nskip=1, verbose=False, spm=False):
        """Initialization of the iteration base class.

        Parameters
        ----------
        xi : array_like
            Discretized coordinate xi.
        cL : array_like
            Coefficients c_L = (c_1, c_2, c_3, c_4) defining the linera operator L.
        F : function
            Nonlinear functional with call signature `F(I, xi)`, where the
            arguments are the squared magnitute of U (`I`), and the transverse
            coordinate xi (`xi`).
        tol : float, optional
            Iteration is stopped if the accuracy falls below this tolerance
            threshold (default: 1e-12).
        maxiter : int, optional
            Maximum number of allowed iterations (default: 10000).
        nskip : 1, optional
            Number of iterations to skip in between storing intermediate results (default: 1)
        verbose : bool
            Set to True to print details during iteration (default: False)
        spm : bool
            Set to True to enable a structure-preserving method for a GNSE with
            nodeless, even, and real-valued solution (default: False).

            Enabling this option requires some caution: intented for  GNSEs
            with even and real solitons, it yields catastrophic results for
            GNSEs with complex-valued, oscillating-tail solitons.


        """
        IterBase.__init__(self, xi, tol=tol, maxiter=maxiter, nskip=nskip, verbose=verbose)
        self.F = F
        self.Lw = self._set_Lk(xi, cL)
        self.gam = 1.5
        self.spm = spm

    def _set_Lk(self, xi, coeffs):
        c1, c2, c3, c4 = coeffs
        k = FTFREQ(xi.size, d=xi[1]-xi[0])*2*np.pi
        return c1*k + c2*k**2 + c3*k**3 + c4*k**4

    def functional_N(self, U):
        """Functional number 1.

        Parameters
        ----------
        U : array_like
            Current solution.

        Returns
        -------
        N : float
            Value of the functional N at the current iteration step.
        """
        return np.trapz(np.abs(U)**2, x=self.xi, axis=-1)

    def functional_H(self, U):
        """Functional number 2.

        Implements functional H by employing spectral derivatives to handle the
        linear operator L.

        Parameters
        ----------
        U : array_like
            Current solution.

        Returns
        -------
        H : float
            Value of the functional H at the current iteration step.
        """
        xi, Lw, F = self.xi, self.Lw, self.F
        return np.real(np.trapz( np.conj(U)*IFT(Lw*FT(U)) + F(np.abs(U)**2, xi)*np.abs(U)**2, x=xi, axis=-1))

    def singleUpdate(self, U, N, H, kap, **kwargs):
        """Single iteration step of the SRM.

        Implements a single step of the d=1 SRM, thoroughly detailed in
        [A2003,M2004,A2005,A2009].

        Note
        ----
        This implementation addresses the case of a one-dimensional (d=1)
        transverse coordinate xi and nonlinear functional quadratic in U.

        If the boolean flag spm=True, the method implements the
        structure-preserving add-on discussed in Appendix B of [F2008].

        References
        ----------
        [A2003] M. J. Ablowitz, Z. H. Musslimani, Discrete spatial solitons in
        a diffraction-managed nonlinear waveguide array: a unified approach,
        Physica D 184 (2003) 276–303,
        https://doi.org/10.1016/S0167-2789(03)00226-4.

        [M2004] Z. Musslimani, J. Yang, Self-trapping of light in a
        two-dimensional photonic lattice, J. Opt. Soc. Am.  B 21 (2004) 973,
        https://doi.org/10.1364/JOSAB.21.000973.

        [A2005] M. J. Ablowitz, Z. H. Musslimani, Spectral renormalization
        method for computing self-localized solutions to nonlinear systems,
        Opt. Lett. 30 (2005) 2140, https://doi.org/10.1364/OL.30.002140.

        [F2008] G. Fibich, Y. Sivan, M. I. Weinstein, Bound states of nonlinear
        Schrödinger equations with a periodic nonlinear microstructure, Physica
        D 217 (2006) 31, https://doi.org/10.1016/j.physd.2006.03.009.

        [A2009] M. Ablowitz, T. Horikis, Solitons and spectral renormalization
        methods in nonlinear optics, Eur. Phys. J.  Spec. Top. 173 (2009) 147,
        https://doi.org/10.1140/epjst/e2009-01072-0.

        Parameters
        ----------
        U : array_like
            Current solution.
        N : float
            Current value of functional 1.
        H : float
            Current value of functional 2.
        kap : float
            Eigenvalue of the sought-for solution.

        Returns
        -------
        U : array_like
            Updated solution.
        """
        # -- STRIP SELF KEYWORD
        xi, Lw, F, gam, spm = self.xi, self.Lw, self.F, self.gam, self.spm
        # -- USEFUL FUNCTIONS AND ABBREVIATIONS 
        _IP = lambda f,g: np.trapz(np.conj(f)*g, x=xi)

        # -- SRM UPDATE
        # (1) PROPOSE UPDATED SOLUTION
        U_tmp = IFT(FT(F(np.abs(U)**2,xi)*U)/(kap - Lw))
        # (2) RESCALE SOLUTION SO IT SATISFIES THE DESIRED INTEGRAL IDENTITY
        s_tmp = np.abs(_IP(U,U)/_IP(U,U_tmp))
        U_tmp *= s_tmp**gam
        # (3) ENABLE STRUCTURE-PRESERVING ADD-ON FOR EVEN AND REAL SOLUTIONS
        if spm:
         U_tmp = np.abs(U_tmp)

        return U_tmp

    def show(self, f_name='none'):
        """Prepare simple plot showing the results.

        Uses Pythons matplotlibe to prepare a two-panel figure that might be
        captioned as follows:

        Nonlinear bound state of the considered nonlinear eigenvalue problem. Left
        panel: Solution U.  Shown are the real part (Re[U]), imaginary part
        (Im[U]), and modulus (|U|) of the solution.  Right panel: Variation of the
        accuracy upon iteration.

        Parameters
        ----------
        f_name : str, optional
            Figure name. If no name is set, the figure is displayed directly.
        """
        xi, U, it, acc = self.xi, self.U, self.iter_list, self.acc_list
        helper_show_d1(xi, U, it, acc, f_name=f_name)

Spectral renormalization method (SRM).

Note

This implementation addresses the case of a one-dimensional (d=1) transverse coordinate xi and nonlinear functional quadratic in U.

Initialization of the iteration base class.

Parameters

xi : array_like
Discretized coordinate xi.
cL : array_like
Coefficients c_L = (c_1, c_2, c_3, c_4) defining the linera operator L.
F : function
Nonlinear functional with call signature F(I, xi), where the arguments are the squared magnitute of U (I), and the transverse coordinate xi (xi).
tol : float, optional
Iteration is stopped if the accuracy falls below this tolerance threshold (default: 1e-12).
maxiter : int, optional
Maximum number of allowed iterations (default: 10000).
nskip : 1, optional
Number of iterations to skip in between storing intermediate results (default: 1)
verbose : bool
Set to True to print details during iteration (default: False)
spm : bool

Set to True to enable a structure-preserving method for a GNSE with nodeless, even, and real-valued solution (default: False).

Enabling this option requires some caution: intented for GNSEs with even and real solitons, it yields catastrophic results for GNSEs with complex-valued, oscillating-tail solitons.

Ancestors

Methods

def functional_H(self, U)
Expand source code
def functional_H(self, U):
    """Functional number 2.

    Implements functional H by employing spectral derivatives to handle the
    linear operator L.

    Parameters
    ----------
    U : array_like
        Current solution.

    Returns
    -------
    H : float
        Value of the functional H at the current iteration step.
    """
    xi, Lw, F = self.xi, self.Lw, self.F
    return np.real(np.trapz( np.conj(U)*IFT(Lw*FT(U)) + F(np.abs(U)**2, xi)*np.abs(U)**2, x=xi, axis=-1))

Functional number 2.

Implements functional H by employing spectral derivatives to handle the linear operator L.

Parameters

U : array_like
Current solution.

Returns

H : float
Value of the functional H at the current iteration step.
def functional_N(self, U)
Expand source code
def functional_N(self, U):
    """Functional number 1.

    Parameters
    ----------
    U : array_like
        Current solution.

    Returns
    -------
    N : float
        Value of the functional N at the current iteration step.
    """
    return np.trapz(np.abs(U)**2, x=self.xi, axis=-1)

Functional number 1.

Parameters

U : array_like
Current solution.

Returns

N : float
Value of the functional N at the current iteration step.
def show(self, f_name='none')
Expand source code
def show(self, f_name='none'):
    """Prepare simple plot showing the results.

    Uses Pythons matplotlibe to prepare a two-panel figure that might be
    captioned as follows:

    Nonlinear bound state of the considered nonlinear eigenvalue problem. Left
    panel: Solution U.  Shown are the real part (Re[U]), imaginary part
    (Im[U]), and modulus (|U|) of the solution.  Right panel: Variation of the
    accuracy upon iteration.

    Parameters
    ----------
    f_name : str, optional
        Figure name. If no name is set, the figure is displayed directly.
    """
    xi, U, it, acc = self.xi, self.U, self.iter_list, self.acc_list
    helper_show_d1(xi, U, it, acc, f_name=f_name)

Prepare simple plot showing the results.

Uses Pythons matplotlibe to prepare a two-panel figure that might be captioned as follows:

Nonlinear bound state of the considered nonlinear eigenvalue problem. Left panel: Solution U. Shown are the real part (Re[U]), imaginary part (Im[U]), and modulus (|U|) of the solution. Right panel: Variation of the accuracy upon iteration.

Parameters

f_name : str, optional
Figure name. If no name is set, the figure is displayed directly.
def singleUpdate(self, U, N, H, kap, **kwargs)
Expand source code
def singleUpdate(self, U, N, H, kap, **kwargs):
    """Single iteration step of the SRM.

    Implements a single step of the d=1 SRM, thoroughly detailed in
    [A2003,M2004,A2005,A2009].

    Note
    ----
    This implementation addresses the case of a one-dimensional (d=1)
    transverse coordinate xi and nonlinear functional quadratic in U.

    If the boolean flag spm=True, the method implements the
    structure-preserving add-on discussed in Appendix B of [F2008].

    References
    ----------
    [A2003] M. J. Ablowitz, Z. H. Musslimani, Discrete spatial solitons in
    a diffraction-managed nonlinear waveguide array: a unified approach,
    Physica D 184 (2003) 276–303,
    https://doi.org/10.1016/S0167-2789(03)00226-4.

    [M2004] Z. Musslimani, J. Yang, Self-trapping of light in a
    two-dimensional photonic lattice, J. Opt. Soc. Am.  B 21 (2004) 973,
    https://doi.org/10.1364/JOSAB.21.000973.

    [A2005] M. J. Ablowitz, Z. H. Musslimani, Spectral renormalization
    method for computing self-localized solutions to nonlinear systems,
    Opt. Lett. 30 (2005) 2140, https://doi.org/10.1364/OL.30.002140.

    [F2008] G. Fibich, Y. Sivan, M. I. Weinstein, Bound states of nonlinear
    Schrödinger equations with a periodic nonlinear microstructure, Physica
    D 217 (2006) 31, https://doi.org/10.1016/j.physd.2006.03.009.

    [A2009] M. Ablowitz, T. Horikis, Solitons and spectral renormalization
    methods in nonlinear optics, Eur. Phys. J.  Spec. Top. 173 (2009) 147,
    https://doi.org/10.1140/epjst/e2009-01072-0.

    Parameters
    ----------
    U : array_like
        Current solution.
    N : float
        Current value of functional 1.
    H : float
        Current value of functional 2.
    kap : float
        Eigenvalue of the sought-for solution.

    Returns
    -------
    U : array_like
        Updated solution.
    """
    # -- STRIP SELF KEYWORD
    xi, Lw, F, gam, spm = self.xi, self.Lw, self.F, self.gam, self.spm
    # -- USEFUL FUNCTIONS AND ABBREVIATIONS 
    _IP = lambda f,g: np.trapz(np.conj(f)*g, x=xi)

    # -- SRM UPDATE
    # (1) PROPOSE UPDATED SOLUTION
    U_tmp = IFT(FT(F(np.abs(U)**2,xi)*U)/(kap - Lw))
    # (2) RESCALE SOLUTION SO IT SATISFIES THE DESIRED INTEGRAL IDENTITY
    s_tmp = np.abs(_IP(U,U)/_IP(U,U_tmp))
    U_tmp *= s_tmp**gam
    # (3) ENABLE STRUCTURE-PRESERVING ADD-ON FOR EVEN AND REAL SOLUTIONS
    if spm:
     U_tmp = np.abs(U_tmp)

    return U_tmp

Single iteration step of the SRM.

Implements a single step of the d=1 SRM, thoroughly detailed in [A2003,M2004,A2005,A2009].

Note

This implementation addresses the case of a one-dimensional (d=1) transverse coordinate xi and nonlinear functional quadratic in U.

If the boolean flag spm=True, the method implements the structure-preserving add-on discussed in Appendix B of [F2008].

References

[A2003] M. J. Ablowitz, Z. H. Musslimani, Discrete spatial solitons in a diffraction-managed nonlinear waveguide array: a unified approach, Physica D 184 (2003) 276–303, https://doi.org/10.1016/S0167-2789(03)00226-4.

[M2004] Z. Musslimani, J. Yang, Self-trapping of light in a two-dimensional photonic lattice, J. Opt. Soc. Am. B 21 (2004) 973, https://doi.org/10.1364/JOSAB.21.000973.

[A2005] M. J. Ablowitz, Z. H. Musslimani, Spectral renormalization method for computing self-localized solutions to nonlinear systems, Opt. Lett. 30 (2005) 2140, https://doi.org/10.1364/OL.30.002140.

[F2008] G. Fibich, Y. Sivan, M. I. Weinstein, Bound states of nonlinear Schrödinger equations with a periodic nonlinear microstructure, Physica D 217 (2006) 31, https://doi.org/10.1016/j.physd.2006.03.009.

[A2009] M. Ablowitz, T. Horikis, Solitons and spectral renormalization methods in nonlinear optics, Eur. Phys. J. Spec. Top. 173 (2009) 147, https://doi.org/10.1140/epjst/e2009-01072-0.

Parameters

U : array_like
Current solution.
N : float
Current value of functional 1.
H : float
Current value of functional 2.
kap : float
Eigenvalue of the sought-for solution.

Returns

U : array_like
Updated solution.

Inherited members