7.7 PDE constrained Topology Optimization

We are again interested in minimizing a shape function of the type :nbsphinx-math:`begin{align*}

mathcal J(Omega) = int_{mathsf{D}} (u-u_d)^2 ; dx

end{align*}` subject to \(u:\mathsf{D} \to \mathbb{R}\) solves

\[\int_{\mathsf{D}} \beta_\Omega\nabla u\cdot \nabla \varphi\;dx = \int_{\mathsf{D}} f_\Omega\varphi \quad \text{ for all } \varphi \in H^1_0(\mathsf{D}),\]

where

  • $:nbsphinx-math:beta_:nbsphinx-math:Omega `= :nbsphinx-math:beta`1:nbsphinx-math:`chi`\Omega `+ :nbsphinx-math:beta`2 :nbsphinx-math:`chi`{\mathsf{D}\setminus `:nbsphinx-math:Omega`},:nbsphinx-math:qquad `$ :math:beta_1,beta_2>0`,

  • $f_:nbsphinx-math:Omega `= f_1:nbsphinx-math:chi`:nbsphinx-math:`Omega `+ f_2 :nbsphinx-math:`chi`{\mathsf{D}\setminus `:nbsphinx-math:Omega`}, \qquad `$ :math:`f_1,f_2\in \mathbb{R},

  • \(u_d:\mathsf{D} \to \mathbb{R}\).

[1]:
from netgen.geom2d import SplineGeometry  # SplieGeometry to define a 2D mesh

from ngsolve import * # import everything from the ngsolve module
from ngsolve.webgui import Draw

import numpy as np

from interpolations import InterpolateLevelSetToElems # function which interpolates a levelset function


myMAXH = 0.05
EPS = myMAXH * 1e-6      #variable defining when a point is on the interface and when not

We begin with defining the domain \(\mathsf{D}\). For this we define a rectangular mesh with side lenght \(R\) using the NGSolve function SplineGeometry() with maximum triangle size maxh. The boundary name of \(\partial\mathsf{D}\) is “rectangle”.

[2]:
geo = SplineGeometry()

R = 2

## add a rectangle
geo.AddRectangle(p1=(-R,-R),
                 p2=(R,R),
                 bc="rectangle",
                 leftdomain=1,
                 rightdomain=0)


geo.SetMaterial (1, "outer") # give the domain the name "outer"

mesh = Mesh(geo.GenerateMesh(maxh=myMAXH)) # generate ngsolve mesh
Draw(mesh)
[2]:
BaseWebGuiScene
  • fes_state,fes_adj - \(H^1\) conforming finite element space of order 1

  • pwc - \(L^2\) subspace of functions constant on each element of mesh

  • \(u,v\) are our trial and test functions

  • gfu, gfp are Gridfunctions storing the state and adjoint variable, respectively

  • gfud stores \(u_d\) - the target function

[3]:
# H1-conforming finite element space

fes_state = H1(mesh, order=1, dirichlet="rectangle")
fes_adj = H1(mesh, order=1, dirichlet="rectangle")
fes_level = H1(mesh, order=1)

pwc = L2(mesh)   #piecewise constant space


## test and trial functions
u, v = fes_state.TnT()

p, q = fes_adj.TnT()

gfu = GridFunction(fes_state)
gfp = GridFunction(fes_adj)

gfud = GridFunction(fes_state)

We represent the design \(\Omega \subset \mathsf{D}\) by means of a level set function \(\psi: \mathsf{D} \rightarrow \mathbb R\) in the following way:

:nbsphinx-math:`begin{align*}

psi(x) < 0 &quad Longleftrightarrowquad x in Omega \ psi(x) = 0 &quad Longleftrightarrowquad x in partial Omega \ psi(x) > 0 &quad Longleftrightarrowquad x in mathsf{D} setminus overline Omega.

end{align*}`

  • psi - gridfunction in fes_state defining \(\psi\)

  • <li> psides - gridfunction describing the optimal shape </li>
    <li> psinew - dummy function for line search </li>
    
    [4]:
    
    psi = GridFunction(fes_level)
    psi.Set(1)
    
    psides = GridFunction(fes_level)
    psinew = GridFunction(fes_level)
    
    scene_psi = Draw(psi, mesh, "psi")
    

    Next we solve the state equation \begin{equation} \int_{\mathsf{D}} \beta_\Omega \nabla u \cdot \nabla \varphi \;dx = \int_{\mathsf{D}} f_\Omega\varphi \;dx \quad \text{ for all } \varphi \in H^1_0(\mathsf{D}). \end{equation}

    • beta, f_rhs - piecewise constant gridfunction

    • B - bilinear form defining \(\int_{\mathsf{D}} \beta_\Omega\nabla \psi \cdot \nabla \varphi \;dx\)

    • B_adj - bilinear form defining \(\int_{\mathsf{D}} \beta_\Omega\nabla \psi \cdot \nabla \varphi \;dx\)

    • L - right hand side \(\int_{\mathsf{D}} f_\Omega \varphi \;dx\)

    [5]:
    
    # constants for f_rhs and beta
    f1 = 10
    f2 = 1
    
    beta1 = 10
    beta2 = 1
    
    # piecewise constant coefficient functions beta and f_rhs
    
    beta = GridFunction(pwc)
    beta.Set(beta1)
    
    f_rhs = GridFunction(pwc)
    f_rhs.Set(f1)
    
    #Draw(beta, mesh, "beta")
    #Draw(f_rhs, mesh, "f_rhs")
    
    # bilinear form for state equation
    B = BilinearForm(fes_state)
    B += beta*grad(u) * grad(v) * dx
    
    B_adj = BilinearForm(fes_adj)
    B_adj += beta*grad(p) * grad(q) * dx
    
    L = LinearForm(fes_state)
    L += f_rhs * v *dx
    
    duCost = LinearForm(fes_adj)
    
    
    # solving
    
    psides.Set(1)
    InterpolateLevelSetToElems(psides, beta1, beta2, beta, mesh, EPS)
    InterpolateLevelSetToElems(psides, f1, f2, f_rhs, mesh, EPS)
    
    B.Assemble()
    L.Assemble()
    
    inv = B.mat.Inverse(fes_state.FreeDofs(), inverse="sparsecholesky") # inverse of bilinear form
    gfu.vec.data = inv*L.vec
    
    scene_u = Draw(gfu)
    

    As an optimal shape we define \(\Omega_{opt} := \{f<0\}\), where \(f\) is the function from the levelset example describing a clover shape. We construct \(u_d\) by solving \begin{equation} \int_{\mathsf{D}} \beta_{\Omega_{opt}} \nabla u_d \cdot \nabla \varphi \;dx = \int_{\mathsf{D}} f_{\Omega_{opt}}\varphi \;dx \quad \text{ for all } \varphi \in H^1_0(\mathsf{D}). \end{equation}

    [6]:
    
    a = 4.0/5.0
    b = 2
    f = CoefficientFunction( 0.1*( (sqrt((x - a)**2 + b * y**2) - 1) \
                    * (sqrt((x + a)**2 + b * y**2) - 1) \
                    * (sqrt(b * x**2 + (y - a)**2) - 1) \
                    * (sqrt(b * x**2 + (y + a)**2) - 1) - 0.001) )
    
    psides.Set(f)
    InterpolateLevelSetToElems(psides, beta1, beta2, beta, mesh, EPS)
    InterpolateLevelSetToElems(psides, f1, f2, f_rhs, mesh, EPS)
    
    B.Assemble()
    L.Assemble()
    inv = B.mat.Inverse(fes_state.FreeDofs(), inverse="sparsecholesky") # inverse of bilinear form
    
    gfud.vec.data = inv*L.vec
    
    Draw(gfud, mesh, 'gfud')
    
    [6]:
    
    BaseWebGuiScene
    

    The function SolvePDE solves the state and adjoint state each time it is called. Note that since the functions beta and f_rhs are GridFunctions we do not have to redefine the bilinearform and linearform when beta and f_rhs change.

    [7]:
    
    def SolvePDE(adjoint=False):
        #Newton(a, gfu, printing = False, maxerr = 3e-9)
    
        B.Assemble()
        L.Assemble()
    
        inv_state = B.mat.Inverse(fes_state.FreeDofs(), inverse="sparsecholesky")
    
        # solve state equation
        gfu.vec.data = inv_state*L.vec
    
        if adjoint == True:
            # solve adjoint state equatoin
            duCost.Assemble()
            B_adj.Assemble()
            inv_adj = B_adj.mat.Inverse(fes_adj.FreeDofs(), inverse="sparsecholesky")
            gfp.vec.data = -inv_adj * duCost.vec
        scene_u.Redraw()
    
    
    InterpolateLevelSetToElems(psi, beta1, beta2, beta, mesh, EPS)
    InterpolateLevelSetToElems(psi, f1, f2, f_rhs, mesh, EPS)
    
    SolvePDE()
    
    Redraw()
    
    
    # define the cost function
    
    def Cost(u):
        return (u - gfud)**2*dx
    
    # derivative of cost function
    
    duCost += 2*(gfu-gfud) * q * dx
    
    
    print("initial cost = ", Integrate(Cost(gfu), mesh))
    
    initial cost =  39.29566667329117
    
    It remains to implement the topological derivative: In define \(NegPos\) and \(PosNeg\) as the topological derivative in \(\Omega\) respectively \(\Omega^c\): :nbsphinx-math:`begin{align*}

    mathsf{NegPos}(x) := -DJ(Omega,omega)(x) &= (-1) * left( 2 beta_2 left(frac{beta_2-beta_1}{beta_1+beta_2}right)nabla u(x)cdot nabla p(x) - (f_2-f_1)p(x) right) \ &= 2 beta_2 left(frac{beta_1-beta_2}{beta_1+beta_2}right)nabla u(x)cdot nabla p(x) - (f_1-f_2)p(x) quad text{ for }xin Omega \ mathsf{PosNeg}(x) := DJ(Omega,omega)(x) &= 2 beta_1 left(frac{beta_1-beta_2}{beta_1+beta_2}right)nabla u(x)cdot nabla p(x) - (f_1-f_2)p(x) quad text{ for }xin Omega^c

    end{align*}` and the update function (also called generalised topological derivative) \begin{equation} g_\Omega(x):= -\chi_\Omega (x)DJ(\Omega,\omega)(x) + (1-\chi_\Omega)*DJ(\Omega,\omega)(x) . \end{equation}

    [8]:
    
    BetaPosNeg = 2 * beta2 * (beta1-beta2)/(beta1+beta2)    # factors in TD in positive part {x: \psi(x)>0} ={x: \beta(x) = \beta_2}
    BetaNegPos = 2 * beta1 * (beta1-beta2)/(beta1+beta2)    # factors in TD in positive part {x: \psi(x)>0} ={x: \beta(x) = \beta_2}
    FPosNeg = -(f1-f2)
    
    psinew.vec.data = psi.vec
    
    ## normalise psi in L2
    normPsi = sqrt(Integrate(psi**2*dx, mesh))
    psi.vec.data = 1/normPsi * psi.vec
    
    kappa = 0.05
    
    # set level set function to data
    InterpolateLevelSetToElems(psi, beta1, beta2, beta, mesh, EPS)
    InterpolateLevelSetToElems(psi, f1, f2, f_rhs, mesh, EPS)
    
    Redraw()
    
    TD_node = GridFunction(fes_level)
    #scene1 = Draw(TD_node, mesh, "TD_node")
    
    TD_pwc = GridFunction(pwc)
    #scene2 = Draw(TD_pwc, mesh, "TD_pwc")
    
    TDPosNeg_pwc = GridFunction(pwc)
    #Draw(TDPosNeg_pwc, mesh, "TDPosNeg_pwc")
    
    TDNegPos_pwc = GridFunction(pwc)
    #Draw(TDNegPos_pwc, mesh, "TDNegPos_pwc")
    
    cutRatio = GridFunction(pwc)
    #scene3 = Draw(cutRatio, mesh, "cutRatio")
    
    # solve for current configuration
    SolvePDE(adjoint=True)
    
    Finally we can define a loop defining the levelset update: :nbsphinx-math:`begin{align*}

    psi_{k+1} = (1-s_k) frac{psi_k}{|\psi_k|} + s_k frac{g_{psi_k}}{|g_{\psi_k}|}

    end{align*}`

    [9]:
    
    scene = Draw(psi)
    scene_cr = Draw(cutRatio, mesh, "cutRatio")
    scene_TD0 = Draw(TD_pwc, mesh, "TD_pwc")
    scene_TD1 = Draw(TD_node, mesh, "TD_node")
    
    [10]:
    
    # iter_max = 1000
    iter_max = 20
    converged = False
    
    
    
    xm=0.
    ym=0.
    psi.Set( (x-xm)**2+(y-ym)**2-0.25**2)
    psinew.vec.data= psi.vec
    scene.Redraw()
    # input("press enter to start optimization")
    
    J = Integrate(Cost(gfu),mesh)
    
    with TaskManager():
    
        for k in range(iter_max):
            print("================ iteration ", k, "===================")
    
            # copy new levelset data from psinew into psi
            psi.vec.data = psinew.vec
            scene.Redraw()
    
            SolvePDE(adjoint=True)
    
            J_current = Integrate(Cost(gfu),mesh)
    
            print( Integrate( (gfu-gfud)**2*dx, mesh) )
    
            print("cost in beginning of iteration", k, ": Cost = ", J_current)
    
    
            # compute the piecewise constant topological derivative in each domain
            TDPosNeg_pwc.Set( BetaPosNeg * grad(gfu) * grad(gfp)  + FPosNeg*gfp )
            TDNegPos_pwc.Set( BetaNegPos * grad(gfu) * grad(gfp)  + FPosNeg*gfp )
    
            # compute the cut ratio of the interface elements
            InterpolateLevelSetToElems(psi, 1, 0, cutRatio, mesh, EPS)
    
            # compute the combined topological derivative using the cut ratio information
            for j in range(len(TD_pwc.vec)):
                TD_pwc.vec[j] = cutRatio.vec[j] * TDNegPos_pwc.vec[j] + (1-cutRatio.vec[j])*TDPosNeg_pwc.vec[j]
    
            TD_node.Set(TD_pwc)
    
            scene_cr.Redraw()
            scene_TD0.Redraw()
            scene_TD1.Redraw()
    
            normTD = sqrt(Integrate(TD_node**2*dx, mesh)) # L2 norm of TD_node
            TD_node.vec.data = 1/normTD * TD_node.vec # normalised TD_node
    
            normPsi = sqrt(Integrate(psi**2*dx, mesh)) # L2 norm of psi
            psi.vec.data = 1/normPsi * psi.vec  # normalised psi
    
            linesearch = True
    
            for j in range(10):
    
                # update the level set function
                psinew.vec.data = (1-kappa)*psi.vec + kappa*TD_node.vec
    
                # update beta and f_rhs
                InterpolateLevelSetToElems(psinew, beta1, beta2, beta, mesh, EPS)
                InterpolateLevelSetToElems(psinew, f1, f2, f_rhs, mesh, EPS)
    
                # solve PDE without adjoint
                SolvePDE()
    
                Redraw(blocking=True)
    
                Jnew = Integrate(Cost(gfu), mesh)
    
                if Jnew > J_current:
                    print("--------------------")
                    print("-----line search ---")
                    print("--------------------")
                    kappa = kappa*0.8
                    print("kappa", kappa)
                else:
                    break
    
            Redraw(blocking=True)
            print("----------- Jnew in  iteration ", k, " = ", Jnew, " (kappa = ", kappa, ")")
            print('')
            print("iter" + str(k) + ", Jnew = " + str(Jnew) + " (kappa = ", kappa, ")")
            kappa = min(1, kappa*1.2)
    
            print("end of iter " + str(k))
    
    # input("Finished")
    
    ================ iteration  0 ===================
    39.29566667329117
    cost in beginning of iteration 0 : Cost =  39.29566667329117
    ----------- Jnew in  iteration  0  =  11.859499746558795  (kappa =  0.05 )
    
    iter0, Jnew = 11.859499746558795 (kappa =  0.05 )
    end of iter 0
    ================ iteration  1 ===================
    11.859499746558438
    cost in beginning of iteration 1 : Cost =  11.859499746558438
    ----------- Jnew in  iteration  1  =  6.366218300004624  (kappa =  0.06 )
    
    iter1, Jnew = 6.366218300004624 (kappa =  0.06 )
    end of iter 1
    ================ iteration  2 ===================
    6.366218300004593
    cost in beginning of iteration 2 : Cost =  6.3662183000045935
    ----------- Jnew in  iteration  2  =  6.219405317328888  (kappa =  0.072 )
    
    iter2, Jnew = 6.219405317328888 (kappa =  0.072 )
    end of iter 2
    ================ iteration  3 ===================
    6.219405317328601
    cost in beginning of iteration 3 : Cost =  6.219405317328601
    --------------------
    -----line search ---
    --------------------
    kappa 0.06912
    --------------------
    -----line search ---
    --------------------
    kappa 0.055296000000000005
    --------------------
    -----line search ---
    --------------------
    kappa 0.04423680000000001
    --------------------
    -----line search ---
    --------------------
    kappa 0.03538944000000001
    --------------------
    -----line search ---
    --------------------
    kappa 0.028311552000000007
    --------------------
    -----line search ---
    --------------------
    kappa 0.02264924160000001
    --------------------
    -----line search ---
    --------------------
    kappa 0.018119393280000007
    --------------------
    -----line search ---
    --------------------
    kappa 0.014495514624000005
    ----------- Jnew in  iteration  3  =  5.650462582702466  (kappa =  0.014495514624000005 )
    
    iter3, Jnew = 5.650462582702466 (kappa =  0.014495514624000005 )
    end of iter 3
    ================ iteration  4 ===================
    5.650462582702473
    cost in beginning of iteration 4 : Cost =  5.650462582702473
    --------------------
    -----line search ---
    --------------------
    kappa 0.013915694039040007
    ----------- Jnew in  iteration  4  =  5.5382443063099895  (kappa =  0.013915694039040007 )
    
    iter4, Jnew = 5.5382443063099895 (kappa =  0.013915694039040007 )
    end of iter 4
    ================ iteration  5 ===================
    5.538244306310142
    cost in beginning of iteration 5 : Cost =  5.538244306310142
    --------------------
    -----line search ---
    --------------------
    kappa 0.013359066277478408
    --------------------
    -----line search ---
    --------------------
    kappa 0.010687253021982727
    --------------------
    -----line search ---
    --------------------
    kappa 0.008549802417586181
    --------------------
    -----line search ---
    --------------------
    kappa 0.006839841934068946
    ----------- Jnew in  iteration  5  =  5.4770959762939695  (kappa =  0.006839841934068946 )
    
    iter5, Jnew = 5.4770959762939695 (kappa =  0.006839841934068946 )
    end of iter 5
    ================ iteration  6 ===================
    5.477095976293961
    cost in beginning of iteration 6 : Cost =  5.477095976293961
    --------------------
    -----line search ---
    --------------------
    kappa 0.006566248256706188
    ----------- Jnew in  iteration  6  =  5.085960177817964  (kappa =  0.006566248256706188 )
    
    iter6, Jnew = 5.085960177817964 (kappa =  0.006566248256706188 )
    end of iter 6
    ================ iteration  7 ===================
    5.085960177817951
    cost in beginning of iteration 7 : Cost =  5.085960177817951
    --------------------
    -----line search ---
    --------------------
    kappa 0.00630359832643794
    --------------------
    -----line search ---
    --------------------
    kappa 0.005042878661150352
    --------------------
    -----line search ---
    --------------------
    kappa 0.004034302928920282
    ----------- Jnew in  iteration  7  =  5.024681725171359  (kappa =  0.004034302928920282 )
    
    iter7, Jnew = 5.024681725171359 (kappa =  0.004034302928920282 )
    end of iter 7
    ================ iteration  8 ===================
    5.024681725171355
    cost in beginning of iteration 8 : Cost =  5.024681725171355
    --------------------
    -----line search ---
    --------------------
    kappa 0.003872930811763471
    ----------- Jnew in  iteration  8  =  4.680169502588493  (kappa =  0.003872930811763471 )
    
    iter8, Jnew = 4.680169502588493 (kappa =  0.003872930811763471 )
    end of iter 8
    ================ iteration  9 ===================
    4.680169502588663
    cost in beginning of iteration 9 : Cost =  4.680169502588663
    --------------------
    -----line search ---
    --------------------
    kappa 0.003718013579292932
    --------------------
    -----line search ---
    --------------------
    kappa 0.0029744108634343455
    --------------------
    -----line search ---
    --------------------
    kappa 0.0023795286907474767
    --------------------
    -----line search ---
    --------------------
    kappa 0.0019036229525979814
    ----------- Jnew in  iteration  9  =  4.548015054184545  (kappa =  0.0019036229525979814 )
    
    iter9, Jnew = 4.548015054184545 (kappa =  0.0019036229525979814 )
    end of iter 9
    ================ iteration  10 ===================
    4.548015054184546
    cost in beginning of iteration 10 : Cost =  4.548015054184547
    ----------- Jnew in  iteration  10  =  4.297148176577941  (kappa =  0.0022843475431175778 )
    
    iter10, Jnew = 4.297148176577941 (kappa =  0.0022843475431175778 )
    end of iter 10
    ================ iteration  11 ===================
    4.2971481765778075
    cost in beginning of iteration 11 : Cost =  4.297148176577806
    --------------------
    -----line search ---
    --------------------
    kappa 0.002192973641392875
    --------------------
    -----line search ---
    --------------------
    kappa 0.0017543789131143001
    --------------------
    -----line search ---
    --------------------
    kappa 0.00140350313049144
    ----------- Jnew in  iteration  11  =  4.240519539842544  (kappa =  0.00140350313049144 )
    
    iter11, Jnew = 4.240519539842544 (kappa =  0.00140350313049144 )
    end of iter 11
    ================ iteration  12 ===================
    4.240519539842537
    cost in beginning of iteration 12 : Cost =  4.240519539842537
    ----------- Jnew in  iteration  12  =  4.1161495921772655  (kappa =  0.0016842037565897282 )
    
    iter12, Jnew = 4.1161495921772655 (kappa =  0.0016842037565897282 )
    end of iter 12
    ================ iteration  13 ===================
    4.1161495921773374
    cost in beginning of iteration 13 : Cost =  4.1161495921773374
    ----------- Jnew in  iteration  13  =  4.105216573071324  (kappa =  0.002021044507907674 )
    
    iter13, Jnew = 4.105216573071324 (kappa =  0.002021044507907674 )
    end of iter 13
    ================ iteration  14 ===================
    4.105216573071334
    cost in beginning of iteration 14 : Cost =  4.105216573071334
    ----------- Jnew in  iteration  14  =  4.030300475898055  (kappa =  0.0024252534094892086 )
    
    iter14, Jnew = 4.030300475898055 (kappa =  0.0024252534094892086 )
    end of iter 14
    ================ iteration  15 ===================
    4.030300475898059
    cost in beginning of iteration 15 : Cost =  4.030300475898059
    --------------------
    -----line search ---
    --------------------
    kappa 0.00232824327310964
    --------------------
    -----line search ---
    --------------------
    kappa 0.0018625946184877122
    ----------- Jnew in  iteration  15  =  4.013368794663212  (kappa =  0.0018625946184877122 )
    
    iter15, Jnew = 4.013368794663212 (kappa =  0.0018625946184877122 )
    end of iter 15
    ================ iteration  16 ===================
    4.01336879466317
    cost in beginning of iteration 16 : Cost =  4.01336879466317
    ----------- Jnew in  iteration  16  =  3.88456630972969  (kappa =  0.0022351135421852545 )
    
    iter16, Jnew = 3.88456630972969 (kappa =  0.0022351135421852545 )
    end of iter 16
    ================ iteration  17 ===================
    3.884566309729684
    cost in beginning of iteration 17 : Cost =  3.884566309729684
    --------------------
    -----line search ---
    --------------------
    kappa 0.0021457090004978444
    --------------------
    -----line search ---
    --------------------
    kappa 0.0017165672003982757
    ----------- Jnew in  iteration  17  =  3.836992925193452  (kappa =  0.0017165672003982757 )
    
    iter17, Jnew = 3.836992925193452 (kappa =  0.0017165672003982757 )
    end of iter 17
    ================ iteration  18 ===================
    3.8369929251934427
    cost in beginning of iteration 18 : Cost =  3.8369929251934427
    ----------- Jnew in  iteration  18  =  3.7363846493231434  (kappa =  0.0020598806404779307 )
    
    iter18, Jnew = 3.7363846493231434 (kappa =  0.0020598806404779307 )
    end of iter 18
    ================ iteration  19 ===================
    3.7363846493232207
    cost in beginning of iteration 19 : Cost =  3.7363846493232207
    --------------------
    -----line search ---
    --------------------
    kappa 0.0019774854148588133
    ----------- Jnew in  iteration  19  =  3.718138572521834  (kappa =  0.0019774854148588133 )
    
    iter19, Jnew = 3.718138572521834 (kappa =  0.0019774854148588133 )
    end of iter 19
    
    [11]:
    
    # After 999 iterations: $J = $
    # <center>
    #    <img src="psi_iter999.jpg" width="800" aligh="center"/>
    # </center>
    
    [ ]: