diff --git a/.github/workflows/TagBot.yml b/.github/workflows/TagBot.yml new file mode 100644 index 00000000..f49313b6 --- /dev/null +++ b/.github/workflows/TagBot.yml @@ -0,0 +1,15 @@ +name: TagBot +on: + issue_comment: + types: + - created + workflow_dispatch: +jobs: + TagBot: + if: github.event_name == 'workflow_dispatch' || github.actor == 'JuliaTagBot' + runs-on: ubuntu-latest + steps: + - uses: JuliaRegistries/TagBot@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + ssh: ${{ secrets.DOCUMENTER_KEY }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..8a33a153 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,55 @@ +name: CI +on: [push, pull_request] +jobs: + test: + name: Tests ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} - ${{ github.event_name }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + version: + - '1.9' + os: + - ubuntu-latest + arch: + - x64 + steps: + - uses: actions/checkout@v2 + - uses: julia-actions/setup-julia@v1 + with: + version: ${{ matrix.version }} + arch: ${{ matrix.arch }} + - uses: actions/cache@v1 + env: + cache-name: cache-artifacts + with: + path: ~/.julia/artifacts + key: ${{ runner.os }}-test-${{ env.cache-name }}-${{ hashFiles('**/Project.toml') }} + restore-keys: | + ${{ runner.os }}-test-${{ env.cache-name }}- + ${{ runner.os }}-test- + ${{ runner.os }}- + - uses: julia-actions/julia-buildpkg@v1 + - uses: julia-actions/julia-runtest@v1 + - uses: julia-actions/julia-processcoverage@v1 + - uses: codecov/codecov-action@v1 + with: + file: ./lcov.info + +# docs: +# name: Documentation +# runs-on: ubuntu-latest +# steps: +# - uses: actions/checkout@v2 +# - uses: julia-actions/setup-julia@v1 +# with: +# version: '1.9' +# - run: | +# julia --project=docs -e ' +# using Pkg +# Pkg.develop(PackageSpec(path=pwd())) +# Pkg.instantiate()' +# - run: julia --project=docs docs/make.jl +# env: +# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} +# DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }} diff --git a/.github/workflows/ci_mpi.yml b/.github/workflows/ci_mpi.yml new file mode 100644 index 00000000..778b40b5 --- /dev/null +++ b/.github/workflows/ci_mpi.yml @@ -0,0 +1,70 @@ +name: CI_MPI +on: [push, pull_request] +jobs: + test: + name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} - ${{ github.event_name }} + runs-on: ${{ matrix.os }} + env: + P4EST_ROOT_DIR: "/opt/p4est/2.2/" + JULIA_PETSC_LIBRARY: "/opt/petsc/3.18/lib/libpetsc" + strategy: + fail-fast: false + matrix: + version: + - '1.9' + os: + - ubuntu-latest + arch: + - x64 + steps: + - uses: actions/checkout@v2 + - name: Cache p4est + id: cache-p4est + uses: actions/cache@v2 + with: + path: ${{env.P4EST_ROOT_DIR}} + key: ${{ runner.os }}-build-${{ env.P4EST_ROOT_DIR }}- + restore-keys: | + ${{ runner.os }}-build-${{ env.P4EST_ROOT_DIR }}- + ${{ runner.os }}-build- + ${{ runner.os }}- + - uses: julia-actions/setup-julia@v1 + with: + version: ${{ matrix.version }} + arch: ${{ matrix.arch }} + - name: Install p4est/petsc dependencies + run: | + sudo apt-get update + sudo apt-get install -y wget gfortran g++ openmpi-bin libopenmpi-dev + - name: Install petsc + run: | + CURR_DIR=$(pwd) + PACKAGE=petsc + VERSION=3.18 + INSTALL_ROOT=/opt + PETSC_INSTALL=$INSTALL_ROOT/$PACKAGE/$VERSION + TAR_FILE=$PACKAGE-$VERSION.tar.gz + URL="https://ftp.mcs.anl.gov/pub/petsc/release-snapshots/" + ROOT_DIR=/tmp + SOURCES_DIR=$ROOT_DIR/$PACKAGE-$VERSION + BUILD_DIR=$SOURCES_DIR/build + wget -q $URL/$TAR_FILE -O $ROOT_DIR/$TAR_FILE + mkdir -p $SOURCES_DIR + tar xzf $ROOT_DIR/$TAR_FILE -C $SOURCES_DIR --strip-components=1 + cd $SOURCES_DIR + ./configure --prefix=$PETSC_INSTALL --with-cc=mpicc --with-cxx=mpicxx --with-fc=mpif90 \ + --download-mumps --download-scalapack --download-parmetis --download-metis \ + --download-ptscotch --with-debugging --with-x=0 --with-shared=1 \ + --with-mpi=1 --with-64-bit-indices + make + make install + - uses: julia-actions/julia-buildpkg@latest + - run: echo $PWD + - run: julia --project=. -e 'using Pkg; Pkg.instantiate();' + - run: julia --project=. -e 'using Pkg; Pkg.add("MPIPreferences")' + - run: julia --project=. -e 'using MPIPreferences; MPIPreferences.use_system_binary()' + - run: julia --project=. -e 'using Pkg; Pkg.build(); Pkg.precompile()' + - run: julia --project=. --color=yes --check-bounds=yes test/mpi/runtests.jl + - uses: codecov/codecov-action@v1 + with: + file: lcov.info diff --git a/.gitignore b/.gitignore index b29fa7e8..b8aba4f7 100644 --- a/.gitignore +++ b/.gitignore @@ -28,4 +28,4 @@ results/* # environment. Manifest.toml LocalPreferences.toml -LSTO_Distributed.so +LevelSetTopOpt.so diff --git a/Deprecated/Deprecated_root_files/Utilities.jl b/Deprecated/Deprecated_root_files/Utilities.jl index 62051593..af5f0792 100755 --- a/Deprecated/Deprecated_root_files/Utilities.jl +++ b/Deprecated/Deprecated_root_files/Utilities.jl @@ -30,7 +30,7 @@ Base.@kwdef struct SmoothErsatzMaterialInterpolation{M<:AbstractFloat} end write_vtk(Ω,path,φh,uh,H) = writevtk(Ω,path,cellfields=["phi"=>φh, - "H(phi)"=>(H ∘ φh),"|nabla(phi))|"=>(norm ∘ ∇(φh)),"uh"=>uh]) + "H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh)),"uh"=>uh]) # Update layout of ghost nodes to match columns of stiffness matrix function correct_ghost_layout(xh::DistributedCellField,cols) diff --git a/Deprecated/Deprecated_root_files/auto_diff_elastic_compliance_parallel.jl b/Deprecated/Deprecated_root_files/auto_diff_elastic_compliance_parallel.jl index 2a4f91a1..5816ae01 100644 --- a/Deprecated/Deprecated_root_files/auto_diff_elastic_compliance_parallel.jl +++ b/Deprecated/Deprecated_root_files/auto_diff_elastic_compliance_parallel.jl @@ -99,7 +99,7 @@ function main(mesh_partition,distribute) # path = dirname(dirname(@__DIR__))*"/results/AutoDiffTesting_Parallel"; # writevtk(Ω,path,cellfields=["phi"=>φh, # "H(phi)"=>(interp.H ∘ φh), - # "|nabla(phi))|"=>(norm ∘ ∇(φh)), + # "|nabla(phi)|"=>(norm ∘ ∇(φh)), # "uh"=>uh, # "J′_analytic"=>FEFunction(U_reg,dF_analytic), # "J′_autodiff"=>FEFunction(U_reg,dF), diff --git a/Deprecated/Deprecated_root_files/inverse_homogenisation_parallel.jl b/Deprecated/Deprecated_root_files/inverse_homogenisation_parallel.jl index 818ff69c..7ffb6e68 100644 --- a/Deprecated/Deprecated_root_files/inverse_homogenisation_parallel.jl +++ b/Deprecated/Deprecated_root_files/inverse_homogenisation_parallel.jl @@ -113,7 +113,7 @@ function main(mesh_partition,distribute) path = dirname(dirname(@__DIR__))*"/results/InvHomLE_AutoDiffTesting_Parallel"; writevtk(Ω,path,cellfields=["phi"=>φh, "H(phi)"=>(interp.H ∘ φh), - "|nabla(phi))|"=>(norm ∘ ∇(φh)), + "|nabla(phi)|"=>(norm ∘ ∇(φh)), "uh1"=>uh[1], "uh2"=>uh[2], "uh3"=>uh[3], diff --git a/Deprecated/Deprecated_scripts/AD_AM_testing.jl b/Deprecated/Deprecated_scripts/AD_AM_testing.jl index 74ffa4ac..c944b317 100644 --- a/Deprecated/Deprecated_scripts/AD_AM_testing.jl +++ b/Deprecated/Deprecated_scripts/AD_AM_testing.jl @@ -1,4 +1,4 @@ -using Gridap, GridapDistributed, GridapPETSc, PartitionedArrays, LSTO_Distributed +using Gridap, GridapDistributed, GridapPETSc, PartitionedArrays, LevelSetTopOpt """ ... @@ -122,13 +122,13 @@ function main() # print_history(it,["J"=>Ji]) # write_history(history,path*"/history.csv") # uhi = get_state(pcfs) - # write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi))|"=>(norm ∘ ∇(φh))];iter_mod=1) + # write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh))];iter_mod=1) # end # it,Ji,_,_ = last(optimiser.history) # print_history(it,["J"=>Ji]) # write_history(optimiser.history,path*"/history.csv") # uhi = get_state(pcfs) - # write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi))|"=>(norm ∘ ∇(φh))];iter_mod=1) + # write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh))];iter_mod=1) @show J_val diff --git a/Deprecated/Deprecated_scripts/inverter_AD_testing.jl b/Deprecated/Deprecated_scripts/inverter_AD_testing.jl index 0f6d831d..79425c83 100644 --- a/Deprecated/Deprecated_scripts/inverter_AD_testing.jl +++ b/Deprecated/Deprecated_scripts/inverter_AD_testing.jl @@ -1,4 +1,4 @@ -using Gridap, GridapDistributed, GridapPETSc, PartitionedArrays, LSTO_Distributed +using Gridap, GridapDistributed, GridapPETSc, PartitionedArrays, LevelSetTopOpt """ (Serial) Inverter mechanism with augmented Lagrangian method in 2D. diff --git a/Deprecated/Deprecated_scripts/main_inverter_(Based on Laurain).jl b/Deprecated/Deprecated_scripts/main_inverter_(Based on Laurain).jl index d18c7dc3..11a8e574 100644 --- a/Deprecated/Deprecated_scripts/main_inverter_(Based on Laurain).jl +++ b/Deprecated/Deprecated_scripts/main_inverter_(Based on Laurain).jl @@ -1,4 +1,4 @@ -using Gridap, GridapDistributed, GridapPETSc, PartitionedArrays, LSTO_Distributed +using Gridap, GridapDistributed, GridapPETSc, PartitionedArrays, LevelSetTopOpt """ (Serial) Inverter mechanism with augmented Lagrangian method in 2D. @@ -99,14 +99,14 @@ function main() print_history(it,["J"=>Ji,"C"=>Ci,"L"=>Li,"λ"=>λi,"Λ"=>Λi]) write_history(history,path*"/history.csv") # uhi = get_state(pcfs) - # write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi))|"=>(norm ∘ ∇(φh)),"uh"=>uhi];iter_mod=1) + # write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh)),"uh"=>uhi];iter_mod=1) end it,Ji,Ci,Li = last(optimiser.history) λi = optimiser.λ; Λi = optimiser.Λ print_history(it,["J"=>Ji,"C"=>Ci,"L"=>Li,"λ"=>λi,"Λ"=>Λi]) write_history(optimiser.history,path*"/history.csv") uhi = get_state(pcfs) - write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi))|"=>(norm ∘ ∇(φh)),"uh"=>uhi];iter_mod=1) + write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh)),"uh"=>uhi];iter_mod=1) end main(); \ No newline at end of file diff --git a/Deprecated/auto_diff_elastic_compliance_serial.jl b/Deprecated/auto_diff_elastic_compliance_serial.jl index f7108447..53bf8681 100644 --- a/Deprecated/auto_diff_elastic_compliance_serial.jl +++ b/Deprecated/auto_diff_elastic_compliance_serial.jl @@ -200,7 +200,7 @@ hilb_rel_error = (abs(dϕh_Ω-v_J_Ω))/abs(v_J_Ω) path = dirname(dirname(@__DIR__))*"/results/AutoDiffTesting"; writevtk(Ω,path,cellfields=["phi"=>φh, "H(phi)"=>(interp.H ∘ φh), - "|nabla(phi))|"=>(norm ∘ ∇(φh)), + "|nabla(phi)|"=>(norm ∘ ∇(φh)), "uh"=>uh, "J′_abs_error"=>abs_error, "J′_rel_error"=>rel_error, diff --git a/Deprecated/elastic_compliance_for_jordi.jl b/Deprecated/elastic_compliance_for_jordi.jl index 6498a8d9..ad149f7f 100644 --- a/Deprecated/elastic_compliance_for_jordi.jl +++ b/Deprecated/elastic_compliance_for_jordi.jl @@ -32,7 +32,7 @@ function main(mesh_partition,distribute) φh = interpolate(x->-sqrt((x[1]-0.5)^2+(x[2]-0.5)^2+(x[3]-0.5)^2)+0.25,V_φ) J,v_J,uh = elastic_compliance(φh,g,C,solve_data,interp,t) display("Objective = $J") - writevtk(Ω,path,cellfields=["phi"=>φh,"H(phi)"=>(interp.H ∘ φh),"|nabla(phi))|"=>(norm ∘ ∇(φh)),"uh"=>uh]) + writevtk(Ω,path,cellfields=["phi"=>φh,"H(phi)"=>(interp.H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh)),"uh"=>uh]) end t end diff --git a/Deprecated/test_periodic_finitediff.jl b/Deprecated/test_periodic_finitediff.jl index 83b04e8e..08d9b16a 100644 --- a/Deprecated/test_periodic_finitediff.jl +++ b/Deprecated/test_periodic_finitediff.jl @@ -157,4 +157,4 @@ v = get_free_dof_values(vh) advect!(φ,v,model,Δ,γ,50) reinit!(φ,model,Δ,0.5,2000,reinit_tol) -writevtk(Ω,(@__DIR__)*"\\Results\\test_reinit_2d_plus_advect",cellfields=["phi"=>φh,"H(phi)"=>(interp.H ∘ φh),"|nabla(phi))|"=>(norm ∘ ∇(φh))]); +writevtk(Ω,(@__DIR__)*"\\Results\\test_reinit_2d_plus_advect",cellfields=["phi"=>φh,"H(phi)"=>(interp.H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh))]); diff --git a/Deprecated/tests_solver.jl b/Deprecated/tests_solver.jl index 0def73f3..f418acb3 100644 --- a/Deprecated/tests_solver.jl +++ b/Deprecated/tests_solver.jl @@ -117,7 +117,7 @@ function main(mesh_partition,distribute) solver = ElasticitySolver(solve_data.V) J,v_J,uh = elastic_compliance(φh,g,C,solve_data,interp,t,solver) display("Objective = $J") - writevtk(Ω,path,cellfields=["phi"=>φh,"H(phi)"=>(interp.H ∘ φh),"|nabla(phi))|"=>(norm ∘ ∇(φh)),"uh"=>uh]) + writevtk(Ω,path,cellfields=["phi"=>φh,"H(phi)"=>(interp.H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh)),"uh"=>uh]) end t end diff --git a/Deprecated/tests_solver2_mpi.jl b/Deprecated/tests_solver2_mpi.jl index 0c717db0..1589a392 100644 --- a/Deprecated/tests_solver2_mpi.jl +++ b/Deprecated/tests_solver2_mpi.jl @@ -12,7 +12,7 @@ import GridapDistributed.DistributedDiscreteModel import GridapDistributed.DistributedMeasure import GridapDistributed.DistributedTriangulation -using LSTO_Distributed +using LevelSetTopOpt function isotropic_3d(E::M,nu::M) where M<:AbstractFloat λ = E*nu/((1+nu)*(1-2nu)); μ = E/(2*(1+nu)) diff --git a/Project.toml b/Project.toml index 5d1bda6a..5bb5edb6 100755 --- a/Project.toml +++ b/Project.toml @@ -1,4 +1,4 @@ -name = "LSTO_Distributed" +name = "LevelSetTopOpt" uuid = "27dd0110-1916-4fd6-8b4b-1bc109db1170" authors = ["Zach Wegert ", "JordiManyer "] version = "0.1.0" @@ -24,7 +24,7 @@ PartitionedArrays = "5a9dfac6-5c52-46f7-8278-5e2210713be9" Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" SparseMatricesCSR = "a0a7dd2c-ebf4-11e9-1f05-cf50bc540ca1" -Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f" +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [extras] MPIPreferences = "3da0fdf6-3ccc-4f1b-acd9-58baa6c99267" diff --git a/README.md b/README.md index 3463e3a4..18d66ae5 100755 --- a/README.md +++ b/README.md @@ -1,346 +1,3 @@ -# LSTO_Distributed +# LevelSetTopOpt.jl -## To-do for paper: - -- [ ] Benchmark and scaling tests -- [ ] Implement optimisation problems and test: - - | **Problem type** | **2D** | **3D** | **Serial** | **MPI** | - |:---------------------------------:|---------|---------|:----------:|---------| - | Minimum thermal compliance | ☑ | ☑ | ☑ | ☑ | - | Minimum elastic compliance | ☑ | ☑ | ☑ | ☑ | - | Inverter mechanism | ☑ | ☑ | ☑ | ☑* | - | Elastic inverse homogenisation | ☑ | ☑** | ☑ | ☑ | - | Minimum NL thermal compliance | ☑ | ☑ | ☑ | ☑* | - | Minimum hyperelastic compliance | ☑ | ☑ | ☑ | ☑* | - -*: 3D only. - -**: Need to test. - -- [x] Testing automatic differentation - * ☑ Test inverter mechanism problem with analytic adjoint. Edit: See discussion below. - * ☑ Compare to ray integrals for multi-material ([Paper](https://doi.org/10.1051/cocv/2013076)) and additive manufacturing ([Paper](https://doi.org/10.1051/m2an/2019056)) problems. Edit: We cannot use AD for these. Related to discussion below. -- [x] Implement HPM ([Paper](https://doi.org/10.1007/s00158-023-03663-0)) -- [x] Implement `NonlinearFEStateMap`. - -## Future work (not this paper) -- [ ] Implement CutFEM in serial using GridapEmbedded. -- [ ] Extend to parallel (I'm sure this will be a massive undertaking) - -## Known bugs/issues -- [x] Higher order FEs breaks `Advection.jl` in parallel. -- [x] `create_dof_permutation` doesn't work for periodic models. -- [x] `MultiFieldFESpace` breaks `ElasticitySolver` -- [x] `NonlinearFEStateMap` breaks in parallel due need for transpose of jacobian. This likely means there is also a bug in `AffineFEStateMap` when dealing with non-symmetric weak forms. See `scripts/nonlinear_adjoint_MWE.jl` -- [x] Inverse homogenisation problems over allocate the stiffness matrix. Only one stiffness matrix should be assembled, then we should create a larger block matrix from this. - -## Other notes -- PETSc's GAMG preconditioner breaks for split Dirichlet DoFs (e.g., x constrained while y free for a single node). There is no simple fix for this. I would recommend instead using MUMPS or another preconditioner. -- There is a known memory leak in Julia (ver>1.9) IO that affects `write_vtk`. For now we occasionally use `gc()`. This will hopefully be sorted in the near future. - -## Some notes on auto differentiation - -The shape derivative of $J^\prime(\Omega)$ can be defined as the Frechet derivative under a mapping $\tilde\Omega = (\boldsymbol{I}+\boldsymbol{\theta})(\Omega)$ at $\boldsymbol{0}$ with - -$$J(\tilde{\Omega})=J(\Omega)+J^\prime(\Omega)(\boldsymbol{\theta})+o(\lVert\boldsymbol{\theta}\rVert).$$ - -Consider some illustrative examples. First suppose that $J_1(\Omega)=\int_\Omega f(\boldsymbol{x})\~\mathrm{d}\boldsymbol{x}$, then - -$$J_1(\tilde{\Omega})=\int_{\tilde{\Omega}}f(\tilde{\boldsymbol{x}})\~\mathrm{d}\boldsymbol{x}=\int_{\Omega}f(\boldsymbol{x}+\boldsymbol{\theta})\left\lvert\frac{\partial\boldsymbol{\tilde{x}}}{\partial\boldsymbol{x}}\right\rvert\~\mathrm{d}\boldsymbol{x}=...=J(\Omega)+\int_{\partial\Omega}f(\boldsymbol{x})\~\boldsymbol{\theta}\cdot\boldsymbol{n}\~\mathrm{d}s+...$$ - -So, for this simple case $$J_1^\prime(\Omega)(\boldsymbol{\theta})=\int_{\partial\Omega}f(\boldsymbol{x})\~\boldsymbol{\theta}\cdot\boldsymbol{n}\~\mathrm{d}s\~\~(\star).$$ For a surface integral $J_2(\Omega)=\int_{\partial\Omega} f(\boldsymbol{x})\~\mathrm{d}s$, one finds in a similar way $$J_2^\prime(\Omega)(\boldsymbol{\theta})=\int_{\partial\Omega}(f\nabla\cdot\boldsymbol{n}+\nabla f\cdot\boldsymbol{n})\~\boldsymbol{\theta}\cdot\boldsymbol{n}\~\mathrm{d}s.$$ - -Suppose that we attribute a signed distance function $\varphi:D\rightarrow\mathbb{R}$ to our domain $\Omega\subset D$ with $\bar{\Omega}=\lbrace \boldsymbol{x}:\varphi(\boldsymbol{x})\leq0\rbrace$ and $\Omega^\complement=\lbrace \boldsymbol{x}:\varphi(\boldsymbol{x})>0\rbrace$. We can then define a smooth characteristic function $\chi_\epsilon:D\rightarrow[\epsilon,1]$ as $\chi_\epsilon(\boldsymbol{x})=(1-H(\varphi(\boldsymbol{x})))+\epsilon H(\varphi(\boldsymbol{x}))$ where $H$ is a smoothed Heaviside function with smoothing radius $\eta$, and $\epsilon\ll1$ allows for an ersatz material approximation. Of course, $\epsilon$ can be taken as zero depending on the computational regime. We can now rewrite $J_1$ over the computational domain $D$ as $J_{1\Omega}(\varphi)=\int_D \chi_\epsilon(\varphi)f(\boldsymbol{x})\~\mathrm{d}\boldsymbol{x}$. Considering the directional derivative of $J_{1\Omega}$ under a variation $\tilde{\varphi}=\varphi+sv$ gives - -$$J_{1\Omega}^\prime(\varphi)(v)=\frac{\mathrm{d}}{\mathrm{d}s} J_{1\Omega}(\varphi+sv)\rvert_{s=0}=\int_D v\chi_\epsilon^\prime(\varphi)f(\boldsymbol{x})\~\mathrm{d}\boldsymbol{x}$$ - -or - -$$J_{1\Omega}^\prime(\varphi)(v)=\int_D (\epsilon-1)vH^\prime(\varphi)f(\boldsymbol{x})\~\mathrm{d}\boldsymbol{x}$$ - -The final result of course does not yet match $(\star)$. Over a fixed computational domain we may relax integrals to be over all of $D$ via $\mathrm{d}s = H'(\varphi)\lvert\nabla\varphi\rvert\~\mathrm{d}\boldsymbol{x}$. In addition suppose we take $\boldsymbol{\theta}=-v\boldsymbol{n}$. Then $(\star)$ can be rewritten as -$$J_1^\prime(\Omega)(v\boldsymbol{n})=-\int_{D}vf(\boldsymbol{x})H'(\varphi)\lvert\nabla\varphi\rvert\~\mathrm{d}s$$ - -As $\varphi$ is a signed distance function we have $\lvert\nabla\varphi\rvert=1$ for $D\setminus\Sigma$ where $\Sigma$ is the skeleton of $\Omega$ and $\Omega^\complement$. Furthermore, $H'(\varphi)$ provides support only within a band of $\partial\Omega$. Therefore, we have that almost everywhere - -$${\huge{|}} J_1^\prime(\Omega)(v\boldsymbol{n}) - J_{1\Omega}^\prime(\varphi)(v){\huge{|}}=O(\epsilon),$$ - -with equaility as $\epsilon\rightarrow0$. - -This is not the case when we consider applying the same process to a surface integral such as $J_2(\Omega)$. In this case, we can never recover $\nabla f$ under a variation of $\varphi$. For argument sake we can take $J_{2\Omega}(\varphi)=\int_D f(\boldsymbol{x})H'(\varphi)\lvert\nabla\varphi\rvert\~\mathrm{d}\boldsymbol{x}$. Then a variation in $\varphi$ gives -$$J_{2\Omega}^\prime(\varphi)(v)=\int_D f(\boldsymbol{x})\left(H^{\prime\prime}(\varphi)\lvert\nabla\varphi\rvert v + H^\prime(\varphi)\frac{\nabla v\cdot\nabla \varphi}{\lvert\nabla\varphi\rvert}\right)\~\mathrm{d}\boldsymbol{x}.$$ -On the other hand, relaxing the shape derivative of $J_2$ in the same way as above gives -$$J_2^\prime(\Omega)(-v\boldsymbol{n})=\int_{D}-\left(f\nabla\cdot\frac{\nabla\varphi}{\lvert\nabla\varphi\rvert}+\nabla f\cdot\frac{\nabla\varphi}{\lvert\nabla\varphi\rvert}\right)vH'(\varphi)\lvert\nabla\varphi\rvert\~\mathrm{d}\boldsymbol{x}.$$ - -### Adding PDE constraints -Consider $\Omega\subset D$ with $J(\Omega)=\int_\Omega j(\boldsymbol{u})\~\mathrm{d}\boldsymbol{x}+\int_{\Gamma_N} l_1(\boldsymbol{u})\~\mathrm{d}s+\int_{\Gamma_R} l_2(\boldsymbol{u})\~\mathrm{d}s$ where $\boldsymbol{u}$ satisfies - -$$ -\begin{aligned} --\mathrm{div}(\boldsymbol{C\varepsilon}(\boldsymbol{u})) &= \boldsymbol{f}\text{ on }\Omega, \\ -\boldsymbol{u} &= \boldsymbol{0}\text{ on }\Gamma_D,\\ -\boldsymbol{C\varepsilon}(\boldsymbol{u})\boldsymbol{n} &= \boldsymbol{0}\text{ on }\Gamma_0,\\ -\boldsymbol{C\varepsilon}(\boldsymbol{u})\boldsymbol{n} &= \boldsymbol{g}\text{ on }\Gamma_N,\\ -\boldsymbol{C\varepsilon}(\boldsymbol{u})\boldsymbol{n} &= -\boldsymbol{w}(\boldsymbol{u})\text{ on }\Gamma_R.\\ -\end{aligned} -$$ - -In the above $\boldsymbol{\varepsilon}$ is the strain tensor, $\boldsymbol{C}$ is the stiffness tensor, $\Gamma_0 = \partial\Omega\setminus(\Gamma_D\cup\Gamma_N\cup\Gamma_R)$, and $\Gamma_D$, $\Gamma_N$, $\Gamma_R$ are required to be fixed. - -#### Shape derivative -Let us first consider the shape derivative of $J$. Disregarding embedding inside the computational domain $D$, the above strong form can be written in weak form as: *Find* $\boldsymbol{u}\in H^1_{\Gamma_D}(\Omega)^d$ *such that* -$$\int_{\Omega} \boldsymbol{C\varepsilon}(\boldsymbol{u})\boldsymbol{\varepsilon}(\boldsymbol{v})\~\mathrm{d}\boldsymbol{x}+\int_{\Gamma_R}\boldsymbol{w}(\boldsymbol{u})\cdot\boldsymbol{v}\~\mathrm{d}s=\int_\Omega \boldsymbol{f}\cdot\boldsymbol{v}\~\mathrm{d}\boldsymbol{x}+\int_{\Gamma_N}\boldsymbol{g}\cdot\boldsymbol{v}\~\mathrm{d}s,\~\forall \boldsymbol{v}\in H^1_{\Gamma_D}(\Omega)^d.$$ - -Following Céa's formal adjoint method we introduce the following Lagrangian - -$$ -\begin{aligned} -\mathcal{L}(\Omega,\boldsymbol{v},\boldsymbol{q})=&\int_\Omega j(\boldsymbol{v})\~\mathrm{d}\boldsymbol{x}+\int_{\Gamma_N} l_1(\boldsymbol{v})\~\mathrm{d}s+\int_{\Gamma_R} l_2(\boldsymbol{v})\~\mathrm{d}s+\int_{\Omega} \boldsymbol{C\varepsilon}(\boldsymbol{v})\boldsymbol{\varepsilon}(\boldsymbol{q})\~\mathrm{d}\boldsymbol{x}\\ -&+\int_{\Gamma_R}\boldsymbol{w}(\boldsymbol{v})\cdot\boldsymbol{q}\~\mathrm{d}s-\int_\Omega \boldsymbol{f}\cdot\boldsymbol{q}\~\mathrm{d}\boldsymbol{x}-\int_{\Gamma_N}\boldsymbol{g}\cdot\boldsymbol{q}\~\mathrm{d}s\\ -&-\int_{\Gamma_D}\boldsymbol{q}\cdot\boldsymbol{C\varepsilon}(\boldsymbol{v})\boldsymbol{n}+\boldsymbol{v}\cdot\boldsymbol{C\varepsilon}(\boldsymbol{q})\boldsymbol{n}\~\mathrm{d}s\\ -\end{aligned} -$$ - -where $\boldsymbol{v},\boldsymbol{q}\in H^1(\mathbb{R}^d)^d$. Requiring stationarity of the Lagrangian and taking a partial derivative of $\mathcal{L}$ with respect to $\boldsymbol{q}$ in the direction $\boldsymbol{\phi}\in H^1(\mathbb{R}^d)^d$ gives - -$$ -\begin{aligned} -0=\frac{\partial\mathcal{L}}{\partial\boldsymbol{q}}(\boldsymbol{\phi})&=\int_{\Omega} \boldsymbol{C\varepsilon}(\boldsymbol{v})\boldsymbol{\varepsilon}(\boldsymbol{\phi})\~\mathrm{d}\boldsymbol{x}+\int_{\Gamma_R}\boldsymbol{w}(\boldsymbol{v})\cdot\boldsymbol{\phi}\~\mathrm{d}s-\int_\Omega \boldsymbol{f}\cdot\boldsymbol{\phi}\~\mathrm{d}\boldsymbol{x}-\int_{\Gamma_N}\boldsymbol{g}\cdot\boldsymbol{\phi}\~\mathrm{d}s\\ -&\quad-\int_{\Gamma_D}\boldsymbol{\phi}\cdot\boldsymbol{C\varepsilon}(\boldsymbol{v})\boldsymbol{n}+\boldsymbol{v}\cdot\boldsymbol{C\varepsilon}(\boldsymbol{\phi})\boldsymbol{n}\~\mathrm{d}s\\ -&=\int_\Omega\boldsymbol{\phi}\cdot(-\mathrm{div}(\boldsymbol{C\varepsilon}(\boldsymbol{u}))-\boldsymbol{f})\~\mathrm{d}\boldsymbol{x}+\int_{\Gamma_R}\boldsymbol{\phi}\cdot(\boldsymbol{C\varepsilon}(\boldsymbol{v})\boldsymbol{n}+\boldsymbol{w}(\boldsymbol{v}))\~\mathrm{d}s\\ -&\quad+\int_{\Gamma_N}\boldsymbol{\phi}\cdot(\boldsymbol{C\varepsilon}(\boldsymbol{v})\boldsymbol{n}-\boldsymbol{g})\~\mathrm{d}s-\int_{\Gamma_D}\boldsymbol{v}\cdot\boldsymbol{C\varepsilon}(\boldsymbol{\phi})\boldsymbol{n}\~\mathrm{d}s+\int_{\Gamma_0}\boldsymbol{\phi}\cdot\boldsymbol{C\varepsilon}(\boldsymbol{v})\boldsymbol{n}\~\mathrm{d}s -\end{aligned} -$$ - -after applying integration by parts. Under a suitable variations of $\boldsymbol{\phi}$, the state equation and boundary conditions are generated as required. In other words, $\boldsymbol{v}$ is given by the solution $\boldsymbol{u}$ to the equations of state. We can derive the adjoint equation by again requiring stationarity of the Lagrangian and taking a partial derivative of $\mathcal{L}$ with respect to $\boldsymbol{v}$ in the direction $\boldsymbol{\phi}\in H^1(\mathbb{R}^d)^d$. This gives - -$$ -\begin{aligned} -0=\frac{\partial\mathcal{L}}{\partial\boldsymbol{v}}(\boldsymbol{\phi})&=\int_\Omega \boldsymbol{\phi}\cdot j^\prime(\boldsymbol{v})\~\mathrm{d}\boldsymbol{x}+\int_{\Gamma_N} \boldsymbol{\phi}\cdot l_1^\prime(\boldsymbol{v})\~\mathrm{d}s+\int_{\Gamma_R} \boldsymbol{\phi}\cdot l_2^\prime(\boldsymbol{v})\~\mathrm{d}s+\int_{\Omega} \boldsymbol{C\varepsilon}(\boldsymbol{\phi})\boldsymbol{\varepsilon}(\boldsymbol{q})\~\mathrm{d}\boldsymbol{x}\\ -&\quad+\int_{\Gamma_R}\boldsymbol{\phi}\cdot J_{\boldsymbol{w}}(\boldsymbol{v})\cdot\boldsymbol{q}\~\mathrm{d}s-\int_{\Gamma_D}\boldsymbol{q}\cdot\boldsymbol{C\varepsilon}(\boldsymbol{\phi})\boldsymbol{n}+\boldsymbol{\phi}\cdot\boldsymbol{C\varepsilon}(\boldsymbol{q})\boldsymbol{n}\~\mathrm{d}s\\ -&=\int_{\Omega} \boldsymbol{\phi}\cdot(-\mathrm{div}(\boldsymbol{C\varepsilon}(\boldsymbol{q}))-j^\prime(\boldsymbol{v}))\~\mathrm{d}\boldsymbol{x}+\int_{\Gamma_N} \boldsymbol{\phi}\cdot (\boldsymbol{C\varepsilon}(\boldsymbol{q})\boldsymbol{n}+l_1^\prime(\boldsymbol{v}))\~\mathrm{d}s\\ -&\quad+\int_{\Gamma_R} \boldsymbol{\phi}\cdot (\boldsymbol{C\varepsilon}(\boldsymbol{q})\boldsymbol{n}+J_{\boldsymbol{w}}(\boldsymbol{v})\cdot\boldsymbol{q}+l_2^\prime(\boldsymbol{v}))\~\mathrm{d}s-\int_{\Gamma_D}\boldsymbol{q}\cdot\boldsymbol{C\varepsilon}(\boldsymbol{\phi})\boldsymbol{n}\~\mathrm{d}s\\ -&\quad+\int_{\Gamma_0}\boldsymbol{\phi}\cdot\boldsymbol{C\varepsilon}(\boldsymbol{q})\boldsymbol{n}\~\mathrm{d}s -\end{aligned} -$$ - -where integration by parts has been applied. The adjoint equations are then generated under suitable variations of $\boldsymbol{\phi}$, while the previous result $\boldsymbol{v}=\boldsymbol{u}$ implies that we can identify a unique $\boldsymbol{q}=\boldsymbol{\lambda}$ that satisfies stationaity. In particular, - -$$ -\begin{aligned} --\mathrm{div}(\boldsymbol{C\varepsilon}(\boldsymbol{\lambda})) &= j^\prime(\boldsymbol{u})\text{ on }\Omega, \\ -\boldsymbol{\lambda} &= \boldsymbol{0}\text{ on }\Gamma_D,\\ -\boldsymbol{C\varepsilon}(\boldsymbol{\lambda})\boldsymbol{n} &= \boldsymbol{0} \text{ on }\Gamma_0,\\ -\boldsymbol{C\varepsilon}(\boldsymbol{\lambda})\boldsymbol{n} &= -l_1^\prime(\boldsymbol{u})\text{ on }\Gamma_N,\\ -\boldsymbol{C\varepsilon}(\boldsymbol{\lambda})\boldsymbol{n} &= -J_{\boldsymbol{w}}(\boldsymbol{u})\cdot\boldsymbol{\lambda}-l_2^\prime(\boldsymbol{u}) \text{ on }\Gamma_R.\\ -\end{aligned} -$$ - -The shape derivative of $J(\Omega)=\mathcal{L}(\Omega,\boldsymbol{u},\boldsymbol{\lambda})$ then follows by application of the chainrule along with the shape derivative results for $J_1$ and $J_2$ above: - -$$ -\begin{aligned} -J^\prime(\Omega)(\boldsymbol{\theta})&=\frac{\partial\mathcal{L}}{\partial\Omega}(\Omega,\boldsymbol{u},\boldsymbol{\lambda})(\boldsymbol{\theta})+\cancel{\frac{\partial\mathcal{L}}{\partial\boldsymbol{v}}(\Omega,\boldsymbol{u},\boldsymbol{\lambda})}(\boldsymbol{u}^\prime(\boldsymbol{\theta}))+\cancel{\frac{\partial\mathcal{L}}{\partial\boldsymbol{q}}(\Omega,\boldsymbol{u},\boldsymbol{\lambda})}(\boldsymbol{\lambda}^\prime(\boldsymbol{\theta}))\\ -&=\int_{\Gamma_0} (\boldsymbol{C\varepsilon}(\boldsymbol{u})\boldsymbol{\varepsilon}(\boldsymbol{\lambda})+j(\boldsymbol{u}) -\boldsymbol{f}\cdot\boldsymbol{\lambda})\~\boldsymbol{\theta}\cdot\boldsymbol{n}\~\mathrm{d}s. -\end{aligned} -$$ - -As required. Note that in the above, we have used that $\boldsymbol{\theta}\cdot\boldsymbol{n}=0$ on $\Gamma_N$, $\Gamma_R$, and $\Gamma_D$. - - -#### Derivative in $\varphi$ -Let us now return to derivatives of $J$ with respect to $\varphi$ over the whole computational domain. As previously, suppose that we rewrite $J$ as - -$$\hat{\mathcal{J}}(\varphi)=\int_D \chi_\epsilon(\varphi)j(\boldsymbol{u})\~\mathrm{d}\boldsymbol{x}+\int_{\Gamma_N} l_1(\boldsymbol{u})\~\mathrm{d}s+\int_{\Gamma_R} l_2(\boldsymbol{u})\~\mathrm{d}s$$ - -where $\boldsymbol{u}$ satisfies the state equations as previously with relaxation over the whole computational domain $D$ as - -$$ -\begin{aligned} --\mathrm{div}(\chi_\epsilon(\varphi)\boldsymbol{C\varepsilon}(\boldsymbol{u})) &= \chi_\epsilon(\varphi)\boldsymbol{f}\text{ on }D, \\ -\boldsymbol{u} &= \boldsymbol{0}\text{ on }\Gamma_D,\\ -\chi_\epsilon(\varphi)\boldsymbol{C\varepsilon}(\boldsymbol{u})\boldsymbol{n} &= \boldsymbol{0}\text{ on }\Gamma_0,\\ -\chi_\epsilon(\varphi)\boldsymbol{C\varepsilon}(\boldsymbol{u})\boldsymbol{n} &= \boldsymbol{g}\text{ on }\Gamma_N,\\ -\chi_\epsilon(\varphi)\boldsymbol{C\varepsilon}(\boldsymbol{u})\boldsymbol{n} &= -\boldsymbol{w}(\boldsymbol{u})\text{ on }\Gamma_R,\\ -\end{aligned} -$$ - -and admits a weak form: *Find* $\boldsymbol{u}\in H^1_{\Gamma_D}(\Omega)^d$ *such that* - -$$ -\int_{\Omega} \chi_\epsilon(\varphi)\boldsymbol{C\varepsilon}(\boldsymbol{u})\boldsymbol{\varepsilon}(\boldsymbol{v})\~\mathrm{d}\boldsymbol{x}+\int_{\Gamma_R}\boldsymbol{w}(\boldsymbol{u})\cdot\boldsymbol{v}\~\mathrm{d}s=\int_D\chi_\epsilon(\varphi)\boldsymbol{f}\cdot\boldsymbol{v}\~\mathrm{d}\boldsymbol{x}+\int_{\Gamma_N}\boldsymbol{g}\cdot\boldsymbol{v}\~\mathrm{d}s,\~\forall \boldsymbol{v}\in H^1_{\Gamma_D}(\Omega)^d. -$$ - -At this stage it is important to note that this is almost verbatim as the case without relaxation over $D$. Indeed, we may follow Céa's method as previously with only a minor adjustment to the Lagrangian that relaxes it over $D$: - -$$ -\begin{aligned} -\hat{\mathcal{L}}(\varphi,\boldsymbol{v},\boldsymbol{q})=&\int_D \chi_\epsilon(\varphi)j(\boldsymbol{v})\~\mathrm{d}\boldsymbol{x}+\int_{\Gamma_N} l_1(\boldsymbol{v})\~\mathrm{d}s+\int_{\Gamma_R} l_2(\boldsymbol{v})\~\mathrm{d}s+\int_{D}\chi_\epsilon(\varphi)\boldsymbol{C\varepsilon}(\boldsymbol{v})\boldsymbol{\varepsilon}(\boldsymbol{q})\~\mathrm{d}\boldsymbol{x}\\ -&+\int_{\Gamma_R}\boldsymbol{w}(\boldsymbol{v})\cdot\boldsymbol{q}\~\mathrm{d}s-\int_D \chi_\epsilon(\varphi) \boldsymbol{f}\cdot\boldsymbol{q}\~\mathrm{d}\boldsymbol{x}-\int_{\Gamma_N}\boldsymbol{g}\cdot\boldsymbol{q}\~\mathrm{d}s\\ -&-\int_{\Gamma_D}\boldsymbol{q}\cdot\chi_\epsilon(\varphi)\boldsymbol{C\varepsilon}(\boldsymbol{v})\boldsymbol{n}+\boldsymbol{v}\cdot\chi_\epsilon(\varphi)\boldsymbol{C\varepsilon}(\boldsymbol{q})\boldsymbol{n}\~\mathrm{d}s.\\ -\end{aligned} -$$ - -As a result the partial derivatives of $\hat{\mathcal{L}}$ in $\boldsymbol{v}$ and $\boldsymbol{q}$ are the same as previously up to relaxation of $D$ with $\chi_\epsilon(\varphi)$, i.e., - -$$ -\begin{aligned} -0=\frac{\partial\hat{\mathcal{L}}}{\partial\boldsymbol{q}}(\boldsymbol{\phi})&=\int_\Omega\boldsymbol{\phi}\cdot(-\mathrm{div}(\chi_\epsilon(\varphi)\boldsymbol{C\varepsilon}(\boldsymbol{u}))-\chi_\epsilon(\varphi)\boldsymbol{f})\~\mathrm{d}\boldsymbol{x}+\int_{\Gamma_R}\boldsymbol{\phi}\cdot(\chi_\epsilon(\varphi)\boldsymbol{C\varepsilon}(\boldsymbol{v})\boldsymbol{n}+\boldsymbol{w}(\boldsymbol{v}))\~\mathrm{d}s\\ -&\quad+\int_{\Gamma_N}\boldsymbol{\phi}\cdot(\chi_\epsilon(\varphi)\boldsymbol{C\varepsilon}(\boldsymbol{v})\boldsymbol{n}-\boldsymbol{g})\~\mathrm{d}s-\int_{\Gamma_D}\boldsymbol{v}\cdot\chi_\epsilon(\varphi)\boldsymbol{C\varepsilon}(\boldsymbol{\phi})\boldsymbol{n}\~\mathrm{d}s+\int_{\Gamma_0}\boldsymbol{\phi}\cdot\chi_\epsilon(\varphi)\boldsymbol{C\varepsilon}(\boldsymbol{v})\boldsymbol{n}\~\mathrm{d}s -\end{aligned} -$$ - -and - -$$ -\begin{aligned} -0=\frac{\partial\hat{\mathcal{L}}}{\partial\boldsymbol{v}}(\boldsymbol{\phi})&=\int_{\Omega} \boldsymbol{\phi}\cdot(-\mathrm{div}(\chi_\epsilon(\varphi)\boldsymbol{C\varepsilon}(\boldsymbol{q}))-\chi_\epsilon(\varphi)j^\prime(\boldsymbol{v}))\~\mathrm{d}\boldsymbol{x}+\int_{\Gamma_N} \boldsymbol{\phi}\cdot (\chi_\epsilon(\varphi)\boldsymbol{C\varepsilon}(\boldsymbol{q})\boldsymbol{n}+l_1^\prime(\boldsymbol{v}))\~\mathrm{d}s\\ -&\quad+\int_{\Gamma_R} \boldsymbol{\phi}\cdot (\chi_\epsilon(\varphi)\boldsymbol{C\varepsilon}(\boldsymbol{q})\boldsymbol{n}+J_{\boldsymbol{w}}(\boldsymbol{v})\cdot\boldsymbol{q}+l_2^\prime(\boldsymbol{v}))\~\mathrm{d}s-\int_{\Gamma_D}\boldsymbol{q}\cdot\chi_\epsilon(\varphi)\boldsymbol{C\varepsilon}(\boldsymbol{\phi})\boldsymbol{n}\~\mathrm{d}s\\ -&\quad+\int_{\Gamma_0}\boldsymbol{\phi}\cdot\chi_\epsilon(\varphi)\boldsymbol{C\varepsilon}(\boldsymbol{q})\boldsymbol{n}\~\mathrm{d}s. -\end{aligned} -$$ - -Therefore, we may identify $\boldsymbol{v}=\boldsymbol{u}$ and $\boldsymbol{q}=\boldsymbol{\lambda}$ where the latter satisfies the adjoint equation - -$$ -\begin{aligned} --\mathrm{div}(\chi_\epsilon(\varphi)\boldsymbol{C\varepsilon}(\boldsymbol{\lambda})) &= \chi_\epsilon(\varphi)j^\prime(\boldsymbol{u})\text{ on }\Omega, \\ -\boldsymbol{\lambda} &= \boldsymbol{0}\text{ on }\Gamma_D,\\ -\chi_\epsilon(\varphi)\boldsymbol{C\varepsilon}(\boldsymbol{\lambda})\boldsymbol{n} &= \boldsymbol{0} \text{ on }\Gamma_0,\\ -\chi_\epsilon(\varphi)\boldsymbol{C\varepsilon}(\boldsymbol{\lambda})\boldsymbol{n} &= -l_1^\prime(\boldsymbol{u})\text{ on }\Gamma_N,\\ -\chi_\epsilon(\varphi)\boldsymbol{C\varepsilon}(\boldsymbol{\lambda})\boldsymbol{n} &= -J_{\boldsymbol{w}}(\boldsymbol{u})\cdot\boldsymbol{\lambda}-l_2^\prime(\boldsymbol{u}) \text{ on }\Gamma_R.\\ -\end{aligned} -$$ - -This exactly as previously up to relaxation over $D$. Finally, The derivative of $\hat{J}(\varphi)=\hat{\mathcal{L}}(\varphi,\boldsymbol{u},\boldsymbol{\lambda})$ then follows by application of the chainrule as previously: - -$$ -\begin{aligned} -\hat{J}^\prime(\varphi)(v)&=\frac{\partial\hat{\mathcal{L}}}{\partial\varphi}(\varphi,\boldsymbol{u},\boldsymbol{\lambda})(v)+\cancel{\frac{\partial\hat{\mathcal{L}}}{\partial\boldsymbol{v}}(\varphi,\boldsymbol{u},\boldsymbol{\lambda})}(\boldsymbol{u}^\prime(v))+\cancel{\frac{\partial\hat{\mathcal{L}}}{\partial\boldsymbol{q}}(\varphi,\boldsymbol{u},\boldsymbol{\lambda})}(\boldsymbol{\lambda}^\prime(v))\\ -&=\int_D v(\boldsymbol{C\varepsilon}(\boldsymbol{u})\boldsymbol{\varepsilon}(\boldsymbol{\lambda})+j(\boldsymbol{u})- \boldsymbol{f}\cdot\boldsymbol{\lambda})\chi^\prime_\epsilon(\varphi)\~\mathrm{d}\boldsymbol{x}\\ -&=\int_D v(\epsilon-1)(\boldsymbol{C\varepsilon}(\boldsymbol{u})\boldsymbol{\varepsilon}(\boldsymbol{\lambda})+j(\boldsymbol{u})- \boldsymbol{f}\cdot\boldsymbol{\lambda})H^\prime(\varphi)\~\mathrm{d}\boldsymbol{x} -\end{aligned} -$$ - -where we have used that $v=0$ on $\Gamma_D$ as previously. As previously taking $\boldsymbol{\theta}=v\boldsymbol{n}$ and relaxing the shape derivative of $J$ over $D$ with a signed distance function $\varphi$ yields: - -$${\huge{|}} J^\prime(\Omega)(v\boldsymbol{n}) - \hat{\mathcal{J}}^\prime(\varphi)(v){\huge{|}}=O(\epsilon).$$ - -### What is not captured by a Gateaux derivative at $\varphi$? -Owing to the fact that we do not fully capture a variation of the doamin in $\varphi$, the Gateaux derivative of certain types of functionals will not match the shape derivative. For example the shape derivative of $J_2$ and the Gateaux derivative of $J_{2\Omega}$ fail to match even in the relaxed setting. Regardless of this, accurate resolution of $J_2$ is difficult owing to the appearance of mean curvature. This problem is further exacebated when a discretisation of the boundary is not available.

- -In addition, functionals of the signed distance function posed over the whole bounding domain $D$ admit a special structure under shape differentiation ([Paper](https://doi.org/10.1051/m2an/2019056)). Such cases are not captured by a Gateaux derivative at $\varphi$ under relaxation. It is unclear whether these cases will be captured by considering a Gateaux derivative of the mapping - -$$ -\varphi \mapsto \int_{\Omega(\varphi)}\varphi\~\mathrm{d}\boldsymbol{x} + \int_{\Omega(\varphi)^\complement}\varphi\~\mathrm{d}\boldsymbol{x}. -$$ - -








---------------------------------------------------------------------------------------------------- -# Deprecated: -## Setup: - -1. Download and activate repo in Julia using `]` followed by `activate .`. -2. Download dependencies using `]` followed by `instantiate`. -3. Configure - * (Desktop) install `mpiexecjl` by running `using MPI; MPI.install_mpiexecjl()` in Julia. Add `.../.julia/bin/` to system path. - * (HPC) It is best to utilise pre-exisiting MPI and PETSc installations on the HPC. The PBS Pro script provided below builds required packges with system settings. -4. Run - * (Desktop) `mpiexecjl` from terminal (GIT Bash if on Windows). E.g., - `mpiexecjl --project=. -n 4 julia MainMPI.jl pipe_setup_2d 2 2` - * (HPC) Queue a run script - see examples provided. - -## Options: -We typically execute `mpiexecjl` from the terminal via the following - `mpiexecjl --project=. -n NP julia MainMPI.jl SETUP_NAME NX NY NZ` -where -* `NP` -> Number of processors to launch, -* `SETUP_NAME` -> Name of setup function, -* `NX` -> Number of partitions in X, -* `NY` -> Number of partitions in Y, -* `NZ` -> Number of partitions in Z (optional). - - -`MainMPI` can also be replaced by `MainSerial` to run the code in serial mode (currently this is quite slow?). - -## Output & Visualisation -Output files consist of a `.csv` file containing the iteration history, `.pvtu` files and `.vtu` files in corresponding subdirectories. For the augmented Lagrangian method the `history.csv` file consists of three columns for the objective, volume, and Lagrangian respectively. - -Results can be visualised in Paraview via the following: - -      Open (any `.pvtu`) -> Apply -> `CTRL + Space` -> `Iso Volume` - -Use the following settings in the `Iso Volume` filter: `Input Scalars` = `phi`, `Minimum` = -1, `Maximum` = 0. - -Bug: Currently `.pvtu` files contain the whole path to the corresponding `.vtu` instead of the relative path. This is caused by passing the whole path to `write_vtk`. Passing the relative path to `write_vtk` includes the location folder in the path to each piece (`.vtu` file). If moving several visualisation files use the command (replace `/` by `\/` in `PATH` below) - `sed -i 's/PATH/./g' *.pvtu` - -## Algorithm Overview: - -The flowchart below gives a rough overview of the augmented Lagrangian-based optimisation algorithm for the problem `pipe_setup_2d` with objective `thermal_compliance`. - -![Screenshot](Images/Algorithm.png) - -## HPC PBS Scripts: - -The following PBS script is provided for setting up `MPI.jl` in conjuction with `GridapDistributed.jl`, `GridapPETSc.jl`, and `MUMPS` on a PBS Pro-based HPC cluster: -``` -#!/bin/bash -l - -#PBS -P LSTO_Distributed_Setup -#PBS -l ncpus=1 -#PBS -l mem=32GB -#PBS -l walltime=00:30:00 -#PBS -j oe - -module load julia/1.8.3-linux-x86_64 -module load mumps/5.5.1-foss-2022a-metis -module load petsc/3.18.6-foss-2022a -module load openmpi/4.1.4-gcc-11.3.0 - -# Path to PETSc Library - replace with appropriate system path -export JULIA_PETSC_LIBRARY=$EBROOTPETSC/lib/libpetsc.so - -cd $PBS_O_WORKDIR - -julia --project=. -e 'using Pkg; Pkg.instantiate()' -echo '------------------------------------------------------------' -julia --project -e 'using Pkg; Pkg.add("MPIPreferences"); - using MPIPreferences; MPIPreferences.use_system_binary()' -echo '------------------------------------------------------------' -julia --project=. -e 'using MPI; MPI.install_mpiexecjl()' -echo '------------------------------------------------------------' -julia --project=. -e 'using Pkg; Pkg.build("MPI"; verbose=true); - Pkg.build("GridapPETSc")' -echo '------------------------------------------------------------' -julia --project=. -e 'using MPI, Gridap, GridapDistributed, GridapPETSc' -echo '------------------------------------------------------------' -echo 'Done' -``` - -The following PBS script can be used to run the code on a PBS Pro-based HPC: -``` -#!/bin/bash -l - -#PBS -P LSTO_Dist_Pipe -#PBS -l cputype=7713 -#PBS -l ncpus=27 -#PBS -l mpiprocs=27 -#PBS -l mem=128GB -#PBS -l walltime=48:00:00 -#PBS -j oe - -module load julia/1.8.3-linux-x86_64 -module load mumps/5.5.1-foss-2022a-metis -module load petsc/3.18.6-foss-2022a -module load openmpi/4.1.4-gcc-11.3.0 - -# Path to PETSc Library - replace with appropriate system path -export JULIA_PETSC_LIBRARY=$EBROOTPETSC/lib/libpetsc.so -export mpiexecjl=~/.julia/bin/mpiexecjl - -cd $PBS_O_WORKDIR - -$mpiexecjl --project=. -n 27 julia ./MainMPI.jl pipe_setup_3d 3 3 3 -``` +... \ No newline at end of file diff --git a/compile/compile.jl b/compile/compile.jl index 66cf735d..60943604 100644 --- a/compile/compile.jl +++ b/compile/compile.jl @@ -1,5 +1,5 @@ using PackageCompiler -create_sysimage([:LSTO_Distributed], - sysimage_path=joinpath(@__DIR__,"..","LSTO_Distributed.so"), +create_sysimage([:LevelSetTopOpt], + sysimage_path=joinpath(@__DIR__,"..","LevelSetTopOpt.so"), precompile_execution_file=joinpath(@__DIR__,"warmup.jl")) diff --git a/compile/warmup.jl b/compile/warmup.jl index a9de56a4..c79ecbd1 100644 --- a/compile/warmup.jl +++ b/compile/warmup.jl @@ -1,4 +1,4 @@ -using Gridap, GridapDistributed, GridapPETSc, PartitionedArrays, LSTO_Distributed +using Gridap, GridapDistributed, GridapPETSc, PartitionedArrays, LevelSetTopOpt """ (MPI) Minimum thermal compliance with Lagrangian method in 2D. @@ -15,14 +15,14 @@ function main(mesh_partition,distribute) ## Parameters order = 1; xmax=ymax=1.0 - prop_Γ_N = 0.4; + prop_Γ_N = 0.2; prop_Γ_D = 0.2 dom = (0,xmax,0,ymax); el_size = (30,30); γ = 0.1; γ_reinit = 0.5; max_steps = 1 - tol = 1/(order^2*10)*prod(inv,minimum(el_size)) # <- change to 1/order^2*prod(...) ? + tol = 1/(order^2*10)/minimum(el_size) # <- change to 1/order^2*prod(...) ? D = 1; η_coeff = 2; α_coeff = 4; @@ -30,11 +30,11 @@ function main(mesh_partition,distribute) ## FE Setup model = CartesianDiscreteModel(ranks,mesh_partition,dom,el_size); - Δ = get_Δ(model) + Δ = get_el_size(model) f_Γ_D(x) = (x[1] ≈ 0.0 && (x[2] <= ymax*prop_Γ_D + eps() || x[2] >= ymax-ymax*prop_Γ_D - eps())) ? true : false; - f_Γ_N(x) = (x[1] ≈ xmax && ymax/2-ymax*prop_Γ_N/4 - eps() <= x[2] <= - ymax/2+ymax*prop_Γ_N/4 + eps()) ? true : false; + f_Γ_N(x) = (x[1] ≈ xmax && ymax/2-ymax*prop_Γ_N/2 - eps() <= x[2] <= + ymax/2+ymax*prop_Γ_N/2 + eps()) ? true : false; update_labels!(1,model,f_Γ_D,"Gamma_D") update_labels!(2,model,f_Γ_N,"Gamma_N") @@ -53,7 +53,7 @@ function main(mesh_partition,distribute) U_reg = TrialFESpace(V_reg,0) ## Create FE functions - φh = interpolate(gen_lsf(4,0.2),V_φ); + φh = interpolate(initial_lsf(4,0.2),V_φ); φ = get_free_dof_values(φh) ## Interpolation and weak form @@ -83,7 +83,7 @@ function main(mesh_partition,distribute) vel_ext = VelocityExtension(a_hilb,U_reg,V_reg) ## Optimiser - _conv_cond = t->LSTO_Distributed.conv_cond(t;coef=1/50); + _conv_cond = t->LevelSetTopOpt.conv_cond(t;coef=1/50); optimiser = AugmentedLagrangian(φ,pcfs,stencil,vel_ext,interp,el_size,γ,γ_reinit,conv_criterion=_conv_cond); for history in optimiser it,Ji,_,_ = last(history) diff --git a/docs/make.jl b/docs/make.jl index dc8e3b8f..b9f14f4e 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -1,10 +1,40 @@ using Documenter -using LSTO_Distributed +using LevelSetTopOpt makedocs( - sitename = "LSTO_Distributed", - format = Documenter.HTML(), - modules = [LSTO_Distributed] + sitename = "LevelSetTopOpt.jl", + format = Documenter.HTML( + prettyurls = false, + collapselevel = 1, + ), + modules = [LevelSetTopOpt], + pages = [ + "Home" => "index.md", + "Usage" => [ + "usage/getting-started.md", + "usage/ad.md", + "usage/petsc.md", + "usage/mpi-mode.md", + ], + "Tutorials" => [ + "tutorials/minimum_thermal_compliance.md", + "tutorials/minimum_elastic_compliance.md", + "tutorials/inverter_mechanism.md", + "tutorials/inverse_homogenisation.md", + ], + "Reference" => [ + "reference/optimisers.md", + "reference/chainrules.md", + "reference/advection.md", + "reference/velext.md", + "reference/io.md", + "reference/utilities.md", + "reference/benchmarking.md" + ], + "Developer Notes" => [ + "dev/shape_der.md", + ] + ], ) # Documenter can also automatically deploy documentation to gh-pages. diff --git a/docs/src/dev/shape_der.md b/docs/src/dev/shape_der.md new file mode 100644 index 00000000..8d4ddb8d --- /dev/null +++ b/docs/src/dev/shape_der.md @@ -0,0 +1,236 @@ +# Shape derivative versus Gâteaux derivative in ``\varphi`` + +The shape derivative of ``J^\prime(\Omega)`` can be defined as the Fréchet derivative under a mapping ``\tilde\Omega = (\boldsymbol{I}+\boldsymbol{\theta})(\Omega)`` at ``\boldsymbol{0}`` with + +```math +J(\tilde{\Omega})=J(\Omega)+J^\prime(\Omega)(\boldsymbol{\theta})+o(\lVert\boldsymbol{\theta}\rVert). +``` + +Consider some illustrative examples. First suppose that ``J_1(\Omega)=\int_\Omega f(\boldsymbol{x})~\mathrm{d}\boldsymbol{x}``, then + +```math +J_1(\tilde{\Omega})=\int_{\tilde{\Omega}}f(\tilde{\boldsymbol{x}})~\mathrm{d}\boldsymbol{x}=\int_{\Omega}f(\boldsymbol{x}+\boldsymbol{\theta})\left\lvert\frac{\partial\boldsymbol{\tilde{x}}}{\partial\boldsymbol{x}}\right\rvert~\mathrm{d}\boldsymbol{x}=...=J(\Omega)+\int_{\partial\Omega}f(\boldsymbol{x})~\boldsymbol{\theta}\cdot\boldsymbol{n}~\mathrm{d}s+... +``` + +So, for this simple case ``J_1^\prime(\Omega)(\boldsymbol{\theta})=\int_{\partial\Omega}f(\boldsymbol{x})~\boldsymbol{\theta}\cdot\boldsymbol{n}~\mathrm{d}s~~(\star).`` For a surface integral ``J_2(\Omega)=\int_{\partial\Omega} f(\boldsymbol{x})~\mathrm{d}s``, one finds in a similar way ``J_2^\prime(\Omega)(\boldsymbol{\theta})=\int_{\partial\Omega}(f\nabla\cdot\boldsymbol{n}+\nabla f\cdot\boldsymbol{n})~\boldsymbol{\theta}\cdot\boldsymbol{n}~\mathrm{d}s.`` + +Suppose that we attribute a signed distance function ``\varphi:D\rightarrow\mathbb{R}`` to our domain ``\Omega\subset D`` with ``\bar{\Omega}=\lbrace \boldsymbol{x}:\varphi(\boldsymbol{x})\leq0\rbrace`` and ``\Omega^\complement=\lbrace \boldsymbol{x}:\varphi(\boldsymbol{x})>0\rbrace``. We can then define a smooth characteristic function ``\chi_\epsilon:\mathbb{R}\rightarrow[\epsilon,1]`` as ``\chi_\epsilon(\varphi)=(1-H(\varphi))+\epsilon H(\varphi)`` where ``H`` is a smoothed Heaviside function with smoothing radius ``\eta``, and ``\epsilon\ll1`` allows for an ersatz material approximation. Of course, ``\epsilon`` can be taken as zero depending on the considered integral and/or computational regime. We can now rewrite ``J_1`` over the computational domain ``D`` as ``J_{1\Omega}(\varphi)=\int_D \chi_\epsilon(\varphi)f(\boldsymbol{x})~\mathrm{d}\boldsymbol{x}``. Considering the directional derivative of ``J_{1\Omega}`` under a variation ``\tilde{\varphi}=\varphi+sv`` gives + +```math +J_{1\Omega}^\prime(\varphi)(v)=\frac{\mathrm{d}}{\mathrm{d}s} J_{1\Omega}(\varphi+sv)\rvert_{s=0}=\int_D v\chi_\epsilon^\prime(\varphi)f(\boldsymbol{x})~\mathrm{d}\boldsymbol{x} +``` + +or + +```math +J_{1\Omega}^\prime(\varphi)(v)=\int_D (\epsilon-1)vH^\prime(\varphi)f(\boldsymbol{x})~\mathrm{d}\boldsymbol{x}. +``` + +The final result of course does not yet match ``(\star)``. Over a fixed computational domain we may relax integrals to be over all of ``D`` via ``\mathrm{d}s = H'(\varphi)\lvert\nabla\varphi\rvert~\mathrm{d}\boldsymbol{x}``. In addition suppose we take ``\boldsymbol{\theta}=-v\boldsymbol{n}``. Then ``(\star)`` can be rewritten as + +```math +J_1^\prime(\Omega)(-v\boldsymbol{n})=-\int_{D}vf(\boldsymbol{x})H'(\varphi)\lvert\nabla\varphi\rvert~\mathrm{d}s. +``` + +As ``\varphi`` is a signed distance function we have ``\lvert\nabla\varphi\rvert=1`` for ``D\setminus\Sigma`` where ``\Sigma`` is the skeleton of ``\Omega`` and ``\Omega^\complement``. Furthermore, ``H'(\varphi)`` provides support only within a band of ``\partial\Omega``. + +!!! tip "Result I" + Therefore, we have that almost everywhere + + ```math + {\huge{|}} J_1^\prime(\Omega)(-v\boldsymbol{n}) - J_{1\Omega}^\prime(\varphi)(v){\huge{|}}=O(\epsilon), + ``` + + with equaility as ``\epsilon\rightarrow0``. + +This is not the case when we consider applying the same process to a surface integral such as ``J_2(\Omega)``. In this case, we can never recover ``\nabla f`` under a variation of ``\varphi``. For argument sake we can take ``J_{2\Omega}(\varphi)=\int_D f(\boldsymbol{x})H'(\varphi)\lvert\nabla\varphi\rvert~\mathrm{d}\boldsymbol{x}``. Then a variation in ``\varphi`` gives + +```math +J_{2\Omega}^\prime(\varphi)(v)=\int_D f(\boldsymbol{x})\left(H^{\prime\prime}(\varphi)\lvert\nabla\varphi\rvert v + H^\prime(\varphi)\frac{\nabla v\cdot\nabla \varphi}{\lvert\nabla\varphi\rvert}\right)~\mathrm{d}\boldsymbol{x}. +``` + +On the other hand, relaxing the shape derivative of ``J_2`` in the same way as above gives + +```math +J_2^\prime(\Omega)(-v\boldsymbol{n})=\int_{D}-\left(f\nabla\cdot\frac{\nabla\varphi}{\lvert\nabla\varphi\rvert}+\nabla f\cdot\frac{\nabla\varphi}{\lvert\nabla\varphi\rvert}\right)vH'(\varphi)\lvert\nabla\varphi\rvert~\mathrm{d}\boldsymbol{x}. +``` + +## Adding PDE constraints +Consider ``\Omega\subset D`` with ``J(\Omega)=\int_\Omega j(\boldsymbol{u})~\mathrm{d}\boldsymbol{x}+\int_{\Gamma_N} l_1(\boldsymbol{u})~\mathrm{d}s+\int_{\Gamma_R} l_2(\boldsymbol{u})~\mathrm{d}s`` where ``\boldsymbol{u}`` satisfies + +```math +\begin{aligned} +-\mathrm{div}(\boldsymbol{C\varepsilon}(\boldsymbol{u})) &= \boldsymbol{f}\text{ on }\Omega, \\ +\boldsymbol{u} &= \boldsymbol{0}\text{ on }\Gamma_D,\\ +\boldsymbol{C\varepsilon}(\boldsymbol{u})\boldsymbol{n} &= \boldsymbol{0}\text{ on }\Gamma_0,\\ +\boldsymbol{C\varepsilon}(\boldsymbol{u})\boldsymbol{n} &= \boldsymbol{g}\text{ on }\Gamma_N,\\ +\boldsymbol{C\varepsilon}(\boldsymbol{u})\boldsymbol{n} &= -\boldsymbol{w}(\boldsymbol{u})\text{ on }\Gamma_R.\\ +\end{aligned} +``` + +In the above ``\boldsymbol{\varepsilon}`` is the strain tensor, ``\boldsymbol{C}`` is the stiffness tensor, ``\Gamma_0 = \partial\Omega\setminus(\Gamma_D\cup\Gamma_N\cup\Gamma_R)``, and ``\Gamma_D``, ``\Gamma_N``, ``\Gamma_R`` are required to be fixed. + +### Shape derivative +Let us first consider the shape derivative of ``J``. Disregarding embedding inside the computational domain ``D``, the above strong form can be written in weak form as: + +``\quad`` *Find* ``\boldsymbol{u}\in H^1_{\Gamma_D}(\Omega)^d`` *such that* + +```math +\int_{\Omega} \boldsymbol{C\varepsilon}(\boldsymbol{u})\boldsymbol{\varepsilon}(\boldsymbol{v})~\mathrm{d}\boldsymbol{x}+\int_{\Gamma_R}\boldsymbol{w}(\boldsymbol{u})\cdot\boldsymbol{v}~\mathrm{d}s=\int_\Omega \boldsymbol{f}\cdot\boldsymbol{v}~\mathrm{d}\boldsymbol{x}+\int_{\Gamma_N}\boldsymbol{g}\cdot\boldsymbol{v}~\mathrm{d}s,~\forall \boldsymbol{v}\in H^1_{\Gamma_D}(\Omega)^d. +``` + +Following Céa's formal adjoint method we introduce the following Lagrangian + +```math +\begin{aligned} +\mathcal{L}(\Omega,\boldsymbol{v},\boldsymbol{q})=&\int_\Omega j(\boldsymbol{v})~\mathrm{d}\boldsymbol{x}+\int_{\Gamma_N} l_1(\boldsymbol{v})~\mathrm{d}s+\int_{\Gamma_R} l_2(\boldsymbol{v})~\mathrm{d}s+\int_{\Omega} \boldsymbol{C\varepsilon}(\boldsymbol{v})\boldsymbol{\varepsilon}(\boldsymbol{q})~\mathrm{d}\boldsymbol{x}\\ +&+\int_{\Gamma_R}\boldsymbol{w}(\boldsymbol{v})\cdot\boldsymbol{q}~\mathrm{d}s-\int_\Omega \boldsymbol{f}\cdot\boldsymbol{q}~\mathrm{d}\boldsymbol{x}-\int_{\Gamma_N}\boldsymbol{g}\cdot\boldsymbol{q}~\mathrm{d}s\\ +&-\int_{\Gamma_D}\boldsymbol{q}\cdot\boldsymbol{C\varepsilon}(\boldsymbol{v})\boldsymbol{n}+\boldsymbol{v}\cdot\boldsymbol{C\varepsilon}(\boldsymbol{q})\boldsymbol{n}~\mathrm{d}s\\ +\end{aligned} +``` + +where ``\boldsymbol{v},\boldsymbol{q}\in H^1(\mathbb{R}^d)^d``. Requiring stationarity of the Lagrangian and taking a partial derivative of ``\mathcal{L}`` with respect to ``\boldsymbol{q}`` in the direction ``\boldsymbol{\phi}\in H^1(\mathbb{R}^d)^d`` gives + +```math +\begin{aligned} +0=\frac{\partial\mathcal{L}}{\partial\boldsymbol{q}}(\boldsymbol{\phi})&=\int_{\Omega} \boldsymbol{C\varepsilon}(\boldsymbol{v})\boldsymbol{\varepsilon}(\boldsymbol{\phi})~\mathrm{d}\boldsymbol{x}+\int_{\Gamma_R}\boldsymbol{w}(\boldsymbol{v})\cdot\boldsymbol{\phi}~\mathrm{d}s-\int_\Omega \boldsymbol{f}\cdot\boldsymbol{\phi}~\mathrm{d}\boldsymbol{x}-\int_{\Gamma_N}\boldsymbol{g}\cdot\boldsymbol{\phi}~\mathrm{d}s\\ +&\quad-\int_{\Gamma_D}\boldsymbol{\phi}\cdot\boldsymbol{C\varepsilon}(\boldsymbol{v})\boldsymbol{n}+\boldsymbol{v}\cdot\boldsymbol{C\varepsilon}(\boldsymbol{\phi})\boldsymbol{n}~\mathrm{d}s\\ +&=\int_\Omega\boldsymbol{\phi}\cdot(-\mathrm{div}(\boldsymbol{C\varepsilon}(\boldsymbol{u}))-\boldsymbol{f})~\mathrm{d}\boldsymbol{x}+\int_{\Gamma_R}\boldsymbol{\phi}\cdot(\boldsymbol{C\varepsilon}(\boldsymbol{v})\boldsymbol{n}+\boldsymbol{w}(\boldsymbol{v}))~\mathrm{d}s\\ +&\quad+\int_{\Gamma_N}\boldsymbol{\phi}\cdot(\boldsymbol{C\varepsilon}(\boldsymbol{v})\boldsymbol{n}-\boldsymbol{g})~\mathrm{d}s-\int_{\Gamma_D}\boldsymbol{v}\cdot\boldsymbol{C\varepsilon}(\boldsymbol{\phi})\boldsymbol{n}~\mathrm{d}s+\int_{\Gamma_0}\boldsymbol{\phi}\cdot\boldsymbol{C\varepsilon}(\boldsymbol{v})\boldsymbol{n}~\mathrm{d}s +\end{aligned} +``` + +after applying integration by parts. Under a suitable variations of ``\boldsymbol{\phi}``, the state equation and boundary conditions are generated as required. In other words, ``\boldsymbol{v}`` is given by the solution ``\boldsymbol{u}`` to the equations of state. We can derive the adjoint equation by again requiring stationarity of the Lagrangian and taking a partial derivative of ``\mathcal{L}`` with respect to ``\boldsymbol{v}`` in the direction ``\boldsymbol{\phi}\in H^1(\mathbb{R}^d)^d``. This gives + +```math +\begin{aligned} +0=\frac{\partial\mathcal{L}}{\partial\boldsymbol{v}}(\boldsymbol{\phi})&=\int_\Omega \boldsymbol{\phi}\cdot j^\prime(\boldsymbol{v})~\mathrm{d}\boldsymbol{x}+\int_{\Gamma_N} \boldsymbol{\phi}\cdot l_1^\prime(\boldsymbol{v})~\mathrm{d}s+\int_{\Gamma_R} \boldsymbol{\phi}\cdot l_2^\prime(\boldsymbol{v})~\mathrm{d}s+\int_{\Omega} \boldsymbol{C\varepsilon}(\boldsymbol{\phi})\boldsymbol{\varepsilon}(\boldsymbol{q})~\mathrm{d}\boldsymbol{x}\\ +&\quad+\int_{\Gamma_R}\boldsymbol{\phi}\cdot w^\prime(\boldsymbol{v})\cdot\boldsymbol{q}~\mathrm{d}s-\int_{\Gamma_D}\boldsymbol{q}\cdot\boldsymbol{C\varepsilon}(\boldsymbol{\phi})\boldsymbol{n}+\boldsymbol{\phi}\cdot\boldsymbol{C\varepsilon}(\boldsymbol{q})\boldsymbol{n}~\mathrm{d}s\\ +&=\int_{\Omega} \boldsymbol{\phi}\cdot(-\mathrm{div}(\boldsymbol{C\varepsilon}(\boldsymbol{q}))-j^\prime(\boldsymbol{v}))~\mathrm{d}\boldsymbol{x}+\int_{\Gamma_N} \boldsymbol{\phi}\cdot (\boldsymbol{C\varepsilon}(\boldsymbol{q})\boldsymbol{n}+l_1^\prime(\boldsymbol{v}))~\mathrm{d}s\\ +&\quad+\int_{\Gamma_R} \boldsymbol{\phi}\cdot (\boldsymbol{C\varepsilon}(\boldsymbol{q})\boldsymbol{n}+w^\prime(\boldsymbol{v})\cdot\boldsymbol{q}+l_2^\prime(\boldsymbol{v}))~\mathrm{d}s-\int_{\Gamma_D}\boldsymbol{q}\cdot\boldsymbol{C\varepsilon}(\boldsymbol{\phi})\boldsymbol{n}~\mathrm{d}s\\ +&\quad+\int_{\Gamma_0}\boldsymbol{\phi}\cdot\boldsymbol{C\varepsilon}(\boldsymbol{q})\boldsymbol{n}~\mathrm{d}s +\end{aligned} +``` + +where integration by parts has been applied. The adjoint equations are then generated under suitable variations of ``\boldsymbol{\phi}``, while the previous result ``\boldsymbol{v}=\boldsymbol{u}`` implies that we can identify a unique ``\boldsymbol{q}=\boldsymbol{\lambda}`` that satisfies stationaity. In particular, + +```math +\begin{aligned} +-\mathrm{div}(\boldsymbol{C\varepsilon}(\boldsymbol{\lambda})) &= j^\prime(\boldsymbol{u})\text{ on }\Omega, \\ +\boldsymbol{\lambda} &= \boldsymbol{0}\text{ on }\Gamma_D,\\ +\boldsymbol{C\varepsilon}(\boldsymbol{\lambda})\boldsymbol{n} &= \boldsymbol{0} \text{ on }\Gamma_0,\\ +\boldsymbol{C\varepsilon}(\boldsymbol{\lambda})\boldsymbol{n} &= -l_1^\prime(\boldsymbol{u})\text{ on }\Gamma_N,\\ +\boldsymbol{C\varepsilon}(\boldsymbol{\lambda})\boldsymbol{n} &= -w^\prime(\boldsymbol{u})\cdot\boldsymbol{\lambda}-l_2^\prime(\boldsymbol{u}) \text{ on }\Gamma_R.\\ +\end{aligned} +``` + +The shape derivative of ``J(\Omega)=\mathcal{L}(\Omega,\boldsymbol{u},\boldsymbol{\lambda})`` then follows by application of the chainrule along with the shape derivative results for ``J_1`` and ``J_2`` above: + +```math +\begin{aligned} +J^\prime(\Omega)(\boldsymbol{\theta})&=\frac{\partial\mathcal{L}}{\partial\Omega}(\Omega,\boldsymbol{u},\boldsymbol{\lambda})(\boldsymbol{\theta})+\cancel{\frac{\partial\mathcal{L}}{\partial\boldsymbol{v}}(\Omega,\boldsymbol{u},\boldsymbol{\lambda})}(\boldsymbol{u}^\prime(\boldsymbol{\theta}))+\cancel{\frac{\partial\mathcal{L}}{\partial\boldsymbol{q}}(\Omega,\boldsymbol{u},\boldsymbol{\lambda})}(\boldsymbol{\lambda}^\prime(\boldsymbol{\theta}))\\ +&=\int_{\Gamma_0} (\boldsymbol{C\varepsilon}(\boldsymbol{u})\boldsymbol{\varepsilon}(\boldsymbol{\lambda})+j(\boldsymbol{u}) -\boldsymbol{f}\cdot\boldsymbol{\lambda})~\boldsymbol{\theta}\cdot\boldsymbol{n}~\mathrm{d}s. +\end{aligned} +``` + +As required. Note that in the above, we have used that ``\boldsymbol{\theta}\cdot\boldsymbol{n}=0`` on ``\Gamma_N``, ``\Gamma_R``, and ``\Gamma_D``. + + +### Gâteaux derivative in ``\varphi`` +Let us now return to derivatives of ``J`` with respect to ``\varphi`` over the whole computational domain. As previously, suppose that we rewrite ``J`` as + +```math +\hat{\mathcal{J}}(\varphi)=\int_D (1-H(\varphi))j(\boldsymbol{u})~\mathrm{d}\boldsymbol{x}+\int_{\Gamma_N} l_1(\boldsymbol{u})~\mathrm{d}s+\int_{\Gamma_R} l_2(\boldsymbol{u})~\mathrm{d}s +``` + +where ``\boldsymbol{u}`` satisfies the state equations as previously with relaxation over the whole computational domain ``D`` as + +```math +\begin{aligned} +-\mathrm{div}(\chi_\epsilon(\varphi)\boldsymbol{C\varepsilon}(\boldsymbol{u})) &= (1-H(\varphi))\boldsymbol{f}\text{ on }D, \\ +\boldsymbol{u} &= \boldsymbol{0}\text{ on }\Gamma_D,\\ +\chi_\epsilon(\varphi)\boldsymbol{C\varepsilon}(\boldsymbol{u})\boldsymbol{n} &= \boldsymbol{0}\text{ on }\Gamma_0,\\ +\chi_\epsilon(\varphi)\boldsymbol{C\varepsilon}(\boldsymbol{u})\boldsymbol{n} &= \boldsymbol{g}\text{ on }\Gamma_N,\\ +\chi_\epsilon(\varphi)\boldsymbol{C\varepsilon}(\boldsymbol{u})\boldsymbol{n} &= -\boldsymbol{w}(\boldsymbol{u})\text{ on }\Gamma_R,\\ +\end{aligned} +``` + +and admits a weak form: *Find* ``\boldsymbol{u}\in H^1_{\Gamma_D}(\Omega)^d`` *such that* + +```math +\int_D \chi_\epsilon(\varphi)\boldsymbol{C\varepsilon}(\boldsymbol{u})\boldsymbol{\varepsilon}(\boldsymbol{v})~\mathrm{d}\boldsymbol{x}+\int_{\Gamma_R}\boldsymbol{w}(\boldsymbol{u})\cdot\boldsymbol{v}~\mathrm{d}s=\int_D(1-H(\varphi))\boldsymbol{f}\cdot\boldsymbol{v}~\mathrm{d}\boldsymbol{x}+\int_{\Gamma_N}\boldsymbol{g}\cdot\boldsymbol{v}~\mathrm{d}s,~\forall \boldsymbol{v}\in H^1_{\Gamma_D}(\Omega)^d. +``` + +At this stage it is important to note that this is almost verbatim as the case without relaxation over ``D``. Indeed, we may follow Céa's method as previously with only a minor adjustment to the Lagrangian that relaxes it over ``D``: + +```math +\begin{aligned} +\hat{\mathcal{L}}(\varphi,\boldsymbol{v},\boldsymbol{q})=&\int_D (1-H(\varphi))j(\boldsymbol{v})~\mathrm{d}\boldsymbol{x}+\int_{\Gamma_N} l_1(\boldsymbol{v})~\mathrm{d}s+\int_{\Gamma_R} l_2(\boldsymbol{v})~\mathrm{d}s+\int_{D}\chi_\epsilon(\varphi)\boldsymbol{C\varepsilon}(\boldsymbol{v})\boldsymbol{\varepsilon}(\boldsymbol{q})~\mathrm{d}\boldsymbol{x}\\ +&+\int_{\Gamma_R}\boldsymbol{w}(\boldsymbol{v})\cdot\boldsymbol{q}~\mathrm{d}s-\int_D (1-H(\varphi)) \boldsymbol{f}\cdot\boldsymbol{q}~\mathrm{d}\boldsymbol{x}-\int_{\Gamma_N}\boldsymbol{g}\cdot\boldsymbol{q}~\mathrm{d}s\\ +&-\int_{\Gamma_D}\boldsymbol{q}\cdot\chi_\epsilon(\varphi)\boldsymbol{C\varepsilon}(\boldsymbol{v})\boldsymbol{n}+\boldsymbol{v}\cdot\chi_\epsilon(\varphi)\boldsymbol{C\varepsilon}(\boldsymbol{q})\boldsymbol{n}~\mathrm{d}s.\\ +\end{aligned} +``` + +As a result the partial derivatives of ``\hat{\mathcal{L}}`` in ``\boldsymbol{v}`` and ``\boldsymbol{q}`` are the same as previously up to relaxation over ``D``, i.e., + +```math +\begin{aligned} +0=\frac{\partial\hat{\mathcal{L}}}{\partial\boldsymbol{q}}(\boldsymbol{\phi})&=\int_D\boldsymbol{\phi}\cdot(-\mathrm{div}(\chi_\epsilon(\varphi)\boldsymbol{C\varepsilon}(\boldsymbol{u}))-(1-H(\varphi))\boldsymbol{f})~\mathrm{d}\boldsymbol{x}+\int_{\Gamma_R}\boldsymbol{\phi}\cdot(\chi_\epsilon(\varphi)\boldsymbol{C\varepsilon}(\boldsymbol{v})\boldsymbol{n}+\boldsymbol{w}(\boldsymbol{v}))~\mathrm{d}s\\ +&\quad+\int_{\Gamma_N}\boldsymbol{\phi}\cdot(\chi_\epsilon(\varphi)\boldsymbol{C\varepsilon}(\boldsymbol{v})\boldsymbol{n}-\boldsymbol{g})~\mathrm{d}s-\int_{\Gamma_D}\boldsymbol{v}\cdot\chi_\epsilon(\varphi)\boldsymbol{C\varepsilon}(\boldsymbol{\phi})\boldsymbol{n}~\mathrm{d}s+\int_{\Gamma_0}\boldsymbol{\phi}\cdot\chi_\epsilon(\varphi)\boldsymbol{C\varepsilon}(\boldsymbol{v})\boldsymbol{n}~\mathrm{d}s +\end{aligned} +``` + +and + +```math +\begin{aligned} +0=\frac{\partial\hat{\mathcal{L}}}{\partial\boldsymbol{v}}(\boldsymbol{\phi})&=\int_D \boldsymbol{\phi}\cdot(-\mathrm{div}(\chi_\epsilon(\varphi)\boldsymbol{C\varepsilon}(\boldsymbol{q}))-(1-H(\varphi))j^\prime(\boldsymbol{v}))~\mathrm{d}\boldsymbol{x}+\int_{\Gamma_N} \boldsymbol{\phi}\cdot (\chi_\epsilon(\varphi)\boldsymbol{C\varepsilon}(\boldsymbol{q})\boldsymbol{n}+l_1^\prime(\boldsymbol{v}))~\mathrm{d}s\\ +&\quad+\int_{\Gamma_R} \boldsymbol{\phi}\cdot (\chi_\epsilon(\varphi)\boldsymbol{C\varepsilon}(\boldsymbol{q})\boldsymbol{n}+w^\prime(\boldsymbol{v})\cdot\boldsymbol{q}+l_2^\prime(\boldsymbol{v}))~\mathrm{d}s-\int_{\Gamma_D}\boldsymbol{q}\cdot\chi_\epsilon(\varphi)\boldsymbol{C\varepsilon}(\boldsymbol{\phi})\boldsymbol{n}~\mathrm{d}s\\ +&\quad+\int_{\Gamma_0}\boldsymbol{\phi}\cdot\chi_\epsilon(\varphi)\boldsymbol{C\varepsilon}(\boldsymbol{q})\boldsymbol{n}~\mathrm{d}s. +\end{aligned} +``` + +Therefore, we may identify ``\boldsymbol{v}=\boldsymbol{u}`` and ``\boldsymbol{q}=\boldsymbol{\lambda}`` where the latter satisfies the adjoint equation + +```math +\begin{aligned} +-\mathrm{div}(\chi_\epsilon(\varphi)\boldsymbol{C\varepsilon}(\boldsymbol{\lambda})) &= (1-H(\varphi))j^\prime(\boldsymbol{u})\text{ on }\Omega, \\ +\boldsymbol{\lambda} &= \boldsymbol{0}\text{ on }\Gamma_D,\\ +\chi_\epsilon(\varphi)\boldsymbol{C\varepsilon}(\boldsymbol{\lambda})\boldsymbol{n} &= \boldsymbol{0} \text{ on }\Gamma_0,\\ +\chi_\epsilon(\varphi)\boldsymbol{C\varepsilon}(\boldsymbol{\lambda})\boldsymbol{n} &= -l_1^\prime(\boldsymbol{u})\text{ on }\Gamma_N,\\ +\chi_\epsilon(\varphi)\boldsymbol{C\varepsilon}(\boldsymbol{\lambda})\boldsymbol{n} &= -w^\prime(\boldsymbol{u})\cdot\boldsymbol{\lambda}-l_2^\prime(\boldsymbol{u}) \text{ on }\Gamma_R.\\ +\end{aligned} +``` + +This exactly as previously up to relaxation over ``D``. Finally, The derivative of ``\hat{J}(\varphi)=\hat{\mathcal{L}}(\varphi,\boldsymbol{u},\boldsymbol{\lambda})`` then follows by application of the chainrule as previously: + +```math +\begin{aligned} +\hat{J}^\prime(\varphi)(v)&=\frac{\partial\hat{\mathcal{L}}}{\partial\varphi}(\varphi,\boldsymbol{u},\boldsymbol{\lambda})(v)+\cancel{\frac{\partial\hat{\mathcal{L}}}{\partial\boldsymbol{v}}(\varphi,\boldsymbol{u},\boldsymbol{\lambda})}(\boldsymbol{u}^\prime(v))+\cancel{\frac{\partial\hat{\mathcal{L}}}{\partial\boldsymbol{q}}(\varphi,\boldsymbol{u},\boldsymbol{\lambda})}(\boldsymbol{\lambda}^\prime(v))\\ +&=\int_D v(\chi^\prime_\epsilon(\varphi)\boldsymbol{C\varepsilon}(\boldsymbol{u})\boldsymbol{\varepsilon}(\boldsymbol{\lambda})+(-H^\prime(\varphi))j(\boldsymbol{u})- (-H^\prime(\varphi))\boldsymbol{f}\cdot\boldsymbol{\lambda})~\mathrm{d}\boldsymbol{x}\\ +&=-\int_D v(\boldsymbol{C\varepsilon}(\boldsymbol{u})\boldsymbol{\varepsilon}(\boldsymbol{\lambda})+j(\boldsymbol{u})- \boldsymbol{f}\cdot\boldsymbol{\lambda})H^\prime(\varphi)~\mathrm{d}\boldsymbol{x}+\epsilon\int_D v\boldsymbol{C\varepsilon}(\boldsymbol{u})\boldsymbol{\varepsilon}(\boldsymbol{\lambda})H^\prime(\varphi)~\mathrm{d}\boldsymbol{x} +\end{aligned} +``` + +where we have used that ``v=0`` on ``\Gamma_D`` as previously. As previously taking ``\boldsymbol{\theta}=v\boldsymbol{n}`` and relaxing the shape derivative of ``J`` over ``D`` with a signed distance function ``\varphi`` yields: + +!!! tip "Result II" + ```math + {\huge{|}} J^\prime(\Omega)(v\boldsymbol{n}) - \hat{\mathcal{J}}^\prime(\varphi)(v){\huge{|}}=O(\epsilon). + ``` + +As required. + +## What is not captured by a Gâteaux derivative at ``\varphi``? +Owing to a fixed computational regime we do not capture a variation of the domain ``\Omega`` in ``\varphi``, so the Gâteaux derivative of certain types of functionals in this context will not match the shape derivative. For example, in the discussion above the shape derivative of ``J_2`` and the Gâteaux derivative of ``J_{2\Omega}`` fail to match even in the relaxed setting. Regardless of this, accurate resolution of ``J_2`` is difficult owing to the appearance of mean curvature. This problem is further exacerbated when a discretisation of the boundary is not available. + +In addition, functionals of the signed distance function posed over the whole bounding domain ``D`` admit a special structure under shape differentiation ([Paper](https://doi.org/10.1051/m2an/2019056)). Such cases are not captured by a Gâteaux derivative at ``\varphi`` under relaxation. + +!!! note + In future, we plan to implement CellFEM via GridapEmbedded in LevelSetTopOpt. This will enable Gâteaux derivative of the mapping + + ```math + \varphi \mapsto \int_{\Omega(\varphi)}f(\varphi)~\mathrm{d}\boldsymbol{x} + \int_{\Omega(\varphi)^\complement}f(\varphi)~\mathrm{d}\boldsymbol{x}. + ``` + + We expect this to rectify the discussion above. \ No newline at end of file diff --git a/docs/src/index.md b/docs/src/index.md index 434388c5..c26e8171 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -1,7 +1,6 @@ -# LSTO_Distributed.jl +# LevelSetTopOpt.jl +Welcome to the documentation for LevelSetTopOpt.jl! -Documentation for LSTO_Distributed.jl +# What -```@autodocs -Modules = [LSTO_Distributed,] -``` \ No newline at end of file +# Why diff --git a/docs/src/reference/advection.md b/docs/src/reference/advection.md new file mode 100644 index 00000000..46166d58 --- /dev/null +++ b/docs/src/reference/advection.md @@ -0,0 +1,25 @@ +# Advection + +## `AdvectionStencil` +```@docs +LevelSetTopOpt.AdvectionStencil +LevelSetTopOpt.AdvectionStencil(stencil::LevelSetTopOpt.Stencil,model,space,tol=1.e-3,max_steps=100,max_steps_reinit=2000) +LevelSetTopOpt.advect! +LevelSetTopOpt.reinit! +``` + +## Stencils + +```@docs +LevelSetTopOpt.FirstOrderStencil +``` + +## Custom `Stencil` + +```@docs +LevelSetTopOpt.Stencil +LevelSetTopOpt.advect!(::LevelSetTopOpt.Stencil,φ,vel,Δt,Δx,isperiodic,caches) +LevelSetTopOpt.reinit!(::LevelSetTopOpt.Stencil,φ_new,φ,vel,Δt,Δx,isperiodic,caches) +LevelSetTopOpt.compute_Δt(::LevelSetTopOpt.Stencil,φ,vel) +LevelSetTopOpt.allocate_caches(::LevelSetTopOpt.Stencil,φ,vel) +``` \ No newline at end of file diff --git a/docs/src/reference/benchmarking.md b/docs/src/reference/benchmarking.md new file mode 100644 index 00000000..e5e6d42e --- /dev/null +++ b/docs/src/reference/benchmarking.md @@ -0,0 +1,15 @@ +# Benchmarking + +```@docs +LevelSetTopOpt.benchmark +``` + +## Existing benchmark methods +```@docs +LevelSetTopOpt.benchmark_optimizer +LevelSetTopOpt.benchmark_forward_problem +LevelSetTopOpt.benchmark_advection +LevelSetTopOpt.benchmark_reinitialisation +LevelSetTopOpt.benchmark_velocity_extension +LevelSetTopOpt.benchmark_hilbertian_projection_map +``` \ No newline at end of file diff --git a/docs/src/reference/chainrules.md b/docs/src/reference/chainrules.md new file mode 100644 index 00000000..36559333 --- /dev/null +++ b/docs/src/reference/chainrules.md @@ -0,0 +1,78 @@ +# ChainRules + +## `PDEConstrainedFunctionals` + +```@docs +LevelSetTopOpt.PDEConstrainedFunctionals +LevelSetTopOpt.evaluate! +LevelSetTopOpt.evaluate_functionals! +LevelSetTopOpt.evaluate_derivatives! +LevelSetTopOpt.get_state +``` + +## `StateParamIntegrandWithMeasure` + +```@docs +LevelSetTopOpt.StateParamIntegrandWithMeasure +LevelSetTopOpt.rrule(u_to_j::LevelSetTopOpt.StateParamIntegrandWithMeasure,uh,φh) +``` + +## Implemented types of `AbstractFEStateMap` + +```@docs +LevelSetTopOpt.AbstractFEStateMap +``` + +### `AffineFEStateMap` +```@docs +LevelSetTopOpt.AffineFEStateMap +LevelSetTopOpt.AffineFEStateMap(a::Function,l::Function,U,V,V_φ,U_reg,φh,dΩ...;assem_U = SparseMatrixAssembler(U,V),assem_adjoint = SparseMatrixAssembler(V,U),assem_deriv = SparseMatrixAssembler(U_reg,U_reg),ls::LinearSolver = LUSolver(),adjoint_ls::LinearSolver = LUSolver()) +``` + +### `NonlinearFEStateMap` +```@docs +LevelSetTopOpt.NonlinearFEStateMap +LevelSetTopOpt.NonlinearFEStateMap(res::Function,U,V,V_φ,U_reg,φh,dΩ...;assem_U = SparseMatrixAssembler(U,V),assem_adjoint = SparseMatrixAssembler(V,U),assem_deriv = SparseMatrixAssembler(U_reg,U_reg),nls::NonlinearSolver = NewtonSolver(LUSolver();maxiter=50,rtol=1.e-8,verbose=true),adjoint_ls::LinearSolver = LUSolver()) +``` + +### `RepeatingAffineFEStateMap` +```@docs +LevelSetTopOpt.RepeatingAffineFEStateMap +LevelSetTopOpt.RepeatingAffineFEStateMap(nblocks::Int,a::Function,l::Vector{<:Function},U0,V0,V_φ,U_reg,φh,dΩ...;assem_U = SparseMatrixAssembler(U0,V0),assem_adjoint = SparseMatrixAssembler(V0,U0),assem_deriv = SparseMatrixAssembler(U_reg,U_reg),ls::LinearSolver = LUSolver(),adjoint_ls::LinearSolver = LUSolver()) +``` + +## Advanced + +### Inheriting from `AbstractFEStateMap` + +#### Existing methods +```@docs +LevelSetTopOpt.rrule(φ_to_u::LevelSetTopOpt.AbstractFEStateMap,φh) +LevelSetTopOpt.pullback +``` + +#### Required to implement +```@docs +LevelSetTopOpt.forward_solve! +LevelSetTopOpt.adjoint_solve! +LevelSetTopOpt.update_adjoint_caches! +LevelSetTopOpt.dRdφ +LevelSetTopOpt.get_state(::LevelSetTopOpt.AbstractFEStateMap) +LevelSetTopOpt.get_measure +LevelSetTopOpt.get_spaces +LevelSetTopOpt.get_assemblers +LevelSetTopOpt.get_trial_space +LevelSetTopOpt.get_test_space +LevelSetTopOpt.get_aux_space +LevelSetTopOpt.get_deriv_space +LevelSetTopOpt.get_pde_assembler +LevelSetTopOpt.get_deriv_assembler +``` + +### `IntegrandWithMeasure` + +```@docs +LevelSetTopOpt.IntegrandWithMeasure +LevelSetTopOpt.gradient +LevelSetTopOpt.jacobian +``` \ No newline at end of file diff --git a/docs/src/reference/io.md b/docs/src/reference/io.md new file mode 100644 index 00000000..cc6fccc6 --- /dev/null +++ b/docs/src/reference/io.md @@ -0,0 +1,24 @@ +# IO + +## Optimiser history and visualisation +```@docs +LevelSetTopOpt.write_history +``` + +```@docs +LevelSetTopOpt.write_vtk +``` + +## Object IO in serial +```@docs +LevelSetTopOpt.save +LevelSetTopOpt.load +LevelSetTopOpt.load! +``` + +## Object IO in parallel +```@docs +LevelSetTopOpt.psave +LevelSetTopOpt.pload +LevelSetTopOpt.pload! +``` \ No newline at end of file diff --git a/docs/src/reference/optimisers.md b/docs/src/reference/optimisers.md new file mode 100644 index 00000000..f886ccf2 --- /dev/null +++ b/docs/src/reference/optimisers.md @@ -0,0 +1,33 @@ +# Optimisers + +## Lagrangian & Augmented Lagrangian method +```@autodocs +Modules = [LevelSetTopOpt] +Pages = ["Optimisers/AugmentedLagrangian.jl"] +``` + +## Hilbertian projection method +```@autodocs +Modules = [LevelSetTopOpt] +Pages = ["Optimisers/HilbertianProjection.jl"] +``` + +```@autodocs +Modules = [LevelSetTopOpt] +Pages = ["Optimisers/OrthogonalisationMaps.jl"] +``` + +## Optimiser history +```@docs +LevelSetTopOpt.OptimiserHistory +LevelSetTopOpt.OptimiserHistorySlice +``` + +## Custom optimiser +```@docs +LevelSetTopOpt.Optimiser +LevelSetTopOpt.iterate(::LevelSetTopOpt.Optimiser) +LevelSetTopOpt.iterate(::LevelSetTopOpt.Optimiser,state) +LevelSetTopOpt.get_history(::LevelSetTopOpt.Optimiser) +LevelSetTopOpt.converged(::LevelSetTopOpt.Optimiser) +``` \ No newline at end of file diff --git a/docs/src/reference/utilities.md b/docs/src/reference/utilities.md new file mode 100644 index 00000000..02bc4ba7 --- /dev/null +++ b/docs/src/reference/utilities.md @@ -0,0 +1,19 @@ +# Utilities + +## Ersatz material interpolation +```@docs +LevelSetTopOpt.SmoothErsatzMaterialInterpolation +``` + +## Mesh labelling +```@docs +LevelSetTopOpt.update_labels! +``` + +## Helpers + +```@docs +LevelSetTopOpt.initial_lsf +LevelSetTopOpt.isotropic_elast_tensor +LevelSetTopOpt.get_el_Δ +``` \ No newline at end of file diff --git a/docs/src/reference/velext.md b/docs/src/reference/velext.md new file mode 100644 index 00000000..7e94e680 --- /dev/null +++ b/docs/src/reference/velext.md @@ -0,0 +1,6 @@ +# Velocity extension + +```@autodocs +Modules = [LevelSetTopOpt] +Pages = ["VelocityExtension.jl"] +``` \ No newline at end of file diff --git a/docs/src/tutorials/2d_min_thermal_comp_final_struc.png b/docs/src/tutorials/2d_min_thermal_comp_final_struc.png new file mode 100644 index 00000000..bf3d962f Binary files /dev/null and b/docs/src/tutorials/2d_min_thermal_comp_final_struc.png differ diff --git a/docs/src/tutorials/2d_min_thermal_comp_initial_lsf_combined.png b/docs/src/tutorials/2d_min_thermal_comp_initial_lsf_combined.png new file mode 100644 index 00000000..eafccc33 Binary files /dev/null and b/docs/src/tutorials/2d_min_thermal_comp_initial_lsf_combined.png differ diff --git a/docs/src/tutorials/2d_min_thermal_comp_setup.png b/docs/src/tutorials/2d_min_thermal_comp_setup.png new file mode 100644 index 00000000..ef4aa09f Binary files /dev/null and b/docs/src/tutorials/2d_min_thermal_comp_setup.png differ diff --git a/docs/src/tutorials/2d_min_thermal_nl_final_struc.png b/docs/src/tutorials/2d_min_thermal_nl_final_struc.png new file mode 100644 index 00000000..fc398a36 Binary files /dev/null and b/docs/src/tutorials/2d_min_thermal_nl_final_struc.png differ diff --git a/docs/src/tutorials/3d_min_thermal_combined.png b/docs/src/tutorials/3d_min_thermal_combined.png new file mode 100644 index 00000000..fecf49c0 Binary files /dev/null and b/docs/src/tutorials/3d_min_thermal_combined.png differ diff --git a/docs/src/tutorials/3d_min_thermal_comp.png b/docs/src/tutorials/3d_min_thermal_comp.png new file mode 100644 index 00000000..9f808805 Binary files /dev/null and b/docs/src/tutorials/3d_min_thermal_comp.png differ diff --git a/docs/src/tutorials/inverse_homogenisation.md b/docs/src/tutorials/inverse_homogenisation.md new file mode 100644 index 00000000..39f022f2 --- /dev/null +++ b/docs/src/tutorials/inverse_homogenisation.md @@ -0,0 +1 @@ +# Inverse homogenisation \ No newline at end of file diff --git a/docs/src/tutorials/inverter_mechanism.md b/docs/src/tutorials/inverter_mechanism.md new file mode 100644 index 00000000..b1c1f0e9 --- /dev/null +++ b/docs/src/tutorials/inverter_mechanism.md @@ -0,0 +1 @@ +# Inverter mechanism \ No newline at end of file diff --git a/docs/src/tutorials/minimum_elastic_compliance.md b/docs/src/tutorials/minimum_elastic_compliance.md new file mode 100644 index 00000000..931748c4 --- /dev/null +++ b/docs/src/tutorials/minimum_elastic_compliance.md @@ -0,0 +1 @@ +# Minimum elastic compliance \ No newline at end of file diff --git a/docs/src/tutorials/minimum_thermal_compliance.md b/docs/src/tutorials/minimum_thermal_compliance.md new file mode 100644 index 00000000..ddbb4337 --- /dev/null +++ b/docs/src/tutorials/minimum_thermal_compliance.md @@ -0,0 +1,1115 @@ +# Minimum thermal compliance + +The goal of this tutorial is to learn +- How to formulate a topology optimisation problem +- How to describe the problem over a fixed computational domain ``D`` via the level-set method. +- How to setup and solve the problem in LevelSetTopOpt + +We consider the following extensions at the end of the tutorial: +- How to extend problems to 3D and utilise PETSc solvers +- How to solve problems with nonlinear state equations and use automatic differentiation +- How to run problems in MPI mode + +We will first consider formulation of the state equations and a topology optimisation problem in a continuous setting. We will then discretise via a level set function in a fixed computational regime. Note that this approach corresponds to an "optimise-then-discretise" approach [4] where shape derivatives are calculated analytically in the continuous space then relaxed via a level set function ``\varphi``. Automatic differentiation can be used to calculate these quantities and is discussed [here](../usage/ad.md). + +## State equations + +The homogeneous steady-state heat equation (equivalently Laplace's equation) is perhaps one of the most well-understood partial differential equations and usually the first introduced to an undergraduate student in applied mathematics. For this reason, we will use it to describe the heat transfer through a solid and how one comes to the notion of optimising the shape of that solid. + +Consider the geometric conditions outlined in the Figure 1 and suppose that we prescribe the following conditions: +- *Heat source*: unitary normal heat flow across ``\Gamma_{N}``. +- *Insulating*: zero normal heat flow across $\partial\Omega\setminus\Gamma_N$, +- *Heat sink*: zero heat on ``\Gamma_D``. + +| ![](2d_min_thermal_comp_setup.png) | +|:--:| +|Figure 1: The setup for the two-dimensional minimum thermal compliance problem| + +Physically we can imagine this as describing the transfer of heat through a domain ``\Omega`` from the sources to the sinks. From a mathematical perspective, we can write down the partial differential equations describing this as + +```math +\begin{aligned} +-\nabla(\kappa\nabla u) &= 0~\text{in }\Omega,\\ +\kappa\nabla u\cdot\boldsymbol{n} &= g~\text{on }\Gamma_N,\\ +\kappa\nabla u\cdot\boldsymbol{n} &= 0~\text{on }\partial\Omega\setminus\Gamma_N,\\ +u &= 0~\text{on }\Gamma_D. +\end{aligned} +``` + +where ``\kappa`` is the diffusivity through ``\Omega`` and ``\boldsymbol{n}`` is the unit normal on the boundary. The weak formulation of the above strong formulation can be found by multiplying by a test function $v$ and applying integration by parts. This gives + +```math +\begin{aligned} +&\textit{Find }u\in H^1_{\Gamma_D}(\Omega)\textit{ such that}\\ +&\int_{\Omega}\kappa\boldsymbol{\nabla}u\cdot\boldsymbol{\nabla}v~\mathrm{d}\boldsymbol{x} = \int_{\Gamma_N}gv~\mathrm{d}s,~\forall v\in H^1_{\Gamma_D}(\Omega) +\end{aligned} +``` + +where ``H^1_{\Gamma_D}(\Omega)=\{v\in H^1(\Omega):~v=0\text{ on }\Gamma_D\}``. + +## Optimisation problem + +For this tutorial, we consider minimising the thermal compliance (or dissipated energy) as discussed in [1,2]. The corresponding optimisation problem is + +```math +\begin{aligned} +\min_{\Omega\in\mathcal{U}}&~J(\Omega)=\int_{\Omega}\kappa\lvert\boldsymbol{\nabla}u\rvert^2~\mathrm{d}\boldsymbol{x}\\ +\text{s.t. }&~\operatorname{Vol}(\Omega)=V_f,\\ +&\left\{ +\begin{aligned} +&\textit{Find }u\in H^1_{\Gamma_D}(\Omega)\textit{ such that}\\ +&\int_{\Omega}\kappa\boldsymbol{\nabla}u\cdot\boldsymbol{\nabla}v~\mathrm{d}\boldsymbol{x} = \int_{\Gamma_N}gv~\mathrm{d}s,~\forall v\in H^1_{\Gamma_D}(\Omega) +\end{aligned} +\right. +\end{aligned} +``` +where ``\operatorname{Vol}(\Omega)=\int_\Omega1~\mathrm{d}\boldsymbol{x}``. This objective is equivalent to equivalent to maximising the heat transfer efficiency through ``\Omega``. + +## Shape differentiation + +We consider the change in quantities under the variation of the domain using *shape derivatives*. For the purpose of this tutorial we will give the mathematical description of a shape derivative along with the shape derivatives of the functionals ``J`` and ``\operatorname{Vol}``. Further discussion can be found in [3,4]. + +Suppose that we consider smooth variations of the domain ``\Omega`` of the form ``\Omega_{\boldsymbol{\theta}} =(\boldsymbol{I}+\boldsymbol{\theta})(\Omega)``, where ``\boldsymbol{\theta} \in W^{1,\infty}(\mathbb{R}^d,\mathbb{R}^d)``. Then the following definition and lemma follow: + +!!! note "Definition [3]" + The shape derivative of ``J(\Omega)`` at ``\Omega`` is defined as the Fréchet derivative in ``W^{1, \infty}(\mathbb{R}^d, \mathbb{R}^d)`` at ``\boldsymbol{\theta}`` of the application ``\boldsymbol{\theta} \rightarrow J(\Omega_{\boldsymbol{\theta}})``, i.e., + ```math + J(\Omega_{\boldsymbol{\theta}})(\Omega)=J(\Omega)+J^{\prime}(\Omega)(\boldsymbol{\theta})+\mathrm{o}(\boldsymbol{\theta}) + ``` + with ``\lim _{\boldsymbol{\theta} \rightarrow 0} \frac{\lvert\mathrm{o}(\boldsymbol{\theta})\rvert}{\|\boldsymbol{\theta}\|}=0,`` where the shape derivative ``J^{\prime}(\Omega)`` is a continuous linear form on ``W^{1, \infty}(\mathbb{R}^d, \mathbb{R}^d)`` + + +The shape derivatives of ``J`` and ``\operatorname{Vol}`` are then + +```math +J'(\Omega)(\boldsymbol{\theta}) = -\int_{\Gamma}\kappa\boldsymbol{\nabla}(u)\cdot\boldsymbol{\nabla}(u)~\boldsymbol{\theta}\cdot\boldsymbol{n}~\mathrm{d}s +``` +and +```math +\operatorname{Vol}'(\Omega)(\boldsymbol{\theta}) = \int_{\Gamma}\boldsymbol{\theta}\cdot\boldsymbol{n}~\mathrm{d}s +``` +where ``\Gamma = \partial\Omega\setminus(\Gamma_D\cup\Gamma_N)``. The first of these follows from Céa's formal method (see discussion in [3,4]), while the latter result follows from application of Lemma 4 of [3]. Finally, taking a deformation field according to ``\boldsymbol{\theta}=-q\boldsymbol{n}`` amounts to a descent direction according to the definition above. This gives +```math +J'(\Omega)(-q\boldsymbol{n}) = \int_{\Gamma}q\kappa\boldsymbol{\nabla}(u)\cdot\boldsymbol{\nabla}(u)~\mathrm{d}s +``` +and +```math +\operatorname{Vol}'(\Omega)(-q\boldsymbol{n}) = -\int_{\Gamma}q~\mathrm{d}s. +``` + +## Discretisation via a level set + +Suppose that we attribute a level set function ``\varphi:D\rightarrow\mathbb{R}`` to our domain ``\Omega\subset D`` with ``\bar{\Omega}=\lbrace \boldsymbol{x}:\varphi(\boldsymbol{x})\leq0\rbrace`` and ``\Omega^\complement=\lbrace \boldsymbol{x}:\varphi(\boldsymbol{x})>0\rbrace``. We can then define a smooth characteristic function ``I:\mathbb{R}\rightarrow[\epsilon,1]`` as ``I(\varphi)=(1-H(\varphi))+\epsilon H(\varphi)`` where ``H`` is a smoothed Heaviside function with smoothing radius ``\eta``, and ``\epsilon\ll1`` allows for an ersatz material approximation. Of course, ``\epsilon`` can be taken as zero depending on the computational regime. Over the fixed computational domain we may relax integrals to be over all of ``D`` via ``\mathrm{d}\boldsymbol{x}= H(\varphi)~\mathrm{d}\boldsymbol{x}`` and ``\mathrm{d}s = H'(\varphi)\lvert\nabla\varphi\rvert~\mathrm{d}\boldsymbol{x}``. The above optimisation problem then rewrites in terms of ``\varphi`` as + +```math +\begin{aligned} +\min_{\varphi}&~J(\varphi)=\int_{D}I(\varphi)\kappa\lvert\boldsymbol{\nabla}u\rvert^2~\mathrm{d}\boldsymbol{x}\\ +\text{s.t. }&~C(\varphi)=0,\\ +&\left\{ +\begin{aligned} +&\textit{Find }u\in H^1_{\Gamma_D}(D)\\ +&\int_{D}I(\varphi)\kappa\boldsymbol{\nabla}(u)\cdot\boldsymbol{\nabla}(v)~\mathrm{d}\boldsymbol{x} = \int_{\Gamma_N}v~\mathrm{d}s,~\forall v\in H^1_{\Gamma_D}(D) +\end{aligned} +\right. +\end{aligned} +``` + +where we retain an exact triangulation and measure of ``\Gamma_N`` as this is a fixed boundary. In addition, we have rewritten the volume constraint as + +```math +\begin{aligned} +C(\varphi)&=\int_D (\rho(\varphi) - V_f)/\operatorname{Vol}(D)~\mathrm{d}\boldsymbol{x}\\ +&=\int_D \rho(\varphi)~\mathrm{d}\boldsymbol{x}/\operatorname{Vol}(D) - V_f\\ +&=\int_\Omega~\mathrm{d}\boldsymbol{x}/\operatorname{Vol}(D)-V_f = \operatorname{Vol}(\Omega)/\operatorname{Vol}(D)-V_f +\end{aligned} +``` + +where ``\rho(\varphi)=1-H(\varphi)`` is the smoothed volume density function. + +!!! note + In LevelSetTopOpt we assume constraints are of the integral form above. + +The shape derivatives from the previous section can be relaxed over the computational domain as + +```math +J'(\varphi)(-q\boldsymbol{n}) = \int_{D}q\kappa\boldsymbol{\nabla}(u)\cdot\boldsymbol{\nabla}(u)H'(\varphi)\lvert\nabla\varphi\rvert~\mathrm{d}\boldsymbol{x} +``` +and +```math +C'(\varphi)(-q\boldsymbol{n}) = -\int_{D}qH'(\varphi)\lvert\nabla\varphi\rvert~\mathrm{d}\boldsymbol{x}/\operatorname{Vol}(D). +``` + +## Computational method + +In the following, we discuss the implementation of the above optimisation problem in LevelSetTopOpt. For the purpose of this tutorial we break the computational formulation into chunks. + +The first step in creating our script is to load any packages required: +```julia +using LevelSetTopOpt, Gridap +``` + +### Parameters +The following are user defined parameters for the problem. +These parameters will be discussed over the course of this tutorial. + +```julia +# FE parameters +order = 1 # Finite element order +xmax = ymax = 1.0 # Domain size +dom = (0,xmax,0,ymax) # Bounding domain +el_size = (200,200) # Mesh partition size +prop_Γ_N = 0.2 # Γ_N size parameter +prop_Γ_D = 0.2 # Γ_D size parameter +f_Γ_N(x) = (x[1] ≈ xmax && # Γ_N indicator function + ymax/2-ymax*prop_Γ_N/2 - eps() <= x[2] <= ymax/2+ymax*prop_Γ_N/2 + eps()) +f_Γ_D(x) = (x[1] ≈ 0.0 && # Γ_D indicator function + (x[2] <= ymax*prop_Γ_D + eps() || x[2] >= ymax-ymax*prop_Γ_D - eps())) +# FD parameters +γ = 0.1 # HJ equation time step coefficient +γ_reinit = 0.5 # Reinit. equation time step coefficient +max_steps = floor(Int,minimum(el_size)/10) # Max steps for advection +tol = 1/(5order^2)/minimum(el_size) # Advection tolerance +# Problem parameters +κ = 1 # Diffusivity +g = 1 # Heat flow in +vf = 0.4 # Volume fraction constraint +lsf_func = initial_lsf(4,0.2) # Initial level set function +iter_mod = 10 # Output VTK files every 10th iteration +path = "./results/tut1/" # Output path +mkpath(path) # Create path +``` + +### Finite element setup +We first create a Cartesian mesh over ``[0,x_{\max}]\times[0,y_{\max}]`` with partition size `el_size` by creating an object `CartesianDiscreteModel`. In addition, we label the boundaries ``\Gamma_D`` and ``\Gamma_N`` using the [`update_labels!`](@ref) function. +```julia +# Model +model = CartesianDiscreteModel(dom,el_size); +update_labels!(1,model,f_Γ_D,"Gamma_D") +update_labels!(2,model,f_Γ_N,"Gamma_N") +``` +The first argument of [`update_labels!`](@ref) indicates the label number associated to the region as indicated by the functions `f_Γ_D` and `f_Γ_N`. These functions should take a vector `x` and return `true` or `false` depending on whether a point is present in this region. + +Once the model is defined we create an integration mesh and measure for both ``\Omega`` and ``\Gamma_N``. These are built using +```julia +# Triangulation and measures +Ω = Triangulation(model) +Γ_N = BoundaryTriangulation(model,tags="Gamma_N") +dΩ = Measure(Ω,2*order) +dΓ_N = Measure(Γ_N,2*order) +``` +where `2*order` indicates the quadrature degree for numerical integration. + +The final stage of the finite element setup is the approximation of the finite element spaces. This is given as follows: +```julia +# Spaces +reffe = ReferenceFE(lagrangian,Float64,order) +V = TestFESpace(model,reffe;dirichlet_tags=["Gamma_D"]) +U = TrialFESpace(V,0.0) +V_φ = TestFESpace(model,reffe) +V_reg = TestFESpace(model,reffe;dirichlet_tags=["Gamma_N"]) +U_reg = TrialFESpace(V_reg,0) +``` + +In the above, we first define a scalar-valued Lagrangian reference element. This is then used to define the test space `V` and trial space `U` corresponding to ``H^1_{\Gamma_{D}}(\Omega)``. We then construct an FE space `V_φ` over which the level set function is defined, along with an FE test space `V_reg` and trial space `U_reg` over which derivatives are defined. We require that `V_reg` and `U_reg` have zero Dirichlet boundary conditions over regions where the extended shape sensitivity is zero. In general, we allow Dirichlet boundaries to have non-zero shape sensitivity. + +### Initial level set function and interpolant + +We interpolate an initial level set function onto `V_φ` given a function `lsf_func` using the `interpolate` provided by Gridap. +```julia +# Level set and interpolator +φh = interpolate(lsf_func,V_φ) +``` +For this problem we set `lsf_func` using the function [`initial_lsf`](@ref) in the problem parameters. This generates an initial level set according to +```math +\varphi_{\xi,a}(\boldsymbol{x})=-\frac{1}{4} \prod_i^D(\cos(\xi\pi x_i)) - a/4 +``` +with ``\xi,a=(4,0.2)`` and ``D=2`` in two dimensions. + +We also generate a smooth characteristic function of radius ``\eta`` using: + +```julia +interp = SmoothErsatzMaterialInterpolation(η = 2*maximum(get_el_Δ(model))) +I,H,DH,ρ = interp.I,interp.H,interp.DH,interp.ρ +``` + +This the [`SmoothErsatzMaterialInterpolation`](@ref) structure defines the characteristic or interpolator `I`, the smoothed Heaviside function `H` and it's derivative `DH`, and the smoothed density function `ρ`. Below we visualise `φh` and the smoothed density function `ρ` at `φh`: + +| ![](2d_min_thermal_comp_initial_lsf_combined.png) | +|:--:| +|Figure 2: A visualisation of the initial level set function and the interpolated density function ``\rho`` for ``\Omega``.| + +Optional: we can generate a VTK file for visualisation in Paraview via +```julia +writevtk(Ω,"initial_lsf",cellfields=["phi"=>φh, + "ρ(phi)"=>(ρ ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh))]) +``` +Note that the operator `∘` is used to compose other Julia `Functions` with Gridap `FEFunctions`. This will be used extensively as we progress through the tutorial. + +### Weak formulation and the state map +The weak formulation for the problem above can be written as +```julia +# Weak formulation +a(u,v,φ,dΩ,dΓ_N) = ∫((I ∘ φ)*κ*∇(u)⋅∇(v))dΩ +l(v,φ,dΩ,dΓ_N) = ∫(g*v)dΓ_N +``` +where the `∘` operator composes the interpolator `I` with the level set function `φ`. + +!!! warning + The measures must be included as arguments at the end of all functionals. + This ensures compatibility with Gridap's automatic differentiation. + +At this point we can build an [`AffineFEStateMap`](@ref). This structure is designed to +1) Enable the forward problem that solves a Gridap `AffineFEOperator`; and +2) Encode the implicit dependence of the solution `u` on the level set function `φ` to enable the differentiation of `u`. + +``` +state_map = AffineFEStateMap(a,l,U,V,V_φ,U_reg,φh,dΩ,dΓ_N) +``` + +### Optimisation functionals + +The objective functional ``J`` and it's shape derivative is given by + +```julia +# Objective and constraints +J(u,φ,dΩ,dΓ_N) = ∫((I ∘ φ)*κ*∇(u)⋅∇(u))dΩ +dJ(q,u,φ,dΩ,dΓ_N) = ∫(κ*∇(u)⋅∇(u)*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ +``` + +while the constraint on the volume and its derivative is + +```julia +vol_D = sum(∫(1)dΩ) +C(u,φ,dΩ,dΓ_N) = ∫(((ρ ∘ φ) - vf)/vol_D)dΩ +dC(q,u,φ,dΩ,dΓ_N) = ∫(-1/vol_D*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ +``` + +We can now create an object [`PDEConstrainedFunctionals`](@ref) that handles the objective and constraints, and their analytic or automatic differentiation. +```julia +pcfs = PDEConstrainedFunctionals(J,[C],state_map,analytic_dJ=dJ,analytic_dC=[dC]) +``` +In this case, the analytic shape derivatives are passed as optional arguments. When these are not given, automatic differentiation in ``φ`` is used. + +### Velocity extension-regularisation method + +The Hilbertian extension-regularisation [4] method involves solving an +identification problem over a Hilbert space ``H`` on ``D`` with +inner product ``\langle\cdot,\cdot\rangle_H``: +*Find* ``g_\Omega\in H`` *such that* ``\langle g_\Omega,q\rangle_H +=-J^{\prime}(\Omega)(q\boldsymbol{n})~ +\forall q\in H.`` + +This provides two benefits: + 1) It naturally extends the shape sensitivity from ``\partial\Omega`` + onto the bounding domain ``D``; and + 2) ensures a descent direction for ``J(\Omega)`` with additional regularity + (i.e., ``H`` as opposed to ``L^2(\partial\Omega)``). + +For our problem above we take the inner product + +```math +\langle p,q\rangle_H=\int_{D}\alpha^2\nabla(p)\nabla(q)+pq~\mathrm{d}\boldsymbol{x}, +``` +where ``\alpha`` is the smoothing length scale. Equivalently in our script we have +```julia +# Velocity extension +α = 4*maximum(get_el_Δ(model)) +a_hilb(p,q) =∫(α^2*∇(p)⋅∇(q) + p*q)dΩ +``` + +We then build an object [`VelocityExtension`](@ref). This object provides a method [`project!`](@ref) that applies the Hilbertian velocity-extension method to a given shape derivative. + +```julia +vel_ext = VelocityExtension(a_hilb,U_reg,V_reg) +``` + +### Advection and reinitialisation + +To advect the level set function, we solve the Hamilton-Jacobi evolution equation [3,4,5]. This is given by + +```math +\frac{\partial\phi}{\partial t} + V(\boldsymbol{x})\lVert\boldsymbol{\nabla}\phi\rVert = 0, +``` + +with ``\phi(0,\boldsymbol{x})=\phi_0(\boldsymbol{x})`` and ``\boldsymbol{x}\in D,~t\in(0,T)``. + +After advection of the interface, we solve the reinitialisation equation to find an equivalent signed distance function for the given level set function. This is given by + +```math +\frac{\partial\phi}{\partial t} + \mathrm{sign}(\phi_0)(\lVert\boldsymbol{\nabla}\phi\rVert-1) = 0, +``` + +with ``\phi(0,\boldsymbol{x})=\phi_0(\boldsymbol{x})`` and ``\boldsymbol{x}\in D,~t\in(0,T)``. + +Both of these equations can be solved numerically on a Cartesian mesh using a first order Godunov upwind difference scheme based on [5]. This functionality is provided by the following objects: + +```julia +# Finite difference scheme +scheme = FirstOrderStencil(length(el_size),Float64) +stencil = AdvectionStencil(scheme,model,V_φ,tol,max_steps) +``` + +In the above we first build an object [`FirstOrderStencil`](@ref) that represents a finite difference stencil for a single step of the Hamilton-Jacobi evolution equation and reinitialisation equation. We use `length(el_size)` to indicate the dimension of the problem. We then create an [`AdvectionStencil`](@ref) which enables finite differencing on order `O` finite elements in serial or parallel. The [`AdvectionStencil`](@ref) object provides two important methods [`advect!`](@ref) and [`reinit!`](@ref) that correspond to solving the Hamilton-Jacobi evolution equation and reinitialisation equation, respectively. + +### Optimiser, visualisation and IO + +We may now create the optimiser object. This structure holds all information regarding the optimisation problem that we wish to solve and implements an optimisation algorithm as a Julia [iterator](https://docs.julialang.org/en/v1/manual/interfaces/). For the purpose of this tutorial we use a standard augmented Lagrangian method based on [6]. In our script, we create an instance of the [`AugmentedLagrangian`](@ref) via + +```julia +# Optimiser +optimiser = AugmentedLagrangian(pcfs,stencil,vel_ext,φh;γ,γ_reinit,verbose=true,constraint_names=[:Vol]) +``` + +As optimisers inheriting from [`LevelSetTopOpt.Optimiser`](@ref) implement Julia's iterator functionality, we can solve the optimisation problem to convergence by iterating over the optimiser: + +```julia +# Solve +for (it,uh,φh) in optimiser end +``` + +This allows the user to inject code between iterations. For example, we can write VTK files for visualisation and save the history using the following: + +```julia +# Solve +for (it,uh,φh) in optimiser + data = ["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh)),"uh"=>uh] + iszero(it % iter_mod) && (writevtk(Ω,path*"struc_$it",cellfields=data);GC.gc()) + write_history(path*"/history.txt",get_history(optimiser)) +end +``` + +!!! warning + Due to a possible memory leak in Julia 1.9.* IO, we include a call to the garbage collector using `GC.gc()`. + +Depending on whether we use `iszero(it % iter_mod)`, the VTK file for the final structure +may need to be saved using + +```julia +# Final structure +it = get_history(optimiser).niter; uh = get_state(pcfs) +writevtk(Ω,path*"_$it",cellfields=["phi"=>φh, + "H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh)),"uh"=>uh]) +``` + +### The full script + +```@raw html +
Script 1: combining the above gives (click me!) +``` + +```julia +using Gridap, LevelSetTopOpt + +# FE parameters +order = 1 # Finite element order +xmax = ymax = 1.0 # Domain size +dom = (0,xmax,0,ymax) # Bounding domain +el_size = (200,200) # Mesh partition size +prop_Γ_N = 0.2 # Γ_N size parameter +prop_Γ_D = 0.2 # Γ_D size parameter +f_Γ_N(x) = (x[1] ≈ xmax && # Γ_N indicator function + ymax/2-ymax*prop_Γ_N/2 - eps() <= x[2] <= ymax/2+ymax*prop_Γ_N/2 + eps()) +f_Γ_D(x) = (x[1] ≈ 0.0 && # Γ_D indicator function + (x[2] <= ymax*prop_Γ_D + eps() || x[2] >= ymax-ymax*prop_Γ_D - eps())) +# FD parameters +γ = 0.1 # HJ equation time step coefficient +γ_reinit = 0.5 # Reinit. equation time step coefficient +max_steps = floor(Int,minimum(el_size)/10) # Max steps for advection +tol = 1/(5order^2)/minimum(el_size) # Advection tolerance +# Problem parameters +κ = 1 # Diffusivity +g = 1 # Heat flow in +vf = 0.4 # Volume fraction constraint +lsf_func = initial_lsf(4,0.2) # Initial level set function +iter_mod = 10 # Output VTK files every 10th iteration +path = "./results/tut1/" # Output path +mkpath(path) # Create path +# Model +model = CartesianDiscreteModel(dom,el_size); +update_labels!(1,model,f_Γ_D,"Gamma_D") +update_labels!(2,model,f_Γ_N,"Gamma_N") +# Triangulation and measures +Ω = Triangulation(model) +Γ_N = BoundaryTriangulation(model,tags="Gamma_N") +dΩ = Measure(Ω,2*order) +dΓ_N = Measure(Γ_N,2*order) +# Spaces +reffe = ReferenceFE(lagrangian,Float64,order) +V = TestFESpace(model,reffe;dirichlet_tags=["Gamma_D"]) +U = TrialFESpace(V,0.0) +V_φ = TestFESpace(model,reffe) +V_reg = TestFESpace(model,reffe;dirichlet_tags=["Gamma_N"]) +U_reg = TrialFESpace(V_reg,0) +# Level set and interpolator +φh = interpolate(lsf_func,V_φ) +interp = SmoothErsatzMaterialInterpolation(η = 2*maximum(get_el_Δ(model))) +I,H,DH,ρ = interp.I,interp.H,interp.DH,interp.ρ +# Weak formulation +a(u,v,φ,dΩ,dΓ_N) = ∫((I ∘ φ)*κ*∇(u)⋅∇(v))dΩ +l(v,φ,dΩ,dΓ_N) = ∫(g*v)dΓ_N +state_map = AffineFEStateMap(a,l,U,V,V_φ,U_reg,φh,dΩ,dΓ_N) +# Objective and constraints +J(u,φ,dΩ,dΓ_N) = ∫((I ∘ φ)*κ*∇(u)⋅∇(u))dΩ +dJ(q,u,φ,dΩ,dΓ_N) = ∫(κ*∇(u)⋅∇(u)*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ +vol_D = sum(∫(1)dΩ) +C(u,φ,dΩ,dΓ_N) = ∫(((ρ ∘ φ) - vf)/vol_D)dΩ +dC(q,u,φ,dΩ,dΓ_N) = ∫(-1/vol_D*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ +pcfs = PDEConstrainedFunctionals(J,[C],state_map,analytic_dJ=dJ,analytic_dC=[dC]) +# Velocity extension +α = 4*maximum(get_el_Δ(model)) +a_hilb(p,q) =∫(α^2*∇(p)⋅∇(q) + p*q)dΩ +vel_ext = VelocityExtension(a_hilb,U_reg,V_reg) +# Finite difference scheme +scheme = FirstOrderStencil(length(el_size),Float64) +stencil = AdvectionStencil(scheme,model,V_φ,tol,max_steps) +# Optimiser +optimiser = AugmentedLagrangian(pcfs,stencil,vel_ext,φh;γ,γ_reinit,verbose=true,constraint_names=[:Vol]) +# Solve +for (it,uh,φh) in optimiser + data = ["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh)),"uh"=>uh] + iszero(it % iter_mod) && (writevtk(Ω,path*"struc_$it",cellfields=data);GC.gc()) + write_history(path*"/history.txt",get_history(optimiser)) +end +# Final structure +it = get_history(optimiser).niter; uh = get_state(pcfs) +writevtk(Ω,path*"struc_$it",cellfields=["phi"=>φh, + "H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh)),"uh"=>uh]) +``` + +```@raw html +
+``` + +Running this problem until convergence gives a final result of +``` +Iteration: 102 | L=1.1256e-01, J=1.1264e-01, Vol=-3.6281e-04, γ=5.6250e-02 +``` +with the optimised domain ``\Omega`` given by + +| ![](2d_min_thermal_comp_final_struc.png) | +|:--:| +|Figure 3: Visualisation of ``\Omega`` using the isovolume with ``\varphi\leq0``.| + +## Extensions + +In the following we outline several extensions to the avoid optimisation problem. + +!!! note + We assume that PETSc and MPI have been installed correctly. Please see [PETSc instructions](../usage/petsc.md) and [MPI instructions](../usage/mpi-mode.md) for additional information. + +### 3D with PETSc +The first and most straightforward in terms of programmatic changes is extending the problem to 3D. For this extension, we consider the following setup for the boundary conditions: + +| ![](3d_min_thermal_comp.png) | +|:--:| +|Figure 4: The setup for the three-dimensional minimum thermal compliance problem.| + +We use a unit cube for the bounding domain ``D`` with ``50^3`` elements. This corresponds to changing lines 5-7 in the above script to + +```julia +xmax=ymax=zmax=1.0 # Domain size +dom = (0,xmax,0,ymax,0,zmax) # Bounding domain +el_size = (100,100,100) # Mesh partition size +``` + +To apply the boundary conditions per Figure 4, we also adjust the boundary indicator functions on lines 10-13 to + +```julia +f_Γ_N(x) = (x[1] ≈ xmax) && # Γ_N indicator function + (ymax/2-ymax*prop_Γ_N/2 - eps() <= x[2] <= ymax/2+ymax*prop_Γ_N/2 + eps()) && + (zmax/2-zmax*prop_Γ_N/2 - eps() <= x[3] <= zmax/2+zmax*prop_Γ_N/2 + eps()) +f_Γ_D(x) = (x[1] ≈ 0.0) && # Γ_D indicator function + (x[2] <= ymax*prop_Γ_D + eps() || x[2] >= ymax-ymax*prop_Γ_D - eps()) && + (x[3] <= zmax*prop_Γ_D + eps() || x[3] >= zmax-zmax*prop_Γ_D - eps()) +``` + +We adjust the output path on line 25 to be +```julia +path = "./results/tut1_3d/" # Output path +``` + +Finally, we adjust the finite difference parameters on lines 17-18 to +```julia +max_steps = floor(Int,minimum(el_size)/3) # Max steps for advection +tol = 1/(2order^2)/minimum(el_size) # Advection tolerance +``` + +```@raw html +
Script 2: combining the above gives (click me!) +``` + +```julia +using Gridap, LevelSetTopOpt + +# FE parameters +order = 1 # Finite element order +xmax=ymax=zmax=1.0 # Domain size +dom = (0,xmax,0,ymax,0,zmax) # Bounding domain +el_size = (100,100,100) # Mesh partition size +prop_Γ_N = 0.2 # Γ_N size parameter +prop_Γ_D = 0.2 # Γ_D size parameter +f_Γ_N(x) = (x[1] ≈ xmax) && # Γ_N indicator function + (ymax/2-ymax*prop_Γ_N/2 - eps() <= x[2] <= ymax/2+ymax*prop_Γ_N/2 + eps()) && + (zmax/2-zmax*prop_Γ_N/2 - eps() <= x[3] <= zmax/2+zmax*prop_Γ_N/2 + eps()) +f_Γ_D(x) = (x[1] ≈ 0.0) && # Γ_D indicator function + (x[2] <= ymax*prop_Γ_D + eps() || x[2] >= ymax-ymax*prop_Γ_D - eps()) && + (x[3] <= zmax*prop_Γ_D + eps() || x[3] >= zmax-zmax*prop_Γ_D - eps()) +# FD parameters +γ = 0.1 # HJ equation time step coefficient +γ_reinit = 0.5 # Reinit. equation time step coefficient +max_steps = floor(Int,minimum(el_size)/3) # Max steps for advection +tol = 1/(2order^2)/minimum(el_size) # Advection tolerance +# Problem parameters +κ = 1 # Diffusivity +g = 1 # Heat flow in +vf = 0.4 # Volume fraction constraint +lsf_func = initial_lsf(4,0.2) # Initial level set function +iter_mod = 10 # Output VTK files every 10th iteration +path = "./results/tut1_3d/" # Output path +mkpath(path) # Create path +# Model +model = CartesianDiscreteModel(dom,el_size); +update_labels!(1,model,f_Γ_D,"Gamma_D") +update_labels!(2,model,f_Γ_N,"Gamma_N") +# Triangulation and measures +Ω = Triangulation(model) +Γ_N = BoundaryTriangulation(model,tags="Gamma_N") +dΩ = Measure(Ω,2*order) +dΓ_N = Measure(Γ_N,2*order) +# Spaces +reffe = ReferenceFE(lagrangian,Float64,order) +V = TestFESpace(model,reffe;dirichlet_tags=["Gamma_D"]) +U = TrialFESpace(V,0.0) +V_φ = TestFESpace(model,reffe) +V_reg = TestFESpace(model,reffe;dirichlet_tags=["Gamma_N"]) +U_reg = TrialFESpace(V_reg,0) +# Level set and interpolator +φh = interpolate(lsf_func,V_φ) +interp = SmoothErsatzMaterialInterpolation(η = 2*maximum(get_el_Δ(model))) +I,H,DH,ρ = interp.I,interp.H,interp.DH,interp.ρ +# Weak formulation +a(u,v,φ,dΩ,dΓ_N) = ∫((I ∘ φ)*κ*∇(u)⋅∇(v))dΩ +l(v,φ,dΩ,dΓ_N) = ∫(g*v)dΓ_N +state_map = AffineFEStateMap(a,l,U,V,V_φ,U_reg,φh,dΩ,dΓ_N) +# Objective and constraints +J(u,φ,dΩ,dΓ_N) = ∫((I ∘ φ)*κ*∇(u)⋅∇(u))dΩ +dJ(q,u,φ,dΩ,dΓ_N) = ∫(κ*∇(u)⋅∇(u)*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ +vol_D = sum(∫(1)dΩ) +C(u,φ,dΩ,dΓ_N) = ∫(((ρ ∘ φ) - vf)/vol_D)dΩ +dC(q,u,φ,dΩ,dΓ_N) = ∫(-1/vol_D*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ +pcfs = PDEConstrainedFunctionals(J,[C],state_map,analytic_dJ=dJ,analytic_dC=[dC]) +# Velocity extension +α = 4*maximum(get_el_Δ(model)) +a_hilb(p,q) =∫(α^2*∇(p)⋅∇(q) + p*q)dΩ +vel_ext = VelocityExtension(a_hilb,U_reg,V_reg) +# Finite difference scheme +scheme = FirstOrderStencil(length(el_size),Float64) +stencil = AdvectionStencil(scheme,model,V_φ,tol,max_steps) +# Optimiser +optimiser = AugmentedLagrangian(pcfs,stencil,vel_ext,φh;γ,γ_reinit,verbose=true,constraint_names=[:Vol]) +# Solve +for (it,uh,φh) in optimiser + data = ["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh)),"uh"=>uh] + iszero(it % iter_mod) && (writevtk(Ω,path*"struc_$it",cellfields=data);GC.gc()) + write_history(path*"/history.txt",get_history(optimiser)) +end +# Final structure +it = get_history(optimiser).niter; uh = get_state(pcfs) +writevtk(Ω,path*"struc_$it",cellfields=["phi"=>φh, + "H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh)),"uh"=>uh]) +``` + +```@raw html +
+``` + +At this stage the problem will not be possible to run as we're using a standard LU solver. For this reason we now consider adjusting Script 2 to use an iterative solver provided by PETSc. We rely on the GridapPETSc satellite package to utilise PETSc. This provides the necessary structures to efficiently interface with the linear and nonlinear solvers provided by the PETSc library. To call GridapPETSc we change line 1 of Script 2 to + +```julia +using Gridap, GridapPETSc, SparseMatricesCSR, LevelSetTopOpt +``` + +We also use `SparseMatricesCSR` as PETSc is based on the `SparseMatrixCSR` datatype. We then replace line 52 and 63 with + +```julia +# State map +Tm = SparseMatrixCSR{0,PetscScalar,PetscInt} +Tv = Vector{PetscScalar} +state_map = AffineFEStateMap( + a,l,U,V,V_φ,U_reg,φh,dΩ,dΓ_N; + assem_U = SparseMatrixAssembler(Tm,Tv,U,V), + assem_adjoint = SparseMatrixAssembler(Tm,Tv,V,U), + assem_deriv = SparseMatrixAssembler(Tm,Tv,U_reg,U_reg), + ls = PETScLinearSolver(),adjoint_ls = PETScLinearSolver() +) +``` +and +```julia +vel_ext = VelocityExtension( + a_hilb, U_reg, V_reg; + assem = SparseMatrixAssembler(Tm,Tv,U_reg,V_reg), + ls = PETScLinearSolver() + ) +``` +respectively. Here we specify that the `SparseMatrixAssembler` should be based on the `SparseMatrixCSR` datatype along with the globals `PetscScalar` and `PetscInt`. We then set the linear solver, adjoint solver, and linear solver for the velocity extension to be the `PETScLinearSolver()`. The `PETScLinearSolver` is a wrapper for the PETSc solver as specified by the solver options (see below). + +Finally, we wrap the entire script in a function and call it inside a `GridapPETSc.with` block. This ensures that PETSc is safely initialised. This should take the form +```Julia +using Gridap, GridapPETSc, SparseMatricesCSR, LevelSetTopOpt + +function main() + ... +end + +solver_options = "-pc_type gamg -ksp_type cg -ksp_error_if_not_converged true + -ksp_converged_reason -ksp_rtol 1.0e-12" +GridapPETSc.with(args=split(solver_options)) do + main() +end +``` + +We utilise a conjugate gradient method with geometric algebraic multigrid preconditioner using the `solver_options` string. This should match the PETSc database keys (see [documentation](https://petsc.org/release/manual/ksp/#ch-ksp)). + +```@raw html +
Script 3: combining the above gives (click me!) +``` + +```julia +using Gridap, GridapPETSc, SparseMatricesCSR, LevelSetTopOpt + +function main() + # FE parameters + order = 1 # Finite element order + xmax=ymax=zmax=1.0 # Domain size + dom = (0,xmax,0,ymax,0,zmax) # Bounding domain + el_size = (100,100,100) # Mesh partition size + prop_Γ_N = 0.2 # Γ_N size parameter + prop_Γ_D = 0.2 # Γ_D size parameter + f_Γ_N(x) = (x[1] ≈ xmax) && # Γ_N indicator function + (ymax/2-ymax*prop_Γ_N/2 - eps() <= x[2] <= ymax/2+ymax*prop_Γ_N/2 + eps()) && + (zmax/2-zmax*prop_Γ_N/2 - eps() <= x[3] <= zmax/2+zmax*prop_Γ_N/2 + eps()) + f_Γ_D(x) = (x[1] ≈ 0.0) && # Γ_D indicator function + (x[2] <= ymax*prop_Γ_D + eps() || x[2] >= ymax-ymax*prop_Γ_D - eps()) && + (x[3] <= zmax*prop_Γ_D + eps() || x[3] >= zmax-zmax*prop_Γ_D - eps()) + # FD parameters + γ = 0.1 # HJ equation time step coefficient + γ_reinit = 0.5 # Reinit. equation time step coefficient + max_steps = floor(Int,minimum(el_size)/3) # Max steps for advection + tol = 1/(2order^2)/minimum(el_size) # Advection tolerance + # Problem parameters + κ = 1 # Diffusivity + g = 1 # Heat flow in + vf = 0.4 # Volume fraction constraint + lsf_func = initial_lsf(4,0.2) # Initial level set function + iter_mod = 10 # Output VTK files every 10th iteration + path = "./results/tut1_3d_petsc/" # Output path + mkpath(path) # Create path + # Model + model = CartesianDiscreteModel(dom,el_size); + update_labels!(1,model,f_Γ_D,"Gamma_D") + update_labels!(2,model,f_Γ_N,"Gamma_N") + # Triangulation and measures + Ω = Triangulation(model) + Γ_N = BoundaryTriangulation(model,tags="Gamma_N") + dΩ = Measure(Ω,2*order) + dΓ_N = Measure(Γ_N,2*order) + # Spaces + reffe = ReferenceFE(lagrangian,Float64,order) + V = TestFESpace(model,reffe;dirichlet_tags=["Gamma_D"]) + U = TrialFESpace(V,0.0) + V_φ = TestFESpace(model,reffe) + V_reg = TestFESpace(model,reffe;dirichlet_tags=["Gamma_N"]) + U_reg = TrialFESpace(V_reg,0) + # Level set and interpolator + φh = interpolate(lsf_func,V_φ) + interp = SmoothErsatzMaterialInterpolation(η = 2*maximum(get_el_Δ(model))) + I,H,DH,ρ = interp.I,interp.H,interp.DH,interp.ρ + # Weak formulation + a(u,v,φ,dΩ,dΓ_N) = ∫((I ∘ φ)*κ*∇(u)⋅∇(v))dΩ + l(v,φ,dΩ,dΓ_N) = ∫(g*v)dΓ_N + # State map + Tm = SparseMatrixCSR{0,PetscScalar,PetscInt} + Tv = Vector{PetscScalar} + state_map = AffineFEStateMap( + a,l,U,V,V_φ,U_reg,φh,dΩ,dΓ_N; + assem_U = SparseMatrixAssembler(Tm,Tv,U,V), + assem_adjoint = SparseMatrixAssembler(Tm,Tv,V,U), + assem_deriv = SparseMatrixAssembler(Tm,Tv,U_reg,U_reg), + ls = PETScLinearSolver(),adjoint_ls = PETScLinearSolver() + ) + # Objective and constraints + J(u,φ,dΩ,dΓ_N) = ∫((I ∘ φ)*κ*∇(u)⋅∇(u))dΩ + dJ(q,u,φ,dΩ,dΓ_N) = ∫(κ*∇(u)⋅∇(u)*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ + vol_D = sum(∫(1)dΩ) + C(u,φ,dΩ,dΓ_N) = ∫(((ρ ∘ φ) - vf)/vol_D)dΩ + dC(q,u,φ,dΩ,dΓ_N) = ∫(-1/vol_D*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ + pcfs = PDEConstrainedFunctionals(J,[C],state_map,analytic_dJ=dJ,analytic_dC=[dC]) + # Velocity extension + α = 4*maximum(get_el_Δ(model)) + a_hilb(p,q) =∫(α^2*∇(p)⋅∇(q) + p*q)dΩ + vel_ext = VelocityExtension( + a_hilb, U_reg, V_reg; + assem = SparseMatrixAssembler(Tm,Tv,U_reg,V_reg), + ls = PETScLinearSolver() + ) + # Finite difference scheme + scheme = FirstOrderStencil(length(el_size),Float64) + stencil = AdvectionStencil(scheme,model,V_φ,tol,max_steps) + # Optimiser + optimiser = AugmentedLagrangian(pcfs,stencil,vel_ext,φh;γ,γ_reinit,verbose=true,constraint_names=[:Vol]) + # Solve + for (it,uh,φh) in optimiser + data = ["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh)),"uh"=>uh] + iszero(it % iter_mod) && (writevtk(Ω,path*"struc_$it",cellfields=data);GC.gc()) + write_history(path*"/history.txt",get_history(optimiser)) + end + # Final structure + it = get_history(optimiser).niter; uh = get_state(pcfs) + writevtk(Ω,path*"struc_$it",cellfields=["phi"=>φh, + "H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh)),"uh"=>uh]) +end + +solver_options = "-pc_type gamg -ksp_type cg -ksp_error_if_not_converged true + -ksp_converged_reason -ksp_rtol 1.0e-12" +GridapPETSc.with(args=split(solver_options)) do + main() +end +``` + +```@raw html +
+``` + +We can run this script and visualise the initial and final structures using Paraview: + +| ![](3d_min_thermal_combined.png) | +|:--:| +|Figure 5: Visualisation of initial structure (left) and final structure (right) for Script 3 using the isovolume with ``\varphi\leq0``.| + +### Serial to MPI + +Script 3 contains no parallelism to enable further speedup or scalability. To enable MPI-based computing we rely on the tools implemented in PartitionedArrays and GridapDistributed. Further information regarding these packages and how they interface with LevelSetTopOpt can be found [here](../usage/mpi-mode.md). To add these packages we adjust the first line of our script: + +```julia +using Gridap, GridapPETSc, GridapDistributed, PartitionedArrays, SparseMatricesCSR, LevelSetTopOpt +``` + +Before we change any parts of the function `main`, we adjust the end of the script to safely launch MPI inside a Julia `do` block. We replace lines 95-99 in Script 3 with + +```julia +with_mpi() do distribute + mesh_partition = (2,2,2) + solver_options = "-pc_type gamg -ksp_type cg -ksp_error_if_not_converged true + -ksp_converged_reason -ksp_rtol 1.0e-12" + GridapPETSc.with(args=split(solver_options)) do + main(mesh_partition,distribute) + end +end +``` + +We use `mesh_partition = (2,2,2)` to set the number of partitions of the Cartesian mesh in each axial direction. For this example we end up with a total of 8 partitions. We then pass `main` two arguments: `mesh_partition` and `distribute`. We use these to to create MPI ranks at the start of `main`: + +```julia +function main(mesh_partition,distribute) + ranks = distribute(LinearIndices((prod(mesh_partition),))) + ... +``` + +We then adjust lines 28-31 as follows: + +```julia + path = "./results/tut1_3d_petsc_mpi/" # Output path + i_am_main(ranks) && mkpath(path) # Create path + # Model + model = CartesianDiscreteModel(ranks,mesh_partition,dom,el_size); +``` + +The function `i_am_main` returns true only on the first processor. This function is useful for ensuring certain operations only happen once instead of several times across each executable. In addition, we now create a partitioned Cartesian model using `CartesianDiscreteModel(ranks,mesh_partition,dom,el_size)`. Finally, we adjust line 82 to ensure that verbosity only happens on the first processors: + +```julia +optimiser = AugmentedLagrangian(pcfs,stencil,vel_ext,φh; + γ,γ_reinit,verbose=i_am_main(ranks),constraint_names=[:Vol]) +``` + +That's it! These are the only changes that are necessary to run your application using MPI. + +```@raw html +
Script 4: combining the above gives (click me!) +``` + +```julia +using Gridap, GridapPETSc, GridapDistributed, PartitionedArrays, SparseMatricesCSR, LevelSetTopOpt + +function main(mesh_partition,distribute) + ranks = distribute(LinearIndices((prod(mesh_partition),))) + # FE parameters + order = 1 # Finite element order + xmax=ymax=zmax=1.0 # Domain size + dom = (0,xmax,0,ymax,0,zmax) # Bounding domain + el_size = (100,100,100) # Mesh partition size + prop_Γ_N = 0.2 # Γ_N size parameter + prop_Γ_D = 0.2 # Γ_D size parameter + f_Γ_N(x) = (x[1] ≈ xmax) && # Γ_N indicator function + (ymax/2-ymax*prop_Γ_N/2 - eps() <= x[2] <= ymax/2+ymax*prop_Γ_N/2 + eps()) && + (zmax/2-zmax*prop_Γ_N/2 - eps() <= x[3] <= zmax/2+zmax*prop_Γ_N/2 + eps()) + f_Γ_D(x) = (x[1] ≈ 0.0) && # Γ_D indicator function + (x[2] <= ymax*prop_Γ_D + eps() || x[2] >= ymax-ymax*prop_Γ_D - eps()) && + (x[3] <= zmax*prop_Γ_D + eps() || x[3] >= zmax-zmax*prop_Γ_D - eps()) + # FD parameters + γ = 0.1 # HJ equation time step coefficient + γ_reinit = 0.5 # Reinit. equation time step coefficient + max_steps = floor(Int,minimum(el_size)/3) # Max steps for advection + tol = 1/(2order^2)/minimum(el_size) # Advection tolerance + # Problem parameters + κ = 1 # Diffusivity + g = 1 # Heat flow in + vf = 0.4 # Volume fraction constraint + lsf_func = initial_lsf(4,0.2) # Initial level set function + iter_mod = 10 # Output VTK files every 10th iteration + path = "./results/tut1_3d_petsc_mpi/" # Output path + i_am_main(ranks) && mkpath(path) # Create path + # Model + model = CartesianDiscreteModel(ranks,mesh_partition,dom,el_size); + update_labels!(1,model,f_Γ_D,"Gamma_D") + update_labels!(2,model,f_Γ_N,"Gamma_N") + # Triangulation and measures + Ω = Triangulation(model) + Γ_N = BoundaryTriangulation(model,tags="Gamma_N") + dΩ = Measure(Ω,2*order) + dΓ_N = Measure(Γ_N,2*order) + # Spaces + reffe = ReferenceFE(lagrangian,Float64,order) + V = TestFESpace(model,reffe;dirichlet_tags=["Gamma_D"]) + U = TrialFESpace(V,0.0) + V_φ = TestFESpace(model,reffe) + V_reg = TestFESpace(model,reffe;dirichlet_tags=["Gamma_N"]) + U_reg = TrialFESpace(V_reg,0) + # Level set and interpolator + φh = interpolate(lsf_func,V_φ) + interp = SmoothErsatzMaterialInterpolation(η = 2*maximum(get_el_Δ(model))) + I,H,DH,ρ = interp.I,interp.H,interp.DH,interp.ρ + # Weak formulation + a(u,v,φ,dΩ,dΓ_N) = ∫((I ∘ φ)*κ*∇(u)⋅∇(v))dΩ + l(v,φ,dΩ,dΓ_N) = ∫(g*v)dΓ_N + # State map + Tm = SparseMatrixCSR{0,PetscScalar,PetscInt} + Tv = Vector{PetscScalar} + state_map = AffineFEStateMap( + a,l,U,V,V_φ,U_reg,φh,dΩ,dΓ_N; + assem_U = SparseMatrixAssembler(Tm,Tv,U,V), + assem_adjoint = SparseMatrixAssembler(Tm,Tv,V,U), + assem_deriv = SparseMatrixAssembler(Tm,Tv,U_reg,U_reg), + ls = PETScLinearSolver(),adjoint_ls = PETScLinearSolver() + ) + # Objective and constraints + J(u,φ,dΩ,dΓ_N) = ∫((I ∘ φ)*κ*∇(u)⋅∇(u))dΩ + dJ(q,u,φ,dΩ,dΓ_N) = ∫(κ*∇(u)⋅∇(u)*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ + vol_D = sum(∫(1)dΩ) + C(u,φ,dΩ,dΓ_N) = ∫(((ρ ∘ φ) - vf)/vol_D)dΩ + dC(q,u,φ,dΩ,dΓ_N) = ∫(-1/vol_D*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ + pcfs = PDEConstrainedFunctionals(J,[C],state_map,analytic_dJ=dJ,analytic_dC=[dC]) + # Velocity extension + α = 4*maximum(get_el_Δ(model)) + a_hilb(p,q) =∫(α^2*∇(p)⋅∇(q) + p*q)dΩ + vel_ext = VelocityExtension( + a_hilb, U_reg, V_reg; + assem = SparseMatrixAssembler(Tm,Tv,U_reg,V_reg), + ls = PETScLinearSolver() + ) + # Finite difference scheme + scheme = FirstOrderStencil(length(el_size),Float64) + stencil = AdvectionStencil(scheme,model,V_φ,tol,max_steps) + # Optimiser + optimiser = AugmentedLagrangian(pcfs,stencil,vel_ext,φh; + γ,γ_reinit,verbose=i_am_main(ranks),constraint_names=[:Vol]) + # Solve + for (it,uh,φh) in optimiser + data = ["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh)),"uh"=>uh] + iszero(it % iter_mod) && (writevtk(Ω,path*"struc_$it",cellfields=data);GC.gc()) + write_history(path*"/history.txt",get_history(optimiser)) + end + # Final structure + it = get_history(optimiser).niter; uh = get_state(pcfs) + writevtk(Ω,path*"struc_$it",cellfields=["phi"=>φh, + "H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh)),"uh"=>uh]) +end + +with_mpi() do distribute + mesh_partition = (2,2,2) + solver_options = "-pc_type gamg -ksp_type cg -ksp_error_if_not_converged true + -ksp_converged_reason -ksp_rtol 1.0e-12" + GridapPETSc.with(args=split(solver_options)) do + main(mesh_partition,distribute) + end +end +``` + +```@raw html +
+``` + +We can then run this script by calling [`mpiexecjl`](https://juliaparallel.org/MPI.jl/stable/usage/#Julia-wrapper-for-mpiexec) using bash: + +```bash +mpiexecjl -n 8 julia tut1_3d_petsc_mpi.jl +``` + +This gives the same final result as the previous script. + +### Nonlinear thermal conductivity + +Our final extension considers two-dimensional nonlinear thermal conductivity problem: + +```math +\begin{aligned} +-\nabla(\kappa(u)\nabla u) &= 0~\text{in }\Omega,\\ +\kappa(u)\nabla u\cdot\boldsymbol{n} &= g~\text{on }\Gamma_N,\\ +\kappa(u)\nabla u\cdot\boldsymbol{n} &= 0~\text{on }\partial\Omega\setminus\Gamma_N,\\ +u &= 0~\text{on }\Gamma_D. +\end{aligned} +``` + +where ``\kappa(u)=\kappa_0\exp{\xi u}``. The weak formulation for this problem with relaxation over the computational domain is: *Find* ``u\in H^1_{\Gamma_D}(D)`` *such that* ``R(u,v;\varphi)=0`` for all ``v\in H^1_{\Gamma_D}(D)`` where + +```math +R(u,v;\varphi) = \int_{D}I(\varphi)\kappa(u)\boldsymbol{\nabla}u\cdot\boldsymbol{\nabla}v~\mathrm{d}\boldsymbol{x} - \int_{\Gamma_N}gv~\mathrm{d}\boldsymbol{x}. +``` + +As we're considering a 2D problem, we consider modifications of Script 1 as follows. First, we introduce a nonlinear diffusivity by replacing line 20 with +```julia +κ(u) = exp(-u) # Diffusivity +``` + +We then replace the `a` and `l` on line 48 and 49 by the residual `R` given by +```julia +R(u,v,φ,dΩ,dΓ_N) = ∫((I ∘ φ)*(κ ∘ u)*∇(u)⋅∇(v))dΩ - ∫(g*v)dΓ_N +``` + +In addition we replace the `AffineFEStateMap` on line 50 with a [`NonlinearFEStateMap`](@ref). This enables automatic differentiation when the forward problem is nonlinear. +```julia +state_map = NonlinearFEStateMap(R,U,V,V_φ,U_reg,φh,dΩ,dΓ_N) +``` +This by default implements a standard `NewtonSolver` from GridapSolvers while utilising an LU solver for intermediate linear solves involving the Jacobian. As with other `FEStateMap` types, this constructor can optionally take assemblers and different solvers (e.g., PETSc solvers). + +Next, we replace the objective functional on line 52 with + +```julia +J(u,φ,dΩ,dΓ_N) = ∫((I ∘ φ)*(κ ∘ u)*∇(u)⋅∇(u))dΩ +``` + +For this problem we utilise automatic differentiation as the analytic calculation of the sensitivity is somewhat involved. We therefore remove line 53 and replace line 57 with +```julia +pcfs = PDEConstrainedFunctionals(J,[C],state_map,analytic_dC=[dC]) +``` + +Notice that the argument `analytic_dJ=...` has been removed, this enables the AD capability. We now have everything we need to run a nonlinear problem. + +```@raw html +
Script 5: combining the above gives (click me!) +``` + +```julia +using Gridap, LevelSetTopOpt + +# FE parameters +order = 1 # Finite element order +xmax = ymax = 1.0 # Domain size +dom = (0,xmax,0,ymax) # Bounding domain +el_size = (200,200) # Mesh partition size +prop_Γ_N = 0.2 # Γ_N size parameter +prop_Γ_D = 0.2 # Γ_D size parameter +f_Γ_N(x) = (x[1] ≈ xmax && # Γ_N indicator function + ymax/2-ymax*prop_Γ_N/2 - eps() <= x[2] <= ymax/2+ymax*prop_Γ_N/2 + eps()) +f_Γ_D(x) = (x[1] ≈ 0.0 && # Γ_D indicator function + (x[2] <= ymax*prop_Γ_D + eps() || x[2] >= ymax-ymax*prop_Γ_D - eps())) +# FD parameters +γ = 0.1 # HJ equation time step coefficient +γ_reinit = 0.5 # Reinit. equation time step coefficient +max_steps = floor(Int,minimum(el_size)/10) # Max steps for advection +tol = 1/(5order^2)/minimum(el_size) # Advection tolerance +# Problem parameters +κ(u) = exp(-u) # Diffusivity +g = 1 # Heat flow in +vf = 0.4 # Volume fraction constraint +lsf_func = initial_lsf(4,0.2) # Initial level set function +iter_mod = 10 # Output VTK files every 10th iteration +path = "./results/tut1_nonlinear/" # Output path +mkpath(path) # Create path +# Model +model = CartesianDiscreteModel(dom,el_size); +update_labels!(1,model,f_Γ_D,"Gamma_D") +update_labels!(2,model,f_Γ_N,"Gamma_N") +# Triangulation and measures +Ω = Triangulation(model) +Γ_N = BoundaryTriangulation(model,tags="Gamma_N") +dΩ = Measure(Ω,2*order) +dΓ_N = Measure(Γ_N,2*order) +# Spaces +reffe = ReferenceFE(lagrangian,Float64,order) +V = TestFESpace(model,reffe;dirichlet_tags=["Gamma_D"]) +U = TrialFESpace(V,0.0) +V_φ = TestFESpace(model,reffe) +V_reg = TestFESpace(model,reffe;dirichlet_tags=["Gamma_N"]) +U_reg = TrialFESpace(V_reg,0) +# Level set and interpolator +φh = interpolate(lsf_func,V_φ) +interp = SmoothErsatzMaterialInterpolation(η = 2*maximum(get_el_Δ(model))) +I,H,DH,ρ = interp.I,interp.H,interp.DH,interp.ρ +# Weak formulation +R(u,v,φ,dΩ,dΓ_N) = ∫((I ∘ φ)*(κ ∘ u)*∇(u)⋅∇(v))dΩ - ∫(g*v)dΓ_N +state_map = NonlinearFEStateMap(R,U,V,V_φ,U_reg,φh,dΩ,dΓ_N) +# Objective and constraints +J(u,φ,dΩ,dΓ_N) = ∫((I ∘ φ)*(κ ∘ u)*∇(u)⋅∇(u))dΩ +vol_D = sum(∫(1)dΩ) +C(u,φ,dΩ,dΓ_N) = ∫(((ρ ∘ φ) - vf)/vol_D)dΩ +dC(q,u,φ,dΩ,dΓ_N) = ∫(-1/vol_D*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ +pcfs = PDEConstrainedFunctionals(J,[C],state_map,analytic_dC=[dC]) +# Velocity extension +α = 4*maximum(get_el_Δ(model)) +a_hilb(p,q) =∫(α^2*∇(p)⋅∇(q) + p*q)dΩ +vel_ext = VelocityExtension(a_hilb,U_reg,V_reg) +# Finite difference scheme +scheme = FirstOrderStencil(length(el_size),Float64) +stencil = AdvectionStencil(scheme,model,V_φ,tol,max_steps) +# Optimiser +optimiser = AugmentedLagrangian(pcfs,stencil,vel_ext,φh;γ,γ_reinit,verbose=true,constraint_names=[:Vol]) +# Solve +for (it,uh,φh) in optimiser + data = ["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh)),"uh"=>uh] + iszero(it % iter_mod) && (writevtk(Ω,path*"struc_$it",cellfields=data);GC.gc()) + write_history(path*"/history.txt",get_history(optimiser)) +end +# Final structure +it = get_history(optimiser).niter; uh = get_state(pcfs) +writevtk(Ω,path*"struc_$it",cellfields=["phi"=>φh, + "H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh)),"uh"=>uh]) +``` + +```@raw html +
+``` + +Running this problem until convergence gives a final result of +``` +Iteration: 95 | L=1.6554e-01, J=1.6570e-01, Vol=-2.8836e-04, γ=7.5000e-02 +``` +with the optimised domain ``\Omega`` given by + +| ![](2d_min_thermal_nl_final_struc.png) | +|:--:| +|Figure 6: Visualisation of ``\Omega`` using the isovolume with ``\varphi\leq0``.| + +A 3D example of a nonlinear thermal conductivity problem can be found under `scripts/MPI/3d_nonlinear_thermal_compliance_ALM.jl`. + +## References +> 1. *Z. Guo, X. Cheng, and Z. Xia. Least dissipation principle of heat transport potential capacity and its application in heat conduction optimization. Chinese Science Bulletin, 48(4):406–410, Feb 2003. ISSN 1861-9541. doi: 10.1007/BF03183239.* +> +> 2. *C. Zhuang, Z. Xiong, and H. Ding. A level set method for topology optimization of heat conduction problem under multiple load cases. Computer Methods in Applied Mechanics and Engineering, 196(4–6):1074–1084, Jan 2007. ISSN 00457825. doi: 10.1016/j.cma.2006.08.005.* +> +> 3. *Allaire G, Jouve F, Toader AM (2004) Structural optimization using sensitivity analysis and a level-set method. Journal of Computational Physics 194(1):363–393. doi: 10.1016/j.jcp.2003.09.032* +> +> 4. *Allaire G, Dapogny C, Jouve F (2021) Shape and topology optimization, vol 22, Elsevier, p 1–132. doi: 10.1016/bs.hna.2020.10.004* +> +> 5. *Osher S, Fedkiw R (2006) Level Set Methods and Dynamic Implicit Surfaces, 1st edn. Applied Mathematical Sciences, Springer Science & Business Media. doi: 10.1007/b98879* +> +> 6. *Nocedal J, Wright SJ (2006) Numerical optimization, 2nd edn. Springer series in operations research, Springer, New York. doi: 10.1007/978-0-387-40065-5* \ No newline at end of file diff --git a/docs/src/usage/ad.md b/docs/src/usage/ad.md new file mode 100644 index 00000000..c8269b1e --- /dev/null +++ b/docs/src/usage/ad.md @@ -0,0 +1 @@ +# Automatic Differentiation \ No newline at end of file diff --git a/docs/src/usage/getting-started.md b/docs/src/usage/getting-started.md new file mode 100644 index 00000000..4f18339e --- /dev/null +++ b/docs/src/usage/getting-started.md @@ -0,0 +1,7 @@ +# Getting started + + + +## Known issues +- PETSc's GAMG preconditioner breaks for split Dirichlet DoFs (e.g., x constrained while y free for a single node). There is no simple fix for this. We recommend instead using MUMPS or another preconditioner for this case. +- There is a memory leak in Julia (ver>1.9) IO that affects `write_vtk`. This is a known problem and for now we occasionally use `gc()`. This will hopefully be fixed in an upcoming release. diff --git a/docs/src/usage/mpi-mode.md b/docs/src/usage/mpi-mode.md new file mode 100644 index 00000000..75398f1b --- /dev/null +++ b/docs/src/usage/mpi-mode.md @@ -0,0 +1 @@ +# MPI and PartitionedArrays.jl \ No newline at end of file diff --git a/docs/src/usage/petsc.md b/docs/src/usage/petsc.md new file mode 100644 index 00000000..163955df --- /dev/null +++ b/docs/src/usage/petsc.md @@ -0,0 +1 @@ +# PETSc \ No newline at end of file diff --git a/scripts/Benchmarks/benchmark.jl b/scripts/Benchmarks/benchmark.jl index 87d08e77..d91cbe6f 100644 --- a/scripts/Benchmarks/benchmark.jl +++ b/scripts/Benchmarks/benchmark.jl @@ -1,7 +1,10 @@ using Gridap, Gridap.MultiField, GridapDistributed, GridapPETSc, GridapSolvers, - PartitionedArrays, LSTO_Distributed, SparseMatricesCSR + PartitionedArrays, LevelSetTopOpt, SparseMatricesCSR + +using LevelSetTopOpt: get_deriv_space, get_aux_space,benchmark_optimizer, + benchmark_forward_problem,benchmark_advection,benchmark_reinitialisation, + benchmark_velocity_extension,benchmark_hilbertian_projection_map -using LSTO_Distributed: get_deriv_space, get_aux_space using GridapSolvers: NewtonSolver global NAME = ARGS[1] @@ -17,22 +20,22 @@ global NREPS = parse(Int,ARGS[9]) function nl_elast(mesh_partition,ranks,el_size,order,verbose) ## Parameters xmax=ymax=zmax=1.0 - prop_Γ_N = 0.4 + prop_Γ_N = 0.2 dom = (0,xmax,0,ymax,0,zmax) γ = 0.05 γ_reinit = 0.5 max_steps = floor(Int,minimum(el_size)/3) - tol = 1/(2order^2)*prod(inv,minimum(el_size)) + tol = 1/(2order^2)/minimum(el_size) vf = 0.5 η_coeff = 2 α_coeff = 4 ## FE Setup model = CartesianDiscreteModel(ranks,mesh_partition,dom,el_size) - Δ = get_Δ(model) + el_Δ = get_el_Δ(model) f_Γ_D(x) = (x[1] ≈ 0.0) - f_Γ_N(x) = (x[1] ≈ xmax) && (ymax/2-ymax*prop_Γ_N/4 - eps() <= x[2] <= ymax/2+ymax*prop_Γ_N/4 + eps()) && - (zmax/2-zmax*prop_Γ_N/4 - eps() <= x[3] <= zmax/2+zmax*prop_Γ_N/4 + eps()) + f_Γ_N(x) = (x[1] ≈ xmax) && (ymax/2-ymax*prop_Γ_N/2 - eps() <= x[2] <= ymax/2+ymax*prop_Γ_N/2 + eps()) && + (zmax/2-zmax*prop_Γ_N/2 - eps() <= x[3] <= zmax/2+zmax*prop_Γ_N/2 + eps()) update_labels!(1,model,f_Γ_D,"Gamma_D") update_labels!(2,model,f_Γ_N,"Gamma_N") @@ -53,10 +56,10 @@ function nl_elast(mesh_partition,ranks,el_size,order,verbose) U_reg = TrialFESpace(V_reg,0) ## Create FE functions - φh = interpolate(gen_lsf(4,0.2),V_φ) + φh = interpolate(initial_lsf(4,0.2),V_φ) ## Interpolation and weak form - interp = SmoothErsatzMaterialInterpolation(η = η_coeff*maximum(Δ)) + interp = SmoothErsatzMaterialInterpolation(η = η_coeff*maximum(el_Δ)) I,H,DH,ρ = interp.I,interp.H,interp.DH,interp.ρ ## Material properties @@ -89,7 +92,7 @@ function nl_elast(mesh_partition,ranks,el_size,order,verbose) ## Optimisation functionals Obj(u,φ,dΩ,dΓ_N) = ∫((I ∘ φ)*((dE∘(∇(u),∇(u))) ⊙ (S∘∇(u))))dΩ Vol(u,φ,dΩ,dΓ_N) = ∫(((ρ ∘ φ) - vf)/vol_D)dΩ - dVol(q,u,φ,dΩ,dΓ_N) = ∫(1/vol_D*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ + dVol(q,u,φ,dΩ,dΓ_N) = ∫(-1/vol_D*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ ## Finite difference solver stencil = AdvectionStencil(FirstOrderStencil(3,Float64),model,V_φ,tol,max_steps) @@ -110,7 +113,7 @@ function nl_elast(mesh_partition,ranks,el_size,order,verbose) pcfs = PDEConstrainedFunctionals(Obj,[Vol],state_map,analytic_dC=[dVol]) ## Hilbertian extension-regularisation problems - α = α_coeff*maximum(Δ) + α = α_coeff*maximum(el_Δ) a_hilb(p,q) = ∫(α^2*∇(p)⋅∇(q) + p*q)dΩ; vel_ext = VelocityExtension( a_hilb,U_reg,V_reg; @@ -125,24 +128,24 @@ end function therm(mesh_partition,ranks,el_size,order,verbose) ## Parameters xmax=ymax=zmax=1.0 - prop_Γ_N = 0.4 + prop_Γ_N = 0.2 prop_Γ_D = 0.2 dom = (0,xmax,0,ymax,0,zmax) γ = 0.05 γ_reinit = 0.5 max_steps = floor(Int,minimum(el_size)/3) - tol = 1/(2order^2)*prod(inv,minimum(el_size)) + tol = 1/(2order^2)/minimum(el_size) D = 1 η_coeff = 2 α_coeff = 4 ## FE Setup model = CartesianDiscreteModel(ranks,mesh_partition,dom,el_size); - Δ = get_Δ(model) + el_Δ = get_el_Δ(model) f_Γ_D(x) = (x[1] ≈ 0.0) && (x[2] <= ymax*prop_Γ_D + eps() || x[2] >= ymax-ymax*prop_Γ_D - eps()) && (x[3] <= zmax*prop_Γ_D + eps() || x[3] >= zmax-zmax*prop_Γ_D - eps()) - f_Γ_N(x) = (x[1] ≈ xmax) && (ymax/2-ymax*prop_Γ_N/4 - eps() <= x[2] <= ymax/2+ymax*prop_Γ_N/4 + eps()) && - (zmax/2-zmax*prop_Γ_N/4 - eps() <= x[3] <= zmax/2+zmax*prop_Γ_N/4 + eps()) + f_Γ_N(x) = (x[1] ≈ xmax) && (ymax/2-ymax*prop_Γ_N/2 - eps() <= x[2] <= ymax/2+ymax*prop_Γ_N/2 + eps()) && + (zmax/2-zmax*prop_Γ_N/2 - eps() <= x[3] <= zmax/2+zmax*prop_Γ_N/2 + eps()) update_labels!(1,model,f_Γ_D,"Gamma_D") update_labels!(2,model,f_Γ_N,"Gamma_N") @@ -161,10 +164,10 @@ function therm(mesh_partition,ranks,el_size,order,verbose) U_reg = TrialFESpace(V_reg,0) ## Create FE functions - φh = interpolate(gen_lsf(4,0.2),V_φ); + φh = interpolate(initial_lsf(4,0.2),V_φ); ## Interpolation and weak form - interp = SmoothErsatzMaterialInterpolation(η = η_coeff*maximum(Δ)) + interp = SmoothErsatzMaterialInterpolation(η = η_coeff*maximum(el_Δ)) I,H,DH,ρ = interp.I,interp.H,interp.DH,interp.ρ a(u,v,φ,dΩ,dΓ_N) = ∫((I ∘ φ)*D*∇(u)⋅∇(v))dΩ @@ -173,7 +176,7 @@ function therm(mesh_partition,ranks,el_size,order,verbose) ## Optimisation functionals ξ = 0.1 J(u,φ,dΩ,dΓ_N) = ∫((I ∘ φ)*D*∇(u)⋅∇(u) + ξ*(ρ ∘ φ))dΩ - dJ(q,u,φ,dΩ,dΓ_N) = ∫((ξ-D*∇(u)⋅∇(u))*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ + dJ(q,u,φ,dΩ,dΓ_N) = ∫((-ξ+D*∇(u)⋅∇(u))*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ ## Finite difference solver stencil = AdvectionStencil(FirstOrderStencil(3,Float64),model,V_φ,tol,max_steps) @@ -193,7 +196,7 @@ function therm(mesh_partition,ranks,el_size,order,verbose) pcfs = PDEConstrainedFunctionals(J,state_map,analytic_dJ=dJ) ## Hilbertian extension-regularisation problems - α = α_coeff*maximum(Δ) + α = α_coeff*maximum(el_Δ) a_hilb(p,q) = ∫(α^2*∇(p)⋅∇(q) + p*q)dΩ; vel_ext = VelocityExtension( a_hilb, U_reg, V_reg; @@ -208,13 +211,13 @@ end function elast(mesh_partition,ranks,el_size,order,verbose) ## Parameters xmax=ymax=zmax=1.0 - prop_Γ_N = 0.4 + prop_Γ_N = 0.2 dom = (0,xmax,0,ymax,0,zmax) γ = 0.05 γ_reinit = 0.5 max_steps = floor(Int,minimum(el_size)/3) - tol = 1/(2order^2)*prod(inv,minimum(el_size)) - C = isotropic_3d(1.,0.3) + tol = 1/(2order^2)/minimum(el_size) + C = isotropic_elast_tensor(3,1.,0.3) g = VectorValue(0,0,-1) η_coeff = 2 α_coeff = 4 @@ -222,10 +225,10 @@ function elast(mesh_partition,ranks,el_size,order,verbose) ## FE Setup model = CartesianDiscreteModel(ranks,mesh_partition,dom,el_size); - Δ = get_Δ(model) + el_Δ = get_el_Δ(model) f_Γ_D(x) = (x[1] ≈ 0.0) - f_Γ_N(x) = (x[1] ≈ xmax) && (ymax/2-ymax*prop_Γ_N/4 - eps() <= x[2] <= ymax/2+ymax*prop_Γ_N/4 + eps()) && - (zmax/2-zmax*prop_Γ_N/4 - eps() <= x[3] <= zmax/2+zmax*prop_Γ_N/4 + eps()) + f_Γ_N(x) = (x[1] ≈ xmax) && (ymax/2-ymax*prop_Γ_N/2 - eps() <= x[2] <= ymax/2+ymax*prop_Γ_N/2 + eps()) && + (zmax/2-zmax*prop_Γ_N/2 - eps() <= x[3] <= zmax/2+zmax*prop_Γ_N/2 + eps()) update_labels!(1,model,f_Γ_D,"Gamma_D") update_labels!(2,model,f_Γ_N,"Gamma_N") @@ -246,10 +249,10 @@ function elast(mesh_partition,ranks,el_size,order,verbose) U_reg = TrialFESpace(V_reg,0) ## Create FE functions - φh = interpolate(gen_lsf(4,0.2),V_φ) + φh = interpolate(initial_lsf(4,0.2),V_φ) ## Interpolation and weak form - interp = SmoothErsatzMaterialInterpolation(η = η_coeff*maximum(Δ)) + interp = SmoothErsatzMaterialInterpolation(η = η_coeff*maximum(el_Δ)) I,H,DH,ρ = interp.I,interp.H,interp.DH,interp.ρ a(u,v,φ,dΩ,dΓ_N) = ∫((I ∘ φ)*(C ⊙ ε(u) ⊙ ε(v)))dΩ @@ -257,9 +260,9 @@ function elast(mesh_partition,ranks,el_size,order,verbose) ## Optimisation functionals J(u,φ,dΩ,dΓ_N) = ∫((I ∘ φ)*(C ⊙ ε(u) ⊙ ε(u)))dΩ - dJ(q,u,φ,dΩ,dΓ_N) = ∫(( - C ⊙ ε(u) ⊙ ε(u))*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ + dJ(q,u,φ,dΩ,dΓ_N) = ∫((C ⊙ ε(u) ⊙ ε(u))*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ Vol(u,φ,dΩ,dΓ_N) = ∫(((ρ ∘ φ) - vf)/vol_D)dΩ - dVol(q,u,φ,dΩ,dΓ_N) = ∫(1/vol_D*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ + dVol(q,u,φ,dΩ,dΓ_N) = ∫(-1/vol_D*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ ## Finite difference solver stencil = AdvectionStencil(FirstOrderStencil(3,Float64),model,V_φ,tol,max_steps) @@ -279,7 +282,7 @@ function elast(mesh_partition,ranks,el_size,order,verbose) pcfs = PDEConstrainedFunctionals(J,[Vol],state_map;analytic_dJ=dJ,analytic_dC=[dVol]) ## Hilbertian extension-regularisation problems - α = α_coeff*maximum(Δ) + α = α_coeff*maximum(el_Δ) a_hilb(p,q) = ∫(α^2*∇(p)⋅∇(q) + p*q)dΩ; vel_ext = VelocityExtension( a_hilb,U_reg,V_reg; @@ -297,8 +300,8 @@ function inverter_HPM(mesh_partition,ranks,el_size,order,verbose) γ = 0.05 γ_reinit = 0.5 max_steps = floor(Int,minimum(el_size)/3) - tol = 1/(2order^2)*prod(inv,minimum(el_size)) - C = isotropic_3d(1.0,0.3) + tol = 1/(2order^2)/minimum(el_size) + C = isotropic_elast_tensor(3,1.0,0.3) η_coeff = 2 α_coeff = 4 vf=0.4 @@ -308,7 +311,7 @@ function inverter_HPM(mesh_partition,ranks,el_size,order,verbose) ## FE Setup model = CartesianDiscreteModel(ranks,mesh_partition,dom,el_size); - Δ = get_Δ(model) + el_Δ = get_el_Δ(model) f_Γ_in(x) = (x[1] ≈ 0.0) && (0.4 - eps() <= x[2] <= 0.6 + eps()) && (0.4 - eps() <= x[3] <= 0.6 + eps()) f_Γ_out(x) = (x[1] ≈ 1.0) && (0.4 - eps() <= x[2] <= 0.6 + eps()) && @@ -342,10 +345,10 @@ function inverter_HPM(mesh_partition,ranks,el_size,order,verbose) U_reg = TrialFESpace(V_reg,[0,0,0]) ## Create FE functions - φh = interpolate(gen_lsf(4,0.1),V_φ); + φh = interpolate(initial_lsf(4,0.1),V_φ); ## Interpolation and weak form - interp = SmoothErsatzMaterialInterpolation(η = η_coeff*maximum(Δ)) + interp = SmoothErsatzMaterialInterpolation(η = η_coeff*maximum(el_Δ)) I,H,DH,ρ = interp.I,interp.H,interp.DH,interp.ρ a(u,v,φ,dΩ,dΓ_in,dΓ_out) = ∫((I ∘ φ)*(C ⊙ ε(u) ⊙ ε(v)))dΩ + ∫(ks*(u⋅v))dΓ_out @@ -355,7 +358,7 @@ function inverter_HPM(mesh_partition,ranks,el_size,order,verbose) e₁ = VectorValue(1,0,0) J(u,φ,dΩ,dΓ_in,dΓ_out) = ∫((u⋅e₁)/vol_Γ_in)dΓ_in Vol(u,φ,dΩ,dΓ_in,dΓ_out) = ∫(((ρ ∘ φ) - vf)/vol_D)dΩ; - dVol(q,u,φ,dΩ,dΓ_in,dΓ_out) = ∫(1/vol_D*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ + dVol(q,u,φ,dΩ,dΓ_in,dΓ_out) = ∫(-1/vol_D*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ UΓ_out(u,φ,dΩ,dΓ_in,dΓ_out) = ∫((u⋅-e₁-δₓ)/vol_Γ_out)dΓ_out ## Finite difference solver @@ -376,7 +379,7 @@ function inverter_HPM(mesh_partition,ranks,el_size,order,verbose) pcfs = PDEConstrainedFunctionals(J,[Vol,UΓ_out],state_map,analytic_dC=[dVol,nothing]) ## Hilbertian extension-regularisation problems - α = α_coeff*maximum(Δ) + α = α_coeff*maximum(el_Δ) a_hilb(p,q) =∫(α^2*∇(p)⋅∇(q) + p*q)dΩ; vel_ext = VelocityExtension( a_hilb,U_reg,V_reg; diff --git a/scripts/Benchmarks/generate_benchmark_scripts.jl b/scripts/Benchmarks/generate_benchmark_scripts.jl index 29907994..257fdf4c 100644 --- a/scripts/Benchmarks/generate_benchmark_scripts.jl +++ b/scripts/Benchmarks/generate_benchmark_scripts.jl @@ -59,7 +59,7 @@ nreps = 10; # Number of benchmark repetitions dofs_per_proc = 32000; fe_order= 1; verbose= 1; -dir_name= "LSTO_Distributed"; +dir_name= "LevelSetTopOpt"; write_dir = "\$HOME/$dir_name/results/benchmarks/" N = 1:10; # Number of partitions in x-axis diff --git a/scripts/MPI/3d_elastic_compliance_ALM.jl b/scripts/MPI/3d_elastic_compliance_ALM.jl index a9769e14..1a706773 100644 --- a/scripts/MPI/3d_elastic_compliance_ALM.jl +++ b/scripts/MPI/3d_elastic_compliance_ALM.jl @@ -1,5 +1,5 @@ using Gridap, Gridap.MultiField, GridapDistributed, GridapPETSc, GridapSolvers, - PartitionedArrays, LSTO_Distributed, SparseMatricesCSR + PartitionedArrays, LevelSetTopOpt, SparseMatricesCSR """ (MPI) Minimum elastic compliance with augmented Lagrangian method in 3D. @@ -17,13 +17,13 @@ function main(mesh_partition,distribute,el_size) ## Parameters order = 1 xmax,ymax,zmax=(2.0,1.0,1.0) - prop_Γ_N = 0.4 + prop_Γ_N = 0.2 dom = (0,xmax,0,ymax,0,zmax) - γ = 0.05 + γ = 0.1 γ_reinit = 0.5 - max_steps = floor(Int,minimum(el_size)/3) - tol = 1/(2order^2)*prod(inv,minimum(el_size)) - C = isotropic_3d(1.,0.3) + max_steps = floor(Int,minimum(el_size)/10) + tol = 1/(5order^2)/minimum(el_size) + C = isotropic_elast_tensor(3,1.,0.3) g = VectorValue(0,0,-1) η_coeff = 2 α_coeff = 4 @@ -32,10 +32,10 @@ function main(mesh_partition,distribute,el_size) ## FE Setup model = CartesianDiscreteModel(ranks,mesh_partition,dom,el_size); - Δ = get_Δ(model) + el_Δ = get_el_Δ(model) f_Γ_D(x) = (x[1] ≈ 0.0) - f_Γ_N(x) = (x[1] ≈ xmax) && (ymax/2-ymax*prop_Γ_N/4 - eps() <= x[2] <= ymax/2+ymax*prop_Γ_N/4 + eps()) && - (zmax/2-zmax*prop_Γ_N/4 - eps() <= x[3] <= zmax/2+zmax*prop_Γ_N/4 + eps()) + f_Γ_N(x) = (x[1] ≈ xmax) && (ymax/2-ymax*prop_Γ_N/2 - eps() <= x[2] <= ymax/2+ymax*prop_Γ_N/2 + eps()) && + (zmax/2-zmax*prop_Γ_N/2 - eps() <= x[3] <= zmax/2+zmax*prop_Γ_N/2 + eps()) update_labels!(1,model,f_Γ_D,"Gamma_D") update_labels!(2,model,f_Γ_N,"Gamma_N") @@ -56,10 +56,10 @@ function main(mesh_partition,distribute,el_size) U_reg = TrialFESpace(V_reg,0) ## Create FE functions - φh = interpolate(gen_lsf(4,0.2),V_φ) + φh = interpolate(initial_lsf(4,0.2),V_φ) ## Interpolation and weak form - interp = SmoothErsatzMaterialInterpolation(η = η_coeff*maximum(Δ)) + interp = SmoothErsatzMaterialInterpolation(η = η_coeff*maximum(el_Δ)) I,H,DH,ρ = interp.I,interp.H,interp.DH,interp.ρ a(u,v,φ,dΩ,dΓ_N) = ∫((I ∘ φ)*(C ⊙ ε(u) ⊙ ε(v)))dΩ @@ -68,9 +68,9 @@ function main(mesh_partition,distribute,el_size) ## Optimisation functionals J(u,φ,dΩ,dΓ_N) = ∫((I ∘ φ)*(C ⊙ ε(u) ⊙ ε(u)))dΩ - dJ(q,u,φ,dΩ,dΓ_N) = ∫(( - C ⊙ ε(u) ⊙ ε(u))*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ + dJ(q,u,φ,dΩ,dΓ_N) = ∫((C ⊙ ε(u) ⊙ ε(u))*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ Vol(u,φ,dΩ,dΓ_N) = ∫(((ρ ∘ φ) - vf)/vol_D)dΩ - dVol(q,u,φ,dΩ,dΓ_N) = ∫(1/vol_D*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ + dVol(q,u,φ,dΩ,dΓ_N) = ∫(-1/vol_D*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ ## Finite difference solver and level set function stencil = AdvectionStencil(FirstOrderStencil(3,Float64),model,V_φ,tol,max_steps) @@ -90,7 +90,7 @@ function main(mesh_partition,distribute,el_size) pcfs = PDEConstrainedFunctionals(J,[Vol],state_map;analytic_dJ=dJ,analytic_dC=[dVol]) ## Hilbertian extension-regularisation problems - α = α_coeff*maximum(Δ) + α = α_coeff*maximum(el_Δ) a_hilb(p,q) = ∫(α^2*∇(p)⋅∇(q) + p*q)dΩ; vel_ext = VelocityExtension( a_hilb,U_reg,V_reg; @@ -99,15 +99,15 @@ function main(mesh_partition,distribute,el_size) ) ## Optimiser - make_dir(path;ranks=ranks) + i_am_main(ranks) && mkdir(path) optimiser = AugmentedLagrangian(pcfs,stencil,vel_ext,φh; γ,γ_reinit,verbose=i_am_main(ranks),constraint_names=[:Vol]) for (it, uh, φh) in optimiser - write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi))|"=>(norm ∘ ∇(φh)),"uh"=>uh]) + write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh)),"uh"=>uh]) write_history(path*"/history.txt",optimiser.history;ranks=ranks) end - it = optimiser.history.niter; uh = get_state(optimiser.problem) - write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi))|"=>(norm ∘ ∇(φh)),"uh"=>uh];iter_mod=1) + it = get_history(optimiser).niter; uh = get_state(pcfs) + write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh)),"uh"=>uh];iter_mod=1) end with_mpi() do distribute diff --git a/scripts/MPI/3d_hyperelastic_compliance_ALM.jl b/scripts/MPI/3d_hyperelastic_compliance_ALM.jl index a8f1e464..7bb9a0b2 100644 --- a/scripts/MPI/3d_hyperelastic_compliance_ALM.jl +++ b/scripts/MPI/3d_hyperelastic_compliance_ALM.jl @@ -1,5 +1,5 @@ using Gridap, Gridap.MultiField, GridapDistributed, GridapPETSc, GridapSolvers, - PartitionedArrays, LSTO_Distributed, SparseMatricesCSR + PartitionedArrays, LevelSetTopOpt, SparseMatricesCSR using GridapSolvers: NewtonSolver @@ -15,12 +15,12 @@ function main(mesh_partition,distribute,el_size) ## Parameters order = 1 xmax,ymax,zmax=(2.0,1.0,1.0) - prop_Γ_N = 0.4 + prop_Γ_N = 0.2 dom = (0,xmax,0,ymax,0,zmax) - γ = 0.05 + γ = 0.1 γ_reinit = 0.5 - max_steps = floor(Int,minimum(el_size)/3) - tol = 1/(2order^2)*prod(inv,minimum(el_size)) + max_steps = floor(Int,minimum(el_size)/10) + tol = 1/(5order^2)/minimum(el_size) η_coeff = 2 α_coeff = 4 vf = 0.4 @@ -28,10 +28,10 @@ function main(mesh_partition,distribute,el_size) ## FE Setup model = CartesianDiscreteModel(ranks,mesh_partition,dom,el_size) - Δ = get_Δ(model) + el_Δ = get_el_Δ(model) f_Γ_D(x) = (x[1] ≈ 0.0) - f_Γ_N(x) = (x[1] ≈ xmax) && (ymax/2-ymax*prop_Γ_N/4 - eps() <= x[2] <= ymax/2+ymax*prop_Γ_N/4 + eps()) && - (zmax/2-zmax*prop_Γ_N/4 - eps() <= x[3] <= zmax/2+zmax*prop_Γ_N/4 + eps()) + f_Γ_N(x) = (x[1] ≈ xmax) && (ymax/2-ymax*prop_Γ_N/2 - eps() <= x[2] <= ymax/2+ymax*prop_Γ_N/2 + eps()) && + (zmax/2-zmax*prop_Γ_N/2 - eps() <= x[3] <= zmax/2+zmax*prop_Γ_N/2 + eps()) update_labels!(1,model,f_Γ_D,"Gamma_D") update_labels!(2,model,f_Γ_N,"Gamma_N") @@ -52,10 +52,10 @@ function main(mesh_partition,distribute,el_size) U_reg = TrialFESpace(V_reg,0) ## Create FE functions - φh = interpolate(gen_lsf(4,0.2),V_φ) + φh = interpolate(initial_lsf(4,0.2),V_φ) ## Interpolation and weak form - interp = SmoothErsatzMaterialInterpolation(η = η_coeff*maximum(Δ)) + interp = SmoothErsatzMaterialInterpolation(η = η_coeff*maximum(el_Δ)) I,H,DH,ρ = interp.I,interp.H,interp.DH,interp.ρ ## Material properties and loading @@ -74,7 +74,7 @@ function main(mesh_partition,distribute,el_size) ## Optimisation functionals J(u,φ,dΩ,dΓ_N) = ∫((I ∘ φ)*((T ∘ ∇(u)) ⊙ ∇(u)))dΩ Vol(u,φ,dΩ,dΓ_N) = ∫(((ρ ∘ φ) - vf)/vol_D)dΩ - dVol(q,u,φ,dΩ,dΓ_N) = ∫(1/vol_D*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ + dVol(q,u,φ,dΩ,dΓ_N) = ∫(-1/vol_D*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ ## Finite difference solver and level set function stencil = AdvectionStencil(FirstOrderStencil(3,Float64),model,V_φ,tol,max_steps) @@ -95,7 +95,7 @@ function main(mesh_partition,distribute,el_size) pcfs = PDEConstrainedFunctionals(J,[Vol],state_map,analytic_dC=[dVol]) ## Hilbertian extension-regularisation problems - α = α_coeff*maximum(Δ) + α = α_coeff*maximum(el_Δ) a_hilb(p,q) = ∫(α^2*∇(p)⋅∇(q) + p*q)dΩ; vel_ext = VelocityExtension( a_hilb,U_reg,V_reg; @@ -104,15 +104,15 @@ function main(mesh_partition,distribute,el_size) ) ## Optimiser - make_dir(path,ranks=ranks) + i_am_main(ranks) && mkdir(path) optimiser = AugmentedLagrangian(pcfs,stencil,vel_ext,φh; γ,γ_reinit,verbose=i_am_main(ranks),constraint_names=[:Vol]) for (it, uh, φh) in optimiser - write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi))|"=>(norm ∘ ∇(φh)),"uh"=>uh]) + write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh)),"uh"=>uh]) write_history(path*"/history.txt",optimiser.history;ranks=ranks) end - it = optimiser.history.niter; uh = get_state(optimiser.problem) - write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi))|"=>(norm ∘ ∇(φh)),"uh"=>uh];iter_mod=1) + it = get_history(optimiser).niter; uh = get_state(pcfs) + write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh)),"uh"=>uh];iter_mod=1) end with_mpi() do distribute diff --git a/scripts/MPI/3d_hyperelastic_compliance_neohook_ALM.jl b/scripts/MPI/3d_hyperelastic_compliance_neohook_ALM.jl index 5276e858..c394a268 100644 --- a/scripts/MPI/3d_hyperelastic_compliance_neohook_ALM.jl +++ b/scripts/MPI/3d_hyperelastic_compliance_neohook_ALM.jl @@ -1,5 +1,5 @@ using Gridap, Gridap.MultiField, GridapDistributed, GridapPETSc, GridapSolvers, - PartitionedArrays, LSTO_Distributed, SparseMatricesCSR + PartitionedArrays, LevelSetTopOpt, SparseMatricesCSR using GridapSolvers: NewtonSolver @@ -15,12 +15,12 @@ function main(mesh_partition,distribute,el_size) ## Parameters order = 1 xmax,ymax,zmax=(2.0,1.0,1.0) - prop_Γ_N = 0.4 + prop_Γ_N = 0.2 dom = (0,xmax,0,ymax,0,zmax) - γ = 0.05 + γ = 0.1 γ_reinit = 0.5 - max_steps = floor(Int,minimum(el_size)/3) - tol = 1/(2order^2)*prod(inv,minimum(el_size)) + max_steps = floor(Int,minimum(el_size)/10) + tol = 1/(5order^2)/minimum(el_size) η_coeff = 2 α_coeff = 4 vf = 0.4 @@ -28,10 +28,10 @@ function main(mesh_partition,distribute,el_size) ## FE Setup model = CartesianDiscreteModel(ranks,mesh_partition,dom,el_size) - Δ = get_Δ(model) + el_Δ = get_el_Δ(model) f_Γ_D(x) = (x[1] ≈ 0.0) - f_Γ_N(x) = (x[1] ≈ xmax) && (ymax/2-ymax*prop_Γ_N/4 - eps() <= x[2] <= ymax/2+ymax*prop_Γ_N/4 + eps()) && - (zmax/2-zmax*prop_Γ_N/4 - eps() <= x[3] <= zmax/2+zmax*prop_Γ_N/4 + eps()) + f_Γ_N(x) = (x[1] ≈ xmax) && (ymax/2-ymax*prop_Γ_N/2 - eps() <= x[2] <= ymax/2+ymax*prop_Γ_N/2 + eps()) && + (zmax/2-zmax*prop_Γ_N/2 - eps() <= x[3] <= zmax/2+zmax*prop_Γ_N/2 + eps()) update_labels!(1,model,f_Γ_D,"Gamma_D") update_labels!(2,model,f_Γ_N,"Gamma_N") @@ -52,10 +52,10 @@ function main(mesh_partition,distribute,el_size) U_reg = TrialFESpace(V_reg,0) ## Create FE functions - φh = interpolate(gen_lsf(4,0.2),V_φ) + φh = interpolate(initial_lsf(4,0.2),V_φ) ## Interpolation and weak form - interp = SmoothErsatzMaterialInterpolation(η = η_coeff*maximum(Δ)) + interp = SmoothErsatzMaterialInterpolation(η = η_coeff*maximum(el_Δ)) I,H,DH,ρ = interp.I,interp.H,interp.DH,interp.ρ ## Material properties @@ -88,7 +88,7 @@ function main(mesh_partition,distribute,el_size) ## Optimisation functionals Obj(u,φ,dΩ,dΓ_N) = ∫((I ∘ φ)*((dE∘(∇(u),∇(u))) ⊙ (S∘∇(u))))dΩ Vol(u,φ,dΩ,dΓ_N) = ∫(((ρ ∘ φ) - vf)/vol_D)dΩ - dVol(q,u,φ,dΩ,dΓ_N) = ∫(1/vol_D*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ + dVol(q,u,φ,dΩ,dΓ_N) = ∫(-1/vol_D*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ ## Finite difference solver and level set function stencil = AdvectionStencil(FirstOrderStencil(3,Float64),model,V_φ,tol,max_steps) @@ -109,7 +109,7 @@ function main(mesh_partition,distribute,el_size) pcfs = PDEConstrainedFunctionals(Obj,[Vol],state_map,analytic_dC=[dVol]) ## Hilbertian extension-regularisation problems - α = α_coeff*maximum(Δ) + α = α_coeff*maximum(el_Δ) a_hilb(p,q) = ∫(α^2*∇(p)⋅∇(q) + p*q)dΩ; vel_ext = VelocityExtension( a_hilb,U_reg,V_reg; @@ -118,15 +118,15 @@ function main(mesh_partition,distribute,el_size) ) ## Optimiser - make_dir(path,ranks=ranks) + i_am_main(ranks) && mkdir(path) optimiser = AugmentedLagrangian(pcfs,stencil,vel_ext,φh; γ,γ_reinit,verbose=i_am_main(ranks),constraint_names=[:Vol]) for (it, uh, φh) in optimiser - write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi))|"=>(norm ∘ ∇(φh)),"uh"=>uh]) + write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh)),"uh"=>uh]) write_history(path*"/history.txt",optimiser.history;ranks=ranks) end - it = optimiser.history.niter; uh = get_state(optimiser.problem) - write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi))|"=>(norm ∘ ∇(φh)),"uh"=>uh];iter_mod=1) + it = get_history(optimiser).niter; uh = get_state(pcfs) + write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh)),"uh"=>uh];iter_mod=1) end with_mpi() do distribute diff --git a/scripts/MPI/3d_inverse_homenisation_ALM.jl b/scripts/MPI/3d_inverse_homenisation_ALM.jl index e4f8b39c..6b78ed2e 100644 --- a/scripts/MPI/3d_inverse_homenisation_ALM.jl +++ b/scripts/MPI/3d_inverse_homenisation_ALM.jl @@ -1,5 +1,5 @@ using Gridap, Gridap.MultiField, GridapDistributed, GridapPETSc, GridapSolvers, - PartitionedArrays, LSTO_Distributed, SparseMatricesCSR + PartitionedArrays, LevelSetTopOpt, SparseMatricesCSR """ (MPI) Maximum bulk modulus inverse homogenisation with augmented Lagrangian method in 3D. @@ -18,19 +18,20 @@ function main(mesh_partition,distribute,el_size) order = 1 xmax,ymax,zmax=(1.0,1.0,1.0) dom = (0,xmax,0,ymax,0,zmax) - γ = 0.05 + γ = 0.1 γ_reinit = 0.5 - max_steps = floor(Int,minimum(el_size)/3) - tol = 1/(2order^2)*prod(inv,minimum(el_size)) - C = isotropic_3d(1.,0.3) + max_steps = floor(Int,minimum(el_size)/10) + tol = 1/(5order^2)/minimum(el_size) + C = isotropic_elast_tensor(3,1.,0.3) η_coeff = 2 α_coeff = 4 vf = 0.5 path = dirname(dirname(@__DIR__))*"/results/3d_inverse_homenisation_ALM" + i_am_main(ranks) && mkdir(path) ## FE Setup model = CartesianDiscreteModel(ranks,mesh_partition,dom,el_size,isperiodic=(true,true,true)) - Δ = get_Δ(model) + el_Δ = get_el_Δ(model) f_Γ_D(x) = iszero(x) update_labels!(1,model,f_Γ_D,"origin") @@ -48,11 +49,11 @@ function main(mesh_partition,distribute,el_size) U_reg = TrialFESpace(V_reg) ## Create FE functions - lsf_fn(x) cos(2π*x[1]) + cos(2π*x[2]) + cos(2π*x[3]) + lsf_fn(x) = cos(2π*x[1]) + cos(2π*x[2]) + cos(2π*x[3]) φh = interpolate(lsf_fn,V_φ) ## Interpolation and weak form - interp = SmoothErsatzMaterialInterpolation(η = η_coeff*maximum(Δ)) + interp = SmoothErsatzMaterialInterpolation(η = η_coeff*maximum(el_Δ)) I,H,DH,ρ = interp.I,interp.H,interp.DH,interp.ρ εᴹ = (TensorValue(1.,0.,0.,0.,0.,0.,0.,0.,0.), # ϵᵢⱼ⁽¹¹⁾≡ϵᵢⱼ⁽¹⁾ @@ -75,9 +76,9 @@ function main(mesh_partition,distribute,el_size) 2(_C(C,ε(u[1])+εᴹ[1],ε(u[2])+εᴹ[2]) + _C(C,ε(u[1])+εᴹ[1],ε(u[3])+εᴹ[3]) + _C(C,ε(u[2])+εᴹ[2],ε(u[3])+εᴹ[3])))/9 J(u,φ,dΩ) = ∫(-(I ∘ φ)*_K(C,u,εᴹ))dΩ - dJ(q,u,φ,dΩ) = ∫(-_v_K(C,u,εᴹ)*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ + dJ(q,u,φ,dΩ) = ∫(_v_K(C,u,εᴹ)*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ Vol(u,φ,dΩ) = ∫(((ρ ∘ φ) - vf)/vol_D)dΩ; - dVol(q,u,φ,dΩ) = ∫(1/vol_D*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ + dVol(q,u,φ,dΩ) = ∫(-1/vol_D*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ ## Finite difference solver and level set function stencil = AdvectionStencil(FirstOrderStencil(3,Float64),model,V_φ,tol,max_steps) @@ -97,7 +98,7 @@ function main(mesh_partition,distribute,el_size) pcfs = PDEConstrainedFunctionals(J,[Vol],state_map;analytic_dJ=dJ,analytic_dC=[dVol]) ## Hilbertian extension-regularisation problems - α = α_coeff*maximum(Δ) + α = α_coeff*maximum(el_Δ) a_hilb(p,q) = ∫(α^2*∇(p)⋅∇(q) + p*q)dΩ; vel_ext = VelocityExtension( a_hilb,U_reg,V_reg, @@ -106,15 +107,14 @@ function main(mesh_partition,distribute,el_size) ) ## Optimiser - make_dir(path;ranks=ranks) optimiser = AugmentedLagrangian(pcfs,stencil,vel_ext,φh; γ,γ_reinit,verbose=i_am_main(ranks),constraint_names=[:Vol]) for (it, uh, φh) in optimiser - write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi))|"=>(norm ∘ ∇(φh))]) + write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh))]) write_history(path*"/history.txt",optimiser.history;ranks=ranks) end - it = optimiser.history.niter; uh = get_state(optimiser.problem) - write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi))|"=>(norm ∘ ∇(φh))];iter_mod=1) + it = get_history(optimiser).niter; uh = get_state(pcfs) + write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh))];iter_mod=1) end with_mpi() do distribute diff --git a/scripts/MPI/3d_inverter_ALM.jl b/scripts/MPI/3d_inverter_ALM.jl new file mode 100644 index 00000000..7f5e2534 --- /dev/null +++ b/scripts/MPI/3d_inverter_ALM.jl @@ -0,0 +1,141 @@ +using Gridap, Gridap.MultiField, GridapDistributed, GridapPETSc, GridapSolvers, + PartitionedArrays, LevelSetTopOpt, SparseMatricesCSR + +""" + (MPI) Inverter mechanism with Hilbertian projection method in 3D. + + Optimisation problem: + Min J(Ω) = ηᵢₙ*∫ u⋅e₁ dΓᵢₙ + Ω + s.t., Vol(Ω) = vf, + C(Ω) = 0, + ⎡u∈V=H¹(Ω;u(Γ_D)=0)ᵈ, + ⎣∫ C ⊙ ε(u) ⊙ ε(v) dΩ + ∫ kₛv⋅u dΓₒᵤₜ = ∫ v⋅g dΓᵢₙ , ∀v∈V. + + where C(Ω) = ∫ -u⋅e₁-δₓ dΓₒᵤₜ. We assume symmetry in the problem to aid + convergence. +""" +function main(mesh_partition,distribute,el_size) + ranks = distribute(LinearIndices((prod(mesh_partition),))) + + ## Parameters + order = 1 + dom = (0,1,0,1,0,1) + γ = 0.1 + γ_reinit = 0.5 + max_steps = floor(Int,minimum(el_size)/10) + tol = 1/(5order^2)/minimum(el_size) + C = isotropic_elast_tensor(3,1.0,0.3) + η_coeff = 2 + α_coeff = 4 + vf=0.4 + δₓ=0.5 + ks = 0.01 + g = VectorValue(1,0,0) + path = dirname(dirname(@__DIR__))*"/results/3d_inverter_ALM" + i_am_main(ranks) && mkdir(path) + + ## FE Setup + model = CartesianDiscreteModel(ranks,mesh_partition,dom,el_size); + el_Δ = get_el_Δ(model) + f_Γ_in(x) = (x[1] ≈ 0.0) && (0.4 - eps() <= x[2] <= 0.6 + eps()) && + (0.4 - eps() <= x[3] <= 0.6 + eps()) + f_Γ_out(x) = (x[1] ≈ 1.0) && (0.4 - eps() <= x[2] <= 0.6 + eps()) && + (0.4 - eps() <= x[3] <= 0.6 + eps()) + f_Γ_out_ext(x) = ~f_Γ_out(x) && (0.9 <= x[1] <= 1.0) && (0.3 - eps() <= x[2] <= 0.7 + eps()) && + (0.3 - eps() <= x[3] <= 0.7 + eps()) + f_Γ_D(x) = (x[1] ≈ 0.0) && (x[2] <= 0.1 || x[2] >= 0.9) && (x[3] <= 0.1 || x[3] >= 0.9) + update_labels!(1,model,f_Γ_in,"Gamma_in") + update_labels!(2,model,f_Γ_out,"Gamma_out") + update_labels!(3,model,f_Γ_out_ext,"Gamma_out_ext") + update_labels!(4,model,f_Γ_D,"Gamma_D") + + ## Triangulations and measures + Ω = Triangulation(model) + Γ_in = BoundaryTriangulation(model,tags="Gamma_in") + Γ_out = BoundaryTriangulation(model,tags="Gamma_out") + dΩ = Measure(Ω,2*order) + dΓ_in = Measure(Γ_in,2*order) + dΓ_out = Measure(Γ_out,2*order) + vol_D = sum(∫(1)dΩ) + vol_Γ_in = sum(∫(1)dΓ_in) + vol_Γ_out = sum(∫(1)dΓ_out) + + ## Spaces + reffe = ReferenceFE(lagrangian,VectorValue{3,Float64},order) + reffe_scalar = ReferenceFE(lagrangian,Float64,order) + V = TestFESpace(model,reffe;dirichlet_tags=["Gamma_D"]) + U = TrialFESpace(V,VectorValue(0.0,0.0,0.0)) + V_φ = TestFESpace(model,reffe_scalar) + V_reg = TestFESpace(model,reffe_scalar;dirichlet_tags=["Gamma_in","Gamma_out","Gamma_out_ext"]) + U_reg = TrialFESpace(V_reg,[0,0,0]) + + ## Create FE functions + sphere(x,(xc,yc,zc)) = -sqrt((x[1]-xc)^2+(x[2]-yc)^2+(x[3]-zc)^2) + 0.2 + lsf_fn(x) = max(initial_lsf(4,0.2)(x),sphere(x,(1,0,0)), + sphere(x,(1,0,1)),sphere(x,(1,1,0)),sphere(x,(1,1,1))) + φh = interpolate(lsf_fn,V_φ); + + ## Interpolation and weak form + interp = SmoothErsatzMaterialInterpolation(η = η_coeff*maximum(el_Δ)) + I,H,DH,ρ = interp.I,interp.H,interp.DH,interp.ρ + + a(u,v,φ,dΩ,dΓ_in,dΓ_out) = ∫((I ∘ φ)*(C ⊙ ε(u) ⊙ ε(v)))dΩ + ∫(ks*(u⋅v))dΓ_out + l(v,φ,dΩ,dΓ_in,dΓ_out) = ∫(v⋅g)dΓ_in + + ## Optimisation functionals + e₁ = VectorValue(1,0,0) + J(u,φ,dΩ,dΓ_in,dΓ_out) = ∫((u⋅e₁)/vol_Γ_in)dΓ_in + Vol(u,φ,dΩ,dΓ_in,dΓ_out) = ∫(((ρ ∘ φ) - vf)/vol_D)dΩ; + dVol(q,u,φ,dΩ,dΓ_in,dΓ_out) = ∫(-1/vol_D*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ + UΓ_out(u,φ,dΩ,dΓ_in,dΓ_out) = ∫((u⋅-e₁-δₓ)/vol_Γ_out)dΓ_out + + ## Finite difference solver and level set function + stencil = AdvectionStencil(FirstOrderStencil(3,Float64),model,V_φ,tol,max_steps) + + ## Setup solver and FE operators + Tm = SparseMatrixCSR{0,PetscScalar,PetscInt} + Tv = Vector{PetscScalar} + solver = ElasticitySolver(V) + + state_map = AffineFEStateMap( + a,l,U,V,V_φ,U_reg,φh,dΩ,dΓ_in,dΓ_out; + assem_U = SparseMatrixAssembler(Tm,Tv,U,V), + assem_adjoint = SparseMatrixAssembler(Tm,Tv,V,U), + assem_deriv = SparseMatrixAssembler(Tm,Tv,U_reg,U_reg), + ls = solver, adjoint_ls = solver + ) + pcfs = PDEConstrainedFunctionals(J,[Vol,UΓ_out],state_map,analytic_dC=[dVol,nothing]) + + ## Hilbertian extension-regularisation problems + α = α_coeff*maximum(el_Δ) + a_hilb(p,q) =∫(α^2*∇(p)⋅∇(q) + p*q)dΩ; + vel_ext = VelocityExtension( + a_hilb,U_reg,V_reg; + assem = SparseMatrixAssembler(Tm,Tv,U_reg,V_reg), + ls = PETScLinearSolver() + ) + + ## Optimiser + ls_enabled=false # Setting to true will use a line search instead of oscillation detection + optimiser = HilbertianProjection(pcfs,stencil,vel_ext,φh;γ,γ_reinit,α_min=0.4,ls_enabled, + verbose=i_am_main(ranks),constraint_names=[:Vol,:UΓ_out]) + for (it, uh, φh) in optimiser + write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh)),"uh"=>uh]) + write_history(path*"/history.txt",optimiser.history;ranks=ranks) + end + it = get_history(optimiser).niter; uh = get_state(pcfs) + write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh)),"uh"=>uh];iter_mod=1) +end + +with_mpi() do distribute + mesh_partition = (5,5,5) + el_size = (100,100,100) + hilb_solver_options = "-pc_type gamg -ksp_type cg -ksp_error_if_not_converged true + -ksp_converged_reason -ksp_rtol 1.0e-12 -mat_block_size 3 + -mg_levels_ksp_type chebyshev -mg_levels_esteig_ksp_type cg -mg_coarse_sub_pc_type cholesky" + + GridapPETSc.with(args=split(hilb_solver_options)) do + main(mesh_partition,distribute,el_size) + end +end \ No newline at end of file diff --git a/scripts/MPI/3d_inverter_HPM.jl b/scripts/MPI/3d_inverter_HPM.jl index 9b782036..ad041a18 100644 --- a/scripts/MPI/3d_inverter_HPM.jl +++ b/scripts/MPI/3d_inverter_HPM.jl @@ -1,5 +1,5 @@ using Gridap, Gridap.MultiField, GridapDistributed, GridapPETSc, GridapSolvers, - PartitionedArrays, LSTO_Distributed, SparseMatricesCSR + PartitionedArrays, LevelSetTopOpt, SparseMatricesCSR """ (MPI) Inverter mechanism with Hilbertian projection method in 3D. @@ -21,11 +21,11 @@ function main(mesh_partition,distribute,el_size) ## Parameters order = 1 dom = (0,1,0,1,0,1) - γ = 0.05 + γ = 0.1 γ_reinit = 0.5 - max_steps = floor(Int,minimum(el_size)/3) - tol = 1/(2order^2)*prod(inv,minimum(el_size)) - C = isotropic_3d(1.0,0.3) + max_steps = floor(Int,minimum(el_size)/10) + tol = 1/(5order^2)/minimum(el_size) + C = isotropic_elast_tensor(3,1.0,0.3) η_coeff = 2 α_coeff = 4 vf=0.4 @@ -33,10 +33,11 @@ function main(mesh_partition,distribute,el_size) ks = 0.01 g = VectorValue(1,0,0) path = dirname(dirname(@__DIR__))*"/results/3d_inverter_HPM" + i_am_main(ranks) && mkdir(path) ## FE Setup model = CartesianDiscreteModel(ranks,mesh_partition,dom,el_size); - Δ = get_Δ(model) + el_Δ = get_el_Δ(model) f_Γ_in(x) = (x[1] ≈ 0.0) && (0.4 - eps() <= x[2] <= 0.6 + eps()) && (0.4 - eps() <= x[3] <= 0.6 + eps()) f_Γ_out(x) = (x[1] ≈ 1.0) && (0.4 - eps() <= x[2] <= 0.6 + eps()) && @@ -71,12 +72,12 @@ function main(mesh_partition,distribute,el_size) ## Create FE functions sphere(x,(xc,yc,zc)) = -sqrt((x[1]-xc)^2+(x[2]-yc)^2+(x[3]-zc)^2) + 0.2 - lsf_fn(x) = max(gen_lsf(4,0.2)(x),sphere(x,(1,0,0)), + lsf_fn(x) = max(initial_lsf(4,0.2)(x),sphere(x,(1,0,0)), sphere(x,(1,0,1)),sphere(x,(1,1,0)),sphere(x,(1,1,1))) φh = interpolate(lsf_fn,V_φ); ## Interpolation and weak form - interp = SmoothErsatzMaterialInterpolation(η = η_coeff*maximum(Δ)) + interp = SmoothErsatzMaterialInterpolation(η = η_coeff*maximum(el_Δ)) I,H,DH,ρ = interp.I,interp.H,interp.DH,interp.ρ a(u,v,φ,dΩ,dΓ_in,dΓ_out) = ∫((I ∘ φ)*(C ⊙ ε(u) ⊙ ε(v)))dΩ + ∫(ks*(u⋅v))dΓ_out @@ -86,7 +87,7 @@ function main(mesh_partition,distribute,el_size) e₁ = VectorValue(1,0,0) J(u,φ,dΩ,dΓ_in,dΓ_out) = ∫((u⋅e₁)/vol_Γ_in)dΓ_in Vol(u,φ,dΩ,dΓ_in,dΓ_out) = ∫(((ρ ∘ φ) - vf)/vol_D)dΩ; - dVol(q,u,φ,dΩ,dΓ_in,dΓ_out) = ∫(1/vol_D*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ + dVol(q,u,φ,dΩ,dΓ_in,dΓ_out) = ∫(-1/vol_D*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ UΓ_out(u,φ,dΩ,dΓ_in,dΓ_out) = ∫((u⋅-e₁-δₓ)/vol_Γ_out)dΓ_out ## Finite difference solver and level set function @@ -107,7 +108,7 @@ function main(mesh_partition,distribute,el_size) pcfs = PDEConstrainedFunctionals(J,[Vol,UΓ_out],state_map,analytic_dC=[dVol,nothing]) ## Hilbertian extension-regularisation problems - α = α_coeff*maximum(Δ) + α = α_coeff*maximum(el_Δ) a_hilb(p,q) =∫(α^2*∇(p)⋅∇(q) + p*q)dΩ; vel_ext = VelocityExtension( a_hilb,U_reg,V_reg; @@ -116,15 +117,15 @@ function main(mesh_partition,distribute,el_size) ) ## Optimiser - make_dir(path;ranks=ranks) - optimiser = HilbertianProjection(pcfs,stencil,vel_ext,φh;γ,γ_reinit,α_min=0.7,ls_γ_max=0.05, - verbose=i_am_main(ranks),constraint_names=[:Vol,:UΓ_out]) + ls_enabled=false # Setting to true will use a line search instead of oscillation detection + optimiser = HilbertianProjection(pcfs,stencil,vel_ext,φh;γ,γ_reinit,ls_enabled,α_min=0.7, + ls_γ_max=0.05,verbose=i_am_main(ranks),constraint_names=[:Vol,:UΓ_out]) for (it, uh, φh) in optimiser - write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi))|"=>(norm ∘ ∇(φh)),"uh"=>uh]) + write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh)),"uh"=>uh]) write_history(path*"/history.txt",optimiser.history;ranks=ranks) end - it = optimiser.history.niter; uh = get_state(optimiser.problem) - write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi))|"=>(norm ∘ ∇(φh)),"uh"=>uh];iter_mod=1) + it = get_history(optimiser).niter; uh = get_state(pcfs) + write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh)),"uh"=>uh];iter_mod=1) end with_mpi() do distribute diff --git a/scripts/MPI/3d_nonlinear_thermal_compliance_ALM.jl b/scripts/MPI/3d_nonlinear_thermal_compliance_ALM.jl index 84357e3b..2fb8ce00 100644 --- a/scripts/MPI/3d_nonlinear_thermal_compliance_ALM.jl +++ b/scripts/MPI/3d_nonlinear_thermal_compliance_ALM.jl @@ -1,5 +1,5 @@ using Gridap, Gridap.MultiField, GridapDistributed, GridapPETSc, GridapSolvers, - PartitionedArrays, LSTO_Distributed, SparseMatricesCSR + PartitionedArrays, LevelSetTopOpt, SparseMatricesCSR using GridapSolvers: NewtonSolver @@ -21,25 +21,26 @@ function main(mesh_partition,distribute,el_size) ## Parameters order = 1 xmax=ymax=zmax=1.0 - prop_Γ_N = 0.4 + prop_Γ_N = 0.2 prop_Γ_D = 0.2 dom = (0,xmax,0,ymax,0,zmax) γ = 0.1 γ_reinit = 0.5 - max_steps = floor(Int,minimum(el_size)/3) - tol = 1/(2order^2)*prod(inv,minimum(el_size)) + max_steps = floor(Int,minimum(el_size)/10) + tol = 1/(5order^2)/minimum(el_size) η_coeff = 2 α_coeff = 4 vf = 0.4 path = dirname(dirname(@__DIR__))*"/results/3d_nonlinear_thermal_compliance_ALM" + i_am_main(ranks) && mkdir(path) ## FE Setup model = CartesianDiscreteModel(ranks,mesh_partition,dom,el_size); - Δ = get_Δ(model) + el_Δ = get_el_Δ(model) f_Γ_D(x) = (x[1] ≈ 0.0 && (x[2] <= ymax*prop_Γ_D + eps() || x[2] >= ymax-ymax*prop_Γ_D - eps()) && (x[3] <= zmax*prop_Γ_D + eps() || x[3] >= zmax-zmax*prop_Γ_D - eps())) - f_Γ_N(x) = (x[1] ≈ xmax && ymax/2-ymax*prop_Γ_N/4 - eps() <= x[2] <= ymax/2+ymax*prop_Γ_N/4 + eps() && - zmax/2-zmax*prop_Γ_N/4 - eps() <= x[3] <= zmax/2+zmax*prop_Γ_N/4 + eps()) + f_Γ_N(x) = (x[1] ≈ xmax && ymax/2-ymax*prop_Γ_N/2 - eps() <= x[2] <= ymax/2+ymax*prop_Γ_N/2 + eps() && + zmax/2-zmax*prop_Γ_N/2 - eps() <= x[3] <= zmax/2+zmax*prop_Γ_N/2 + eps()) update_labels!(1,model,f_Γ_D,"Gamma_D") update_labels!(2,model,f_Γ_N,"Gamma_N") @@ -59,10 +60,10 @@ function main(mesh_partition,distribute,el_size) U_reg = TrialFESpace(V_reg,0) ## Create FE functions - φh = interpolate(gen_lsf(4,0.2),V_φ) + φh = interpolate(initial_lsf(4,0.2),V_φ) ## Interpolation and weak form - interp = SmoothErsatzMaterialInterpolation(η = η_coeff*maximum(Δ)) + interp = SmoothErsatzMaterialInterpolation(η = η_coeff*maximum(el_Δ)) I,H,DH,ρ = interp.I,interp.H,interp.DH,interp.ρ κ0 = 1 @@ -73,7 +74,7 @@ function main(mesh_partition,distribute,el_size) ## Optimisation functionals J(u,φ,dΩ,dΓ_N) = ∫((I ∘ φ)*(κ ∘ u)*∇(u)⋅∇(u))dΩ Vol(u,φ,dΩ,dΓ_N) = ∫(((ρ ∘ φ) - vf)/vol_D)dΩ - dVol(q,u,φ,dΩ,dΓ_N) = ∫(1/vol_D*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ + dVol(q,u,φ,dΩ,dΓ_N) = ∫(-1/vol_D*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ ## Finite difference solver and level set function stencil = AdvectionStencil(FirstOrderStencil(3,Float64),model,V_φ,tol,max_steps) @@ -95,7 +96,7 @@ function main(mesh_partition,distribute,el_size) pcfs = PDEConstrainedFunctionals(J,[Vol],state_map,analytic_dC=[dVol]) ## Hilbertian extension-regularisation problems - α = α_coeff*maximum(Δ) + α = α_coeff*maximum(el_Δ) a_hilb(p,q) = ∫(α^2*∇(p)⋅∇(q) + p*q)dΩ vel_ext = VelocityExtension( a_hilb,U_reg,V_reg; @@ -104,24 +105,21 @@ function main(mesh_partition,distribute,el_size) ) ## Optimiser - make_dir(path,ranks=ranks) optimiser = AugmentedLagrangian(pcfs,stencil,vel_ext,φh; γ,γ_reinit,verbose=i_am_main(ranks),constraint_names=[:Vol]) for (it, uh, φh) in optimiser - write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi))|"=>(norm ∘ ∇(φh)),"uh"=>uh]) + write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh)),"uh"=>uh]) write_history(path*"/history.txt",optimiser.history;ranks=ranks) end - it = optimiser.history.niter; uh = get_state(optimiser.problem) - write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi))|"=>(norm ∘ ∇(φh)),"uh"=>uh];iter_mod=1) + it = get_history(optimiser).niter; uh = get_state(pcfs) + write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh)),"uh"=>uh];iter_mod=1) end with_mpi() do distribute mesh_partition = (5,5,5) el_size = (150,150,150) - options = "-snes_type newtonls -snes_linesearch_type basic -snes_linesearch_damping 1.0"* - " -snes_rtol 1.0e-14 -snes_atol 0.0 -snes_monitor -pc_type gamg -ksp_type cg"* - " -snes_converged_reason -ksp_converged_reason -ksp_error_if_not_converged true -ksp_rtol 1.0e-12"* - " -mat_block_size 3 -mg_levels_ksp_type chebyshev -mg_levels_esteig_ksp_type cg -mg_coarse_sub_pc_type cholesky" + options = "-pc_type gamg -ksp_type cg -ksp_error_if_not_converged true + -ksp_converged_reason -ksp_rtol 1.0e-12" GridapPETSc.with(args=split(options)) do main(mesh_partition,distribute,el_size) diff --git a/scripts/MPI/3d_thermal_compliance_ALM.jl b/scripts/MPI/3d_thermal_compliance_ALM.jl index d191f53b..4e84a0b3 100644 --- a/scripts/MPI/3d_thermal_compliance_ALM.jl +++ b/scripts/MPI/3d_thermal_compliance_ALM.jl @@ -1,8 +1,15 @@ using Gridap, Gridap.MultiField, GridapDistributed, GridapPETSc, GridapSolvers, - PartitionedArrays, LSTO_Distributed, SparseMatricesCSR + PartitionedArrays, LevelSetTopOpt, SparseMatricesCSR using GridapSolvers: NewtonSolver +global elx = parse(Int,ARGS[1]) +global ely = parse(Int,ARGS[2]) +global elz = parse(Int,ARGS[3]) +global Px = parse(Int,ARGS[4]) +global Py = parse(Int,ARGS[5]) +global Pz = parse(Int,ARGS[6]) + """ (MPI) Minimum thermal compliance with augmented Lagrangian method in 3D. @@ -13,32 +20,33 @@ using GridapSolvers: NewtonSolver ⎡u∈V=H¹(Ω;u(Γ_D)=0), ⎣∫ κ*∇(u)⋅∇(v) dΩ = ∫ v dΓ_N, ∀v∈V. """ -function main(mesh_partition,distribute,el_size) +function main(mesh_partition,distribute,el_size,coef,step) ranks = distribute(LinearIndices((prod(mesh_partition),))) ## Parameters order = 1 xmax=ymax=zmax=1.0 - prop_Γ_N = 0.4 + prop_Γ_N = 0.2 prop_Γ_D = 0.2 dom = (0,xmax,0,ymax,0,zmax) γ = 0.1 γ_reinit = 0.5 - max_steps = floor(Int,minimum(el_size)/3) - tol = 1/(2order^2)*prod(inv,minimum(el_size)) + max_steps = floor(Int,minimum(el_size)/step) + tol = 1/(coef*order^2)/minimum(el_size) κ = 1 η_coeff = 2 α_coeff = 4 vf = 0.4 - path = dirname(dirname(@__DIR__))*"/results/3d_thermal_compliance_ALM" + path = dirname(dirname(@__DIR__))*"/results/3d_thermal_compliance_ALM_Nx$(el_size[1])_Coef$(coef)_Step$(step)" + i_am_main(ranks) && mkdir(path) ## FE Setup model = CartesianDiscreteModel(ranks,mesh_partition,dom,el_size); - Δ = get_Δ(model) + el_Δ = get_el_Δ(model) f_Γ_D(x) = (x[1] ≈ 0.0) && (x[2] <= ymax*prop_Γ_D + eps() || x[2] >= ymax-ymax*prop_Γ_D - eps()) && (x[3] <= zmax*prop_Γ_D + eps() || x[3] >= zmax-zmax*prop_Γ_D - eps()) - f_Γ_N(x) = (x[1] ≈ xmax) && (ymax/2-ymax*prop_Γ_N/4 - eps() <= x[2] <= ymax/2+ymax*prop_Γ_N/4 + eps()) && - (zmax/2-zmax*prop_Γ_N/4 - eps() <= x[3] <= zmax/2+zmax*prop_Γ_N/4 + eps()) + f_Γ_N(x) = (x[1] ≈ xmax) && (ymax/2-ymax*prop_Γ_N/2 - eps() <= x[2] <= ymax/2+ymax*prop_Γ_N/2 + eps()) && + (zmax/2-zmax*prop_Γ_N/2 - eps() <= x[3] <= zmax/2+zmax*prop_Γ_N/2 + eps()) update_labels!(1,model,f_Γ_D,"Gamma_D") update_labels!(2,model,f_Γ_N,"Gamma_N") @@ -58,10 +66,10 @@ function main(mesh_partition,distribute,el_size) U_reg = TrialFESpace(V_reg,0) ## Create FE functions - φh = interpolate(gen_lsf(4,0.2),V_φ); + φh = interpolate(initial_lsf(4,0.2),V_φ); ## Interpolation and weak form - interp = SmoothErsatzMaterialInterpolation(η = η_coeff*maximum(Δ)) + interp = SmoothErsatzMaterialInterpolation(η = η_coeff*maximum(el_Δ)) I,H,DH,ρ = interp.I,interp.H,interp.DH,interp.ρ a(u,v,φ,dΩ,dΓ_N) = ∫((I ∘ φ)*κ*∇(u)⋅∇(v))dΩ @@ -69,9 +77,9 @@ function main(mesh_partition,distribute,el_size) ## Optimisation functionals J(u,φ,dΩ,dΓ_N) = ∫((I ∘ φ)*κ*∇(u)⋅∇(u))dΩ - dJ(q,u,φ,dΩ,dΓ_N) = ∫((-κ*∇(u)⋅∇(u))*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ + dJ(q,u,φ,dΩ,dΓ_N) = ∫((κ*∇(u)⋅∇(u))*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ Vol(u,φ,dΩ,dΓ_N) = ∫(((ρ ∘ φ) - vf)/vol_D)dΩ - dVol(q,u,φ,dΩ,dΓ_N) = ∫(1/vol_D*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ + dVol(q,u,φ,dΩ,dΓ_N) = ∫(-1/vol_D*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ ## Finite difference solver and level set function stencil = AdvectionStencil(FirstOrderStencil(3,Float64),model,V_φ,tol,max_steps) @@ -91,7 +99,7 @@ function main(mesh_partition,distribute,el_size) pcfs = PDEConstrainedFunctionals(J,[Vol],state_map,analytic_dJ=dJ,analytic_dC=[dVol]) ## Hilbertian extension-regularisation problems - α = α_coeff*maximum(Δ) + α = α_coeff*maximum(el_Δ) a_hilb(p,q) = ∫(α^2*∇(p)⋅∇(q) + p*q)dΩ; vel_ext = VelocityExtension( a_hilb, U_reg, V_reg; @@ -100,24 +108,30 @@ function main(mesh_partition,distribute,el_size) ) ## Optimiser - make_dir(path;ranks=ranks) optimiser = AugmentedLagrangian(pcfs,stencil,vel_ext,φh; γ,γ_reinit,verbose=i_am_main(ranks),constraint_names=[:Vol]) for (it, uh, φh) in optimiser - write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi))|"=>(norm ∘ ∇(φh)),"uh"=>uh]) + write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh)),"uh"=>uh]) write_history(path*"/history.txt",optimiser.history;ranks=ranks) end - it = optimiser.history.niter; uh = get_state(optimiser.problem) - write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi))|"=>(norm ∘ ∇(φh)),"uh"=>uh];iter_mod=1) + it = get_history(optimiser).niter; uh = get_state(pcfs) + write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh)),"uh"=>uh];iter_mod=1) end with_mpi() do distribute - mesh_partition = (5,5,5) - el_size = (150,150,150) + mesh_partition = (Px,Py,Pz) + el_size = (elx,ely,elz) all_solver_options = "-pc_type gamg -ksp_type cg -ksp_error_if_not_converged true -ksp_converged_reason -ksp_rtol 1.0e-12" GridapPETSc.with(args=split(all_solver_options)) do - main(mesh_partition,distribute,el_size) + main(mesh_partition,distribute,el_size,5,10) + main(mesh_partition,distribute,el_size,2,10) + + main(mesh_partition,distribute,el_size,5,5) + main(mesh_partition,distribute,el_size,2,5) + + main(mesh_partition,distribute,el_size,5,3) + main(mesh_partition,distribute,el_size,2,3) end end; \ No newline at end of file diff --git a/scripts/MPI/elastic_compliance_ALM.jl b/scripts/MPI/elastic_compliance_ALM.jl index 005ffb61..5be16714 100644 --- a/scripts/MPI/elastic_compliance_ALM.jl +++ b/scripts/MPI/elastic_compliance_ALM.jl @@ -1,5 +1,5 @@ using Gridap, Gridap.MultiField, GridapDistributed, GridapPETSc, GridapSolvers, - PartitionedArrays, LSTO_Distributed, SparseMatricesCSR + PartitionedArrays, LevelSetTopOpt, SparseMatricesCSR """ (MPI) Minimum elastic compliance with augmented Lagrangian method in 2D. @@ -17,24 +17,25 @@ function main(mesh_partition,distribute,el_size) ## Parameters order = 1 xmax,ymax=(2.0,1.0) - prop_Γ_N = 0.4 + prop_Γ_N = 0.2 dom = (0,xmax,0,ymax) γ = 0.1 γ_reinit = 0.5 max_steps = floor(Int,minimum(el_size)/10) - tol = 1/(10order^2)*prod(inv,minimum(el_size)) - C = isotropic_2d(1.,0.3) + tol = 1/(5order^2)/minimum(el_size) + C = isotropic_elast_tensor(2,1.,0.3) g = VectorValue(0,-1) η_coeff = 2 α_coeff = 4 vf = 0.4 - path = dirname(dirname(@__DIR__))*"/results/elastic_compliance_ALM" + path = dirname(dirname(@__DIR__))*"/results/elastic_compliance_ALM_MPI" + i_am_main(ranks) && mkdir(path) ## FE Setup model = CartesianDiscreteModel(ranks,mesh_partition,dom,el_size) - Δ = get_Δ(model) + el_Δ = get_el_Δ(model) f_Γ_D(x) = (x[1] ≈ 0.0) - f_Γ_N(x) = (x[1] ≈ xmax) && (ymax/2-ymax*prop_Γ_N/4 - eps() <= x[2] <= ymax/2+ymax*prop_Γ_N/4 + eps()) + f_Γ_N(x) = (x[1] ≈ xmax) && (ymax/2-ymax*prop_Γ_N/2 - eps() <= x[2] <= ymax/2+ymax*prop_Γ_N/2 + eps()) update_labels!(1,model,f_Γ_D,"Gamma_D") update_labels!(2,model,f_Γ_N,"Gamma_N") @@ -55,11 +56,11 @@ function main(mesh_partition,distribute,el_size) U_reg = TrialFESpace(V_reg,0) ## Create FE functions - φh = interpolate(gen_lsf(4,0.2),V_φ) + φh = interpolate(initial_lsf(4,0.2),V_φ) φ = get_free_dof_values(φh) ## Interpolation and weak form - interp = SmoothErsatzMaterialInterpolation(η = η_coeff*maximum(Δ)) + interp = SmoothErsatzMaterialInterpolation(η = η_coeff*maximum(el_Δ)) I,H,DH,ρ = interp.I,interp.H,interp.DH,interp.ρ a(u,v,φ,dΩ,dΓ_N) = ∫((I ∘ φ)*(C ⊙ ε(u) ⊙ ε(v)))dΩ @@ -67,9 +68,9 @@ function main(mesh_partition,distribute,el_size) ## Optimisation functionals J(u,φ,dΩ,dΓ_N) = ∫((I ∘ φ)*(C ⊙ ε(u) ⊙ ε(u)))dΩ - dJ(q,u,φ,dΩ,dΓ_N) = ∫((- C ⊙ ε(u) ⊙ ε(u))*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ + dJ(q,u,φ,dΩ,dΓ_N) = ∫((C ⊙ ε(u) ⊙ ε(u))*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ Vol(u,φ,dΩ,dΓ_N) = ∫(((ρ ∘ φ) - vf)/vol_D)dΩ - dVol(q,u,φ,dΩ,dΓ_N) = ∫(1/vol_D*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ + dVol(q,u,φ,dΩ,dΓ_N) = ∫(-1/vol_D*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ ## Finite difference solver and level set function stencil = AdvectionStencil(FirstOrderStencil(2,Float64),model,V_φ,tol,max_steps) @@ -89,27 +90,26 @@ function main(mesh_partition,distribute,el_size) pcfs = PDEConstrainedFunctionals(J,[Vol],state_map,analytic_dJ=dJ,analytic_dC=[dVol]) ## Hilbertian extension-regularisation problems - α = α_coeff*maximum(Δ) + α = α_coeff*maximum(el_Δ) a_hilb(p,q) =∫(α^2*∇(p)⋅∇(q) + p*q)dΩ vel_ext = VelocityExtension(a_hilb,U_reg,V_reg; assem=SparseMatrixAssembler(Tm,Tv,U_reg,V_reg), ls=PETScLinearSolver()) ## Optimiser - make_dir(path;ranks=ranks) optimiser = AugmentedLagrangian(pcfs,stencil,vel_ext,φh; γ,γ_reinit,verbose=i_am_main(ranks),constraint_names=[:Vol]) for (it, uh, φh) in optimiser - write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi))|"=>(norm ∘ ∇(φh)),"uh"=>uh]) + write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh)),"uh"=>uh]) write_history(path*"/history.txt",optimiser.history;ranks=ranks) end - it = optimiser.history.niter; uh = get_state(optimiser.problem) - write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi))|"=>(norm ∘ ∇(φh)),"uh"=>uh];iter_mod=1) + it = get_history(optimiser).niter; uh = get_state(pcfs) + write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh)),"uh"=>uh];iter_mod=1) end with_mpi() do distribute mesh_partition = (2,2) - el_size = (100,100) + el_size = (200,100) hilb_solver_options = "-pc_type gamg -ksp_type cg -ksp_error_if_not_converged true -ksp_converged_reason -ksp_rtol 1.0e-12 -mat_block_size 3 -mg_levels_ksp_type chebyshev -mg_levels_esteig_ksp_type cg -mg_coarse_sub_pc_type cholesky" diff --git a/scripts/MPI/inverse_homenisation_ALM.jl b/scripts/MPI/inverse_homenisation_ALM.jl index 063b32b9..f08691d0 100644 --- a/scripts/MPI/inverse_homenisation_ALM.jl +++ b/scripts/MPI/inverse_homenisation_ALM.jl @@ -1,5 +1,5 @@ using Gridap, Gridap.MultiField, GridapDistributed, GridapPETSc, GridapSolvers, - PartitionedArrays, LSTO_Distributed, SparseMatricesCSR + PartitionedArrays, LevelSetTopOpt, SparseMatricesCSR """ (MPI) Maximum bulk modulus inverse homogenisation with augmented Lagrangian method in 2D. @@ -21,16 +21,17 @@ function main(mesh_partition,distribute,el_size) γ = 0.05 γ_reinit = 0.5 max_steps = floor(Int,minimum(el_size)/10) - tol = 1/(10order^2)*prod(inv,minimum(el_size)) - C = isotropic_2d(1.,0.3) + tol = 1/(5order^2)/minimum(el_size) + C = isotropic_elast_tensor(2,1.,0.3) η_coeff = 2 α_coeff = 4 vf = 0.5 - path = dirname(dirname(@__DIR__))*"/results/inverse_homenisation_ALM" + path = dirname(dirname(@__DIR__))*"/results/inverse_homenisation_ALM_MPI" + i_am_main(ranks) && mkdir(path) ## FE Setup model = CartesianDiscreteModel(ranks,mesh_partition,dom,el_size,isperiodic=(true,true)); - Δ = get_Δ(model) + el_Δ = get_el_Δ(model) f_Γ_D(x) = iszero(x) update_labels!(1,model,f_Γ_D,"origin") @@ -48,11 +49,11 @@ function main(mesh_partition,distribute,el_size) U_reg = TrialFESpace(V_reg) ## Create FE functions - lsf_fn(x) = max(gen_lsf(2,0.4)(x),gen_lsf(2,0.4;b=VectorValue(0,0.5))(x)); + lsf_fn(x) = max(initial_lsf(2,0.4)(x),initial_lsf(2,0.4;b=VectorValue(0,0.5))(x)); φh = interpolate(lsf_fn,V_φ) ## Interpolation and weak form - interp = SmoothErsatzMaterialInterpolation(η = η_coeff*maximum(Δ)) + interp = SmoothErsatzMaterialInterpolation(η = η_coeff*maximum(el_Δ)) I,H,DH,ρ = interp.I,interp.H,interp.DH,interp.ρ εᴹ = (TensorValue(1.,0.,0.,0.), # ϵᵢⱼ⁽¹¹⁾≡ϵᵢⱼ⁽¹⁾ @@ -68,9 +69,9 @@ function main(mesh_partition,distribute,el_size) _v_K(C,(u1,u2,u3),εᴹ) = (_C(C,ε(u1)+εᴹ[1],ε(u1)+εᴹ[1]) + _C(C,ε(u2)+εᴹ[2],ε(u2)+εᴹ[2]) + 2*_C(C,ε(u1)+εᴹ[1],ε(u2)+εᴹ[2]))/4 J(u,φ,dΩ) = ∫(-(I ∘ φ)*_K(C,u,εᴹ))dΩ - dJ(q,u,φ,dΩ) = ∫(-_v_K(C,u,εᴹ)*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ; + dJ(q,u,φ,dΩ) = ∫(_v_K(C,u,εᴹ)*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ; Vol(u,φ,dΩ) = ∫(((ρ ∘ φ) - vf)/vol_D)dΩ; - dVol(q,u,φ,dΩ) = ∫(1/vol_D*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ + dVol(q,u,φ,dΩ) = ∫(-1/vol_D*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ ## Finite difference solver and level set function stencil = AdvectionStencil(FirstOrderStencil(2,Float64),model,V_φ,tol,max_steps) @@ -90,7 +91,7 @@ function main(mesh_partition,distribute,el_size) pcfs = PDEConstrainedFunctionals(J,[Vol],state_map;analytic_dJ=dJ,analytic_dC=[dVol]) ## Hilbertian extension-regularisation problems - α = α_coeff*maximum(Δ) + α = α_coeff*maximum(el_Δ) a_hilb(p,q) =∫(α^2*∇(p)⋅∇(q) + p*q)dΩ vel_ext = VelocityExtension( a_hilb,U_reg,V_reg, @@ -99,19 +100,18 @@ function main(mesh_partition,distribute,el_size) ) ## Optimiser - make_dir(path;ranks=ranks) optimiser = AugmentedLagrangian(pcfs,stencil,vel_ext,φh; γ,γ_reinit,verbose=i_am_main(ranks),constraint_names=[:Vol]) for (it, uh, φh) in optimiser - write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi))|"=>(norm ∘ ∇(φh))]) + write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh))]) write_history(path*"/history.txt",optimiser.history;ranks=ranks) end - it = optimiser.history.niter; uh = get_state(optimiser.problem) - write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi))|"=>(norm ∘ ∇(φh))];iter_mod=1) + it = get_history(optimiser).niter; uh = get_state(pcfs) + write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh))];iter_mod=1) end with_mpi() do distribute - mesh_partition = (3,3) + mesh_partition = (2,2) el_size = (200,200) hilb_solver_options = "-pc_type gamg -ksp_type cg -ksp_error_if_not_converged true -ksp_converged_reason -ksp_rtol 1.0e-12 -mat_block_size 3 diff --git a/scripts/MPI/jobs/3d_elastic_compliance_ALM.pbs b/scripts/MPI/jobs/3d_elastic_compliance_ALM.pbs index 82852834..3d6df3ef 100644 --- a/scripts/MPI/jobs/3d_elastic_compliance_ALM.pbs +++ b/scripts/MPI/jobs/3d_elastic_compliance_ALM.pbs @@ -3,11 +3,11 @@ #PBS -N 3d_elastic_compliance_ALM #PBS -l select=125:ncpus=1:mpiprocs=1:ompthreads=1:mem=8GB:cputype=6140 -#PBS -l walltime=200:00:00 +#PBS -l walltime=50:00:00 #PBS -j oe source $HOME/hpc-environments-main/lyra/load-ompi.sh -PROJECT_DIR=$HOME/LSTO_Distributed/ +PROJECT_DIR=$HOME/LevelSetTopOpt/ julia --project=$PROJECT_DIR -e "using Pkg; Pkg.precompile()" diff --git a/scripts/MPI/jobs/3d_hyperelastic_compliance_ALM.pbs b/scripts/MPI/jobs/3d_hyperelastic_compliance_ALM.pbs index 0431b0e6..19e1bde9 100644 --- a/scripts/MPI/jobs/3d_hyperelastic_compliance_ALM.pbs +++ b/scripts/MPI/jobs/3d_hyperelastic_compliance_ALM.pbs @@ -3,11 +3,11 @@ #PBS -N 3d_hyperelastic_compliance_ALM #PBS -l select=125:ncpus=1:mpiprocs=1:ompthreads=1:mem=8GB:cputype=6140 -#PBS -l walltime=200:00:00 +#PBS -l walltime=50:00:00 #PBS -j oe source $HOME/hpc-environments-main/lyra/load-ompi.sh -PROJECT_DIR=$HOME/LSTO_Distributed/ +PROJECT_DIR=$HOME/LevelSetTopOpt/ julia --project=$PROJECT_DIR -e "using Pkg; Pkg.precompile()" diff --git a/scripts/MPI/jobs/3d_hyperelastic_compliance_neohook_ALM.pbs b/scripts/MPI/jobs/3d_hyperelastic_compliance_neohook_ALM.pbs index 3ea3c6c2..e100e2e4 100644 --- a/scripts/MPI/jobs/3d_hyperelastic_compliance_neohook_ALM.pbs +++ b/scripts/MPI/jobs/3d_hyperelastic_compliance_neohook_ALM.pbs @@ -3,11 +3,11 @@ #PBS -N 3d_hyperelastic_compliance_neohook_ALM #PBS -l select=125:ncpus=1:mpiprocs=1:ompthreads=1:mem=8GB:cputype=6140 -#PBS -l walltime=200:00:00 +#PBS -l walltime=50:00:00 #PBS -j oe source $HOME/hpc-environments-main/lyra/load-ompi.sh -PROJECT_DIR=$HOME/LSTO_Distributed/ +PROJECT_DIR=$HOME/LevelSetTopOpt/ julia --project=$PROJECT_DIR -e "using Pkg; Pkg.precompile()" diff --git a/scripts/MPI/jobs/3d_inverse_homenisation_ALM.pbs b/scripts/MPI/jobs/3d_inverse_homenisation_ALM.pbs index 1146db18..111cb9a1 100644 --- a/scripts/MPI/jobs/3d_inverse_homenisation_ALM.pbs +++ b/scripts/MPI/jobs/3d_inverse_homenisation_ALM.pbs @@ -3,11 +3,11 @@ #PBS -N 3d_inverse_homenisation_ALM #PBS -l select=125:ncpus=1:mpiprocs=1:ompthreads=1:mem=8GB:cputype=6140 -#PBS -l walltime=200:00:00 +#PBS -l walltime=50:00:00 #PBS -j oe source $HOME/hpc-environments-main/lyra/load-ompi.sh -PROJECT_DIR=$HOME/LSTO_Distributed/ +PROJECT_DIR=$HOME/LevelSetTopOpt/ julia --project=$PROJECT_DIR -e "using Pkg; Pkg.precompile()" diff --git a/scripts/MPI/jobs/3d_inverter_ALM.pbs b/scripts/MPI/jobs/3d_inverter_ALM.pbs new file mode 100644 index 00000000..7499fd5a --- /dev/null +++ b/scripts/MPI/jobs/3d_inverter_ALM.pbs @@ -0,0 +1,16 @@ +#!/bin/bash -l +#PBS -P LSTO +#PBS -N 3d_inverter_ALM + +#PBS -l select=125:ncpus=1:mpiprocs=1:ompthreads=1:mem=8GB:cputype=6140 +#PBS -l walltime=50:00:00 +#PBS -j oe + +source $HOME/hpc-environments-main/lyra/load-ompi.sh +PROJECT_DIR=$HOME/LevelSetTopOpt/ + +julia --project=$PROJECT_DIR -e "using Pkg; Pkg.precompile()" + +mpirun --hostfile $PBS_NODEFILE \ + julia --project=$PROJECT_DIR --check-bounds no -O3 --compiled-modules=no \ + $PROJECT_DIR/scripts/MPI/3d_inverter_ALM.jl diff --git a/scripts/MPI/jobs/3d_inverter_HPM.pbs b/scripts/MPI/jobs/3d_inverter_HPM.pbs index cccc72eb..b4092519 100644 --- a/scripts/MPI/jobs/3d_inverter_HPM.pbs +++ b/scripts/MPI/jobs/3d_inverter_HPM.pbs @@ -3,11 +3,11 @@ #PBS -N 3d_inverter_HPM #PBS -l select=125:ncpus=1:mpiprocs=1:ompthreads=1:mem=8GB:cputype=6140 -#PBS -l walltime=200:00:00 +#PBS -l walltime=50:00:00 #PBS -j oe source $HOME/hpc-environments-main/lyra/load-ompi.sh -PROJECT_DIR=$HOME/LSTO_Distributed/ +PROJECT_DIR=$HOME/LevelSetTopOpt/ julia --project=$PROJECT_DIR -e "using Pkg; Pkg.precompile()" diff --git a/scripts/MPI/jobs/3d_nonlinear_thermal_compliance_ALM.pbs b/scripts/MPI/jobs/3d_nonlinear_thermal_compliance_ALM.pbs index b8e3d288..acbc4a6c 100644 --- a/scripts/MPI/jobs/3d_nonlinear_thermal_compliance_ALM.pbs +++ b/scripts/MPI/jobs/3d_nonlinear_thermal_compliance_ALM.pbs @@ -3,11 +3,11 @@ #PBS -N 3d_nonlinear_thermal_compliance_ALM #PBS -l select=125:ncpus=1:mpiprocs=1:ompthreads=1:mem=8GB:cputype=6140 -#PBS -l walltime=200:00:00 +#PBS -l walltime=50:00:00 #PBS -j oe source $HOME/hpc-environments-main/lyra/load-ompi.sh -PROJECT_DIR=$HOME/LSTO_Distributed/ +PROJECT_DIR=$HOME/LevelSetTopOpt/ julia --project=$PROJECT_DIR -e "using Pkg; Pkg.precompile()" diff --git a/scripts/MPI/jobs/3d_thermal_compliance_ALM.pbs b/scripts/MPI/jobs/3d_thermal_compliance_ALM.pbs index 70799a36..a1424958 100644 --- a/scripts/MPI/jobs/3d_thermal_compliance_ALM.pbs +++ b/scripts/MPI/jobs/3d_thermal_compliance_ALM.pbs @@ -8,10 +8,15 @@ #PBS -j oe source $HOME/hpc-environments-main/lyra/load-ompi.sh -PROJECT_DIR=$HOME/LSTO_Distributed/ +PROJECT_DIR=$HOME/LevelSetTopOpt/ julia --project=$PROJECT_DIR -e "using Pkg; Pkg.precompile()" -mpirun --hostfile $PBS_NODEFILE \ - julia --project=$PROJECT_DIR --check-bounds no -O3 --compiled-modules=no \ - $PROJECT_DIR/scripts/MPI/3d_thermal_compliance_ALM.jl +mpirun --hostfile $PBS_NODEFILE julia --project=$PROJECT_DIR --check-bounds no -O3 --compiled-modules=no \ + $PROJECT_DIR/scripts/MPI/3d_thermal_compliance_ALM.jl \ + 150 \ + 150 \ + 150 \ + 5 \ + 5 \ + 5 \ No newline at end of file diff --git a/scripts/MPI/thermal_compliance_ALM.jl b/scripts/MPI/thermal_compliance_ALM.jl index 5f0e99c0..f4208c11 100644 --- a/scripts/MPI/thermal_compliance_ALM.jl +++ b/scripts/MPI/thermal_compliance_ALM.jl @@ -1,4 +1,4 @@ -using Gridap, GridapDistributed, GridapPETSc, PartitionedArrays, LSTO_Distributed +using Gridap, GridapDistributed, GridapPETSc, PartitionedArrays, LevelSetTopOpt """ (MPI) Minimum thermal compliance with augmented Lagrangian method in 2D. @@ -16,25 +16,26 @@ function main(mesh_partition,distribute) ## Parameters order = 1 xmax=ymax=1.0 - prop_Γ_N = 0.4 + prop_Γ_N = 0.2 prop_Γ_D = 0.2 dom = (0,xmax,0,ymax) - el_size = (400,400) + el_size = (200,200) γ = 0.1 γ_reinit = 0.5 max_steps = floor(Int,minimum(el_size)/10) - tol = 1/(10order^2)*prod(inv,minimum(el_size)) + tol = 1/(5order^2)/minimum(el_size) κ = 1 η_coeff = 2 α_coeff = 4 vf = 0.4 - path = dirname(dirname(@__DIR__))*"/results/thermal_compliance_ALM" + path = dirname(dirname(@__DIR__))*"/results/thermal_compliance_ALM_MPI" + i_am_main(ranks) && mkdir(path) ## FE Setup model = CartesianDiscreteModel(ranks,mesh_partition,dom,el_size); - Δ = get_Δ(model) + el_Δ = get_el_Δ(model) f_Γ_D(x) = (x[1] ≈ 0.0) && ((x[2] <= ymax*prop_Γ_D + eps()) || (x[2] >= ymax-ymax*prop_Γ_D - eps())) - f_Γ_N(x) = (x[1] ≈ xmax) && (ymax/2-ymax*prop_Γ_N/4 - eps() <= x[2] <= ymax/2+ymax*prop_Γ_N/4 + eps()) + f_Γ_N(x) = (x[1] ≈ xmax) && (ymax/2-ymax*prop_Γ_N/2 - eps() <= x[2] <= ymax/2+ymax*prop_Γ_N/2 + eps()) update_labels!(1,model,f_Γ_D,"Gamma_D") update_labels!(2,model,f_Γ_N,"Gamma_N") @@ -54,10 +55,10 @@ function main(mesh_partition,distribute) U_reg = TrialFESpace(V_reg,0) ## Create FE functions - φh = interpolate(gen_lsf(4,0.2),V_φ) + φh = interpolate(initial_lsf(4,0.2),V_φ) ## Interpolation and weak form - interp = SmoothErsatzMaterialInterpolation(η = η_coeff*maximum(Δ)) + interp = SmoothErsatzMaterialInterpolation(η = η_coeff*maximum(el_Δ)) I,H,DH,ρ = interp.I,interp.H,interp.DH,interp.ρ a(u,v,φ,dΩ,dΓ_N) = ∫((I ∘ φ)*κ*∇(u)⋅∇(v))dΩ @@ -65,34 +66,33 @@ function main(mesh_partition,distribute) ## Optimisation functionals J(u,φ,dΩ,dΓ_N) = ∫((I ∘ φ)*κ*∇(u)⋅∇(u))dΩ - dJ(q,u,φ,dΩ,dΓ_N) = ∫(-κ*∇(u)⋅∇(u)*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ; + dJ(q,u,φ,dΩ,dΓ_N) = ∫(κ*∇(u)⋅∇(u)*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ; Vol(u,φ,dΩ,dΓ_N) = ∫(((ρ ∘ φ) - vf)/vol_D)dΩ; - dVol(q,u,φ,dΩ,dΓ_N) = ∫(1/vol_D*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ + dVol(q,u,φ,dΩ,dΓ_N) = ∫(-1/vol_D*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ ## Finite difference solver and level set function stencil = AdvectionStencil(FirstOrderStencil(2,Float64),model,V_φ,tol,max_steps) ## Setup solver and FE operators state_map = AffineFEStateMap(a,l,U,V,V_φ,U_reg,φh,dΩ,dΓ_N) - pcfs = PDEConstrainedFunctionals(J,[Vol],state_map,analytic_dJ=dJ) + pcfs = PDEConstrainedFunctionals(J,[Vol],state_map,analytic_dJ=dJ,analytic_dC=[dVol]) ## Hilbertian extension-regularisation problems - α = α_coeff*maximum(Δ) + α = α_coeff*maximum(el_Δ) a_hilb(p,q) =∫(α^2*∇(p)⋅∇(q) + p*q)dΩ; vel_ext = VelocityExtension(a_hilb,U_reg,V_reg) ## Optimiser - make_dir(path;ranks=ranks) optimiser = AugmentedLagrangian(pcfs,stencil,vel_ext,φh;γ,γ_reinit, verbose=i_am_main(ranks),constraint_names=[:Vol]) for (it, uh, φh) in optimiser - write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi))|"=>(norm ∘ ∇(φh)),"uh"=>uh]) + write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh)),"uh"=>uh]) write_history(path*"/history.txt",optimiser.history;ranks=ranks) end - it = optimiser.history.niter; uh = get_state(optimiser.problem) - write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi))|"=>(norm ∘ ∇(φh)),"uh"=>uh];iter_mod=1) + it = get_history(optimiser).niter; uh = get_state(pcfs) + write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh)),"uh"=>uh];iter_mod=1) end with_mpi() do distribute - main((2,3),distribute) + main((2,2),distribute) end \ No newline at end of file diff --git a/scripts/MPI/thermal_compliance_ALM_PETSc.jl b/scripts/MPI/thermal_compliance_ALM_PETSc.jl new file mode 100644 index 00000000..a39f1a6a --- /dev/null +++ b/scripts/MPI/thermal_compliance_ALM_PETSc.jl @@ -0,0 +1,119 @@ +using Gridap, GridapDistributed, GridapPETSc, GridapSolvers, + PartitionedArrays, LevelSetTopOpt, SparseMatricesCSR + +""" + (MPI) Minimum thermal compliance with augmented Lagrangian method in 2D. + + Optimisation problem: + Min J(Ω) = ∫ κ*∇(u)⋅∇(u) dΩ + Ω + s.t., Vol(Ω) = vf, + ⎡u∈V=H¹(Ω;u(Γ_D)=0), + ⎣∫ κ*∇(u)⋅∇(v) dΩ = ∫ v dΓ_N, ∀v∈V. +""" +function main(mesh_partition,distribute,el_size) + ranks = distribute(LinearIndices((prod(mesh_partition),))) + + ## Parameters + order = 1 + xmax=ymax=1.0 + prop_Γ_N = 0.2 + prop_Γ_D = 0.2 + dom = (0,xmax,0,ymax) + γ = 0.1 + γ_reinit = 0.5 + max_steps = floor(Int,minimum(el_size)/10) + tol = 1/(5order^2)/minimum(el_size) + κ = 1 + η_coeff = 2 + α_coeff = 4 + vf = 0.4 + path = dirname(dirname(@__DIR__))*"/results/thermal_compliance_ALM_MPI+PETSc" + i_am_main(ranks) && mkdir(path) + + ## FE Setup + model = CartesianDiscreteModel(ranks,mesh_partition,dom,el_size); + el_Δ = get_el_Δ(model) + f_Γ_D(x) = (x[1] ≈ 0.0) && ((x[2] <= ymax*prop_Γ_D + eps()) || (x[2] >= ymax-ymax*prop_Γ_D - eps())) + f_Γ_N(x) = (x[1] ≈ xmax) && (ymax/2-ymax*prop_Γ_N/2 - eps() <= x[2] <= ymax/2+ymax*prop_Γ_N/2 + eps()) + update_labels!(1,model,f_Γ_D,"Gamma_D") + update_labels!(2,model,f_Γ_N,"Gamma_N") + + ## Triangulations and measures + Ω = Triangulation(model) + Γ_N = BoundaryTriangulation(model,tags="Gamma_N") + dΩ = Measure(Ω,2order) + dΓ_N = Measure(Γ_N,2order) + vol_D = sum(∫(1)dΩ) + + ## Spaces + reffe_scalar = ReferenceFE(lagrangian,Float64,order) + V = TestFESpace(model,reffe_scalar;dirichlet_tags=["Gamma_D"]) + U = TrialFESpace(V,0.0) + V_φ = TestFESpace(model,reffe_scalar) + V_reg = TestFESpace(model,reffe_scalar;dirichlet_tags=["Gamma_N"]) + U_reg = TrialFESpace(V_reg,0) + + ## Create FE functions + φh = interpolate(initial_lsf(4,0.2),V_φ) + + ## Interpolation and weak form + interp = SmoothErsatzMaterialInterpolation(η = η_coeff*maximum(el_Δ)) + I,H,DH,ρ = interp.I,interp.H,interp.DH,interp.ρ + + a(u,v,φ,dΩ,dΓ_N) = ∫((I ∘ φ)*κ*∇(u)⋅∇(v))dΩ + l(v,φ,dΩ,dΓ_N) = ∫(v)dΓ_N + + ## Optimisation functionals + J(u,φ,dΩ,dΓ_N) = ∫((I ∘ φ)*κ*∇(u)⋅∇(u))dΩ + dJ(q,u,φ,dΩ,dΓ_N) = ∫(κ*∇(u)⋅∇(u)*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ; + Vol(u,φ,dΩ,dΓ_N) = ∫(((ρ ∘ φ) - vf)/vol_D)dΩ; + dVol(q,u,φ,dΩ,dΓ_N) = ∫(-1/vol_D*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ + + ## Finite difference solver and level set function + stencil = AdvectionStencil(FirstOrderStencil(2,Float64),model,V_φ,tol,max_steps) + + ## Setup solver and FE operators + Tm = SparseMatrixCSR{0,PetscScalar,PetscInt} + Tv = Vector{PetscScalar} + solver = PETScLinearSolver() + + state_map = AffineFEStateMap( + a,l,U,V,V_φ,U_reg,φh,dΩ,dΓ_N; + assem_U = SparseMatrixAssembler(Tm,Tv,U,V), + assem_adjoint = SparseMatrixAssembler(Tm,Tv,V,U), + assem_deriv = SparseMatrixAssembler(Tm,Tv,U_reg,U_reg), + ls = solver,adjoint_ls = solver + ) + pcfs = PDEConstrainedFunctionals(J,[Vol],state_map,analytic_dJ=dJ,analytic_dC=[dVol]) + + ## Hilbertian extension-regularisation problems + α = α_coeff*maximum(el_Δ) + a_hilb(p,q) = ∫(α^2*∇(p)⋅∇(q) + p*q)dΩ; + vel_ext = VelocityExtension( + a_hilb, U_reg, V_reg; + assem = SparseMatrixAssembler(Tm,Tv,U_reg,V_reg), + ls = solver + ) + + ## Optimiser + optimiser = AugmentedLagrangian(pcfs,stencil,vel_ext,φh;γ,γ_reinit, + verbose=i_am_main(ranks),constraint_names=[:Vol]) + for (it, uh, φh) in optimiser + write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh)),"uh"=>uh]) + write_history(path*"/history.txt",optimiser.history;ranks=ranks) + end + it = get_history(optimiser).niter; uh = get_state(pcfs) + write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh)),"uh"=>uh];iter_mod=1) +end + +with_mpi() do distribute + mesh_partition = (2,2) + el_size = (400,400) + solver_options = "-pc_type gamg -ksp_type cg -ksp_error_if_not_converged true + -ksp_converged_reason -ksp_rtol 1.0e-12" + + GridapPETSc.with(args=split(solver_options)) do + main(mesh_partition,distribute,el_size) + end +end \ No newline at end of file diff --git a/scripts/Serial/elastic_compliance_ALM.jl b/scripts/Serial/elastic_compliance_ALM.jl index 5bcfe923..a193d39b 100644 --- a/scripts/Serial/elastic_compliance_ALM.jl +++ b/scripts/Serial/elastic_compliance_ALM.jl @@ -1,4 +1,4 @@ -using Gridap, LSTO_Distributed +using Gridap, LevelSetTopOpt """ (Serial) Minimum elastic compliance with augmented Lagrangian method in 2D. @@ -14,25 +14,26 @@ function main() ## Parameters order = 1 xmax,ymax=(2.0,1.0) - prop_Γ_N = 0.4 + prop_Γ_N = 0.2 dom = (0,xmax,0,ymax) - el_size = (200,200) + el_size = (200,100) γ = 0.1 γ_reinit = 0.5 max_steps = floor(Int,minimum(el_size)/10) - tol = 1/(10order^2)*prod(inv,minimum(el_size)) - C = isotropic_2d(1.,0.3) + tol = 1/(5order^2)/minimum(el_size) + C = isotropic_elast_tensor(2,1.,0.3) η_coeff = 2 α_coeff = 4 vf = 0.4 g = VectorValue(0,-1) path = dirname(dirname(@__DIR__))*"/results/elastic_compliance_ALM" + mkdir(path) ## FE Setup model = CartesianDiscreteModel(dom,el_size) - Δ = get_Δ(model) + el_Δ = get_el_Δ(model) f_Γ_D(x) = (x[1] ≈ 0.0) - f_Γ_N(x) = (x[1] ≈ xmax && ymax/2-ymax*prop_Γ_N/4 - eps() <= x[2] <= ymax/2+ymax*prop_Γ_N/4 + eps()) + f_Γ_N(x) = (x[1] ≈ xmax && ymax/2-ymax*prop_Γ_N/2 - eps() <= x[2] <= ymax/2+ymax*prop_Γ_N/2 + eps()) update_labels!(1,model,f_Γ_D,"Gamma_D") update_labels!(2,model,f_Γ_N,"Gamma_N") @@ -53,10 +54,10 @@ function main() U_reg = TrialFESpace(V_reg,0) ## Create FE functions - φh = interpolate(gen_lsf(4,0.2),V_φ) + φh = interpolate(initial_lsf(4,0.2),V_φ) ## Interpolation and weak form - interp = SmoothErsatzMaterialInterpolation(η = η_coeff*maximum(Δ)) + interp = SmoothErsatzMaterialInterpolation(η = η_coeff*maximum(el_Δ)) I,H,DH,ρ = interp.I,interp.H,interp.DH,interp.ρ a(u,v,φ,dΩ,dΓ_N) = ∫((I ∘ φ)*(C ⊙ ε(u) ⊙ ε(v)))dΩ @@ -64,33 +65,31 @@ function main() ## Optimisation functionals J(u,φ,dΩ,dΓ_N) = ∫((I ∘ φ)*(C ⊙ ε(u) ⊙ ε(u)))dΩ - dJ(q,u,φ,dΩ,dΓ_N) = ∫((- C ⊙ ε(u) ⊙ ε(u))*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ; + dJ(q,u,φ,dΩ,dΓ_N) = ∫((C ⊙ ε(u) ⊙ ε(u))*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ; Vol(u,φ,dΩ,dΓ_N) = ∫(((ρ ∘ φ) - vf)/vol_D)dΩ; - dVol(q,u,φ,dΩ,dΓ_N) = ∫(1/vol_D*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ + dVol(q,u,φ,dΩ,dΓ_N) = ∫(-1/vol_D*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ ## Finite difference solver and level set function stencil = AdvectionStencil(FirstOrderStencil(2,Float64),model,V_φ,tol,max_steps) - reinit!(stencil,φh,γ_reinit) ## Setup solver and FE operators state_map = AffineFEStateMap(a,l,U,V,V_φ,U_reg,φh,dΩ,dΓ_N) pcfs = PDEConstrainedFunctionals(J,[Vol],state_map,analytic_dJ=dJ,analytic_dC=[dVol]) ## Hilbertian extension-regularisation problems - α = α_coeff*maximum(Δ) + α = α_coeff*maximum(el_Δ) a_hilb(p,q) =∫(α^2*∇(p)⋅∇(q) + p*q)dΩ; vel_ext = VelocityExtension(a_hilb,U_reg,V_reg) ## Optimiser - make_dir(path) optimiser = AugmentedLagrangian(pcfs,stencil,vel_ext,φh; γ,γ_reinit,verbose=true,constraint_names=[:Vol]) for (it,uh,φh) in optimiser - write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi))|"=>(norm ∘ ∇(φh)),"uh"=>uh]) + write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh)),"uh"=>uh]) write_history(path*"/history.txt",optimiser.history) end - it = optimiser.history.niter; uh = get_state(optimiser.problem) - write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi))|"=>(norm ∘ ∇(φh)),"uh"=>uh];iter_mod=1) + it = get_history(optimiser).niter; uh = get_state(pcfs) + write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh)),"uh"=>uh];iter_mod=1) end main() \ No newline at end of file diff --git a/scripts/Serial/elastic_compliance_HPM.jl b/scripts/Serial/elastic_compliance_HPM.jl index fd4737d2..825e92f9 100644 --- a/scripts/Serial/elastic_compliance_HPM.jl +++ b/scripts/Serial/elastic_compliance_HPM.jl @@ -1,4 +1,4 @@ -using Gridap, LSTO_Distributed +using Gridap, LevelSetTopOpt """ (Serial) Minimum elastic compliance with Hilbertian projection method in 2D. @@ -14,26 +14,27 @@ function main() ## Parameters order = 1 xmax,ymax=(2.0,1.0) - prop_Γ_N = 0.4 + prop_Γ_N = 0.2 dom = (0,xmax,0,ymax) - el_size = (200,200) + el_size = (200,100) γ = 0.1 γ_reinit = 0.5 max_steps = floor(Int,minimum(el_size)/10) - tol = 1/(10order^2)*prod(inv,minimum(el_size)) - C = isotropic_2d(1.,0.3) + tol = 1/(5order^2)/minimum(el_size) + C = isotropic_elast_tensor(2,1.,0.3) η_coeff = 2 α_coeff = 4 vf = 0.4 g = VectorValue(0,-1) path = dirname(dirname(@__DIR__))*"/results/elastic_compliance_HPM" + mkdir(path) ## FE Setup model = CartesianDiscreteModel(dom,el_size) - Δ = get_Δ(model) + el_Δ = get_el_Δ(model) f_Γ_D(x) = (x[1] ≈ 0.0) - f_Γ_N(x) = (x[1] ≈ xmax && ymax/2-ymax*prop_Γ_N/4 - eps() <= x[2] <= - ymax/2+ymax*prop_Γ_N/4 + eps()) + f_Γ_N(x) = (x[1] ≈ xmax && ymax/2-ymax*prop_Γ_N/2 - eps() <= x[2] <= + ymax/2+ymax*prop_Γ_N/2 + eps()) update_labels!(1,model,f_Γ_D,"Gamma_D") update_labels!(2,model,f_Γ_N,"Gamma_N") @@ -54,10 +55,10 @@ function main() U_reg = TrialFESpace(V_reg,0) ## Create FE functions - φh = interpolate(gen_lsf(4,0.2),V_φ) + φh = interpolate(initial_lsf(4,0.2),V_φ) ## Interpolation and weak form - interp = SmoothErsatzMaterialInterpolation(η = η_coeff*maximum(Δ)) + interp = SmoothErsatzMaterialInterpolation(η = η_coeff*maximum(el_Δ)) I,H,DH,ρ = interp.I,interp.H,interp.DH,interp.ρ a(u,v,φ,dΩ,dΓ_N) = ∫((I ∘ φ)*(C ⊙ ε(u) ⊙ ε(v)))dΩ @@ -65,33 +66,31 @@ function main() ## Optimisation functionals J = (u,φ,dΩ,dΓ_N) -> ∫((I ∘ φ)*(C ⊙ ε(u) ⊙ ε(u)))dΩ - dJ = (q,u,φ,dΩ,dΓ_N) -> ∫((- C ⊙ ε(u) ⊙ ε(u))*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ; + dJ = (q,u,φ,dΩ,dΓ_N) -> ∫((C ⊙ ε(u) ⊙ ε(u))*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ; Vol = (u,φ,dΩ,dΓ_N) -> ∫(((ρ ∘ φ) - vf)/vol_D)dΩ; - dVol = (q,u,φ,dΩ,dΓ_N) -> ∫(1/vol_D*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ + dVol = (q,u,φ,dΩ,dΓ_N) -> ∫(-1/vol_D*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ ## Finite difference solver and level set function stencil = AdvectionStencil(FirstOrderStencil(2,Float64),model,V_φ,tol,max_steps) - reinit!(stencil,φh,γ_reinit) ## Setup solver and FE operators state_map = AffineFEStateMap(a,l,U,V,V_φ,U_reg,φh,dΩ,dΓ_N) pcfs = PDEConstrainedFunctionals(J,[Vol],state_map,analytic_dJ=dJ,analytic_dC=[dVol]) ## Hilbertian extension-regularisation problems - α = α_coeff*maximum(Δ) + α = α_coeff*maximum(el_Δ) a_hilb(p,q) =∫(α^2*∇(p)⋅∇(q) + p*q)dΩ; vel_ext = VelocityExtension(a_hilb,U_reg,V_reg) ## Optimiser - make_dir(path) optimiser = HilbertianProjection(pcfs,stencil,vel_ext,φh; γ,γ_reinit,verbose=true,constraint_names=[:Vol]) for (it,uh,φh) in optimiser - write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi))|"=>(norm ∘ ∇(φh)),"uh"=>uh]) + write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh)),"uh"=>uh]) write_history(path*"/history.txt",optimiser.history) end - it = optimiser.history.niter; uh = get_state(optimiser.problem) - write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi))|"=>(norm ∘ ∇(φh)),"uh"=>uh];iter_mod=1) + it = get_history(optimiser).niter; uh = get_state(pcfs) + write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh)),"uh"=>uh];iter_mod=1) end main(); \ No newline at end of file diff --git a/scripts/Serial/elastic_compliance_LM.jl b/scripts/Serial/elastic_compliance_LM.jl index 84db856c..64b04677 100644 --- a/scripts/Serial/elastic_compliance_LM.jl +++ b/scripts/Serial/elastic_compliance_LM.jl @@ -1,4 +1,4 @@ -using Gridap, LSTO_Distributed +using Gridap, LevelSetTopOpt """ (Serial) Minimum elastic compliance with Lagrangian method in 2D. @@ -13,25 +13,26 @@ function main() ## Parameters order = 1 xmax,ymax=(2.0,1.0) - prop_Γ_N = 0.4 + prop_Γ_N = 0.2 dom = (0,xmax,0,ymax) - el_size = (200,200) + el_size = (200,100) γ = 0.1 γ_reinit = 0.5 max_steps = floor(Int,minimum(el_size)/10) - tol = 1/(10order^2)*prod(inv,minimum(el_size)) - C = isotropic_2d(1.,0.3) + tol = 1/(5order^2)/minimum(el_size) + C = isotropic_elast_tensor(2,1.,0.3) η_coeff = 2 α_coeff = 4 g = VectorValue(0,-1) path = dirname(dirname(@__DIR__))*"/results/elastic_compliance_LM" + mkdir(path) ## FE Setup model = CartesianDiscreteModel(dom,el_size) - Δ = get_Δ(model) + el_Δ = get_el_Δ(model) f_Γ_D(x) = iszero(x[1]) - f_Γ_N(x) = (x[1] ≈ xmax && ymax/2-ymax*prop_Γ_N/4 - eps() <= x[2] <= - ymax/2+ymax*prop_Γ_N/4 + eps()) + f_Γ_N(x) = (x[1] ≈ xmax && ymax/2-ymax*prop_Γ_N/2 - eps() <= x[2] <= + ymax/2+ymax*prop_Γ_N/2 + eps()) update_labels!(1,model,f_Γ_D,"Gamma_D") update_labels!(2,model,f_Γ_N,"Gamma_N") @@ -51,10 +52,10 @@ function main() U_reg = TrialFESpace(V_reg,0) ## Create FE functions - φh = interpolate(gen_lsf(4,0.2),V_φ) + φh = interpolate(initial_lsf(4,0.2),V_φ) ## Interpolation and weak form - interp = SmoothErsatzMaterialInterpolation(η = η_coeff*maximum(Δ)) + interp = SmoothErsatzMaterialInterpolation(η = η_coeff*maximum(el_Δ)) I,H,DH,ρ = interp.I,interp.H,interp.DH,interp.ρ a(u,v,φ,dΩ,dΓ_N) = ∫((I ∘ φ)*(C ⊙ ε(u) ⊙ ε(v)))dΩ @@ -63,30 +64,28 @@ function main() ## Optimisation functionals ξ = 2 J = (u,φ,dΩ,dΓ_N) -> ∫((I ∘ φ)*(C ⊙ ε(u) ⊙ ε(u)) + ξ*(ρ ∘ φ))dΩ - dJ = (q,u,φ,dΩ,dΓ_N) -> ∫((ξ - C ⊙ ε(u) ⊙ ε(u))*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ; + dJ = (q,u,φ,dΩ,dΓ_N) -> ∫((- ξ + C ⊙ ε(u) ⊙ ε(u))*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ; ## Finite difference solver and level set function stencil = AdvectionStencil(FirstOrderStencil(2,Float64),model,V_φ,tol,max_steps) - reinit!(stencil,φh,γ_reinit) ## Setup solver and FE operators state_map = AffineFEStateMap(a,l,U,V,V_φ,U_reg,φh,dΩ,dΓ_N) pcfs = PDEConstrainedFunctionals(J,state_map,analytic_dJ=dJ) ## Hilbertian extension-regularisation problems - α = α_coeff*maximum(Δ) + α = α_coeff*maximum(el_Δ) a_hilb(p,q) =∫(α^2*∇(p)⋅∇(q) + p*q)dΩ; vel_ext = VelocityExtension(a_hilb,U_reg,V_reg) ## Optimiser - make_dir(path) optimiser = AugmentedLagrangian(pcfs,stencil,vel_ext,φh;γ,γ_reinit,verbose=true) for (it, uh, φh) in optimiser - write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi))|"=>(norm ∘ ∇(φh)),"uh"=>uh]) + write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh)),"uh"=>uh]) write_history(path*"/history.txt",optimiser.history) end - it = optimiser.history.niter; uh = get_state(optimiser.problem) - write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi))|"=>(norm ∘ ∇(φh)),"uh"=>uh];iter_mod=1) + it = get_history(optimiser).niter; uh = get_state(pcfs) + write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh)),"uh"=>uh];iter_mod=1) end main(); \ No newline at end of file diff --git a/scripts/Serial/hyperelastic_compliance_ALM.jl b/scripts/Serial/hyperelastic_compliance_ALM.jl index 4fe15242..f2117446 100644 --- a/scripts/Serial/hyperelastic_compliance_ALM.jl +++ b/scripts/Serial/hyperelastic_compliance_ALM.jl @@ -1,4 +1,4 @@ -using Gridap, LSTO_Distributed +using Gridap, LevelSetTopOpt """ (Serial) Minimum hyperelastic compliance with augmented Lagrangian method in 2D. @@ -7,23 +7,24 @@ function main() ## Parameters order = 1 xmax,ymax=2.0,1.0 - prop_Γ_N = 0.4 + prop_Γ_N = 0.2 dom = (0,xmax,0,ymax) el_size = (200,200) γ = 0.05 γ_reinit = 0.5 max_steps = floor(Int,minimum(el_size)/10) - tol = 1/(10order^2)*prod(inv,minimum(el_size)) + tol = 1/(5order^2)/minimum(el_size) η_coeff = 2 α_coeff = 4 vf = 0.6 path = dirname(dirname(@__DIR__))*"/results/hyperelastic_compliance_ALM" + mkdir(path) ## FE Setup model = CartesianDiscreteModel(dom,el_size); - Δ = get_Δ(model) + el_Δ = get_el_Δ(model) f_Γ_D(x) = (x[1] ≈ 0.0) - f_Γ_N(x) = (x[1] ≈ xmax && ymax/2-ymax*prop_Γ_N/4 - eps() <= x[2] <= ymax/2+ymax*prop_Γ_N/4 + eps()) + f_Γ_N(x) = (x[1] ≈ xmax && ymax/2-ymax*prop_Γ_N/2 - eps() <= x[2] <= ymax/2+ymax*prop_Γ_N/2 + eps()) update_labels!(1,model,f_Γ_D,"Gamma_D") update_labels!(2,model,f_Γ_N,"Gamma_N") @@ -44,16 +45,16 @@ function main() U_reg = TrialFESpace(V_reg,0) ## Create FE functions - φh = interpolate(gen_lsf(4,0.2),V_φ) + φh = interpolate(initial_lsf(4,0.2),V_φ) ## Interpolation and weak form - interp = SmoothErsatzMaterialInterpolation(η = η_coeff*maximum(Δ)) + interp = SmoothErsatzMaterialInterpolation(η = η_coeff*maximum(el_Δ)) I,H,DH,ρ = interp.I,interp.H,interp.DH,interp.ρ ## Material properties and loading _E = 1000 ν = 0.3 - μ, λ = _E/(2*(1 + ν)), _E*ν/((1 + ν)*(1 - 2*ν)) + μ, λ = _E/(2*(1 + ν)), _E*ν/((1 + ν)*(1 - ν)) g = VectorValue(0,-10) ## Saint Venant–Kirchhoff law @@ -66,31 +67,29 @@ function main() ## Optimisation functionals J(u,φ,dΩ,dΓ_N) = ∫((I ∘ φ)*((T ∘ ∇(u)) ⊙ ∇(u)))dΩ Vol(u,φ,dΩ,dΓ_N) = ∫(((ρ ∘ φ) - vf)/vol_D)dΩ; - dVol(q,u,φ,dΩ,dΓ_N) = ∫(1/vol_D*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ + dVol(q,u,φ,dΩ,dΓ_N) = ∫(-1/vol_D*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ ## Finite difference solver and level set function stencil = AdvectionStencil(FirstOrderStencil(2,Float64),model,V_φ,tol,max_steps) - reinit!(stencil,φh,γ_reinit) ## Setup solver and FE operators state_map = NonlinearFEStateMap(res,U,V,V_φ,U_reg,φh,dΩ,dΓ_N) pcfs = PDEConstrainedFunctionals(J,[Vol],state_map,analytic_dC=[dVol]) ## Hilbertian extension-regularisation problems - α = α_coeff*maximum(Δ) + α = α_coeff*maximum(el_Δ) a_hilb(p,q) =∫(α^2*∇(p)⋅∇(q) + p*q)dΩ vel_ext = VelocityExtension(a_hilb,U_reg,V_reg) ## Optimiser - make_dir(path) optimiser = AugmentedLagrangian(pcfs,stencil,vel_ext,φh; γ,γ_reinit,verbose=true,constraint_names=[:Vol]) for (it,uh,φh) in optimiser - write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi))|"=>(norm ∘ ∇(φh)),"uh"=>uh]) + write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh)),"uh"=>uh]) write_history(path*"/history.txt",optimiser.history) end - it = optimiser.history.niter; uh = get_state(optimiser.problem) - write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi))|"=>(norm ∘ ∇(φh)),"uh"=>uh];iter_mod=1) + it = get_history(optimiser).niter; uh = get_state(pcfs) + write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh)),"uh"=>uh];iter_mod=1) end main() \ No newline at end of file diff --git a/scripts/Serial/hyperelastic_compliance_neohook_ALM.jl b/scripts/Serial/hyperelastic_compliance_neohook_ALM.jl index 35681265..0ebae329 100644 --- a/scripts/Serial/hyperelastic_compliance_neohook_ALM.jl +++ b/scripts/Serial/hyperelastic_compliance_neohook_ALM.jl @@ -1,4 +1,4 @@ -using Gridap, LSTO_Distributed +using Gridap, LevelSetTopOpt """ (Serial) Minimum hyperelastic (neohookean) compliance with augmented Lagrangian method in 2D. @@ -7,23 +7,24 @@ function main() ## Parameters order = 1 xmax,ymax=2.0,1.0 - prop_Γ_N = 0.4 + prop_Γ_N = 0.2 dom = (0,xmax,0,ymax) el_size = (200,200) γ = 0.05 γ_reinit = 0.5 max_steps = floor(Int,minimum(el_size)/10) - tol = 1/(10order^2)*prod(inv,minimum(el_size)) + tol = 1/(5order^2)/minimum(el_size) η_coeff = 2 α_coeff = 4 vf = 0.6 path = dirname(dirname(@__DIR__))*"/results/hyperelastic_compliance_neohook_ALM" + mkdir(path) ## FE Setup model = CartesianDiscreteModel(dom,el_size); - Δ = get_Δ(model) + el_Δ = get_el_Δ(model) f_Γ_D(x) = (x[1] ≈ 0.0) - f_Γ_N(x) = (x[1] ≈ xmax && ymax/2-ymax*prop_Γ_N/4 - eps() <= x[2] <= ymax/2+ymax*prop_Γ_N/4 + eps()) + f_Γ_N(x) = (x[1] ≈ xmax && ymax/2-ymax*prop_Γ_N/2 - eps() <= x[2] <= ymax/2+ymax*prop_Γ_N/2 + eps()) update_labels!(1,model,f_Γ_D,"Gamma_D") update_labels!(2,model,f_Γ_N,"Gamma_N") @@ -44,16 +45,16 @@ function main() U_reg = TrialFESpace(V_reg,0) ## Create FE functions - φh = interpolate(gen_lsf(4,0.2),V_φ) + φh = interpolate(initial_lsf(4,0.2),V_φ) ## Interpolation and weak form - interp = SmoothErsatzMaterialInterpolation(η = η_coeff*maximum(Δ)) + interp = SmoothErsatzMaterialInterpolation(η = η_coeff*maximum(el_Δ)) I,H,DH,ρ = interp.I,interp.H,interp.DH,interp.ρ ## Material properties _E = 1000 ν = 0.3 - μ, λ = _E/(2*(1 + ν)), _E*ν/((1 + ν)*(1 - 2*ν)) + μ, λ = _E/(2*(1 + ν)), _E*ν/((1 + ν)*(1 - ν)) g = VectorValue(0,-20) ## Neohookean hyperelastic material @@ -80,31 +81,29 @@ function main() ## Optimisation functionals Obj(u,φ,dΩ,dΓ_N) = ∫((I ∘ φ)*((dE∘(∇(u),∇(u))) ⊙ (S∘∇(u))))dΩ Vol(u,φ,dΩ,dΓ_N) = ∫(((ρ ∘ φ) - vf)/vol_D)dΩ; - dVol(q,u,φ,dΩ,dΓ_N) = ∫(1/vol_D*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ + dVol(q,u,φ,dΩ,dΓ_N) = ∫(-1/vol_D*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ ## Finite difference solver and level set function stencil = AdvectionStencil(FirstOrderStencil(2,Float64),model,V_φ,tol,max_steps) - reinit!(stencil,φh,γ_reinit) ## Setup solver and FE operators state_map = NonlinearFEStateMap(res,U,V,V_φ,U_reg,φh,dΩ,dΓ_N) pcfs = PDEConstrainedFunctionals(Obj,[Vol],state_map,analytic_dC=[dVol]) ## Hilbertian extension-regularisation problems - α = α_coeff*maximum(Δ) + α = α_coeff*maximum(el_Δ) a_hilb(p,q) =∫(α^2*∇(p)⋅∇(q) + p*q)dΩ vel_ext = VelocityExtension(a_hilb,U_reg,V_reg) ## Optimiser - make_dir(path) optimiser = AugmentedLagrangian(pcfs,stencil,vel_ext,φh; γ,γ_reinit,verbose=true,constraint_names=[:Vol]) for (it,uh,φh) in optimiser - write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi))|"=>(norm ∘ ∇(φh)),"uh"=>uh]) + write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh)),"uh"=>uh]) write_history(path*"/history.txt",optimiser.history) end - it = optimiser.history.niter; uh = get_state(optimiser.problem) - write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi))|"=>(norm ∘ ∇(φh)),"uh"=>uh];iter_mod=1) + it = get_history(optimiser).niter; uh = get_state(pcfs) + write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh)),"uh"=>uh];iter_mod=1) end main() \ No newline at end of file diff --git a/scripts/Serial/inverse_homenisation_ALM.jl b/scripts/Serial/inverse_homenisation_ALM.jl index 59779e88..c480820d 100644 --- a/scripts/Serial/inverse_homenisation_ALM.jl +++ b/scripts/Serial/inverse_homenisation_ALM.jl @@ -1,4 +1,4 @@ -using Gridap, LSTO_Distributed +using Gridap, LevelSetTopOpt """ (Serial) Maximum bulk modulus inverse homogenisation with augmented Lagrangian method in 2D. @@ -19,16 +19,17 @@ function main() γ = 0.05 γ_reinit = 0.5 max_steps = floor(Int,minimum(el_size)/10) - tol = 1/(10order^2)*prod(inv,minimum(el_size)) - C = isotropic_2d(1.,0.3) + tol = 1/(5order^2)/minimum(el_size) + C = isotropic_elast_tensor(2,1.,0.3) η_coeff = 2 α_coeff = 4 vf = 0.5 path = dirname(dirname(@__DIR__))*"/results/inverse_homenisation_ALM" + mkdir(path) ## FE Setup model = CartesianDiscreteModel(dom,el_size,isperiodic=(true,true)) - Δ = get_Δ(model) + el_Δ = get_el_Δ(model) f_Γ_D(x) = iszero(x) update_labels!(1,model,f_Γ_D,"origin") @@ -46,11 +47,11 @@ function main() U_reg = TrialFESpace(V_reg) ## Create FE functions - lsf_fn = x->max(gen_lsf(2,0.4)(x),gen_lsf(2,0.4;b=VectorValue(0,0.5))(x)) + lsf_fn = x->max(initial_lsf(2,0.4)(x),initial_lsf(2,0.4;b=VectorValue(0,0.5))(x)) φh = interpolate(lsf_fn,V_φ) ## Interpolation and weak form - interp = SmoothErsatzMaterialInterpolation(η = η_coeff*maximum(Δ)) + interp = SmoothErsatzMaterialInterpolation(η = η_coeff*maximum(el_Δ)) I,H,DH,ρ = interp.I,interp.H,interp.DH,interp.ρ εᴹ = (TensorValue(1.,0.,0.,0.), # ϵᵢⱼ⁽¹¹⁾≡ϵᵢⱼ⁽¹⁾ @@ -66,33 +67,31 @@ function main() _v_K(C,(u1,u2,u3),εᴹ) = (_C(C,ε(u1)+εᴹ[1],ε(u1)+εᴹ[1]) + _C(C,ε(u2)+εᴹ[2],ε(u2)+εᴹ[2]) + 2*_C(C,ε(u1)+εᴹ[1],ε(u2)+εᴹ[2]))/4 J(u,φ,dΩ) = ∫(-(I ∘ φ)*_K(C,u,εᴹ))dΩ - dJ(q,u,φ,dΩ) = ∫(-_v_K(C,u,εᴹ)*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ + dJ(q,u,φ,dΩ) = ∫(_v_K(C,u,εᴹ)*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ Vol(u,φ,dΩ) = ∫(((ρ ∘ φ) - vf)/vol_D)dΩ - dVol(q,u,φ,dΩ) = ∫(1/vol_D*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ + dVol(q,u,φ,dΩ) = ∫(-1/vol_D*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ ## Finite difference solver and level set function stencil = AdvectionStencil(FirstOrderStencil(2,Float64),model,V_φ,tol,max_steps) - reinit!(stencil,φh,γ_reinit) ## Setup solver and FE operators state_map = RepeatingAffineFEStateMap(3,a,l,U,V,V_φ,U_reg,φh,dΩ) pcfs = PDEConstrainedFunctionals(J,[Vol],state_map;analytic_dJ=dJ,analytic_dC=[dVol]) ## Hilbertian extension-regularisation problems - α = α_coeff*maximum(Δ) + α = α_coeff*maximum(el_Δ) a_hilb(p,q) =∫(α^2*∇(p)⋅∇(q) + p*q)dΩ vel_ext = VelocityExtension(a_hilb,U_reg,V_reg) ## Optimiser - make_dir(path) optimiser = AugmentedLagrangian(pcfs,stencil,vel_ext,φh; γ,γ_reinit,verbose=true,constraint_names=[:Vol]) for (it,uh,φh) in optimiser - write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi))|"=>(norm ∘ ∇(φh))]) + write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh))]) write_history(path*"/history.txt",optimiser.history) end - it = optimiser.history.niter; uh = get_state(optimiser.problem) - write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi))|"=>(norm ∘ ∇(φh))];iter_mod=1) + it = get_history(optimiser).niter; uh = get_state(pcfs) + write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh))];iter_mod=1) end main() \ No newline at end of file diff --git a/scripts/Serial/inverter_ALM.jl b/scripts/Serial/inverter_ALM.jl new file mode 100644 index 00000000..85b28d89 --- /dev/null +++ b/scripts/Serial/inverter_ALM.jl @@ -0,0 +1,110 @@ +using Gridap, LevelSetTopOpt + +""" + (Serial) Inverter mechanism with Hilbertian projection method in 2D. + + Optimisation problem: + Min J(Ω) = ηᵢₙ*∫ u⋅e₁ dΓᵢₙ/Vol(Γᵢₙ) + Ω + s.t., Vol(Ω) = vf, + C(Ω) = 0, + ⎡u∈V=H¹(Ω;u(Γ_D)=0)ᵈ, + ⎣∫ C ⊙ ε(u) ⊙ ε(v) dΩ + ∫ kₛv⋅u dΓₒᵤₜ = ∫ v⋅g dΓᵢₙ , ∀v∈V. + + where C(Ω) = ∫ -u⋅e₁-δₓ dΓₒᵤₜ/Vol(Γₒᵤₜ). We assume symmetry in the problem to aid + convergence. +""" +function main() + ## Parameters + order = 1 + dom = (0,1,0,0.5) + el_size = (200,100) + γ = 0.1 + γ_reinit = 0.5 + max_steps = floor(Int,minimum(el_size)/10) + tol = 1/(5order^2)/minimum(el_size) + C = isotropic_elast_tensor(2,1.0,0.3) + η_coeff = 2 + α_coeff = 4 + vf = 0.4 + δₓ = 0.2 + ks = 0.1 + g = VectorValue(0.5,0) + path = dirname(dirname(@__DIR__))*"/results/inverter_ALM" + mkdir(path) + + ## FE Setup + model = CartesianDiscreteModel(dom,el_size) + el_Δ = get_el_Δ(model) + f_Γ_in(x) = (x[1] ≈ 0.0) && (x[2] <= 0.03 + eps()) + f_Γ_out(x) = (x[1] ≈ 1.0) && (x[2] <= 0.07 + eps()) + f_Γ_D(x) = (x[1] ≈ 0.0) && (x[2] >= 0.4) + f_Γ_D2(x) = (x[2] ≈ 0.0) + update_labels!(1,model,f_Γ_in,"Gamma_in") + update_labels!(2,model,f_Γ_out,"Gamma_out") + update_labels!(3,model,f_Γ_D,"Gamma_D") + update_labels!(4,model,f_Γ_D2,"SymLine") + + ## Triangulations and measures + Ω = Triangulation(model) + Γ_in = BoundaryTriangulation(model,tags="Gamma_in") + Γ_out = BoundaryTriangulation(model,tags="Gamma_out") + dΩ = Measure(Ω,2order) + dΓ_in = Measure(Γ_in,2order) + dΓ_out = Measure(Γ_out,2order) + vol_D = sum(∫(1)dΩ) + vol_Γ_in = sum(∫(1)dΓ_in) + vol_Γ_out = sum(∫(1)dΓ_out) + + ## Spaces + reffe = ReferenceFE(lagrangian,VectorValue{2,Float64},order) + reffe_scalar = ReferenceFE(lagrangian,Float64,order) + V = TestFESpace(model,reffe;dirichlet_tags=["Gamma_D","SymLine"], + dirichlet_masks=[(true,true),(false,true)]) + U = TrialFESpace(V,[VectorValue(0.0,0.0),VectorValue(0.0,0.0)]) + V_φ = TestFESpace(model,reffe_scalar) + V_reg = TestFESpace(model,reffe_scalar;dirichlet_tags=["Gamma_in","Gamma_out"]) + U_reg = TrialFESpace(V_reg,[0,0]) + + ## Create FE functions + lsf_fn(x) = min(max(initial_lsf(6,0.2)(x),-sqrt((x[1]-1)^2+(x[2]-0.5)^2)+0.2),sqrt((x[1])^2+(x[2]-0.5)^2)-0.1) + φh = interpolate(lsf_fn,V_φ) + + ## Interpolation and weak form + interp = SmoothErsatzMaterialInterpolation(η = η_coeff*maximum(el_Δ)) + I,H,DH,ρ = interp.I,interp.H,interp.DH,interp.ρ + + a(u,v,φ,dΩ,dΓ_in,dΓ_out) = ∫((I ∘ φ)*(C ⊙ ε(u) ⊙ ε(v)))dΩ + ∫(ks*(u⋅v))dΓ_out + l(v,φ,dΩ,dΓ_in,dΓ_out) = ∫(v⋅g)dΓ_in + + ## Optimisation functionals + e₁ = VectorValue(1,0) + J(u,φ,dΩ,dΓ_in,dΓ_out) = ∫((u⋅e₁)/vol_Γ_in)dΓ_in + Vol(u,φ,dΩ,dΓ_in,dΓ_out) = ∫(((ρ ∘ φ) - vf)/vol_D)dΩ + dVol(q,u,φ,dΩ,dΓ_in,dΓ_out) = ∫(-1/vol_D*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ + UΓ_out(u,φ,dΩ,dΓ_in,dΓ_out) = ∫((u⋅-e₁-δₓ)/vol_Γ_out)dΓ_out + + ## Finite difference solver and level set function + stencil = AdvectionStencil(FirstOrderStencil(2,Float64),model,V_φ,tol,max_steps) + + ## Setup solver and FE operators + state_map = AffineFEStateMap(a,l,U,V,V_φ,U_reg,φh,dΩ,dΓ_in,dΓ_out) + pcfs = PDEConstrainedFunctionals(J,[Vol,UΓ_out],state_map,analytic_dC=[dVol,nothing]) + + ## Hilbertian extension-regularisation problems + α = α_coeff*maximum(el_Δ) + a_hilb(p,q) = ∫(α^2*∇(p)⋅∇(q) + p*q)dΩ; + vel_ext = VelocityExtension(a_hilb,U_reg,V_reg) + + ## Optimiser + optimiser = AugmentedLagrangian(pcfs,stencil,vel_ext,φh; + γ,γ_reinit,verbose=true,constraint_names=[:Vol,:UΓ_out]) + for (it,uh,φh) in optimiser + write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh)),"uh"=>uh]) + write_history(path*"/history.txt",optimiser.history) + end + it = get_history(optimiser).niter; uh = get_state(pcfs) + write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh)),"uh"=>uh];iter_mod=1) +end + +main() \ No newline at end of file diff --git a/scripts/Serial/inverter_HPM.jl b/scripts/Serial/inverter_HPM.jl index 9eb3df18..f79ca7e9 100644 --- a/scripts/Serial/inverter_HPM.jl +++ b/scripts/Serial/inverter_HPM.jl @@ -1,4 +1,4 @@ -using Gridap, LSTO_Distributed +using Gridap, LevelSetTopOpt """ (Serial) Inverter mechanism with Hilbertian projection method in 2D. @@ -19,22 +19,23 @@ function main() order = 1 dom = (0,1,0,0.5) el_size = (200,100) - γ = 0.05 + γ = 0.1 γ_reinit = 0.5 max_steps = floor(Int,minimum(el_size)/10) - tol = 1/(10order^2)*prod(inv,minimum(el_size)) - C = isotropic_2d(1.0,0.3) + tol = 1/(5order^2)/minimum(el_size) + C = isotropic_elast_tensor(2,1.0,0.3) η_coeff = 2 α_coeff = 4 vf = 0.4 - δₓ = 0.75 - ks = 0.01 - g = VectorValue(1,0) - path = dirname(dirname(@__DIR__))*"/results/inverter_HPM" + δₓ = 0.2 + ks = 0.1 + g = VectorValue(0.5,0) + path = dirname(dirname(@__DIR__))*"/results/inverter_HPM_osc" + mkdir(path) ## FE Setup model = CartesianDiscreteModel(dom,el_size) - Δ = get_Δ(model) + el_Δ = get_el_Δ(model) f_Γ_in(x) = (x[1] ≈ 0.0) && (x[2] <= 0.03 + eps()) f_Γ_out(x) = (x[1] ≈ 1.0) && (x[2] <= 0.07 + eps()) f_Γ_D(x) = (x[1] ≈ 0.0) && (x[2] >= 0.4) @@ -66,11 +67,12 @@ function main() U_reg = TrialFESpace(V_reg,[0,0]) ## Create FE functions - lsf_fn(x) = max(gen_lsf(6,0.2)(x),-sqrt((x[1]-1)^2+(x[2]-0.5)^2)+0.2) + lsf_fn(x) = min(max(initial_lsf(6,0.2)(x),-sqrt((x[1]-1)^2+(x[2]-0.5)^2)+0.2), + sqrt((x[1])^2+(x[2]-0.5)^2)-0.1) φh = interpolate(lsf_fn,V_φ) ## Interpolation and weak form - interp = SmoothErsatzMaterialInterpolation(η = η_coeff*maximum(Δ)) + interp = SmoothErsatzMaterialInterpolation(η = η_coeff*maximum(el_Δ)) I,H,DH,ρ = interp.I,interp.H,interp.DH,interp.ρ a(u,v,φ,dΩ,dΓ_in,dΓ_out) = ∫((I ∘ φ)*(C ⊙ ε(u) ⊙ ε(v)))dΩ + ∫(ks*(u⋅v))dΓ_out @@ -80,32 +82,31 @@ function main() e₁ = VectorValue(1,0) J(u,φ,dΩ,dΓ_in,dΓ_out) = ∫((u⋅e₁)/vol_Γ_in)dΓ_in Vol(u,φ,dΩ,dΓ_in,dΓ_out) = ∫(((ρ ∘ φ) - vf)/vol_D)dΩ - dVol(q,u,φ,dΩ,dΓ_in,dΓ_out) = ∫(1/vol_D*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ + dVol(q,u,φ,dΩ,dΓ_in,dΓ_out) = ∫(-1/vol_D*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ UΓ_out(u,φ,dΩ,dΓ_in,dΓ_out) = ∫((u⋅-e₁-δₓ)/vol_Γ_out)dΓ_out ## Finite difference solver and level set function stencil = AdvectionStencil(FirstOrderStencil(2,Float64),model,V_φ,tol,max_steps) - reinit!(stencil,φh,γ_reinit) ## Setup solver and FE operators state_map = AffineFEStateMap(a,l,U,V,V_φ,U_reg,φh,dΩ,dΓ_in,dΓ_out) pcfs = PDEConstrainedFunctionals(J,[Vol,UΓ_out],state_map,analytic_dC=[dVol,nothing]) ## Hilbertian extension-regularisation problems - α = α_coeff*maximum(Δ) + α = α_coeff*maximum(el_Δ) a_hilb(p,q) = ∫(α^2*∇(p)⋅∇(q) + p*q)dΩ; vel_ext = VelocityExtension(a_hilb,U_reg,V_reg) ## Optimiser - make_dir(path) + ls_enabled=false # Setting to true will use a line search instead of oscillation detection optimiser = HilbertianProjection(pcfs,stencil,vel_ext,φh; - γ,γ_reinit,α_min=0.5,verbose=true,constraint_names=[:Vol,:UΓ_out]) + γ,γ_reinit,α_min=0.4,ls_enabled,verbose=true,constraint_names=[:Vol,:UΓ_out]) for (it,uh,φh) in optimiser - write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi))|"=>(norm ∘ ∇(φh)),"uh"=>uh]) + write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh)),"uh"=>uh]) write_history(path*"/history.txt",optimiser.history) end - it = optimiser.history.niter; uh = get_state(optimiser.problem) - write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi))|"=>(norm ∘ ∇(φh)),"uh"=>uh];iter_mod=1) + it = get_history(optimiser).niter; uh = get_state(pcfs) + write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh)),"uh"=>uh];iter_mod=1) end main() \ No newline at end of file diff --git a/scripts/Serial/nonlinear_thermal_compliance_ALM.jl b/scripts/Serial/nonlinear_thermal_compliance_ALM.jl index d07d566c..270eedcf 100644 --- a/scripts/Serial/nonlinear_thermal_compliance_ALM.jl +++ b/scripts/Serial/nonlinear_thermal_compliance_ALM.jl @@ -1,4 +1,4 @@ -using Gridap, LSTO_Distributed +using Gridap, LevelSetTopOpt """ (Serial) Minimum thermal compliance with augmented Lagrangian method in 2D with nonlinear diffusivity. @@ -16,26 +16,27 @@ function main() ## Parameters order = 1 xmax=ymax=1.0 - prop_Γ_N = 0.4 + prop_Γ_N = 0.2 prop_Γ_D = 0.2 dom = (0,xmax,0,ymax) el_size = (200,200) γ = 0.1 γ_reinit = 0.5 max_steps = floor(Int,minimum(el_size)/10) - tol = 1/(10order^2)*prod(inv,minimum(el_size)) + tol = 1/(5order^2)/minimum(el_size) η_coeff = 2 α_coeff = 4 vf = 0.4 path = dirname(dirname(@__DIR__))*"/results/nonlinear_thermal_compliance_ALM" + mkdir(path) ## FE Setup model = CartesianDiscreteModel(dom,el_size); - Δ = get_Δ(model) + el_Δ = get_el_Δ(model) f_Γ_D(x) = (x[1] ≈ 0.0 && (x[2] <= ymax*prop_Γ_D + eps() || x[2] >= ymax-ymax*prop_Γ_D - eps())); - f_Γ_N(x) = (x[1] ≈ xmax && ymax/2-ymax*prop_Γ_N/4 - eps() <= x[2] <= - ymax/2+ymax*prop_Γ_N/4 + eps()); + f_Γ_N(x) = (x[1] ≈ xmax && ymax/2-ymax*prop_Γ_N/2 - eps() <= x[2] <= + ymax/2+ymax*prop_Γ_N/2 + eps()); update_labels!(1,model,f_Γ_D,"Gamma_D") update_labels!(2,model,f_Γ_N,"Gamma_N") @@ -55,10 +56,10 @@ function main() U_reg = TrialFESpace(V_reg,0) ## Create FE functions - φh = interpolate(gen_lsf(4,0.2),V_φ) + φh = interpolate(initial_lsf(4,0.2),V_φ) ## Interpolation and weak form - interp = SmoothErsatzMaterialInterpolation(η = η_coeff*maximum(Δ)) + interp = SmoothErsatzMaterialInterpolation(η = η_coeff*maximum(el_Δ)) I,H,DH,ρ = interp.I,interp.H,interp.DH,interp.ρ κ0 = 1 @@ -69,31 +70,29 @@ function main() ## Optimisation functionals J(u,φ,dΩ,dΓ_N) = ∫((I ∘ φ)*(κ ∘ u)*∇(u)⋅∇(u))dΩ Vol(u,φ,dΩ,dΓ_N) = ∫(((ρ ∘ φ) - vf)/vol_D)dΩ; - dVol(q,u,φ,dΩ,dΓ_N) = ∫(1/vol_D*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ + dVol(q,u,φ,dΩ,dΓ_N) = ∫(-1/vol_D*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ ## Finite difference solver and level set function stencil = AdvectionStencil(FirstOrderStencil(2,Float64),model,V_φ,tol,max_steps) - reinit!(stencil,φh,γ_reinit) ## Setup solver and FE operators state_map = NonlinearFEStateMap(res,U,V,V_φ,U_reg,φh,dΩ,dΓ_N) pcfs = PDEConstrainedFunctionals(J,[Vol],state_map,analytic_dC=[dVol]) ## Hilbertian extension-regularisation problems - α = α_coeff*maximum(Δ) + α = α_coeff*maximum(el_Δ) a_hilb(p,q) =∫(α^2*∇(p)⋅∇(q) + p*q)dΩ vel_ext = VelocityExtension(a_hilb,U_reg,V_reg) ## Optimiser - make_dir(path) optimiser = AugmentedLagrangian(pcfs,stencil,vel_ext,φh; γ,γ_reinit,verbose=true,constraint_names=[:Vol]) for (it, uh, φh) in optimiser - write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi))|"=>(norm ∘ ∇(φh)),"uh"=>uh]) + write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh)),"uh"=>uh]) write_history(path*"/history.txt",optimiser.history) end - it = optimiser.history.niter; uh = get_state(optimiser.problem) - write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi))|"=>(norm ∘ ∇(φh)),"uh"=>uh];iter_mod=1) + it = get_history(optimiser).niter; uh = get_state(pcfs) + write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh)),"uh"=>uh];iter_mod=1) end main() \ No newline at end of file diff --git a/scripts/Serial/thermal_compliance_ALM.jl b/scripts/Serial/thermal_compliance_ALM.jl index 8fdde107..171d8d49 100644 --- a/scripts/Serial/thermal_compliance_ALM.jl +++ b/scripts/Serial/thermal_compliance_ALM.jl @@ -1,4 +1,4 @@ -using Gridap, LSTO_Distributed +using Gridap, LevelSetTopOpt """ (Serial) Minimum thermal compliance with augmented Lagrangian method in 2D. @@ -11,30 +11,31 @@ using Gridap, LSTO_Distributed ⎣∫ κ*∇(u)⋅∇(v) dΩ = ∫ v dΓ_N, ∀v∈V. """ function main() - ## Parameters + ## Parameters| order = 1 xmax=ymax=1.0 - prop_Γ_N = 0.4 + prop_Γ_N = 0.2 prop_Γ_D = 0.2 dom = (0,xmax,0,ymax) el_size = (200,200) γ = 0.1 γ_reinit = 0.5 max_steps = floor(Int,minimum(el_size)/10) - tol = 1/(10order^2)*prod(inv,minimum(el_size)) + tol = 1/(5*order^2)/minimum(el_size) κ = 1 vf = 0.4 η_coeff = 2 α_coeff = 4 path = dirname(dirname(@__DIR__))*"/results/thermal_compliance_ALM" + mkdir(path) ## FE Setup model = CartesianDiscreteModel(dom,el_size); - Δ = get_Δ(model) + el_Δ = get_el_Δ(model) f_Γ_D(x) = (x[1] ≈ 0.0 && (x[2] <= ymax*prop_Γ_D + eps() || x[2] >= ymax-ymax*prop_Γ_D - eps())) - f_Γ_N(x) = (x[1] ≈ xmax && ymax/2-ymax*prop_Γ_N/4 - eps() <= x[2] <= - ymax/2+ymax*prop_Γ_N/4 + eps()) + f_Γ_N(x) = (x[1] ≈ xmax && ymax/2-ymax*prop_Γ_N/2 - eps() <= x[2] <= + ymax/2+ymax*prop_Γ_N/2 + eps()) update_labels!(1,model,f_Γ_D,"Gamma_D") update_labels!(2,model,f_Γ_N,"Gamma_N") @@ -54,10 +55,10 @@ function main() U_reg = TrialFESpace(V_reg,0) ## Create FE functions - φh = interpolate(gen_lsf(4,0.2),V_φ) + φh = interpolate(initial_lsf(4,0.2),V_φ) ## Interpolation and weak form - interp = SmoothErsatzMaterialInterpolation(η = η_coeff*maximum(Δ)) + interp = SmoothErsatzMaterialInterpolation(η = η_coeff*maximum(el_Δ)) I,H,DH,ρ = interp.I,interp.H,interp.DH,interp.ρ a(u,v,φ,dΩ,dΓ_N) = ∫((I ∘ φ)*κ*∇(u)⋅∇(v))dΩ @@ -65,34 +66,31 @@ function main() ## Optimisation functionals J(u,φ,dΩ,dΓ_N) = ∫((I ∘ φ)*κ*∇(u)⋅∇(u))dΩ - dJ(q,u,φ,dΩ,dΓ_N) = ∫(-κ*∇(u)⋅∇(u)*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ; + dJ(q,u,φ,dΩ,dΓ_N) = ∫(κ*∇(u)⋅∇(u)*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ; Vol(u,φ,dΩ,dΓ_N) = ∫(((ρ ∘ φ) - vf)/vol_D)dΩ; - dVol(q,u,φ,dΩ,dΓ_N) = ∫(1/vol_D*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ + dVol(q,u,φ,dΩ,dΓ_N) = ∫(-1/vol_D*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ ## Finite difference solver and level set function stencil = AdvectionStencil(FirstOrderStencil(2,Float64),model,V_φ,tol,max_steps) - reinit!(stencil,φh,γ_reinit) ## Setup solver and FE operators state_map = AffineFEStateMap(a,l,U,V,V_φ,U_reg,φh,dΩ,dΓ_N) pcfs = PDEConstrainedFunctionals(J,[Vol],state_map,analytic_dJ=dJ,analytic_dC=[dVol]) ## Hilbertian extension-regularisation problems - α = α_coeff*maximum(Δ) + α = α_coeff*maximum(el_Δ) a_hilb(p,q) =∫(α^2*∇(p)⋅∇(q) + p*q)dΩ; vel_ext = VelocityExtension(a_hilb,U_reg,V_reg) ## Optimiser - make_dir(path) - # converged = m -> LSTO_Distributed.default_al_converged(m;L_tol = 0.01*maximum(Δ)) optimiser = AugmentedLagrangian(pcfs,stencil,vel_ext,φh; γ,γ_reinit,verbose=true,constraint_names=[:Vol]) for (it,uh,φh) in optimiser - write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi))|"=>(norm ∘ ∇(φh)),"uh"=>uh]) + write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh)),"uh"=>uh]) write_history(path*"/history.txt",optimiser.history) end - it = optimiser.history.niter; uh = get_state(optimiser.problem) - write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi))|"=>(norm ∘ ∇(φh)),"uh"=>uh];iter_mod=1) + it = get_history(optimiser).niter; uh = get_state(pcfs) + write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh)),"uh"=>uh];iter_mod=1) end main() \ No newline at end of file diff --git a/scripts/_dev/3d_hyperelastic_compliance_ALM.jl b/scripts/_dev/3d_hyperelastic_compliance_ALM.jl index 188a49b4..8066e3e6 100644 --- a/scripts/_dev/3d_hyperelastic_compliance_ALM.jl +++ b/scripts/_dev/3d_hyperelastic_compliance_ALM.jl @@ -1,5 +1,5 @@ using Gridap, Gridap.MultiField, GridapDistributed, GridapPETSc, GridapSolvers, - PartitionedArrays, LSTO_Distributed, SparseMatricesCSR + PartitionedArrays, LevelSetTopOpt, SparseMatricesCSR using GridapSolvers: NewtonSolver function main(mesh_partition,distribute,el_size,δₓ) @@ -10,16 +10,16 @@ function main(mesh_partition,distribute,el_size,δₓ) xmax,ymax,zmax=(1.0,1.0,1.0) dom = (0,xmax,0,ymax,0,zmax) η_coeff = 2 - prop_Γ_N = 0.4; + prop_Γ_N = 0.2; path = dirname(dirname(@__DIR__))*"/results/testing_hyper_elast" i_am_main(ranks) && ~isdir(path) && mkdir(path) ## FE Setup model = CartesianDiscreteModel(ranks,mesh_partition,dom,el_size) - Δ = get_Δ(model) + el_Δ = get_el_Δ(model) f_Γ_D(x) = (x[1] ≈ 0.0) - f_Γ_N(x) = (x[1] ≈ xmax) && (ymax/2-ymax*prop_Γ_N/4 - eps() <= x[2] <= ymax/2+ymax*prop_Γ_N/4 + eps()) && - (zmax/2-zmax*prop_Γ_N/4 - eps() <= x[3] <= zmax/2+zmax*prop_Γ_N/4 + eps()) + f_Γ_N(x) = (x[1] ≈ xmax) && (ymax/2-ymax*prop_Γ_N/2 - eps() <= x[2] <= ymax/2+ymax*prop_Γ_N/2 + eps()) && + (zmax/2-zmax*prop_Γ_N/2 - eps() <= x[3] <= zmax/2+zmax*prop_Γ_N/2 + eps()) update_labels!(1,model,f_Γ_D,"Gamma_D") update_labels!(2,model,f_Γ_N,"Gamma_N") @@ -37,11 +37,11 @@ function main(mesh_partition,distribute,el_size,δₓ) U_reg = TrialFESpace(V_reg,0) ## Create FE functions - # φh = interpolate(gen_lsf(4,0.2),V_φ) + # φh = interpolate(initial_lsf(4,0.2),V_φ) φh = interpolate(x->-1,V_φ) ## Interpolation and weak form - interp = SmoothErsatzMaterialInterpolation(η = η_coeff*maximum(Δ)) + interp = SmoothErsatzMaterialInterpolation(η = η_coeff*maximum(el_Δ)) I,H,DH,ρ = interp.I,interp.H,interp.DH,interp.ρ ## Material properties and loading @@ -71,9 +71,9 @@ function main(mesh_partition,distribute,el_size,δₓ) ) ## Optimiser - u = LSTO_Distributed.forward_solve(state_map,φh) + u = LevelSetTopOpt.forward_solve!(state_map,φh) uh = FEFunction(U,u) - write_vtk(Ω,path*"/struc_$δₓ",0,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi))|"=>(norm ∘ ∇(φh)),"uh"=>uh];iter_mod=1) + write_vtk(Ω,path*"/struc_$δₓ",0,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh)),"uh"=>uh];iter_mod=1) end function main_alt(mesh_partition,distribute,el_size,gz) @@ -87,16 +87,16 @@ function main_alt(mesh_partition,distribute,el_size,gz) prop_Γ_N = 0.8; γ_reinit = 0.5 max_steps = floor(Int,minimum(el_size)/3) - tol = 1/(10order^2)*prod(inv,minimum(el_size)) + tol = 1/(5order^2)/minimum(el_size) path = dirname(dirname(@__DIR__))*"/results/testing_hyper_elast" i_am_main(ranks) && ~isdir(path) && mkdir(path) ## FE Setup model = CartesianDiscreteModel(ranks,mesh_partition,dom,el_size) - Δ = get_Δ(model) + el_Δ = get_el_Δ(model) f_Γ_D(x) = (x[1] ≈ 0.0) - f_Γ_N(x) = (x[1] ≈ xmax) && (ymax/2-ymax*prop_Γ_N/4 - eps() <= x[2] <= ymax/2+ymax*prop_Γ_N/4 + eps()) && - (zmax/2-zmax*prop_Γ_N/4 - eps() <= x[3] <= zmax/2+zmax*prop_Γ_N/4 + eps()) + f_Γ_N(x) = (x[1] ≈ xmax) && (ymax/2-ymax*prop_Γ_N/2 - eps() <= x[2] <= ymax/2+ymax*prop_Γ_N/2 + eps()) && + (zmax/2-zmax*prop_Γ_N/2 - eps() <= x[3] <= zmax/2+zmax*prop_Γ_N/2 + eps()) update_labels!(1,model,f_Γ_D,"Gamma_D") update_labels!(2,model,f_Γ_N,"Gamma_N") @@ -116,11 +116,11 @@ function main_alt(mesh_partition,distribute,el_size,gz) U_reg = TrialFESpace(V_reg,0) ## Create FE functions - # φh = interpolate(gen_lsf(4,0.2),V_φ) + # φh = interpolate(initial_lsf(4,0.2),V_φ) φh = interpolate(x->-1,V_φ) ## Interpolation and weak form - interp = SmoothErsatzMaterialInterpolation(η = η_coeff*maximum(Δ)) + interp = SmoothErsatzMaterialInterpolation(η = η_coeff*maximum(el_Δ)) I,H,DH,ρ = interp.I,interp.H,interp.DH,interp.ρ ## Material properties and loading @@ -171,9 +171,9 @@ function main_alt(mesh_partition,distribute,el_size,gz) ) ## Optimiser - u = LSTO_Distributed.forward_solve(state_map,φh) + u = LevelSetTopOpt.forward_solve!(state_map,φh) uh = FEFunction(U,u) - write_vtk(Ω,path*"/struc_neohook_$gz",0,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi))|"=>(norm ∘ ∇(φh)),"uh"=>uh];iter_mod=1) + write_vtk(Ω,path*"/struc_neohook_$gz",0,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh)),"uh"=>uh];iter_mod=1) end with_mpi() do distribute diff --git a/scripts/_dev/Added_for_paper/hyperelastic_compliance_neohook_ALM_COMPARE.jl b/scripts/_dev/Added_for_paper/hyperelastic_compliance_neohook_ALM_COMPARE.jl new file mode 100644 index 00000000..673ae38c --- /dev/null +++ b/scripts/_dev/Added_for_paper/hyperelastic_compliance_neohook_ALM_COMPARE.jl @@ -0,0 +1,193 @@ +using Gridap, LevelSetTopOpt + +""" + (Serial) Minimum hyperelastic (neohookean) compliance with augmented Lagrangian method in 2D. +""" +function main() + ## Parameters + order = 1 + xmax,ymax=2.0,1.0 + prop_Γ_N = 0.2 + dom = (0,xmax,0,ymax) + el_size = (400,200) + γ = 0.1 + γ_reinit = 0.5 + max_steps = floor(Int,minimum(el_size)/10) + tol = 1/(5order^2)/minimum(el_size) + η_coeff = 2 + α_coeff = 4 + vf = 0.4 + path = dirname(dirname(@__DIR__))*"/results/highres_hyperelastic_compliance_neohook_ALM" + mkdir(path) + + ## FE Setup + model = CartesianDiscreteModel(dom,el_size); + el_Δ = get_el_Δ(model) + f_Γ_D(x) = (x[1] ≈ 0.0) + f_Γ_N(x) = (x[1] ≈ xmax && ymax/2-ymax*prop_Γ_N/2 - eps() <= x[2] <= ymax/2+ymax*prop_Γ_N/2 + eps()) + update_labels!(1,model,f_Γ_D,"Gamma_D") + update_labels!(2,model,f_Γ_N,"Gamma_N") + + ## Triangulations and measures + Ω = Triangulation(model) + Γ_N = BoundaryTriangulation(model,tags="Gamma_N") + dΩ = Measure(Ω,2*order) + dΓ_N = Measure(Γ_N,2*order) + vol_D = sum(∫(1)dΩ) + + ## Spaces + reffe = ReferenceFE(lagrangian,VectorValue{2,Float64},order) + reffe_scalar = ReferenceFE(lagrangian,Float64,order) + V = TestFESpace(model,reffe;dirichlet_tags=["Gamma_D"]) + U = TrialFESpace(V,VectorValue(0.0,0.0)) + V_φ = TestFESpace(model,reffe_scalar) + V_reg = TestFESpace(model,reffe_scalar;dirichlet_tags=["Gamma_N"]) + U_reg = TrialFESpace(V_reg,0) + + ## Create FE functions + φh = interpolate(initial_lsf(4,0.2),V_φ) + + ## Interpolation and weak form + interp = SmoothErsatzMaterialInterpolation(η = η_coeff*maximum(el_Δ)) + I,H,DH,ρ = interp.I,interp.H,interp.DH,interp.ρ + + ## Material properties + _E = 1000 + ν = 0.3 + μ, λ = _E/(2*(1 + ν)), _E*ν/((1 + ν)*(1 - 2*ν)) + g = VectorValue(0,-10) + + ## Neohookean hyperelastic material + # Deformation gradient + F(∇u) = one(∇u) + ∇u' + J(F) = sqrt(det(C(F))) + + # Derivative of green Strain + dE(∇du,∇u) = 0.5*( ∇du⋅F(∇u) + (∇du⋅F(∇u))' ) + + # Right Caughy-green deformation tensor + C(F) = (F')⋅F + + # Constitutive law (Neo hookean) + function S(∇u) + Cinv = inv(C(F(∇u))) + μ*(one(∇u)-Cinv) + λ*log(J(F(∇u)))*Cinv + end + + # Cauchy stress tensor and residual + σ(∇u) = (1.0/J(F(∇u)))*F(∇u)⋅S(∇u)⋅(F(∇u))' + res(u,v,φ,dΩ,dΓ_N) = ∫( (I ∘ φ)*((dE∘(∇(v),∇(u))) ⊙ (S∘∇(u))) )*dΩ - ∫(g⋅v)dΓ_N + + ## Optimisation functionals + Obj(u,φ,dΩ,dΓ_N) = ∫((I ∘ φ)*((dE∘(∇(u),∇(u))) ⊙ (S∘∇(u))))dΩ + Vol(u,φ,dΩ,dΓ_N) = ∫(((ρ ∘ φ) - vf)/vol_D)dΩ; + dVol(q,u,φ,dΩ,dΓ_N) = ∫(-1/vol_D*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ + + ## Finite difference solver and level set function + stencil = AdvectionStencil(FirstOrderStencil(2,Float64),model,V_φ,tol,max_steps) + + ## Setup solver and FE operators + state_map = NonlinearFEStateMap(res,U,V,V_φ,U_reg,φh,dΩ,dΓ_N) + pcfs = PDEConstrainedFunctionals(Obj,[Vol],state_map,analytic_dC=[dVol]) + + ## Hilbertian extension-regularisation problems + α = α_coeff*maximum(el_Δ) + a_hilb(p,q) =∫(α^2*∇(p)⋅∇(q) + p*q)dΩ + vel_ext = VelocityExtension(a_hilb,U_reg,V_reg) + + ## Optimiser + optimiser = AugmentedLagrangian(pcfs,stencil,vel_ext,φh; + γ,γ_reinit,verbose=true,constraint_names=[:Vol]) + for (it,uh,φh) in optimiser + write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh)),"uh"=>uh]) + write_history(path*"/history.txt",optimiser.history) + end + it = get_history(optimiser).niter; uh = get_state(pcfs) + write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh)),"uh"=>uh];iter_mod=1) +end + +main() + +function main_LE_comparison() + ## Parameters + order = 1 + xmax,ymax=(2.0,1.0) + prop_Γ_N = 0.2 + dom = (0,xmax,0,ymax) + el_size = (400,200) + γ = 0.1 + γ_reinit = 0.5 + max_steps = floor(Int,minimum(el_size)/10) + tol = 1/(5order^2)/minimum(el_size) + C = isotropic_elast_tensor(2,1000,0.3) + η_coeff = 2 + α_coeff = 4 + vf = 0.4 + g = VectorValue(0,-10) + path = dirname(dirname(@__DIR__))*"/results/highres_LE_comparison" + mkdir(path) + + ## FE Setup + model = CartesianDiscreteModel(dom,el_size) + el_Δ = get_el_Δ(model) + f_Γ_D(x) = (x[1] ≈ 0.0) + f_Γ_N(x) = (x[1] ≈ xmax && ymax/2-ymax*prop_Γ_N/2 - eps() <= x[2] <= ymax/2+ymax*prop_Γ_N/2 + eps()) + update_labels!(1,model,f_Γ_D,"Gamma_D") + update_labels!(2,model,f_Γ_N,"Gamma_N") + + ## Triangulations and measures + Ω = Triangulation(model) + Γ_N = BoundaryTriangulation(model,tags="Gamma_N") + dΩ = Measure(Ω,2*order) + dΓ_N = Measure(Γ_N,2*order) + vol_D = sum(∫(1)dΩ) + + ## Spaces + reffe = ReferenceFE(lagrangian,VectorValue{2,Float64},order) + reffe_scalar = ReferenceFE(lagrangian,Float64,order) + V = TestFESpace(model,reffe;dirichlet_tags=["Gamma_D"]) + U = TrialFESpace(V,VectorValue(0.0,0.0)) + V_φ = TestFESpace(model,reffe_scalar) + V_reg = TestFESpace(model,reffe_scalar;dirichlet_tags=["Gamma_N"]) + U_reg = TrialFESpace(V_reg,0) + + ## Create FE functions + φh = interpolate(initial_lsf(4,0.2),V_φ) + + ## Interpolation and weak form + interp = SmoothErsatzMaterialInterpolation(η = η_coeff*maximum(el_Δ)) + I,H,DH,ρ = interp.I,interp.H,interp.DH,interp.ρ + + a(u,v,φ,dΩ,dΓ_N) = ∫((I ∘ φ)*(C ⊙ ε(u) ⊙ ε(v)))dΩ + l(v,φ,dΩ,dΓ_N) = ∫(v⋅g)dΓ_N + + ## Optimisation functionals + J(u,φ,dΩ,dΓ_N) = ∫((I ∘ φ)*(C ⊙ ε(u) ⊙ ε(u)))dΩ + dJ(q,u,φ,dΩ,dΓ_N) = ∫((C ⊙ ε(u) ⊙ ε(u))*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ; + Vol(u,φ,dΩ,dΓ_N) = ∫(((ρ ∘ φ) - vf)/vol_D)dΩ; + dVol(q,u,φ,dΩ,dΓ_N) = ∫(-1/vol_D*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ + + ## Finite difference solver and level set function + stencil = AdvectionStencil(FirstOrderStencil(2,Float64),model,V_φ,tol,max_steps) + + ## Setup solver and FE operators + state_map = AffineFEStateMap(a,l,U,V,V_φ,U_reg,φh,dΩ,dΓ_N) + pcfs = PDEConstrainedFunctionals(J,[Vol],state_map,analytic_dJ=dJ,analytic_dC=[dVol]) + + ## Hilbertian extension-regularisation problems + α = α_coeff*maximum(el_Δ) + a_hilb(p,q) =∫(α^2*∇(p)⋅∇(q) + p*q)dΩ; + vel_ext = VelocityExtension(a_hilb,U_reg,V_reg) + + ## Optimiser + optimiser = AugmentedLagrangian(pcfs,stencil,vel_ext,φh; + γ,γ_reinit,verbose=true,constraint_names=[:Vol]) + for (it,uh,φh) in optimiser + write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh)),"uh"=>uh]) + write_history(path*"/history.txt",optimiser.history) + end + it = get_history(optimiser).niter; uh = get_state(pcfs) + write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh)),"uh"=>uh];iter_mod=1) +end + +main_LE_comparison() \ No newline at end of file diff --git a/scripts/_dev/Added_for_paper/thermal_MPI.jl b/scripts/_dev/Added_for_paper/thermal_MPI.jl new file mode 100644 index 00000000..446c0a54 --- /dev/null +++ b/scripts/_dev/Added_for_paper/thermal_MPI.jl @@ -0,0 +1,88 @@ +using LevelSetTopOpt, Gridap, GridapDistributed, GridapPETSc, PartitionedArrays, + SparseMatricesCSR + +function main(mesh_partition,distribute) + ranks = distribute(LinearIndices((prod(mesh_partition),))) + # FE parameters + order = 1 # Finite element order + xmax = ymax = 1.0 # Domain size + dom = (0,xmax,0,ymax) # Bounding domain + el_size = (200,200) # Mesh partition size + prop_Γ_N = 0.2 # Γ_N size parameter + prop_Γ_D = 0.2 # Γ_D size parameter + f_Γ_N(x) = (x[1] ≈ xmax && # Γ_N indicator function + ymax/2-ymax*prop_Γ_N/2 - eps() <= x[2] <= ymax/2+ymax*prop_Γ_N/2 + eps()) + f_Γ_D(x) = (x[1] ≈ 0.0 && # Γ_D indicator function + (x[2] <= ymax*prop_Γ_D + eps() || x[2] >= ymax-ymax*prop_Γ_D - eps())) # @$\lvert\lvert$@ + # FD parameters + γ = 0.1 # HJ eqn time step coeff + γ_reinit = 0.5 # Reinit. eqn time step coeff + max_steps = floor(Int,minimum(el_size)/10) # Max steps for advection + tol = 1/(5order^2)/minimum(el_size) # Advection tolerance + # Problem parameters + κ = 1 # Diffusivity + g = 1 # Heat flow in + vf = 0.4 # Volume fraction constraint + lsf_func = initial_lsf(4,0.2) # Initial level set function + iter_mod = 10 # VTK Output modulo + path = "./results/tut1_MPI_FIXEDSign/" # Output path + i_am_main(ranks) && mkpath(path) # Create path + # Model + model = CartesianDiscreteModel(ranks,mesh_partition,dom,el_size); + update_labels!(1,model,f_Γ_D,"Gamma_D") + update_labels!(2,model,f_Γ_N,"Gamma_N") + # Triangulation and measures + Ω = Triangulation(model) + Γ_N = BoundaryTriangulation(model,tags="Gamma_N") + dΩ = Measure(Ω,2*order) + dΓ_N = Measure(Γ_N,2*order) + # Spaces + reffe = ReferenceFE(lagrangian,Float64,order) + V = TestFESpace(model,reffe;dirichlet_tags=["Gamma_D"]) + U = TrialFESpace(V,0.0) + V_φ = TestFESpace(model,reffe) + V_reg = TestFESpace(model,reffe;dirichlet_tags=["Gamma_N"]) + U_reg = TrialFESpace(V_reg,0) + # Level set and interpolator + φh = interpolate(lsf_func,V_φ) + interp = SmoothErsatzMaterialInterpolation(η = 2*maximum(get_el_Δ(model))) + I,H,DH,ρ = interp.I,interp.H,interp.DH,interp.ρ + # Weak formulation + a(u,v,φ,dΩ,dΓ_N) = ∫((I ∘ φ)*κ*∇(u)⋅∇(v))dΩ + l(v,φ,dΩ,dΓ_N) = ∫(g*v)dΓ_N + solver = PETScLinearSolver() + state_map = AffineFEStateMap(a,l,U,V,V_φ,U_reg,φh,dΩ,dΓ_N) + # Objective and constraints + J(u,φ,dΩ,dΓ_N) = ∫((I ∘ φ)*κ*∇(u)⋅∇(u))dΩ + dJ(q,u,φ,dΩ,dΓ_N) = ∫(κ*∇(u)⋅∇(u)*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ + vol_D = sum(∫(1)dΩ) + C(u,φ,dΩ,dΓ_N) = ∫(((ρ ∘ φ) - vf)/vol_D)dΩ + dC(q,u,φ,dΩ,dΓ_N) = ∫(-1/vol_D*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ + pcfs = PDEConstrainedFunctionals(J,[C],state_map, + analytic_dJ=dJ,analytic_dC=[dC]) + # Velocity extension + α = 4*maximum(get_el_Δ(model)) + a_hilb(p,q) =∫(α^2*∇(p)⋅∇(q) + p*q)dΩ + vel_ext = VelocityExtension(a_hilb, U_reg, V_reg) + # Finite difference scheme + scheme = FirstOrderStencil(length(el_size),Float64) + stencil = AdvectionStencil(scheme,model,V_φ,tol,max_steps) + # Optimiser + optimiser = AugmentedLagrangian(pcfs,stencil,vel_ext,φh; + γ,γ_reinit,verbose=i_am_main(ranks),constraint_names=[:Vol]) + # Solve + for (it,uh,φh) in optimiser + data = ["φ"=>φh,"H(φ)"=>(H ∘ φh),"|nabla(φ)|"=>(norm ∘ ∇(φh)),"uh"=>uh] + iszero(it % iter_mod) && (writevtk(Ω,path*"out$it",cellfields=data);GC.gc()) + write_history(path*"/history.txt",get_history(optimiser);ranks) + end + # Final structure + it = get_history(optimiser).niter; uh = get_state(pcfs) + writevtk(Ω,path*"out$it",cellfields=["φ"=>φh, + "H(φ)"=>(H ∘ φh),"|nabla(φ)|"=>(norm ∘ ∇(φh)),"uh"=>uh]) +end + +with_mpi() do distribute + mesh_partition = (2,2) + main(mesh_partition,distribute) +end \ No newline at end of file diff --git a/scripts/_dev/Added_for_paper/thermal_MPI_PETSc.jl b/scripts/_dev/Added_for_paper/thermal_MPI_PETSc.jl new file mode 100644 index 00000000..ef08aa72 --- /dev/null +++ b/scripts/_dev/Added_for_paper/thermal_MPI_PETSc.jl @@ -0,0 +1,104 @@ +using LevelSetTopOpt, Gridap, GridapDistributed, GridapPETSc, PartitionedArrays, + SparseMatricesCSR + +function main(mesh_partition,distribute) + ranks = distribute(LinearIndices((prod(mesh_partition),))) + # FE parameters + order = 1 # Finite element order + xmax = ymax = 1.0 # Domain size + dom = (0,xmax,0,ymax) # Bounding domain + el_size = (200,200) # Mesh partition size + prop_Γ_N = 0.2 # Γ_N size parameter + prop_Γ_D = 0.2 # Γ_D size parameter + f_Γ_N(x) = (x[1] ≈ xmax && # Γ_N indicator function + ymax/2-ymax*prop_Γ_N/2 - eps() <= x[2] <= ymax/2+ymax*prop_Γ_N/2 + eps()) + f_Γ_D(x) = (x[1] ≈ 0.0 && # Γ_D indicator function + (x[2] <= ymax*prop_Γ_D + eps() || x[2] >= ymax-ymax*prop_Γ_D - eps())) + # FD parameters + γ = 0.1 # HJ eqn time step coeff + γ_reinit = 0.5 # Reinit. eqn time step coeff + max_steps = floor(Int,minimum(el_size)/10) # Max steps for advection + tol = 1/(5order^2)/minimum(el_size) # Reinitialisation tolerance + # Problem parameters + κ = 1 # Diffusivity + g = 1 # Heat flow in + vf = 0.4 # Volume fraction constraint + lsf_func = initial_lsf(4,0.2) # Initial level set function + iter_mod = 10 # VTK Output modulo + path = "./results/tut1_MPI_PETSc/" # Output path + i_am_main(ranks) && mkpath(path) # Create path + # Model + model = CartesianDiscreteModel(ranks,mesh_partition,dom,el_size); + update_labels!(1,model,f_Γ_D,"Gamma_D") + update_labels!(2,model,f_Γ_N,"Gamma_N") + # Triangulation and measures + Ω = Triangulation(model) + Γ_N = BoundaryTriangulation(model,tags="Gamma_N") + dΩ = Measure(Ω,2*order) + dΓ_N = Measure(Γ_N,2*order) + # Spaces + reffe = ReferenceFE(lagrangian,Float64,order) + V = TestFESpace(model,reffe;dirichlet_tags=["Gamma_D"]) + U = TrialFESpace(V,0.0) + V_φ = TestFESpace(model,reffe) + V_reg = TestFESpace(model,reffe;dirichlet_tags=["Gamma_N"]) + U_reg = TrialFESpace(V_reg,0) + # Level set and interpolator + φh = interpolate(lsf_func,V_φ) + interp = SmoothErsatzMaterialInterpolation(η = 2*maximum(get_el_Δ(model))) + I,H,DH,ρ = interp.I,interp.H,interp.DH,interp.ρ + # Weak formulation + a(u,v,φ,dΩ,dΓ_N) = ∫((I ∘ φ)*κ*∇(u)⋅∇(v))dΩ + l(v,φ,dΩ,dΓ_N) = ∫(g*v)dΓ_N + Tm = SparseMatrixCSR{0,PetscScalar,PetscInt} + Tv = Vector{PetscScalar} + solver = PETScLinearSolver() + state_map = AffineFEStateMap( + a,l,U,V,V_φ,U_reg,φh,dΩ,dΓ_N; + assem_U = SparseMatrixAssembler(Tm,Tv,U,V), + assem_adjoint = SparseMatrixAssembler(Tm,Tv,V,U), + assem_deriv = SparseMatrixAssembler(Tm,Tv,U_reg,U_reg), + ls = solver,adjoint_ls = solver + ) + # Objective and constraints + J(u,φ,dΩ,dΓ_N) = ∫((I ∘ φ)*κ*∇(u)⋅∇(u))dΩ + dJ(q,u,φ,dΩ,dΓ_N) = ∫(κ*∇(u)⋅∇(u)*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ + vol_D = sum(∫(1)dΩ) + C(u,φ,dΩ,dΓ_N) = ∫(((ρ ∘ φ) - vf)/vol_D)dΩ + dC(q,u,φ,dΩ,dΓ_N) = ∫(-1/vol_D*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ + pcfs = PDEConstrainedFunctionals(J,[C],state_map, + analytic_dJ=dJ,analytic_dC=[dC]) + # Velocity extension + α = 4*maximum(get_el_Δ(model)) + a_hilb(p,q) =∫(α^2*∇(p)⋅∇(q) + p*q)dΩ + vel_ext = VelocityExtension( + a_hilb, U_reg, V_reg; + assem = SparseMatrixAssembler(Tm,Tv,U_reg,V_reg), + ls = solver + ) + # Finite difference scheme + scheme = FirstOrderStencil(length(el_size),Float64) + stencil = AdvectionStencil(scheme,model,V_φ,tol,max_steps) + # Optimiser + optimiser = AugmentedLagrangian(pcfs,stencil,vel_ext,φh; + γ,γ_reinit,verbose=i_am_main(ranks),constraint_names=[:Vol]) + # Solve + for (it,uh,φh) in optimiser + data = ["φ"=>φh,"H(φ)"=>(H ∘ φh),"|∇(φ)|"=>(norm ∘ ∇(φh)),"uh"=>uh] + iszero(it % iter_mod) && (writevtk(Ω,path*"out$it",cellfields=data);GC.gc()) + write_history(path*"/history.txt",get_history(optimiser);ranks) + end + # Final structure + it = get_history(optimiser).niter; uh = get_state(pcfs) + writevtk(Ω,path*"out$it",cellfields=["φ"=>φh, + "H(φ)"=>(H ∘ φh),"|∇(φ)|"=>(norm ∘ ∇(φh)),"uh"=>uh]) +end + +with_mpi() do distribute + mesh_partition = (2,2) + solver_options = "-pc_type gamg -ksp_type cg -ksp_error_if_not_converged true + -ksp_converged_reason -ksp_rtol 1.0e-12" + GridapPETSc.with(args=split(solver_options)) do + main(mesh_partition,distribute) + end +end \ No newline at end of file diff --git a/scripts/_dev/Added_for_paper/thermal_MPI_PETSc_3D.jl b/scripts/_dev/Added_for_paper/thermal_MPI_PETSc_3D.jl new file mode 100644 index 00000000..90addf1d --- /dev/null +++ b/scripts/_dev/Added_for_paper/thermal_MPI_PETSc_3D.jl @@ -0,0 +1,106 @@ +using LevelSetTopOpt, Gridap, GridapDistributed, GridapPETSc, PartitionedArrays, + SparseMatricesCSR + +function main(mesh_partition,distribute) + ranks = distribute(LinearIndices((prod(mesh_partition),))) + # FE parameters + order = 1 # Finite element order + xmax = ymax = 1.0 # Domain size + dom = (0,xmax,0,ymax) # Bounding domain + el_size = (150,150,150) # Mesh partition size + prop_Γ_N = 0.2 # Γ_N size parameter + prop_Γ_D = 0.2 # Γ_D size parameter + f_Γ_N(x) = (x[1] ≈ xmax) && # Γ_N indicator function + (ymax/2-ymax*prop_Γ_N/2 - eps() <= x[2] <= ymax/2+ymax*prop_Γ_N/2 + eps())&& + (zmax/2-zmax*prop_Γ_N/2 - eps() <= x[3] <= zmax/2+zmax*prop_Γ_N/2 + eps()) + f_Γ_D(x) = (x[1] ≈ 0.0) && # Γ_D indicator function + (x[2] <= ymax*prop_Γ_D + eps() || x[2] >= ymax-ymax*prop_Γ_D - eps()) && + (x[3] <= zmax*prop_Γ_D + eps() || x[3] >= zmax-zmax*prop_Γ_D - eps()) + # FD parameters + γ = 0.1 # HJ eqn time step coeff + γ_reinit = 0.5 # Reinit. eqn time step coeff + max_steps = floor(Int,minimum(el_size)/10) # Max steps for advection + tol = 1/(5order^2)/minimum(el_size) # Reinitialisation tolerance + # Problem parameters + κ = 1 # Diffusivity + g = 1 # Heat flow in + vf = 0.4 # Volume fraction constraint + lsf_func = initial_lsf(4,0.2) # Initial level set function + iter_mod = 10 # VTK Output modulo + path = "./results/tut1_MPI_PETSc_3D/" # Output path + i_am_main(ranks) && mkpath(path) # Create path + # Model + model = CartesianDiscreteModel(ranks,mesh_partition,dom,el_size); + update_labels!(1,model,f_Γ_D,"Gamma_D") + update_labels!(2,model,f_Γ_N,"Gamma_N") + # Triangulation and measures + Ω = Triangulation(model) + Γ_N = BoundaryTriangulation(model,tags="Gamma_N") + dΩ = Measure(Ω,2*order) + dΓ_N = Measure(Γ_N,2*order) + # Spaces + reffe = ReferenceFE(lagrangian,Float64,order) + V = TestFESpace(model,reffe;dirichlet_tags=["Gamma_D"]) + U = TrialFESpace(V,0.0) + V_φ = TestFESpace(model,reffe) + V_reg = TestFESpace(model,reffe;dirichlet_tags=["Gamma_N"]) + U_reg = TrialFESpace(V_reg,0) + # Level set and interpolator + φh = interpolate(lsf_func,V_φ) + interp = SmoothErsatzMaterialInterpolation(η = 2*maximum(get_el_Δ(model))) + I,H,DH,ρ = interp.I,interp.H,interp.DH,interp.ρ + # Weak formulation + a(u,v,φ,dΩ,dΓ_N) = ∫((I ∘ φ)*κ*∇(u)⋅∇(v))dΩ + l(v,φ,dΩ,dΓ_N) = ∫(g*v)dΓ_N + Tm = SparseMatrixCSR{0,PetscScalar,PetscInt} + Tv = Vector{PetscScalar} + solver = PETScLinearSolver() + state_map = AffineFEStateMap( + a,l,U,V,V_φ,U_reg,φh,dΩ,dΓ_N; + assem_U = SparseMatrixAssembler(Tm,Tv,U,V), + assem_adjoint = SparseMatrixAssembler(Tm,Tv,V,U), + assem_deriv = SparseMatrixAssembler(Tm,Tv,U_reg,U_reg), + ls = solver,adjoint_ls = solver + ) + # Objective and constraints + J(u,φ,dΩ,dΓ_N) = ∫((I ∘ φ)*κ*∇(u)⋅∇(u))dΩ + dJ(q,u,φ,dΩ,dΓ_N) = ∫(κ*∇(u)⋅∇(u)*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ + vol_D = sum(∫(1)dΩ) + C(u,φ,dΩ,dΓ_N) = ∫(((ρ ∘ φ) - vf)/vol_D)dΩ + dC(q,u,φ,dΩ,dΓ_N) = ∫(-1/vol_D*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ + pcfs = PDEConstrainedFunctionals(J,[C],state_map, + analytic_dJ=dJ,analytic_dC=[dC]) + # Velocity extension + α = 4*maximum(get_el_Δ(model)) + a_hilb(p,q) =∫(α^2*∇(p)⋅∇(q) + p*q)dΩ + vel_ext = VelocityExtension( + a_hilb, U_reg, V_reg; + assem = SparseMatrixAssembler(Tm,Tv,U_reg,V_reg), + ls = solver + ) + # Finite difference scheme + scheme = FirstOrderStencil(length(el_size),Float64) + stencil = AdvectionStencil(scheme,model,V_φ,tol,max_steps) + # Optimiser + optimiser = AugmentedLagrangian(pcfs,stencil,vel_ext,φh; + γ,γ_reinit,verbose=i_am_main(ranks),constraint_names=[:Vol]) + # Solve + for (it,uh,φh) in optimiser + data = ["φ"=>φh,"H(φ)"=>(H ∘ φh),"|∇(φ)|"=>(norm ∘ ∇(φh)),"uh"=>uh] + iszero(it % iter_mod) && (writevtk(Ω,path*"out$it",cellfields=data);GC.gc()) + write_history(path*"/history.txt",get_history(optimiser);ranks) + end + # Final structure + it = get_history(optimiser).niter; uh = get_state(pcfs) + writevtk(Ω,path*"out$it",cellfields=["φ"=>φh, + "H(φ)"=>(H ∘ φh),"|∇(φ)|"=>(norm ∘ ∇(φh)),"uh"=>uh]) +end + +with_mpi() do distribute + mesh_partition = (5,5,5) + solver_options = "-pc_type gamg -ksp_type cg -ksp_error_if_not_converged true + -ksp_converged_reason -ksp_rtol 1.0e-12" + GridapPETSc.with(args=split(solver_options)) do + main(mesh_partition,distribute) + end +end \ No newline at end of file diff --git a/scripts/_dev/Added_for_paper/thermal_PETSc.jl b/scripts/_dev/Added_for_paper/thermal_PETSc.jl new file mode 100644 index 00000000..9b1383cf --- /dev/null +++ b/scripts/_dev/Added_for_paper/thermal_PETSc.jl @@ -0,0 +1,99 @@ +using LevelSetTopOpt, Gridap, GridapPETSc, SparseMatricesCSR + +function main() + # FE parameters + order = 1 # Finite element order + xmax = ymax = 1.0 # Domain size + dom = (0,xmax,0,ymax) # Bounding domain + el_size = (200,200) # Mesh partition size + prop_Γ_N = 0.2 # Γ_N size parameter + prop_Γ_D = 0.2 # Γ_D size parameter + f_Γ_N(x) = (x[1] ≈ xmax && # Γ_N indicator function + ymax/2-ymax*prop_Γ_N/2 - eps() <= x[2] <= ymax/2+ymax*prop_Γ_N/2 + eps()) + f_Γ_D(x) = (x[1] ≈ 0.0 && # Γ_D indicator function + (x[2] <= ymax*prop_Γ_D + eps() || x[2] >= ymax-ymax*prop_Γ_D - eps())) + # FD parameters + γ = 0.1 # HJ eqn time step coeff + γ_reinit = 0.5 # Reinit. eqn time step coeff + max_steps = floor(Int,minimum(el_size)/10) # Max steps for advection + tol = 1/(5order^2)/minimum(el_size) # Reinitialisation tolerance + # Problem parameters + κ = 1 # Diffusivity + g = 1 # Heat flow in + vf = 0.4 # Volume fraction constraint + lsf_func = initial_lsf(4,0.2) # Initial level set function + iter_mod = 10 # VTK Output modulo + path = "./results/tut1_PETSc/" # Output path + mkpath(path) # Create path + # Model + model = CartesianDiscreteModel(dom,el_size); + update_labels!(1,model,f_Γ_D,"Gamma_D") + update_labels!(2,model,f_Γ_N,"Gamma_N") + # Triangulation and measures + Ω = Triangulation(model) + Γ_N = BoundaryTriangulation(model,tags="Gamma_N") + dΩ = Measure(Ω,2*order) + dΓ_N = Measure(Γ_N,2*order) + # Spaces + reffe = ReferenceFE(lagrangian,Float64,order) + V = TestFESpace(model,reffe;dirichlet_tags=["Gamma_D"]) + U = TrialFESpace(V,0.0) + V_φ = TestFESpace(model,reffe) + V_reg = TestFESpace(model,reffe;dirichlet_tags=["Gamma_N"]) + U_reg = TrialFESpace(V_reg,0) + # Level set and interpolator + φh = interpolate(lsf_func,V_φ) + interp = SmoothErsatzMaterialInterpolation(η = 2*maximum(get_el_Δ(model))) + I,H,DH,ρ = interp.I,interp.H,interp.DH,interp.ρ + # Weak formulation + a(u,v,φ,dΩ,dΓ_N) = ∫((I ∘ φ)*κ*∇(u)⋅∇(v))dΩ + l(v,φ,dΩ,dΓ_N) = ∫(g*v)dΓ_N + Tm = SparseMatrixCSR{0,PetscScalar,PetscInt} + Tv = Vector{PetscScalar} + solver = PETScLinearSolver() + state_map = AffineFEStateMap( + a,l,U,V,V_φ,U_reg,φh,dΩ,dΓ_N; + assem_U = SparseMatrixAssembler(Tm,Tv,U,V), + assem_adjoint = SparseMatrixAssembler(Tm,Tv,V,U), + assem_deriv = SparseMatrixAssembler(Tm,Tv,U_reg,U_reg), + ls = solver,adjoint_ls = solver + ) + # Objective and constraints + J(u,φ,dΩ,dΓ_N) = ∫((I ∘ φ)*κ*∇(u)⋅∇(u))dΩ + dJ(q,u,φ,dΩ,dΓ_N) = ∫(κ*∇(u)⋅∇(u)*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ + vol_D = sum(∫(1)dΩ) + C(u,φ,dΩ,dΓ_N) = ∫(((ρ ∘ φ) - vf)/vol_D)dΩ + dC(q,u,φ,dΩ,dΓ_N) = ∫(-1/vol_D*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ + pcfs = PDEConstrainedFunctionals(J,[C],state_map, + analytic_dJ=dJ,analytic_dC=[dC]) + # Velocity extension + α = 4*maximum(get_el_Δ(model)) + a_hilb(p,q) =∫(α^2*∇(p)⋅∇(q) + p*q)dΩ + vel_ext = VelocityExtension( + a_hilb, U_reg, V_reg; + assem = SparseMatrixAssembler(Tm,Tv,U_reg,V_reg), + ls = solver + ) + # Finite difference scheme + scheme = FirstOrderStencil(length(el_size),Float64) + stencil = AdvectionStencil(scheme,model,V_φ,tol,max_steps) + # Optimiser + optimiser = AugmentedLagrangian(pcfs,stencil,vel_ext,φh; + γ,γ_reinit,verbose=true,constraint_names=[:Vol]) + # Solve + for (it,uh,φh) in optimiser + data = ["φ"=>φh,"H(φ)"=>(H ∘ φh),"|∇(φ)|"=>(norm ∘ ∇(φh)),"uh"=>uh] + iszero(it % iter_mod) && (writevtk(Ω,path*"out$it",cellfields=data);GC.gc()) + write_history(path*"/history.txt",get_history(optimiser)) + end + # Final structure + it = get_history(optimiser).niter; uh = get_state(pcfs) + writevtk(Ω,path*"out$it",cellfields=["φ"=>φh, + "H(φ)"=>(H ∘ φh),"|∇(φ)|"=>(norm ∘ ∇(φh)),"uh"=>uh]) +end + +solver_options = "-pc_type gamg -ksp_type cg -ksp_error_if_not_converged true + -ksp_converged_reason -ksp_rtol 1.0e-12" +GridapPETSc.with(args=split(solver_options)) do + main() +end \ No newline at end of file diff --git a/scripts/_dev/Added_for_paper/thermal_serial.jl b/scripts/_dev/Added_for_paper/thermal_serial.jl new file mode 100644 index 00000000..115e1b5e --- /dev/null +++ b/scripts/_dev/Added_for_paper/thermal_serial.jl @@ -0,0 +1,82 @@ +using LevelSetTopOpt, Gridap + +function main() + # FE parameters + order = 1 # Finite element order + xmax = ymax = 1.0 # Domain size + dom = (0,xmax,0,ymax) # Bounding domain + el_size = (200,200) # Mesh partition size + prop_Γ_N = 0.2 # Γ_N size parameter + prop_Γ_D = 0.2 # Γ_D size parameter + f_Γ_N(x) = (x[1] ≈ xmax && # Γ_N indicator function + ymax/2-ymax*prop_Γ_N/2 - eps() <= x[2] <= ymax/2+ymax*prop_Γ_N/2 + eps()) + f_Γ_D(x) = (x[1] ≈ 0.0 && # Γ_D indicator function + (x[2] <= ymax*prop_Γ_D + eps() || x[2] >= ymax-ymax*prop_Γ_D - eps())) + # FD parameters + γ = 0.1 # HJ eqn time step coeff + γ_reinit = 0.5 # Reinit. eqn time step coeff + max_steps = floor(Int,minimum(el_size)/10) # Max steps for advection + tol = 1/(5order^2)/minimum(el_size) # Reinitialisation tolerance + # Problem parameters + κ = 1 # Diffusivity + g = 1 # Heat flow in + vf = 0.4 # Volume fraction constraint + lsf_func = initial_lsf(4,0.2) # Initial level set function + iter_mod = 10 # VTK Output modulo + path = "./results/tut1/" # Output path + mkpath(path) # Create path + # Model + model = CartesianDiscreteModel(dom,el_size); + update_labels!(1,model,f_Γ_D,"Gamma_D") + update_labels!(2,model,f_Γ_N,"Gamma_N") + # Triangulation and measures + Ω = Triangulation(model) + Γ_N = BoundaryTriangulation(model,tags="Gamma_N") + dΩ = Measure(Ω,2*order) + dΓ_N = Measure(Γ_N,2*order) + # Spaces + reffe = ReferenceFE(lagrangian,Float64,order) + V = TestFESpace(model,reffe;dirichlet_tags=["Gamma_D"]) + U = TrialFESpace(V,0.0) + V_φ = TestFESpace(model,reffe) + V_reg = TestFESpace(model,reffe;dirichlet_tags=["Gamma_N"]) + U_reg = TrialFESpace(V_reg,0) + # Level set and interpolator + φh = interpolate(lsf_func,V_φ) + interp = SmoothErsatzMaterialInterpolation(η = 2*maximum(get_el_Δ(model))) + I,H,DH,ρ = interp.I,interp.H,interp.DH,interp.ρ + # Weak formulation + a(u,v,φ,dΩ,dΓ_N) = ∫((I ∘ φ)*κ*∇(u)⋅∇(v))dΩ + l(v,φ,dΩ,dΓ_N) = ∫(g*v)dΓ_N + state_map = AffineFEStateMap(a,l,U,V,V_φ,U_reg,φh,dΩ,dΓ_N) + # Objective and constraints + J(u,φ,dΩ,dΓ_N) = ∫((I ∘ φ)*κ*∇(u)⋅∇(u))dΩ + dJ(q,u,φ,dΩ,dΓ_N) = ∫(κ*∇(u)⋅∇(u)*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ + vol_D = sum(∫(1)dΩ) + C(u,φ,dΩ,dΓ_N) = ∫(((ρ ∘ φ) - vf)/vol_D)dΩ + dC(q,u,φ,dΩ,dΓ_N) = ∫(-1/vol_D*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ + pcfs = PDEConstrainedFunctionals(J,[C],state_map, + analytic_dJ=dJ,analytic_dC=[dC]) + # Velocity extension + α = 4*maximum(get_el_Δ(model)) + a_hilb(p,q) =∫(α^2*∇(p)⋅∇(q) + p*q)dΩ + vel_ext = VelocityExtension(a_hilb,U_reg,V_reg) + # Finite difference scheme + scheme = FirstOrderStencil(length(el_size),Float64) + stencil = AdvectionStencil(scheme,model,V_φ,tol,max_steps) + # Optimiser + optimiser = AugmentedLagrangian(pcfs,stencil,vel_ext,φh; + γ,γ_reinit,verbose=true,constraint_names=[:Vol]) + # Solve + for (it,uh,φh) in optimiser + data = ["φ"=>φh,"H(φ)"=>(H ∘ φh),"|∇(φ)|"=>(norm ∘ ∇(φh)),"uh"=>uh] + iszero(it % iter_mod) && (writevtk(Ω,path*"out$it",cellfields=data);GC.gc()) + write_history(path*"/history.txt",get_history(optimiser)) + end + # Final structure + it = get_history(optimiser).niter; uh = get_state(pcfs) + writevtk(Ω,path*"out$it",cellfields=["φ"=>φh, + "H(φ)"=>(H ∘ φh),"|∇(φ)|"=>(norm ∘ ∇(φh)),"uh"=>uh]) +end + +main() \ No newline at end of file diff --git a/scripts/_dev/OptimiserHistoryTests.jl b/scripts/_dev/OptimiserHistoryTests.jl index 376bf1f8..4e4b64a8 100644 --- a/scripts/_dev/OptimiserHistoryTests.jl +++ b/scripts/_dev/OptimiserHistoryTests.jl @@ -1,7 +1,7 @@ -using LSTO_Distributed +using LevelSetTopOpt -h = LSTO_Distributed.OptimiserHistory(Float64,[:L,:J,:C1,:C2,:C3],Dict(:C => [:C1,:C2,:C3]),100,true) +h = LevelSetTopOpt.OptimiserHistory(Float64,[:L,:J,:C1,:C2,:C3],Dict(:C => [:C1,:C2,:C3]),100,true) push!(h,(1.0,2.0,3.0,4.0,5.0)) diff --git a/scripts/_dev/benchmark_tests/MPI_benchmark_test_2D_themal.jl b/scripts/_dev/benchmark_tests/MPI_benchmark_test_2D_themal.jl index 864ef21a..e4a309ad 100644 --- a/scripts/_dev/benchmark_tests/MPI_benchmark_test_2D_themal.jl +++ b/scripts/_dev/benchmark_tests/MPI_benchmark_test_2D_themal.jl @@ -1,4 +1,4 @@ -using Gridap, GridapDistributed, GridapPETSc, PartitionedArrays, LSTO_Distributed +using Gridap, GridapDistributed, GridapPETSc, PartitionedArrays, LevelSetTopOpt """ (MPI) Minimum thermal compliance with Lagrangian method in 2D. @@ -13,22 +13,22 @@ function main(mesh_partition,el_size,ranks) ## Parameters order = 1 xmax=ymax=1.0 - prop_Γ_N = 0.4 + prop_Γ_N = 0.2 prop_Γ_D = 0.2 dom = (0,xmax,0,ymax) γ = 0.1 γ_reinit = 0.5 max_steps = floor(Int,minimum(el_size)/10) - tol = 1/(order^2*10)*prod(inv,minimum(el_size)) + tol = 1/(order^2*10)/minimum(el_size) D = 1 η_coeff = 2 α_coeff = 4 ## FE Setup model = CartesianDiscreteModel(ranks,mesh_partition,dom,el_size); - Δ = get_Δ(model) + el_Δ = get_el_Δ(model) f_Γ_D(x) = (x[1] ≈ 0.0) && ((x[2] <= ymax*prop_Γ_D + eps()) || (x[2] >= ymax-ymax*prop_Γ_D - eps())) - f_Γ_N(x) = (x[1] ≈ xmax) && (ymax/2-ymax*prop_Γ_N/4 - eps() <= x[2] <= ymax/2+ymax*prop_Γ_N/4 + eps()) + f_Γ_N(x) = (x[1] ≈ xmax) && (ymax/2-ymax*prop_Γ_N/2 - eps() <= x[2] <= ymax/2+ymax*prop_Γ_N/2 + eps()) update_labels!(1,model,f_Γ_D,"Gamma_D") update_labels!(2,model,f_Γ_N,"Gamma_N") @@ -47,10 +47,10 @@ function main(mesh_partition,el_size,ranks) U_reg = TrialFESpace(V_reg,0) ## Create FE functions - φh = interpolate(gen_lsf(4,0.2),V_φ) + φh = interpolate(initial_lsf(4,0.2),V_φ) ## Interpolation and weak form - interp = SmoothErsatzMaterialInterpolation(η = η_coeff*maximum(Δ)) + interp = SmoothErsatzMaterialInterpolation(η = η_coeff*maximum(el_Δ)) I,H,DH,ρ = interp.I,interp.H,interp.DH,interp.ρ a(u,v,φ,dΩ,dΓ_N) = ∫((I ∘ φ)*D*∇(u)⋅∇(v))dΩ @@ -59,7 +59,7 @@ function main(mesh_partition,el_size,ranks) ## Optimisation functionals ξ = 0.3 J(u,φ,dΩ,dΓ_N) = ∫((I ∘ φ)*D*∇(u)⋅∇(u) + ξ*(ρ ∘ φ))dΩ - dJ(q,u,φ,dΩ,dΓ_N) = ∫((ξ-D*∇(u)⋅∇(u))*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ; + dJ(q,u,φ,dΩ,dΓ_N) = ∫((-ξ+D*∇(u)⋅∇(u))*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ; ## Finite difference solver and level set function stencil = AdvectionStencil(FirstOrderStencil(2,Float64),model,V_φ,tol,max_steps) @@ -70,7 +70,7 @@ function main(mesh_partition,el_size,ranks) pcfs = PDEConstrainedFunctionals(J,state_map,analytic_dJ=dJ) ## Hilbertian extension-regularisation problems - α = α_coeff*maximum(Δ) + α = α_coeff*maximum(el_Δ) a_hilb(p,q) =∫(α^2*∇(p)⋅∇(q) + p*q)dΩ; vel_ext = VelocityExtension(a_hilb,U_reg,V_reg) @@ -88,8 +88,8 @@ with_debug() do distribute ## Benchmark forward problem bfwd = benchmark_forward_problem(opt.problem.state_map, opt.φ0, ranks) ## Benchmark advection - v = get_free_dof_values(interpolate(FEFunction(LSTO_Distributed.get_deriv_space(opt.problem.state_map), - opt.problem.dJ),LSTO_Distributed.get_aux_space(opt.problem.state_map))) + v = get_free_dof_values(interpolate(FEFunction(LevelSetTopOpt.get_deriv_space(opt.problem.state_map), + opt.problem.dJ),LevelSetTopOpt.get_aux_space(opt.problem.state_map))) badv = benchmark_advection(opt.stencil, get_free_dof_values(opt.φ0), v, 0.1, ranks) ## Benchmark reinitialisation brinit = benchmark_reinitialisation(opt.stencil, get_free_dof_values(opt.φ0), 0.1, ranks) diff --git a/scripts/_dev/benchmark_tests/MPI_benchmark_test_3D_themal.jl b/scripts/_dev/benchmark_tests/MPI_benchmark_test_3D_themal.jl index 442f3063..37b149c8 100644 --- a/scripts/_dev/benchmark_tests/MPI_benchmark_test_3D_themal.jl +++ b/scripts/_dev/benchmark_tests/MPI_benchmark_test_3D_themal.jl @@ -1,5 +1,5 @@ using Gridap, Gridap.MultiField, GridapDistributed, GridapPETSc, GridapSolvers, - PartitionedArrays, LSTO_Distributed, SparseMatricesCSR + PartitionedArrays, LevelSetTopOpt, SparseMatricesCSR """ (MPI) Minimum thermal compliance with Lagrangian method in 3D. @@ -16,13 +16,13 @@ function main(mesh_partition,distribute,el_size) ## Parameters order = 1 xmax=ymax=zmax=1.0 - prop_Γ_N = 0.4 + prop_Γ_N = 0.2 prop_Γ_D = 0.2 dom = (0,xmax,0,ymax,0,zmax) γ = 0.1 γ_reinit = 0.5 max_steps = floor(Int,minimum(el_size)/3) - tol = 1/(order^2*10)*prod(inv,minimum(el_size)) + tol = 1/(order^2*10)/minimum(el_size) D = 1 η_coeff = 2 α_coeff = 4 @@ -30,11 +30,11 @@ function main(mesh_partition,distribute,el_size) ## FE Setup model = CartesianDiscreteModel(ranks,mesh_partition,dom,el_size); - Δ = get_Δ(model) + el_Δ = get_el_Δ(model) f_Γ_D(x) = (x[1] ≈ 0.0) && (x[2] <= ymax*prop_Γ_D + eps() || x[2] >= ymax-ymax*prop_Γ_D - eps()) && (x[3] <= zmax*prop_Γ_D + eps() || x[3] >= zmax-zmax*prop_Γ_D - eps()) - f_Γ_N(x) = (x[1] ≈ xmax) && (ymax/2-ymax*prop_Γ_N/4 - eps() <= x[2] <= ymax/2+ymax*prop_Γ_N/4 + eps()) && - (zmax/2-zmax*prop_Γ_N/4 - eps() <= x[3] <= zmax/2+zmax*prop_Γ_N/4 + eps()) + f_Γ_N(x) = (x[1] ≈ xmax) && (ymax/2-ymax*prop_Γ_N/2 - eps() <= x[2] <= ymax/2+ymax*prop_Γ_N/2 + eps()) && + (zmax/2-zmax*prop_Γ_N/2 - eps() <= x[3] <= zmax/2+zmax*prop_Γ_N/2 + eps()) update_labels!(1,model,f_Γ_D,"Gamma_D") update_labels!(2,model,f_Γ_N,"Gamma_N") @@ -53,10 +53,10 @@ function main(mesh_partition,distribute,el_size) U_reg = TrialFESpace(V_reg,0) ## Create FE functions - φh = interpolate(gen_lsf(4,0.2),V_φ); + φh = interpolate(initial_lsf(4,0.2),V_φ); ## Interpolation and weak form - interp = SmoothErsatzMaterialInterpolation(η = η_coeff*maximum(Δ)) + interp = SmoothErsatzMaterialInterpolation(η = η_coeff*maximum(el_Δ)) I,H,DH,ρ = interp.I,interp.H,interp.DH,interp.ρ a(u,v,φ,dΩ,dΓ_N) = ∫((I ∘ φ)*D*∇(u)⋅∇(v))dΩ @@ -86,7 +86,7 @@ function main(mesh_partition,distribute,el_size) pcfs = PDEConstrainedFunctionals(J,state_map,analytic_dJ=dJ) ## Hilbertian extension-regularisation problems - α = α_coeff*maximum(Δ) + α = α_coeff*maximum(el_Δ) a_hilb(p,q) = ∫(α^2*∇(p)⋅∇(q) + p*q)dΩ; vel_ext = VelocityExtension( a_hilb, U_reg, V_reg; @@ -112,8 +112,8 @@ with_mpi() do distribute ## Benchmark forward problem bfwd = benchmark_forward_problem(opt.problem.state_map, opt.φ0, nothing) ## Benchmark advection - v = get_free_dof_values(interpolate(FEFunction(LSTO_Distributed.get_deriv_space(opt.problem.state_map), - opt.problem.dJ),LSTO_Distributed.get_aux_space(opt.problem.state_map))) + v = get_free_dof_values(interpolate(FEFunction(LevelSetTopOpt.get_deriv_space(opt.problem.state_map), + opt.problem.dJ),LevelSetTopOpt.get_aux_space(opt.problem.state_map))) badv = benchmark_advection(opt.stencil, get_free_dof_values(opt.φ0), v, 0.1, nothing) ## Benchmark reinitialisation brinit = benchmark_reinitialisation(opt.stencil, get_free_dof_values(opt.φ0), 0.1, nothing) diff --git a/scripts/_dev/benchmark_tests/serial_benchmark_test.jl b/scripts/_dev/benchmark_tests/serial_benchmark_test.jl index 8d5c673e..8de90168 100644 --- a/scripts/_dev/benchmark_tests/serial_benchmark_test.jl +++ b/scripts/_dev/benchmark_tests/serial_benchmark_test.jl @@ -1,28 +1,28 @@ -using Gridap, GridapDistributed, GridapPETSc, PartitionedArrays, LSTO_Distributed +using Gridap, GridapDistributed, GridapPETSc, PartitionedArrays, LevelSetTopOpt function main() ## Parameters order = 1 xmax = ymax = 1.0 - prop_Γ_N = 0.4 + prop_Γ_N = 0.2 prop_Γ_D = 0.2 dom = (0,xmax,0,ymax) el_size = (200,200) γ = 0.1 γ_reinit = 0.5 max_steps = floor(Int,minimum(el_size)/10) - tol = 1/(order^2*10)*prod(inv,minimum(el_size)) # <- We can do better than this I think + tol = 1/(order^2*10)/minimum(el_size) # <- We can do better than this I think D = 1 η_coeff = 2 α_coeff = 4 ## FE Setup model = CartesianDiscreteModel(dom,el_size); - Δ = get_Δ(model) + el_Δ = get_el_Δ(model) f_Γ_D(x) = (x[1] ≈ 0.0 && (x[2] <= ymax*prop_Γ_D + eps() || x[2] >= ymax-ymax*prop_Γ_D - eps())) ? true : false - f_Γ_N(x) = (x[1] ≈ xmax && ymax/2-ymax*prop_Γ_N/4 - eps() <= x[2] <= - ymax/2+ymax*prop_Γ_N/4 + eps()) ? true : false + f_Γ_N(x) = (x[1] ≈ xmax && ymax/2-ymax*prop_Γ_N/2 - eps() <= x[2] <= + ymax/2+ymax*prop_Γ_N/2 + eps()) ? true : false update_labels!(1,model,f_Γ_D,"Gamma_D") update_labels!(2,model,f_Γ_N,"Gamma_N") @@ -41,10 +41,10 @@ function main() U_reg = TrialFESpace(V_reg,0) ## Create FE functions - φh = interpolate(gen_lsf(4,0.2),V_φ) + φh = interpolate(initial_lsf(4,0.2),V_φ) ## Interpolation and weak form - interp = SmoothErsatzMaterialInterpolation(η = η_coeff*maximum(Δ)) + interp = SmoothErsatzMaterialInterpolation(η = η_coeff*maximum(el_Δ)) I,H,DH,ρ = interp.I,interp.H,interp.DH,interp.ρ a(u,v,φ,dΩ,dΓ_N) = ∫((I ∘ φ)*D*∇(u)⋅∇(v))dΩ @@ -64,7 +64,7 @@ function main() pcfs = PDEConstrainedFunctionals(J,state_map,analytic_dJ=dJ) ## Hilbertian extension-regularisation problems - α = α_coeff*maximum(Δ) + α = α_coeff*maximum(el_Δ) a_hilb(p,q) =∫(α^2*∇(p)⋅∇(q) + p*q)dΩ vel_ext = VelocityExtension(a_hilb,U_reg,V_reg) @@ -79,8 +79,8 @@ function main_benchmark() ## Benchmark forward problem bfwd = benchmark_forward_problem(opt.problem.state_map, opt.φ0, nothing) ## Benchmark advection - v = get_free_dof_values(interpolate(FEFunction(LSTO_Distributed.get_deriv_space(opt.problem.state_map), - opt.problem.dJ),LSTO_Distributed.get_aux_space(opt.problem.state_map))) + v = get_free_dof_values(interpolate(FEFunction(LevelSetTopOpt.get_deriv_space(opt.problem.state_map), + opt.problem.dJ),LevelSetTopOpt.get_aux_space(opt.problem.state_map))) badv = benchmark_advection(opt.stencil, get_free_dof_values(opt.φ0), v, 0.1, nothing) ## Benchmark reinitialisation brinit = benchmark_reinitialisation(opt.stencil, get_free_dof_values(opt.φ0), 0.1, nothing) diff --git a/scripts/_dev/fileio_testing.jl b/scripts/_dev/fileio_testing.jl index c9c63185..4562d157 100644 --- a/scripts/_dev/fileio_testing.jl +++ b/scripts/_dev/fileio_testing.jl @@ -1,6 +1,6 @@ -using Gridap, GridapDistributed, GridapPETSc, PartitionedArrays, LSTO_Distributed +using Gridap, GridapDistributed, GridapPETSc, PartitionedArrays, LevelSetTopOpt -using LSTO_Distributed: save, load, load!, psave, pload, pload! +using LevelSetTopOpt: save, load, load!, psave, pload, pload! with_debug() do distribute mesh_partition = (2,2) diff --git a/scripts/_dev/full_piezo_script.jl b/scripts/_dev/full_piezo_script.jl index 429a3f9a..7cb7c149 100644 --- a/scripts/_dev/full_piezo_script.jl +++ b/scripts/_dev/full_piezo_script.jl @@ -5,7 +5,7 @@ using GridapPETSc: PetscScalar, PetscInt, PETSC, @check_error_code using PartitionedArrays using SparseMatricesCSR using ChainRulesCore -using LSTO_Distributed +using LevelSetTopOpt using GridapSolvers using Gridap.MultiField @@ -20,8 +20,8 @@ function main(mesh_partition,distribute,el_size) γ = 0.05 γ_reinit = 0.5 max_steps = floor(Int,minimum(el_size)/3) - tol = 1/(2order^2)*prod(inv,minimum(el_size)) - C = isotropic_3d(1.,0.3) + tol = 1/(2order^2)/minimum(el_size) + C = isotropic_elast_tensor(3,1.,0.3) η_coeff = 2 α_coeff = 4 vf = 0.5 @@ -29,7 +29,7 @@ function main(mesh_partition,distribute,el_size) ## FE Setup model = CartesianDiscreteModel(ranks,mesh_partition,dom,el_size,isperiodic=(true,true,true)) - Δ = get_Δ(model) + el_Δ = get_el_Δ(model) f_Γ_D(x) = iszero(x) update_labels!(1,model,f_Γ_D,"origin") @@ -55,7 +55,7 @@ function main(mesh_partition,distribute,el_size) φh = interpolate(lsf_fn,V_φ) ## Interpolation and weak form - interp = SmoothErsatzMaterialInterpolation(η = η_coeff*maximum(Δ)) + interp = SmoothErsatzMaterialInterpolation(η = η_coeff*maximum(el_Δ)) I,H,DH,ρ = interp.I,interp.H,interp.DH,interp.ρ ## Material tensors @@ -116,7 +116,7 @@ function main(mesh_partition,distribute,el_size) J, C, dJ, dC = Gridap.evaluate!(pcfs,φh) # ## Hilbertian extension-regularisation problems - # α = α_coeff*maximum(Δ) + # α = α_coeff*maximum(el_Δ) # a_hilb(p,q) = ∫(α^2*∇(p)⋅∇(q) + p*q)dΩ; # vel_ext = VelocityExtension( # a_hilb,U_reg,V_reg, @@ -128,11 +128,11 @@ function main(mesh_partition,distribute,el_size) # make_dir(path;ranks=ranks) # optimiser = AugmentedLagrangian(pcfs,stencil,vel_ext,φh;γ,γ_reinit,verbose=i_am_main(ranks)) # for (it, uh, φh) in optimiser - # write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi))|"=>(norm ∘ ∇(φh)),"uh"=>uh]) + # write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh)),"uh"=>uh]) # write_history(path*"/history.txt",optimiser.history;ranks=ranks) # end # it = optimiser.history.niter; uh = get_state(optimiser.problem) - # write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi))|"=>(norm ∘ ∇(φh)),"uh"=>uh];iter_mod=1) + # write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh)),"uh"=>uh];iter_mod=1) end; with_mpi() do distribute diff --git a/scripts/_dev/inv_hom_block_assem_testing.jl b/scripts/_dev/inv_hom_block_assem_testing.jl index dec716f6..acd4c897 100644 --- a/scripts/_dev/inv_hom_block_assem_testing.jl +++ b/scripts/_dev/inv_hom_block_assem_testing.jl @@ -1,5 +1,5 @@ using Gridap, Gridap.MultiField, GridapDistributed, GridapPETSc, GridapSolvers, - PartitionedArrays, LSTO_Distributed, SparseMatricesCSR + PartitionedArrays, LevelSetTopOpt, SparseMatricesCSR nothing @@ -17,8 +17,8 @@ el_size = (10,10); γ = 0.05; γ_reinit = 0.5; max_steps = floor(Int,minimum(el_size)/10) -tol = 1/(order^2*10)*prod(inv,minimum(el_size)) -C = isotropic_2d(1.,0.3); +tol = 1/(order^2*10)/minimum(el_size) +C = isotropic_elast_tensor(2,1.,0.3); η_coeff = 2; α_coeff = 4; path = dirname(dirname(@__DIR__))*"/results/block_testing" @@ -26,7 +26,7 @@ path = dirname(dirname(@__DIR__))*"/results/block_testing" ## FE Setup model = CartesianDiscreteModel(ranks,(2,3),dom,el_size,isperiodic=(true,true)); # model = CartesianDiscreteModel(dom,el_size,isperiodic=(true,true)); -Δ = get_Δ(model) +el_Δ = get_el_Δ(model) f_Γ_D(x) = iszero(x) update_labels!(1,model,f_Γ_D,"origin") @@ -47,12 +47,12 @@ V_reg = V_φ = TestFESpace(model,reffe_scalar) U_reg = TrialFESpace(V_reg) ## Create FE functions -lsf_fn = x->max(gen_lsf(2,0.4)(x),gen_lsf(2,0.4;b=VectorValue(0,0.5))(x)); +lsf_fn = x->max(initial_lsf(2,0.4)(x),initial_lsf(2,0.4;b=VectorValue(0,0.5))(x)); φh = interpolate(-1,V_φ); φ = get_free_dof_values(φh) ## Interpolation and weak form -interp = SmoothErsatzMaterialInterpolation(η = η_coeff*maximum(Δ)) +interp = SmoothErsatzMaterialInterpolation(η = η_coeff*maximum(el_Δ)) I,H,DH,ρ = interp.I,interp.H,interp.DH,interp.ρ εᴹ = (TensorValue(1.,0.,0.,0.), # ϵᵢⱼ⁽¹¹⁾≡ϵᵢⱼ⁽¹⁾ @@ -74,7 +74,7 @@ Vol = (u,φ,dΩ) -> ∫(((ρ ∘ φ) - 0.5)/vol_D)dΩ; dVol = (q,u,φ,dΩ) -> ∫(1/vol_D*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ ## Finite difference solver and level set function -stencil = AdvectionStencil(FirstOrderStencil(2,Float64),model,V_φ,Δ./order,max_steps,tol); +stencil = AdvectionStencil(FirstOrderStencil(2,Float64),model,V_φ,el_size./order,max_steps,tol); reinit!(stencil,φ,γ_reinit) ## Initialise op @@ -91,11 +91,11 @@ assem_adjoint = DiagonalBlockMatrixAssembler(SparseMatrixAssembler(V,U)); adjoint_K = assemble_matrix((u,v) -> a(v,u,φh,dΩ),assem_adjoint,V,U); ## Update mat and vec -LSTO_Distributed._assemble_matrix_and_vector!((u,v) -> a(u,v,φh,dΩ),v -> l(v,φh,dΩ),K,b,assem,U,V,uhd) +LevelSetTopOpt._assemble_matrix_and_vector!((u,v) -> a(u,v,φh,dΩ),v -> l(v,φh,dΩ),K,b,assem,U,V,uhd) # numerical_setup!(...) ## Update adjoint -LSTO_Distributed.assemble_matrix!((u,v) -> a(v,u,φh,dΩ),adjoint_K,assem_adjoint,V,U) +LevelSetTopOpt.assemble_matrix!((u,v) -> a(v,u,φh,dΩ),adjoint_K,assem_adjoint,V,U) ### Test @time op_test = AffineFEOperator((u,v) -> a(u,v,φh,dΩ),v -> l(v,φh,dΩ),U,V,SparseMatrixAssembler(U,V)) diff --git a/scripts/_dev/inv_hom_block_assem_testing_3D_MPI.jl b/scripts/_dev/inv_hom_block_assem_testing_3D_MPI.jl index 3ccf4c40..aa2d019e 100644 --- a/scripts/_dev/inv_hom_block_assem_testing_3D_MPI.jl +++ b/scripts/_dev/inv_hom_block_assem_testing_3D_MPI.jl @@ -1,5 +1,5 @@ using Gridap, Gridap.MultiField, GridapDistributed, GridapPETSc, GridapSolvers, - PartitionedArrays, LSTO_Distributed, SparseMatricesCSR + PartitionedArrays, LevelSetTopOpt, SparseMatricesCSR using Gridap.MultiField: BlockMultiFieldStyle """ @@ -23,15 +23,15 @@ function main(mesh_partition,distribute,el_size,diag_assem::Bool) γ = 0.05; γ_reinit = 0.5; max_steps = floor(Int,minimum(el_size)/3) - tol = 1/(order^2*10)*prod(inv,minimum(el_size)) - C = isotropic_3d(1.,0.3); + tol = 1/(order^2*10)/minimum(el_size) + C = isotropic_elast_tensor(3,1.,0.3); η_coeff = 2; α_coeff = 4; path = dirname(dirname(@__DIR__))*"/results/MPI_main_3d_inverse_homenisation_ALM" ## FE Setup model = CartesianDiscreteModel(ranks,mesh_partition,dom,el_size,isperiodic=(true,true,true)); - Δ = get_Δ(model) + el_Δ = get_el_Δ(model) f_Γ_D(x) = iszero(x) update_labels!(1,model,f_Γ_D,"origin") @@ -57,7 +57,7 @@ function main(mesh_partition,distribute,el_size,diag_assem::Bool) φ = get_free_dof_values(φh) ## Interpolation and weak form - interp = SmoothErsatzMaterialInterpolation(η = η_coeff*maximum(Δ)) + interp = SmoothErsatzMaterialInterpolation(η = η_coeff*maximum(el_Δ)) I,H,DH,ρ = interp.I,interp.H,interp.DH,interp.ρ εᴹ = (TensorValue(1.,0.,0.,0.,0.,0.,0.,0.,0.), # ϵᵢⱼ⁽¹¹⁾≡ϵᵢⱼ⁽¹⁾ @@ -86,7 +86,7 @@ function main(mesh_partition,distribute,el_size,diag_assem::Bool) dVol = (q,u,φ,dΩ) -> ∫(1/vol_D*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ ## Finite difference solver and level set function - stencil = AdvectionStencil(FirstOrderStencil(3,Float64),model,V_φ,Δ./order,max_steps,tol) + stencil = AdvectionStencil(FirstOrderStencil(3,Float64),model,V_φ,el_size./order,max_steps,tol) reinit!(stencil,φ,γ_reinit) ## Setup solver and FE operators @@ -122,7 +122,7 @@ function main(mesh_partition,distribute,el_size,diag_assem::Bool) return [J_init,C_init,dJ,dC],u_vec,t ## Hilbertian extension-regularisation problems - α = α_coeff*maximum(Δ) + α = α_coeff*maximum(el_Δ) a_hilb(p,q) =∫(α^2*∇(p)⋅∇(q) + p*q)dΩ; vel_ext = VelocityExtension(a_hilb,U_reg,V_reg, assem=SparseMatrixAssembler(Tm,Tv,U_reg,V_reg), @@ -136,13 +136,13 @@ function main(mesh_partition,distribute,el_size,diag_assem::Bool) λi = optimiser.λ; Λi = optimiser.Λ print_history(it,["J"=>Ji,"C"=>Ci,"L"=>Li,"λ"=>λi,"Λ"=>Λi];ranks=ranks) write_history(history,path*"/history.csv";ranks=ranks) - write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi))|"=>(norm ∘ ∇(φh))]) + write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh))]) end it,Ji,Ci,Li = last(optimiser.history) λi = optimiser.λ; Λi = optimiser.Λ print_history(it,["J"=>Ji,"C"=>Ci,"L"=>Li,"λ"=>λi,"Λ"=>Λi];ranks=ranks) write_history(optimiser.history,path*"/history.csv";ranks=ranks) - write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi))|"=>(norm ∘ ∇(φh))];iter_mod=1) + write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh))];iter_mod=1) end # RUN: mpiexecjl --project=. -n 8 julia ./scripts/inv_hom_block_assem_testing_3D_MPI.jl diff --git a/scripts/_dev/inv_hom_block_assem_testing_full_script.jl b/scripts/_dev/inv_hom_block_assem_testing_full_script.jl index dfeeaf49..75018ee6 100644 --- a/scripts/_dev/inv_hom_block_assem_testing_full_script.jl +++ b/scripts/_dev/inv_hom_block_assem_testing_full_script.jl @@ -1,4 +1,4 @@ -using Gridap, GridapDistributed, GridapPETSc, PartitionedArrays, LSTO_Distributed +using Gridap, GridapDistributed, GridapPETSc, PartitionedArrays, LevelSetTopOpt using GridapSolvers: LinearSolvers using Gridap.MultiField: BlockMultiFieldStyle @@ -21,15 +21,15 @@ function main(diag_block) γ = 0.05; γ_reinit = 0.5; max_steps = floor(Int,minimum(el_size)/10) - tol = 1/(order^2*10)*prod(inv,minimum(el_size)) - C = isotropic_2d(1.,0.3); + tol = 1/(order^2*10)/minimum(el_size) + C = isotropic_elast_tensor(2,1.,0.3); η_coeff = 2; α_coeff = 4; path = dirname(dirname(@__DIR__))*"/results/main_inverse_homogenisation_ALM" ## FE Setup model = CartesianDiscreteModel(dom,el_size,isperiodic=(true,true)); - Δ = get_Δ(model) + el_Δ = get_el_Δ(model) f_Γ_D(x) = iszero(x) update_labels!(1,model,f_Γ_D,"origin") @@ -50,12 +50,12 @@ function main(diag_block) U_reg = TrialFESpace(V_reg) ## Create FE functions - lsf_fn = x->max(gen_lsf(2,0.4)(x),gen_lsf(2,0.4;b=VectorValue(0,0.5))(x)); + lsf_fn = x->max(initial_lsf(2,0.4)(x),initial_lsf(2,0.4;b=VectorValue(0,0.5))(x)); φh = interpolate(lsf_fn,V_φ); φ = get_free_dof_values(φh) ## Interpolation and weak form - interp = SmoothErsatzMaterialInterpolation(η = η_coeff*maximum(Δ)) + interp = SmoothErsatzMaterialInterpolation(η = η_coeff*maximum(el_Δ)) I,H,DH,ρ = interp.I,interp.H,interp.DH,interp.ρ εᴹ = (TensorValue(1.,0.,0.,0.), # ϵᵢⱼ⁽¹¹⁾≡ϵᵢⱼ⁽¹⁾ @@ -77,7 +77,7 @@ function main(diag_block) dVol = (q,u,φ,dΩ) -> ∫(1/vol_D*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ ## Finite difference solver and level set function - stencil = AdvectionStencil(FirstOrderStencil(2,Float64),model,V_φ,Δ./order,max_steps,tol) + stencil = AdvectionStencil(FirstOrderStencil(2,Float64),model,V_φ,el_size./order,max_steps,tol) reinit!(stencil,φ,γ_reinit) ## Setup solver and FE operators @@ -103,7 +103,7 @@ function main(diag_block) return pcfs,[J_init,C_init,dJ,dC],u_vec ## Hilbertian extension-regularisation problems - α = α_coeff*maximum(Δ) + α = α_coeff*maximum(el_Δ) a_hilb(p,q) =∫(α^2*∇(p)⋅∇(q) + p*q)dΩ; vel_ext = VelocityExtension(a_hilb,U_reg,V_reg) @@ -115,13 +115,13 @@ function main(diag_block) λi = optimiser.λ; Λi = optimiser.Λ print_history(it,["J"=>Ji,"C"=>Ci,"L"=>Li,"λ"=>λi,"Λ"=>Λi]) write_history(history,path*"/history.csv") - write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi))|"=>(norm ∘ ∇(φh))]) + write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh))]) end it,Ji,Ci,Li = last(optimiser.history) λi = optimiser.λ; Λi = optimiser.Λ print_history(it,["J"=>Ji,"C"=>Ci,"L"=>Li,"λ"=>λi,"Λ"=>Λi]) write_history(optimiser.history,path*"/history.csv") - write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi))|"=>(norm ∘ ∇(φh))];iter_mod=1) + write_vtk(Ω,path*"/struc_$it",it,["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh))];iter_mod=1) end using LinearAlgebra diff --git a/scripts/_dev/nonlinear_adjoint_MWE.jl b/scripts/_dev/nonlinear_adjoint_MWE.jl index 9449618e..a84705a2 100644 --- a/scripts/_dev/nonlinear_adjoint_MWE.jl +++ b/scripts/_dev/nonlinear_adjoint_MWE.jl @@ -1,4 +1,4 @@ -using Gridap, GridapDistributed, GridapPETSc, PartitionedArrays, LSTO_Distributed +using Gridap, GridapDistributed, GridapPETSc, PartitionedArrays, LevelSetTopOpt """ (Serial) Minimum thermal compliance with Lagrangian method in 2D with nonlinear diffusivity. @@ -15,25 +15,25 @@ function main() ## Parameters order = 1; xmax=ymax=1.0 - prop_Γ_N = 0.4; + prop_Γ_N = 0.2; prop_Γ_D = 0.2 dom = (0,xmax,0,ymax); el_size = (200,200); γ = 0.1; γ_reinit = 0.5; max_steps = floor(Int,minimum(el_size)/10) - tol = 1/(order^2*10)*prod(inv,minimum(el_size)) # <- We can do better than this I think + tol = 1/(order^2*10)/minimum(el_size) # <- We can do better than this I think η_coeff = 2; α_coeff = 4; path = dirname(dirname(@__DIR__))*"/results/main_nonlinear" ## FE Setup model = CartesianDiscreteModel(dom,el_size); - Δ = get_Δ(model) + el_Δ = get_el_Δ(model) f_Γ_D(x) = (x[1] ≈ 0.0 && (x[2] <= ymax*prop_Γ_D + eps() || x[2] >= ymax-ymax*prop_Γ_D - eps())) ? true : false; - f_Γ_N(x) = (x[1] ≈ xmax && ymax/2-ymax*prop_Γ_N/4 - eps() <= x[2] <= - ymax/2+ymax*prop_Γ_N/4 + eps()) ? true : false; + f_Γ_N(x) = (x[1] ≈ xmax && ymax/2-ymax*prop_Γ_N/2 - eps() <= x[2] <= + ymax/2+ymax*prop_Γ_N/2 + eps()) ? true : false; update_labels!(1,model,f_Γ_D,"Gamma_D") update_labels!(2,model,f_Γ_N,"Gamma_N") @@ -52,11 +52,11 @@ function main() U_reg = TrialFESpace(V_reg,0) ## Create FE functions - φh = interpolate(gen_lsf(4,0.2),V_φ); + φh = interpolate(initial_lsf(4,0.2),V_φ); φ = get_free_dof_values(φh) ## Interpolation and weak form - interp = SmoothErsatzMaterialInterpolation(η = η_coeff*maximum(Δ)) + interp = SmoothErsatzMaterialInterpolation(η = η_coeff*maximum(el_Δ)) I,H,DH,ρ = interp.I,interp.H,interp.DH,interp.ρ D0 = 1; @@ -70,7 +70,7 @@ function main() J = (u,φ,dΩ,dΓ_N) -> ∫((I ∘ φ)*(D ∘ u)*∇(u)⋅∇(u) + ξ*(ρ ∘ φ))dΩ ## Finite difference solver and level set function - stencil = AdvectionStencil(FirstOrderStencil(2,Float64),model,V_φ,Δ./order,max_steps,tol) + stencil = AdvectionStencil(FirstOrderStencil(2,Float64),model,V_φ,el_size./order,max_steps,tol) reinit!(stencil,φ,γ_reinit) ## Setup solver and FE operators diff --git a/scripts/_dev/operations/ops_testing.jl b/scripts/_dev/operations/ops_testing.jl new file mode 100644 index 00000000..fe4a38b3 --- /dev/null +++ b/scripts/_dev/operations/ops_testing.jl @@ -0,0 +1,85 @@ +using LevelSetTopOpt +using Gridap +using Gridap.CellData: DomainContribution +using Gridap.FESpaces: _gradient +using GridapDistributed: DistributedDomainContribution +import Base.*, Base./ + +ContributionTypes = Union{DomainContribution,DistributedDomainContribution} + +struct DomainContributionOperation{A} + a + b + op :: A +end + +(F::DomainContributionOperation)(args...) = F.op(sum(F.a),sum(F.b)) + +function (*)(a::ContributionTypes,b::ContributionTypes) + DomainContributionOperation(a,b,*) +end + +function (*)(a::ContributionTypes,b::DomainContributionOperation) + DomainContributionOperation(a,b,*) +end + +(*)(a::DomainContributionOperation,b::ContributionTypes) = b*a + +Gridap.gradient(F::DomainContributionOperation{typeof(*)},xh) = gradient(F.a,xh)*sum(F.b(xh)) + gradient(F.b,xh)*sum(F.a(xh)) + +# function Gridap.FESpaces._gradient(f,xh,fuh::DomainContributionOperation) +# @show typeof(fuh.a) +# _gradient(f,xh,fuh.a)#*sum(fuh.b(xh)) + _gradient(f,xh,fuh.b)*sum(fuh.a(xh)) +# end + +## TESTING +model = CartesianDiscreteModel((0,1,0,1),(10,10)); +Ω = Triangulation(model) +dΩ = Measure(Ω,2) +V = TestFESpace(model,ReferenceFE(lagrangian,Float64,1)) +uh = interpolate(x->x[1]^2*x[2]^2,V); +F(u) = ∫(u)dΩ; +G(u) = ∫(u*u)dΩ; +# H(u) = ∫(cos ∘ u)dΩ; + +struct FunctionOperation + a + b + op +end + +function (*)(a::Function,b::Function) + FunctionOperation(a,b,*) +end + +function (*)(a::Function,b::FunctionOperation) + IntegrandWithMeasureOperation(a,b,*) +end + +(*)(a::FunctionOperation,b::Function) = b*a + +(F*G) + +# d/du F*F +dF = gradient(F,uh); +dFxF = 2dF*sum(F(uh)); +dFxF_new = gradient(FG,uh); +@assert get_array(dFxF_new) == get_array(dFxF) + +# # d/du F*G +# dF_iwf = gradient(F_iwf,uh); +# dG_iwf = gradient(G_iwf,uh); +# dFxG = dF_iwf*sum(G_iwf(uh)) + dG_iwf*sum(F_iwf(uh)); +# dFxG_new = gradient(F_iwf*G_iwf,uh); +# @assert get_array(dFxG) == get_array(dFxG_new) +# # d/du (F*G)*H +# dF_iwf = gradient(F_iwf,uh); +# dG_iwf = gradient(G_iwf,uh); +# dH_iwf = gradient(H_iwf,uh); +# # dFxGxH = dF_iwf*sum(G_iwf(uh)) + dG_iwf*sum(F_iwf(uh)); +# dFxGxH_new = gradient(F_iwf*G_iwf*H_iwf,uh); + +# sum(F_iwf(uh))*sum(G_iwf(uh)) +# (F_iwf*G_iwf)(uh) + +# # get_array(dFxG) == get_array(dFxG_new) \ No newline at end of file diff --git a/scripts/_dev/operations/ops_testing_fn_version.jl b/scripts/_dev/operations/ops_testing_fn_version.jl new file mode 100644 index 00000000..d710c66b --- /dev/null +++ b/scripts/_dev/operations/ops_testing_fn_version.jl @@ -0,0 +1,96 @@ +using LevelSetTopOpt +using Gridap +using Gridap.CellData: DomainContribution +using Gridap.FESpaces: _gradient +using GridapDistributed: DistributedDomainContribution +import Base.*, Base./ + +struct FunctionOperation{A} <: Function + a + b + op :: A +end + +(F::FunctionOperation)(args...) = F.op(sum(F.a(args...)),sum(F.b(args...))) + +function (*)(a::Function,b::Function) + FunctionOperation(a,b,*) +end + +function (*)(a::Function,b::FunctionOperation) + FunctionOperation(a,b,*) +end + +(*)(a::FunctionOperation,b::Function) = b*a + +Gridap.gradient(F::FunctionOperation{typeof(*)},xh) = gradient(F.a,xh)*sum(F.b(xh)) + gradient(F.b,xh)*sum(F.a(xh)) + +## Testing with ChainRules.jl +D = 1 +model = CartesianDiscreteModel((0,1,0,1),(10,10)); +Ω = Triangulation(model) +Γ_N = BoundaryTriangulation(model,tags=[6,]) +dΩ = Measure(Ω,2) +dΓ_N = Measure(Γ_N,2) +reffe_scalar = ReferenceFE(lagrangian,Float64,1) +V = TestFESpace(model,reffe_scalar;dirichlet_tags=[1]) +U = TrialFESpace(V,0.0) +V_φ = TestFESpace(model,reffe_scalar) +V_reg = TestFESpace(model,reffe_scalar;dirichlet_tags=[6,]) +U_reg = TrialFESpace(V_reg,0) + +## Create FE functions +φh = interpolate(x->-1,V_φ) +interp = SmoothErsatzMaterialInterpolation(η = 2maximum(get_el_size(model))); +I,_H,DH,ρ = interp.I,interp.H,interp.DH,interp.ρ + +a(u,v,φ,dΩ,dΓ_N) = ∫((I ∘ φ)*D*∇(u)⋅∇(v))dΩ +l(v,φ,dΩ,dΓ_N) = ∫(v)dΓ_N + +## Optimisation functionals +J1(u,φ,dΩ,dΓ_N) = ∫((I ∘ φ)*D*∇(u)⋅∇(u))dΩ +J2(u,φ,dΩ,dΓ_N) = ∫(ρ ∘ φ)dΩ + +J = J1*J2 + +J_iwm = LevelSetTopOpt.IntegrandWithMeasure(J,(dΩ,dΓ_N)); +uh = interpolate(x->x[1]^2*x[2]^2,V); +gradient(J_iwm,[uh,φh],1) + +function Gridap.gradient(F::LevelSetTopOpt.IntegrandWithMeasure{<:FunctionOperation},uh::Vector{<:FEFunction},K::Int) + _f(uk) = F.F(uh[1:K-1]...,uk,uh[K+1:end]...,F.dΩ...) + return gradient(_f,uh[K]) +end + +# state_map = AffineFEStateMap(a,l,U,V,V_φ,U_reg,φh,dΩ,dΓ_N) +# pcfs = PDEConstrainedFunctionals(J,state_map) + +## TESTING +model = CartesianDiscreteModel((0,1,0,1),(10,10)); +Ω = Triangulation(model) +dΩ = Measure(Ω,2) +V = TestFESpace(model,ReferenceFE(lagrangian,Float64,1)) +uh = interpolate(x->x[1]^2*x[2]^2,V); +F(u) = ∫(u)dΩ; +G(u) = ∫(u*u)dΩ; +H(u) = ∫(cos ∘ u)dΩ; + +# d/du F*F +dF = gradient(F,uh); +dFxF = 2dF*sum(F(uh)); +dFxF_new = gradient(F*F,uh); +@assert get_array(dFxF_new) == get_array(dFxF) + +# d/du F*G +dF = gradient(F,uh); +dG = gradient(G,uh); +dFxG = dF*sum(G(uh)) + dG*sum(F(uh)); +dFxG_new = gradient(F*G,uh); +@assert get_array(dFxG) == get_array(dFxG_new) +# d/du F*G*H +dF = gradient(F,uh); +dG = gradient(G,uh); +dH = gradient(H,uh); +dFxGxH = dF*sum(G(uh))*sum(H(uh)) + dG*sum(F(uh))*sum(H(uh)) + dH*sum(F(uh))*sum(G(uh)); +dFxGxH_new = gradient(F*G*H,uh); +get_array(dFxG) == get_array(dFxG_new) \ No newline at end of file diff --git a/scripts/_dev/operations/ops_testing_iwm.jl b/scripts/_dev/operations/ops_testing_iwm.jl new file mode 100644 index 00000000..1f50bd63 --- /dev/null +++ b/scripts/_dev/operations/ops_testing_iwm.jl @@ -0,0 +1,72 @@ +using LevelSetTopOpt +using Gridap +using LevelSetTopOpt: IntegrandWithMeasure +import Base.*, Base./ + +struct IntegrandWithMeasureOperation{A<:Function} + a + b + op :: A +end + +(F::IntegrandWithMeasureOperation)(args...) = F.op(sum(F.a(args...)),sum(F.b(args...))) + +function (*)(a::IntegrandWithMeasure,b::IntegrandWithMeasure) + IntegrandWithMeasureOperation(a,b,*) +end + +function (*)(a::IntegrandWithMeasure,b::IntegrandWithMeasureOperation) + IntegrandWithMeasureOperation(a,b,*) +end + +(*)(a::IntegrandWithMeasureOperation,b::IntegrandWithMeasure) = b*a + +Gridap.gradient(F::IntegrandWithMeasureOperation{typeof(*)},xh) = gradient(F.a,xh)*sum(F.b(xh)) + gradient(F.b,xh)*sum(F.a(xh)) + +# function (/)(a::Number,b::DomainContribution) +# DomainContributionOperation(a,b,/) +# end + +# function (/)(a::DomainContribution,b::DomainContribution) +# DomainContributionOperation(a,b,/) +# end + +# function (/)(a::DomainContribution,b::DomainContributionOperation) +# DomainContributionOperation(a,b,/) +# end + +# function (/)(a::DomainContributionOperation,b::DomainContribution) +# DomainContributionOperation(a,b,/) +# end + +## TESTING +model = CartesianDiscreteModel((0,1,0,1),(10,10)); +Ω = Triangulation(model) +dΩ = Measure(Ω,2) +V = TestFESpace(model,ReferenceFE(lagrangian,Float64,1)) +uh = interpolate(x->x[1]^2*x[2]^2,V); +F_iwf = IntegrandWithMeasure((u,dΩ)->∫(u)dΩ,(dΩ,)); +G_iwf = IntegrandWithMeasure((u,dΩ)->∫(u*u)dΩ,(dΩ,)); +H_iwf = IntegrandWithMeasure((u,dΩ)->∫(cos ∘ u)dΩ,(dΩ,)); +# d/du F*F +dF_iwf = gradient(F_iwf,uh); +dFxF = 2dF_iwf*sum(F_iwf(uh)); +dFxF_new = gradient(F_iwf*F_iwf,uh); +@assert get_array(dFxF_new) == get_array(dFxF) +# d/du F*G +dF_iwf = gradient(F_iwf,uh); +dG_iwf = gradient(G_iwf,uh); +dFxG = dF_iwf*sum(G_iwf(uh)) + dG_iwf*sum(F_iwf(uh)); +dFxG_new = gradient(F_iwf*G_iwf,uh); +@assert get_array(dFxG) == get_array(dFxG_new) +# d/du (F*G)*H +dF_iwf = gradient(F_iwf,uh); +dG_iwf = gradient(G_iwf,uh); +dH_iwf = gradient(H_iwf,uh); +# dFxGxH = dF_iwf*sum(G_iwf(uh)) + dG_iwf*sum(F_iwf(uh)); +dFxGxH_new = gradient(F_iwf*G_iwf*H_iwf,uh); + +sum(F_iwf(uh))*sum(G_iwf(uh)) +(F_iwf*G_iwf)(uh) + +# get_array(dFxG) == get_array(dFxG_new) \ No newline at end of file diff --git a/scripts/_dev/piezo_script.jl b/scripts/_dev/piezo_script.jl index 77ea3279..f4e7faae 100644 --- a/scripts/_dev/piezo_script.jl +++ b/scripts/_dev/piezo_script.jl @@ -5,7 +5,7 @@ using GridapPETSc: PetscScalar, PetscInt, PETSC, @check_error_code using PartitionedArrays using SparseMatricesCSR using ChainRulesCore -using LSTO_Distributed +using LevelSetTopOpt using GridapSolvers using Gridap.MultiField diff --git a/scripts/_dev/tutorial_test/minimum_thermal_compliance_implementation.jl b/scripts/_dev/tutorial_test/minimum_thermal_compliance_implementation.jl new file mode 100644 index 00000000..6867340e --- /dev/null +++ b/scripts/_dev/tutorial_test/minimum_thermal_compliance_implementation.jl @@ -0,0 +1,76 @@ +using Gridap, LevelSetTopOpt + +# FE parameters +order = 1 # Finite element order +xmax = ymax = 1.0 # Domain size +dom = (0,xmax,0,ymax) # Bounding domain +el_size = (200,200) # Mesh partition size +prop_Γ_N = 0.2 # Γ_N size parameter +prop_Γ_D = 0.2 # Γ_D size parameter +f_Γ_N(x) = (x[1] ≈ xmax && # Γ_N indicator function + ymax/2-ymax*prop_Γ_N/2 - eps() <= x[2] <= ymax/2+ymax*prop_Γ_N/2 + eps()) +f_Γ_D(x) = (x[1] ≈ 0.0 && # Γ_D indicator function + (x[2] <= ymax*prop_Γ_D + eps() || x[2] >= ymax-ymax*prop_Γ_D - eps())) +# FD parameters +γ = 0.1 # HJ equation time step coefficient +γ_reinit = 0.5 # Reinit. equation time step coefficient +max_steps = floor(Int,minimum(el_size)/10) # Max steps for advection +tol = 1/(5order^2)/minimum(el_size) # Advection tolerance +# Problem parameters +κ = 1 # Diffusivity +g = 1 # Heat flow in +vf = 0.4 # Volume fraction constraint +lsf_func = initial_lsf(4,0.2) # Initial level set function +iter_mod = 10 # Output VTK files every 10th iteration +path = "./results/tut1/" # Output path +mkpath(path) # Create path +# Model +model = CartesianDiscreteModel(dom,el_size); +update_labels!(1,model,f_Γ_D,"Gamma_D") +update_labels!(2,model,f_Γ_N,"Gamma_N") +# Triangulation and measures +Ω = Triangulation(model) +Γ_N = BoundaryTriangulation(model,tags="Gamma_N") +dΩ = Measure(Ω,2*order) +dΓ_N = Measure(Γ_N,2*order) +# Spaces +reffe = ReferenceFE(lagrangian,Float64,order) +V = TestFESpace(model,reffe;dirichlet_tags=["Gamma_D"]) +U = TrialFESpace(V,0.0) +V_φ = TestFESpace(model,reffe) +V_reg = TestFESpace(model,reffe;dirichlet_tags=["Gamma_N"]) +U_reg = TrialFESpace(V_reg,0) +# Level set and interpolator +φh = interpolate(lsf_func,V_φ) +interp = SmoothErsatzMaterialInterpolation(η = 2*maximum(get_el_Δ(model))) +I,H,DH,ρ = interp.I,interp.H,interp.DH,interp.ρ +# Weak formulation +a(u,v,φ,dΩ,dΓ_N) = ∫((I ∘ φ)*κ*∇(u)⋅∇(v))dΩ +l(v,φ,dΩ,dΓ_N) = ∫(g*v)dΓ_N +state_map = AffineFEStateMap(a,l,U,V,V_φ,U_reg,φh,dΩ,dΓ_N) +# Objective and constraints +J(u,φ,dΩ,dΓ_N) = ∫((I ∘ φ)*κ*∇(u)⋅∇(u))dΩ +dJ(q,u,φ,dΩ,dΓ_N) = ∫(κ*∇(u)⋅∇(u)*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ +vol_D = sum(∫(1)dΩ) +C(u,φ,dΩ,dΓ_N) = ∫(((ρ ∘ φ) - vf)/vol_D)dΩ +dC(q,u,φ,dΩ,dΓ_N) = ∫(-1/vol_D*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ +pcfs = PDEConstrainedFunctionals(J,[C],state_map,analytic_dJ=dJ,analytic_dC=[dC]) +# Velocity extension +α = 4*maximum(get_el_Δ(model)) +a_hilb(p,q) =∫(α^2*∇(p)⋅∇(q) + p*q)dΩ +vel_ext = VelocityExtension(a_hilb,U_reg,V_reg) +# Finite difference scheme +scheme = FirstOrderStencil(length(el_size),Float64) +stencil = AdvectionStencil(scheme,model,V_φ,tol,max_steps) +# Optimiser +optimiser = AugmentedLagrangian(pcfs,stencil,vel_ext,φh;γ,γ_reinit,verbose=true,constraint_names=[:Vol]) +# Solve +for (it,uh,φh) in optimiser + data = ["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh)),"uh"=>uh] + iszero(it % iter_mod) && (writevtk(Ω,path*"struc_$it",cellfields=data);GC.gc()) + write_history(path*"/history.txt",get_history(optimiser)) +end +# Final structure +it = get_history(optimiser).niter; uh = get_state(pcfs) +writevtk(Ω,path*"struc_$it",cellfields=["phi"=>φh, + "H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh)),"uh"=>uh]) \ No newline at end of file diff --git a/scripts/_dev/tutorial_test/minimum_thermal_compliance_implementation_3d.jl b/scripts/_dev/tutorial_test/minimum_thermal_compliance_implementation_3d.jl new file mode 100644 index 00000000..0f167337 --- /dev/null +++ b/scripts/_dev/tutorial_test/minimum_thermal_compliance_implementation_3d.jl @@ -0,0 +1,78 @@ +using Gridap, LevelSetTopOpt + +# FE parameters +order = 1 # Finite element order +xmax=ymax=zmax=1.0 # Domain size +dom = (0,xmax,0,ymax,0,zmax) # Bounding domain +el_size = (40,40,40) # Mesh partition size +prop_Γ_N = 0.2 # Γ_N size parameter +prop_Γ_D = 0.2 # Γ_D size parameter +f_Γ_N(x) = (x[1] ≈ xmax) && # Γ_N indicator function + (ymax/2-ymax*prop_Γ_N/2 - eps() <= x[2] <= ymax/2+ymax*prop_Γ_N/2 + eps()) && + (zmax/2-zmax*prop_Γ_N/2 - eps() <= x[3] <= zmax/2+zmax*prop_Γ_N/2 + eps()) +f_Γ_D(x) = (x[1] ≈ 0.0) && # Γ_D indicator function + (x[2] <= ymax*prop_Γ_D + eps() || x[2] >= ymax-ymax*prop_Γ_D - eps()) && + (x[3] <= zmax*prop_Γ_D + eps() || x[3] >= zmax-zmax*prop_Γ_D - eps()) +# FD parameters +γ = 0.1 # HJ equation time step coefficient +γ_reinit = 0.5 # Reinit. equation time step coefficient +max_steps = floor(Int,minimum(el_size)/3) # Max steps for advection +tol = 1/(2order^2)/minimum(el_size) # Advection tolerance +# Problem parameters +κ = 1 # Diffusivity +g = 1 # Heat flow in +vf = 0.4 # Volume fraction constraint +lsf_func = initial_lsf(4,0.2) # Initial level set function +iter_mod = 10 # Output VTK files every 10th iteration +path = "./results/tut1_3d/" # Output path +mkpath(path) # Create path +# Model +model = CartesianDiscreteModel(dom,el_size); +update_labels!(1,model,f_Γ_D,"Gamma_D") +update_labels!(2,model,f_Γ_N,"Gamma_N") +# Triangulation and measures +Ω = Triangulation(model) +Γ_N = BoundaryTriangulation(model,tags="Gamma_N") +dΩ = Measure(Ω,2*order) +dΓ_N = Measure(Γ_N,2*order) +# Spaces +reffe = ReferenceFE(lagrangian,Float64,order) +V = TestFESpace(model,reffe;dirichlet_tags=["Gamma_D"]) +U = TrialFESpace(V,0.0) +V_φ = TestFESpace(model,reffe) +V_reg = TestFESpace(model,reffe;dirichlet_tags=["Gamma_N"]) +U_reg = TrialFESpace(V_reg,0) +# Level set and interpolator +φh = interpolate(lsf_func,V_φ) +interp = SmoothErsatzMaterialInterpolation(η = 2*maximum(get_el_Δ(model))) +I,H,DH,ρ = interp.I,interp.H,interp.DH,interp.ρ +# Weak formulation +a(u,v,φ,dΩ,dΓ_N) = ∫((I ∘ φ)*κ*∇(u)⋅∇(v))dΩ +l(v,φ,dΩ,dΓ_N) = ∫(g*v)dΓ_N +state_map = AffineFEStateMap(a,l,U,V,V_φ,U_reg,φh,dΩ,dΓ_N) +# Objective and constraints +J(u,φ,dΩ,dΓ_N) = ∫((I ∘ φ)*κ*∇(u)⋅∇(u))dΩ +dJ(q,u,φ,dΩ,dΓ_N) = ∫(κ*∇(u)⋅∇(u)*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ +vol_D = sum(∫(1)dΩ) +C(u,φ,dΩ,dΓ_N) = ∫(((ρ ∘ φ) - vf)/vol_D)dΩ +dC(q,u,φ,dΩ,dΓ_N) = ∫(-1/vol_D*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ +pcfs = PDEConstrainedFunctionals(J,[C],state_map,analytic_dJ=dJ,analytic_dC=[dC]) +# Velocity extension +α = 4*maximum(get_el_Δ(model)) +a_hilb(p,q) =∫(α^2*∇(p)⋅∇(q) + p*q)dΩ +vel_ext = VelocityExtension(a_hilb,U_reg,V_reg) +# Finite difference scheme +scheme = FirstOrderStencil(length(el_size),Float64) +stencil = AdvectionStencil(scheme,model,V_φ,tol,max_steps) +# Optimiser +optimiser = AugmentedLagrangian(pcfs,stencil,vel_ext,φh;γ,γ_reinit,verbose=true,constraint_names=[:Vol]) +# Solve +for (it,uh,φh) in optimiser + data = ["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh)),"uh"=>uh] + iszero(it % iter_mod) && (writevtk(Ω,path*"struc_$it",cellfields=data);GC.gc()) + write_history(path*"/history.txt",get_history(optimiser)) +end +# Final structure +it = get_history(optimiser).niter; uh = get_state(pcfs) +writevtk(Ω,path*"struc_$it",cellfields=["phi"=>φh, + "H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh)),"uh"=>uh]) \ No newline at end of file diff --git a/scripts/_dev/tutorial_test/minimum_thermal_compliance_implementation_3d_petsc.jl b/scripts/_dev/tutorial_test/minimum_thermal_compliance_implementation_3d_petsc.jl new file mode 100644 index 00000000..a8db1de4 --- /dev/null +++ b/scripts/_dev/tutorial_test/minimum_thermal_compliance_implementation_3d_petsc.jl @@ -0,0 +1,99 @@ +using Gridap, GridapPETSc, SparseMatricesCSR, LevelSetTopOpt + +function main() + # FE parameters + order = 1 # Finite element order + xmax=ymax=zmax=1.0 # Domain size + dom = (0,xmax,0,ymax,0,zmax) # Bounding domain + el_size = (100,100,100) # Mesh partition size + prop_Γ_N = 0.2 # Γ_N size parameter + prop_Γ_D = 0.2 # Γ_D size parameter + f_Γ_N(x) = (x[1] ≈ xmax) && # Γ_N indicator function + (ymax/2-ymax*prop_Γ_N/2 - eps() <= x[2] <= ymax/2+ymax*prop_Γ_N/2 + eps()) && + (zmax/2-zmax*prop_Γ_N/2 - eps() <= x[3] <= zmax/2+zmax*prop_Γ_N/2 + eps()) + f_Γ_D(x) = (x[1] ≈ 0.0) && # Γ_D indicator function + (x[2] <= ymax*prop_Γ_D + eps() || x[2] >= ymax-ymax*prop_Γ_D - eps()) && + (x[3] <= zmax*prop_Γ_D + eps() || x[3] >= zmax-zmax*prop_Γ_D - eps()) + # FD parameters + γ = 0.1 # HJ equation time step coefficient + γ_reinit = 0.5 # Reinit. equation time step coefficient + max_steps = floor(Int,minimum(el_size)/3) # Max steps for advection + tol = 1/(2order^2)/minimum(el_size) # Advection tolerance + # Problem parameters + κ = 1 # Diffusivity + g = 1 # Heat flow in + vf = 0.4 # Volume fraction constraint + lsf_func = initial_lsf(4,0.2) # Initial level set function + iter_mod = 10 # Output VTK files every 10th iteration + path = "./results/tut1_3d_petsc/" # Output path + mkpath(path) # Create path + # Model + model = CartesianDiscreteModel(dom,el_size); + update_labels!(1,model,f_Γ_D,"Gamma_D") + update_labels!(2,model,f_Γ_N,"Gamma_N") + # Triangulation and measures + Ω = Triangulation(model) + Γ_N = BoundaryTriangulation(model,tags="Gamma_N") + dΩ = Measure(Ω,2*order) + dΓ_N = Measure(Γ_N,2*order) + # Spaces + reffe = ReferenceFE(lagrangian,Float64,order) + V = TestFESpace(model,reffe;dirichlet_tags=["Gamma_D"]) + U = TrialFESpace(V,0.0) + V_φ = TestFESpace(model,reffe) + V_reg = TestFESpace(model,reffe;dirichlet_tags=["Gamma_N"]) + U_reg = TrialFESpace(V_reg,0) + # Level set and interpolator + φh = interpolate(lsf_func,V_φ) + interp = SmoothErsatzMaterialInterpolation(η = 2*maximum(get_el_Δ(model))) + I,H,DH,ρ = interp.I,interp.H,interp.DH,interp.ρ + # Weak formulation + a(u,v,φ,dΩ,dΓ_N) = ∫((I ∘ φ)*κ*∇(u)⋅∇(v))dΩ + l(v,φ,dΩ,dΓ_N) = ∫(g*v)dΓ_N + # State map + Tm = SparseMatrixCSR{0,PetscScalar,PetscInt} + Tv = Vector{PetscScalar} + state_map = AffineFEStateMap( + a,l,U,V,V_φ,U_reg,φh,dΩ,dΓ_N; + assem_U = SparseMatrixAssembler(Tm,Tv,U,V), + assem_adjoint = SparseMatrixAssembler(Tm,Tv,V,U), + assem_deriv = SparseMatrixAssembler(Tm,Tv,U_reg,U_reg), + ls = PETScLinearSolver(),adjoint_ls = PETScLinearSolver() + ) + # Objective and constraints + J(u,φ,dΩ,dΓ_N) = ∫((I ∘ φ)*κ*∇(u)⋅∇(u))dΩ + dJ(q,u,φ,dΩ,dΓ_N) = ∫(κ*∇(u)⋅∇(u)*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ + vol_D = sum(∫(1)dΩ) + C(u,φ,dΩ,dΓ_N) = ∫(((ρ ∘ φ) - vf)/vol_D)dΩ + dC(q,u,φ,dΩ,dΓ_N) = ∫(-1/vol_D*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ + pcfs = PDEConstrainedFunctionals(J,[C],state_map,analytic_dJ=dJ,analytic_dC=[dC]) + # Velocity extension + α = 4*maximum(get_el_Δ(model)) + a_hilb(p,q) =∫(α^2*∇(p)⋅∇(q) + p*q)dΩ + vel_ext = VelocityExtension( + a_hilb, U_reg, V_reg; + assem = SparseMatrixAssembler(Tm,Tv,U_reg,V_reg), + ls = PETScLinearSolver() + ) + # Finite difference scheme + scheme = FirstOrderStencil(length(el_size),Float64) + stencil = AdvectionStencil(scheme,model,V_φ,tol,max_steps) + # Optimiser + optimiser = AugmentedLagrangian(pcfs,stencil,vel_ext,φh;γ,γ_reinit,verbose=true,constraint_names=[:Vol]) + # Solve + for (it,uh,φh) in optimiser + data = ["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh)),"uh"=>uh] + iszero(it % iter_mod) && (writevtk(Ω,path*"struc_$it",cellfields=data);GC.gc()) + write_history(path*"/history.txt",get_history(optimiser)) + end + # Final structure + it = get_history(optimiser).niter; uh = get_state(pcfs) + writevtk(Ω,path*"struc_$it",cellfields=["phi"=>φh, + "H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh)),"uh"=>uh]) +end + +solver_options = "-pc_type gamg -ksp_type cg -ksp_error_if_not_converged true + -ksp_converged_reason -ksp_rtol 1.0e-12" +GridapPETSc.with(args=split(solver_options)) do + main() +end \ No newline at end of file diff --git a/scripts/_dev/tutorial_test/minimum_thermal_compliance_implementation_3d_petsc_mpi.jl b/scripts/_dev/tutorial_test/minimum_thermal_compliance_implementation_3d_petsc_mpi.jl new file mode 100644 index 00000000..d9fde797 --- /dev/null +++ b/scripts/_dev/tutorial_test/minimum_thermal_compliance_implementation_3d_petsc_mpi.jl @@ -0,0 +1,104 @@ +using Gridap, GridapPETSc, GridapDistributed, PartitionedArrays, SparseMatricesCSR, LevelSetTopOpt + +function main(mesh_partition,distribute) + ranks = distribute(LinearIndices((prod(mesh_partition),))) + # FE parameters + order = 1 # Finite element order + xmax=ymax=zmax=1.0 # Domain size + dom = (0,xmax,0,ymax,0,zmax) # Bounding domain + el_size = (100,100,100) # Mesh partition size + prop_Γ_N = 0.2 # Γ_N size parameter + prop_Γ_D = 0.2 # Γ_D size parameter + f_Γ_N(x) = (x[1] ≈ xmax) && # Γ_N indicator function + (ymax/2-ymax*prop_Γ_N/2 - eps() <= x[2] <= ymax/2+ymax*prop_Γ_N/2 + eps()) && + (zmax/2-zmax*prop_Γ_N/2 - eps() <= x[3] <= zmax/2+zmax*prop_Γ_N/2 + eps()) + f_Γ_D(x) = (x[1] ≈ 0.0) && # Γ_D indicator function + (x[2] <= ymax*prop_Γ_D + eps() || x[2] >= ymax-ymax*prop_Γ_D - eps()) && + (x[3] <= zmax*prop_Γ_D + eps() || x[3] >= zmax-zmax*prop_Γ_D - eps()) + # FD parameters + γ = 0.1 # HJ equation time step coefficient + γ_reinit = 0.5 # Reinit. equation time step coefficient + max_steps = floor(Int,minimum(el_size)/3) # Max steps for advection + tol = 1/(2order^2)/minimum(el_size) # Advection tolerance + # Problem parameters + κ = 1 # Diffusivity + g = 1 # Heat flow in + vf = 0.4 # Volume fraction constraint + lsf_func = initial_lsf(4,0.2) # Initial level set function + iter_mod = 10 # Output VTK files every 10th iteration + path = "./results/tut1_3d_petsc_mpi/" # Output path + i_am_main(ranks) && mkpath(path) # Create path + # Model + model = CartesianDiscreteModel(ranks,mesh_partition,dom,el_size); + update_labels!(1,model,f_Γ_D,"Gamma_D") + update_labels!(2,model,f_Γ_N,"Gamma_N") + # Triangulation and measures + Ω = Triangulation(model) + Γ_N = BoundaryTriangulation(model,tags="Gamma_N") + dΩ = Measure(Ω,2*order) + dΓ_N = Measure(Γ_N,2*order) + # Spaces + reffe = ReferenceFE(lagrangian,Float64,order) + V = TestFESpace(model,reffe;dirichlet_tags=["Gamma_D"]) + U = TrialFESpace(V,0.0) + V_φ = TestFESpace(model,reffe) + V_reg = TestFESpace(model,reffe;dirichlet_tags=["Gamma_N"]) + U_reg = TrialFESpace(V_reg,0) + # Level set and interpolator + φh = interpolate(lsf_func,V_φ) + interp = SmoothErsatzMaterialInterpolation(η = 2*maximum(get_el_Δ(model))) + I,H,DH,ρ = interp.I,interp.H,interp.DH,interp.ρ + # Weak formulation + a(u,v,φ,dΩ,dΓ_N) = ∫((I ∘ φ)*κ*∇(u)⋅∇(v))dΩ + l(v,φ,dΩ,dΓ_N) = ∫(g*v)dΓ_N + # State map + Tm = SparseMatrixCSR{0,PetscScalar,PetscInt} + Tv = Vector{PetscScalar} + state_map = AffineFEStateMap( + a,l,U,V,V_φ,U_reg,φh,dΩ,dΓ_N; + assem_U = SparseMatrixAssembler(Tm,Tv,U,V), + assem_adjoint = SparseMatrixAssembler(Tm,Tv,V,U), + assem_deriv = SparseMatrixAssembler(Tm,Tv,U_reg,U_reg), + ls = PETScLinearSolver(),adjoint_ls = PETScLinearSolver() + ) + # Objective and constraints + J(u,φ,dΩ,dΓ_N) = ∫((I ∘ φ)*κ*∇(u)⋅∇(u))dΩ + dJ(q,u,φ,dΩ,dΓ_N) = ∫(κ*∇(u)⋅∇(u)*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ + vol_D = sum(∫(1)dΩ) + C(u,φ,dΩ,dΓ_N) = ∫(((ρ ∘ φ) - vf)/vol_D)dΩ + dC(q,u,φ,dΩ,dΓ_N) = ∫(-1/vol_D*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ + pcfs = PDEConstrainedFunctionals(J,[C],state_map,analytic_dJ=dJ,analytic_dC=[dC]) + # Velocity extension + α = 4*maximum(get_el_Δ(model)) + a_hilb(p,q) =∫(α^2*∇(p)⋅∇(q) + p*q)dΩ + vel_ext = VelocityExtension( + a_hilb, U_reg, V_reg; + assem = SparseMatrixAssembler(Tm,Tv,U_reg,V_reg), + ls = PETScLinearSolver() + ) + # Finite difference scheme + scheme = FirstOrderStencil(length(el_size),Float64) + stencil = AdvectionStencil(scheme,model,V_φ,tol,max_steps) + # Optimiser + optimiser = AugmentedLagrangian(pcfs,stencil,vel_ext,φh; + γ,γ_reinit,verbose=i_am_main(ranks),constraint_names=[:Vol]) + # Solve + for (it,uh,φh) in optimiser + data = ["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh)),"uh"=>uh] + iszero(it % iter_mod) && (writevtk(Ω,path*"struc_$it",cellfields=data);GC.gc()) + write_history(path*"/history.txt",get_history(optimiser)) + end + # Final structure + it = get_history(optimiser).niter; uh = get_state(pcfs) + writevtk(Ω,path*"struc_$it",cellfields=["phi"=>φh, + "H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh)),"uh"=>uh]) +end + +with_mpi() do distribute + mesh_partition = (2,2,2) + solver_options = "-pc_type gamg -ksp_type cg -ksp_error_if_not_converged true + -ksp_converged_reason -ksp_rtol 1.0e-12" + GridapPETSc.with(args=split(solver_options)) do + main(mesh_partition,distribute) + end +end \ No newline at end of file diff --git a/scripts/_dev/tutorial_test/minimum_thermal_compliance_implementation_nonlinear.jl b/scripts/_dev/tutorial_test/minimum_thermal_compliance_implementation_nonlinear.jl new file mode 100644 index 00000000..058f1f4f --- /dev/null +++ b/scripts/_dev/tutorial_test/minimum_thermal_compliance_implementation_nonlinear.jl @@ -0,0 +1,74 @@ +using Gridap, LevelSetTopOpt + +# FE parameters +order = 1 # Finite element order +xmax = ymax = 1.0 # Domain size +dom = (0,xmax,0,ymax) # Bounding domain +el_size = (200,200) # Mesh partition size +prop_Γ_N = 0.2 # Γ_N size parameter +prop_Γ_D = 0.2 # Γ_D size parameter +f_Γ_N(x) = (x[1] ≈ xmax && # Γ_N indicator function + ymax/2-ymax*prop_Γ_N/2 - eps() <= x[2] <= ymax/2+ymax*prop_Γ_N/2 + eps()) +f_Γ_D(x) = (x[1] ≈ 0.0 && # Γ_D indicator function + (x[2] <= ymax*prop_Γ_D + eps() || x[2] >= ymax-ymax*prop_Γ_D - eps())) +# FD parameters +γ = 0.1 # HJ equation time step coefficient +γ_reinit = 0.5 # Reinit. equation time step coefficient +max_steps = floor(Int,minimum(el_size)/10) # Max steps for advection +tol = 1/(5order^2)/minimum(el_size) # Advection tolerance +# Problem parameters +κ(u) = exp(-u) # Diffusivity +g = 1 # Heat flow in +vf = 0.4 # Volume fraction constraint +lsf_func = initial_lsf(4,0.2) # Initial level set function +iter_mod = 10 # Output VTK files every 10th iteration +path = "./results/tut1_nonlinear/" # Output path +mkpath(path) # Create path +# Model +model = CartesianDiscreteModel(dom,el_size); +update_labels!(1,model,f_Γ_D,"Gamma_D") +update_labels!(2,model,f_Γ_N,"Gamma_N") +# Triangulation and measures +Ω = Triangulation(model) +Γ_N = BoundaryTriangulation(model,tags="Gamma_N") +dΩ = Measure(Ω,2*order) +dΓ_N = Measure(Γ_N,2*order) +# Spaces +reffe = ReferenceFE(lagrangian,Float64,order) +V = TestFESpace(model,reffe;dirichlet_tags=["Gamma_D"]) +U = TrialFESpace(V,0.0) +V_φ = TestFESpace(model,reffe) +V_reg = TestFESpace(model,reffe;dirichlet_tags=["Gamma_N"]) +U_reg = TrialFESpace(V_reg,0) +# Level set and interpolator +φh = interpolate(lsf_func,V_φ) +interp = SmoothErsatzMaterialInterpolation(η = 2*maximum(get_el_Δ(model))) +I,H,DH,ρ = interp.I,interp.H,interp.DH,interp.ρ +# Weak formulation +R(u,v,φ,dΩ,dΓ_N) = ∫((I ∘ φ)*(κ ∘ u)*∇(u)⋅∇(v))dΩ - ∫(g*v)dΓ_N +state_map = NonlinearFEStateMap(R,U,V,V_φ,U_reg,φh,dΩ,dΓ_N) +# Objective and constraints +J(u,φ,dΩ,dΓ_N) = ∫((I ∘ φ)*(κ ∘ u)*∇(u)⋅∇(u))dΩ +vol_D = sum(∫(1)dΩ) +C(u,φ,dΩ,dΓ_N) = ∫(((ρ ∘ φ) - vf)/vol_D)dΩ +dC(q,u,φ,dΩ,dΓ_N) = ∫(-1/vol_D*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ +pcfs = PDEConstrainedFunctionals(J,[C],state_map,analytic_dC=[dC]) +# Velocity extension +α = 4*maximum(get_el_Δ(model)) +a_hilb(p,q) =∫(α^2*∇(p)⋅∇(q) + p*q)dΩ +vel_ext = VelocityExtension(a_hilb,U_reg,V_reg) +# Finite difference scheme +scheme = FirstOrderStencil(length(el_size),Float64) +stencil = AdvectionStencil(scheme,model,V_φ,tol,max_steps) +# Optimiser +optimiser = AugmentedLagrangian(pcfs,stencil,vel_ext,φh;γ,γ_reinit,verbose=true,constraint_names=[:Vol]) +# Solve +for (it,uh,φh) in optimiser + data = ["phi"=>φh,"H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh)),"uh"=>uh] + iszero(it % iter_mod) && (writevtk(Ω,path*"struc_$it",cellfields=data);GC.gc()) + write_history(path*"/history.txt",get_history(optimiser)) +end +# Final structure +it = get_history(optimiser).niter; uh = get_state(pcfs) +writevtk(Ω,path*"struc_$it",cellfields=["phi"=>φh, + "H(phi)"=>(H ∘ φh),"|nabla(phi)|"=>(norm ∘ ∇(φh)),"uh"=>uh]) \ No newline at end of file diff --git a/src/Advection.jl b/src/Advection.jl index 3bc7f3be..ebd31306 100644 --- a/src/Advection.jl +++ b/src/Advection.jl @@ -1,57 +1,55 @@ """ - abstract type Stencil end + abstract type Stencil Finite difference stencil for a single step of the Hamilton-Jacobi - evolution equation (Eqn. 1) and reinitialisation equation (Eqn. 2). - -Equation 1: - ∂ϕ/∂t + V|∇ϕ| = 0 for x∈D, t>0 - with ϕ(0,x) = ϕ₀ for x∈D. -Equation 2: - ∂ϕ/∂t + Sign(ϕ₀)(|∇ϕ|-1) = 0 for x∈D, t>0 - with ϕ(0,x) = ϕ₀ for x∈D. +evolution equation and reinitialisation equation. + +Your own stencil can be implemented by extending the methods below. """ abstract type Stencil end """ + allocate_caches(::Stencil,φ,vel) +Allocate caches for a given `Stencil`. """ function allocate_caches(::Stencil,φ,vel) nothing # By default, no caches are required. end """ - reinit!(::Stencil,φ_new,φ,vel,Δt,Δx,isperiodic,caches) -> φ + reinit!(::Stencil,φ_new,φ,vel,Δt,Δx,isperiodic,caches) -> φ -Single finite difference step of the reinitialisation equation: - ∂ϕ/∂t + Sign(ϕ₀)(|∇ϕ|-1) = 0 for x∈D, t>0 - with ϕ(0,x) = ϕ₀ for x∈D. +Single finite difference step of the reinitialisation equation for a given `Stencil`. """ function reinit!(::Stencil,φ_new,φ,vel,Δt,Δx,isperiodic,caches) @abstractmethod end """ - advect!(::Stencil,φ_new,φ,vel,Δt,Δx,isperiodic,caches) -> φ + advect!(::Stencil,φ,vel,Δt,Δx,isperiodic,caches) -> φ -Single finite difference step of the HJ evoluation equation: - ∂ϕ/∂t + V|∇ϕ| = 0 for x∈D, t>0 - with ϕ(0,x) = ϕ₀ for x∈D. +Single finite difference step of the Hamilton-Jacobi evoluation equation for a given +`Stencil`. """ -function advect!(::Stencil,φ,vel,Δt,Δx,caches) +function advect!(::Stencil,φ,vel,Δt,Δx,isperiodic,caches) @abstractmethod end +""" + compute_Δt(::Stencil,φ,vel) + +Compute the time step for the `Stencil`. +""" function compute_Δt(::Stencil,φ,vel) @abstractmethod end -# First order stencil """ - struct FirstOrderStencil{D,T} <: Stencil end + struct FirstOrderStencil{D,T} <: Stencil end -Godunov upwind difference scheme per Osher and Fedkiw - (10.1007/b98879) +A first order Godunov upwind difference scheme based on Osher and Fedkiw +([link](https://doi.org/10.1007/b98879)). """ struct FirstOrderStencil{D,T} <: Stencil function FirstOrderStencil(D::Integer,::Type{T}) where T<:Real @@ -73,6 +71,11 @@ function reinit!(::FirstOrderStencil{2,T},φ_new,φ,vel,Δt,Δx,isperiodic,cache # Prepare shifted lsf circshift!(D⁺ʸ,φ,(0,-1)); circshift!(D⁻ʸ,φ,(0,1)) circshift!(D⁺ˣ,φ,(-1,0)); circshift!(D⁻ˣ,φ,(1,0)) + # Sign approximation + ∇⁺ .= @. (D⁺ʸ - D⁻ʸ)/(2Δy); ~yperiodic ? ∇⁺[:,[1,end]] .= zero(T) : 0; + ∇⁻ .= @. (D⁺ˣ - D⁻ˣ)/(2Δx); ~xperiodic ? ∇⁻[[1,end],:] .= zero(T) : 0; + ϵₛ = maximum((Δx,Δy)) + vel .= @. φ/sqrt(φ^2 + ϵₛ^2*(∇⁺^2+∇⁻^2)) # Forward (+) & Backward (-) D⁺ʸ .= @. (D⁺ʸ - φ)/Δy; ~yperiodic ? D⁺ʸ[:,end] .= zero(T) : 0; D⁺ˣ .= @. (D⁺ˣ - φ)/Δx; ~xperiodic ? D⁺ˣ[end,:] .= zero(T) : 0; @@ -121,6 +124,13 @@ function reinit!(::FirstOrderStencil{3,T},φ_new,φ,vel,Δt,Δx,isperiodic,cache circshift!(D⁺ʸ,φ,(0,-1,0)); circshift!(D⁻ʸ,φ,(0,1,0)) circshift!(D⁺ˣ,φ,(-1,0,0)); circshift!(D⁻ˣ,φ,(1,0,0)) circshift!(D⁺ᶻ,φ,(0,0,-1)); circshift!(D⁻ᶻ,φ,(0,0,1)) + # Sign approximation + ∇⁺ .= @. (D⁺ʸ - D⁻ʸ)/(2Δy); ~yperiodic ? ∇⁺[:,[1,end],:] .= zero(T) : 0; + ∇⁻ .= @. (D⁺ˣ - D⁻ˣ)/(2Δx); ~xperiodic ? ∇⁻[[1,end],:,:] .= zero(T) : 0; + ∇⁺ .= @. ∇⁺^2+∇⁻^2 # |∇φ|² (partially computed) + ∇⁻ .= @. (D⁺ᶻ - D⁻ᶻ)/(2Δz); ~xperiodic ? ∇⁻[:,:,[1,end]] .= zero(T) : 0; + ϵₛ = maximum((Δx,Δy)) + vel .= @. φ/sqrt(φ^2 + ϵₛ^2*(∇⁺+∇⁻^2)) # Forward (+) & Backward (-) D⁺ʸ .= (D⁺ʸ - φ)/Δy; ~yperiodic ? D⁺ʸ[:,end,:] .= zero(T) : 0; D⁺ˣ .= (D⁺ˣ - φ)/Δx; ~xperiodic ? D⁺ˣ[end,:,:] .= zero(T) : 0; @@ -165,10 +175,21 @@ function compute_Δt(::FirstOrderStencil{D,T},Δ,γ,φ,vel) where {D,T} end """ - struct AdvectionStencil{O} end + struct AdvectionStencil{O} -Wrapper around Stencil and other structs to enable - finite differences on arbitrary order finite elements. +Wrapper to enable finite differencing to solve the +Hamilton-Jacobi evolution equation and reinitialisation equation +on order `O` finite elements in serial or parallel. + +# Parameters + +- `stencil::Stencil`: Finite difference stencil for a single step HJ + equation and reinitialisation equation. +- `model`: A `CartesianDiscreteModel`. +- `space`: FE space for level-set function +- `perm`: A permutation vector +- `params`: Tuple of additional params +- `cache`: Stencil cache """ struct AdvectionStencil{O} stencil :: Stencil @@ -179,6 +200,13 @@ struct AdvectionStencil{O} cache end +""" + AdvectionStencil(stencil::Stencil,model,space,tol,max_steps,max_steps_reinit) + +Create an instance of `AdvectionStencil` given a stencil, model, FE space, and +additional optional arguments. This automatically creates the DoF permutation +to handle high-order finite elements. +""" function AdvectionStencil( stencil::Stencil, model, @@ -223,15 +251,8 @@ end Gridap.ReferenceFEs.get_order(f::Gridap.Fields.LinearCombinationFieldVector) = get_order(f.fields) -""" - create_dof_permutation( - model::CartesianDiscreteModel{Dc}, - space::UnconstrainedFESpace, - order::Integer) where Dc -> n2o_dof_map - -Create dof permutation vector to enable finite differences on - higher order Lagrangian finite elements on a Cartesian mesh. -""" +# Create dof permutation vector to enable finite differences on +# higher order Lagrangian finite elements on a Cartesian mesh. function create_dof_permutation(model::CartesianDiscreteModel{Dc}, space::UnconstrainedFESpace, order::Integer) where Dc @@ -334,6 +355,23 @@ function advect!(s::AdvectionStencil,φh,args...) advect!(s,get_free_dof_values(φh),args...) end +""" + advect!(s::AdvectionStencil{O},φ,vel,γ) where O + +Solve the Hamilton-Jacobi evolution equation using the `AdvectionStencil`. + +# Hamilton-Jacobi evolution equation +``\\frac{\\partial\\phi}{\\partial t} + V(\\boldsymbol{x})\\lVert\\boldsymbol{\\nabla}\\phi\\rVert = 0,`` + +with ``\\phi(0,\\boldsymbol{x})=\\phi_0(\\boldsymbol{x})`` and ``\\boldsymbol{x}\\in D,~t\\in(0,T)``. + +# Arguments + +- `s::AdvectionStencil{O}`: Stencil for computation +- `φ`: level set function as a vector of degrees of freedom +- `vel`: the normal velocity as a vector of degrees of freedom +- `γ`: coeffient on the time step size. +""" function advect!(s::AdvectionStencil{O},φ::PVector,vel::PVector,γ) where O _, _, perm_caches, stencil_cache = s.cache Δ, isperiodic, = s.params.Δ, s.params.isperiodic @@ -381,6 +419,22 @@ function reinit!(s::AdvectionStencil,φh,args...) reinit!(s,get_free_dof_values(φh),args...) end +""" + reinit!(s::AdvectionStencil{O},φ,γ) where O + +Solve the reinitialisation equation using the `AdvectionStencil`. + +# Reinitialisation equation +``\\frac{\\partial\\phi}{\\partial t} + \\mathrm{sign}(\\phi_0)(\\lVert\\boldsymbol{\\nabla}\\phi\\rVert-1) = 0,`` + +with ``\\phi(0,\\boldsymbol{x})=\\phi_0(\\boldsymbol{x})`` and ``\\boldsymbol{x}\\in D,~t\\in(0,T)``. + +# Arguments + +- `s::AdvectionStencil{O}`: Stencil for computation +- `φ`: level set function as a vector of degrees of freedom +- `γ`: coeffient on the time step size. +""" function reinit!(s::AdvectionStencil{O},φ::PVector,γ) where O φ_tmp, vel_tmp, perm_caches, stencil_cache = s.cache Δ, isperiodic, ndof = s.params.Δ, s.params.isperiodic, s.params.ndof @@ -388,9 +442,6 @@ function reinit!(s::AdvectionStencil{O},φ::PVector,γ) where O _φ = (O >= 2) ? permute!(perm_caches[1],φ,s.perm) : φ - # Compute approx sign function S - vel_tmp = _φ ./ sqrt.(_φ .* _φ .+ prod(Δ)) - ## CFL Condition (requires γ≤0.5). Note inform(vel_tmp) = 1.0 Δt = compute_Δt(s.stencil,Δ,γ,_φ,1.0) @@ -425,9 +476,6 @@ function reinit!(s::AdvectionStencil{O},φ::Vector,γ) where O _φ = (O >= 2) ? permute!(perm_caches[1],φ,s.perm) : φ - # Compute approx sign function S - vel_tmp .= _φ ./ sqrt.(_φ .* _φ .+ prod(Δ)) - ## CFL Condition (requires γ≤0.5) Δt = compute_Δt(s.stencil,Δ,γ,_φ,1.0) @@ -441,8 +489,8 @@ function reinit!(s::AdvectionStencil{O},φ::Vector,γ) where O reinit!(s.stencil,φ_tmp_mat,φ_mat,vel_tmp_mat,Δt,Δ,isperiodic,stencil_cache) # Compute error - _φ .-= φ_tmp # φ - φ_tmp - err = maximum(abs,_φ) # Ghosts not needed yet: partial maximums computed using owned values only. + _φ .-= φ_tmp + err = maximum(abs,_φ) step += 1 # Update φ diff --git a/src/Benchmarks.jl b/src/Benchmarks.jl index 358d0bae..d59ff5bd 100644 --- a/src/Benchmarks.jl +++ b/src/Benchmarks.jl @@ -1,19 +1,24 @@ -function process_timer(t::PTimer) - data = t.data - map_main(data) do data - times = map(x -> x.max,values(data)) - process_timer(times) - end |> PartitionedArrays.getany -end +""" + benchmark(f, args, ranks; nreps, reset!) -function process_timer(t::Vector) - # return length(t), maximum(t), minimum(t), sum(t)/length(t) - return t -end +Benchmark a function `f` that takes arguments `args`. + +In MPI mode, benchmark will always return the maximum CPU time across all ranks. +This behaviour can be changed by overwritting `process_timer`. + +# Important +The input `ranks` allows the user to provide the MPI ranks, `benchmark` +will not function correctly in MPI mode if these are not supplied. In serial, +set `ranks = nothing`. +# Optional + +- `nreps = 10`: Number of benchmark repetitions +- `reset!= (x...) -> nothing`: Function for resetting inital data (e.g., level-set function ``\\varphi``). +""" function benchmark(f, args, ranks::Nothing; nreps = 10, reset! = (x...) -> nothing) t = zeros(Float64,nreps) - println("<------------- Compilation ------------->") + println("<------------- Compilation ------------->") f(args...) println("<------------- Benchmarking ------------->") for i in 1:nreps @@ -26,7 +31,7 @@ end function benchmark(f, args, ranks; nreps = 10, reset! = (x...) -> nothing) t = PTimer(ranks) - i_am_main(ranks) && println("<------------- Compilation ------------->") + i_am_main(ranks) && println("<------------- Compilation ------------->") f(args...) i_am_main(ranks) && println("<------------- Benchmarking ------------->") for i in 1:nreps @@ -39,6 +44,25 @@ function benchmark(f, args, ranks; nreps = 10, reset! = (x...) -> nothing) return process_timer(t) end +function process_timer(t::Vector) + # return length(t), maximum(t), minimum(t), sum(t)/length(t) + return t +end + +function process_timer(t::PTimer) + data = t.data + map_main(data) do data + times = map(x -> x.max,values(data)) + process_timer(times) + end |> PartitionedArrays.getany +end + +## Standard benchmarks +""" + benchmark_optimizer(m::Optimiser, niter, ranks; nreps) + +Given an optimiser `m`, benchmark `niter` iterations. +""" function benchmark_optimizer(m::Optimiser, niter, ranks; nreps = 10) function f(m) _, state = iterate(m) @@ -55,9 +79,15 @@ function benchmark_optimizer(m::Optimiser, niter, ranks; nreps = 10) return benchmark(f, (m,), ranks; nreps, reset! = opt_reset!) end +""" + benchmark_forward_problem(m::AbstractFEStateMap, φh, ranks; nreps) + +Benchmark the forward FE solve given `m::AbstractFEStateMap` and a level-set +function `φh`. See [`forward_solve!`](@ref) for input types. +""" function benchmark_forward_problem(m::AbstractFEStateMap, φh, ranks; nreps = 10) function f(m, φh) - forward_solve(m,φh) + forward_solve!(m,φh) end function reset!(m,φh) u = get_free_dof_values(get_state(m)); @@ -66,6 +96,13 @@ function benchmark_forward_problem(m::AbstractFEStateMap, φh, ranks; nreps = 10 return benchmark(f, (m,φh), ranks; nreps) end +""" + benchmark_advection(stencil::AdvectionStencil, φ0, v0, γ, ranks; nreps) + +Benchmark solving the Hamilton-Jacobi evolution equation given a `stencil`, +level-set function `φ0`, velocity function `v0`, and time step coefficient `γ`. +See [`advect!`](@ref) for input types. +""" function benchmark_advection(stencil::AdvectionStencil, φ0, v0, γ, ranks; nreps = 10) function f(stencil,φ,v,γ) advect!(stencil,φ,v,γ) @@ -79,6 +116,12 @@ function benchmark_advection(stencil::AdvectionStencil, φ0, v0, γ, ranks; nrep return benchmark(f, (stencil,φ,v,γ), ranks; nreps, reset!) end +""" + benchmark_reinitialisation(stencil::AdvectionStencil, φ0, γ_reinit, ranks; nreps) + +Benchmark solving the reinitialisation equation given a `stencil`, level-set function +`φ0`, and time step coefficient `γ`. See [`reinit!`](@ref) for input types. +""" function benchmark_reinitialisation(stencil::AdvectionStencil, φ0, γ_reinit, ranks; nreps = 10) function f(stencil,φ,γ_reinit) reinit!(stencil,φ,γ_reinit) @@ -90,6 +133,12 @@ function benchmark_reinitialisation(stencil::AdvectionStencil, φ0, γ_reinit, r return benchmark(f, (stencil,φ,γ_reinit), ranks; nreps, reset!) end +""" + benchmark_velocity_extension(ext::VelocityExtension, v0, ranks; nreps) + +Benchmark the Hilbertian velocity-extension method `ext` given a RHS `v0`. +See [`project!`](@ref) for input types. +""" function benchmark_velocity_extension(ext::VelocityExtension, v0, ranks; nreps = 10) function f(ext,v) project!(ext,v) @@ -101,6 +150,13 @@ function benchmark_velocity_extension(ext::VelocityExtension, v0, ranks; nreps = return benchmark(f, (ext,v), ranks; nreps, reset!) end +""" + benchmark_hilbertian_projection_map(m::HilbertianProjectionMap, dV, C, dC, K, ranks; nreps) + +Benchmark `update_descent_direction!` for `HilbertianProjectionMap` given a objective +sensitivity `dV`, constraint values C, constraint sensitivities `dC`, and stiffness +matrix `K` for the velocity-extension. +""" function benchmark_hilbertian_projection_map(m::HilbertianProjectionMap, dV, C, dC, K, ranks; nreps = 10) function f(m,dV,C,dC,K) update_descent_direction!(m,dV,C,dC,K) diff --git a/src/ChainRules.jl b/src/ChainRules.jl index f3cc631f..900d35db 100644 --- a/src/ChainRules.jl +++ b/src/ChainRules.jl @@ -1,17 +1,43 @@ """ - IntegrandWithMeasure + struct IntegrandWithMeasure{A,B<:Tuple} - Enables partial differentation of an integrand F via Gridap.gradient. +A wrapper to enable serial or parallel partial differentation of an +integral `F` using `Gridap.gradient`. This is required to allow automatic +differentation with `DistributedMeasure`. + +# Properties +- `F :: A`: A function that returns a `DomainContribution` or `DistributedDomainContribution`. +- `dΩ :: B`: A tuple of measures. """ struct IntegrandWithMeasure{A,B<:Tuple} F :: A dΩ :: B end +""" + (F::IntegrandWithMeasure)(args...) + +Evaluate `F.F` given arguments `args`. +""" (F::IntegrandWithMeasure)(args...) = F.F(args...,F.dΩ...) -Gridap.gradient(F::IntegrandWithMeasure,uh) = Gridap.gradient(F,[uh],1) +""" + Gridap.gradient(F::IntegrandWithMeasure,uh::Vector,K::Int) + +Given an an `IntegrandWithMeasure` `F` and a vector of `FEFunctions` `uh` (excluding measures) +evaluate the partial derivative of `F.F` with respect to `uh[K]`. + +# Example +Suppose `uh` and `φh` are FEFunctions with measures `dΩ` and `dΓ_N`. +Then the partial derivative of a function `J` wrt to `φh` is computed via +```` +J(u,φ,dΩ,dΓ_N) = ∫(f(u,φ))dΩ + ∫(g(u,φ))dΓ_N +J_iwm = IntegrandWithMeasure(J,(dΩ,dΓ_N)) +∂J∂φh = ∇(J_iwm,[uh,φh],2) +```` +where `f` and `g` are user defined. +""" function Gridap.gradient(F::IntegrandWithMeasure,uh::Vector{<:FEFunction},K::Int) @check 0 < K <= length(uh) _f(uk) = F.F(uh[1:K-1]...,uk,uh[K+1:end]...,F.dΩ...) @@ -29,6 +55,14 @@ function Gridap.gradient(F::IntegrandWithMeasure,uh::Vector,K::Int) return DistributedDomainContribution(contribs) end +Gridap.gradient(F::IntegrandWithMeasure,uh) = Gridap.gradient(F,[uh],1) + +""" + Gridap.jacobian(F::IntegrandWithMeasure,uh::Vector,K::Int) + +Given an an `IntegrandWithMeasure` `F` and a vector of `FEFunctions` or `CellField` `uh` +(excluding measures) evaluate the Jacobian `F.F` with respect to `uh[K]`. +""" function Gridap.jacobian(F::IntegrandWithMeasure,uh::Vector{<:Union{FEFunction,CellField}},K::Int) @check 0 < K <= length(uh) _f(uk) = F.F(uh[1:K-1]...,uk,uh[K+1:end]...,F.dΩ...) @@ -46,6 +80,8 @@ function Gridap.jacobian(F::IntegrandWithMeasure,uh::Vector,K::Int) return DistributedDomainContribution(contribs) end +Gridap.jacobian(F::IntegrandWithMeasure,uh) = Gridap.jacobian(F,[uh],1) + function GridapDistributed.to_parray_of_arrays(a::NTuple{N,T}) where {N,T<:DebugArray} indices = linear_indices(first(a)) map(indices) do i @@ -65,17 +101,19 @@ function GridapDistributed.to_parray_of_arrays(a::NTuple{N,T}) where {N,T<:MPIAr end """ - StateParamIntegrandWithMeasure + struct StateParamIntegrandWithMeasure{A<:IntegrandWithMeasure,B,C,D} - Assume that we have a IntegrandWithMeasure of the following form: - F: (u,φ,[dΩ₁,dΩ₂,...]) ↦ ∫_Ω₁ f(u(φ),φ) dΩ₁ + ∫_Ω₂ g(u(φ),φ) dΩ₂ + ..., - where u is a state field and φ is auxilary. - - Assumptions: - - The arguments to F match the weak form. - - The argument u is the solution to an FE problem. This can be a single field or multifield. - - There is a single auxilary field. Again, this can possibly be a MultiFieldFEFunction. - E.g., multiple level set functions. +A wrapper to handle partial differentation of an [`IntegrandWithMeasure`](@ref) +of a specific form (see below) in a `ChainRules.jl` compatible way with caching. + +# Assumptions + +We assume that we have a `IntegrandWithMeasure` of the following form: + +`F(u,φ,dΩ₁,dΩ₂,...) = ∫(f(u,φ))dΩ₁ + ∫(g(u,φ))dΩ₂ + ...,`. + +where `u` and `φ` are each expected to inherit from `Union{FEFunction,MultiFieldFEFunction}` +or the GridapDistributed equivalent. """ struct StateParamIntegrandWithMeasure{A<:IntegrandWithMeasure,B,C,D} F :: A @@ -84,22 +122,33 @@ struct StateParamIntegrandWithMeasure{A<:IntegrandWithMeasure,B,C,D} caches :: D end +""" + StateParamIntegrandWithMeasure(F::IntegrandWithMeasure,U::FESpace,V_φ::FESpace, + U_reg::FESpace,assem_U::Assembler,assem_deriv::Assembler) + +Create an instance of `StateParamIntegrandWithMeasure`. +""" function StateParamIntegrandWithMeasure( F::IntegrandWithMeasure, U::FESpace,V_φ::FESpace,U_reg::FESpace, assem_U::Assembler,assem_deriv::Assembler ) φ₀, u₀ = zero(V_φ), zero(U) - djdu_vecdata = collect_cell_vector(U,∇(F,[u₀,φ₀],1)) - djdφ_vecdata = collect_cell_vector(U_reg,∇(F,[u₀,φ₀],2)) - djdu_vec = allocate_vector(assem_U,djdu_vecdata) - djdφ_vec = allocate_vector(assem_deriv,djdφ_vecdata) + ∂j∂u_vecdata = collect_cell_vector(U,∇(F,[u₀,φ₀],1)) + ∂j∂φ_vecdata = collect_cell_vector(U_reg,∇(F,[u₀,φ₀],2)) + ∂j∂u_vec = allocate_vector(assem_U,∂j∂u_vecdata) + ∂j∂φ_vec = allocate_vector(assem_deriv,∂j∂φ_vecdata) assems = (assem_U,assem_deriv) spaces = (U,V_φ,U_reg) - caches = (djdu_vec,djdφ_vec) + caches = (∂j∂u_vec,∂j∂φ_vec) return StateParamIntegrandWithMeasure(F,spaces,assems,caches) end +""" + (u_to_j::StateParamIntegrandWithMeasure)(uh,φh) + +Evaluate the `StateParamIntegrandWithMeasure` at parameters `uh` and `φh`. +""" (u_to_j::StateParamIntegrandWithMeasure)(uh,φh) = sum(u_to_j.F(uh,φh)) function (u_to_j::StateParamIntegrandWithMeasure)(u::AbstractVector,φ::AbstractVector) @@ -109,23 +158,30 @@ function (u_to_j::StateParamIntegrandWithMeasure)(u::AbstractVector,φ::Abstract return u_to_j(uh,φh) end +""" + ChainRulesCore.rrule(u_to_j::StateParamIntegrandWithMeasure,uh,φh) + +Return the evaluation of a `StateParamIntegrandWithMeasure` and a +a function for evaluating the pullback of `u_to_j`. This enables +compatiblity with `ChainRules.jl` +""" function ChainRulesCore.rrule(u_to_j::StateParamIntegrandWithMeasure,uh,φh) F = u_to_j.F U,V_φ,U_reg = u_to_j.spaces assem_U,assem_deriv = u_to_j.assems - djdu_vec,djdφ_vec = u_to_j.caches + ∂j∂u_vec,∂j∂φ_vec = u_to_j.caches function u_to_j_pullback(dj) ## Compute ∂F/∂uh(uh,φh) and ∂F/∂φh(uh,φh) - djdu = ∇(F,[uh,φh],1) - djdu_vecdata = collect_cell_vector(U,djdu) - assemble_vector!(djdu_vec,assem_U,djdu_vecdata) - djdφ = ∇(F,[uh,φh],2) - djdφ_vecdata = collect_cell_vector(U_reg,djdφ) - assemble_vector!(djdφ_vec,assem_deriv,djdφ_vecdata) - djdu_vec .*= dj - djdφ_vec .*= dj - ( NoTangent(), djdu_vec, djdφ_vec ) + ∂j∂u = ∇(F,[uh,φh],1) + ∂j∂u_vecdata = collect_cell_vector(U,∂j∂u) + assemble_vector!(∂j∂u_vec,assem_U,∂j∂u_vecdata) + ∂j∂φ = ∇(F,[uh,φh],2) + ∂j∂φ_vecdata = collect_cell_vector(U_reg,∂j∂φ) + assemble_vector!(∂j∂φ_vec,assem_deriv,∂j∂φ_vecdata) + ∂j∂u_vec .*= dj + ∂j∂φ_vec .*= dj + ( NoTangent(), ∂j∂u_vec, ∂j∂φ_vec ) end return u_to_j(uh,φh), u_to_j_pullback end @@ -138,34 +194,116 @@ function ChainRulesCore.rrule(u_to_j::StateParamIntegrandWithMeasure,u::Abstract end """ - AbstractFEStateMap + abstract type AbstractFEStateMap + +Types inheriting from this abstract type should enable the evaluation and differentiation of +the solution to an FE problem `u` that implicitly depends on an auxiliary parameter `φ`. """ abstract type AbstractFEStateMap end +""" + get_state(m::AbstractFEStateMap) + +Return the solution/state `u` to the FE problem. +""" get_state(::AbstractFEStateMap) = @abstractmethod + +""" + get_measure(m::AbstractFEStateMap) + +Return the measures associated with the FE problem. +""" get_measure(::AbstractFEStateMap) = @abstractmethod + +""" + get_spaces(m::AbstractFEStateMap) + +Return a collection of FE spaces. The first four entires should correspond to +[`get_trial_space`](@ref), [`get_test_space`](@ref), [`get_aux_space`](@ref), and +[`get_deriv_space`](@ref) unless these are overloaded for a particular implementation. +""" get_spaces(::AbstractFEStateMap) = @abstractmethod + +""" + get_assemblers(m::AbstractFEStateMap) + +Return a collection of assemblers. The first two entires should correspond to +[`get_pde_assembler`](@ref) and [`get_deriv_assembler`](@ref) unless these are +overloaded for a particular implementation. +""" get_assemblers(::AbstractFEStateMap) = @abstractmethod +""" + get_trial_space(m::AbstractFEStateMap) + +Return trial space for FE problem. +""" get_trial_space(m::AbstractFEStateMap) = get_spaces(m)[1] + +""" + get_test_space(m::AbstractFEStateMap) + +Return test space for FE problem. +""" get_test_space(m::AbstractFEStateMap) = get_spaces(m)[2] + +""" + get_aux_space(m::AbstractFEStateMap) + +Return space for auxillary parameter. +""" get_aux_space(m::AbstractFEStateMap) = get_spaces(m)[3] + +""" + get_deriv_space(m::AbstractFEStateMap) + +Return space for derivatives. +""" get_deriv_space(m::AbstractFEStateMap) = get_spaces(m)[4] +""" + get_pde_assembler(m::AbstractFEStateMap) + +Return assembler for FE problem. +""" get_pde_assembler(m::AbstractFEStateMap) = get_assemblers(m)[1] + +""" + get_deriv_assembler(m::AbstractFEStateMap) + +Return assembler for derivatives. +""" get_deriv_assembler(m::AbstractFEStateMap) = get_assemblers(m)[2] -@inline (φ_to_u::AbstractFEStateMap)(φh) = forward_solve(φ_to_u,φh) +""" + (φ_to_u::AbstractFEStateMap)(φh) + +Evaluate the forward problem `u` given `φ`. This should compute the +FE problem. +""" +@inline (φ_to_u::AbstractFEStateMap)(φh) = forward_solve!(φ_to_u,φh) -function forward_solve(φ_to_u::AbstractFEStateMap,φh) +""" + forward_solve!(φ_to_u::AbstractFEStateMap,φh) + +Evaluate the forward problem `u` given `φ`. This should compute the +FE problem. +""" +function forward_solve!(φ_to_u::AbstractFEStateMap,φh) @abstractmethod end -function forward_solve(φ_to_u::AbstractFEStateMap,φ::AbstractVector) +function forward_solve!(φ_to_u::AbstractFEStateMap,φ::AbstractVector) φh = FEFunction(get_aux_space(φ_to_u),φ) - return forward_solve(φ_to_u,φh) + return forward_solve!(φ_to_u,φh) end +""" + update_adjoint_caches!(φ_to_u::AbstractFEStateMap,uh,φh) + +Update the cache for the adjoint problem. This is usually a tuple +of objects. +""" function update_adjoint_caches!(φ_to_u::AbstractFEStateMap,uh,φh) @abstractmethod end @@ -176,10 +314,21 @@ function update_adjoint_caches!(φ_to_u::AbstractFEStateMap,u::AbstractVector,φ return update_adjoint_caches!(φ_to_u,uh,φh) end +""" + adjoint_solve!(φ_to_u::AbstractFEStateMap,du::AbstractVector) + +Evaluate the solution to the adjoint problem given a RHS vector `∂F∂u` denoted `du`. +This should solve the linear problem `dRduᵀ*λ = ∂F∂uᵀ`. +""" function adjoint_solve!(φ_to_u::AbstractFEStateMap,du::AbstractVector) @abstractmethod end +""" + dRdφ(φ_to_u::AbstractFEStateMap,uh,vh,φh) + +Compute the derivative with respect to `φh` of the residual R. +""" function dRdφ(φ_to_u::AbstractFEStateMap,uh,vh,φh) @abstractmethod end @@ -191,8 +340,17 @@ function dRdφ(φ_to_u::AbstractFEStateMap,u::AbstractVector,v::AbstractVector, return dRdφ(φ_to_u,uh,vh,φh) end +""" + pullback(φ_to_u::AbstractFEStateMap,uh,φh,du;updated) + +Compute `∂F∂u*dudφ` at `φh` and `uh` using the adjoint method. I.e., let + +`∂F∂u*dudφ = -λᵀ*dRdφ` + +and solve the adjoint problem `dRduᵀ*λ = ∂F∂uᵀ` using [`adjoint_solve!`](@ref). +""" function pullback(φ_to_u::AbstractFEStateMap,uh,φh,du;updated=false) - dφdu_vec, assem_deriv = φ_to_u.plb_caches + dudφ_vec, assem_deriv = φ_to_u.plb_caches U_reg = get_deriv_space(φ_to_u) ## Adjoint Solve @@ -203,11 +361,11 @@ function pullback(φ_to_u::AbstractFEStateMap,uh,φh,du;updated=false) λh = FEFunction(get_test_space(φ_to_u),λ) ## Compute grad - dφdu_vecdata = collect_cell_vector(U_reg,dRdφ(φ_to_u,uh,λh,φh)) - assemble_vector!(dφdu_vec,assem_deriv,dφdu_vecdata) - rmul!(dφdu_vec, -1) + dudφ_vecdata = collect_cell_vector(U_reg,dRdφ(φ_to_u,uh,λh,φh)) + assemble_vector!(dudφ_vec,assem_deriv,dudφ_vecdata) + rmul!(dudφ_vec, -1) - return (NoTangent(),dφdu_vec) + return (NoTangent(),dudφ_vec) end function pullback(φ_to_u::AbstractFEStateMap,u::AbstractVector,φ::AbstractVector,du::AbstractVector;updated=false) @@ -216,8 +374,15 @@ function pullback(φ_to_u::AbstractFEStateMap,u::AbstractVector,φ::AbstractVect return pullback(φ_to_u,uh,φh,du;updated=updated) end +""" + rrule(φ_to_u::AbstractFEStateMap,φh) + +Return the evaluation of a `AbstractFEStateMap` and a +a function for evaluating the pullback of `φ_to_u`. This enables +compatiblity with `ChainRules.jl` +""" function ChainRulesCore.rrule(φ_to_u::AbstractFEStateMap,φh) - u = forward_solve(φ_to_u,φh) + u = forward_solve!(φ_to_u,φh) uh = FEFunction(get_trial_space(φ_to_u),u) update_adjoint_caches!(φ_to_u,uh,φh) return u, du -> pullback(φ_to_u,uh,φh,du;updated=true) @@ -228,6 +393,12 @@ function ChainRulesCore.rrule(φ_to_u::AbstractFEStateMap,φ::AbstractVector) return ChainRulesCore.rrule(φ_to_u,φh) end +""" + StateParamIntegrandWithMeasure(f::Function,φ_to_u::AbstractFEStateMap) + +Create an instance of `StateParamIntegrandWithMeasure` given a `f` and +`φ_to_u`. +""" function StateParamIntegrandWithMeasure(f::Function,φ_to_u::AbstractFEStateMap) dΩ = get_measure(φ_to_u) F = IntegrandWithMeasure(f,dΩ) @@ -242,7 +413,19 @@ function StateParamIntegrandWithMeasure(F::IntegrandWithMeasure,φ_to_u::Abstrac end """ - AffineFEStateMap + struct AffineFEStateMap{A,B,C,D,E,F} <: AbstractFEStateMap + +A structure to enable the forward problem and pullback for affine finite +element operators `AffineFEOperator`. + +# Parameters + +- `biform::A`: `IntegrandWithMeasure` defining the bilinear form. +- `liform::B`: `IntegrandWithMeasure` defining the linear form. +- `spaces::C`: `Tuple` of finite element spaces. +- `plb_caches::D`: A cache for the pullback operator. +- `fwd_caches::E`: A cache for the forward problem. +- `adj_caches::F`: A cache for the adjoint problem. """ struct AffineFEStateMap{A,B,C,D,E,F} <: AbstractFEStateMap biform :: A @@ -252,7 +435,24 @@ struct AffineFEStateMap{A,B,C,D,E,F} <: AbstractFEStateMap fwd_caches :: E adj_caches :: F - function AffineFEStateMap( + @doc """ + AffineFEStateMap( + a::Function,l::Function, + U,V,V_φ,U_reg,φh,dΩ...; + assem_U = SparseMatrixAssembler(U,V), + assem_adjoint = SparseMatrixAssembler(V,U), + assem_deriv = SparseMatrixAssembler(U_reg,U_reg), + ls::LinearSolver = LUSolver(), + adjoint_ls::LinearSolver = LUSolver() + ) + + Create an instance of `AffineFEStateMap` given the bilinear form `a` and linear + form `l` as `Function` types, trial and test spaces `U` and `V`, the FE space `V_φ` + for `φh`, the FE space `U_reg` for derivatives, and the measures as additional arguments. + + Optional arguments enable specification of assemblers and linear solvers. + """ + function AffineFEStateMap( a::Function,l::Function, U,V,V_φ,U_reg,φh,dΩ...; assem_U = SparseMatrixAssembler(U,V), @@ -270,8 +470,8 @@ struct AffineFEStateMap{A,B,C,D,E,F} <: AbstractFEStateMap ## Pullback cache uhd = zero(U) vecdata = collect_cell_vector(U_reg,∇(biform,[uhd,uhd,φh],3) - ∇(liform,[uhd,φh],2)) - dφdu_vec = allocate_vector(assem_deriv,vecdata) - plb_caches = (dφdu_vec,assem_deriv) + dudφ_vec = allocate_vector(assem_deriv,vecdata) + plb_caches = (dudφ_vec,assem_deriv) ## Forward cache op = AffineFEOperator((u,v)->biform(u,v,φh),v->liform(v,φh),U,V,assem_U) @@ -298,7 +498,7 @@ get_measure(m::AffineFEStateMap) = m.biform.dΩ get_spaces(m::AffineFEStateMap) = m.spaces get_assemblers(m::AffineFEStateMap) = (m.fwd_caches[6],m.plb_caches[2],m.adj_caches[4]) -function forward_solve(φ_to_u::AffineFEStateMap,φh) +function forward_solve!(φ_to_u::AffineFEStateMap,φh) biform, liform = φ_to_u.biform, φ_to_u.liform U, V, _, _ = φ_to_u.spaces ns, K, b, x, uhd, assem_U = φ_to_u.fwd_caches @@ -319,7 +519,7 @@ end function update_adjoint_caches!(φ_to_u::AffineFEStateMap,uh,φh) adjoint_ns, adjoint_K, _, assem_adjoint = φ_to_u.adj_caches U, V, _, _ = φ_to_u.spaces - assemble_matrix!((u,v) -> φ_to_u.biform(v,u,φh),adjoint_K,assem_adjoint,V,U) + assemble_matrix!((u,v) -> φ_to_u.biform(v,u,φh),adjoint_K,assem_adjoint,V,U) # TODO: @Jordi, should this be `assemble_adjoint_matrix!`? numerical_setup!(adjoint_ns,adjoint_K) return φ_to_u.adj_caches end @@ -331,7 +531,19 @@ function adjoint_solve!(φ_to_u::AffineFEStateMap,du::AbstractVector) end """ - NonlinearFEStateMap + struct NonlinearFEStateMap{A,B,C,D,E,F} <: AbstractFEStateMap + +A structure to enable the forward problem and pullback for nonlinear finite +element operators. + +# Parameters + +- `res::A`: an `IntegrandWithMeasure` defining the residual of the problem. +- `jac::B`: a `Function` defining Jacobian of the residual. +- `spaces::C`: `Tuple` of finite element spaces. +- `plb_caches::D`: A cache for the pullback operator. +- `fwd_caches::E`: A cache for the forward problem. +- `adj_caches::F`: A cache for the adjoint problem. """ struct NonlinearFEStateMap{A,B,C,D,E,F} <: AbstractFEStateMap res :: A @@ -341,6 +553,22 @@ struct NonlinearFEStateMap{A,B,C,D,E,F} <: AbstractFEStateMap fwd_caches :: E adj_caches :: F + @doc """ + NonlinearFEStateMap( + res::Function,U,V,V_φ,U_reg,φh,dΩ...; + assem_U = SparseMatrixAssembler(U,V), + assem_adjoint = SparseMatrixAssembler(V,U), + assem_deriv = SparseMatrixAssembler(U_reg,U_reg), + nls::NonlinearSolver = NewtonSolver(LUSolver();maxiter=50,rtol=1.e-8,verbose=true), + adjoint_ls::LinearSolver = LUSolver() + ) + + Create an instance of `NonlinearFEStateMap` given the residual `res` as a `Function` type, + trial and test spaces `U` and `V`, the FE space `V_φ` for `φh`, the FE space `U_reg` + for derivatives, and the measures as additional arguments. + + Optional arguments enable specification of assemblers, nonlinear solver, and adjoint (linear) solver. + """ function NonlinearFEStateMap( res::Function,U,V,V_φ,U_reg,φh,dΩ...; assem_U = SparseMatrixAssembler(U,V), @@ -356,8 +584,8 @@ struct NonlinearFEStateMap{A,B,C,D,E,F} <: AbstractFEStateMap ## Pullback cache uhd = zero(U) vecdata = collect_cell_vector(U_reg,∇(res,[uhd,uhd,φh],3)) - dφdu_vec = allocate_vector(assem_deriv,vecdata) - plb_caches = (dφdu_vec,assem_deriv) + dudφ_vec = allocate_vector(assem_deriv,vecdata) + plb_caches = (dudφ_vec,assem_deriv) ## Forward cache x = zero_free_values(U) @@ -385,7 +613,7 @@ get_measure(m::NonlinearFEStateMap) = m.res.dΩ get_spaces(m::NonlinearFEStateMap) = m.spaces get_assemblers(m::NonlinearFEStateMap) = (m.fwd_caches[4],m.plb_caches[2],m.adj_caches[4]) -function forward_solve(φ_to_u::NonlinearFEStateMap,φh) +function forward_solve!(φ_to_u::NonlinearFEStateMap,φh) U, V, _, _ = φ_to_u.spaces nls, nls_cache, x, assem_U = φ_to_u.fwd_caches @@ -417,18 +645,55 @@ function adjoint_solve!(φ_to_u::NonlinearFEStateMap,du::AbstractVector) end """ - RepeatingAffineFEStateMap - #TODO: please give me a better name + struct RepeatingAffineFEStateMap <: AbstractFEStateMap + +A structure to enable the forward problem and pullback for affine finite +element operators `AffineFEOperator` with multiple linear forms but only +a single bilinear form. + +# Parameters + +- `biform`: `IntegrandWithMeasure` defining the bilinear form. +- `liform`: A vector of `IntegrandWithMeasure` defining the linear forms. +- `spaces`: Repeated finite element spaces. +- `spaces_0`: Original finite element spaces that are being repeated. +- `plb_caches`: A cache for the pullback operator. +- `fwd_caches`: A cache for the forward problem. +- `adj_caches`: A cache for the adjoint problem. """ -struct RepeatingAffineFEStateMap{A,B,C,D,E,F} <: AbstractFEStateMap +struct RepeatingAffineFEStateMap{A,B,C,D,E,F,G} <: AbstractFEStateMap biform :: A liform :: B spaces :: C - plb_caches :: D - fwd_caches :: E - adj_caches :: F - - function RepeatingAffineFEStateMap( + spaces_0 :: D + plb_caches :: E + fwd_caches :: F + adj_caches :: G + + @doc """ + RepeatingAffineFEStateMap( + nblocks::Int,a::Function,l::Vector{<:Function}, + U0,V0,V_φ,U_reg,φh,dΩ...; + assem_U = SparseMatrixAssembler(U0,V0), + assem_adjoint = SparseMatrixAssembler(V0,U0), + assem_deriv = SparseMatrixAssembler(U_reg,U_reg), + ls::LinearSolver = LUSolver(), + adjoint_ls::LinearSolver = LUSolver() + ) + + Create an instance of `RepeatingAffineFEStateMap` given the number of blocks `nblocks`, + a bilinear form `a`, a vector of linear form `l` as `Function` types, the trial and test + spaces `U` and `V`, the FE space `V_φ` for `φh`, the FE space `U_reg` for derivatives, + and the measures as additional arguments. + + Optional arguments enable specification of assemblers and linear solvers. + + # Note + + - The resulting `FEFunction` will be a `MultiFieldFEFunction` (or GridapDistributed equivalent) + where each field corresponds to an entry in the vector of linear forms + """ + function RepeatingAffineFEStateMap( nblocks::Int,a::Function,l::Vector{<:Function}, U0,V0,V_φ,U_reg,φh,dΩ...; assem_U = SparseMatrixAssembler(U0,V0), @@ -439,10 +704,10 @@ struct RepeatingAffineFEStateMap{A,B,C,D,E,F} <: AbstractFEStateMap ) @check nblocks == length(l) - biform = IntegrandWithMeasure(a,dΩ) + spaces_0 = (U0,V0) + biform = IntegrandWithMeasure(a,dΩ) liforms = map(li -> IntegrandWithMeasure(li,dΩ),l) - U = MultiFieldFESpace([U0 for i in 1:nblocks];style=BlockMultiFieldStyle()) - V = MultiFieldFESpace([V0 for i in 1:nblocks];style=BlockMultiFieldStyle()) + U, V = repeat_spaces(nblocks,U0,V0) spaces = (U,V,V_φ,U_reg) ## Pullback cache @@ -451,8 +716,8 @@ struct RepeatingAffineFEStateMap{A,B,C,D,E,F} <: AbstractFEStateMap for liform in liforms contr = contr - ∇(liform,[uhd,φh],2) end - dφdu_vec = allocate_vector(assem_deriv,collect_cell_vector(U_reg,contr)) - plb_caches = (dφdu_vec,assem_deriv) + dudφ_vec = allocate_vector(assem_deriv,collect_cell_vector(U_reg,contr)) + plb_caches = (dudφ_vec,assem_deriv) ## Forward cache K = assemble_matrix((u,v) -> biform(u,v,φh),assem_U,U0,V0) @@ -468,22 +733,38 @@ struct RepeatingAffineFEStateMap{A,B,C,D,E,F} <: AbstractFEStateMap adjoint_ns = numerical_setup(symbolic_setup(adjoint_ls,adjoint_K),adjoint_K) adj_caches = (adjoint_ns,adjoint_K,adjoint_x,assem_adjoint) - A,B,C = typeof(biform), typeof(liforms), typeof(spaces) - D,E,F = typeof(plb_caches), typeof(fwd_caches), typeof(adj_caches) - return new{A,B,C,D,E,F}(biform,liforms,spaces,plb_caches,fwd_caches,adj_caches) + A,B,C,D = typeof(biform), typeof(liforms), typeof(spaces), typeof(spaces_0) + E,F,G = typeof(plb_caches), typeof(fwd_caches), typeof(adj_caches) + return new{A,B,C,D,E,F,G}(biform,liforms,spaces,spaces_0,plb_caches,fwd_caches,adj_caches) end end +function repeat_spaces(nblocks::Integer,U0::FESpace,V0::FESpace) + U = MultiFieldFESpace([U0 for i in 1:nblocks];style=BlockMultiFieldStyle()) + V = MultiFieldFESpace([V0 for i in 1:nblocks];style=BlockMultiFieldStyle()) + return U,V +end + +function repeat_spaces( + nblocks::Integer,U0::T,V0::T +) where T <: Union{MultiField.MultiFieldFESpace,GridapDistributed.DistributedMultiFieldFESpace} + nfields = num_fields(U0) + @assert nfields == num_fields(V0) + mfs = BlockMultiFieldStyle(nblocks,Tuple(fill(nfields,nblocks))) + U = MultiFieldFESpace(repeat([U0...],nblocks);style=mfs) + V = MultiFieldFESpace(repeat([V0...],nblocks);style=mfs) + return U,V +end + get_state(m::RepeatingAffineFEStateMap) = FEFunction(get_trial_space(m),m.fwd_caches[4]) get_measure(m::RepeatingAffineFEStateMap) = m.biform.dΩ get_spaces(m::RepeatingAffineFEStateMap) = m.spaces get_assemblers(m::RepeatingAffineFEStateMap) = (m.fwd_caches[6],m.plb_caches[2],m.adj_caches[4]) -function forward_solve(φ_to_u::RepeatingAffineFEStateMap,φh) +function forward_solve!(φ_to_u::RepeatingAffineFEStateMap,φh) biform, liforms = φ_to_u.biform, φ_to_u.liform - U, V, _, _ = φ_to_u.spaces + U0, V0 = φ_to_u.spaces_0 ns, K, b, x, uhd, assem_U, b0 = φ_to_u.fwd_caches - U0, V0 = first(U), first(V) a_fwd(u,v) = biform(u,v,φh) assemble_matrix!(a_fwd,K,assem_U,U0,V0) @@ -506,7 +787,7 @@ end function dRdφ(φ_to_u::RepeatingAffineFEStateMap,uh,vh,φh) biform, liforms = φ_to_u.biform, φ_to_u.liform - res = DomainContribution() # TODO: This will blow up in parallel, needs trick from ODE refactoring branch + res = DomainContribution() for (liform,uhi,vhi) in zip(liforms,uh,vh) res = res + ∇(biform,[uhi,vhi,φh],3) - ∇(liform,[vhi,φh],2) end @@ -515,8 +796,7 @@ end function update_adjoint_caches!(φ_to_u::RepeatingAffineFEStateMap,uh,φh) adjoint_ns, adjoint_K, _, assem_adjoint = φ_to_u.adj_caches - U, V, _, _ = φ_to_u.spaces - U0, V0 = first(U), first(V) + U0, V0 = φ_to_u.spaces_0 assemble_matrix!((u,v) -> φ_to_u.biform(v,u,φh),adjoint_K,assem_adjoint,V0,U0) numerical_setup!(adjoint_ns,adjoint_K) return φ_to_u.adj_caches @@ -531,7 +811,55 @@ function adjoint_solve!(φ_to_u::RepeatingAffineFEStateMap,du::AbstractVector) end """ - PDEConstrainedFunctionals + struct PDEConstrainedFunctionals{N,A} + +An object that computes the objective, constraints, and their derivatives. + +# Implementation + +This implementation computes derivatives of a integral quantity + +``F(u(\\varphi),\\varphi,\\mathrm{d}\\Omega_1,\\mathrm{d}\\Omega_2,...) = +\\Sigma_{i}\\int_{\\Omega_i} f_i(\\varphi)~\\mathrm{d}\\Omega`` + +with respect to an auxiliary parameter ``\\varphi`` where ``u`` +is the solution to a PDE and implicitly depends on ``\\varphi``. +This requires two pieces of information: + + 1) Computation of ``\\frac{\\partial F}{\\partial u}`` and + ``\\frac{\\partial F}{\\partial \\varphi}`` (handled by [`StateParamIntegrandWithMeasure `](@ref)). + 2) Computation of ``\\frac{\\partial F}{\\partial u} + \\frac{\\partial u}{\\partial \\varphi}`` at ``\\varphi`` and ``u`` + using the adjoint method (handled by [`AbstractFEStateMap`](@ref)). I.e., let + + ``\\frac{\\partial F}{\\partial u} + \\frac{\\partial u}{\\partial \\varphi} = -\\lambda^\\intercal + \\frac{\\partial \\mathcal{R}}{\\partial \\varphi}`` + + where ``\\mathcal{R}`` is the residual and solve the (linear) adjoint + problem: + + ``\\frac{\\partial \\mathcal{R}}{\\partial u}^\\intercal\\lambda = + \\frac{\\partial F}{\\partial u}^\\intercal.`` + +The gradient is then ``\\frac{\\partial F}{\\partial \\varphi} = +\\frac{\\partial F}{\\partial \\varphi} - +\\frac{\\partial F}{\\partial u}\\frac{\\partial u}{\\partial \\varphi}``. + +# Parameters + +- `J`: A `StateParamIntegrandWithMeasure` corresponding to the objective. +- `C`: A vector of `StateParamIntegrandWithMeasure` corresponding to the constraints. +- `dJ`: The DoFs for the objective sensitivity. +- `dC`: The DoFs for each constraint sensitivity. +- `analytic_dJ`: a `Function` for computing the analytic objective sensitivity. +- `analytic_dC`: A vector of `Function` for computing the analytic objective sensitivities. +- `state_map::A`: The state map for the problem. + +# Note + +- If `analytic_dJ = nothing` automatic differentiation will be used. +- If `analytic_dC[i] = nothing` automatic differentiation will be used for `C[i]`. """ struct PDEConstrainedFunctionals{N,A} J @@ -542,7 +870,17 @@ struct PDEConstrainedFunctionals{N,A} analytic_dC state_map :: A - function PDEConstrainedFunctionals( + @doc """ + PDEConstrainedFunctionals(objective::Function,constraints::Vector{<:Function}, + state_map::AbstractFEStateMap;analytic_dJ;analytic_dC) + + Create an instance of `PDEConstrainedFunctionals`. The arguments for the objective + and constraints must follow the specification in [`StateParamIntegrandWithMeasure`](@ref). + By default we use automatic differentation for the objective and all constraints. This + can be disabled by passing the shape derivative as a type `Function` to `analytic_dJ` + and/or entires in `analytic_dC`. + """ + function PDEConstrainedFunctionals( objective :: Function, constraints :: Vector{<:Function}, state_map :: AbstractFEStateMap; @@ -563,17 +901,21 @@ struct PDEConstrainedFunctionals{N,A} end end +""" + PDEConstrainedFunctionals(objective,state_map;analytic_dJ) + +Create an instance of `PDEConstrainedFunctionals` when the problem has no constraints. +""" PDEConstrainedFunctionals(J::Function,state_map::AbstractFEStateMap;analytic_dJ=nothing) = PDEConstrainedFunctionals(J,Function[],state_map;analytic_dJ = analytic_dJ,analytic_dC = Nothing[]) get_state(m::PDEConstrainedFunctionals) = get_state(m.state_map) -function evaluate_functionals!(pcf::PDEConstrainedFunctionals,φ::AbstractVector) - V_φ = get_aux_space(pcf.state_map) - φh = FEFunction(V_φ,φ) - return evaluate_functionals!(pcf,φh) -end +""" + evaluate_functionals!(pcf::PDEConstrainedFunctionals,φh) +Evaluate the objective and constraints at `φh`. +""" function evaluate_functionals!(pcf::PDEConstrainedFunctionals,φh) u = pcf.state_map(φh) U = get_trial_space(pcf.state_map) @@ -581,17 +923,34 @@ function evaluate_functionals!(pcf::PDEConstrainedFunctionals,φh) return pcf.J(uh,φh), map(Ci->Ci(uh,φh),pcf.C) end +function evaluate_functionals!(pcf::PDEConstrainedFunctionals,φ::AbstractVector) + V_φ = get_aux_space(pcf.state_map) + φh = FEFunction(V_φ,φ) + return evaluate_functionals!(pcf,φh) +end + +""" + evaluate_derivatives!(pcf::PDEConstrainedFunctionals,φh) + +Evaluate the derivatives of the objective and constraints at `φh`. +""" function evaluate_derivatives!(pcf::PDEConstrainedFunctionals,φh) _,_,dJ,dC = evaluate!(pcf,φh) return dJ,dC end -function Fields.evaluate!(pcf::PDEConstrainedFunctionals,φ::AbstractVector) +function evaluate_derivatives!(pcf::PDEConstrainedFunctionals,φ::AbstractVector) V_φ = get_aux_space(pcf.state_map) φh = FEFunction(V_φ,φ) - return evaluate!(pcf,φh) + return evaluate_derivatives!(pcf,φh) end +""" + Fields.evaluate!(pcf::PDEConstrainedFunctionals,φh) + +Evaluate the objective and constraints, and their derivatives at +`φh`. +""" function Fields.evaluate!(pcf::PDEConstrainedFunctionals,φh) J, C, dJ, dC = pcf.J,pcf.C,pcf.dJ,pcf.dC analytic_dJ = pcf.analytic_dJ @@ -620,7 +979,6 @@ function Fields.evaluate!(pcf::PDEConstrainedFunctionals,φh) j_val = F(uh,φh) _dF(q) = dF_analytic(q,uh,φh,dΩ...) assemble_vector!(_dF,dF,deriv_assem,U_reg) - dF .*= -1 # <- Take θ=-vn return j_val end j = ∇!(J,dJ,analytic_dJ) @@ -629,6 +987,12 @@ function Fields.evaluate!(pcf::PDEConstrainedFunctionals,φh) return j,c,dJ,dC end +function Fields.evaluate!(pcf::PDEConstrainedFunctionals,φ::AbstractVector) + V_φ = get_aux_space(pcf.state_map) + φh = FEFunction(V_φ,φ) + return evaluate!(pcf,φh) +end + # IO function Base.show(io::IO,object::IntegrandWithMeasure) @@ -644,6 +1008,6 @@ function Base.show(io::IO,object::AbstractFEStateMap) end function Base.show(io::IO,::MIME"text/plain",f::PDEConstrainedFunctionals) - print(io,"$(nameof(typeof(object))): - num_constraints: $(length(object.C))") + print(io,"$(nameof(typeof(f))): + num_constraints: $(length(f.C))") end diff --git a/src/GridapExtensions.jl b/src/GridapExtensions.jl index 6e6edcc6..f209d0f7 100644 --- a/src/GridapExtensions.jl +++ b/src/GridapExtensions.jl @@ -26,6 +26,10 @@ function instantiate_caches(x,nls::NewtonSolver,op::NonlinearOperator) return GridapSolvers.NonlinearSolvers.NewtonCache(A,b,dx,ns) end +function instantiate_caches(x,nls::PETScNonlinearSolver,op::NonlinearOperator) + return GridapPETSc._setup_cache(x,nls,op) +end + # Transpose contributions before assembly transpose_contributions(b::DistributedDomainContribution) = @@ -74,3 +78,18 @@ function Gridap.FESpaces.assemble_matrix_and_vector!( assemble_matrix_and_vector!(A,b,assem,collect_cell_matrix_and_vector(U,V,a(u,v),l(v),uhd)) end +# PETScNonlinearSolver override + +function Gridap.Algebra.solve!(x::T,nls::PETScNonlinearSolver,op::Gridap.Algebra.NonlinearOperator, + cache::GridapPETSc.PETScNonlinearSolverCache{<:T}) where T <: AbstractVector + @check_error_code GridapPETSc.PETSC.SNESSolve(cache.snes[],C_NULL,cache.x_petsc.vec[]) + copy!(x,cache.x_petsc) + cache +end + +# Stuff from ODE refactor + +function (+)(a::Gridap.CellData.DomainContribution,b::GridapDistributed.DistributedDomainContribution) + @assert iszero(Gridap.CellData.num_domains(a)) + return b +end diff --git a/src/Io.jl b/src/Io.jl index 2662db79..db741fbd 100644 --- a/src/Io.jl +++ b/src/Io.jl @@ -1,5 +1,5 @@ """ - save(filename::AbstractString, x) + save(filename::AbstractString, x) Save an object `x` to `filename` as a JLD2 file. @@ -10,7 +10,7 @@ function save(filename::AbstractString, x) end """ - load(filename::AbstractString) + load(filename::AbstractString) Load an object stored in a JLD2 file at `filename`. @@ -21,7 +21,7 @@ function load(filename::AbstractString) end """ - load!(filename::AbstractString, x) + load!(filename::AbstractString, x) Load an object stored in a JLD2 file at `filename` and copy its contents to `x`. @@ -35,7 +35,7 @@ function load!(filename::AbstractString, x) end """ - psave(filename::AbstractString, x) + psave(filename::AbstractString, x) Save a partitioned object `x` to a directory `dir` as a set of JLD2 files corresponding to each part. @@ -51,7 +51,7 @@ function psave(dir::AbstractString, x) end """ - pload(dir::AbstractString, ranks::AbstractArray{<:Integer}) + pload(dir::AbstractString, ranks::AbstractArray{<:Integer}) Load a partitioned object stored in a set of JLD2 files in directory `dir` indexed by MPI ranks `ranks`. @@ -65,7 +65,7 @@ function pload(dir::AbstractString, ranks::AbstractArray{<:Integer}) end """ - pload!(dir::AbstractString, x) + pload!(dir::AbstractString, x) Load a partitioned object stored in a set of JLD2 files in directory `dir` and copy contents to the equivilent object `x`. diff --git a/src/LSTO_Distributed.jl b/src/LevelSetTopOpt.jl similarity index 77% rename from src/LSTO_Distributed.jl rename to src/LevelSetTopOpt.jl index baf12997..39917e95 100644 --- a/src/LSTO_Distributed.jl +++ b/src/LevelSetTopOpt.jl @@ -1,17 +1,21 @@ -module LSTO_Distributed +module LevelSetTopOpt + +using GridapPETSc, GridapPETSc.PETSC +using GridapPETSc: PetscScalar, PetscInt, PETSC, @check_error_code using MPI using BlockArrays, SparseArrays, CircularArrays using LinearAlgebra, SparseMatricesCSR using ChainRulesCore using DelimitedFiles, Printf -using ChaosTools +using ChaosTools: estimate_period using Gridap using Gridap.Helpers, Gridap.Algebra, Gridap.TensorValues using Gridap.Geometry, Gridap.CellData, Gridap.Fields using Gridap.ReferenceFEs, Gridap.FESpaces, Gridap.MultiField using Gridap.Geometry: get_faces +using Gridap: writevtk using GridapDistributed using GridapDistributed: DistributedDiscreteModel, DistributedTriangulation, @@ -19,9 +23,6 @@ using GridapDistributed: DistributedDiscreteModel, DistributedTriangulation, allocate_in_domain, DistributedCellField, DistributedMultiFieldFEBasis, BlockPMatrix, BlockPVector, change_ghost -using GridapPETSc, GridapPETSc.PETSC -using GridapPETSc: PetscScalar, PetscInt, PETSC, @check_error_code - using PartitionedArrays using PartitionedArrays: getany, tuple_of_arrays, matching_ghost_indices @@ -31,6 +32,8 @@ using GridapSolvers.SolverInterfaces: SolverVerboseLevel, SOLVER_VERBOSE_NONE, S using JLD2: save_object, load_object +import Base: + + include("GridapExtensions.jl") include("ChainRules.jl") @@ -43,16 +46,12 @@ export evaluate_functionals! export evaluate_derivatives! include("Utilities.jl") -export gen_lsf -export get_Δ +export SmoothErsatzMaterialInterpolation export update_labels! -export isotropic_2d -export isotropic_3d -export make_dir -export print_history +export initial_lsf +export get_el_Δ +export isotropic_elast_tensor export write_vtk -export save_object -export load_object! include("Advection.jl") export AdvectionStencil @@ -69,25 +68,13 @@ include("VelocityExtension.jl") export VelocityExtension export project! -include("MaterialInterpolation.jl") -export SmoothErsatzMaterialInterpolation - include("Optimisers/Optimisers.jl") -export Optimiser -export OptimiserHistory export get_history export write_history export AugmentedLagrangian export HilbertianProjection -export HPModifiedGramSchmidt include("Benchmarks.jl") -export benchmark_optimizer -export benchmark_forward_problem -export benchmark_advection -export benchmark_reinitialisation -export benchmark_velocity_extension -export benchmark_hilbertian_projection_map include("Io.jl") export save, load, load! diff --git a/src/MaterialInterpolation.jl b/src/MaterialInterpolation.jl deleted file mode 100644 index 7ac7bd4c..00000000 --- a/src/MaterialInterpolation.jl +++ /dev/null @@ -1,37 +0,0 @@ -""" - struct SmoothErsatzMaterialInterpolation{M<:AbstractFloat} end - -Parameters and methods used for interpolating material properties across - a boundary. -""" -Base.@kwdef struct SmoothErsatzMaterialInterpolation{M<:AbstractFloat} - η::M # Smoothing radius - ϵₘ::M = 10^-3 # Void material multiplier - H = x -> H_η(x,η) - DH = x -> DH_η(x,η) - I = φ -> (1 - H(φ)) + ϵₘ*H(φ) - ρ = φ -> 1 - H(φ) -end - -# Heaviside function and its derivative -function H_η(t,η) - M = typeof(η*t) - if t<-η - return zero(M) - elseif abs(t)<=η - return 1/2*(1+t/η+1/pi*sin(pi*t/η)) - elseif t>η - return one(M) - end -end - -function DH_η(t,η) - M = typeof(η*t) - if t<-η - return zero(M) - elseif abs(t)<=η - return 1/2/η*(1+cos(pi*t/η)) - elseif t>η - return zero(M) - end -end diff --git a/src/Optimisers/AugmentedLagrangian.jl b/src/Optimisers/AugmentedLagrangian.jl index d9337cbd..fb9d74fe 100644 --- a/src/Optimisers/AugmentedLagrangian.jl +++ b/src/Optimisers/AugmentedLagrangian.jl @@ -1,5 +1,26 @@ """ - struct AugmentedLagrangian{N,O} <: Optimiser end + struct AugmentedLagrangian{N,O} <: Optimiser + +An augmented Lagrangian method based on Nocedal and Wright, 2006 +([link](https://doi.org/10.1007/978-0-387-40065-5)). Note that +this method will function as a Lagrangian method if no constraints +are defined in `problem::PDEConstrainedFunctionals`. + +# Parameters + +- `problem::PDEConstrainedFunctionals{N}`: The objective and constraint setup. +- `stencil::AdvectionStencil{O}`: The finite difference stencil for + solving the evolution and reinitisation equations. +- `vel_ext::VelocityExtension`: The velocity-extension method for extending + shape sensitivities onto the computational domain. +- `history::OptimiserHistory{Float64}`: Historical information for optimisation problem. +- `converged::Function`: A function to check optimiser convergence. +- `has_oscillations::Function`: A function to check for oscillations. +- `params::NamedTuple`: Optimisation parameters. + +The `has_oscillations` function has been added to avoid oscillations in the +iteration history. By default this uses a mean zero crossing algorithm as implemented +in ChaosTools. Oscillations checking can be disabled by taking `has_oscillations = (args...) -> false`. """ struct AugmentedLagrangian{N,O} <: Optimiser problem :: PDEConstrainedFunctionals{N} @@ -10,23 +31,69 @@ struct AugmentedLagrangian{N,O} <: Optimiser has_oscillations :: Function params :: NamedTuple φ0 # TODO: Please remove me + + @doc """ + AugmentedLagrangian( + problem :: PDEConstrainedFunctionals{N}, + stencil :: AdvectionStencil{O}, + vel_ext :: VelocityExtension, + φ0; + Λ_max = 5.0, ζ = 1.1, update_mod = 5, γ = 0.1, γ_reinit = 0.5, os_γ_mult = 0.75, + maxiter = 1000, verbose=false, constraint_names = map(i -> Symbol("C_\$i"),1:N), + converged::Function = default_al_converged, debug = false, + has_oscillations::Function = default_has_oscillations + ) where {N,O} + + Create an instance of `AugmentedLagrangian` with several adjustable defaults. + + # Required + + - `problem::PDEConstrainedFunctionals{N}`: The objective and constraint setup. + - `stencil::AdvectionStencil{O}`: The finite difference stencil for + solving the evolution and reinitisation equations. + - `vel_ext::VelocityExtension`: The velocity-extension method for extending + shape sensitivities onto the computational domain. + - `φ0`: An initial level-set function defined as a FEFunction or GridapDistributed equivilent. + + # Optional defaults + + - `γ = 0.1`: Initial coeffient on the time step size for solving the Hamilton-Jacobi evolution equation. + - `γ_reinit = 0.5`: Coeffient on the time step size for solving the reinitisation equation. + - `ζ = 1.1`: Increase multiplier on Λ every `update_mod` iterations. + - `Λ_max = 5.0`: Maximum value on any entry in Λ. + - `update_mod = 5`: Number of iterations before increasing `Λ`. + - `reinit_mod = 1`: How often we solve reinitialisation equation. + - `maxiter = 1000`: Maximum number of algorithm iterations. + - `verbose=false`: Verbosity flag. + - `constraint_names = map(i -> Symbol("C_\$i"),1:N)`: Constraint names for history output. + - `has_oscillations::Function = default_has_oscillations`: Function to check for oscillations + in the history. + - `initial_parameters::Function = default_al_init_params`: Function to generate initial λ, Λ. + This can be replaced to inject different λ and Λ, for example. + - `os_γ_mult = 0.75`: Decrease multiplier for `γ` when `has_oscillations` returns true + - `converged::Function = default_hp_converged`: Convergence criteria. + - `debug = false`: Debug flag. + """ function AugmentedLagrangian( problem :: PDEConstrainedFunctionals{N}, stencil :: AdvectionStencil{O}, vel_ext :: VelocityExtension, φ0; - Λ_max = 5.0, ζ = 1.1, update_mod = 5, γ = 0.1, γ_reinit = 0.5, - maxiter = 1000, verbose=false, constraint_names = map(i -> Symbol("C_$i"),1:N), + Λ_max = 5.0, ζ = 1.1, update_mod = 5, reinit_mod = 1, γ = 0.1, γ_reinit = 0.5, + os_γ_mult = 0.75, maxiter = 1000, verbose=false, constraint_names = map(i -> Symbol("C_$i"),1:N), converged::Function = default_al_converged, debug = false, - has_oscillations::Function = default_has_oscillations + has_oscillations::Function = default_has_oscillations, + initial_parameters::Function = default_al_init_params ) where {N,O} constraint_names = map(Symbol,constraint_names) - al_keys = [:L,:J,constraint_names...,:γ] - al_bundles = Dict(:C => constraint_names) + λ_names = map(i -> Symbol("λ$i"),1:N) + Λ_names = map(i -> Symbol("Λ$i"),1:N) + al_keys = [:L,:J,constraint_names...,:γ,λ_names...,Λ_names...] + al_bundles = Dict(:C => constraint_names, :λ => λ_names, :Λ => Λ_names) history = OptimiserHistory(Float64,al_keys,al_bundles,maxiter,verbose) - params = (;Λ_max,ζ,update_mod,γ,γ_reinit,debug) + params = (;Λ_max,ζ,update_mod,reinit_mod,γ,γ_reinit,os_γ_mult,debug,initial_parameters) new{N,O}(problem,stencil,vel_ext,history,converged,has_oscillations,params,φ0) end end @@ -44,6 +111,13 @@ function default_has_oscillations(m::AugmentedLagrangian,os_it;itlength=25,algo= return ~isnan(estimate_period(L[it-itlength+1:it+1],algo)) end +function default_al_init_params(J,C) + λ = zeros(eltype(J),length(C)) + Λ = @. 0.1*abs(J)/abs(C)^1.5 + + return λ,Λ +end + function converged(m::AugmentedLagrangian) return m.converged(m) end @@ -60,7 +134,7 @@ function default_al_converged( end Li, Ci = h[:L,it], h[:C,it] - L_prev = h[:L,it-5:it-1] + L_prev = h[:L,it-10:it-1] A = all(L -> abs(Li - L)/abs(Li) < L_tol, L_prev) B = all(C -> abs(C) < C_tol,Ci) return A && B @@ -78,8 +152,7 @@ function Base.iterate(m::AugmentedLagrangian) vel = copy(get_free_dof_values(φh)) ## Compute initial lagrangian - λ = zeros(eltype(J),length(C)) - Λ = convert(Vector{eltype(J)},map(Ci -> 0.1*abs(J)/abs(Ci)^1.5,C)) + λ,Λ = params.initial_parameters(J,C) L = J for (λi,Λi,Ci) in zip(λ,Λ,C) L += -λi*Ci + 0.5*Λi*Ci^2 @@ -93,7 +166,7 @@ function Base.iterate(m::AugmentedLagrangian) project!(m.vel_ext,dL) # Update history and build state - push!(history,(L,J,C...,params.γ)) + push!(history,(L,J,C...,params.γ,λ...,Λ...)) state = (;it=1,L,J,C,dL,dJ,dC,uh,φh,vel,λ,Λ,params.γ,os_it=-1) vars = params.debug ? (0,uh,φh,state) : (0,uh,φh) return vars, state @@ -102,7 +175,8 @@ end function Base.iterate(m::AugmentedLagrangian,state) it, L, J, C, dL, dJ, dC, uh, φh, vel, λ, Λ, γ, os_it = state params, history = m.params, m.history - update_mod, ζ, Λ_max, γ_reinit = params.update_mod, params.ζ, params.Λ_max, params.γ_reinit + update_mod, reinit_mod, ζ, Λ_max, γ_reinit, os_γ_mult = params.update_mod, + params.reinit_mod, params.ζ, params.Λ_max, params.γ_reinit, params.os_γ_mult if finished(m) return nothing @@ -111,7 +185,7 @@ function Base.iterate(m::AugmentedLagrangian,state) ## Advect & Reinitialise if (γ > 0.001) && m.has_oscillations(m,os_it) os_it = it + 1 - γ *= 3/4 + γ *= os_γ_mult print_msg(m.history," Oscillations detected, reducing γ to $(γ)\n",color=:yellow) end @@ -119,7 +193,7 @@ function Base.iterate(m::AugmentedLagrangian,state) V_φ = get_aux_space(m.problem.state_map) interpolate!(FEFunction(U_reg,dL),vel,V_φ) advect!(m.stencil,φh,vel,γ) - reinit!(m.stencil,φh,γ_reinit) + iszero(it % reinit_mod) && reinit!(m.stencil,φh,γ_reinit) ## Calculate objective, constraints, and shape derivatives J, C, dJ, dC = evaluate!(m.problem,φh) @@ -143,7 +217,7 @@ function Base.iterate(m::AugmentedLagrangian,state) project!(m.vel_ext,dL) ## Update history and build state - push!(history,(L,J,C...,γ)) + push!(history,(L,J,C...,γ,λ...,Λ...)) state = (it+1,L,J,C,dL,dJ,dC,uh,φh,vel,λ,Λ,γ,os_it) vars = params.debug ? (it,uh,φh,state) : (it,uh,φh) return vars, state diff --git a/src/Optimisers/HilbertianProjection.jl b/src/Optimisers/HilbertianProjection.jl index 1916df6a..c64a9d7f 100644 --- a/src/Optimisers/HilbertianProjection.jl +++ b/src/Optimisers/HilbertianProjection.jl @@ -123,31 +123,117 @@ function compute_α(C,dC_orthog,dC,normsq,K,P,λ,α_min,α_max) return α, ∑α², debug_flag end -# Optimiser """ - struct HilbertianProjection{T,N} <: Optimiser end + struct HilbertianProjection{T,N} <: Optimiser + +A Hilbertian projection method as described by Wegert et al., 2023 +([link](https://doi.org/10.1007/s00158-023-03663-0)). + +# Parameters + +- `problem::PDEConstrainedFunctionals{N}`: The objective and constraint setup. +- `stencil::AdvectionStencil{O}`: The finite difference stencil for + solving the evolution and reinitisation equations. +- `vel_ext::VelocityExtension`: The velocity-extension method for extending + shape sensitivities onto the computational domain. +- `projector::HilbertianProjectionMap`: Sensitivity information projector +- `history::OptimiserHistory{Float64}`: Historical information for optimisation problem. +- `converged::Function`: A function to check optimiser convergence. +- `has_oscillations::Function`: A function to check for oscillations. +- `params::NamedTuple`: Optimisation parameters. """ struct HilbertianProjection{T,N} <: Optimiser - problem :: PDEConstrainedFunctionals{N} - stencil :: AdvectionStencil - vel_ext :: VelocityExtension - projector :: HilbertianProjectionMap - history :: OptimiserHistory{Float64} - converged :: Function - params :: NamedTuple + problem :: PDEConstrainedFunctionals{N} + stencil :: AdvectionStencil + vel_ext :: VelocityExtension + projector :: HilbertianProjectionMap + history :: OptimiserHistory{Float64} + converged :: Function + has_oscillations :: Function + params :: NamedTuple φ0 # TODO: Remove me please - function HilbertianProjection( + + @doc """ + HilbertianProjection( + problem :: PDEConstrainedFunctionals{N}, + stencil :: AdvectionStencil, + vel_ext :: VelocityExtension, + φ0; + orthog = HPModifiedGramSchmidt(), + λ=0.5, α_min=0.1, α_max=1.0, γ=0.1, γ_reinit=0.5, + ls_max_iters = 10, ls_δ_inc = 1.1, ls_δ_dec = 0.7, + ls_ξ = 0.0025, ls_ξ_reduce_coef = 0.1, ls_ξ_reduce_abs_tol = 0.01, + ls_γ_min = 0.001, ls_γ_max = 0.1, + maxiter = 1000, verbose=false, constraint_names = map(i -> Symbol("C_\$i"),1:N), + converged::Function = default_hp_converged, debug = false + ) where {N} + + Create an instance of `HilbertianProjection` with several adjustable defaults + including the orthogonalisation method. By default the later is [`HPModifiedGramSchmidt`](@ref). + + # Required + + - `problem::PDEConstrainedFunctionals{N}`: The objective and constraint setup. + - `stencil::AdvectionStencil{O}`: The finite difference stencil for + solving the evolution and reinitisation equations. + - `vel_ext::VelocityExtension`: The velocity-extension method for extending + shape sensitivities onto the computational domain. + - `φ0`: An initial level-set function defined as a FEFunction or GridapDistributed equivilent. + + # Algorithm defaults + + - `γ = 0.1`: Initial coeffient on the time step size for solving the Hamilton-Jacobi evolution equation. + - `γ_reinit = 0.5`: Coeffient on the time step size for solving the reinitisation equation. + - `maxiter = 1000`: Maximum number of algorithm iterations. + - `verbose=false`: Verbosity flag. + - `constraint_names = map(i -> Symbol("C_\$i"),1:N)`: Constraint names for history output. + - `converged::Function = default_hp_converged`: Convergence criteria. + - `has_oscillations::Function = (ls_enabled ? (args...)->false : default_has_oscillations`: + By default this is disabled when a line search in enabled. + - `os_γ_mult = 0.5`: Decrease multiplier for `γ` when `has_oscillations` returns true + - `debug = false`: Debug flag. + - `α_min ∈ [0,1] = 0.1`: Controls lower bound on on the projected objective descent coefficent. + `α_min = 1` ignores the objective function and instead solves a constraint satisfaction problem. + - `α_max ∈ [0,1] = 1.0`: Controls the upper bound on the projected objective descent coeffient. + Typically this shouldn't change unless wanting to approach the optimum 'slower'. + - `λ = 0.5`: The rate of contraint decrease. + + Note that in practice we usually only adjust `α_min` to control the balance between improving the + objective or constraints. + + # Line search defaults + + - `ls_enabled = true`: Set whether a line search is used. + - `ls_max_iters = 10`: Maximum number of line search iterations. + - `ls_δ_inc = 1.1`: Increase multiplier for `γ` on acceptance. + - `ls_δ_dec = 0.7`: Decrease multiplier for `γ` on rejection. + - `ls_ξ = 0.0025`: Line search tolerance for objective reduction. + - `ls_ξ_reduce_coef = 0.1`: Coeffient on `ls_ξ` if constraints within tolerance (see below). + - `ls_ξ_reduce_abs_tol = 0.01`: Tolerance on constraints to reduce `ls_ξ` via `ls_ξ_reduce_coef`. + - `ls_γ_min = 0.001`: Minimum coeffient on the time step size for solving the HJ evolution equation. + - `ls_γ_max = 0.1`: Maximum coeffient on the time step size for solving the HJ evolution equation. + + A more concervative evolution of the boundary can be achieved by decreasing `ls_γ_max`. + + !!! note + For some problems (e.g., inverter mechanism), we have observed that a simple oscillation + detection algorithm leads to better convergence compared to the line search. By default + disabling the line search via `ls_enabled = false` will enable oscillation detection. + """ + function HilbertianProjection( problem :: PDEConstrainedFunctionals{N}, stencil :: AdvectionStencil, vel_ext :: VelocityExtension, φ0; orthog = HPModifiedGramSchmidt(), - λ=0.5, α_min=0.1, α_max=1.0, γ=0.1, γ_reinit=0.5, - ls_max_iters = 10, ls_δ_inc = 1.1, ls_δ_dec = 0.7, + λ=0.5, α_min=0.1, α_max=1.0, γ=0.1, γ_reinit=0.5, reinit_mod = 1, + ls_enabled = true, ls_max_iters = 10, ls_δ_inc = 1.1, ls_δ_dec = 0.7, ls_ξ = 0.0025, ls_ξ_reduce_coef = 0.1, ls_ξ_reduce_abs_tol = 0.01, ls_γ_min = 0.001, ls_γ_max = 0.1, maxiter = 1000, verbose=false, constraint_names = map(i -> Symbol("C_$i"),1:N), - converged::Function = default_hp_converged, debug = false + converged::Function = default_hp_converged, debug = false, + has_oscillations::Function = (ls_enabled ? (args...)->false : default_has_oscillations), + os_γ_mult = 0.5 ) where {N} constraint_names = map(Symbol,constraint_names) @@ -156,10 +242,11 @@ struct HilbertianProjection{T,N} <: Optimiser history = OptimiserHistory(Float64,al_keys,al_bundles,maxiter,verbose) projector = HilbertianProjectionMap(N,orthog,vel_ext;λ,α_min,α_max,debug) - params = (;debug,γ,γ_reinit,ls_max_iters,ls_δ_inc,ls_δ_dec,ls_ξ, - ls_ξ_reduce_coef,ls_ξ_reduce_abs_tol,ls_γ_min,ls_γ_max) + params = (;debug,γ,γ_reinit,reinit_mod,ls_enabled,ls_max_iters,ls_δ_inc,ls_δ_dec,ls_ξ, + ls_ξ_reduce_coef,ls_ξ_reduce_abs_tol,ls_γ_min,ls_γ_max,os_γ_mult) T = typeof(orthog) - new{T,N}(problem,stencil,vel_ext,projector,history,converged,params,φ0) + new{T,N}(problem,stencil,vel_ext,projector,history,converged, + has_oscillations,params,φ0) end end @@ -187,6 +274,22 @@ function default_hp_converged( return (it > 10) && A && B end +function default_has_oscillations(m::HilbertianProjection,os_it; + itlength=50,itstart=2itlength,algo=:zerocrossing) + h = m.history + it = get_last_iteration(h) + if it < itstart || it < os_it + itlength + 1 + return false + end + + J = h[:J] + J_osc = ~isnan(estimate_period(J[it-itlength+1:it+1],algo)); + C = h[:C,it-itlength+1:it+1] + C_osc = all(x->~isnan(estimate_period(x,algo)),C); + + return J_osc && C_osc +end + # 0th iteration function Base.iterate(m::HilbertianProjection) history, params = m.history, m.params @@ -208,25 +311,34 @@ function Base.iterate(m::HilbertianProjection) # Update history and build state push!(history,(J,C...,params.γ)) - state = (0,J,C,θ,dJ,dC,uh,φh,vel,φ_tmp,params.γ) # TODO: it changed to 0 here. + state = (;it=0,J,C,θ,dJ,dC,uh,φh,vel,φ_tmp,params.γ,os_it=-1) vars = params.debug ? (0,uh,φh,state) : (0,uh,φh) return vars, state end # ith iteration function Base.iterate(m::HilbertianProjection,state) - it, J, C, θ, dJ, dC, uh, φh, vel, φ_tmp, γ = state + it, J, C, θ, dJ, dC, uh, φh, vel, φ_tmp, γ, os_it = state history, params = m.history, m.params if finished(m) return nothing end + ## Oscillation detection + if (γ > 0.001) && m.has_oscillations(m,os_it) + os_it = it + 1 + γ *= params.os_γ_mult + print_msg(m.history," Oscillations detected, reducing γ to $(γ)\n",color=:yellow) + end + ## Line search U_reg = get_deriv_space(m.problem.state_map) V_φ = get_aux_space(m.problem.state_map) interpolate!(FEFunction(U_reg,θ),vel,V_φ) + ls_enabled = params.ls_enabled + reinit_mod = params.reinit_mod ls_max_iters,δ_inc,δ_dec = params.ls_max_iters,params.ls_δ_inc,params.ls_δ_dec ξ, ξ_reduce, ξ_reduce_tol = params.ls_ξ, params.ls_ξ_reduce_coef, params.ls_ξ_reduce_abs_tol γ_min, γ_max = params.ls_γ_min,params.ls_γ_max @@ -236,7 +348,9 @@ function Base.iterate(m::HilbertianProjection,state) while !done && (ls_it <= ls_max_iters) # Advect & Reinitialise advect!(m.stencil,φ,vel,γ) - reinit!(m.stencil,φ,params.γ_reinit) + iszero(it % reinit_mod) && reinit!(m.stencil,φ,params.γ_reinit) + + ~ls_enabled && break # Calcuate new objective and constraints J_interm, C_interm = evaluate_functionals!(m.problem,φh) @@ -267,7 +381,7 @@ function Base.iterate(m::HilbertianProjection,state) ## Update history and build state push!(history,(J,C...,γ)) - state = (it+1, J, C, θ, dJ, dC, uh, φh, vel, φ_tmp, γ) + state = (it+1, J, C, θ, dJ, dC, uh, φh, vel, φ_tmp, γ, os_it) vars = params.debug ? (it,uh,φh,state) : (it,uh,φh) return vars, state end diff --git a/src/Optimisers/Optimisers.jl b/src/Optimisers/Optimisers.jl index 137c1bc9..c95aa097 100644 --- a/src/Optimisers/Optimisers.jl +++ b/src/Optimisers/Optimisers.jl @@ -1,12 +1,12 @@ """ - abstract type Optimiser end + abstract type Optimiser +Optimisers in LevelSetTopOpt.jl are implemented as iterators. Your own optimiser can be implemented by implementing - concrete functionality of the below. +concrete functionality of the below. """ abstract type Optimiser end -# TODO: Add quality of life Base.show for each optimiser which displays extra info function Base.show(io::IO,object::Optimiser) print(io,typeof(object)) end @@ -14,19 +14,39 @@ end Base.IteratorEltype(::Type{<:Optimiser}) = Base.EltypeUnknown() Base.IteratorSize(::Type{<:Optimiser}) = Base.SizeUnknown() -# Return tuple of first iteration state +""" + Base.iterate(::Optimiser) + +Return tuple of first iteration state for `Optimiser`. +""" function Base.iterate(::Optimiser) @abstractmethod end -# Return tuple of next iteration state given current state +""" + Base.iterate(::Optimiser,state) + +Return tuple of next iteration state given current state +for `Optimiser`. +""" function Base.iterate(::Optimiser,state) @abstractmethod end +""" + get_history(::Optimiser) :: OptimiserHistory + +Get `OptimiserHistory` from `Optimiser`. +""" get_history(::Optimiser) :: OptimiserHistory = @abstractmethod -function converged(::Optimiser) +""" + converged(::Optimiser) + +Return a `Bool` that is true if the `Optimiser` has +converged, otherwise false. +""" +function converged(::Optimiser) :: Bool @abstractmethod end @@ -40,23 +60,30 @@ function finished(m::Optimiser) :: Bool end function print_msg(opt::Optimiser,msg::String;kwargs...) - print_msg(get_optimiser_history(opt),msg;kwargs...) + print_msg(get_history(opt),msg;kwargs...) end """ - mutable struct OptimiserHistory{T} end + mutable struct OptimiserHistory{T} Track historical information on optimisation problem - iteration history with - - keys -- Vector of symbols associated to values - - values -- Dictionary of vectors associated to keys - - bundles -- Groups of symbols (e.g., a group of constraints) +iteration history. + +# Parameters + +- `niter::Int`: Current iteration number +- `keys::Vector{Symbol}`: Vector of symbols associated to values +- `values::Dict{Symbol,Vector{T}}`: Dictionary of vectors associated to keys +- `bundles::Dict{Symbol,Vector{Symbol}}`: Groups of symbols (e.g., a group of constraints) +- `verbose::SolverVerboseLevel`: Verbosity level +- `maxiter::Int`: Maximum number of iterations. + +# Behaviour -Behaviour: - - Indexing at a specific iteration returns an OptimiserHistorySlice. - - Indexing with a key returns all values of that key - - Indexing with a key and iteration returns value/s of - the key at the iteration. +- Indexing at a specific iteration returns an OptimiserHistorySlice. +- Indexing with a key returns all values of that key +- Indexing with a key and iteration returns value/s of + the key at the iteration. """ mutable struct OptimiserHistory{T} niter :: Int @@ -67,6 +94,18 @@ mutable struct OptimiserHistory{T} maxiter :: Int end +""" + OptimiserHistory( + T::Type{<:Real}, + keys::Vector{Symbol}, + bundles::Dict{Symbol,Vector{Symbol}}=Dict{Symbol,Vector{Symbol}}(), + maxiter = 200, + verbose = SOLVER_VERBOSE_NONE + ) + +Create an instance of Optimiser history with some defaults for +`bundles`, `maxiter`, and `verbose`. +""" function OptimiserHistory( T::Type{<:Real}, keys::Vector{Symbol}, @@ -162,6 +201,12 @@ function Base.write(io::IO,h::OptimiserHistory) Base.write(io,content) end +""" + write_history(path::String,h::OptimiserHistory;ranks=nothing) + +Write the contents of an `OptimiserHistory` object to a `path`. +Provide MPI `ranks` when running in parallel. +""" function write_history(path::String,h::OptimiserHistory;ranks=nothing) if i_am_main(ranks) open(path,"w") do f @@ -177,10 +222,10 @@ function print_msg(h::OptimiserHistory,msg::String;kwargs...) end """ - struct OptimiserHistorySlice{T} end + struct OptimiserHistorySlice{T} end A read-only wrapper of OptimiserHistory for IO display - of iteration history at a specific iteration. +of iteration history at a specific iteration. """ struct OptimiserHistorySlice{T} it :: Int diff --git a/src/Optimisers/OrthogonalisationMaps.jl b/src/Optimisers/OrthogonalisationMaps.jl index b4a133c4..dc7d4945 100644 --- a/src/Optimisers/OrthogonalisationMaps.jl +++ b/src/Optimisers/OrthogonalisationMaps.jl @@ -1,6 +1,6 @@ """ - OrthogonalisationMap + OrthogonalisationMap """ abstract type OrthogonalisationMap <: Gridap.Arrays.Map end @@ -8,9 +8,9 @@ Gridap.Arrays.evaluate!(cache,::OrthogonalisationMap,::AbstractArray{<:AbstractA Gridap.Arrays.return_cache(::OrthogonalisationMap,::AbstractArray{<:AbstractArray},::AbstractMatrix) = @abstractmethod """ - HPModifiedGramSchmidt + HPModifiedGramSchmidt - High performance modified Gram-Schmidt. Based on Algorithm 6 in https://doi.org/10.1007/s13160-019-00356-4. +High performance modified Gram-Schmidt. Based on Algorithm 6 in this [paper](https://doi.org/10.1007/s13160-019-00356-4). """ struct HPModifiedGramSchmidt <: OrthogonalisationMap end diff --git a/src/Solvers.jl b/src/Solvers.jl index 6cbebc61..a81ab340 100644 --- a/src/Solvers.jl +++ b/src/Solvers.jl @@ -1,3 +1,4 @@ +## TODO: @Jordi, are these going into GridapSolvers or should I write documentation? struct ElasticitySolver{A} <: LinearSolver space ::A diff --git a/src/Utilities.jl b/src/Utilities.jl index 396031e9..82ba9f0f 100644 --- a/src/Utilities.jl +++ b/src/Utilities.jl @@ -1,25 +1,80 @@ -## Initial LSF -gen_lsf(ξ,a;b=0) = x::VectorValue -> -1/4*prod(cos.(get_array(@.(ξ*pi*(x-b))))) - a/4 +""" + struct SmoothErsatzMaterialInterpolation{M<:Vector{<:Number},N<:Vector{<:Number}} -## Get element size Δ -function get_Δ(model::CartesianDiscreteModel) - desc = get_cartesian_descriptor(model) - return desc.sizes +A wrapper holding parameters and methods for interpolating an +integrand across a single boundary ``\\partial\\Omega``. + +E.g., ``\\int f~\\mathrm{d}\\Omega = \\int I(\\varphi)f~\\mathrm{d}D`` where ``\\Omega\\subset D`` is described by a level-set +function ``\\varphi`` and ``I`` is an indicator function. + +# Properties + +- `η::M`: the interpolation or smoothing radius across ∂Ω +- `ϵ::M`: the ersatz material density +- `H`: a smoothed Heaviside function +- `DH`: the derivative of `H` +- `I`: an indicator function +- `ρ`: a function describing the volume density of ``\\Omega`` + (e.g., ``\\mathrm{Vol}(\\Omega) = \\int \\rho(\\varphi))~\\mathrm{d}D)`` + +# Note +- We store η and ϵ as length-one vectors so that updating these values propagates through H, DH, etc. +- To update η and/or ϵ in an instance `m`, take `m.η .= `. +- A conviencence constructor is provided to create an instance given `η<:Number` and `ϵ<:Number`. +""" +Base.@kwdef struct SmoothErsatzMaterialInterpolation{M<:Vector{<:Number},N<:Vector{<:Number}} + η::M + ϵ::N + H = x -> H_η(x,first(η)) + DH = x -> DH_η(x,first(η)) + I = φ -> (1 - H(φ)) + first(ϵ)*H(φ) + ρ = φ -> 1 - H(φ) end -function get_Δ(model::DistributedDiscreteModel) - local_Δ = map(get_Δ,local_views(model)) - return getany(local_Δ) +function SmoothErsatzMaterialInterpolation(;η::M,ϵ::N=10^-3) where {M<:Number,N<:Number} + return SmoothErsatzMaterialInterpolation{Vector{M},Vector{N}}(η=[η],ϵ=[ϵ]) end -## Create label given function f_Γ. e is a count of added tags. TODO: Can this go in GridapExtensions.jl? @Jordi -function update_labels!(e::Int,model::CartesianDiscreteModel,f_Γ::Function,name::String) +function H_η(t,η) + M = typeof(η*t) + if t<-η + return zero(M) + elseif abs(t)<=η + return 1/2*(1+t/η+1/pi*sin(pi*t/η)) + elseif t>η + return one(M) + end +end + +function DH_η(t,η) + M = typeof(η*t) + if t<-η + return zero(M) + elseif abs(t)<=η + return 1/2/η*(1+cos(pi*t/η)) + elseif t>η + return zero(M) + end +end + +## Helpers + +""" + update_labels!(e::Int,model,f_Γ::Function,name::String) + +Given a tag number `e`, a `CartesianDiscreteModel` or `DistributedDiscreteModel` model, +an indicator function `f_Γ`, and a string `name`, label the corresponding vertices, edges, and faces +as `name`. + +Note: `f_Γ` must recieve a Vector and return a Boolean depending on whether it indicates Γ +""" +function update_labels!(e::Integer,model::CartesianDiscreteModel,f_Γ::Function,name::String) mask = mark_nodes(f_Γ,model) _update_labels_locally!(e,model,mask,name) nothing end -function update_labels!(e::Int,model::DistributedDiscreteModel,f_Γ::Function,name::String) +function update_labels!(e::Integer,model::DistributedDiscreteModel,f_Γ::Function,name::String) mask = mark_nodes(f_Γ,model) cell_to_entity = map(local_views(model),local_views(mask)) do model,mask _update_labels_locally!(e,model,mask,name) @@ -90,62 +145,96 @@ function mark_nodes(f,model::DiscreteModel) return mask end -# Isotropic elasticity tensors -function isotropic_2d(E::M,ν::M) where M<:AbstractFloat - λ = E*ν/((1+ν)*(1-ν)); μ = E/(2*(1+ν)) - C = [λ+2μ λ 0 - λ λ+2μ 0 - 0 0 μ]; - SymFourthOrderTensorValue( - C[1,1], C[3,1], C[2,1], - C[1,3], C[3,3], C[2,3], - C[1,2], C[3,2], C[2,2] - ) +""" + initial_lsf(ξ,a;b) + +Generate a function `f` according to +f(x) = -1/4 ∏ᵢ(cos(ξ*π*(xᵢ-bᵢ))) - a/4 +where x is a vector with components xᵢ. +""" +initial_lsf(ξ,a;b=0) = x::VectorValue -> -1/4*prod(cos.(get_array(@.(ξ*pi*(x-b))))) - a/4 + +""" + get_el_Δ(model) + +Given a CartesianDiscreteModel or DistributedDiscreteModel that is +uniform, return the element size as a tuple. +""" +function get_el_Δ(model::CartesianDiscreteModel) + desc = get_cartesian_descriptor(model) + return desc.sizes +end + +function get_el_Δ(model::DistributedDiscreteModel) + local_Δ = map(local_views(model)) do model + get_el_Δ(model) + end + return getany(local_Δ) end -function isotropic_3d(E::M,ν::M) where M<:AbstractFloat - λ = E*ν/((1+ν)*(1-2ν)); μ = E/(2*(1+ν)) - C =[λ+2μ λ λ 0 0 0 +""" + isotropic_elast_tensor(D::Int,E::M,v::M) + +Generate an isotropic `SymFourthOrderTensorValue` given +a dimension `D`, Young's modulus `E`, and Poisson's ratio `v`. +""" +function isotropic_elast_tensor(D::Int,E::Number,v::Number) + if D == 2 + λ = E*v/((1+v)*(1-v)); μ = E/(2*(1+v)) + C = [ + λ+2μ λ 0 + λ λ+2μ 0 + 0 0 μ + ]; + return SymFourthOrderTensorValue( + C[1,1], C[3,1], C[2,1], + C[1,3], C[3,3], C[2,3], + C[1,2], C[3,2], C[2,2] + ) + elseif D == 3 + λ = E*v/((1+v)*(1-2v)); μ = E/(2*(1+v)) + C = [ + λ+2μ λ λ 0 0 0 λ λ+2μ λ 0 0 0 λ λ λ+2μ 0 0 0 0 0 0 μ 0 0 0 0 0 0 μ 0 - 0 0 0 0 0 μ]; - return SymFourthOrderTensorValue( + 0 0 0 0 0 μ + ]; + return SymFourthOrderTensorValue( C[1,1], C[6,1], C[5,1], C[2,1], C[4,1], C[3,1], C[1,6], C[6,6], C[5,6], C[2,6], C[4,6], C[3,6], C[1,5], C[6,5], C[5,5], C[2,5], C[4,5], C[3,5], C[1,2], C[6,2], C[5,2], C[2,2], C[4,2], C[3,2], C[1,4], C[6,4], C[5,4], C[2,4], C[4,4], C[3,4], C[1,3], C[6,3], C[5,3], C[2,3], C[4,3], C[3,3] - ) -end - -# Logging -function make_dir(path;ranks=nothing) # TODO: Adjust to mkpath? - if i_am_main(ranks) - !isdir(path) ? mkdir(path) : rm(path,recursive=true); - !isdir(path) ? mkdir(path) : 0; + ) + else + @notimplemented end end -function print_history(it,entries::Vector{<:Pair};ranks=nothing) # TODO: Remove - if i_am_main(ranks) - str = "it: $it" - for pair in entries - val = round.(pair.second;digits=5) - str*=" | $(pair.first): $val" - end - println(str) - end -end +""" + write_vtk(Ω,path,it,entries::Vector{<:Pair};iter_mod=10) + +Write a VTK file to `path`. This functions in a similar way to +Gridap's `writevtk` function except we + +!!! note + This may be removed in future and replaced by + + ```(isone(it) || iszero(it % iter_mod)) && writevtk(Ω,path,cellfields=entries)``` -function write_vtk(Ω,path,it,entries::Vector{<:Pair};iter_mod=10) # TODO: Rename to writevtk + or + + ```iszero(it-1 % iter_mod) && writevtk(Ω,path,cellfields=entries)``` +""" +function write_vtk(Ω,path,it,entries::Vector{<:Pair};iter_mod=10) # TODO: Rename to writevtk? if isone(it) || iszero(it % iter_mod) writevtk(Ω,path,cellfields=entries) end if iszero(it % 10*iter_mod) - GC.gc() # Garbage collection, due to memory leak in writevtk - TODO + GC.gc() # TODO: Test in Julia 1.10.0 and check if fixed. end end diff --git a/src/VelocityExtension.jl b/src/VelocityExtension.jl index ba05f4e0..7e645335 100644 --- a/src/VelocityExtension.jl +++ b/src/VelocityExtension.jl @@ -1,20 +1,50 @@ """ - struct VelocityExtension{A,B} end + struct VelocityExtension{A,B} -Wrapper to hold stiffness matrix and cache for - Hilbertian extension-regularisation procedure. +Wrapper to hold a stiffness matrix and a cache for +the Hilbertian extension-regularisation. See Allaire et al. 2022 +([link](https://doi.org/10.1016/bs.hna.2020.10.004)). + +The Hilbertian extension-regularisation method involves solving an +identification problem over a Hilbert space ``H`` on ``D`` with +inner product ``\\langle\\cdot,\\cdot\\rangle_H``: +*Find* ``g_\\Omega\\in H`` *such that* ``\\langle g_\\Omega,w\\rangle_H +=-J^{\\prime}(\\Omega)(w\\boldsymbol{n})~ +\\forall w\\in H.`` + +This provides two benefits: + 1) It naturally extends the shape sensitivity from ``\\partial\\Omega`` + onto the bounding domain ``D``; and + 2) ensures a descent direction for ``J(\\Omega)`` with additional regularity + (i.e., ``H`` as opposed to ``L^2(\\partial\\Omega)``) + +# Properties + +- `K::A`: The discretised inner product over ``H``. +- `cache::B`: Cached objects used for [`project!`](@ref) """ struct VelocityExtension{A,B} K :: A cache :: B end +""" + VelocityExtension(biform,U_reg,V_reg;assem,ls) + +Create an instance of `VelocityExtension` given a bilinear form `biform`, +trial space `U_reg`, and test space `V_reg`. + +# Optional + +- `assem`: A matrix assembler +- `ls::LinearSolver`: A linear solver +""" function VelocityExtension( - biform, - U_reg, - V_reg; + biform::Function, + U_reg::FESpace, + V_reg::FESpace; assem = SparseMatrixAssembler(U_reg,V_reg), - ls = LUSolver()) + ls::LinearSolver = LUSolver()) ## Assembly K = assemble_matrix(biform,assem,U_reg,V_reg) ns = numerical_setup(symbolic_setup(ls,K),K) @@ -24,12 +54,10 @@ function VelocityExtension( end """ - project!(vel_ext::VelocityExtension,dF::AbstractVector) -> dF + project!(vel_ext::VelocityExtension,dF::AbstractVector) -> dF -Apply Hilbertian extension-regularisation to dF to project - gradient into a space with additional regularity over the - bounding domain. See Allaire et al. - (10.1016/bs.hna.2020.10.004_978-0-444-64305-6_2021). +Project shape derivative `dF` onto a function space described +by the `vel_ext`. """ function project!(vel_ext::VelocityExtension,dF::AbstractVector) ns, x = vel_ext.cache @@ -39,4 +67,8 @@ function project!(vel_ext::VelocityExtension,dF::AbstractVector) return dF end -project!(vel_ext::VelocityExtension,dF_vec::Vector{<:AbstractVector}) = map(dF -> project!(vel_ext,dF),dF_vec) \ No newline at end of file +project!(vel_ext::VelocityExtension,dF_vec::Vector{<:AbstractVector}) = broadcast(dF -> project!(vel_ext,dF),dF_vec) + +function Base.show(io::IO,object::VelocityExtension) + print(io,"$(nameof(typeof(object)))") +end \ No newline at end of file diff --git a/test/mpi/runtests.jl b/test/mpi/runtests.jl new file mode 100644 index 00000000..172e27bc --- /dev/null +++ b/test/mpi/runtests.jl @@ -0,0 +1,20 @@ +module LSTOMPITests + +using Test +using MPI + +testdir = @__DIR__ +istest(f) = endswith(f, ".jl") && !(f=="runtests.jl") +testfiles = sort(filter(istest, readdir(testdir))) + +MPI.mpiexec() do cmd + for file in testfiles + path = joinpath(testdir,file) + _cmd = `$(cmd) -np 4 --allow-run-as-root --oversubscribe $(Base.julia_cmd()) --project=. $path` + @show _cmd + run(_cmd) + @test true + end +end + +end \ No newline at end of file diff --git a/test/runtests.jl b/test/runtests.jl new file mode 100644 index 00000000..64966857 --- /dev/null +++ b/test/runtests.jl @@ -0,0 +1,2 @@ +using Test +include("seq/runtests.jl") \ No newline at end of file diff --git a/test/seq/ThermalComplianceALMTests.jl b/test/seq/ThermalComplianceALMTests.jl new file mode 100644 index 00000000..1d227f11 --- /dev/null +++ b/test/seq/ThermalComplianceALMTests.jl @@ -0,0 +1,96 @@ +module ThermalComplianceALMTests + +using Gridap, LevelSetTopOpt + +""" + (Serial) Minimum thermal compliance with augmented Lagrangian method in 2D. + + Optimisation problem: + Min J(Ω) = ∫ κ*∇(u)⋅∇(u) dΩ + Ω + s.t., Vol(Ω) = vf, + ⎡u∈V=H¹(Ω;u(Γ_D)=0), + ⎣∫ κ*∇(u)⋅∇(v) dΩ = ∫ v dΓ_N, ∀v∈V. +""" +function main() + ## Parameters| + order = 1 + xmax=ymax=1.0 + prop_Γ_N = 0.2 + prop_Γ_D = 0.2 + dom = (0,xmax,0,ymax) + el_size = (20,20) + γ = 0.1 + γ_reinit = 0.5 + max_steps = floor(Int,minimum(el_size)/10) + tol = 1/(5*order^2)/minimum(el_size) + κ = 1 + vf = 0.4 + η_coeff = 2 + α_coeff = 4 + + ## FE Setup + model = CartesianDiscreteModel(dom,el_size); + el_Δ = get_el_Δ(model) + f_Γ_D(x) = (x[1] ≈ 0.0 && (x[2] <= ymax*prop_Γ_D + eps() || + x[2] >= ymax-ymax*prop_Γ_D - eps())) + f_Γ_N(x) = (x[1] ≈ xmax && ymax/2-ymax*prop_Γ_N/2 - eps() <= x[2] <= + ymax/2+ymax*prop_Γ_N/2 + eps()) + update_labels!(1,model,f_Γ_D,"Gamma_D") + update_labels!(2,model,f_Γ_N,"Gamma_N") + + ## Triangulations and measures + Ω = Triangulation(model) + Γ_N = BoundaryTriangulation(model,tags="Gamma_N") + dΩ = Measure(Ω,2*order) + dΓ_N = Measure(Γ_N,2*order) + vol_D = sum(∫(1)dΩ) + + ## Spaces + reffe_scalar = ReferenceFE(lagrangian,Float64,order) + V = TestFESpace(model,reffe_scalar;dirichlet_tags=["Gamma_D"]) + U = TrialFESpace(V,0.0) + V_φ = TestFESpace(model,reffe_scalar) + V_reg = TestFESpace(model,reffe_scalar;dirichlet_tags=["Gamma_N"]) + U_reg = TrialFESpace(V_reg,0) + + ## Create FE functions + φh = interpolate(initial_lsf(4,0.2),V_φ) + + ## Interpolation and weak form + interp = SmoothErsatzMaterialInterpolation(η = η_coeff*maximum(el_Δ)) + I,H,DH,ρ = interp.I,interp.H,interp.DH,interp.ρ + + a(u,v,φ,dΩ,dΓ_N) = ∫((I ∘ φ)*κ*∇(u)⋅∇(v))dΩ + l(v,φ,dΩ,dΓ_N) = ∫(v)dΓ_N + + ## Optimisation functionals + J(u,φ,dΩ,dΓ_N) = ∫((I ∘ φ)*κ*∇(u)⋅∇(u))dΩ + dJ(q,u,φ,dΩ,dΓ_N) = ∫(κ*∇(u)⋅∇(u)*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ; + Vol(u,φ,dΩ,dΓ_N) = ∫(((ρ ∘ φ) - vf)/vol_D)dΩ; + dVol(q,u,φ,dΩ,dΓ_N) = ∫(-1/vol_D*q*(DH ∘ φ)*(norm ∘ ∇(φ)))dΩ + + ## Finite difference solver and level set function + stencil = AdvectionStencil(FirstOrderStencil(2,Float64),model,V_φ,tol,max_steps) + + ## Setup solver and FE operators + state_map = AffineFEStateMap(a,l,U,V,V_φ,U_reg,φh,dΩ,dΓ_N) + pcfs = PDEConstrainedFunctionals(J,[Vol],state_map,analytic_dJ=dJ,analytic_dC=[dVol]) + + ## Hilbertian extension-regularisation problems + α = α_coeff*maximum(el_Δ) + a_hilb(p,q) =∫(α^2*∇(p)⋅∇(q) + p*q)dΩ; + vel_ext = VelocityExtension(a_hilb,U_reg,V_reg) + + ## Optimiser + optimiser = AugmentedLagrangian(pcfs,stencil,vel_ext,φh; + γ,γ_reinit,verbose=true,constraint_names=[:Vol]) + + # Do a few iterations + vars, state = iterate(optimiser) + vars, state = iterate(optimiser,state) +end + +main() + +end # module \ No newline at end of file diff --git a/test/seq/runtests.jl b/test/seq/runtests.jl new file mode 100644 index 00000000..92ccacfa --- /dev/null +++ b/test/seq/runtests.jl @@ -0,0 +1,7 @@ +module LSTOSequentialTests + +using Test + +@time @testset "Thermal Compliance - ALM" begin include("ThermalComplianceALMTests.jl") end + +end # module \ No newline at end of file