diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml new file mode 100644 index 00000000..245016c4 --- /dev/null +++ b/.github/workflows/CI.yml @@ -0,0 +1,47 @@ +name: CI +on: + pull_request: + branches: + - master + push: + branches: + - master + tags: '*' +jobs: + test: + name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} - ${{ github.event_name }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + version: + - '1.10' # Replace this with the minimum Julia version that your package supports. E.g. if your package requires Julia 1.5 or higher, change this to '1.5'. + - '1' # Leave this line unchanged. '1' will automatically expand to the latest stable 1.x release of Julia. + 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@v4 + 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@v4 + with: + files: lcov.info + token: ${{ secrets.CODECOV_TOKEN }} + fail_ci_if_error: false diff --git a/.github/workflows/Documentation.yml b/.github/workflows/Documentation.yml new file mode 100644 index 00000000..fda05bab --- /dev/null +++ b/.github/workflows/Documentation.yml @@ -0,0 +1,29 @@ +name: Documentation + +on: + push: + branches: + - master # update to match your development branch (master, main, dev, trunk, ...) + tags: '*' + pull_request: + branches: + - master + +jobs: + build: + permissions: + contents: write + statuses: write + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: julia-actions/setup-julia@v1 + with: + version: '1' + - name: Install dependencies + run: julia --project=docs/ -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate()' + - name: Build and deploy + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # If authenticating with GitHub Actions token + DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }} # If authenticating with SSH deploy key + run: julia --project=docs/ docs/make.jl \ No newline at end of file diff --git a/.github/workflows/TagBot.yml b/.github/workflows/TagBot.yml index 35600114..4905fd1d 100644 --- a/.github/workflows/TagBot.yml +++ b/.github/workflows/TagBot.yml @@ -4,6 +4,22 @@ on: types: - created workflow_dispatch: + inputs: + lookback: + default: "3" +permissions: + actions: read + checks: read + contents: write + deployments: read + issues: read + discussions: read + packages: read + pages: read + pull-requests: read + repository-projects: read + security-events: read + statuses: read jobs: TagBot: if: github.event_name == 'workflow_dispatch' || github.actor == 'JuliaTagBot' @@ -12,5 +28,6 @@ jobs: - uses: JuliaRegistries/TagBot@v1 with: token: ${{ secrets.GITHUB_TOKEN }} - ssh: ${{ secrets.DOCUMENTER_KEY }} - + # Edit the following line to reflect the actual name of the GitHub Secret containing your private key + ssh: ${{ secrets.DOCUMENTER_SSH_KEY }} + # ssh: ${{ secrets.NAME_OF_MY_SSH_PRIVATE_KEY_SECRET }} diff --git a/.gitignore b/.gitignore index d29bd28c..6944773e 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,9 @@ docs/site/ temp/ dev/ profile/ -Manifest.toml +/envs/ +/Manifest.toml +/test/Manifest.toml +/docs/Manifest.toml +/examples/Manifest.toml +data/ \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index c3be022e..00000000 --- a/.travis.yml +++ /dev/null @@ -1,13 +0,0 @@ -# Documentation: http://docs.travis-ci.com/user/languages/julia/ -language: julia -os: - - linux - - osx - - windows -julia: - - 1.3 - - 1.5 -notifications: - email: false -after_success: - - julia -e 'using Pkg; Pkg.add("Coverage"); using Coverage; Codecov.submit(process_folder())' diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..ca4bd827 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,11 @@ +# Changelog + +# New in 2.7.2 + +- GMRES from Krylov.jl wrapped as a LinearMap +- Function `union` for a vector of spaces of the same type and defined on the same mesh returns a space containing the union of the functions in the input spaces. +- Function `extend` computes the extension by zero of functions defined on a submesh. Currently works only when the corresponding cellls in the submesh and supermesh are ordered identically (oriented identically is not sufficient). + +## New in 2.7.0 + +- PR #157: Improved support for 2D Helmholtz problems diff --git a/Project.toml b/Project.toml index cde04326..782dec8f 100644 --- a/Project.toml +++ b/Project.toml @@ -1,51 +1,63 @@ name = "BEAST" uuid = "bb4162c7-ba94-5a20-af32-d8ec4428bdd1" -version = "1.3.0" +version = "2.7.2" [deps] +AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" BlockArrays = "8e7c35d0-a365-5155-bbbb-fb81a777f24e" CollisionDetection = "2b5bf9a6-f3f8-5352-af9c-82bb4af718d8" Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa" CompScienceMeshes = "3e66a162-7b8c-5da0-b8f8-124ecd2c3ae1" Compat = "34da2185-b29b-5c13-b0c7-acf172513d20" +ConvolutionOperators = "15927181-a1bb-497c-b745-8dbf505c019d" Distributed = "8ba89e20-285c-5b6f-9357-94700520ee1b" +ExtendableSparse = "95c220a8-a1cf-11e9-0c77-dbfce5f500b3" FFTW = "7a1cc6ca-52ef-59f5-83cd-3a7055c09341" FastGaussQuadrature = "442a2c76-b920-505d-bb47-c5924d526838" FillArrays = "1a297f60-69ca-5386-bcde-b61e274b549b" +Infiltrator = "5903a43b-9cc3-4c30-8d17-598619ec4e9b" +InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240" IterativeSolvers = "42fd0dbc-a981-5370-80f2-aaf504508153" +Krylov = "ba0b0d4f-ebba-5204-a429-3ac8c609bfb7" +LiftedMaps = "d22a30c1-52ac-4762-a8c9-5838452405e0" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" +LinearMaps = "7a12625a-238d-50fd-b39a-03d52299707e" +NestedUnitRanges = "032820ab-dc03-4b49-91f4-7d58d4da98b3" +Requires = "ae029012-a4dd-5104-9daa-d747884805df" +SauterSchwab3D = "0a13313b-1c00-422e-8263-562364ed9544" SauterSchwabQuadrature = "535c7bfe-2023-5c1d-b712-654ef9d93a38" SharedArrays = "1a1011a3-84de-559e-8e89-a11a2f7dc383" SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" -SparseMatrixDicts = "5cb6c4b0-9b79-11e8-24c9-f9621d252589" SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b" StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" +Suppressor = "fd094767-a336-5f1f-9728-57cf17d0bbfb" +TestItems = "1c621080-faea-4a02-84b6-bbd5e436b8fe" WiltonInts84 = "a3e2863e-c0ee-5ff6-a523-307a4cdc8724" [compat] -BlockArrays = "^0.10, ^0.11, ^0.12, ^0.13, ^0.14, 0.15" -CollisionDetection = "^0.1" -Combinatorics = "^0.7, ^1" -CompScienceMeshes = "^0.2.8" -Compat = "^2, ^3" -FFTW = "^0.2.3, ^1" -FastGaussQuadrature = "^0.3, ^0.4" -FillArrays = "0.11" -IterativeSolvers = "^0.9" -SauterSchwabQuadrature = "^2.1.1, 2.1" -SparseMatrixDicts = "0.2" -SpecialFunctions = "^0.7, ^0.8, ^0.9, ^0.10, ^1" -StaticArrays = "^0.8.3, ^0.9, ^0.10, ^0.11, ^0.12, ^1" -WiltonInts84 = "^0.2" -julia = "^1.3" - -[extras] -DelimitedFiles = "8bb1440f-4735-579b-a4ab-409b98df4dab" -Distributed = "8ba89e20-285c-5b6f-9357-94700520ee1b" -LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" -Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" -SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" -Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" - -[targets] -test = ["Pkg", "DelimitedFiles", "LinearAlgebra", "SparseArrays", "Test", "Distributed"] +AbstractTrees = "0.4.4" +BlockArrays = "1" +CollisionDetection = "0.1.7" +Combinatorics = "0.7, 1" +CompScienceMeshes = "0.10" +Compat = "2, 3, 4" +ConvolutionOperators = "0.4" +ExtendableSparse = "1.4" +FFTW = "0.2.3, 1" +FastGaussQuadrature = "0.3, 0.4, 0.5, 1" +FillArrays = "0.11, 0.12, 0.13, 1" +Infiltrator = "1.8.2" +IterativeSolvers = "0.9" +Krylov = "0.10.1" +LiftedMaps = "0.5.1" +LinearMaps = "3.7 - 3.9, 3.11.2" +NestedUnitRanges = "0.2.2" +Requires = "1" +SauterSchwab3D = "0.2" +SauterSchwabQuadrature = "2.4.0" +SpecialFunctions = "0.7, 0.8, 0.9, 0.10, 1, 2" +StaticArrays = "0.8.3, 0.9, 0.10, 0.11, 0.12, 1" +Suppressor = "0.2.8" +TestItems = "0.1.1, 1" +WiltonInts84 = "0.2.8" +julia = "1.10" diff --git a/README.md b/README.md index dcb060af..a6d8a107 100644 --- a/README.md +++ b/README.md @@ -1,72 +1,62 @@ -# BEAST -Boundary Element Analysis and Simulation Toolkit + + + + + -[![Build Status](https://travis-ci.org/krcools/BEAST.jl.svg?branch=master)](https://travis-ci.org/krcools/BEAST.jl) + +[![Docs-stable](https://img.shields.io/badge/docs-stable-blue.svg)](https://krcools.github.io/BEAST.jl/stable/) +[![MIT license](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/krcools/BEAST.jl/blob/master/LICENSE) +[![CI](https://github.com/krcools/BEAST.jl/actions/workflows/CI.yml/badge.svg)](https://github.com/krcools/BEAST.jl/actions/workflows/CI.yml) [![codecov.io](http://codecov.io/github/krcools/BEAST.jl/coverage.svg?branch=master)](http://codecov.io/github/krcools/BEAST.jl?branch=master) -[![Documentation](https://img.shields.io/badge/docs-latest-blue.svg)](https://krcools.github.io/BEAST.jl/latest/) [![DOI](https://zenodo.org/badge/87720391.svg)](https://zenodo.org/badge/latestdoi/87720391) + ## Introduction -This package contains common basis functions and assembly routines for the implementation of -boundary element methods. Examples are included for the 2D and 3D Helmholtz equations and for -the 3D Maxwell equations. +This Julia package, the *boundary element analysis and simulation toolkit (BEAST)*, provides routines to convert integral and differential equations to linear systems of equations +via the boundary element method (BEM) and the finite element method (FEM). +To this end, the (Petrov-) **Galerkin method** is employed. -Support for the space-time Galerkin based solution of time domain integral equations is in -place for the 3D Helmholtz and Maxwell equations. +Currently, the focus is on equations encountered in **classical electromagnetism**, where frequency and time domain equations are covered. +Several operators, basis functions, and geometry representations are implemented. -## Installation -Installing `BEAST` is done by entering the package manager (enter `]` at the julia REPL) and issuing: +## Documentation -``` -pkg>add BEAST -``` +- Documentation for the [latest stable version](https://krcools.github.io/BEAST.jl/stable/). +- Documentation for the [development version](https://krcools.github.io/BEAST.jl/dev/). -To run the examples, the following steps are required in addition: - -``` -pkg> add CompScienceMeshes # For the creation of scatterer geometries -pkg> add Plots # For visualising the results -pkg> add GR # Other Plots compatible back-ends can be chosen -``` - -Examples can be run by: - -``` -julia>using BEAST -julia>d = dirname(pathof(BEAST)) -julia>include(joinpath(d,"../examples/efie.jl")) -``` ## Hello World -To solve scattering of a time harmonic electromagnetic plane wave by a perfectly conducting -sphere: +To solve scattering of a time-harmonic electromagnetic plane wave by a perfectly conducting sphere: ```julia -using CompScienceMeshes, BEAST +using CompScienceMeshes +using BEAST -Γ = readmesh(joinpath(dirname(pathof(BEAST)),"../examples/sphere2.in")) -X = raviartthomas(Γ) +# --- basis functions +Γ = meshsphere(1.0, 2.5) # triangulate sphere of radius one +RT = raviartthomas(Γ) # define basis functions -t = Maxwell3D.singlelayer(wavenumber=1.0) -E = Maxwell3D.planewave(direction=ẑ, polarization=x̂, wavenumber=1.0) -e = (n × E) × n +# --- operators & excitation +𝑇 = Maxwell3D.singlelayer(wavenumber=2.0) # integral operator +𝐸 = Maxwell3D.planewave(direction=x̂, polarization=ẑ, wavenumber=2.0) # excitation +𝑒 = (n × 𝐸) × n # tangential part -@hilbertspace j -@hilbertspace k -efie = @discretise t[k,j]==e[k] j∈X k∈X -u = gmres(efie) -``` -![](output.png) +# --- compute the RHS and system matrix +e = assemble(𝑒, RT) # assemble RHS +T = assemble(𝑇, RT, RT) # assemble system matrix -## Features +# --- solve +u = T \ -e -- General framework allowing to easily add support for more kernels, finite element spaces, and excitations. -- Assembly routines that take in symbolic representations of the defining bilinear form. Support for block systems and finite element spaces defined in terms of direct products or tensor products of atomic spaces. -- LU and iterative solution of the resulting system. -- Computation of secondary quantities of interest such as the near field and the limiting far field. -- Support for space-time Galerkin and convolution quadrature approaches to the solution of time domain boundary integral equations. -- Implementation of Lagrange zeroth and first order space, Raviart-Thomas, Brezzi-Douglas-Marini, and Buffa-Christianssen vector elemenents. +# ... post processing ... +``` +

+ +                  + +

diff --git a/TODO.md b/TODO.md deleted file mode 100644 index 686ee4fd..00000000 --- a/TODO.md +++ /dev/null @@ -1,5 +0,0 @@ -[x] Create wrapper type around Array{T,3} to make it behave like a convolution operator - -[] Properly implement the convolution of two piecewise polynomial time basis functions - -[] assemble and allocatestorage for tensor operators need to be more robust diff --git a/docs/Project.toml b/docs/Project.toml new file mode 100644 index 00000000..e0042947 --- /dev/null +++ b/docs/Project.toml @@ -0,0 +1,10 @@ +[deps] +BEAST = "bb4162c7-ba94-5a20-af32-d8ec4428bdd1" +CompScienceMeshes = "3e66a162-7b8c-5da0-b8f8-124ecd2c3ae1" +Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" +DocumenterCitations = "daee34ce-89f3-4625-b898-19384cb65244" +Krylov = "ba0b0d4f-ebba-5204-a429-3ac8c609bfb7" +Literate = "98b081ad-f1c9-55d3-8b20-4c87d4299306" +PlotlyBase = "a03496cd-edff-5a9b-9e67-9cda94a718b5" +PlotlyDocumenter = "9b90f1cd-2639-4507-8b17-2fbe371ceceb" +TypeTree = "04da0e3b-1cad-4b2c-a963-fc1602baf1af" diff --git a/docs/make.jl b/docs/make.jl index 1e48f7a1..4051c366 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -1,10 +1,97 @@ -using Documenter, BEAST +using Documenter +using DocumenterCitations +using BEAST -makedocs( - clean=false, - sitename="BEAST Documentation") +bib = CitationBibliography(joinpath(@__DIR__, "src", "refs.bib"); style=:alpha) -# deploydocs( -# deps = Deps.pip("mkdocs", "python-markdown-math"), -# repo = "github.com/krcools/BEAST.jl.git", -# julia = "1.0") +makedocs(; + modules=[BEAST], + authors="Kristof Cools and contributors", + sitename="BEAST.jl", + checkdocs=:none, + format=Documenter.HTML(; + prettyurls=get(ENV, "CI", "false") == "true", + canonical="https://krcools.github.io/BEAST.jl", + edit_link="master", + assets=String[], + collapselevel=1, + sidebar_sitename=true, + ), + plugins=[bib], + pages=[ + "Introduction" => "index.md", + "Manual" => Any[ + "General Usage"=>"manual/usage.md", + "Custom excitations"=>"manual/customexc.md", + "Setting the Quadrature Strategy" => "manual/quadstrat.md", + "Custom Quadrature Rules" => "manual/quadrule.md", + "Custom Operators" => "manual/customop.md", + "Application Examples"=>Any[ + "Time-Harmonic"=>Any[ + "EFIE"=>"manual/examplesTH/efie.md", + "MFIE"=>"manual/examplesTH/mfie.md", + ], + "Time-Domain"=>Any["EFIE"=>"manual/examplesTD/tdefie.md"], + ], + "System of Equations and Bilinear Forms" => "manual/bilinear.md", + ], + "Tutorials" => Any[ + "tutorials/efie.md", + ], + "Operators & Excitations" => Any[ + "Overview"=>"operators/overview.md", + "Local Operators"=>Any["Identiy"=>"operators/identity.md",], + "Boundary Integral Operators"=>Any[ + "Helmholtz"=>"operators/helmholtz.md", + "Maxwell Single Layer"=>Any[ + "Time Harmonic"=>"operators/maxwellsinglelayer.md", + "Time Domain"=>"operators/maxwellsinglelayer_td.md", + ], + "Maxwell Double Layer"=>Any[ + "Time Harmonic"=>"operators/maxwelldoublelayer.md", + "Time Domain"=>"operators/maxwelldoublelayer_td.md", + ], + ], + "Volume Integral Operators"=>Any[ + "Maxwell Single Layer"=>"operators/maxwellsinglelayerVIE.md", + "Maxwell Double Layer"=>Any[], + ], + "Excitations"=>Any[ + "Plane Wave"=>"excitations/planewave.md", + "Dipole"=>"excitations/dipole.md", + "Monopole"=>"excitations/monopole.md", + "Linear Potential"=>"excitations/linearpotential.md", + ], + ], + "Basis Functions" => Any[ + "Overview"=>"bases/overview.md", + "Spatial"=>Any[ + "Raviart Thomas"=>"bases/raviartthomas.md", + "Buffa Christiansen"=>"bases/buffachristiansen.md", + "Brezzi-Douglas-Marini"=>"bases/brezzidouglasmarini.md", + "Graglia-Wilton-Peterson"=>"bases/gragliawiltonpeterson.md", + ], + "Temporal"=>Any[], + ], + "Geometry Representations" => Any["Flat"=>"geometry/flat.md", "Curvilinear"=>Any[]], + "____________________________________" => Any[], + "Internals" => Any[ + "Overview"=>"internals/overview.md", + "The Matrix Assemble Routine"=>"internals/assemble.md", + "Quadrature"=>"internals/quadstrat.md", + "Parametric Domain"=>Any[], + "Multithreading"=>Any[], + ], + "Contributing" => "contributing.md", + "References" => "references.md", + "API Reference" => "apiref.md", + ], +) + +deploydocs(; + repo="github.com/krcools/BEAST.jl.git", + target="build", + # push_preview=true, + forcepush=false, + #devbranch = "feature/docs", +) diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml deleted file mode 100644 index 004c791e..00000000 --- a/docs/mkdocs.yml +++ /dev/null @@ -1,17 +0,0 @@ -site_name: BEAST.jl -site_author: krcools -site_description: Documentation for BEAST.jl -repo_url: https://github.com/krcools/BEAST.jl - -markdown_extensions: - - mdx_math - -extra_javascript: - - https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS_HTML - - assets/mathjaxhelper.js - -docs_dir: build -pages: - - Home: index.md - - Tutorial: tutorial.md - - Assembly: assemble.md diff --git a/docs/src/apiref.md b/docs/src/apiref.md new file mode 100644 index 00000000..9b37de9f --- /dev/null +++ b/docs/src/apiref.md @@ -0,0 +1,8 @@ + +# API Reference + + + +```@autodocs +Modules = [BEAST,VIE,Maxwell3D,TDMaxwell3D,BEAST.Variational] +``` \ No newline at end of file diff --git a/docs/src/assets/currentREADME.png b/docs/src/assets/currentREADME.png new file mode 100644 index 00000000..659f5031 Binary files /dev/null and b/docs/src/assets/currentREADME.png differ diff --git a/docs/src/assets/currentRealREADME.png b/docs/src/assets/currentRealREADME.png new file mode 100644 index 00000000..dedcd463 Binary files /dev/null and b/docs/src/assets/currentRealREADME.png differ diff --git a/docs/src/assets/logo-dark.svg b/docs/src/assets/logo-dark.svg new file mode 100644 index 00000000..0933c2a5 --- /dev/null +++ b/docs/src/assets/logo-dark.svg @@ -0,0 +1,474 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/src/assets/logo.svg b/docs/src/assets/logo.svg new file mode 100644 index 00000000..92a297f0 --- /dev/null +++ b/docs/src/assets/logo.svg @@ -0,0 +1,474 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/src/assets/logo_README.svg b/docs/src/assets/logo_README.svg new file mode 100644 index 00000000..877c7be0 --- /dev/null +++ b/docs/src/assets/logo_README.svg @@ -0,0 +1,486 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + BEAST + + diff --git a/docs/src/assets/logo_README_white.svg b/docs/src/assets/logo_README_white.svg new file mode 100644 index 00000000..62247f56 --- /dev/null +++ b/docs/src/assets/logo_README_white.svg @@ -0,0 +1,300 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + BEAST + + diff --git a/docs/src/assets/postproc.md b/docs/src/assets/postproc.md deleted file mode 100644 index ce87622a..00000000 --- a/docs/src/assets/postproc.md +++ /dev/null @@ -1,25 +0,0 @@ -# Post-processing and Visualisation - -## Field computation - -The main API for the computation of fields in a vector of points is the potential function. This function is capable of computing - -```math -F(x) = \int_{\Gamma} K(x,y) u(y) dy -``` - -with ``u(y) = \Sigma_{i=1}^N u_i f_i(y)`` and ``K(x,y)`` the integation kernel defining the type of potential. For example, the far field for a vector valued surface density is - -```math -F(x) = \int_{\Gamma} e^{ik \frac{x \cdot y}{|x|}} u(y) dy -``` - -For a far field potential such as this, the value only depends on the direction, not the magnitude, of ``x``, as can be read off from the normalisation in the exponent. - -The following script computes the far field along a semi-circle in the xz-plane. - -```julia -Θ, Φ = range(0.0,stop=2π,length=100), 0.0 -dirs = [point(cos(ϕ)*sin(θ), sin(ϕ)*sin(θ), cos(θ)) for θ in Θ for ϕ in Φ] -farfield = potential(MWFarField3D(wavenumber=κ), dirs, u, X) -``` diff --git a/docs/src/bases/brezzidouglasmarini.md b/docs/src/bases/brezzidouglasmarini.md new file mode 100644 index 00000000..6a9af90f --- /dev/null +++ b/docs/src/bases/brezzidouglasmarini.md @@ -0,0 +1,2 @@ + +# Title \ No newline at end of file diff --git a/docs/src/bases/buffachristiansen.md b/docs/src/bases/buffachristiansen.md new file mode 100644 index 00000000..91181233 --- /dev/null +++ b/docs/src/bases/buffachristiansen.md @@ -0,0 +1,20 @@ + +# [Buffa Christiansen Basis Functions](@id bcDef) + +Another citation [buffaDualFiniteElement2007](@cite) + +--- +## First Order + +```@docs +BEAST.buffachristiansen +``` + +### Triangles + +Some details. + + +### Quadrilaterals + +Not implemented yet. \ No newline at end of file diff --git a/docs/src/bases/gragliawiltonpeterson.md b/docs/src/bases/gragliawiltonpeterson.md new file mode 100644 index 00000000..8c8d1d17 --- /dev/null +++ b/docs/src/bases/gragliawiltonpeterson.md @@ -0,0 +1,4 @@ + +# [Graglia-Wilton-Peterson](@id gwpRef) + +TODO \ No newline at end of file diff --git a/docs/src/bases/overview.md b/docs/src/bases/overview.md new file mode 100644 index 00000000..1d4f3489 --- /dev/null +++ b/docs/src/bases/overview.md @@ -0,0 +1,9 @@ + +# [Bases Overview](@id basesRef) + +```@example introductory +using TypeTree +using BEAST + +print(join(tt(BEAST.Space), "")) +``` \ No newline at end of file diff --git a/docs/src/bases/raviartthomas.md b/docs/src/bases/raviartthomas.md new file mode 100644 index 00000000..f8ddc491 --- /dev/null +++ b/docs/src/bases/raviartthomas.md @@ -0,0 +1,28 @@ + +# [Raviart-Thomas Basis Functions](@id raviartthomasDef) + +Raviart-Thomas basis functions are also known as Rao-Wilton-Glisson functions [raoElectromagneticScatteringSurfaces1982](@cite) and commonly employed for ... + + +--- +## First Order + +```@docs +BEAST.raviartthomas +``` + +### Triangles + +Details on definition. + + +### Quadrilaterals + +Details on definition. + + + +--- +## Higher Order + +... same as GWP? \ No newline at end of file diff --git a/docs/src/contributing.md b/docs/src/contributing.md new file mode 100644 index 00000000..f207c5d8 --- /dev/null +++ b/docs/src/contributing.md @@ -0,0 +1,60 @@ + +# [Contributing](@id contribRef) + +In order to contribute to this package directly create a pull request against the `master` branch. Before doing so please: + +- Follow the style of the surrounding code. +- Supplement the documentation. +- Write tests and check that no errors occur. + + +--- +## Style + +For a consistent style the [JuliaFormatter.jl](https://github.com/domluna/JuliaFormatter.jl) package is used which enforces the style defined in the *.JuliaFormatter.toml* file. To follow this style simply run +```julia +using JuliaFormatter +format(pkgdir(BEAST)) +``` + +!!! note + That all files follow the JuliaFormatter style is tested during the unit tests. Hence, do not forget to execute the two lines above. Otherwise, the tests are likely to not pass. + + +--- +## Documentation + +Add documentation for any changes or new features following the style of the existing documentation. For more information you can have a look at the [Documenter.jl](https://documenter.juliadocs.org/stable/) documentation. + + +--- +## [Tests](@id tests) + +Write tests for your code changes and verify that no errors occur, e.g., by running +```julia +using Pkg +Pkg.test("BEAST") +``` + +For more detailed information on which parts are tested the coverage can be evaluated on your local machine, e.g., by +```julia +using Pkg +Pkg.test("BEAST"; coverage=true, julia_args=`--threads 4`) + +# determine coverage +using Coverage +src_folder = pkgdir(BEAST) * "/src" +coverage = process_folder(src_folder) +LCOV.writefile("path-to-folder-you-like" * "BEAST.lcov.info", coverage) + +clean_folder(src_folder) # delete .cov files + +# extract information about coverage +covered_lines, total_lines = get_summary(coverage) +@info "Current coverage:\n$covered_lines of $total_lines lines ($(round(Int, covered_lines / total_lines * 100)) %)" +``` + +In Visual Studio Code the [Coverage Gutters](https://marketplace.visualstudio.com/items?itemName=ryanluker.vscode-coverage-gutters) plugin can be used to visualize the tested lines of the code by inserting the path of the *BEAST.lcov.info* file in the settings. + +!!! note + From Julia 1.11 onwards the the coverage can be displayed in Visual Studio Code directly, since the [TestItemRunner.jl](https://www.julia-vscode.org/docs/stable/userguide/testitems/#Code-coverage) package is employed. \ No newline at end of file diff --git a/docs/src/excitations/dipole.md b/docs/src/excitations/dipole.md new file mode 100644 index 00000000..2c471d6e --- /dev/null +++ b/docs/src/excitations/dipole.md @@ -0,0 +1,15 @@ + +# [Dipole Excitation](@id dipoleRef) + +A dipole ... + +--- +## Time-Harmonic + +TODO + +### API + +```@docs +BEAST.dipolemw3d +``` \ No newline at end of file diff --git a/docs/src/excitations/linearpotential.md b/docs/src/excitations/linearpotential.md new file mode 100644 index 00000000..33217437 --- /dev/null +++ b/docs/src/excitations/linearpotential.md @@ -0,0 +1,11 @@ + +# [Linear Potential Excitation](@id linpotRef) + +Text + +## API + +```@docs; canonical=false +BEAST.HH3DLinearPotential +BEAST.linearpotentialvie +``` \ No newline at end of file diff --git a/docs/src/excitations/monopole.md b/docs/src/excitations/monopole.md new file mode 100644 index 00000000..330f2332 --- /dev/null +++ b/docs/src/excitations/monopole.md @@ -0,0 +1,11 @@ + +# [Monopole Excitation](@id monopoleRef) + +Text + + +## API + +```@docs; canonical=false +BEAST.HH3DMonopole +``` \ No newline at end of file diff --git a/docs/src/excitations/planewave.md b/docs/src/excitations/planewave.md new file mode 100644 index 00000000..c87e2436 --- /dev/null +++ b/docs/src/excitations/planewave.md @@ -0,0 +1,42 @@ + +# [Plane Wave Excitation](@id planewaveEx) + +A plane wave can be used as excitation in time-harmonic and time-domain scenarios. + +--- +## Time-Harmonic + +A time-harmonic plane wave with amplitude ``a``, wave vector ``\bm k = k \hat{\bm k}``, and polarization ``\hat{\bm p}`` (vectors with a hat denote unit vectors) is defined by the field +```math +\bm e_\mathrm{PW}(\bm x) = a \hat{\bm p} \, \mathrm{e}^{-\mathrm{j} \bm k \cdot \bm x} \,, +``` +where the polarization and wave vector are orthogonal, that is, +```math +\bm k \cdot \hat{\bm p} = 0 +``` +holds. + +### API + +!!! warning + There are currently two APIs for the BEM plane wave. Fix in future. + +```@docs +BEAST.planewavemw3d +Maxwell3D.planewave +BEAST.planewavevie +``` + +--- +## Time-Domain + +A plane wave in the time-domain is defined as ... + + +### API + +Some more details would be helpful here. + +```@docs +BEAST.planewave +``` \ No newline at end of file diff --git a/docs/src/geometry/flat.md b/docs/src/geometry/flat.md new file mode 100644 index 00000000..76bc3389 --- /dev/null +++ b/docs/src/geometry/flat.md @@ -0,0 +1,14 @@ +# Geometry + +```@setup geo +import PlotlyBase +import PlotlyDocumenter +``` + +```@example geo +using CompScienceMeshes +Γ = meshsphere(radius=1.0, h=0.35) +pt = CompScienceMeshes.patch(Γ) +pl = PlotlyBase.Plot(pt) +PlotlyDocumenter.to_documenter(pl) # hide +``` diff --git a/docs/src/index.md b/docs/src/index.md index 04ee0b30..71e46c99 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -1,73 +1,90 @@ -```@meta -CurrentModule = BEAST -``` - -# BEAST.jl documentation -BEAST provides a number of types modelling concepts and a number of algorithms for the efficient and simple implementation of boundary and finite element solvers. It provides full implementations of these concepts for the LU based solution of boundary integral equations for the Maxwell and Helmholtz systems. +# BEAST.jl -Because Julia only compiles code at execution time, users of this library can hook into the code provided in this package at any level. In the extreme case it suffices to provide overwrites of the `assemble` functions. In that case, only the LU solution will be performed by the code here. +This Julia package, the *boundary element analysis and simulation toolkit (BEAST)*, provides routines to convert integral and differential equations to linear systems of equations +via the boundary element method (BEM) and the finite element method (FEM). +To this end, the (Petrov-) **Galerkin method** is employed. -At the other end it suffices that users only supply integration kernels that act on the element-element interaction level. This package will manage all required steps for matrix assembly. +Currently, the focus is on equations encountered in **classical electromagnetism**, where frequency and time domain equations are covered. +Several [operators](@ref operator), [basis functions](@ref basesRef), and geometry representations are implemented. -For the Helmholtz 2D and Maxwell 3D systems, complete implementations are supplied. These models will be discussed in detail to give a more concrete idea of the APIs provides and how to extend them. +!!! note + SI units and for time-harmonic simulations a time convention of ``\mathrm{e}^{\,\mathrm{j}\omega t}`` are used everywhere. -Central to the solution of boundary integral equations is the assembly of the system matrix. The system matrix is fully determined by specifying a kernel G, a set of trial functions, and a set of test functions. +!!! tip + To use the code have a look at the [general usage](@ref usageRef). -## Basis + However, the code is designed such that users can easily hook into the code at any level and implement new features. + To do so, have a look at the [internals documentation](@ref InternalsRef) and the [contribution guidelines](@ref contribRef). + Design goals are extendability and a performant execution. -Sets of both trial and testing functions are implemented by models following the basis concept. The term basis is somewhat misleading as it is nowhere required nor enforced that these functions are linearly independent. Models implementing the Basis concept need to comply to the following semantics. +--- +## Installation -- [`numfunctions(basis)`](@ref): number of functions in the Basis. -- [`coordtype(basis)`](@ref): type of (the components of) the values taken on by the functions in the Basis. -- [`scalartype(d)`](@ref): the scalar field underlying the vector space the basis functions take value in. -- [`refspace(basis)`](@ref): returns the ReferenceSpace of local shape functions on which the Basis is built. -- [`assemblydata(basis)`](@ref): `assemblydata` returns an iterable collection `elements` of geometric elements and a look table `ad` for use in assembly of interaction matrices. In particular, for an index `element_idx` into `elements` and an index `local_shape_idx` in basis of local shape functions `refspace(basis)`, `ad[element_idx, local_shape_idx]` returns the iterable collection of `(global_idx, weight)` tuples such that the local shape function at `local_shape_idx` defined on the element at `element_idx` contributes to the basis function at `global_idx` with a weight of `weight`. -- [`geometry(basis)`](@ref): returns an iterable collection of Elements. The order in which these Elements are encountered corresponds to the indices used in the assembly data structure. +Installing BEAST is done by entering the package manager (enter `]` at the julia REPL) and issuing: +``` +pkg> add BEAST +``` -## Reference Space -The *reference space* concept defines an API for working with spaces of local shape functions. The main role of objects implementing this concept is to allow specialization of the functions that depend on the precise reference space used. +--- +## Overview -The functions that depend on the type and value of arguments modeling *reference space* are: +The following [operators](@ref operator), [basis functions](@ref basesRef), and geometry representations are implemented. +To see details and all variations, have a look at the corresponding sections of this documentation. -- [`numfunctions(refspace)`](@ref): returns the number of shape functions on each element. +### Operators -## Kernel +- **Boundary Integral operators** + + Maxwell (3D) + - Single Layer (time-harmonic & time-domain) + - Double Layer (time-harmonic & time-domain) + + Helmholtz (2D & 3D) + - Single Layer + - Double Layer -A kernel is a fairly simple concept that mainly exists as part of the definition of a Discrete Operator. A kernel should obey the following semantics: +- **Volume Integral operators** + + Maxwell + - Single Layer (time-harmonic) + - Double Layer (time-harmonic) -In many function definitions the kernel object is referenced by `operator` or something similar. This is a misleading name as an operator definition should always be accompanied by the domain and range space. +- **Local Operators** + + Identity (+ variations thereof) -## Discrete Operator +```@raw html +
+``` -Informally speaking, a Discrete Operator is a concept that allows for the computation of an interaction matrix. It is a kernel together with a test and trial basis. A Discrete Operator can be passed to `assemble` and friends to compute its matrix representation. +### Basis functions -A discrete operator is a triple `(kernel, test_basis, trial_basis)`, where `kernel` is a Kernel, and `test_basis` and `trial_basis` are Bases. In addition, the following expressions should be implemented and behave according to the correct semantics: +- **Spatial** + + Low Order + - Raviart-Thomas / Rao-Wilton-Glisson + - Buffa-Christiansen + - Brezzi-Douglas-Marini + + High Order + - Graglia-Wilton-Peterson (GWP) + - B-Spline based -- [`quaddata(operator,test_refspace,trial_refspace,test_elements,trial_elements)`](@ref): create the data required for the computation of element-element interactions during assembly of discrete operator matrices. -- [`quadrule(operator,test_refspace,trial_refspace,p,test_element,q_trial_element,qd)`](@ref): returns an integration strategy object that will be passed to `momintegrals!` to select an integration strategy. This rule can depend on the test/trial reference spaces and interacting elements. The indices `p` and `q` refer to the position of the interacting elements in the enumeration defined by `geometry(basis)` and allow for fast retrieval of any element specific data stored in the quadrature data object `qd`. -- [`momintegrals!(operator,test_refspace,trial_refspace,test_element,trial_element,zlocal,qr)`](@ref): this function computes the local interaction matrix between the set of local test and trial shape functions and a specific pair of elements. The target matrix `zlocal` is provided as an argument to minimise memory allocations over subsequent calls. `qr` is an object returned by `quadrule` and contains all static and dynamic data defining the integration strategy used. -In the context of fast methods such as the Fast Multipole Method other algorithms on Discrete Operators will typically be defined to compute matrix vector products. These algorithms do not explicitly compute and store the interaction matrix (this would lead to unacceptable computational and memory complexity). +- **Temporal** + + Lagrange? + + ... -```@docs -elements +```@raw html +
``` -```@docs -numfunctions -coordtype -scalartype -assemblydata -geometry -refspace -``` +### Geometry Representations + +- **Low Order** ("flat") + + Triangular + + Quadrilaterals + + Tetrahedra + + +- **High Order** (curvilinear) + + NURBS-surfaces -```@docs -quaddata -quadrule -momintegrals! -``` diff --git a/docs/src/assemble.md b/docs/src/internals/assemble.md similarity index 97% rename from docs/src/assemble.md rename to docs/src/internals/assemble.md index f937e44f..00824548 100644 --- a/docs/src/assemble.md +++ b/docs/src/internals/assemble.md @@ -134,7 +134,7 @@ function quaddata(operator::SingleLayerTrace, end function quadrule(op::SingleLayerTrace, g::LagrangeRefSpace, f::LagrangeRefSpace, i, τ, j, σ, qd) - DoubleQuadStrategy( + DoubleQuadRule( qd.tpoints[1,i], qd.bpoints[1,j] ) @@ -145,7 +145,7 @@ integrand(op::SingleLayerTrace, kernel, g, τ, f, σ) = f[1]*g[1]*kernel.green Every kernel corresponds with a type. Kernels can potentially depend on a set of parameters; these appear as fields in the type. Here our Nitsche kernel depends on the wavenumber. In quaddata we precompute quadrature points for all geometric cells in the supports of test and trial elements. This is fairly sloppy: only one rule for test and trial integration is considered. A high accuracy implementation would typically compute points for both low quality and high quality quadrature rules. -Also `quadrule` is sloppy: we always select a `DoubleQuadStrategy` to perform the computation of interactions between local shape functions. No singularity extraction or other advanced technique is considered for nearby interactions. Clearly amateurs at work here! +Also `quadrule` is sloppy: we always select a `DoubleQuadRule` to perform the computation of interactions between local shape functions. No singularity extraction or other advanced technique is considered for nearby interactions. Clearly amateurs at work here! `BEAST` provides a default implementation of an integration routine using double numerical quadrature. All that is required to tap into that implementation is a method overloading `integrand`. From the above formula it is clear what this method should look like. diff --git a/docs/src/internals/overview.md b/docs/src/internals/overview.md new file mode 100644 index 00000000..7d1924ad --- /dev/null +++ b/docs/src/internals/overview.md @@ -0,0 +1,69 @@ + +# [Internals](@id InternalsRef) + + +- In general, the framework is designed such that it allows to easily add support for more kernels, finite element spaces, and excitations. +- Key are assembly routines that take in symbolic representations of the defining bilinear form. Support for block systems and finite element spaces defined in terms of direct products or tensor products of atomic spaces. + + +```@meta +CurrentModule = BEAST +``` + + +## Basis + +Sets of both trial and testing functions are implemented by models following the basis concept. The term basis is somewhat misleading as it is nowhere required nor enforced that these functions are linearly independent. Models implementing the Basis concept need to comply to the following semantics. + + +- [`numfunctions(basis)`](@ref numfunctions): number of functions in the Basis. +- [`coordtype(basis)`](): type of (the components of) the values taken on by the functions in the Basis. +- [`scalartype(d)`](@ref): the scalar field underlying the vector space the basis functions take value in. +- [`refspace(basis)`](@ref): returns the ReferenceSpace of local shape functions on which the Basis is built. +- [`assemblydata(basis)`](@ref): `assemblydata` returns an iterable collection `elements` of geometric elements and a look table `ad` for use in assembly of interaction matrices. In particular, for an index `element_idx` into `elements` and an index `local_shape_idx` in basis of local shape functions `refspace(basis)`, `ad[element_idx, local_shape_idx]` returns the iterable collection of `(global_idx, weight)` tuples such that the local shape function at `local_shape_idx` defined on the element at `element_idx` contributes to the basis function at `global_idx` with a weight of `weight`. +- [`geometry(basis)`](@ref): returns an iterable collection of Elements. The order in which these Elements are encountered corresponds to the indices used in the assembly data structure. + + +## Reference Space + +The *reference space* concept defines an API for working with spaces of local shape functions. The main role of objects implementing this concept is to allow specialization of the functions that depend on the precise reference space used. + +The functions that depend on the type and value of arguments modeling *reference space* are: + +- [`numfunctions(refspace, domain)`](@ref): returns the number of shape functions on each element. + +## Kernel + +A kernel is a fairly simple concept that mainly exists as part of the definition of a Discrete Operator. A kernel should obey the following semantics: + +In many function definitions the kernel object is referenced by `operator` or something similar. This is a misleading name as an operator definition should always be accompanied by the domain and range space. + +## Discrete Operator + +Informally speaking, a Discrete Operator is a concept that allows for the computation of an interaction matrix. It is a kernel together with a test and trial basis. A Discrete Operator can be passed to `assemble` and friends to compute its matrix representation. + +A discrete operator is a triple `(kernel, test_basis, trial_basis)`, where `kernel` is a Kernel, and `test_basis` and `trial_basis` are Bases. In addition, the following expressions should be implemented and behave according to the correct semantics: + +- [`quaddata(operator,test_refspace,trial_refspace,test_elements,trial_elements)`](@ref): create the data required for the computation of element-element interactions during assembly of discrete operator matrices. +- [`quadrule(operator,test_refspace,trial_refspace,p,test_element,q_trial_element,qd)`](@ref): returns an integration strategy object that will be passed to `momintegrals!` to select an integration strategy. This rule can depend on the test/trial reference spaces and interacting elements. The indices `p` and `q` refer to the position of the interacting elements in the enumeration defined by `geometry(basis)` and allow for fast retrieval of any element specific data stored in the quadrature data object `qd`. +- [`momintegrals!(operator,test_refspace,trial_refspace,test_element,trial_element,zlocal,qr)`](@ref): this function computes the local interaction matrix between the set of local test and trial shape functions and a specific pair of elements. The target matrix `zlocal` is provided as an argument to minimise memory allocations over subsequent calls. `qr` is an object returned by `quadrule` and contains all static and dynamic data defining the integration strategy used. + +In the context of fast methods such as the Fast Multipole Method other algorithms on Discrete Operators will typically be defined to compute matrix vector products. These algorithms do not explicitly compute and store the interaction matrix (this would lead to unacceptable computational and memory complexity). + +```@docs; canonical=false +elements +``` + +```@docs; canonical=false +numfunctions +scalartype +assemblydata +geometry +refspace +``` + +```@docs; canonical=false +quaddata +quadrule +momintegrals! +``` diff --git a/docs/src/internals/quadstrat.md b/docs/src/internals/quadstrat.md new file mode 100644 index 00000000..ac2b37e4 --- /dev/null +++ b/docs/src/internals/quadstrat.md @@ -0,0 +1,82 @@ +# Quadrature strategies + +There are many ways to approximately compute the singular integrals that appear in boundary element discretisations of surface and volume integral euqations. + +BEAST.jl is configured to select reasonable defaults, but advanced users may want to select their own quadrature rules. This section provides information on how to do this. + +!!! warning + TODO: discuss directly available/implemented singularity treatment + +## quaddata and quadrule + +Numerical quadrature is governed by a pair of functions that need to be designed to work together: + +- `quaddata`: this function is executed before the assembly loop is entered. It's job is to compute all data needed for quadrature that the developer wants to be cached. Typically this is all geometric information such as the parametric and cartesian coordinates of all quadratures rules for all elemenents. It makes sense to cache this data as it will be used many times over in the double for loop that governs assembly. Typically, near singular interactions require more careful treatment than far interactions. This means that multiple quadrature rules per elements can be required. In such cases, the developer may want to opt to sture quadrature points and weights for all these rules. The function returns a quaddata object that holds all the cached data. +- `quadrule`: quadrule is executed inside the assembly hotloop. It receives a pair of elements and the quaddata object as its arguments. Based on this, the relevant cached data is extracted and stored in a quadrule object. The type of this object will determine the actual quadrature routined that will be called upon to do the numerical quadrature. + +## quadstrat + +The pair of quaddata and quadrule methods that is used is determined by the type of the operator and finite elements, and a `quadstrat` object. This object is passed to the assembly routine and passed on to `quaddata` and `quadstrat`, so it can be considered during dispatch. + +Parameters, such as those that determine the accuracy of the numerical quadrature, are part of the runtime payload of the quadstrat object. This is usefull when the user is interested on the impact of these parameters on the performance and the accuracy of the solver without having to supply a new pair of `quadstrat`/`quaddata` methods for each possible value of these parameters. + +Roughly this leads to the following (simplified) assembly routine: + +```julia +function assemble(op, tfs, bfs, store; quadstrat=QS) + + tad, tels = assemblydata(op, tfs) + bad, bels = assemblydata(op, bfs) + + qd = quadata(op,tels,bels,quadstrat) + for tel in tels + for bel in bels + qr = quadrule(op,tel,bel,qd,quadstrat) + zlocal = momintegrals(op,tel,bel,qr) + + for i in axes(zlocal,1) + for j in axes(zlocal,2) + m, a = tad[tel,i] + n, b = bad[bel,j] + store(a*zlocal[i,j]*b,m,n) +end end end end end +``` + +It is conceivable that the types and functions described above look like this: + +```julia +struct DoubleNumQS + test_precision + trial_precision +end + +function quaddata(op, tels, bels, quadstrat::DoubleNumQS) + tqps = [quadpoints(tel,precision=quadstrat.test_precision) for tel in tels] + bqps = [quadpoints(bel,precision=quadstrat.basis_precision) for bel in bels] + return (test_quadpoints=tqps, basis_quadpoints=bqps) +end + + +struct DoubleNumQR + test_quadpoints + trial_quadpoints +end + +struct HighPrecisionQR end + +function quadrule(op, tel, bel, qd, quadstrat::DoubleNumQs) + if wellseparated(tel, bel) + return DoubleNumQR(qd.test_quadpoints[tel], qd.basis_quadpoints[bel]) + else + return HighPrecisionQR(tel, bel) + end +end + +function momintegrals(op, tel, bel, qr::DoubleNumQR) + ... +end + +function momintegrals(op, tel, bel, qr::HighPrecisionQR) + ... +end +``` \ No newline at end of file diff --git a/docs/src/manual/bilinear.md b/docs/src/manual/bilinear.md new file mode 100644 index 00000000..13cdef33 --- /dev/null +++ b/docs/src/manual/bilinear.md @@ -0,0 +1,134 @@ +# Systems of boundary integral equations and bilinear forms + +For the most simple variational formulations such as the EFIE and MFIE, the boundary element matrices and right hand sided can be manually assembled. + +For more complex formulations, such as those encountered in solving the transmission problem and in problems involving composite systems, this approach quickly becomes unwieldy. To facilitate the formulation and solution of these problems, BEAST.jl provides the ability to define quite general linear and bilinear forms. + +## The single body transmission problem + +THe transmission problem is defined by the material properties of the exterior and interior domain and the incident field: + +```@example transmission +using CompScienceMeshes +using BEAST + +κ1 = 1.0 +κ2 = 2.0 + +E = Maxwell3D.planewave(direction=ẑ, polarization=x̂, wavenumber=κ1) +H = -1/(im*κ1)*curl(E) +nothing # hide +``` + +The PMCHWT can be used to model this transmission problem. To write down the variational formulation, pairs of placeholders for the trial functions and test functions are declared by the `@hilbertspace` macro. + +```@example transmission +T1 = Maxwell3D.singlelayer(wavenumber=κ1) +K1 = Maxwell3D.doublelayer(wavenumber=κ1) +T2 = Maxwell3D.singlelayer(wavenumber=κ2) +K2 = Maxwell3D.doublelayer(wavenumber=κ2) + +e = (n × E) × n +h = (n × H) × n + +@hilbertspace j m +@hilbertspace k l + +a = + (T1+T2)[k,j] + (-K1-K2)[k,m] + + (K1+K2)[l,j] + (T1+T2)[l,m] +b = + -e[k] -h[l] +``` + +Note that we are able to define the formulation without specifying or constructing the mesh, the boundary element spaces, or the system matrices. The above constitutes a lazy representation of the formulation that takes neglible time to construct! + +Now this formulation is applied to a concrete geometry. + +```@example transmission +Γ = meshsphere(;radius=1.0, h=0.35) +RT = raviartthomas(Γ) + +X = BEAST.DirectProductSpace([RT,RT]) + +bx = assemble(b, X) +Axx = assemble(a, X, X) +``` + +The type of `Axx` reveals that the structure of the underlying Hilbert space (with two generators in this case) is preserved. Also noteworthy is that only non-zero blocks in the system matrix are stored. This is important both for to limit memory consumption and to avoid unnecessary computations in performing the matrix-vector product. + +BEAST.jl provides wrappers for iterative solvers that conform to the `LinearMaps.jl` interface. For example, `SXX = GMRESSolver(Axx)` acts like a `LinearMap` representing the inverse of `Axx`. Computing the action `uX = SXX * bx` runs the Krylov iterative process with right hand side `bx` and returns the solution to the linear system: + +```@example transmission +SXX = BEAST.GMRESSolver(Axx) +uX = SXX * bx +typeof(uX) +``` + +The solution of this iterative process *remembers* its block structure. This makes it easy to select the electric and magnetic components: + +```@example transmission +import PlotlyBase +import PlotlyDocumenter # hide +using LinearAlgebra + +fcrm, geom = facecurrents(uX[m], RT) +fcrj, geoj = facecurrents(uX[j], RT) + +ptm = CompScienceMeshes.patch(geom, norm.(fcrm); caxis=(0,1.2) , showscale=false) +ptj= CompScienceMeshes.patch(geoj, norm.(fcrj); caxis=(0,1.2)) + +pl = [ PlotlyBase.Plot(ptm) PlotlyBase.Plot(ptj) ] +PlotlyDocumenter.to_documenter(pl) # hide +``` + +## Calderon preconditioning for the PMCHWT + +Let's try to speed up convergence of the iterative solver by constructing a Calderon preconditioner. We opt for a block diagonal preconditioner containing only single layer contributions. The wavenumber used for construction of the preconditioner is purely imaginary to avoid the introduction of resonances. + +```@example transmission +Ty = Maxwell3D.singlelayer(gamma=κ1) + +c = Ty[k,j] + Ty[l,m] +``` + +We also require discrete versions of the duality pairing to map primal test coefficients to dual expansion coefficients + +```@example transmission +Nx = BEAST.NCross() +d = Nx[k,j] + Nx[l,m] +``` + +The corresponding block matrices can be built as before by appealing to the `assemble` function + +```@example transmission +BC = buffachristiansen(Γ) +Y = BEAST.DirectProductSpace([BC,BC]) + +Cyy = assemble(c, Y, Y) +Dxy = assemble(d, X, Y) +``` + +A *lazy* inverse for the discrete duality can be constructed by wrapping the block matrix in a Krylov solver object: + +```@example transmission +DYX = BEAST.GMRESSolver(Dxy, verbose=false) +DXY = BEAST.GMRESSolver(Dxy', verbose=false) +``` + +We have now all the ingredients to write down the system we want to solve: + +```@example transmission +PAXx = DXY * Cyy * DYX * Axx +PbX = DXY * Cyy * DYX * bx + +PSXx = BEAST.GMRESSolver(PAXx) + +u1, ch1 = solve(SXX, bx) +u2, ch2 = solve(PSXx, PbX) + +using LinearAlgebra +norm(u1-u2), ch1.iters, ch2.iters +``` + +Even for this small example, the solution was reconstructed in a much smaller number of iterations. Because objects of type `GMRESSolver` effectively behave as the inverse of the wrapped `LinearMap`, an internal iterative solver to compute the action of the inverse of the duality pairing is triggered during each outer iteration without introducing notational overhead! \ No newline at end of file diff --git a/docs/src/manual/customexc.md b/docs/src/manual/customexc.md new file mode 100644 index 00000000..2c6aa1b4 --- /dev/null +++ b/docs/src/manual/customexc.md @@ -0,0 +1,36 @@ +# Defining Custom Excitation + +BEAST.jl aims to use your own fields as excitations as easy as possible. Specifying your own field can be done by defining a function of the Cartesian coordinates and supplementing this definition with a method of `BEAST.scalartype` to announce the scalar type of the value returned by the field evaluation. Scalar type is, as the name suggest, always a scalar type such as `ComplexF64`, even if the field is vector valued. + +Upon supplying this data, the field function happily interacts with the rest of the BEAST.jl infrastructure. For example, taking the trace of the field is in no way different from doing so for the supplied exciations: + +```julia + using CompScienceMeshes + + κ = 1.0 + + # Custom excitations can be defined as functions of a point in Cartesian space, + # complemented with a function specifying the return type. + E(x) = point(ComplexF64, 1.0, 1.0im, 0.0) * exp(-im*κ*x[3]) + BEAST.scalartype(::typeof(E)) = ComplexF64 + + # Such a custom field plays nicely with the tangential trace + # operators defined as part of the BEAST framework + e = (n × E) × n + + fn = joinpath(pathof(BEAST), "../../examples/assets/sphere45.in") + Γ = readmesh(fn) + RT = raviartthomas(Γ) + + t = Maxwell3D.singlelayer(wavenumber=κ) + + @hilbertspace j + @hilbertspace k + + Txx = assemble(t[k,j], j∈RT, k∈RT) + ex = assemble(e[k], k∈RT) + + iTXX = BEAST.GMRESSolver(Txx; maxiter=1000, reltol=1e-5) + uX = iTXX * ex +``` + diff --git a/docs/src/manual/customop.md b/docs/src/manual/customop.md new file mode 100644 index 00000000..dbe6d2e6 --- /dev/null +++ b/docs/src/manual/customop.md @@ -0,0 +1,58 @@ +# Custom Operators + +BEAST.jl provides the single and double layer operators for the 3D Laplace operator, the 2D and 3D Helmholtz equations, and the 3D Maxwell equations. Nevertheless there are situations where the user may want to define their own integral operator. BEAST.jl makes this process as easy as possible. + +Defining a new integral operator can be as simple as defining a type representing the operator, a method for the evaluation of the integral operator's integrand, and a method for `BEAST.scalartype` so the framework can determine the most appropriate storage type. + +```@example customop +using LinearAlgebra +using CompScienceMeshes +using BEAST + +struct BellCurveOp{T} <: BEAST.IntegralOperator + width::T +end +``` + +By default, assembly of new operators is dealt using the `DoubleNumSauterQstrat` quadrature strategy. This strategy selects either a simplex tensorial quadrature rule when triangles are well-separated, or a bespoke Sauter-Schwab rule when triangles have vertices in common. These rules are to a large extend independent of the integrand. When defining operators exhibiting highly irregular integrands, or when using panels for which the mentioned quadrature rules are not applicable, additional implementation may be required. + +At the heart of the operator defintion lies the routine that allows evaluation of the integrand. This should be provided in the following format: + +```@example customop +function (igd::BEAST.Integrand{<:BellCurveOp})(p,q,f,g) + α = igd.operator.width + + x = CompScienceMeshes.cartesian(p) + y = CompScienceMeshes.cartesian(q) + R = LinearAlgebra.norm(x-y) + + BEAST._integrands(f,g) do fi,gj + dot(fi.value, gj.value) * exp(-(R/α)^2) + end +end +``` + +Note that `(p,q)` are neighborhoods (see CompScienceMeshes.jl). The convenience function `BEAST._integrands` populates a `StaticArrays.SMatrix` with all posible combinations of the operator integrans with trial and test functions without allocating memory. + +Finally, a method declaring the scalar type used by the operator needs to be defined so that BEAST.jl can provide the most efficient storage for any combination of real or complex values operators and finite element spaces. + +```@example customop +BEAST.scalartype(op::BellCurveOp{T}) where {T} = T +``` + +```@example customop +Γ = CompScienceMeshes.meshsphere(radius=1.0, h=0.3) +X = BEAST.raviartthomas(Γ) + +op = BellCurveOp(2.0) +Z = assemble(op, X, X) + +sv = svdvals(Z) +import PlotlyBase +t1 = PlotlyBase.scatter(y=sv) +plt = PlotlyBase.Plot([t1]) +import PlotlyDocumenter # hide +PlotlyDocumenter.to_documenter(plt) #hide +``` + +Being a Hilbert-Schmidt operator, the quickly decaying spectrum is to be expected. \ No newline at end of file diff --git a/docs/src/tdefie.md b/docs/src/manual/examplesTD/tdefie.md similarity index 95% rename from docs/src/tdefie.md rename to docs/src/manual/examplesTD/tdefie.md index d4e2af38..0b2a0b2b 100644 --- a/docs/src/tdefie.md +++ b/docs/src/manual/examplesTD/tdefie.md @@ -1,8 +1,11 @@ # Solving the Time Domain EFIE using Marching-on-in-Time +!!! warning + Make the following an @example which is actually executed. + If broadband information is required or if the system under study will be coupled to non-linear components, the scattering problem should be solved directly in the time domain, i.e. as a hyperbolic evolution problem. -## Building the geometry +### Building the geometry Building the geometry and defining the spatial finite elements happens in completely the same manner as for frequency domain simulations: @@ -15,6 +18,8 @@ X = raviartthomas(Γ) nothing # hide ``` +### Basis functions + Time domain currents are approximated in the tensor product of a spatial and temporal finite element space: $j(x) \approx \sum_{i=1}^{N_T} \sum_{m=1}^{N_S} u_{i,m} T_i(t) f_m(x)$ @@ -34,6 +39,8 @@ U = BEAST.timebasisdelta(Δt, Nt) nothing # hide ``` +### Excitation + We want to solve the EFIE, i.e. we want to find the current $j$ such that $Tj = -e^i,$ @@ -50,6 +57,8 @@ T = BEAST.MWSingleLayerTDIO(1.0,-1.0,-1.0,2,0) nothing; # hide ``` +### Setting up the LSE + Using the finite element spaces defined above this retarded potential equation can be discretized. ```julia @@ -71,6 +80,9 @@ W0 = inv(Z0) x = BEAST.marchonintime(W0,Z,-B,Nt) nothing # hide ``` + +### Post processing + Computing the values of the induced current is now possible in the same manner as for frequency domain simulations by first converting our MOT solution back to the frequency domain using the fourier transform, along with some adjustments. ```julia diff --git a/docs/src/manual/examplesTH/efie.md b/docs/src/manual/examplesTH/efie.md new file mode 100644 index 00000000..3e2430ee --- /dev/null +++ b/docs/src/manual/examplesTH/efie.md @@ -0,0 +1,11 @@ + +# Electric Field Integral Equation + +Text + +--- +### Geometry + +Text + +... diff --git a/docs/src/manual/examplesTH/mfie.md b/docs/src/manual/examplesTH/mfie.md new file mode 100644 index 00000000..15f86992 --- /dev/null +++ b/docs/src/manual/examplesTH/mfie.md @@ -0,0 +1,4 @@ + +# Magnetic Field Integral Equation + +TODO \ No newline at end of file diff --git a/docs/src/manual/quadrule.md b/docs/src/manual/quadrule.md new file mode 100644 index 00000000..9f9c9c99 --- /dev/null +++ b/docs/src/manual/quadrule.md @@ -0,0 +1,196 @@ +# Designing your own quadrature rule and strategy + +In the context of multi-trace solvers, testing and trial functions with logically separate but geometrically coinciding support can interact. In general these support can be equipped with completely indepenent meshes. + +For meshes of flat faceted triangular panels, BEAST.jl defines the `BEAST.NonConformingIntegralOpQStrat` strategy. The constructor of `NonConformingIntegralOpQStrat` takes another quadratue strategy `ctrat` that is fit to deal with pairs of mutually conforming meshes of flat faceted meshes. For a pair comprising a test triangle and a trial triangle, the appropriate quadrature rule is chosen as follows: + +- If the triangles are well-separated, `cstrat` is run on the pair and the resulting quadrature strategy is chosen. +- If the triangles have a single vertex in common, the Sauter-Schwab quadrature rule for common vertices is chosen. +- If the triangles overlap or have edges that overlap, they are both refined so that a geometrically conforming mesh is obtained. For each pair of triangles in this refinement, `cstrat` is run and the resulting quadrature strategy is chosen. + +This quadrature strategy is powerful but requires a lot of geometric processing, resulting in significant increases in matrix assembly times. + +When the user has additional knowledge about the precise geometric constellation of the interacting meshes, a more economic approach can be desirable. It is of course the user's responsibility not to use this economic approach in a context where it is not applicable. + +In this tutorial we assume that the test mesh is conforming to the barycentric refinement of the trial mesh. We propose a quadrature strategy `NonConfTestBaryRefOfTrialQStrat`, parametrised by an underlying quadrature strategy fit for mutually conforming meshes, that implements the following algorithm: + +- If the triangles are well-separated, `cstrat` is run on the pair and the resulting quadrature strategy is chosen. +- If the test and trial triangle share a vertex, the barycentric refinement of the trial triangle is constructed and the decision on the quadrature rule is deferred to `cstrat`. + +Because of the a priori knowledge about the relative constellation of the meshes, the construction in the second case will automatically result in a pair of mutually conforming meshes that can safely be send off to `cstrat`. + +The first step in defining a new quadrature strategy is the definition of the corresponding type: + +```julia +struct NonConfTestBaryRefOfTrialQStrat{P} <: BEAST.AbstractQuadStrat + conforming_qstrat::P +end +``` + +The semantics of the quadrature strategy are captured by the definition of a pair of methods for the functions **quaddata** and **quadrule**, respectively. The purpose of quaddata is the computation of cache data that can speed up the assembly. Typically, this involves computing and storing triangle normals, and the quadrature points and weights for the various quadrature rules that the quadrature strategy considers. + +Because in the majority of cases, the computation of the interaction is deferred to the conforming quadrature strategy, the computation of the cache is forwarded to its method of quaddata, resulting simply in: + +```julia +function BEAST.quaddata(a, X, Y, test_charts, trial_charts, + quadstrat::NonConfTestBaryRefOfTrialQStrat) + + return quaddata(a, X, Y, test_charts, trial_charts, + quadstrat.conforming_qstrat) +end +``` + +The method of `quadrule` is key to the definition of the quadrature strategy and contains the the actual algorithm in charge of choosing the quadrature rule for any given pair of triangles: + +```julia +function BEAST.quadrule(a, X, Y, i, test_chart, j, trial_chart, qd, + quadstrat::NonConfTestBaryRefOfTrialQStrat) + + nh = BEAST._numhits(test_chart, trial_chart) + nh > 0 && return TestInBaryRefOfTrialQRule(quadstrat.conforming_qstrat) + return BEAST.quadrule(a, X, Y, i, test_chart, j, trial_chart, qd, + quadstrat.conforming_qstrat) +end +``` + +The function body is essentially a one-to-one translation of the quadrature rule selection algorithm above to julia. The object `qd` passed to `quadrule` is the cache computed by quadrule. In our case, it is simply passed on to the underlying conforming quadrature rule in the case of well separated triangles. + +Next, we define a type representing the quadrature rule that is used when test triangle and trial triangle are not well-separated: + +```julia +struct TestInBaryRefOfTrialQRule{S} + conforming_qstrat::S +end +``` + +The type `TestInBaryRefOfTrialQRule` refers to the quadrature rule that is responsible for the actual computation of the interactions in case of adjacency or overlap. Quadrature rules are implemented by specifying a method for the function `BEAST.momintegrals!`. + +```julia +function BEAST.momintegrals!(out, op, + test_functions, test_cell, test_chart, + trial_functions, trial_cell, trial_chart, + qr::TestInBaryRefOfTrialQRule) + + test_local_space = refspace(test_functions) + trial_local_space = refspace(trial_functions) + + num_tshapes = numfunctions(test_local_space, domain(test_chart)) + num_bshapes = numfunctions(trial_local_space, domain(trial_chart)) + + T = coordtype(test_chart) + z, u, h, t = zero(T), one(T), T(1//2), T(1//3) + + c = CompScienceMeshes.point(T, t, t) + v = ( + CompScienceMeshes.point(T, u, z), + CompScienceMeshes.point(T, z, u), + CompScienceMeshes.point(T, z, z)) + e = ( + CompScienceMeshes.point(T, z, h), + CompScienceMeshes.point(T, h, z), + CompScienceMeshes.point(T, h, h)) + + X = ( + CompScienceMeshes.simplex(v[1], e[3], c), + CompScienceMeshes.simplex(v[2], c, e[3]), + CompScienceMeshes.simplex(v[2], e[1], c), + CompScienceMeshes.simplex(v[3], c, e[1]), + CompScienceMeshes.simplex(v[3], e[2], c), + CompScienceMeshes.simplex(v[1], c, e[2])) + + C = CompScienceMeshes.cartesian( + CompScienceMeshes.neighborhood(trial_chart, c)) + V = CompScienceMeshes.cartesian.(( + CompScienceMeshes.neighborhood(trial_chart, v[1]), + CompScienceMeshes.neighborhood(trial_chart, v[2]), + CompScienceMeshes.neighborhood(trial_chart, v[3]))) + E = CompScienceMeshes.cartesian.(( + CompScienceMeshes.neighborhood(trial_chart, e[1]), + CompScienceMeshes.neighborhood(trial_chart, e[2]), + CompScienceMeshes.neighborhood(trial_chart, e[3]))) + + trial_charts = ( + CompScienceMeshes.simplex(V[1], E[3], C), + CompScienceMeshes.simplex(V[2], C, E[3]), + CompScienceMeshes.simplex(V[2], E[1], C), + CompScienceMeshes.simplex(V[3], C, E[1]), + CompScienceMeshes.simplex(V[3], E[2], C), + CompScienceMeshes.simplex(V[1], C, E[2])) + + quadstrat = qr.conforming_qstrat + qd = BEAST.quaddata(op, test_local_space, trial_local_space, + (test_chart,), trial_charts, quadstrat) + + Q = zeros(T, num_tshapes, num_bshapes) + out1 = zero(out) + for (q,chart) in enumerate(trial_charts) + qr1 = BEAST.quadrule(op, test_local_space, trial_local_space, + 1, test_chart, q ,chart, qd, quadstrat) + + BEAST.restrict!(Q, trial_local_space, trial_chart, chart, X[q]) + + fill!(out1, 0) + BEAST.momintegrals!(out1, op, + test_functions, nothing, test_chart, + trial_functions, nothing, chart, qr1) + + for j in 1:num_bshapes + for i in 1:num_tshapes + for k in 1:size(Q, 2) + out[i,j] += out1[i,k] * Q[j,k] +end end end end end +``` + +The algorithm constructs the charts of the barycentric refinement of the trial chart, which either share a vertex or an edge with the test chart, or completely coincide with the test chart. Because of this, contributions from any of the refinement charts can be computed accuratey by a classic quadrature rule: + +```julia +momintegrals!(outq, op, + test_functions, nothing, test_chart, + trial_functions, nothing, chart, qr) +``` + +This call to `momintegrals!` calculates interactions between the shape functions on the original test chart and the shape functions on one of the six subcharts in the refinement of the trial chart. The corresponding contribution to the interaction with the shape functions on the coarse trial chart can be calculated if we know how the restriction of the coarse shape functions to any of the subcharts can be written as linear combinations of the shape on that subchart. This information is provided by + +```julia +BEAST.restrict!(Q, trial_local_space, trial_chart, chart, X[q]) +``` + +For efficiency, the overlap function from the domain of `chart` to the domain of `trial_chart` has to be supplied. + +!!! note + The quadrature strategy and related quadrature rules implemented here can be rearded as meta-strategies, and meta-rules, as they defer most of the heavy lifting to underlying strategies and rules for mutually conforming meshes. + + In a *primitive* rule, methods of `momintegrals!` typically contain implementations of numerical quadrature methods. + +To verify correctness of the above strategy, we can compare the results against existing routines that either provide less accurate results or similar results at reduced efficiency. + +```julia +@testitem "NonConfTestBaryRefOfTrialQStrat" begin + using CompScienceMeshes + + fnm = joinpath(dirname(pathof(BEAST)), "../test/assets/sphere45.in") + Γ1 = BEAST.readmesh(fnm) + Γ2 = deepcopy(Γ1) + + X = raviartthomas(Γ1) + Y1 = buffachristiansen(Γ1) + Y = buffachristiansen(Γ2) + + K = Maxwell3D.doublelayer(gamma=1.0) + qs1 = BEAST.DoubleNumWiltonSauterQStrat(2, 3, 6, 7, 5, 5, 4, 3) + qs2 = BEAST.NonConformingIntegralOpQStrat(qs1) + qs3 = BEAST.NonConfTestBaryRefOfTrialQStrat(qs1) + + @time Kyx1 = assemble(K, Y, X; quadstrat=qs1) + @time Kyx2 = assemble(K, Y, X; quadstrat=qs2) + @time Kyx3 = assemble(K, Y, X; quadstrat=qs3) + @time Kyx4 = assemble(K, Y1, X; quadstrat=qs1) + + using LinearAlgebra + @test norm(Kyx1 - Kyx2) < 0.05 + @test norm(Kyx1 - Kyx3) < 0.05 + @test norm(Kyx2 - Kyx3) < 0.002 + @test norm(Kyx2 - Kyx4) < 0.002 + @test norm(Kyx3 - Kyx4) < 1e-12 +end +``` diff --git a/docs/src/manual/quadstrat.md b/docs/src/manual/quadstrat.md new file mode 100644 index 00000000..1b7322c4 --- /dev/null +++ b/docs/src/manual/quadstrat.md @@ -0,0 +1,121 @@ +# Customising the choice of quadrature rules + +At the heart of any boundary element code are routines that can compute matrix entries that take on the form of double integrals over pairs of panels of an integrand that is the product of a test function, a trial function, and the fundamental solution of the equation under study. The fundamental solution is singular at the origin, implying that simple quadrature methods are not sufficient to compute the entries to satisfactory accuracy. A mechanism to activate the most appropriate rule is required. BEAST.jl attempts to offer a reasonable default, but the advanced user may wish to take control beyond this. + +This page describes how the user can intervene in this system. + +## List of implemented quadrature strategies + +In BEAST, a clear distinction is made between a **quadrature rule**, which is a numerical algorithm that computes approximate values of a given integral with a given domain (a pair of panels in the case of BEM) and a given integral (comprising testing functions, trial functions, and a fundamental solution in the case of BEM). Different integrands and domains call for different quadrature rules. For a fixed integrand structure, the algorithm that chooses for any given constellation of panels the most appropriate rule is called the **quadrature strategy**. + +The algorithm that determines which quadrature rule is used for any given geometric constellation of interacting panels is called the quadrature strategy. The list of strategies currently implemented in BEAST is: + +```@example introductory +using TypeTree +using BEAST + +print(join(tt(BEAST.AbstractQuadStrat), "")) +nothing # hide +``` + +The type of the quadrature strategy object determines the quadrature rule selection algorithm. The value of the fields contained by the quadrature strategy object typically selects the order or number of quadrature points used by the various possible quadrature rules. + +The methods responsible for caching quadrature related data and quadrature rule selection are named `quaddata` and `quadrule`, respectively. For a given combination of a discrete boundary integral operator and quadrature strategy, the methods that will be dispatched to can be queried as follows: + +```@example introductory +using CompScienceMeshes +using BEAST + +Γ = meshsphere(radius=1.0, h=0.45) # triangulate sphere of radius one +RT = raviartthomas(Γ) +𝑇 = Maxwell3D.singlelayer(wavenumber=2.0) +qs = BEAST.DoubleNumWiltonSauterQStrat(6, 7, 7, 8, 6, 6, 6, 6) +``` + +```@example introductory +BEAST.quadinfo(𝑇, RT, RT; quadstrat=qs) +nothing # hide +``` + +## Explicitly providing the quadrature strategy + +The assembly function takes a keyword argument that allows to specify a specific quadrature strategy to use. + +```@example introductory +using CompScienceMeshes +using BEAST + +Γ = meshsphere(radius=1.0, h=0.45) # triangulate sphere of radius one +RT = raviartthomas(Γ) +𝑇 = Maxwell3D.singlelayer(wavenumber=2.0) + +Z1 = assemble(𝑇, RT, RT; quadstrat=BEAST.DoubleNumWiltonSauterQStrat(2, 3, 6, 7, 5, 5, 5, 5)) +Z2 = assemble(𝑇, RT, RT; quadstrat=BEAST.DoubleNumWiltonSauterQStrat(6, 7, 7, 8, 6, 6, 6, 6)) + +using LinearAlgebra +2 * norm(Z1-Z2) / norm(Z1+Z2) +``` + +This method works well when only a single integral operator appears in the boundary integral equation. For systems containing multiple equations or when linear combinations of different operators appear, specifying a single quadrature strategy at the callsite of `assemble` will likely not be appropriate. + + +## Setting the default quadrature rule + +To query the set default quadrature strategy for a triple `(op, testfns, trialfns)`, + +```@example introductory +using CompScienceMeshes +using BEAST + +Γ = meshsphere(radius=1.0, h=0.45) # triangulate sphere of radius one +RT = raviartthomas(Γ) +𝑇 = Maxwell3D.singlelayer(wavenumber=2.0) + +BEAST.defaultquadstrat(𝑇, RT, RT) +``` + +A new default can be set using the `@defaultquadstrat` macro. This creates a new method for the function `defaultquadstrat` that will be dispatched to for the arguments of the provided types. + +```@example introductory +BEAST.@defaultquadstrat (𝑇, RT, RT) BEAST.DoubleNumWiltonSauterQStrat(6, 7, 7, 8, 6, 6, 6, 6) +Z2 = assemble(𝑇, RT, RT) + +BEAST.@defaultquadstrat (𝑇, RT, RT) BEAST.DoubleNumWiltonSauterQStrat(2, 3, 6, 7, 5, 5, 5, 5) +Z1 = assemble(𝑇, RT, RT) + +using LinearAlgebra +2 * norm(Z1-Z2) / norm(Z1+Z2) +``` + +The above number provides some insight into the accuracy of the selected quadrature strategy. The advantage of setting the default quadrature strategy like this is that it is global and will automatically affect all subsequent calls to assembly. This means the set strategy will be used even if assembly is called as part of the assembly of a more complicated larger system, potentially containing linear combinations of integral operators. + +The downside is that the default can only be set for concrete types of `(op, testfns, trialfns)` and that the call to the macro needs to be at Module scope. This makes it less appealing for complicated simulations and sweeps over the quadrature accuracy. + +# Specifying a quadrature strategy selection method + +This method is somehwat more involved, but is the most general and should allow for full control of the quadrature rules used in assembly. + +```@example introductory +function myquadstrat1(op, testfns, trialfns) + if op isa BEAST.MWSingleLayer3D + return BEAST.DoubleNumWiltonSauterQStrat(2, 3, 6, 7, 5, 5, 5, 5) + end + return BEAST.defaultquadstrat(op, testfns, trialfns) +end + +function myquadstrat2(op, testfns, trialfns) + if op isa BEAST.MWSingleLayer3D + return BEAST.DoubleNumWiltonSauterQStrat(6, 7, 7, 8, 6, 6, 6, 6) + end + return BEAST.defaultquadstrat(op, testfns, trialfns) +end + + +Z1 = assemble(𝑇, RT, RT; quadstrat=myquadstrat1) +Z3 = assemble(𝑇, RT, RT; quadstrat=myquadstrat2) +2 * norm(Z1-Z2) / norm(Z1+Z2) +``` + +Best practice is too return `BEAST.defaultquadstrat(op, testnfs, trialfns)` by default to ensure that all operators are supported, also those for which no explicit overwrite is specified. + + diff --git a/docs/src/manual/usage.md b/docs/src/manual/usage.md new file mode 100644 index 00000000..a32ed0dc --- /dev/null +++ b/docs/src/manual/usage.md @@ -0,0 +1,114 @@ + +# [General Usage](@id usageRef) + +!!! info + The fundamental approach, which applies in most cases is: + 1. Define trial and test functions. + 2. Define an operator and an excitation. + 3. Assemble the system matrix and the right-hand side + +The available basis functions and corresponding geometry representations, available [operators](@ref operator), and excitations are defined in the corresponding sections of this documentation. + + +--- +## Introductory Example: EFIE + +The fundamental procedure is exemplified for the electric field integral equation (EFIE); further common steps are discussed afterwards: + +```@setup introductory +import PlotlyBase +import PlotlyDocumenter +``` + +```@example introductory +using CompScienceMeshes +using BEAST + +# --- 1. basis functions +Γ = meshsphere(radius=1.0, h=0.4) # triangulate sphere of radius one +RT = raviartthomas(Γ) # define basis functions + +# --- 2. operators & excitation +𝑇 = Maxwell3D.singlelayer(wavenumber=2.0) # integral operator +𝐸 = Maxwell3D.planewave(direction=x̂, polarization=ẑ, wavenumber=2.0) # excitation +𝑒 = (n × 𝐸) × n # tangential part + +# --- 3. compute the RHS and system matrix +e = assemble(𝑒, RT) # assemble RHS +T = assemble(𝑇, RT, RT) # assemble system matrix +nothing #hide +``` + +--- +### Explanation + +The example follows the 3 steps. +Specifically, in the example trial and test functions are the same ([Raviart-Thomas](@ref raviartthomasDef)), the excitation is a [plane wave](@ref planewaveEx), and the operator is the [Maxwell single layer operator](@ref MWsinglelayerDef). + +!!! tip + The [`assemble`](@ref assemble) function is the key function of this package, it accepts either *excitation + test function* or *operator + test + trial function*. + + The operator can also be a linear combination of several operators. + +```@docs +assemble +``` + +--- +### Further Common Steps + +The linear system of equations can now, for example, be solved via the iterative GMRES solver of the [Krylov.jl](https://github.com/JuliaSmoothOptimizers/Krylov.jl) package. +However, other solver could be used. +Subsequently, different post-processing steps can be conducted, such as computing the scattered field from the determined expansion coefficients. + +!!! tip + Key functions for the post-processing are the [`potential`](@ref potential) and [`facecurrents`](@ref facecurrents) functions. + +This is shown in the following: + + +```@example introductory +using Krylov, LinearAlgebra + +# --- solve linear system iteratively +u, ch = Krylov.gmres(T, -e, rtol=1e-5) + +fcr, geo = facecurrents(u, RT) +pt = CompScienceMeshes.patch(Γ, norm.(fcr)) +pl = PlotlyBase.Plot(pt) +PlotlyDocumenter.to_documenter(pl) # hide +``` + +```@example introductory +# --- post processing: compute scattered electric field at two Cartesian points +points = [[3.0, 4.0, 2.0], [3.0, 4.0, 3.0]] +EF = potential(MWSingleLayerField3D(gamma=im*2.0), points, u, RT) +``` + + +!!! warning + more details + +```@raw html +
+ Setup +             + Setup +
+
+``` + +--- +## Plotting & Exporting + +Plotting details. + +Export VTK. + +... \ No newline at end of file diff --git a/docs/src/operators/helmholtz.md b/docs/src/operators/helmholtz.md new file mode 100644 index 00000000..e69de29b diff --git a/docs/src/operators/identity.md b/docs/src/operators/identity.md new file mode 100644 index 00000000..944fce7b --- /dev/null +++ b/docs/src/operators/identity.md @@ -0,0 +1,54 @@ + +# [Identity Operator](@id identityDef) + +The identity operator is implemented in two flavors `BEAST.Identity()` and `BEAST.NCross()`. + +--- +## Definition + +The identity operator +```math +\bm{\mathcal{I}} \bm b = \bm b +``` +returns the function it is provided unchanged. + + +### As bilinear form + +When handed to the [`assemble`](@ref assemble) function, the operator is interpreted as the bilinear form +```math +a(\bm t, \bm b) = \int_\Gamma \bm t(\bm x) ⋅ \bm{\mathcal{I}} \bm b(\bm x) \,\mathrm{d}\bm x = \int_\Gamma \bm t(\bm x) ⋅ \bm b(\bm x) \,\mathrm{d}\bm x \,. +``` +Hence, the resulting matrix ``\bm A`` contains the entries +```math +[\bm A]_{mn} = \int_\Gamma \bm t_m(\bm x) ⋅ \bm b_n(\bm x) \,\mathrm{d}\bm x \,. +``` + +### API + +```@docs; canonical=false +BEAST.Identity +``` + + +--- +## Variant with ``\bm n \times`` + +As a variation, the identity operator is provided with a cross product with the normal vector ``\bm n`` of the surface ``\Gamma``. + +### The bilinear form + +The [`assemble`](@ref assemble) function, interprets the operator as the bilinear form +```math +a(\bm t, \bm b) = \int_\Gamma \bm t(\bm x) ⋅ \bm{\mathcal{I}} (\bm n(\bm x) \times \bm b(\bm x)) \,\mathrm{d}\bm x = \int_\Gamma \bm t(\bm x) ⋅ (\bm n(\bm x) \times \bm b(\bm x)) \,\mathrm{d}\bm x \,. +``` +Hence, the resulting matrix ``\bm A`` contains the entries +```math +[\bm A]_{mn} = \int_\Gamma \bm t_m(\bm x) ⋅ (\bm n(\bm x) \times \bm b_n(\bm x)) \,\mathrm{d}\bm x \,. +``` + +### API + +```@docs; canonical=false +BEAST.NCross +``` \ No newline at end of file diff --git a/docs/src/operators/maxwelldoublelayer.md b/docs/src/operators/maxwelldoublelayer.md new file mode 100644 index 00000000..83685609 --- /dev/null +++ b/docs/src/operators/maxwelldoublelayer.md @@ -0,0 +1,76 @@ + +# [Maxwell Double Layer Operator](@id MWdoublelayerDef) + +The Maxwell double layer operator is encountered in many time-harmonic BEM scattering formulations in electromagnetics. +So far, only the 3D variant is implemented. + +--- +## Definition + +The operator is defined as (see, e.g., ...) +```math +\bm{\mathcal{K}} \bm b = α \int_\Gamma ∇_{\!x} g_γ(\bm x,\bm y) \times \bm b(\bm y) \,\mathrm{d}\bm y +``` +for a vector field ``\bm{b}`` and a parameter ``α`` with the free-space Green's function +```math +g_{γ}(\bm x,\bm y) = \dfrac{\mathrm{e}^{-γ|x-y|}}{4π|x-y|} \,. +``` +The parameters are typically ``α=1`` and ``γ = \mathrm{j}k`` with ``k`` denoting the wavenumber and ``\mathrm{j}`` the imaginary unit. +As variation, the rotaded double layer operator +```math +\bar{\bm{\mathcal{K}}} = \bm{n} \times \bm{\mathcal{K}} +``` +can be used which simply involves the cross product with the normal vector ``\bm{n}`` of the surface ``\Gamma``. + + +--- +## As Bilinear Form + +When handed to the [`assemble`](@ref assemble) function, the operators are interpreted as the corresponding bilinear forms +```math +a(\bm t, \bm b) = α ∬_{\Gamma \times \Gamma} \bm t(\bm x) ⋅ \,( ∇_{\!x} g_γ(\bm x,\bm y) \times \bm b(\bm y) ) \,\mathrm{d}\bm y \mathrm{d}\bm x +``` +and +```math +\bar{a}(\bm t, \bm b) = α ∬_{\Gamma \times \Gamma} \bm t(\bm x) ⋅ \, (\bm{n} \times ( ∇_{\!x} g_γ(\bm x,\bm y) \times \bm b(\bm y) )) \,\mathrm{d}\bm y \mathrm{d}\bm x +``` +resulting in the matrix +```math +[\bm A]_{mn} = a(\bm t_m, \bm b_n) \,. +``` + + +### API + +```@docs +Maxwell3D.doublelayer +``` + + +--- +## As Linear Map + +When handed to the [`potential`](@ref potential) function, the operator can be evaluated at provided points in space. +Commonly, this is used in post-processing. + + +### Far-Field + +In the limit that the observation point ``\bm x \rightarrow \infty``, the operator simplifies to the far-field (FF) version +```math +(\bm{\mathcal{K}}_\mathrm{FF} \bm b)(\bm x) = α \bm{u}_r \times \int_\Gamma \bm{b}(\bm y) \mathrm{e}^{\mathrm{j}\bm{u}_r \cdot\, \bm{y}} \,\mathrm{d}\bm{y} +``` + +### API + +```@docs +BEAST.MWDoubleLayerField3D +BEAST.MWDoubleLayerFarField3D +MWDoubleLayerRotatedFarField3D +``` + +!!! tip + The provided points for the [`potential`](@ref potential) should be in Cartesian coordinates. The returned fields are also in Cartesian from. + +!!! warning + Singularities are not addressed: the case when the evaluation point is close to the surface is not treated properly, so far. \ No newline at end of file diff --git a/docs/src/operators/maxwelldoublelayer_td.md b/docs/src/operators/maxwelldoublelayer_td.md new file mode 100644 index 00000000..5e82365e --- /dev/null +++ b/docs/src/operators/maxwelldoublelayer_td.md @@ -0,0 +1,4 @@ + +# [Maxwell Double Layer Operator](@id MWdoublelayerTDDef) + +Text \ No newline at end of file diff --git a/docs/src/operators/maxwellsinglelayer.md b/docs/src/operators/maxwellsinglelayer.md new file mode 100644 index 00000000..e4324a68 --- /dev/null +++ b/docs/src/operators/maxwellsinglelayer.md @@ -0,0 +1,92 @@ + +# [Maxwell Single Layer Operator](@id MWsinglelayerDef) + +The Maxwell single layer operator, also known als electric field integral operator (EFIO) is encountered in many time-harmonic BEM scattering formulations in electromagnetics. +So far, only the 3D variant is implemented. + +--- +## Definition + +The operator is defined as (see, e.g., [raoElectromagneticScatteringSurfaces1982](@cite)) +```math +\bm{\mathcal{T}} \bm b = α \bm{\mathcal{T}}_{\!\!s} \bm b + β \bm{\mathcal{T}}_{\!\!h} \bm b +``` +for a vector field ``\bm{b}`` and parameters ``α`` and ``β``, as well as, the weakly singular operator (also known as vector potential operator) +```math +\bm{\mathcal{T}}_{\!\!s} \bm b = \int_\Gamma g_γ(\bm x,\bm y) \, \bm b(\bm y) \,\mathrm{d}\bm y +``` +and the hyper singular operator (also known as scalar potential operator) +```math +\bm{\mathcal{T}}_{\!\!h} \bm b = ∇\int_\Gamma g_γ(\bm x,\bm y) \, ∇_Γ⋅\bm b(\bm y) \,\mathrm{d}\bm y +``` +with the free-space Green's function +```math +g_{γ}(\bm x,\bm y) = \dfrac{\mathrm{e}^{-γ|x-y|}}{4π|x-y|} \,. +``` +The parameters are typically ``α=-\mathrm{j}k``, ``β=-1/(\mathrm{j}k)``, and ``γ = \mathrm{j}k`` with ``k`` denoting the wavenumber and ``\mathrm{j}`` the imaginary unit. + + +--- +## As Bilinear Form + +When handed to the [`assemble`](@ref assemble) function, the operators are interpreted as the corresponding bilinear forms +```math +a(\bm t, \bm b) = α ∬_{\Gamma \times \Gamma} \bm t(\bm x) ⋅ \bm b(\bm y) \, g_γ(\bm x,\bm y) \,\mathrm{d}\bm y \mathrm{d}\bm x + β ∬_{Γ×Γ} ∇_Γ⋅\bm t(\bm x) \, ∇_Γ⋅\bm b(\bm y) \, g_γ(\bm x,\bm y) \,\mathrm{d}\bm y \mathrm{d}\bm x +``` +for the complete Maxwell single layer operator, +```math +a_s(\bm t, \bm b) = α ∬_{\Gamma \times \Gamma} \bm t(\bm x) ⋅ \bm b(\bm y) \, g_γ(\bm x,\bm y) \,\mathrm{d}\bm y \mathrm{d}\bm x +``` +for the weakly singular part, and +```math +a_h(\bm t, \bm b) = β ∬_{Γ×Γ} ∇_Γ⋅\bm t(\bm x) \, ∇_Γ⋅\bm b(\bm y) \, g_γ(\bm x,\bm y) \,\mathrm{d}\bm y \mathrm{d}\bm x +``` +for the hyper singular part. +Note that the gradient in the hypersingular operator has been moved to the test function using, e.g., a Stokes identity [nedelecWaveEquations2001; p. 73](@cite). +The corresponding matrices contain the entries +```math +[\bm A]_{mn} = a_x(\bm t_m, \bm b_n) \,. +``` + +### API + +The weakly singular and the hyper singular operator can be used as such or combined in the single layer operator. + +!!! tip + The qualifier `Maxwell3D` has to be used. + + +```@docs +Maxwell3D.singlelayer +Maxwell3D.weaklysingular +Maxwell3D.hypersingular +``` + +--- +## As Linear Map + +When handed to the [`potential`](@ref potential) function, the operator can be evaluated at provided points in space. +Commonly, this is used in post-processing. + + +### Far-Field + +In the limit that the observation point ``\bm x \rightarrow \infty``, the operator simplifies to the far-field (FF) version +```math +(\bm{\mathcal{T}}_\mathrm{FF} \bm b)(\bm x) = α \bm{u}_r \times \int_\Gamma \bm{b}(\bm y) \mathrm{e}^{\mathrm{j}\bm{u}_r \cdot\, \bm{y}} \,\mathrm{d}\bm{y} \times \bm{u}_r +``` +where ``\bm{u}_r`` is the unit vector in the direction of the evaluation point. + + +### API + +```@docs +MWSingleLayerField3D +MWFarField3D +``` + +!!! tip + The provided points for the [`potential`](@ref potential) should be in Cartesian coordinates. The returned fields are also in Cartesian from. + +!!! warning + Singularities are not addressed: the case when the evaluation point is close to the surface is not treated properly, so far. \ No newline at end of file diff --git a/docs/src/operators/maxwellsinglelayerVIE.md b/docs/src/operators/maxwellsinglelayerVIE.md new file mode 100644 index 00000000..4d4f4b56 --- /dev/null +++ b/docs/src/operators/maxwellsinglelayerVIE.md @@ -0,0 +1,4 @@ + +# [Maxwell Single Layer Operator](@id MWsinglelayerVIEDef) + +TODO \ No newline at end of file diff --git a/docs/src/operators/maxwellsinglelayer_td.md b/docs/src/operators/maxwellsinglelayer_td.md new file mode 100644 index 00000000..48eb41a8 --- /dev/null +++ b/docs/src/operators/maxwellsinglelayer_td.md @@ -0,0 +1,14 @@ + +# [Maxwell Single Layer Operator](@id MWsinglelayerTDDef) + +Text + +--- +## Definition + +Text + + + +--- +## As ... \ No newline at end of file diff --git a/docs/src/operators/overview.md b/docs/src/operators/overview.md new file mode 100644 index 00000000..bdab8bc7 --- /dev/null +++ b/docs/src/operators/overview.md @@ -0,0 +1,11 @@ + +# [Operator Overview](@id operator) + +```@example introductory +using TypeTree +using BEAST + +print(join(tt(BEAST.Operator), "")) +print("\n\n\n")#hide +print(join(tt(BEAST.SpaceTimeOperator), "")) +``` \ No newline at end of file diff --git a/docs/src/references.md b/docs/src/references.md new file mode 100644 index 00000000..a56db3de --- /dev/null +++ b/docs/src/references.md @@ -0,0 +1,5 @@ + +# References + +```@bibliography +``` \ No newline at end of file diff --git a/docs/src/refs.bib b/docs/src/refs.bib new file mode 100644 index 00000000..dbd949b6 --- /dev/null +++ b/docs/src/refs.bib @@ -0,0 +1,31 @@ + +@article{raoElectromagneticScatteringSurfaces1982, + title = {Electromagnetic Scattering by Surfaces of Arbitrary Shape}, + author = {Rao, S. and Wilton, D. and Glisson, A.}, + year = {1982}, + month = {may}, + journal = {IEEE Transactions on Antennas and Propagation}, + volume = {30}, + number = {3}, + pages = {409--418} +} + +@article{buffaDualFiniteElement2007, + title = {A Dual Finite Element Complex on the Barycentric Refinement}, + author = {Buffa, Annalisa and Christiansen, Snorre}, + year = {2007}, + journal = {Mathematics of Computation}, + volume = {76}, + number = {260}, + pages = {1743--1769} +} + +@book{nedelecWaveEquations2001, + title = {Acoustic and Electromagnetic Equations}, + author = {N\'ed\'elec, Jean-Claude}, + year = {2001}, + series = {Applied Mathematical Sciences}, + publisher = {Springer}, + location = {New York}, + isbn = {978-1-4757-4393-7} +} diff --git a/docs/src/tutorial.md b/docs/src/tutorial.md deleted file mode 100644 index dd380779..00000000 --- a/docs/src/tutorial.md +++ /dev/null @@ -1,74 +0,0 @@ -# Tutorial - -```math -\newcommand{\vt}[1]{\boldsymbol{#1}} -\newcommand{\uv}[1]{\hat{\boldsymbol{#1}}} -\newcommand{\arr}[1]{\mathsf{#1}} -\newcommand{\mat}[1]{\boldsymbol{\mathsf{#1}}} -``` - -In this tutorial we will go through the steps required for the formulation and the solution of the scattering of a time harmonic electromagnetic wave by a rectangular plate by means of the solution of the electric field integral equation. - -## Building the geometry - -The sibling package `CompScienceMeshes` provides data structures and algorithms for working with simplical meshes in computational science. We will use it to create the geometry: - -```@example 1 -using CompScienceMeshes, BEAST -o, x, y, z = euclidianbasis(3) - -h = 0.2 -Γ = meshrectangle(1.0, 1.0, h) -@show numvertices(Γ) -@show numcells(Γ) -nothing # hide -``` - -Next, we create the finite element space of Raviart-Thomas aka Rao-Wilton-Glisson functions subordinate to the triangulation `Γ` constructed above: - -```@example 1 -X = raviartthomas(Γ) -nothing # hide -``` - -The scattering problem is defined by specifying the single layer operator and the functional acting as excitation. Here, the plate is illuminated by a plane wave. The actual excitation is the tangential trace of this electric field. This trace be constructed easily by using the symbolic normal vector field `n` defined as part of the `BEAST` package. - -```@example 1 -κ = 1.0 -E = Maxwell3D.planewave(direction=ẑ, polarization=x̂, wavenumber=κ) -e = (n × E) × n -nothing # hide -``` - -The single layer potential is also predefined by the `BEAST` package: - -```@example 1 -t = Maxwell3D.singlelayer(wavenumber=κ) -nothing # hide -``` - -It corresponds to the bilinear form - -```math -t(\vt{k},\vt{j}) = \frac{1}{ik} \int_{\Gamma} \int_{\Gamma'} \nabla \cdot \vt{k}(x) \nabla \cdot \vt{j}(y) \frac{e^{-ik|x-y|}}{4\pi|x-y|} dy dx - ik \int_{\Gamma} \int_{\Gamma'} \vt{k}(x) \cdot \vt{j}(y) \frac{e^{-ik|x-y|}}{4\pi|x-y|} dy dx -``` - -Using the `LinearForms` package, which implements a simple form compiler for Julia (`@varform`), the EFIE can be defined and discretised by - -```@example 1 -@hilbertspace j -@hilbertspace k -efie = @discretise t[k,j]==e[k] j∈X k∈X -nothing # hide -``` -Solving and computing the values of the induced current in the centers of the triangles of the mesh is now straightforward: - -```@example 1 -u = solve(efie) -fcr, geo = facecurrents(u,X) -nothing # hide -``` - -The resulting current distribution can be visualised by e.g. Matlab, Paraview, Plotly,... - -![](assets/facecurrents.png) diff --git a/docs/src/tutorials/efie.md b/docs/src/tutorials/efie.md new file mode 100644 index 00000000..a1abc7f1 --- /dev/null +++ b/docs/src/tutorials/efie.md @@ -0,0 +1,110 @@ +```@meta +EditURL = "../../../examples/efie.jl" +``` + +# EFIE + +This example demonstrates the modelling of scattering by a perfectly conducting +sphere using the Electric Field Integral Equation (EFIE). + +````julia +using LinearAlgebra +using CompScienceMeshes +using BEAST +import PlotlyDocumenter # hide +```` + +We call upon GMsh to create a flat-faceted triangular mesh for the sphere. +Subordinate to this mesh the space of lowest order Raviart-Thomas elements is +constructed. For reproducibility of this example, a pre-constructed mesh is +loaded from disk. + +````julia +Γ = readmesh(joinpath(dirname(pathof(BEAST)),"../examples/sphere2.in")) +X = raviartthomas(Γ); +```` + +The integral equation can be wrtiten down in terms of the single layer operaotr +and a plane wave excitation. + +````julia +κ, η = 1.0, 1.0; +t = Maxwell3D.singlelayer(wavenumber=κ); +E = Maxwell3D.planewave(direction=ẑ, polarization=x̂, wavenumber=κ); +e = (n × E) × n; +```` + +To write down the final variational formulation, placeholders for the test and +trial functions are required. + +````julia +@hilbertspace j; +@hilbertspace k; +efie = @discretise t[k,j]==e[k] j∈X k∈X; +```` + +The system is assembled and solved by GMRES. When this processs terminated, the +solution and the convergence history are returned. + +````julia +u, ch = BEAST.gmres_ch(efie; restart=1500); +@show ch +```` + +```` +Converged after 188 iterations. +```` + +With the solution in hand, various secondary quatities can be computed. Here we +query the far field pattern, the near field, and the norm of the solution at the +barycenters of the triangles in the mesh. + +````julia +Φ, Θ = [0.0], range(0,stop=π,length=100); +pts = [point(cos(ϕ)*sin(θ), sin(ϕ)*sin(θ), cos(θ)) for ϕ in Φ for θ in Θ]; +ffd = potential(MWFarField3D(wavenumber=κ), pts, u, X); + +fcr, geo = facecurrents(u, X); + +ys = range(-2,stop=2,length=50); +zs = range(-4,stop=4,length=100); +gridpoints = [point(0,y,z) for y in ys, z in zs]; +Esc = potential(MWSingleLayerField3D(wavenumber = κ), gridpoints, u, X); +Ein = E.(gridpoints); +```` + +```` +"" +```` + +The results can be exported or visualised using e.g. Plotly + +````julia +using PlotlyBase +plt = Plot(Layout(Subplots(rows=2, cols=2, specs=[Spec() Spec(rowspan=2); Spec(kind="mesh3d") missing]))) +add_trace!(plt, scatter(x=Θ, y=norm.(ffd)), row=1, col=1) +add_trace!(plt, heatmap(x=zs, y=ys, z=norm.(Esc-Ein)', colorscale="Viridis", zmin=0, zmax=2, showscale=false), row=1, col=2) +add_trace!(plt, patch(geo, norm.(fcr), caxis=(0, 2)), row=2, col=1) +```` + +```@raw html +
+ + +``` + +--- + +*This page was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).* + diff --git a/examples/Project.toml b/examples/Project.toml index 6c64e382..9ddc7664 100644 --- a/examples/Project.toml +++ b/examples/Project.toml @@ -1,5 +1,10 @@ [deps] BEAST = "bb4162c7-ba94-5a20-af32-d8ec4428bdd1" CompScienceMeshes = "3e66a162-7b8c-5da0-b8f8-124ecd2c3ae1" +Literate = "98b081ad-f1c9-55d3-8b20-4c87d4299306" +Makeitso = "5616b7aa-48d8-5ba3-83fe-06b58d5edc20" Plotly = "58dd65bb-95f3-509e-9936-c39a10fdeae7" +PlotlyBase = "a03496cd-edff-5a9b-9e67-9cda94a718b5" +PlotlyDocumenter = "9b90f1cd-2639-4507-8b17-2fbe371ceceb" +PlotlyJS = "f0f68f2c-4968-5e81-91da-67840de0976a" Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" diff --git a/examples/assets/M1.in b/examples/assets/M1.in new file mode 100644 index 00000000..909e09df --- /dev/null +++ b/examples/assets/M1.in @@ -0,0 +1,1009 @@ +1 +337 670 +0.0 0.0 0.0 +0.0 0.0 1.0 +0.5 0.0 1.0 +0.5 0.0 0.0 +0.0 1.0 0.0 +0.0 1.0 1.0 +0.5 1.0 1.0 +0.5 1.0 0.0 +0.0 0.0 0.1249999999997732 +0.0 0.0 0.2499999999994112 +0.0 0.0 0.3749999999990465 +0.0 0.0 0.4999999999986927 +0.0 0.0 0.624999999999011 +0.0 0.0 0.7499999999993407 +0.0 0.0 0.8749999999996704 +0.1249999999997056 0.0 1.0 +0.2499999999993463 0.0 1.0 +0.3749999999996704 0.0 1.0 +0.5 0.0 0.8749999999995015 +0.5 0.0 0.7500000000003476 +0.5 0.0 0.6250000000012154 +0.5 0.0 0.5000000000020615 +0.5 0.0 0.3750000000015626 +0.5 0.0 0.2500000000010417 +0.5 0.0 0.1250000000005209 +0.3750000000001738 0.0 0.0 +0.2500000000010307 0.0 0.0 +0.1250000000005209 0.0 0.0 +0.0 1.0 0.1249999999997732 +0.0 1.0 0.2499999999994112 +0.0 1.0 0.3749999999990465 +0.0 1.0 0.4999999999986927 +0.0 1.0 0.624999999999011 +0.0 1.0 0.7499999999993407 +0.0 1.0 0.8749999999996704 +0.1249999999997056 1.0 1.0 +0.2499999999993463 1.0 1.0 +0.3749999999996704 1.0 1.0 +0.5 1.0 0.8749999999995015 +0.5 1.0 0.7500000000003476 +0.5 1.0 0.6250000000012154 +0.5 1.0 0.5000000000020615 +0.5 1.0 0.3750000000015626 +0.5 1.0 0.2500000000010417 +0.5 1.0 0.1250000000005209 +0.3750000000001738 1.0 0.0 +0.2500000000010307 1.0 0.0 +0.1250000000005209 1.0 0.0 +0.0 0.1249999999997732 0.0 +0.0 0.2499999999994112 0.0 +0.0 0.3749999999990465 0.0 +0.0 0.4999999999986927 0.0 +0.0 0.624999999999011 0.0 +0.0 0.7499999999993407 0.0 +0.0 0.8749999999996704 0.0 +0.0 0.1249999999997732 1.0 +0.0 0.2499999999994112 1.0 +0.0 0.3749999999990465 1.0 +0.0 0.4999999999986927 1.0 +0.0 0.624999999999011 1.0 +0.0 0.7499999999993407 1.0 +0.0 0.8749999999996704 1.0 +0.5 0.1249999999997732 1.0 +0.5 0.2499999999994112 1.0 +0.5 0.3749999999990465 1.0 +0.5 0.4999999999986927 1.0 +0.5 0.624999999999011 1.0 +0.5 0.7499999999993407 1.0 +0.5 0.8749999999996704 1.0 +0.5 0.1249999999997732 0.0 +0.5 0.2499999999994112 0.0 +0.5 0.3749999999990465 0.0 +0.5 0.4999999999986927 0.0 +0.5 0.624999999999011 0.0 +0.5 0.7499999999993407 0.0 +0.5 0.8749999999996704 0.0 +0.3874424230244282 0.0 0.6879859346131602 +0.3917468245269385 0.0 0.4375000000016365 +0.108253175472739 0.0 0.3124999999992288 +0.1104767907802638 0.0 0.5495034007864623 +0.3941534943676811 0.0 0.1880296711609176 +0.1058465056321822 0.0 0.8119703288394107 +0.3204049132914262 0.0 0.9011617204021374 +0.1795950867088213 0.0 0.098838279597838 +0.3921479361669236 0.0 0.3125882785281645 +0.2888568906084722 0.0 0.3750147130896808 +0.2855166394278844 0.0 0.497289094523428 +0.1752404735809824 0.0 0.4375000000021429 +0.2980878577804649 0.0 0.2493377043688435 +0.1082531754732787 0.0 0.6874999999992824 +0.2028728677521018 0.0 0.7506794609007599 +0.2024306704562214 0.0 0.6333638102810373 +0.3946405315505427 0.0 0.556458537272301 +0.4027268679816424 0.0 0.8199103159451206 +0.09655573176801091 0.0 0.1801706731567673 +0.3035071324072291 0.0 0.1118491644533768 +0.1965628913197734 0.0 0.8881520866595964 +0.2791806972154096 0.0 0.6919651983778352 +0.07647483778489925 0.0 0.4374999999988696 +0.2120651139228778 0.0 0.3146474036946574 +0.408493649053709 0.0 0.9084936490536635 +0.4084936490535773 0.0 0.09150635094660889 +0.0915063509465559 0.0 0.09150635094635556 +0.09150635094628985 0.0 0.9084936490535431 +0.1999010530413667 0.0 0.2052734479857873 +0.300020222520291 0.0 0.7968205916019144 +0.2969510646885307 0.0 0.5956725561948821 +0.2031529418856968 0.0 0.5371732103368083 +0.0 0.5670536380046087 0.8943758686552945 +0.0 0.5624999999988518 0.1082531754730394 +0.0 0.1124243810245421 0.5714876988465326 +0.0 0.8808310019958672 0.5741214795607947 +0.0 0.112037015619272 0.3016592931610405 +0.0 0.8959570073500618 0.3197922505577883 +0.0 0.3128108659840297 0.8955068139137721 +0.0 0.3124999999992842 0.1082531754728282 +0.0 0.8076656081751146 0.8945379619478856 +0.0 0.120769536763528 0.813090642376082 +0.0 0.8119703288394229 0.1058465056321634 +0.0 0.6874117214724684 0.1078520638329271 +0.0 0.6236899155261182 0.2156916159880773 +0.0 0.4997816525869273 0.2163705617862025 +0.0 0.5609498899663387 0.3238532227145602 +0.0 0.4372052570918011 0.3245858442746743 +0.0 0.49969252450903 0.4328327042500744 +0.0 0.6162104309147753 0.4337567545930024 +0.0 0.5593815006335847 0.5422849894666124 +0.0 0.4369290041899559 0.5414057297743196 +0.0 0.4993850841365262 0.6426639661559268 +0.0 0.6276311351227919 0.6463285523418856 +0.0 0.3748023480540149 0.6483998471250553 +0.0 0.3157952145740176 0.5391261956275301 +0.0 0.4374999999990491 0.7577722283096371 +0.0 0.2332538498070924 0.6405275871648053 +0.0 0.7505502180075261 0.2051526528524255 +0.0 0.1103830098873939 0.6904888009980186 +0.0 0.3749563698701903 0.2164800353587819 +0.0 0.3160693983617056 0.3268195465292577 +0.0 0.6874999999992156 0.8917468245266825 +0.0 0.7537479060341656 0.7865878827521369 +0.0 0.8855247200895949 0.7967657096188994 +0.0 0.8170240472073322 0.6845448607410298 +0.0 0.6343680478893469 0.7749720748802604 +0.0 0.1273937278648768 0.4354123541366469 +0.0 0.4398459425664173 0.8866050300249827 +0.0 0.8906292179090657 0.4406522883524269 +0.0 0.7834936490542321 0.4999999999989449 +0.0 0.7952549537499449 0.3843067512267823 +0.0 0.2310045288704008 0.2274878048597269 +0.0 0.1866262784732894 0.1058176347971227 +0.0 0.4374563370754987 0.108226158015142 +0.0 0.3749999999993984 0.4330127018913694 +0.0 0.2585921732017576 0.4335162638741182 +0.0 0.3124999999994057 0.7577722283095741 +0.0 0.1918537211810209 0.9070743513264233 +0.0 0.8856109484396917 0.1966413099095797 +0.0 0.1038676364273152 0.1864502994601513 +0.0 0.695482497809001 0.326840676787839 +0.0 0.6775056216160694 0.5255800299582942 +0.0 0.2055883939701014 0.7454468522927225 +0.0 0.2090770309821333 0.5255549114488995 +0.0 0.9166759538585588 0.6860864099838151 +0.0 0.2090866243337802 0.3427906503125625 +0.0 0.7189163598558483 0.6972086508131028 +0.0 0.5279873263605452 0.8039633961756577 +0.0 0.09150635094629671 0.9084936490535541 +0.0 0.9084936490535542 0.09150635094629654 +0.0 0.09150635094593877 0.09150635094593877 +0.0 0.9084936490536488 0.9084936490536488 +0.0 0.7540175483468565 0.6014818206493495 +0.0 0.7165063509454275 0.4374999999991191 +0.0 0.3686845263394476 0.8247185888446038 +0.0 0.5502673705008129 0.7223808150408089 +0.0 0.2510355733930868 0.8253550026151322 +0.0 0.8052157255528772 0.2876237401409069 +0.391746824527261 0.3124999999992288 1.0 +0.3917468245269606 0.5624999999988518 1.0 +0.1082531754728408 0.4374999999988965 1.0 +0.102316632530284 0.6804156785873245 1.0 +0.3076656081752543 0.894537961948085 1.0 +0.1058465056316792 0.1880296711597031 1.0 +0.3204049132913607 0.0988382795978682 1.0 +0.1877360370596645 0.8943230240947262 1.0 +0.2469258655412699 0.7945981967009401 1.0 +0.3845501428764332 0.7979100401831004 1.0 +0.3067607882128057 0.6775841068209039 1.0 +0.105678205107362 0.5613192797635527 1.0 +0.2088374798569323 0.5008063548263575 1.0 +0.2099318161495282 0.3751343924702438 1.0 +0.3247595264185403 0.4374999999986193 1.0 +0.1067563080332205 0.3126106772711187 1.0 +0.2015276340457712 0.2493613840553483 1.0 +0.4034442682320421 0.1801706731567887 1.0 +0.1964648422157708 0.1118508903778972 1.0 +0.117489182722353 0.802350626167754 1.0 +0.2013945080314591 0.6170446839893258 1.0 +0.4166115511232398 0.6825988294002414 1.0 +0.2878811751500963 0.5617818348618663 1.0 +0.2877069144461185 0.314667883559932 1.0 +0.4235251622148318 0.4374999999988697 1.0 +0.4084936490535855 0.09150635094626534 1.0 +0.4084936490536801 0.9084936490536802 1.0 +0.09150635094588741 0.09150635094590549 1.0 +0.09150635094587027 0.9084936490539626 1.0 +0.3000915333581194 0.2061138508397521 1.0 +0.1939393929793829 0.7136238347383141 1.0 +0.5 0.894375868655086 0.4329463619958044 +0.5 0.1082531754729179 0.4375000000015332 +0.5 0.571487698846054 0.8875756189756092 +0.5 0.5741214795607102 0.119168998004383 +0.5 0.1082531754726617 0.6875000000006011 +0.5 0.8955068139140753 0.687189134016003 +0.5 0.3197922505576257 0.1040429926499073 +0.5 0.301659293160697 0.8879629843806891 +0.5 0.8945379619477741 0.1923343918252038 +0.5 0.8130906423761161 0.8792304632365157 +0.5 0.1058465056323024 0.1880296711609376 +0.5 0.1078520638329425 0.3125882785282355 +0.5 0.2156916159879368 0.3763100844746096 +0.5 0.2163705617858266 0.5002183474134949 +0.5 0.3238532227140545 0.4390501100342684 +0.5 0.3245858442739504 0.5627947429086161 +0.5 0.4328327042491723 0.5003074754916487 +0.5 0.4337567545923268 0.383789569085849 +0.5 0.5422849894654955 0.4406184993672195 +0.5 0.5414057297730945 0.5630709958106961 +0.5 0.6426639661546197 0.5006149158643687 +0.5 0.6463285523409318 0.3723688648781147 +0.5 0.648399847123551 0.6251976519466464 +0.5 0.5391261956262033 0.6842047854264401 +0.5 0.7577722283079495 0.562500000001865 +0.5 0.6405275871638941 0.7667461501933093 +0.5 0.2051526528523045 0.2494497819928434 +0.5 0.8917468245265333 0.3125000000012341 +0.5 0.7865878827518458 0.246252093966229 +0.5 0.7967657096187415 0.1144752799106367 +0.5 0.6845448607408022 0.1829759527931888 +0.5 0.77497207487972 0.3656319521113494 +0.5 0.6904888009978722 0.8896169901127362 +0.5 0.2164800353582474 0.6250436301299697 +0.5 0.3268195465283675 0.6839306016384717 +0.5 0.886605030024842 0.5601540574339758 +0.5 0.4406522883523321 0.1093707820909126 +0.5 0.499999999998945 0.2165063509456713 +0.5 0.3843067512265817 0.204745046250173 +0.5 0.435412354136021 0.8726062721352169 +0.5 0.1082261580149423 0.5625436629248126 +0.5 0.2274878048591829 0.7689954711295537 +0.5 0.105817634796763 0.8133737215264445 +0.5 0.7577722283077662 0.6875000000012509 +0.5 0.4330127018902896 0.6250000000010248 +0.5 0.4335162638730714 0.7414078267985001 +0.5 0.9070743513264905 0.8081462788187956 +0.5 0.1864502994599095 0.8961323635725842 +0.5 0.1966413099094998 0.1143890515604102 +0.5 0.3268406767874762 0.3045175021914229 +0.5 0.5255800299577877 0.3224943783845682 +0.5 0.7454468522921446 0.7944116060301174 +0.5 0.5255549114478966 0.7909229690181839 +0.5 0.6860864099837212 0.0833240461416417 +0.5 0.3427906503117716 0.7909133756662735 +0.5 0.8039633961749467 0.4720126736401245 +0.5 0.6972086508126907 0.2810836401448817 +0.5 0.09150635094584145 0.908493649053964 +0.5 0.9084936490536776 0.9084936490536318 +0.5 0.09150635094641765 0.09150635094661799 +0.5 0.9084936490535275 0.09150635094652358 +0.5 0.6014818206490755 0.245982451653818 +0.5 0.8247185888439466 0.6313154736608839 +0.5 0.4374999999988745 0.283493649055028 +0.5 0.7223808150397706 0.4497326295000922 +0.5 0.8253550026150741 0.7489644266067749 +0.5 0.2876237401402548 0.194784274447189 +0.1023166325302679 0.3195843214106644 0.0 +0.1082531754730394 0.5624999999988518 0.0 +0.3846839867109718 0.426516278510507 0.0 +0.3976833674697007 0.680415678587349 0.0 +0.1923343918249383 0.8945379619482097 0.0 +0.3076656081763919 0.1054620380513241 0.0 +0.3122639629403485 0.894323024094841 0.0 +0.2530741344588454 0.7945981967011437 0.0 +0.1154498571236755 0.7979100401832013 0.0 +0.1932392117872833 0.6775841068210785 0.0 +0.1877360370611273 0.1056769759046468 0.0 +0.2469258655419687 0.2054018032976537 0.0 +0.3845501428770587 0.2020899598158116 0.0 +0.3077361438196501 0.3207815340793625 0.0 +0.3917468245270575 0.5624999999988518 0.0 +0.2905799906466582 0.5083224996962605 0.0 +0.1017775495552221 0.434767294664808 0.0 +0.1174891827227065 0.197649373831149 0.0 +0.3825108172776542 0.8023506261678207 0.0 +0.2069930716957313 0.3749999999988985 0.0 +0.2980792419915447 0.6184941615069081 0.0 +0.08338844887679964 0.6825988294002967 0.0 +0.4162658773658106 0.3124999999992288 0.0 +0.2166508075061639 0.5686840429211586 0.0 +0.091506350946844 0.09150635094664367 0.0 +0.09150635094646144 0.9084936490535899 0.0 +0.4084936490542588 0.09150635094572702 0.0 +0.408493649054128 0.9084936490539932 0.0 +0.1954634844235517 0.4765633337193599 0.0 +0.1939393929787376 0.2863761652592501 0.0 +0.3060606070205576 0.7136238347384073 0.0 +0.2845212715646553 0.4147109486860401 0.0 +0.3874424230244282 1.0 0.6879859346131602 +0.3917468245269385 1.0 0.4375000000016365 +0.108253175472739 1.0 0.3124999999992288 +0.1104767907802638 1.0 0.5495034007864623 +0.3941534943676811 1.0 0.1880296711609176 +0.1058465056321822 1.0 0.8119703288394109 +0.3204049132914262 1.0 0.9011617204021374 +0.1795950867088213 1.0 0.098838279597838 +0.3921479361669236 1.0 0.3125882785281645 +0.2888568906084722 1.0 0.3750147130896808 +0.2855166394278844 1.0 0.497289094523428 +0.1752404735809824 1.0 0.4375000000021429 +0.2980878577804649 1.0 0.2493377043688435 +0.108253175473279 1.0 0.6874999999992826 +0.2028728677521019 1.0 0.7506794609007601 +0.2024306704562215 1.0 0.6333638102810374 +0.3946405315505428 1.0 0.556458537272301 +0.4027268679816423 1.0 0.8199103159451206 +0.09655573176801091 1.0 0.1801706731567673 +0.3035071324072291 1.0 0.1118491644533768 +0.1965628913197734 1.0 0.8881520866595967 +0.2791806972154096 1.0 0.6919651983778353 +0.07647483778489925 1.0 0.4374999999988696 +0.2120651139228778 1.0 0.3146474036946574 +0.408493649053709 1.0 0.9084936490536635 +0.4084936490535773 1.0 0.09150635094660889 +0.0915063509465559 1.0 0.09150635094635556 +0.09150635094628991 1.0 0.908493649053543 +0.1999010530413667 1.0 0.2052734479857873 +0.3000202225202909 1.0 0.7968205916019147 +0.2969510646885307 1.0 0.5956725561948821 +0.2031529418856968 1.0 0.5371732103368083 +10 113 157 +9 157 168 +9 10 157 +1 9 168 +49 1 168 +150 49 168 +50 49 150 +116 50 150 +149 116 150 +157 150 168 +149 150 157 +113 149 157 +51 50 116 +51 116 151 +52 51 151 +110 52 151 +122 110 151 +137 122 151 +116 137 151 +137 116 149 +153 138 163 +138 149 163 +138 137 149 +124 138 152 +124 137 138 +124 122 137 +123 122 124 +123 124 125 +128 125 152 +125 124 152 +152 138 153 +132 152 153 +132 153 161 +113 144 163 +149 113 163 +10 11 113 +113 11 144 +11 12 144 +144 153 163 +153 144 161 +80 99 88 +12 99 80 +11 99 12 +88 99 79 +79 99 11 +10 79 11 +79 105 100 +100 105 89 +79 100 88 +88 100 86 +87 88 86 +87 108 88 +293 290 302 +274 290 293 +293 302 305 +290 275 302 +52 275 290 +51 52 290 +274 51 290 +50 51 274 +50 274 291 +291 274 303 +274 293 303 +293 287 303 +287 285 303 +285 291 303 +285 284 291 +291 284 298 +279 284 285 +49 50 291 +49 291 298 +1 49 298 +95 103 84 +28 103 1 +28 1 298 +1 103 9 +9 95 10 +10 95 79 +9 103 95 +84 105 95 +95 105 79 +96 105 84 +27 96 84 +84 103 28 +279 27 284 +284 28 298 +27 84 28 +27 28 284 +26 96 27 +26 27 279 +26 102 96 +26 279 300 +4 102 26 +4 26 300 +25 4 266 +25 102 4 +4 70 266 +70 4 300 +286 70 300 +70 255 266 +70 71 255 +71 70 286 +286 285 287 +279 286 300 +279 285 286 +233 255 273 +233 217 255 +255 217 266 +217 25 266 +24 25 217 +24 81 25 +81 102 25 +85 89 81 +96 102 81 +89 96 81 +89 105 96 +86 100 89 +86 89 85 +78 86 85 +78 87 86 +78 93 87 +22 93 78 +78 85 23 +208 23 218 +22 23 208 +22 78 23 +208 219 220 +208 218 219 +219 218 233 +219 233 256 +218 217 233 +24 217 218 +24 85 81 +23 85 24 +23 24 218 +244 257 270 +257 224 270 +224 256 270 +256 245 270 +221 219 256 +224 221 256 +220 219 221 +223 221 224 +223 224 225 +243 244 245 +245 244 270 +213 243 245 +213 245 273 +245 256 273 +256 233 273 +255 213 273 +287 276 296 +287 293 305 +286 287 296 +71 286 296 +71 213 255 +72 71 296 +71 72 213 +276 72 296 +213 72 243 +73 72 276 +72 73 243 +73 276 288 +288 276 289 +289 276 305 +276 287 305 +302 289 305 +294 289 297 +277 288 294 +288 289 294 +73 210 243 +74 73 288 +73 74 210 +277 74 288 +210 74 260 +75 74 277 +74 75 260 +75 236 260 +75 277 292 +292 277 304 +283 281 304 +294 283 304 +277 294 304 +236 237 260 +237 235 263 +237 263 268 +237 210 260 +210 237 268 +244 210 268 +243 210 244 +228 225 257 +225 224 257 +227 225 228 +227 228 271 +228 238 271 +238 228 263 +263 228 268 +228 257 268 +257 244 268 +44 43 234 +43 44 314 +44 310 314 +43 207 234 +215 44 234 +215 234 235 +235 234 238 +235 238 263 +234 207 238 +238 207 262 +238 262 271 +262 231 271 +207 242 262 +207 42 242 +42 43 307 +43 42 207 +307 43 314 +42 307 322 +307 316 322 +307 315 316 +307 314 315 +315 314 318 +315 318 329 +318 325 334 +318 310 325 +325 310 331 +314 310 318 +310 45 331 +44 45 310 +45 44 215 +45 215 267 +215 236 267 +215 235 236 +236 235 237 +281 280 292 +292 280 301 +278 280 281 +281 292 304 +76 75 292 +75 76 236 +236 76 267 +76 292 301 +8 76 301 +76 8 267 +45 8 331 +8 45 267 +46 8 301 +8 46 331 +280 46 301 +46 325 331 +278 47 280 +47 46 280 +46 47 325 +48 47 278 +47 313 325 +313 48 332 +47 48 313 +48 278 299 +278 282 299 +278 281 282 +282 281 283 +325 313 334 +313 324 334 +324 308 334 +30 308 324 +29 324 332 +29 30 324 +54 55 282 +282 55 299 +55 5 299 +48 5 332 +5 48 299 +324 313 332 +5 29 332 +5 55 167 +29 5 167 +156 29 167 +30 29 156 +156 135 175 +119 135 156 +119 156 167 +55 119 167 +55 54 119 +54 53 120 +119 54 120 +119 120 135 +120 121 135 +135 121 158 +120 110 121 +121 110 122 +53 110 120 +53 52 110 +52 53 275 +275 53 295 +275 297 302 +297 289 302 +283 294 297 +275 283 297 +283 275 295 +282 283 295 +54 282 295 +53 54 295 +126 125 127 +123 125 126 +123 126 158 +121 122 123 +121 123 158 +148 158 171 +158 126 171 +126 159 171 +159 147 171 +147 148 171 +147 146 148 +146 114 148 +158 148 175 +148 114 175 +114 156 175 +135 158 175 +114 30 156 +31 30 114 +31 114 146 +32 31 146 +32 309 328 +31 32 328 +317 308 328 +309 317 328 +30 31 308 +308 31 328 +308 329 334 +308 317 329 +329 318 334 +317 315 329 +316 315 317 +316 317 337 +321 336 337 +309 321 337 +309 319 321 +317 309 337 +321 320 327 +319 320 321 +34 311 319 +33 34 319 +309 33 319 +32 33 309 +112 32 146 +33 32 112 +34 33 162 +33 112 162 +141 34 162 +140 142 164 +142 141 162 +112 142 162 +164 142 170 +142 112 170 +112 146 147 +112 147 170 +147 159 170 +159 130 170 +130 164 170 +130 143 164 +143 130 173 +130 129 173 +127 129 130 +127 130 159 +126 127 159 +61 60 179 +61 179 195 +195 179 206 +186 184 206 +196 186 206 +179 196 206 +179 187 196 +196 188 198 +187 188 196 +179 60 187 +60 59 187 +59 60 109 +59 109 145 +145 109 165 +109 143 165 +165 143 173 +133 165 173 +143 140 164 +109 139 143 +139 140 143 +139 117 140 +61 117 139 +109 60 139 +60 61 139 +61 62 117 +117 62 169 +141 117 169 +140 117 141 +140 141 142 +35 34 141 +35 141 169 +6 35 169 +62 6 169 +35 6 333 +36 6 204 +6 36 333 +6 62 204 +62 195 204 +62 61 195 +34 35 311 +311 35 333 +319 311 320 +320 311 326 +326 311 333 +320 326 335 +184 195 206 +184 183 195 +195 183 204 +180 183 184 +180 37 183 +183 36 204 +36 326 333 +36 37 326 +37 36 183 +38 37 180 +37 312 326 +312 38 330 +38 180 202 +37 38 312 +38 7 330 +7 38 202 +323 312 330 +39 7 265 +7 39 330 +7 69 265 +69 7 202 +185 69 202 +69 216 265 +69 68 216 +68 69 185 +216 68 239 +185 184 186 +180 185 202 +180 184 185 +216 258 272 +253 216 272 +216 253 265 +40 39 253 +253 39 265 +39 40 323 +39 323 330 +40 306 323 +323 306 335 +312 323 335 +326 312 335 +327 320 335 +321 327 336 +327 306 336 +306 327 335 +336 316 337 +306 322 336 +322 316 336 +306 41 322 +41 212 242 +42 41 242 +41 42 322 +242 231 262 +231 242 269 +242 212 269 +250 231 269 +250 269 272 +258 250 272 +269 212 272 +212 253 272 +212 40 253 +41 40 212 +40 41 306 +230 229 232 +230 232 259 +232 229 250 +232 250 258 +229 231 250 +229 227 231 +231 227 271 +226 225 227 +223 225 226 +226 230 251 +226 229 230 +226 227 229 +209 246 259 +232 209 259 +209 232 239 +239 232 258 +216 239 258 +186 177 197 +185 186 197 +68 185 197 +68 67 239 +67 68 197 +67 209 239 +177 67 197 +67 66 209 +66 67 177 +209 66 246 +190 177 198 +177 186 198 +186 196 198 +188 190 198 +190 189 199 +177 190 200 +66 177 200 +66 65 246 +65 66 200 +190 176 200 +65 214 246 +176 65 200 +65 64 214 +64 65 176 +176 199 205 +199 192 205 +176 190 199 +214 248 261 +246 214 261 +252 246 261 +246 252 259 +222 223 251 +223 226 251 +222 221 223 +220 221 222 +220 222 240 +240 222 241 +241 222 251 +240 241 248 +248 241 261 +241 252 261 +241 251 252 +251 230 252 +252 230 259 +20 21 211 +20 77 21 +211 21 247 +211 240 248 +240 211 247 +220 240 247 +208 220 247 +22 208 247 +21 93 22 +21 22 247 +77 93 21 +93 107 87 +77 107 93 +107 108 87 +77 106 98 +98 107 77 +92 107 98 +98 106 91 +97 106 83 +83 106 94 +94 106 77 +20 94 77 +19 101 94 +19 94 20 +19 20 249 +20 211 249 +19 249 264 +249 254 264 +249 248 254 +211 248 249 +248 214 254 +182 193 205 +194 182 205 +193 176 205 +64 176 193 +214 64 254 +64 63 254 +63 64 193 +254 63 264 +63 193 201 +193 182 201 +3 63 201 +63 3 264 +3 101 19 +3 19 264 +94 101 83 +18 3 201 +18 101 3 +17 83 18 +17 18 182 +182 18 201 +83 101 18 +17 97 83 +17 182 194 +16 97 17 +16 17 194 +16 194 203 +16 104 97 +91 106 97 +90 91 82 +91 97 82 +97 104 82 +82 104 15 +14 82 15 +15 104 2 +2 16 203 +2 104 16 +56 2 203 +181 56 203 +57 56 181 +194 181 203 +192 181 194 +192 194 205 +189 192 199 +189 191 192 +191 181 192 +57 181 191 +58 57 191 +178 58 191 +59 58 178 +59 178 187 +187 178 188 +188 178 189 +188 189 190 +189 178 191 +88 108 80 +92 108 107 +80 108 92 +80 92 90 +92 98 91 +90 92 91 +14 90 82 +13 90 14 +80 90 13 +12 80 13 +111 134 161 +144 111 161 +12 111 144 +12 13 111 +111 13 136 +13 14 136 +136 118 160 +134 136 160 +134 111 136 +131 132 134 +134 132 161 +129 128 131 +131 128 132 +132 128 152 +127 125 128 +127 128 129 +129 133 173 +129 131 133 +133 131 154 +154 134 160 +131 134 154 +115 172 174 +154 160 174 +172 154 174 +133 154 172 +115 145 172 +145 133 172 +133 145 165 +58 59 145 +115 58 145 +57 58 115 +57 115 155 +155 115 174 +160 118 174 +118 155 174 +56 57 155 +56 155 166 +155 118 166 +2 56 166 +15 2 166 +14 15 118 +118 15 166 +14 118 136 diff --git a/examples/assets/M2.in b/examples/assets/M2.in new file mode 100644 index 00000000..45a98a3a --- /dev/null +++ b/examples/assets/M2.in @@ -0,0 +1,574 @@ +1 +192 380 +-0.5 0.0 0.0 +-0.5 0.0 1.0 +0.0 0.0 1.0 +0.0 0.0 0.0 +-0.5 1.0 0.0 +-0.5 1.0 1.0 +0.0 1.0 1.0 +0.0 1.0 0.0 +-0.5 0.0 0.1666666666663218 +-0.5 0.0 0.3333333333325029 +-0.5 0.0 0.4999999999986942 +-0.5 0.0 0.6666666666657899 +-0.5 0.0 0.8333333333328949 +-0.3333333333337485 0.0 1.0 +-0.1666666666671051 0.0 1.0 +0.0 0.0 0.8333333333331017 +0.0 0.0 0.6666666666675913 +0.0 0.0 0.5000000000020592 +0.0 0.0 0.3333333333347197 +0.0 0.0 0.1666666666673599 +-0.16666666666620428 0.0 0.0 +-0.3333333333326401 0.0 0.0 +-0.5 1.0 0.1666666666663218 +-0.5 1.0 0.3333333333325029 +-0.5 1.0 0.4999999999986942 +-0.5 1.0 0.6666666666657899 +-0.5 1.0 0.8333333333328949 +-0.3333333333337485 1.0 1.0 +-0.1666666666671051 1.0 1.0 +0.0 1.0 0.8333333333331017 +0.0 1.0 0.6666666666675913 +0.0 1.0 0.5000000000020592 +0.0 1.0 0.3333333333347197 +0.0 1.0 0.1666666666673599 +-0.16666666666620428 1.0 0.0 +-0.3333333333326401 1.0 0.0 +-0.5 0.1666666666663218 0.0 +-0.5 0.3333333333325029 0.0 +-0.5 0.4999999999986942 0.0 +-0.5 0.6666666666657899 0.0 +-0.5 0.8333333333328949 0.0 +-0.5 0.1666666666663218 1.0 +-0.5 0.3333333333325029 1.0 +-0.5 0.4999999999986942 1.0 +-0.5 0.6666666666657899 1.0 +-0.5 0.8333333333328949 1.0 +0.0 0.1666666666663218 1.0 +0.0 0.3333333333325029 1.0 +0.0 0.4999999999986942 1.0 +0.0 0.6666666666657899 1.0 +0.0 0.8333333333328949 1.0 +0.0 0.1666666666663218 0.0 +0.0 0.3333333333325029 0.0 +0.0 0.4999999999986942 0.0 +0.0 0.6666666666657899 0.0 +0.0 0.8333333333328949 0.0 +-0.12758290745376222 0.0 0.749233318682745 +-0.14433756729720648 0.0 0.4166666666683895 +-0.2499999999999593 0.0 0.1563666820692418 +-0.3556624327026136 0.0 0.583333333332242 +-0.3556624327030097 0.0 0.4166666666655985 +-0.3724170925460668 0.0 0.7492333186820921 +-0.140254853105448 0.0 0.5833000710948419 +-0.1277243392043234 0.0 0.2548742700117366 +-0.3722756607959292 0.0 0.2548742700107636 +-0.2500000000002154 0.0 0.8561293975766916 +-0.2499999999988981 0.0 0.6666666666678633 +-0.24999999999907008 0.0 0.5000000000013808 +-0.25000000000049016 0.0 0.3337127944750626 +-0.37799153207151803 0.0 0.1220084679282038 +-0.12200846792768633 0.0 0.1220084679279961 +-0.1211598774260445 0.0 0.878840122574012 +-0.3788401225744618 0.0 0.8788401225742289 +-0.5 0.5797429994424209 0.832554293016586 +-0.5 0.5774318462589461 0.1587006338148569 +-0.5 0.143551118310792 0.5819711637299791 +-0.5 0.8508651158937737 0.5823730257250648 +-0.5 0.2653042698421442 0.1509495412396307 +-0.5 0.8486919098558521 0.2623742252839392 +-0.5 0.2444948570943373 0.8570246023043264 +-0.5 0.143420043479498 0.4178018080007029 +-0.5 0.28839113912686 0.4999621619540175 +-0.5 0.3022135546980175 0.6915391893516348 +-0.5 0.4330127018914469 0.5833333333312153 +-0.5 0.4329653693130474 0.4166603603242623 +-0.5 0.5743994663660459 0.4999989489408719 +-0.5 0.5773502691887096 0.6666666666638472 +-0.5 0.5743328095552369 0.3363888904820139 +-0.5 0.7258696540860385 0.4187894882451219 +-0.5 0.4258960160381263 0.251279709339806 +-0.5 0.8607386135250659 0.7553478405345799 +-0.5 0.401590711830649 0.8503519556208952 +-0.5 0.2825992851963176 0.3324710997450823 +-0.5 0.7505700937546201 0.8636886363796377 +-0.5 0.1365896778738224 0.7486799101409657 +-0.5 0.7489285336822704 0.1374375424148716 +-0.5 0.1367163526849468 0.2551007332358406 +-0.5 0.8850853359671329 0.4193740145170646 +-0.5 0.6868830780615857 0.5784008611301219 +-0.5 0.6950509506876689 0.2627381560481607 +-0.5 0.7176916949776959 0.7131718872416397 +-0.5 0.4166666666655987 0.1116454968456831 +-0.5 0.452108162661239 0.7220076636675707 +-0.5 0.8779915320715334 0.8779915320715334 +-0.5 0.1220084679278423 0.1220084679278423 +-0.5 0.1220084679277092 0.8779915320720809 +-0.5 0.8779915320720809 0.1220084679277092 +-0.3556624327030097 0.4166666666655986 1.0 +-0.3724170925462842 0.749233318681713 1.0 +-0.2500000000002106 0.1563666820689724 1.0 +-0.14433756729738628 0.583333333332242 1.0 +-0.14433756729699032 0.4166666666655985 1.0 +-0.1275829074539802 0.7492333186816733 1.0 +-0.35974514689443937 0.5833000710926574 1.0 +-0.1277243392042693 0.2548742700107182 1.0 +-0.3722756607958772 0.2548742700106801 1.0 +-0.25000000000032974 0.8561293975764704 1.0 +-0.25 0.4999999999986692 1.0 +-0.2500000000000653 0.6666666666654585 1.0 +-0.2500000000000596 0.3332414259033728 1.0 +-0.12200846792841458 0.1220084679282047 1.0 +-0.3779915320720428 0.122008467927976 1.0 +-0.12115987742600282 0.8788401225739974 1.0 +-0.3788401225744688 0.8788401225742359 1.0 +0.0 0.8556624327027218 0.4166666666681411 +0.0 0.150842647608969 0.4184890431676929 +0.0 0.5977876091177665 0.8506173125076427 +0.0 0.5977876091175213 0.1493826874931945 +0.0 0.1513080901440255 0.7376257747159785 +0.0 0.858871325824756 0.7492937717869144 +0.0 0.2477062749973538 0.1388379787973146 +0.0 0.8588713258236131 0.2507062282145385 +0.0 0.6975287124468637 0.307435353338931 +0.0 0.7090255065795987 0.4956836700021359 +0.0 0.5669872981090046 0.4166666666688204 +0.0 0.5638129342172751 0.5777795531772322 +0.0 0.4250635842228545 0.4990743699759462 +0.0 0.4212660308645567 0.6600894341827865 +0.0 0.4226497308120571 0.3333333333363592 +0.0 0.2722221709521693 0.5800999919032748 +0.0 0.4220429753558262 0.8308249698881572 +0.0 0.4209213154059258 0.1681987915372704 +0.0 0.8558140217522868 0.5824962402991329 +0.0 0.6967684076373145 0.6910350462068958 +0.0 0.7514051943308058 0.8611729007695287 +0.0 0.1369493419814799 0.2471168955184743 +0.0 0.7514957068078485 0.1386450039386763 +0.0 0.2555622040073565 0.8607216437582962 +0.0 0.1148745817410327 0.5805762952913194 +0.0 0.3131019839193649 0.4215655857139594 +0.0 0.3044802942647868 0.7338723628896986 +0.0 0.2820285491208584 0.2879236046785119 +0.0 0.5502404735825772 0.2790063509475008 +0.0 0.5403355914385477 0.7220692631925427 +0.0 0.1220084679277514 0.8779915320720942 +0.0 0.8779915320721717 0.8779915320722271 +0.0 0.8779915320713725 0.1220084679286958 +0.0 0.1220084679276659 0.1220084679279441 +-0.14433756729699032 0.4166666666655986 0.0 +-0.1275829074536758 0.7492333186817307 0.0 +-0.249999999999715 0.15636668206895 0.0 +-0.3556624327026136 0.583333333332242 0.0 +-0.3556624327030097 0.4166666666655985 0.0 +-0.37241709254598176 0.7492333186816766 0.0 +-0.1402548531055589 0.5833000710926582 0.0 +-0.37227566079570706 0.2548742700107145 0.0 +-0.1277243392040947 0.2548742700106629 0.0 +-0.24999999999955658 0.8561293975764912 0.0 +-0.2500000000000001 0.4999999999986692 0.0 +-0.24999999999993472 0.6666666666654587 0.0 +-0.2499999999999195 0.3332414259033656 0.0 +-0.3779915320715181 0.1220084679282038 0.0 +-0.12200846792786268 0.1220084679278942 0.0 +-0.3788401225739197 0.8788401225739894 0.0 +-0.12115987742544199 0.878840122574312 0.0 +-0.12758290745376222 1.0 0.749233318682745 +-0.14433756729720648 1.0 0.4166666666683895 +-0.2499999999999593 1.0 0.1563666820692418 +-0.3556624327026136 1.0 0.583333333332242 +-0.3556624327030097 1.0 0.4166666666655985 +-0.3724170925460668 1.0 0.7492333186820921 +-0.140254853105448 1.0 0.5833000710948419 +-0.1277243392043234 1.0 0.2548742700117366 +-0.3722756607959292 1.0 0.2548742700107636 +-0.2500000000002154 1.0 0.8561293975766916 +-0.2499999999988981 1.0 0.6666666666678633 +-0.24999999999907008 1.0 0.5000000000013808 +-0.25000000000049016 1.0 0.3337127944750626 +-0.37799153207151803 1.0 0.1220084679282038 +-0.12200846792768633 1.0 0.1220084679279961 +-0.1211598774260445 1.0 0.878840122574012 +-0.3788401225744618 1.0 0.8788401225742289 +9 97 105 +1 9 105 +37 1 105 +78 37 105 +97 78 105 +93 78 97 +38 37 78 +38 78 102 +39 38 102 +75 39 102 +90 75 102 +78 90 102 +90 78 93 +85 90 93 +85 88 90 +85 84 86 +82 84 85 +82 85 93 +81 93 97 +9 10 97 +10 81 97 +10 11 81 +11 76 81 +81 82 93 +81 76 82 +11 61 60 +10 61 11 +10 65 61 +9 65 10 +65 69 61 +61 69 68 +61 68 60 +163 169 171 +163 162 169 +39 162 163 +38 39 163 +38 163 166 +37 38 166 +166 163 171 +161 166 171 +166 161 172 +37 166 172 +1 37 172 +22 70 1 +22 1 172 +1 70 9 +9 70 65 +65 70 59 +59 69 65 +59 70 22 +161 22 172 +21 59 22 +21 22 161 +21 71 59 +21 161 173 +4 21 173 +4 71 21 +20 71 4 +20 4 158 +52 4 173 +4 52 158 +167 52 173 +52 131 158 +52 53 131 +167 161 171 +161 167 173 +146 131 152 +131 146 158 +146 20 158 +19 20 146 +64 71 20 +59 71 64 +64 69 59 +58 69 64 +68 69 58 +63 68 58 +18 63 58 +18 58 19 +18 19 126 +18 126 149 +140 126 150 +150 126 152 +126 146 152 +126 19 146 +58 64 19 +19 64 20 +139 142 153 +142 139 152 +139 150 152 +135 137 139 +139 137 150 +131 142 152 +159 167 171 +53 52 167 +131 53 142 +159 53 167 +54 53 159 +53 54 142 +54 159 165 +165 159 169 +169 159 171 +165 169 170 +54 128 142 +54 55 128 +55 54 165 +160 55 165 +128 55 147 +56 55 160 +160 165 170 +133 128 147 +128 133 153 +142 128 153 +135 136 137 +135 134 136 +133 134 135 +133 135 153 +135 139 153 +33 34 183 +34 33 132 +177 33 183 +33 125 132 +132 125 133 +133 125 134 +134 125 143 +125 32 143 +33 32 125 +32 33 177 +32 177 182 +182 177 187 +187 177 188 +177 183 188 +183 178 188 +178 183 190 +183 34 190 +34 132 157 +132 147 157 +132 133 147 +160 168 175 +168 160 170 +55 56 147 +147 56 157 +56 160 175 +56 8 157 +8 56 175 +8 34 157 +34 8 190 +8 35 190 +35 8 175 +168 35 175 +35 178 190 +36 35 168 +35 36 178 +178 36 189 +36 168 174 +168 164 174 +164 168 170 +178 184 188 +184 178 189 +23 184 189 +164 41 174 +41 5 174 +5 36 174 +36 5 189 +5 23 189 +5 41 107 +23 5 107 +79 23 107 +79 96 100 +96 79 107 +41 96 107 +41 40 96 +40 75 96 +96 75 100 +88 75 90 +40 39 75 +39 40 162 +169 162 170 +162 164 170 +40 41 164 +162 40 164 +88 86 89 +85 86 88 +75 88 100 +88 89 100 +89 86 99 +89 77 98 +89 79 100 +79 89 98 +24 23 79 +24 79 98 +25 24 98 +24 25 180 +25 179 180 +23 24 184 +24 180 184 +184 180 188 +180 179 187 +180 187 188 +179 186 187 +179 181 186 +179 26 181 +26 27 181 +25 26 179 +77 25 98 +26 25 77 +26 77 91 +91 77 101 +77 99 101 +77 89 99 +99 87 101 +74 87 103 +87 74 101 +86 84 87 +86 87 99 +109 45 114 +46 45 109 +109 114 119 +114 118 119 +45 44 114 +44 45 74 +74 94 101 +74 45 94 +45 46 94 +94 46 104 +91 94 104 +94 91 101 +27 26 91 +27 91 104 +6 27 104 +46 6 104 +27 6 192 +6 28 192 +28 6 124 +6 46 124 +46 109 124 +181 27 192 +185 181 192 +181 185 186 +117 109 119 +109 117 124 +117 28 124 +28 185 192 +28 29 185 +29 28 117 +185 29 191 +29 117 123 +7 29 123 +29 7 191 +7 30 191 +30 7 156 +51 7 123 +7 51 156 +113 51 123 +51 145 156 +51 50 145 +113 117 119 +117 113 123 +144 130 145 +145 130 156 +130 30 156 +30 176 191 +176 185 191 +185 176 186 +176 182 186 +186 182 187 +31 32 182 +32 31 143 +134 143 144 +143 130 144 +31 130 143 +176 31 182 +31 30 130 +30 31 176 +138 136 154 +136 144 154 +136 134 144 +127 141 154 +144 127 154 +127 144 145 +111 113 119 +50 51 113 +50 127 145 +111 50 113 +49 50 111 +50 49 127 +127 49 141 +118 111 119 +112 118 120 +112 111 118 +49 111 112 +49 48 141 +48 49 112 +48 112 115 +141 48 148 +47 48 115 +48 47 148 +115 112 120 +141 148 151 +137 136 138 +137 140 150 +137 138 140 +140 138 151 +138 141 151 +141 138 154 +16 57 17 +16 17 129 +57 63 17 +129 17 149 +129 140 151 +140 129 149 +126 140 149 +17 18 149 +17 63 18 +67 68 63 +57 67 63 +66 67 57 +57 72 66 +16 72 57 +16 129 155 +129 148 155 +148 129 151 +115 110 121 +110 115 120 +148 47 155 +47 115 121 +47 3 155 +3 47 121 +3 16 155 +3 72 16 +15 72 3 +15 3 121 +110 15 121 +66 72 15 +14 15 110 +14 66 15 +14 110 122 +14 73 66 +62 67 66 +66 73 62 +62 73 13 +13 73 2 +2 14 122 +2 73 14 +42 2 122 +116 42 122 +110 116 122 +116 110 120 +108 116 120 +43 42 116 +108 43 116 +44 43 108 +44 108 114 +114 108 118 +118 108 120 +60 68 67 +60 67 62 +12 62 13 +60 62 12 +11 60 12 +82 76 83 +11 12 76 +76 12 95 +12 13 95 +83 76 95 +82 83 84 +87 84 103 +84 83 103 +83 80 92 +83 92 103 +92 74 103 +44 74 92 +43 44 92 +80 43 92 +80 83 95 +80 95 106 +42 43 80 +42 80 106 +2 42 106 +13 2 106 +95 13 106 diff --git a/examples/assets/rectangular_torus.geo b/examples/assets/rectangular_torus.geo new file mode 100644 index 00000000..a24b9571 --- /dev/null +++ b/examples/assets/rectangular_torus.geo @@ -0,0 +1,128 @@ +h = 1.0; +H = 1.0; +// Gmsh project created on Sun Nov 06 14:23:16 2022 +SetFactory("Built-in"); +//+ +Point(1) = {2, -2, 0, h}; +//+ +Point(2) = {2, 2, 0, h}; +//+ +Point(3) = {-2, 2, 0, h}; +//+ +Point(4) = {-2, -2, 0, h}; +//+ +Point(5) = {1, -1, 0, h}; +//+ +Point(6) = {1, 1, 0, h}; +//+ +Point(7) = {-1, 1, 0, h}; +//+ +Point(8) = {-1, -1, 0, h}; +//+ +Line(1) = {2, 3}; +//+ +Line(2) = {3, 4}; +//+ +Line(3) = {4, 1}; +//+ +Line(4) = {1, 2}; +//+ +Line(5) = {6, 7}; +//+ +Line(6) = {7, 8}; +//+ +Line(7) = {8, 5}; +//+ +Line(8) = {5, 6}; +//+ +Point(9) = {2, -2, H, h}; +//+ +Point(10) = {2, 2, H, h}; +//+ +Point(11) = {-2, 2, H, h}; +//+ +Point(12) = {-2, -2, H, h}; +//+ +Point(13) = {1, -1, H, h}; +//+ +Point(14) = {1, 1, H, h}; +//+ +Point(15) = {-1, 1, H, h}; +//+ +Point(16) = {-1, -1, H, h}; +//+ +Line(9) = {10, 11}; +//+ +Line(10) = {11, 12}; +//+ +Line(11) = {12, 9}; +//+ +Line(12) = {9, 10}; +//+ +Line(13) = {14, 15}; +//+ +Line(14) = {15, 16}; +//+ +Line(15) = {16, 13}; +//+ +Line(16) = {13, 14}; +//+ +Line(17) = {2, 10}; +//+ +Line(18) = {3, 11}; +//+ +Line(19) = {4, 12}; +//+ +Line(20) = {1, 9}; +//+ +Line(21) = {6, 14}; +//+ +Line(22) = {7, 15}; +//+ +Line(23) = {8, 16}; +//+ +Line(24) = {5, 13}; +//+ +Curve Loop(1) = {-12, 17, 4, -20}; +//+ +Plane Surface(1) = {1}; +//+ +Curve Loop(2) = {1, 18, -9, -17}; +//+ +Plane Surface(2) = {2}; +//+ +Curve Loop(3) = {-18, -10, 19, 2}; +//+ +Plane Surface(3) = {3}; +//+ +Curve Loop(4) = {-19, -11, 20, 3}; +//+ +Plane Surface(4) = {4}; +//+ +Curve Loop(5) = {24, 16, -21, -8}; +//+ +Plane Surface(5) = {5}; +//+ +Curve Loop(6) = {-5, -22, 13, 21}; +//+ +Plane Surface(6) = {6}; +//+ +Curve Loop(7) = {-6, -23, 14, 22}; +//+ +Plane Surface(7) = {7}; +//+ +Curve Loop(8) = {23, 15, -24, -7}; +//+ +Plane Surface(8) = {8}; +//+ +Curve Loop(9) = {10, 11, 12, 9}; +//+ +Curve Loop(10) = {14, 15, 16, 13}; +//+ +Plane Surface(9) = {9, 10}; +//+ +Curve Loop(11) = {-3, -4, -1, -2}; +//+ +Curve Loop(12) = {-8, -5, -6, -7}; +//+ +Plane Surface(10) = {11, 12}; diff --git a/examples/assets/sphere45.in b/examples/assets/sphere45.in new file mode 100644 index 00000000..f1d63191 --- /dev/null +++ b/examples/assets/sphere45.in @@ -0,0 +1,323 @@ +1 +109 212 +0.0 0.0 0.0 +1.0 0.0 0.0 +0.0 1.0 0.0 +-1.0 0.0 0.0 +0.0 -1.0 0.0 +0.0 0.0 1.0 +0.0 0.0 -1.0 +0.923879532082714 0.3826834333997559 0.0 +0.7071067795767625 0.7071067827963327 0.0 +0.3826834312295727 0.9238795329816333 0.0 +-0.3826834333997559 0.923879532082714 0.0 +-0.7071067827963327 0.7071067795767625 0.0 +-0.9238795329816333 0.3826834312295727 0.0 +-0.923879532082714 -0.3826834333997559 0.0 +-0.7071067795767625 -0.7071067827963327 0.0 +-0.3826834312295727 -0.9238795329816333 0.0 +0.3826834333997559 -0.923879532082714 0.0 +0.7071067827963327 -0.7071067795767625 0.0 +0.9238795329816333 -0.3826834312295727 0.0 +0.0 0.3826834333997559 0.923879532082714 +0.0 0.7071067827963327 0.7071067795767625 +0.0 0.9238795329816333 0.3826834312295727 +0.0 0.923879532082714 -0.3826834333997559 +0.0 0.7071067795767625 -0.7071067827963327 +0.0 0.3826834312295727 -0.9238795329816333 +0.0 -0.3826834333997559 -0.923879532082714 +0.0 -0.7071067827963327 -0.7071067795767625 +0.0 -0.9238795329816333 -0.3826834312295727 +0.0 -0.923879532082714 0.3826834333997559 +0.0 -0.7071067795767625 0.7071067827963327 +0.0 -0.3826834312295727 0.9238795329816333 +0.7712990324977224 -0.5261836316964337 -0.3580901956251096 +0.7832272477767324 0.5233357175207212 -0.3356706795464225 +0.3356706794722104 -0.183771065508847 -0.923879532419897 +0.3298749710844383 0.7884309806178305 -0.5191908052479812 +0.3364914460521806 -0.5356371469604048 -0.7745103960114611 +0.4943952293272733 -0.7818671890563442 -0.3798118690820019 +0.6320232753776562 -0.3163329681330331 -0.7074461341000068 +0.9179912585278028 0.1751715968730272 -0.3558187191752205 +0.7153911295286901 0.2972003361018707 -0.632366580404594 +0.9247531464560557 -0.2034408415038567 -0.3216262460183055 +0.3182620562185089 0.5161949160681655 -0.7951427998774474 +0.2034339032461793 0.191010725638466 -0.9602757675277923 +0.3318408257257821 -0.7290341394220538 -0.5986575731908655 +0.5143125430732531 0.08759839546161578 -0.8531173009323357 +0.5429324605256425 -0.5927866926067963 -0.5948346664207352 +0.7870222775749804 0.001326899353937282 -0.6169231507560687 +0.5709878661731511 0.6078735380892875 -0.5517813139041982 +0.5481592105556712 0.79368928263904 -0.2637779416609913 +0.2458184845179587 0.9376281485435064 -0.2458184853264101 +0.3356706784848729 -0.1837710644888045 0.9238795329815229 +0.3808964856541316 0.5323383179218478 0.7559985333904224 +0.9238795324198404 0.1837710655050317 0.3356706794744551 +0.7981217874283407 -0.4624241295859055 0.3862195448302793 +0.3239596594514724 -0.7578760046838178 0.5662809378502687 +0.3379819903774152 0.7843488125142359 0.5201587397016689 +0.7746503893874388 0.54404208556427 0.3223894901462653 +0.6762424833857329 0.3096909027609308 0.6684217593790739 +0.7952068118384303 -0.1011561765969377 0.5978407432937709 +0.9361182415423337 -0.1795770587008401 0.3023817419092033 +0.5777467983327468 -0.4173250611831379 0.7014616385268339 +0.2994166271545614 0.2150136179002493 0.9295799199105109 +0.5623828314586349 -0.7723464303375097 0.2953075387210339 +0.2757249103344169 -0.5179234431073846 0.809772240139701 +0.577313184287358 -0.02857822908601 0.8160225315947462 +0.5386170963264684 0.795865112582042 0.2765688813290775 +0.5745678182057902 0.6089374928036108 0.5468701419352591 +0.2640750467215406 0.9279472313776503 0.2630176904270309 +0.26349397886833 -0.9286880418750045 0.2609778610888406 +-0.7782808169164066 -0.5197822937894205 -0.3522858740894094 +-0.7832272498784564 0.5233357150134625 -0.335670678551431 +-0.3356706794739734 -0.1837710655065382 -0.9238795324197158 +-0.5233357175261585 0.7832272477702242 -0.3356706795531307 +-0.3223894903743268 -0.5440420859519188 -0.7746503890202773 +-0.54510520194119 0.5451051986321889 -0.636962040659266 +-0.7050491528145805 0.3255910057135039 -0.629996975479971 +-0.9313698262072214 -0.2067000597440215 -0.2997087455056128 +-0.769142567051783 -0.2521831216418175 -0.5872166420563893 +-0.4625858909497461 -0.7952774519371975 -0.3918520995662882 +-0.5066801882500737 0.3013212963140176 -0.8077627518167138 +-0.5595946567772138 -0.5549240882847868 -0.6155591574720422 +-0.2885513214281583 0.1343905017269157 -0.9479859323574646 +-0.6326688931838378 0.01593457962769268 -0.7742584586361497 +-0.294002442557577 0.5074470400059689 -0.8099753486121409 +-0.2206785158701794 -0.8109710613787934 -0.5418735371278878 +-0.9037335009357622 0.1116491840495477 -0.4132798313340016 +-0.2761954901451649 0.7695834710164464 -0.5757233123313235 +-0.2657595433294145 0.9261685617758262 -0.2675512293140113 +-0.5610283527690639 -0.3131152481702204 -0.7662936961456306 +-0.8986511462892972 -0.2234519520144048 0.3774855525896111 +-0.7832272498729063 0.5233357150155545 0.3356706785611194 +-0.4063938126531793 0.1675901986980527 0.898196857229839 +-0.3862195463776329 -0.4624241289548164 0.7981217870452071 +-0.5163412162948556 0.7883957432990804 0.3344008078684016 +-0.3223894901399288 0.5440420855535103 0.7746503893976325 +-0.5613920645927912 -0.7538101640409378 0.3414808726719812 +-0.6259579262136886 0.3574127511108542 0.6931325991133507 +-0.5654909040789513 -0.2106776780151426 0.7973925967740848 +-0.2733654792854793 -0.2010368837014314 0.9406675747184216 +-0.6948473160149551 -0.4285218225779893 0.5775432927506401 +-0.7787080013966404 -0.546208098467257 0.3086592971702507 +-0.9346316977779511 0.1510686554600859 0.3219345443505349 +-0.2953075384818359 -0.7723464300921336 0.5623828319212242 +-0.2762115444539761 0.7961810039040885 0.5383335320529707 +-0.788420074759952 0.01466700895224715 0.6149623277599258 +-0.5809154623227699 0.5832144663068862 0.5678011200452993 +-0.2626771544471102 0.9281258472912962 0.2637861333000328 +-0.2665923219327522 -0.9241423246462652 0.2736594556815108 +-0.8002939633135487 0.3205923101786786 0.5067051834530513 +72 89 83 +78 86 83 +83 89 78 +81 89 74 +78 89 81 +78 81 70 +77 78 70 +77 86 78 +4 86 77 +4 77 14 +14 77 70 +14 70 15 +70 81 79 +70 79 15 +15 79 16 +16 79 28 +79 85 28 +16 28 5 +26 72 7 +72 82 7 +72 83 82 +74 89 72 +26 74 72 +27 74 26 +27 85 74 +74 85 81 +81 85 79 +28 85 27 +26 36 27 +36 44 27 +44 46 37 +36 46 44 +27 44 28 +28 17 5 +28 44 37 +28 37 17 +37 46 32 +18 37 32 +17 37 18 +18 32 19 +32 46 38 +38 46 36 +34 45 38 +45 47 38 +38 47 41 +41 47 39 +38 41 32 +32 41 19 +19 41 2 +2 41 39 +34 36 26 +34 38 36 +7 34 26 +25 43 7 +7 43 34 +43 45 34 +42 45 43 +42 43 25 +2 39 8 +8 39 33 +39 40 33 +39 47 40 +40 47 45 +40 48 33 +42 48 40 +40 45 42 +8 33 9 +9 49 10 +33 49 9 +48 49 33 +35 49 48 +35 50 49 +49 50 10 +10 50 3 +3 50 23 +23 50 35 +23 35 24 +35 48 42 +35 42 24 +24 42 25 +25 84 24 +84 87 24 +24 87 23 +73 87 75 +75 87 84 +75 80 76 +75 76 71 +83 86 76 +80 83 76 +82 83 80 +7 82 25 +82 84 25 +80 84 82 +75 84 80 +13 86 4 +71 86 13 +76 86 71 +12 73 71 +73 75 71 +12 71 13 +11 73 12 +11 88 73 +73 88 87 +87 88 23 +3 88 11 +23 88 3 +3 107 22 +11 107 3 +22 107 104 +104 107 94 +94 107 11 +12 94 11 +13 91 12 +91 106 94 +91 94 12 +91 109 106 +102 109 91 +13 102 91 +4 102 13 +95 97 92 +20 95 92 +20 92 6 +97 105 92 +97 109 105 +105 109 102 +106 109 97 +95 106 97 +104 106 95 +94 106 104 +22 104 21 +21 104 95 +21 95 20 +20 52 21 +52 56 21 +52 67 56 +21 56 22 +56 68 22 +22 68 3 +3 68 10 +10 68 66 +66 68 56 +56 67 66 +66 67 57 +9 66 57 +10 66 9 +9 57 8 +57 67 58 +58 67 52 +58 65 59 +62 65 58 +58 59 53 +57 58 53 +8 57 53 +8 53 2 +20 62 52 +52 62 58 +51 62 6 +6 62 20 +31 51 6 +51 65 62 +51 64 61 +31 64 51 +53 60 2 +2 60 19 +19 60 54 +54 60 59 +59 60 53 +61 65 51 +59 65 61 +59 61 54 +55 63 61 +19 54 18 +18 63 17 +54 63 18 +61 63 54 +55 69 63 +63 69 17 +17 69 5 +5 69 29 +29 69 55 +29 55 30 +61 64 55 +55 64 30 +30 64 31 +30 103 29 +100 103 93 +93 103 30 +31 93 30 +31 99 93 +93 99 98 +98 99 92 +92 99 6 +6 99 31 +5 108 16 +29 108 5 +103 108 29 +96 108 103 +16 108 96 +96 101 15 +16 96 15 +100 101 96 +15 101 14 +14 101 90 +14 90 4 +90 102 4 +90 105 102 +90 101 100 +98 105 100 +100 105 90 +92 105 98 +98 100 93 +96 103 100 diff --git a/examples/assets/twoboxes.geo b/examples/assets/twoboxes.geo new file mode 100644 index 00000000..7ec8f787 --- /dev/null +++ b/examples/assets/twoboxes.geo @@ -0,0 +1,116 @@ +// Gmsh project created on Fri Dec 13 11:47:12 2024 +h = 0.2; +//+ +Point(1) = {0, 0, 0, h}; +//+ +Point(3) = {1, 0, 0, h}; +//+ +Point(4) = {-1, 0, 0, h}; +//+ +Point(5) = {0, 0, 1, h}; +//+ +Point(6) = {1, 0, 1, h}; +//+ +Point(7) = {-1, 0, 1, h}; +//+ +Point(8) = {0, 1, 0, h}; +//+ +Point(9) = {1, 1, 0, h}; +//+ +Point(10) = {-1, 1, 0, h}; +//+ +Point(11) = {0, 1, 1, h}; +//+ +Point(12) = {1, 1, 1, h}; +//+ +Point(13) = {-1, 1, 1, h}; +//+ +Line(1) = {3, 9}; +//+ +Line(2) = {1, 8}; +//+ +Line(3) = {4, 10}; +//+ +Line(4) = {6, 12}; +//+ +Line(5) = {5, 11}; +//+ +Line(6) = {7, 13}; +//+ +Line(7) = {6, 5}; +//+ +Line(8) = {5, 7}; +//+ +Line(9) = {12, 11}; +//+ +Line(10) = {11, 13}; +//+ +Line(11) = {3, 1}; +//+ +Line(12) = {1, 4}; +//+ +Line(13) = {9, 8}; +//+ +Line(14) = {8, 10}; +//+ +Line(15) = {3, 6}; +//+ +Line(16) = {9, 12}; +//+ +Line(17) = {1, 5}; +//+ +Line(18) = {8, 11}; +//+ +Line(19) = {4, 7}; +//+ +Line(20) = {10, 13}; +//+ +Curve Loop(1) = {16, -4, -15, 1}; +//+ +Plane Surface(1) = {1}; +//+ +Curve Loop(2) = {18, -9, -16, 13}; +//+ +Plane Surface(2) = {2}; +//+ +Curve Loop(3) = {20, -10, -18, 14}; +//+ +Plane Surface(3) = {3}; +//+ +Curve Loop(4) = {6, -20, -3, 19}; +//+ +Plane Surface(4) = {4}; +//+ +Curve Loop(5) = {11, 2, -13, -1}; +//+ +Plane Surface(5) = {5}; +//+ +Curve Loop(6) = {12, 3, -14, -2}; +//+ +Plane Surface(6) = {6}; +//+ +Curve Loop(7) = {10, -6, -8, 5}; +//+ +Plane Surface(7) = {7}; +//+ +Curve Loop(8) = {9, -5, -7, 4}; +//+ +Plane Surface(8) = {8}; +//+ +Curve Loop(9) = {7, -17, -11, 15}; +//+ +Plane Surface(9) = {9}; +//+ +Curve Loop(10) = {8, -19, -12, 17}; +//+ +Plane Surface(10) = {10}; +//+ +Curve Loop(11) = {2, 18, -5, -17}; +//+ +Plane Surface(11) = {11}; + + +//classify parts of geometry +Physical Surface("G12") = {11}; +Physical Surface("G10") = {3,4,6,7,10}; +Physical Surface("G20") = {1,2,5,8,9}; diff --git a/examples/assets/twoboxes2.geo b/examples/assets/twoboxes2.geo new file mode 100644 index 00000000..b00b2bcb --- /dev/null +++ b/examples/assets/twoboxes2.geo @@ -0,0 +1,115 @@ +h = 0.1; +//+ +Point(1) = {0, 0, 0, h}; +//+ +Point(2) = {0.5, 0, 0, h}; +//+ +Point(3) = {1, 0, 0, h}; +//+ +Point(4) = {0, 1, 0, h}; +//+ +Point(5) = {0.5, 1, 0, h}; +//+ +Point(6) = {1, 1, 0, h}; +//+ +Point(7) = {0, 0, 1, h}; +//+ +Point(8) = {0.5, 0, 1, h}; +//+ +Point(9) = {1, 0, 1, h}; +//+ +Point(10) = {0, 1, 1, h}; +//+ +Point(11) = {0.5, 1, 1, h}; +//+ +Point(12) = {1, 1, 1, h}; +//+ +Line(1) = {1, 4}; +//+ +Line(2) = {2, 5}; +//+ +Line(3) = {3, 6}; +//+ +Line(4) = {7, 10}; +//+ +Line(5) = {8, 11}; +//+ +Line(6) = {9, 12}; +//+ +Line(7) = {7, 8}; +//+ +Line(8) = {8, 9}; +//+ +Line(9) = {10, 11}; +//+ +Line(10) = {11, 12}; +//+ +Line(11) = {1, 2}; +//+ +Line(12) = {2, 3}; +//+ +Line(13) = {4, 5}; +//+ +Line(14) = {5, 6}; +//+ +Line(15) = {9, 3}; +//+ +Line(16) = {8, 2}; +//+ +Line(17) = {7, 1}; +//+ +Line(18) = {10, 4}; +//+ +Line(19) = {11, 5}; +//+ +Line(20) = {12, 6}; +//+ +Curve Loop(1) = {7, 5, -9, -4}; +//+ +Plane Surface(1) = {1}; +//+ +Curve Loop(2) = {8, 6, -10, -5}; +//+ +Plane Surface(2) = {2}; +//+ +Curve Loop(3) = {11, 2, -13, -1}; +//+ +Plane Surface(3) = {3}; +//+ +Curve Loop(4) = {12, 3, -14, -2}; +//+ +Plane Surface(4) = {4}; +//+ +Curve Loop(5) = {7, 16, -11, -17}; +//+ +Plane Surface(5) = {5}; +//+ +Curve Loop(6) = {8, 15, -12, -16}; +//+ +Plane Surface(6) = {6}; +//+ +Curve Loop(7) = {10, 20, -14, -19}; +//+ +Plane Surface(7) = {7}; +//+ +Curve Loop(8) = {9, 19, -13, -18}; +//+ +Plane Surface(8) = {8}; +//+ +Curve Loop(9) = {4, 18, -1, -17}; +//+ +Plane Surface(9) = {9}; +//+ +Curve Loop(10) = {5, 19, -2, -16}; +//+ +Plane Surface(10) = {10}; +//+ +Curve Loop(11) = {6, 20, -3, -15}; +//+ +Plane Surface(11) = {11}; +//+ +Physical Surface("Gamma1") = {5, 1, 9, 8, 3}; +//+ +Physical Surface("Gamma2") = {6, 2, 7, 4, 11}; +//+ +Physical Surface("Gamma3") = {10}; diff --git a/examples/bases/neumann_bcs.jl b/examples/bases/neumann_bcs.jl new file mode 100644 index 00000000..7a0ca234 --- /dev/null +++ b/examples/bases/neumann_bcs.jl @@ -0,0 +1,44 @@ +using BEAST +using CompScienceMeshes + + +Γ = meshrectangle(1.0, 1.0, 0.1, 3) +all_edges = skeleton(Γ, 1) +bnd_edges = boundary(Γ) +int_edges = setminus(all_edges, bnd_edges) + +X = raviartthomas(Γ, all_edges) +Y = Ydi = BEAST.buffachristiansen(Γ, bnd_edges) + +T = Maxwell3D.singlelayer(wavenumber=1.0) +Id = BEAST.NCross() + +Txx = assemble(T, X, X) +Tyy = assemble(T, Y, Y) + +Ixy = assemble(Id, X, Y) +IYX = BEAST.GMRES(Ixy) +IXY = BEAST.GMRES(Ixy') + +using LinearAlgebra +@show cond(Txx) + +𝗧 = Matrix(Txx) +𝗜 = Matrix(Ixy) +𝗜⁻¹ = inv(𝗜) +𝗣 = transpose(𝗜⁻¹) * Matrix(Tyy) * 𝗜⁻¹ +𝗣𝗧 = 𝗣*𝗧 + +@show cond(𝗧) +@show cond(𝗜) +@show cond(𝗣) +@show cond(𝗣*𝗧) + +σI = svdvals(𝗜) + +using PlotlyJS +plot( + scatter(y=σI), + Layout( + yaxis_type=:log)) + diff --git a/examples/betterbasis.jl b/examples/betterbasis.jl deleted file mode 100644 index 78fac978..00000000 --- a/examples/betterbasis.jl +++ /dev/null @@ -1,85 +0,0 @@ -using CompScienceMeshes, BEAST -Base.getindex(m::CompScienceMeshes.AbstractMesh, i::Int) = cells(m)[i] -cellvertices(m::CompScienceMeshes.AbstractMesh, i::Int) = vertices(m)[cells(m)[i]] - -m = meshsphere(1.0, 0.35) -# m = meshcuboid(1.0, 1.0, 0.2, 0.2) - -X = raviartthomas(m) -Y = buffachristiansen(m, ibscaled=true) - -Id = BEAST.Identity() -Nx = BEAST.NCross() - -E = Maxwell3D.planewave(direction=ẑ, polarization=x̂, wavenumber=1.0) -G = -assemble(Id, Y, Y) - -b = assemble((n×E)×n, Y) -x = G \ b - -# x = zeros(Float64,numfunctions(Y)) -# x[20] = 1 -fcr, geo = facecurrents(x, Y) - -import PlotlyJS -using LinearAlgebra -PlotlyJS.plot(patch(geo.mesh, norm.(fcr))) -# PlotlyJS.plot(patch(geo.mesh, rand(numcells(geo)))) - -edges = skeleton(m,1) -verts = skeleton(m,0) - -vertex_list = [v[1] for v in cells(verts)] -Q = lagrangec0d1(m, vertex_list, Val{3}) -Z = curl(Q) - -Gzy = assemble(Id, Z, Y) - -# vtoc, nc = vertextocellmap(edges) -D = connectivity(verts, edges) -@assert size(D) == (numcells(edges), numcells(verts)) - -los = geometry(Y) -# Choose a vertex: -for n_vert in 1:numcells(verts) - @assert cellvertices(verts, n_vert)[1] ≈ Q.pos[n_vert] -# find the connected edges - # n_edges = vtoc[n_vert,1:nc[n_vert]] - using SparseArrays - rows = rowvals(D) - vals = nonzeros(D) - for k in nzrange(D,n_vert) - n_edge = rows[k] - cht = chart(edges, edges[n_edge]) - ctr = cartesian(center(cht)) - vt1 = cht.vertices[1] - lvt = los.mesh.vertices[numvertices(m)+n_edge] - pos = Y.pos[n_edge] - @assert norm(lvt - pos) ≤ 1e-8 - @assert norm(cross(pos-vt1, ctr-vt1)) ≤ 1e-8 - end -end - -n_vert = 13 -x = [float(i == n_vert) for i in 1:numfunctions(Q)] -fcr1, geo1 = facecurrents(x, Q) -colors = getindex.(fcr1,1) -PlotlyJS.plot(patch(m, colors)) - -divY = divergence(Y) -fcr2, geo2 = facecurrents(rand(numfunctions(divY)), divY) -colors = getindex.(fcr2,1) -PlotlyJS.plot(patch(geo2.mesh, colors)) - -for n_fn in 1:numfunctions(divY) - n_fn = 21 - charges = zeros(numcells(los)) - for shape in divY.fns[n_fn] - f = shape.cellid - ch = chart(los, los[f]) - charges[f] += shape.coeff #* volume(ch) - end - nzs = (charges[(charges .≈ 0) .== false]) - xtr = extrema(nzs) - @assert all( Vector{Bool}(nzs .≈ xtr[1]) .| Vector{Bool}(nzs .≈ xtr[2])) -end diff --git a/examples/builddual.jl b/examples/builddual.jl index 4d965626..84308adc 100644 --- a/examples/builddual.jl +++ b/examples/builddual.jl @@ -21,30 +21,30 @@ for (i,face) in enumerate(cells(faces)) push!(patch_idcs, i) end end -patch = Mesh(vertices(faces), cells(faces)[patch_idcs]) +ptch = Mesh(vertices(faces), cells(faces)[patch_idcs]) -port = Mesh(vertices(edges), filter(c -> port_idx in c, cells(boundary(patch)))) +port = Mesh(vertices(edges), filter(c -> port_idx in c, cells(boundary(ptch)))) -@show numcells(patch) +@show numcells(ptch) @show numcells(port) # D, C, d, c, d0, d1, -RT_int, RT_prt, x_int, x_prt = BEAST.buildhalfbc2(patch, port, nothing) - -BF = BEAST.Shape{Float64}[] -for (m,bf) in enumerate(RT_int.fns) - for sh in bf - cellid = patch_idcs[sh.cellid] - BEAST.add!(BF,cellid, sh.refid, sh.coeff * x_int[m]) - end -end - -for (m,bf) in enumerate(RT_prt.fns) - for sh in bf - cellid = patch_idcs[sh.cellid] - BEAST.add!(BF,cellid, sh.refid, sh.coeff * x_prt[m]) - end -end +# RT_int, RT_prt, x_int, x_prt = BEAST.buildhalfbc2(ptch, port, nothing) +# +# BF = BEAST.Shape{Float64}[] +# for (m,bf) in enumerate(RT_int.fns) +# for sh in bf +# cellid = patch_idcs[sh.cellid] +# BEAST.add!(BF,cellid, sh.refid, sh.coeff * x_int[m]) +# end +# end +# +# for (m,bf) in enumerate(RT_prt.fns) +# for sh in bf +# cellid = patch_idcs[sh.cellid] +# BEAST.add!(BF,cellid, sh.refid, sh.coeff * x_prt[m]) +# end +# end # RT_prt = raviartthomas(patch, cellpairs(patch, port)) @@ -71,11 +71,11 @@ G3 = assemble(BEAST.NCross(), bcs3, rts) Q3 = assemble(BEAST.Identity(), divergence(bcs3), divergence(bcs3)) using LinearAlgebra -@show cond(G3) +@show cond(Matrix(G3)) using LinearAlgebra -@assert (numcells(Faces) - 1) == (size(Q,1) - rank(Q)) -@assert cond(G) < 3.5 +# @assert (numcells(Faces) - 1) == (size(Q,1) - rank(Q)) +@assert cond(Matrix(G1)) < 3.5 function compress!(space) T = scalartype(space) diff --git a/examples/calderon.jl b/examples/calderon.jl index 71f81a90..5e2631c2 100644 --- a/examples/calderon.jl +++ b/examples/calderon.jl @@ -1,24 +1,45 @@ -# If you want a scatter plot of the spectra, define `plotresults = true` -# prior to running this script. - using CompScienceMeshes, BEAST using LinearAlgebra -Γ = readmesh(joinpath(@__DIR__,"sphere2.in")) -println("Mesh with $(numvertices(Γ)) vertices and $(numcells(Γ)) cells.") +Γ = readmesh(joinpath(dirname(pathof(BEAST)),"../examples/sphere2.in")) X = raviartthomas(Γ) -Y = BEAST.buffachristiansen2(Γ) +Y = BEAST.buffachristiansen(Γ) -κ = ω = 1.0; γ = κ*im -T = MWSingleLayer3D(γ) +κ = 1.0; +T = Maxwell3D.singlelayer(wavenumber=κ) N = NCross() -Txx = assemble(T,X,X); println("primal discretisation assembled.") -Tyy = assemble(T,Y,Y); println("dual discretisation assembled.") -Nxy = assemble(N,X,Y); println("duality form assembled.") +E = Maxwell3D.planewave(direction=ẑ, polarization=x̂, wavenumber=κ) +e = (n × E) × n + +b = assemble(e, X) +A = assemble(T,X,X); println("primal discretisation assembled.") + +Tyy = assemble(T,Y,Y); println("dual discretisation assembled.") +Nxy = Matrix(assemble(N,X,Y)); println("duality form assembled.") iNxy = inv(Nxy); println("duality form inverted.") -A = iNxy' * Tyy * iNxy * Txx +P = iNxy' * Tyy * iNxy + +iT1 = BEAST.GMRESSolver(A; restart=1_500, abstol=1e-8, reltol=1e-8, maxiter=1_500) +iT2 = BEAST.GMRESSolver(A; restart=1_500, abstol=1e-8, reltol=1e-8, maxiter=1_500, left_preconditioner=P) + +u1, ch1 = BEAST.solve(iT1, bx) +u2, ch2 = BEAST.solve(iT2, bx) + +using Plots +Plots.plot(title="log10 residual error vs iteration count") +Plots.plot!(log10.(ch1[:resnorm]), label="A=b", l=2) +Plots.plot!(log10.(ch2[:resnorm]), label="P*A=P*b", l=2) + +ss1 = svdvals(Txx) +ss2 = svdvals(P*Txx) + +Plots.plot(title="singular value spectrum") +Plots.plot!(ss1, label="A", l=2) +Plots.plot!(ss2, label="P*A", l=2) + +@show norm(u1-u2) / norm(u1) @show cond(Txx) @show cond(Tyy) diff --git a/examples/calderon_open.jl b/examples/calderon_open.jl index 88a370a1..02b0a5eb 100644 --- a/examples/calderon_open.jl +++ b/examples/calderon_open.jl @@ -18,9 +18,10 @@ Txx = assemble(T,X,X); println("primal discretisation assembled.") Tyy = assemble(T,Y,Y); println("dual discretisation assembled.") Nxy = assemble(N,X,Y); println("duality form assembled.") -iNxy = inv(Nxy); println("duality form inverted.") +iNxy = inv(Matrix(Nxy)); println("duality form inverted.") A = iNxy' * Tyy * iNxy * Txx +@show cond(Matrix(Nxy)) @show cond(Txx) @show cond(Tyy) @show cond(A) diff --git a/examples/capacitor.jl b/examples/capacitor.jl index e18b0cd8..ff7e36ba 100644 --- a/examples/capacitor.jl +++ b/examples/capacitor.jl @@ -23,18 +23,18 @@ println("λ = ", λₑ, "m; l = ", l ,"m") # Build and mesh the structure Γ₁ = meshrectangle(l,w,h); -translate!(Γ₁, point(0.0,0.0,d)) +CompScienceMeshes.translate!(Γ₁, point(0.0,0.0,d)) Γ₀ = meshrectangle(l,w,h) γ₁ = meshsegment(l, h, 3) -translate!(γ₁, point(0.0,0.0,d)) +CompScienceMeshes.translate!(γ₁, point(0.0,0.0,d)) γ₀ = meshsegment(l, h, 3) Γ = weld(Γ₁, Γ₀) # define the excitation V₀ = 1.0 -f = ScalarTrace(p -> V₀) +f = ScalarTrace{typeof(V₀)}(p -> V₀) #define basis function RT = rt_ports(Γ,[γ₁,γ₀]) @@ -58,7 +58,7 @@ fcr,geo = facecurrents(u, RT); println("Face currents calculated") z = range(-0.5, stop=0.5, length=100) pts1 = point.(0.5,0.5,z) # pts1 = [point(0.5,0.5,a) for a in range(-0.5,stop=0.5,length=100)] -volt = η*potential(MWSingleLayerPotential3D(κ), pts1, u, RT) +volt = η*potential(MWSingleLayerPotential3D(κ), pts1, u, RT, type=ComplexF64) # Plot the Scalar potential using Plots diff --git a/examples/capacitor_new.jl b/examples/capacitor_new.jl new file mode 100644 index 00000000..7dc8c88a --- /dev/null +++ b/examples/capacitor_new.jl @@ -0,0 +1,113 @@ +using BEAST +using CompScienceMeshes +using LinearAlgebra +using SparseArrays + +import PlotlyJS +# import Plots +# Plots.plotly() + +# function Base.:+(a::S, b::S) where {S<:BEAST.Space} +# @assert geometry(a) == geometry(b) +# S(geometry(a), [a.fns; b.fns], [a.pos; b.pos]) +# end + +l = w = 1.0 #Length and width of capacitor plates +d = 0.1 #seperation of plates +h = 1/60 #size of meshes + +Γ₀ = meshrectangle(l,w,h) +Γ₁ = CompScienceMeshes.translate(Γ₀, point(0.0,0.0,d)) + +γ₀ = meshsegment(l,h,3) +γ₁ = CompScienceMeshes.translate(γ₀, point(0.0,0.0,d)) + +Γ = weld(Γ₁, Γ₀) + +κ = 2pi / 100 +V₀ = 1.0 +f = ScalarTrace{typeof(V₀)}(p -> V₀) + +edges_all = skeleton(Γ, 1) +edges_int = submesh(!in(boundary(Γ)), edges_all) + +on_γ₀ = overlap_gpredicate(γ₀) +edges_pt0 = submesh(edges_all) do m, edge + ch = chart(m, edge) + return on_γ₀(ch) +end + +on_γ₁ = overlap_gpredicate(γ₁) +edges_pt1 = submesh(edges_all) do m, edge + ch = chart(m, edge) + return on_γ₁(ch) +end + +RT_int = raviartthomas(Γ, edges_int) +RT_pt0 = raviartthomas(Γ, edges_pt0) +RT_pt1 = raviartthomas(Γ, edges_pt1) + +verts_pt0_int = submesh(!in(boundary(edges_pt0)), skeleton(edges_pt0,0)) +verts_pt1_int = submesh(!in(boundary(edges_pt1)), skeleton(edges_pt1,0)) + +C0 = connectivity(verts_pt0_int, edges_pt0) +C1 = connectivity(verts_pt1_int, edges_pt1) + +X = RT_int +Y0 = RT_pt0 * C0 +Y1 = RT_pt1 * C1 + +# This is ugly and will fail if the two ports are not equal... +D = sparse(reshape([fill(+1.0, length(edges_pt0)); fill(-1.0, length(edges_pt1))],length(edges_pt0)+length(edges_pt1),1)) +RT_pt = BEAST.union([RT_pt0, RT_pt1]) +Z = RT_pt * D + +@hilbertspace x y0 y1 z +@hilbertspace ξ η0 η1 ζ + +T = Maxwell3D.singlelayer(wavenumber=κ) +trc = X->ntrace(X,γ₁) + +efie = @discretise( @varform( + T[ξ,x] + T[ξ,y0] + T[ξ,y1] + T[ξ,z] + + T[η0,x] + T[η0,y0] + T[η0,y1] + T[η0,z] + + T[η1,x] + T[η1,y0] + T[η1,y1] + T[η1,z] + + T[ζ,x] + T[ζ,y0] + T[ζ,y1] + T[ζ,z] == f[trc(ζ)]), + ξ∈X, η0∈Y0, η1∈Y1, ζ∈Z, + x∈X, y0∈Y0, y1∈Y1, z∈Z) +u = solve(efie) + +# assemble(efie.equation.rhs, efie.test_space_dict) +# @enter assemble(efie.equation.rhs, X + Y0 + Y1 + Z) +# assemble(efie.equation.lhs, efie.test_space_dict, efie.trial_space_dict) + +# You can access the current coefficients pertaining to subspaces +# using the Hilbert space 'placeholder' +@show length(u[x]) +@show length(u[y0]) +@show length(u[y1]) +@show length(u[z]) + +# S = (((X + Y0) + Y1) + Z) +S = BEAST.DirectProductSpace([X, Y0, Y1, Z]) +fcr, geo = facecurrents(u, S) +PlotlyJS.plot(patch(geo, norm.(fcr))) |> display + +# Compute the Scalar Potential across the ports. Note how a voltage +# gap of 1V comes out as a 'natural' condition on the solution. +zs = range(-0.5, stop=0.5, length=100) +pts = [point(0.5,0.5,z) for z ∈ zs] +Φ = potential(MWSingleLayerPotential3D(κ), pts, u, S; type=ComplexF64) +sc1 = PlotlyJS.scatter(x=zs, y=real(Φ), xlabel="height",ylabel="scalar potential",label=false) +PlotlyJS.Plot(sc1) + +# Plot current along γ₀ and γ₁. Note: in this symmetric situation, we +# expect these to be opposite on corresponding points of the two ports. +# In general this is not true; in fact, the two ports could have different shape and size +# traceS0 = ntrace(S, γ₀) +# traceS1 = ntrace(S, γ₁) +# fcr0, geo0 = facecurrents(u, traceS0) +# fcr1, geo1 = facecurrents(u, traceS1) +# Plots.plot() +# Plots.plot!(imag.(fcr0)) +# Plots.plot!(imag.(fcr1)) |> display \ No newline at end of file diff --git a/examples/customexcitation.jl b/examples/customexcitation.jl new file mode 100644 index 00000000..b951b44e --- /dev/null +++ b/examples/customexcitation.jl @@ -0,0 +1,41 @@ +macro testitem(name, tags, code) esc(code) end + +using BEAST, Test + +@testitem "example: custom excitation" tags=[:example] begin + using CompScienceMeshes + + κ = 1.0 + + # Custom excitations can be defined as functions of a point in Cartesian space, + # complemented with a function specifying the return type. + E(x) = point(ComplexF64, 1.0, 1.0im, 0.0) * exp(-im*κ*x[3]) + BEAST.scalartype(::typeof(E)) = ComplexF64 + + # Such a custom field plays nicely with the tangential trace + # operators defined as part of the BEAST framework + e = (n × E) × n + + fn = joinpath(pathof(BEAST), "../../examples/assets/sphere45.in") + Γ = readmesh(fn) + # Γ = meshsphere(radius=1.0, h=0.1) + RT = raviartthomas(Γ) + + t = Maxwell3D.singlelayer(wavenumber=κ) + + @hilbertspace j + @hilbertspace k + + Txx = assemble(t[k,j], j∈RT, k∈RT) + ex = assemble(e[k], k∈RT) + + iTXX = BEAST.GMRESSolver(Txx; maxiter=1000, reltol=1e-5) + uX, ch = solve(iTXX, ex) + @test ch.isconverged +end + +using LinearAlgebra +import Plotly + +fcr, geo = facecurrents(uX, RT) +Plotly.plot(patch(geo, norm.(fcr))) \ No newline at end of file diff --git a/examples/disabled/efie_ibc_nd_convergence_cuboid.jl b/examples/disabled/efie_ibc_nd_convergence_cuboid.jl new file mode 100644 index 00000000..abbcba1b --- /dev/null +++ b/examples/disabled/efie_ibc_nd_convergence_cuboid.jl @@ -0,0 +1,129 @@ +using CompScienceMeshes +using BEAST + +using Makeitso +using BakerStreet +using DataFrames + +using LinearAlgebra +BLAS.set_num_threads(32) + +fn = splitext(splitdir(@__FILE__)[end])[1] + +# const κ = 1.00 +# const η = 1.00 +# const ρ = 10.0 + +struct SingField{T} <: BEAST.Functional + a::T + b::T +end + +function (f::SingField)(p) + x = cartesian(p) + x[3] ≈ 0.5 || return point(0,0,0) + return sqrt(f.b - x[2]) * sqrt(x[2] - f.a) / sqrt(f.b-x[1]) / sqrt(x[1]-f.a) * point(1,0,0) +end + +function BEAST.integrand(f::SingField, gx, ϕx) + return dot(gx[1], ϕx) +end + +function BEAST.scalartype(::SingField{T}) where {T} + T +end + +@target params () -> begin + (;κ=1.0, η=1.0, ρ=1.0) +end + +@target solutions () -> begin + + (;κ, η, ρ) = params + + function payload(;h) + d = 1.0 + Γ = meshcuboid(d, d, d/2, h) + X = raviartthomas(Γ) + + t = Maxwell3D.singlelayer(wavenumber=κ) + δ = 0.1 + d = -2*Maxwell3D.singlelayer(gamma=1/δ) + # E = Maxwell3D.planewave(direction=ẑ, polarization=x̂, wavenumber=κ) + # e = (n × E) × n + e = SingField(0.0, 1.0) + + @hilbertspace j + @hilbertspace k + + a = t - ρ*d + A = assemble(a[k,j], X, X) + b = assemble(e[k], X) + + Ai = BEAST.GMRESSolver(A; restart=1500, abstol=1e-8, maxiter=10_000) + u = Ai * b + return (;u, X) + end + + α = 0.8 + h = collect(0.4 * α.^(0:17)) + runsims(payload, "$(fn)/solutions"; h) +end + +@target refnormsquared (solutions) -> begin + function payload(;h) + (;u, X) = getrow(solutions; h=h) + s = -Maxwell3D.singlelayer(gamma=1.0) + S = assemble(s, X, X) + (;uSu=real(dot(u, S*u))) + end + + runsims(payload, "$fn/refnormsquared"; h=solutions.h) +end + +@target errors (solutions, refnormsquared) -> begin + + hmin = minimum(solutions.h) + uref, Xref = getrow(solutions; h=hmin)[[:u, :X]] + uSuref = getrow(refnormsquared; h=hmin)[:uSu] + + s = -Maxwell3D.singlelayer(gamma=1.0) + qstrat12 = BEAST.NonConformingIntegralOpQStrat(BEAST.DoubleNumSauterQstrat(3, 4, 6, 6, 6, 6)) + + function payload(;h) + @show h + (;h, u, X) = getrow(solutions; h=h) + (;uSu) = getrow(refnormsquared; h=h) + + S12 = assemble(s, Xref, X; quadstrat=[qstrat12]) + err = sqrt(uSuref - 2*real(dot(uref, S12*u)) + uSu) + @show err + return (;err) + end + + runsims(payload, "$(fn)/errors", h=solutions.h[end:-1:2]) +end + + +@make errors +# @make refnormsquared + +using Plots +# rn = sqrt(refnormsquared) +plot(log10.(errors.h), log10.(errors.err), marker=:.); +plot!(log10.(errors.h), -0.27 .+ 0.75*log10.(errors.h), label="p = 0.75") + + +# @make solutions +# import Plotly +# using LinearAlgebra +# (;u, X) = solutions[1,:] +# fcr, geo = facecurrents(u, X) +# Plotly.plot([ +# CompScienceMeshes.patch(geo, log10.(norm.(fcr)); +# lighting_specular=0.0, +# lighting_diffuse=0.0, +# lighting_fresnel=0.0), +# CompScienceMeshes.wireframe(skeleton(geo,1)) +# ]) + diff --git a/examples/disabled/efie_ibc_nd_convergence_sphere.jl b/examples/disabled/efie_ibc_nd_convergence_sphere.jl new file mode 100644 index 00000000..404d9751 --- /dev/null +++ b/examples/disabled/efie_ibc_nd_convergence_sphere.jl @@ -0,0 +1,143 @@ +using CompScienceMeshes +using BEAST + +using Makeitso +using BakerStreet +using DataFrames + +using LinearAlgebra +BLAS.set_num_threads(32) + +fn = splitext(splitdir(@__FILE__)[end])[1] + +const κ = 1.00 +const η = 1.00 +const ρ = 10.0 + +struct SingField{T} <: BEAST.Functional + a::T + b::T +end + +function (f::SingField)(p) + x = cartesian(p) + x[3] ≈ 0.5 || return point(0,0,0) + return sqrt(f.b - x[2]) * sqrt(x[2] - f.a) / sqrt(f.b-x[1]) / sqrt(x[1]-f.a) * point(1,0,0) +end + +function BEAST.integrand(f::SingField, gx, ϕx) + return dot(gx[1], ϕx) +end + +function BEAST.scalartype(::SingField{T}) where {T} + T +end + +# const e = SingField(0.0, 1.0) + +@target solutions () -> begin + function payload(;h) + d = 1.0 + # Γ = meshcuboid(d, d, d/2, h) + Γ = meshsphere(;radius=d/2, h) + X = raviartthomas(Γ) + + t = Maxwell3D.singlelayer(wavenumber=κ) + δ = 0.1 + d = -2*Maxwell3D.singlelayer(gamma=1/δ) + E = Maxwell3D.planewave(direction=ẑ, polarization=x̂, wavenumber=κ) + e = (n × E) × n + + @hilbertspace j + @hilbertspace k + + a = t - ρ*d + A = assemble(a[k,j], X, X) + b = assemble(e[k], X) + + Ai = BEAST.GMRESSolver(A; restart=1500, abstol=1e-8, maxiter=10_000) + u = Ai * b + return (;u, X) + end + + α = 0.8 + h = collect(0.4 * α.^(0:17)) + runsims(payload, "$(fn)/solutions"; h) +end + +@target refnormsquared (solutions) -> begin + function payload(;h) + (;u, X) = getrow(solutions; h=h) + s = -Maxwell3D.singlelayer(gamma=1.0) + S = assemble(s, X, X) + (;uSu=real(dot(u, S*u))) + end + + runsims(payload, "$fn/refnormsquared"; h=solutions.h) +end + +@target errors (solutions, refnormsquared) -> begin + + hmin = minimum(solutions.h) + uref, Xref = getrow(solutions; h=hmin)[[:u, :X]] + uSuref = getrow(refnormsquared; h=hmin)[:uSu] + + s = -Maxwell3D.singlelayer(gamma=1.0) + qstrat12 = BEAST.NonConformingIntegralOpQStrat(BEAST.DoubleNumSauterQstrat(3, 4, 6, 6, 6, 6)) + + function payload(;h) + @show h + (;h, u, X) = getrow(solutions; h=h) + (;uSu) = getrow(refnormsquared; h=h) + + S12 = assemble(s, Xref, X; quadstrat=[qstrat12]) + err = sqrt(uSuref - 2*real(dot(uref, S12*u)) + uSu) + @show err + return (;err) + end + + runsims(payload, "$(fn)/errors", h=solutions.h[end:-1:2]) +end + + +@make errors +@make refnormsquared + +hmin = minimum(refnormsquared.h) +rn = sqrt(getrow(refnormsquared; h=hmin)[:uSu]) + +using Plots +# rn = sqrt(refnormsquared) +plot(log10.(errors.h), log10.(errors.err /rn), marker=:.); +plot!(log10.(errors.h), -0.27 .+ 0.75*log10.(errors.h), label="p = 0.75") + + +# @make solutions +# import Plotly +# using LinearAlgebra +# (;u, X) = solutions[1,:] +# fcr, geo = facecurrents(u, X) +# Plotly.plot([ +# CompScienceMeshes.patch(geo, log10.(norm.(fcr)); +# lighting_specular=0.0, +# lighting_diffuse=0.0, +# lighting_fresnel=0.0), +# CompScienceMeshes.wireframe(skeleton(geo,1)) +# ]) + +sol_nx = loadsims("efie_ibc_nx_convergence_sphere/solutions") +sol_nd = loadsims("efie_ibc_nd_convergence_sphere/solutions") + +h = minimum(sol_nx.h) +u_nx, X_nx = getrow(sol_nx; h=h)[[:u,:X]] +u_nd, X_nd = getrow(sol_nd; h=h)[[:u,:X]] + +using Plots +Plots.plot(abs.(u_nx[1:823:end])) +Plots.plot!(abs.(u_nd[1:823:end])) + +fcr_nx, geo_nx = facecurrents(u_nx, X_nx) +fcr_nd, geo_nd = facecurrents(u_nd, X_nd) +import Plotly +Plotly.plot(patch(geo_nx, norm.(fcr_nx))) +Plotly.plot(patch(geo_nd, norm.(fcr_nd))) \ No newline at end of file diff --git a/examples/disabled/efie_ibc_nx_convergence_cuboid.jl b/examples/disabled/efie_ibc_nx_convergence_cuboid.jl new file mode 100644 index 00000000..82573156 --- /dev/null +++ b/examples/disabled/efie_ibc_nx_convergence_cuboid.jl @@ -0,0 +1,141 @@ +using CompScienceMeshes +using BEAST + +using Makeitso +using BakerStreet +using DataFrames + +using LinearAlgebra +BLAS.set_num_threads(32) + +fn = splitext(splitdir(@__FILE__)[end])[1] + +# const κ = 1.00 +# const η = 1.00 +# const ρ = 10.0 + +struct SingField{T} <: BEAST.Functional + a::T + b::T +end + +function (f::SingField)(p) + x = cartesian(p) + x[3] ≈ 0.5 || return point(0,0,0) + return sqrt(f.b - x[2]) * sqrt(x[2] - f.a) / sqrt(f.b-x[1]) / sqrt(x[1]-f.a) * point(1,0,0) +end + +function BEAST.integrand(f::SingField, gx, ϕx) + return dot(gx[1], ϕx) +end + +function BEAST.scalartype(::SingField{T}) where {T} + T +end + +@target params () -> begin + (;κ=1.0, η=1.0, ρ=1.0) +end + +@target solutions (params) -> begin + + (;κ, η, ρ) = params + + function payload(;h) + d = 1.0 + Γ = meshcuboid(d, d, d/2, h) + X = raviartthomas(Γ) + + t = Maxwell3D.singlelayer(wavenumber=κ) + d = BEAST.Identity() + # E = Maxwell3D.planewave(direction=ẑ, polarization=x̂, wavenumber=κ) + # e = (n × E) × n + e = SingField(0.0, 1.0) + + @hilbertspace j + @hilbertspace k + + a = t - ρ*d + A = assemble(a[k,j], X, X) + b = assemble(e[k], X) + + Ai = BEAST.GMRESSolver(A; restart=1500, abstol=1e-8, maxiter=10_000) + u = Ai * b + return (;u, X) + end + + α = 0.8 + h = collect(0.4 * α.^(0:17)) + runsims(payload, "$(fn)/solutions"; h) +end + +@target refnormsquared (solutions) -> begin + function payload(;h) + (;u, X) = getrow(solutions; h=h) + s = -Maxwell3D.singlelayer(gamma=1.0) + S = assemble(s, X, X) + (;uSu=real(dot(u, S*u))) + end + + runsims(payload, "$fn/refnormsquared"; h=solutions.h) +end + +@target errors (solutions, refnormsquared) -> begin + + hmin = minimum(solutions.h) + uref, Xref = getrow(solutions; h=hmin)[[:u, :X]] + uSuref = getrow(refnormsquared; h=hmin)[:uSu] + + s = -Maxwell3D.singlelayer(gamma=1.0) + qstrat12 = BEAST.NonConformingIntegralOpQStrat(BEAST.DoubleNumSauterQstrat(3, 4, 6, 6, 6, 6)) + + function payload(;h) + @show h + (;h, u, X) = getrow(solutions; h=h) + (;uSu) = getrow(refnormsquared; h=h) + + S12 = assemble(s, Xref, X; quadstrat=[qstrat12]) + err = sqrt(uSuref - 2*real(dot(uref, S12*u)) + uSu) + @show err + return (;err) + end + + runsims(payload, "$(fn)/errors", h=solutions.h[end:-1:2]) +end + + +@make errors +# @make refnormsquared + +using Plots +# rn = sqrt(refnormsquared) +plot(log10.(errors.h), log10.(errors.err), marker=:.) +plot!(log10.(errors.h), -1.5 .+ 0.75*log10.(errors.h), label="p = 0.75") + + +# @make solutions + +# fn = splitext(splitdir(@__FILE__)[end])[1] +# errnx = loadsims("$fn/errors") +# plot(log10.(errnx.h), log10.(errnx.err)) +# plot!(log10.(errnx.h), -2 .+ 0.75 * log10.(errnx.h)) + +# h = 0.1 +# d = 1.0 +# Γ = meshcuboid(d, d, d/2, h) +# X = raviartthomas(Γ) + +# t = Maxwell3D.singlelayer(wavenumber=κ) +# d = BEAST.Identity() +# # E = Maxwell3D.planewave(direction=ẑ, polarization=x̂, wavenumber=κ) +# # e = (n × E) × n + +# @hilbertspace j +# @hilbertspace k + +# a = t - ρ*d +# A = assemble(a[k,j], X, X) +# b = assemble(e[k], X) + +# Ai = BEAST.GMRESSolver(A; restart=1500, abstol=1e-8, maxiter=10_000) +# u = Ai * b \ No newline at end of file diff --git a/examples/disabled/efie_ibc_nx_convergence_sphere.jl b/examples/disabled/efie_ibc_nx_convergence_sphere.jl new file mode 100644 index 00000000..b0af0499 --- /dev/null +++ b/examples/disabled/efie_ibc_nx_convergence_sphere.jl @@ -0,0 +1,142 @@ +using CompScienceMeshes +using BEAST + +using Makeitso +using BakerStreet +using DataFrames + +using LinearAlgebra +BLAS.set_num_threads(32) + +fn = splitext(splitdir(@__FILE__)[end])[1] + +# const κ = 1.00 +# const η = 1.00 +# const ρ = 10.0 + +struct SingField{T} <: BEAST.Functional + a::T + b::T +end + +function (f::SingField)(p) + x = cartesian(p) + x[3] ≈ 0.5 || return point(0,0,0) + return sqrt(f.b - x[2]) * sqrt(x[2] - f.a) / sqrt(f.b-x[1]) / sqrt(x[1]-f.a) * point(1,0,0) +end + +function BEAST.integrand(f::SingField, gx, ϕx) + return dot(gx[1], ϕx) +end + +function BEAST.scalartype(::SingField{T}) where {T} + T +end + +# const e = SingField(0.0, 1.0) + +@target params () -> begin + (;κ=1.0, η=1.0, ρ=1.0) +end + +@target solutions (params) -> begin + + (;κ, η, ρ) = params + + function payload(;h) + d = 1.0 + # Γ = meshcuboid(d, d, d/2, h) + Γ = meshsphere(;radius=d/2, h) + X = raviartthomas(Γ) + + t = Maxwell3D.singlelayer(wavenumber=κ) + d = BEAST.Identity() + E = Maxwell3D.planewave(direction=ẑ, polarization=x̂, wavenumber=κ) + e = (n × E) × n + + @hilbertspace j + @hilbertspace k + + a = t - ρ*d + A = assemble(a[k,j], X, X) + b = assemble(e[k], X) + + Ai = BEAST.GMRESSolver(A; restart=1500, abstol=1e-8, maxiter=10_000) + u = Ai * b + return (;u, X) + end + + α = 0.8 + h = collect(0.4 * α.^(0:17)) + runsims(payload, "$(fn)/solutions"; h) +end + +@target refnormsquared (solutions) -> begin + function payload(;h) + (;u, X) = getrow(solutions; h=h) + s = -Maxwell3D.singlelayer(gamma=1.0) + S = assemble(s, X, X) + (;uSu=real(dot(u, S*u))) + end + + runsims(payload, "$fn/refnormsquared"; h=solutions.h) +end + +@target errors (solutions, refnormsquared) -> begin + + hmin = minimum(solutions.h) + uref, Xref = getrow(solutions; h=hmin)[[:u, :X]] + uSuref = getrow(refnormsquared; h=hmin)[:uSu] + + s = -Maxwell3D.singlelayer(gamma=1.0) + qstrat12 = BEAST.NonConformingIntegralOpQStrat(BEAST.DoubleNumSauterQstrat(3, 4, 6, 6, 6, 6)) + + function payload(;h) + @show h + (;h, u, X) = getrow(solutions; h=h) + (;uSu) = getrow(refnormsquared; h=h) + + S12 = assemble(s, Xref, X; quadstrat=[qstrat12]) + err = sqrt(uSuref - 2*real(dot(uref, S12*u)) + uSu) + @show err + return (;err) + end + + runsims(payload, "$(fn)/errors", h=solutions.h[end:-1:2]) +end + + +# @make errors +# @make refnormsquared +@make solutions + +# using Plots +# rn = sqrt(refnormsquared) +# plot(log10.(errors.h), log10.(errors.err / rn), marker=:.) +# plot!(log10.(errors.h), -1.5 .+ 0.75*log10.(errors.h / rn), label="p = 0.75") + + +# fn = splitext(splitdir(@__FILE__)[end])[1] +# errnx = loadsims("$fn/errors") +# plot(log10.(errnx.h), log10.(errnx.err)) +# plot!(log10.(errnx.h), -2 .+ 0.75 * log10.(errnx.h)) + +# h = 0.1 +# d = 1.0 +# Γ = meshcuboid(d, d, d/2, h) +# X = raviartthomas(Γ) + +# t = Maxwell3D.singlelayer(wavenumber=κ) +# d = BEAST.Identity() +# # E = Maxwell3D.planewave(direction=ẑ, polarization=x̂, wavenumber=κ) +# # e = (n × E) × n + +# @hilbertspace j +# @hilbertspace k + +# a = t - ρ*d +# A = assemble(a[k,j], X, X) +# b = assemble(e[k], X) + +# Ai = BEAST.GMRESSolver(A; restart=1500, abstol=1e-8, maxiter=10_000) +# u = Ai * b \ No newline at end of file diff --git a/examples/scomplex_efie.jl b/examples/disabled/scomplex_efie.jl similarity index 82% rename from examples/scomplex_efie.jl rename to examples/disabled/scomplex_efie.jl index f945a7ab..2f7fc27a 100644 --- a/examples/scomplex_efie.jl +++ b/examples/disabled/scomplex_efie.jl @@ -1,7 +1,7 @@ using CompScienceMeshes using BEAST -Γ1 = readmesh(joinpath(@__DIR__,"sphere2.in")) +Γ1 = readmesh(joinpath(dirname(pathof(BEAST)),"../examples/sphere2.in")) Γ2 = CompScienceMeshes.SComplex2D(Γ1) # Y = raviartthomas(Γ1) Nd1 = BEAST.nedelec(Γ1) @@ -22,8 +22,8 @@ for (e1,e2) in zip(E1.faces, E2.edges) q = getindex.(Ref(E2.nodes), e2) @assert q[1][1] == e1[1] @assert q[2][1] == e1[2] - ctr1 = cartesian(center(chart(E1, e1))) - ctr2 = cartesian(center(chart(E2, e2))) + ctr1 = cartesian(CompScienceMeshes.center(chart(E1, e1))) + ctr2 = cartesian(CompScienceMeshes.center(chart(E2, e2))) if norm(ctr1-ctr2) > 1e-8 @show ctr1 @show ctr2 diff --git a/examples/disabled/tdefie_irk.jl b/examples/disabled/tdefie_irk.jl deleted file mode 100644 index 7e31f767..00000000 --- a/examples/disabled/tdefie_irk.jl +++ /dev/null @@ -1,29 +0,0 @@ -using CompScienceMeshes, BEAST - -o, x, y, z = euclidianbasis(3) - -sol = 5.0; -Δt, Nt = 100.0/sol,200 - -D, Δx = 1.0, 0.45 -Γ = meshsphere(D, Δx) -X = raviartthomas(Γ) - -(A, b, c) = butcher_tableau_radau_2stages(); -V = StagedTimeStep(X, c, Δt, Nt); - -duration, delay, amplitude = 2000.0/sol, 2000.0/sol, 1.0 -gaussian = creategaussian(duration, delay, duration) - -direction, polarisation = z, x -E = planewave(polarisation, direction, derive(gaussian), sol) - -LaplaceEFIO(s::T) where {T} = MWSingleLayer3D(-s/sol, s*s/sol, T(sol)); -kmax = 15; -rho = 1.0001; -T = RungeKuttaConvolutionQuadrature(LaplaceEFIO, A, b, Δt, kmax, rho); - -@hilbertspace j -@hilbertspace j′ -tdefie = @discretise T[j′,j] == -1E[j′] j∈V j′∈V -xefie_irk = solve(tdefie) diff --git a/examples/disabled/tdhelmholtz3d.jl b/examples/disabled/tdhelmholtz3d.jl index 3f3b5362..de418316 100644 --- a/examples/disabled/tdhelmholtz3d.jl +++ b/examples/disabled/tdhelmholtz3d.jl @@ -12,7 +12,7 @@ e = BEAST.planewave(point(0,0,1), c, gaussian) X = lagrangecxd0(G) -Δt, Nt = 0.08, 300 +Δt, Nt = 1.0, 300 #T = timebasisshiftedlagrange(Δt, Nt, 0) #T = timebasiscxd0(Δt, Nt) T = timebasisc0d1(Δt, Nt) diff --git a/examples/disabled/tdhh3d_neumann.jl b/examples/disabled/tdhh3d_neumann.jl index 0c7f6508..adf753ab 100644 --- a/examples/disabled/tdhh3d_neumann.jl +++ b/examples/disabled/tdhh3d_neumann.jl @@ -2,8 +2,8 @@ using CompScienceMeshes using BEAST using LinearAlgebra -# G = meshsphere(1.0, 0.25) -G = meshsphere(1.0, 0.30) +#G = meshsphere(1.0, 0.30) +G = CompScienceMeshes.meshmobius(h=0.2) c = 1.0 S = BEAST.HH3DSingleLayerTDBIO(c) @@ -20,9 +20,9 @@ h = BEAST.gradient(e) # X = lagrangecxd0(G) X = lagrangecxd0(G) -Y = duallagrangec0d1(G) -# Y = lagrangecxd0(G) - +#Y = duallagrangec0d1(G) +Y = lagrangecxd0(G) +numfunctions(X) # X = duallagrangecxd0(G, boundary(G)) # Y = lagrangec0d1(G) @@ -31,21 +31,29 @@ Y = duallagrangec0d1(G) Δt, Nt = 0.16, 300 # T = timebasisc0d1(Δt, Nt) P = timebasiscxd0(Δt, Nt) -H = timebasisc0d1(Δt, Nt) +#H = timebasisc0d1(Δt, Nt) δ = timebasisdelta(Δt, Nt) # assemble the right hand side bd = assemble(n⋅h, X ⊗ P) -Z1d = assemble(Id ⊗ Id, X ⊗ P, X ⊗ P, Val{:bandedstorage}) -Z0d = assemble(D, X ⊗ P, X ⊗ P, Val{:bandedstorage}) +Z1d = assemble(Id ⊗ Id, X ⊗ P, X ⊗ P) +Z0d = assemble(D, X ⊗ P, X ⊗ P) Zd = Z0d + (-0.5)*Z1d u = marchonintime(inv(Zd[:,:,1]), Zd, bd, Nt) bs = assemble(e, X ⊗ δ) -Zs = assemble(S, X ⊗ δ, X ⊗ P, Val{:bandedstorage}) +Zs = assemble(S, X ⊗ δ, X ⊗ P) v = marchonintime(inv(Zs[:,:,1]), Zs, -bs, Nt) + +tdacusticsl = @discretise D[j′,j] == 1.0(n⋅h)[j′] j∈ (X ⊗ P) j′∈ (X ⊗ δ) +xacusticsl = solve(tdacusticsl) + + +import Plots +Plots.plot(xacusticsl[1000,2900:3000]) + U, Δω, ω0 = fouriertransform(u, Δt, 0.0, 2) V, Δω, ω0 = fouriertransform(v, Δt, 0.0, 2) diff --git a/examples/timeonly.jl b/examples/disabled/timeonly.jl similarity index 100% rename from examples/timeonly.jl rename to examples/disabled/timeonly.jl diff --git a/examples/discontinuous_pairing.jl b/examples/discontinuous_pairing.jl new file mode 100644 index 00000000..fb7be053 --- /dev/null +++ b/examples/discontinuous_pairing.jl @@ -0,0 +1,81 @@ +# compute the continuity and infsup constants for various continuous and discontinuous pairings + +using CompScienceMeshes +using BEAST + +using Makeitso +using BakerStreet + +h = 0.1 +Γ = meshcuboid(1.0, 1.0, 1.0, h) + +id = BEAST.Identity() +nx = BEAST.NCross() +s = Maxwell3D.singlelayer(gamma=1.0) +t = Maxwell3D.singlelayer(wavenumber=1.0) + +X = raviartthomas(Γ) +Y = buffachristiansen(Γ) + +A = assemble(id, Y, X) +B = assemble(nx, Y, X) +C = assemble(t, X, X) + +SXX = Symmetric(-assemble(s, X, X)) +SYY = Symmetric(-assemble(s, Y, Y)) + +using LinearAlgebra +HX = sqrt(SXX) +HY = sqrt(SYY) + +QX = inv(HX) +QY = inv(HY) + +svdA = svd(QY*A*QX) +svdB = svd(QY*B*QX) +svdC = svd(QX*C*QX) + +using Plots +plot(svdA.S) +plot!(svdB.S) +plot!(svdC.S) + +E = Maxwell3D.planewave( + direction=point(0,0,1), + polarization=point(1,0,0), + wavenumber=1.0) + +e = (n × E) × n + +struct SingField{T} <: BEAST.Functional + a::T + b::T +end + +function (f::SingField)(p) + x = cartesian(p) + x[3] ≈ 1.0 || return point(0,0,0) + return sqrt(f.b - x[2]) * sqrt(x[2] - f.a) / sqrt(f.b-x[1]) / sqrt(x[1]-f.a) * point(0,1,0) +end + +function BEAST.integrand(f::SingField, gx, ϕx) + return dot(gx[1], ϕx) +end + +e = SingField(0.0, 1.0) + +b = assemble(e, X) +G = assemble(id, X, X) +u = G \ b + +import Plotly +fcr, geo = facecurrents(u, X) +Plotly.plot( + [ + # patch(geo, norm.(fcr), opacity=0.5), + CompScienceMeshes.cones(geo, real.(fcr), sizeref=10.0), + ]) + +q = adjoint(svdA.U) * HY * b +plot(log10.(abs.(q))) +plot!(log10.(abs.(svdA.S))) \ No newline at end of file diff --git a/examples/divgwp_expansion.jl b/examples/divgwp_expansion.jl new file mode 100644 index 00000000..c6ed125d --- /dev/null +++ b/examples/divgwp_expansion.jl @@ -0,0 +1,66 @@ +using BEAST +using CompScienceMeshes +const CSM = CompScienceMeshes +using StaticArrays + +T = Float64 +D = 4 +NF = binomial(2+D, 2) +gwp = BEAST.GWPDivRefSpace{T,D}() +lgx = BEAST.LagrangeRefSpace{T,D,3,10}() + +function fields(p) + map(gwp(p)) do x + x.divergence + end +end + +ch = CSM.simplex( + point(1,0,0), + point(0,1,0), + point(0,0,0)) + +# p = CSM.center(ch) +# v = fields(p) + +coeffs = BEAST.interpolate(fields, lgx, ch) +Q = rationalize.(coeffs; tol=sqrt(eps(T))) + +using LinearAlgebra +norm(Q - coeffs) + +Q3 = Rational{Int64}[89//3 223//81 -7//81 -2 223//81 -7//81 -2 -7//81 -2 -2; -59//2 236//27 -13//54 13 -83//9 -4//27 8 -1//18 3 -2; 13 -13//54 236//27 -59//2 8 -4//27 -83//9 3 -1//18 -2; -2 -7//81 223//81 89//3 -2 -7//81 223//81 -2 -7//81 -2; 89//3 223//81 -7//81 -2 223//81 -7//81 -2 -7//81 -2 -2; -59//2 -83//9 -1//18 -2 236//27 -4//27 3 -13//54 8 13; 13 8 3 -2 -13//54 -4//27 -1//18 236//27 -83//9 -59//2; -2 -2 -2 -2 -7//81 -7//81 -7//81 223//81 223//81 89//3; -2 -7//81 223//81 89//3 -2 -7//81 223//81 -2 -7//81 -2; -2 -1//18 -83//9 -59//2 3 -4//27 236//27 8 -13//54 13; -2 3 8 13 -1//18 -4//27 -13//54 -83//9 236//27 -59//2; -2 -2 -2 -2 -7//81 -7//81 -7//81 223//81 223//81 89//3; -50 890//81 55//81 35//3 -565//81 125//162 20//3 70//81 5//3 -10//3; 50 565//81 -70//81 10//3 -890//81 -125//162 -5//3 -55//81 -20//3 -35//3; 30 -355//27 355//27 -30 80//9 0 -80//9 -10//9 10//9 0; -40 440//27 85//27 -10 5//2 -125//27 205//18 35//3 95//9 -25//2; -35//3 -55//81 -890//81 50 -20//3 -125//162 565//81 -5//3 -70//81 10//3; 15 -5//27 485//27 0 5 0 -485//27 -5 5//27 -15; 40 -5//2 -35//3 25//2 -440//27 125//27 -95//9 -85//27 -205//18 10; -30 -80//9 10//9 0 355//27 0 -10//9 -355//27 80//9 30; -25//2 35//3 5//2 -40 95//9 -125//27 440//27 205//18 85//27 -10; 25//2 -95//9 -205//18 10 -35//3 125//27 -85//27 -5//2 -440//27 40; -15 -5 5 15 5//27 0 -5//27 -485//27 485//27 0; 35//3 20//3 5//3 -10//3 55//81 125//162 70//81 890//81 -565//81 -50] +function BEAST.divergence(localspace::BEAST.GWPDivRefSpace, sh, ch) + BEAST.divergence(localspace, sh, ch, BEAST.dimtype(localspace, domain(ch))) +end + +function BEAST.divergence(localspace::BEAST.GWPDivRefSpace{T,D}, cellid, ch, ::Type{Val{N}}) where {N,D} + function fields(p) + map(localspace(p)) do x + x.divergence + end + end + T = coordtype(ch) + Dim = 2 + NFout = div((Dim+1)*(Dim+2), 2) + lag = BEAST.LagrangeRefSpace{T,D,Dim+1,NFout}() + coeffs = BEAST.interpolate(fields, lag, ch) + S = BEAST.Shape{T} + A = Vector{Vector{S}}(undef, size(coeffs,1)) + for r in axes(coeffs,1) + A[r] = collect(S(cellid, c, coeffs[r,c]) for c in axes(coeffs,2)) + end + return SVector{N}(A) +end + +divfns = BEAST.divergence(gwp, 1, ch) + +p = neighborhood(ch, (0.2, 0.6)) +gwp_vals = gwp(p) +val1 = gwp_vals[1].divergence + +lgx_vals = lgx(p) +val2 = zero(T) +for sh in divfns[1] + val2 += sh.coeff * lgx_vals[sh.refid].value +end \ No newline at end of file diff --git a/examples/dot_tdmfie.jl b/examples/dot_tdmfie.jl index 380a81ac..47a90c84 100644 --- a/examples/dot_tdmfie.jl +++ b/examples/dot_tdmfie.jl @@ -1,41 +1,33 @@ using CompScienceMeshes, BEAST o, x, y, z = euclidianbasis(3) -# D, Δx = 1.0, 0.35 - -Γ = readmesh(joinpath(@__DIR__,"sphere2.in")) -# Γ = meshsphere(1.0, 0.08) -@show numcells(Γ) +Γ = readmesh(joinpath(dirname(pathof(BEAST)),"../examples/sphere2.in")) X, Y = raviartthomas(Γ), buffachristiansen(Γ) -# Δt, Nt = 0.11, 200 -Δt, Nt = 0.6, 200 -# Δt, Nt = 0.05, 400 +Δt ,Nt = 0.3, 200 T = timebasisshiftedlagrange(Δt, Nt, 2) δ = timebasisdelta(Δt, Nt) V = X ⊗ T W = Y ⊗ δ -# width, delay, scaling = 8.0, 12.0, 1.0 -duration = 20 * Δt +duration = 20 * Δt * 2 delay = 1.5 * duration amplitude = 1.0 gaussian = derive(creategaussian(duration, delay, amplitude)) direction, polarisation = ẑ, x̂ -E = BEAST.planewave(polarisation, direction, gaussian, 1.0) -# E = BEAST.planewave(polarisation, direction, derive(gaussian), 1.0) -H = direction × E - -@hilbertspace j; @hilbertspace m′ -# K, I, N = MWDoubleLayerTDIO(1.0, 1.0, 0), Identity(), NCross() -K = MWDoubleLayerTDIO(1.0, 1.0, 1) -N = BEAST.TemporalDifferentiation(NCross()⊗Identity()) - -M = 0.5*N + 1.0*K -Z_mfie = assemble(M, W, V, storage_policy = Val{:bandedstorage}) -b_mfie = assemble(H, W) -dot_xmfie = marchonintime(inv(Z_mfie[:,:,1]), Z_mfie, b_mfie, Nt) +Ė = BEAST.planewave(polarisation, direction, gaussian, 1.0) +Ḣ = direction × Ė + +@hilbertspace j +@hilbertspace k +K̇ = TDMaxwell3D.doublelayer(speedoflight=1.0, numdiffs=1) +Nİ = BEAST.TemporalDifferentiation(NCross()⊗Identity()) + +@hilbertspace k +@hilbertspace j +mfie_dot = @discretise (0.5*Nİ)[k,j] + K̇[k,j] == -1.0Ḣ[k] k∈W j∈V +xmfie_dot = BEAST.motsolve(mfie_dot) # Xmfie, Δω, ω0 = fouriertransform(xmfie, Δt, 0.0, 2) # ω = collect(ω0 .+ (0:Nt-1)*Δω) diff --git a/examples/dpie.jl b/examples/dpie.jl index 7724d4a8..e80825f1 100644 --- a/examples/dpie.jl +++ b/examples/dpie.jl @@ -1,7 +1,7 @@ using CompScienceMeshes using BEAST -Γ = readmesh(joinpath(@__DIR__,"sphere2.in")) +Γ = readmesh(joinpath(dirname(pathof(BEAST)),"../examples/sphere2.in")) X = raviartthomas(Γ) d = BEAST.CurlSingleLayerDP3D(1.0im, 1.0) @@ -12,11 +12,13 @@ Y = lagrangec0d1(Γ) x = refspace(X) y = refspace(Y) -charts = [chart(Γ,c) for c in cells(Γ)] -qd = quaddata(s, x, x, charts, charts) +charts = [chart(Γ,c) for c in Γ] + +qs = BEAST.defaultquadstrat(s, x, x) +qd = quaddata(s, x, x, charts, charts, qs) m1,m2 = 10,11 -ql = quadrule(s, x, x, m1, charts[m1], m2, charts[m2], qd) -@show @which quadrule(s, x, x, m1, charts[m1], m2, charts[m2], qd) +ql = quadrule(s, x, x, m1, charts[m1], m2, charts[m2], qd, qs) +@show @which quadrule(s, x, x, m1, charts[m1], m2, charts[m2], qd, qs) @show typeof(ql) BEAST.momintegrals!(s, x, x, charts[10], charts[20], zeros(ComplexF64,3,3), ql) @@ -31,8 +33,8 @@ dvg = divergence @hilbertspace k q κ = 1.0 -curlA = strace(((x,y,z),)->-exp(-im*κ*z)*ŷ, Γ) -ndotA = dot(n, ((x,y,z),)->-x*exp(-im*κ*z)*ẑ) +curlA = BEAST.ScalarTrace{ComplexF64}(((x,y,z),)->-exp(-im*κ*z)*ŷ) +ndotA = BEAST.NDotTrace{ComplexF64}(((x,y,z),)->-x*exp(-im*κ*z)*ẑ) a = @varform s[k,j] + s[dvg(k),p] + s[q,dvg(j)] - κ^2*s[q,p] == curlA[k]+ndotA[q] @@ -40,14 +42,22 @@ Eq = @discretise a k∈X q∈Y j∈X p∈Y u = solve(Eq) -uj = u[1:numfunctions(X)] -up = u[numfunctions(X)+1:end] +# lform = Eq.equation.rhs +# @which scalartype(lform.terms[2].functional.field) +# @enter assemble(Eq.equation.rhs, Eq.test_space_dict) +# assemble(Eq.equation.lhs, Eq.test_space_dict, Eq.trial_space_dict) + +# uj = u[1:numfunctions(X)] +# up = u[numfunctions(X)+1:end] + +uj = u[j] +up = u[p] -#xrange = range(-2.0, stop=2.0, length=40) -xrange = [0.0] -yrange = [0.0] +xrange = range(-2.0, stop=2.0, length=40) +# xrange = [0.0] +# yrange = [0.0] zrange = range(-2.0, stop=2.0, length=40) -pts = [point(x,y,z) for x ∈ xrange, y ∈ yrange, z ∈ zrange] +pts = [point(x,0,z) for x ∈ xrange, z ∈ zrange] A1 = BEAST.DPVectorialTerm(1.0, 1.0*im) A2 = BEAST.DPScalarTerm(1.0, 1.0*im) diff --git a/examples/dvie.jl b/examples/dvie.jl new file mode 100644 index 00000000..1b86906e --- /dev/null +++ b/examples/dvie.jl @@ -0,0 +1,84 @@ +using CompScienceMeshes, BEAST + +using LinearAlgebra +#using Profile +#using TimerOutputs +using StaticArrays +using Plots + +function tau(x::SVector{U,T}) where {U,T} + 1.0-1.0/5.0 +end + +ntrc = X->ntrace(X,y) + +T = tetmeshsphere(1.0,0.2) +X = nedelecd3d(T) +y = boundary(T) +@show numfunctions(X) + +ϵ, μ, ω = 1.0, 1.0, 1.0; κ, η = ω * √(ϵ*μ), √(μ/ϵ) +ϵ_r =5.0 +χ = tau +K, I, B = VIE.singlelayer(wavenumber=κ, tau=χ), Identity(), VIE.boundary(wavenumber=κ, tau=χ) +E = VIE.planewave(direction=ẑ, polarization=x̂, wavenumber=κ) +H = -1/(im*κ*η)*curl(E) + +@hilbertspace j +@hilbertspace k +α = 1.0/ϵ_r +eq = @varform α*I[j,k]-K[j,k]-B[ntrc(j),k] == E[j] + +dbvie = @discretise eq j∈X k∈X +u_m = solve(dbvie) +#= +#postprocessing +Φ, Θ = [0.0], range(0,stop=2π,length=100) +pts = [point(cos(ϕ)*sin(θ), sin(ϕ)*sin(θ), cos(θ)) for θ in Θ for ϕ in Φ] +ffd = potential(VIE.farfield(wavenumber=κ, tau=χ), pts, u, X) +ff = im*κ*ffd + +using Plots + +#Farfield +plot(xlabel="theta") +plot!(Θ, norm.(ff), label="far field", title="D-VIE") +=# + +#NearField +Z = range(-1,1,length=100) +Y = range(-1,1,length=100) +nfpoints = [point(0,y,z) for z in Z, y in Y] + +Enear = BEAST.grideval(nfpoints,α.*u_m,X) +Enear = reshape(Enear,100,100) + +contour(real.(getindex.(Enear,1))) +heatmap(Z, Y, real.(getindex.(Enear,1))) + + +plot!(Y[2:99],real.(getindex.(Enear[2:99,50],1)),label="D-VIE (simplex)", linestyle=:dash, linecolor=:darkorange4) +#plot!(Y[2:99],real.(getindex.(Enear_simplex[2:99,50],1)),label="D-VIE (simplex)", linestyle=:solid, linecolor=:darkorange3) + +#= +import Cairo +using DataFrames, Gadfly, RDatasets +D = dataset("datasets","HairEyeColor") +palette = ["skyblue","skyblue3"] + + +D = DataFrame( + Type=["D-VIE \n (1,190 Tets.)","D-VIE \n (1,190 Tets.)","D-VIE \n (1,190 Tets.)","D-VIE \n (1,190 Tets.)","E-VIE \n (1,190 Tets.)","E-VIE \n (1,190 Tets.)","E-VIE \n (1,190 Tets.)","E-VIE \n (1,190 Tets.)","D-VIE \n (2,706 Tets.)","D-VIE \n (2,706 Tets.)","D-VIE \n (2,706 Tets.)","D-VIE \n (2,706 Tets.)","E-VIE \n (2,706 Tets.)","E-VIE \n (2,706 Tets.)","E-VIE \n (2,706 Tets.)","E-VIE \n (2,706 Tets.)"], + Quad=["Simplex","Simplex","Classic","Classic","Simplex","Simplex","Classic","Classic","Simplex","Simplex","Classic","Classic","Simplex","Simplex","Classic","Classic"], + Sing=["Regular","Singular","Regular","Singular","Regular","Singular","Regular","Singular","Regular","Singular","Regular","Singular","Regular","Singular","Regular","Singular"], + Time=[24,4.77,23.1,27.9,41.9,8.49,41.5,45.7,125,13.1,125,68.6,222,20.5,222,115], +) + +p = Gadfly.plot(D, x=:Quad, y=:Time, color=:Sing, xgroup=:Type, + Geom.subplot_grid(Geom.bar(position=:stack)), + Scale.color_discrete_manual(palette...), + Guide.xlabel(""),Guide.ylabel("Assembly time [s]"), Guide.colorkey("Interaction"),Theme(major_label_font="Times",minor_label_font="Times",key_title_font="Times", key_label_font="Times",background_color=color("white"))) + +# draw(PNG("haireyecolor.png", 6.6inch, 4inch), p) +draw(PNG(6.6inch, 4inch), p) +=# \ No newline at end of file diff --git a/examples/dvsie.jl b/examples/dvsie.jl new file mode 100644 index 00000000..e6d80c09 --- /dev/null +++ b/examples/dvsie.jl @@ -0,0 +1,198 @@ +using CompScienceMeshes, BEAST +using LinearAlgebra +using Profile +#using LiftedMaps +#using BlockArrays +using StaticArrays + + + +ntrc = X->ntrace(X,Γ) + +T = tetmeshsphere(1.0,0.3) +X = nedelecd3d(T) +Γ = boundary(T) +Y = raviartthomas(Γ) + +@show numfunctions(X) +@show numfunctions(Y) + +Z = BEAST.buffachristiansen2(Γ) + + +κ, η = 1.0, 1.0 +κ′, η′ = √2κ, η/√2 +ϵ_r =3 +ϵ_b =2.0 +κ_in, η_in = √6κ, η/√6 + + +#χ = x->(1.0-1.0/ϵ_r) + +function tau(x::SVector{U,T}) where {U,T} + 1.0-1.0/3.0 +end + +χ = tau + + + +N = NCross() +#Volume-Volume +L,I,B = VIE.singlelayer(wavenumber=κ′, tau=χ), Identity(), VIE.boundary(wavenumber=κ′, tau=χ) +#Volume-Surface +Lt,Bt,Kt = transpose(VSIE.singlelayer(wavenumber=κ′)), transpose(VSIE.boundary(wavenumber=κ′)), transpose(VSIE.doublelayer(wavenumber=κ′)) +#Kt = VSIE.doublelayerT(wavenumber=κ′) + +Ls,Bs,Ks = VSIE.singlelayer(wavenumber=κ′, tau=χ), VSIE.boundary(wavenumber=κ′, tau=χ), VSIE.doublelayer(wavenumber=κ′, tau=χ) +#Surface-Surface +T = Maxwell3D.singlelayer(wavenumber=κ) #Outside +T′ = Maxwell3D.singlelayer(wavenumber=κ′) #Inside +K = Maxwell3D.doublelayer(wavenumber=κ) #Outside +K′ = Maxwell3D.doublelayer(wavenumber=κ′) #Inside + +E = Maxwell3D.planewave(direction=ẑ, polarization=x̂, wavenumber=κ) +H = -1/(im*κ*η)*curl(E) + +e, h = (n × E) × n, (n × H) × n + +@hilbertspace D j m +@hilbertspace k l o +β = 1/(ϵ_r*ϵ_b) +ν = 1/ϵ_b +α, α′ = 1/η, 1/η′ +γ′ = im*η′/κ′ +ζ′ = im*κ′/(ϵ_b*η′) +δ′ = im*κ′/ϵ_b + +#= +eq = @varform (β*I[k,D] + η′*Lt[k,j]-γ′*Bt[ntrc(k),j] - Kt[k,m] + + (η*T+η′*T′)[l,j] - (K+K′)[l,m] + + (K+K′)[o,j] + (α*T+α′*T′)[o,m] == -e[l] - h[o]) + +=# + +eq = @varform (β*I[k,D]-ν*L[k,D]-ν*B[ntrc(k),D] + η′*Lt[k,j]-γ′*Bt[ntrc(k),j] - Kt[k,m] + + -δ′*Ls[l,D]-ν*Bs[l,ntrc(D)] + (η*T+η′*T′)[l,j] - (K+K′)[l,m] + + -ζ′*Ks[o,D] + (K+K′)[o,j] + (α*T+α′*T′)[o,m] == -e[l] - h[o]) + + +dvsie = @discretise eq D∈X k∈X j∈Y m∈Y l∈Y o∈Y + +u_n = solve(dvsie) + + +#= +#preconditioner +Mxx = assemble(I,X,X) +iMxx = inv(Matrix(Mxx)) + +Tzz = assemble(T,Z,Z); println("dual discretisation assembled.") +Nyz = Matrix(assemble(N,Y,Z)); println("duality form assembled.") + +iNyz = inv(Nyz); println("duality form inverted.") +NTN = iNyz' * Tzz * iNyz + +M = zeros(Int, 3) +N = zeros(Int, 3) + +M[1] = numfunctions(X) +N[1] = numfunctions(X) +M[2] = M[3] = numfunctions(Y) +N[2] = N[3] = numfunctions(Y) + +U = BlockArrays.blockedrange(M) +V = BlockArrays.blockedrange(N) + +precond = BEAST.ZeroMap{Float32}(U, V) + +z1 = LiftedMap(iMxx,Block(1),Block(1),U,V) +z2 = LiftedMap(NTN,Block(2),Block(2),U,V) +z3 = LiftedMap(NTN,Block(3),Block(3),U,V) +precond = precond +ϵ_r*z1 + z2 + z3 + +A_dvsie_precond = precond*A_dvsie + +#GMREs +import IterativeSolvers +cT = promote_type(eltype(A_dvsie), eltype(rhs)) +x = BlockedVector{cT}(undef, M) +fill!(x, 0) +x, ch = IterativeSolvers.gmres!(x, A_dvsie, Rhs, log=true, reltol=1e-4) +fill!(x, 0) +x, ch = IterativeSolvers.gmres!(x, precond*A_dvsie, precond*Rhs, log=true, reltol=1e-8) +=# + +#Post processing +Θ, Φ = range(0.0,stop=2π,length=100), 0.0 +ffpoints = [point(cos(ϕ)*sin(θ), sin(ϕ)*sin(θ), cos(θ)) for θ in Θ for ϕ in Φ] + +# Don't forget the far field comprises two contributions +ffm = potential(MWFarField3D(gamma=κ*im), ffpoints, u_n[m], Y) +ffj = potential(MWFarField3D(gamma=κ*im), ffpoints, u_n[j], Y) +ff = -η*im*κ*ffj + im*κ*cross.(ffpoints, ffm) + + +ffd = potential(VIE.farfield(wavenumber=κ, tau=χ), ffpoints, u_n[D], X) +ff2 = im*κ*ffd + +using Plots +plot(xlabel="theta") +plot!(Θ,norm.(ff),label="far field",title="DVSIE") +plot!(Θ,√6.0*norm.(ff),label="far field",title="DVSIE 2") + +import Plotly +using LinearAlgebra +fcrj, _ = facecurrents(u_n[j],Y) +fcrm, _ = facecurrents(u_n[m],Y) +vsie_j = Plotly.plot([patch(Γ, norm.(fcrj))], Plotly.Layout(title="j D-VSIE")) +vsie_m = Plotly.plot(patch(Γ, norm.(fcrm)), Plotly.Layout(title="m D-VSIE")) + +#NearField +function nearfield(um,uj,Xm,Xj,κ,η,points, + Einc=(x->point(0,0,0)), + Hinc=(x->point(0,0,0))) + + K = BEAST.MWDoubleLayerField3D(wavenumber=κ) + T = BEAST.MWSingleLayerField3D(wavenumber=κ) + + Em = potential(K, points, um, Xm) + Ej = potential(T, points, uj, Xj) + E = -Em + η*Ej + Einc.(points) + + Hm = potential(T, points, um, Xm) + Hj = potential(K, points, uj, Xj) + H = 1/η*Hm + Hj + Hinc.(points) + + return E, H +end + +Zz = range(-1,1,length=100) +Yy = range(-1,1,length=100) +nfpoints = [point(0.0,y,z) for z in Zz, y in Yy] + + +import Base.Threads: @spawn +task1 = @spawn nearfield(u_n[m],u_n[j],Y,Y,κ,η,nfpoints,E,H) +task2 = @spawn nearfield(-u_n[m],-u_n[j],Y,Y,κ_in,η_in,nfpoints) + +E_ex, H_ex = fetch(task1) +E_in, H_in = fetch(task2) + + + +Enear = BEAST.grideval(nfpoints,β.* u_n[D],X) +Enear = reshape(Enear,100,100) + +contour(real.(getindex.(Enear,1))) +heatmap(Zz, Yy, real.(getindex.(Enear,1))) + +heatmap(Zz, Yy, real.(getindex.(E_in,1))) +heatmap(Zz, Yy, real.(getindex.(E_ex,1))) + +contour(real.(getindex.(E_ex,1))) + + +Dd = range(1,100,step=1) +plot!(Yy,real.(getindex.(E_in[Dd,50],1))) +plot(collect(Yy)[2:99],real.(getindex.(Enear[2:99,50],1))) \ No newline at end of file diff --git a/examples/efie.jl b/examples/efie.jl index 5292f83f..68179b83 100644 --- a/examples/efie.jl +++ b/examples/efie.jl @@ -1,19 +1,64 @@ +# # EFIE +# +# This example demonstrates the modelling of scattering by a perfectly conducting +# sphere using the Electric Field Integral Equation (EFIE). +# + +using LinearAlgebra using CompScienceMeshes using BEAST +import PlotlyDocumenter # hide + +# We call upon GMsh to create a flat-faceted triangular mesh for the sphere. +# Subordinate to this mesh the space of lowest order Raviart-Thomas elements is +# constructed. For reproducibility of this example, a pre-constructed mesh is +# loaded from disk. Γ = readmesh(joinpath(dirname(pathof(BEAST)),"../examples/sphere2.in")) -X = raviartthomas(Γ) +X = raviartthomas(Γ); + +# The integral equation can be wrtiten down in terms of the single layer operaotr +# and a plane wave excitation. + +κ, η = 1.0, 1.0; +t = Maxwell3D.singlelayer(wavenumber=κ); +E = Maxwell3D.planewave(direction=ẑ, polarization=x̂, wavenumber=κ); +e = (n × E) × n; + +# To write down the final variational formulation, placeholders for the test and +# trial functions are required. + +@hilbertspace j; +@hilbertspace k; +efie = @discretise t[k,j]==e[k] j∈X k∈X; + +# The system is assembled and solved by GMRES. When this processs terminated, the +# solution and the convergence history are returned. + +u, ch = BEAST.gmres_ch(efie; restart=1500); +@show ch + +# With the solution in hand, various secondary quatities can be computed. Here we +# query the far field pattern, the near field, and the norm of the solution at the +# barycenters of the triangles in the mesh. + +Φ, Θ = [0.0], range(0,stop=π,length=100); +pts = [point(cos(ϕ)*sin(θ), sin(ϕ)*sin(θ), cos(θ)) for ϕ in Φ for θ in Θ]; +ffd = potential(MWFarField3D(wavenumber=κ), pts, u, X); -κ, η = 1.0, 1.0 -t = Maxwell3D.singlelayer(wavenumber=κ) -E = Maxwell3D.planewave(direction=ẑ, polarization=x̂, wavenumber=κ) -# E = -η/(im*κ)*BEAST.CurlCurlGreen(κ, ẑ, point(2,0,0)) -e = (n × E) × n +fcr, geo = facecurrents(u, X); -@hilbertspace j -@hilbertspace k -efie = @discretise t[k,j]==e[k] j∈X k∈X -u = gmres(efie) +ys = range(-2,stop=2,length=50); +zs = range(-4,stop=4,length=100); +gridpoints = [point(0,y,z) for y in ys, z in zs]; +Esc = potential(MWSingleLayerField3D(wavenumber = κ), gridpoints, u, X); +Ein = E.(gridpoints); +"" #hide -include("utils/postproc.jl") -include("utils/plotresults.jl") +# The results can be exported or visualised using e.g. Plotly +using PlotlyBase +plt = Plot(Layout(Subplots(rows=2, cols=2, specs=[Spec() Spec(rowspan=2); Spec(kind="mesh3d") missing]))) +add_trace!(plt, scatter(x=Θ, y=norm.(ffd)), row=1, col=1) +add_trace!(plt, heatmap(x=zs, y=ys, z=norm.(Esc-Ein)', colorscale="Viridis", zmin=0, zmax=2, showscale=false), row=1, col=2) +add_trace!(plt, patch(geo, norm.(fcr), caxis=(0, 2)), row=2, col=1) +PlotlyDocumenter.to_documenter(plt) #hide \ No newline at end of file diff --git a/examples/efie_bdm.jl b/examples/efie_bdm.jl index 5b83042b..b1ae2c41 100644 --- a/examples/efie_bdm.jl +++ b/examples/efie_bdm.jl @@ -1,7 +1,8 @@ using CompScienceMeshes using BEAST -Γ = readmesh(joinpath(@__DIR__,"sphere2.in")) +# Γ = readmesh(joinpath(@__DIR__,"sphere2.in")) +Γ = meshsphere(radius=1.0, h=0.1) X = brezzidouglasmarini(Γ) κ = 1.0 diff --git a/examples/efie_convergence.jl b/examples/efie_convergence.jl new file mode 100644 index 00000000..ff22ece0 --- /dev/null +++ b/examples/efie_convergence.jl @@ -0,0 +1,55 @@ +using CompScienceMeshes +using BEAST + +using BakerStreet +using DataFrames + +function payload(;h) + # Γ = readmesh(joinpath(dirname(pathof(BEAST)),"../examples/sphere2.in")) + @show h + Γ = meshsphere(radius=1.0, h=h) + # @show length(Γ) + X = raviartthomas(Γ) + + κ, η = 1.0, 1.0 + t = Maxwell3D.singlelayer(wavenumber=κ) + s = -Maxwell3D.singlelayer(gamma=κ) + E = Maxwell3D.planewave(direction=ẑ, polarization=x̂, wavenumber=κ) + e = (n × E) × n + + @hilbertspace j + @hilbertspace k + + A = assemble(t[k,j], X, X) + S = assemble(s[k,j], X, X) + b = assemble(e[k], X) + + Ai = BEAST.GMRESSolver(A; restart=1500, abstol=1e-5, maxiter=10_000) + u = Ai * b + u_Snorm = real(sqrt(dot(u, S*u))) + return (;u, X, u_Snorm) +end + +α = 0.8 +h = collect(0.4 * α.^(0:10)) +# h = collect(logrange(0.4, 0.05, length=8)) +@runsims payload h + +df = @collect_results + +using LinearAlgebra +using Plots +plot(df.h, real.(df.u_Snorm), marker=:.) + +# α = df.h[1] / df.h[2] +W1 = reverse(df.u_Snorm) +W2 = Iterators.drop(W1,1) +W3 = Iterators.drop(W1,2) +p = [log((w2-w1)/(w3-w2)) / log(1/α) for (w1,w2,w3) in zip(W1,W2,W3)] + +# efie = @discretise t[k,j]==e[k] j∈X k∈X +# u, ch = BEAST.gmres_ch(efie; restart=1500) + +# include("utils/postproc.jl") +# include("utils/plotresults.jl") + diff --git a/examples/efie_convergence_cuboid.jl b/examples/efie_convergence_cuboid.jl new file mode 100644 index 00000000..60d6deb5 --- /dev/null +++ b/examples/efie_convergence_cuboid.jl @@ -0,0 +1,79 @@ +using CompScienceMeshes +using BEAST + +using Makeitso +using BakerStreet +using DataFrames + +fn = splitext(splitdir(@__FILE__)[end])[1] + +@target solutions () -> begin + function payload(;h) + d = 1.0 + Γ = meshcuboid(d, d, d/2, h) + X = raviartthomas(Γ) + + κ, η = 1.0, 1.0 + t = Maxwell3D.singlelayer(wavenumber=κ) + s = -Maxwell3D.singlelayer(gamma=κ) + E = Maxwell3D.planewave(direction=ẑ, polarization=x̂, wavenumber=κ) + e = (n × E) × n + + @hilbertspace j + @hilbertspace k + + A = assemble(t[k,j], X, X) + S = assemble(s[k,j], X, X) + b = assemble(e[k], X) + + Ai = BEAST.GMRESSolver(A; restart=1500, abstol=1e-8, maxiter=10_000) + u = Ai * b + u_Snorm = real(sqrt(dot(u, S*u))) + return (;u, X, u_Snorm) + end + + α = 0.8 + h = collect(0.4 * α.^(0:15)) + runsims(payload, "$(fn)/solutions"; h) +end + +@target refnormsquared (solutions) -> begin + (;u, X) = solutions[1,:] + s = -Maxwell3D.singlelayer(gamma=1.0) + S = assemble(s, X, X) + return real(dot(u, S*u)) +end + +@target errors (solutions, refnormsquared) -> begin + + uref = solutions.u[1] + Xref = solutions.X[1] + + s = -Maxwell3D.singlelayer(gamma=1.0) + qstrat12 = BEAST.NonConformingIntegralOpQStrat(BEAST.DoubleNumSauterQstrat(3, 4, 6, 6, 6, 6)) + + function payload(;h) + r = getrow(solutions; h=h) + @show r + (;h, u, X) = r + Γ = geometry(X) + + S12 = assemble(s, Xref, X; quadstrat=[qstrat12]) + S22 = assemble(s, X, X) + err = sqrt(refnormsquared - 2*real(dot(uref, S12*u)) + real(dot(u, S22*u))) + return (;err) + end + + runsims(payload, "$(fn)/errors", h=solutions.h[end:-1:2]) +end + + +@make errors + +using Plots +plot(log10.(errors.h), log10.(errors.err), marker=:.) +plot!(log10.(errors.h), -0.05 .+ 0.75*log10.(errors.h), label="p = 0.75") + + + + diff --git a/examples/efie_convergence_screen.jl b/examples/efie_convergence_screen.jl new file mode 100644 index 00000000..9d1923b8 --- /dev/null +++ b/examples/efie_convergence_screen.jl @@ -0,0 +1,100 @@ +using CompScienceMeshes +using BEAST + +using Makeitso +using BakerStreet +using DataFrames + +fn = splitext(splitdir(@__FILE__)[end])[1] + +@target solutions () -> begin + function payload(;h) + d = 1.0 + # Γ = meshsphere(radius=d, h=h) + Γ = meshrectangle(d, d, h, 3) + # Γ = meshcuboid(d, d, d/2, h) + X = raviartthomas(Γ) + + κ, η = 1.0, 1.0 + t = Maxwell3D.singlelayer(wavenumber=κ) + s = -Maxwell3D.singlelayer(gamma=κ) + E = Maxwell3D.planewave(direction=ẑ, polarization=x̂, wavenumber=κ) + e = (n × E) × n + + @hilbertspace j + @hilbertspace k + + A = assemble(t[k,j], X, X) + S = assemble(s[k,j], X, X) + b = assemble(e[k], X) + + Ai = BEAST.GMRESSolver(A; restart=1500, abstol=1e-8, maxiter=10_000) + u = Ai * b + u_Snorm = real(sqrt(dot(u, S*u))) + return (;u, X, u_Snorm) + end + + α = 0.8 + h = collect(0.4 * α.^(0:15)) + runsims(payload, "$(fn)/solutions"; h) + return true + # return loadsims("$(fn)/solutions") +end + +@target errors (solutions) -> begin + df = loadsims("$(fn)/solutions") + + uref = df.u[1] + Xref = df.X[1] + + s = -Maxwell3D.singlelayer(gamma=1.0) + qstrat12 = BEAST.NonConformingIntegralOpQStrat(BEAST.DoubleNumSauterQstrat(3, 4, 6, 6, 6, 6)) + + S11 = assemble(s, Xref, Xref) + term11 = real(dot(uref, S11*uref)) + + function payload(;h) + r = getrow(df; h=h) + (;h, u, X) = r + + S12 = assemble(s, Xref, X; quadstrat=[qstrat12]) + S22 = assemble(s, X, X) + err = sqrt(term11 - 2*real(dot(uref, S12*u)) + real(dot(u, S22*u))) + return (;err) + end + + runsims(payload, "$(fn)/errors", h=df.h[end:-1:2]) + return true +end + + +@target weak_errors (solutions) -> begin + df = loadsims("sphere/solutions") + + α = df.h[1] / df.h[2] + W1 = reverse(df.u_Snorm) + W2 = Iterators.drop(W1,1) + W3 = Iterators.drop(W1,2) + p = [log(abs((w2-w1)/(w3-w2))) / log(1/α) for (w1,w2,w3) in zip(W1,W2,W3)] +end + + +@make errors + +df = loadsims("$fn/solutions") +hmin, i = findmin(df.h) +(;u, X) = df[i,:] + +using LinearAlgebra +import Plotly +using Plots +fcr, geo = facecurrents(u, X) +Plotly.plot(patch(geo, log10.(norm.(fcr)))) + +df2 = loadsims("$fn/errors") +plot(log10.(df2.h), log10.(df2.err), marker=:.) +plot!(log10.(df2.h), -0.1 .+ 0.5*log10.(df2.h), label="p = 0.5") +# plot!(log10.(df2.h), 0.1 .+ 0.7*log10.(df2.h), label="p = 0.625") +plot!(log10.(df2.h), 0.2 .+ 0.75*log10.(df2.h), label="p = 0.75") +# plot!(log10.(df2.h), 0.625 .+ 1.0*log10.(df2.h), label="p = 1") +# plot!(log10.(df2.h), 1.5 .+ 1.5*log10.(df2.h), label="p = 1.5") \ No newline at end of file diff --git a/examples/efie_convergence_sphere.jl b/examples/efie_convergence_sphere.jl new file mode 100644 index 00000000..8d516ee4 --- /dev/null +++ b/examples/efie_convergence_sphere.jl @@ -0,0 +1,82 @@ +using CompScienceMeshes +using BEAST + +using Makeitso +using BakerStreet +using DataFrames + +fn = splitext(splitdir(@__FILE__)[end])[1] + + +@target solutions () -> begin + function payload(;h) + d = 1.0 + Γ = meshsphere(radius=d, h=h) + X = raviartthomas(Γ) + + κ, η = 1.0, 1.0 + t = Maxwell3D.singlelayer(wavenumber=κ) + s = -Maxwell3D.singlelayer(gamma=κ) + E = Maxwell3D.planewave(direction=ẑ, polarization=x̂, wavenumber=κ) + e = (n × E) × n + + @hilbertspace j + @hilbertspace k + + A = assemble(t[k,j], X, X) + S = assemble(s[k,j], X, X) + b = assemble(e[k], X) + + Ai = BEAST.GMRESSolver(A; restart=1500, abstol=1e-8, maxiter=10_000) + u = Ai * b + u_Snorm = real(sqrt(dot(u, S*u))) + return (;u, X, u_Snorm) + end + + α = 0.8 + h = collect(0.4 * α.^(0:15)) + return runsims(payload, "$fn/solutions"; h) +end + + +@target refnormsquared (solutions) -> begin + (;u, X) = solutions[1,:] + s = -Maxwell3D.singlelayer(gamma=1.0) + S = assemble(s, X, X) + return real(dot(u, S*u)) +end + + +@target errors (solutions, refnormsquared) -> begin + + uref = solutions.u[1] + Xref = solutions.X[1] + + s = -Maxwell3D.singlelayer(gamma=1.0) + qstrat12 = BEAST.NonConformingIntegralOpQStrat(BEAST.DoubleNumSauterQstrat(3, 4, 6, 6, 6, 6)) + + function payload(;h) + r = getrow(solutions; h=h) + (;h, u, X) = r + Γ = geometry(X) + + S12 = assemble(s, Xref, X; quadstrat=[qstrat12]) + S22 = assemble(s, X, X) + err = sqrt(refnormsquared - 2*real(dot(uref, S12*u)) + real(dot(u, S22*u))) + return (;err) + end + + return runsims(payload, "$(fn)/errors", h=solutions.h[end:-1:2]) +end + + +@make errors + +using Plots +plot(log10.(errors.h), log10.(errors.err), marker=:.) +plot!(log10.(errors.h), -0.05 .+ 0.75*log10.(errors.h), label="p = 0.75") +plot!(log10.(errors.h), 1.0 .+ 1.50*log10.(errors.h), label="p = 1.50") + + + + diff --git a/examples/efie_graded.jl b/examples/efie_graded.jl new file mode 100644 index 00000000..da2988a5 --- /dev/null +++ b/examples/efie_graded.jl @@ -0,0 +1,44 @@ +using CompScienceMeshes +using BEAST + +# Γ = readmesh(joinpath(dirname(pathof(BEAST)),"../examples/sphere2.in")) +# Γ = meshsphere(radius=1.0, h=0.1) +Γ = CompScienceMeshes.meshrectangle_unit_tri_2graded(0.25, 3) +@show length(Γ) +X = raviartthomas(Γ) + +κ, η = 4.0, 1.0 +t = Maxwell3D.singlelayer(wavenumber=κ) +E = Maxwell3D.planewave(direction=ŷ, polarization=x̂, wavenumber=κ) +e = (n × E) × n + +@hilbertspace j +@hilbertspace k + +qs = BEAST.DoubleNumWiltonSauterQStrat{Int64, Int64}(11, 11, 11, 12, 8, 8, 8, 8) +qsl = BEAST.SingleNumQStrat(13) + +U = BEAST.DirectProductSpace([X]) +A = assemble(t[k,j], U, U; quadstrat=qs) +b = assemble(e[k], U; quadstrat=qsl) + +u = AbstractMatrix(A) \ b +fcr, geo = facecurrents(u, X) + +import Plotly +using LinearAlgebra +Plotly.plot(patch(geo, real.(getindex.(fcr,1)), caxis=(-10,10))) +Plotly.plot(patch(geo, real.(getindex.(fcr,2)), caxis=(-10,10))) +Plotly.plot(patch(geo, norm.(fcr), caxis=(0,10))) + +# efie = @discretise t[k,j]==e[k] j∈X k∈X +# u, ch = BEAST.gmres_ch(efie; restart=1500) + +# include("utils/postproc.jl") +# include("utils/plotresults.jl") +Id = Identity() +Nuu = assemble(Id[k,j], U, U) +v = AbstractMatrix(Nuu) \ b +fcr, geo = facecurrents(v, X) + +Plotly.plot(patch(geo, real(getindex.(fcr,1)))) \ No newline at end of file diff --git a/examples/efie_gwp2.jl b/examples/efie_gwp2.jl new file mode 100644 index 00000000..ccc911d6 --- /dev/null +++ b/examples/efie_gwp2.jl @@ -0,0 +1,31 @@ +using CompScienceMeshes +using BEAST + +# Γ = readmesh(joinpath(dirname(pathof(BEAST)),"../examples/sphere2.in")) +Γ = meshsphere(radius=1.0, h=0.2) +@show length(Γ) +# X = raviartthomas(Γ) +X = BEAST.gwpdiv(Γ; order=2) + +κ, η = 3.0, 1.0 +t = Maxwell3D.singlelayer(wavenumber=κ) +E = Maxwell3D.planewave(direction=ẑ, polarization=x̂, wavenumber=κ) +e = (n × E) × n + +BEAST.@defaultquadstrat (t,X,X) BEAST.DoubleNumSauterQstrat(2,3,4,4,4,4) +BEAST.@defaultquadstrat (e,X) BEAST.SingleNumQStrat(12) + +@hilbertspace j +@hilbertspace k +# efie = @discretise t[k,j]==e[k] j∈X k∈X +# u, ch = BEAST.gmres_ch(efie; restart=1500) + +A = assemble(t[k,j], X, X) +b = assemble(e[k], X) +Ai = BEAST.GMRESSolver(A; reltol=1e-6, restart=1500) +u = Ai * b + +include("utils/postproc.jl") +include("utils/plotresults.jl") + +# Id = BEAST.Identity() \ No newline at end of file diff --git a/examples/efie_quad.jl b/examples/efie_quad.jl new file mode 100644 index 00000000..2e9a4d8d --- /dev/null +++ b/examples/efie_quad.jl @@ -0,0 +1,47 @@ +using CompScienceMeshes +using BEAST +using LinearAlgebra +import Plots + +κ, η = 1.0, 1.0 +t = Maxwell3D.singlelayer(wavenumber=κ) +E = Maxwell3D.planewave(direction=ẑ, polarization=x̂, wavenumber=κ) +e = (n × E) × n + +Φ, Θ = [0.0], range(0,stop=π,length=100) +pts = [point(cos(ϕ)*sin(θ), sin(ϕ)*sin(θ), cos(θ)) for ϕ in Φ for θ in Θ] +xs, y, zs = range(-4,stop=6,length=200), 1.0, range(-5,stop=5,length=200) +gridpoints = [point(x,y,z) for z in zs, x in xs] + +Γ = CompScienceMeshes.meshrectangle(2.0, 2.0, 0.025; structured=:quadrilateral) +X = raviartthomas(Γ) + +@hilbertspace j +@hilbertspace k +efie = @discretise t[k,j]==e[k] j∈X k∈X +u, ch = BEAST.gmres_ch(efie; restart=1500) + +ffd = potential(MWFarField3D(wavenumber=κ), pts, u, X) +nfd = potential(MWSingleLayerField3D(wavenumber = κ), gridpoints, u, X) +nfd .-= E.(gridpoints) + +p1 = Plots.scatter(Θ, real.(norm.(ffd))) +p2 = Plots.heatmap(clamp.(abs.(getindex.(nfd,1)), 0, 2.0), clims=(0,2), colormap=:viridis) + +Γ = CompScienceMeshes.meshrectangle(2.0, 2.0, 0.025) +X = raviartthomas(Γ) + +@hilbertspace j +@hilbertspace k +efie = @discretise t[k,j]==e[k] j∈X k∈X +u, ch = BEAST.gmres_ch(efie; restart=1500) + +ffd = potential(MWFarField3D(wavenumber=κ), pts, u, X) +nfd = potential(MWSingleLayerField3D(wavenumber = κ), gridpoints, u, X) +nfd .-= E.(gridpoints) + +p3 = Plots.scatter(Θ, real.(norm.(ffd))) +p4 = Plots.heatmap(clamp.(abs.(getindex.(nfd,1)), 0, 2.0), clims=(0,2), colormap=:viridis) + +Plots.plot(p2,p4,layout=(1,2)) + diff --git a/examples/efie_rt2.jl b/examples/efie_rt2.jl new file mode 100644 index 00000000..8e0a8c61 --- /dev/null +++ b/examples/efie_rt2.jl @@ -0,0 +1,21 @@ +using CompScienceMeshes +using BEAST + +#Γ = readmesh(joinpath(dirname(pathof(BEAST)),"../examples/sphere2.in")) +Γ = meshsphere(radius=1.0, h=0.2) +# Γ = CompScienceMeshes.meshmobius(h=0.035) +X = BEAST.raviartthomas2(Γ) + +κ, η = 1.0, 1.0 +t = Maxwell3D.singlelayer(wavenumber=κ) +E = Maxwell3D.planewave(direction=ẑ, polarization=x̂, wavenumber=κ) +# E = -η/(im*κ)*BEAST.CurlCurlGreen(κ, ẑ, point(2,0,0)) +e = (n × E) × n + +@hilbertspace j +@hilbertspace k +efie = @discretise t[k,j]==e[k] j∈X k∈X +u, ch = BEAST.gmres_ch(efie; restart=1500) + +include("utils/postproc.jl") +include("utils/plotresults.jl") diff --git a/examples/evie.jl b/examples/evie.jl new file mode 100644 index 00000000..358512bb --- /dev/null +++ b/examples/evie.jl @@ -0,0 +1,60 @@ +using CompScienceMeshes, BEAST +using LinearAlgebra +using Profile +using StaticArrays + + + +ttrc = X->ttrace(X,y) + +T= tetmeshsphere(1.0,0.2) +X = nedelecc3d(T) +y = boundary(T) +@show numfunctions(X) + +ϵ, μ, ω = 1.0, 1.0, 1.0; κ, η = ω * √(ϵ*μ), √(μ/ϵ) +ϵ_r = 5.0 + +function tau(x::SVector{U,T}) where {U,T} + 5.0 -1.0 +end + +χ = tau +K, I, B = VIE.singlelayer2(wavenumber=κ, tau=χ), Identity(), VIE.boundary2(wavenumber=κ, tau=χ) +E = VIE.planewave(direction=ẑ, polarization=x̂, wavenumber=κ) +H = -1/(im*κ*η)*curl(E) + +@hilbertspace j +@hilbertspace k +α = ϵ_r +eq = @varform α*I[j,k]-K[j,k]+B[ttrc(j),k] == E[j] + +evie = @discretise eq j∈X k∈X +u_n = solve(evie) + +#postprocessing +#Φ, Θ = [0.0], range(0,stop=2π,length=100) +#pts = [point(cos(ϕ)*sin(θ), sin(ϕ)*sin(θ), cos(θ)) for θ in Θ for ϕ in Φ ] +#ffe = potential(VIE.farfield(wavenumber=κ, tau=χ), pts, u_n, X) +#ff = ffe + +#Farfield +#plot(xlabel="theta") +#plot!(Θ, norm.(ff), label="far field", title="E-VIE") + +using Plots + + +#Nearfield +Z = range(-1,1,length=100) +Y = range(-1,1,length=100) +nfpoints = [point(0,y,z) for z in Z, y in Y] + +Enear2 = BEAST.grideval(nfpoints,u_n,X) +Enear2 = reshape(Enear2,100,100) + +contour(real.(getindex.(Enear2,1))) +heatmap(Z, Y, real.(getindex.(Enear2,1))) + +plot!(Y[2:99],real.(getindex.(Enear2[2:99,50],1)),label="E-VIE (simplex)", linestyle=:dash, linecolor=:darkolivegreen4) + diff --git a/examples/ex_dual1forms.jl b/examples/ex_dual1forms.jl new file mode 100644 index 00000000..9b12e48d --- /dev/null +++ b/examples/ex_dual1forms.jl @@ -0,0 +1,207 @@ +using CompScienceMeshes +using BEAST +using LinearAlgebra + +const Id = BEAST.Identity() + +function add!(fn, x, space) + for (m,bf) in enumerate(space.fns) + for sh in bf + BEAST.add!(fn, sh.cellid, sh.refid, sh.coeff * x[m]) + end + end +end + + +function compress!(space) + T = scalartype(space) + for (i,fn) in pairs(space.fns) + shapes = Dict{Tuple{Int,Int},T}() + for shape in fn + v = get(shapes, (shape.cellid, shape.refid), zero(T)) + shapes[(shape.cellid, shape.refid)] = v + shape.coeff + # set!(shapes, (shape.cellid, shape.refid), v + shape.coeff) + end + space.fns[i] = [BEAST.Shape(k[1], k[2], v) for (k,v) in shapes] + end +end + +import Plotly +function showfn(space,i) + geo = geometry(space) + T = coordtype(geo) + X = Dict{Int,T}() + Y = Dict{Int,T}() + Z = Dict{Int,T}() + U = Dict{Int,T}() + V = Dict{Int,T}() + W = Dict{Int,T}() + for sh in space.fns[i] + chrt = chart(geo, cells(geo)[sh.cellid]) + nbd = center(chrt) + vals = refspace(space)(nbd) + x,y,z = cartesian(nbd) + # @show vals[sh.refid].value + u,v,w = vals[sh.refid].value + # @show x, y, z + # @show u, v, w + X[sh.cellid] = x + Y[sh.cellid] = y + Z[sh.cellid] = z + U[sh.cellid] = get(U,sh.cellid,zero(T)) + sh.coeff * u + V[sh.cellid] = get(V,sh.cellid,zero(T)) + sh.coeff * v + W[sh.cellid] = get(W,sh.cellid,zero(T)) + sh.coeff * w + end + X = collect(values(X)) + Y = collect(values(Y)) + Z = collect(values(Z)) + U = collect(values(U)) + V = collect(values(V)) + W = collect(values(W)) + Plotly.cone(x=X,y=Y,z=Z,u=U,v=V,w=W) +end + +Tetrs = CompScienceMeshes.tetmeshsphere(1.0, 0.45) +tetrs = barycentric_refinement(Tetrs) + +bnd_Tetrs = boundary(Tetrs) +bnd_tetrs = boundary(tetrs) +# srt_bnd_Tetrs = sort.(bnd_Tetrs) +# srt_bnd_tetrs = sort.(bnd_tetrs) + +Faces = submesh(!in(bnd_Tetrs), skeleton(Tetrs,2)) +# Faces = submesh(Face -> !(sort(Face) in srt_bnd_Tetrs), skeleton(Tetrs,2)) +@show length(Faces) + +F = 396 +Face = cells(Faces)[F] +pos = cartesian(CompScienceMeshes.center(chart(Faces, F))) + +supp1 = submesh((m,tet) -> Face[1] in CompScienceMeshes.indices(m,tet), tetrs.mesh) +supp2 = submesh((m,tet) -> Face[2] in CompScienceMeshes.indices(m,tet), tetrs.mesh) +supp3 = submesh((m,tet) -> Face[3] in CompScienceMeshes.indices(m,tet), tetrs.mesh) + +supp = CompScienceMeshes.union(supp1, supp2) +supp = CompScienceMeshes.union(supp, supp3) +# PlotlyJS.plot(patch(boundary(supp))) + +port = skeleton(supp1, 1) +port = submesh(in(skeleton(supp2,1)), port) +port = submesh(in(skeleton(supp3,1)), port) +# port = submesh(edge -> sort(edge) in sort.(skeleton(supp2,1)), port) +# port = submesh(edge -> sort(edge) in sort.(skeleton(supp3,1)), port) +@show length(port) +@assert 1 ≤ length(port) ≤ 2 + +dir = submesh(in(bnd_tetrs), boundary(supp)) +# dir = submesh(face -> sort(face) in srt_bnd_tetrs, boundary(supp)) +rim = boundary(dir) +@show length(dir) + +# srt_port = sort.(port) +# srt_dir_edges = sort.(skeleton(dir,1)) +# srt_bnd_edges = sort.(skeleton(boundary(supp),1)) +# srt_rim = sort.(rim) + +in_port = in(port) +in_dir_edges = in(skeleton(dir,1)) +in_bnd_edges = in(skeleton(boundary(supp),1)) +in_rim = in(rim) + +int_edges = submesh(skeleton(supp,1)) do m,edge + in_port(m,edge) && return false + in_rim(m,edge) && return false + in_dir_edges(m,edge) && return true + in_bnd_edges(m,edge) && return false + return true +end + +# int_edges = submesh(skeleton(supp,1)) do edge +# sort(edge) in srt_port && return false +# sort(edge) in srt_rim && return false +# sort(edge) in srt_dir_edges && return true +# sort(edge) in srt_bnd_edges && return false +# return true +# end +@show length(int_edges) +@assert length(skeleton(supp,0)) - length(skeleton(supp,1)) + + length(skeleton(supp,2)) - length(supp) == 1 + +Nd_prt = BEAST.nedelecc3d(supp, port) +Nd_int = BEAST.nedelecc3d(supp, int_edges) +@show numfunctions(Nd_int) + +x0 = ones(length(port)) / length(port) +for (i,edge) in enumerate(port) + tgt = tangents(CompScienceMeshes.center(chart(port,edge)),1) + if dot(normal(chart(Faces,F)), tgt) < 0 + x0[i] *= -1 + end +end + +curl_Nd_int = curl(Nd_int) +A = Matrix(assemble(Id, curl_Nd_int, curl_Nd_int)) +N = nullspace(A) +@show size(N,2) +a = -assemble(Id, curl_Nd_int, curl(Nd_prt)) * x0 +x1 = pinv(A) * a +# x1 = A \ a +@show norm(N'*a) +@show norm(A*x1-a) + +# srt_bnd_verts = sort.(skeleton(boundary(supp),0)) +# srt_prt_verts = sort.(skeleton(port,0)) +# int_verts = submesh(skeleton(supp,0)) do vert +# sort(vert) in srt_bnd_verts && return false +# sort(vert) in srt_prt_verts && return false +# return true +# end + +in_bnd_verts = in(skeleton(boundary(supp),0)) +in_prt_verts = in(skeleton(port,0)) +int_verts = submesh(skeleton(supp,0)) do m,vert + in_bnd_verts(m,vert) && return false + in_prt_verts(m,vert) && return false + return true +end +@show length(int_verts) + +L0_int = lagrangec0d1(supp, int_verts) +@show numfunctions(L0_int) +grad_L0_int = BEAST.gradient(L0_int) +@assert numfunctions(grad_L0_int) == numfunctions(L0_int) +B = assemble(Id, grad_L0_int, Nd_int) + +freeze, store = BEAST.allocatestorage(Id, grad_L0_int, Nd_int, + Val{:bandedstorage}, BEAST.LongDelays{:ignore}) +BEAST.assemble_local_mixed!(Id, grad_L0_int, Nd_int, store) +B2 = freeze() +@assert isapprox(B, B2, atol=1e-8) +@show rank(B2) + +b = -assemble(Id, grad_L0_int, Nd_prt) * x0 +p = (B*N) \ (b-B*x1) +x1 = x1 + N*p + +@show norm(A*x1-a) +@show norm(B*x1-b) + +fn = BEAST.Shape{Float64}[] +add!(fn, x0, Nd_prt) +add!(fn, x1, Nd_int) + +Y1 = BEAST.NDLCCBasis(supp, [fn],[pos]) +curlY1 = curl(Y1); compress!(curl(Y1)); + +include(joinpath(@__DIR__, "utils/edge_values.jl")) +EV = edge_values(Y1,1) +check_edge_values(EV) + +# error("stop") + +Dir = boundary(Tetrs) +Y = BEAST.dual1forms(Tetrs, Faces, Dir) +curlY = curl(Y) +CC = assemble(Id, curlY, curlY) + +Plotly.plot(svdvals(Matrix(CC))) diff --git a/examples/ex_dual1forms_bis.jl b/examples/ex_dual1forms_bis.jl new file mode 100644 index 00000000..f7bac6fb --- /dev/null +++ b/examples/ex_dual1forms_bis.jl @@ -0,0 +1,252 @@ +using CompScienceMeshes +using BEAST +using LinearAlgebra + +const Id = BEAST.Identity() +const Nx = BEAST.NCross() + +# function Base.in(mesh::CompScienceMeshes.AbstractMesh) +# cells_mesh = sort.(mesh) +# function f(cell) +# sort(cell) in cells_mesh +# end +# end + +function add!(fn, x, space, idcs) + for (m,bf) in enumerate(space.fns) + for sh in bf + cellid = idcs[sh.cellid] + BEAST.add!(fn, cellid, sh.refid, sh.coeff * x[m]) + end + end +end + + +function compress!(space) + T = scalartype(space) + for (i,fn) in pairs(space.fns) + shapes = Dict{Tuple{Int,Int},T}() + for shape in fn + v = get(shapes, (shape.cellid, shape.refid), zero(T)) + shapes[(shape.cellid, shape.refid)] = v + shape.coeff + end + space.fns[i] = [BEAST.Shape(k[1], k[2], v) for (k,v) in shapes] + end +end + +import Plotly +function showfn(space,i) + geo = geometry(space) + T = coordtype(geo) + X = Dict{Int,T}() + Y = Dict{Int,T}() + Z = Dict{Int,T}() + U = Dict{Int,T}() + V = Dict{Int,T}() + W = Dict{Int,T}() + for sh in space.fns[i] + chrt = chart(geo, cells(geo)[sh.cellid]) + nbd = center(chrt) + vals = refspace(space)(nbd) + x,y,z = cartesian(nbd) + u,v,w = vals[sh.refid].value + X[sh.cellid] = x + Y[sh.cellid] = y + Z[sh.cellid] = z + U[sh.cellid] = get(U,sh.cellid,zero(T)) + sh.coeff * u + V[sh.cellid] = get(V,sh.cellid,zero(T)) + sh.coeff * v + W[sh.cellid] = get(W,sh.cellid,zero(T)) + sh.coeff * w + end + X = collect(values(X)) + Y = collect(values(Y)) + Z = collect(values(Z)) + U = collect(values(U)) + V = collect(values(V)) + W = collect(values(W)) + Plotly.cone(x=X,y=Y,z=Z,u=U,v=V,w=W) +end + +function extend_edge_to_face(supp, dirichlet, x_prt, port_edges) + bnd_supp = boundary(supp) + supp_edges = CompScienceMeshes.skeleton_fast(supp, 1) + supp_nodes = CompScienceMeshes.skeleton_fast(supp, 0) + + dir_compl_edges = submesh(!in(dirichlet), bnd_supp) + dir_compl_nodes = CompScienceMeshes.skeleton_fast(dir_compl_edges, 0) + + int_edges = submesh(!in(dir_compl_edges), supp_edges) + int_nodes = submesh(!in(dir_compl_nodes), supp_nodes) + + Nd_prt = BEAST.nedelec(supp, port_edges) + Nd_int = BEAST.nedelec(supp, int_edges) + Lg_int = BEAST.lagrangec0d1(supp, int_nodes) + + curl_Nd_prt = divergence(n × Nd_prt) + curl_Nd_int = divergence(n × Nd_int) + grad_Lg_int = n × curl(Lg_int) + + A = assemble(Id, curl_Nd_int, curl_Nd_int) + B = assemble(Id, grad_Lg_int, Nd_int) + + a = -assemble(Id, curl_Nd_int, curl_Nd_prt) * x_prt + b = -assemble(Id, grad_Lg_int, Nd_prt) * x_prt + + Z = zeros(eltype(b), length(b), length(b)) + u = [A B'; B Z] \ [a;b] + x_int = u[1:end-length(b)] + + return x_int, int_edges, Nd_int +end + + + +function extend_face_to_tetr(supp, dirichlet, x_prt, port_edges) + bnd_supp = boundary(supp) + supp_edges = CompScienceMeshes.skeleton_fast(supp, 1) + supp_nodes = CompScienceMeshes.skeleton_fast(supp, 0) + + dir_compl_faces = submesh(!in(dirichlet), bnd_supp) + dir_compl_edges = CompScienceMeshes.skeleton_fast(dir_compl_faces, 1) + dir_compl_nodes = CompScienceMeshes.skeleton_fast(dir_compl_faces, 0) + + int_edges = submesh(!in(dir_compl_edges), supp_edges) + int_nodes = submesh(!in(dir_compl_nodes), supp_nodes) + + Nd_prt = BEAST.nedelecc3d(supp, port_edges) + Nd_int = BEAST.nedelecc3d(supp, int_edges) + Lg_int = BEAST.lagrangec0d1(supp, int_nodes) + + curl_Nd_prt = curl(Nd_prt) + curl_Nd_int = curl(Nd_int) + grad_Lg_int = BEAST.gradient(Lg_int) + + A = assemble(Id, curl_Nd_int, curl_Nd_int) + B = assemble(Id, grad_Lg_int, Nd_int) + + a = -assemble(Id, curl_Nd_int, curl_Nd_prt) * x_prt + b = -assemble(Id, grad_Lg_int, Nd_prt) * x_prt + + Z = zeros(eltype(b), length(b), length(b)) + u = [A B'; B Z] \ [a;b] + x_int = u[1:end-length(b)] + + return x_int, int_edges, Nd_int +end + + +Tetrs = CompScienceMeshes.tetmeshsphere(1.0, 0.45) +tetrs = barycentric_refinement(Tetrs) + +v2t, v2n = CompScienceMeshes.vertextocellmap(tetrs) + +Bnd = boundary(Tetrs) +bnd = boundary(tetrs) + +Dir = Bnd +dir = bnd + +Faces = submesh(!in(Bnd), CompScienceMeshes.skeleton_fast(Tetrs,2)) +@show length(Faces) + +F = 396 +Face = cells(Faces)[F] +pos = cartesian(CompScienceMeshes.center(chart(Faces, F))) + +idcs1 = v2t[Face[1],1:v2n[Face[1]]] +idcs2 = v2t[Face[2],1:v2n[Face[2]]] +idcs3 = v2t[Face[3],1:v2n[Face[3]]] + +supp1 = tetrs.mesh[idcs1] +supp2 = tetrs.mesh[idcs2] +supp3 = tetrs.mesh[idcs3] + +dir1_faces = submesh(in(dir), boundary(supp1)) +dir2_faces = submesh(in(dir), boundary(supp2)) +dir3_faces = submesh(in(dir), boundary(supp3)) + +supp23 = submesh(in(boundary(supp2)), boundary(supp3)) +supp31 = submesh(in(boundary(supp3)), boundary(supp1)) +supp12 = submesh(in(boundary(supp1)), boundary(supp2)) + +dir23_edges = submesh(in(boundary(dir2_faces)), boundary(dir3_faces)) +dir31_edges = submesh(in(boundary(dir3_faces)), boundary(dir1_faces)) +dir12_edges = submesh(in(boundary(dir1_faces)), boundary(dir2_faces)) +port_edges = boundary(supp23) +port_edges = submesh(in(boundary(supp31)), port_edges) +port_edges = submesh(in(boundary(supp12)), port_edges) +@assert 1 ≤ length(port_edges) ≤ 2 + +# Step 1: set port flux and extend to dual faces +x0 = ones(length(port_edges)) / length(port_edges) +for (i,edge) in enumerate(port_edges) + tgt = tangents(CompScienceMeshes.center(chart(port_edges, edge)),1) + if dot(normal(chart(Faces, F)), tgt) < 0 + x0[i] *= -1 + end +end + +x23, supp23_int_edges = extend_edge_to_face(supp23, dir23_edges, x0, port_edges) +x31, supp31_int_edges = extend_edge_to_face(supp31, dir31_edges, x0, port_edges) +x12, supp12_int_edges = extend_edge_to_face(supp12, dir12_edges, x0, port_edges) + +port1_edges = CompScienceMeshes.union(port_edges, supp31_int_edges) +port1_edges = CompScienceMeshes.union(port1_edges, supp12_int_edges) + +port2_edges = CompScienceMeshes.union(port_edges, supp12_int_edges) +port2_edges = CompScienceMeshes.union(port2_edges, supp23_int_edges) + +port3_edges = CompScienceMeshes.union(port_edges, supp23_int_edges) +port3_edges = CompScienceMeshes.union(port3_edges, supp31_int_edges) + +x1_prt = [x0; x31; x12] +x2_prt = [x0; x12; x23] +x3_prt = [x0; x23; x31] + +Nd1_prt = BEAST.nedelecc3d(supp1, port1_edges) +Nd2_prt = BEAST.nedelecc3d(supp2, port2_edges) +Nd3_prt = BEAST.nedelecc3d(supp3, port3_edges) + +x1_int, _, Nd1_int = extend_face_to_tetr(supp1, dir1_faces, x1_prt, port1_edges) +x2_int, _, Nd2_int = extend_face_to_tetr(supp2, dir2_faces, x2_prt, port2_edges) +x3_int, _, Nd3_int = extend_face_to_tetr(supp3, dir3_faces, x3_prt, port3_edges) + +# inject in the global space +fn = BEAST.Shape{Float64}[] +add!(fn, x1_prt, Nd1_prt, idcs1) +add!(fn, x1_int, Nd1_int, idcs1) + +add!(fn, x2_prt, Nd2_prt, idcs2) +add!(fn, x2_int, Nd2_int, idcs2) + +add!(fn, x3_prt, Nd3_prt, idcs3) +add!(fn, x3_int, Nd3_int, idcs3) + +pos = cartesian(CompScienceMeshes.center(chart(Faces, F))) +space = BEAST.NDLCCBasis(tetrs, [fn], [pos]) + + +tetrs, bnd, dir, v2t, v2n = BEAST.dualforms_init(Tetrs, Dir) +D1 = BEAST.dual1forms_body(Faces, tetrs, bnd, dir, v2t, v2n) +P2 = BEAST.nedelecd3d(Tetrs, Faces) +# S = BEAST.dual1forms(Tetrs, Faces[collect(396:405)], Dir) + +Edges = CompScienceMeshes.skeleton_fast(Tetrs, 1) +int_Edges = submesh(!in(skeleton(boundary(Tetrs),1)), Edges) +P1 = BEAST.nedelecc3d(Tetrs, int_Edges) + +curl_D1 = curl(D1) +curl_P1 = curl(P1) +div_P2 = divergence(P2) + +G = assemble(Id, curl_D1, curl_D1) +GP = assemble(Id, div_P2, div_P2) +M = assemble(Id, D1, P2) + +CDP = assemble(Id, D1, curl_P1) +CPD = CDP' +GDD = assemble(Id, D1, D1) + +iM = inv(Matrix(M)) +A1 = CPD * inv(Matrix(GDD)) * CDP; +A2 = assemble(Id, curl_P1, curl_P1) +A3 = CPD * iM' * inv(Matrix(GDD)) * iM * CDP; diff --git a/examples/ex_dual2forms.jl b/examples/ex_dual2forms.jl new file mode 100644 index 00000000..2f980581 --- /dev/null +++ b/examples/ex_dual2forms.jl @@ -0,0 +1,252 @@ +using CompScienceMeshes +using BEAST +using LinearAlgebra + +const Id = BEAST.Identity() + +function add!(fn, x, space) + for (m,bf) in enumerate(space.fns) + for sh in bf + BEAST.add!(fn, sh.cellid, sh.refid, sh.coeff * x[m]) + end + end +end + + +function compress!(space) + T = scalartype(space) + for (i,fn) in pairs(space.fns) + shapes = Dict{Tuple{Int,Int},T}() + for shape in fn + v = get(shapes, (shape.cellid, shape.refid), zero(T)) + shapes[(shape.cellid, shape.refid)] = v + shape.coeff + # set!(shapes, (shape.cellid, shape.refid), v + shape.coeff) + end + space.fns[i] = [BEAST.Shape(k[1], k[2], v) for (k,v) in shapes] + end +end + +Tetrs = CompScienceMeshes.tetmeshsphere(1.0, 0.15) +tetrs = barycentric_refinement(Tetrs) + +cells_Bndry = [sort(c) for c in cells(skeleton(boundary(Tetrs),1))] + +Bnd = boundary(Tetrs) +Neu = Bnd +Dir = Mesh(vertices(Bnd), CompScienceMeshes.celltype(Bnd)[]) + +bnd = boundary(tetrs) +neu = bnd +dir = Mesh(vertices(bnd), CompScienceMeshes.celltype(bnd)[]) + +# srt_Dir = sort.(Dir) +# Edges = submesh(skeleton(Tetrs,1)) do Edge +# sort(Edge) in srt_Dir && return false +# return true +# end + +Edges = submesh(!in(Dir), skeleton(Tetrs,1)) + +# Edges = skeleton(Tetrs,1) +# srt_bnd_Faces = sort.(boundary(Tetrs)) +# srt_neu = sort.(neu) +# Faces = submesh(skeleton(Tetrs,2)) do Face +# !(sort(Face) in srt_bnd_Faces) +# end + +# srt_bnd_Nodes = sort.(skeleton(boundary(Tetrs),0)) +# Nodes = submesh(skeleton(Tetrs,0)) do node +# !(sort(node) in srt_bnd_Nodes) +# end + +Nodes = submesh(!in(skeleton(boundary(Tetrs),0)), skeleton(Tetrs,0)) + +@show numcells(Edges) +# @show length(Faces) + +# pred = CompScienceMeshes.interior_tpredicate(Tetrs) +# AllFaces = skeleton(Tetrs,2) +# sm = submesh(pred, AllFaces) + +E = 1 + +Edge = cells(Edges)[E] +pos = cartesian(CompScienceMeshes.center(chart(Edges, E))) +# v = argmin(norm.(vertices(tetrs) .- Ref(pos))) +support1 = submesh((m,tetr) -> Edge[1] in CompScienceMeshes.indices(m,tetr), tetrs.mesh) +support2 = submesh((m,tetr) -> Edge[2] in CompScienceMeshes.indices(m,tetr), tetrs.mesh) +# port = submesh(face -> v in face, boundary(support1)) +# port2 = submesh(face -> v in face, boundary(support2)) +# port = submesh(face -> sort(face) in sort.(boundary(support2)), boundary(support1)) +port = submesh(in(boundary(support2)), boundary(support1)) +# for p in port +# @assert sort(p) in sort.(port2) +# end +@show length(port) + +support = CompScienceMeshes.union(support1, support2) +@assert CompScienceMeshes.isoriented(support) +@show length(support) +@assert length(skeleton(support1,2)) + length(skeleton(support2,2)) == + length(skeleton(support,2)) + length(port) +edges = skeleton(support,1) + +# cells_bnd_edges = [sort(c) for c in skeleton(boundary(support),1)] +# cells_prt_edges = [sort(c) for c in skeleton(port,1)] +# cells_dir_edges = [sort(c) for c in skeleton(boundary(tetrs),1)] + +bnd_support = boundary(support) +# cells_bnd_tetrs = sort.(boundary(tetrs)) + +# srt_dir = sort.(dir) +# dir_cap_bnd = submesh(face -> sort(face) in sort.(dir), bnd_support) +dir_cap_bnd = submesh(in(dir), bnd_support) +# cells_bnd_dir_cap_bnd = sort.(boundary(dir_cap_bnd)) + +in_bnd_dir_cap_bnd = in(boundary(dir_cap_bnd)) +in_dir_edges = in(skeleton(boundary(tetrs),1)) +in_bnd_edges = in(skeleton(boundary(support),1)) +in_prt_edges = in(skeleton(port,1)) + +int_edges = submesh(edges) do m,edge + in_bnd_dir_cap_bnd(m,edge) && return false + in_bnd_edges(m,edge) && return true + in_dir_edges(m,edge) && return false + in_prt_edges(m,edge) && return false + return true +end + +# int_edges = submesh(edges) do edge +# sort(edge) in cells_bnd_dir_cap_bnd && return false +# sort(edge) in cells_dir_edges && return true +# sort(edge) in cells_bnd_edges && return false +# sort(edge) in cells_prt_edges && return false +# return true +# end +@show length(int_edges) + +Nd_int = BEAST.nedelecc3d(support, int_edges) +Q = assemble(Id, Nd_int, Nd_int) + +freeze, store = BEAST.allocatestorage(Id, Nd_int, Nd_int, + Val{:bandedstorage}, BEAST.LongDelays{:ignore}) +BEAST.assemble_local_mixed!(Id, Nd_int, Nd_int, store) +Q2 = freeze() + +@assert Q ≈ Q2 + +Q3 = assemble(Id, curl(Nd_int), curl(Nd_int)) +rank(Q3) + +# cells_bnd_faces = [sort(c) for c in boundary(support)] +# cells_prt_faces = [sort(c) for c in port] +# srt_dir_cap_bnd = sort.(dir_cap_bnd) + +in_dir_cap_bnd = in(dir_cap_bnd) +in_bnd_faces = in(boundary(support)) +in_prt_faces = in(port) + +int_faces = submesh(skeleton(support,2)) do m,face + in_dir_cap_bnd(m,face) && return true + in_bnd_faces(m,face) && return false + in_prt_faces(m,face) && return false + return true +end + +# int_faces = submesh(skeleton(support,2)) do face +# # sort(face) in cells_bnd_tetrs && return true +# sort(face) in srt_dir_cap_bnd && return true +# sort(face) in cells_bnd_faces && return false +# sort(face) in cells_prt_faces && return false +# return true +# end +@show length(int_faces) +@assert length(int_faces) + length(boundary(support)) + length(port) == + length(skeleton(support,2)) + length(dir_cap_bnd) + +RT_int = BEAST.nedelecd3d(support, int_faces) +for (m,fn) in enumerate(RT_int.fns) + ct = count(sh -> sh.cellid <= length(support1), fn) + # @show m, ct + @assert 0 ≤ ct ≤ 2 +end +Q4 = assemble(Id, divergence(RT_int), divergence(RT_int)) +numfunctions(RT_int) - rank(Q4) + +RT_prt = BEAST.nedelecd3d(support, port) +for fn in RT_prt.fns + @assert count(sh -> sh.cellid <= length(support1), fn) == 1 + @assert count(sh -> sh.cellid > length(support1), fn) == 1 +end +Q5 = assemble(Id, divergence(RT_int), divergence(RT_int)) +x0 = ones(length(port)) / length(port) +tgt = vertices(Edges)[Edge[1]] - vertices(Edges)[Edge[2]] +for (i,face) in enumerate(port) + x0[i] *= sign(dot(normal(chart(port,face)), tgt)) + # if RT_prt.fns[i][1].coeff < 0 + # x0[i] *= -1 + # end +end +Q6 = assemble(Id, divergence(RT_int), divergence(RT_prt)) +d = -Q6 * x0 +x1 = pinv(Matrix(Q5)) * d + +# L0 = lagrangecxd0(support) +# Q7 = assemble(Id, L0, divergence(RT_int)) +# Q8 = assemble(Id, L0, L0) +# ch1 = [] +# ch2 = [] +# ch = [ch1; ch2] +# x1 = pinv(Q7) * () + +fn = BEAST.Shape{Float64}[] +add!(fn, x0, RT_prt) +add!(fn, x1, RT_int) +Y1 = BEAST.NDLCDBasis(support, [fn], [pos]) +divY1 = divergence(Y1); compress!(divY1) + + +import Plotly +function showfn(space,i) + geo = geometry(space) + T = coordtype(geo) + X = Dict{Int,T}() + Y = Dict{Int,T}() + Z = Dict{Int,T}() + U = Dict{Int,T}() + V = Dict{Int,T}() + W = Dict{Int,T}() + for sh in space.fns[i] + chrt = chart(geo, cells(geo)[sh.cellid]) + nbd = CompScienceMeshes.center(chrt) + vals = refspace(space)(nbd) + x,y,z = cartesian(nbd) + # @show vals[sh.refid].value + u,v,w = vals[sh.refid].value + # @show x, y, z + # @show u, v, w + X[sh.cellid] = x + Y[sh.cellid] = y + Z[sh.cellid] = z + U[sh.cellid] = get(U,sh.cellid,zero(T)) + sh.coeff * u + V[sh.cellid] = get(V,sh.cellid,zero(T)) + sh.coeff * v + W[sh.cellid] = get(W,sh.cellid,zero(T)) + sh.coeff * w + end + X = collect(values(X)) + Y = collect(values(Y)) + Z = collect(values(Z)) + U = collect(values(U)) + V = collect(values(V)) + W = collect(values(W)) + Plotly.cone(x=X,y=Y,z=Z,u=U,v=V,w=W) +end + +# error("stop") +Dir = Mesh(vertices(Tetrs), CompScienceMeshes.celltype(int_faces)[]) +# error() + +tetrs, bnd, dir, v2t, v2n = BEAST.dualforms_init(Tetrs, Dir) +Y = BEAST.dual2forms_body(Edges[collect(1:10)], tetrs, bnd, dir, v2t, v2n) + +# Y = BEAST.dual2forms(Tetrs, Edges, Dir) +nothing diff --git a/examples/ex_duals.jl b/examples/ex_duals.jl new file mode 100644 index 00000000..0febe78d --- /dev/null +++ b/examples/ex_duals.jl @@ -0,0 +1,135 @@ +using LinearAlgebra + +using CompScienceMeshes +using BEAST + +include("utils/showfn.jl") + +Tetrs = CompScienceMeshes.meshball(radius=1.0, h=0.26) +Faces = CompScienceMeshes.skeleton_fast(Tetrs, 2) +Edges = CompScienceMeshes.skeleton_fast(Tetrs, 1) +Nodes = CompScienceMeshes.skeleton_fast(Tetrs, 0) + +hemi = submesh(Tetrs) do m,Tetr + cartesian(CompScienceMeshes.center(chart(Tetrs, Tetr)))[3] < 0 +end + +bnd_Faces = boundary(Tetrs) +bnd_Edges = CompScienceMeshes.skeleton_fast(bnd_Faces, 1) +bnd_Nodes = CompScienceMeshes.skeleton_fast(bnd_Faces, 0) + +int_Faces = submesh(!in(bnd_Faces), Faces) +int_Edges = submesh(!in(bnd_Edges), Edges) +int_Nodes = submesh(!in(bnd_Nodes), Nodes) + +@show length(int_Edges) +@show length(int_Faces) + +primal2 = BEAST.nedelecd3d(Tetrs, Faces) +primal1 = BEAST.nedelecc3d(Tetrs, int_Edges) + +Dir = bnd_Faces +Neu = submesh(!in(Dir), bnd_Faces) + +# tetrs, bnd, dir, v2t, v2n = BEAST.dualforms_init(Tetrs, Dir) +# dual1 = BEAST.dual1forms_body(Faces, tetrs, bnd, dir, v2t, v2n) + +# error() + +dual2 = BEAST.dual2forms(Tetrs, int_Edges, Dir) +dual1 = BEAST.dual1forms(Tetrs, Faces, Dir) + + +@assert numfunctions(primal1) == numfunctions(dual2) +@assert numfunctions(primal2) == numfunctions(dual1) + +Id = BEAST.Identity() +G = assemble(Id, dual1, primal2) +H = assemble(Id, dual2, primal1) + +TF = connectivity(Faces, Tetrs) +FE = connectivity(int_Edges, Faces) +EN = connectivity(int_Nodes, int_Edges) + +@show norm(G) +@show norm(H) +@show norm(TF*G*FE) +@show norm(FE*H*EN) + +# A = assemble(Id, primal2, primal2) +# B = assemble(Id, curl(primal1), curl(primal1)) +# B2 = FE'*A*FE; +# @show norm(B - B2) + +# EV = eigen(B) +# idx = findfirst(abs.(EV.values) .> 1e-6) +# u = real(EV.vectors[:,idx+1]) + +# primal1_hemi = restrict(primal1, hemi) +# trace_primal1_hemi = BEAST.ttrace(primal1_hemi, boundary(hemi)) +# fcr, geo = facecurrents(u, trace_primal1_hemi) +# Plotly.plot(patch(geo, norm.(fcr))) + + +# tetrs, bnd, dir, v2t, v2n = BEAST.dual1forms_init(Tetrs, Dir) +# duals11 = BEAST.dual1forms_body(int_Faces[collect(1:10)], tetrs, bnd, dir, v2t, v2n) + +# Ggf = assemble(Id, dual1, primal2); +Cgg = assemble(Id, dual1, curl(primal1)) + +# Cfg = Ggf \ Cgg; +# Conn = Matrix(connectivity(int_Edges, Faces)) +# @show norm(Cfg - Conn) + +Zpp = zeros(numfunctions(primal1), numfunctions(primal1)) +Zdd = zeros(numfunctions(dual1), numfunctions(dual1)) + +ϵ = BEAST.Multiplicative(p -> 1.0) +μ = BEAST.Multiplicative(p -> 1.0) + +Ipp = assemble(ϵ, primal1, primal1) +Idd = assemble(μ, dual1, dual1, storage_policy=Val{:densestorage}) + +Zpd = zeros(numfunctions(primal1), numfunctions(dual1)) + +C = [Zpp Cgg'; -Cgg Zdd]; +J = [Ipp Zpd; Zpd' Idd]; +γ = 1.0 * im + +# P = inv(2J - C) * (2J + C); +# Q = (2J - C) * inv(2J + C); + +jg = assemble(BEAST.ScalarTrace{eltype(x̂)}(p -> x̂), primal1) +mG = zeros(numfunctions(dual1)) + +b = [jg; mG] +u = (C - γ*J) \ b + +eg = u[1:numfunctions(primal1)] +hG = u[numfunctions(primal1)+1:end] + +primal1_hemi = restrict(primal1, hemi) +trace_primal1_hemi = BEAST.ttrace(primal1_hemi, boundary(hemi)) +fcr, geo = facecurrents(eg, trace_primal1_hemi) +Plotly.plot(patch(geo, norm.(fcr))) + +curl_primal1_hemi = BEAST.curl(primal1_hemi) +ncurl_primal1_hemi = BEAST.ntrace(curl_primal1_hemi, boundary(hemi)) +fcr, geo = facecurrents(eg, ncurl_primal1_hemi) +Plotly.plot(patch(geo, norm.(fcr))) + +tetrs = geometry(dual1) +bary_hemi = submesh(tetrs) do m,tetr + cartesian(CompScienceMeshes.center(chart(tetrs, tetr)))[1] < 0 +end + +dual1_hemi = restrict(dual1, bary_hemi) +bnd_bary_hemi = boundary(bary_hemi) +trace_dual1_hemi = BEAST.ttrace(dual1_hemi, bnd_bary_hemi) +fcr, geo = facecurrents(hG, trace_dual1_hemi) +Plotly.plot(patch(geo, norm.(fcr))) + + + +# tetrs, bnd, dir, v2t, v2n = BEAST.dualforms_init(Tetrs, Dir) +# @profview BEAST.dual1forms_body(Faces[collect(1:10)], tetrs, bnd, dir, v2t, v2n) \ No newline at end of file diff --git a/examples/ex_fem.jl b/examples/ex_fem.jl index f43505bb..a7268119 100644 --- a/examples/ex_fem.jl +++ b/examples/ex_fem.jl @@ -1,19 +1,15 @@ using CompScienceMeshes using BEAST +using SparseArrays -tetrs = CompScienceMeshes.tetmeshsphere(1.0, 0.10) -# tetrs = CompScienceMeshes.tetmeshsphere(1.0, 0.35) + +tetrs = CompScienceMeshes.tetmeshsphere(1.0, 0.15) @show numcells(tetrs) bndry = boundary(tetrs) edges = skeleton(tetrs, 1) -bndry_edges = [sort(c) for c in cells(skeleton(bndry, 1))] -function is_interior(edge) - !(sort(edge) in bndry_edges) -end - -interior_edges = submesh(is_interior, edges) +interior_edges = submesh(!in(skeleton(bndry,1)), edges) @assert numcells(interior_edges) + numcells(skeleton(bndry,1)) == numcells(edges) X = BEAST.nedelecc3d(tetrs, interior_edges) @@ -26,23 +22,23 @@ A2 = assemble(Id, X, X) A = A1 - A2 using LinearAlgebra -f = BEAST.ScalarTrace(x -> point(1,0,0) * exp(-norm(x)^2/4)) +f = BEAST.ScalarTrace{typeof(x̂)}(x -> x̂) b = assemble(f, X) u = A \ b using Plots -pts = [point(0,t,0) for t in range(-2,2,length=200)]; +pts = [point(0.0,t,0.0) for t in range(-2,2,length=200)]; vals = BEAST.grideval(pts, u, X) vals2 = BEAST.grideval(pts, u, Y) vals2 = [point(0,1,0) × val for val in vals2] -plot(getindex.(real(vals),1), m=2) +plot(getindex.(real(vals),1), m=1) plot(norm.(vals2), m=2) # Inspect the value of a single basis function p = 120 -tet = chart(tetrs, cells(tetrs)[p]) +tet = chart(tetrs, p) nbd = neighborhood(tet, [0.5, 0.5, 0.0]) edg = simplex(tet.vertices[1], tet.vertices[2]) tgt = normalize(edg.vertices[2] - edg.vertices[1]) @@ -59,8 +55,6 @@ fce = simplex(tet.vertices[2], tet.vertices[3], tet.vertices[4]) nbd_rt = neighborhood(tet, [0.0, 1/3, 1/3]) vals_rt = refspace(Z)(nbd_rt) -# carttobary(fce, cartesian(nbd_rt)) - # dot(vals_rt[1].value, normal(fce)) * volume(fce) o, x, y, z = euclidianbasis(3) @@ -70,17 +64,31 @@ G = boundary(tetrs) Z = BEAST.ttrace(curl(X), G) fcr, geo = facecurrents(u, Z) -import PlotlyJS -PlotlyJS.plot(patch(geo, norm.(fcr))) +import Plotly +Plotly.plot(patch(geo, norm.(fcr))) -const CSM = CompScienceMeshes -hemi = submesh(tet -> cartesian(CSM.center(chart(tetrs,tet)))[3] < 0, tetrs) -# error() -bnd_hemi = boundary(hemi) +Dir = Mesh(vertices(tetrs), CompScienceMeshes.celltype(G)[]) +# error("stop") +Xplus = BEAST.nedelecc3d(tetrs, skeleton(tetrs,1)) -Xhemi = BEAST.restrict(X, hemi) -tXhemi = BEAST.ttrace(Xhemi, bnd_hemi) +bnd_tetrs = boundary(tetrs) +ttXplus = BEAST.ttrace(Xplus, bnd_tetrs) +TF, Idcs = isdivconforming(ttXplus) +@show length(Idcs) + +# error("stop") +# @target Q ()->BEAST.dual2forms(tetrs, skeleton(tetrs,1), Dir) +# Q = @make Q +Q = BEAST.dual2forms(tetrs, skeleton(tetrs,1), Dir) + + +QXplus = assemble(Id, Q, Xplus) +curlX = curl(X) +QcurlX = assemble(Id, Q, curlX) + +v = QXplus \ (QcurlX * u) +fcr1, geo1 = facecurrents(v, BEAST.ttrace(Xplus, bnd_tetrs)); +fcr2, geo2 = facecurrents(u, BEAST.ttrace(curl(X), bnd_tetrs)); +fcr3, geo3 = facecurrents(v, divergence(ttXplus)); -fcr, geo = facecurrents(u, tXhemi) -import Plotly Plotly.plot(patch(geo, norm.(fcr))) diff --git a/examples/ex_globalmultitrace.jl b/examples/ex_globalmultitrace.jl new file mode 100644 index 00000000..193f5e12 --- /dev/null +++ b/examples/ex_globalmultitrace.jl @@ -0,0 +1,138 @@ +using CompScienceMeshes +using BEAST +using LinearAlgebra +import PlotlyJS + +# Exterior wavenumber +κ₀ = 3.0 + +# This is where the number of domains enters the problem description +κ = [1.5κ₀, 2.5κ₀] +const numdoms = length(κ) + +# Description of the domain boundariess +h = 0.125 +Γ1 = meshcuboid(0.5, 1.0, 1.0, h) +Γ2 = -Mesh([point(-x,y,z) for (x,y,z) in vertices(Γ1)], deepcopy(cells(Γ1))) +Γ = [Γ1, Γ2] + +# Beyond this point the problem description is completely independent +# of the number of domains and their relative positioning + +# Incident field +Einc = Maxwell3D.planewave(direction=(x̂+ẑ)/√2, polarization=ŷ, wavenumber=κ₀) +Hinc = -1/(im*κ₀)*curl(Einc) + +# Definition of the boundary integral operators +N = BEAST.NCross() + +T0 = Maxwell3D.singlelayer(wavenumber=κ₀) +K0 = Maxwell3D.doublelayer(wavenumber=κ₀) + +T = [Maxwell3D.singlelayer(wavenumber=κᵢ) for κᵢ ∈ κ] +K = [Maxwell3D.doublelayer(wavenumber=κᵢ) for κᵢ ∈ κ] + +# Definition of per-domain bilinear forms +@hilbertspace m j +@hilbertspace k l +A0 = K0[k,m] - T0[k,j] + T0[l,m] + K0[l,j] +Ai = [Kᵢ[k,m] - Tᵢ[k,j] + Tᵢ[l,m] + Kᵢ[l,j] for (Tᵢ,Kᵢ) ∈ zip(T,K)] + +Ni = -0.5*N[k,m] - 0.5*N[l,j] + +p = BEAST.hilbertspace(:p, numdoms) +q = BEAST.hilbertspace(:q, numdoms) + +Adiag = sum(Ai[i][q[i],p[i]] for i in 1:numdoms) +# Ndiag = sum(Ni[q[i],p[i]] for i in 1:numdoms) + +Aextr = sum(A0[q[i],p[j]] for i in 1:numdoms, j in 1:numdoms) +Noffd = sum(Ni[q[i],p[j]] for i in 1:numdoms, j in 1:numdoms if i != j) + +# Adiag = BEAST.Variational.DirectProductKernel(A) +# Ndiag = BEAST.Variational.BlockDiagKernel(Nᵢ) +B = Aextr + Adiag - Noffd +# B = A0[p,q] + Adiag[p,q] + Ndiag[p,q] - Nᵢ[p,q] + +# Also for the right hand side, first per domain contributions +# are constructed, followed by the global synthesis +e = (n × Einc) × n +h = (n × Hinc) × n +bi = e[k] - h[l] +# b = bᵢ[p] +b = sum(bi[q[i]] for i in 1:numdoms) + +Xₕ = raviartthomas.(Γ) +Yₕ = raviartthomas.(Γ) + +Pₕ = BEAST.DirectProductSpace([Xᵢ×Yᵢ for (Xᵢ,Yᵢ) ∈ zip(Xₕ,Yₕ)]) +Qₕ = BEAST.DirectProductSpace([Xᵢ×Yᵢ for (Xᵢ,Yᵢ) ∈ zip(Xₕ,Yₕ)]) + +Bqp = assemble(B, Qₕ, Pₕ) +bq = assemble(b, Qₕ) + +u = Matrix(Bqp) \ Vector(bq) + +import BEAST.BlockArrays.BlockedVector +import BEAST.NestedUnitRanges.nestedrange +u = BlockedVector(u, (nestedrange(Pₕ, 1, numfunctions),)) + +# deq = BEAST.discretise(B==b, (p .∈ Pₕ)..., (q .∈ Qₕ)...) +# u = solve(deq) +# u = gmres(deq, tol=1e-4, maxiter=2500) + +fcrm = [facecurrents(u[qᵢ][m], Xᵢ)[1] for (qᵢ,Xᵢ) ∈ zip(q,Xₕ)] +fcrj = [facecurrents(u[qᵢ][j], Xᵢ)[1] for (qᵢ,Xᵢ) ∈ zip(q,Xₕ)] + +PlotlyJS.plot([patch(Γᵢ, norm.(fcrᵢ), caxis=(0,2)) for (fcrᵢ,Γᵢ) ∈ zip(fcrm,Γ)]) +PlotlyJS.plot([patch(Γᵢ, norm.(fcrᵢ), caxis=(0,2)) for (fcrᵢ,Γᵢ) ∈ zip(fcrj,Γ)]) + +function nearfield(um,Xm,uj,Xj,κ,η,points) + + K = BEAST.MWDoubleLayerField3D(wavenumber=κ) + T = BEAST.MWSingleLayerField3D(wavenumber=κ) + + Em = potential(K, points, um, Xm) + Ej = potential(T, points, uj, Xj) + + Hm = potential(T, points, um, Xm) + Hj = potential(K, points, uj, Xj) + + return -Em + η * Ej, 1/η*Hm + Hj +end + +Xs = range(-2.0,2.0,length=150) +Zs = range(-1.5,2.5,length=100) +pts = [point(x,0.5,z) for z in Zs, x in Xs] + +EH0 = [nearfield(u[qᵢ][m], Xᵢ, u[qᵢ][j], Yᵢ, κ₀, 1.0, pts) for (qᵢ,Xᵢ,Yᵢ) ∈ zip(q,Xₕ,Yₕ)] +EHi = [nearfield(-u[qᵢ][m], Xᵢ, -u[qᵢ][j], Yᵢ, κᵢ, 1.0, pts) for (qᵢ,Xᵢ,Yᵢ,κᵢ) ∈ zip(q,Xₕ,Yₕ,κ)] + +Eo = sum(getindex.(EH0,1)) + Einc.(pts) +Ho = sum(getindex.(EH0,2)) + Hinc.(pts) + +Ei = getindex.(EHi,1) +Hi = getindex.(EHi,2) + +Etot = Eo + sum(Ei) +Htot = Ho + sum(Hi) + +# import Plots +# Plots.heatmap(Xs, Zs, clamp.(real.(getindex.(Etot,2)),-2.0,2.0); colormap=:viridis) + +hm1 = PlotlyJS.heatmap(x=Xs, y=Zs, z=real.(getindex.(Ei[1],2)), + colorscale="Viridis", + zmin=-2, zmax=2) +hm2 = PlotlyJS.heatmap(x=Xs, y=Zs, z=real.(getindex.(Ei[2],2)), + colorscale="Viridis", + zmin=-2, zmax=2) +hmo = PlotlyJS.heatmap(x=Xs, y=Zs, z=real.(getindex.(Eo,2)), + colorscale="Viridis", + zmin=-2, zmax=2) +hmt = PlotlyJS.heatmap(x=Xs, y=Zs, z=real.(getindex.(Etot,2)), + colorscale="Viridis", + zmin=-2, zmax=2) +PlotlyJS.plot(hm1) +PlotlyJS.plot(hm2) +PlotlyJS.plot(hmo) +PlotlyJS.plot(hmt) \ No newline at end of file diff --git a/examples/ex_interpolation.jl b/examples/ex_interpolation.jl new file mode 100644 index 00000000..927389c2 --- /dev/null +++ b/examples/ex_interpolation.jl @@ -0,0 +1,162 @@ +using CompScienceMeshes +using BEAST +using LinearAlgebra + +trias = CompScienceMeshes.meshrectangle(1.0,1.0,0.05) +trias = CompScienceMeshes.meshcircle(1.0,0.5) +tetrs = CompScienceMeshes.tetmeshcuboid(1,1,1,0.2) + +f = BEAST.ScalarTrace{Float64}(x -> sin(pi*x[1]*x[2])*sin(pi*x[2])) +f2 = BEAST.ScalarTrace{Float64}(x -> (-x[2]*sin(2*pi*(x[1]^2+x[2]^2))*sin(atan(x[2],x[1])), x[1]*sin(2*pi*(x[1]^2+x[2]^2))*sin(atan(x[2],x[1])), 0) ) +g = BEAST.ScalarTrace{Float64}(x -> (sin(pi*x[1])*cos(pi*x[2]), -cos(pi*x[1])*sin(pi*x[2]), 0 )) +g = BEAST.ScalarTrace{Float64}(x -> (sin(pi*x[1])*cos(pi*x[2]), sin(pi*x[2])*cos(pi*x[2]), sin(pi*x[3])*cos(pi*x[2]))) +g2 = BEAST.ScalarTrace{Float64}(x -> ( sin(pi*x[1])*cos(pi*x[3]), 0, -cos(pi*x[1])*sin(pi*x[3]))) + + +N = 4 + +h = Vector(undef, N) +errorL = Vector(undef, N) +errorBDM = Vector(undef, N) +errorRT = Vector(undef, N) + +for n in 1:N + + h[n] = 0.5^(n) + + trias = CompScienceMeshes.meshrectangle(1.0,1.0, h[n]) + + # trias = CompScienceMeshes.meshcircle(1.0, h[n], 3) + + Z = BEAST.lagrangec0d1(trias, dirichlet=false) + Y = BEAST.brezzidouglasmarini(trias) + X = BEAST.raviartthomas(trias) + + u_exact = DofInterpolate(Z,f) + b_exact = DofInterpolate(Y,f2) + r_exact = DofInterpolate(X,f2) + + identity = BEAST.Identity() + + M = assemble(identity, Z, Z) + b = assemble(f,Z) + u_n = M \ b + + errorL[n] = sqrt((u_n-u_exact)'*M*(u_n-u_exact)) + + + M = assemble(identity, Y, Y) + b = assemble(f2,Y) + b_n = M \ b + + errorBDM[n] = sqrt((b_n-b_exact)'*M*(b_n-b_exact)) + + + M = assemble(identity, X, X) + b = assemble(f2,X) + r_n = M \ b + + errorRT[n] = sqrt((r_n-r_exact)'*M*(r_n-r_exact)) + +end + +N = 15 + +delta = Vector(undef, N) +error = Vector(undef, N) + + +for i in 1:N + + delta[i] = 0.5-0.025*i + + tetrs = tetmeshcuboid(1.0,1.0,1.0, delta[i]) + + bndry = boundary(tetrs) + faces = skeleton(tetrs, 2) + + # onbnd1 = CompScienceMeshes.in(bnd_edges) + # onbnd0 = CompScienceMeshes.in(bnd_verts) + + # interior_edges = submesh(!onbnd1, all_edges) + # interior_verts = submesh(!onbnd0, all_verts) + + # bndry_faces = [sort(c) for c in cells(skeleton(bndry, 2))] + # function is_interior(faces) + # !(sort(faces) in bndry_faces) + # end + + onbnd = CompScienceMeshes.in(bndry) + interior_faces = submesh(!onbnd, faces) + + # interior_faces = submesh(is_interior, faces) + + W = BEAST.brezzidouglasmarini3d(tetrs, interior_faces) + + u_exact = DofInterpolate(W,g2) + + identity = BEAST.Identity() + + M = assemble(identity, W, W) + + b = assemble(g2,W) + u_n = M \ b + + error[i] = real(sqrt((u_n-u_exact)'*M*(u_n-u_exact))) +end + + +N=15 + +delta2 = Vector(undef, N) +error2 = Vector(undef, N) + +for i in 1:N + + delta2[i] =0.5-0.025*i + + tetrs = tetmeshcuboid(1.0,1.0,1.0, delta2[i]) + + bndry = boundary(tetrs) + faces = skeleton(tetrs, 2) + + # bndry_faces = [sort(c) for c in cells(skeleton(bndry, 2))] + # function is_interior(faces) + # !(sort(faces) in bndry_faces) + # end + + # interior_faces = submesh(is_interior, faces) + + onbnd = CompScienceMeshes.in(bndry) + interior_faces = submesh(!onbnd, faces) + + V = BEAST.nedelecd3d(tetrs, interior_faces) + + + + q_exact = DofInterpolate(V,g) + + identity = BEAST.Identity() + + M2 = assemble(identity, V, V) + + + b2 = assemble(g,V) + + q_n = M2 \ b2 + + + error2[i] = real(sqrt((q_n-q_exact)'*M2*(q_n-q_exact))) +end + +using Plots + +p2 = plot(delta,error, label="BDM3D", title="L2-Error on [0,1]^3",xlabel="h", ylabel="Error", legend=:bottomright, xaxis=:log, yaxis=:log) +plot!(p2, delta2, error2, label="Nedelec Div", xlims=[0.075,0.55]) + +p1 = plot(h,real(errorL), label="Lagrange", title="L2-Error on [0,1]^2",xlabel="h", ylabel="Error", legend=:bottomright, xaxis=:log, yaxis=:log, xlims=[0.025,0.5]) +plot!(p1,h,real(errorBDM),label="BDM") +plot!(p1,h,real(errorRT),label="Raviart-Thomas") + +plot(p1,p2, layout=2) + diff --git a/examples/ex_restrict_space.jl b/examples/ex_restrict_space.jl index 08e0d192..28b3aae9 100644 --- a/examples/ex_restrict_space.jl +++ b/examples/ex_restrict_space.jl @@ -2,7 +2,7 @@ using CompScienceMeshes using BEAST sphere = CompScienceMeshes.tetmeshsphere(1.0, 0.35) -hemi = submesh(tet -> cartesian(center(chart(sphere,tet)))[3] < 0, sphere) +hemi = submesh((m,tet) -> cartesian(CompScienceMeshes.center(chart(m,tet)))[3] < 0, sphere) X = BEAST.nedelecd3d(sphere) Y = BEAST.restrict(X, hemi) diff --git a/examples/helmholtz2d.jl b/examples/helmholtz2d.jl index 8227e108..82987645 100644 --- a/examples/helmholtz2d.jl +++ b/examples/helmholtz2d.jl @@ -4,8 +4,10 @@ h = 2π / 51; Γ = meshcircle(1.0, h) X, Y = lagrangecxd0(Γ), lagrangec0d1(Γ) κ = 1.0 -S, Dᵀ = SingleLayer(κ), DoubleLayerTransposed(κ) -D, N = DoubleLayer(κ), HyperSingular(κ) +S = Helmholtz2D.singlelayer(; wavenumber=κ) +Dᵀ = Helmholtz2D.doublelayer_transposed(; wavenumber = κ) +D = Helmholtz2D.doublelayer(; wavenumber=κ) +N = Helmholtz2D.hypersingular(; wavenumber=κ) I = Identity() # TM scattering diff --git a/examples/helmholtz3d_dirichlet.jl b/examples/helmholtz3d_dirichlet.jl index 3f59e985..107c6089 100644 --- a/examples/helmholtz3d_dirichlet.jl +++ b/examples/helmholtz3d_dirichlet.jl @@ -2,10 +2,9 @@ using CompScienceMeshes, BEAST o, x, y, z = euclidianbasis(3) -# Γ = meshsphere(1.0, 0.11) -Γ = readmesh(joinpath(@__DIR__,"sphere2.in")) -# Γ = readmesh("/Users/Benjamin/Documents/sphere.in") -# Γ = readmesh(joinpath(@__DIR__,"sphere_subd1.in")) + +Γ = readmesh(joinpath(dirname(pathof(BEAST)),"../examples/sphere2.in")) + X = lagrangecxd0(Γ) # X = subdsurface(Γ) # X = raviartthomas(Γ) @@ -25,35 +24,37 @@ g = ∂n(uⁱ) eq1 = @discretise a[v,u] == f[v] u∈X v∈X eq2 = @discretise b[v,u] == g[v] u∈X v∈X -x1 = solve(eq1) -x2 = solve(eq2) +x1 = gmres(eq1) +x2 = gmres(eq2) fcr1, geo1 = facecurrents(x1, X) fcr2, geo2 = facecurrents(x2, X) # include(Pkg.dir("CompScienceMeshes","examples","plotlyjs_patches.jl")) -# p1 = patch(geo1, real.(norm.(fcr1))) -# p2 = patch(geo2, real.(norm.(fcr2))) - using LinearAlgebra -using Test - -## test the results -Z = assemble(a,X,X); -m1, m2 = 1, numfunctions(X) -chm, chn = chart(Γ,cells(Γ)[m1]), chart(Γ,cells(Γ)[m2]) -ctm, ctn = center(chm), center(chn) -R = norm(cartesian(ctm)-cartesian(ctn)) -G = exp(-im*κ*R)/(4π*R) -Wmn = volume(chm) * volume(chn) * G -@show abs(Wmn-Z[m1,m2]) / abs(Z[m1,m2]) -@test abs(Wmn-Z[m1,m2]) / abs(Z[m1,m2]) < 2.0e-3 - -r = assemble(f,X) -m1 = 1 -chm = chart(Γ,cells(Γ)[m1]) -ctm = center(chm) -sm = volume(chm) * f(ctm) -r[m1] -@show abs(sm - r[m1]) / abs(r[m1]) -@test abs(sm - r[m1]) / abs(r[m1]) < 1e-3 +using Plotly +pt1 = Plotly.plot(patch(geo1, real.(norm.(fcr1)))); +pt2 = Plotly.plot(patch(geo2, real.(norm.(fcr2)))); +display([pt1 pt2]) + +# using Test + +# ## test the results +# Z = assemble(a,X,X); +# m1, m2 = 1, numfunctions(X) +# chm, chn = chart(Γ,cells(Γ)[m1]), chart(Γ,cells(Γ)[m2]) +# ctm, ctn = CompScienceMeshes.center(chm), CompScienceMeshes.center(chn) +# R = norm(cartesian(ctm)-cartesian(ctn)) +# G = exp(-im*κ*R)/(4π*R) +# Wmn = volume(chm) * volume(chn) * G +# @show abs(Wmn-Z[m1,m2]) / abs(Z[m1,m2]) +# @test abs(Wmn-Z[m1,m2]) / abs(Z[m1,m2]) < 2.0e-3 + +# r = assemble(f,X) +# m1 = 1 +# chm = chart(Γ,cells(Γ)[m1]) +# ctm = CompScienceMeshes.center(chm) +# sm = volume(chm) * f(ctm) +# r[m1] +# @show abs(sm - r[m1]) / abs(r[m1]) +# @test abs(sm - r[m1]) / abs(r[m1]) < 1e-3 diff --git a/examples/helmholtz3d_neumann.jl b/examples/helmholtz3d_neumann.jl index 9d14db8a..6dfaa5fc 100644 --- a/examples/helmholtz3d_neumann.jl +++ b/examples/helmholtz3d_neumann.jl @@ -1,10 +1,14 @@ using CompScienceMeshes, BEAST +using LinearAlgebra, Pkg -Γ = readmesh(joinpath(@__DIR__,"sphere2.in")) +Pkg.activate(@__DIR__) +# Γ = readmesh(joinpath(@__DIR__,"sphere2.in")) +Γ = readmesh(joinpath(dirname(pathof(BEAST)),"../examples/sphere2.in")) +# Γ = meshrectangle(1.0, 1.0, 0.05, 3) X = lagrangec0d1(Γ) @show numfunctions(X) -κ = 1.0; γ = im*κ +κ = 2π; γ = im*κ a = -1Helmholtz3D.hypersingular(gamma=γ) b = Helmholtz3D.doublelayer(gamma=γ) - 0.5Identity() @@ -17,16 +21,29 @@ g = ∂n(uⁱ) eq1 = @discretise a[v,u] == g[v] u∈X v∈X eq2 = @discretise b[v,u] == f[v] u∈X v∈X -x1 = solve(eq1) -x2 = solve(eq2) +x1 = gmres(eq1) +x2 = gmres(eq2) -@assert norm(x1-x2)/norm(x1+x2) < 0.5e-2 +# @assert norm(x1-x2)/norm(x1+x2) < 0.5e-2 fcr1, geo1 = facecurrents(x1, X) fcr2, geo2 = facecurrents(x2, X) using Plots -using LinearAlgebra -plot(title="Comparse 1st and 2nd kind eqs.") -plot!(norm.(fcr1),c=:blue,label="1st") -scatter!(norm.(fcr2),c=:red,label="2nd") +Plots.plot(title="Comparse 1st and 2nd kind eqs.") +Plots.plot!(norm.(fcr1),c=:blue,label="1st") +Plots.scatter!(norm.(fcr2),c=:red,label="2nd") + +import Plotly +plt1 = Plotly.plot(patch(Γ, norm.(fcr1))) +plt2 = Plotly.plot(patch(Γ, norm.(fcr2))) +display([plt1 plt2]) + +ys = range(-2,2,length=200) +zs = range(-2,2,length=200) +pts = [point(0.5, y, z) for y in ys, z in zs] + +nf = BEAST.HH3DDoubleLayerNear(wavenumber=κ) +near = BEAST.potential(nf, pts, x1, X, type=ComplexF64) +inc = uⁱ.(pts) +tot = near + inc diff --git a/examples/hh3d/pmchwt.jl b/examples/hh3d/pmchwt.jl new file mode 100644 index 00000000..9e0904d3 --- /dev/null +++ b/examples/hh3d/pmchwt.jl @@ -0,0 +1,139 @@ +using BEAST +using CompScienceMeshes +using Makeitso +using PlotlyJS + +@target geo (;h) -> begin + (;∂Ω = [meshcuboid(1.0, 1.0, 1.0, h)]) +end + +@target material (;κ) -> begin + return [ + (;κ=κ, η=1.0), + (;κ=3κ, η=1.0), + ] +end + +@target excitation (material) -> begin + + κ = material[1].κ + Uinc = Helmholtz3D.planewave(wavenumber=κ, direction=point(0,0,1)) + return [(;Uinc)] +end + +@target formulation (material, excitation) -> begin + + numdoms = 2 + Γ = ∂Ω[1] + + # 𝐼 = BEAST.Identity() + 𝑆 = [Helmholtz3D.singlelayer(wavenumber=m.κ) for m in material] + 𝐾 = [Helmholtz3D.doublelayer_transposed(wavenumber=m.κ) for m in material] + 𝐷 = [Helmholtz3D.doublelayer(wavenumber=m.κ) for m in material] + 𝑊 = [Helmholtz3D.hypersingular(wavenumber=m.κ) for m in material] + + @hilbertspace u v + @hilbertspace p q + + uinc = strace(excitation[1].Uinc, Γ) + vinc = ∂n(excitation[1].Uinc) + + 𝐴 = [ + 𝐷[i][p,u] + 𝑆[i][p,v] + + 𝑊[i][q,u] - 𝐾[i][q,v] for i in 1:numdoms] + + 𝑀 = 𝐴[1] + 𝐴[2] + 𝑏 = -uinc[p] + vinc[q] + + return (;bilforms=(;𝑀), linforms=(;𝑏)) +end + +@target spaces (geo) -> begin + (;∂Ω) = geo + Γ = ∂Ω[1] + + L = lagrangec0d1(Γ) + P = lagrangecxd0(Γ) + + U = L × P + V = P × L + return (;U=U, V=V) +end + +@target discretization (formulation, spaces) -> begin + (;bilforms, linforms) = formulation + (;𝑀) = bilforms + (;𝑏) = linforms + + (;U, V) = spaces + + M = assemble(𝑀, V, U) + b = assemble(𝑏, V) + + return (;matrices=(;M), vectors=(;b)) +end + +@target solution (discretization, spaces) -> begin + (;matrices, vectors) = discretization + (;M) = matrices + (;b) = vectors + + (;U, V) = spaces + + 𝗠 = Matrix(M) + 𝗯 = Vector(b) + + 𝘂 = 𝗠 \ 𝗯 + u = BEAST.BlockArrays.BlockedVector(𝘂, ( + BEAST.NestedUnitRanges.nestedrange(U, 1, numfunctions),)) + u = BEAST.FEMFunction(u, U) + return (;u) +end + +@target nearfield (solution, material, excitation; pts) -> begin + + function nf(um,uj,mat,pts) + + (;κ) = mat + + Xm = um.space + Xj = uj.space + + um = um.coeffs + uj = uj.coeffs + + 𝒟 = BEAST.HH3DDoubleLayerNear(wavenumber=κ) + 𝒮 = BEAST.HH3DSingleLayerNear(wavenumber=κ) + + U1 = potential(𝒟, pts, um, Xm; type=ComplexF64) + U2 = potential(𝒮, pts, uj, Xj; type=ComplexF64) + + return U1 + U2 + end + + (;u) = solution + (;Uinc) = excitation[1] + + @hilbertspace p v + U = [ + nf(u[p], u[v], material[1], pts), + nf(-u[p], -u[v], material[2], pts),] + U[1] .+= Uinc.(pts) + + return (;U) +end + +x = range(-2.0,2.0,length=75) +z = range(-1.5,2.5,length=50) +pts = [point(xi, 0.5, zi) for xi in x, zi in z] +nf = make(nearfield; h=0.1, κ=3.0, pts=pts) + +using LinearAlgebra +using PlotlyJS +hm1 = PlotlyJS.heatmap(x=x, y=z, z=real.(nf.U[1]), colorscale="Viridis", zmin=-2, zmax=2) +hm2 = PlotlyJS.heatmap(x=x, y=z, z=real.(nf.U[2]), colorscale="Viridis", zmin=-2, zmax=2) + +plt = Plot(Layout(Subplots(rows=2,cols=1, specs=reshape([Spec(); Spec()], 2, 1)))); +PlotlyJS.add_trace!(plt, hm1, row=1, col=1); +PlotlyJS.add_trace!(plt, hm2, row=2, col=1); +display(plt) \ No newline at end of file diff --git a/examples/hh3d_dirichlet_order3.jl b/examples/hh3d_dirichlet_order3.jl new file mode 100644 index 00000000..9205026a --- /dev/null +++ b/examples/hh3d_dirichlet_order3.jl @@ -0,0 +1,69 @@ +using CompScienceMeshes, BEAST + +o, x, y, z = euclidianbasis(3) + + +Γ = readmesh(joinpath(dirname(pathof(BEAST)),"../examples/sphere2.in")) + +X = BEAST.lagrangecx(Γ; order=3) +# X = subdsurface(Γ) +# X = raviartthomas(Γ) +@show numfunctions(X) + +κ = 1.0; γ = im*κ +a = Helmholtz3D.singlelayer(wavenumber=κ) +# b = Helmholtz3D.doublelayer_transposed(gamma=κ*im) +0.5Identity() + +uⁱ = Helmholtz3D.planewave(wavenumber=κ, direction=z) +f = strace(uⁱ,Γ) +# g = ∂n(uⁱ) + +BEAST.@defaultquadstrat (a,X,X) BEAST.DoubleNumSauterQstrat(7,8,6,6,6,6) +BEAST.@defaultquadstrat (f,X) BEAST.SingleNumQStrat(12) + +@hilbertspace u +@hilbertspace v + +# eq1 = @discretise a[v,u] == f[v] u∈X v∈X +# eq2 = @discretise b[v,u] == g[v] u∈X v∈X + +A = assemble(a[v,u], X, X) +b = assemble(f[v], X) +x1 = AbstractMatrix(A) \ b +# x1 = BEAST.GMRESSolver(A; reltol=1e-10) * b + +# x1 = gmres(eq1; tol=1e-6) +# x2 = gmres(eq2) + +fcr1, geo1 = facecurrents(x1, X) +# fcr2, geo2 = facecurrents(x2, X) + +# include(Pkg.dir("CompScienceMeshes","examples","plotlyjs_patches.jl")) +using LinearAlgebra +using Plotly +pt1 = Plotly.plot(patch(geo1, real.(norm.(fcr1)))) +# pt1 = Plotly.plot(patch(geo1, ones(length(geo1)))) +# pt2 = Plotly.plot(patch(geo2, real.(norm.(fcr2)))); +display(pt1) + +# using Test + +# ## test the results +# Z = assemble(a,X,X); +# m1, m2 = 1, numfunctions(X) +# chm, chn = chart(Γ,cells(Γ)[m1]), chart(Γ,cells(Γ)[m2]) +# ctm, ctn = CompScienceMeshes.center(chm), CompScienceMeshes.center(chn) +# R = norm(cartesian(ctm)-cartesian(ctn)) +# G = exp(-im*κ*R)/(4π*R) +# Wmn = volume(chm) * volume(chn) * G +# @show abs(Wmn-Z[m1,m2]) / abs(Z[m1,m2]) +# @test abs(Wmn-Z[m1,m2]) / abs(Z[m1,m2]) < 2.0e-3 + +# r = assemble(f,X) +# m1 = 1 +# chm = chart(Γ,cells(Γ)[m1]) +# ctm = CompScienceMeshes.center(chm) +# sm = volume(chm) * f(ctm) +# r[m1] +# @show abs(sm - r[m1]) / abs(r[m1]) +# @test abs(sm - r[m1]) / abs(r[m1]) < 1e-3 diff --git a/examples/hh3d_neumann order3.jl b/examples/hh3d_neumann order3.jl new file mode 100644 index 00000000..14e8070e --- /dev/null +++ b/examples/hh3d_neumann order3.jl @@ -0,0 +1,56 @@ +using CompScienceMeshes, BEAST +using LinearAlgebra, Pkg + +# Pkg.activate(@__DIR__) +Γ = readmesh(joinpath(dirname(pathof(BEAST)),"../examples/sphere2.in")) +# Γ = meshrectangle(1.0, 1.0, 0.05, 3) +X = BEAST.lagrangec0(Γ; order=2) +@show numfunctions(X) + +κ = 2π; γ = im*κ +a = Helmholtz3D.hypersingular(gamma=γ) +# b = Helmholtz3D.doublelayer(gamma=γ) - 0.5Identity() + +uⁱ = Helmholtz3D.planewave(wavenumber=κ, direction=ẑ) +# f = strace(uⁱ,Γ) +g = ∂n(uⁱ) + +BEAST.@defaultquadstrat (a,X,X) BEAST.DoubleNumSauterQstrat(7,8,6,6,6,6) +BEAST.@defaultquadstrat (g,X) BEAST.SingleNumQStrat(12) + +@hilbertspace u +@hilbertspace v + +A = assemble(a[v,u], X, X) +b = assemble(g[v], X) +x1 = AbstractMatrix(A) \ b + +# eq1 = @discretise a[v,u] == g[v] u∈X v∈X +# eq2 = @discretise b[v,u] == f[v] u∈X v∈X + +# x1 = gmres(eq1) +# x2 = gmres(eq2) + +# @assert norm(x1-x2)/norm(x1+x2) < 0.5e-2 + +fcr1, geo1 = facecurrents(x1, X) +# fcr2, geo2 = facecurrents(x2, X) + +using Plots +Plots.plot(title="Comparse 1st and 2nd kind eqs.") +Plots.plot!(norm.(fcr1),c=:blue,label="1st") +# Plots.scatter!(norm.(fcr2),c=:red,label="2nd") + +import Plotly +plt1 = Plotly.plot(patch(Γ, norm.(fcr1))) +# plt2 = Plotly.plot(patch(Γ, norm.(fcr2))) +# display([plt1 plt2]) + +# ys = range(-2,2,length=200) +# zs = range(-2,2,length=200) +# pts = [point(0.5, y, z) for y in ys, z in zs] + +# nf = BEAST.HH3DDoubleLayerNear(wavenumber=κ) +# near = BEAST.potential(nf, pts, x1, X, type=ComplexF64) +# inc = uⁱ.(pts) +# tot = near + inc diff --git a/examples/hh3dexample.jl b/examples/hh3dexample.jl new file mode 100644 index 00000000..a68e2c18 --- /dev/null +++ b/examples/hh3dexample.jl @@ -0,0 +1,213 @@ +using BEAST +using CompScienceMeshes +using StaticArrays +using LinearAlgebra +using Plotly + +## Looking at convergence +hs = [0.3, 0.2, 0.1, 0.09]#,0.08,0.07,0.06,0.04] +ir = 0.8 +err_IDPSL_pot = zeros(Float64, length(hs)) +err_IDPDL_pot = zeros(Float64, length(hs)) +err_INPSL_pot = zeros(Float64, length(hs)) +err_INPDL_pot = zeros(Float64, length(hs)) +err_IDPSL_field = zeros(Float64, length(hs)) +err_IDPDL_field = zeros(Float64, length(hs)) +err_INPSL_field = zeros(Float64, length(hs)) +err_INPDL_field = zeros(Float64, length(hs)) + +err_EDPSL_pot = zeros(Float64, length(hs)) +err_EDPDL_pot = zeros(Float64, length(hs)) +err_ENPSL_pot = zeros(Float64, length(hs)) +err_ENPDL_pot = zeros(Float64, length(hs)) + +err_EDPSL_field = zeros(Float64, length(hs)) +err_EDPDL_field = zeros(Float64, length(hs)) +err_ENPSL_field = zeros(Float64, length(hs)) +err_ENPDL_field = zeros(Float64, length(hs)) + +for (i, h) in enumerate(hs) + r = 50.0 + sphere = meshsphere(r, h * r) + X0 = lagrangecxd0(sphere) + X1 = lagrangec0d1(sphere) + + S = Helmholtz3D.singlelayer(; gamma=0.0) + D = Helmholtz3D.doublelayer(; gamma=0.0) + Dt = Helmholtz3D.doublelayer_transposed(; gamma=0.0) + N = Helmholtz3D.hypersingular(; gamma=0.0) + + q = 100.0 + ϵ = 1.0 +# Interior problem +# Formulations from Sauter and Schwab, Boundary Element Methods(2011), Chapter 3.4.1.1 + + pos1 = SVector(r * 1.5, 0.0, 0.0) + pos2 = SVector(-r * 1.5, 0.0, 0.0) + + charge1 = HH3DMonopole(position=pos1, amplitude=q/(4*π*ϵ), wavenumber=0.0) + charge2 = HH3DMonopole(position=pos2, amplitude=-q/(4*π*ϵ), wavenumber=0.0) + + #Potential of point charges + Φ_inc(x) = charge1(x) + charge2(x) + + gD0 = assemble(DirichletTrace(charge1), X0) + assemble(DirichletTrace(charge2), X0) + gD1 = assemble(DirichletTrace(charge1), X1) + assemble(DirichletTrace(charge2), X1) + gN = assemble(∂n(charge1), X1) + assemble(BEAST.n ⋅ grad(charge2), X1) + + G = assemble(Identity(), X1, X1) + o = ones(numfunctions(X1)) + #Interior Dirichlet problem - compare Sauter & Schwab + M_IDPSL = assemble(S, X0, X0) + M_IDPDL = (-1 / 2 * assemble(Identity(), X1, X1) + assemble(D, X1, X1)) + + #Interior Neumann problem + M_INPSL = (1 / 2 * assemble(Identity(), X1, X1) + assemble(Dt, X1, X1)) + G * o * o' * G + M_INPDL = assemble(N, X1, X1) + G * o * o' * G + + ρ_IDPSL = M_IDPSL \ (-gD0) + ρ_IDPDL = M_IDPDL \ (-gD1) + ρ_INPSL = M_INPSL \ (-gN) + ρ_INPDL = M_INPDL \ (gN) + + pts = meshsphere(r * ir, r * ir * 0.6).vertices + + pot_IDPSL = potential(HH3DSingleLayerNear(0.0), pts, ρ_IDPSL, X0; type=ComplexF64) + pot_IDPDL = potential(HH3DDoubleLayerNear(0.0), pts, ρ_IDPDL, X1; type=ComplexF64) + pot_INPSL = potential(HH3DSingleLayerNear(0.0), pts, ρ_INPSL, X1; type=ComplexF64) + pot_INPDL = potential(HH3DDoubleLayerNear(0.0), pts, ρ_INPDL, X1; type=ComplexF64) + + # Total potential inside should be zero + err_IDPSL_pot[i] = norm(pot_IDPSL + Φ_inc.(pts)) ./ norm(Φ_inc.(pts)) + err_IDPDL_pot[i] = norm(pot_IDPDL + Φ_inc.(pts)) ./ norm(Φ_inc.(pts)) + err_INPSL_pot[i] = norm(pot_INPSL + Φ_inc.(pts)) ./ norm(Φ_inc.(pts)) + err_INPDL_pot[i] = norm(pot_INPDL + Φ_inc.(pts)) ./ norm(Φ_inc.(pts)) + + Efield(x) = -grad(charge1)(x) + -grad(charge2)(x) + + field_IDPSL = -potential(HH3DDoubleLayerTransposedNear(0.0), pts, ρ_IDPSL, X0) + field_IDPDL = -potential(HH3DHyperSingularNear(0.0), pts, ρ_IDPDL, X1) + field_INPSL = -potential(HH3DDoubleLayerTransposedNear(0.0), pts, ρ_INPSL, X1) + field_INPDL = -potential(HH3DHyperSingularNear(0.0), pts, ρ_INPDL, X1) + + # Total field inside should be zero + err_IDPSL_field[i] = norm(field_IDPSL + Efield.(pts)) / norm(Efield.(pts)) + err_IDPDL_field[i] = norm(field_IDPDL + Efield.(pts)) / norm(Efield.(pts)) + err_INPSL_field[i] = norm(field_INPSL + Efield.(pts)) / norm(Efield.(pts)) + err_INPDL_field[i] = norm(field_INPDL + Efield.(pts)) / norm(Efield.(pts)) + + # Exterior problem + # formulations from Sauter and Schwab, Boundary Element Methods(2011), Chapter 3.4.1.2 + + pos1 = SVector(r * 0.5, 0.0, 0.0) + pos2 = SVector(-r * 0.5, 0.0, 0.0) + + charge1 = HH3DMonopole(position=pos1, amplitude=q/(4*π*ϵ), wavenumber=0.0) + charge2 = HH3DMonopole(position=pos2, amplitude=-q/(4*π*ϵ), wavenumber=0.0) + + gD0 = assemble(DirichletTrace(charge1), X0) + assemble(DirichletTrace(charge2), X0) + gD1 = assemble(DirichletTrace(charge1), X1) + assemble(DirichletTrace(charge2), X1) + gN = assemble(∂n(charge1), X1) + assemble(∂n(charge2), X1) + + G = assemble(Identity(), X1, X1) + o = ones(numfunctions(X1)) + + M_EDPSL = assemble(S, X0, X0) + M_EDPDL = (1 / 2 * assemble(Identity(), X1, X1) + assemble(D, X1, X1)) + + M_ENPSL = (-1 / 2 * assemble(Identity(), X1, X1) + assemble(Dt, X1, X1)) + G * o * o' * G + M_ENPDL = assemble(N, X1, X1) + G * o * o' * G + + ρ_EDPSL = M_EDPSL \ (-gD0) + ρ_EDPDL = M_EDPDL \ (-gD1) + ρ_ENPSL = M_ENPSL \ (-gN) + ρ_ENPDL = M_ENPDL \ (gN) + + testsphere = meshsphere(r / ir, r / ir * 0.6) + pts = testsphere.vertices[norm.(testsphere.vertices) .> r] + + pot_EDPSL = potential(HH3DSingleLayerNear(0.0), pts, ρ_EDPSL, X0; type=ComplexF64) + pot_EDPDL = potential(HH3DDoubleLayerNear(0.0), pts, ρ_EDPDL, X1; type=ComplexF64) + pot_ENPSL = potential(HH3DSingleLayerNear(0.0), pts, ρ_ENPSL, X1; type=ComplexF64) + pot_ENPDL = potential(HH3DDoubleLayerNear(0.0), pts, ρ_ENPDL, X1; type=ComplexF64) + + err_EDPSL_pot[i] = norm(pot_EDPSL + Φ_inc.(pts)) ./ norm(Φ_inc.(pts)) + err_EDPDL_pot[i] = norm(pot_EDPDL + Φ_inc.(pts)) ./ norm(Φ_inc.(pts)) + err_ENPSL_pot[i] = norm(pot_ENPSL + Φ_inc.(pts)) ./ norm(Φ_inc.(pts)) + err_ENPDL_pot[i] = norm(pot_ENPDL + Φ_inc.(pts)) ./ norm(Φ_inc.(pts)) + + field_EDPSL = -potential(HH3DDoubleLayerTransposedNear(0.0), pts, ρ_EDPSL, X0) + field_EDPDL = -potential(HH3DHyperSingularNear(0.0), pts, ρ_EDPDL, X1) + field_ENPSL = -potential(HH3DDoubleLayerTransposedNear(0.0), pts, ρ_ENPSL, X1) + field_ENPDL = -potential(HH3DHyperSingularNear(0.0), pts, ρ_ENPDL, X1) + + err_EDPSL_field[i] = norm(field_EDPSL + Efield.(pts)) / norm(Efield.(pts)) + err_EDPDL_field[i] = norm(field_EDPDL + Efield.(pts)) / norm(Efield.(pts)) + err_ENPSL_field[i] = norm(field_ENPSL + Efield.(pts)) / norm(Efield.(pts)) + err_ENPDL_field[i] = norm(field_ENPDL + Efield.(pts)) / norm(Efield.(pts)) +end + +## +Plotly.plot( + [ + Plotly.scatter(; x=hs, y=err_IDPSL_pot[:, end], name="IDPSL"), + Plotly.scatter(; x=hs, y=err_IDPDL_pot[:, end], name="IDPDL"), + Plotly.scatter(; x=hs, y=err_INPSL_pot[:, end], name="INPSL"), + Plotly.scatter(; x=hs, y=err_INPDL_pot[:, end], name="INPDL"), + ], + Layout(; + xaxis_type="log", + yaxis_type="log", + xaxis_title="h / r", + yaxis_title="rel. error", + title="Errors - potential - interior problem", + ), +) + +Plotly.plot( + [ + Plotly.scatter(; x=hs, y=err_EDPSL_pot[:, end], name="EDPSL"), + Plotly.scatter(; x=hs, y=err_EDPDL_pot[:, end], name="EDPDL"), + Plotly.scatter(; x=hs, y=err_ENPSL_pot[:, end], name="ENPSL"), + Plotly.scatter(; x=hs, y=err_ENPDL_pot[:, end], name="ENPDL"), + ], + Layout(; + xaxis_type="log", + yaxis_type="log", + xaxis_title="h / r", + yaxis_title="rel. error", + title="Errors - potential - exterior problem", + ), +) + +Plotly.plot( + [ + Plotly.scatter(; x=hs, y=err_IDPSL_field[:, end], name="IDPSL"), + Plotly.scatter(; x=hs, y=err_IDPDL_field[:, end], name="IDPDL"), + Plotly.scatter(; x=hs, y=err_INPSL_field[:, end], name="INPSL"), + Plotly.scatter(; x=hs, y=err_INPDL_field[:, end], name="INPDL"), + ], + Layout(; + xaxis_type="log", + yaxis_type="log", + xaxis_title="h / r", + yaxis_title="rel. error", + title="Errors - field - interior problem", + ), +) + +Plotly.plot( + [ + Plotly.scatter(; x=hs, y=err_EDPSL_field[:, end], name="EDPSL"), + Plotly.scatter(; x=hs, y=err_EDPDL_field[:, end], name="EDPDL"), + Plotly.scatter(; x=hs, y=err_ENPSL_field[:, end], name="ENPSL"), + Plotly.scatter(; x=hs, y=err_ENPDL_field[:, end], name="ENPDL"), + ], + Layout(; + xaxis_type="log", + yaxis_type="log", + xaxis_title="h / r", + yaxis_title="rel. error", + title="Errors - field - exterior problem", + ), +) \ No newline at end of file diff --git a/examples/hh_lsvie.jl b/examples/hh_lsvie.jl new file mode 100644 index 00000000..8e32897b --- /dev/null +++ b/examples/hh_lsvie.jl @@ -0,0 +1,123 @@ +using BEAST +using CompScienceMeshes +using StaticArrays +using LinearAlgebra +using Test +using Plots +using SphericalScattering + +# Layered Dielectic Sphere +# MoM (Lippmann Schwinger VIE) vs. Analytical Solution (SphericalScattering) + + + +# Environment +ε1 = 1.0*ε0 +μ1 = μ0 + +# Layered Dielectic Sphere +ε2 = 5.0*ε0 # outer shell +μ2 = μ0 + +ε3 = 20.0*ε0 +μ3 = μ0 + +ε4 = 7.0*ε0 +μ4 = μ0 + +ε5 = 30.0*ε0 # inner core +μ5 = μ0 + +r = 1.0 +radii = SVector(0.2*r, 0.6*r, 0.8*r, r) # inner core stops at first radius +filling = SVector(Medium(ε5, μ5), Medium(ε4, μ4), Medium(ε3, μ3), Medium(ε2, μ2)) + + + + +# Mesh, Basis +h = 0.1 +mesh = CompScienceMeshes.tetmeshsphere(r,h) +X = lagrangec0d1(mesh; dirichlet = false) +@show numfunctions(X) + +bnd = boundary(mesh) +strc = X -> strace(X, bnd) + + +# VIE Operators +function generate_tau(ε_env, radii, filling) + + function tau(x::SVector{U,T}) where {U,T} + for (i, radius) in enumerate(radii) + norm(x) <= radius && (return T(1.0 - filling[i].ε/ε_env)) + end + #return 0.0 + error("Evaluated contrast outside the sphere!") + end + + return tau +end +τ = generate_tau(ε1, radii, filling) #contrast function + +I = Identity() +V = VIE.hhvolume(tau = τ, wavenumber = 0.0) +B = VIE.hhboundary(tau = τ, wavenumber = 0.0) +Y = VIE.hhvolumegradG(tau = τ, wavenumber = 0.0) + + +# Exitation +dirE = SVector(1.0, 0.0, 0.0) +dirgradΦ = - dirE +amp = 1.0 +Φ_inc = VIE.linearpotential(direction = dirgradΦ, amplitude = amp) + + +# Assembly +b = assemble(Φ_inc, X) + +Z_I = assemble(I, X, X) + +Z_V = assemble(V, X, X) +Z_B = assemble(B, strc(X), X) + +Z_Y = assemble(Y, X, X) + + +# System matrix +Z_version1 = Z_I + Z_V + Z_B +Z_version2 = Z_I + Z_Y + + +# Solution of the linear system of equations +u_version1 = Z_version1 \ Vector(b) +u_version2 = Z_version2 \ Vector(b) + + + +## Observe the potential on a x-line in dirgradΦ direction + +# x-line at y0, z0 - Potential only inside the sphere mesh valid! +y0 = 0.0 +z0 = 0.0 +x_range = range(0.0, stop = 1.0*r, length = 200) +points_x = [point(x, y0, z0) for x in x_range] +x = collect(x_range) + +# SphericalScattering solution on x-line +sp = LayeredSphere(radii = radii, filling = filling) +ex = UniformField(direction = dirE, amplitude = amp) +Φ_x = real.(field(sp, ex, ScalarPotential(points_x))) + +# MoM solution on x-line +Φ_MoM_version1_x = BEAST.grideval(points_x, u_version1, X, type = Float64) +Φ_MoM_version2_x = BEAST.grideval(points_x, u_version2, X, type = Float64) + +# Plot +plot(x, Φ_x, label = "SphericalScattering") +plot!(x, Φ_MoM_version1_x, label = "MoM: S=I+B+V, boundary+volume") +plot!(x, Φ_MoM_version2_x, label = "MoM: S=I+Y, gradgreen") +xlims!(0.0, 1.0) # sphere center to radius r +ylims!(-0.3, 0.0) +title!("Potential Φ(x, y0, z0)") +xlabel!("x") \ No newline at end of file diff --git a/examples/junction_calderon.jl b/examples/junction_calderon.jl new file mode 100644 index 00000000..bc4db4a8 --- /dev/null +++ b/examples/junction_calderon.jl @@ -0,0 +1,62 @@ +using CompScienceMeshes +using BEAST +using LinearAlgebra + +width, height, h = 1.0, 0.5, 0.05 +ℑ₁ = meshrectangle(width, height, h) +ℑ₂ = CompScienceMeshes.rotate(ℑ₁, 0.5π * x̂) +ℑ₃ = CompScienceMeshes.rotate(ℑ₁, 1.0π * x̂) +Γ₁ = weld(ℑ₁,-ℑ₂) +Γ₂ = weld(ℑ₂,-ℑ₃) + +X₁ = raviartthomas(Γ₁) +X₂ = raviartthomas(Γ₂) + +Y₁ = buffachristiansen(Γ₁) +Y₂ = buffachristiansen(Γ₂) + +X = X₁ × X₂ +Y = Y₁ × Y₂ + +@hilbertspace p +@hilbertspace q + +κ = 3.0 +SL = Maxwell3D.singlelayer(wavenumber=κ) +N = NCross() +E = Maxwell3D.planewave(direction=ẑ, polarization=x̂, wavenumber=κ) +e = (n × E) × n; + +import BEAST: diag, blocks +Sxx = assemble(@discretise blocks(SL)[p,q] p∈X q∈X) +ex = assemble(e, X) + +# Build the preconditioner +Nxy = assemble(@discretise(BEAST.diag(N)[p,q], p∈X, q∈Y)) +Dyx = BEAST.GMRESSolver(Nxy, tol=2e-12, restart=250, verbose=false) +Dxy = BEAST.GMRESSolver(transpose(Nxy), tol=2e-12, restart=250, verbose=false) +Syy = BEAST.assemble(@discretise BEAST.diag(SL)[p,q] p∈Y q∈Y) +P = Dxy * Syy * Dyx + +# Solve system without and with preconditioner +u1, ch1 = solve(BEAST.GMRESSolver(Sxx,tol=2e-5, restart=250), ex) +u2, ch2 = solve(BEAST.GMRESSolver(P*Sxx, tol=2e-5, restart=250), P*ex) + +# error() + +# Q = P*Sxx +# S = BEAST.GMRESSolver(Q, tol=2e-5, restart=250) +# rhs = P*ex +# @run solve(S, rhs) + +# Compute and visualise the far field +Φ, Θ = [0.0], range(0,stop=π,length=100) +pts = [point(cos(ϕ)*sin(θ), sin(ϕ)*sin(θ), cos(θ)) for ϕ in Φ for θ in Θ] +near1 = potential(MWFarField3D(wavenumber=κ), pts, u1, X) +near2 = potential(MWFarField3D(wavenumber=κ), pts, u2, X) + +using Plots +@show norm(u1-u2) +# Plots.plot(title="far field") +Plots.plot!(Θ, real.(getindex.(near1,1)), label="no preconditioner") +Plots.scatter!(Θ, real.(getindex.(near2,1)), label="Calderon preconditioner") \ No newline at end of file diff --git a/examples/junction_classic.jl b/examples/junction_classic.jl index 0c96fa26..f5e77789 100644 --- a/examples/junction_classic.jl +++ b/examples/junction_classic.jl @@ -19,8 +19,8 @@ u = gmres(efie) Θ, Φ = range(0.0,stop=π,length=100), 0.0 ffpoints = [point(cos(ϕ)*sin(θ), sin(ϕ)*sin(θ), cos(θ)) for θ in Θ for ϕ in Φ] -farfield = potential(MWFarField3D(κ*im), ffpoints, u, X) +farfield = potential(MWFarField3D(wavenumber=κ), ffpoints, u, X) using Plots using LinearAlgebra -plot(Θ,norm.(farfield)) +Plots.plot(Θ,norm.(farfield)) diff --git a/examples/lagc0d2.jl b/examples/lagc0d2.jl new file mode 100644 index 00000000..bc39d067 --- /dev/null +++ b/examples/lagc0d2.jl @@ -0,0 +1,28 @@ +using CompScienceMeshes +using BEAST +using LinearAlgebra + +import Plotly + +m = meshsphere(radius=1.0, h=0.25) +nodes = skeleton(m,0) +edges = skeleton(m,1) + +X = BEAST.lagrangec0d2(m, nodes, edges) +Y = BEAST.lagrangecxd0(m) + +uⁱ = Helmholtz3D.planewave(wavenumber=1.0, direction=point(0,0,1)) +f = strace(uⁱ,m) +fy = assemble(f,Y) + +Id = BEAST.Identity() +qs = BEAST.SingleNumQStrat(8) +Gxy = assemble(Id, X, Y, quadstrat=qs) +Gxx = assemble(Id, X, X, quadstrat=qs) +Gyy = assemble(Id, Y, Y, quadstrat=qs) + +Pxy = inv(Matrix(Gxx)) * Gxy * inv(Matrix(Gyy)) + +fX = Pxy * fy +fcr, geo = facecurrents(fX, X) +Plotly.plot(patch(m, real.(fcr))) \ No newline at end of file diff --git a/examples/mfie.jl b/examples/mfie.jl index d49f424f..c5f14469 100644 --- a/examples/mfie.jl +++ b/examples/mfie.jl @@ -1,6 +1,6 @@ using CompScienceMeshes, BEAST -Γ = readmesh(joinpath(dirname(@__FILE__),"sphere2.in")) +Γ = readmesh(joinpath(dirname(pathof(BEAST)),"../examples/sphere2.in")) X, Y = raviartthomas(Γ), buffachristiansen(Γ) ϵ, μ, ω = 1.0, 1.0, 1.0; κ, η = ω * √(ϵ*μ), √(μ/ϵ) diff --git a/examples/mfie_bdm.jl b/examples/mfie_bdm.jl index 4040a81e..d3380659 100644 --- a/examples/mfie_bdm.jl +++ b/examples/mfie_bdm.jl @@ -1,17 +1,23 @@ using CompScienceMeshes, BEAST -Γ = readmesh(joinpath(dirname(@__FILE__),"sphere2.in")) -X, Y = raviartthomas(Γ), buffachristiansen(Γ) +# Γ = readmesh(joinpath(dirname(pathof(BEAST)),"../examples/sphere2.in")) +Γ = meshsphere(radius=1.0, h=0.1) +# X, Y = raviartthomas(Γ), buffachristiansen(Γ) +X = brezzidouglasmarini(Γ) +Y = brezzidouglasmarini(Γ) +# X = raviartthomas(Γ) +# Y = raviartthomas(Γ) ϵ, μ, ω = 1.0, 1.0, 1.0; κ = ω * √(ϵ*μ) -NK, Id = BEAST.DoubleLayerRotatedMW3D(im*κ), Identity() +# κ = 3.0 +NK, Id = BEAST.DoubleLayerRotatedMW3D(1.0, im*κ), Identity() E = Maxwell3D.planewave(direction=ẑ, polarization=x̂, wavenumber=κ) H = -1/(im*μ*ω)*curl(E) -h = (n × H) × n +h = n × H @hilbertspace j @hilbertspace m -mfie = @discretise (K+0.5N)[m,j] == h[m] j∈X m∈Y +mfie = @discretise (NK-0.5Id)[m,j] == h[m] j∈X m∈Y u = gmres(mfie) include("utils/postproc.jl") diff --git a/examples/mfie_gwp2.jl b/examples/mfie_gwp2.jl new file mode 100644 index 00000000..ccef8606 --- /dev/null +++ b/examples/mfie_gwp2.jl @@ -0,0 +1,25 @@ +using CompScienceMeshes, BEAST + +# Γ = readmesh(joinpath(dirname(pathof(BEAST)),"../examples/sphere2.in")) +Γ = meshsphere(radius=1.0, h=0.2) +X = BEAST.gwpdiv(Γ; order=3) +Y = BEAST.gwpdiv(Γ; order=3) + +ϵ, μ, ω = 1.0, 1.0, 1.0; κ = ω * √(ϵ*μ) +# κ = 3.0 +NK, Id = BEAST.DoubleLayerRotatedMW3D(1.0, im*κ), Identity() +E = Maxwell3D.planewave(direction=ẑ, polarization=x̂, wavenumber=κ) +H = -1/(im*μ*ω)*curl(E) +h = n × H + +BEAST.@defaultquadstrat (NK,X,X) BEAST.DoubleNumSauterQstrat(7,8,6,6,6,6) +BEAST.@defaultquadstrat (Id,X,X) BEAST.SingleNumQStrat(7) +BEAST.@defaultquadstrat (h,X) BEAST.SingleNumQStrat(12) + +@hilbertspace j +@hilbertspace m +mfie = @discretise (NK-0.5Id)[m,j] == h[m] j∈X m∈Y +u = gmres(mfie) + +include("utils/postproc.jl") +include("utils/plotresults.jl") diff --git a/examples/mfie_monopolar.jl b/examples/mfie_monopolar.jl index ea2c428d..21cf4816 100644 --- a/examples/mfie_monopolar.jl +++ b/examples/mfie_monopolar.jl @@ -1,6 +1,6 @@ using CompScienceMeshes, BEAST -Γ = readmesh(joinpath(dirname(@__FILE__),"sphere2.in")) +Γ = readmesh(joinpath(dirname(pathof(BEAST)),"../examples/sphere2.in")) X = raviartthomas(Γ) Y = buffachristiansen(Γ) Z = raviartthomas(Γ, BEAST.Continuity{:none}) @@ -29,9 +29,11 @@ fcr2, _ = facecurrents(u2, Z) using LinearAlgebra using Plots -plot(title="Compare current density") -plot!(norm.(fcr1), label="MxMIFE") -scatter!(norm.(fcr2), label="DGMFIE", c=:red) +Plots.plot(title="Compare current density") +Plots.plot!(norm.(fcr1), label="MxMIFE") +Plots.scatter!(norm.(fcr2), label="DGMFIE", c=:red) +u = u2 +X = Z include("utils/postproc.jl") include("utils/plotresults.jl") diff --git a/examples/mthelmholtz.jl b/examples/mthelmholtz.jl index 17e2b26b..270fbe25 100644 --- a/examples/mthelmholtz.jl +++ b/examples/mthelmholtz.jl @@ -70,33 +70,7 @@ w1 = eigvals(Sxx) w2 = eigvals(Q) using Plots -plot() -scatter!(w1) -scatter!(w2) -# u = gmres(mtefie) -# -# offset = 1; u12 = u[offset:offset+numfunctions(X12)-1] -# offset += numfunctions(X12); u23 = u[offset:offset+numfunctions(X23)-1] -# offset += numfunctions(X23); u31 = u[offset:offset+numfunctions(X31)-1] -# -# fcr12, _ = facecurrents(u12, X12) -# fcr23, _ = facecurrents(u23, X23) -# fcr31, _ = facecurrents(u31, X31) - -# import PlotlyJS -using LinearAlgebra -# p1 = PlotlyJS.Plot(patch(G12, norm.(fcr12))) -# p2 = PlotlyJS.Plot(patch(G23, norm.(fcr23))) -# p3 = PlotlyJS.Plot(patch(G31, norm.(fcr31))) - -# PlotlyJS.plot([p1, p2, p3]) - - - - -Sxx = BEAST.sysmatrix(mtefie) - -Syy = assemble(SL, Y, Y) -iNxy = inv(Nxy) +Plots.plot() +Plots.scatter!(w1) +Plots.scatter!(w2) -# gmres() diff --git a/examples/mtjunction.jl b/examples/mtjunction.jl index ea35748a..125ed771 100644 --- a/examples/mtjunction.jl +++ b/examples/mtjunction.jl @@ -43,7 +43,7 @@ mtefie = @discretise( # fcr23, _ = facecurrents(u23, X23) # fcr31, _ = facecurrents(u31, X31) -import PlotlyJS +import Plotly using LinearAlgebra # p1 = PlotlyJS.Plot(patch(G12, norm.(fcr12))) # p2 = PlotlyJS.Plot(patch(G23, norm.(fcr23))) @@ -76,10 +76,11 @@ Nxy = blkdiagm(N1,N2,N3) Syy = assemble(SL, Y, Y) iNxy = inv(Nxy) -cond(Sxx) +# cond(Matrix(Sxx)) ex = assemble(e,X) -Q = transpose(iNxy) * Syy * iNxy * Sxx; -R = transpose(iNxy) * Syy * iNxy * ex; +P = transpose(iNxy) * Syy * iNxy +Q = P * Sxx; +R = P * ex; u1, ch1 = solve(BEAST.GMRESSolver(Sxx),ex) @@ -88,3 +89,48 @@ u2, ch2 = solve(BEAST.GMRESSolver(Q),R) @show ch1.iters @show ch2.iters # gmres() + +ns = [ + 0, + numfunctions(X12), + numfunctions(X23), + numfunctions(X31)] + +cns = cumsum(ns) +u12 = u1[cns[1]+1:cns[2]] +u23 = u1[cns[2]+1:cns[3]] +u31 = u1[cns[3]+1:cns[4]] + +fcr1, geo1 = facecurrents(u12, X12) +fcr2, geo2 = facecurrents(u23, X23) +fcr3, geo3 = facecurrents(u31, X31) + +p1 = patch(geo1, norm.(fcr1)) +p2 = patch(geo2, norm.(fcr2)) +p3 = patch(geo3, norm.(fcr3)) + +Plotly.plot([p1,p2,p3]) + +G123 = weld(G1,G2,G3) +X123 = raviartthomas(G123) + +stefie = @discretise( + SL[k,j] == e[k], + j ∈ X123, k ∈ X123) + +Sst = assemble(SL, X123, X123) +bst = assemble(e, X123) +ust, chst = solve(BEAST.GMRESSolver(Sst),bst) + +fcrst, geost = facecurrents(ust, X123) +Plotly.plot(patch(geost, norm.(fcrst))) + + +Φ, Θ = [0.0], range(0,stop=π,length=100) +pts = [point(cos(ϕ)*sin(θ), sin(ϕ)*sin(θ), cos(θ)) for ϕ in Φ for θ in Θ] + +ffd_mt = potential(MWFarField3D(wavenumber=κ), pts, u1, X) +ffd_st = potential(MWFarField3D(wavenumber=κ), pts, ust, X123) + +Plots.plot(norm.(ffd_mt)) +Plots.scatter!(norm.(ffd_st)) \ No newline at end of file diff --git a/examples/mueller.jl b/examples/mueller.jl new file mode 100644 index 00000000..5c95731a --- /dev/null +++ b/examples/mueller.jl @@ -0,0 +1,88 @@ +using CompScienceMeshes, BEAST + + +T = tetmeshsphere(1.0,0.3) +X = nedelecc3d(T) +Γ = boundary(T) + +X, Y = raviartthomas(Γ), buffachristiansen(Γ) + +ω = 1.0 +ϵ, ϵ′ = 1.0, 5.0 +μ, μ′ = 1.0, 1.0 +κ, η = ω*√(ϵ*μ), √(μ/ϵ) +κ′, η′ = ω*√(ϵ′*μ′), √(μ′/ϵ′) + + + +N = NCross() + +T = Maxwell3D.singlelayer(wavenumber=κ) +T′ = Maxwell3D.singlelayer(wavenumber=κ′) +K = Maxwell3D.doublelayer(wavenumber=κ) +K′ = Maxwell3D.doublelayer(wavenumber=κ′) + +E = Maxwell3D.planewave(direction=ẑ, polarization=x̂, wavenumber=κ) +H = -1/(im*κ*η)*curl(E) +e, h = (n × E) × n, (n × H) × n + +@hilbertspace j m +@hilbertspace k l + +α, α′ = μ/η, μ′/η′ +mueller = @discretise( + (ϵ*η*T-ϵ′*η′*T′)[k,j] - ((ϵ+ϵ′)/2*N+ϵ*K-ϵ′*K′)[k,m] + +((μ+μ′)/2*N+μ*K-μ′*K′)[l,j] + (α*T-α′*T′)[l,m] == -ϵ*e[k] - μ*h[l], +j∈X, m∈X, k∈Y, l∈Y) +u,A_mueller,rhs_mueller = solve(mueller) + +#postprocessing +using Plots +import Plotly +using LinearAlgebra +fcrj, _ = facecurrents(u[j],X) +fcrm, _ = facecurrents(u[m],X) +Plotly.plot(patch(Γ, norm.(fcrj)),Plotly.Layout(title="j Mueller")) +Plotly.plot(patch(Γ, norm.(fcrm)),Plotly.Layout(title="m Mueller")) + + +function nearfield(um,uj,Xm,Xj,κ,η,points, + Einc=(x->point(0,0,0)), + Hinc=(x->point(0,0,0))) + + K = BEAST.MWDoubleLayerField3D(wavenumber=κ) + T = BEAST.MWSingleLayerField3D(wavenumber=κ) + + Em = potential(K, points, um, Xm) + Ej = potential(T, points, uj, Xj) + E = -Em + η * Ej + Einc.(points) + + Hm = potential(T, points, um, Xm) + Hj = potential(K, points, uj, Xj) + H = 1/η*Hm + Hj + Hinc.(points) + + return E, H +end + + +Z = range(-1,1,length=100) +Y = range(-1,1,length=100) +nfpoints = [point(0,y,z) for z in Z, y in Y] + +import Base.Threads: @spawn +task1 = @spawn nearfield(u[m],u[j],X,X,κ,η,nfpoints,E,H) +task2 = @spawn nearfield(-u[m],-u[j],X,X,κ′,η′,nfpoints) + +E_ex, H_ex = fetch(task1) +E_in, H_in = fetch(task2) + +E_tot = E_in + E_ex +H_tot = H_in + H_ex + +contour(real.(getindex.(E_tot,1))) +contour(real.(getindex.(H_tot,2))) + +heatmap(Z, Y, real.(getindex.(E_tot,1)),title="E_tot Mueller") +#heatmap(Z, Y, real.(getindex.(H_tot,2))) +#heatmap(Z, Y, real.(getindex.(E_in,1))) +#heatmap(Z, Y, real.(getindex.(E_ex,1))) \ No newline at end of file diff --git a/examples/nitsche.jl b/examples/nitsche.jl index 980e9545..9574b34e 100644 --- a/examples/nitsche.jl +++ b/examples/nitsche.jl @@ -11,12 +11,28 @@ width, height = 1.0, 0.5 γ = meshsegment(width, width, 3) κ = 1.0 -S, T, I = SingleLayerTrace(κ), MWSingleLayer3D(κ), Identity() +S, T, I = SingleLayerTrace(κ*im), MWSingleLayer3D(κ), Identity() St = transpose(S) E = Maxwell3D.planewave(direction=ẑ, polarization=x̂, wavenumber=κ) e = (n×E)×n -X1, X2, X3 = raviartthomas(Γ1, γ), raviartthomas(Γ2, γ), raviartthomas(Γ3, γ) +in_interior1 = CompScienceMeshes.interior_tpredicate(Γ1) +in_interior2 = CompScienceMeshes.interior_tpredicate(Γ2) +in_interior3 = CompScienceMeshes.interior_tpredicate(Γ3) + +on_junction = CompScienceMeshes.overlap_gpredicate(γ) +edges1 = submesh((m,e)->in_interior1(m,e) || on_junction(chart(m,e)), skeleton(Γ1,1)) +edges2 = submesh((m,e)->in_interior2(m,e) || on_junction(chart(m,e)), skeleton(Γ2,1)) +edges3 = submesh((m,e)->in_interior3(m,e) || on_junction(chart(m,e)), skeleton(Γ3,1)) + +# edges1 = skeleton(e -> in_interior1(e) || on_junction(chart(Γ1,e)), Γ1,1) +# edges2 = skeleton(e -> in_interior2(e) || on_junction(chart(Γ2,e)), Γ2,1) +# edges3 = skeleton(e -> in_interior3(e) || on_junction(chart(Γ3,e)), Γ3,1) + +X1 = raviartthomas(Γ1, edges1) +X2 = raviartthomas(Γ2, edges2) +X3 = raviartthomas(Γ3, edges3) +# X1, X2, X3 = raviartthomas(Γ1, γ), raviartthomas(Γ2, γ), raviartthomas(Γ3, γ) X = X1 × X2 × X3 @hilbertspace j @@ -24,11 +40,18 @@ X = X1 × X2 × X3 α, β = 1/(im*κ), log(abs(h))/(im*κ) Eq = @varform T[k,j] + α*S[trc(k), dvg(j)] + α*St[dvg(k), trc(j)] + β*I[trc(k), trc(j)] == e[k] eq = @discretise Eq j∈X k∈X + +# trcX = trc(X) +# dvgX = dvg(X) +# Q1 = assemble(Eq.lhs.terms[2].kernel, trcX, dvgX; threading=BEAST.Threading{:single}) +# Q2 = assemble(Eq.lhs.terms[3].kernel, dvgX, trcX; threading=BEAST.Threading{:single}) + u = solve(eq) + Θ, Φ = range(0.0,stop=π,length=100), 0.0 ffpoints = [point(cos(ϕ)*sin(θ), sin(ϕ)*sin(θ), cos(θ)) for θ in Θ for ϕ in Φ] -farfield = potential(MWFarField3D(κ*im), ffpoints, u, X) +farfield = potential(MWFarField3D(wavenumber=κ), ffpoints, u, X) using Plots using LinearAlgebra diff --git a/examples/nonconfmeshes.jl b/examples/nonconfmeshes.jl new file mode 100644 index 00000000..fa38d391 --- /dev/null +++ b/examples/nonconfmeshes.jl @@ -0,0 +1,184 @@ +using CompScienceMeshes +using BEAST + +Γ = readmesh(joinpath(dirname(pathof(BEAST)),"../examples/sphere2.in")) +Γref = CompScienceMeshes.barycentric_refinement(Γ) +X = raviartthomas(Γ, BEAST.Continuity{:none}) +Y = raviartthomas(Γref, BEAST.Continuity{:none}) + +# X = subset(X,1690:1692) +# Y = subset(Y,10141:10143) + +# X = subset(X,[1692]) +# Y = subset(Y,[10147]) + +# Y = buffachristiansen(Γ) + +κ, η = 1.0, 1.0 +t = Maxwell3D.singlelayer(wavenumber=κ) +E = Maxwell3D.planewave(direction=ẑ, polarization=x̂, wavenumber=κ) +e = (n × E) × n + +g = 6 +# ssstrat(g) = BEAST.DoubleNumSauterQstrat(7, 6, g, g, g, g) +ssstrat(g) = BEAST.CommonFaceVertexSauterCommonEdgeWiltonPostitiveDistanceNumQStrat( + 7, 6, 10, 8, g, g, g+3, g) + +qs1 = ssstrat(g) +qs2 = BEAST.NonConformingIntegralOpQStrat(ssstrat(g)) + +A1 = assemble(t,Y,X, quadstrat=qs1, threading=BEAST.Threading{:single}) +A2 = assemble(t,Y,X, quadstrat=qs2, threading=BEAST.Threading{:single}) + +# @enter assemble(t,Y,X, quadstrat=qs2, threading=BEAST.Threading{:single}) + +import Plots +Plots.plotly() +step = 1 +rowidx = 10894 +Plots.plot(imag.(A1[rowidx,1:step:end])) +Plots.scatter!(imag.(A2[rowidx,1:step:end])) +val, pos = findmax(abs.(A1-A2)) + +rowidx = Y.fns[pos[1]][1].cellid +colidx = X.fns[pos[2]][1].cellid + +τ = chart(Γref, rowidx) +σ = chart(Γ, colidx) + +# τ = [ +# [0.18517788982868066, 0.05998671122432393, 0.9735312677690339], +# [0.1647116144395066, 0.0050896055947335095, 0.9807011064756881], +# [0.245326672427806, -0.06121735585211891, 0.9675056894602609]] + +# σ = [ +# [0.1133762981558132, -0.1176791567283826, 0.9865583769286949], +# [0.1647116144395066, 0.0050896055947335025, 0.9807011064756881], +# [0.08409655645120719, 0.07139656704158594, 0.9938965234911153]] + +# eτ = simplex( +# point(0.1647116144395066, 0.0050896055947335095, 0.9807011064756881), +# point(0.245326672427806, -0.06121735585211891, 0.9675056894602609)) + +# eσ = simplex( +# point(0.1647116144395066, 0.0050896055947335025, 0.9807011064756881), +# point(0.08409655645120719, 0.07139656704158594, 0.9938965234911153)) + +# CompScienceMeshes.overlap(eτ, eσ) + +# τ = τ.vertices +# σ = σ.vertices + +import Plotly +function Plotly.mesh3d(a::Vector{<:CompScienceMeshes.Simplex}; opacity=0.5, kwargs...) + T = coordtype(a[1]) + n = length(a) + X = Vector{T}(undef, 3*n) + Y = Vector{T}(undef, 3*n) + Z = Vector{T}(undef, 3*n) + for i in 1:n + X[3*(i-1)+1:3*i] = getindex.(a[i].vertices, 1) + Y[3*(i-1)+1:3*i] = getindex.(a[i].vertices, 2) + Z[3*(i-1)+1:3*i] = getindex.(a[i].vertices, 3) + end + + I = collect(0:3:3*(n-1)) + J = I .+ 1 + K = I .+ 2 + + return Plotly.mesh3d(x=X,y=Y,z=Z,i=I,j=J,k=K; opacity, kwargs...) +end + +m1 = Plotly.mesh3d([τ], opacity=0.5) +m2 = Plotly.mesh3d([σ], opacity=0.5) + + +# m1 = Plotly.mesh3d( +# x=getindex.(τ,1), +# y=getindex.(τ,2), +# z=getindex.(τ,3), +# i=[0], j=[1], k=[2]) +# m2 = Plotly.mesh3d( +# x=getindex.(σ,1), +# y=getindex.(σ,2), +# z=getindex.(σ,3), +# i=[0], j=[1], k=[2]) +Plotly.plot([m1,m2]) + + + + +isct1, clps1 = CompScienceMeshes.intersection_keep_clippings(τ,σ) +isct2, clps2 = CompScienceMeshes.intersection_keep_clippings(σ,τ) + + +m3 = Plotly.mesh3d(isct1, opacity=0.5, color="blue") +m4 = Plotly.mesh3d(isct2, opacity=0.5, color="red") +Plotly.plot([m3,m4]) + +m5 = Plotly.mesh3d(clps2[1], opacity=0.5, color="green") +m6 = Plotly.mesh3d(clps2[1][[1]], opacity=0.5, color="yellow") +Plotly.plot([m3,m6]) + +p = isct1[1] +q = clps2[1][1] +for (i,λ) in pairs(faces(p)) + for (j,μ) in pairs(faces(q)) + if CompScienceMeshes.overlap(λ, μ) + global gi = i + global gj = j + global qr = BEAST.NonConformingTouchQRule(qs1, i, j) +end end end + +𝒳 = refspace(X) +𝒴 = refspace(Y) +out10 = zeros(ComplexF64, 3, 3) +BEAST.momintegrals!(t, 𝒴, 𝒳, p, q, out10, BEAST.NonConformingTouchQRule(ssstrat(10), gi, gj)) +out20 = zeros(ComplexF64, 3, 3) +BEAST.momintegrals!(t, 𝒴, 𝒳, p, q, out20, BEAST.NonConformingTouchQRule(ssstrat(20), gi, gj)) + +@show out10[1,1] out20[1,1] + +τs, σs = BEAST._conforming_refinement_touching_triangles(p,q,2,3) +@assert length(τs) == 1 +@assert length(σs) == 2 +@assert volume(p) ≈ sum(volume.(τs)) +@assert volume(q) ≈ sum(volume.(σs)) +@assert all(volume.(τs) .> eps(Float64)*1e3) +@assert all(volume.(σs) .> eps(Float64)*1e3) +m7 = Plotly.mesh3d(τs[[1]], color="blue", opacity=0.5) +m8 = Plotly.mesh3d(σs[[1]], color="red", opacity=0.5) +Plotly.plot([m7,m8]) + +out1_10 = zeros(ComplexF64, 3, 3) +qs = ssstrat(10) +qd = quaddata(t, 𝒴, 𝒳, τs, σs, qs) +qr = quadrule(t, 𝒴, 𝒳, 1, τs[1], 1, σs[1], qd, qs) +BEAST.momintegrals!(t, 𝒴, 𝒳, τs[1], σs[1], out1_10, qr) +out1_20 = zeros(ComplexF64, 3, 3) +qs = ssstrat(20) +qd = quaddata(t, 𝒴, 𝒳, τs, σs, qs) +qr = quadrule(t, 𝒴, 𝒳, 1, τs[1], 1, σs[1], qd, qs) +BEAST.momintegrals!(t, 𝒴, 𝒳, τs[1], σs[1], out1_20, qr) +@show out1_10[1,1] out1_20[1,1] + +out2_10 = zeros(ComplexF64, 3, 3) +qs = ssstrat(10) +qd = quaddata(t, 𝒴, 𝒳, τs, σs, qs) +qr = quadrule(t, 𝒴, 𝒳, 1, τs[1], 1, σs[2], qd, qs) +BEAST.momintegrals!(t, 𝒴, 𝒳, τs[1], σs[2], out2_10, qr) +out2_20 = zeros(ComplexF64, 3, 3) +qs = ssstrat(20) +qd = quaddata(t, 𝒴, 𝒳, τs, σs, qs) +qr = quadrule(t, 𝒴, 𝒳, 1, τs[1], 2, σs[2], qd, qs) +BEAST.momintegrals!(t, 𝒴, 𝒳, τs[1], σs[2], out2_20, qr) +@show out2_10[1,1] out2_20[1,1] + + +out_ref = zeros(ComplexF64, 3, 3) +qrss = BEAST.SauterSchwabQuadrature.CommonVertex(g) +BEAST.momintegrals_test_refines_trial!(out_ref, t, + Y, rowidx, p, + X, colidx, q, + qrss, qs1) +@show out_ref \ No newline at end of file diff --git a/examples/nonconfmeshes_general.jl b/examples/nonconfmeshes_general.jl new file mode 100644 index 00000000..13af3ab8 --- /dev/null +++ b/examples/nonconfmeshes_general.jl @@ -0,0 +1,240 @@ +using CompScienceMeshes +using BEAST + +# Γ = readmesh(joinpath(dirname(pathof(BEAST)),"../examples/sphere2.in")) +# Γref = CompScienceMeshes.barycentric_refinement(Γ) + +# h = 0.2*0.7 +# M1 = meshcuboid(0.5, 1.0, 1.0, h) +# M2 = meshcuboid(0.5, 1.0, 1.0, 1.2*h) +# M2 = CompScienceMeshes.translate(M2, point(-0.5, 0, 0)) +M1 = readmesh(joinpath(dirname(pathof(BEAST)),"../examples/assets/M1.in")) +M2 = readmesh(joinpath(dirname(pathof(BEAST)),"../examples/assets/M2.in")) + +import Plotly +Plotly.plot([ + patch(M1,color="red", opacity=0.5), patch(M2,color="blue", opacity=0.5), + wireframe(M1), wireframe(M2)]) + + +X = raviartthomas(M1, BEAST.Continuity{:none}) +Y = raviartthomas(M2, BEAST.Continuity{:none}) + +# X = subset(X,1690:1692) +# Y = subset(Y,10141:10143) + +# X = subset(X,[1692]) +# Y = subset(Y,[10147]) + +# Y = buffachristiansen(Γ) + +κ, η = 1.0, 1.0 +t = Maxwell3D.singlelayer(wavenumber=κ) +E = Maxwell3D.planewave(direction=ẑ, polarization=x̂, wavenumber=κ) +e = (n × E) × n + +g = 6 +# ssstrat(g) = BEAST.DoubleNumSauterQstrat(7, 6, g, g, g, g) +ssstrat(g) = BEAST.DoubleNumWiltonSauterQStrat( + 7, 6, 10, 8, g, g, g+3, g) + +qs1 = BEAST.DoubleNumWiltonSauterQStrat( + 2, 3, 6, 7, 5, 5, 4, 3) +qs2 = BEAST.NonConformingIntegralOpQStrat(qs1) + +# A1 = assemble(t,Y,X, quadstrat=qs1, threading=BEAST.Threading{:single}) +A2 = assemble(t,Y,X, quadstrat=qs2, threading=BEAST.Threading{:single}) +A3 = assemble(t,X,Y, quadstrat=qs2, threading=BEAST.Threading{:single}) + +error() + +# @enter assemble(t,Y,X, quadstrat=qs2, threading=BEAST.Threading{:single}) + +import Plots +Plots.plotly() +step = 1 +rowidx = 10894 +Plots.plot(imag.(A1[rowidx,1:step:end])) +Plots.scatter!(imag.(A2[rowidx,1:step:end])) +val, pos = findmax(abs.(A1-A2)) + +rowidx = Y.fns[pos[1]][1].cellid +colidx = X.fns[pos[2]][1].cellid + +τ = chart(Γref, rowidx) +σ = chart(Γ, colidx) + +# τ = [ +# [0.18517788982868066, 0.05998671122432393, 0.9735312677690339], +# [0.1647116144395066, 0.0050896055947335095, 0.9807011064756881], +# [0.245326672427806, -0.06121735585211891, 0.9675056894602609]] + +# σ = [ +# [0.1133762981558132, -0.1176791567283826, 0.9865583769286949], +# [0.1647116144395066, 0.0050896055947335025, 0.9807011064756881], +# [0.08409655645120719, 0.07139656704158594, 0.9938965234911153]] + +# eτ = simplex( +# point(0.1647116144395066, 0.0050896055947335095, 0.9807011064756881), +# point(0.245326672427806, -0.06121735585211891, 0.9675056894602609)) + +# eσ = simplex( +# point(0.1647116144395066, 0.0050896055947335025, 0.9807011064756881), +# point(0.08409655645120719, 0.07139656704158594, 0.9938965234911153)) + +# CompScienceMeshes.overlap(eτ, eσ) + +# τ = τ.vertices +# σ = σ.vertices + +import Plotly +function Plotly.mesh3d(a::Vector{<:CompScienceMeshes.Simplex}; opacity=0.5, kwargs...) + T = coordtype(a[1]) + n = length(a) + X = Vector{T}(undef, 3*n) + Y = Vector{T}(undef, 3*n) + Z = Vector{T}(undef, 3*n) + for i in 1:n + X[3*(i-1)+1:3*i] = getindex.(a[i].vertices, 1) + Y[3*(i-1)+1:3*i] = getindex.(a[i].vertices, 2) + Z[3*(i-1)+1:3*i] = getindex.(a[i].vertices, 3) + end + + I = collect(0:3:3*(n-1)) + J = I .+ 1 + K = I .+ 2 + + return Plotly.mesh3d(x=X,y=Y,z=Z,i=I,j=J,k=K; opacity, kwargs...) +end + +m1 = Plotly.mesh3d([τ], opacity=0.5) +m2 = Plotly.mesh3d([σ], opacity=0.5) + + +# m1 = Plotly.mesh3d( +# x=getindex.(τ,1), +# y=getindex.(τ,2), +# z=getindex.(τ,3), +# i=[0], j=[1], k=[2]) +# m2 = Plotly.mesh3d( +# x=getindex.(σ,1), +# y=getindex.(σ,2), +# z=getindex.(σ,3), +# i=[0], j=[1], k=[2]) +Plotly.plot([m1,m2]) + + + + +isct1, clps1 = CompScienceMeshes.intersection_keep_clippings(τ,σ) +isct2, clps2 = CompScienceMeshes.intersection_keep_clippings(σ,τ) + + +m3 = Plotly.mesh3d(isct1, opacity=0.5, color="blue") +m4 = Plotly.mesh3d(isct2, opacity=0.5, color="red") +Plotly.plot([m3,m4]) + +m5 = Plotly.mesh3d(clps2[1], opacity=0.5, color="green") +m6 = Plotly.mesh3d(clps2[1][[1]], opacity=0.5, color="yellow") +Plotly.plot([m3,m6]) + +p = isct1[1] +q = clps2[1][1] +for (i,λ) in pairs(faces(p)) + for (j,μ) in pairs(faces(q)) + if CompScienceMeshes.overlap(λ, μ) + global gi = i + global gj = j + global qr = BEAST.NonConformingTouchQRule(qs1, i, j) +end end end + +𝒳 = refspace(X) +𝒴 = refspace(Y) +out10 = zeros(ComplexF64, 3, 3) +BEAST.momintegrals!(t, 𝒴, 𝒳, p, q, out10, BEAST.NonConformingTouchQRule(ssstrat(10), gi, gj)) +out20 = zeros(ComplexF64, 3, 3) +BEAST.momintegrals!(t, 𝒴, 𝒳, p, q, out20, BEAST.NonConformingTouchQRule(ssstrat(20), gi, gj)) + +@show out10[1,1] out20[1,1] + +τs, σs = BEAST._conforming_refinement_touching_triangles(p,q,2,3) +@assert length(τs) == 1 +@assert length(σs) == 2 +@assert volume(p) ≈ sum(volume.(τs)) +@assert volume(q) ≈ sum(volume.(σs)) +@assert all(volume.(τs) .> eps(Float64)*1e3) +@assert all(volume.(σs) .> eps(Float64)*1e3) +m7 = Plotly.mesh3d(τs[[1]], color="blue", opacity=0.5) +m8 = Plotly.mesh3d(σs[[1]], color="red", opacity=0.5) +Plotly.plot([m7,m8]) + +out1_10 = zeros(ComplexF64, 3, 3) +qs = ssstrat(10) +qd = quaddata(t, 𝒴, 𝒳, τs, σs, qs) +qr = quadrule(t, 𝒴, 𝒳, 1, τs[1], 1, σs[1], qd, qs) +BEAST.momintegrals!(t, 𝒴, 𝒳, τs[1], σs[1], out1_10, qr) +out1_20 = zeros(ComplexF64, 3, 3) +qs = ssstrat(20) +qd = quaddata(t, 𝒴, 𝒳, τs, σs, qs) +qr = quadrule(t, 𝒴, 𝒳, 1, τs[1], 1, σs[1], qd, qs) +BEAST.momintegrals!(t, 𝒴, 𝒳, τs[1], σs[1], out1_20, qr) +@show out1_10[1,1] out1_20[1,1] + +out2_10 = zeros(ComplexF64, 3, 3) +qs = ssstrat(10) +qd = quaddata(t, 𝒴, 𝒳, τs, σs, qs) +qr = quadrule(t, 𝒴, 𝒳, 1, τs[1], 1, σs[2], qd, qs) +BEAST.momintegrals!(t, 𝒴, 𝒳, τs[1], σs[2], out2_10, qr) +out2_20 = zeros(ComplexF64, 3, 3) +qs = ssstrat(20) +qd = quaddata(t, 𝒴, 𝒳, τs, σs, qs) +qr = quadrule(t, 𝒴, 𝒳, 1, τs[1], 2, σs[2], qd, qs) +BEAST.momintegrals!(t, 𝒴, 𝒳, τs[1], σs[2], out2_20, qr) +@show out2_10[1,1] out2_20[1,1] + + +out_ref = zeros(ComplexF64, 3, 3) +qrss = BEAST.SauterSchwabQuadrature.CommonVertex(g) +BEAST.momintegrals_test_refines_trial!(out_ref, t, + Y, rowidx, p, + X, colidx, q, + qrss, qs1) +@show out_ref + + +τ = simplex( + point(0.0, 0.0, 0.1666666666673599), + point(-0.12200846792768633, 0.0, 0.1220084679279961), + point(0.0, 0.0, 0.0)) + +σ = simplex( + point(0.0, 0.0, 0.1249999999997732), + point(0.0, 0.0, 0.2499999999994112), + point(0.0, 0.1038676364273151, 0.1864502994601513)) + +for (i,λ) in pairs(faces(τ)) + for (j,μ) in pairs(faces(σ)) + if CompScienceMeshes.overlap(λ, μ) + global gi = i + global gj = j + # global qr = BEAST.NonConformingTouchQRule(qs1, i, j) +end end end + + +τs, σs = BEAST._conforming_refinement_touching_triangles(τ,σ,gi,gj); +@assert volume(τ) ≈ sum(volume.(τs)) +@assert volume(σ) ≈ sum(volume.(σs)) + +Plotly.plot([Plotly.mesh3d(τs, color="red"), Plotly.mesh3d(σs, color="blue")]) +Plotly.plot([Plotly.mesh3d([τ], color="red"), Plotly.mesh3d([σ], color="blue")]) + + +τ = simplex( + point(0.0, 0.893151804804907, 0.4384102834058539), + point(0.0, 0.8931541172685571, 0.4383125008447698), + point(0.0, 0.8932364620235818, 0.43836004261125),) +σ = simplex( + point(0.0, 0.893151804804907, 0.4384102834058539), + point(0.0, 0.8931541172685571, 0.4383125008447698), + point(0.0, 0.8932364620235818, 0.43836004261125),) +CompScienceMeshes.overlap(τ, σ) \ No newline at end of file diff --git a/examples/nxmfie.jl b/examples/nxmfie.jl new file mode 100644 index 00000000..1599819d --- /dev/null +++ b/examples/nxmfie.jl @@ -0,0 +1,33 @@ +using CompScienceMeshes, BEAST + +# Γ = readmesh(joinpath(dirname(pathof(BEAST)),"../examples/sphere2.in")) +Γ = meshsphere(radius=1.0, h=0.15) +X, Y = raviartthomas(Γ), buffachristiansen(Γ) + +ϵ, μ, ω = 1.0, 1.0, 1.0; κ, η = ω * √(ϵ*μ), √(μ/ϵ) +# K, N = Maxwell3D.doublelayer(wavenumber=κ), NCross() +nxK = BEAST.DoubleLayerRotatedMW3D(1.0, κ*im) +Id = BEAST.Identity() +E = Maxwell3D.planewave(direction=ẑ, polarization=x̂, wavenumber=κ) +# E = -η/(im*κ)*BEAST.CurlCurlGreen(κ, ẑ, point(2,0,0)) +H = -1/(im*μ*ω)*curl(E) + +h = (n × H) × n +nxh = n × H +h = nxh × n + +@hilbertspace j +@hilbertspace m +mfie = @discretise (nxK-0.5Id)[m,j] == nxh[m] j∈X m∈X +u = solve(mfie) + +include("utils/postproc.jl") +include("utils/plotresults.jl") + +# freeze, store = BEAST.allocatestorage(nxK, X, X, Val{:bandedstorage}, BEAST.LongDelays{:compress}) +# @enter BEAST.assemble!(nxK, X, X, store, BEAST.Threading{:single}) +# Z1 = freeze() +# Z2 = assemble(Id, X, X) +# Z = Z1 - 0.5*Z2 +# b = assemble(nxh, X) +# u = Z \ b diff --git a/examples/planewave.jl b/examples/planewave.jl index d31905c1..3721685d 100644 --- a/examples/planewave.jl +++ b/examples/planewave.jl @@ -1,7 +1,7 @@ using CompScienceMeshes using BEAST -Γ = readmesh(joinpath(@__DIR__,"sphere2.in")) +Γ = readmesh(joinpath(dirname(pathof(BEAST)),"../examples/sphere2.in")) X = raviartthomas(Γ) Y = buffachristiansen(Γ, ibscaled=true) @@ -19,4 +19,4 @@ fcr, geo = facecurrents(x, Y) using Plotly using LinearAlgebra -Plotly.plot(patch(geo.mesh, norm.(fcr), (0.0, 1.0))) +Plotly.plot(patch(geo.mesh, norm.(fcr); caxis=(0.0, 1.0))) diff --git a/examples/pmchwt.jl b/examples/pmchwt.jl index 177eb81b..10fcc3ee 100644 --- a/examples/pmchwt.jl +++ b/examples/pmchwt.jl @@ -1,14 +1,48 @@ using CompScienceMeshes, BEAST using LinearAlgebra +using LiftedMaps +using BlockArrays -Γ = meshcuboid(0.5, 1.0, 1.0, 0.075) -CompScienceMeshes.translate!(Γ, point(-0.25, -0.5, -0.5)) -# Γ = meshsphere(1.0, 0.075) +T = CompScienceMeshes.tetmeshsphere(1.0,0.12) +X = BEAST.nedelecc3d(T) +Γ = boundary(T) + +function nearfield(um,uj,Xm,Xj,κ,η,points, + Einc=(x->point(0,0,0)), + Hinc=(x->point(0,0,0))) + + K = BEAST.MWDoubleLayerField3D(wavenumber=κ) + T = BEAST.MWSingleLayerField3D(wavenumber=κ) + + Em = potential(K, points, um, Xm) + Ej = potential(T, points, uj, Xj) + E = -Em + η * Ej + Einc.(points) + + Hm = potential(T, points, um, Xm) + Hj = potential(K, points, uj, Xj) + H = 1/η*Hm + Hj + Hinc.(points) + + return E, H +end + +ϵ0 = 8.854e-12 +μ0 = 4π*1e-7 +c = 1/√(ϵ0*μ0) + +λ = 2.9979563769321627 +ω = 2π*c/λ + +Ω = CompScienceMeshes.tetmeshsphere(λ,0.1*λ) +Γ = boundary(Ω) X = raviartthomas(Γ) @show numfunctions(X) +Y = BEAST.buffachristiansen2(Γ) -κ, η = 6.0, 1.0 -κ′, η′ = 2.4κ, η/2.4 + +κ, η = 1.0, 1.0 +κ′, η′ = √5.0κ, η/√5.0 + +N = NCross() T = Maxwell3D.singlelayer(wavenumber=κ) T′ = Maxwell3D.singlelayer(wavenumber=κ′) @@ -18,7 +52,8 @@ K′ = Maxwell3D.doublelayer(wavenumber=κ′) E = Maxwell3D.planewave(direction=ẑ, polarization=x̂, wavenumber=κ) H = -1/(im*κ*η)*curl(E) -e, h = (n × E) × n, (n × H) × n +e = (n × E) × n +h = (n × H) × n @hilbertspace j m @hilbertspace k l @@ -31,58 +66,108 @@ pmchwt = @discretise( u = solve(pmchwt) +#preconditioner +#= +Tyy = assemble(T,Y,Y); println("dual discretisation assembled.") +Nxy = Matrix(assemble(N,X,Y)); println("duality form assembled.") + +iNxy = inv(Nxy); println("duality form inverted.") +NTN = iNxy' * Tyy * iNxy + +M = zeros(Int, 2) +N = zeros(Int, 2) + +M[1] = M[2] = numfunctions(X) +N[1] = N[2] = numfunctions(X) + +U = BlockArrays.blockedrange(M) +V = BlockArrays.blockedrange(N) + +precond = BEAST.ZeroMap{Float32}(U, V) + +z1 = LiftedMap(NTN,Block(1),Block(1),U,V) +z2 = LiftedMap(NTN,Block(2),Block(2),U,V) +precond = precond + z1 + z2 + +A_pmchwt_precond = precond*A_pmchwt + +#GMREs +import IterativeSolvers +cT = promote_type(eltype(A_pmchwt), eltype(rhs)) +x = BlockedVector{cT}(undef, M) +fill!(x, 0) +x, ch = IterativeSolvers.gmres!(x, A_pmchwt, rhs, log=true, reltol=1e-6) +fill!(x, 0) +x, ch = IterativeSolvers.gmres!(x, precond*A_pmchwt, precond*rhs, log=true, reltol=1e-6) +=# Θ, Φ = range(0.0,stop=2π,length=100), 0.0 ffpoints = [point(cos(ϕ)*sin(θ), sin(ϕ)*sin(θ), cos(θ)) for θ in Θ for ϕ in Φ] # Don't forget the far field comprises two contributions -ffm = potential(MWFarField3D(κ*im), ffpoints, u[m], X) -ffj = potential(MWFarField3D(κ*im), ffpoints, u[j], X) -ff = η*im*κ*ffj + im*κ*cross.(ffpoints, ffj) +ffm = potential(MWFarField3D(gamma=κ*im), ffpoints, u[m], X) +ffj = potential(MWFarField3D(gamma=κ*im), ffpoints, u[j], X) +ff = -η*im*κ*ffj + im*κ*cross.(ffpoints, ffm) + +# Compare the far field and the field far +using Plots +ffradius = 100.0 +E_far, H_far = nearfield(u[m],u[j],X,X,κ,η, ffradius .* ffpoints) +nxE_far = cross.(ffpoints, E_far) * (4π*ffradius) / exp(-im*κ*ffradius) +Et_far = -cross.(ffpoints, nxE_far) + +Plots.plot() +Plots.plot!(Θ, norm.(ff)/η ,label="far field") +Plots.scatter!(Θ, norm.(Et_far), label="field far") using Plots -plot(xlabel="theta") -plot!(Θ,norm.(ff),label="far field") +Plots.plot(xlabel="theta") +Plots.plot!(Θ,norm.(ff),label="far field",title="PMCHWT") + import Plotly using LinearAlgebra fcrj, _ = facecurrents(u[j],X) fcrm, _ = facecurrents(u[m],X) -Plotly.plot(patch(Γ, norm.(fcrj))) -Plotly.plot(patch(Γ, norm.(fcrm))) - - -function nearfield(um,uj,Xm,Xj,κ,η,points, - Einc=(x->point(0,0,0)), - Hinc=(x->point(0,0,0))) +Plotly.plot(patch(Γ, norm.(fcrj)),Plotly.Layout(title="j PMCHWT")) +Plotly.plot(patch(Γ, norm.(fcrm)),Plotly.Layout(title="m PMCHWT")) - K = BEAST.MWDoubleLayerField3D(wavenumber=κ) - T = BEAST.MWSingleLayerField3D(wavenumber=κ) - Em = potential(K, points, um, Xm) - Ej = potential(T, points, uj, Xj) - E = -Em + η * Ej + Einc.(points) - Hm = potential(T, points, um, Xm) - Hj = potential(K, points, uj, Xj) - H = 1/η*Hm + Hj + Hinc.(points) - return E, H -end -Z = range(-1,1,length=100) -Y = range(-1,1,length=100) +Z = range(-6,6,length=200) +Y = range(-4,4,length=200) nfpoints = [point(0,y,z) for z in Z, y in Y] -E_ex, H_ex = nearfield(u[m],u[j],X,X,κ,η,nfpoints,E,H) -E_in, H_in = nearfield(-u[m],-u[j],X,X,κ′,η′,nfpoints) +import Base.Threads: @spawn +task1 = @spawn nearfield(u[m],u[j],X,X,κ,η,nfpoints,E,H) +task2 = @spawn nearfield(-u[m],-u[j],X,X,κ′,η′,nfpoints) + +E_ex, H_ex = fetch(task1) +E_in, H_in = fetch(task2) E_tot = E_in + E_ex H_tot = H_in + H_ex contour(real.(getindex.(E_tot,1))) -heatmap(Z, Y, real.(getindex.(E_tot,1))) -plot(real.(getindex.(E_tot[:,51],1))) - contour(real.(getindex.(H_tot,2))) -heatmap(Z, Y, real.(getindex.(H_tot,2))) -plot(real.(getindex.(H_tot[:,51],2))) \ No newline at end of file + +Plots.heatmap(Z, Y, clamp.(real.(getindex.(E_tot,1)),-1.5,1.5)) +Plots.heatmap(Z, Y, clamp.(imag.(getindex.(E_tot,1)),-1.5,1.5)) +Plots.heatmap(Z, Y, real.(getindex.(H_tot,2))) +Plots.heatmap(Z, Y, imag.(getindex.(H_tot,2))) + +Plots.plot(real.(getindex.(E_tot[:,51],1))) +Plots.plot!(real.(getindex.(H_tot[:,51],2))) + +#= +# Compare the far field and the field far +ffradius = 100.0 +E_far, H_far = nearfield(u[m],u[j],X,X,κ,η, ffradius .* ffpoints) +nxE_far = cross.(ffpoints, E_far) * (4π*ffradius) / exp(-im*κ*ffradius) +Et_far = -cross.(ffpoints, nxE_far) + +plot() +plot!(Θ, norm.(ff),label="far field") +scatter!(Θ, norm.(Et_far), label="field far") +=# \ No newline at end of file diff --git a/examples/pmchwt_calderon.jl b/examples/pmchwt_calderon.jl new file mode 100644 index 00000000..0b568044 --- /dev/null +++ b/examples/pmchwt_calderon.jl @@ -0,0 +1,75 @@ +using CompScienceMeshes +using BEAST + +κ1 = 1.0 +κ2 = 2.0 + +E = Maxwell3D.planewave(direction=ẑ, polarization=x̂, wavenumber=κ1) +H = -1/(im*κ1)*curl(E) + +T1 = Maxwell3D.singlelayer(wavenumber=κ1) +K1 = Maxwell3D.doublelayer(wavenumber=κ1) +T2 = Maxwell3D.singlelayer(wavenumber=κ2) +K2 = Maxwell3D.doublelayer(wavenumber=κ2) + +e = (n × E) × n +h = (n × H) × n + +@hilbertspace j m +@hilbertspace k l + +a = + (T1+T2)[k,j] + (-K1-K2)[k,m] + + (K1+K2)[l,j] + (T1+T2)[l,m] +b = + -e[k] -h[l] + +Γ = meshsphere(;radius=1.0, h=0.35) +RT = raviartthomas(Γ) + +X = BEAST.DirectProductSpace([RT,RT]) + +bx = assemble(b, X) +Axx = assemble(a, X, X) + +SXX = BEAST.GMRESSolver(Axx) +uX = SXX * bx +typeof(uX) + +import PlotlyBase +import PlotlyDocumenter # hide +using LinearAlgebra + +fcrm, geom = facecurrents(uX[m], RT) +fcrj, geoj = facecurrents(uX[j], RT) + +ptm = CompScienceMeshes.patch(geom, norm.(fcrm); caxis=(0,1.2) , showscale=false) +ptj= CompScienceMeshes.patch(geoj, norm.(fcrj); caxis=(0,1.2)) + +pl = [ PlotlyBase.Plot(ptm) PlotlyBase.Plot(ptj) ] + +Ty = Maxwell3D.singlelayer(gamma=κ1) + +c = Ty[k,j] + Ty[l,m] + +Nx = BEAST.NCross() +d = Nx[k,j] + Nx[l,m] + +BC = buffachristiansen(Γ) +Y = BEAST.DirectProductSpace([BC,BC]) + +Cyy = assemble(c, Y, Y) +Dxy = assemble(d, X, Y) + +Di = BEAST.GMRES(Dxy, verbose=0) +Dt = BEAST.GMRES(Dxy', verbose=0) +P = Dt * Cyy * Di + +S1 = BEAST.GMRES(Axx; restart=true, atol=1e-8, rtol=1e-8, verbose=0, memory=200) +u1, ch1 = solve(S1, bx); + +S2 = BEAST.GMRES(Axx; M=P, restart=true, atol=1e-6, rtol=1e-6, verbose=1, memory=200) +u2, ch2 = solve(S2, bx); + +using LinearAlgebra +norm(u1-u2), ch1.niter, ch2.niter diff --git a/examples/pmchwt_single_trace.jl b/examples/pmchwt_single_trace.jl new file mode 100644 index 00000000..fbac8105 --- /dev/null +++ b/examples/pmchwt_single_trace.jl @@ -0,0 +1,210 @@ +using BEAST +using CompScienceMeshes +using Makeitso +using PlotlyJS + +@target geo (;h)->begin + + m1 = meshgeo(joinpath(@__DIR__, "assets/twoboxes2.geo"), h = h, physical="Gamma1") + m2 = meshgeo(joinpath(@__DIR__, "assets/twoboxes2.geo"), h = h, physical="Gamma2") + m3 = meshgeo(joinpath(@__DIR__, "assets/twoboxes2.geo"), h = h, physical="Gamma3") + + CompScienceMeshes.orient(m1) + CompScienceMeshes.orient(m2) + CompScienceMeshes.orient(m3) + + m2.vertices = m1.vertices + m3.vertices = m1.vertices + + CompScienceMeshes.isoriented(m1) + CompScienceMeshes.isoriented(m2) + CompScienceMeshes.isoriented(m3) + + Σ = CompScienceMeshes.union(m1, m2, m3) + + b0 = CompScienceMeshes.union(-m1, m2) + b1 = CompScienceMeshes.union(m1, -m3) + b2 = CompScienceMeshes.union(-m2, m3) + + @assert CompScienceMeshes.isoriented(b0) + @assert CompScienceMeshes.isoriented(b1) + @assert CompScienceMeshes.isoriented(b2) + + return (;∂Ω=[b0,b1,b2], Σ) +end + + +@target material (;κ) -> begin + return [ + (;κ=κ, η=1.0), + (;κ=2*κ, η=1.0), + (;κ=3*κ, η=1.0) + ] +end + +@target excitation (material) -> begin + Einc = Maxwell3D.planewave(direction=(x̂+ẑ)/√2, polarization=ŷ, wavenumber=material[1].κ) + Hinc = -1/(im*material[1].κ*material[1].η)*curl(Einc) + return [ + (;Einc=Einc, Hinc=Hinc), + (;Einc=nothing, Hinc=nothing), + (;Einc=nothing, Hinc=nothing) + ] +end + + +@target formulation (geo, material, excitation) -> begin + + numdoms = length(material) + (;∂Ω, Σ) = geo + + (;κ, η) = first(material) + (;Einc, Hinc) = first(excitation) + + e = (n × Einc) × n + h = (n × Hinc) × n + + Ts = [Maxwell3D.singlelayer(wavenumber=m.κ) for m in material] + Ks = [Maxwell3D.doublelayer(wavenumber=m.κ) for m in material] + + @hilbertspace m j + @hilbertspace p q + @hilbertspace z + u = BEAST.hilbertspace(:u, numdoms) + v = BEAST.hilbertspace(:v, numdoms) + + b = -(e[p] - h[q])[v[1]] + + Aloc = [ + (-K)[p,m] + mat.η * T[p,j] - + (1/mat.η) * T[q,m] + (-K)[q,j] for (T,K,mat) in zip(Ts,Ks,material) + ] + A = sum(Aloc[i][u[i],v[i]] for i in 1:numdoms) + + Edges = skeleton(Σ,1) + edges = [skeleton(∂Ωᵢ, 1) for ∂Ωᵢ in ∂Ω] + Rloc = [ CompScienceMeshes.embedding(edges[i], Edges) for i in 1:numdoms] + R = sum((Rloc[i][m,p])[u[i],z] + (Rloc[i][j,q])[u[i],z] for i in 1:numdoms) + + return (;bilforms=(;A,R), linforms=(;b)) +end + +@target spaces (geo) -> begin + + (;∂Ω, Σ) = geo + + Edges = skeleton(Σ,1) + edges = [skeleton(∂Ωᵢ, 1) for ∂Ωᵢ in ∂Ω] + + Nd = BEAST.nedelec(Σ, Edges) + RT = [n×BEAST.nedelec(∂Ωᵢ, e) for (∂Ωᵢ,e) in zip(∂Ω, edges)] + + U = BEAST.DirectProductSpace([rt × rt for rt in RT]) + V = BEAST.DirectProductSpace([Nd × Nd]) + + return (;U, V) +end + + +@target discretization (formulation, spaces) -> begin + + (;bilforms, linforms) = formulation + (;A,R) = bilforms + (;b) = linforms + + (;U,V) = spaces + + bu = assemble(b, U) + RUv = assemble(R, U, V) + Auu = assemble(A, U, U) + + matrices = (;Auu, RUv) + vectors = (;bu) + return (;matrices, vectors) +end + + +@target solution (discretization, spaces) -> begin + + (;matrices, vectors) = discretization + (;Auu, RUv) = matrices + (;bu) = vectors + + (;U) = spaces + + MA = Matrix(Auu) + MR = Matrix(RUv) + vb = Vector(bu) + + @show size(Auu) + @show size(RUv) + @show size(bu) + + v = (MR' * MA * MR) \ (MR' * vb) + u = MR * v + u = BEAST.BlockArrays.BlockedVector(u, ( + BEAST.NestedUnitRanges.nestedrange(U, 1, numfunctions),)) + u = BEAST.FEMFunction(u, U) + + return (;u) +end + +@target nearfield (solution, material, excitation; z, x) -> begin + + function nf(um,uj,κ,η,nts) + + Xm = um.space + Xj = uj.space + + um = um.coeffs + uj = uj.coeffs + + K = BEAST.MWDoubleLayerField3D(wavenumber=κ) + T = BEAST.MWSingleLayerField3D(wavenumber=κ) + + Em = potential(K, pts, um, Xm) + Ej = potential(T, pts, uj, Xj) + + Hm = potential(T, pts, um, Xm) + Hj = potential(K, pts, uj, Xj) + + return -Em + η * Ej, 1/η*Hm + Hj + end + + numdoms = length(material) + (;u) = solution + (;Einc, Hinc) = first(excitation) + + @hilbertspace m j + p = BEAST.hilbertspace(:p, length(material)) + + pts = [point(x,0.5,z) for z in z, x in x] + EH = [nf(-u[p][m], -u[p][j], mat.κ, mat.η, pts) for (p,mat) ∈ zip(p, material)] + + EH[1][1] .-= Einc.(pts) + EH[1][2] .-= Hinc.(pts) + + return (;E = getindex.(EH,1), H = getindex.(EH,2)) +end + + +x = range(-2.0,2.0,length=150) +z = range(-1.5,2.5,length=100) +nf = make(nearfield; h=0.08, κ=2.0, x=x, z=z) + +Etot = sum(nf.E) +Htot = sum(nf.H) + +using LinearAlgebra +using PlotlyJS +hm1 = PlotlyJS.heatmap(x=x, y=z, z=real.(getindex.(nf.E[1],2)), colorscale="Viridis", zmin=-2, zmax=2) +hm2 = PlotlyJS.heatmap(x=x, y=z, z=real.(getindex.(nf.E[2],2)), colorscale="Viridis", zmin=-2, zmax=2) +hm3 = PlotlyJS.heatmap(x=x, y=z, z=real.(getindex.(nf.E[3],2)), colorscale="Viridis", zmin=-2, zmax=2) +hm4 = PlotlyJS.heatmap(x=x, y=z, z=real.(getindex.(Etot,2)), colorscale="Viridis", zmin=-2, zmax=2) + +plt = Plot(Layout(Subplots(rows=2,cols=2, specs=[Spec() Spec(); Spec() Spec()]))); +PlotlyJS.add_trace!(plt, hm1, row=1, col=1); +PlotlyJS.add_trace!(plt, hm2, row=1, col=2); +PlotlyJS.add_trace!(plt, hm3, row=2, col=1); +PlotlyJS.add_trace!(plt, hm4, row=2, col=2); +display(plt) diff --git a/examples/poltor.jl b/examples/poltor.jl new file mode 100644 index 00000000..8bdb86e6 --- /dev/null +++ b/examples/poltor.jl @@ -0,0 +1,65 @@ +using CompScienceMeshes +using LinearAlgebra + +# function cone(p,q; sizemode="absolute", sizeref=2, kwargs...) +# x = getindex.(p,1) +# y = getindex.(p,2) +# z = getindex.(p,3) +# u = getindex.(q,1) +# v = getindex.(q,2) +# w = getindex.(q,3) +# Plotly.cone(;x,y,z,u,v,w, sizemode, sizeref, kwargs...) +# end + + +fn = joinpath(dirname(pathof(CompScienceMeshes)),"geos/torus.geo") +fn = joinpath(@__DIR__, "assets/rectangular_torus.geo") +h = 0.08 +Γ = CompScienceMeshes.meshgeo(fn; dim=2, h) + +Γ0 = skeleton(Γ,0) +Γ1 = skeleton(Γ,1) + +Σ = connectivity(Γ, Γ1, sign) +Λ = connectivity(Γ0, Γ1, sign) + +# using Plotly +# Plotly.plot([patch(Γ, opacity=0.5), wireframe(Γ)]) + +using BEAST +X = raviartthomas(Γ) +Y = buffachristiansen(Γ) + +K = Maxwell3D.doublelayer(gamma=0.0) +Id = BEAST.Identity() +Nx = BEAST.NCross() + +M = K + 0.5Nx +qs = BEAST.defaultquadstrat(M,Y,X) +qs[2] = BEAST.DoubleNumWiltonSauterQStrat(6, 7, 6, 7, 9, 9, 9, 9) + +Myx = assemble(M, Y, X, quadstrat=qs) + +using LinearAlgebra +(;U,S,V) = svd(Myx) +v0 = V[:,end] + +fcr, geo = facecurrents(v0, X) +# pts = [cartesian(center(chart(Γ,p))) for p in Γ] + +import Plotly +pt1 = patch(Γ,norm.(fcr), opacity=0.5) +pt2 = CompScienceMeshes.cones(Γ, fcr, sizeref=0.4) +Plotly.plot([pt1, pt2]) + +G = assemble(Id,X,X) + + +norm(v0) +norm(Σ'*v0) +norm(Λ'*G*v0) + +# Dict{Any, Any} with 3 entries: +# 0.5 => 0.370836 +# 0.25 => 0.315732 +# 0.125 => 0.265276 \ No newline at end of file diff --git a/examples/projectors.jl b/examples/projectors.jl new file mode 100644 index 00000000..99443fa7 --- /dev/null +++ b/examples/projectors.jl @@ -0,0 +1,182 @@ +using BEAST, CompScienceMeshes, LinearAlgebra +using Plots +# using JLD2 + +setminus(A,B) = submesh(!in(B), A) + +radius = 1.0 + +nearstrat = BEAST.DoubleNumWiltonSauterQStrat(6, 7, 6, 7, 7, 7, 7, 7) +farstrat = BEAST.DoubleNumQStrat(1,2) + +dmat(op,tfs,bfs) = BEAST.assemble(op,tfs,bfs; quadstrat=nearstrat) +# hmat(op,tfs,bfs) = AdaptiveCrossApproximation.h1compress(op,tfs,bfs; nearstrat=nearstrat,farstrat=farstrat) +mat = dmat + +h = [0.1, 0.05, 0.025, 0.0125] +κ = [1.0, 10.0] + +h = 0.3 +κ = 0.000001 +γ = im*κ + +# function runsim(;h, κ) + +SL = Maxwell3D.singlelayer(wavenumber=κ) +WS = Maxwell3D.weaklysingular(wavenumber=κ) +HS = Maxwell3D.hypersingular(wavenumber=κ) +N = NCross() + +E = Maxwell3D.planewave(direction=ẑ, polarization=x̂, wavenumber=κ) +e = (n × E) × n; + +Γ = meshsphere(;radius, h) +∂Γ = boundary(Γ) + +edges = setminus(skeleton(Γ,1), ∂Γ) +verts = setminus(skeleton(Γ,0), skeleton(∂Γ,0)) + +Σ = Matrix(connectivity(Γ, edges, sign)) +Λ = Matrix(connectivity(verts, edges, sign)) + +I = LinearAlgebra.I +PΣ = Σ * pinv(Σ'*Σ) * Σ' +PΛH = I - PΣ + +ℙΛ = Λ * pinv(Λ'*Λ) * Λ' +ℙHΣ = I - ℙΛ + +MR = γ * PΣ + PΛH +ML = PΣ + 1/γ * PΛH + +MRΣ = γ * PΣ +MRΛH = PΛH +MLΣ = PΣ +MLΛH = 1/γ * PΛH + +𝕄R = γ * ℙΛ + ℙHΣ +𝕄L = ℙΛ + 1/γ * ℙHΣ + +X = raviartthomas(Γ) +Y = buffachristiansen(Γ) + +@hilbertspace p +@hilbertspace q + +SLxx = assemble(@discretise(SL[p,q], p∈X, q∈X), materialize=mat) +WSxx = assemble(@discretise(WS[p,q], p∈X, q∈X), materialize=mat) + +ex = BEAST.assemble(@discretise e[p] p∈X) + +sys0 = SLxx +sys1 = MLΣ * SLxx * MRΣ + MLΛH * WSxx * MRΣ + MLΣ * WSxx * MRΛH + MLΛH * WSxx * MRΛH + +rhs0 = ex +rhs1 = ML * ex + +u0, ch0 = solve(BEAST.GMRESSolver(sys0, tol=2e-5, restart=250), rhs0) +v1, ch1 = solve(BEAST.GMRESSolver(sys1, tol=2e-5, restart=250), rhs1) + +u1 = MR * v1 +# u2 = MR * v2 + +# error() +# @show ch1.iters +# @show ch2.iters + +Φ, Θ = [0.0], range(0,stop=π,length=40) +pts = [point(cos(ϕ)*sin(θ), sin(ϕ)*sin(θ), cos(θ)) for ϕ in Φ for θ in Θ] + +near0 = potential(MWFarField3D(wavenumber=κ), pts, u0, X) +near1 = potential(MWFarField3D(wavenumber=κ), pts, u1, X) + # near2 = potential(MWFarField3D(wavenumber=κ), pts, u2, X) + + # u1, ch1.iters, u2, ch2.iters, near1, near2, X +# end + +plot(); +plot!(Θ, norm.(near0)); +scatter!(Θ, norm.(near1)) +# scatter!(Θ, norm.(near2)) + +# error() + +# using LinearAlgebra +# using Plots +# plotly() +# w0 = eigvals(Matrix(SLxx)) +# w1 = eigvals(Matrix(sys)) +# w2 = eigvals(Matrix(P * sys)) +# plot(exp.(2pi*im*range(0,1,length=200))) +# scatter!(w0) +# scatter!(w1) +# scatter!(w2) + + +# function makesim(d::Dict) +# @unpack h, κ = d +# u1, ch1, u2, ch2, near1, near2, X = runsim(;h, κ) +# fulld = merge(d, Dict( +# "u1" => u1, +# "u2" => u2, +# "ch1" => ch1, +# "ch2" => ch2, +# "near1" => near1, +# "near2" => near2 +# )) +# end + +# method = splitext(basename(@__FILE__))[1] + + +# params = @strdict h κ +# dicts = dict_list(params) +# for (i,d) in enumerate(dicts) +# @show d +# f = makesim(d) +# @tagsave(datadir("simulations", method, savename(d,"jld2")), f) +# end + +#' Visualise the spectrum + +# mSxx = BEAST.convert_to_dense(Sxx) +# mSyy = BEAST.convert_to_dense(Syy) + +# Z = mSxx +# W = iN' * mSyy * iN * mSxx; + +# wZ = eigvals(Matrix(Z)) +# wW = eigvals(Matrix(W)) + +# plot(exp.(im*range(0,2pi,length=200))) +# scatter!(wZ) +# scatter!(wW) + + +# Study the various kernels +# HS = Maxwell3D.singlelayer(gamma=0.0, alpha=0.0, beta=1.0) +# Id = BEAST.Identity() + +# Z12 = BEAST.lagrangecxd0(G12) +# Z23 = BEAST.lagrangecxd0(Ĝ23) +# Z = Z12 × Z23 + +# W12 = BEAST.duallagrangecxd0(G12) +# W23 = BEAST.duallagrangecxd0(Ĝ23) +# W = W12 × W23 + +# DX = assemble(Id, Z, divergence(X)) +# HX = assemble(HS, X, X) + +# DY = assemble(Id, W, divergence(Y)) +# HY = assemble(HS, Y, Y) + +# Nx = BEAST.NCross() +# NYX = assemble(Nx, Y, X) + +# Q = HY * iN * HX + +# using AlgebraicMultigrid +# A = poisson(100) +# b = rand(100); +# solve(A, b, RugeStubenAMG(), maxiter=1, abstol=1e-6) diff --git a/examples/quadstrats.jl b/examples/quadstrats.jl new file mode 100644 index 00000000..069497c7 --- /dev/null +++ b/examples/quadstrats.jl @@ -0,0 +1,48 @@ +using CompScienceMeshes +using BEAST + +Γ1 = readmesh(joinpath(dirname(pathof(BEAST)),"../examples/sphere2.in")) +Γ2 = CompScienceMeshes.translate(Γ1, [10,0,0]) + +SL = Maxwell3D.singlelayer(wavenumber=1.0) +X1 = raviartthomas(Γ1) +X2 = raviartthomas(Γ2) + +x1 = refspace(X1) +x2 = refspace(X2) + +function BEAST.quaddata(op::typeof(SL), tref::typeof(x1), bref::typeof(x2), + tels, bels, qs::BEAST.DoubleNumQStrat) + + qs = BEAST.DoubleNumWiltonSauterQStrat(qs.outer_rule, qs.inner_rule, 1, 1, 1, 1, 1, 1) + BEAST.quaddata(op, tref, bref, tels, bels, qs) +end + +function BEAST.quadrule(op::typeof(SL), tref::typeof(x1), bref::typeof(x2), + i ,τ, j, σ, qd, qs::BEAST.DoubleNumQStrat) + + return BEAST.DoubleQuadRule( + qd.tpoints[1,i], + qd.bpoints[1,j]) +end + +BEAST.quadinfo(SL,X1,X2) +BEAST.quadinfo(SL,X1,X2, quadstrat=BEAST.DoubleNumQStrat(2,3)) + +@time Z1 = assemble(SL,X1,X2); +@time Z2 = assemble(SL,X1,X2,quadstrat=BEAST.DoubleNumQStrat(2,3)); + +ba1 = blockassembler(SL,X1,X2) +ba2 = blockassembler(SL,X1,X2,quadstrat=BEAST.DoubleNumQStrat(2,3)) + +T = scalartype(SL,X1,X2) +n1 = numfunctions(X1) +n2 = numfunctions(X2) + +W1 = zeros(T,n1,n2); +W2 = zeros(T,n1,n2); + +idcs1 = collect(1:n1) +idcs2 = collect(1:n2) +@time ba1(idcs1,idcs2, (v,m,n)->(W1[m,n]+=v)) +@time ba2(idcs1,idcs2, (v,m,n)->(W2[m,n]+=v)); \ No newline at end of file diff --git a/examples/sandbox.jl b/examples/sandbox.jl deleted file mode 100644 index d1f63f40..00000000 --- a/examples/sandbox.jl +++ /dev/null @@ -1,41 +0,0 @@ -using CompScienceMeshes -using BEAST - -m = meshsphere(1.0, 0.45) -# m = CompScienceMeshes.rotate(m, rand(3)) -X = raviartthomas(m) - -Y = buffachristiansen(m) -# Y = X -Y = BEAST.subset(Y,1:135) -geo = geometry(Y) - -els, ad = BEAST.assemblydata(Y) -Base.length(it::BEAST.ADIterator) = something(findfirst(x->x[1]<1, it.ad.data[:,it.r,it.c]), size(it.ad.data,1)+1)-1 - -Ad = [Int[] for c in 1:length(els)] -for c in 1:length(els) - for r in 1:3 - length(ad[c,r]) == 0 && continue - push!(Ad[c], maximum(m for (m,a) in ad[c,r])) - end -end -top = maximum.(Ad) -bot = minimum.(Ad) - -using Plots - -plot() -plot!(top) -plot!(bot) - -import PlotlyJS - -t1 = patch(Mesh(geo.mesh.vertices, geo.mesh.faces[1:150]), rand(150)) -P = [p[i] for p in Y.pos, i in 1:3] -t2 = PlotlyJS.scatter3d(x=P[1:38,1], y=P[1:38,2], z=P[1:38,3]) -t3 = PlotlyJS.scatter3d(x=P[264:264,1], y=P[264:264,2], z=P[264:264,3]) -t4 = PlotlyJS.scatter3d(x=P[1:180,1], y=P[1:180,2], z=P[1:180,3]) -PlotlyJS.plot(t4) - -PlotlyJS.plot([t1,t2,t3]) diff --git a/examples/stgalerkin.jl b/examples/stgalerkin.jl index aeb95319..3d6f822d 100644 --- a/examples/stgalerkin.jl +++ b/examples/stgalerkin.jl @@ -4,7 +4,7 @@ o, x, y, z = euclidianbasis(3) # D, Δx = 1.0, 0.35 # Γ = meshsphere(D, Δx) -Γ = readmesh(joinpath(@__DIR__,"sphere2.in")) +Γ = readmesh(joinpath(dirname(pathof(BEAST)),"../examples/sphere2.in")) Γ = meshsphere(1.0, 0.45) X = raviartthomas(Γ) #Δt, Nt = 0.08, 400 @@ -12,6 +12,9 @@ X = raviartthomas(Γ) T = timebasisc0d1(Δt, Nt) U = timebasiscxd0(Δt, Nt) +# T = timebasisshiftedlagrange(Δt, Nt, 2) +# U = timebasisdelta(Δt, Nt) + V = X ⊗ T W = X ⊗ U diff --git a/examples/stmfie.jl b/examples/stmfie.jl index cf4bad1a..cd5a8912 100644 --- a/examples/stmfie.jl +++ b/examples/stmfie.jl @@ -3,7 +3,7 @@ o, x, y, z = euclidianbasis(3) # D, Δx = 1.0, 0.35 -Γ = readmesh(joinpath(@__DIR__,"sphere2.in")) +Γ = readmesh(joinpath(dirname(pathof(BEAST)),"../examples/sphere2.in")) X, Y = raviartthomas(Γ), buffachristiansen(Γ) # Δt, Nt = 0.11, 200 diff --git a/examples/tdefie.jl b/examples/tdefie.jl index 37228e5c..ba2a8013 100644 --- a/examples/tdefie.jl +++ b/examples/tdefie.jl @@ -1,16 +1,18 @@ using CompScienceMeshes, BEAST, LinearAlgebra Γ = readmesh(joinpath(@__DIR__,"sphere2.in")) +Γ = meshsphere(radius=1.0, h=0.25) X = raviartthomas(Γ) -Δt, Nt = 0.3, 200 +Δt = 0.1 +Nt = 200 T = timebasisshiftedlagrange(Δt, Nt, 3) U = timebasisdelta(Δt, Nt) V = X ⊗ T W = X ⊗ U -duration = 20 * Δt * 2 +duration = 2 * 20 * Δt delay = 1.5 * duration amplitude = 1.0 gaussian = creategaussian(duration, delay, amplitude) @@ -21,15 +23,14 @@ E = planewave(polarisation, direction, derive(gaussian), 1.0) @hilbertspace j′ SL = TDMaxwell3D.singlelayer(speedoflight=1.0, numdiffs=1) -tdefie = @discretise SL[j′,j] == -1.0E[j′] j∈V j′∈W -xefie = solve(tdefie) -import Plots -Plots.plot(xefie[1,:]) +tdefie = @discretise SL[j′,j] == -1.0E[j′] j∈V j′∈W +xefie = BEAST.motsolve(tdefie) -import Plotly -fcr, geo = facecurrents(xefie[:,60], X) -Plotly.plot(patch(geo, norm.(fcr))) +import PlotlyJS +PlotlyJS.plot(xefie[1,:]) +fcr, geo = facecurrents(xefie[:,125], X) +PlotlyJS.plot(patch(geo, norm.(fcr))) @@ -42,3 +43,6 @@ _, i1 = findmin(abs.(ω.-1.0)) ω1 = ω[i1] ue = Xefie[:,i1] / fouriertransform(gaussian)(ω1) + +fcr, geo = facecurrents(ue, X) +PlotlyJS.plot(patch(geo, norm.(fcr))) \ No newline at end of file diff --git a/examples/tdefie_irk.jl b/examples/tdefie_irk.jl new file mode 100644 index 00000000..7025f299 --- /dev/null +++ b/examples/tdefie_irk.jl @@ -0,0 +1,31 @@ +using CompScienceMeshes, BEAST, StaticArrays, LinearAlgebra #, Plots + +Γ = meshsphere(radius=1.0, h=0.45) +X = raviartthomas(Γ) +sol = 1.0 +Δt, Nt = 10.0, 200 + +(A, b, c) = butcher_tableau_radau_3stages() +T = StagedTimeStep(Δt, Nt, c, A, b, 10, 1.0001) +V = X ⊗ T + +duration = 2 * 20 * Δt +delay = 1.5 * duration +amplitude = 1.0 +gaussian = creategaussian(duration, delay, amplitude) + +direction, polarisation = ẑ , x̂ +E = planewave(polarisation, direction, derive(gaussian), sol) + +T = TDMaxwell3D.singlelayer(speedoflight=1.0, numdiffs=1) + +@hilbertspace j +@hilbertspace j′ +tdefie_irk = @discretise T[j′,j] == -1E[j′] j∈V j′∈V +xefie_irk = solve(tdefie_irk) + +import PlotlyJS +PlotlyJS.plot(xefie_irk[1,:]) + + + diff --git a/examples/tdefie_neumann.jl b/examples/tdefie_neumann.jl index 66bf0662..3746417c 100644 --- a/examples/tdefie_neumann.jl +++ b/examples/tdefie_neumann.jl @@ -43,7 +43,7 @@ using Plotly Xefie, Δω, ω0 = fouriertransform(xefie, Δt, 0.0, 2) -ω = collect(ω0 + (0:Nt-1)*Δω) +ω = collect(ω0 .+ (0:Nt-1)*Δω) _, i1 = findmin(abs.(ω.-sol)) ω1 = ω[i1] diff --git a/examples/tdefie_nodot.jl b/examples/tdefie_nodot.jl index e23cd5c9..d948c6cd 100644 --- a/examples/tdefie_nodot.jl +++ b/examples/tdefie_nodot.jl @@ -2,9 +2,10 @@ using CompScienceMeshes, BEAST # Γ = readmesh(joinpath(@__DIR__,"sphere2.in")) Γ = readmesh(joinpath(dirname(pathof(BEAST)),"../examples/sphere2.in")) +# Γ = meshsphere(radius=1.0, h=0.4) X = raviartthomas(Γ) -Δt, Nt = 0.6, 200 +Δt, Nt = 0.1, 200 T = timebasisshiftedlagrange(Δt, Nt, 1) U = timebasisdelta(Δt, Nt) @@ -24,6 +25,8 @@ SL = TDMaxwell3D.singlelayer(speedoflight=1.0) @hilbertspace j @hilbertspace j′ efie_nodot = @discretise SL[j′,j] == E[j′] j∈V j′∈W +# error() + xefie_nodot = solve(efie_nodot) Xefie, Δω, ω0 = fouriertransform(xefie_nodot, Δt, 0.0, 2) diff --git a/examples/tdemfie.jl b/examples/tdemfie.jl index 85ba0746..2064005c 100644 --- a/examples/tdemfie.jl +++ b/examples/tdemfie.jl @@ -2,7 +2,7 @@ # the code dealing with systems of time domain equations in the special case # of a block diagonal system. using CompScienceMeshes, BEAST -Γ = readmesh(joinpath(@__DIR__,"sphere2.in")) +Γ = readmesh(joinpath(dirname(pathof(BEAST)),"../examples/sphere2.in")) X = raviartthomas(Γ) Y = buffachristiansen(Γ) @@ -33,4 +33,4 @@ N = NCross() emfie = @discretise( (0.5(N⊗I) + 1.0DL)[k,j] + SL[l,m] == -1.0H[k] - E[l], k∈Y⊗δ, l∈X⊗δ, j∈X⊗T1, m∈X⊗T1) -xemfie = solve(emfie) +xemfie = motsolve(emfie) diff --git a/examples/tdmfie.jl b/examples/tdmfie.jl index ed3dd7dc..e3ba50ed 100644 --- a/examples/tdmfie.jl +++ b/examples/tdmfie.jl @@ -1,18 +1,18 @@ using CompScienceMeshes, BEAST -Γ = readmesh(joinpath(@__DIR__,"sphere2.in")) -# Γ = meshsphere(1.0, 0.4) +Γ = readmesh(joinpath(dirname(pathof(BEAST)),"../examples/sphere2.in")) +Γ = meshsphere(radius=1.0, h=0.25) X = raviartthomas(Γ) Y = buffachristiansen(Γ) -Δt, Nt = 0.3, 200 +Δt, Nt = 0.1, 200 T = timebasisshiftedlagrange(Δt, Nt, 2) δ = timebasisdelta(Δt, Nt) V = X ⊗ T W = Y ⊗ δ -duration = 20 * Δt * 2 +duration = 2 * 20 * Δt delay = 1.5 * duration amplitude = 1.0 gaussian = creategaussian(duration, delay, amplitude) @@ -27,9 +27,9 @@ N = NCross() @hilbertspace k @hilbertspace j -mfie = @discretise (0.5(N⊗I) + 1.0DL)[k,j] == -1.0H[k] k∈W j∈V +mfie = @discretise ((0.5*N)⊗I)[k,j] + 1.0DL[k,j] == -1.0H[k] k∈W j∈V -xmfie = solve(mfie) +xmfie = BEAST.motsolve(mfie) Xmfie, Δω, ω0 = fouriertransform(xmfie, Δt, 0.0, 2) ω = collect(ω0 .+ (0:Nt-1)*Δω) @@ -37,3 +37,7 @@ _, i1 = findmin(abs.(ω .- 1.0)) ω1 = ω[i1] um = Xmfie[:,i1] / fouriertransform(gaussian)(ω1) + +import Plotly +fcr, geo = facecurrents(um, X) +Plotly.plot(patch(geo, norm.(fcr))) diff --git a/examples/tdmfie_sym.jl b/examples/tdmfie_sym.jl index 8605223d..4bb90a6a 100644 --- a/examples/tdmfie_sym.jl +++ b/examples/tdmfie_sym.jl @@ -1,11 +1,11 @@ using CompScienceMeshes, BEAST -Γ = readmesh(joinpath(@__DIR__,"sphere2.in")) +Γ = readmesh(joinpath(dirname(pathof(BEAST)),"../examples/sphere2.in")) # Γ = meshsphere(1.0, 0.4) X = raviartthomas(Γ) Y = BEAST.nedelec(Γ) -Δt, Nt = 0.3, 200 +Δt, Nt = 0.1, 200 T = timebasisshiftedlagrange(Δt, Nt, 2) δ = timebasisdelta(Δt, Nt) diff --git a/examples/tdpmchwt.jl b/examples/tdpmchwt.jl index 004228a2..f160b240 100644 --- a/examples/tdpmchwt.jl +++ b/examples/tdpmchwt.jl @@ -6,22 +6,26 @@ using CompScienceMeshes, BEAST using LinearAlgebra # Γ = meshcuboid(1.0, 1.0, 1.0, 0.25) -Γ = meshsphere(1.0, 0.3) +# h = 0.3 +# fn = joinpath(dirname(pathof(CompScienceMeshes)),"geos/torus.geo") +# Γ = CompScienceMeshes.meshgeo(fn; dim=2, h=1.0) + +Γ = meshsphere(radius=1.0, h=0.25) X = raviartthomas(Γ) -Y = buffachristiansen(Γ, ibscaled=true) +Y = buffachristiansen(Γ) sol = 1.0 T = TDMaxwell3D.singlelayer(speedoflight=sol, numdiffs=1) K = TDMaxwell3D.doublelayer(speedoflight=sol, numdiffs=1) -Δt, Nt = 0.6, 200 +Δt, Nt = 0.1, 300 δ = timebasisdelta(Δt, Nt) T0 = timebasiscxd0(Δt, Nt) T1 = timebasisshiftedlagrange(Δt,Nt,1) T2 = timebasisshiftedlagrange(Δt, Nt, 2) T3 = timebasisshiftedlagrange(Δt, Nt, 3) -duration = 20 * Δt +duration = 40 * Δt delay = 1.5 * duration amplitude = 1.0 gaussian = creategaussian(duration, delay, amplitude) @@ -33,13 +37,30 @@ H = direction × E @hilbertspace j m @hilbertspace k l +# pmchwt = @discretise( +# 2.0T[k,j] + 2.0K[k,m] - +# 2.0K[l,j] + 2.0T[l,m] == H[k] + E[l], +# j∈X⊗T2, m∈Y⊗T2, k∈X⊗δ, l∈Y⊗δ) + +BEAST.@defaultquadstrat (T, X⊗δ, X⊗T2) BEAST.OuterNumInnerAnalyticQStrat(7) +BEAST.@defaultquadstrat (K, X⊗δ, X⊗T2) BEAST.OuterNumInnerAnalyticQStrat(7) + pmchwt = @discretise( 2.0T[k,j] + 2.0K[k,m] - 2.0K[l,j] + 2.0T[l,m] == H[k] + E[l], - j∈X⊗T2, m∈Y⊗T2, k∈X⊗δ, l∈Y⊗δ) + j∈X⊗T2, m∈X⊗T2, k∈X⊗δ, l∈X⊗δ) -u = solve(pmchwt) +# Z = BEAST.td_assemble(pmchwt.equation.lhs, pmchwt.test_space_dict, pmchwt.trial_space_dict); +# w = BEAST.ConvolutionOperators.polyvals(Z) +# error() + +u = BEAST.motsolve(pmchwt) + +Z = BEAST.sysmatrix(pmchwt) +using Plots +scatter!(u[1,:]) +nothing # nX = numfunctions(X) # uj = u[1:nX] # um = u[nX+1:end] @@ -61,3 +82,34 @@ u = solve(pmchwt) # using LinearAlgebra # fcrj, _ = facecurrents(uj,X) # PlotlyJS.plot(patch(Γ, norm.(fcrj))) + +# Study the pmchwt static nullspace +# pmchwt = @discretise( +# 2.0T[k,j] + 2.0K[k,m] - +# 2.0K[l,j] + 2.0T[l,m] == H[k] + E[l], +# j∈X⊗T2, m∈X⊗T2, k∈X⊗δ, l∈X⊗δ) + + +# Z = BEAST.td_assemble(pmchwt.equation.lhs, pmchwt.test_space_dict, pmchwt.trial_space_dict) +# function cast(Z) +# # kmax = maximum(length.(Z)) +# kmax = size(Z,3) +# Q = zeros(eltype(eltype(Z)), size(Z)..., kmax) +# for m in axes(Q,1) +# for n in axes(Q,2) +# for k in eachindex(Z[m,n]) +# Q[m,n,k] = Z[m,n][k] +# end +# end +# end +# return Q +# end +# Q = cast(Z) + +# C = companion(Q) +# w = eigvals(C) + +# using Plots +# plotly() +# plot(exp.(im*range(0,2pi,length=200))) +# scatter!(w) \ No newline at end of file diff --git a/examples/tdpmchwt_cq.jl b/examples/tdpmchwt_cq.jl new file mode 100644 index 00000000..e19e8746 --- /dev/null +++ b/examples/tdpmchwt_cq.jl @@ -0,0 +1,81 @@ +using BEAST, LinearAlgebra, CompScienceMeshes, ConvolutionOperators, Printf + +# Physical parameters +ϵ, μ = 1.0, 1.0 +ϵ′, μ′ = 1.0, 1.0 +η, η′ = √(μ/ϵ), √(μ′/ϵ′) + +T = BEAST.LaplaceDomainOperator(s::ComplexF64 -> MWSingleLayer3D(s*√(ϵ*μ), -s*√(ϵ*μ), ComplexF64(1/√(ϵ*μ))/s)) +K = BEAST.LaplaceDomainOperator(s::ComplexF64 -> MWDoubleLayer3D(s*√(ϵ*μ))) +T′ = BEAST.LaplaceDomainOperator(s::ComplexF64 -> MWSingleLayer3D(s*√(ϵ′*μ′), -s*√(ϵ′*μ′), ComplexF64(1/√(ϵ′*μ′))/s)) +K′ = BEAST.LaplaceDomainOperator(s::ComplexF64 -> MWDoubleLayer3D(s*√(ϵ′*μ′))) + +Γ = meshsphere(1.0, 0.3) +X = raviartthomas(Γ) +Y = buffachristiansen(Γ) + +@hilbertspace k l +@hilbertspace j m + +Nt, Δt = 200, 10.0 +(A, b, c) = butcher_tableau_radau_2stages() +CQ = StagedTimeStep(Δt, Nt, c, A, b, 30, 1.0001) + +duration = 40 * Δt +delay = 60 * Δt +amplitude = 1.0 +gaussian = creategaussian(duration, delay, amplitude) +fgaussian = fouriertransform(gaussian) +polarisation, direction = x̂, ẑ +E = planewave(polarisation, direction, gaussian, 1.0) +H = direction × E + +BEAST.@defaultquadstrat (T, X⊗CQ, X⊗CQ) BEAST.DoubleNumWiltonSauterQStrat(6, 7, 6, 7, 7, 7, 7, 7) +BEAST.@defaultquadstrat (T′, X⊗CQ, X⊗CQ) BEAST.DoubleNumWiltonSauterQStrat(6, 7, 6, 7, 7, 7, 7, 7) +BEAST.@defaultquadstrat (K, X⊗CQ, X⊗CQ) BEAST.DoubleNumWiltonSauterQStrat(6, 7, 6, 7, 7, 7, 7, 7) +BEAST.@defaultquadstrat (K′, X⊗CQ, X⊗CQ) BEAST.DoubleNumWiltonSauterQStrat(6, 7, 6, 7, 7, 7, 7, 7) + +pmchwt = @discretise(η*T[k,j] + η′*T′[k,j] + K[l,j] + K′[l,j] + - K[k,m] - K′[k,m] + 1/η*T[l,m] + 1/η′*T′[l,m] == E[k] + H[l], + k∈X⊗CQ, l∈X⊗CQ, j∈X⊗CQ, m∈X⊗CQ) +je = solve(pmchwt) + + +# Exact solution +N = NCross() +Nyx = assemble(N, Y, X) +iNyx = inv(Matrix(Nyx)) +Zr = zeros(size(iNyx)) + +δ = timebasisdelta(Δt, Nt) +linform = @discretise(H[k] + E[l], k∈Y⊗δ, l∈Y⊗δ) +rhs = BEAST.assemble(linform.linform, linform.test_space_dict) +j_ext = [iNyx Zr; Zr iNyx] * rhs + + +# Plot the numerical solutions +using Plots +x = 1e-2 * Δt/3 * [1:1:Nt;] + +plt = Plots.plot( + size = (600, 400), + grid = false, + xscale = :identity, + # xlims = (0, 1620), + # xticks = [400, 800, 1200, 1600], + xtickfont = Plots.font(10, "Computer Modern"), + yscale = :log10, + ylims = (1e-15, 1e0), + yticks = [1e-20, 1e-15, 1e-10, 1e-5, 1e0, 1e5], + ytickfont = Plots.font(10, "Computer Modern"), + xlabel = "Time ({\\mu}s)", + ylabel = "Current density intensity (A/m)", + titlefont = Plots.font(10, "Computer Modern"), + guidefont = Plots.font(11, "Computer Modern"), + colorbar_titlefont = Plots.font(10, "Computer Modern"), + legendfont = Plots.font(11, "Computer Modern"), + legend = :topright, + dpi = 300) + +Plots.plot!(x, abs.(j_ext[1, :]), label="Exact solution", linecolor=1, lw=1.2) +Plots.plot!(x, abs.(je[1, :]), label="TD-PMCHWT", linecolor=2, lw=1.2) \ No newline at end of file diff --git a/examples/tdpmchwt_int.jl b/examples/tdpmchwt_int.jl new file mode 100644 index 00000000..cdf1297f --- /dev/null +++ b/examples/tdpmchwt_int.jl @@ -0,0 +1,121 @@ +# using Pkg +# Pkg.activate(@__DIR__) +# Pkg.instantiate() + +using CompScienceMeshes, BEAST +using LinearAlgebra + +# Γ = meshcuboid(1.0, 1.0, 1.0, 0.25) +# h = 0.3 +fn = joinpath(dirname(pathof(CompScienceMeshes)),"geos/torus.geo") +Γ = CompScienceMeshes.meshgeo(fn; dim=2, h=1.0) + +Γ = meshsphere(radius=1.0, h=0.25) +X = raviartthomas(Γ) +Y = buffachristiansen(Γ, ibscaled=false) + +sol = 1.0 +T = TDMaxwell3D.singlelayer(speedoflight=sol, numdiffs=0) +K = TDMaxwell3D.doublelayer(speedoflight=sol, numdiffs=0) + +Δt, Nt = 0.1, 300 +δ = timebasisdelta(Δt, Nt) +T0 = timebasiscxd0(Δt, Nt) +T1 = timebasisshiftedlagrange(Δt,Nt,1) +T2 = timebasisshiftedlagrange(Δt, Nt, 2) +T3 = timebasisshiftedlagrange(Δt, Nt, 3) + +duration = 40 * Δt +delay = 1.5 * duration +amplitude = 1.0 +gaussian = creategaussian(duration, delay, amplitude) + +direction, polarisation = ẑ, x̂ +E = BEAST.planewave(polarisation, direction, gaussian, 1.0) +H = direction × E + +@hilbertspace j m +@hilbertspace k l + +# pmchwt = @discretise( +# 2.0T[k,j] + 2.0K[k,m] - +# 2.0K[l,j] + 2.0T[l,m] == H[k] + E[l], +# j∈X⊗T2, m∈Y⊗T2, k∈X⊗δ, l∈Y⊗δ) + +pmchwt = @discretise( + 2.0T[k,j] + 2.0K[k,m] - + 2.0K[l,j] + 2.0T[l,m] == H[k] + E[l], + j∈X⊗T1, m∈X⊗T1, k∈X⊗δ, l∈X⊗δ) + +# Z = BEAST.td_assemble(pmchwt.equation.lhs, pmchwt.test_space_dict, pmchwt.trial_space_dict); +# error() + + +function cones(mesh, arrows; sizeref=2) + centers = [cartesian(CompScienceMeshes.center(chart(mesh,cell))) for cell in mesh] + x = getindex.(centers,1) + y = getindex.(centers,2) + z = getindex.(centers,3) + u = getindex.(arrows,1) + v = getindex.(arrows,2) + w = getindex.(arrows,3) + Plotly.cone(x=x,y=y,z=z,u=u,v=v,w=w,sizemode="absolute", sizeref=sizeref) +end + + + +u = solve(pmchwt) +error() + +# nX = numfunctions(X) +# uj = u[1:nX] +# um = u[nX+1:end] + +# Θ, Φ = range(0.0,stop=2π,length=100), 0.0 +# ffpoints = [point(cos(ϕ)*sin(θ), sin(ϕ)*sin(θ), cos(θ)) for θ in Θ for ϕ in Φ] +# +# # Don't forgt the far field comprises two contributions +# κ, η = 1.0, 1.0 +# ffm = potential(MWFarField3D(κ*im), ffpoints, um, X) +# ffj = potential(MWFarField3D(κ*im), ffpoints, uj, X) +# ff = η*im*κ*ffj + im*κ*cross.(ffpoints, ffm) +# +# using Plots +# plot(xlabel="theta") +# plot!(Θ,norm.(ffm),label="far field") +# +# import PlotlyJS +# using LinearAlgebra +# fcrj, _ = facecurrents(uj,X) +# PlotlyJS.plot(patch(Γ, norm.(fcrj))) + +# Study the pmchwt static nullspace +pmchwt = @discretise( + 2.0T[k,j] + 2.0K[k,m] - + 2.0K[l,j] + 2.0T[l,m] == H[k] + E[l], + j∈X⊗T2, m∈X⊗T2, k∈X⊗δ, l∈X⊗δ) + + +# Z = BEAST.td_assemble(pmchwt.equation.lhs, pmchwt.test_space_dict, pmchwt.trial_space_dict) +# function cast(Z) +# # kmax = maximum(length.(Z)) +# kmax = size(Z,3) +# Q = zeros(eltype(eltype(Z)), size(Z)..., kmax) +# for m in axes(Q,1) +# for n in axes(Q,2) +# for k in eachindex(Z[m,n]) +# Q[m,n,k] = Z[m,n][k] +# end +# end +# end +# return Q +# end +# Q = cast(Z) + +# C = companion(Q) +# w = eigvals(C) + +# using Plots +# plotly() +# plot(exp.(im*range(0,2pi,length=200))) +# scatter!(w) \ No newline at end of file diff --git a/examples/utils/edge_values.jl b/examples/utils/edge_values.jl index cd3b20e1..8c8587a1 100644 --- a/examples/utils/edge_values.jl +++ b/examples/utils/edge_values.jl @@ -8,17 +8,14 @@ function edge_values(space, m) Vals = zeros(scalartype(space), length(supp), length(edgs)) for (i,sh) in enumerate(space.fns[m]) tet_id = sh.cellid - tet = chart(supp, cells(supp)[tet_id]) + tet = chart(supp, tet_id) for k in nzrange(Conn, tet_id) edg_id = rowvals(Conn)[k] - # loc_id = abs(nonzeros(Conn)[k]) - # edge = CompScienceMeshes.edges(tet)[loc_id] - edge = chart(edgs, cells(edgs)[edg_id]) - ctr = center(edge) + edge = chart(edgs, edg_id) + ctr = CompScienceMeshes.center(edge) tgt = tangents(ctr,1) ctr1 = neighborhood(tet, carttobary(tet, cartesian(ctr))) vals = rs(ctr1) - # @show vals val = vals[sh.refid].value Vals[tet_id, edg_id] += dot(sh.coeff * val, tgt) end diff --git a/examples/utils/plotresults.jl b/examples/utils/plotresults.jl index 972301e5..74c92fe6 100644 --- a/examples/utils/plotresults.jl +++ b/examples/utils/plotresults.jl @@ -4,12 +4,12 @@ if plotresults postproc || error("Cannot plot results without going through post-processing.") @eval begin - using Plots + import Plots using LinearAlgebra - p1 = scatter(Θ, real.(norm.(ffd))) - p2 = heatmap(clamp.(real.(norm.(nfd)), 0.0, 2.0)) - p3 = contour(clamp.(real.(norm.(nfd)), 0.0, 2.0)) - plot(p1,p2,p3,layout=(3,1)) + p1 = Plots.scatter(Θ, real.(norm.(ffd))) + p2 = Plots.heatmap(clamp.(real.(norm.(nfd)), 0.0, 2.0), clims=(0,2)) + p3 = Plots.contour(clamp.(real.(norm.(nfd)), 0.0, 2.0), clims=(0,2)) + Plots.plot(p1,p2,p3,layout=(3,1)) end end diff --git a/examples/utils/postproc.jl b/examples/utils/postproc.jl index 6b1efd56..0b908822 100644 --- a/examples/utils/postproc.jl +++ b/examples/utils/postproc.jl @@ -11,8 +11,9 @@ if postproc fcr, geo = facecurrents(u, X) nx, nz = 50, 100 - xs, zs = range(-2,stop=2,length=nx), range(-4,stop=4,length=nz) - gridpoints = [point(x,0,z) for x in xs, z in zs] + x = 0.0 + ys, zs = range(-2,stop=2,length=nx), range(-4,stop=4,length=nz) + gridpoints = [point(x,y,z) for y in ys, z in zs] nfd = potential(MWSingleLayerField3D(wavenumber = κ), gridpoints, u, X) nfd = reshape(nfd, (nx,nz)) nfd .-= E.(gridpoints) diff --git a/examples/utils/showfn.jl b/examples/utils/showfn.jl new file mode 100644 index 00000000..f1ea7480 --- /dev/null +++ b/examples/utils/showfn.jl @@ -0,0 +1,47 @@ +import Plotly +function showfn(space,i) + geo = geometry(space) + T = coordtype(geo) + X = Dict{Int,T}() + Y = Dict{Int,T}() + Z = Dict{Int,T}() + U = Dict{Int,T}() + V = Dict{Int,T}() + W = Dict{Int,T}() + for sh in space.fns[i] + chrt = chart(geo, cells(geo)[sh.cellid]) + nbd = CompScienceMeshes.center(chrt) + vals = refspace(space)(nbd) + x,y,z = cartesian(nbd) + # @show vals[sh.refid].value + u,v,w = vals[sh.refid].value + # @show x, y, z + # @show u, v, w + X[sh.cellid] = x + Y[sh.cellid] = y + Z[sh.cellid] = z + U[sh.cellid] = get(U,sh.cellid,zero(T)) + sh.coeff * u + V[sh.cellid] = get(V,sh.cellid,zero(T)) + sh.coeff * v + W[sh.cellid] = get(W,sh.cellid,zero(T)) + sh.coeff * w + end + X = collect(values(X)) + Y = collect(values(Y)) + Z = collect(values(Z)) + U = collect(values(U)) + V = collect(values(V)) + W = collect(values(W)) + Plotly.cone(x=X,y=Y,z=Z,u=U,v=V,w=W) +end + +function compress!(space) + T = scalartype(space) + for (i,fn) in pairs(space.fns) + shapes = Dict{Tuple{Int,Int},T}() + for shape in fn + v = get(shapes, (shape.cellid, shape.refid), zero(T)) + shapes[(shape.cellid, shape.refid)] = v + shape.coeff + # set!(shapes, (shape.cellid, shape.refid), v + shape.coeff) + end + space.fns[i] = [BEAST.Shape(k[1], k[2], v) for (k,v) in shapes] + end +end diff --git a/examples/utils/showfns.jl b/examples/utils/showfns.jl new file mode 100644 index 00000000..47229415 --- /dev/null +++ b/examples/utils/showfns.jl @@ -0,0 +1,34 @@ +import Plotly +function showfn(space,i) + geo = geometry(space) + T = coordtype(geo) + X = Dict{Int,T}() + Y = Dict{Int,T}() + Z = Dict{Int,T}() + U = Dict{Int,T}() + V = Dict{Int,T}() + W = Dict{Int,T}() + for sh in space.fns[i] + chrt = chart(geo, cells(geo)[sh.cellid]) + nbd = CompScienceMeshes.center(chrt) + vals = refspace(space)(nbd) + x,y,z = cartesian(nbd) + @show vals[sh.refid].value + u,v,w = vals[sh.refid].value + # @show x, y, z + # @show u, v, w + X[sh.cellid] = x + Y[sh.cellid] = y + Z[sh.cellid] = z + U[sh.cellid] = get(U,sh.cellid,zero(T)) + sh.coeff * u + V[sh.cellid] = get(V,sh.cellid,zero(T)) + sh.coeff * v + W[sh.cellid] = get(W,sh.cellid,zero(T)) + sh.coeff * w + end + X = collect(values(X)) + Y = collect(values(Y)) + Z = collect(values(Z)) + U = collect(values(U)) + V = collect(values(V)) + W = collect(values(W)) + Plotly.cone(x=X,y=Y,z=Z,u=U,v=V,w=W) +end \ No newline at end of file diff --git a/src/BEAST.jl b/src/BEAST.jl index 5d861afb..4707a8c0 100644 --- a/src/BEAST.jl +++ b/src/BEAST.jl @@ -2,15 +2,26 @@ module BEAST using Distributed using LinearAlgebra -# using Pkg using SharedArrays using SparseArrays using FillArrays using BlockArrays -using SparseMatrixDicts +using ExtendableSparse +using ConvolutionOperators using SauterSchwabQuadrature +using SauterSchwab3D using FastGaussQuadrature +using LinearMaps +using LiftedMaps + +#using TimerOutputs + +using AbstractTrees +using NestedUnitRanges + +using Infiltrator +using TestItems import LinearAlgebra: cross, dot import LinearAlgebra: ×, ⋅ @@ -21,15 +32,20 @@ export dot export planewave export RefSpace, numfunctions, coordtype, scalartype, assemblydata, geometry, refspace, valuetype -export lagrangecxd0, lagrangec0d1, duallagrangec0d1 +export lagrangecxd0, lagrangec0d1, duallagrangec0d1, lagrangec0d2, unitfunctioncxd0, unitfunctionc0d1 export duallagrangecxd0 export lagdimension export restrict export raviartthomas, raowiltonglisson, positions export brezzidouglasmarini +export brezzidouglasmarini3d +export nedelecd3d +export nedelecc3d export portcells, rt_ports, getindex_rtg, subset -export StagedTimeStep -export subdsurface,subdBasis,assemblydata,refspace + +export StagedTimeStep, numstages +export subdsurface, subdBasis, assemblydata, refspace + export spatialbasis, temporalbasis export ⊗ export timebasisc0d1 @@ -39,14 +55,25 @@ export timebasisshiftedlagrange export TimeBasisDeltaShifted export ntrace export strace -export SingleLayer -export DoubleLayer -export DoubleLayerTransposed -export HyperSingular +export ttrace export HH3DSingleLayerTDBIO export HH3DDoubleLayerTDBIO -export ∂n +export ∂n, grad export HH3DHyperSingularFDBIO +export DirichletTrace +export HH3DMonopole, gradHH3DMonopole +export HH3DLinearPotential + +export HH3DSingleLayerNear +export HH3DDoubleLayerNear +export HH3DDoubleLayerTransposedNear +export HH3DHyperSingularNear + +export HH2DSingleLayerNear +export HH2DDoubleLayerNear +export HH2DDoubleLayerTransposedNear +export HH2DHyperSingularNear + export NitscheHH3 export MWSingleLayerTDIO export MWDoubleLayerTDIO @@ -55,12 +82,18 @@ export MWFarField3D export MWSingleLayer3D, MWHyperSingular, MWWeaklySingular export MWDoubleLayer3D export PlaneWaveMW +export dipolemw3d, DipoleMW export TangTraceMW, CrossTraceMW export curl +export gradient export MWSingleLayerField3D export SingleLayerTrace -export DoubleLayerRotatedMW3D +export DoubleLayerRotatedMW3D, MWDoubleLayerRotatedFarField3D export MWSingleLayerPotential3D + +export VIEOperator +export VSIEOperator + export gmres export @hilbertspace, @varform, @discretise export solve @@ -87,6 +120,7 @@ export potential export get_scatter_parameters export quaddata +export DofInterpolate export kernelvals @@ -97,6 +131,7 @@ export PlaneWaveDirichlet export PlaneWaveNeumann struct NormalVector end +struct TangentVector end using CompScienceMeshes using Combinatorics @@ -106,36 +141,54 @@ using SparseArrays function convolve end include("utils/polynomial.jl") -include("utils/mixedblockarrays.jl") -include("timedomain/convop.jl") -include("utils/sparsend.jl") include("utils/specialfns.jl") include("utils/combinatorics.jl") include("utils/linearspace.jl") -include("utils/matrixconv.jl") -include("utils/polyeig.jl") - -include("bases/basis.jl") -include("bases/lincomb.jl") -include("bases/trace.jl") -include("bases/restrict.jl") -include("bases/divergence.jl") +include("utils/zeromap.jl") +include("utils/rank1map.jl") +include("utils/lagpolys.jl") +include("utils/butchertableau.jl") +include("utils/variational.jl") +include("bases/localbasis.jl") include("bases/local/laglocal.jl") include("bases/local/rtlocal.jl") +include("bases/local/rt2local.jl") include("bases/local/ndlocal.jl") +include("bases/local/nd2local.jl") include("bases/local/bdmlocal.jl") +include("bases/local/ncrossbdmlocal.jl") include("bases/local/ndlcclocal.jl") include("bases/local/ndlcdlocal.jl") +include("bases/local/bdm3dlocal.jl") +include("bases/local/rtqlocal.jl") +include("bases/local/gwplocal.jl") +include("bases/local/gwpdivlocal.jl") + + +include("bases/basis.jl") +include("bases/lincomb.jl") +include("bases/trace.jl") +include("bases/restrict.jl") +include("bases/divergence.jl") +include("bases/global/globaldofs.jl") +include("bases/global/gwpglobal.jl") +include("bases/global/gwpdivglobal.jl") include("bases/lagrange.jl") include("bases/rtspace.jl") +include("bases/rt2space.jl") include("bases/rtxspace.jl") include("bases/bcspace.jl") include("bases/ndspace.jl") +include("bases/nd2space.jl") include("bases/bdmdiv.jl") +include("bases/ncrossbdmspace.jl") include("bases/ndlccspace.jl") include("bases/ndlcdspace.jl") +include("bases/dual3d.jl") +include("bases/bdm3dspace.jl") +include("bases/rtqspace.jl") include("bases/subdbasis.jl") @@ -144,18 +197,48 @@ include("bases/stagedtimestep.jl") include("bases/timebasis.jl") include("bases/tensorbasis.jl") -include("excitation.jl") include("operator.jl") + +include("quadrature/strategies/quadstrat.jl") +include("quadrature/quadstrats.jl") +include("quadrature/doublenumqstrat.jl") +include("quadrature/doublenumsauterqstrat.jl") +include("quadrature/doublenumwiltonsauterqstrat.jl") +include("quadrature/doublenumwiltonbogaertqstrat.jl") +include("quadrature/selfsauterdnumotherwiseqstrat.jl") +include("quadrature/nonconformingintegralopqstrat.jl") +include("quadrature/commonfaceoverlappingedgeqstrat.jl") +include("quadrature/strategies/cfcvsautercewiltonpdnumqstrat.jl") +include("quadrature/strategies/testrefinestrialqstrat.jl") +include("quadrature/strategies/trialrefinestestqstrat.jl") +include("quadrature/strategies/nonconftestbaryrefoftrialqstrat.jl") +include("quadrature/strategies/timedomain/nothingqstrat.jl") + + +include("excitation.jl") +include("gridfunction.jl") include("localop.jl") +include("operators/quasilocalops.jl") include("multiplicativeop.jl") include("identityop.jl") include("integralop.jl") -include("quaddata.jl") +include("dyadicop.jl") +include("interpolation.jl") + +include("quadrature/rules/momintegrals.jl") +include("quadrature/doublenumints.jl") +include("quadrature/singularityextractionints.jl") +include("quadrature/SauterSchwabQuadrature1D.jl") +include("quadrature/sauterschwabints.jl") +include("quadrature/nonconformingoverlapqrule.jl") +include("quadrature/nonconformingtouchqrule.jl") +include("quadrature/rules/testrefinestrialqrule.jl") +include("quadrature/rules/trialrefinestestqrule.jl") +include("quadrature/rules/testinbaryrefoftrialqrule.jl") + include("postproc.jl") include("postproc/segcurrents.jl") - -include("quadrature/double_quadrature.jl") -include("quadrature/singularity_extraction.jl") +include("postproc/farfield.jl") include("timedomain/tdintegralop.jl") include("timedomain/tdexcitation.jl") @@ -163,28 +246,54 @@ include("timedomain/motlu.jl") include("timedomain/tdtimeops.jl") include("timedomain/rkcq.jl") include("timedomain/zdomain.jl") +include("timedomain/td_symmetric_quadstrat.jl") + +include("quadrature/strategies/timedomain/excitation/numspacenumtimeqstrat.jl") +include("quadrature/rules/timedomain/excitation/multiquadqrule.jl") +include("quadrature/rules/timedomain/excitation/singlequad2qrule.jl") # Support for Maxwell equations include("maxwell/mwexc.jl") include("maxwell/mwops.jl") -include("maxwell/wiltonints.jl") -include("maxwell/sauterschwabints_rt.jl") -include("maxwell/sauterschwabints_bdm.jl") +include("maxwell/qlmwops.jl") + include("maxwell/nxdbllayer.jl") +include("maxwell/wiltonints.jl") +include("maxwell/sauterschwabints_bdm_rt.jl") +#include("maxwell/sauterschwabints_rt.jl") +#include("maxwell/sauterschwabints_bdm.jl") include("maxwell/nitsche.jl") include("maxwell/farfield.jl") -include("maxwell/nearfield.jl") include("maxwell/spotential.jl") +include("maxwell/nearfield.jl") include("maxwell/maxwell.jl") include("maxwell/sourcefield.jl") # Support for the Helmholtz equation -include("helmholtz2d/helmholtzop.jl") - include("helmholtz3d/hh3dexc.jl") include("helmholtz3d/hh3dops.jl") include("helmholtz3d/nitsche.jl") +include("helmholtz3d/hh3dnear.jl") +include("helmholtz3d/hh3dfar.jl") +include("helmholtz3d/hh3d_sauterschwabqr.jl") +include("helmholtz3d/helmholtz3d.jl") +include("helmholtz3d/wiltonints.jl") + +include("helmholtz2d/hh2dexc.jl") +include("helmholtz2d/hh2dops.jl") +include("helmholtz2d/hh2dnear.jl") +include("helmholtz2d/helmholtz2d.jl") + +#suport for Volume Integral equation +include("volumeintegral/vie.jl") +include("volumeintegral/vieexc.jl") +include("volumeintegral/vieops.jl") +include("volumeintegral/farfield.jl") +include("volumeintegral/vsie.jl") +include("volumeintegral/vsieops.jl") +include("volumeintegral/sauterschwab_ints.jl") + include("decoupled/dpops.jl") include("decoupled/potentials.jl") @@ -197,22 +306,26 @@ include("maxwell/timedomain/mwtdops.jl") include("maxwell/timedomain/mwtdexc.jl") include("maxwell/timedomain/tdfarfield.jl") -include("utils/butchertableau.jl") -include("utils/variational.jl") include("solvers/solver.jl") include("solvers/lusolver.jl") include("solvers/itsolver.jl") +include("solvers/gmres.jl") +include("utils/plotlyglue.jl") -const x̂ = point(1,0,0) -const ŷ = point(0,1,0) -const ẑ = point(0,0,1) + +const x̂ = point(1, 0, 0) +const ŷ = point(0, 1, 0) +const ẑ = point(0, 0, 1) export x̂, ŷ, ẑ const n = NormalVector() export n +export TangentTrace +#const to = TimerOutput() + end # module diff --git a/src/bases/basis.jl b/src/bases/basis.jl index a92474d8..6aca4754 100644 --- a/src/bases/basis.jl +++ b/src/bases/basis.jl @@ -1,7 +1,11 @@ -abstract type RefSpace{T,D} end + + abstract type AbstractSpace end abstract type Space{T} <: AbstractSpace end +Base.length(s::AbstractSpace) = numfunctions(s) +Base.in(x, s::AbstractSpace) = (x => s) + """ scalartype(s) @@ -20,12 +24,12 @@ Returns the ReferenceSpace of local shape functions on which the basis is built. function refspace end -""" - numfunctions(r) +# """ +# numfunctions(r) -Return the number of functions in a `Space` or `RefSpace`. -""" -numfunctions(rs::RefSpace{T,D}) where {T,D} = D +# Return the number of functions in a `Space` or `RefSpace`. +# """ +# numfunctions(rs::RefSpace{T,D}) where {T,D} = D """ @@ -50,21 +54,65 @@ in `basis` are defined. The order the elements are encountered needs correspond to the element indices used in the data structure returned by `assemblydata`. """ geometry(s::Space) = s.geo + +""" + basisfunction(basis, i) + +Returns a vector of the shape functions defining the `i`th function +of the `basis`. +""" basisfunction(s::Space, i) = s.fns[i] + +""" + numfunctions(basis) + +Number of functions in the basis. +""" numfunctions(space::Space) = length(space.fns) -mutable struct DirectProductSpace{T} <: AbstractSpace - factors::Vector{Space{T}} +struct DirectProductSpace{T,S<:AbstractSpace} <: AbstractSpace + factors::Vector{S} +end + +function DirectProductSpace(factors::Vector{S}) where {S<:AbstractSpace} + @assert !isempty(factors) + T = scalartype(factors...) + return DirectProductSpace{T,S}(factors) end -export cross, × +Base.getindex(dps::DirectProductSpace, i) = dps.factors[i] -cross(a::Space{T}, b::Space{T}) where {T} = DirectProductSpace(Space{T}[a,b]) -cross(a::DirectProductSpace{T}, b::Space{T}) where {T} = DirectProductSpace(Space{T}[a.factors; b]) +defaultquadstrat(op, tfs::DirectProductSpace, bfs::DirectProductSpace) = defaultquadstrat(op, tfs.factors[1], bfs.factors[1]) +defaultquadstrat(op, tfs::Space, bfs::DirectProductSpace) = defaultquadstrat(op, tfs, bfs.factors[1]) +defaultquadstrat(op, tfs::DirectProductSpace, bfs::Space) = defaultquadstrat(op, tfs.factors[1], bfs) + +# defaultquadstrat(op, tfs::DirectProductSpace, bfs::DirectProductSpace) = defaultquadstrat(op, tfs.factors[1], bfs.factors[1]) +# defaultquadstrat(op, tfs::RefSpace, bfs::DirectProductSpace) = defaultquadstrat(op, tfs, bfs.factors[1]) +# defaultquadstrat(op, tfs::DirectProductSpace, bfs::RefSpace) = defaultquadstrat(op, tfs.factors[1], bfs) +# scalartype(sp::DirectProductSpace{T}) where {T} = T + +# export cross, × +export × + +function Base.:+(x::AbstractSpace...) + T = scalartype(x...) + return DirectProductSpace{T, AbstractSpace}([x...]) +end + +cross(a::Space{T}, b::Space{T}) where {T} = DirectProductSpace{T,Space{T}}(Space{T}[a,b]) +cross(a::DirectProductSpace{T}, b::Space{T}) where {T} = DirectProductSpace{T,Space{T}}([a.factors; b]) numfunctions(S::DirectProductSpace) = sum([numfunctions(s) for s in S.factors]) +Base.length(S::DirectProductSpace) = numfunctions(S) scalartype(s::DirectProductSpace{T}) where {T} = T geometry(x::DirectProductSpace) = weld(x.geo...) + +AbstractTrees.children(x::AbstractSpace) = () +AbstractTrees.children(x::DirectProductSpace) = x.factors + +Base.iterate(x::DirectProductSpace) = iterate(x.factors) +Base.iterate(x::DirectProductSpace, state) = iterate(x.factors, state) + struct Shape{T} cellid::Int refid::Int @@ -104,42 +152,58 @@ end function add!(bf::Vector{Shape{T}}, cellid, refid, coeff) where T for (i,sh) in pairs(bf) if sh.cellid == cellid && sh.refid == refid - bf[i] = Shape(cellid, refid, sh.coeff + coeff) + bf[i] = Shape(cellid, refid, sh.coeff + T(coeff)) return nothing end end - push!(bf, Shape(cellid, refid, coeff)) + push!(bf, Shape(cellid, refid, T(coeff))) return nothing end """ - charts, admap = assemblydata(basis) + charts, admap, act_to_global = assemblydata(basis; onlyactives=true) + +Given a `basis` this function returns a data structure containing the information +required for matrix assemble, that is, the vector `charts` containing `Simplex` elements, +a variable `admap` of type `AssemblyData`, and a mapping from indices of actively +used simplices to global simplices. -Given a Basis this function returns a data structure containing the information -required for matrix assemble. More precise the following expressions are valid -for the returned object `ad`: +When `onlyactives` is `true`, another layer of indices is introduced to filter out all +cells of the mesh that are not in the union of the support of the basis functions +(i.e., when the basis functions are defined only on a part of the mesh). + +`admap` is, in essence, a three-dimensional array of named tuples, which, +by wrapping it in the struct `AssemblyData`, allows the definition of iterators. +The tuple consists of the two entries ``` -ad[c,r,i].globalindex -ad[c,r,i].coefficient +admap[i,r,c].globalindex +admap[i,r,c].coefficient ``` -Here, `c` and `r` are indices in the iterable set of geometric elements and the -set of local shape functions on each element. `i` ranges from 1 to the maximum -number of basis functions local shape function `r` on element `r` contributes -to. - -For a triplet `(c,r,i)`, `globalindex` is the index in the Basis of the -`i`-th basis function that has a contribution from local shape function `r` on -element `r`. `coefficient` is the coefficient of that contribution in the -linear combination defining that basis function in terms of local shape +Here, `c` and `r` are indices in the iterable set of (active) simplices and the +set of shape functions on each cell/simplex: `r` ranges from 1 to the number of +shape functions on a cell/simplex, `c` ranges from 1 to the number of active +simplices, and `i` ranges from 1 to the number of maximal number of basis functions, +where any of the shape functions contributes to. + +For example, for continuous piecewise linear lagrange functions (c0d1), each of the +three shape functions on a triangle are associated with exactly one Lagrange function, +and therefore `i` is limited to 1. + +*Note*: When `onlyactives=false`, the indices `c` correspond to +the position of the corresponding cell/simplex whilst iterating over `geometry(basis)`. +When `onlyactives=true`, then `act_to_global(c)` correspond to the position of the +corresponding cell/simplex whilst iterating over `geometry(basis)`. + +For a triplet `(i,r,c)`, `globalindex` is the index in the `basis` of the +`i`th basis function that has a contribution from shape function `r` on +(active) cell/simplex `c`. `coefficient` is the coefficient of that contribution in the +linear combination defining that basis function in terms of shape function. - -*Note*: the indices `c` correspond to the position of the corresponding -element whilst iterating over `geometry(basis)`. """ -function assemblydata(basis::Space) +function assemblydata(basis::Space; onlyactives=true) @assert numfunctions(basis) != 0 @@ -149,19 +213,38 @@ function assemblydata(basis::Space) num_cells = numcells(geo) num_bfs = numfunctions(basis) - num_refs = numfunctions(refspace(basis)) + + ch = chart(geo, first(geo)) + dom = domain(ch) + num_refs = numfunctions(refspace(basis), dom) - # # determine the maximum number of function defined over a given cell + # Determine the number of functions defined over a given cell + # and per local shape function. celltonum = make_celltonum(num_cells, num_refs, num_bfs, basis) - # # mark the active elements, i.e. elements over which - # # at least one function is defined. - active, index_among_actives, num_active_cells, act_to_global = - index_actives(num_cells, celltonum) + # In general, a basis function space might only be defined + # over a small portion of the underlying mesh. To avoid + # the inefficient iterating of cells, which are not in the support of + # any of the basis functions, we filter out only those cells, over + # which at least one basis function is defined. + if onlyactives + active, index_among_actives, num_active_cells, act_to_global = + index_actives(num_cells, celltonum) + else + active = trues(num_cells) + num_active_cells = num_cells + index_among_actives = collect(1:num_cells) + act_to_global = collect(1:num_cells) + end + + num_active_cells == 0 && return nothing - @assert num_active_cells != 0 + # Generate the a vector of Simplexes associated + # with the active cells only elements = instantiate_charts(geo, num_active_cells, active) + # Determine the maximal number of functions associated with a + # local shape function max_celltonum = maximum(celltonum) fill!(celltonum, 0) data = fill((0,zero(T)), max_celltonum, num_refs, num_active_cells) @@ -180,7 +263,14 @@ function assemblydata(basis::Space) return elements, AssemblyData(data), act_to_global end +""" + celltonum = make_celltonum(num_cells, num_refs, num_bfs, basis) +Computes the array `celltonum[c,r]` that, given the index +`c` of a cell of the mesh associated with the `basis` and the index `r` +of the shape function in the refspace associated with `basis`, provides the number +of basis functions where the `r`th shape function on cell `c` is used in their definition. +""" function make_celltonum(num_cells, num_refs, num_bfs, basis) celltonum = zeros(Int, num_cells, num_refs) for b in 1 : num_bfs @@ -194,6 +284,20 @@ function make_celltonum(num_cells, num_refs, num_bfs, basis) return celltonum end +""" + active, index_among_actives, num_active_cells, act_to_global = + index_actives(num_cells, celltonum) + +Given the number of cells of the mesh `num_cells` and an array indicating, which +shape functions of each cell are used in the definition of the basis, `celltonum`, +`index_actives(num_cells, celltonum)` computes a Boolean vector `active` that indicates for +each cell index if the cell is in the support of any basis function. For this reduced set +of active cells, a new index set is introduced, where `index_among_actives` is a vector +providing the mapping from the cells of the mesh to the indices of the active cells, +`num_active_cells` provides the number of all active cells, and `act_to_global` is a +vector providing the mapping from the indices of the active cells to the indices of +cells of the underlying mesh of the basis function spce. +""" function index_actives(num_cells, celltonum) @assert num_cells == size(celltonum,1) active = falses(num_cells) @@ -212,14 +316,19 @@ function index_actives(num_cells, celltonum) return active, index_among_actives, num_active_cells, act_to_global end +""" + instantiate_charts(geo, num_active_cells, active) +Returns a vector of all actively used simplices. +""" function instantiate_charts(geo, num_active_cells, active) - E = typeof(chart(geo, first(cells(geo)))) + @assert length(geo) != 0 + E = typeof(chart(geo, first(geo))) elements = Vector{E}(undef,num_active_cells) j = 1 - for (i,cell) in enumerate(cells(geo)) + for (i,p) in enumerate(geo) active[i] || continue - elements[j] = chart(geo, cell) + elements[j] = chart(geo, p) j += 1 end return elements @@ -246,3 +355,99 @@ function Base.:*(space::Space, A::SparseMatrixCSC) end return (typeof(space))(space.geo, fns, pos) end + + +function addf!(fn::Vector{<:Shape}, x::Vector, space::Space, idcs::Vector{Int}) + for (m,bf) in enumerate(space.fns) + for sh in bf + cellid = idcs[sh.cellid] + BEAST.add!(fn, cellid, sh.refid, sh.coeff * x[m]) + end + end +end + +function support(s::BEAST.AbstractSpace, index::Int) + geo = geometry(s) + s1 = subset(s,[index]) + charts, ad, activecells = BEAST.assemblydata(s1) + return geo[activecells] +end + +function functionvals(s::BEAST.Space, index::Int, n=3) + + s1 = subset(s,[index]) + charts, ad, a2g = BEAST.assemblydata(s1) + support = geometry(s)[a2g] + + vals = Any[] + ctrs = Any[] + refs = refspace(s) + for (p,ch) in enumerate(charts) + for i in 1:n-1 + for j in 1:n-1 + i+j < n || continue + val = zero(BEAST.valuetype(refs, typeof(ch))) + ct = CompScienceMeshes.neighborhood(ch, (i/n,j/n)) + fx = refs(ct) + for r in eachindex(fx) + for (m,a) in ad[p,r] + @assert m == 1 + val += a * fx[r].value + end + end + push!(ctrs, cartesian(ct)) + push!(vals, val) + end + end + end + + return ctrs, vals +end + + + +function eval(s::BEAST.Space, i::Int, cellid, u) + + ch = chart(cellid, s.geo) + p = neighborhood(ch, u) + + ϕ = refpsace(s) + ϕp = ϕ(p) + + U = valuetype(ϕ, ch) + + r = zero(U) + for sh in s.fns[i] + sh.cellid == cellid || continue + r += sh.coeff * ϕp[sh.refid].value + end + + return r +end + + +function union(spaces::Vector{S}) where {S<:Space} + + geo1 = geometry(spaces[1]) + for i in 2:length(spaces) + @assert geo1 == geometry(spaces[i]) + end + + fns = reduce(vcat, [s.fns for s in spaces]) + pos = reduce(vcat, [s.pos for s in spaces]) + return S(geo1, fns, pos) +end + +@testitem "union of spaces" begin + using CompScienceMeshes + + m1 = meshrectangle(1.0, 1.0, 1.0, 3) + bnd_edges = boundary(m1) + int_edges = setminus(skeleton(m1, 1), bnd_edges) + + X1 = raviartthomas(m1, int_edges) + X2 = raviartthomas(m1, bnd_edges) + + X = BEAST.union([X1, X2]) + @test numfunctions(X) == 5 +end \ No newline at end of file diff --git a/src/bases/bcspace.jl b/src/bases/bcspace.jl index 5788d1e3..fdd07481 100644 --- a/src/bases/bcspace.jl +++ b/src/bases/bcspace.jl @@ -87,42 +87,78 @@ isclosed(a, pred) = length(a)>2 && pred(a[end], a[1]) Construct the set of Buffa-Christiansen functions subject to mesh Γ and only enforcing zero normal components on ∂Γ ∖ γ. """ -function buffachristiansen(Γ, γ=mesh(coordtype(Γ),1,3); ibscaled=false) +function buffachristiansen(Γ, γ=mesh(coordtype(Γ),1,3); ibscaled=false, sort=:spacefillingcurve, edges=:all) @assert CompScienceMeshes.isoriented(Γ) T = coordtype(Γ) P = vertextype(Γ) - edges = skeleton(Γ, 1) + in_interior = interior_tpredicate(Γ) + on_junction = overlap_gpredicate(γ) + + # edges = skeleton(Γ, 1; sort) + + if edges == :all + edges = skeleton(Γ, 1; sort) + edges = submesh(edges) do edges, edge + ch = chart(edges, edge) + !in_interior(edges, edge) && !on_junction(ch) && return false + return true + end + end + @assert edges isa CompScienceMeshes.AbstractMesh + @assert dimension(edges) == 1 + fine = if ibscaled CompScienceMeshes.lineofsight_refinement(Γ) else - barycentric_refinement(Γ) + barycentric_refinement(Γ; sort) end - in_interior = interior_tpredicate(Γ) - on_junction = overlap_gpredicate(γ) - # first pass to determine the number of functions - numfuncs = 0 - for edge in cells(edges) - ch = chart(edges, edge) - !in_interior(edge) && !on_junction(ch) && continue - numfuncs += 1 - end + # # first pass to determine the number of functions + # numfuncs = 0 + # for edge in edges + # ch = chart(edges, edge) + # !in_interior(edge) && !on_junction(ch) && continue + # numfuncs += 1 + # end vtof, vton = vertextocellmap(fine) jct_pred = overlap_gpredicate(γ) - bcs, k = Vector{Vector{Shape{T}}}(undef,numfuncs), 1 - pos = Vector{P}(undef,numfuncs) - for (i,edge) in enumerate(cells(edges)) + bcs, k = Vector{Vector{Shape{T}}}(undef,length(edges)), 1 + pos = Vector{P}(undef,length(edges)) + for (i,e) in enumerate(edges) - ch = chart(edges, edge) - !in_interior(edge) && !on_junction(ch) && continue + edge = CompScienceMeshes.indices(edges,e) + ch = chart(edges, e) + ln = volume(ch) + # !in_interior(edge) && !on_junction(ch) && continue # index of edge center in fine's vertexbuffer - p = numvertices(edges) + i + v = edge[1] + n = vton[v] + F = vtof[v,1:n] + supp = fine[F] + bnd = boundary(supp) + bnd_nodes = skeleton(bnd,0) + p = 0 + for bn in bnd_nodes + node = CompScienceMeshes.indices(bnd_nodes, bn) + vert1 = vertices(Γ)[edge[1]] + vert2 = vertices(Γ)[edge[2]] + vert = vertices(fine)[node[1]] + dist = norm(vert-vert1) + node[1] == edge[1] && continue + if dot(vert-vert1,vert2-vert1) ≈ dist*ln + p = node[1] + break + end + end + @assert p != 0 + + # p = numvertices(edges) + i pos[k] = vertices(fine)[p] # sanity check @@ -244,7 +280,7 @@ function buildhalfbc(fine, S, v, p, onjunction, ibscaled) # This charge needs to be compensated by interior divergence total_charge = (!c_on_boundary || num_junctions == 2) ? 1 : 0 charges = if ibscaled - face_areas = [volume(chart(fine, cells(fine)[s])) for s in S] + face_areas = [volume(chart(fine, s)) for s in S] face_areas / sum(face_areas) else fill(total_charge/n, n) @@ -306,7 +342,7 @@ function buildhalfbc(fine, S, v, p, onjunction, ibscaled) face = cells(fine)[f] i = something(findfirst(==(v),face), 0) @assert i != 0 - ch = chart(fine, cells(fine)[f]) + ch = chart(fine, f) area = volume(ch) qps = quadpoints(ch, 3) @assert sum(w for (p,w) in qps) ≈ area @@ -326,7 +362,7 @@ function buildhalfbc(fine, S, v, p, onjunction, ibscaled) face = cells(fine)[f] i = something(findfirst(==(v), face), 0) @assert i != 0 - ch = chart(fine, face) + ch = chart(fine, f) area = volume(ch) vct = ch.vertices[mod1(i+2,3)] - ch.vertices[mod1(i+1,3)] γ += 0.25/area * dot(vct,vct) @@ -348,7 +384,7 @@ function buildhalfbc(fine, S, v, p, onjunction, ibscaled) face = cells(fine)[f] i = something(findfirst(==(v),face), 0) @assert i != 0 - ch = chart(fine, cells(fine)[f]) + ch = chart(fine, f) area = volume(ch) qps = quadpoints(ch, 3) @assert sum(w for (p,w) in qps) ≈ area @@ -372,62 +408,74 @@ end using LinearAlgebra function buildhalfbc2(patch, port, dirichlet, prt_fluxes) - println() + # println() edges = skeleton(patch,1) verts = skeleton(patch,0) bndry = boundary(patch) - dirbnd = submesh(dirichlet) do edge - edge in cells(bndry) && return true - reverse(edge) in cells(bndry) && return true - return false + in_bndry = in(bndry) + dirbnd = submesh(dirichlet) do m,edge + in_bndry(m,edge) + # edge in cells(bndry) && return true + # reverse(edge) in cells(bndry) && return true + # return false end - @show numcells(patch) - @show numcells(dirbnd) + # @show numcells(patch) + # @show numcells(dirbnd) @assert numcells(dirbnd) ≤ 4 bnd_dirbnd = boundary(dirbnd) nodes_dirbnd = skeleton(dirbnd,0) - int_nodes_dirbnd = submesh(nodes_dirbnd) do node - node in cells(bnd_dirbnd) && return false - return true + in_bnd_dirbnd = in(bnd_dirbnd) + int_nodes_dirbnd = submesh(nodes_dirbnd) do m,node + # node in cells(bnd_dirbnd) && return false + # return true + return !in_bnd_dirbnd(m, node) end - @show numcells(int_nodes_dirbnd) + # @show numcells(int_nodes_dirbnd) @assert numcells(int_nodes_dirbnd) ≤ 2 int_pred = interior_tpredicate(patch) num_edges_on_port = 0 num_edges_on_dirc = 0 - @show numcells(edges) + # @show numcells(edges) - for edge in cells(edges) - println(edge) - end - println() - for edge in cells(dirbnd) - println(edge) - end - println() - for edge in cells(port) - println(edge) - end + # for edge in cells(edges) + # println(edge) + # end + # println() + # for edge in cells(dirbnd) + # println(edge) + # end + # println() + # for edge in cells(port) + # println(edge) + # end - int_edges = submesh(edges) do edge - (edge in cells(port)) && (num_edges_on_port+=1 ; return false) - (reverse(edge) in cells(port)) && (num_edges_on_port+=1 ; return false) - edge in cells(dirbnd) && (num_edges_on_dirc+=1; return true) - reverse(edge) in cells(dirbnd) && (num_edges_on_dirc+=1; return true) - (!int_pred(edge)) && return false + in_port = in(port) + in_dirbnd = in(dirbnd) + int_edges = submesh(edges) do m,edge + + in_port(m,edge) && (num_edges_on_port+=1; return false) + in_dirbnd(m,edge) && (num_edges_on_dirc+=1; return true) + !int_pred(m,edge) && return false return true + + # (edge in cells(port)) && (num_edges_on_port+=1 ; return false) + # (reverse(edge) in cells(port)) && (num_edges_on_port+=1 ; return false) + # edge in cells(dirbnd) && (num_edges_on_dirc+=1; return true) + # reverse(edge) in cells(dirbnd) && (num_edges_on_dirc+=1; return true) + # (!int_pred(edge)) && return false + # return true end - println() - for edge in cells(int_edges) - println(edge) - end - @show numcells(int_edges) - @show num_edges_on_port - @show num_edges_on_dirc + # println() + # for edge in cells(int_edges) + # println(edge) + # end + # @show numcells(int_edges) + # @show num_edges_on_port + # @show num_edges_on_dirc bnd_verts = skeleton(bndry,0) prt_verts = skeleton(port,0) @@ -436,13 +484,20 @@ function buildhalfbc2(patch, port, dirichlet, prt_fluxes) # end # int_verts = submesh(!isonboundary, verts) # dirichlet_nodes = skeleton(dirichlet,0) - int_verts = submesh(verts) do node - node in cells(int_nodes_dirbnd) && return true - node in cells(bnd_verts) && return false - node in cells(prt_verts) && return false + in_int_nodes_dirbnd = in(int_nodes_dirbnd) + in_bnd_verts = in(bnd_verts) + in_prt_verts = in(prt_verts) + int_verts = submesh(verts) do m,node + in_int_nodes_dirbnd(m,node) && return true + in_bnd_verts(m,node) && return false + in_prt_verts(m,node) && return false return true + # node in cells(int_nodes_dirbnd) && return true + # node in cells(bnd_verts) && return false + # node in cells(prt_verts) && return false + # return true end - @show numcells(int_verts) + # @show numcells(int_verts) RT_int = raviartthomas(patch, cellpairs(patch, int_edges)) RT_prt = raviartthomas(patch, cellpairs(patch, port)) @@ -455,16 +510,17 @@ function buildhalfbc2(patch, port, dirichlet, prt_fluxes) Q = assemble(Id, divergence(RT_int), divergence(RT_prt)) d = -Q * prt_fluxes - @show numfunctions(L0_int) + # @show numfunctions(L0_int) @assert numfunctions(L0_int) in [1,2] C = assemble(Id, curl(L0_int), RT_int) curl_L0_int = curl(L0_int) c = real(assemble(Id, curl_L0_int, RT_prt)) * prt_fluxes - x1 = pinv(D) * d - N = nullspace(D) - @show size(N) - @show size(C) + mD = Matrix(D) + x1 = pinv(mD) * d + N = nullspace(mD) + # @show size(N) + # @show size(C) p = (C*N) \ (c - C*x1) x = x1 + N*p @@ -478,44 +534,47 @@ function buildhalfbc2(patch, port, dirichlet, prt_fluxes) end - -function buffachristiansen2(Faces::CompScienceMeshes.AbstractMesh) +# Use the algebraic construction also used in dual3d +function buffachristiansen2(Faces::CompScienceMeshes.AbstractMesh{U,D1,T}) where {U,D1,T} faces = barycentric_refinement(Faces) Edges = skeleton(Faces,1) Bndry = boundary(Faces) - Edges = submesh(Edges) do Edge - Edge in cells(Bndry) && return false - reverse(Edge) in cells(Bndry) && return false - return true + inBndry = in(Bndry) + Edges = submesh(Edges) do m,Edge + return !inBndry(m,Edge) + # Edge in cells(Bndry) && return false + # reverse(Edge) in cells(Bndry) && return false + # return true end - T = Float64 + #T = Float64 bfs = Vector{Vector{Shape{T}}}(undef, numcells(Edges)) pos = Vector{vertextype(Faces)}(undef, numcells(Edges)) dirichlet = boundary(faces) - for (E,Edge) in enumerate(cells(Edges)) - @show Edge + for (E,Edge) in enumerate(Edges) + EdgeInds = CompScienceMeshes.indices(Edges, Edge) + # @show Edge bfs[E] = Vector{Shape{T}}() pos[E] = cartesian(center(chart(Edges,Edge))) # port_vertex_idx = numvertices(Faces) + E port_vertex_idx = argmin(norm.(vertices(faces) .- Ref(pos[E]))) # pos[E] = vertices(faces)[port_vertex_idx] - @show carttobary(chart(Edges,Edge),pos[E]) - @show pos[E] - @show (Faces.vertices[Edge[1]]+Faces.vertices[Edge[2]])/2 + # @show carttobary(chart(Edges,Edge),pos[E]) + # @show pos[E] + # @show (Faces.vertices[Edge[1]]+Faces.vertices[Edge[2]])/2 # Build the plus-patch - ptch_vert_idx = Edge[1] + ptch_vert_idx = EdgeInds[1] ptch_face_idcs = [i for (i,face) in enumerate(cells(faces)) if ptch_vert_idx in face] patch = Mesh(vertices(faces), cells(faces)[ptch_face_idcs]) patch_bnd = boundary(patch) - @show numcells(patch_bnd) + # @show numcells(patch_bnd) port = Mesh(vertices(faces), filter(c->port_vertex_idx in c, cells(patch_bnd))) - @show numcells(patch) - @show numcells(port) + # @show numcells(patch) + # @show numcells(port) # @assert numcells(patch) >= 6 @assert numcells(port) == 2 @@ -538,8 +597,8 @@ function buffachristiansen2(Faces::CompScienceMeshes.AbstractMesh) end # Build the minus-patch - ptch_vert_idx = Edge[2] - ptch_face_idcs = [i for (i,face) in enumerate(cells(faces)) if ptch_vert_idx in face] + ptch_vert_idx = EdgeInds[2] + ptch_face_idcs = [i for (i,face) in enumerate((faces)) if ptch_vert_idx in CompScienceMeshes.indices(faces, face)] patch = Mesh(vertices(faces), cells(faces)[ptch_face_idcs]) port = Mesh(vertices(faces), filter(c->port_vertex_idx in c, cells(boundary(patch)))) # @assert numcells(patch) >= 6 @@ -567,23 +626,24 @@ function buffachristiansen2(Faces::CompScienceMeshes.AbstractMesh) RTBasis(faces, bfs, pos) end - -function buffachristiansen3(Faces::CompScienceMeshes.AbstractMesh) +# Extend into both dual faces in a single call to buildhalfbc2 (aka extend_2_form) +function buffachristiansen3(Faces::CompScienceMeshes.AbstractMesh{U,D1,T}) where {U,D1,T} faces = barycentric_refinement(Faces) Edges = skeleton(Faces,1) Bndry = boundary(Faces) - Edges = submesh(Edges) do Edge - Edge in cells(Bndry) && return false - reverse(Edge) in cells(Bndry) && return false + inBndry = in(Bndry) + Edges = submesh(Edges) do m,Edge + inBndry(m,Edge) && return false + # reverse(Edge) in cells(Bndry) && return false return true end - T = Float64 + #T = Float64 bfs = Vector{Vector{Shape{T}}}(undef, numcells(Edges)) pos = Vector{vertextype(Faces)}(undef, numcells(Edges)) dirichlet = boundary(faces) - for (E,Edge) in enumerate(cells(Edges)) + for (E,Edge) in enumerate(Edges) bfs[E] = Vector{Shape{T}}() # port_vertex_idx = numvertices(Faces) + E @@ -593,16 +653,17 @@ function buffachristiansen3(Faces::CompScienceMeshes.AbstractMesh) port_vertex_idx = argmin(norm.(vertices(faces) .- Ref(pos[E]))) # Build the dual support - ptch_face_idcs1 = [i for (i,face) in enumerate(cells(faces)) if Edge[1] in face] - ptch_face_idcs2 = [i for (i,face) in enumerate(cells(faces)) if Edge[2] in face] + EdgeInds = CompScienceMeshes.indices(Edges, Edge) + ptch_face_idcs1 = [i for (i,face) in enumerate(faces) if EdgeInds[1] in CompScienceMeshes.indices(faces,face)] + ptch_face_idcs2 = [i for (i,face) in enumerate(faces) if EdgeInds[2] in CompScienceMeshes.indices(faces,face)] patch1 = Mesh(vertices(faces), cells(faces)[ptch_face_idcs1]) patch2 = Mesh(vertices(faces), cells(faces)[ptch_face_idcs2]) patch_bnd = boundary(patch1) port = Mesh(vertices(faces), filter(c->port_vertex_idx in c, cells(patch_bnd))) patch = CompScienceMeshes.union(patch1, patch2) - @show numcells(patch_bnd) - @show numcells(patch) - @show numcells(port) + # @show numcells(patch_bnd) + # @show numcells(patch) + # @show numcells(port) # @assert numcells(skeleton(patch,1))+2 == numcells(skeleton(patch1,1)) + numcells(skeleton(patch2,1)) @assert numcells(patch) >= 6 @@ -625,28 +686,28 @@ function buffachristiansen3(Faces::CompScienceMeshes.AbstractMesh) end end - # # Build the minus-patch - # ptch_vert_idx = Edge[2] - # ptch_face_idcs = [i for (i,face) in enumerate(cells(faces)) if ptch_vert_idx in face] - # patch = Mesh(vertices(faces), cells(faces)[ptch_face_idcs]) - # port = Mesh(vertices(faces), filter(c->port_vertex_idx in c, cells(boundary(patch)))) - # @assert numcells(patch) >= 6 - # @assert numcells(port) == 2 - # RT_int, RT_prt, x_int, x_prt = buildhalfbc2(patch, port, nothing) - # - # for (m,bf) in enumerate(RT_int.fns) - # for sh in bf - # cellid = ptch_face_idcs[sh.cellid] - # BEAST.add!(bfs[E], cellid, sh.refid, -1.0 * sh.coeff * x_int[m]) - # end - # end - # - # for (m,bf) in enumerate(RT_prt.fns) - # for sh in bf - # cellid = ptch_face_idcs[sh.cellid] - # BEAST.add!(bfs[E],cellid, sh.refid, -1.0 * sh.coeff * x_prt[m]) - # end - # end + # # Build the minus-patch + # ptch_vert_idx = Edge[2] + # ptch_face_idcs = [i for (i,face) in enumerate(cells(faces)) if ptch_vert_idx in face] + # patch = Mesh(vertices(faces), cells(faces)[ptch_face_idcs]) + # port = Mesh(vertices(faces), filter(c->port_vertex_idx in c, cells(boundary(patch)))) + # @assert numcells(patch) >= 6 + # @assert numcells(port) == 2 + # RT_int, RT_prt, x_int, x_prt = buildhalfbc2(patch, port, nothing) + + # for (m,bf) in enumerate(RT_int.fns) + # for sh in bf + # cellid = ptch_face_idcs[sh.cellid] + # BEAST.add!(bfs[E], cellid, sh.refid, -1.0 * sh.coeff * x_int[m]) + # end + # end + + # for (m,bf) in enumerate(RT_prt.fns) + # for sh in bf + # cellid = ptch_face_idcs[sh.cellid] + # BEAST.add!(bfs[E],cellid, sh.refid, -1.0 * sh.coeff * x_prt[m]) + # end + # end end diff --git a/src/bases/bdm3dspace.jl b/src/bases/bdm3dspace.jl new file mode 100644 index 00000000..cabb8774 --- /dev/null +++ b/src/bases/bdm3dspace.jl @@ -0,0 +1,114 @@ +mutable struct BDM3DBasis{T,M,P} <: Space{T} + geo::M + fns::Vector{Vector{Shape{T}}} + pos::Vector{P} +end + +refspace(s::BDM3DBasis{T}) where {T} = BDM3DRefSpace{T}() + + +function brezzidouglasmarini3d(mesh, faces) + T = coordtype(mesh) + P = vertextype(mesh) + num_faces = numcells(faces) + + C = connectivity(faces, mesh, identity) + + #println(C) + rows = rowvals(C) + vals = nonzeros(C) + + #println(rows) + + fns = Vector{Vector{Shape{T}}}(undef, 3*num_faces) + pos = Vector{P}(undef, 3*num_faces) + + #println(cells(faces)) + + + for (i,face) in enumerate(faces) + + fns[3*(i-1)+1] = Vector{Shape{T}}() + fns[3*(i-1)+2] = Vector{Shape{T}}() + fns[3*(i-1)+3] = Vector{Shape{T}}() + + pos[3*(i-1)+1] = cartesian(center(chart(faces,face))) + pos[3*(i-1)+2] = cartesian(center(chart(faces,face))) + pos[3*(i-1)+3] = cartesian(center(chart(faces,face))) + + #println( cartesian(center(chart(faces,face)))) + #println(face) + + # add shape function for each cell to the basis + for k in nzrange(C,i) + + #Cell index + j = rows[k] + #face index (sign gives local orientation) + s = vals[k] + + cell = cells(mesh)[j] + + #local face index + lf = abs(s); + #local vertex index + lv1 = mod1(lf+1,4) + lv2 = mod1(lf+2,4) + lv3 = mod1(lf+3,4) + + #println("\t",lv1," ",lv2," ",lv3) + #println("\t=>",cell[lv1]," ",cell[lv2]," ",cell[lv3]) + #global vertex index + gv1 = cell[lv1] + gv2 = cell[lv2] + gv3 = cell[lv3] + + if (gv1 < gv2) && (gv2 < gv3) + push!(fns[3*(i-1)+1], Shape{T}(j, 3*(abs(s)-1)+1, sign(s))) + push!(fns[3*(i-1)+2], Shape{T}(j, 3*(abs(s)-1)+2, sign(s))) + push!(fns[3*(i-1)+3], Shape{T}(j, 3*(abs(s)-1)+3, sign(s))) + #println("Case 1") + elseif (gv1 < gv3) && (gv3 < gv2) + push!(fns[3*(i-1)+1], Shape{T}(j, 3*(abs(s)-1)+1, sign(s))) + push!(fns[3*(i-1)+2], Shape{T}(j, 3*(abs(s)-1)+3, sign(s))) + push!(fns[3*(i-1)+3], Shape{T}(j, 3*(abs(s)-1)+2, sign(s))) + #println("Case 2") + elseif (gv2 < gv1) && (gv1 < gv3) + push!(fns[3*(i-1)+1], Shape{T}(j, 3*(abs(s)-1)+2, sign(s))) + push!(fns[3*(i-1)+2], Shape{T}(j, 3*(abs(s)-1)+1, sign(s))) + push!(fns[3*(i-1)+3], Shape{T}(j, 3*(abs(s)-1)+3, sign(s))) + #println("Case 3") + elseif (gv2 < gv3) && (gv3 < gv1) + push!(fns[3*(i-1)+1], Shape{T}(j, 3*(abs(s)-1)+2, sign(s))) + push!(fns[3*(i-1)+2], Shape{T}(j, 3*(abs(s)-1)+3, sign(s))) + push!(fns[3*(i-1)+3], Shape{T}(j, 3*(abs(s)-1)+1, sign(s))) + #println("Case 4") + elseif (gv3 < gv2) && (gv2 < gv1) + push!(fns[3*(i-1)+1], Shape{T}(j, 3*(abs(s)-1)+3, sign(s))) + push!(fns[3*(i-1)+2], Shape{T}(j, 3*(abs(s)-1)+2, sign(s))) + push!(fns[3*(i-1)+3], Shape{T}(j, 3*(abs(s)-1)+1, sign(s))) + #println("Case 5") + elseif (gv3 < gv1) && (gv1 < gv2) + push!(fns[3*(i-1)+1], Shape{T}(j, 3*(abs(s)-1)+3, sign(s))) + push!(fns[3*(i-1)+2], Shape{T}(j, 3*(abs(s)-1)+1, sign(s))) + push!(fns[3*(i-1)+3], Shape{T}(j, 3*(abs(s)-1)+2, sign(s))) + #println("Case 6") + end + + + #println(s) + + #push!(fns[3*(i-1)+1], Shape{T}(j, 3*(abs(s)-1)+1, sign(s))) + #push!(fns[3*(i-1)+2], Shape{T}(j, 3*(abs(s)-1)+2, sign(s))) + #push!(fns[3*(i-1)+3], Shape{T}(j, 3*(abs(s)-1)+3, sign(s))) + end + + end + + BDM3DBasis(mesh, fns, pos) +end + +function brezzidouglasmarini3d(mesh) + faces = skeleton(mesh, 2) + brezzidouglasmarini3d(mesh, faces) +end diff --git a/src/bases/bdmdiv.jl b/src/bases/bdmdiv.jl index 76851f5d..52ba6b01 100644 --- a/src/bases/bdmdiv.jl +++ b/src/bases/bdmdiv.jl @@ -4,6 +4,9 @@ struct BDMBasis{T,M,P} <: Space{T} pos::Vector{P} end +BDMBasis(geo, fns) = BDMBasis(geo, fns, Vector{vertextype(geo)}(undef,length(fns))) + + refspace(s::BDMBasis{T}) where {T} = BDMRefSpace{T}() subset(s::BDMBasis,I) = BDMBasis(s.geo, s.fns[I], s.pos[I]) @@ -16,7 +19,8 @@ end function brezzidouglasmarini(mesh, cellpairs::Array{Int,2}) - @warn "brezzidouglasmarini(mesh, cellpairs) assumes mesh is oriented" + @assert CompScienceMeshes.isoriented(mesh) "brezzidouglasmarini assumes mesh is oriented" + # @warn "brezzidouglasmarini(mesh, cellpairs) assumes mesh is oriented" @assert size(cellpairs,1) == 2 @@ -50,3 +54,8 @@ function brezzidouglasmarini(mesh, cellpairs::Array{Int,2}) BDMBasis(mesh, fns, pos) end + + + +divergence(X::BDMBasis, geo, fns) = LagrangeBasis{0,-1,1}(geo, fns, deepcopy(positions(X))) + diff --git a/src/bases/divergence.jl b/src/bases/divergence.jl index 12943376..a4b32add 100644 --- a/src/bases/divergence.jl +++ b/src/bases/divergence.jl @@ -17,10 +17,11 @@ function divergence(x::Space) fns = x.fns dvs = similar(fns) for (i,fn) in enumerate(fns) - dvs[i] = similar(fns[i]) + dvs[i] = similar(fns[i], 0) for (j,sh) in enumerate(fn) el = els[sh.cellid] - dvs[i][j] = divergence(ref, sh, el) + append!(dvs[i], divergence(ref, sh, el)) + # dvs[i][j] = divergence(ref, sh, el) end end divergence(x, geo, dvs) diff --git a/src/bases/dual3d.jl b/src/bases/dual3d.jl new file mode 100644 index 00000000..6d635fbf --- /dev/null +++ b/src/bases/dual3d.jl @@ -0,0 +1,272 @@ +using LinearAlgebra + + +# function addf!(fn::Vector{<:Shape}, x::Vector, space::Space, idcs::Vector{Int}) +# for (m,bf) in enumerate(space.fns) +# for sh in bf +# cellid = idcs[sh.cellid] +# BEAST.add!(fn, cellid, sh.refid, sh.coeff * x[m]) +# end +# end +# end + +function dual2forms_body(Edges, tetrs, bnd, dir, v2t, v2n) + + T = coordtype(tetrs) + bfs = Vector{Vector{Shape{T}}}(undef, numcells(Edges)) + pos = Vector{vertextype(Edges)}(undef, numcells(Edges)) + + Cells = cells(Edges) + num_threads = Threads.nthreads() + Threads.@threads for F in eachindex(Cells) + Edge = Cells[F] + + myid = Threads.threadid() + myid == 1 && (F % 20 == 0) && + println("Constructing dual 2-forms: $(F*num_threads) out of $(length(Edges)).") + + idcs1 = v2t[Edge[1],1:v2n[Edge[1]]] + idcs2 = v2t[Edge[2],1:v2n[Edge[2]]] + + supp1 = tetrs.mesh[idcs1] + supp2 = tetrs.mesh[idcs2] + + bnd_supp1 = boundary(supp1) + bnd_supp2 = boundary(supp2) + + dir1_faces = submesh(in(dir), bnd_supp1) + dir2_faces = submesh(in(dir), bnd_supp2) + + port_faces = bnd_supp1 + port_faces = submesh(in(bnd_supp2), port_faces) + + x0 = zeros(length(port_faces)) + total_vol = sum(volume(chart(port_faces, fc)) for fc in port_faces) + tgt = tangents(center(chart(Edges, F)),1) + for (i,face) in enumerate(port_faces) + chrt = chart(port_faces, face) + nrm = normal(chrt) + vol = volume(chrt) + sgn = sign(dot(nrm, tgt)) + x0[i] = sgn * vol / total_vol + end + + RT1_prt = BEAST.raviartthomas(supp1, port_faces) + RT2_prt = BEAST.raviartthomas(supp2, port_faces) + + x1_int, _, RT1_int = extend_2_form(supp1, dir1_faces, x0, port_faces) + x2_int, _, RT2_int = extend_2_form(supp2, dir2_faces, x0, port_faces) + + bfs[F] = Vector{Shape{T}}() + pos[F] = cartesian(center(chart(Edges,F))) + addf!(bfs[F], x1_int, RT1_int, idcs1) + addf!(bfs[F], x0, RT1_prt, idcs1) + + addf!(bfs[F], x2_int, RT2_int, idcs2) + addf!(bfs[F], x0, RT2_prt, idcs2) + + end + + NDLCDBasis(tetrs, bfs, pos) +end + + +function extend_1_form(supp, dirichlet, x_prt, port_edges) + + Id = BEAST.Identity() + + bnd_supp = boundary(supp) + supp_edges = CompScienceMeshes.skeleton_fast(supp, 1) + supp_nodes = CompScienceMeshes.skeleton_fast(supp, 0) + + dir_compl = submesh(!in(dirichlet), bnd_supp) + dir_compl_edges = CompScienceMeshes.skeleton_fast(dir_compl, 1) + dir_compl_nodes = CompScienceMeshes.skeleton_fast(dir_compl, 0) + + int_edges = submesh(!in(dir_compl_edges), supp_edges) + int_nodes = submesh(!in(dir_compl_nodes), supp_nodes) + + Nd_prt = BEAST.nedelec(supp, port_edges) + Nd_int = BEAST.nedelec(supp, int_edges) + + curl_Nd_prt = curl(Nd_prt) + curl_Nd_int = curl(Nd_int) + A = assemble(Id, curl_Nd_int, curl_Nd_int, threading=Threading{:single}) + a = -assemble(Id, curl_Nd_int, curl_Nd_prt, threading=Threading{:single}) * x_prt + + if length(int_nodes) > 0 + Lg_int = BEAST.lagrangec0d1(supp, int_nodes) + grad_Lg_int = gradient(Lg_int) + B = assemble(Id, grad_Lg_int, Nd_int, threading=Threading{:single}) + b = -assemble(Id, grad_Lg_int, Nd_prt, threading=Threading{:single}) * x_prt + Z = zeros(eltype(b), length(b), length(b)) + S = [A B'; B Z] + u = S \ [a;b] + else + u = A \ a + end + + x_int = u[1:numfunctions(Nd_int)] + return x_int, int_edges, Nd_int +end + + +function extend_2_form(supp, dirichlet, x_prt, port_faces) + + Id = BEAST.Identity() + + bnd_supp = boundary(supp) + supp_faces = CompScienceMeshes.skeleton_fast(supp, 2) + supp_edges = CompScienceMeshes.skeleton_fast(supp, 1) + supp_nodes = CompScienceMeshes.skeleton_fast(supp, 0) + + dir_compl = submesh(!in(dirichlet), bnd_supp) + dir_compl_faces = CompScienceMeshes.skeleton_fast(dir_compl, 2) + dir_compl_edges = CompScienceMeshes.skeleton_fast(dir_compl, 1) + dir_compl_nodes = CompScienceMeshes.skeleton_fast(dir_compl, 0) + + int_faces = submesh(!in(dir_compl_faces), supp_faces) + int_edges = submesh(!in(dir_compl_edges), supp_edges) + int_nodes = submesh(!in(dir_compl_nodes), supp_nodes) + + if length(int_nodes) > 0 + @assert length(int_nodes) == 1 + int_edges = int_edges[collect(1:length(int_edges)-length(int_nodes))] + end + + RT_prt = BEAST.raviartthomas(supp, port_faces) + RT_int = BEAST.raviartthomas(supp, int_faces) + Nd_int = BEAST.nedelec(supp, int_edges) + + div_RT_prt = divergence(RT_prt) + div_RT_int = divergence(RT_int) + curl_Nd_int = curl(Nd_int) + + A = assemble(Id, div_RT_int, div_RT_int) + B = assemble(Id, curl_Nd_int, RT_int) + + a = -assemble(Id, div_RT_int, div_RT_prt) * x_prt + b = -assemble(Id, curl_Nd_int, RT_prt) * x_prt + + Z = zeros(eltype(b), length(b), length(b)) + u = [A B'; B Z] \ [a;b] + x_int = u[1:end-length(b)] + + return x_int, int_faces, RT_int +end + +function dual1forms(Tetrs, Faces, Dir) + tetrs, bnd, dir, v2t, v2n = dualforms_init(Tetrs, Dir) + dual1forms_body(Faces, tetrs, bnd, dir, v2t, v2n) +end + +function dual2forms(Tetrs, Faces, Dir) + tetrs, bnd, dir, v2t, v2n = dualforms_init(Tetrs, Dir) + dual2forms_body(Faces, tetrs, bnd, dir, v2t, v2n) +end + + + +function dual1forms_body(Faces, tetrs, bnd, dir, v2t, v2n) + + @assert dimension(Faces) == 2 + + T = coordtype(tetrs) + bfs = Vector{Vector{Shape{T}}}(undef, length(Faces)) + pos = Vector{vertextype(Faces)}(undef, length(Faces)) + + Cells = cells(Faces) + num_threads = Threads.nthreads() + Threads.@threads for F in 1:length(Faces) + Face = Cells[F] + Chart = chart(Faces, F) + + myid = Threads.threadid() + myid == 1 && F % 20 == 0 && + println("Constructing dual 1-forms: $(F*num_threads) out of $(length(Faces)).") + + idcs1 = v2t[Face[1],1:v2n[Face[1]]] + idcs2 = v2t[Face[2],1:v2n[Face[2]]] + idcs3 = v2t[Face[3],1:v2n[Face[3]]] + + supp1 = tetrs.mesh[idcs1] + supp2 = tetrs.mesh[idcs2] + supp3 = tetrs.mesh[idcs3] + + bnd_supp1 = boundary(supp1) + bnd_supp2 = boundary(supp2) + bnd_supp3 = boundary(supp3) + + dir1_faces = submesh(in(dir), bnd_supp1) + dir2_faces = submesh(in(dir), bnd_supp2) + dir3_faces = submesh(in(dir), bnd_supp3) + + bnd_dir1 = boundary(dir1_faces) + bnd_dir2 = boundary(dir2_faces) + bnd_dir3 = boundary(dir3_faces) + + supp23 = submesh(in(bnd_supp2), bnd_supp3) + supp31 = submesh(in(bnd_supp3), bnd_supp1) + supp12 = submesh(in(bnd_supp1), bnd_supp2) + + dir23_edges = submesh(in(bnd_dir2), bnd_dir3) + dir31_edges = submesh(in(bnd_dir3), bnd_dir1) + dir12_edges = submesh(in(bnd_dir1), bnd_dir2) + + port_edges = boundary(supp23) + port_edges = submesh(in(boundary(supp31)), port_edges) + port_edges = submesh(in(boundary(supp12)), port_edges) + @assert 1 ≤ length(port_edges) ≤ 2 + + # Step 1: set port flux and extend to dual faces + x0 = zeros(length(port_edges)) + total_vol = sum(volume(chart(port_edges, edge)) for edge in port_edges) + nrm = normal(Chart) + for (i,edge) in enumerate(port_edges) + cht = chart(port_edges, edge) + tgt = tangents(center(cht),1) + vol = volume(cht) + sgn = sign(dot(nrm, tgt)) + x0[i] = sgn * vol / total_vol + end + + x23, supp23_int_edges, _ = extend_1_form(supp23, dir23_edges, x0, port_edges) + x31, supp31_int_edges, _ = extend_1_form(supp31, dir31_edges, x0, port_edges) + x12, supp12_int_edges, _ = extend_1_form(supp12, dir12_edges, x0, port_edges) + + port1_edges = CompScienceMeshes.union(port_edges, supp31_int_edges) + port1_edges = CompScienceMeshes.union(port1_edges, supp12_int_edges) + port2_edges = CompScienceMeshes.union(port_edges, supp12_int_edges) + port2_edges = CompScienceMeshes.union(port2_edges, supp23_int_edges) + port3_edges = CompScienceMeshes.union(port_edges, supp23_int_edges) + port3_edges = CompScienceMeshes.union(port3_edges, supp31_int_edges) + + x1_prt = [x0; x31; x12] + x2_prt = [x0; x12; x23] + x3_prt = [x0; x23; x31] + + Nd1_prt = BEAST.nedelec(supp1, port1_edges) + Nd2_prt = BEAST.nedelec(supp2, port2_edges) + Nd3_prt = BEAST.nedelec(supp3, port3_edges) + + x1_int, _, Nd1_int = extend_1_form(supp1, dir1_faces, x1_prt, port1_edges) + x2_int, _, Nd2_int = extend_1_form(supp2, dir2_faces, x2_prt, port2_edges) + x3_int, _, Nd3_int = extend_1_form(supp3, dir3_faces, x3_prt, port3_edges) + + # inject in the global space + fn = BEAST.Shape{Float64}[] + addf!(fn, x1_prt, Nd1_prt, idcs1) + addf!(fn, x1_int, Nd1_int, idcs1) + + addf!(fn, x2_prt, Nd2_prt, idcs2) + addf!(fn, x2_int, Nd2_int, idcs2) + + addf!(fn, x3_prt, Nd3_prt, idcs3) + addf!(fn, x3_int, Nd3_int, idcs3) + + pos[F] = cartesian(CompScienceMeshes.center(Chart)) + bfs[F] = fn + end + + NDLCCBasis(tetrs, bfs, pos) +end diff --git a/src/bases/global/globaldofs.jl b/src/bases/global/globaldofs.jl new file mode 100644 index 00000000..ad7908bd --- /dev/null +++ b/src/bases/global/globaldofs.jl @@ -0,0 +1,139 @@ +function trace(edge_ch, face_ch, fields) + T = coordtype(edge_ch) + atol = sqrt(eps(T)) + + face_dom = domain(face_ch) + + edge_verts = vertices(edge_ch) + face_verts = vertices(face_ch) + + perm = [something(findfirst(w -> isapprox(v, w; atol), face_verts), 0) for v in edge_verts] + # @show perm + injection = simplex(vertices(face_dom)[perm]) + + function f(s) + u = neighborhood(injection, s) + p = neighborhood(face_ch, cartesian(u)) + fields(p) + end + + return f +end + + + +struct _LagrangeGlobalEdgeDoFs + order::Int +end +function numfunctions(dof::_LagrangeGlobalEdgeDoFs) dof.order-1 end +function (dof::_LagrangeGlobalEdgeDoFs)(s) + T = typeof(s) + nodes = range(zero(T), one(T), length=order+1) + [_lagpoly(nodes, i, s) for i in 2:order] +end + +struct _LagrangeGlobalFaceDoFs + order::Int +end +# function numfunctions(dof::_LagrangeGlobalFaceDoFs) div((order-1)*(order-2),2) end +# function (dof::_LagrangeGlobalFaceDoFs)(s) +# T = eltype(s) +# nodes = range(zero(T), one(T), length=order+1) +# r = zeros(T, numfunctions(dof)) +# s1, s2 = s +# s3 = 1 - s1 - s2 +# idx = 1 +# degree = dof.degree +# for i in 0:degree +# prodi = _lagpoly(nodes, i+1, s1, i) +# for j in 0:degree +# k = degree - i - j +# k < 0 && continue +# prodj = _lagpoly(nodes, j+1, s2, j) +# prodk = _lagpoly(nodes, k+1, s3, k) +# r[idx] = prodi * prodj * prodk +# end end +# return r +# end + +function globaldofs(edge_ch, face_ch, localspace, dof::_LagrangeGlobalEdgeDoFs) + + T = coordtype(edge_ch) + f = trace(edge_ch, face_ch, localspace) + # r = zero(numfunctions(localspace), numfunctions(dof)) + # for (s,w) in CompScienceMeshes.quadpoints(edge_dom, 2*order) + # u = neighborhood(injection, s) + # p = neighborhood(face_ch, cartesian(u)) + # r .+= w * [x.value for x in localspace(p)] * dof(s)' + # end + + ds = one(T) / dof.order + return stack(range(ds, step=ds, length=dof.order-1)) do s + [x.value for x in f(s)] + end +end + + +function globaldofs(edge_ch, face_ch, localspace, dof::_LagrangeGlobalFaceDoFs) + + T = coordtype(edge_ch) + f = trace(edge_ch, face_ch, localspace) + # r = zero(numfunctions(localspace), numfunctions(dof)) + # for (s,w) in CompScienceMeshes.quadpoints(edge_dom, 2*order) + # u = neighborhood(injection, s) + # p = neighborhood(face_ch, cartesian(u)) + # r .+= w * [x.value for x in localspace(p)] * dof(s)' + # end + + d = dof.order + S = ((i,j,d-i-j) for i in 0:d for j in 0:d if (i+j < d && i > 0 && j > 0)) + return stack(S) do s + s = (s[1]/d,s[2]/d) + [x.value for x in f(s)] + end +end + +struct _LagrangeGlobalNodesDoFs + order::Int +end + +function globaldofs(edge_ch, face_ch, localspace, dof::_LagrangeGlobalNodesDoFs) + f = trace(edge_ch, face_ch, localspace) + return stack([()]) do s + [x.value for x in f(s)] + end +end + + +@testitem "globaldofs: interpolatory on edge" begin + using CompScienceMeshes + + T = Float64 + face_ch = simplex( + point(3,0,0), + point(2,0,-1), + point(0,0,1), + ) + edge_ch = simplex( + point(0,0,1), + point(2,0,-1), + ) + + localspace = BEAST.LagrangeRefSpace{T,3,3,10}() + L = BEAST._LagrangeGlobalEdgeDoFs(3) + dofs = BEAST.globaldofs(edge_ch, face_ch, localspace, L) + + @test size(dofs) == (10,2) + A = [ + 0 0 + 0 1 + 1 0 + 0 0 + 0 0 + 0 0 + 0 0 + 0 0 + 0 0 + 0 0] + @test dofs ≈ A +end \ No newline at end of file diff --git a/src/bases/global/gwpdivglobal.jl b/src/bases/global/gwpdivglobal.jl new file mode 100644 index 00000000..f707b805 --- /dev/null +++ b/src/bases/global/gwpdivglobal.jl @@ -0,0 +1,97 @@ +struct GWPDivSpace{T,M,P} <: Space{T} + geo::M + fns::Vector{Vector{Shape{T}}} + pos::P + degree::Int +end + +function refspace(s::GWPDivSpace{T}) where {T} GWPDivRefSpace{T,s.degree}() end +function subset(s::S,I) where {S<:GWPDivSpace} S(s.geo, s.fns[I], s.pos[I], s.degree) end + +function gwpdiv(mesh, edges=nothing; order) + + T = coordtype(mesh) + S = Shape{T} + + space = BEAST.gwpcurl(mesh, edges; order) + fns = Vector{Vector{S}}(undef, length(space.fns)) + for (i,fn) in enumerate(space.fns) + fns[i] = [S(s.cellid, s.refid, -s.coeff) for s in fn] + end + + return GWPDivSpace(space.geo, fns, space.pos, order) +end + +@testitem "GWPcurl global: numfunctions" begin + using CompScienceMeshes + + h = 0.5 + mesh = meshrectangle(1.0, 1.0, 0.5) + edges = setminus(skeleton(mesh,1), boundary(mesh)) + + order = 2 + gwp = BEAST.gwpdiv(mesh, edges; order=order) + + ne = order+1 + nf = order * (order+1) + Nt = length(edges)*ne + length(mesh)*nf + @test numfunctions(gwp) == Nt +end + + +function divergence(X::GWPDivSpace, geo, fns) + ch = chart(geo, first(geo)) + dim = dimension(ch) + dom = domain(ch) + # dim = dimension(dom) + + P = X.degree + Cont = -1 + NF = binomial(dim+P, dim) + + LagrangeBasis{P,Cont,NF}(geo, fns, deepcopy(positions(X))) +end + + +@testitem "divergence - global" begin + using CompScienceMeshes + const CSM = CompScienceMeshes + + T = Float64 + + m = CSM.meshrectangle(1.0, 1.0, 0.5, 3) + X = BEAST.gwpdiv(m; order=4) + divX = BEAST.divergence(X) + + x = BEAST.refspace(X) + divx = BEAST.refspace(divX) + + err = zero(T) + for i in eachindex(X.fns) + fn = X.fns[i] + for j in eachindex(fn) + cellid = X.fns[i][j].cellid + ch = chart(m, cellid) + + u = (0.2341, 0.4312) + p = neighborhood(ch, u) + + r1 = zero(T) + ϕp = x(p) + for sh in X.fns[i] + sh.cellid == cellid || continue + r1 += sh.coeff * ϕp[sh.refid].divergence + end + + r2 = zero(T) + ϕp = divx(p) + for sh in divX.fns[i] + sh.cellid == cellid || continue + r2 += sh.coeff * ϕp[sh.refid].value + end + global err = max(err, abs(r1-r2)) + end + end + + @test err < sqrt(eps(T)) +end \ No newline at end of file diff --git a/src/bases/global/gwpglobal.jl b/src/bases/global/gwpglobal.jl new file mode 100644 index 00000000..b0bda1c8 --- /dev/null +++ b/src/bases/global/gwpglobal.jl @@ -0,0 +1,179 @@ +struct GWPCurlSpace{T,M,P} <: Space{T} + geo::M + fns::Vector{Vector{Shape{T}}} + pos::P + degree::Int +end + +function refspace(s::GWPCurlSpace{T}) where {T} GWPCurlRefSpace{T,s.degree}() end +function subset(s::S,I) where {S<:GWPCurlSpace} S(s.geo, s.fns[I], s.pos[I], s.degree) end + +struct GWPGlobalEdgeDoFs order::Int end +struct GWPGlobalFaceDoFs order::Int end + +function globaldofs(edge_ch, face_ch, localspace, dof::GWPGlobalEdgeDoFs) + f = trace(edge_ch, face_ch, localspace) + T = coordtype(edge_ch) + ds = one(T) / (dof.order+2) + return stack(range(ds, step=ds, length=dof.order+1)) do s + p = neighborhood(edge_ch, s) + t = tangents(p, 1) + [dot(t,x.value) for x in f(s)] + end +end + +@testitem "GWP edge global dofs" begin + using CompScienceMeshes + T = Float64 + face_ch = simplex( + point(3,0,0), + point(2,0,-1), + point(0,0,1), + ) + edge_ch = simplex( + point(0,0,1), + point(2,0,-1), + ) + + degree = 3 + localspace = BEAST.GWPCurlRefSpace{T,degree}() + dofs = BEAST.GWPGlobalEdgeDoFs(degree) + A = BEAST.globaldofs(edge_ch, face_ch, localspace, dofs) + # display(round.(A, digits=3)) +end + +function globaldofs(edge_ch, face_ch, localspace, dof::GWPGlobalFaceDoFs) + + d = dof.order + T = coordtype(edge_ch) + ds = one(T) / (d+2) + + f = trace(edge_ch, face_ch, localspace) + Q = zeros(T, numfunctions(localspace, domain(face_ch)), d*(d+1)) + S = ((i,j,d+2-i-j) for i in 1:d+1 for j in 1:d+1 if d+2-i-j > 0) + # return stack(S) do (i,j,k) + idx = 1 + for (i,j,k) in S + u = (i*ds,j*ds) + p = neighborhood(edge_ch, u) + t1 = tangents(p,1) + t2 = tangents(p,2) + vals = f(u) + q1 = [dot(t1,x.value) for x in vals] + q2 = [dot(t2,x.value) for x in vals] + Q[:,idx] = q1; idx += 1 + Q[:,idx] = q2; idx += 1 + end + + return Q +end + + +@testitem "GWP face global dofs" begin + using CompScienceMeshes + T = Float64 + face_ch = simplex( + point(3,0,0), + point(2,0,-1), + point(0,0,1), + ) + # edge_ch = simplex( + # point(0,0,1), + # point(2,0,-1), + # point(3,0,0) + # ) + edge_ch = simplex( + point(3,0,0), + point(2,0,-1), + point(0,0,1), + ) + + degree = 3 + localspace = BEAST.GWPCurlRefSpace{T,degree}() + dofs = BEAST.GWPGlobalFaceDoFs(degree) + A = BEAST.globaldofs(edge_ch, face_ch, localspace, dofs) + # display(round.(A, digits=3)) +end + +function _addshapes!(fns, cell, gids, lids, β) + T = eltype(β) + atol = sqrt(eps(T)) + for i in axes(β,1) + fn = fns[gids[i]] + for j in axes(β,2) + isapprox(β[i,j], 0; atol) && continue + push!(fn, Shape{T}(cell, lids[j], β[i,j])) + end + end +end + + +function gwpcurl(mesh, edges=nothing; order) + + T = coordtype(mesh) + atol = sqrt(eps(T)) + + if edges == nothing + edges = setminus(skeleton(mesh, 1), boundary(mesh)) + end + + C12 = connectivity(mesh, edges, abs) + R12 = rowvals(C12) + V12 = nonzeros(C12) + + ne = order+1 + nf = order*(order+1) + + Ne = ne * length(edges) + Nf = nf * length(mesh) + Nt = Ne + Nf + + localspace = GWPCurlRefSpace{T,order}() + refdom = domain(chart(mesh, first(mesh))) + localdim = numfunctions(localspace, refdom) + + fns = [Shape{T}[] for n in 1:Nt] + pos = fill(point(0,0,0), Nt) + for cell in mesh + cell_ch = chart(mesh, cell) + for k in nzrange(C12, cell) + e = R12[k] + i = V12[k] + gids = (e-1)*ne .+ (1:ne) + lids = localindices(localspace, refdom, Val{1}, i) + edge_ch = chart(edges, e) + vals = globaldofs(edge_ch, cell_ch, localspace, GWPGlobalEdgeDoFs(order)) + α = vals[lids,:] + _addshapes!(fns, cell, gids, lids, inv(α')) + end + + order < 1 && continue + face_ch = chart(mesh, cell) + gids = Ne + (cell-1)*nf .+ (1:nf) + lids = localindices(localspace, refdom, Val{2}, 1) + vals = globaldofs(face_ch, face_ch, localspace, GWPGlobalFaceDoFs(order)) + α = vals[lids,:] + _addshapes!(fns, cell, gids, lids, inv(α')) + end + + for e in edges pos[(e-1)*ne .+ (1:ne)] .= Ref(cartesian(center(chart(edges, e)))) end + for f in mesh pos[Ne+(f-1)*nf .+ (1: nf)] .= Ref(cartesian(center(chart(mesh, f)))) end + + return GWPCurlSpace(mesh, fns, pos, order) +end + +@testitem "GWPcurl global: numfunctions" begin + using CompScienceMeshes + + h = 0.5 + mesh = meshrectangle(1.0, 1.0, 0.5) + edges = setminus(skeleton(mesh,1), boundary(mesh)) + + order = 2 + gwp = BEAST.gwpcurl(mesh, edges; order=order) + + ne = order+1 + nf = order * (order+1) + Nt = length(edges)*ne + length(mesh)*nf + @test numfunctions(gwp) == Nt +end \ No newline at end of file diff --git a/src/bases/lagrange.jl b/src/bases/lagrange.jl index bdbe7165..f56a7a63 100644 --- a/src/bases/lagrange.jl +++ b/src/bases/lagrange.jl @@ -11,6 +11,7 @@ function lagdimension end # M: mesh type # T: field type # NF: number of local shape functions +# P: point type mutable struct LagrangeBasis{D,C,M,T,NF,P} <: Space{T} geo::M fns::Vector{Vector{Shape{T}}} @@ -32,15 +33,15 @@ function lagrangecxd0(mesh) U = universedimension(mesh) D1 = dimension(mesh)+1 - + T = coordtype(mesh) geometry = mesh num_cells = numcells(mesh) # create the local shapes - fns = Vector{Vector{Shape{Float64}}}(undef,num_cells) + fns = Vector{Vector{Shape{T}}}(undef,num_cells) pos = Vector{vertextype(mesh)}(undef,num_cells) - for (i,cell) in enumerate(cells(mesh)) - fns[i] = [Shape(i, 1, 1.0)] + for (i,cell) in enumerate(mesh) + fns[i] = [Shape(i, 1, T(1.0))] pos[i] = cartesian(center(chart(mesh, cell))) end @@ -48,6 +49,130 @@ function lagrangecxd0(mesh) LagrangeBasis{0,-1,NF}(geometry, fns, pos) end +""" + unitfunctioncxd0(mesh) + +Constructs a constant function with value 1 on `mesh`. +""" +function unitfunctioncxd0(mesh) + + T = coordtype(mesh) + geometry = mesh + + # create the local shapes + fns = Vector{Vector{Shape{T}}}(undef, 1) + pos = Vector{vertextype(mesh)}(undef, 1) + fns[1] = [Shape(i, 1, T(1.0)) for (i, cell) in enumerate(mesh)] + + # Arguably, the position is fairly meaningless + # in case of a global function. Might be replaced by something + # more useful. + # For now, we fill it with the average position of the shape functions + p = vertextype(mesh)(0.0, 0.0, 0.0) + for cell in mesh + p += cartesian(center(chart(mesh, cell))) + end + pos[1] = p ./ numcells(mesh) + + NF = 1 + LagrangeBasis{0,-1,NF}(geometry, fns, pos) +end + +""" + unitfunctionc0d1(mesh) + +Constructs a constant function with value 1 on `mesh` consisting of linear shapes. For dirichlet=true goes to zero on the boundary. +""" +function unitfunctionc0d1(mesh; dirichlet=true) + if dirichlet == false + return unitfunctionc0d1(mesh, skeleton(mesh,0)) + else + return unitfunctionc0d1_dirichlet(mesh) + end +end + +function unitfunctionc0d1_dirichlet(mesh) + + T = coordtype(mesh) + + verts = skeleton(mesh, 0) + detached = trues(numvertices(mesh)) + for v in cells(verts) + detached[v] = false + end + + bnd = boundary(mesh) + bndverts = skeleton(bnd, 0) + notonbnd = trues(numvertices(mesh)) + for v in cells(bndverts) + notonbnd[v] = false + end + + vertexlist = findall(notonbnd .& .!detached) + + cellids, ncells = vertextocellmap(mesh) + + Cells = cells(mesh) + Verts = vertices(mesh) + + # create the local shapes + fns = Vector{Vector{Shape{T}}}(undef, 1) + pos = Vector{vertextype(mesh)}(undef, 1) + + numshapes = sum(ncells[vertexlist]) + shapes = Vector{Shape{T}}(undef,numshapes) + n = 0 + for v in vertexlist + nshapes = ncells[v] + nshapes == 0 && continue + + for s in 1: nshapes + c = cellids[v,s] + + cell = Cells[c] + + localid = something(findfirst(isequal(v), cell),0) + @assert localid != 0 + + shapes[s+n] = Shape(c, localid, T(1.0)) + + end + n += nshapes + end + fns[1] = shapes + p = sum(mesh.vertices[vertexlist])/length(vertexlist) + pos[1] = p + + NF = 3 + LagrangeBasis{1,0,NF}(mesh, fns, pos) +end + +function unitfunctionc0d1(mesh, nodes::CompScienceMeshes.AbstractMesh{U,1} where {U}) + Conn = connectivity(nodes, mesh, abs) + rows = rowvals(Conn) + vals = nonzeros(Conn) + + T = coordtype(mesh) + P = vertextype(mesh) + S = Shape{T} + + fns = Vector{Vector{Shape{T}}}(undef, 1) + pos = Vector{vertextype(mesh)}(undef, 1) + fn = Vector{S}() + for (i,node) in enumerate(nodes) + for k in nzrange(Conn,i) + cellid = rows[k] + refid = vals[k] + push!(fn, Shape(cellid, refid, T(1.0))) + end + end + fns[1] = fn + p = sum(nodes.vertices)/length(nodes.vertices) + pos[1] = p + + NF = dimension(mesh) + 1 + LagrangeBasis{1,0,NF}(mesh, fns, pos) +end """ lagrangec0d1(mesh[, bnd]) @@ -107,15 +232,24 @@ end """ duallagrangecxd0(mesh, jct) -> basis -Build dual Lagrange piecewise constant elements. Boundary nodes are only considered if they are in the interior of `jct`. +Build dual Lagrange piecewise constant elements. Boundary nodes are only considered if +they are in the interior of `jct`. + +The default dual function (`interpolatory=false`) is similar to the one depicted +in Figure 3 of Buffa et al (doi: 10.1090/S0025-5718-07-01965-5), with the +difference that each individual shape function is normalized with respect to +the area so that overall the integral over the dual function is one. + +When `interpolatory=true` is used, the function value is one on the support, and thus, +it gives rise to a partition of unity. """ -function duallagrangecxd0(mesh, jct=CompScienceMeshes.mesh(coordtype(mesh), dimension(mesh)-1)) +function duallagrangecxd0(mesh, jct=CompScienceMeshes.mesh(coordtype(mesh), dimension(mesh)-1); interpolatory=false) vertexlist = interior_and_junction_vertices(mesh, jct) - duallagrangecxd0(mesh, vertexlist) + duallagrangecxd0(mesh, vertexlist; interpolatory=interpolatory) end -function duallagrangecxd0(mesh, vertexlist::Vector) +function duallagrangecxd0(mesh, vertexlist::Vector{Int}; interpolatory=false) T = coordtype(mesh) @@ -124,11 +258,12 @@ function duallagrangecxd0(mesh, vertexlist::Vector) fine = barycentric_refinement(mesh) vtoc, vton = vertextocellmap(fine) + verts = vertices(mesh) for (k,v) in enumerate(vertexlist) n = vton[v] F = vtoc[v,1:n] - fns[k] = singleduallagd0(fine, F, v) - push!(pos, mesh.vertices[v]) + fns[k] = singleduallagd0(fine, F, v, interpolatory=interpolatory) + push!(pos, verts[v]) end NF = 1 @@ -136,19 +271,35 @@ function duallagrangecxd0(mesh, vertexlist::Vector) end +function duallagrangecxd0(mesh, vertices::CompScienceMeshes.AbstractMesh{U,1}; interpolatory=false) where {U} + # vertexlist = Int[v[1] for v in vertices] + vertexlist =Int[CompScienceMeshes.indices(vertices, v)[1] for v in vertices] + return duallagrangecxd0(mesh, vertexlist; interpolatory=interpolatory) +end + + """ - singleduallagd0(fine, F, v) + singleduallagd0(fine, F, v; interpolatory=false) + +Build a single dual constant Lagrange element a mesh `fine`. `F` contains the indices +to cells in the support and v is the index in the vertex list of the defining vertex. + +The default dual function (`interpolatory=false`) is similar to the one depicted +in Figure 3 of Buffa et al (doi: 10.1090/S0025-5718-07-01965-5), with the +difference that each individual shape function is normalized with respect to +the area so that overall the integral over the dual function is one. -Build a single dual constant Lagrange element a mesh `fine`. `F` contains the indices to cells in the support and v is the index in the vertex list of the defining vertex. +When `interpolatory=true` is used, the function value is one on the support, and thus, +it gives rise to a partition of unity. """ -function singleduallagd0(fine, F, v) +function singleduallagd0(fine, F, v; interpolatory=false) T = coordtype(fine) fn = Shape{T}[] for cellid in F - cell = cells(fine)[cellid] - ptch = chart(fine, cell) - coeff = 1 / volume(ptch) / length(F) + # cell = cells(fine)[cellid] + ptch = chart(fine, cellid) + coeff = interpolatory ? T(1.0) : 1 / volume(ptch) / length(F) refid = 1 push!(fn, Shape(cellid, refid, coeff)) end @@ -163,7 +314,8 @@ Build lagrangec0d1 elements, including (dirichlet=false) or excluding (dirichlet """ function lagrangec0d1(mesh; dirichlet::Bool=true) if dirichlet == false - return lagrangec0d1(mesh, boundary(mesh)) + # return lagrangec0d1(mesh, boundary(mesh)) + return lagrangec0d1(mesh, skeleton(mesh,0)) else return lagrangec0d1_dirichlet(mesh) end @@ -186,7 +338,7 @@ function lagrangec0d1(mesh, vertexlist::Vector, ::Type{Val{3}}) Verts = vertices(mesh) # create the local shapes - fns = Vector{Shape{Float64}}[] + fns = Vector{Shape{T}}[] pos = Vector{vertextype(mesh)}() sizehint!(fns, length(vertexlist)) @@ -196,7 +348,7 @@ function lagrangec0d1(mesh, vertexlist::Vector, ::Type{Val{3}}) numshapes = ncells[v] numshapes == 0 && continue - shapes = Vector{Shape{Float64}}(undef,numshapes) + shapes = Vector{Shape{T}}(undef,numshapes) for s in 1: numshapes c = cellids[v,s] # cell = mesh.faces[c] @@ -205,7 +357,7 @@ function lagrangec0d1(mesh, vertexlist::Vector, ::Type{Val{3}}) localid = something(findfirst(isequal(v), cell),0) @assert localid != 0 - shapes[s] = Shape(c, localid, 1.0) + shapes[s] = Shape(c, localid, T(1.0)) end push!(fns, shapes) @@ -240,14 +392,14 @@ function lagrangec0d1(mesh, vertexlist, ::Type{Val{2}}) numshapes = ncells[v] numshapes == 0 && continue # skip detached vertices - shapes = Vector{Shape{Float64}}(undef,numshapes) + shapes = Vector{Shape{T}}(undef,numshapes) for s in 1: numshapes c = cellids[v,s] cell = mesh.faces[c] if cell[1] == v - shapes[s] = Shape(c, 1, 1.0) + shapes[s] = Shape(c, 1, T(1.0)) elseif cell[2] == v - shapes[s] = Shape(c, 2, 1.0) + shapes[s] = Shape(c, 2, T(1.0)) else error("Junctions not supported") end @@ -279,13 +431,59 @@ function lagrangec0d1(mesh, nodes::CompScienceMeshes.AbstractMesh{U,1} where {U} for k in nzrange(Conn,i) cellid = rows[k] refid = vals[k] - push!(fn, Shape(cellid, refid, 1.0)) + push!(fn, Shape(cellid, refid, T(1.0))) + end + push!(fns,fn) + push!(pos,cartesian(center(chart(nodes,node)))) + end + + NF = dimension(mesh) + 1 + LagrangeBasis{1,0,NF}(mesh, fns, pos) +end + + +function lagrangec0d2(mesh::CompScienceMeshes.AbstractMesh{U,3}, + nodes::CompScienceMeshes.AbstractMesh{U,1}, + edges::CompScienceMeshes.AbstractMesh{U,2}) where {U} + + Conn = connectivity(nodes, mesh, abs) + rows = rowvals(Conn) + vals = nonzeros(Conn) + + T = coordtype(mesh) + P = vertextype(mesh) + S = Shape{T} + + fns = Vector{Vector{S}}() + pos = Vector{P}() + for (i,node) in enumerate(nodes) + fn = Vector{S}() + for k in nzrange(Conn,i) + cellid = rows[k] + refid = vals[k] + push!(fn, Shape(cellid, refid, T(1.0))) end push!(fns,fn) push!(pos,cartesian(center(chart(nodes,node)))) end - LagrangeBasis{1,0,3}(mesh, fns, pos) + Conn = connectivity(edges, mesh, abs) + rows = rowvals(Conn) + vals = nonzeros(Conn) + + for (i,edge) in enumerate(edges) + fn = Vector{S}() + for k in nzrange(Conn,i) + cellid = rows[k] + refid = vals[k] + push!(fn, Shape(cellid, 3+refid, T(1.0))) + end + push!(fns,fn) + push!(pos,cartesian(center(chart(edges,edge)))) + end + + NF = 6 + LagrangeBasis{2,0,NF}(mesh, fns, pos) end @@ -312,16 +510,19 @@ function duallagrangec0d1(mesh, refined, jct_pred, ::Type{Val{3}}) uv_ctr = ones(dimension(mesh))/(dimension(mesh)+1) vtoc, vton = vertextocellmap(refined) - for (i,coarse_idcs) in enumerate(cells(mesh)) + for (i,p) in enumerate(mesh) + coarse_idcs = CompScienceMeshes.indices(mesh, p) + coarse_chart = chart(mesh,p) + fns[i] = Vector{Shape{T}}() - push!(pos, cartesian(center(chart(mesh, coarse_idcs)))) + push!(pos, cartesian(center(coarse_chart))) # It is assumed the vertices of this cell have the same index # mesh and its refinement. - coarse_cell = chart(mesh, coarse_idcs) + # coarse_cell = chart(mesh, coarse_idcs) # get the index in fine.vertices of the centroid of coarse_cell - centroid = barytocart(coarse_cell, uv_ctr) + centroid = barytocart(coarse_chart, uv_ctr) I = CollisionDetection.find(fine_vertices, centroid) @assert length(I) == 1 centroid_id = I[1] @@ -335,7 +536,7 @@ function duallagrangec0d1(mesh, refined, jct_pred, ::Type{Val{3}}) uv_face_ctr[f] = 0 uv_face_ctr = uv_face_ctr[1:end-1] - face_ctr = barytocart(coarse_cell, uv_face_ctr) + face_ctr = barytocart(coarse_chart, uv_face_ctr) I = CollisionDetection.find(fine_vertices, face_ctr) @assert length(I) == 1 face_center_ids[f] = I[1] @@ -394,14 +595,12 @@ This basis function creats the dual Lagrange basis function and return an object It also return a gemoetry containing the refined mesh. """ function duallagrangec0d1(mesh, mesh2, pred, ::Type{Val{2}}) - T = Float64 + T = coordtype(mesh) U = universedimension(mesh) # get the information about number of vertices, number of faces , and the maping between vertices and faces for the original mesh numverts1 = numvertices(mesh) num_cells1 = numcells(mesh) cellids1, ncells1=vertextocellmap(mesh) - # obtain the refined mesh from the original mesh - #mesh2 = barycentric_refinement(mesh) # get the information about number of vertices, number of faces , and the maping between vertices and faces for the refined mesh num_cells2 = numcells(mesh2) numverts2 = numvertices(mesh2) @@ -462,16 +661,159 @@ function duallagrangec0d1(mesh, mesh2, pred, ::Type{Val{2}}) end # Now assign all of these shapes to the relevent segment in the coarse mesh fns[segment_coarse]=shapes - push!(pos, cartesian(center(chart(mesh, mesh.faces[segment_coarse])))) + push!(pos, cartesian(center(chart(mesh, segment_coarse)))) end NF = 2 LagrangeBasis{1,0,NF}(geometry, fns, pos) end +""" + lagrangec0d2(mesh) -> basis + +Build lagrangec0d2 elements, including boundary vertices (meaning assuming a closed mesh). +""" + +function lagrangec0d2(mesh) + + #mark non connectet vertices + verts = skeleton(mesh, 0) + detached = trues(numvertices(mesh)) + for v in cells(verts) + detached[v] = false + end + + #identify + bnd = boundary(mesh) + bndverts = skeleton(bnd, 0) + notonbnd = trues(numvertices(mesh)) + for v in cells(bndverts) + notonbnd[v] = false + end + + vertexlist = findall(.!detached) + + edges = skeleton(mesh, 1; sort) + cps = cellpairs(mesh, edges, dropjunctionpair=true) + #only interior edges + ids = findall(x -> x>0, cps[2,:]) + + lagrangec0d2(mesh,vertexlist,cps,Val{dimension(mesh)+1}) +end + + +function lagrangec0d2(mesh, vertexlist::Vector, cellpairs::Array{Int,2}, ::Type{Val{3}}) + + T = coordtype(mesh) + U = universedimension(mesh) + + numvertices = length(vertexlist) + numedges = size(cellpairs,2) + + cellids, ncells = vertextocellmap(mesh) + + Cells = cells(mesh) + Verts = vertices(mesh) + + # create the local shapes + functions = Vector{Vector{Shape{Float64}}}(undef,numvertices+numedges) + positions = Vector{vertextype(mesh)}(undef,numvertices+numedges) + + #temp containers for vertex dof + fns = Vector{Vector{Shape{Float64}}}() + pos = Vector{vertextype(mesh)}() + + sizehint!(fns, length(vertexlist)) + sizehint!(pos, length(vertexlist)) + + #shape function associated with vetex dof + for v in vertexlist + + numshapes = ncells[v] + numshapes == 0 && continue + + shapes = Vector{Shape{Float64}}(undef,numshapes) + for s in 1: numshapes + c = cellids[v,s] + # cell = mesh.faces[c] + cell = Cells[c] + + localid = something(findfirst(isequal(v), cell),0) + @assert localid != 0 + + shapes[s] = Shape(c, localid, 1.0) + end + #functions[v] = shapes + #positions[v] = Verts[v] + + push!(fns, shapes) + push!(pos, mesh.vertices[v]) + end + + @assert length(fns) == numvertices + + for i in 1:numvertices + functions[i] = fns[i] + positions[i] = pos[i] + end + + #shape function associated with edge dof + for i in 1:numedges + #internal edge + if cellpairs[2,i] > 0 + c1 = cellpairs[1,i]; cell1 = Cells[c1] #mesh.faces[c1] + c2 = cellpairs[2,i]; cell2 = Cells[c2] #mesh.faces[c2] + e1, e2 = getcommonedge(cell1, cell2) + functions[numvertices+i] = [ + Shape(c1, abs(e1)+3, 1.0), + Shape(c2, abs(e2)+3, 1.0)] + isct = intersect(cell1, cell2) + @assert length(isct) == 2 + @assert !(cell1[abs(e1)] in isct) + @assert !(cell2[abs(e2)] in isct) + + v1 = cell1[mod1(abs(e1)+1,3)] + v2 = cell1[mod1(abs(e1)+2,3)] + + edge = simplex(mesh.vertices[[v1,v2]]...) + + positions[numvertices+i] = cartesian(center(edge)) + #boundary edge + else + c1 = cellpairs[1,i]; cell1 = Cells[c1] + e1 = cellpairs[2,i] + functions[numvertices+i] = [ + Shape(c1, abs(e1)+3, +1.0)] + + v1 = cell1[mod1(abs(e1)+1,3)] + v2 = cell1[mod1(abs(e1)+2,3)] + edge = simplex(mesh.vertices[[v1,v2]]...) + + positions[numvertices+i] = cartesian(center(edge)) + end + end + + + NF = 6 + LagrangeBasis{2,0,NF}(mesh, functions, positions) +end + + gradient(space::LagrangeBasis{1,0}, geo, fns) = NDLCCBasis(geo, fns) +# gradient(space::LagrangeBasis{1,0}, geo::CompScienceMeshes.AbstractMesh{U,3} where {U}, fns) = NDBasis(geo, fns) + curl(space::LagrangeBasis{1,0}, geo, fns) = RTBasis(geo, fns) +#curl(space::LagrangeBasis{2,0}, geo, fns) = BDMBasis(geo, fns) + +gradient(space::LagrangeBasis{1,0,<:CompScienceMeshes.AbstractMesh{<:Any,2}}, geo, fns) = + LagrangeBasis{0,-1,1}(geo, fns, space.pos) + +gradient(space::LagrangeBasis{1,0,<:CompScienceMeshes.AbstractMesh{<:Any,3}}, geo, fns) = + NDBasis(geo, fns, space.pos) + +curl(space::LagrangeBasis{2,0},geo, fns) = BDMBasis(geo, fns) + # # Sclar trace for Laggrange element based spaces # @@ -486,3 +828,370 @@ function strace(X::LagrangeBasis{1,0}, geo, fns::Vector) trpos = deepcopy(positions(X)) LagrangeBasis{1,0,NF}(geo, fns, trpos) end + + + +function extend_0_form(supp, dirichlet, x_prt, Lg_prt) + + Id = BEAST.Identity() + + bnd_supp = boundary(supp) + supp_nodes = CompScienceMeshes.skeleton_fast(supp, 0) + + dir_compl = submesh(!in(dirichlet), bnd_supp) + dir_compl_nodes = CompScienceMeshes.skeleton_fast(dir_compl, 0) + int_nodes = submesh(!in(dir_compl_nodes), supp_nodes) + + if length(int_nodes) == 0 + T = scalartype(Id, Lg_prt, Lg_prt) + S = typeof(Lg_prt) + P = eltype(Lg_prt.pos) + return T[], int_nodes, S(supp, Vector{Vector{Shape{T}}}(), Vector{P}()) + end + + Lg_int = BEAST.lagrangec0d1(supp, int_nodes) + + grad_Lg_prt = gradient(Lg_prt) + grad_Lg_int = gradient(Lg_int) + + A = assemble(Id, grad_Lg_int, grad_Lg_int, threading=Threading{:single}) + a = -assemble(Id, grad_Lg_int, grad_Lg_prt, threading=Threading{:single}) * x_prt + + x_int = A \ a + return x_int, int_nodes, Lg_int +end + + +function dualforms_init(Supp, Dir) + tetrs = barycentric_refinement(Supp) + v2t, v2n = CompScienceMeshes.vertextocellmap(tetrs) + bnd = boundary(tetrs) + gpred = CompScienceMeshes.overlap_gpredicate(Dir) + dir = submesh((m,face) -> gpred(chart(m,face)), bnd) + return tetrs, bnd, dir, v2t, v2n +end + +function duallagrangec0d1(mesh::CompScienceMeshes.AbstractMesh{<:Any,3}, nodes, dirbnd) + refd, bnd, dir, v2t, v2n = dualforms_init(mesh, dirbnd) + dual0forms_body(mesh, refd, bnd, dir, v2t, v2n) +end + +function dual0forms_body(mesh::CompScienceMeshes.AbstractMesh{<:Any,3}, refd, bnd, dir, v2t, v2n) + + T = coordtype(refd) + S = Shape{T} + V = vertextype(mesh) + bfs = Vector{Vector{S}}(undef, length(mesh)) + pos = Vector{V}(undef, length(mesh)) + + # Cells = cells(mesh) + Cells = [c for c in mesh] + num_threads = Threads.nthreads() + for F in 1:length(mesh) + # Cell = Cells[F] + Cell = CompScienceMeshes.indices(mesh, Cells[F]) + + myid = Threads.threadid() + # myid == 1 && F % 20 == 0 && + # println("Constructing dual 1-forms: $(F) out of $(length(mesh)).") + + idcs1 = v2t[Cell[1],1:v2n[Cell[1]]] + idcs2 = v2t[Cell[2],1:v2n[Cell[2]]] + idcs3 = v2t[Cell[3],1:v2n[Cell[3]]] + + supp1 = refd[idcs1] # 2D + supp2 = refd[idcs2] + supp3 = refd[idcs3] + + bnd_supp1 = boundary(supp1) # 1D + bnd_supp2 = boundary(supp2) + bnd_supp3 = boundary(supp3) + + supp23 = submesh(in(bnd_supp2), bnd_supp3) # 1D + supp31 = submesh(in(bnd_supp3), bnd_supp1) + supp12 = submesh(in(bnd_supp1), bnd_supp2) + + port = boundary(supp23) # 0D + port = submesh(in(boundary(supp31)), port) + port = submesh(in(boundary(supp12)), port) + @assert length(port) == 1 + + in_dir = in(dir) + dir1_edges = submesh(in_dir, bnd_supp1) # 1D + dir2_edges = submesh(in_dir, bnd_supp2) + dir3_edges = submesh(in_dir, bnd_supp3) + + bnd_dir1 = boundary(dir1_edges) # 0D + bnd_dir2 = boundary(dir2_edges) + bnd_dir3 = boundary(dir3_edges) + + dir23_nodes = submesh(in(bnd_dir2), bnd_dir3) # 0D + dir31_nodes = submesh(in(bnd_dir3), bnd_dir1) + dir12_nodes = submesh(in(bnd_dir1), bnd_dir2) + + # Step 1: set port flux and extend to dual faces + x0 = ones(T,1) + + Lg12_prt = BEAST.lagrangec0d1(supp12, port) + Lg23_prt = BEAST.lagrangec0d1(supp23, port) + Lg31_prt = BEAST.lagrangec0d1(supp31, port) + + x23, supp23_int_nodes, _ = extend_0_form(supp23, dir23_nodes, x0, Lg23_prt) + x31, supp31_int_nodes, _ = extend_0_form(supp31, dir31_nodes, x0, Lg31_prt) + x12, supp12_int_nodes, _ = extend_0_form(supp12, dir12_nodes, x0, Lg12_prt) + + port1_nodes = union(port, supp31_int_nodes, supp12_int_nodes) + port2_nodes = union(port, supp12_int_nodes, supp23_int_nodes) + port3_nodes = union(port, supp23_int_nodes, supp31_int_nodes) + + x1_prt = [x0; x31; x12] + x2_prt = [x0; x12; x23] + x3_prt = [x0; x23; x31] + + Lg1_prt = BEAST.lagrangec0d1(supp1, port1_nodes) + Lg2_prt = BEAST.lagrangec0d1(supp2, port2_nodes) + Lg3_prt = BEAST.lagrangec0d1(supp3, port3_nodes) + + x1_int, _, Lg1_int = extend_0_form(supp1, dir1_edges, x1_prt, Lg1_prt) + x2_int, _, Lg2_int = extend_0_form(supp2, dir2_edges, x2_prt, Lg2_prt) + x3_int, _, Lg3_int = extend_0_form(supp3, dir3_edges, x3_prt, Lg3_prt) + + # inject in the global space + fn = BEAST.Shape{T}[] + addf!(fn, x1_prt, Lg1_prt, idcs1) + addf!(fn, x1_int, Lg1_int, idcs1) + + addf!(fn, x2_prt, Lg2_prt, idcs2) + addf!(fn, x2_int, Lg2_int, idcs2) + + addf!(fn, x3_prt, Lg3_prt, idcs3) + addf!(fn, x3_int, Lg3_int, idcs3) + + pos[F] = cartesian(CompScienceMeshes.center(chart(mesh, Cells[F]))) + bfs[F] = fn + end + + LagrangeBasis{1,0,3}(refd, bfs, pos) +end + + + +function lagrangecx(mesh::CompScienceMeshes.AbstractMesh{<:Any,3}; order) + + T = coordtype(mesh) + NF = binomial(2+order, 2) + P = vertextype(mesh) + S = Shape{T} + + fns = Vector{Vector{S}}(undef, length(mesh) * NF) + pos = Vector{P}(undef, length(mesh) * NF) + + idx = 1 + u = one(T) + for (c,cell) in enumerate(mesh) + ch = chart(mesh,cell) + for r in 1:NF + fns[idx] = S[S(c,r,u)] + pos[idx] = cartesian(center(ch)) + idx += 1 + end + end + + return LagrangeBasis{order,-1,NF}(mesh, fns, pos) +end + + + +""" + localindices(dof, chart, i) + +Returns a vector of indices into the vector of local shape functions that correspond to +global degrees of freedom supported on sub-entity `i`, where the type of entity (nodes, +edge, face) is encoded in the type of 'dof'. +""" +function localindices(dof::_LagrangeGlobalNodesDoFs, chart::CompScienceMeshes.Simplex, + localspace, i) + + d = dof.order + [div((d+1)*(d+2),2), d+1, 1][[i]] +end + +function localindices(dof::_LagrangeGlobalEdgeDoFs, chart::CompScienceMeshes.Simplex, + localspace, i) + + d = dof.order + lids, lid = Int[], 0 + if i == 1 + for i in 0:d for j in 0:d + k = d - i - j + k < 0 && continue + lid += 1 + i != 0 && continue + j in (0,d) && continue + push!(lids, lid) + end end + elseif i == 2 + for i in 0:d for j in 0:d + k = d - i - j + k < 0 && continue + lid += 1 + i in (0,d) && continue + j != 0 && continue + push!(lids, lid) + end end + elseif i ==3 + for i in 0:d for j in 0:d + k = d - i - j + k < 0 && continue + lid += 1 + j in (0,d) && continue + k != 0 && continue + push!(lids, lid) + end end + else + error("wrong local edge index") + end + return lids +end + +function localindices(dof::_LagrangeGlobalFaceDoFs, chart::CompScienceMeshes.Simplex, + localspace, i) + + d = dof.order + lids, lid = Int[], 0 + for i in 0:d, j in 0:d + k = d - i - j + k < 0 && continue + lid += 1 + (i == 0 || j == 0 || k == 0) && continue + # @show (i,j,k) + push!(lids, lid) + end + return lids +end + +function localindices(dof::_LagrangeGlobalNodesDoFs, chart::CompScienceMeshes.Simplex, + localspace::LagrangeRefSpace{<:Real,2}, i) + return [1,3,6][[i]] +end + +function localindices(dof::_LagrangeGlobalEdgeDoFs, chart::CompScienceMeshes.Simplex, + localspace::LagrangeRefSpace{<:Real,2}, i) + return [5,4,2][[i]] +end + +function localindices(dof::_LagrangeGlobalFaceDoFs, chart::CompScienceMeshes.Simplex, + localspace::LagrangeRefSpace{<:Real,2}, i) + return [] +end + +function localindices(dof::_LagrangeGlobalNodesDoFs, chart::CompScienceMeshes.Simplex, + localspace::LagrangeRefSpace{<:Real,1}, i) + return [i] +end + +function localindices(dof::_LagrangeGlobalEdgeDoFs, chart::CompScienceMeshes.Simplex, + localspace::LagrangeRefSpace{<:Real,1}, i) + return [] +end + +function localindices(dof::_LagrangeGlobalFaceDoFs, chart::CompScienceMeshes.Simplex, + localspace::LagrangeRefSpace{<:Real,1}, i) + return [] +end + + + +function lagrangec0(mesh::CompScienceMeshes.AbstractMesh{<:Any,3}; order) + + T = coordtype(mesh) + atol = sqrt(eps(T)) + + P = vertextype(mesh) + S = Shape{T} + F = Vector{S} + + verts = skeleton(mesh, 0) + edges = skeleton(mesh, 1) + + C02 = connectivity(mesh, verts, abs); R02 = rowvals(C02); V02 = nonzeros(C02) + C12 = connectivity(mesh, edges, abs); R12 = rowvals(C12); V12 = nonzeros(C12) + + ne = order-1 + nf = div((order-2)*(order-1), 2) + + nV = length(verts) + nE = length(edges) * ne + nF = length(mesh) * nf + N = nV + nE + nF + + localspace = LagrangeRefSpace{T,order,3,binomial(2+order,2)}() + dom = domain(chart(mesh, first(mesh))) + localdim = numfunctions(localspace, dom) + + d = order + fns = [S[] for n in 1:(nV+nE+nF)] + pos = fill(point(0,0,0), nV+nE+nF) + for cell in mesh + cell_ch = chart(mesh, cell) + V = R02[nzrange(C02,cell)] + I = V02[nzrange(C02,cell)] + for (i,v) in zip(I, V) + vertex_ch = chart(verts, v) + gids = [v] + lids = localindices(_LagrangeGlobalNodesDoFs(d), cell_ch, localspace, i) + v = globaldofs(vertex_ch, cell_ch, localspace, _LagrangeGlobalNodesDoFs(d)) + α = v[lids, :] + β = inv(α') + for i in axes(β,1) + for j in axes(β,2) + isapprox(β[i,j], 0; atol) && continue + push!(fns[gids[i]], S(cell, lids[j], β[i,j])) + end + end + end + + order < 2 && continue + E = R12[nzrange(C12,cell)] + I = V12[nzrange(C12,cell)] + for (i,e) in zip(I, E) + edge_ch = chart(edges, e) + gids = nV + (e-1)*ne + 1: nV + e*ne + lids = localindices(_LagrangeGlobalEdgeDoFs(d), cell_ch, localspace, i) + @assert length(lids) == length(gids) + v = globaldofs(edge_ch, cell_ch, localspace, _LagrangeGlobalEdgeDoFs(d)) + α = v[lids, :] + β = inv(α') + for i in axes(β,1) + for j in axes(β,2) + isapprox(β[i,j], 0; atol) && continue + push!(fns[gids[i]], S(cell, lids[j], β[i,j])) + end + end + end + + order < 3 && continue + F = [cell] + for (q,f) in enumerate(F) + face_ch = chart(mesh, f) + gids = nV + nE + (f-1)*nf + 1 : nV + nE + f*nf + lids = localindices(_LagrangeGlobalFaceDoFs(d), cell_ch, localspace, 1) + v = globaldofs(face_ch, cell_ch, localspace, _LagrangeGlobalFaceDoFs(d)) + α = v[lids, :] + # @show α + β = inv(α') + for i in axes(β,1) + for j in axes(β,2) + isapprox(β[i,j], 0; atol) && continue + push!(fns[gids[i]], S(cell, lids[j], β[i,j])) + end + end + end + end + + for v in verts pos[v] = cartesian(center(chart(verts, v))) end + for e in edges pos[nV + (e-1)*ne + 1: nV + e*ne] .= Ref(cartesian(center(chart(edges, e)))) end + for f in mesh pos[nV + nE + (f-1)*nf + 1: nV + nE + f*nf] .= Ref(cartesian(center(chart(mesh, f)))) end + + return LagrangeBasis{order,0,localdim}(mesh, fns, pos) +end \ No newline at end of file diff --git a/src/bases/local/bdm3dlocal.jl b/src/bases/local/bdm3dlocal.jl new file mode 100644 index 00000000..02b3b02a --- /dev/null +++ b/src/bases/local/bdm3dlocal.jl @@ -0,0 +1,35 @@ +struct BDM3DRefSpace{T} <: RefSpace{T} end + +function (f::BDM3DRefSpace)(p) + + u,v,w = parametric(p) + + tu = tangents(p,1) + tv = tangents(p,2) + tw = tangents(p,3) + + j = jacobian(p) + d=1/j + + return SVector(( + + + (value= 2*(-v*tu+v*tv)/j , divergence= 2*d), + (value= 2*(-w*tu+w*tw)/j , divergence= 2*d), + (value= 2*((u+v+w-1)*tu)/j , divergence= 2*d), + + (value= 2*(-w*tv+w*tw)/j , divergence= 2*d), + (value= 2*((u+v+w-1)*tv)/j , divergence= 2*d), + (value= 2*(u*tu-u*tv)/j , divergence= 2*d), + + + (value= 2*((u+v+w-1)*tw)/j , divergence= 2*d), + (value= 2*(u*tu-u*tw)/j , divergence= 2*d), + (value= 2*(v*tv-v*tw)/j , divergence= 2*d), + + (value= 2*(u*tu)/j , divergence= 2*d), + (value= 2*(v*tv)/j , divergence= 2*d), + (value= 2*(w*tw)/j , divergence= 2*d) + + )) +end \ No newline at end of file diff --git a/src/bases/local/bdmlocal.jl b/src/bases/local/bdmlocal.jl index 8991e587..d1a87df1 100644 --- a/src/bases/local/bdmlocal.jl +++ b/src/bases/local/bdmlocal.jl @@ -1,4 +1,8 @@ -struct BDMRefSpace{T} <: RefSpace{T,6} end +struct BDMRefSpace{T} <: RefSpace{T} end + +function valuetype(ref::BDMRefSpace{T}, charttype::Type) where {T} + SVector{universedimension(charttype),T} +end function (f::BDMRefSpace)(p) @@ -18,3 +22,82 @@ function (f::BDMRefSpace)(p) (value=u*tu/j, divergence=d), (value=v*tv/j, divergence=d),] end + +divergence(ref::BDMRefSpace, sh, el) = [Shape(sh.cellid, 1, sh.coeff/(2*volume(el)))] + +numfunctions(x::BDMRefSpace, dom::CompScienceMeshes.ReferenceSimplex{2}) = 6 + +const _vert_perms_bdm = [ + (1,2,3), + (2,3,1), + (3,1,2), + (2,1,3), + (1,3,2), + (3,2,1), +] +const _dof_perms_bdm = [ + (1,2,3,4,5,6), + (5,6,1,2,3,4), + (3,4,5,6,1,2), + (4,3,2,1,6,5), + (2,1,6,5,4,3), + (6,5,4,3,2,1), +] + +function dof_permutation(::BDMRefSpace, vert_permutation) + i = findfirst(==(tuple(vert_permutation...)), _vert_perms_bdm) + return _dof_perms_bdm[i] +end + +function dof_perm_matrix(::BDMRefSpace, vert_permutation) + i = findfirst(==(tuple(vert_permutation...)), _vert_perms_bdm) + return _dof_bdmperm_matrix[i] +end + +dimtype(::BDMRefSpace, ::CompScienceMeshes.Simplex{U,2}) where {U} = Val{6} + +const _dof_bdmperm_matrix = [ + + @SMatrix[1 0 0 0 0 0; #1. {1,2,3} + 0 1 0 0 0 0; + 0 0 1 0 0 0; + 0 0 0 1 0 0; + 0 0 0 0 1 0; + 0 0 0 0 0 1], + + @SMatrix[0 0 0 0 1 0; #2. {2,3,1} + 0 0 0 0 0 1; + 1 0 0 0 0 0; + 0 1 0 0 0 0; + 0 0 1 0 0 0; + 0 0 0 1 0 0], + + @SMatrix[0 0 1 0 0 0; #3. {3,1,2} + 0 0 0 1 0 0; + 0 0 0 0 1 0; + 0 0 0 0 0 1; + 1 0 0 0 0 0; + 0 1 0 0 0 0], + + @SMatrix[0 0 0 1 0 0; #4. {2,1,3} + 0 0 1 0 0 0; + 0 1 0 0 0 0; + 1 0 0 0 0 0; + 0 0 0 0 0 1; + 0 0 0 0 1 0], + + @SMatrix[0 1 0 0 0 0; #5. {1,3,2} + 1 0 0 0 0 0; + 0 0 0 0 0 1; + 0 0 0 0 1 0; + 0 0 0 1 0 0; + 0 0 1 0 0 0], + + @SMatrix[0 0 0 0 0 1; #6. {3,2,1} + 0 0 0 0 1 0; + 0 0 0 1 0 0; + 0 0 1 0 0 0; + 0 1 0 0 0 0; + 1 0 0 0 0 0] + +] \ No newline at end of file diff --git a/src/bases/local/gwpdivlocal.jl b/src/bases/local/gwpdivlocal.jl new file mode 100644 index 00000000..ee1d5f8f --- /dev/null +++ b/src/bases/local/gwpdivlocal.jl @@ -0,0 +1,150 @@ +struct GWPDivRefSpace{T,Degree} <: RefSpace{T} + storage::Vector{@NamedTuple{value::SVector{2,T}, curl::T}} +end + +function numfunctions(x::GWPDivRefSpace{<:Any,D}, + dom::CompScienceMeshes.ReferenceSimplex{2}) where {D} + (D+1)*(D+3) +end +function dimtype(x::GWPDivRefSpace{<:Any,D}, + dom::CompScienceMeshes.ReferenceSimplex{2}) where {D} + Val{(D+1)*(D+3)} +end + +function GWPDivRefSpace{T,Degree}() where {T,Degree} + NT = @NamedTuple{value::T, curl::SVector{2,T}} + GWPDivRefSpace{T,Degree}(Vector{NT}()) +end + +function (ϕ::GWPDivRefSpace{T,Degree})(p) where {T,Degree} + ψ = GWPCurlRefSpace{T,Degree}(ϕ.storage) + dom = domain(chart(p)) + u = parametric(p) + vals = ψ(dom, u) + vals = pushforwardcurl(vals, p) + n = normal(p) + map(vals) do v + (value = -n × v.value, divergence=v.curl) + end +end + +function interpolate(fields, interpolant::GWPDivRefSpace{T,Degree}, chart) where {T,Degree} + + function nxfields(p) + vals = fields(p) + n = normal(p) + return map(v -> n×v, vals) + end + + return interpolate(nxfields, GWPCurlRefSpace{T,Degree}(), chart) +end + +@testitem "divergence - pointwise" begin + using CompScienceMeshes, LinearAlgebra + + T = Float64 + s = simplex( + point(3,0,0), + point(0,2,1), + point(-1,-1,-1), + ) + + order = 2 + ϕ = BEAST.GWPDivRefSpace{T,order}() + + u = T(0.2432); dx = sqrt(eps(T)) + v = T(0.5786); dy = sqrt(eps(T)) + + # p00 = neighborhood(s, (u,v)) + # p10 = neighborhood(s, (u+du,v)) + # p01 = neighborhood(s, (u, v+dv)) + + p00 = neighborhood(s, (u,v)) + t1 = normalize(tangents(p00,1)) + t2 = normal(p00) × t1 + + p10 = neighborhood(s, carttobary(s, cartesian(p00) + dx*t1 + 0*t2)) + p01 = neighborhood(s, carttobary(s, cartesian(p00) + 0*t1 + dy*t2)) + + ϕ00 = ϕ(p00) + ϕ10 = ϕ(p10) + ϕ01 = ϕ(p01) + + + for (f00, f10, f01) in zip(ϕ00, ϕ10, ϕ01) + df1dx = dot(t1, f10.value - f00.value) / dx + df2dy = dot(t2, f01.value - f00.value) / dy + curl_num = df1dx + df2dy + curl_ana = f00.divergence + # @show curl_num, curl_ana, abs(curl_num - curl_ana) + @test curl_num ≈ curl_ana atol=sqrt(eps(T))*100 + end +end + + +function divergence(localspace::GWPDivRefSpace, sh, ch) + fns = divergence(localspace, sh.cellid, ch) + α = sh.coeff + S = typeof(sh) + return S[S(s.cellid, s.refid, α*s.coeff) for s in fns[sh.refid]] +end + + +function divergence(localspace::GWPDivRefSpace, cellid::Int, ch) + divergence(localspace, cellid, ch, dimtype(localspace, domain(ch))) +end + + +function divergence(localspace::GWPDivRefSpace{T,D}, cellid::Int, ch, ::Type{Val{N}}) where {N,D,T} + function fields(p) + map(localspace(p)) do x + x.divergence + end + end + atol = sqrt(eps(T)) + Dim = 2 + NFout = div((Dim+1)*(Dim+2), 2) + lag = LagrangeRefSpace{T,D,Dim+1,NFout}() + coeffs = interpolate(fields, lag, ch) + S = BEAST.Shape{T} + A = Vector{Vector{S}}(undef, size(coeffs,1)) + for r in axes(coeffs,1) + A[r] = collect(S(cellid, c, coeffs[r,c]) for c in axes(coeffs,2) if abs(c) > atol) + end + return SVector{N}(A) +end + + +@testitem "divergence - chartwise" begin + using CompScienceMeshes + const CSM = CompScienceMeshes + + T = Float64 + D = 4 + NF = binomial(2+D, 2) + gwp = BEAST.GWPDivRefSpace{T,D}() + lgx = BEAST.LagrangeRefSpace{T,D,3,10}() + + ch = CSM.simplex( + point(1,0,0), + point(0,1,0), + point(0,0,0)) + + divfns = BEAST.divergence(gwp, 1, ch) + + p = neighborhood(ch, (0.2, 0.6)) + gwp_vals = gwp(p) + lgx_vals = lgx(p) + + err = similar(Vector{T}, axes(gwp_vals)) + for i in eachindex(gwp_vals) + val1 = gwp_vals[i].divergence + val2 = zero(T) + for sh in divfns[i] + val2 += sh.coeff * lgx_vals[sh.refid].value + end + err[i] = abs(val1 - val2) + end + atol = sqrt(eps(T)) + @test all(err .< atol) +end \ No newline at end of file diff --git a/src/bases/local/gwplocal.jl b/src/bases/local/gwplocal.jl new file mode 100644 index 00000000..9d9cdd1b --- /dev/null +++ b/src/bases/local/gwplocal.jl @@ -0,0 +1,226 @@ +struct GWPCurlRefSpace{T,Degree} <: RefSpace{T} + storage::Vector{@NamedTuple{value::SVector{2,T}, curl::T}} +end + +function numfunctions(x::GWPCurlRefSpace{<:Any,D}, + dom::CompScienceMeshes.ReferenceSimplex{2}) where {D} + (D+1)*(D+3) +end +function dimtype(x::GWPCurlRefSpace{<:Any,D}, + dom::CompScienceMeshes.ReferenceSimplex{2}) where {D} + Val{(D+1)*(D+3)} +end + +function GWPCurlRefSpace{T,Degree}() where {T,Degree} + NT = @NamedTuple{value::T, curl::SVector{2,T}} + GWPCurlRefSpace{T,Degree}(Vector{NT}()) +end + +function (ϕ::GWPCurlRefSpace{T,Degree})(p) where {T,Degree} + dom = domain(chart(p)) + u = parametric(p) + vals = ϕ(dom, u) + pushforwardcurl(vals, p) +end + +function (ϕ::GWPCurlRefSpace{T,Deg})(dom::CompScienceMeshes.ReferenceSimplex{Dim}, + u) where {T,Deg,Dim} + + ϕ(dom, u, dimtype(ϕ,dom)) +end + +function (ϕ::GWPCurlRefSpace{T,Deg})(dom::CompScienceMeshes.ReferenceSimplex{Dim}, + u, ::Type{Val{NF}}) where {T,Deg,Dim,NF} + + d = Deg + u, v = u + w = 1-u-v + + s = range(zero(T), one(T), length=d+3) + + nd1 = point(T, -v, u-1) + nd2 = point(T, -v+1, u) + nd3 = point(T, -v, u) + + P = SVector{2,T} + NT = @NamedTuple{value::P, curl::T} + # nts = Vector{NT}(undef, NF) + # nts = MVector{NF,NT}(undef) + nts = resize!(ϕ.storage, NF) + idx = 1 + + i = 0 + for j in 1:d+1 + k = (d+2)-i-j + Rᵢ = _sylpoly(s, i+1, u) + Rⱼ = _sylpoly_shift(s, j+1, v) + Rₖ = _sylpoly_shift(s, k+1, w) + + dRᵢ = _sylpoly_diff(s, i+1, u) + dRⱼ = _sylpoly_shift_diff(s, j+1, v) + dRₖ = _sylpoly_shift_diff(s, k+1, w) + du = dRᵢ*Rⱼ*Rₖ - Rᵢ*Rⱼ*dRₖ + dv = Rᵢ*dRⱼ*Rₖ - Rᵢ*Rⱼ*dRₖ + curl = (du*nd1[2] - dv*nd1[1]) + 2*Rᵢ*Rⱼ*Rₖ + + nts[idx] = (value=Rᵢ*Rⱼ*Rₖ*nd1, curl=curl) + idx += 1 + end + + for i in 1:d+1 + j = 0 + k = (d+2)-i-j + Rᵢ = _sylpoly_shift(s, i+1, u) + Rⱼ = _sylpoly(s, j+1, v) + Rₖ = _sylpoly_shift(s, k+1, w) + + dRᵢ = _sylpoly_shift_diff(s, i+1, u) + dRⱼ = _sylpoly_diff(s, j+1, v) + dRₖ = _sylpoly_shift_diff(s, k+1, w) + du = dRᵢ*Rⱼ*Rₖ - Rᵢ*Rⱼ*dRₖ + dv = Rᵢ*dRⱼ*Rₖ - Rᵢ*Rⱼ*dRₖ + curl = (du*nd2[2] - dv*nd2[1]) + 2*Rᵢ*Rⱼ*Rₖ + + nts[idx] = (value=Rᵢ*Rⱼ*Rₖ*nd2, curl=curl) + idx += 1 + end + + for i in 1:d+1 + j = (d+2)-i + k = 0 + Rᵢ = _sylpoly_shift(s, i+1, u) + Rⱼ = _sylpoly_shift(s, j+1, v) + Rₖ = _sylpoly(s, k+1, w) + + dRᵢ = _sylpoly_shift_diff(s, i+1, u) + dRⱼ = _sylpoly_shift_diff(s, j+1, v) + dRₖ = _sylpoly_diff(s, k+1, w) + + du = dRᵢ*Rⱼ*Rₖ - Rᵢ*Rⱼ*dRₖ + dv = Rᵢ*dRⱼ*Rₖ - Rᵢ*Rⱼ*dRₖ + curl = (du*nd3[2] - dv*nd3[1]) + 2*Rᵢ*Rⱼ*Rₖ + + nts[idx] = (value=Rᵢ*Rⱼ*Rₖ*nd3, curl=curl) + idx += 1 + end + + for i in 1:d+1 + for j in 1:d+1 + k = (d+2)-i-j + k <= 0 && continue + Rsᵢ = _sylpoly_shift(s, i+1, u) + Rsⱼ = _sylpoly_shift(s, j+1, v) + Rsₖ = _sylpoly_shift(s, k+1, w) + Rᵢ = _sylpoly(s, i+1, u) + Rⱼ = _sylpoly(s, j+1, v) + Rₖ = _sylpoly(s, k+1, w) + S1 = Rᵢ*Rsⱼ*Rsₖ*nd1 + S2 = Rsᵢ*Rⱼ*Rsₖ*nd2 + S3 = Rsᵢ*Rsⱼ*Rₖ*nd3 + + dRsᵢ = _sylpoly_shift_diff(s, i+1, u) + dRsⱼ = _sylpoly_shift_diff(s, j+1, v) + dRsₖ = _sylpoly_shift_diff(s, k+1, w) + dRᵢ = _sylpoly_diff(s, i+1, u) + dRⱼ = _sylpoly_diff(s, j+1, v) + dRₖ = _sylpoly_diff(s, k+1, w) + + du = dRᵢ*Rsⱼ*Rsₖ - Rᵢ*Rsⱼ*dRsₖ + dv = Rᵢ*dRsⱼ*Rsₖ - Rᵢ*Rsⱼ*dRsₖ + curlS1 = du*nd1[2] - dv*nd1[1] + 2*Rᵢ*Rsⱼ*Rsₖ + + du = dRsᵢ*Rⱼ*Rsₖ - Rsᵢ*Rⱼ*dRsₖ + dv = Rsᵢ*dRⱼ*Rsₖ - Rsᵢ*Rⱼ*dRsₖ + curlS2 = du*nd2[2] - dv*nd2[1] + 2*Rsᵢ*Rⱼ*Rsₖ + + du = dRsᵢ*Rsⱼ*Rₖ - Rsᵢ*Rsⱼ*dRₖ + dv = Rsᵢ*dRsⱼ*Rₖ - Rsᵢ*Rsⱼ*dRₖ + curlS3 = du*nd3[2] - dv*nd3[1] + 2*Rsᵢ*Rsⱼ*Rₖ + + nts[idx] = (value=S2-S3, curl=curlS2 - curlS3) + idx += 1 + + nts[idx] = (value=S3-S1, curl=curlS3 - curlS1) + idx += 1 + end end + + return SVector{NF}(nts) +end + + +function interpolate(fields, interpolant::GWPCurlRefSpace{T,Degree}, chart) where {T,Degree} + + d = Degree + dim = (d+1)*(d+3) + + s = range(zero(T), one(T), length=d+3) + + edges = faces(chart) + + edge = edges[1] + fields_edge = trace(edge, chart, fields) + i = 0 + Q1 = stack(1:d+1) do j + k = (d+2)-i-j + u_edge = s[j+1] + p_edge = neighborhood(edge, (u_edge,)) + # @show cartesian(p_edge) + t_edge = -tangents(p_edge, 1) + vals = fields_edge(u_edge) + [dot(t_edge, val) for val in vals] + end + + edge = edges[2] + fields_edge = trace(edge, chart, fields) + Q2 = stack(1:d+1) do i + j = 0 + k = (d+2)-i-j + u_edge = 1 - s[i+1] + p_edge = neighborhood(edge, (u_edge,)) + t_edge = -tangents(p_edge, 1) + vals = fields_edge(u_edge) + [dot(t_edge, val) for val in vals] + end + + edge = edges[3] + fields_edge = trace(edge, chart, fields) + Q3 = stack(1:d+1) do i + j = (d+2)-i + k = 0 + u_edge = 1-s[j+1] + p_edge = neighborhood(edge, (u_edge,)) + t_edge = -tangents(p_edge, 1) + vals = fields_edge(u_edge) + [dot(t_edge, val) for val in vals] + end + + Q = hcat(Q1,Q2,Q3) + if d >= 1 + S = ((i,j,d+2-i-j) for i in 1:d+1 for j in 1:d+1 if d+2-i-j > 0) + for (i,j,k) in S + p_chart = neighborhood(chart, (s[i+1],s[j+1])) + t_i = tangents(p_chart, 1) + t_j = tangents(p_chart, 2) + vals = fields(p_chart) + q_i = [dot(t_i, val) for val in vals] + q_j = [dot(t_j, val) for val in vals] + Q = hcat(Q, q_i, q_j) + end + end + return Q +end + +function localindices(localspace::GWPCurlRefSpace{<:Any,Degree}, domain, + dim::Type{Val{1}}, i) where {Degree} + + ne = Degree+1 + (i-1)*ne .+ (1:ne) +end + +function localindices(localspace::GWPCurlRefSpace{<:Any,Degree}, domain, + dim::Type{Val{2}}, i) where {Degree} + + ne = Degree+1 + nf = Degree * (Degree + 1) + 3*ne .+ (1:nf) +end diff --git a/src/bases/local/laglocal.jl b/src/bases/local/laglocal.jl index 4ff1cc88..dc5ff08e 100644 --- a/src/bases/local/laglocal.jl +++ b/src/bases/local/laglocal.jl @@ -1,17 +1,22 @@ # T: coeff type # Degree: degree # Dim1: dimension of the support + 1 -mutable struct LagrangeRefSpace{T,Degree,Dim1,NF} <: RefSpace{T,NF} end +struct LagrangeRefSpace{T,Degree,Dim1,NF} <: RefSpace{T} end -numfunctions(s::LagrangeRefSpace{T,D,2}) where {T,D} = D+1 -numfunctions(s::LagrangeRefSpace{T,0,3}) where {T} = 1 -numfunctions(s::LagrangeRefSpace{T,1,3}) where {T} = 3 +numfunctions(s::LagrangeRefSpace{T,D,2}, ch::CompScienceMeshes.ReferenceSimplex{1}) where {T,D} = D+1 +numfunctions(s::LagrangeRefSpace{T,0,3}, ch::CompScienceMeshes.ReferenceSimplex{2}) where {T} = 1 +numfunctions(s::LagrangeRefSpace{T,1,3}, ch::CompScienceMeshes.ReferenceSimplex{2}) where {T} = 3 +numfunctions(s::LagrangeRefSpace{T,2,3}, ch::CompScienceMeshes.ReferenceSimplex{2}) where {T} = 6 +numfunctions(s::LagrangeRefSpace{T,Dg}, ch::CompScienceMeshes.ReferenceSimplex{D}) where {T,Dg,D} = binomial(D+Dg,Dg) -valuetype(ref::LagrangeRefSpace{T}, charttype) where {T} = - SVector{numfunctions(ref), Tuple{T,T}} +# valuetype(ref::LagrangeRefSpace{T}, charttype) where {T} = +# SVector{numfunctions(ref), Tuple{T,T}} +valuetype(ref::LagrangeRefSpace{T}, charttype) where {T} = T # Evaluate constant lagrange elements on anything (ϕ::LagrangeRefSpace{T,0})(tp) where {T} = SVector(((value=one(T), derivative=zero(T)),)) +(ϕ::LagrangeRefSpace{T,0,3})(tp) where {T} = SVector(((value=one(T), derivative=zero(T)),)) + # Evaluate linear Lagrange elements on a segment function (f::LagrangeRefSpace{T,1,2})(mp) where T @@ -25,47 +30,54 @@ end # Evaluete linear lagrange elements on a triangle function (f::LagrangeRefSpace{T,1,3})(t) where T u,v,w, = barycentric(t) - SVector( - (value=u,), - (value=v,), - (value=w,)) -end - -""" - f(tangent_space, Val{:withcurl}) - -Compute the values of the shape functions together with their curl. -""" -function (f::LagrangeRefSpace{T,1,3})(t, ::Type{Val{:withcurl}}) where T - # Evaluete linear Lagrange elements on a triange, together with their curl j = jacobian(t) - u,v,w, = barycentric(t) p = t.patch + σ = sign(dot(normal(t), cross(p[1]-p[3],p[2]-p[3]))) SVector( - (value=u, curl=(p[3]-p[2])/j), - (value=v, curl=(p[1]-p[3])/j), - (value=w, curl=(p[2]-p[1])/j) - ) + (value=u, curl=σ*(p[3]-p[2])/j), + (value=v, curl=σ*(p[1]-p[3])/j), + (value=w, curl=σ*(p[2]-p[1])/j)) end + # Evaluate constant Lagrange elements on a triangle, with their curls function (f::LagrangeRefSpace{T,0,3})(t, ::Type{Val{:withcurl}}) where T i = one(T) z = zero(cartesian(t)) - ( - (value=i, curl=z,), - ) + SVector(((value=i, curl=z,),)) end -function curl(ref::LagrangeRefSpace, sh, el) +function curl(ref::LagrangeRefSpace{T,1,3} where {T}, sh, el) sh1 = Shape(sh.cellid, mod1(sh.refid+1,3), -sh.coeff) sh2 = Shape(sh.cellid, mod1(sh.refid+2,3), +sh.coeff) return [sh1, sh2] end +#= +function curl(ref::LagrangeRefSpace{T,2,3}, sh, el) where T + + j = 1.0 #volume(el) * factorial(dimension(el)) + + if sh.refid < 4 + sh1 = Shape(sh.cellid, mod1(2*sh.refid+1,6), -sh.coeff*j) + sh2 = Shape(sh.cellid, mod1(2*sh.refid+2,6), 3*sh.coeff*j) + sh3 = Shape(sh.cellid, mod1(2*sh.refid+3,6), -3*sh.coeff*j) + sh4 = Shape(sh.cellid, mod1(2*sh.refid+4,6), sh.coeff*j) + + return [sh1, sh2, sh3, sh4] + else + sh1 = Shape(sh.cellid, 2*mod1(sh.refid,3)-1, 4*sh.coeff*j) + sh2 = Shape(sh.cellid, 2*mod1(sh.refid,3), -4*sh.coeff*j) + + return [sh1, sh2] + end + +end +=# + function gradient(ref::LagrangeRefSpace{T,1,4}, sh, tet) where {T} this_vert = tet.vertices[sh.refid] # other_verts = deleteat(tet.vertices, sh.refid) @@ -75,7 +87,7 @@ function gradient(ref::LagrangeRefSpace{T,1,4}, sh, tet) where {T} n = normal(ctr_opp_face) h = -dot(this_vert - cartesian(ctr_opp_face), n) @assert h > 0 - gradval = -h*n + gradval = -(1/h)*n output = Vector{Shape{T}}() for (i,edge) in enumerate(CompScienceMeshes.edges(tet)) ctr_edge = center(edge) @@ -84,11 +96,26 @@ function gradient(ref::LagrangeRefSpace{T,1,4}, sh, tet) where {T} lgt = volume(edge) cff = -lgt * dot(tgt, gradval) isapprox(cff, 0, atol=sqrt(eps(T))) && continue - push!(output, Shape(sh.cellid, i, cff)) + push!(output, Shape(sh.cellid, i, sh.coeff * cff)) end return output end +function gradient(ref::LagrangeRefSpace{T,1,3} where {T}, sh, el) + sh1 = Shape(sh.cellid, mod1(sh.refid+1,3), +sh.coeff) + sh2 = Shape(sh.cellid, mod1(sh.refid+2,3), -sh.coeff) + return [sh1, sh2] +end + +function gradient(ref::LagrangeRefSpace{T,1,2}, sh, seg) where {T} + + sh.refid == 1 && return [Shape(sh.cellid, 1, +sh.coeff/volume(seg))] + @assert sh.refid == 2 + return [Shape(sh.cellid, 1, -sh.coeff/volume(seg))] + +end + + function strace(x::LagrangeRefSpace, cell, localid, face) @@ -106,7 +133,8 @@ function strace(x::LagrangeRefSpace, cell, localid, face) vals1 = x(P1) vals2 = x(P2) - for j in 1:numfunctions(x) + num_shapes = numfunctions(x, domain(cell)) + for j in 1:num_shapes Q[1,j] = vals1[j].value Q[2,j] = vals2[j].value end @@ -114,15 +142,32 @@ function strace(x::LagrangeRefSpace, cell, localid, face) Q end +function strace(x::LagrangeRefSpace{T, 1, 4, 4}, cell, localid, face) where {T} + + #T = scalartype(x) + t = zeros(T, 3, 4) + for (k,fvert) in enumerate(face.vertices) + for (l,cvert) in enumerate(cell.vertices) + nrm = norm(fvert - cvert) + if isapprox(nrm, 0, atol=sqrt(eps(T))) + t[k,l] = T(1.0) + break + end + end + end + + return t +end + function restrict(refs::LagrangeRefSpace{T,0}, dom1, dom2) where T - #Q = eye(T, numfunctions(refs)) - Q = Matrix{T}(I, numfunctions(refs), numfunctions(refs)) + n = numfunctions(refs, domain(dom1)) + Q = Matrix{T}(I, n, n) end function restrict(f::LagrangeRefSpace{T,1}, dom1, dom2) where T - D = numfunctions(f) + D = numfunctions(f, domain(dom1)) Q = zeros(T, D, D) # for each point of the new domain @@ -143,3 +188,310 @@ function restrict(f::LagrangeRefSpace{T,1}, dom1, dom2) where T return Q end + + + +## Quadratic Lagrange element on a triangle +function (f::LagrangeRefSpace{T,2,3})(t) where T + u,v,w, = barycentric(t) + + j = jacobian(t) + p = t.patch + + + σ = sign(dot(normal(t), cross(p[1]-p[3],p[2]-p[3]))) + SVector( + (value=u*(2*u-1), curl=σ*(p[3]-p[2])*(4u-1)/j), + (value=4*u*v, curl=4*σ*(u*(p[1]-p[3])+v*(p[3]-p[2]))/j), + (value=v*(2*v-1), curl=σ*(p[1]-p[3])*(4v-1)/j), + (value=4*w*u, curl=4*σ*(w*(p[3]-p[2])+u*(p[2]-p[1]))/j), + (value=4*v*w, curl=4*σ*(w*(p[1]-p[3])+v*(p[2]-p[1]))/j), + (value=w*(2*w-1), curl=σ*(p[2]-p[1])*(4w-1)/j), + ) +end + + +function curl(ref::LagrangeRefSpace{T,2,3} where {T}, sh, el) + #curl of lagc0d2 as combination of bdm functions + z=zero(typeof(sh.coeff)) + if sh.refid < 4 + sh1 = Shape(sh.cellid, mod1(2*sh.refid+1,6), +sh.coeff) + sh2 = Shape(sh.cellid, mod1(2*sh.refid+2,6), -3*sh.coeff) + sh3 = Shape(sh.cellid, mod1(2*sh.refid+3,6), +3*sh.coeff) + sh4 = Shape(sh.cellid, mod1(2*sh.refid+4,6), -sh.coeff) + else + sh1 = Shape(sh.cellid, mod1(2*sh.refid+4,6), z*sh.coeff) + sh2 = Shape(sh.cellid, mod1(2*sh.refid+5,6), -4*sh.coeff) + sh3 = Shape(sh.cellid, mod1(2*sh.refid+6,6), +4*sh.coeff) + sh4 = Shape(sh.cellid, mod1(2*sh.refid+7,6), z*sh.coeff) + end + return [sh1, sh2, sh3, sh4] +end + +function restrict(f::LagrangeRefSpace{T,2}, dom1, dom2) where T + + D = numfunctions(f) + Q = zeros(T, D, D) + + # for each point of the new domain + for i in 1:3 + + #vertices + v = dom2.vertices[i] + + # find the barycentric coordinates in dom1 + uvn = carttobary(dom1, v) + + # evaluate the shape functions in this point + x = neighborhood(dom1, uvn) + fx = f(x) + + for j in 1:D + Q[j,i] = fx[j][1] + end + + + #edges + # find the center of edge i of dom2 + a = dom2.vertices[mod1(i+1,3)] + b = dom2.vertices[mod1(i+2,3)] + v = (a + b) / 2 + + # find the barycentric coordinates in dom1 + uvn = carttobary(dom1, v) + + # evaluate the shape functions in this point + x = neighborhood(dom1, uvn) + fx = f(x) + + for j in 4:D + Q[j,i+3] = fx[j][1] + end + end + + return Q +end + + +const _vert_perms_lag = [ + (1,2,3), + (2,3,1), + (3,1,2), + (2,1,3), + (1,3,2), + (3,2,1), +] + +const _dof_perms_lag0 = [ + (1), + (1), + (1), + (1), + (1), + (1), +] +const _dof_perms_lag1 = [ + (1,2,3), + (3,1,2), + (2,3,1), + (2,1,3), + (1,3,2), + (3,2,1), +] + +function dof_permutation(::LagrangeRefSpace{<:Any,0}, vert_permutation) + i = findfirst(==(tuple(vert_permutation...)), _vert_perms_lag) + return _dof_perms_lag0[i] +end + +function dof_permutation(::LagrangeRefSpace{<:Any,1}, vert_permutation) + i = findfirst(==(tuple(vert_permutation...)), _vert_perms_lag) + return _dof_perms_lag1[i] +end + +function dof_perm_matrix(::LagrangeRefSpace{<:Any,0}, vert_permutation) + i = findfirst(==(tuple(vert_permutation...)), _vert_perms_rt) + @assert i != nothing + return _dof_lag0perm_matrix[i] +end + +function dof_perm_matrix(::LagrangeRefSpace{<:Any,1}, vert_permutation) + i = findfirst(==(tuple(vert_permutation...)), _vert_perms_rt) + @assert i != nothing + return _dof_rtperm_matrix[i] +end + +const _dof_lag0perm_matrix = [ + @SMatrix[1], # 1. {1,2,3} + @SMatrix[1], # 2. {2,3,1} + @SMatrix[1], # 3. {3,1,2} + @SMatrix[1], # 4. {2,1,3} + @SMatrix[1], # 5. {1,3,2} + @SMatrix[1] # 6. {3,2,1} +] + +function (ϕ::LagrangeRefSpace{T, 1, 4, 4})(lag) where T + + u, v, w = parametric(lag) + + tu = tangents(lag, 1) + tv = tangents(lag, 2) + tw = tangents(lag, 3) + + B = [tu tv tw] + A = inv(transpose(B)) + + # gradient in u,v,w (unit tetrahedron) + gr1=SVector{3, T}(1.0, 0.0, 0.0) + gr2=SVector{3, T}(0.0, 1.0, 0.0) + gr3=SVector{3, T}(0.0, 0.0, 1.0) + gr4=SVector{3, T}(-1.0, -1.0, -1.0) + + return SVector(( + (value = u, gradient = A*gr1), + (value = v, gradient = A*gr2), + (value = w, gradient = A*gr3), + (value = T(1.0)-u-v-w, gradient = A*gr4) + )) +end + + + +# Evaluate higher order Lagrange elements on triangles +# TODO: Optimise using code generation +function (ϕ::LagrangeRefSpace{T,Degree,3})(p) where {T,Degree} + + u, v = parametric(p) + w = 1 - u - v + idx = 0 + + suppdim = 2 + localdim = binomial(suppdim+Degree, suppdim) + vals = T[] + diffus = T[] + diffvs = T[] + + D1 = Degree + 1 + s = range(zero(T), one(T), length=D1) + for i in 0:Degree + ui = i/Degree + for j in 0:Degree + vj = j/Degree + for k in 0:Degree + wk = k/Degree + i + j + k == Degree || continue + + prod_p = one(T) + for p in 0:i-1 + up = p / Degree + prod_p *= (u-up) / (ui-up) + end + prod_q = one(T) + for q in 0:j-1 + vq = q / Degree + prod_q *= (v-vq) / (vj-vq) + end + prod_r = one(T) + for r in 0:k-1 + wr = r / Degree + prod_r *= (w-wr) / (wk-wr) + end + push!(vals, prod_p * prod_q * prod_r) + + diffu = zero(T) + diffv = zero(T) + for l in 0:i-1 + ul = l/Degree + prod_pl = one(T) + for p in 0:i-1 + p == l && continue + up = p/Degree + prod_pl *= (u-up) / (ui-up) + end + diffu += prod_pl * prod_q * prod_r / (ui-ul) + end + for m in 0:j-1 + vm = m/Degree + prod_qm = one(T) + for q in 0:j-1 + q == m && continue + vq = q/Degree + prod_qm *= (v-vq) / (vj-vq) + end + diffv += prod_p * prod_qm * prod_r / (vj-vm) + end + for n in 0:k-1 + wn = n/Degree + prod_rn = one(T) + for r in 0:k-1 + r == n && continue + wr = r/Degree + prod_rn *= (w-wr) / (wk-wr) + end + diffu -= prod_p * prod_q * prod_rn / (wk-wn) + diffv -= prod_p * prod_q * prod_rn / (wk-wn) + end + + push!(diffus, diffu) + push!(diffvs, diffv) + + idx += 1 + end end end + + tu = tangents(p,1) + tv = tangents(p,2) + j = jacobian(p) + NF = length(vals) + SVector{NF}([(value=f, curl=(-dv*tu+du*tv)/j) for (f,du,dv) in zip(vals, diffus, diffvs)]) +end + +# fields[i] ≈ sum(Q[j,i] * interpolant[j].value for j in 1:numfunctions(interpolant)) +function interpolate(fields, interpolant::LagrangeRefSpace{T,Degree,3}, chart) where {T,Degree} + + dim = binomial(2+Degree, Degree) + vals = Vector{Vector{T}}() + if Degree > 0 + I = 0:Degree + s = range(0,1,length=Degree+1) + Is = zip(I,s) + for (i,ui) in Is + for (j,vj) in Is + for (k,wk) in Is + i + j + k == Degree || continue + @assert ui + vj + wk ≈ 1 + p = neighborhood(chart, (ui,vj)) + push!(vals, fields(p)) + end end end + else + p = center(chart) + push!(vals, fields(p)) + end + # Q = hcat(vals...) + Q = Matrix{T}(undef, length(vals[1]), length(vals)) + for i in eachindex(vals) + Q[:,i] .= vals[i] + end + return Q +end + + +function interpolate!(out, fields, interpolant::LagrangeRefSpace{T,Degree,3}, chart) where {T,Degree} + Is = zip((0:Degree), range(0,1,length=Degree+1)) + idx = 0 + for (i,ui) in Is + for (j,vj) in Is + for (k,wk) in Is + i + j + k == Degree || continue; idx += 1 + @assert ui + vj + wk ≈ 1 + p = neighborhood(chart, (ui,vj)) + vals = fields(p) + for (g, val) in zip(axes(out, 1), vals) + out[g,idx] = val +end end end end end + +function interpolate!(out, fields, interpolant::LagrangeRefSpace{T,0,3}, chart) where {T} + p = center(chart) + vals = fields(p) + for (g, val) in zip(axes(out, 1), vals) + out[g,1] = val +end end \ No newline at end of file diff --git a/src/bases/local/ncrossbdmlocal.jl b/src/bases/local/ncrossbdmlocal.jl new file mode 100644 index 00000000..cb332499 --- /dev/null +++ b/src/bases/local/ncrossbdmlocal.jl @@ -0,0 +1,22 @@ +struct NCrossBDMRefSpace{T} <: RefSpace{T} end + +function (f::NCrossBDMRefSpace{T})(p) where T + + u,v = parametric(p) + n = normal(p) + tu = tangents(p,1) + tv = tangents(p,2) + + j = jacobian(p) + d = 1/j + + return @SVector[ + (value= n × (-v*tu+v*tv)/j, curl=d), + (value= n × ((u+v-1)*tu)/j, curl=d), + (value= n × ((u+v-1)*tv) /j, curl=d), + (value= n × (u*tu-u*tv)/j, curl=d), + (value= n × (u*tu)/j, curl=d), + (value= n × (v*tv)/j, curl=d),] +end + +numfunctions(x::NCrossBDMRefSpace, dom::CompScienceMeshes.ReferenceSimplex{2}) = 6 diff --git a/src/bases/local/nd2local.jl b/src/bases/local/nd2local.jl new file mode 100644 index 00000000..31dd94c0 --- /dev/null +++ b/src/bases/local/nd2local.jl @@ -0,0 +1,30 @@ + +mutable struct ND2RefSpace{T} <: RefSpace{T} end + +function (ϕ::ND2RefSpace)(nbd) + + u, v = parametric(nbd) + n = normal(nbd) + j = jacobian(nbd) + + tu = tangents(nbd,1) + tv = tangents(nbd,2) + + d = 2/j + + inv_j = 1/j + + return SVector(( + (value= n × ((8*u^2+8*u*v-12*u-6*v+4)*tu + (2*v*(4*u+4*v-3))*tv)*inv_j, curl=(24*u+24*v-18)*inv_j), + (value= n × ((-8*u*v+2*u+6*v-2)*tu + (4*v*(-2*v+1))*tv)*inv_j, curl=(6-24*v)*inv_j), + (value= n × ((4*u*(1-2*u))*tu + (-8*u*v+6*u+2*v-2)*tv)*inv_j, curl=(6-24*u)*inv_j), + (value= n × ((2*u*(4*u+4*v-3))*tu + (8*u*v-6*u+8*v^2-12*v+4)*tv)*inv_j, curl=(24*u+24*v-18)*inv_j), + (value= n × ((2u*(1-4v))*tu + (4v*(1-2v))*tv)*inv_j, curl=(6-24*v)*inv_j), + (value= n × ((4u*(1-2u))*tu + (2v*(1-4u))*tv)*inv_j, curl=(6-24*u)*inv_j), + (value= n × ((8*u*(-2*u-v+2))*tu + (8*v*(-2*u-v+1))*tv)*inv_j, curl=(-48*u-24*v+24)*inv_j), + (value= n × ((8*u*(-u-2*v+1))*tu + (8*v*(-u-2*v+2))*tv)*inv_j, curl=(-24*u-48*v+24)*inv_j) + )) + +end + +numfunctions(x::ND2RefSpace, dom::CompScienceMeshes.ReferenceSimplex{2}) = 8 \ No newline at end of file diff --git a/src/bases/local/ndlcclocal.jl b/src/bases/local/ndlcclocal.jl index ca18947b..37edb79c 100644 --- a/src/bases/local/ndlcclocal.jl +++ b/src/bases/local/ndlcclocal.jl @@ -1,4 +1,4 @@ -struct NDLCCRefSpace{T} <: RefSpace{T,6} end +struct NDLCCRefSpace{T} <: RefSpace{T} end function valuetype(ref::NDLCCRefSpace{T}, charttype::Type) where {T} SVector{universedimension(charttype),T} @@ -58,6 +58,8 @@ function (ϕ::NDLCCRefSpace)(ndlc) ## )) end +numfunctions(x::NDLCCRefSpace, dom::CompScienceMeshes.ReferenceSimplex{3}) = 6 + #check orientation function curl(ref::NDLCCRefSpace, sh, el) a = [4,2,3,4,1,2]##[2,1,1,1,2,4]#[2,1,4,4,3,2]#[4,2,3,4,1,2] @@ -71,7 +73,7 @@ end function restrict(ϕ::NDLCCRefSpace{T}, dom1, dom2) where {T} # dom2 is the smaller of the domains - K = numfunctions(ϕ) + K = numfunctions(ϕ, domain(dom1)) D = dimension(dom1) @assert K == 6 diff --git a/src/bases/local/ndlcdlocal.jl b/src/bases/local/ndlcdlocal.jl index aabbca81..8b924607 100644 --- a/src/bases/local/ndlcdlocal.jl +++ b/src/bases/local/ndlcdlocal.jl @@ -1,4 +1,4 @@ -struct NDLCDRefSpace{T} <: RefSpace{T,4} end +struct NDLCDRefSpace{T} <: RefSpace{T} end function valuetype(ref::NDLCDRefSpace{T}, charttype::Type) where {T} SVector{universedimension(charttype),T} @@ -30,6 +30,8 @@ function (ϕ::NDLCDRefSpace)(ndlc) )) end +numfunctions(x::NDLCDRefSpace, dom::CompScienceMeshes.ReferenceSimplex{3}) = 4 + function ntrace(x::NDLCDRefSpace, el, q, fc) t = zeros(scalartype(x),1,4) t[q] = 1 / volume(fc) @@ -76,13 +78,13 @@ function ttrace(x::NDLCDRefSpace, el, q, fc) return t end -divergence(ref::NDLCDRefSpace, sh, el) = Shape(sh.cellid, 1, sh.coeff/volume(el)) +divergence(ref::NDLCDRefSpace, sh, el) = [Shape(sh.cellid, 1, sh.coeff/volume(el))] function restrict(ϕ::NDLCDRefSpace{T}, dom1, dom2) where {T} # dom2 is the smaller of the domains - K = numfunctions(ϕ) + K = numfunctions(ϕ, domain(dom1)) D = dimension(dom1) @assert K == 4 @@ -97,7 +99,7 @@ function restrict(ϕ::NDLCDRefSpace{T}, dom1, dom2) where {T} A = volume(face) m = normal(p) - u = carttobary(dom1,p) + # u = carttobary(dom1,cartesian(p)) u = carttobary(dom1, c) x = neighborhood(dom1, u) diff --git a/src/bases/local/ndlocal.jl b/src/bases/local/ndlocal.jl index 0c400837..a7f0f977 100644 --- a/src/bases/local/ndlocal.jl +++ b/src/bases/local/ndlocal.jl @@ -6,7 +6,12 @@ This is not the edge starting at vertex `r`. The downside of this local numberin scheme is that it cannot be extended to cells that are not simplices because there is no well defined concept of adjacent-ness. """ -mutable struct NDRefSpace{T} <: RefSpace{T,3} end +mutable struct NDRefSpace{T} <: RefSpace{T} end + +function valuetype(ref::NDRefSpace{T}, charttype::Type) where {T} + SVector{universedimension(charttype),T} +end + function (ϕ::NDRefSpace)(nbd) @@ -27,10 +32,11 @@ function (ϕ::NDRefSpace)(nbd) end +numfunctions(x::NDRefSpace, dom::CompScienceMeshes.ReferenceSimplex{2}) = 3 function restrict(ϕ::NDRefSpace{T}, dom1, dom2) where T - K = numfunctions(ϕ) + K = numfunctions(ϕ, domain(dom1)) D = dimension(dom1) @assert K == 3 @@ -61,7 +67,7 @@ function restrict(ϕ::NDRefSpace{T}, dom1, dom2) where T for j in 1:K # Q[j,i] = dot(y[j][1], m) * l - Q[i,j] = dot(y[j][1], t) + Q[j,i] = dot(y[j][1], t) end end diff --git a/src/bases/local/rt2local.jl b/src/bases/local/rt2local.jl new file mode 100644 index 00000000..bbecede9 --- /dev/null +++ b/src/bases/local/rt2local.jl @@ -0,0 +1,203 @@ +struct RT2RefSpace{T} <: RefSpace{T} end + +function (f::RT2RefSpace)(p) + + u, v = parametric(p) + j = jacobian(p) + + tu = tangents(p,1) + tv = tangents(p,2) + + inv_j = 1/j + + # return SVector( + # (value=-(4u*(1-2u))*tu - (2v*(1-4u))*tv, divergence=inv_j), + # (value=-(2u*(1-4v))*tu - (4v*(1-2v))*tv, divergence=inv_j), + # (value=(-8*u^2-8*u*v+12*u+6*v-4)*tu + (2*v*(-4*u-4*v+3))*tv, divergence=inv_j), + # (value=(8*u*v-2*u-6*v+2)*tu + (4*v*(2*v-1))*tv, divergence=inv_j), + # (value=-(2*u*(4*u+4*v-3))*tu - (8*u*v-6*u+8*v^2-12*v+4)*tv, divergence=inv_j), + # (value=-(4*u*(1-2*u))*tu - (-8*u*v+6*u+2*v-2)*tv, divergence=inv_j), + # (value=(8*u*(-2*u-v+2))*tu + (8*v*(-2*u-v+1))*tv, divergence=inv_j), + # (value=(8*u*(-u-2*v+1))*tu + (8*v*(-u-2*v+2))*tv, divergence=inv_j), + # ) + + return SVector( + (value=((8*u^2+8*u*v-12*u-6*v+4)*tu + (2*v*(4*u+4*v-3))*tv)*inv_j, divergence=(24*u+24*v-18)*inv_j), + (value=((-8*u*v+2*u+6*v-2)*tu + (4*v*(-2*v+1))*tv)*inv_j, divergence=(6-24*v)*inv_j), + (value=((4*u*(1-2*u))*tu + (-8*u*v+6*u+2*v-2)*tv)*inv_j, divergence=(6-24*u)*inv_j), + (value=((2*u*(4*u+4*v-3))*tu + (8*u*v-6*u+8*v^2-12*v+4)*tv)*inv_j, divergence=(24*u+24*v-18)*inv_j), + (value=((2u*(1-4v))*tu + (4v*(1-2v))*tv)*inv_j, divergence=(6-24*v)*inv_j), + (value=((4u*(1-2u))*tu + (2v*(1-4u))*tv)*inv_j, divergence=(6-24*u)*inv_j), + (value=((8*u*(-2*u-v+2))*tu + (8*v*(-2*u-v+1))*tv)*inv_j, divergence=(-48*u-24*v+24)*inv_j), + (value=((8*u*(-u-2*v+1))*tu + (8*v*(-u-2*v+2))*tv)*inv_j, divergence=(-24*u-48*v+24)*inv_j) + ) +end + +numfunctions(x::RT2RefSpace, dom::CompScienceMeshes.ReferenceSimplex{2}) = 8 + +function interpolate(fields, interpolant::BEAST.RT2RefSpace, chart) + + T = coordtype(chart) + + Q = Any[] + refchart = CompScienceMeshes.domain(chart) + nfields = length(fields(center(chart))) + + for (edge, refedge) in zip(faces(chart), faces(refchart)) + l0 = zeros(T,nfields) + l1 = zeros(T,nfields) + qps = CompScienceMeshes.quadpoints(edge,4) + for (p_edge,w) in qps + s = parametric(p_edge) + + p_refedge = neighborhood(refedge,s) + u = cartesian(p_refedge) + p_refchart = neighborhood(refchart, u) + t_refedge = tangents(p_refedge,1) + m_refedge = point(-t_refedge[2], t_refedge[1]) + m_refedge /= norm(m_refedge) + q0ref = (-1) * (1-s[1]) * m_refedge + q1ref = (-1) * s[1] * m_refedge + + nxq0ref = point(-q0ref[2], q0ref[1]) + nxq1ref = point(-q1ref[2], q1ref[1]) + p_chart = neighborhood(chart, u) + n_chart = normal(p_chart) + J_chart = jacobian(p_chart) + t1 = tangents(p_chart,1) + t2 = tangents(p_chart,2) + q0 = -n_chart × (nxq0ref[1]*t1 + nxq0ref[2]*t2) / J_chart + q1 = -n_chart × (nxq1ref[1]*t1 + nxq1ref[2]*t2) / J_chart + + vals = fields(p_chart) + J_edge = jacobian(p_edge) + J_refedge = jacobian(p_refedge) + l0 .+= [w * dot(f,q0) * J_chart / J_edge * J_refedge for f in vals] + l1 .+= [w * dot(f,q1) * J_chart / J_edge * J_refedge for f in vals] + end + push!(Q,l0) + push!(Q,l1) + end + + l6 = zeros(T,nfields) + l7 = zeros(T,nfields) + qps = CompScienceMeshes.quadpoints(chart, 4) + for (p,w) in qps + + q6ref = point(1,0) + q7ref = point(0,1) + + nxq6ref = point(-q6ref[2], q6ref[1]) + nxq7ref = point(-q7ref[2], q7ref[1]) + + J_chart = jacobian(p) + n_chart = normal(p) + + t1 = tangents(p,1) + t2 = tangents(p,2) + + q6 = -n_chart × (nxq6ref[1] * t1 + nxq6ref[2] * t2) / J_chart + q7 = -n_chart × (nxq7ref[1] * t1 + nxq7ref[2] * t2) / J_chart + + vals = fields(p) + l6 .+= [w * dot(f,q6) for f in vals] + l7 .+= [w * dot(f,q7) for f in vals] + end + + push!(Q,l6) + push!(Q,l7) + + return hcat(Q...) +end + + +function dof_perm_matrix(::RT2RefSpace, vert_permutation) + i = findfirst(==(tuple(vert_permutation...)), _vert_perms_rt) + @assert i != nothing + return _dof_rt2perm_matrix[i] +end + +const _dof_rt2perm_matrix = [ + @SMatrix[1 0 0 0 0 0 0 0; #1. {1,2,3} + 0 1 0 0 0 0 0 0; + 0 0 1 0 0 0 0 0; + 0 0 0 1 0 0 0 0; + 0 0 0 0 1 0 0 0; + 0 0 0 0 0 1 0 0; + 0 0 0 0 0 0 1 0; + 0 0 0 0 0 0 0 1], + + @SMatrix[0 0 0 0 1 0 0 0; #2. {2,3,1} + 0 0 0 0 0 1 0 0; + 1 0 0 0 0 0 0 0; + 0 1 0 0 0 0 0 0; + 0 0 1 0 0 0 0 0; + 0 0 0 1 0 0 0 0; + 0 0 0 0 0 0 0 -1; + 0 0 0 0 0 0 1 -1], + + @SMatrix[0 0 1 0 0 0 0 0; #3. {3,1,2} + 0 0 0 1 0 0 0 0; + 0 0 0 0 1 0 0 0; + 0 0 0 0 0 1 0 0; + 1 0 0 0 0 0 0 0; + 0 1 0 0 0 0 0 0; + 0 0 0 0 0 0 -1 1; + 0 0 0 0 0 0 -1 0], + + @SMatrix[0 0 0 1 0 0 0 0; #4. {2,1,3} + 0 0 1 0 0 0 0 0; + 0 1 0 0 0 0 0 0; + 1 0 0 0 0 0 0 0; + 0 0 0 0 0 1 0 0; + 0 0 0 0 1 0 0 0; + 0 0 0 0 0 0 0 1; + 0 0 0 0 0 0 1 0], + + @SMatrix[0 1 0 0 0 0 0 0; #5. {1,3,2} + 1 0 0 0 0 0 0 0; + 0 0 0 0 0 1 0 0; + 0 0 0 0 1 0 0 0; + 0 0 0 1 0 0 0 0; + 0 0 1 0 0 0 0 0; + 0 0 0 0 0 0 1 -1; + 0 0 0 0 0 0 0 -1], + + @SMatrix[0 0 0 0 0 1 0 0; #6. {3,2,1} + 0 0 0 0 1 0 0 0; + 0 0 0 1 0 0 0 0; + 0 0 1 0 0 0 0 0; + 0 1 0 0 0 0 0 0; + 1 0 0 0 0 0 0 0; + 0 0 0 0 0 0 -1 0; + 0 0 0 0 0 0 -1 1] +] + +@testitem "restrict RT2" begin + using CompScienceMeshes + using LinearAlgebra + + ref_vertices = [ + point(1,0), + point(0,1), + point(0,0), + ] + vertices = [ + point(1,0,0), + point(0,1,0), + point(0,0,0), + ] + chart1 = simplex(vertices...) + for I in BEAST._vert_perms_rt + chart2 = simplex( + chart1.vertices[I[1]], + chart1.vertices[I[2]], + chart1.vertices[I[3]],) + chart2tochart1 = CompScienceMeshes.simplex(ref_vertices[collect(I)]...) + rs = BEAST.RT2RefSpace{Float64}() + Q1 = BEAST.dof_perm_matrix(rs, I) + Q3 = BEAST.interpolate(rs, chart2, rs, chart1, chart2tochart1) + @test Q1 ≈ Q3 + @show norm(Q1-Q3) + end +end \ No newline at end of file diff --git a/src/bases/local/rtlocal.jl b/src/bases/local/rtlocal.jl index a54ea44e..5aca7cff 100644 --- a/src/bases/local/rtlocal.jl +++ b/src/bases/local/rtlocal.jl @@ -1,4 +1,4 @@ -struct RTRefSpace{T} <: RefSpace{T,3} end +struct RTRefSpace{T} <: DivRefSpace{T} end # valuetype(ref::RTRefSpace{T}, charttype) where {T} = SVector{3,Tuple{SVector{universedimension(charttype),T},T}} function valuetype(ref::RTRefSpace{T}, charttype::Type) where {T} @@ -26,7 +26,9 @@ function (ϕ::RTRefSpace)(mp) )) end -divergence(ref::RTRefSpace, sh, el) = Shape(sh.cellid, 1, sh.coeff/volume(el)) +numfunctions(x::RTRefSpace, dom::CompScienceMeshes.ReferenceSimplex{2}) = 3 + +divergence(ref::RTRefSpace, sh, el) = [Shape(sh.cellid, 1, sh.coeff/volume(el))] """ ntrace(refspace, element, localindex, face) @@ -44,38 +46,181 @@ function ntrace(x::RTRefSpace, el, q, fc) return t end -function restrict(ϕ::RTRefSpace{T}, dom1, dom2) where T - K = numfunctions(ϕ) - D = dimension(dom1) +const _vert_perms_rt = [ + (1,2,3), + (2,3,1), + (3,1,2), + (2,1,3), + (1,3,2), + (3,2,1), +] +const _dof_perms_rt = [ + (1,2,3), + (3,1,2), + (2,3,1), + (2,1,3), + (1,3,2), + (3,2,1), +] + +function dof_permutation(::RTRefSpace, vert_permutation) + i = something(findfirst(==(tuple(vert_permutation...)), _vert_perms_rt),0) + @assert i != 0 + return _dof_perms_rt[i] +end + +function dof_perm_matrix(::RTRefSpace, vert_permutation) + i = findfirst(==(tuple(vert_permutation...)), _vert_perms_rt) + @assert i != nothing + return _dof_rtperm_matrix[i] +end + +""" + interpolate(interpolant::RefSpace, chart1, interpolee::RefSpace, chart2) + +Computes by interpolation approximations of the local shape functions for +`interpolee` on `chart2` in terms of the local shape functions for `interpolant` +on `chart1`. The returned value is a matrix `Q` such that + +```math +\\phi_i \\approx \\sum_j Q_{ij} \\psi_j +``` + +with ``\\phi_i`` the i-th local shape function for `interpolee` and ``\\psi_j`` the +j-th local shape function for `interpolant`. +""" +function interpolate(interpolant::RefSpace, chart1, interpolee::RefSpace, chart2) + function fields(p) + x = cartesian(p) + v = carttobary(chart2, x) + r = neighborhood(chart2, v) + fieldvals = [f.value for f in interpolee(r)] + end + + interpolate(fields, interpolant, chart1) +end + +function interpolate!(out, interpolant::RefSpace, chart1, interpolee::RefSpace, chart2) + function fields(p) + x = cartesian(p) + v = carttobary(chart2, x) + r = neighborhood(chart2, v) + fieldvals = [f.value for f in interpolee(r)] + end + + interpolate!(out, fields, interpolant, chart1) +end + + +function interpolate(interpolant::RefSpace, chart1, interpolee::RefSpace, chart2, ch1toch2) + function fields(p1) + u1 = parametric(p1) + u2 = cartesian(ch1toch2, u1) + p2 = neighborhood(chart2, u2) + fieldvals = [f.value for f in interpolee(p2)] + end + + interpolate(fields, interpolant, chart1) +end + +function interpolate!(out, interpolant::RefSpace, chart1, interpolee::RefSpace, chart2, ch1toch2) + function fields(p1) + u1 = parametric(p1) + u2 = cartesian(ch1toch2, u1) + p2 = neighborhood(chart2, u2) + fieldvals = [f.value for f in interpolee(p2)] + end - @assert K == 3 - @assert D == 2 - @assert D == dimension(dom2) + interpolate!(out, fields, interpolant, chart1) +end - Q = zeros(T,K,K) - for i in 1:K - # find the center of edge i of dom2 - a = dom2.vertices[mod1(i+1,D+1)] - b = dom2.vertices[mod1(i+2,D+1)] - c = (a + b) / 2 +function interpolate(fields, interpolant::RTRefSpace{T}, chart) where {T} + Q = map(CompScienceMeshes.subcharts(chart, Val{1})) do (face, inj) - # find the outer binormal there - t = b - a - l = norm(t) - n = dom2.normals[1] - m = cross(t, n) / l + u_face = T(1//2) + p_face = neighborhood(face, (u_face,)) + t_face = -tangents(p_face, 1) + u_chart = cartesian(inj, u_face) + p_chart = neighborhood(chart, u_chart) + n_chart = normal(p_chart) + m_face = cross(t_face, n_chart) - u = carttobary(dom1, c) - x = neighborhood(dom1, u) + # p = center(face) + # x = cartesian(p) + # u = carttobary(chart, x) + # q = neighborhood(chart, u) + # n = normal(q) - y = ϕ(x) + # minus because in CSM the tangent points towards vertex[1] + # t = -tangents(p,1) + # m = cross(t,n) - for j in 1:K - Q[j,i] = dot(y[j][1], m) * l - end + fieldvals = fields(p_chart) + q = [dot(fv,m_face) for fv in fieldvals] end - return Q + return hcat(Q...) +end + + +function interpolate!(out, fields, interpolant::RTRefSpace{T}, chart) where {T} + + for (f,(face, inj)) in zip(axes(out,2), + CompScienceMeshes.subcharts(chart, Val{1})) + + u_face = T(1//2) + p_face = neighborhood(face, (u_face,)) + t_face = -tangents(p_face, 1) + u_chart = cartesian(inj, u_face) + p_chart = neighborhood(chart, u_chart) + n_chart = normal(p_chart) + m_face = cross(t_face, n_chart) + vals = fields(p_chart) + for (g,val) in zip(axes(out, 1), vals) + out[g,f] = dot(m_face, val) +end end end + + +function restrict(ϕ::RefSpace, dom1, dom2) + interpolate(ϕ, dom2, ϕ, dom1) +end + +function restrict!(out, ϕ::RefSpace, dom1, dom2) + interpolate!(out, ϕ, dom2, ϕ, dom1) +end + +function restrict(ϕ::RefSpace, dom1, dom2, dom2todom1) + interpolate(ϕ, dom2, ϕ, dom1, dom2todom1) end + +function restrict!(out, ϕ::RefSpace, dom1, dom2, dom2todom1) + interpolate!(out, ϕ, dom2, ϕ, dom1, dom2todom1) +end + +const _dof_rtperm_matrix = [ + @SMatrix[1 0 0; # 1. {1,2,3} + 0 1 0; + 0 0 1], + + @SMatrix[0 0 1; # 2. {2,3,1} + 1 0 0; + 0 1 0], + + @SMatrix[0 1 0; # 3. {3,1,2} + 0 0 1; + 1 0 0], + + @SMatrix[0 1 0; # 4. {2,1,3} + 1 0 0; + 0 0 1], + + @SMatrix[1 0 0; # 5. {1,3,2} + 0 0 1; + 0 1 0], + + @SMatrix[0 0 1; # 6. {3,2,1} + 0 1 0; + 1 0 0] +] diff --git a/src/bases/local/rtqlocal.jl b/src/bases/local/rtqlocal.jl new file mode 100644 index 00000000..cf6e13ea --- /dev/null +++ b/src/bases/local/rtqlocal.jl @@ -0,0 +1,128 @@ +struct RTQuadRefSpace{T} <: DivRefSpace{T} end + +function (ϕ::RTQuadRefSpace{T})(p) where {T} + + u, v = parametric(p) + j = jacobian(p) + + D = tangents(p) + + eu = point(T,1,0) + ev = point(T,0,1) + + i = one(T) + vals = SVector( + (value=(v-1)*ev, divergence=i), + (value=u*eu, divergence=i), + (value=v*ev, divergence=i), + (value=(u-1)*eu, divergence=i), + ) + + map(vals) do f + (value=D*f.value/j, divergence=f.divergence/j) end +end + +function numfunctions(ϕ::RTQuadRefSpace, dom::CompScienceMeshes.RefQuadrilateral) 4 end + +function interpolate(fields, interpolant::RTQuadRefSpace{T}, chart) where {T} + + refchart = domain(chart) + Q = map(zip(faces(chart), faces(refchart))) do (edge,refedge) + s = T(0.5) + p_edge = neighborhood(edge, s) + p_refedge = neighborhood(refedge, s) + + p_refchart = neighborhood(refchart, cartesian(p_refedge)) + p_chart = neighborhood(chart, cartesian(p_refchart)) + n_chart = normal(p_chart) + + t_edge = tangents(p_edge, 1) + m_edge = -cross(t_edge, n_chart) + + fieldvals = fields(p_chart) + map(fv -> dot(fv,m_edge), fieldvals) + end + + return hcat(Q...) +end + +@testitem "interpolate" begin + using CompScienceMeshes + + p1 = point(0,0,0) + p2 = point(2,0,0) + p3 = point(2,4,0) + p4 = point(0,4,0) + + quad = CompScienceMeshes.Quadrilateral(p1,p2,p3,p4) + f(p) = (x = cartesian(p); return [point(x[1]+2, -x[2]-3, 0)]) + + rtq = BEAST.RTQuadRefSpace{Float64}() + Q = BEAST.interpolate(f, rtq, quad) + p = neighborhood(quad, point(0.5, 0.5)) + val1 = f(p)[1] + val2 = sum(Q[1,i] * ϕ.value for (i,ϕ) in zip(axes(Q,2), rtq(p))) + + # @show val1 + # @show val2 + @test val1 ≈ val2 +end + + +const _vrtperm_matrix_rtq = [ + (1,2,3,4), + (2,3,4,1), + (3,4,1,2), + (4,1,2,3), + (2,1,4,3), + (1,4,3,2), + (4,3,2,1), + (3,2,1,4), +] + +const _dofperm_matrix_rtq = [ + @SMatrix[1.0 0.0 0.0 0.0; 0.0 1.0 0.0 0.0; 0.0 0.0 1.0 0.0; 0.0 0.0 0.0 1.0], + @SMatrix[0.0 0.0 0.0 1.0; 1.0 0.0 0.0 0.0; 0.0 1.0 0.0 0.0; 0.0 0.0 1.0 0.0], + @SMatrix[0.0 0.0 1.0 0.0; 0.0 0.0 0.0 1.0; 1.0 0.0 0.0 0.0; 0.0 1.0 0.0 0.0], + @SMatrix[0.0 1.0 0.0 0.0; 0.0 0.0 1.0 0.0; 0.0 0.0 0.0 1.0; 1.0 0.0 0.0 0.0], + @SMatrix[1.0 0.0 0.0 0.0; 0.0 0.0 0.0 1.0; 0.0 0.0 1.0 0.0; 0.0 1.0 0.0 0.0], + @SMatrix[0.0 0.0 0.0 1.0; 0.0 0.0 1.0 0.0; 0.0 1.0 0.0 0.0; 1.0 0.0 0.0 0.0], + @SMatrix[0.0 0.0 1.0 0.0; 0.0 1.0 0.0 0.0; 1.0 0.0 0.0 0.0; 0.0 0.0 0.0 1.0], + @SMatrix[0.0 1.0 0.0 0.0; 1.0 0.0 0.0 0.0; 0.0 0.0 0.0 1.0; 0.0 0.0 1.0 0.0], +] + +function dof_perm_matrix(::RTQuadRefSpace, vert_permutation) + i = findfirst(==(tuple(vert_permutation...)), _vrtperm_matrix_rtq) + @assert i != nothing + return _dofperm_matrix_rtq[i] +end + +@testitem "restrict RTQ0" begin + using CompScienceMeshes + using Combinatorics + + ref_vertices = [ + point(0,0), + point(1,0), + point(1,1), + point(0,1)] + vertices = [ + point(0,0,0), + point(1,0,0), + point(1,1,0), + point(0,1,0)] + chart1 = CompScienceMeshes.Quadrilateral(vertices...) + for I in BEAST._vrtperm_matrix_rtq + # @show I + chart2 = CompScienceMeshes.Quadrilateral( + vertices[I[1]], + vertices[I[2]], + vertices[I[3]], + vertices[I[4]]) + chart2tochart1 = CompScienceMeshes.Quadrilateral(ref_vertices[collect(I)]...) + rs = BEAST.RTQuadRefSpace{Float64}() + Q1 = BEAST.dof_perm_matrix(rs, I) + Q2 = BEAST.restrict(rs, chart1, chart2, chart2tochart1) + @test Q1 ≈ Q1 + end +end \ No newline at end of file diff --git a/src/bases/localbasis.jl b/src/bases/localbasis.jl new file mode 100644 index 00000000..33110983 --- /dev/null +++ b/src/bases/localbasis.jl @@ -0,0 +1,29 @@ +abstract type RefSpace{T} end +abstract type DivRefSpace{T} <: RefSpace{T} end + +function pushforwardcurl(vals, nbhd) + tu = tangents(nbhd, 1) + tv = tangents(nbhd, 2) + j = jacobian(nbhd) + n = normal(nbhd) + map(vals) do v + f = v.value + σ = v.curl + gu = +f[2] + gv = -f[1] + (value=cross(n, (gu*tu + gv*tv)/j), curl=σ/j) + end +end + + +function pushforwarddiv(vals, nbhd) + tu = tangents(nbhd, 1) + tv = tangents(nbhd, 2) + j = jacobian(nbhd) + n = normal(nbhd) + map(vals) do v + f = v.value + σ = v.curl + (value=(f[1]*tu + f[2]*tv)/j, curl=σ/j) + end +end \ No newline at end of file diff --git a/src/bases/ncrossbdmspace.jl b/src/bases/ncrossbdmspace.jl new file mode 100644 index 00000000..77f5aba0 --- /dev/null +++ b/src/bases/ncrossbdmspace.jl @@ -0,0 +1,54 @@ +struct NCrossBDMBasis{T,M,P} <: Space{T} + geo::M + fns::Vector{Vector{Shape{T}}} + pos::Vector{P} +end + +NCrossBDMBasis(geo, fns) = NCrossBDMBasis(geo, fns, Vector{vertextype(geo)}(undef,length(fns))) + +refspace(s::NCrossBDMBasis{T}) where {T} = NCrossBDMRefSpace{T}() + +function ncrossbdm(mesh) + edges = skeleton(mesh, 1) + cps = cellpairs(mesh, edges, dropjunctionpair=true) + ids = findall(x -> x>0, cps[2,:]) + ncrossbdm(mesh, cps[:,ids]) +end + +function ncrossbdm(mesh, cellpairs::Array{Int,2}) + + @assert CompScienceMeshes.isoriented(mesh) "brezzidouglasmarini assumes mesh is oriented" + # @warn "brezzidouglasmarini(mesh, cellpairs) assumes mesh is oriented" + + @assert size(cellpairs,1) == 2 + + T = coordtype(mesh) + P = vertextype(mesh) + + S = Shape{T} + F = Vector{Shape{T}} + + nf = 2*size(cellpairs,2) + fns = Vector{F}(undef, nf) + pos = Vector{P}(undef, nf) + + for i in axes(cellpairs)[2] + c1, c2 = cellpairs[:,i] + cell1 = cells(mesh)[c1] + cell2 = cells(mesh)[c2] + e1, e2 = getcommonedge(cell1, cell2) + @assert e1*e2 < 0 + e1, e2 = abs(e1), abs(e2) + fns[2*(i-1)+1] = [ S(c1, 2*(e1-1)+1 ,+1.0), S(c2, 2*(e2-1)+2,-1.0)] + fns[2*(i-1)+2] = [ S(c1, 2*(e1-1)+2 ,+1.0), S(c2, 2*(e2-1)+1,-1.0)] + + v1 = cell1[mod1(e1+1,3)] + v2 = cell1[mod1(e1+2,3)] + edge = simplex(mesh.vertices[[v1,v2]]...) + cntr = cartesian(center(edge)) + pos[2*(i-1)+1] = cntr + pos[2*(i-1)+2] = cntr + end + + NCrossBDMBasis(mesh, fns, pos) +end \ No newline at end of file diff --git a/src/bases/nd2space.jl b/src/bases/nd2space.jl new file mode 100644 index 00000000..c44bc7f0 --- /dev/null +++ b/src/bases/nd2space.jl @@ -0,0 +1,90 @@ +mutable struct ND2Basis{T,M,P} <: Space{T} + geo::M + fns::Vector{Vector{Shape{T}}} + pos::Vector{P} +end + +ND2Basis(geo, fns) = ND2Basis(geo, fns, Vector{vertextype(geo)}(undef,length(fns))) + +refspace(space::ND2Basis{T}) where T = ND2RefSpace{T}() + +""" + nedelec2(mesh, edges) + +Constructs the 2nd degree Nedelec basis of the first kind i.e. H(curl). + +Returns an object of type 'ND2Basis'. + +""" +function nedelec2(surface, edges=skeleton(surface,1)) + + T = coordtype(surface) + P = vertextype(surface) + num_edges = numcells(edges) + + C = connectivity(edges, surface, identity) + rows = rowvals(C) + vals = nonzeros(C) + + Cells = cells(surface) + num_cells = size(Cells,1) + + fns = Vector{Vector{Shape{T}}}(undef,2*(num_edges+num_cells)) + pos = Vector{P}(undef,2*(num_edges+num_cells)) + + chooseref1(x) = if x<0 return 0 else return -1 end + chooseref2(x) = if x<0 return -1 else return 0 end + for (i,edge) in enumerate(edges) + + fns[2*i-1] = Vector{Shape{T}}() + fns[2*i] = Vector{Shape{T}}() + pos[2*i-1] = cartesian(center(chart(edges,edge))) + pos[2*i] = cartesian(center(chart(edges,edge))) + + sgn = 1.0 + + for k in nzrange(C,i) + + j = rows[k] # j is the index of a cell adjacent to edge + s = vals[k] # s contains the oriented (signed) local index of edge[i] in cell[j] + + # i == 3 && @show s + push!(fns[2*i-1], Shape{T}(j, 2*abs(s)+chooseref1(sign(s)), T(sgn))) + sgn *= -1 + end + + sgn = 1.0 + + for k in nzrange(C,i) + + j = rows[k] # j is the index of a cell adjacent to edge + s = vals[k] # s contains the oriented (signed) local index of edge[i] in cell[j] + + # i == 3 && @show s + push!(fns[2*i], Shape{T}(j, 2*abs(s)+chooseref2(sign(s)), T(sgn))) + sgn *= -1 + end + end + + for (i,cell) in enumerate(Cells) + fns[2*num_edges+2*i-1] = Vector{Shape{T}}() + fns[2*num_edges+2*i] = Vector{Shape{T}}() + push!(fns[2*num_edges+2*i-1], Shape{T}(i, 7, T(1.0))) + push!(fns[2*num_edges+2*i], Shape{T}(i, 8, T(1.0))) + ctr = cartesian(center(chart(surface,i))) + pos[2*num_edges+2*i-1] = ctr + pos[2*num_edges+2*i] = ctr + end + + ND2Basis(surface, fns, pos) +end + +function LinearAlgebra.cross(::NormalVector, s::ND2Basis) + # @assert CompScienceMeshes.isoriented(s.geo) + return raviartthomas2(s.geo) +end + +#= +function curl(space::ND2Basis) + divergence(n × space) +end =# diff --git a/src/bases/ndlccspace.jl b/src/bases/ndlccspace.jl index c9827458..aabea3e6 100644 --- a/src/bases/ndlccspace.jl +++ b/src/bases/ndlccspace.jl @@ -26,10 +26,10 @@ function nedelecc3d(mesh, edges) fns = Vector{Vector{Shape{T}}}(undef,num_edges) pos = Vector{P}(undef,num_edges) - for (i,edge) in enumerate(cells(edges)) + for (i,e) in enumerate(edges) fns[i] = Vector{Shape{T}}() - pos[i] = cartesian(center(chart(edges,edge))) + pos[i] = cartesian(center(chart(edges,e))) for k in nzrange(C,i) @@ -51,6 +51,13 @@ function nedelecc3d(mesh) nedelecc3d(mesh, edges) end + +function nedelec(mesh::CompScienceMeshes.AbstractMesh{U,4} where {U}, + edges=skeleton(mesh,1)) + + nedelecc3d(mesh, edges) +end + curl(space::NDLCCBasis, geo, fns) = NDLCDBasis(geo, fns, space.pos) ttrace(X::NDLCCBasis, geo, fns) = RTBasis(geo, fns, deepcopy(X.pos)) diff --git a/src/bases/ndlcdspace.jl b/src/bases/ndlcdspace.jl index 8f10daf7..11d43b81 100644 --- a/src/bases/ndlcdspace.jl +++ b/src/bases/ndlcdspace.jl @@ -20,7 +20,7 @@ function nedelecd3d(mesh, faces) fns = Vector{Vector{Shape{T}}}(undef,num_faces) pos = Vector{P}(undef,num_faces) - for (i,face) in enumerate(cells(faces)) + for (i,face) in enumerate(faces) fns[i] = Vector{Shape{T}}() pos[i] = cartesian(center(chart(faces,face))) @@ -41,5 +41,11 @@ function nedelecd3d(mesh) nedelecd3d(mesh, faces) end +function raviartthomas(m::CompScienceMeshes.AbstractMesh{U,4} where {U}, faces=skeleton(m,2)) + nedelecd3d(m, faces) +end + ntrace(X::NDLCDBasis, geo, fns) = LagrangeBasis{0,-1,1}(geo, fns, deepcopy(X.pos)) ttrace(X::NDLCDBasis, geo, fns) = NDBasis{}(geo, fns, deepcopy(X.pos)) + +divergence(space::NDLCDBasis, geo, fns) = LagrangeBasis{0,-1,1}(geo, fns, space.pos) diff --git a/src/bases/ndspace.jl b/src/bases/ndspace.jl index b0c83a88..0393e5bb 100644 --- a/src/bases/ndspace.jl +++ b/src/bases/ndspace.jl @@ -4,6 +4,8 @@ struct NDBasis{T,M,P} <: Space{T} pos::Vector{P} end +NDBasis(geo, fns) = NDBasis(geo, fns, Vector{vertextype(geo)}(undef,length(fns))) + refspace(s::NDBasis) = NDRefSpace{scalartype(s)}() @@ -20,7 +22,7 @@ function nedelec(surface, edges=skeleton(surface,1)) fns = Vector{Vector{Shape{T}}}(undef,num_edges) pos = Vector{P}(undef,num_edges) - for (i,edge) in enumerate(cells(edges)) + for (i,edge) in enumerate(edges) fns[i] = Vector{Shape{T}}() pos[i] = cartesian(center(chart(edges,edge))) @@ -39,6 +41,11 @@ function nedelec(surface, edges=skeleton(surface,1)) end function LinearAlgebra.cross(::NormalVector, s::NDBasis) - @assert CompScienceMeshes.isoriented(s.geo) + # @assert CompScienceMeshes.isoriented(s.geo) RTBasis(s.geo, s.fns, s.pos) end + + +function curl(space::NDBasis) + divergence(n × space) +end diff --git a/src/bases/restrict.jl b/src/bases/restrict.jl index d3079a5d..9334ff5a 100644 --- a/src/bases/restrict.jl +++ b/src/bases/restrict.jl @@ -27,3 +27,101 @@ function restrict(sp::Space, submesh::CompScienceMeshes.AbstractMesh) similar(sp, submesh, fns, sp.pos) end + + +""" + extend(submesh, parentmesh) + +Extend functions defined on a submesh to their extension by zero on +the parent mesh. +""" +function extend(space::S, submesh::CompScienceMeshes.SubMesh, supermesh) where {S <: Space} + + @assert CompScienceMeshes.parent(submesh) == supermesh + + fns = similar(space.fns) + for i in 1:numfunctions(space) + fn = space.fns[i] + fns[i] = [Shape(CompScienceMeshes.extend(submesh, shape.cellid), + shape.refid, shape.coeff) for shape in fn] + end + + return similar(space, supermesh, fns, space.pos) +end + + +function extend(space::S, supermesh) where {S <: Space} + extend(space, geometry(space), supermesh) +end + + +@testitem "extend by zero" begin + using CompScienceMeshes + + m1 = meshrectangle(1.0, 1.0, 1.0, 3) + m2 = m1 + + m, s = union([m1,m2], Val{:submeshes}) + + + X1 = raviartthomas(s[1]) + X = BEAST.extend(X1, m) + + @test numfunctions(X1) == 1 + @test numfunctions(X) == 1 + + Id = Identity() + + for i in 1:2 + G1 = assemble(Id, X1, X1) + G2 = assemble(Id, X, X) + @test G1 ≈ G2 + end +end + +function extend(space::F, submesh, supermesh) where {F <: Space} + + S = copy(transpose(CompScienceMeshes.embedding(submesh, supermesh))) + + R = rowvals(S) + V = nonzeros(S) + + fns = similar(space.fns) + for i in 1:numfunctions(space) + fn = space.fns[i] + fns[i] = map(fn) do sh + k = nzrange(S, sh.cellid)[1] + cellid = R[k] + sign = V[k] + @assert sign == +1 + Shape(cellid, sh.refid, sh.coeff) + end + end + + return similar(space, supermesh, fns, space.pos) +end + + +@testitem "extend by zero" begin + using CompScienceMeshes + + m1 = meshrectangle(1.0, 1.0, 1.0, 3) + m2 = CompScienceMeshes.translate(m1, point(1.0, 0.0, 0.0)) + + m = CompScienceMeshes.weld(m1, m2) + + + X1 = raviartthomas(m1) + X = BEAST.extend(X1, m) + + @test numfunctions(X1) == 1 + @test numfunctions(X) == 1 + + Id = Identity() + + for i in 1:2 + G1 = assemble(Id, X1, X1) + G2 = assemble(Id, X, X) + @test G1 ≈ G2 + end +end \ No newline at end of file diff --git a/src/bases/rt2space.jl b/src/bases/rt2space.jl new file mode 100644 index 00000000..b012237b --- /dev/null +++ b/src/bases/rt2space.jl @@ -0,0 +1,133 @@ + + +mutable struct RT2Basis{T,M,P} <: Space{T} + geo::M + fns::Vector{Vector{Shape{T}}} + pos::Vector{P} +end + +RT2Basis(geo, fns) = RT2Basis(geo, fns, Vector{vertextype(geo)}(undef,length(fns))) + +#= positions(rt) = rt.pos =# +refspace(space::RT2Basis{T}) where {T} = RT2RefSpace{T}() +subset(rt::RT2Basis,I) = RT2Basis(rt.geo, rt.fns[I], rt.pos[I]) + +#= mutable struct ValDiv end =# + + + +""" + raviartthomas2(mesh, cellpairs::Array{Int,2}) + +Constructs the RT2 basis on the input `mesh`. The i-th RT2 basis function will + represent a current distribution flowing from cell `cellpairs[1,i]` to + `cellpairs[2,i]` on the mesh. + +Returns an object of type `RT2Basis`, which comprises both the mesh and pairs of + Shape objects which corresponds to the cell pairs, containing the necsessary + coefficients and indices to compute the exact basis functions when required + by the solver. +""" +function raviartthomas2(mesh::CompScienceMeshes.AbstractMesh{U,D1,T}, cellpairs::Array{Int,2}) where {U,D1,T} + + # combine now the pairs of monopolar RWGs in div-conforming RWGs + numpairs = size(cellpairs,2) + Cells = cells(mesh) + numcells = size(Cells,1) + functions = Vector{Vector{Shape{T}}}(undef,2*(numpairs+numcells)) + positions = Vector{vertextype(mesh)}(undef,2*(numpairs+numcells)) + chooseref1(x) = if x<0 return 0 else return -1 end + chooseref2(x) = if x<0 return -1 else return 0 end + for i in 1:numpairs + if cellpairs[2,i] > 0 + c1 = cellpairs[1,i]; # cell1 = Cells[c1] #mesh.faces[c1] + c2 = cellpairs[2,i]; # cell2 = Cells[c2] #mesh.faces[c2] + + cell1 = CompScienceMeshes.indices(mesh, c1) + cell2 = CompScienceMeshes.indices(mesh, c2) + + e1, e2 = getcommonedge(cell1, cell2) + functions[2*i-1] = [ + Shape{T}(c1, 2*abs(e1)+chooseref1(sign(e1)), T(+1.0)), + Shape{T}(c2, 2*abs(e2)+chooseref1(sign(e2)), T(-1.0))] + functions[2*i] = [ + Shape{T}(c1, 2*abs(e1)+chooseref2(sign(e1)), T(+1.0)), + Shape{T}(c2, 2*abs(e2)+chooseref2(sign(e2)), T(-1.0))] + isct = intersect(cell1, cell2) + @assert length(isct) == 2 + @assert !(cell1[abs(e1)] in isct) + @assert !(cell2[abs(e2)] in isct) + + ctr1 = cartesian(center(chart(mesh, c1))) + ctr2 = cartesian(center(chart(mesh, c2))) + positions[2*i-1] = (ctr1 + ctr2) / 2 + positions[2*i] = (ctr1 + ctr2) / 2 + else + c1 = cellpairs[1,i] + e1 = cellpairs[2,i] + functions[i] = [ + Shape(c1, abs(e1), T(+1.0))] + positions[i] = cartesian(center(chart(mesh, c1))) + end + end + + for (i,cell) in enumerate(Cells) + functions[2*numpairs+2*i-1] = [ + Shape{T}(i, 7, T(+1.0))] + + functions[2*numpairs+2*i] = [ + Shape{T}(i, 8, T(+1.0))] + + ctr1 = cartesian(center(chart(mesh, i))) + positions[2*numpairs+2*i-1] = ctr1 + positions[2*numpairs+2*i] = ctr1 + end + + geo = mesh + RT2Basis(geo, functions, positions) +end + + +function raviartthomas2(mesh, edges::CompScienceMeshes.AbstractMesh{U,2} where {U}) + cps = CompScienceMeshes.cellpairs(mesh, edges) + # ids = findall(x -> x>0, cps[2,:]) + raviartthomas2(mesh, cps) +end + +function raviartthomas2(mesh::CompScienceMeshes.AbstractMesh{U,3} where {U}) + bnd = boundary(mesh) + edges = submesh(!in(bnd), skeleton(mesh,1)) + return raviartthomas2(mesh, edges) +end + + +""" + raviartthomas2(mesh) + +Conducts pre-processing on the input `mesh` by extracting the cell edges, cell pairs + and indices required to construct the RT2 basis on the `mesh`. + +Calls raviartthomas2(mesh::Mesh, cellpairs::Array{Int,2}), which constructs + the RT2 basis on the `mesh`, using the cell pairs identified. + +Returns the RT2 basis object. +""" +function raviartthomas2(mesh; sort=:spacefillingcurve) + edges = skeleton(mesh, 1; sort) + cps = cellpairs(mesh, edges, dropjunctionpair=true) + ids = findall(x -> x>0, cps[2,:]) + raviartthomas2(mesh, cps[:,ids]) +end + +#= divergence(X::RT2Basis, geo, fns) = LagrangeBasis{0,-1,1}(geo, fns, deepcopy(positions(X))) +ntrace(X::RT2Basis, geo, fns) = LagrangeBasis{0,-1,1}(geo, fns, deepcopy(positions(X))) =# + + +#= function LinearAlgebra.cross(::NormalVector, s::RT2Basis) + @assert CompScienceMeshes.isoriented(s.geo) + fns = similar(s.fns) + for (i,fn) in pairs(s.fns) + fns[i] = [Shape(sh.cellid, sh.refid, -sh.coeff) for sh in fn] + end + ND2Basis(s.geo, fns, s.pos) +end =# diff --git a/src/bases/rtqspace.jl b/src/bases/rtqspace.jl new file mode 100644 index 00000000..ce0ad06c --- /dev/null +++ b/src/bases/rtqspace.jl @@ -0,0 +1,105 @@ +struct RTQSpace{T,M,P} <: Space{T} + geo::M + fns::Vector{Vector{Shape{T}}} + pos::Vector{P} +end + +# RTQSpace(g::M, fns::Vector{}) + +function positions(s::RTQSpace) s.pos end +function refspace(s::RTQSpace{T}) where {T} RTQuadRefSpace{T}() end +function subset(rt::RTQSpace,I) RTQSpace(rt.geo, rt.fns[I], rt.pos[I]) end + +function raviartthomas( + mesh::CompScienceMeshes.QuadMesh{T}, + edges::CompScienceMeshes.AbstractMesh{3,2,T}, + connectivity, orientations) where {T<:Any} + + fns = Vector{Vector{Shape{T}}}(undef, length(edges)) + pos = Vector{SVector{3,T}}(undef, length(edges)) + + rows = rowvals(connectivity) + vals = nonzeros(connectivity) + for (i,edge) in enumerate(edges) + σ = orientations[i] + fn = map(zip(nzrange(connectivity, i),(σ,-σ))) do (j, α) + Shape{T}(rows[j], abs(vals[j]), α) + end + fns[i] = fn + pos[i] = cartesian(CompScienceMeshes.center(chart(edges, edge))) + end + + return RTQSpace(mesh, fns, pos) +end + +function raviartthomas( + mesh::CompScienceMeshes.QuadMesh{T}, + edges::CompScienceMeshes.AbstractMesh{3,2,T}, + orientations::Vector{Bool}) where {T<:Any} + + conn = connectivity(edges, mesh) + return raviartthomas(mesh, edges, conn, orientations) +end + +function raviartthomas( + mesh::CompScienceMeshes.QuadMesh{T}, + edges::CompScienceMeshes.AbstractMesh{3,2,T}) where {T} + + c = connectivity(edges, mesh, identity) + o = ones(length(edges)) + + vals = nonzeros(c) + rows = rowvals(c) + for i in axes(c,2) + k = first(nzrange(c,i)) + vals[k] < 0 && (o[i] = -1) + end + + return raviartthomas(mesh, edges, c, o) +end + +function raviartthomas(mesh::CompScienceMeshes.QuadMesh{T}) where {T} + edges = skeleton(mesh,1) + bnd = boundary(mesh) + edges_int = submesh(!in(bnd), edges) + raviartthomas(mesh, edges_int) +end + +@testitem "RTQSpace construction" begin + using CompScienceMeshes + + m = CompScienceMeshes.meshrectangle(2.0, 2.0, 1.0; element=:quadrilateral) + edges = skeleton(m, 1) + edges_bnd = boundary(m) + # @show length(edges_bnd) + @test length(edges_bnd) == 8 + pred = !in(edges_bnd) + edges_int = submesh(pred, edges) + + c = CompScienceMeshes.connectivity(edges_int, m, identity) + @test size(c) == (length(m), length(edges_int)) + o = ones(length(edges_int)) + s = raviartthomas(m, edges_int, c, o) + @test numfunctions(s) == 4 +end + +@testitem "RTQSpace assembly data" begin + using CompScienceMeshes + m = CompScienceMeshes.meshrectangle(2.0, 2.0, 1.0; element=:quadrilateral) + edges = skeleton(m, 1) + edges_bnd = boundary(m) + pred = !in(edges_bnd) + edges_int = submesh(pred, edges) + c = CompScienceMeshes.connectivity(edges_int, m, identity) + o = ones(length(edges_int)) + s = raviartthomas(m, edges_int, c, o) + + num_cells = numcells(m) + num_bfs = numfunctions(s) + r = refspace(s) + dom = domain(chart(m, first(m))) + num_refs = numfunctions(r, dom) + celltonum = BEAST.make_celltonum(num_cells, num_refs, num_bfs, s) + + els, ad, a2g = BEAST.assemblydata(s) +end \ No newline at end of file diff --git a/src/bases/rtspace.jl b/src/bases/rtspace.jl index 5ab29623..c9494df9 100644 --- a/src/bases/rtspace.jl +++ b/src/bases/rtspace.jl @@ -7,6 +7,9 @@ mutable struct RTBasis{T,M,P} <: Space{T} end RTBasis(geo, fns) = RTBasis(geo, fns, Vector{vertextype(geo)}(undef,length(fns))) +Base.similar(space::RTBasis{T,M,P} where {T,M,P}, geo, fns, pos) = RTBasis(geo, fns, pos) + + positions(rt) = rt.pos refspace(space::RTBasis{T}) where {T} = RTRefSpace{T}() @@ -29,35 +32,39 @@ Returns an object of type `RTBasis`, which comprises both the mesh and pairs of coefficients and indices to compute the exact basis functions when required by the solver. """ -function raviartthomas(mesh, cellpairs::Array{Int,2}) +function raviartthomas(mesh::CompScienceMeshes.AbstractMesh{U,D1,T}, cellpairs::Array{Int,2}) where {U,D1,T} # combine now the pairs of monopolar RWGs in div-conforming RWGs numpairs = size(cellpairs,2) - functions = Vector{Vector{Shape{Float64}}}(undef,numpairs) + functions = Vector{Vector{Shape{T}}}(undef,numpairs) positions = Vector{vertextype(mesh)}(undef,numpairs) - Cells = cells(mesh) + # Cells = cells(mesh) for i in 1:numpairs if cellpairs[2,i] > 0 - c1 = cellpairs[1,i]; cell1 = Cells[c1] #mesh.faces[c1] - c2 = cellpairs[2,i]; cell2 = Cells[c2] #mesh.faces[c2] + c1 = cellpairs[1,i]; # cell1 = Cells[c1] #mesh.faces[c1] + c2 = cellpairs[2,i]; # cell2 = Cells[c2] #mesh.faces[c2] + + cell1 = CompScienceMeshes.indices(mesh, c1) + cell2 = CompScienceMeshes.indices(mesh, c2) + e1, e2 = getcommonedge(cell1, cell2) functions[i] = [ - Shape(c1, abs(e1), +1.0), - Shape(c2, abs(e2), -1.0)] + Shape{T}(c1, abs(e1), T(+1.0)), + Shape{T}(c2, abs(e2), T(-1.0))] isct = intersect(cell1, cell2) @assert length(isct) == 2 @assert !(cell1[abs(e1)] in isct) @assert !(cell2[abs(e2)] in isct) - ctr1 = cartesian(center(chart(mesh, cell1))) - ctr2 = cartesian(center(chart(mesh, cell2))) + ctr1 = cartesian(center(chart(mesh, c1))) + ctr2 = cartesian(center(chart(mesh, c2))) positions[i] = (ctr1 + ctr2) / 2 else c1 = cellpairs[1,i] e1 = cellpairs[2,i] functions[i] = [ - Shape(c1, abs(e1), +1.0)] - positions[i] = cartesian(center(chart(mesh, Cells[c1]))) + Shape(c1, abs(e1), T(+1.0))] + positions[i] = cartesian(center(chart(mesh, c1))) end end @@ -66,6 +73,18 @@ function raviartthomas(mesh, cellpairs::Array{Int,2}) end +function raviartthomas(mesh, edges::CompScienceMeshes.AbstractMesh{U,2} where {U}) + cps = CompScienceMeshes.cellpairs(mesh, edges) + # ids = findall(x -> x>0, cps[2,:]) + raviartthomas(mesh, cps) +end + +function raviartthomas(mesh::CompScienceMeshes.AbstractMesh{U,3} where {U}) + bnd = boundary(mesh) + edges = submesh(!in(bnd), skeleton(mesh,1)) + return raviartthomas(mesh, edges) +end + """ raviartthomas(mesh) @@ -78,8 +97,8 @@ Calls raviartthomas(mesh::Mesh, cellpairs::Array{Int,2}), which constructs Returns the RT basis object. """ -function raviartthomas(mesh) - edges = skeleton(mesh, 1) +function raviartthomas(mesh; sort=:spacefillingcurve) + edges = skeleton(mesh, 1; sort) cps = cellpairs(mesh, edges, dropjunctionpair=true) ids = findall(x -> x>0, cps[2,:]) raviartthomas(mesh, cps[:,ids]) @@ -87,31 +106,30 @@ end -""" - raviartthomas(Γ, γ) +# """ +# raviartthomas(Γ; neumann) -Constructs the RT space relative to boundary `γ` of an open surface, only - selecting cell pairs whose common edge does not lie on `γ` . (This prevents - the calculation of physically-impossible surface currents, such as those - flowing 'off the edge' of a surface.) +# Constructs the RT space relative to boundary `neumann` of an open surface, only +# selecting cell pairs whose common edge does lie on `γ` . (This prevents +# the calculation of physically-impossible surface currents, such as those +# flowing 'off the edge' of a surface.) +# """ +# function raviartthomas(Γ; neumann) -Calls raviartthomas(Γ, duals) which constructs - the RT basis on the `mesh`, using the cell pairs identified. -""" -function raviartthomas(Γ, γ) +# γ = neumann - in_interior = interior_tpredicate(Γ) - on_junction = overlap_gpredicate(γ) +# in_interior = interior_tpredicate(Γ) +# on_junction = overlap_gpredicate(γ) - pred = c -> (in_interior(c) || on_junction(chart(Γ,c))) +# pred = c -> (in_interior(c) || on_junction(chart(Γ,c))) - edges = skeleton(pred, Γ, 1) - cps = cellpairs(Γ, edges, dropjunctionpair=true) +# edges = skeleton(pred, Γ, 1) +# cps = cellpairs(Γ, edges, dropjunctionpair=true) - raviartthomas(Γ, cps) -end +# raviartthomas(Γ, cps) +# end -raowiltonglisson = raviartthomas +# raowiltonglisson = raviartthomas """ @@ -123,11 +141,12 @@ function portcells(Γ, γ) in_interior = interior_tpredicate(Γ) overlaps = overlap_gpredicate(γ) - on_junction = c -> overlaps(simplex(vertices(Γ,c))) + on_junction = (m,c) -> overlaps(chart(m,c)) - pred = x -> (!in_interior(x) && on_junction(x)) #check only for exterior overlapping edges + pred = (m,x) -> (!in_interior(m,x) && on_junction(m,x)) #check only for exterior overlapping edges # - if γ is defined to overlap within the structure Γ, this isn't considered a port - - edges = skeleton(pred, Γ, 1) #Take only exterior edges overlapping γ segment + # edges = skeleton(pred, Γ, 1) #Take only exterior edges overlapping γ segment + edges = submesh(pred, skeleton(Γ,1)) cps = cellpairs(Γ, edges, dropjunctionpair=true) return cps end @@ -139,10 +158,11 @@ leaving or entering port defined by cellpairs cps. weight defines the total current over the port and its direction (+ve = out, -ve = in) """ function rt_cedge(cps::Array{Int,2}, weight) + T=typeof(weight) numpairs = size(cps,2) @assert numpairs > 0 weight = weight / numpairs #total current leaving and entering equal 1 - functions = Vector{Shape{Float64}}(undef,numpairs) #note: not a Vector{Vector} + functions = Vector{Shape{T}}(undef,numpairs) #note: not a Vector{Vector} for i in 1:numpairs c1 = cps[1,i] e1 = cps[2,i] @@ -161,10 +181,11 @@ weight defines the magnitude of individual current in and out the half triangles and it's polarity simply defines whether to start with in or out """ function rt_vedge(cps::Array{Int,2}, weight) + T=typeof(weight) numpairs = size(cps,2) @assert numpairs > 0 #adjacent cells are considered a pair, so we have one less numpairs - functions = Vector{Vector{Shape{Float64}}}(undef,numpairs - 1) + functions = Vector{Vector{Shape{T}}}(undef,numpairs - 1) for i in 1:numpairs if i < numpairs # stop when on last cellpair c1 = cps[1,i]; e1 = cps[2,i] @@ -204,14 +225,14 @@ function rt_ports(Γ, γ...) port1 = portcells(Γ, γ[i][1]) port2 = portcells(Γ, γ[i][2]) - ce1 = rt_cedge(port1, +1.0) - ce2 = rt_cedge(port2, -1.0) + ce1 = rt_cedge(port1, T(+1.0)) + ce2 = rt_cedge(port2, T(-1.0)) ffs = Vector{Shape{T}}(undef,length(ce1) + length(ce2)) ffs = [ce1;ce2] - ve1 = rt_vedge(port1, +1.0) - ve2 = rt_vedge(port2, -1.0) + ve1 = rt_vedge(port1, T(+1.0)) + ve2 = rt_vedge(port2, T(-1.0)) fns = [fns;[ffs];ve1;ve2] end @@ -240,3 +261,13 @@ end divergence(X::RTBasis, geo, fns) = LagrangeBasis{0,-1,1}(geo, fns, deepcopy(positions(X))) ntrace(X::RTBasis, geo, fns) = LagrangeBasis{0,-1,1}(geo, fns, deepcopy(positions(X))) + + +function LinearAlgebra.cross(::NormalVector, s::RTBasis) + @assert CompScienceMeshes.isoriented(s.geo) + fns = similar(s.fns) + for (i,fn) in pairs(s.fns) + fns[i] = [Shape(sh.cellid, sh.refid, -sh.coeff) for sh in fn] + end + NDBasis(s.geo, fns, s.pos) +end diff --git a/src/bases/rtxspace.jl b/src/bases/rtxspace.jl index 4440ffcb..77f728b1 100644 --- a/src/bases/rtxspace.jl +++ b/src/bases/rtxspace.jl @@ -17,7 +17,10 @@ function raviartthomas(mesh, ::Type{Continuity{:none}}) functions[3*(i-1)+2] = [S(i,2,+1.0)] functions[3*(i-1)+3] = [S(i,3,+1.0)] - ctr = sum(mesh.vertices[mesh.faces[i]])/3 + I = CompScienceMeshes.indices(mesh,i) + V = CompScienceMeshes.vertices(mesh,I) + ctr = sum(V)/3 + # ctr = sum(mesh.vertices[mesh.faces[i]])/3 positions[3*(i-1)+1] = ctr positions[3*(i-1)+2] = ctr positions[3*(i-1)+3] = ctr diff --git a/src/bases/stagedtimestep.jl b/src/bases/stagedtimestep.jl index 08215e37..5ad66006 100644 --- a/src/bases/stagedtimestep.jl +++ b/src/bases/stagedtimestep.jl @@ -2,19 +2,27 @@ """ - StagedTimeStep{T,N} + StagedTimeStep{T,N,NN} T: the value type of the basis function. N: the number of stages. -It corresponds to a time-space basis function where each time step has -intermediary stages given by the vertor c in a Butcher tableau (A,b,c) +NN: the number of stages squared +Each time step has intermediary stages given by the vertor c in a Butcher tableau (A,b,c) """ -struct StagedTimeStep{SB, T, N} - spatialBasis :: SB - c :: SVector{N,T} +struct StagedTimeStep{T, N, NN, I} Δt :: T - Nt :: Int + Nt :: I + c :: SVector{N,T} + A :: SArray{Tuple{N,N},T,2,NN} + b :: SVector{N,T} + zTransformedTermCount :: I + contourRadius :: T end -scalartype(sts :: StagedTimeStep{SB, T, N}) where {SB, T, N} = T; -temporalbasis(sts :: StagedTimeStep{SB, T, N}) where {SB, T, N} = timebasisdelta(sts.Δt, sts.Nt) +scalartype(sts :: StagedTimeStep{T, N, NN, I}) where {T, N, NN, I} = T +temporalbasis(sts :: StagedTimeStep{T, N, NN, I}) where {T, N, NN, I} = timebasisdelta(sts.Δt, sts.Nt) + +numfunctions(s::StagedTimeStep) = s.Nt + +numstages(s) = 1 +numstages(s::StagedTimeStep) = size(s.c,1) diff --git a/src/bases/subdbasis.jl b/src/bases/subdbasis.jl index 6c3cb8b3..9556cd47 100644 --- a/src/bases/subdbasis.jl +++ b/src/bases/subdbasis.jl @@ -14,7 +14,7 @@ function subset(s::S,I) where {S<:subdBasis} end -mutable struct subReferenceSpace{T,D} <: RefSpace{T,D} end +mutable struct subReferenceSpace{T,D} <: RefSpace{T} end refspace(s::subdBasis{T,M,P}) where {T,M,P} = subReferenceSpace{T,12}() # 12 only for regular case @@ -28,13 +28,13 @@ function (rs::subReferenceSpace)(nbhd) return s #,scurl end -function subdsurface(mesh) +function subdsurface(mesh::Mesh{U,D1,T}) where {U,D1,T} subd_mesh = GSubdMesh(mesh) vertices = mesh.vertices subd_elements = subd_mesh.elements nvertices = length(vertices) nelem = length(subd_elements) - funs=Array{Vector{BEAST.Shape{Float64}}}(undef,nvertices) + funs=Array{Vector{BEAST.Shape{T}}}(undef,nvertices) for i = 1 : nvertices funs[i] = [] end for ie = 1:nelem inodes = subd_elements[ie].RingNodes @@ -42,7 +42,7 @@ function subdsurface(mesh) for ib = 1:N nodeid = inodes[ib] coeff = 1.0 - ifun = BEAST.Shape{Float64}(ie,ib,coeff) + ifun = BEAST.Shape{T}(ie,ib,coeff) push!(funs[nodeid], ifun) end end diff --git a/src/bases/tensorbasis.jl b/src/bases/tensorbasis.jl index c19990f5..b8bce8ab 100644 --- a/src/bases/tensorbasis.jl +++ b/src/bases/tensorbasis.jl @@ -1,14 +1,40 @@ -mutable struct SpaceTimeBasis{S,T} +struct SpaceTimeBasis{S,T,U} <: Space{U} space::S time::T end +function SpaceTimeBasis(space, time) + S = typeof(space) + T = typeof(time) + + U = scalartype(space,time) + return SpaceTimeBasis{S,T,U}(space,time) +end + +spatialbasis(s) = s spatialbasis(s::SpaceTimeBasis) = s.space +function spatialbasis(s::DirectProductSpace) + r = [spatialbasis(ch) for ch in s.factors] + return DirectProductSpace(r) +end + temporalbasis(s::SpaceTimeBasis) = s.time ⊗(a, b) = SpaceTimeBasis(a,b) numfunctions(S::SpaceTimeBasis) = numfunctions(S.space) * numfunctions(S.time) scalartype(st::SpaceTimeBasis) = promote_type(scalartype(st.space), scalartype(st.time)) + +function tensordim(s::SpaceTimeBasis,i) + i == 1 && return numfunctions(s.space) + i == 2 && return numfunctions(s.time) + return 1 +end + +function tensordim(s, i) + i == 1 && return numfunctions(s) + return 1 +end + diff --git a/src/bases/timebasis.jl b/src/bases/timebasis.jl index 172c0630..4f7fd1ed 100644 --- a/src/bases/timebasis.jl +++ b/src/bases/timebasis.jl @@ -2,7 +2,7 @@ using Compat -mutable struct MonomialBasis{T,Degree,NF} <: RefSpace{T,NF} end +mutable struct MonomialBasis{T,Degree,NF} <: RefSpace{T} end valuetype(::MonomialBasis{T}) where {T} = T degree(::MonomialBasis{T,D}) where {T,D} = D @@ -66,7 +66,7 @@ mutable struct TimeBasisDelta{T} <: AbstractTimeBasisFunction numfunctions::Int end -mutable struct DiracBoundary{T} <: RefSpace{T,1} end +mutable struct DiracBoundary{T} <: RefSpace{T} end numfunctions(x::DiracBoundary) = 1 scalartype(x::TimeBasisDelta{T}) where {T} = T @@ -140,7 +140,7 @@ end Create a temporal basis based on shifted copies of the quadratic spline. The spline is the convolution of a cxd0 and a c0d1 basis function. """ -function timebasisspline2(dt, numfunctions, T::Type=Float64) +function timebasisspline2(dt, numfunctions, T::Type=typeof(dt)) i, z = one(T), zero(T) polys = SVector{4,Polynomial{3,T}}( Polynomial(SVector(i/2, i/dt, i/2/dt/dt)), @@ -159,11 +159,11 @@ function timebasisshiftedlagrange(dt, numfunctions, degree, T::Type=typeof(dt)) for k = 0:degree f = c for i in 1:k - f = (1/i) * f * (i - t) + f = T(1/i) * f * (i - t) end g = c for i in 1:(degree-k) - g = (1/i) * g * (i + t) + g = T.(1/i) * g * (i + t) end push!(polys, f*g) end diff --git a/src/bases/trace.jl b/src/bases/trace.jl index a7cd8308..89058e01 100644 --- a/src/bases/trace.jl +++ b/src/bases/trace.jl @@ -24,7 +24,7 @@ function ntrace(X::Space, γ) # on_target = overlap_gpredicate(γ) # ad = assemblydata(X) x = refspace(X) - E, ad = assemblydata(X) + E, ad, P = assemblydata(X) igeo = geometry(X) @assert dimension(γ) == dimension(igeo)-1 # Γ = geo @@ -33,11 +33,13 @@ function ntrace(X::Space, γ) ogeo = boundary(igeo) on_target = overlap_gpredicate(γ) - ogeo = submesh(ogeo) do face - on_target(chart(ogeo,face)) + ogeo = submesh(ogeo) do m,f + ch = chart(m,f) + on_target(ch) end - D = copy(transpose(connectivity(ogeo, igeo, abs))) + # D = copy(transpose(connectivity(ogeo, igeo, abs))) + D = connectivity(igeo, ogeo, abs) rows, vals = rowvals(D), nonzeros(D) T = scalartype(X) @@ -53,12 +55,12 @@ function ntrace(X::Space, γ) # @assert norm(Q,Inf) != 0 r = 0 - for k in nzrange(D,p) + for k in nzrange(D,P[p]) vals[k] == q && (r = rows[k]; break) end @assert r != 0 - fc1 = chart(ogeo, cells(ogeo)[r]) + fc1 = chart(ogeo, r) Q = ntrace(x, el, q, fc1) for i in 1:size(Q,1) @@ -115,8 +117,8 @@ function strace(X::Space, γ, dim1::Type{Val{2}}) @assert e != 0 # make sure we use fc as oriented in Σ - cell = Σ.faces[e] - fc = chart(Σ, cell) + cell = CompScienceMeshes.indices(Σ,e) #Σ.faces[e] + fc = chart(Σ, e) on_target(fc) || continue Q = strace(x,el,q,fc) @@ -137,6 +139,60 @@ function strace(X::Space, γ, dim1::Type{Val{2}}) strace(X, Σ, fns) end +function strace(X::Space, γ, dim1::Type{Val{3}}) + + x = refspace(X) + E, ad, P = assemblydata(X) + igeo = geometry(X) + + @assert dimension(γ) == dimension(igeo)-1 + + ogeo = boundary(igeo) + on_target = overlap_gpredicate(γ) + ogeo = submesh(ogeo) do m,f + ch = chart(m,f) + on_target(ch) + end + + D = connectivity(igeo, ogeo, abs) + rows, vals = rowvals(D), nonzeros(D) + + T = scalartype(X) + S = Shape{T} + fns = [Vector{S}() for i in 1:numfunctions(X)] + + for (p,el) in enumerate(E) + + for (q,fc) in enumerate(faces(el)) + on_target(fc) || continue + + r = 0 + for k in nzrange(D,P[p]) + vals[k] == q && (r = rows[k]; break) + end + @assert r != 0 + + fc1 = chart(ogeo, r) + Q = strace(x, el, q, fc1) + + for i in 1:size(Q,1) + for j in 1:size(Q,2) + for (m,a) in ad[p,j] + v = a*Q[i,j] + isapprox(v,0,atol=sqrt(eps(T))) && continue + push!(fns[m], Shape(r, i, v)) + end + end + end + + end + + end + + strace(X, ogeo, fns) + +end + """ currently not working! """ @@ -200,14 +256,13 @@ function ttrace(X::Space, γ) igeo = geometry(X) @assert dimension(γ) == dimension(igeo)-1 - # ogeo = skeleton(igeo, dimension(γ)) ogeo = boundary(igeo) on_target = overlap_gpredicate(γ) - ogeo = submesh(ogeo) do face - on_target(chart(ogeo, face)) + ogeo = submesh(ogeo) do m,face + ch = chart(m,face) + on_target(ch) end - # D = copy(transpose(connectivity(ogeo, igeo, abs))) D = connectivity(igeo, ogeo, abs) rows, vals = rowvals(D), nonzeros(D) @@ -226,7 +281,7 @@ function ttrace(X::Space, γ) end @assert r != 0 - fc1 = chart(ogeo, cells(ogeo)[r]) + fc1 = chart(ogeo, r) @assert cartesian(center(fc)) ≈ cartesian(center(fc1)) Q = ttrace(x, el, q, fc1) diff --git a/src/decoupled/dpops.jl b/src/decoupled/dpops.jl index 3bcbd068..502b3e6c 100644 --- a/src/decoupled/dpops.jl +++ b/src/decoupled/dpops.jl @@ -1,4 +1,4 @@ -struct CurlSingleLayerDP3D{T,U} <: MaxwellOperator3D +struct CurlSingleLayerDP3D{T,U} <: MaxwellOperator3D{U,T} gamma::T alpha::U end @@ -17,13 +17,14 @@ function integrand(op::CurlSingleLayerDP3D, kernel_vals, return -α * dot(nx × gx, ∇G * fy) end -function momintegrals!(op::CurlSingleLayerDP3D, - test_local_space::RTRefSpace, trial_local_space::LagrangeRefSpace, - test_triangular_element, trial_triangular_element, out, strat::SauterSchwabStrategy) +function momintegrals!(out, op::CurlSingleLayerDP3D, + test_local_space::RTRefSpace, tptr, test_triangular_element, + trial_local_space::LagrangeRefSpace, bptr, trial_triangular_element, + qrule::SauterSchwabStrategy) I, J, K, L = SauterSchwabQuadrature.reorder( test_triangular_element.vertices, - trial_triangular_element.vertices, strat) + trial_triangular_element.vertices, qrule) test_triangular_element = simplex(test_triangular_element.vertices[I]...) trial_triangular_element = simplex(trial_triangular_element.vertices[J]...) @@ -55,7 +56,7 @@ function momintegrals!(op::CurlSingleLayerDP3D, return @SMatrix[dot(f[i].value × nx, R[j]) for i in 1:3, j in 1:3] end - Q = sauterschwab_parameterized(igd, strat) + Q = sauterschwab_parameterized(igd, qrule) for j ∈ 1:3 for i ∈ 1:3 out[i,j] += Q[K[i],L[j]] diff --git a/src/dyadicop.jl b/src/dyadicop.jl new file mode 100644 index 00000000..5306d397 --- /dev/null +++ b/src/dyadicop.jl @@ -0,0 +1,46 @@ +struct DyadicOp{T,F,G} <: Operator + α::T + f::F + g::G +end + +function DyadicOp(f::F, g::G) where {F,G} + T = scalartype(f,g) + DyadicOp{T,F,G}(one(T), f, g) +end + +Base.:*(b::Number, op::DyadicOp) = DyadicOp(b * op.α, op.f, op.g) + +scalartype(op::DyadicOp) = promote_type(scalartype(op.α), scalartype(op.f), scalartype(op.g)) +defaultquadstrat(op::DyadicOp, testspace, refspace) = nothing + + +function allocatestorage(op::DyadicOp, test_functions, trial_functions, + storage_trait, longdelays_trait) + + T = scalartype(op, test_functions, trial_functions) + M = length(test_functions) + N = length(trial_functions) + + u = zeros(T,M) + v = zeros(T,N) + + A = BEAST.Rank1Map{T}(u,v) + store() = A + freeze() = A + + return freeze, store +end + + +function assemble!(biop::DyadicOp, tfs::Space, bfs::Space, store, + threading::Type{BEAST.Threading{:multi}}; + quadstrat=defaultquadstrat(biop, tfs, bfs)) + + u = assemble(biop.f, tfs) + v = assemble(biop.g, bfs) + + A = store() + A.u .= biop.α * u + A.v .= adjoint.(v) +end \ No newline at end of file diff --git a/src/excitation.jl b/src/excitation.jl index 6c139a87..5051517e 100644 --- a/src/excitation.jl +++ b/src/excitation.jl @@ -1,9 +1,12 @@ -abstract type Functional end +abstract type Functional{T} end +scalartype(::Functional{T}) where {T} = T -quaddata(fn::Functional, refs, cells) = quadpoints(refs, cells, [5]) -quadrule(fn::Functional, refs, p, cell, qd) = qd[1,p] +defaultquadstrat(fn::Functional, basis) = SingleNumQStrat(8) +quaddata(fn::Functional, refs, cells, qs::SingleNumQStrat) = + quadpoints(refs, cells, [qs.quad_rule]) +quadrule(fn::Functional, refs, p, cell, qd, qs::SingleNumQStrat) = qd[1,p] """ assemble(fn, tfs) @@ -11,37 +14,47 @@ quadrule(fn::Functional, refs, p, cell, qd) = qd[1,p] Assemble the vector of test coefficients corresponding to functional `fn` and test functions `tfs`. """ -function assemble(field::Functional, tfs) +function assemble(field::Functional, tfs; + quadstrat=defaultquadstrat(field, tfs)) - b = zeros(ComplexF64, numfunctions(tfs)) + R = scalartype(tfs) + b = zeros(Complex{R}, numfunctions(tfs)) store(v,m) = (b[m] += v) - assemble!(field, tfs, store) + assemble!(field, tfs, store; quadstrat) return b end -function assemble!(field::Functional, tfs::DirectProductSpace, store) +function assemble!(field::Functional, tfs::DirectProductSpace, store; + quadstrat=defaultquadstrat(field, tfs)) + I = Int[0] for s in tfs.factors push!(I, last(I) + numfunctions(s)) end for (i,s) in enumerate(tfs.factors) store1(v,m) = store(v, m + I[i]) - assemble!(field, s, store1) + assemble!(field, s, store1; quadstrat) end end -function assemble!(field::Functional, tfs::Space, store) +function assemble!(field::Functional, tfs::Space, store; + quadstrat=defaultquadstrat(field, tfs)) + qs = quadstrat(field, tfs) tels, tad = assemblydata(tfs) trefs = refspace(tfs) - qd = quaddata(field, trefs, tels) + qd = quaddata(field, trefs, tels, qs) + + tgeo = geometry(tfs) + tdom = domain(chart(tgeo, first(tgeo))) + num_trefs = numfunctions(trefs, tdom) for (t, tcell) in enumerate(tels) # compute the testing with the reference elements - qr = quadrule(field, trefs, t, tcell, qd) + qr = quadrule(field, trefs, t, tcell, qd, qs) blocal = celltestvalues(trefs, tcell, field, qr) - for i in 1 : numfunctions(trefs) + for i in 1 : num_trefs for (m,a) in tad[t,i] store(a*blocal[i], m) end @@ -51,7 +64,8 @@ function assemble!(field::Functional, tfs::Space, store) end -function assemble!(field::Functional, tfs::subdBasis, store) +function assemble!(field::Functional, tfs::subdBasis, store; + quadstrat=defaultquadstrat(field, tfs)) tels, tad = assemblydata(tfs) @@ -61,8 +75,8 @@ function assemble!(field::Functional, tfs::subdBasis, store) for (t, tcell) in enumerate(tels) # compute the testing with the reference elements - qr = quadrule(field, trefs, t, tcell, qd) - blocal = celltestvalues(trefs, tcell, field, qr) + qr = quadrule(field, trefs, t, tcell, qd, quadstrat) + blocal = celltestvalues(trefs, tcell, field, qr, quadstrat) for i in 1 : length(tad[t]) for (m,a) in tad[t][i] @@ -74,10 +88,10 @@ function assemble!(field::Functional, tfs::subdBasis, store) end -function celltestvalues(tshs, tcell, field, qr) +function celltestvalues(tshs::RefSpace{T}, tcell, field, qr) where {T} - num_tshs = numfunctions(tshs) - interactions = zeros(ComplexF64, num_tshs) + num_tshs = numfunctions(tshs, domain(tcell)) + interactions = zeros(Complex{T}, num_tshs) num_oqp = length(qr) @@ -100,11 +114,11 @@ function celltestvalues(tshs, tcell, field, qr) return interactions end -function celltestvalues(tshs::subReferenceSpace, tcell, field, qr) +function celltestvalues(tshs::subReferenceSpace{T,D}, tcell, field, qr) where {T,D} num_oqp = length(qr) num_tshs = length(qr[1].value[1]) - interactions = (ComplexF64, num_tshs) + interactions = (Complex{T}, num_tshs) for p in 1 : num_oqp mp = qr[p].point @@ -113,7 +127,7 @@ function celltestvalues(tshs::subReferenceSpace, tcell, field, qr) fval = field(mp) tvals = qr[p].value - interactions = zeros(ComplexF64, num_tshs) + interactions = zeros(Complex{T}, num_tshs) for m in 1 : num_tshs tval = tvals[1][m] @@ -124,3 +138,64 @@ function celltestvalues(tshs::subReferenceSpace, tcell, field, qr) return interactions end + + + +### Framework allowing to create Functional from Field by taking the trace +mutable struct CrossTraceMW{T,F} <: Functional{T} + field::F +end + +mutable struct TangTraceMW{T,F} <: Functional{T} + field::F +end + +CrossTraceMW(f::F) where {F} = CrossTraceMW{scalartype(f), F}(f) +CrossTraceMW{T}(f::F) where {T,F} = CrossTraceMW{T,F}(f) + +TangTraceMW(f::F) where {F} = TangTraceMW{scalartype(f), F}(f) +TangTraceMW{T}(f::F) where {T,F} = TangTraceMW{T,F}(f) + +# scalartype(x::CrossTraceMW) = scalartype(x.field) +# scalartype(::CrossTraceMW{T}) where {T} = T +# scalartype(::TangTraceMW{T}) where {T} = T + + +# scalartype(t::TangTraceMW) = scalartype(t.field) + +cross(::NormalVector, p) = CrossTraceMW(p) +cross(t::CrossTraceMW, ::NormalVector) = TangTraceMW(t.field) + +function (ϕ::CrossTraceMW)(p) + F = ϕ.field + x = cartesian(p) + n = normal(p) + return n × F(x) +end + +function (ϕ::TangTraceMW)(p) + F = ϕ.field + x = cartesian(p) + n = normal(p) + return (n × F(x)) × n +end + + + +struct NDotTrace{T,F} <: Functional{T} + field::F +end + + +NDotTrace(f::F) where {F} = NDotTrace{scalartype(f), F}(f) +NDotTrace{T}(f::F) where {T,F} = NDotTrace{T,F}(f) +LinearAlgebra.dot(::NormalVector, f) = NDotTrace(f) + +# scalartype(s::NDotTrace{T}) where {T} = T + +(ϕ::NDotTrace)(p) = dot(normal(p), ϕ.field(cartesian(p))) + +integrand(::Any, testvals, fieldval) = dot(testvals[1], fieldval) +# integrand(::TangTraceMW, gx, ϕx) = gx[1] ⋅ ϕx +# integrand(::CrossTraceMW, test_vals, field_val) = test_vals[1] ⋅ field_val +# integrand(::NDotTrace, g, ϕ) = dot(g.value, ϕ) \ No newline at end of file diff --git a/src/gridfunction.jl b/src/gridfunction.jl new file mode 100644 index 00000000..b42b3f7f --- /dev/null +++ b/src/gridfunction.jl @@ -0,0 +1,362 @@ +abstract type AbstractMeshFunction end + +Base.:*(b::Number, fun::AbstractMeshFunction) = LinearCombinationOfAbstractMeshFunctions([b], [fun]) + +function Base.:+(fun1::AbstractMeshFunction, fun2::AbstractMeshFunction) + if geometry(fun1) != geometry(fun2) + error("Functions must be defined over the same geometry.") + end + return LinearCombinationOfAbstractMeshFunctions([1.0; 1.0], [fun1; fun2]) +end + +function Base.:-(fun1::AbstractMeshFunction, fun2::AbstractMeshFunction) + if geometry(fun1) != geometry(fun2) + error("Functions must be defined over the same geometry.") + end + return LinearCombinationOfAbstractMeshFunctions([1.0; -1.0], [fun1; fun2]) +end + +struct GlobalFunction{F,X,I} <: AbstractMeshFunction + fun::F + geo::X + cells::Vector{I} +end + +defaultquadstrat(field::GlobalFunction) = SingleNumQStrat(7) +quaddata(fn::GlobalFunction, simplexes, qs::SingleNumQStrat) = quadpoints(fn, simplexes, [qs.quad_rule]) +quadrule(fn::GlobalFunction, refs, p, cell, qd, qs::SingleNumQStrat) = qd[1,p] +geometry(fn::GlobalFunction) = fn.geo + +function (f::GlobalFunction{F, X, I})(mp::CompScienceMeshes.MeshPointNM) where {F, X, I} + + # Predict return type + #PT = promote_type(coordtype(chart(mp))) + + return f.fun(cartesian(mp)) +end + +function (f::GlobalFunction{F, X, I})(x) where {F, X, I} + return f.fun(x) +end + +struct FEMFunction{T, X} <: AbstractMeshFunction + coeffs::AbstractVector{T} + space::X +end + +function Base.getindex(u::BEAST.FEMFunction, b::BEAST.Variational.HilbertVector) + return BEAST.FEMFunction(u.coeffs[b], u.space[b.idx]) +end + +function Base.:-(u::BEAST.FEMFunction) + return BEAST.FEMFunction(-u.coeffs, u.space) +end + +@testitem "FEMFunction getindex" begin + using CompScienceMeshes + + Γ = CompScienceMeshes.meshrectangle(1.0, 1.0, 0.5) + X1 = BEAST.raviartthomas(Γ) + + X = X1 × X1 + ax = BEAST.NestedUnitRanges.nestedrange(X, 1, numfunctions) + + coeffs = rand(numfunctions(X)) + coeffs = BEAST.BlockArrays.BlockedVector(coeffs, (ax,)) + + u = BEAST.FEMFunction(coeffs, X) + + @hilbertspace m[1:2] + u1 = u[m[1]] + u2 = u[m[2]] + u2 = -u1 + + @test u1 isa BEAST.FEMFunction + @test u2 isa BEAST.FEMFunction + + @test u1.coeffs == -u2.coeffs +end + +defaultquadstrat(field::FEMFunction) = SingleNumQStrat(7) +returntype(f::FEMFunction{T,X}) where {T,X} = T + +quaddata(fn::FEMFunction, refs, cells, qs::SingleNumQStrat) = quadpoints(refs, cells, [qs.quad_rule]) +quadrule(fn::FEMFunction, refs, p, cell, qd, qs::SingleNumQStrat) = qd[1,p] +geometry(fn::FEMFunction) = geometry(fn.space) + +mutable struct LinearCombinationOfAbstractMeshFunctions{T} + coeffs::Vector{T} + gfs::Vector +end + +geometry(lincombfun::LinearCombinationOfAbstractMeshFunctions) = geometry(first(lincombfun.gfs)) + +Base.length(lc::LinearCombinationOfAbstractMeshFunctions) = length(lc.coeffs) + +Base.:*(b::Number, lcb::LinearCombinationOfAbstractMeshFunctions) = + LinearCombinationOfAbstractMeshFunctions(b .* lcb.coeffs, lcb.gfs) + +function Base.:+(lcb::LinearCombinationOfAbstractMeshFunctions{T}, fun::AbstractMeshFunction) where {T} + + if geometry(lcb) != geometry(fun) + error("Functions must be defined over the same geometry.") + end + + return LinearCombinationOfAbstractMeshFunctions([lcb.coeffs[:]; T(1)], [lcb.gfs[:]; fun]) +end + +function Base.:-(lcb::LinearCombinationOfAbstractMeshFunctions{T}, fun::AbstractMeshFunction) where {T} + + if geometry(lcb) != geometry(fun) + error("Functions must be defined over the same geometry.") + end + + return LinearCombinationOfAbstractMeshFunctions([lcb.coeffs[:]; -T(1)], [lcb.gfs[:]; fun]) +end + +Base.:+(fun::AbstractMeshFunction, lcb::LinearCombinationOfAbstractMeshFunctions) = lcb + fun + +Base.:-(fun::AbstractMeshFunction, lcb::LinearCombinationOfAbstractMeshFunctions) = lcb - fun + +function Base.:+(lcb1::LinearCombinationOfAbstractMeshFunctions, lcb2::LinearCombinationOfAbstractMeshFunctions) + + if geometry(lcb1) != geometry(lcb2) + error("Functions must be defined over the same geometry.") + end + + return LinearCombinationOfAbstractMeshFunctions([lcb1.coeffs[:]; lcb2.coeffs[:]], [lcb1.gfs[:]; lcb2.gfs[:]]) +end + +function Base.:-(lcb1::LinearCombinationOfAbstractMeshFunctions{T1}, lcb2::LinearCombinationOfAbstractMeshFunctions{T2}) where {T1, T2} + + if geometry(lcb1) != geometry(lcb2) + error("Functions must be defined over the same geometry.") + end + + return LinearCombinationOfAbstractMeshFunctions([lcb1.coeffs[:]; -T2(1) .* lcb2.coeffs[:]], [lcb1.gfs[:]; lcb2.gfs[:]]) +end + +Base.iterate(lc::LinearCombinationOfAbstractMeshFunctions, state=1) = + state > length(lc) ? nothing : ((lc.coeffs[state], lc.gfs[state]), state+1) + +defaultquadstrat(field::LinearCombinationOfAbstractMeshFunctions) = SingleNumQStrat(7) + +""" + Lp_integrate + +Integrates FEMFunctions and GlobalFunctions as well as linear combinations. +NOTE: FEMFunctions on the dual mesh are currently not supported. +""" + +function Lp_integrate(gf::FEMFunction; p=2, quadstrat=defaultquadstrat(gf)) + return Lp_integrate(LinearCombinationOfAbstractMeshFunctions([1.0], [gf]); p=p, quadstrat=quadstrat) +end + +function Lp_integrate( + glf::GlobalFunction{F, X, I}; + p=2, + quadstrat=defaultquadstrat(glf) +) where {F, X, I} + return Lp_integrate(LinearCombinationOfAbstractMeshFunctions([1.0], [glf]); p=p, quadstrat=quadstrat) +end + +""" + indices_splitfemglobal(lincombgfs) + +Given a linear combination of FEMFunctions and/or GlobalFunctions, return the indices +of the fem and the global functions +""" +function indices_splitfemglobal(lincombgfs::LinearCombinationOfAbstractMeshFunctions{T}) where T + + indices_fem = Int64[] + indices_global = Int64[] + + for (i, gf) in enumerate(lincombgfs.gfs) + if gf isa FEMFunction + push!(indices_fem, i) + else + push!(indices_global, i) + end + end + + return indices_fem, indices_global +end + +function Lp_integrate( + lincombgfs::LinearCombinationOfAbstractMeshFunctions{T}; + p=2, + quadstrat=SingleNumQStrat(7) +) where {T} + + # TODO: Add routine that geos are consistent + indices_fem, indices_global = indices_splitfemglobal(lincombgfs) + + if length(indices_fem) >= 1 + geo = geometry(lincombgfs.gfs[indices_fem[1]].space) + elseif length(indices_global) >= 1 + geo = lincombgfs.gfs[indices_global[1]].geo + else + error("Error: empty linear combination") + end + + asmdata = [ assemblydata(lincombgfs.gfs[i].space, onlyactives=false) for i in indices_fem] + + qds_fem = Any[] + trefs_fem = Any[] + + numquadpoints = 0 + for (i, j) in enumerate(indices_fem) + push!(trefs_fem, refspace(lincombgfs.gfs[j].space)) + tels = asmdata[i][1] + qd = quaddata(lincombgfs.gfs[j], trefs_fem[i], tels, quadstrat) + push!(qds_fem, qd) + end + + qds_global = Any[] + active_global = Any[] + for (i, j) in enumerate(indices_global) + push!(active_global, falses(numcells(lincombgfs.gfs[j].geo))) + + for cellindex in lincombgfs.gfs[j].cells + active_global[i][cellindex] = true + end + + tels = instantiate_charts(lincombgfs.gfs[j].geo, numcells(lincombgfs.gfs[j].geo), trues(numcells(lincombgfs.gfs[j].geo))) + qd = quaddata(lincombgfs.gfs[j], tels, quadstrat) + push!(qds_global, qd) + end + + if length(indices_fem) >= 1 + numquadpoints = length(qds_fem[1][1, 1]) + elseif length(indices_global) >= 1 + numquadpoints = length(qds_global[1][1, 1]) + else + error("Error: empty linear combination") + end + + # It is difficult to predict the return type of an arbitrary function + quadrature_sampling_gf_res = zeros(ComplexF64, numquadpoints, numcells(geo)) + + retval = Float64(0) #returntype(first(lincombgfs.gfs))(0) + + tels = instantiate_charts(geo, numcells(geo), trues(numcells(geo))) + + for (t, tcell) in enumerate(tels) + + qrs_fem = [quadrule(lincombgfs.gfs[j], trefs_fem[i], t, tcell, qds_fem[i], quadstrat) for (i, j) in enumerate(indices_fem)] + + # Loop over FEM functions first + + #for (i, (~, gftad, ~)) in enumerate(asmdata) + for (i, j) in enumerate(indices_fem) + ~, gftad, ~ = asmdata[i] + cellquadsamplingvaluesvalues!( + quadrature_sampling_gf_res, + t, + trefs_fem[i], + gftad, + lincombgfs.coeffs[j], + lincombgfs.gfs[j], + qrs_fem[i] + ) + end + + qrs_global = [ + quadrule(lincombgfs.gfs[j], nothing, t, tcell, qds_global[i], quadstrat) + for (i, j) in enumerate(indices_global) + ] + + # TODO: loop over grid functions + + for (i, j) in enumerate(indices_global) + + # We only integrate, if the cell is part of the domain + active_global[i][t] == false && continue + + cellquadsamplingvaluesvalues!( + quadrature_sampling_gf_res, + t, + nothing, + nothing, + lincombgfs.coeffs[j], + lincombgfs.gfs[j], + qrs_global[i] + ) + end + + # The weights are all the same since we use the same quadrature + # Just pick any from the no-empty sets + if length(indices_fem) >= 1 + qr = first(qrs_fem) + elseif length(indices_global) >= 1 + qr = first(qrs_global) + else + error("Error: empty linear combination") + end + + for i = 1:numquadpoints + retval += abs(quadrature_sampling_gf_res[i, t])^p*qr[i].weight + end + end + + return retval^(1/p) +end + +function cellquadsamplingvaluesvalues!( + quadsampling, + cellindex, + tshs::RefSpace, + tad, + lincombcoeff, + gf, + qr +) + + # num_tshs = numfunctions(tshs) + + num_oqp = length(qr) + + data = tad.data + + for p in 1 : num_oqp + + tvals = qr[p].value + + for m in 1 : length(tvals) + # for m in 1 : num_tshs + tval = tvals[m] + + for k = 1:size(data, 1) + # w is the is weight of shape function + # of the global basis function, omitted via ~ + b, w = data[k, m, cellindex] + + igd = tval[1]*w*lincombcoeff*gf.coeffs[b] + quadsampling[p, cellindex] += igd + end + end + end + +end + +function cellquadsamplingvaluesvalues!( + quadsampling, + cellindex, + tshs, + tad::Nothing, + lincombcoeff, + gf::GlobalFunction, + qr +) + + num_oqp = length(qr) + + for p in 1 : num_oqp + igd = qr[p].value + if lincombcoeff isa Number + igd *= lincombcoeff + end + quadsampling[p, cellindex] += igd + end + +end \ No newline at end of file diff --git a/src/helmholtz2d/helmholtz2d.jl b/src/helmholtz2d/helmholtz2d.jl new file mode 100644 index 00000000..c43a507f --- /dev/null +++ b/src/helmholtz2d/helmholtz2d.jl @@ -0,0 +1,114 @@ +module Helmholtz2D + + using ..BEAST + const Mod = BEAST + using LinearAlgebra + + function singlelayer(; + alpha=nothing, + gamma=nothing, + wavenumber=nothing + ) + + alpha, gamma = Mod.operator_parameter_handler(alpha, gamma, wavenumber) + + return Mod.HH2DSingleLayerFDBIO(alpha, gamma) + end + + function doublelayer(; + alpha=nothing, + gamma=nothing, + wavenumber=nothing + ) + + alpha, gamma = Mod.operator_parameter_handler(alpha, gamma, wavenumber) + + return Mod.HH2DDoubleLayerFDBIO(alpha, gamma) + end + + function doublelayer_transposed(; + alpha=nothing, + gamma=nothing, + wavenumber=nothing + ) + + alpha, gamma = Mod.operator_parameter_handler(alpha, gamma, wavenumber) + + return Mod.HH2DDoubleLayerTransposedFDBIO(alpha, gamma) + end + + function hypersingular(; + alpha=nothing, + beta=nothing, + gamma=nothing, + wavenumber=nothing + ) + + gamma, wavenumber = Mod.gamma_wavenumber_handler(gamma, wavenumber) + + if alpha === nothing + if Mod.isstatic(gamma) #static case + alpha = 0.0 # In the long run, this should probably be rather 'nothing' + else + alpha = gamma^2 + end + + end + + if beta === nothing + if Mod.isstatic(gamma) #static case + beta = one(alpha) + else + beta = one(gamma) + end + end + + return Mod.HH2DHyperSingularFDBIO(alpha, beta, gamma) + end + + function monopole(; + position=SVector(0.0, 0.0), + gamma=nothing, + wavenumber=nothing, + amplitude=1.0 + ) + + gamma, wavenumber = Mod.gamma_wavenumber_handler(gamma, wavenumber) + Mod.isstatic(gamma) && (gamma = zero(amplitude)) + + return Mod.HH2DMonopole(position, gamma, amplitude) + end + + function directedmonopole(; + position=SVector(0.0, 0.0), + direction=SVector(1.0, 0.0), + gamma=nothing, + wavenumber=nothing, + amplitude=1.0 + ) + + gamma, wavenumber = Mod.gamma_wavenumber_handler(gamma, wavenumber) + Mod.isstatic(gamma) && (gamma = zero(amplitude)) + + return Mod.HH2DDirectedMonopole(position, direction, gamma, amplitude) + end + + function planewave(; + direction=error("direction is a required argument"), + gamma=nothing, + wavenumber=nothing, + amplitude=one(eltype(direction))) + + gamma, wavenumber = Mod.gamma_wavenumber_handler(gamma, wavenumber) + + # Note: Unlike for the operators, there seems little benefit in + # explicitly declaring a Laplace-Type excitation. + + Mod.isstatic(gamma) && (gamma = zero(amplitude)) + + return Mod.HH2DPlaneWave(direction, gamma, amplitude) + end + +end + +export Helmholtz2D \ No newline at end of file diff --git a/src/helmholtz2d/helmholtzop.jl b/src/helmholtz2d/helmholtzop.jl deleted file mode 100644 index 3b0c41ff..00000000 --- a/src/helmholtz2d/helmholtzop.jl +++ /dev/null @@ -1,174 +0,0 @@ -using SpecialFunctions - -abstract type HelmholtzOperator2D <: IntegralOperator end -scalartype(::HelmholtzOperator2D) = ComplexF64 - -struct SingleLayer{T} <: HelmholtzOperator2D - wavenumber::T -end - -struct HyperSingular{T} <: HelmholtzOperator2D - wavenumber::T -end - -struct DoubleLayer{T} <: HelmholtzOperator2D - wavenumber::T -end - -struct DoubleLayerTransposed{T} <: HelmholtzOperator2D - wavenumber::T -end - -function cellcellinteractions!(biop::HelmholtzOperator2D, tshs, bshs, tcell, bcell, z) - - regularcellcellinteractions!(biop, tshs, bshs, tcell, bcell, z) - -end - -function testfunc1() - print("test function!") -end - -function quaddata(op::HelmholtzOperator2D, g::LagrangeRefSpace, f::LagrangeRefSpace, tels, bels) - - tqd = quadpoints(g, tels, (4,)) - bqd = quadpoints(f, bels, (3,)) - - return (tpoints=tqd, bpoints=bqd) -end - - -function quadrule(op::HelmholtzOperator2D, g::LagrangeRefSpace, f::LagrangeRefSpace, i, τ, j, σ, qd) - - DoubleQuadStrategy( - qd.tpoints[1,i], - qd.bpoints[1,j] - ) - -end - - -mutable struct KernelValsHelmholtz2D - wavenumber - vect - dist - green - gradgreen - txty -end - - -function kernelvals(biop::HelmholtzOperator2D, tgeo, bgeo) - - k = biop.wavenumber - r = tgeo.cart - bgeo.cart - R = norm(r) - - kr = k * R - hankels = hankelh2.([0 1], kr) - green = -im / 4 * hankels[1] - gradgreen = k * im / 4 * hankels[2] * r / R - - txty = dot(normal(tgeo), normal(bgeo)) - - KernelValsHelmholtz2D(k, r, R, green, gradgreen, txty) -end - - -shapevals(op::HelmholtzOperator2D, ϕ, ts) = shapevals(ValDiff(), ϕ, ts) - - -function integrand(biop::SingleLayer, kerneldata, tvals, - tgeo, bvals, bgeo) - - gx = tvals[1] - fy = bvals[1] - - gx * kerneldata.green * fy -end - - -function integrand(biop::HyperSingular, kernel, tvals, tgeo, - bvals, bgeo) - - gx = tvals[1] - fy = bvals[1] - - dgx = tvals[2] - dfy = bvals[2] - - k = kernel.wavenumber - G = kernel.green - txty = kernel.txty - - (dgx * dfy - k*k * txty * gx * fy) * G -end - -function integrand(biop::DoubleLayer, kernel, fp, mp, fq, mq) - nq = normal(mq) - fp[1] * dot(nq, -kernel.gradgreen) * fq[1] -end - -function integrand(biop::DoubleLayerTransposed, kernel, fp, mp, fq, mq) - np = normal(mp) - fp[1] * dot(np, kernel.gradgreen) * fq[1] -end - -mutable struct PlaneWaveDirichlet{T,P} <: Functional - wavenumber::T - direction::P -end - -mutable struct PlaneWaveNeumann{T,P} <: Functional - wavenumber::T - direction::P -end - -mutable struct ScalarTrace{F} <: Functional - f::F -end - -strace(f, mesh::Mesh) = ScalarTrace(f) - -(s::ScalarTrace)(x) = s.f(cartesian(x)) -integrand(s::ScalarTrace, tx, fx) = dot(tx.value, fx) - -shapevals(f::Functional, ϕ, ts) = shapevals(ValOnly(), ϕ, ts) - -function (field::PlaneWaveDirichlet)(mp) - - wavenumber = field.wavenumber - direction = field.direction - - cart = cartesian(mp) - exp(-im * wavenumber * dot(direction, cart)) -end - - -function(field::PlaneWaveNeumann)(mp) - - wavenumber = field.wavenumber - direction = field.direction - - cart = cartesian(mp) - norm = normal(mp) - - wave = exp(-im * wavenumber * dot(direction, cart)) - grad = -im * wavenumber * direction * wave - - d = norm[1] * grad[1] - for i in 2:length(norm) d += norm[i] * grad[i] end - return d -end - -function integrand(pw::PlaneWaveDirichlet, sv, fx) - tx = sv[1] - return dot(tx, fx) -end - -function integrand(pw::PlaneWaveNeumann, sv, fx) - tx = sv[1] - d = tx[1] * fx[1] - for i in 2:length(tx) d += tx[i]*fx[i] end - return d -end diff --git a/src/helmholtz2d/hh2dexc.jl b/src/helmholtz2d/hh2dexc.jl new file mode 100644 index 00000000..db5b8351 --- /dev/null +++ b/src/helmholtz2d/hh2dexc.jl @@ -0,0 +1,355 @@ + +struct HH2DPlaneWave{P,K,T} <: Functional{T} + direction::P + gamma::K + amplitude::T +end + +function (f::HH2DPlaneWave)(r) + d = f.direction + γ = f.gamma + a = f.amplitude + return a * exp(-γ*dot(d,r)) +end + +scalartype(f::HH2DPlaneWave{P,K,T}) where {P,K,T} = promote_type(eltype(P), K, T) + +struct gradHH2DPlaneWave{P,K,T} <: Functional{T} + direction::P + gamma::K + amplitude::T +end + +function (f::gradHH2DPlaneWave)(r) + d = f.direction + γ = f.gamma + a = f.amplitude + + return - a * γ * d * exp(-γ * dot(d, r)) +end + +scalartype(f::gradHH2DPlaneWave{P,K,T}) where {P,K,T} = promote_type(eltype(P), K, T) + +struct curlHH2DPlaneWave{P,K,T} <: Functional{T} + direction::P + polarization::P + gamma::K + amplitude::T +end + +function (f::curlHH2DPlaneWave)(r) + d = f.direction + p = f.polarization + γ = f.gamma + a = f.amplitude + return a * p * exp(-γ*dot(d,r)) +end + +scalartype(f::curlHH2DPlaneWave{P,K,T}) where {P,K,T} = promote_type(eltype(P), K, T) + +function curl(m::HH2DPlaneWave) + d = m.direction + polarization = -SVector(+d[2], -d[1]) # By using a minus sign here, the amplitude stays positive + return curlHH2DPlaneWave(d, polarization, m.gamma, m.amplitude * (m.gamma)) +end + +*(a::Number, m::HH2DPlaneWave) = HH2DPlaneWave(m.direction, m.gamma, a * m.amplitude) +*(a::Number, m::gradHH2DPlaneWave) = gradHH2DPlaneWave(m.direction, m.gamma, a * m.amplitude) +*(a::Number, m::curlHH2DPlaneWave) = curlHH2DPlaneWave(m.direction, m.polarization, m.gamma, a * m.amplitude) + + +""" + HH2DMonopole + +Potential of a monopole-type point source (e.g., of an electric charge) +""" +struct HH2DMonopole{P,K,T} + position::P + gamma::K + amplitude::T +end + +scalartype(x::HH2DMonopole{P,K,T}) where {P,K,T} = promote_type(eltype(P), K, T) + +function (f::HH2DMonopole)(r::CompScienceMeshes.MeshPointNM) + return f(cartesian(r)) +end + +function (f::HH2DMonopole)(r) + γ = f.gamma + p = f.position + a = f.amplitude + + return a / (4 * im) * hankelh2(0, -im*γ * norm(r - p)) +end + +struct curlHH2DMonopole{P,K,T} + position::P + gamma::K + amplitude::T +end + +scalartype(x::curlHH2DMonopole{P,K,T}) where {P,K,T} = promote_type(eltype(P), K, T) + +function (f::curlHH2DMonopole)(r) + a = f.amplitude + γ = f.gamma + p = f.position + vecR = r-p + R = norm(vecR) + + return a / (4 * im) * 1/R * (-im*γ) * (-hankelh2(1,(-im*γ*R))) * (SVector(-vecR[2],vecR[1])) +end + +function (f::curlHH2DMonopole)(mp::CompScienceMeshes.MeshPointNM) + fieldval = f(cartesian(mp)) + t = tangents(mp, 1) + return dot(t, fieldval) +end + +function curl(m::HH2DMonopole) + return curlHH2DMonopole(m.position, m.gamma, m.amplitude) +end + +struct gradHH2DMonopole{P,K,T} + position::P + gamma::K + amplitude::T +end + +scalartype(x::gradHH2DMonopole{P,K,T}) where {P,K,T} = promote_type(eltype(P), K, T) + +function (f::gradHH2DMonopole)(r) + a = f.amplitude + γ = f.gamma + p = f.position + vecR = r - p + R = norm(vecR) + + return a / (4 * im) * -hankelh2(1, -im*γ * R) * (-im*γ) * vecR / R +end + +function grad(m::HH2DMonopole) + return gradHH2DMonopole(m.position, m.gamma, m.amplitude) +end + +*(a::Number, m::HH2DMonopole) = HH2DMonopole(m.position, m.gamma, a * m.amplitude) +*(a::Number, m::curlHH2DMonopole) = curlHH2DMonopole(m.position, m.gamma, a * m.amplitude) +*(a::Number, m::gradHH2DMonopole) = gradHH2DMonopole(m.position, m.gamma, a * m.amplitude) + +dot(::NormalVector, m::gradHH2DMonopole) = NormalDerivative(HH2DMonopole(m.position, m.gamma, m.amplitude)) + +function (f::NormalDerivative{T,F})(manipoint) where {T,F<:HH2DMonopole} + m = f.field + grad_m = grad(m) + n = normal(manipoint) + r = cartesian(manipoint) + return dot(n, grad_m(r)) +end + +""" + HH2DDirectedMonopole + +Potential of a monopole-type point source (e.g., of an electric charge) +""" +struct HH2DDirectedMonopole{P,K,T} + position::P + direction::P + gamma::K + amplitude::T +end + +scalartype(x::HH2DDirectedMonopole{P,K,T}) where {P,K,T} = promote_type(eltype(P), K, T) + +function (f::HH2DDirectedMonopole)(r) + γ = f.gamma + p = f.position + d = f.direction + a = f.amplitude + + x = r[1] - p[1] + y = r[2] - p[2] + + Ix = d[1] + Iy = d[2] + + R = norm(r - p) + + k = -im*γ + + dhankelh2(x) = -hankelh2(1, x) # H₀^(2)' + + return a / (4 * im) * k * dhankelh2(k*R) * (Iy * x - Ix * y) / R +end + +struct curlHH2DDirectedMonopole{P,K,T} <: Functional{T} + position::P + direction::P + gamma::K + amplitude::T +end + +function (f::curlHH2DDirectedMonopole)(r) + γ = f.gamma + p = f.position + d = f.direction + a = f.amplitude + + x = r[1] - p[1] + y = r[2] - p[2] + + Ix = d[1] + Iy = d[2] + + R = norm(r - p) + + k = -im*γ + + dhankelh2(x) = -hankelh2(1, x) # H₀^(2)' + ddhankelh2(x) = hankelh2(1, x)/x - hankelh2(0, x) # H₀^(2)'' + + X = ddhankelh2(k * R) * k * (Iy * x - Ix * y) / R^2 * y + dhankelh2(k * R) * (-Ix / R - (Iy * x - Ix * y) * y / R^3) + Y = ddhankelh2(k * R) * k * (Iy * x - Ix * y) / R^2 * x + dhankelh2(k * R) * (+Iy / R - (Iy * x - Ix * y) * x / R^3) + + return a / (4 * im) * k * SVector(X, -Y) +end + +scalartype(f::curlHH2DDirectedMonopole{P,K,T}) where {P,K,T} = promote_type(eltype(P), K, T) + +function curl(m::HH2DDirectedMonopole) + return curlHH2DDirectedMonopole(m.position, m.direction, m.gamma, m.amplitude) +end + +*(a::Number, m::HH2DDirectedMonopole) = HH2DDirectedMonopole( + m.position, + m.direction, + m.gamma, + a * m.amplitude +) + +#= +*(a::Number, m::gradHH2DDirectedMonopole) = gradHH2DDirectedMonopole( + m.position, + m.direction, + m.gamma, + a * m.amplitude +) +=# + +*(a::Number, m::curlHH2DDirectedMonopole) = curlHH2DDirectedMonopole( + m.position, + m.direction, + m.gamma, + a * m.amplitude +) + +struct ScalarTrace{T,F} <: Functional{T} + field::F +end + +ScalarTrace(f::F) where {F} = ScalarTrace{scalartype(f), F}(f) +ScalarTrace{T}(f::F) where {T,F} = ScalarTrace{T,F}(f) + +strace(f::F) where {F} = ScalarTrace{scalartype(f), F}(f) +strace(f, mesh::Mesh) = ScalarTrace(f) + +@testitem "scalar trace" begin + using CompScienceMeshes + fn = joinpath(dirname(pathof(BEAST)), "../test/assets/sphere45.in") + Γ = readmesh(fn) + X = lagrangecxd0(Γ) + + U = Helmholtz3D.planewave(wavenumber=1.0, direction=ẑ) + u = strace(U) + + ux = assemble(u, X) + @test u isa BEAST.ScalarTrace + @test BEAST.scalartype(u) == ComplexF64 +end + +(s::ScalarTrace)(x) = s.field(cartesian(x)) +integrand(s::ScalarTrace, tx, fx) = dot(tx.value, fx) +scalartype(s::ScalarTrace{T}) where {T} = T + +mutable struct TangentTrace{T,F} <: Functional{T} + field::F +end + +TangentTrace(f::F) where {F} = TangentTrace{scalartype(f), F}(f) +TangentTrace{T}(f::F) where {T,F} = TangentTrace{T,F}(f) +scalartype(s::TangentTrace{T}) where {T} = T + +function (ϕ::TangentTrace)(p) + F = ϕ.field + x = cartesian(p) + t = tangents(p,1) + t = t / norm(t) + + return dot(t, F(x)) +end + +shapevals(f::Functional, ϕ, ts) = shapevals(ValOnly(), ϕ, ts) + +function (f::NormalDerivative{T,F})(manipoint) where {T,F<:HH2DPlaneWave} + d = f.field.direction + γ = f.field.gamma + a = f.field.amplitude + n = normal(manipoint) + r = cartesian(manipoint) + return -γ*a * dot(d,n) * exp(-γ*dot(d,r)) +end + +dot(::NormalVector, m::gradHH2DPlaneWave) = NormalDerivative(HH2DPlaneWave(m.direction, m.gamma, m.amplitude)) + +struct PlaneWaveDirichlet{T,P} <: Functional{T} + wavenumber::T + direction::P +end + +scalartype(x::PlaneWaveDirichlet{T}) where {T} = complex(T) + +struct PlaneWaveNeumann{T,P} <: Functional{T} + wavenumber::T + direction::P +end + +scalartype(x::PlaneWaveNeumann{T}) where {T} = complex(T) + + +function (field::PlaneWaveDirichlet)(mp) + + wavenumber = field.wavenumber + direction = field.direction + + cart = cartesian(mp) + exp(-im * wavenumber * dot(direction, cart)) +end + + +function(field::PlaneWaveNeumann)(mp) + + wavenumber = field.wavenumber + direction = field.direction + + cart = cartesian(mp) + norm = normal(mp) + + wave = exp(-im * wavenumber * dot(direction, cart)) + grad = -im * wavenumber * direction * wave + + d = norm[1] * grad[1] + for i in 2:length(norm) d += norm[i] * grad[i] end + return d +end + +function integrand(pw::PlaneWaveDirichlet, sv, fx) + tx = sv[1] + return dot(tx, fx) +end + +function integrand(pw::PlaneWaveNeumann, sv, fx) + tx = sv[1] + d = tx[1] * fx[1] + for i in 2:length(tx) d += tx[i]*fx[i] end + return d +end diff --git a/src/helmholtz2d/hh2dnear.jl b/src/helmholtz2d/hh2dnear.jl new file mode 100644 index 00000000..842ad34e --- /dev/null +++ b/src/helmholtz2d/hh2dnear.jl @@ -0,0 +1,163 @@ + +struct HH2DSingleLayerNear{T, K} + alpha::T + gamma::K + + function HH2DSingleLayerNear(alpha, gamma) + gamma = hh2d_makegammacomplexifneeded(gamma) + return new{typeof(alpha),typeof(gamma)}(alpha, gamma) + end + + function HH2DSingleLayerNear(op::HH2DSingleLayerFDBIO) + return HH2DSingleLayerNear(op.alpha, op.gamma) + end +end + +struct HH2DDoubleLayerNear{T, K} + alpha::T + gamma::K + + function HH2DDoubleLayerNear(alpha, gamma) + gamma = hh2d_makegammacomplexifneeded(gamma) + return new{typeof(alpha),typeof(gamma)}(alpha, gamma) + end + + function HH2DDoubleLayerNear(op::HH2DDoubleLayerFDBIO) + return HH2DDoubleLayerNear(op.alpha, op.gamma) + end +end + +struct HH2DDoubleLayerTransposedNear{T, K} + alpha::T + gamma::K + + function HH2DDoubleLayerTransposedNear(alpha, gamma) + gamma = hh2d_makegammacomplexifneeded(gamma) + return new{typeof(alpha),typeof(gamma)}(alpha, gamma) + end + + function HH2DDoubleLayerTransposedNear(op::HH2DDoubleLayerTransposedFDBIO) + return HH2DDoubleLayerTransposedNear(op.alpha, op.gamma) + end +end + +struct HH2DHyperSingularNear{T, K} + alpha::T + beta::T + gamma::K + + function HH2DHyperSingularNear(alpha, beta, gamma) + gamma = hh2d_makegammacomplexifneeded(gamma) + return new{typeof(alpha),typeof(gamma)}(alpha, beta, gamma) + end + + function HH2DHyperSingularNear(op::HH2DHyperSingularFDBIO) + return HH2DHyperSingularNear(op.alpha, op.beta, op.gamma) + end +end + + +HH2DNear = Union{HH2DSingleLayerNear, HH2DDoubleLayerNear, HH2DDoubleLayerTransposedNear, HH2DHyperSingularNear} +defaultquadstrat(op::HH2DNear, basis) = SingleNumQStrat(20) +quaddata(op::HH2DNear,rs,els,qs::SingleNumQStrat) = quadpoints(rs,els,(qs.quad_rule,)) +quadrule(op::HH2DNear,refspace,p,y,q,el,qdata,qs::SingleNumQStrat) = qdata[1,q] + +# TODO: This function is useful for changing the gradient to a curl +# However, it is a case of type piracy. +# (Evidently, the case is not supported by cross function due to mismatch of length) + +@inline function LinearAlgebra.cross(z::SVector{3,T1}, v::SVector{2,T2}) where {T1,T2} + z == ẑ || throw(ArgumentError("Only ẑ = (0,0,1) supported")) + return SVector{2,T2}(-v[2], v[1]) +end + +@inline function LinearAlgebra.cross(v::SVector{2,T2}, z::SVector{3,T1}) where {T1,T2} + z == ẑ || throw(ArgumentError("Only ẑ = (0,0,1) supported")) + return SVector{2,T2}( v[2], -v[1]) +end + +# WARNING: Matching the HH3D we use x for the sources +# TODO: Discuss if we change this? +function kernelvals(op::HH2DNear,y,p) + + γ = op.gamma + if iszero(real(γ)) + k = imag(γ) + else + k = -im*γ + end + x = cartesian(p) + r = y - x + R = norm(r) + + kR = k * R + hankels = hankelh2.([0 1], kR) + #green = - op.alpha * im / 4 * hankels[1] + #gradgreen = op.alpha * k * im / 4 * hankels[2] * r / R + green = - im / 4 * hankels[1] + gradgreen = k * im / 4 * hankels[2] * r / R + + ddhankel = hankels[1] - 1/(kR) * hankels[2] + + #txty = dot(normal(tgeo), normal(bgeo)) + + nx = normal(p) + tx = tangents(p, 1) / norm(tangents(p, 1)) + + # Needed for hypersingular operator + gradnxgradgreen = ddhankel * (k^2/R^2 * dot(r, nx) * r) + gradnxgradgreen += k * hankels[2] * (nx / R - r/R^3 * dot(r, nx)) + gradnxgradgreen *= - im / 4 + + (;γ, r, R, green, gradgreen, nx, tx, gradnxgradgreen) +end + +function integrand(op::HH2DSingleLayerNear, krn, y, f, p) + + α = op.alpha + G = krn.green + fx = f.value + + return α * G * fx +end + +function integrand(op::HH2DDoubleLayerNear, krn, y, f, p) + + α = op.alpha + ∇G = -krn.gradgreen + nx = krn.nx + + fx = f.value + + ∂G∂n = nx ⋅ ∇G + + return α * ∂G∂n * fx +end + +function integrand(op::HH2DDoubleLayerTransposedNear, krn, y, f, p) + + α = op.alpha + ∇G = krn.gradgreen + fx = f.value + + return α * ∇G * fx +end + +function integrand(op::HH2DHyperSingularNear, krn, y, f, p) + α = op.alpha + β = op.beta + + G = krn.green + ∇G = -krn.gradgreen + + #∇nₓ∇ₓG = krn.gradnxgradgreen + + fx = f.value + dfx = f.derivative + tx = krn.tx + + # Based on (2.86) in Kumagai et al, “Integral equation methods for electromagnetics” + # The formula returns the electric field, but we like to match the behavior of the + # dyadic hypersingular operator and this requires an additional rotation + return cross(ẑ, -α * G * tx * fx + β * ∇G * dfx) +end diff --git a/src/helmholtz2d/hh2dops.jl b/src/helmholtz2d/hh2dops.jl new file mode 100644 index 00000000..3b477505 --- /dev/null +++ b/src/helmholtz2d/hh2dops.jl @@ -0,0 +1,246 @@ +import SpecialFunctions: hankelh2 + +abstract type HelmholtzOperator2D{T, K} <: IntegralOperator end + +# Some unfortunate code duplication from mwops.jl +# TODO: Maybe we can unify by introducing another layer of abstract types +# or using Holy traits? +scalartype(op::HelmholtzOperator2D{T, K}) where {T, K <: Val{0}} = T +scalartype(op::HelmholtzOperator2D{T, K}) where {T, K} = promote_type(T, K) + + +# TODO: Another example of code duplication from mwops.jl +# Maybe any integral operator has a gamma field? +# Do we have counter examples? +function isstatic(op::HelmholtzOperator2D) + return typeof(op.gamma) == Val{0} +end + +""" + hh2d_makegammacomplexifneeded(gamma) + +Returns a complexified gamma. Unlike the 3D-case, the handling of the Green's +function is more complicated as, for example, the static kernel is not the limit +of the dynamic kernel for k → 0 + +First, recall that throughout BEAST, we assume a dependency of exp(+iωt). +The wavenumber k and gamma are related via γ = ik (and thus k = -iγ). +Accordingly, the homogeneous 2D Helmholtz equation reads + - Δu - k² u = - Δu + γ² u = 0 + +For physically meaning full real gamma (i.e., γ >= 0), the solutions of +the BIEs are real-valued. For this reason, we will use gamma and not k to +deduce the underlying scalartype of the operator. + +Note that if γ < 0 -- eventhough it is real valued -- the fundamental solution +is no longer the modified Bessel function K, and instead we have to resort to +the general solution, the Hankel function (i.e., we could use the Hankel function for +γ > 0, but it is slower than then modified Bessel function) + +The Green's functions are: + +2D Laplace: + G(x, y) = -1/(2π)* ln|x - y| + +2D modified Helmholtz equation: + G(x, y) = 1/(2π)* K₀(γ |x - y|) + +2D Helmholtz equation + G(x, y) = -i/4 * H₀⁽²⁾(-iγ |x - y|) +where γ = ik (and thus k = -iγ) +""" +function hh2d_makegammacomplexifneeded(gamma::Real) + if gamma < 0.0 + return Complex(gamma) + else + return gamma + end +end + +function hh2d_makegammacomplexifneeded(gamma) + return gamma +end + +struct HH2DSingleLayerFDBIO{T, K} <: HelmholtzOperator2D{T, K} + alpha::T + gamma::K + + function HH2DSingleLayerFDBIO(alpha, gamma) + gamma = hh2d_makegammacomplexifneeded(gamma) + new{typeof(alpha),typeof(gamma)}(alpha, gamma) + end +end + +struct HH2DDoubleLayerFDBIO{T, K} <: HelmholtzOperator2D{T, K} + alpha::T + gamma::K + + function HH2DDoubleLayerFDBIO(alpha, gamma) + gamma = hh2d_makegammacomplexifneeded(gamma) + new{typeof(alpha),typeof(gamma)}(alpha, gamma) + end +end + +struct HH2DDoubleLayerTransposedFDBIO{T, K} <: HelmholtzOperator2D{T, K} + alpha::T + gamma::K + + function HH2DDoubleLayerTransposedFDBIO(alpha, gamma) + gamma = hh2d_makegammacomplexifneeded(gamma) + new{typeof(alpha),typeof(gamma)}(alpha, gamma) + end +end + +struct HH2DHyperSingularFDBIO{T, K} <: HelmholtzOperator2D{T, K} + alpha::T # Weakly singular term + beta::T # Hypersingular term + gamma::K + + function HH2DHyperSingularFDBIO(alpha, beta, gamma) + gamma = hh2d_makegammacomplexifneeded(gamma) + new{typeof(alpha),typeof(gamma)}(alpha, beta, gamma) + end +end + +mutable struct KernelValsHelmholtz2D + gamma + wavenumber + vect + dist + green + gradgreen + txty +end + +# Kernel values for 2D Laplace BIE operators +function kernelvals(biop::HelmholtzOperator2D{T, K}, tgeo, bgeo) where {T, K <: Val{0}} + + error("We currently do not support 2D Laplace equation") +end + +# Kernel values for 2D modified Helmholtz equation BIE operators +function kernelvals(biop::HelmholtzOperator2D{T, K}, tgeo, bgeo) where {T, K <: Real} + + error("We currently do not support 2D modified Helmholtz equation") +end + +# Kernel values for 2D Helmholtz equation BIE operators +function kernelvals(biop::HelmholtzOperator2D{T, K}, tgeo, bgeo) where {T, K <: Complex} + + # Even though the evaluation of the Hankel function delivers + # in general a complex output (sole exception if wavenumber is purely imaginary) + # the Hankel function is evaluated much faster if the wavenumber, and thus + # the argument of the Hankelfunction, is purely real + if iszero(real(biop.gamma)) + k = imag(biop.gamma) + else + k = -im*gamma + end + r = tgeo.cart - bgeo.cart + R = norm(r) + + kr = k * R + hankels = hankelh2.([0 1], kr) + green = - im / 4 * hankels[1] + gradgreen = k * im / 4 * hankels[2] * r / R + + txty = dot(normal(tgeo), normal(bgeo)) + + KernelValsHelmholtz2D(gamma, k, r, R, green, gradgreen, txty) +end + +shapevals(op::HelmholtzOperator2D, ϕ, ts) = shapevals(ValDiff(), ϕ, ts) + +function integrand(biop::HH2DSingleLayerFDBIO, kerneldata, tvals, + tgeo, bvals, bgeo) + + α = biop.alpha + + gx = tvals[1] + fy = bvals[1] + + return α * gx * kerneldata.green * fy +end + +function integrand(biop::HH2DDoubleLayerFDBIO, kernel, fp, mp, fq, mq) + nq = normal(mq) + + α = biop.alpha + + return α * fp[1] * dot(nq, -kernel.gradgreen) * fq[1] +end + +function integrand(biop::HH2DDoubleLayerTransposedFDBIO, kernel, fp, mp, fq, mq) + np = normal(mp) + + α = biop.alpha + + return α * fp[1] * dot(np, kernel.gradgreen) * fq[1] +end + +function integrand(biop::HH2DHyperSingularFDBIO, kernel, tvals, tgeo, + bvals, bgeo) + + α = biop.alpha + β = biop.beta + + gx = tvals[1] + fy = bvals[1] + + dgx = tvals[2] + dfy = bvals[2] + + G = kernel.green + txty = kernel.txty + + return (α * txty * gx * fy + β * dgx * dfy) * G +end + +function cellcellinteractions!(biop::HelmholtzOperator2D, tshs, bshs, tcell, bcell, z) + + regularcellcellinteractions!(biop, tshs, bshs, tcell, bcell, z) + +end + +defaultquadstrat(op::HelmholtzOperator2D, tfs, bfs) = DoubleNumSauterQstrat(30,30,0,4,25,25) + +function quaddata(op::HelmholtzOperator2D, + test_local_space::RefSpace, trial_local_space::RefSpace, + test_charts, trial_charts, qs::DoubleNumSauterQstrat) + + T = coordtype(test_charts[1]) + + tqd = quadpoints(test_local_space, test_charts, (qs.outer_rule,)) + bqd = quadpoints(trial_local_space, trial_charts, (qs.inner_rule,)) + + leg = ( + convert.(NTuple{2,T},_legendre(qs.sauter_schwab_common_vert,0,1)), + convert.(NTuple{2,T},_legendre(qs.sauter_schwab_common_edge,0,1)), + convert.(NTuple{2,T},_legendre(qs.sauter_schwab_common_face,0,1)), + ) + + mrw = ( + convert.(NTuple{2,T},BEAST.SauterSchwabQuadrature1D._MRWrules(qs.sauter_schwab_common_vert,0,1)), + convert.(NTuple{2,T},BEAST.SauterSchwabQuadrature1D._MRWrules(qs.sauter_schwab_common_edge,0,1)), + convert.(NTuple{2,T},BEAST.SauterSchwabQuadrature1D._MRWrules(qs.sauter_schwab_common_face,0,1)), + ) + + return (tpoints=tqd, bpoints=bqd, gausslegendre=leg, marokhlinwandura=mrw) +end + +function quadrule(op::HelmholtzOperator2D, g::LagrangeRefSpace, f::LagrangeRefSpace, + i, τ::CompScienceMeshes.Simplex{<:Any, 1}, + j, σ::CompScienceMeshes.Simplex{<:Any, 1}, + qd, qs::DoubleNumSauterQstrat) + + hits = _numhits(τ, σ) + @assert hits <= 2 + + hits == 2 && return BEAST.SauterSchwabQuadrature1D.CommonEdge(qd.marokhlinwandura[2], qd.gausslegendre[2]) + hits == 1 && return BEAST.SauterSchwabQuadrature1D.CommonVertex(qd.marokhlinwandura[1], qd.gausslegendre[1]) + + return DoubleQuadRule( + qd.tpoints[1,i], + qd.bpoints[1,j], + ) +end \ No newline at end of file diff --git a/src/helmholtz3d/helmholtz3d.jl b/src/helmholtz3d/helmholtz3d.jl new file mode 100644 index 00000000..df517b46 --- /dev/null +++ b/src/helmholtz3d/helmholtz3d.jl @@ -0,0 +1,121 @@ +module Helmholtz3D + + using ..BEAST + const Mod = BEAST + using LinearAlgebra + + function singlelayer(; + alpha=nothing, + gamma=nothing, + wavenumber=nothing + ) + + alpha, gamma = Mod.operator_parameter_handler(alpha, gamma, wavenumber) + + return Mod.HH3DSingleLayerFDBIO(alpha,gamma) + end + + function doublelayer(; + alpha=nothing, + gamma=nothing, + wavenumber=nothing + ) + + alpha, gamma = Mod.operator_parameter_handler(alpha, gamma, wavenumber) + + return Mod.HH3DDoubleLayerFDBIO(alpha, gamma) + end + + function doublelayer_transposed(; + alpha=nothing, + gamma=nothing, + wavenumber=nothing + ) + + alpha, gamma = Mod.operator_parameter_handler(alpha, gamma, wavenumber) + + return Mod.HH3DDoubleLayerTransposedFDBIO(alpha, gamma) + end + + function hypersingular(; + alpha=nothing, + beta=nothing, + gamma=nothing, + wavenumber=nothing + ) + + gamma, wavenumber = Mod.gamma_wavenumber_handler(gamma, wavenumber) + + if alpha === nothing + if Mod.isstatic(gamma) #static case + alpha = 0.0 # In the long run, this should probably be rather 'nothing' + else + alpha = gamma^2 + end + + end + + if beta === nothing + if Mod.isstatic(gamma) #static case + beta = one(alpha) + else + beta = one(gamma) + end + end + + return Mod.HH3DHyperSingularFDBIO(alpha, beta, gamma) + end + + function planewave(; + direction=error("direction is a required argument"), + gamma=nothing, + wavenumber=nothing, + amplitude=one(eltype(direction))) + + gamma, wavenumber = Mod.gamma_wavenumber_handler(gamma, wavenumber) + + # Note: Unlike for the operators, there seems little benefit in + # explicitly declaring a Laplace-Type excitation. + + Mod.isstatic(gamma) && (gamma = zero(amplitude)) + + return Mod.HH3DPlaneWave(direction, gamma, amplitude) + end + + function linearpotential(; direction=SVector(1, 0, 0), amplitude=1.0) + return Mod.HH3DLinearPotential(direction ./ norm(direction), amplitude) + end + + function grad_linearpotential(; direction=SVector(0.0, 0.0, 0.0), amplitude=1.0) + return Mod.gradHH3DLinearPotential(direction, amplitude) + end + + function monopole(; + position=SVector(0.0, 0.0, 0.0), + gamma=nothing, + wavenumber=nothing, + amplitude=1.0 + ) + + gamma, wavenumber = Mod.gamma_wavenumber_handler(gamma, wavenumber) + Mod.isstatic(gamma) && (gamma = zero(amplitude)) + + return Mod.HH3DMonopole(position, gamma, amplitude) + end + + function grad_monopole(; + position=SVector(0.0, 0.0, 0.0), + gamma=nothing, + wavenumber=nothing, + amplitude=1.0 + ) + + gamma, wavenumber = Mod.gamma_wavenumber_handler(gamma, wavenumber) + Mod.isstatic(gamma) && (gamma = zero(amplitude)) + + return Mod.gradHH3DMonopole(position, gamma, amplitude) + end + +end + +export Helmholtz3D diff --git a/src/helmholtz3d/hh3d_sauterschwabqr.jl b/src/helmholtz3d/hh3d_sauterschwabqr.jl new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/src/helmholtz3d/hh3d_sauterschwabqr.jl @@ -0,0 +1 @@ + diff --git a/src/helmholtz3d/hh3dexc.jl b/src/helmholtz3d/hh3dexc.jl index c2d2d267..9333e6ab 100644 --- a/src/helmholtz3d/hh3dexc.jl +++ b/src/helmholtz3d/hh3dexc.jl @@ -1,38 +1,183 @@ +struct HH3DPlaneWave{P,K,T} + direction::P + gamma::K + amplitude::T +end + +function (f::HH3DPlaneWave)(r) + d = f.direction + γ = f.gamma + a = f.amplitude + return a * exp(-γ*dot(d,r)) +end + +function (f::HH3DPlaneWave)(r::CompScienceMeshes.MeshPointNM) + return f(cartesian(r)) +end + +scalartype(f::HH3DPlaneWave{P,K,T}) where {P,K,T} = promote_type(eltype(P), K, T) +""" + HH3DLinearPotential -struct HH3DPlaneWave{T,P} <: Functional +A potential that linearly increases in `direction` with scaling coefficient `amplitude`. +Its negative gradient will be a uniform vector field pointing in the opposite direction. +""" +struct HH3DLinearPotential{P,T} direction::P - wavenumber::T amplitude::T end -function HH3DPlaneWave(direction, wavenumber; amplitude=1) - w, a = promote(wavenumber, amplitude) - HH3DPlaneWave(direction, w, a) +scalartype(f::HH3DLinearPotential{P,T}) where {P,T} = promote_type(eltype(P), T) + +function (f::HH3DLinearPotential)(r) + d = f.direction + a = f.amplitude + return a * dot(d, r) end -function (f::HH3DPlaneWave)(r) +function (f::HH3DLinearPotential)(r::CompScienceMeshes.MeshPointNM) + return f(cartesian(r)) +end +struct gradHH3DLinearPotential{T,P} + direction::P + amplitude::T +end + +function (f::gradHH3DLinearPotential)(r) d = f.direction - k = f.wavenumber a = f.amplitude - a * exp(-im*k*dot(d,r)) + + return a * d +end + +function (f::gradHH3DLinearPotential)(r::CompScienceMeshes.MeshPointNM) + r = cartesian(mp) + return dot(normal(mp), f(r)) end -struct NormalDerivative{F} <: Functional +function grad(m::HH3DLinearPotential) + return gradHH3DLinearPotential(m.direction, m.amplitude) +end + +*(a::Number, m::HH3DLinearPotential) = HH3DLinearPotential(m.direction, a * m.amplitude) +*(a::Number, m::gradHH3DLinearPotential) = gradHH3DLinearPotential(m.direction, a * m.amplitude) + +dot(::NormalVector, m::gradHH3DLinearPotential) = NormalDerivative(HH3DLinearPotential(m.direction, m.amplitude)) + +""" + HH3DMonopole + +Potential of a monopole-type point source (e.g., of an electric charge) +""" +struct HH3DMonopole{P,K,T} + position::P + gamma::K + amplitude::T +end + +scalartype(x::HH3DMonopole{P,K,T}) where {P,K,T} = promote_type(eltype(P), K, T) + +function (f::HH3DMonopole)(r::CompScienceMeshes.MeshPointNM) + return f(cartesian(r)) +end + +function (f::HH3DMonopole)(r) + γ = f.gamma + p = f.position + a = f.amplitude + + return a*exp(-γ * norm(r - p)) / (norm(r - p)) +end + +struct gradHH3DMonopole{P,K,T} + position::P + gamma::K + amplitude::T +end + +scalartype(x::gradHH3DMonopole{P,K,T}) where {P,K,T} = promote_type(eltype(P), K, T) + +function (f::gradHH3DMonopole)(r) + a = f.amplitude + γ = f.gamma + p = f.position + vecR = r - p + R = norm(vecR) + + return -a * vecR * exp(-γ * R) / R^2 * (γ + 1 / R) +end + +function (f::gradHH3DMonopole)(mp::CompScienceMeshes.MeshPointNM) + r = cartesian(mp) + return dot(normal(mp), f(r)) +end + +function grad(m::HH3DMonopole) + return gradHH3DMonopole(m.position, m.gamma, m.amplitude) +end + +*(a::Number, m::HH3DMonopole) = HH3DMonopole(m.position, m.gamma, a * m.amplitude) +*(a::Number, m::gradHH3DMonopole) = gradHH3DMonopole(m.position, m.gamma, a * m.amplitude) + +dot(::NormalVector, m::gradHH3DMonopole) = NormalDerivative(HH3DMonopole(m.position, m.gamma, m.amplitude)) + +mutable struct DirichletTrace{T,F} <: Functional{T} field::F end +DirichletTrace(f::F) where {F} = DirichletTrace{scalartype(f), F}(f) +DirichletTrace{T}(f::F) where {T,F} = DirichletTrace{T,F}(f) +scalartype(s::DirichletTrace{T}) where {T} = T + +function (ϕ::DirichletTrace)(p) + F = ϕ.field + x = cartesian(p) + return F(x) +end + +integrand(::DirichletTrace, test_vals, field_vals) = dot(test_vals[1], field_vals) + +struct NormalDerivative{T,F} <: Functional{T} + field::F +end + +NormalDerivative(f::F) where {F} = NormalDerivative{scalartype(f), F}(f) +NormalDerivative{T}(f::F) where {T,F} = NormalDerivative{T,F}(f) +scalartype(s::NormalDerivative{T}) where {T} = T + const ∂n = Val{:normalderivative} (::Type{Val{:normalderivative}})(f) = NormalDerivative(f) -function (f::NormalDerivative{T})(manipoint) where T<:HH3DPlaneWave +function (f::NormalDerivative)(nbd) + x = cartesian(nbd) + g = gradient(f.field)(x) + n = normal(nbd) + return dot(n,g) +end + +function (f::NormalDerivative{T,F})(manipoint) where {T,F<:HH3DPlaneWave} d = f.field.direction - k = f.field.wavenumber + γ = f.field.gamma a = f.field.amplitude n = normal(manipoint) r = cartesian(manipoint) - -im*k*a * dot(d,n) * exp(-im*k*dot(d,r)) + return -γ*a * dot(d,n) * exp(-γ*dot(d,r)) +end + +function (f::NormalDerivative{T,F})(manipoint) where {T,F<:HH3DLinearPotential} + gradient = f.field.amplitude * f.field.direction + n = normal(manipoint) + return dot(n, gradient) end +function (f::NormalDerivative{T,F})(manipoint) where {T,F<:HH3DMonopole} + m = f.field + grad_m = grad(m) + n = normal(manipoint) + r = cartesian(manipoint) + + return dot(n, grad_m(r)) +end -integrand(::NormalDerivative, test_vals, field_vals) = dot(test_vals[1], field_vals) +integrand(::NormalDerivative, test_vals, field_vals) = dot(test_vals[1], field_vals) \ No newline at end of file diff --git a/src/helmholtz3d/hh3dfar.jl b/src/helmholtz3d/hh3dfar.jl new file mode 100644 index 00000000..cd37fc07 --- /dev/null +++ b/src/helmholtz3d/hh3dfar.jl @@ -0,0 +1,19 @@ +struct HH3DFarField{K} + gamma::K +end + +function HH3DFarField(;wavenumber=error("wavenumber is a required argument")) + if iszero(real(wavenumber)) + HH3DFarField(-imag(wavenumber)) + else + HH3DFarField(wavenumber*im) + end +end + +quaddata(op::HH3DFarField,rs,els) = quadpoints(rs,els,(3,)) +quadrule(op::HH3DFarField,refspace,p,y,q,el,qdata) = qdata[1,q] + +kernelvals(op::HH3DFarField,y,p) = exp(op.gamma*dot(y,cartesian(p))) +function integrand(op::HH3DFarField,krn,y,f,p) + krn * f.value +end \ No newline at end of file diff --git a/src/helmholtz3d/hh3dnear.jl b/src/helmholtz3d/hh3dnear.jl new file mode 100644 index 00000000..fdb7062e --- /dev/null +++ b/src/helmholtz3d/hh3dnear.jl @@ -0,0 +1,110 @@ + +struct HH3DDoubleLayerNear{K} + gamma::K +end + +struct HH3DSingleLayerNear{K} + gamma::K +end + +struct HH3DHyperSingularNear{K} + gamma::K +end + +struct HH3DDoubleLayerTransposedNear{K} + gamma::K +end + +function HH3DDoubleLayerNear(;wavenumber=error("wavenumber is a required argument")) + if iszero(real(wavenumber)) + HH3DDoubleLayerNear(-imag(wavenumber)) + else + HH3DDoubleLayerNear(wavenumber*im) + end +end + +function HH3DSingleLayerNear(;wavenumber=error("wavenumber is a required argument")) + if iszero(real(wavenumber)) + HH3DSingleLayerNear(-imag(wavenumber)) + else + HH3DSingleLayerNear(wavenumber*im) + end +end + +function HH3DHyperSingularNear(;wavenumber=error("wavenumber is a required argument")) + if iszero(real(wavenumber)) + HH3DHyperSingularNear(-imag(wavenumber)) + else + HH3DHyperSingularNear(wavenumber*im) + end +end + +function HH3DDoubleLayerTransposedNear(;wavenumber=error("wavenumber is a required argument")) + if iszero(real(wavenumber)) + HH3DDoubleLayerTransposedNear(-imag(wavenumber)) + else + HH3DDoubleLayerTranposedNear(wavenumber*im) + end +end + +HH3DNear = Union{HH3DSingleLayerNear, HH3DDoubleLayerNear, HH3DDoubleLayerTransposedNear, HH3DHyperSingularNear} +defaultquadstrat(op::HH3DNear, basis) = SingleNumQStrat(3) +quaddata(op::HH3DNear,rs,els,qs::SingleNumQStrat) = quadpoints(rs,els,(qs.quad_rule,)) +quadrule(op::HH3DNear,refspace,p,y,q,el,qdata,qs::SingleNumQStrat) = qdata[1,q] + +function kernelvals(op::HH3DNear,y,p) + + γ = op.gamma + x = cartesian(p) + r = y - x + R = norm(r) + γR = γ*R + + inv_R = 1/R + + expn = exp(-γR) + green = expn * inv_R * inv_4pi + gradgreen = -(γ + inv_R) * green * inv_R * r + + nx = normal(p) + + (;γ, r, R, green, gradgreen, nx) +end + +function integrand(op::HH3DDoubleLayerNear,krn,y,f,p) + + ∇G = -krn.gradgreen + nx = krn.nx + + fx = f.value + + ∂G∂n = nx ⋅ ∇G + + return ∂G∂n * fx +end + +function integrand(op::HH3DSingleLayerNear, krn, y, f, p) + G = krn.green + fx = f.value + + return G * fx +end + +function integrand(op::HH3DDoubleLayerTransposedNear, krn, y, f, p) + ∇G = krn.gradgreen + fx = f.value + + return ∇G * fx +end + +function integrand(op::HH3DHyperSingularNear, krn, y, f, p) + G = krn.green + nx = krn.nx + γ = krn.γ + invR = 1/krn.R + fx = f.value + r = krn.r + + # returns ∇ᵣn̂'∇ᵣ'G - strong singularity, probably makes problems for small distances R + return (nx * G * invR * (γ + invR) - r * dot(nx,r) * G * invR^2 * (γ^2 + 3 * γ * invR + 3 * invR^2)) * fx +end diff --git a/src/helmholtz3d/hh3dops.jl b/src/helmholtz3d/hh3dops.jl index ac9e1238..e1eb021b 100644 --- a/src/helmholtz3d/hh3dops.jl +++ b/src/helmholtz3d/hh3dops.jl @@ -1,13 +1,25 @@ - -abstract type Helmholtz3DOp <: MaxwellOperator3D end -abstract type Helmholtz3DOpReg <: MaxwellOperator3DReg end +abstract type Helmholtz3DOp{T,K} <: MaxwellOperator3D{T,K} end +abstract type Helmholtz3DOpReg{T,K} <: MaxwellOperator3DReg{T,K} end """ ``` ∫_Γ dx ∫_Γ dy \\left(α G g(x) n_x ⋅ n_y f(y) + β G \\mbox{curl} g(x) ⋅ \\mbox{curl} f(y) \\right) ``` with ``G(x,y) = \\frac{e^{-γ |x-y|}}{4 π |x-y|}`` """ -struct HH3DHyperSingularFDBIO{T,K} <: Helmholtz3DOp +struct HH3DHyperSingularFDBIO{T,K} <: Helmholtz3DOp{T,K} + "coefficient of the weakly singular term" + alpha::T + "coefficient of the hyper singular term" + beta::T + "`im*κ` with `κ` the wave number" + gamma::K +end + +function sign_upon_permutation(op::HH3DHyperSingularFDBIO, I, J) + return Combinatorics.levicivita(I) * Combinatorics.levicivita(J) +end + +struct HH3DHyperSingularReg{T,K} <: Helmholtz3DOpReg{T,K} "coefficient of the weakly singular term" alpha::T "coefficient of the hyper singular term" @@ -16,8 +28,15 @@ struct HH3DHyperSingularFDBIO{T,K} <: Helmholtz3DOp gamma::K end +struct HH3DHyperSingularSng{T,K} <: Helmholtz3DOp{T,K} + "coefficient of the weakly singular term" + alpha::T + "coefficient of the hyper singular term" + beta::T + "`im*κ` with `κ` the wave number" + gamma::K +end HH3DHyperSingularFDBIO(gamma) = HH3DHyperSingularFDBIO(gamma^2, one(gamma), gamma) -scalartype(op::HH3DHyperSingularFDBIO) = promote_type(typeof(op.alpha), typeof(op.beta), typeof(op.gamma)) """ ```math @@ -25,274 +44,139 @@ a(u,v) = α ∬_{Γ×Γ} u(x) G_{γ}(|x-y|) v(y) ``` with ``G_{γ}(r) = \\frac{e^{-γr}}{4πr}``. """ -struct HH3DSingleLayerFDBIO{T,K} <: Helmholtz3DOp +struct HH3DSingleLayerFDBIO{T,K} <: Helmholtz3DOp{T,K} alpha::T gamma::K end -struct HH3DSingleLayerReg{T,K} <: Helmholtz3DOpReg +struct HH3DSingleLayerReg{T,K} <: Helmholtz3DOpReg{T,K} alpha::T gamma::K end -struct HH3DSingleLayerSng{T,K} <: Helmholtz3DOp +struct HH3DSingleLayerSng{T,K} <: Helmholtz3DOp{T,K} alpha::T gamma::K end -struct HH3DDoubleLayer{T,K} <: Helmholtz3DOp +struct HH3DDoubleLayerFDBIO{T,K} <: Helmholtz3DOp{T,K} alpha::T gamma::K end -struct HH3DDoubleLayerTransposed{T,K} <: Helmholtz3DOp +struct HH3DDoubleLayerReg{T,K} <: Helmholtz3DOpReg{T,K} alpha::T gamma::K end -function quaddata(op::Helmholtz3DOp, test_refspace::LagrangeRefSpace, - trial_refspace::LagrangeRefSpace, test_elements, trial_elements) - - test_eval(x) = test_refspace(x, Val{:withcurl}) - trial_eval(x) = trial_refspace(x, Val{:withcurl}) - - # The combinations of rules (6,7) and (5,7 are) BAAAADDDD - # they result in many near singularity evaluations with any - # resemblence of accuracy going down the drain! Simply don't! - # (same for (5,7) btw...). - # test_qp = quadpoints(test_eval, test_elements, (6,)) - # bssi_qp = quadpoints(trial_eval, trial_elements, (7,)) - - test_qp = quadpoints(test_eval, test_elements, (4,)) - bsis_qp = quadpoints(trial_eval, trial_elements, (7,)) - - return test_qp, bsis_qp -end - -function quaddata(op::Helmholtz3DOp, test_refspace::subReferenceSpace, - trial_refspace::subReferenceSpace, test_elements, trial_elements) - - test_qp = quadpoints(test_refspace, test_elements, (4,)) - bsis_qp = quadpoints(trial_refspace, trial_elements, (7,)) - - return test_qp, bsis_qp -end - - - -function quadrule(op::HH3DSingleLayerFDBIO, - test_refspace::LagrangeRefSpace{T,0} where T, - trial_refspace::LagrangeRefSpace{T,0} where T, - i, test_element, j, trial_element, quadrature_data) - - tol, hits = sqrt(eps(eltype(eltype(test_element.vertices)))), 0 - for t in test_element.vertices - for s in trial_element.vertices - norm(t-s) < tol && (hits +=1; break) - end end - - test_quadpoints = quadrature_data[1] - trial_quadpoints = quadrature_data[2] - - hits != 0 && return WiltonSEStrategy( - test_quadpoints[1,i], - DoubleQuadStrategy( - test_quadpoints[1,i], - trial_quadpoints[1,j])) - - return DoubleQuadStrategy( - quadrature_data[1][1,i], - quadrature_data[2][1,j]) +struct HH3DDoubleLayerSng{T,K} <: Helmholtz3DOp{T,K} + alpha::T + gamma::K end - -function quadrule(op::HH3DSingleLayerFDBIO, test_refspace::subReferenceSpace, - trial_refspace::subReferenceSpace, i, test_element, j, trial_element, quadrature_data) - - # tol, hits = 1e-10, 0 - # for t in getelementVertices(test_element) - # for s in getelementVertices(trial_element) - # norm(t-s) < tol && (hits +=1; break) - # end end - # - # test_quadpoints = quadrature_data[1] - # trial_quadpoints = quadrature_data[2] - - # hits != 0 && return WiltonSEStrategy( - # test_quadpoints[1,i], - # DoubleQuadStrategy( - # test_quadpoints[1,i], - # trial_quadpoints[1,j])) - - return DoubleQuadStrategy( - quadrature_data[1][1,i], - quadrature_data[2][1,j]) - # return SauterSchwabStrategy(hits) - # test_qd = qd[1] - # trial_qd = qd[2] - # - # DoubleQuadStrategy( - # test_qd[1,i], # rule 1 on test element j - # trial_qd[1,j] # rule 1 on trial element i - # ) +struct HH3DDoubleLayerTransposedFDBIO{T,K} <: Helmholtz3DOp{T,K} + alpha::T + gamma::K end - -function quadrule(op::Helmholtz3DOp, - test_refspace::RefSpace, trial_refspace::RefSpace, - i, test_element, j, trial_element, quadrature_data) - - test_quadpoints = quadrature_data[1] - trial_quadpoints = quadrature_data[2] - - return DoubleQuadStrategy( - quadrature_data[1][1,i], - quadrature_data[2][1,j]) +struct HH3DDoubleLayerTransposedReg{T,K} <: Helmholtz3DOpReg{T,K} + alpha::T + gamma::K end -function quadrule(op::Helmholtz3DOp, - test_refspace::RTRefSpace, trial_refspace::RTRefSpace, - i, test_element, j, trial_element, quadrature_data) - - test_quadpoints = quadrature_data[1] - trial_quadpoints = quadrature_data[2] - - return DoubleQuadStrategy( - quadrature_data[1][1,i], - quadrature_data[2][1,j]) +struct HH3DDoubleLayerTransposedSng{T,K} <: Helmholtz3DOp{T,K} + alpha::T + gamma::K end +defaultquadstrat(::Helmholtz3DOp, ::LagrangeRefSpace, ::LagrangeRefSpace) = + DoubleNumWiltonSauterQStrat(2,3,2,3,4,4,4,4) +defaultquadstrat(::Helmholtz3DOp, ::subReferenceSpace, ::subReferenceSpace) = + DoubleNumWiltonSauterQStrat(4,7,4,7,4,4,4,4) +regularpart(op::HH3DHyperSingularFDBIO) = HH3DHyperSingularReg(op.alpha, op.beta, op.gamma) +singularpart(op::HH3DHyperSingularFDBIO) = HH3DHyperSingularSng(op.alpha, op.beta, op.gamma) -function integrand(op::HH3DHyperSingularFDBIO, - kernel, test_values, test_element, trial_values, trial_element) - - α = op.alpha - β = op.beta +function (igd::Integrand{<:HH3DHyperSingularFDBIO})(x,y,f,g) + α = igd.operator.alpha + β = igd.operator.beta + γ = gamma(igd.operator) - G = kernel.green + r = cartesian(x) - cartesian(y) + R = norm(r) + iR = 1 / R + green = exp(-γ*R)*(i4pi*iR) + nx = normal(x) + ny = normal(y) - g, curlg = test_values - f, curlf = trial_values - - nx = normal(test_element) - ny = normal(trial_element) - - α*dot(nx,ny)*g*f*G + β*dot(curlg,curlf)*G + _integrands(f,g) do fi, gi + α*dot(nx,ny)*gi.value*fi.value*green + β*dot(gi.curl,fi.curl)*green + end end - - - HH3DSingleLayerFDBIO(gamma) = HH3DSingleLayerFDBIO(one(gamma), gamma) regularpart(op::HH3DSingleLayerFDBIO) = HH3DSingleLayerReg(op.alpha, op.gamma) singularpart(op::HH3DSingleLayerFDBIO) = HH3DSingleLayerSng(op.alpha, op.gamma) -function integrand(op::Union{HH3DSingleLayerFDBIO,HH3DSingleLayerReg}, - kernel, test_values, test_element, trial_values, trial_element) - - α = op.alpha - G = kernel.green - - g = test_values.value - f = trial_values.value - - α*dot(g, G*f) -end +function (igd::Integrand{<:HH3DSingleLayerFDBIO})(x,y,f,g) + α = igd.operator.alpha + γ = gamma(igd.operator) + r = cartesian(x) - cartesian(y) + R = norm(r) + iR = 1 / R + green = exp(-γ*R)*(i4pi*iR) -function innerintegrals!(op::HH3DSingleLayerSng, test_neighborhood, - test_refspace::LagrangeRefSpace{T,0} where {T}, - trial_refspace::LagrangeRefSpace{T,0} where {T}, - test_elements, trial_element, zlocal, quadrature_rule::WiltonSEStrategy, dx) + αG = α * green - γ = op.gamma - α = op.alpha - - s1, s2, s3 = trial_element.vertices - - x = cartesian(test_neighborhood) - n = normalize((s1-s3)×(s2-s3)) - ρ = x - dot(x - s1, n) * n - - scal, vec = WiltonInts84.wiltonints(s1, s2, s3, x, Val{1}) - ∫G = (scal[2] - γ*scal[3] + 0.5*γ^2*scal[4]) / (4π) - - zlocal[1,1] += α * ∫G * dx - return nothing + _integrands(f,g) do fi, gi + dot(gi.value, αG*fi.value) + end end +HH3DDoubleLayerFDBIO(gamma) = HH3DDoubleLayerFDBIO(one(gamma), gamma) +regularpart(op::HH3DDoubleLayerFDBIO) = HH3DDoubleLayerReg(op.alpha, op.gamma) +singularpart(op::HH3DDoubleLayerFDBIO) = HH3DDoubleLayerSng(op.alpha, op.gamma) -function integrand(biop::HH3DDoubleLayer, - kernel, fp, mp, fq, mq) - - nq = normal(mq) - fp[1] * dot(nq, -kernel.gradgreen) * fq[1] -end - +function (igd::Integrand{<:HH3DDoubleLayerFDBIO})(x,y,f,g) + γ = gamma(igd.operator) + α = igd.operator.alpha + r = cartesian(x) - cartesian(y) + R = norm(r) + iR = 1/R + green = exp(-γ*R)*(iR*i4pi) + gradgreen = -(γ + iR) * green * (iR * r) + αgradgreen = α * gradgreen + n = normal(y) + fvalue = getvalue(f) + gvalue = getvalue(g) -function integrand(biop::HH3DDoubleLayerTransposed, - kernel, fp, mp, fq, mq) - - np = normal(mp) - fp[1] * dot(np, kernel.gradgreen) * fq[1] + return _krondot(fvalue,gvalue) * dot(n, -αgradgreen) end -module Helmholtz3D - - using ..BEAST - const Mod = BEAST +HH3DDoubleLayerTransposedFDBIO(gamma) = HH3DDoubleLayerTransposedFDBIO(one(gamma), gamma) +regularpart(op::HH3DDoubleLayerTransposedFDBIO) = HH3DDoubleLayerTransposedReg(op.alpha, op.gamma) +singularpart(op::HH3DDoubleLayerTransposedFDBIO) = HH3DDoubleLayerTransposedSng(op.alpha, op.gamma) - function singlelayer(; - gamma=nothing, - wavenumber=nothing, - alpha=nothing) +function (igd::Integrand{<:HH3DDoubleLayerTransposedFDBIO})(x,y,f,g) + γ = gamma(igd.operator) + α = igd.operator.alpha - if (gamma == nothing) && (wavenumber == nothing) - error("Supply one of (not both) gamma or wavenumber") - end + r = cartesian(x) - cartesian(y) + R = norm(r) + iR = 1/R + green = exp(-γ*R)*(iR*i4pi) + gradgreen = -(γ + iR) * green * (iR * r) + αgradgreen = α * gradgreen + n = normal(x) + fvalue = getvalue(f) + gvalue = getvalue(g) - if (gamma != nothing) && (wavenumber != nothing) - error("Supply one of (not both) gamma or wavenumber") - end - - if gamma == nothing - if iszero(real(wavenumber)) - gamma = -imag(wavenumber) - else - gamma = im*wavenumber - end - end - - @assert gamma != nothing - alpha == nothing && (alpha = one(real(typeof(gamma)))) - - Mod.HH3DSingleLayerFDBIO(alpha,gamma) - end - - hypersingular(; - gamma=error("propagation constant is a required argument"), - alpha=gamma^2, - beta=one(gamma)) = - Mod.HH3DHyperSingularFDBIO(alpha, beta, gamma) - - planewave(; - direction=error("direction is a required argument"), - wavenumber=error("wavenumber is a required arguement"), - amplitude=one(eltype(direction))) = - Mod.HH3DPlaneWave(direction, wavenumber) - - doublelayer(;gamma=error("gamma missing"), alpha=one(gamma)) = - Mod.HH3DDoubleLayer(alpha, gamma) - - doublelayer_transposed(;gamma=error("gamma missing"), alpha=one(gamma)) = - Mod.HH3DDoubleLayerTransposed(alpha, gamma) + return _krondot(fvalue,gvalue) * dot(n, αgradgreen) end - -export Helmholtz3D diff --git a/src/helmholtz3d/nitsche.jl b/src/helmholtz3d/nitsche.jl index 4c9e3927..285e8c89 100644 --- a/src/helmholtz3d/nitsche.jl +++ b/src/helmholtz3d/nitsche.jl @@ -1,28 +1,60 @@ -mutable struct NitscheHH3{T} <: MaxwellOperator3D +mutable struct NitscheHH3{T} <: MaxwellOperator3D{T,T} gamma::T end +defaultquadstrat(::NitscheHH3, ::LagrangeRefSpace, ::LagrangeRefSpace) = DoubleNumWiltonSauterQStrat(10,8,10,8,3,3,3,3) + function quaddata(operator::NitscheHH3, localtestbasis::LagrangeRefSpace, localtrialbasis::LagrangeRefSpace, - testelements, trialelements) + testelements, trialelements, qs::DoubleNumWiltonSauterQStrat) - tqd = quadpoints(localtestbasis, testelements, (10,)) - bqd = quadpoints(x -> localtrialbasis(x, Val{:withcurl}), trialelements, (8,)) + tqd = quadpoints(localtestbasis, testelements, (qs.outer_rule_far,)) + bqd = quadpoints(x -> localtrialbasis(x), trialelements, (qs.inner_rule_far,)) #return QuadData(tqd, bqd) return (tpoints=tqd, bpoints=bqd) end -function quadrule(op::NitscheHH3, g::LagrangeRefSpace, f::LagrangeRefSpace, i, τ, j, σ, qd) - DoubleQuadStrategy( +function quadrule(op::NitscheHH3, g::LagrangeRefSpace, f::LagrangeRefSpace, i, τ, j, σ, qd, + qs::DoubleNumWiltonSauterQStrat) + + DoubleQuadRule( qd.tpoints[1,i], qd.bpoints[1,j] ) end + +struct KernelValsMaxwell3D{T,U,P,Q} + "gamma = im * wavenumber" + gamma::U + vect::P + dist::T + green::U + gradgreen::Q +end + +const inv_4pi = 1/(4pi) +function kernelvals(biop::MaxwellOperator3D, p, q) + + γ = gamma(biop) + r = cartesian(p) - cartesian(q) + T = eltype(r) + R = norm(r) + γR = γ*R + + inv_R = 1/R + + expn = exp(-γR) + green = expn * inv_R * T(inv_4pi) + gradgreen = -(γ + inv_R) * green * inv_R * r + + KernelValsMaxwell3D(γ, r, R, green, gradgreen) +end + function integrand(op::NitscheHH3, kernel, test_vals, test_point, trial_vals, trial_point) Gxy = kernel.green @assert length(test_point.patch.tangents) == 1 diff --git a/src/helmholtz3d/timedomain/tdhh3dexc.jl b/src/helmholtz3d/timedomain/tdhh3dexc.jl index 045ea0b8..8db8bd64 100644 --- a/src/helmholtz3d/timedomain/tdhh3dexc.jl +++ b/src/helmholtz3d/timedomain/tdhh3dexc.jl @@ -10,18 +10,22 @@ function planewave(direction, speedoflight::Number, signature, amplitude=one(spe PlaneWaveHH3DTD(direction, speedoflight, signature, amplitude) end +scalartype(p::PlaneWaveHH3DTD) = scalartype(p.amplitude) *(a, f::PlaneWaveHH3DTD) = PlaneWaveHH3DTD(f.direction, f.speed_of_light, f.signature, a*f.amplitude) function(f::PlaneWaveHH3DTD)(r,t) + r = cartesian(r) + t = cartesian(t)[1] + k = f.direction u = sum(k[i]*r[i] for i in 1:length(k)) #u = dot(f.direction, r) a = f.amplitude c = f.speed_of_light h = f.signature - a * h(c*cartesian(t)[1] - u) + a * h(c*t - u) end @@ -40,12 +44,13 @@ struct DotTraceHH{T,F} <: TDFunctional{T} field::F end +scalartype(d::DotTraceHH{T}) where {T} = T dot(::NormalVector, field::TDFunctional) = DotTraceHH{scalartype(field), typeof(field)}(field) function (f::DotTraceHH)(p,t) n = normal(p) - # x = cartesian(p) - return dot(n, f.field(p,t)) + x = cartesian(p) + return dot(n, f.field(x,t)) end diff --git a/src/helmholtz3d/timedomain/tdhh3dops.jl b/src/helmholtz3d/timedomain/tdhh3dops.jl index 50945758..3edd3d37 100644 --- a/src/helmholtz3d/timedomain/tdhh3dops.jl +++ b/src/helmholtz3d/timedomain/tdhh3dops.jl @@ -3,11 +3,22 @@ abstract type HH3DTDBIO{T} <: RetardedPotential{T} end struct HH3DSingleLayerTDBIO{T} <: HH3DTDBIO{T} "speed of light" speed_of_light::T + "operator coefficient" + weight::T "number of temporal differentiations" num_diffs::Int end -HH3DSingleLayerTDBIO(c) = HH3DSingleLayerTDBIO(c, 0) +HH3DSingleLayerTDBIO(c) = HH3DSingleLayerTDBIO(c, 1.0, 0) + + +function Base.:*(a::Number, op::HH3DSingleLayerTDBIO) + #@info "scalar product a * op (SL)" + HH3DSingleLayerTDBIO( + op.speed_of_light, + a * op.weight, + op.num_diffs) +end struct HH3DHyperSingularTDBIO{T} <: HH3DTDBIO{T} speed_of_light::T @@ -22,6 +33,18 @@ function HH3DHyperSingularTDBIO(;speed_of_light, numdiffs) HH3DHyperSingularTDBIO(speed_of_light, id, id, numdiffs+1, numdiffs-1) end + +function Base.:*(a::Number, op::HH3DHyperSingularTDBIO) + #@info "scalar product a * op (HS)" + HH3DHyperSingularTDBIO( + op.speed_of_light, + a * op.weight_of_weakly_singular_term, + a * op.weight_of_hyper_singular_term, + op.num_diffs_on_weakly_singular_term, + op.num_diffs_on_hyper_singular_term) +end + + struct HH3DDoubleLayerTDBIO{T} <: HH3DTDBIO{T} speed_of_light::T weight::T @@ -31,10 +54,12 @@ end HH3DDoubleLayerTDBIO(;speed_of_light) = HH3DDoubleLayerTDBIO(speed_of_light,one(speed_of_light),0) +defaultquadstrat(::HH3DTDBIO, tfs, bfs) = NothingQStrategy() + # See: ?BEAST.quaddata for help function quaddata(operator::HH3DTDBIO, test_local_space, trial_local_space, time_local_space, - test_element, trial_element, time_element) + test_element, trial_element, time_element, quadstrat::NothingQStrategy) dmax = numfunctions(time_local_space)-1 bn = binomial.((0:dmax),(0:dmax)') @@ -42,7 +67,7 @@ function quaddata(operator::HH3DTDBIO, V = eltype(test_element[1].vertices) ws = WiltonInts84.workspace(V) order = 4 - @show order + #@show order quadpoints(test_local_space, test_element, (order,)), bn, ws end @@ -52,7 +77,7 @@ end function quadrule(operator::HH3DTDBIO, test_local_space, trial_local_space, time_local_space, p, test_element, q, trial_element, r, time_element, - quad_data) + quad_data, quadstrat::NothingQStrategy) # WiltonInts84Strat(quad_data[1,p]) qd = quad_data @@ -87,11 +112,11 @@ function innerintegrals!(zlocal, operator::HH3DSingleLayerTDBIO, trial_element[3], x, r, R, Val{2}, quad_rule.workspace) - a = dx / (4*pi) + a = dx / (4*pi) * operator.weight D = operator.num_diffs @assert D == 0 - @assert numfunctions(test_local_space) == 1 - @assert numfunctions(trial_local_space) == 1 + @assert numfunctions(test_local_space, domain(test_element)) == 1 + @assert numfunctions(trial_local_space, domain(trial_element)) == 1 @inline function tmRoR_sl(d, iG) sgn = isodd(d) ? -1 : 1 @@ -137,8 +162,8 @@ function innerintegrals!(zlocal, operator::HH3DHyperSingularTDBIO, trial_element[3], x, r, R, Val{N-1},quad_rule.workspace) - @assert numfunctions(test_local_space) <= 3 - @assert numfunctions(trial_local_space) == 3 + @assert numfunctions(test_local_space, domain(test_element)) <= 3 + @assert numfunctions(trial_local_space, domain(trial_element)) == 3 @inline function tmRoR(d, iG) r = (isodd(d) ? -1 : 1) * iG[d+2] @@ -148,15 +173,15 @@ function innerintegrals!(zlocal, operator::HH3DHyperSingularTDBIO, (isodd(d) ? -1 : 1) * ((1 - h*dot(m,bξ)) * ∫G[d+2] - h*dot(m, ∫Gξy[d+2])) end - test_values = test_local_space(test_point, Val{:withcurl}) - trial_values = trial_local_space(center(trial_element), Val{:withcurl}) + test_values = test_local_space(test_point) + trial_values = trial_local_space(center(trial_element)) # weakly singular term α = dx / (4π) * operator.weight_of_weakly_singular_term Ds = operator.num_diffs_on_weakly_singular_term - for i in 1 : numfunctions(test_local_space) + for i in 1 : numfunctions(test_local_space, domain(test_element)) g, curlg = test_values[i] - for j in 1 : numfunctions(trial_local_space) + for j in 1 : numfunctions(trial_local_space, domain(trial_element)) b = trial_element[j] opp_edge = trial_element[mod1(j+2,3)] - trial_element[mod1(j+1,3)] h = norm(opp_edge)/2/volume(trial_element) @@ -173,9 +198,9 @@ function innerintegrals!(zlocal, operator::HH3DHyperSingularTDBIO, # Hyper-singular term β = dx / (4π) * operator.weight_of_hyper_singular_term Dh = operator.num_diffs_on_hyper_singular_term - for i in 1 : numfunctions(test_local_space) + for i in 1 : numfunctions(test_local_space, domain(test_element)) g, curlg = test_values[i] - for j in 1 : numfunctions(trial_local_space) + for j in 1 : numfunctions(trial_local_space, domain(trial_element)) _, curlf = trial_values[j] for k in 1 : numfunctions(time_local_space) d = k - 1 @@ -194,8 +219,8 @@ function innerintegrals!(zlocal, operator::HH3DDoubleLayerTDBIO, test_element, trial_element, time_element, quad_rule, quad_weight) - @assert numfunctions(test_local_space) <= 3 - @assert numfunctions(trial_local_space) == 1 + @assert numfunctions(test_local_space, domain(test_element)) <= 3 + @assert numfunctions(trial_local_space, domain(trial_element)) == 1 dx = quad_weight x = cartesian(test_point) @@ -220,8 +245,8 @@ function innerintegrals!(zlocal, operator::HH3DDoubleLayerTDBIO, return σ * ∇G[d+1] end - test_values = test_local_space(test_point, Val{:withcurl}) - trial_values = trial_local_space(center(trial_element), Val{:withcurl}) + test_values = test_local_space(test_point) + trial_values = trial_local_space(center(trial_element)) @assert all(getindex.(trial_values,1) .≈ [1]) # @assert all(getindex.(trial_values,2) .≈ Ref([0,0,0])) @@ -229,9 +254,9 @@ function innerintegrals!(zlocal, operator::HH3DDoubleLayerTDBIO, # weakly singular term α = dx / (4π) * operator.weight D = operator.num_diffs - for i in 1 : numfunctions(test_local_space) + for i in 1 : numfunctions(test_local_space, domain(test_element)) g, curlg = test_values[i] - for j in 1 : numfunctions(trial_local_space) + for j in 1 : numfunctions(trial_local_space, domain(trial_element)) f, curlf = trial_values[j] for k in 1 : numfunctions(time_local_space) d = k-1 diff --git a/src/helmholtz3d/wiltonints.jl b/src/helmholtz3d/wiltonints.jl new file mode 100644 index 00000000..28772ce1 --- /dev/null +++ b/src/helmholtz3d/wiltonints.jl @@ -0,0 +1,500 @@ +# single layer +function (igd::Integrand{<:HH3DSingleLayerReg})(x,y,f,g) + # TODO: It is better to dispatch on γ + # to handle the static case more efficiently + γ = gamma(igd.operator) + α = igd.operator.alpha + + r = cartesian(x) - cartesian(y) + R = norm(r) + iR = 1 / R + green = (expm1(-γ*R) - 0.5*γ^2*R^2) / (4pi*R) + αG = α * green + + _integrands(f,g) do fi, gi + dot(gi.value, αG*fi.value) + end +end + +function innerintegrals!(op::HH3DSingleLayerSng, test_neighborhood, + test_refspace::LagrangeRefSpace{T,0} where {T}, + trial_refspace::LagrangeRefSpace{T,0} where {T}, + test_elements, trial_element, zlocal, quadrature_rule::WiltonSERule, dx) + # TODO: It is better to dispatch on γ + # to handle the static case more efficiently + γ = gamma(op) + α = op.alpha + + s1, s2, s3 = trial_element.vertices + + x = cartesian(test_neighborhood) + n = normalize((s1-s3)×(s2-s3)) + ρ = x - dot(x - s1, n) * n + + scal, vec = WiltonInts84.wiltonints(s1, s2, s3, x, Val{1}) + ∫G = (scal[2] + 0.5*γ^2*scal[4]) / (4π) + + zlocal[1,1] += α * ∫G * dx + + return nothing +end + +# single layer with patch basis and pyramid testing +function innerintegrals!(op::HH3DSingleLayerSng, test_neighborhood, + test_refspace::LagrangeRefSpace{T,1} where {T}, + trial_refspace::LagrangeRefSpace{T,0} where {T}, + test_elements, trial_element, zlocal, quadrature_rule::WiltonSERule, dx) + # TODO: It is better to dispatch on γ + # to handle the static case more efficiently + γ = gamma(op) + α = op.alpha + + s1, s2, s3 = trial_element.vertices + t1, t2, t3 = test_elements.vertices + + num_tshapes = numfunctions(test_refspace, domain(test_elements)) + num_bshapes = numfunctions(trial_refspace, domain(trial_element)) + + x = cartesian(test_neighborhood) + n = normalize((s1-s3)×(s2-s3)) + ρ = x - dot(x - s1, n) * n + + scal, vec = WiltonInts84.wiltonints(s1, s2, s3, x, Val{1}) + ∫G = (scal[2] + 0.5*γ^2*scal[4]) / (4π) + Atot = 1/2*norm(cross(t3-t1,t3-t2)) + for i in 1:num_tshapes + Ai = 1/2*norm(cross(test_elements.vertices[mod1(i-1,3)]-x,test_elements.vertices[mod1(i+1,3)]-x)) + g = Ai/Atot + for j in 1:num_bshapes + zlocal[i,j] += α * ∫G * g * dx + end + end + + return nothing +end + +# single layer with pyramid basis and patch testing +function innerintegrals!(op::HH3DSingleLayerSng, test_neighborhood, + test_refspace::LagrangeRefSpace{T,0} where {T}, + trial_refspace::LagrangeRefSpace{T,1} where {T}, + test_elements, trial_element, zlocal, quadrature_rule::WiltonSERule, dx) + # TODO: It is better to dispatch on γ + # to handle the static case more efficiently + γ = gamma(op) + α = op.alpha + + num_tshapes = numfunctions(test_refspace, domain(test_elements)) + num_bshapes = numfunctions(trial_refspace, domain(trial_element)) + + s1, s2, s3 = trial_element.vertices + + x = cartesian(test_neighborhood) + n = normalize((s1-s3)×(s2-s3)) + ρ = x - dot(x - s1, n) * n + + ∫Rⁿ, ∫RⁿN = WiltonInts84.higherorder(s1,s2,s3,x,3) + ∫G = (∫RⁿN[2] + 0.5*γ^2*∫RⁿN[3]) / (4π) + + for i in 1:num_bshapes + zlocal[1,i] += α * ∫G[i] * dx + end + + return nothing +end + +#single layer with pyramid basis and pyramid testing +function innerintegrals!(op::HH3DSingleLayerSng, test_neighborhood, + test_refspace::LagrangeRefSpace{T,1} where {T}, + trial_refspace::LagrangeRefSpace{T,1} where {T}, + test_elements, trial_element, zlocal, quadrature_rule::WiltonSERule, dx) + # TODO: It is better to dispatch on γ + # to handle the static case more efficiently + γ = gamma(op) + α = op.alpha + + s1, s2, s3 = trial_element.vertices + t1, t2, t3 = test_elements.vertices + + num_tshapes = numfunctions(test_refspace, domain(test_elements)) + num_bshapes = numfunctions(trial_refspace, domain(trial_element)) + + x = cartesian(test_neighborhood) + n = normalize((s1-s3)×(s2-s3)) + ρ = x - dot(x - s1, n) * n + + ∫Rⁿ, ∫RⁿN = WiltonInts84.higherorder(s1,s2,s3,x,3) + ∫G = (∫RⁿN[2] + 0.5*γ^2*∫RⁿN[3]) / (4π) + + Atot = 1/2*norm(cross(t3-t1,t3-t2)) + for i in 1:num_tshapes + Ai = 1/2*norm(cross(test_elements.vertices[mod1(i-1,3)]-x,test_elements.vertices[mod1(i+1,3)]-x)) + g = Ai/Atot + for j in 1:num_bshapes + zlocal[i,j] += α * ∫G[j] * g * dx + end + end + + return nothing +end + +# double layer transposed +function (igd::Integrand{<:HH3DDoubleLayerTransposedReg})(x,y,f,g) + # TODO: It is better to dispatch on γ + # to handle the static case more efficiently + γ = gamma(igd.operator) + + r = cartesian(x) - cartesian(y) + R = norm(r) + iR = 1/R + γR = γ*R + expo = exp(-γR) + + gradgreen = ( -(γR + 1)*expo + (1 - 0.5*γR^2) ) * (i4pi*iR^3) * r + + n = normal(x) + fvalue = getvalue(f) + gvalue = getvalue(g) + + return _krondot(fvalue,gvalue) * dot(n, gradgreen) +end + +#double layer transposed with patch basis and pyramid testing +function innerintegrals!(op::HH3DDoubleLayerTransposedSng, test_neighborhood, + test_refspace::LagrangeRefSpace{T,0,3,1} where {T}, + trial_refspace::LagrangeRefSpace{T,0,3,1} where {T}, + test_elements, trial_element, zlocal, quadrature_rule::WiltonSERule, dx) + # TODO: It is better to dispatch on γ + # to handle the static case more efficiently + γ = gamma(op) + α = op.alpha + + s1, s2, s3 = trial_element.vertices + t1, t2, t3 = test_elements.vertices + x = cartesian(test_neighborhood) + n = normalize((t1-t3)×(t2-t3)) + ρ = x - dot(x - s1, n) * n + + num_tshapes = numfunctions(test_refspace, domain(test_elements)) + num_bshapes = numfunctions(trial_refspace, domain(trial_element)) + + scal, vec, grad = WiltonInts84.wiltonints(s1, s2, s3, x, Val{1}) + + ∫∇G = -(grad[1]+0.5*γ^2*grad[3])/(4π) + ∫n∇G = dot(n,∫∇G) + for i in 1:num_tshapes + for j in 1:num_bshapes + zlocal[i,j] += α * ∫n∇G * dx + end + end + + return nothing +end +#double layer transposed with patch basis and pyramid testing +function innerintegrals!(op::HH3DDoubleLayerTransposedSng, test_neighborhood, + test_refspace::LagrangeRefSpace{T,1,3,3} where {T}, + trial_refspace::LagrangeRefSpace{T,0,3,1} where {T}, + test_elements, trial_element, zlocal, quadrature_rule::WiltonSERule, dx) + # TODO: It is better to dispatch on γ + # to handle the static case more efficiently + γ = gamma(op) + α = op.alpha + + s1, s2, s3 = trial_element.vertices + t1, t2, t3 = test_elements.vertices + x = cartesian(test_neighborhood) + n = normalize((t1-t3)×(t2-t3)) + ρ = x - dot(x - s1, n) * n + + num_tshapes = numfunctions(test_refspace, domain(test_elements)) + num_bshapes = numfunctions(trial_refspace, domain(trial_element)) + + scal, vec, grad = WiltonInts84.wiltonints(s1, s2, s3, x, Val{1}) + + ∫∇G = -(grad[1]+0.5*γ^2*grad[3])/(4π) + ∫n∇G = dot(n,∫∇G) + Atot = 1/2*norm(cross(t3-t1,t3-t2)) + for i in 1:num_tshapes + Ai = 1/2*norm(cross(test_elements.vertices[mod1(i-1,3)]-x,test_elements.vertices[mod1(i+1,3)]-x)) + g = Ai/Atot + for j in 1:num_bshapes + zlocal[i,j] += α * ∫n∇G * g * dx + end + end + + return nothing +end +#double layer transposed with pyramid basis and patch testing +function innerintegrals!(op::HH3DDoubleLayerTransposedSng, test_neighborhood, + test_refspace::LagrangeRefSpace{T,0,3,1} where {T}, + trial_refspace::LagrangeRefSpace{T,1,3,3} where {T}, + test_elements, trial_element, zlocal, quadrature_rule::WiltonSERule, dx) + # TODO: It is better to dispatch on γ + # to handle the static case more efficiently + γ = gamma(op) + α = op.alpha + + s1, s2, s3 = trial_element.vertices + t1, t2, t3 = test_elements.vertices + x = cartesian(test_neighborhood) + n = normalize((t1-t3)×(t2-t3)) + ρ = x - dot(x - s1, n) * n + + num_tshapes = numfunctions(test_refspace, domain(test_elements)) + num_bshapes = numfunctions(trial_refspace, domain(trial_element)) + + _, _, _, grad = WiltonInts84.higherorder(s1,s2,s3,x,3) + + ∫∇G = -(grad[1] + 0.5*γ^2*grad[2]) / (4π) + for i in 1:num_tshapes + for j in 1:num_bshapes + ∫n∇G = dot(n,∫∇G[j]) + zlocal[i,j] += α * ∫n∇G * dx + end + end + + return nothing +end +#double layer transposed with pyramid basis and pyramid testing +function innerintegrals!(op::HH3DDoubleLayerTransposedSng, test_neighborhood, + test_refspace::LagrangeRefSpace{T,1,3,3} where {T}, + trial_refspace::LagrangeRefSpace{T,1,3,3} where {T}, + test_elements, trial_element, zlocal, quadrature_rule::WiltonSERule, dx) + # TODO: It is better to dispatch on γ + # to handle the static case more efficiently + γ = gamma(op) + α = op.alpha + + s1, s2, s3 = trial_element.vertices + t1, t2, t3 = test_elements.vertices + x = cartesian(test_neighborhood) + n = normalize((t1-t3)×(t2-t3)) + ρ = x - dot(x - s1, n) * n + + num_tshapes = numfunctions(test_refspace, domain(test_elements)) + num_bshapes = numfunctions(trial_refspace, domain(trial_element)) + + _, _, _, grad = WiltonInts84.higherorder(s1,s2,s3,x,3) + + ∫∇G = -(grad[1] + 0.5*γ^2*grad[2]) / (4π) + + Atot = 1/2*norm(cross(t3-t1,t3-t2)) + for i in 1:num_tshapes + Ai = 1/2*norm(cross(test_elements.vertices[mod1(i-1,3)]-x,test_elements.vertices[mod1(i+1,3)]-x)) + g = Ai/Atot + for j in 1:num_bshapes + ∫n∇G = dot(n,∫∇G[j]) + zlocal[i,j] += α * ∫n∇G * g * dx + end + end + + return nothing +end +# double layer +function (igd::Integrand{<:HH3DDoubleLayerReg})(x,y,f,g) + # TODO: It is better to dispatch on γ + # to handle the static case more efficiently + γ = gamma(igd.operator) + + r = cartesian(x) - cartesian(y) + R = norm(r) + iR = 1/R + γR = γ*R + expo = exp(-γR) + + gradgreen = ( -(γR + 1)*expo + (1 - 0.5*γR^2) ) * (i4pi*iR^3) * r + + n = normal(y) + fvalue = getvalue(f) + gvalue = getvalue(g) + + return _krondot(fvalue,gvalue) * dot(n, -gradgreen) + +end +#double layer with pyramid basis and pyramid testing +function innerintegrals!(op::HH3DDoubleLayerSng, p, + g::LagrangeRefSpace{T,1} where {T}, + f::LagrangeRefSpace{T,1} where {T}, + t, s, z, quadrature_rule::WiltonSERule, dx) + # TODO: It is better to dispatch on γ + # to handle the static case more efficiently + γ = gamma(op) + α = op.alpha + + s1, s2, s3 = s.vertices + t1, t2, t3 = t.vertices + + num_tshapes = numfunctions(g, domain(t)) + num_bshapes = numfunctions(f, domain(s)) + + x = cartesian(p) + n = normalize((s1-s3)×(s2-s3)) + ρ = x - dot(x - s1, n) * n + + _, _, _, grad = WiltonInts84.higherorder(s1,s2,s3,x,3) + + ∫∇G = -(grad[1] + 0.5*γ^2*grad[2]) / (4π) + + Atot = 1/2*norm(cross(t3-t1,t3-t2)) + for i in 1:num_tshapes + Ai = 1/2*norm(cross(t.vertices[mod1(i-1,3)]-x,t.vertices[mod1(i+1,3)]-x)) + g = Ai/Atot + for j in 1:num_bshapes + z[i,j] += α * dot(n,-∫∇G[j]) * g * dx + end + end + + return nothing +end +#double layer with patch basis and pyramid testing +function innerintegrals!(op::HH3DDoubleLayerSng, p, + g::LagrangeRefSpace{T,0} where {T}, + f::LagrangeRefSpace{T,0} where {T}, + t, s, z, quadrature_rule::WiltonSERule, dx) + # TODO: It is better to dispatch on γ + # to handle the static case more efficiently + γ = gamma(op) + α = op.alpha + + num_tshapes = numfunctions(g, domain(t)) + num_bshapes = numfunctions(f, domain(s)) + + s1, s2, s3 = s.vertices + t1, t2, t3 = t.vertices + + x = cartesian(p) + n = normalize((s1-s3)×(s2-s3)) + ρ = x - dot(x - s1, n) * n + + scal, vec, grad = WiltonInts84.wiltonints(s1, s2, s3, x, Val{1}) + + ∫∇G = -(grad[1]+0.5*γ^2*grad[3])/(4π) + + for i in 1:num_tshapes + for j in 1:num_bshapes + z[i,j] += α * dot(n,-∫∇G) * dx #why the minus? + end + end + return nothing +end +#double layer with patch basis and pyramid testing +function innerintegrals!(op::HH3DDoubleLayerSng, p, + g::LagrangeRefSpace{T,1} where {T}, + f::LagrangeRefSpace{T,0} where {T}, + t, s, z, quadrature_rule::WiltonSERule, dx) + # TODO: It is better to dispatch on γ + # to handle the static case more efficiently + γ = gamma(op) + α = op.alpha + + s1, s2, s3 = s.vertices + t1, t2, t3 = t.vertices + + num_tshapes = numfunctions(g, domain(t)) + num_bshapes = numfunctions(f, domain(s)) + + x = cartesian(p) + n = normalize((s1-s3)×(s2-s3)) + ρ = x - dot(x - s1, n) * n + + scal, vec, grad = WiltonInts84.wiltonints(s1, s2, s3, x, Val{1}) + + ∫∇G = -(grad[1]+0.5*γ^2*grad[3])/(4π) + + Atot = 1/2*norm(cross(t3-t1,t3-t2)) + for i in 1:num_tshapes + Ai = 1/2*norm(cross(t.vertices[mod1(i-1,3)]-x,t.vertices[mod1(i+1,3)]-x)) + g = Ai/Atot + for j in 1:num_bshapes + z[i,j] += α * dot(n,-∫∇G) * g * dx + end + end + return nothing +end + +#double layer with pyramid basis and patch testing +function innerintegrals!(op::HH3DDoubleLayerSng, p, + g::LagrangeRefSpace{T,0} where {T}, + f::LagrangeRefSpace{T,1} where {T}, + t, s, z, quadrature_rule::WiltonSERule, dx) + # TODO: It is better to dispatch on γ + # to handle the static case more efficiently + γ = gamma(op) + α = op.alpha + + s1, s2, s3 = s.vertices + + x = cartesian(p) + n = normalize((s1-s3)×(s2-s3)) + ρ = x - dot(x - s1, n) * n + + _, _, _, grad = WiltonInts84.higherorder(s1,s2,s3,x,3) + + ∫∇G = -(grad[1] + 0.5*γ^2*grad[2]) / (4π) + + num_tshapes = numfunctions(g, domain(t)) + num_bshapes = numfunctions(f, domain(s)) + + for i in 1:num_tshapes + for j in 1:num_bshapes + z[i,j] += α * dot(n,-∫∇G[j]) * dx + end + end + + return nothing +end + + +function (igd::Integrand{<:HH3DHyperSingularReg})(x,y,f,g) + α = igd.operator.alpha + β = igd.operator.beta + # TODO: It is better to dispatch on γ + # to handle the static case more efficiently + γ = gamma(igd.operator) + + r = cartesian(x) - cartesian(y) + R = norm(r) + iR = 1 / R + green = (expm1(-γ*R) - 0.5*γ^2*R^2) / (4pi*R) + nx = normal(x) + ny = normal(y) + + _integrands(f,g) do fi, gi + α*dot(nx,ny)*gi.value*fi.value*green + β*dot(gi.curl,fi.curl)*green + end +end + +function innerintegrals!(op::HH3DHyperSingularSng, p, + g::LagrangeRefSpace{T,1} where {T}, + f::LagrangeRefSpace{T,1} where {T}, + t, s, z, quadrature_rule::WiltonSERule, dx) + α = op.alpha + β = op.beta + # TODO: It is better to dispatch on γ + # to handle the static case more efficiently + γ = gamma(op) + s1, s2, s3 = s.vertices + t1, t2, t3 = t.vertices + x = cartesian(p) + nx = normalize((s1-s3)×(s2-s3)) + ny = normalize((t1-t3)×(t2-t3)) + + ∫Rⁿ, ∫RⁿN = WiltonInts84.higherorder(s1,s2,s3,x,3) + greenconst = (∫Rⁿ[2] + 0.5*γ^2*∫Rⁿ[3]) / (4π) + greenlinear = (∫RⁿN[2] + 0.5*γ^2*∫RⁿN[3] ) / (4π) + + num_tshapes = numfunctions(g, domain(t)) + num_bshapes = numfunctions(f, domain(s)) + + jt = volume(t) * factorial(dimension(t)) + js = volume(s) * factorial(dimension(s)) + curlt = [(t3-t2)/jt,(t1-t3)/jt,(t2-t1)/jt] + curls = [(s3-s2)/js,(s1-s3)/js,(s2-s1)/js] + Atot = 1/2*norm(cross(t3-t1,t3-t2)) + for i in 1:num_tshapes + Ai = 1/2*norm(cross(t.vertices[mod1(i-1,3)]-x,t.vertices[mod1(i+1,3)]-x)) + g = Ai/Atot + for j in 1:num_bshapes + z[i,j] += β * dot(curlt[i],curls[j])*greenconst*dx + α*dot(nx,ny) * greenlinear[j]*g*dx + end + end +end diff --git a/src/identityop.jl b/src/identityop.jl index bb6b5981..6d70e61e 100644 --- a/src/identityop.jl +++ b/src/identityop.jl @@ -1,3 +1,9 @@ + +""" + Identity <: LocalOperator + +The identity operator. +""" struct Identity <: LocalOperator end @@ -5,6 +11,11 @@ kernelvals(biop::Identity, x) = nothing integrand(op::Identity, kernel, x, g, f) = dot(f[1], g[1]) scalartype(op::Identity) = Union{} +""" + NCross <: LocalOperator + +The identity operator where the trial function is rotated. +""" struct NCross <: LocalOperator end @@ -12,6 +23,36 @@ kernelvals(op::NCross, mp) = nothing integrand(op::NCross, kernel, x, g, f) = dot(g[1], normal(x) × f[1]) scalartype(op::NCross) = Union{} +struct Curl <: LocalOperator +end + +kernelvals(biop::Curl, x) = nothing +function integrand(op::Curl, kernel, x, g, f) + dot(f.curl, g.value) + end +scalartype(op::Curl) = Union{} + + +defaultquadstrat(::LocalOperator, ::GWPDivRefSpace{<:Real,D1},::LagrangeRefSpace{T,D2,D3}) where {T,D1,D2,D3} = SingleNumQStrat(9) +function quaddata(op::LocalOperator, g::GWPDivRefSpace, f::LagrangeRefSpace{T,D2,D1} where {T,D1,D2} , tels, bels, qs::SingleNumQStrat) + + u, w = trgauss(qs.quad_rule) + qd = [(w[i],SVector(u[1,i],u[2,i])) for i in 1:length(w)] + A = _alloc_workspace(qd, g, f, tels, bels) + + return qd, A +end + +defaultquadstrat(::LocalOperator, ::RTRefSpace, ::LagrangeRefSpace{T,D2,D1}) where {T,D1,D2} = SingleNumQStrat(6) +function quaddata(op::LocalOperator, g::RTRefSpace, f::LagrangeRefSpace{T,D2,D1} where {T,D1,D2} , tels, bels, qs::SingleNumQStrat) + + u, w = trgauss(qs.quad_rule) + qd = [(w[i],SVector(u[1,i],u[2,i])) for i in 1:length(w)] + A = _alloc_workspace(qd, g, f, tels, bels) + + return qd, A +end + function _alloc_workspace(qd, g, f, tels, bels) q = qd[1] τ = tels[1] @@ -20,63 +61,89 @@ function _alloc_workspace(qd, g, f, tels, bels) A = Vector{typeof(a)}(undef,length(qd)) end -const LinearRefSpaceTriangle = Union{RTRefSpace, NDRefSpace} -function quaddata(op::LocalOperator, g::LinearRefSpaceTriangle, f::LinearRefSpaceTriangle, tels, bels) - u, w = trgauss(6) +const LinearRefSpaceTriangle = Union{RTRefSpace, RT2RefSpace, NDRefSpace, ND2RefSpace, BDMRefSpace, NCrossBDMRefSpace} +defaultquadstrat(::LocalOperator, ::LinearRefSpaceTriangle, ::LinearRefSpaceTriangle) = SingleNumQStrat(6) +function quaddata(op::LocalOperator, g::LinearRefSpaceTriangle, f::LinearRefSpaceTriangle, tels, bels, + qs::SingleNumQStrat) + + u, w = trgauss(qs.quad_rule) qd = [(w[i],SVector(u[1,i],u[2,i])) for i in 1:length(w)] A = _alloc_workspace(qd, g, f, tels, bels) return qd, A end -function quaddata(op::LocalOperator, g::subReferenceSpace, f::subReferenceSpace, tels, bels) - u, w = trgauss(6) +defaultquadstrat(::LocalOperator, ::subReferenceSpace, ::subReferenceSpace) = SingleNumQStrat(6) +function quaddata(op::LocalOperator, g::subReferenceSpace, f::subReferenceSpace, tels, bels, + qs::SingleNumQStrat) + + u, w = trgauss(qs.quad_rule) qd = [(w[i],SVector(u[1,i],u[2,i])) for i in 1:length(w)] A = _alloc_workspace(qd, g, f, tels, bels) return qd, A end -const LinearRefSpaceTetr = Union{NDLCCRefSpace, NDLCDRefSpace} -function quaddata(op::LocalOperator, g::LinearRefSpaceTetr, f::LinearRefSpaceTetr, tels, bels) +const LinearRefSpaceTetr = Union{NDLCCRefSpace, NDLCDRefSpace, BDM3DRefSpace} +defaultquadstrat(::LocalOperator, ::LinearRefSpaceTetr, ::LinearRefSpaceTetr) = SingleNumQStrat(3) +function quaddata(op::LocalOperator, g::LinearRefSpaceTetr, f::LinearRefSpaceTetr, tels, bels, qs::SingleNumQStrat) + o, x, y, z = CompScienceMeshes.euclidianbasis(3) reftet = simplex(x,y,z,o) - qps = quadpoints(reftet, 3) + qps = quadpoints(reftet, qs.quad_rule) qd = [(w, parametric(p)) for (p,w) in qps] A = _alloc_workspace(qd, g, f, tels, bels) return qd, A end +defaultquadstrat(::LocalOperator, ::LagrangeRefSpace{T,D1,2}, ::LagrangeRefSpace{T,D2,2}) where {T,D1,D2} = SingleNumQStrat(6) function quaddata(op::LocalOperator, g::LagrangeRefSpace{T,Deg,2} where {T,Deg}, - f::LagrangeRefSpace, tels::Vector, bels::Vector) - - u, w = legendre(6, 0.0, 1.0) + f::LagrangeRefSpace, tels::Vector, bels::Vector, qs::SingleNumQStrat) + U = typeof(tels[1].volume) + u, w = legendre(qs.quad_rule, 0.0, 1.0) qd = [(w[i],u[i]) for i in eachindex(w)] - A = _alloc_workspace(qd, g, f, tels, bels) + A = _alloc_workspace(Tuple{U,U}.(qd), g, f, tels, bels) return qd, A end +defaultquadstrat(::LocalOperator, ::LagrangeRefSpace{T,D1,3}, ::LagrangeRefSpace{T,D2,3}) where {T,D1,D2} = SingleNumQStrat(9) function quaddata(op::LocalOperator, g::LagrangeRefSpace{T,Deg,3} where {T,Deg}, - f::LagrangeRefSpace, tels::Vector, bels::Vector) + f::LagrangeRefSpace, tels::Vector, bels::Vector, qs::SingleNumQStrat) - u, w = trgauss(6) + u, w = trgauss(qs.quad_rule) qd = [(w[i], SVector(u[1,i], u[2,i])) for i in 1:length(w)] A = _alloc_workspace(qd, g, f, tels, bels) return qd, A end +defaultquadstrat(::LocalOperator, ::LagrangeRefSpace{T,D1,4}, ::LagrangeRefSpace{T,D2,4}) where {T,D1,D2} = SingleNumQStrat(6) function quaddata(op::LocalOperator, g::LagrangeRefSpace{T,Deg,4} where {T,Deg}, - f::LagrangeRefSpace, tels::Vector, bels::Vector) + f::LagrangeRefSpace, tels::Vector, bels::Vector, qs::SingleNumQStrat) o, x, y, z = CompScienceMeshes.euclidianbasis(3) reftet = simplex(x,y,z,o) - qps = quadpoints(reftet, 6) + qps = quadpoints(reftet, qs.quad_rule) qd = [(w, parametric(p)) for (p,w) in qps] A = _alloc_workspace(qd, g, f, tels, bels) return qd, A end -function quadrule(op::LocalOperator, ψ::RefSpace, ϕ::RefSpace, τ, (qd,A)) +defaultquadstrat(::LocalOperator, ::GWPDivRefSpace{<:Real,D1}, + ::GWPDivRefSpace{<:Real,D2}) where {D1,D2} = SingleNumQStrat(9) + + +function quaddata(op::LocalOperator, g::GWPDivRefSpace, f::GWPDivRefSpace, + tels::Vector, bels::Vector, + qs::SingleNumQStrat) + + u, w = trgauss(qs.quad_rule) + qd = [(w[i],SVector(u[1,i],u[2,i])) for i in 1:length(w)] + A = _alloc_workspace(qd, g, f, tels, bels) + return qd, A +end + + +function quadrule(op::LocalOperator, ψ::RefSpace, ϕ::RefSpace, τ, (qd,A), qs::SingleNumQStrat) for i in eachindex(qd) q = qd[i] w, p = q[1], neighborhood(τ,q[2]) diff --git a/src/integralop.jl b/src/integralop.jl index 73ea92ce..e9170464 100644 --- a/src/integralop.jl +++ b/src/integralop.jl @@ -1,6 +1,7 @@ -abstract type IntegralOperator <: Operator end +defaultquadstrat(op::IntegralOperator, tfs::RefSpace, bfs::RefSpace) = + DoubleNumSauterQstrat(2,3,5,5,4,3) """ blockassembler(operator, test_space, trial_space) -> assembler @@ -28,25 +29,25 @@ for `I` and `J` permutations of `1:numfunctions(test_space)` and function blockassembler end -""" - quadrule(operator,test_refspace,trial_refspace,p,test_element,q_trial_element, qd) - -Returns an object that contains all the dynamic (runtime) information that -defines the integration strategy that will be used by `momintegrals!` to compute -the interactions between the local test/trial functions defined on the specified -geometric elements. The indices `p` and `q` refer to the position of the test -and trial elements as encountered during iteration over the output of -`geometry`. - -The last argument `qd` provides access to all precomputed data required for -quadrature. For example it might be desirable to precompute all the quadrature -points for all possible numerical quadrature schemes that can potentially be -required during matrix assembly. This makes sense, since the number of point is -order N (where N is the number of faces) but these points will appear in N^2 -computations. Precomputation requires some extra memory but can save a lot on -computation time. -""" -function quadrule end +# """ +# quadrule(operator,test_refspace,trial_refspace,p,test_element,q_trial_element, qd) + +# Returns an object that contains all the dynamic (runtime) information that +# defines the integration strategy that will be used by `momintegrals!` to compute +# the interactions between the local test/trial functions defined on the specified +# geometric elements. The indices `p` and `q` refer to the position of the test +# and trial elements as encountered during iteration over the output of +# `geometry`. + +# The last argument `qd` provides access to all precomputed data required for +# quadrature. For example it might be desirable to precompute all the quadrature +# points for all possible numerical quadrature schemes that can potentially be +# required during matrix assembly. This makes sense, since the number of point is +# order N (where N is the number of faces) but these points will appear in N^2 +# computations. Precomputation requires some extra memory but can save a lot on +# computation time. +# """ +# function quadrule end """ @@ -56,7 +57,7 @@ Create an iterable collection of the elements stored in `geo`. The order in whic this collection produces the elements determines the index used for lookup in the data structures returned by `assemblydata` and `quaddata`. """ -elements(geo) = [chart(geo,cl) for cl in cells(geo)] +elements(geo) = [chart(geo,cl) for cl in geo] elements(sp::Space) = elements(geometry(sp)) @@ -65,45 +66,92 @@ elements(sp::Space) = elements(geometry(sp)) Computes the matrix of operator biop wrt the finite element spaces tfs and bfs """ -function assemblechunk!(biop::IntegralOperator, tfs::Space, bfs::Space, store) +function assemblechunk!(biop::IntegralOperator, tfs::Space, bfs::Space, store; + quadstrat=defaultquadstrat(biop, tfs, bfs)) - test_elements, tad = assemblydata(tfs) - bsis_elements, bad = assemblydata(bfs) + numfunctions(tfs) == 0 && return + numfunctions(bfs) == 0 && return - tshapes = refspace(tfs); num_tshapes = numfunctions(tshapes) - bshapes = refspace(bfs); num_bshapes = numfunctions(bshapes) + tr = assemblydata(tfs); tr == nothing && return + br = assemblydata(bfs); br == nothing && return - qd = quaddata(biop, tshapes, bshapes, test_elements, bsis_elements) - zlocal = zeros(scalartype(biop, tfs, bfs), 2num_tshapes, 2num_bshapes) + test_elements, tad, tcells = tr + bsis_elements, bad, bcells = br + + tgeo = geometry(tfs) + bgeo = geometry(bfs) + + tdom = domain(chart(tgeo, first(tgeo))) + bdom = domain(chart(bgeo, first(bgeo))) - if !CompScienceMeshes.refines(tfs.geo, bfs.geo) - assemblechunk_body!(biop, - tshapes, test_elements, tad, - bshapes, bsis_elements, bad, - qd, zlocal, store) + tshapes = refspace(tfs); num_tshapes = numfunctions(tshapes, tdom) + bshapes = refspace(bfs); num_bshapes = numfunctions(bshapes, bdom) + + qs = if CompScienceMeshes.refines(tgeo, bgeo) + TestRefinesTrialQStrat(quadstrat) + elseif CompScienceMeshes.refines(bgeo, tgeo) + TrialRefinesTestQStrat(quadstrat) else - assemblechunk_body_nested_meshes!(biop, - tshapes, test_elements, tad, - bshapes, bsis_elements, bad, - qd, zlocal, store) + quadstrat end + + qd = quaddata(biop, tshapes, bshapes, test_elements, bsis_elements, qs) + zlocal = zeros(scalartype(biop, tfs, bfs), 2num_tshapes, 2num_bshapes) + assemblechunk_body!(biop, + tfs, test_elements, tad, tcells, + bfs, bsis_elements, bad, bcells, + qd, zlocal, store; quadstrat=qs) +end + +@testitem "assemble!: zero sized block" begin + using CompScienceMeshes + import BEAST.BlockArrays + + fn = joinpath(dirname(pathof(BEAST)), "../examples/assets/sphere45.in") + m1 = readmesh(fn) + m2 = m1[Int[]] + + X = BEAST.DirectProductSpace([raviartthomas(m) for m in [m1, m2]]) + T = Maxwell3D.singlelayer(gamma=1.0) + + @hilbertspace j[1:2] + @hilbertspace k[1:2] + a = T[k[1],j[1]] + T[k[1],j[2]] + T[k[2],j[2]] + T[k[2],j[1]] + + A = assemble(a, X, X) + M = AbstractMatrix(A) + + n1 = numfunctions(X[1]) + n2 = numfunctions(X[2]) + + @test n2 == 0 + + @test BlockArrays.blocksize(M) == (2,2) + @test BlockArrays.blocksizes(M) == [(n1,n1) (n1,n2); (n2,n1) (n2,n2)] end function assemblechunk_body!(biop, - test_shapes, test_elements, test_assembly_data, - trial_shapes, trial_elements, trial_assembly_data, - qd, zlocal, store) + test_space, test_elements, test_assembly_data, test_cell_ptrs, + trial_space, trial_elements, trial_assembly_data, trial_cell_ptrs, + qd, zlocal, store; quadstrat) + + test_shapes = refspace(test_space) + trial_shapes = refspace(trial_space) + + verbose = (length(test_elements) > 256) myid = Threads.threadid() - myid == 1 && print("dots out of 10: ") + verbose && myid == 1 && print("dots out of 10: ") todo, done, pctg = length(test_elements), 0, 0 - for (p,tcell) in enumerate(test_elements) - for (q,bcell) in enumerate(trial_elements) + for (p,(tcell,tptr)) in enumerate(zip(test_elements, test_cell_ptrs)) + for (q,(bcell,bptr)) in enumerate(zip(trial_elements, trial_cell_ptrs)) fill!(zlocal, 0) - strat = quadrule(biop, test_shapes, trial_shapes, p, tcell, q, bcell, qd) - momintegrals!(biop, test_shapes, trial_shapes, tcell, bcell, zlocal, strat) + qrule = quadrule(biop, test_shapes, trial_shapes, p, tcell, q, bcell, qd, quadstrat) + momintegrals!(zlocal, biop, + test_space, tptr, tcell, + trial_space, bptr, bcell, qrule) I = length(test_assembly_data[p]) J = length(trial_assembly_data[q]) for j in 1 : J, i in 1 : I @@ -111,77 +159,86 @@ function assemblechunk_body!(biop, for (n,b) in trial_assembly_data[q][j] zb = zij*b for (m,a) in test_assembly_data[p][i] - azb = a*zb - store(azb, m, n) + store(a*zb, m, n) end end end end done += 1 new_pctg = round(Int, done / todo * 100) if new_pctg > pctg + 9 - myid == 1 && print(".") + verbose && myid == 1 && print(".") pctg = new_pctg end end - myid == 1 && println("") + verbose && myid == 1 && println("") end -function assemblechunk_body_nested_meshes!(biop, - test_shapes, test_elements, test_assembly_data, - trial_shapes, trial_elements, trial_assembly_data, - qd, zlocal, store) - myid = Threads.threadid() - myid == 1 && print("dots out of 10: ") - todo, done, pctg = length(test_elements), 0, 0 - for (p,tcell) in enumerate(test_elements) - for (q,bcell) in enumerate(trial_elements) - fill!(zlocal, 0) - strat = quadrule(biop, test_shapes, trial_shapes, p, tcell, q, bcell, qd) - momintegrals_nested!(biop, test_shapes, trial_shapes, tcell, bcell, zlocal, strat) - I = length(test_assembly_data[p]) - J = length(trial_assembly_data[q]) - for j in 1 : J, i in 1 : I - for (n,b) in trial_assembly_data[q][j], (m,a) in test_assembly_data[p][i] - store(a*zlocal[i,j]*b, m, n) - end end end - done += 1 - new_pctg = round(Int, done / todo * 100) - if new_pctg > pctg + 9 - myid == 1 && print(".") - pctg = new_pctg - end end - myid == 1 && println("") -end +function blockassembler(biop::IntegralOperator, tfs::Space, bfs::Space; + quadstrat=defaultquadstrat(biop, tfs, bfs)) + tgeo = geometry(tfs) + bgeo = geometry(bfs) -function blockassembler(biop::IntegralOperator, tfs::Space, bfs::Space) + qs = if CompScienceMeshes.refines(tgeo, bgeo) + TestRefinesTrialQStrat(quadstrat) + elseif CompScienceMeshes.refines(bgeo, tgeo) + TrialRefinesTestQStrat(quadstrat) + else + quadstrat + end test_elements, test_assembly_data, trial_elements, trial_assembly_data, - quadrature_data, zlocal = assembleblock_primer(biop, tfs, bfs) + quadrature_data, zlocals = assembleblock_primer(biop, tfs, bfs; quadstrat=qs) - return function f(test_ids, trial_ids, store) + return (test_ids, trial_ids, store) -> assembleblock_body!(biop, tfs, test_ids, test_elements, test_assembly_data, bfs, trial_ids, trial_elements, trial_assembly_data, - quadrature_data, zlocal, store) - end + quadrature_data, zlocals, store; quadstrat=qs) + + # if CompScienceMeshes.refines(tgeo, bgeo) + # return (test_ids, trial_ids, store) -> begin + # assembleblock_body_test_refines_trial!(biop, + # tfs, test_ids, test_elements, test_assembly_data, + # bfs, trial_ids, trial_elements, trial_assembly_data, + # quadrature_data, zlocals, store; quadstrat) + # end + # elseif CompScienceMeshes.refines(bgeo, tgeo) + # return (test_ids, trial_ids, store) -> begin + # assembleblock_body_trial_refines_test!(biop, + # tfs, test_ids, test_elements, test_assembly_data, + # bfs, trial_ids, trial_elements, trial_assembly_data, + # quadrature_data, zlocals, store; quadstrat) + # end + # else + # return (test_ids, trial_ids, store) -> begin + # assembleblock_body!(biop, + # tfs, test_ids, test_elements, test_assembly_data, + # bfs, trial_ids, trial_elements, trial_assembly_data, + # quadrature_data, zlocals, store; quadstrat) + # end + # end end -function assembleblock(operator::AbstractOperator, test_functions, trial_functions) +function assembleblock(operator::AbstractOperator, test_functions, trial_functions; + quadstrat=defaultquadstrat(operator, test_functions, trial_functions)) + Z, store = allocatestorage(operator, test_functions, trial_functions) - assembleblock!(operator, test_functions, trial_functions, store) + assembleblock!(operator, test_functions, trial_functions, store; quadstrat) + sdata(Z) end -function assembleblock!(biop::IntegralOperator, tfs::Space, bfs::Space, store) +function assembleblock!(biop::IntegralOperator, tfs::Space, bfs::Space, store; + quadstrat=defaultquadstrat(biop, tfs, bfs)) - test_elements, tad, trial_elements, bad, quadrature_data, zlocal = - assembleblock_primer(biop, tfs, bfs) + test_elements, tad, trial_elements, bad, quadrature_data, zlocals = + assembleblock_primer(biop, tfs, bfs; quadstrat) active_test_dofs = collect(1:numfunctions(tfs)) active_trial_dofs = collect(1:numfunctions(bfs)) @@ -189,29 +246,40 @@ function assembleblock!(biop::IntegralOperator, tfs::Space, bfs::Space, store) assembleblock_body!(biop, tfs, active_test_dofs, test_elements, tad, bfs, active_trial_dofs, trial_elements, bad, - quadrature_data, zlocal, store) + quadrature_data, zlocals, store; quadstrat) end -function assembleblock_primer(biop, tfs, bfs) +function assembleblock_primer(biop, tfs, bfs; + quadstrat=defaultquadstrat(biop, tfs, bfs)) + + test_elements, tad = assemblydata(tfs; onlyactives=false) + bsis_elements, bad = assemblydata(bfs; onlyactives=false) - test_elements, tad = assemblydata(tfs) - bsis_elements, bad = assemblydata(bfs) + tgeo = geometry(tfs) + bgeo = geometry(bfs) - tshapes = refspace(tfs); num_tshapes = numfunctions(tshapes) - bshapes = refspace(bfs); num_bshapes = numfunctions(bshapes) + tdom = domain(chart(tgeo, first(tgeo))) + bdom = domain(chart(bgeo, first(bgeo))) - qd = quaddata(biop, tshapes, bshapes, test_elements, bsis_elements) - zlocal = zeros(scalartype(biop, tfs, bfs), num_tshapes, num_bshapes) + tshapes = refspace(tfs); num_tshapes = numfunctions(tshapes, tdom) + bshapes = refspace(bfs); num_bshapes = numfunctions(bshapes, bdom) - return test_elements, tad, bsis_elements, bad, qd, zlocal + qd = quaddata(biop, tshapes, bshapes, test_elements, bsis_elements, quadstrat) + zlocals = Matrix{scalartype(biop, tfs, bfs)}[] + + for i in 1:Threads.nthreads() + push!(zlocals, zeros(scalartype(biop, tfs, bfs), num_tshapes, num_bshapes)) + end + + return test_elements, tad, bsis_elements, bad, qd, zlocals end function assembleblock_body!(biop::IntegralOperator, tfs, test_ids, test_elements, test_assembly_data, bfs, trial_ids, bsis_elements, trial_assembly_data, - quadrature_data, zlocal, store) + quadrature_data, zlocals, store; quadstrat) test_shapes = refspace(tfs) trial_shapes = refspace(bfs) @@ -220,8 +288,6 @@ function assembleblock_body!(biop::IntegralOperator, active_test_el_ids = Vector{Int}() active_trial_el_ids = Vector{Int}() - #test_id_in_blk = zeros(Int, numfunctions(tfs)) - #trial_id_in_blk = zeros(Int, numfunctions(bfs)) test_id_in_blk = Dict{Int,Int}() trial_id_in_blk = Dict{Int,Int}() @@ -231,42 +297,211 @@ function assembleblock_body!(biop::IntegralOperator, for m in test_ids, sh in tfs.fns[m]; push!(active_test_el_ids, sh.cellid); end for m in trial_ids, sh in bfs.fns[m]; push!(active_trial_el_ids, sh.cellid); end - active_test_el_ids = unique(sort(active_test_el_ids)) - active_trial_el_ids = unique(sort(active_trial_el_ids)) + active_test_el_ids = unique!(sort!(active_test_el_ids)) + active_trial_el_ids = unique!(sort!(active_trial_el_ids)) + + @assert length(active_test_el_ids) <= length(test_elements) + @assert length(active_trial_el_ids) <= length(bsis_elements) + + @assert maximum(active_test_el_ids) <= length(test_elements) "$(maximum(active_test_el_ids)), $(length(test_elements))" + @assert maximum(active_trial_el_ids) <= length(bsis_elements) "$(maximum(active_trial_el_ids)), $(length(bsis_elements))" for p in active_test_el_ids tcell = test_elements[p] for q in active_trial_el_ids bcell = bsis_elements[q] - fill!(zlocal, 0) - strat = quadrule(biop, test_shapes, trial_shapes, p, tcell, q, bcell, quadrature_data) - momintegrals!(biop, test_shapes, trial_shapes, tcell, bcell, zlocal, strat) + fill!(zlocals[Threads.threadid()], 0) + qrule = quadrule(biop, test_shapes, trial_shapes, p, tcell, q, bcell, quadrature_data, quadstrat) + momintegrals!(zlocals[Threads.threadid()], biop, + tfs, p, tcell, + bfs, q, bcell, qrule) - for j in 1 : size(zlocal,2) - for i in 1 : size(zlocal,1) + for j in 1 : size(zlocals[Threads.threadid()],2) + for i in 1 : size(zlocals[Threads.threadid()],1) for (n,b) in trial_assembly_data[q,j] n′ = get(trial_id_in_blk, n, 0) n′ == 0 && continue for (m,a) in test_assembly_data[p,i] m′ = get(test_id_in_blk, m, 0) m′ == 0 && continue - store(a*zlocal[i,j]*b, m′, n′) + store(a*zlocals[Threads.threadid()][i,j]*b, m′, n′) end end end end end end end - -function assemblerow!(biop::IntegralOperator, test_functions::Space, trial_functions::Space, store) - - test_elements = elements(geometry(test_functions)) +# function assembleblock_body_trial_refines_test!(biop::IntegralOperator, +# tfs, test_ids, test_elements, test_assembly_data, +# bfs, trial_ids, bsis_elements, trial_assembly_data, +# quadrature_data, zlocals, store; quadstrat) + +# test_shapes = refspace(tfs) +# trial_shapes = refspace(bfs) + +# # Enumerate all the active test elements +# active_test_el_ids = Vector{Int}() +# active_trial_el_ids = Vector{Int}() + +# test_id_in_blk = Dict{Int,Int}() +# trial_id_in_blk = Dict{Int,Int}() + +# for (i,m) in enumerate(test_ids); test_id_in_blk[m] = i; end +# for (i,m) in enumerate(trial_ids); trial_id_in_blk[m] = i; end + +# for m in test_ids, sh in tfs.fns[m]; push!(active_test_el_ids, sh.cellid); end +# for m in trial_ids, sh in bfs.fns[m]; push!(active_trial_el_ids, sh.cellid); end + +# active_test_el_ids = unique!(sort!(active_test_el_ids)) +# active_trial_el_ids = unique!(sort!(active_trial_el_ids)) + +# @assert length(active_test_el_ids) <= length(test_elements) +# @assert length(active_trial_el_ids) <= length(bsis_elements) + +# @assert maximum(active_test_el_ids) <= length(test_elements) "$(maximum(active_test_el_ids)), $(length(test_elements))" +# @assert maximum(active_trial_el_ids) <= length(bsis_elements) "$(maximum(active_trial_el_ids)), $(length(bsis_elements))" + +# for p in active_test_el_ids +# tcell = test_elements[p] +# for q in active_trial_el_ids +# bcell = bsis_elements[q] + +# fill!(zlocals[Threads.threadid()], 0) +# qrule = quadrule(biop, test_shapes, trial_shapes, p, tcell, q, bcell, quadrature_data, quadstrat) +# momintegrals_trial_refines_test!(zlocals[Threads.threadid()], biop, +# tfs, p, tcell, +# bfs, q, bcell, +# qrule, quadstrat) +# for j in 1 : size(zlocals[Threads.threadid()],2) +# for i in 1 : size(zlocals[Threads.threadid()],1) +# for (n,b) in trial_assembly_data[q,j] +# n′ = get(trial_id_in_blk, n, 0) +# n′ == 0 && continue +# for (m,a) in test_assembly_data[p,i] +# m′ = get(test_id_in_blk, m, 0) +# m′ == 0 && continue +# store(a*zlocals[Threads.threadid()][i,j]*b, m′, n′) +# end end end end end end end + +# function assembleblock_body_test_refines_trial!(biop::IntegralOperator, +# tfs, test_ids, test_elements, test_assembly_data, +# bfs, trial_ids, bsis_elements, trial_assembly_data, +# quadrature_data, zlocals, store; quadstrat) + +# test_shapes = refspace(tfs) +# trial_shapes = refspace(bfs) + +# # Enumerate all the active test elements +# active_test_el_ids = Vector{Int}() +# active_trial_el_ids = Vector{Int}() + +# test_id_in_blk = Dict{Int,Int}() +# trial_id_in_blk = Dict{Int,Int}() + +# for (i,m) in enumerate(test_ids); test_id_in_blk[m] = i; end +# for (i,m) in enumerate(trial_ids); trial_id_in_blk[m] = i; end + +# for m in test_ids, sh in tfs.fns[m]; push!(active_test_el_ids, sh.cellid); end +# for m in trial_ids, sh in bfs.fns[m]; push!(active_trial_el_ids, sh.cellid); end + +# active_test_el_ids = unique!(sort!(active_test_el_ids)) +# active_trial_el_ids = unique!(sort!(active_trial_el_ids)) + +# @assert length(active_test_el_ids) <= length(test_elements) +# @assert length(active_trial_el_ids) <= length(bsis_elements) + +# @assert maximum(active_test_el_ids) <= length(test_elements) "$(maximum(active_test_el_ids)), $(length(test_elements))" +# @assert maximum(active_trial_el_ids) <= length(bsis_elements) "$(maximum(active_trial_el_ids)), $(length(bsis_elements))" + +# for p in active_test_el_ids +# tcell = test_elements[p] +# for q in active_trial_el_ids +# bcell = bsis_elements[q] + +# fill!(zlocals[Threads.threadid()], 0) +# qrule = quadrule(biop, test_shapes, trial_shapes, p, tcell, q, bcell, quadrature_data, quadstrat) +# momintegrals_test_refines_trial!(zlocals[Threads.threadid()], biop, +# tfs, p, tcell, +# bfs, q, bcell, +# qrule, quadstrat) +# for j in 1 : size(zlocals[Threads.threadid()],2) +# for i in 1 : size(zlocals[Threads.threadid()],1) +# for (n,b) in trial_assembly_data[q,j] +# n′ = get(trial_id_in_blk, n, 0) +# n′ == 0 && continue +# for (m,a) in test_assembly_data[p,i] +# m′ = get(test_id_in_blk, m, 0) +# m′ == 0 && continue +# store(a*zlocals[Threads.threadid()][i,j]*b, m′, n′) +# end end end end end end end + + +# function assembleblock_body_nested!(biop::IntegralOperator, +# tfs, test_ids, test_elements, test_assembly_data, +# bfs, trial_ids, bsis_elements, trial_assembly_data, +# quadrature_data, zlocals, store; quadstrat) + +# test_shapes = refspace(tfs) +# trial_shapes = refspace(bfs) + +# # Enumerate all the active test elements +# active_test_el_ids = Vector{Int}() +# active_trial_el_ids = Vector{Int}() + +# test_id_in_blk = Dict{Int,Int}() +# trial_id_in_blk = Dict{Int,Int}() + +# for (i,m) in enumerate(test_ids); test_id_in_blk[m] = i; end +# for (i,m) in enumerate(trial_ids); trial_id_in_blk[m] = i; end + +# for m in test_ids, sh in tfs.fns[m]; push!(active_test_el_ids, sh.cellid); end +# for m in trial_ids, sh in bfs.fns[m]; push!(active_trial_el_ids, sh.cellid); end + +# active_test_el_ids = unique(sort(active_test_el_ids)) +# active_trial_el_ids = unique(sort(active_trial_el_ids)) + +# for p in active_test_el_ids +# tcell = test_elements[p] +# for q in active_trial_el_ids +# bcell = bsis_elements[q] + +# fill!(zlocals[Threads.threadid()], 0) +# qrule = quadrule(biop, test_shapes, trial_shapes, p, tcell, q, bcell, quadrature_data, quadstrat) +# momintegrals_test_refines_trial!(zlocals[Threads.threadid()], biop, +# tfs, p, tcell, +# bfs, q, bcell, +# qrule, quadstrat) +# # momintegrals_test_refines_trial!(biop, test_shapes, trial_shapes, tcell, bcell, zlocals[Threads.threadid()], qrule, quadstrat) + +# for j in 1 : size(zlocals[Threads.threadid()],2) +# for i in 1 : size(zlocals[Threads.threadid()],1) +# for (n,b) in trial_assembly_data[q,j] +# n′ = get(trial_id_in_blk, n, 0) +# n′ == 0 && continue +# for (m,a) in test_assembly_data[p,i] +# m′ = get(test_id_in_blk, m, 0) +# m′ == 0 && continue +# store(a*zlocals[Threads.threadid()][i,j]*b, m′, n′) +# end end end end end end end + + +function assemblerow!(biop::IntegralOperator, test_functions::Space, trial_functions::Space, store; + quadstrat=defaultquadstrat(biop, test_functions, trial_functions)) + + tgeo = geometry(test_functions) + bgeo = geometry(trial_functions) + + tdom = domain(chart(tgeo, first(tgeo))) + bdom = domain(chart(bgeo, first(bgeo))) + + test_elements = elements(tgeo) trial_elements, trial_assembly_data = assemblydata(trial_functions) test_shapes = refspace(test_functions) trial_shapes = refspace(trial_functions) - num_test_shapes = numfunctions(test_shapes) - num_trial_shapes = numfunctions(trial_shapes) + num_test_shapes = numfunctions(test_shapes, tdom) + num_trial_shapes = numfunctions(trial_shapes, bdom) - quadrature_data = quaddata(biop, test_shapes, trial_shapes, test_elements, trial_elements) + quadrature_data = quaddata(biop, test_shapes, trial_shapes, test_elements, trial_elements, + quadstrat) zlocal = zeros(scalartype(biop, test_functions, trial_functions), num_test_shapes, num_trial_shapes) @@ -275,15 +510,15 @@ function assemblerow!(biop::IntegralOperator, test_functions::Space, trial_funct assemblerow_body!(biop, test_functions, test_elements, test_shapes, - trial_assembly_data, trial_elements, trial_shapes, - zlocal, quadrature_data, store) + trial_assembly_data, trial_functions, trial_elements, trial_shapes, + zlocal, quadrature_data, store; quadstrat) end function assemblerow_body!(biop, test_functions, test_elements, test_shapes, - trial_assembly_data, trial_elements, trial_shapes, - zlocal, quadrature_data, store) + trial_assembly_data, trial_functions, trial_elements, trial_shapes, + zlocal, quadrature_data, store; quadstrat) test_function = test_functions.fns[1] for shape in test_function @@ -294,8 +529,11 @@ function assemblerow_body!(biop, for (q,bcell) in enumerate(trial_elements) fill!(zlocal, 0) - strat = quadrule(biop, test_shapes, trial_shapes, p, tcell, q, bcell, quadrature_data) - momintegrals!(biop, test_shapes, trial_shapes, tcell, bcell, zlocal, strat) + qrule = quadrule(biop, test_shapes, trial_shapes, p, tcell, q, bcell, quadrature_data, quadstrat) + momintegrals!(zlocal, biop, + test_functions, nothing, tcell, + trial_functions, nothing, bcell, + qrule) for j in 1:size(zlocal,2) for (n,b) in trial_assembly_data[q,j] @@ -303,7 +541,8 @@ function assemblerow_body!(biop, end end end end end -function assemblecol!(biop::IntegralOperator, test_functions::Space, trial_functions::Space, store) +function assemblecol!(biop::IntegralOperator, test_functions::Space, trial_functions::Space, store; + quadstrat=defaultquadstrat(biop, test_functions, trial_functions)) test_elements, test_assembly_data = assemblydata(test_functions) trial_elements = elements(geometry(trial_functions)) @@ -314,7 +553,7 @@ function assemblecol!(biop::IntegralOperator, test_functions::Space, trial_funct num_test_shapes = numfunctions(test_shapes) num_trial_shapes = numfunctions(trial_shapes) - quadrature_data = quaddata(biop, test_shapes, trial_shapes, test_elements, trial_elements) + quadrature_data = quaddata(biop, test_shapes, trial_shapes, test_elements, trial_elements, quadstrat) zlocal = zeros( scalartype(biop, test_functions, trial_functions), num_test_shapes, num_trial_shapes) @@ -323,16 +562,16 @@ function assemblecol!(biop::IntegralOperator, test_functions::Space, trial_funct @assert numfunctions(trial_functions) == 1 assemblecol_body!(biop, - test_assembly_data, test_elements, test_shapes, + test_assembly_data, test_functions, test_elements, test_shapes, trial_functions, trial_elements, trial_shapes, - zlocal, quadrature_data, store) + zlocal, quadrature_data, store; quadstrat) end function assemblecol_body!(biop, - test_assembly_data, test_elements, test_shapes, + test_assembly_data, test_functions, test_elements, test_shapes, trial_functions, trial_elements, trial_shapes, - zlocal, quadrature_data, store) + zlocal, quadrature_data, store; quadstrat) trial_function = trial_functions.fns[1] for shape in trial_function @@ -344,8 +583,10 @@ function assemblecol_body!(biop, for (p,tcell) in enumerate(test_elements) fill!(zlocal, 0) - strat = quadrule(biop, test_shapes, trial_shapes, p, tcell, q, bcell, quadrature_data) - momintegrals!(biop, test_shapes, trial_shapes, tcell, bcell, zlocal, strat) + qrule = quadrule(biop, test_shapes, trial_shapes, p, tcell, q, bcell, quadrature_data, quadstrat) + momintegrals!(zlocal, biop, + test_functions, nothing, tcell, + trial_functions, nothing, bcell, qrule) for i in 1:size(zlocal,1) for (m,a) in test_assembly_data[p,i] @@ -354,63 +595,3 @@ end end end end end - -# mutable struct SauterSchwabStrategy -# hits::Int64 -# end -# function momintegrals!(biop, tshs::subReferenceSpace, bshs::subReferenceSpace, tcell, bcell, z, strat::SauterSchwabStrategy) -# -# A = biop.alpha -# k = biop.gamma -# -# M, N = size(z) -# -# hits = strat.hits -# print(hits) -# acc = 3 -# -# if hits == 0 -# ssm = PositiveDistance(acc) -# elseif hits == 1 -# ssm = CommonVertex(acc) -# elseif hits == 2 -# ssm = CommonEdge(acc) -# elseif hits == 3 -# ssm = CommonFace(acc) -# else -# error("hits can not exceed 3") -# end -# M= tcell.N -# N= bcell.N -# print("M = $(M) and N = $(N) \n") -# z = Array{Complex{Float64},2}(M,N) -# for i = 1:M -# for j = 1:N -# # print("i = $(i) and j = $(j) \n") -# function integrand(u,v) -# upt = neighborhood(tcell,u) -# vpt = neighborhood(bcell,v) -# tshape = shapefuns(upt) -# bshape = shapefuns(vpt) -# y = cartesian(upt) -# x = cartesian(vpt) -# kernel = A * exp(-complex(0,1)*k*norm(x-y))/(4.0*π*norm(x-y)) -# # kernel = kernelvals(biop, upt, vpt) -# ujac = jacobian(upt) -# vjac = jacobian(vpt) -# return tshape[i]*bshape[j]*kernel* ujac * vjac -# # return kernel* ujac * vjac -# end -# z[i,j] = (sauterschwab_parameterized(tcell,bcell,integrand,ssm)) -# end -# end -# -# return z -# end - - - -# mutable struct QuadData{WPV1,WPV2} -# tpoints::Matrix{Vector{WPV1}} -# bpoints::Matrix{Vector{WPV2}} -# end diff --git a/src/interpolation.jl b/src/interpolation.jl new file mode 100644 index 00000000..a20a69e9 --- /dev/null +++ b/src/interpolation.jl @@ -0,0 +1,344 @@ + +function DofInterpolate(basis::LagrangeBasis, field) + + T = promote_type(scalartype(basis), scalartype(field)) + + num_bfs = numfunctions(basis) + + res = Vector{T}(undef, num_bfs) + + for b in 1 : num_bfs + bfs = basis.fns[b] + + shape = bfs[1] + + cellid = shape.cellid + refid = shape.refid + + cell = cells(basis.geo)[cellid] + + tria = chart(basis.geo, cellid) + + vid = cell[refid] + + if refid == 1 + v = neighborhood(tria, [1, 0]) + elseif refid == 2 + v = neighborhood(tria, [0, 1]) + else + v = neighborhood(tria, [0, 0]) + end + + res[b] = field(v) + end + + return res +end + +### Piecewise constant elements require separate treatment +### TODO: Probably a rewrite is advisable to also take into account +### dual elements properly. +function DofInterpolate(basis::LagrangeBasis{0,-1,M,T,NF,P}, field) where {M, T, NF, P} + + TT = promote_type(scalartype(basis), scalartype(field)) + + num_bfs = numfunctions(basis) + + res = Vector{TT}(undef, num_bfs) + + for b in 1 : num_bfs + bfs = basis.fns[b] + + basis.pos[b] + + shape = bfs[1] + + cellid = shape.cellid + + tria = chart(basis.geo, cellid) + + v = neighborhood(tria, [1/3, 1/3]) + + res[b] = field(v) + end + + return res +end + +function DofInterpolate(basis::RTBasis, field) + + T = promote_type(scalartype(basis), scalartype(field)) + + num_bfs = numfunctions(basis) + + res = Vector{T}(undef, num_bfs) + + for b in 1 : num_bfs + + bfs = basis.fns[b] + + shape = bfs[1] + + cellid = shape.cellid + refid = shape.refid + coeff = shape.coeff + + cell = cells(basis.geo)[cellid] + + e = refid + + v1 = cell[mod1(e+1,3)] + v2 = cell[mod1(e+2,3)] + + edge = simplex(basis.geo.vertices[[v1,v2]]...) + t = tangents(center(edge),1) + tria = chart(basis.geo, cellid) + + n = normal(center(tria)) + + bn = normalize(cross(n,t)) + + v = center(edge) + + res[b] = volume(edge)*coeff*dot(field(v),bn) + + end + + return [r for r in res] + +end + +function DofInterpolate(basis::BDMBasis, field) + + T = promote_type(scalartype(basis), scalartype(field)) + + num_bfs = numfunctions(basis) + + res = Vector{T}(undef, num_bfs) + + for b in 1 : num_bfs + + bfs = basis.fns[b] + + shape = bfs[2] + + cellid = shape.cellid + refid = shape.refid + coeff = shape.coeff + + cell = cells(basis.geo)[cellid] + + e = (refid-1)÷2+1 + + v1 = cell[mod1(e+1,3)] + v2 = cell[mod1(e+2,3)] + + edge = simplex(basis.geo.vertices[[v1,v2]]...) + t = tangents(center(edge),1) + tria = chart(basis.geo, cellid) + + n = normal(center(tria)) + + bn = normalize(cross(n,t)) + + if refid == 1 + v = neighborhood(tria, [0, 1]) + elseif refid == 2 + v = neighborhood(tria, [0, 0]) + elseif refid == 3 + v = neighborhood(tria, [0, 0]) + elseif refid == 4 + v = neighborhood(tria, [1, 0]) + elseif refid == 5 + v = neighborhood(tria, [1, 0]) + else + v = neighborhood(tria, [0, 1]) + end + + res[b] = volume(edge)*coeff*dot(field(v),bn) + + end + + return res + +end + +function DofInterpolate(basis::NDLCDBasis, field) + + T = promote_type(scalartype(basis), scalartype(field)) + + num_bfs = numfunctions(basis) + + res = Vector{T}(undef, num_bfs) + + for b in 1 : num_bfs + + bfs = basis.fns[b] + + shape = bfs[1] + + cellid = shape.cellid + refid = shape.refid + coeff = shape.coeff + + cell = cells(basis.geo)[cellid] + + f = refid + + v1 = cell[mod1(f+1,4)] + v2 = cell[mod1(f+2,4)] + v3 = cell[mod1(f+3,4)] + + v4 = cell[mod1(f,4)] + + face = simplex(basis.geo.vertices[[v1,v2,v3]]...) + + n = normal(center(face)) + + inside = dot(n, basis.geo.vertices[v4]-basis.geo.vertices[v1]) + + n *= -sign(inside) + + v = center(face) + + res[b] = volume(face)*coeff*dot(field(v),n) + + end + + return res + +end + +function DofInterpolate(basis::BEAST.BDM3DBasis, field) + + T = promote_type(scalartype(basis), scalartype(field)) + + num_bfs = numfunctions(basis) + + res = Vector{T}(undef, num_bfs) + + for b in 1 : num_bfs + + bfs = basis.fns[b] + + shape = bfs[1] + + cellid = shape.cellid + refid = shape.refid + coeff = shape.coeff + + cell = cells(basis.geo)[cellid] + + f = (refid-1)÷3+1 + + v1 = cell[mod1(f+1,4)] + v2 = cell[mod1(f+2,4)] + v3 = cell[mod1(f+3,4)] + + v4 = cell[mod1(f,4)] + + face = simplex(basis.geo.vertices[[v1,v2,v3]]...) + + n = normal(center(face)) + + inside = dot(n, basis.geo.vertices[v4]-basis.geo.vertices[v1]) + + n *= -sign(inside) + + tet = simplex(basis.geo.vertices[cell]...) + + if refid == 1 + v = neighborhood(tet, [0, 1, 0]) + elseif refid == 2 + v = neighborhood(tet, [0, 0, 1]) + elseif refid == 3 + v = neighborhood(tet, [0, 0, 0]) + elseif refid == 4 + v = neighborhood(tet, [0, 0, 1]) + elseif refid == 5 + v = neighborhood(tet, [0, 0, 0]) + elseif refid == 6 + v = neighborhood(tet, [1, 0, 0]) + elseif refid == 7 + v = neighborhood(tet, [0, 0, 0]) + elseif refid == 8 + v = neighborhood(tet, [1, 0, 0]) + elseif refid == 9 + v = neighborhood(tet, [0, 1, 0]) + elseif refid == 10 + v = neighborhood(tet, [1, 0, 0]) + elseif refid == 11 + v = neighborhood(tet, [0, 1, 0]) + else + v = neighborhood(tet, [0, 0, 1]) + end + + res[b] = volume(face)*coeff*dot(field(v),n) + + end + + return res + +end + +function Centervalues(mesh::Mesh,f::Functional) + num_cells = numcells(mesh) + T = coordtype(mesh) + res = Vector{SVector{3,Complex{T}}}(undef, num_cells) + + for i in 1:num_cells + cell = cells(mesh)[i] + + tet = chart(mesh, cell) + + v = center(tet) + + res[i] = f(v) + + end + + return res + +end + +function EvalCenter(basis::Space, coeff) + + mesh = basis.geo + T = coordtype(mesh) + num_cells = numcells(mesh) + num_bfs = numfunctions(basis) + ref_space = refspace(basis) + + res = Vector{SVector{3,Complex{T}}}(undef, num_cells) + + for i in 1:num_cells + res[i] = [0,0,0] + end + + for b in 1 : num_bfs + + bfs = basis.fns[b] + + for shape in bfs + + + cellid = shape.cellid + refid = shape.refid + a = shape.coeff + + cell = cells(mesh)[cellid] + + tet = chart(mesh, cell) + + v = center(tet) + + local_bfs = ref_space(v) + + res[cellid] += a*coeff[b]*local_bfs[refid].value + end + + end + + return res + +end diff --git a/src/localop.jl b/src/localop.jl index 1b9a62e5..bebc1725 100644 --- a/src/localop.jl +++ b/src/localop.jl @@ -2,16 +2,12 @@ using CollisionDetection -# mutable struct SingleQuadStrategy{T} -# coords::Vector{T} -# weights::Vector{T} -# end abstract type LocalOperator <: Operator end function allocatestorage(op::LocalOperator, test_functions, trial_functions, - storage_trait::Type{Val{:bandedstorage}}, longdelays_trait) + storage_trait::Type{Val{:bandedstorage}}) T = scalartype(op, test_functions, trial_functions) @@ -35,13 +31,13 @@ function allocatestorage(op::LocalOperator, test_functions, trial_functions, end function allocatestorage(op::LocalOperator, testfunctions, trialfunctions, - storage_trait::Type{Val{:sparsedicts}}, longdelays_trait) + storage_trait::Type{Val{:sparsedicts}}) T = scalartype(op, testfunctions, trialfunctions) m = numfunctions(testfunctions) n = numfunctions(trialfunctions) - Z = SparseMatrixDict{T,Int}(m,n) + Z = ExtendableSparseMatrix(T,m,n) store(v,m,n) = (Z[m,n] += v) freeze() = SparseArrays.SparseMatrixCSC(Z) @@ -50,7 +46,7 @@ function allocatestorage(op::LocalOperator, testfunctions, trialfunctions, end function allocatestorage(op::LocalOperator, test_functions, trial_functions, - storage_trait::Type{Val{:densestorage}}, longdelays_trait) + storage_trait::Type{Val{:densestorage}}) T = scalartype(op, test_functions, trial_functions) @@ -62,20 +58,44 @@ function allocatestorage(op::LocalOperator, test_functions, trial_functions, end function assemble!(biop::LocalOperator, tfs::Space, bfs::Space, store, - threading::Type{Threading{:multi}}) + threading::Type{Threading{:multi}}; + quadstrat=defaultquadstrat) + + quadstrat = quadstrat(biop, tfs, bfs) + + numfunctions(tfs) == 0 && return + numfunctions(bfs) == 0 && return + + if geometry(tfs) == geometry(bfs) + return assemble_local_matched!(biop, tfs, bfs, store; quadstrat) + end + + if CompScienceMeshes.refines(geometry(tfs), geometry(bfs)) + return assemble_local_refines!(biop, tfs, bfs, store; quadstrat) + end + + return assemble_local_mixed!(biop, tfs, bfs, store; quadstrat) +end + +function assemble!(biop::LocalOperator, tfs::Space, bfs::Space, store, + threading::Type{Threading{:single}}; + quadstrat=defaultquadstrat) + + quadstrat = quadstrat(biop, tfs, bfs) if geometry(tfs) == geometry(bfs) - return assemble_local_matched!(biop, tfs, bfs, store) + return assemble_local_matched!(biop, tfs, bfs, store; quadstrat) end if CompScienceMeshes.refines(geometry(tfs), geometry(bfs)) - return assemble_local_refines!(biop, tfs, bfs, store) + return assemble_local_refines!(biop, tfs, bfs, store; quadstrat) end - return assemble_local_mixed!(biop, tfs, bfs, store) + return assemble_local_mixed!(biop, tfs, bfs, store; quadstrat) end -function assemble_local_matched!(biop::LocalOperator, tfs::Space, bfs::Space, store) +function assemble_local_matched!(biop::LocalOperator, tfs::Space, bfs::Space, store; + quadstrat=defaultquadstrat(biop, tfs, bfs)) tels, tad, ta2g = assemblydata(tfs) bels, bad, ba2g = assemblydata(bfs) @@ -86,26 +106,46 @@ function assemble_local_matched!(biop::LocalOperator, tfs::Space, bfs::Space, st trefs = refspace(tfs) brefs = refspace(bfs) - qd = quaddata(biop, trefs, brefs, tels, bels) - locmat = zeros(scalartype(biop, trefs, brefs), numfunctions(trefs), numfunctions(brefs)) + tgeo = geometry(tfs) + bgeo = geometry(bfs) + + tdom = domain(chart(tgeo, first(tgeo))) + bdom = domain(chart(bgeo, first(bgeo))) + + num_trefs = numfunctions(trefs, tdom) + num_brefs = numfunctions(brefs, bdom) + + qd = quaddata(biop, trefs, brefs, tels, bels, quadstrat) + + verbose = length(tels) > 10_000 + verbose && print("dots out of 20: ") + todo, done, pctg = length(tels), 0, 0 + locmat = zeros(scalartype(biop, trefs, brefs), num_trefs, num_brefs) for (p,cell) in enumerate(tels) P = ta2g[p] q = bg2a[P] q == 0 && continue - qr = quadrule(biop, trefs, brefs, cell, qd) + qr = quadrule(biop, trefs, brefs, cell, qd, quadstrat) fill!(locmat, 0) cellinteractions_matched!(locmat, biop, trefs, brefs, cell, qr) for i in 1 : size(locmat, 1), j in 1 : size(locmat, 2) for (m,a) in tad[p,i], (n,b) in bad[q,j] store(a * locmat[i,j] * b, m, n) -end end end end + + end end + + new_pctg = round(Int, (done += 1) / todo * 100) + verbose && new_pctg > pctg + 4 && (print("."); pctg = new_pctg) + end +end -function assemble_local_refines!(biop::LocalOperator, tfs::Space, bfs::Space, store) +function assemble_local_refines!(biop::LocalOperator, tfs::Space, bfs::Space, store; + quadstrat=defaultquadstrat(biop, tfs, bfs)) - println("Using 'refines' algorithm for local assembly:") + # println("Using 'refines' algorithm for local assembly:") tgeo = geometry(tfs) bgeo = geometry(bfs) @@ -114,13 +154,22 @@ function assemble_local_refines!(biop::LocalOperator, tfs::Space, bfs::Space, st trefs = refspace(tfs) brefs = refspace(bfs) + tgeo = geometry(tfs) + bgeo = geometry(bfs) + + tdom = domain(chart(tgeo, first(tgeo))) + bdom = domain(chart(bgeo, first(bgeo))) + + num_trefs = numfunctions(trefs, tdom) + num_brefs = numfunctions(brefs, bdom) + tels, tad, ta2g = assemblydata(tfs) bels, bad, ba2g = assemblydata(bfs) bg2a = zeros(Int, length(geometry(bfs))) for (i,j) in enumerate(ba2g) bg2a[j] = i end - qd = quaddata(biop, trefs, brefs, tels, bels) + qd = quaddata(biop, trefs, brefs, tels, bels, quadstrat) print("dots out of 10: ") todo, done, pctg = length(tels), 0, 0 @@ -139,12 +188,12 @@ function assemble_local_refines!(biop::LocalOperator, tfs::Space, bfs::Space, st P = restrict(brefs, bcell, cell) Q = restrict(trefs, tcell, cell) - qr = quadrule(biop, trefs, brefs, cell, qd) + qr = quadrule(biop, trefs, brefs, cell, qd, quadstrat) zlocal = cellinteractions(biop, trefs, brefs, cell, qr) zlocal = Q * zlocal * P' - for i in 1 : numfunctions(trefs) - for j in 1 : numfunctions(brefs) + for i in 1 : num_trefs + for j in 1 : num_brefs for (m,a) in tad[p,i] for (n,b) in bad[q,j] store(a * zlocal[i,j] * b, m, n) @@ -167,7 +216,8 @@ function assemble_local_refines!(biop::LocalOperator, tfs::Space, bfs::Space, st end -function assemble_local_matched!(biop::LocalOperator, tfs::subdBasis, bfs::subdBasis, store) +function assemble_local_matched!(biop::LocalOperator, tfs::subdBasis, bfs::subdBasis, store; + quadstrat=defaultquadstrat(biop, tfs, bfs)) tels, tad = assemblydata(tfs) bels, bad = assemblydata(bfs) @@ -175,10 +225,10 @@ function assemble_local_matched!(biop::LocalOperator, tfs::subdBasis, bfs::subdB trefs = refspace(tfs) brefs = refspace(bfs) - qd = quaddata(biop, trefs, brefs, tels, bels) + qd = quaddata(biop, trefs, brefs, tels, bels, quadstrat) for (p,cell) in enumerate(tels) - qr = quadrule(biop, trefs, brefs, cell, qd) + qr = quadrule(biop, trefs, brefs, cell, qd, quadstrat) locmat = cellinteractions(biop, trefs, brefs, cell, qr) for i in 1 : size(locmat, 1), j in 1 : size(locmat, 2) @@ -187,7 +237,7 @@ function assemble_local_matched!(biop::LocalOperator, tfs::subdBasis, bfs::subdB end end end end -function elementstree(elements) +function elementstree(elements, expansion_ratio=1) nverts = dimension(eltype(elements)) + 1 ncells = length(elements) @@ -214,7 +264,7 @@ function elementstree(elements) end end - return Octree(points, radii) + return Octree(points, radii, T(expansion_ratio)) end @@ -223,17 +273,27 @@ end For use when basis and test functions are defined on different meshes """ -function assemble_local_mixed!(biop::LocalOperator, tfs::Space, bfs::Space, store) +function assemble_local_mixed!(biop::LocalOperator, tfs::Space{T}, bfs::Space{T}, store; + quadstrat=defaultquadstrat(biop, tfs, bfs)) where {T} - tol = sqrt(eps(Float64)) + tol = sqrt(eps(T)) trefs = refspace(tfs) brefs = refspace(bfs) + tgeo = geometry(tfs) + bgeo = geometry(bfs) + + tdom = domain(chart(tgeo, first(tgeo))) + bdom = domain(chart(bgeo, first(bgeo))) + + num_trefs = numfunctions(trefs, tdom) + num_brefs = numfunctions(brefs, bdom) + tels, tad = assemblydata(tfs) bels, bad = assemblydata(bfs) - qd = quaddata(biop, trefs, brefs, tels, bels) + qd = quaddata(biop, trefs, brefs, tels, bels, quadstrat) # store the bcells in an octree tree = elementstree(bels) @@ -258,12 +318,12 @@ function assemble_local_mixed!(biop::LocalOperator, tfs::Space, bfs::Space, stor P = restrict(brefs, bcell, cell) Q = restrict(trefs, tcell, cell) - qr = quadrule(biop, trefs, brefs, cell, qd) + qr = quadrule(biop, trefs, brefs, cell, qd, quadstrat) zlocal = cellinteractions(biop, trefs, brefs, cell, qr) zlocal = Q * zlocal * P' - for i in 1 : numfunctions(trefs) - for j in 1 : numfunctions(brefs) + for i in 1 : num_trefs + for j in 1 : num_brefs for (m,a) in tad[p,i] for (n,b) in bad[q,j] store(a * zlocal[i,j] * b, m, n) @@ -315,12 +375,12 @@ function cellinteractions_matched!(zlocal, biop, trefs, brefs, cell, qr) return zlocal end -function cellinteractions(biop, trefs, brefs, cell, qr) +function cellinteractions(biop, trefs::U, brefs::V, cell, qr) where {U<:RefSpace{T},V<:RefSpace{T}} where {T} num_tshs = length(qr[1][3]) num_bshs = length(qr[1][4]) - zlocal = zeros(Float64, num_tshs, num_bshs) + zlocal = zeros(T, num_tshs, num_bshs) for q in qr w, mp, tvals, bvals = q[1], q[2], q[3], q[4] @@ -342,3 +402,31 @@ function cellinteractions(biop, trefs, brefs, cell, qr) return zlocal end + + +@testitem "assemble!: zero sized block" begin + using CompScienceMeshes + + fn = joinpath(dirname(pathof(BEAST)), "../examples/assets/sphere45.in") + m1 = readmesh(fn) + m2 = m1[Int[]] + + X = BEAST.DirectProductSpace([raviartthomas(m) for m in [m1, m2]]) + Id = BEAST.Identity() + + @hilbertspace j[1:2] + @hilbertspace k[1:2] + a = Id[k[1],j[1]] + Id[k[2],j[2]] + + A = assemble(a, X, X) + M = AbstractMatrix(A) + import BEAST.BlockArrays + + n1 = numfunctions(X[1]) + n2 = numfunctions(X[2]) + + @test n2 == 0 + + @test BlockArrays.blocksize(M) == (2,2) + @test BlockArrays.blocksizes(M) == [(n1,n1) (n1,n2); (n2,n1) (n2,n2)] +end \ No newline at end of file diff --git a/src/maxwell/bogaertints.jl b/src/maxwell/bogaertints.jl index d5f08e49..9307bb25 100644 --- a/src/maxwell/bogaertints.jl +++ b/src/maxwell/bogaertints.jl @@ -1,4 +1,7 @@ -function momintegrals!(op::MWSingleLayer3D, g::RTRefSpace, f::RTRefSpace, t, s, z, strat::BogaertStrategy) +function momintegrals!(z, op::MWSingleLayer3D, + g::RTRefSpace, tptr, t, + f::RTRefSpace, bptr, s, + strat::BogaertStrategy) T, GG = GetIntegrals(t, s, op.gamma, strat) @@ -52,8 +55,10 @@ end -function momintegrals!(op::MWDoubleLayer3D, g::RTRefSpace, f::RTRefSpace, - τ, σ, z, strat::BogaertStrategy) +function momintegrals!(z, op::MWDoubleLayer3D, + g::RTRefSpace, tptr, τ, + f::RTRefSpace, bptr, σ, + strat::BogaertStrategy) # Get the primitives r = τ.vertices diff --git a/src/maxwell/farfield.jl b/src/maxwell/farfield.jl index 7e252383..348e61b2 100644 --- a/src/maxwell/farfield.jl +++ b/src/maxwell/farfield.jl @@ -1,30 +1,104 @@ +abstract type MWFarField <: FarField end +struct MWFarField3D{K, U} <: MWFarField + gamma::K + amplitude::U +end +struct MWDoubleLayerFarField3D{K, U} <: MWFarField + gamma::K + amplitude::U +end + +struct MWDoubleLayerRotatedFarField3D{K,U} <: MWFarField + gamma::K + amplitude::U +end + +""" + MWFarField3D(;gamma, amplitude) +Maxwell single layer far-field operator for 3D. """ -Operator to compute the far field of a current distribution. In particular, given the current distribution ``j`` this operator allows for the computation of +function MWFarField3D(; + gamma=nothing, + wavenumber=nothing, + amplitude=nothing +) + gamma, _ = gamma_wavenumber_handler(gamma, wavenumber) + @assert !isstatic(gamma) -```math -A j = n × ∫_Γ e^{γ ̂x ⋅ y} dy -``` + amplitude === nothing && (amplitude = 1.0) -where ``̂x`` is the unit vector in the direction of observation. Note that the assembly routing expects the observation directions to be normalised by the caller. + return MWFarField3D(gamma, amplitude) +end + +MWFarField3D(op::MWSingleLayer3D{T,U}) where {T,U} = MWFarField3D(op.gamma, sqrt(op.α*op.β)) + +""" + MWDoubleLayerFarField3D(;gamma, amplitude) + +Maxwell double layer far-field operator for 3D. """ -struct MWFarField3D{K} +function MWDoubleLayerFarField3D(; + gamma=nothing, + wavenumber=nothing, + amplitude=nothing +) + gamma, _ = gamma_wavenumber_handler(gamma, wavenumber) + @assert !isstatic(gamma) + + amplitude === nothing && (amplitude = 1.0) + + return MWDoubleLayerFarField3D(gamma, amplitude) +end + +MWDoubleLayerFarField3D(op::MWDoubleLayer3D{T}) where {T} = MWDoubleLayerFarField3D(op.gamma, 1.0) + +# quaddata(op::MWFarField,rs,els) = quadpoints(rs,els,(3,)) +# quadrule(op::MWFarField,refspace,p,y,q,el,qdata) = qdata[1,q] + +kernelvals(op::MWFarField,y,p) = exp(op.gamma*dot(y,cartesian(p))) +function integrand(op::MWFarField3D,krn,y,f,p) + op.amplitude*(y × (krn * f[1])) × y +end + +function integrand(op::MWDoubleLayerFarField3D,krn,y,f,p) + op.amplitude*(y × (krn * f[1])) +end + +struct MWFarField3DDropConstant{K, U} <: MWFarField gamma::K + coeff::U end +kernelvals(op::MWFarField3DDropConstant,y,p) = expm1(op.gamma*dot(y,cartesian(p))) +function integrand(op::MWFarField3DDropConstant,krn,y,f,p) + op.coeff*(y × (krn * f[1])) × y +end + +""" + MWDoubleLayerRotatedFarField3D + +Rotated Maxwell double layer far-field operator for 3D. +""" +function MWDoubleLayerRotatedFarField3D(; + gamma=nothing, + wavenumber=nothing, + amplitude=nothing +) + gamma, _ = gamma_wavenumber_handler(gamma, wavenumber) + @assert !isstatic(gamma) + + amplitude === nothing && (amplitude = 1.0) -function MWFarField3D(;wavenumber=error("wavenumber is a required argument")) - if iszero(real(wavenumber)) - MWFarField3D(-imag(wavenumber)) - else - MWFarField3D(wavenumber*im) - end + return MWDoubleLayerRotatedFarField3D(gamma, amplitude) end -quaddata(op::MWFarField3D,rs,els) = quadpoints(rs,els,(3,)) -quadrule(op::MWFarField3D,refspace,p,y,q,el,qdata) = qdata[1,q] +MWDoubleLayerRotatedFarField3D(op::DoubleLayerRotatedMW3D{T,U}) where {T,U} = +MWDoubleLayerRotatedFarField3D(op.gamma, T(1)) -kernelvals(op::MWFarField3D,y,p) = exp(op.gamma*dot(y,cartesian(p))) -function integrand(op::MWFarField3D,krn,y,f,p) - (y × (krn * f[1])) × y +function integrand(op::MWDoubleLayerRotatedFarField3D, krn, y, f, p) + op.amplitude * (y × ((krn * f[1]) × normal(p))) end + +LinearAlgebra.cross(::NormalVector, a::MWDoubleLayerFarField3D) = MWDoubleLayerRotatedFarField3D(a.gamma, a.amplitude) +LinearAlgebra.cross(::NormalVector, a::MWDoubleLayerRotatedFarField3D) = MWDoubleLayerFarField3D(a.gamma, -a.amplitude) diff --git a/src/maxwell/maxwell.jl b/src/maxwell/maxwell.jl index a3695b3f..082bb63b 100644 --- a/src/maxwell/maxwell.jl +++ b/src/maxwell/maxwell.jl @@ -7,13 +7,11 @@ module Maxwell3D singlelayer(;gamma, alpha, beta) singlelayer(;wavenumber, alpha, beta) - Bilinear form given by: + Maxwell 3D single layer operator. - ```math - α ∬_{Γ×Γ} j(x)⋅k(y) G_{γ}(x,y) + β ∬_{Γ×Γ} div j(x) div k(y) G_{γ}(x,y) - ``` + Either gamma, or the wavenumber, or α and β must be provided. - with ``G_{γ} = e^{-γ|x-y|} / 4π|x-y|``. + If α and β are not provided explitly, they are set to ``α = -γ`` and ``β = -1/γ`` with ``γ=\\mathrm{j} k``. """ function singlelayer(; gamma=nothing, @@ -21,73 +19,88 @@ module Maxwell3D alpha=nothing, beta=nothing) - if (gamma == nothing) && (wavenumber == nothing) - error("Supply one of (not both) gamma or wavenumber") - end - if (gamma != nothing) && (wavenumber != nothing) - error("Supply one of (not both) gamma or wavenumber") - end + gamma, wavenumber = Mod.gamma_wavenumber_handler(gamma, wavenumber) - if gamma == nothing - if iszero(real(wavenumber)) - gamma = -imag(wavenumber) - else - gamma = im*wavenumber - end + if Mod.isstatic(gamma) # static case + @assert !(isnothing(alpha)) && !(isnothing(beta)) end - @assert gamma != nothing - - alpha == nothing && (alpha = -gamma) - beta == nothing && (beta = -1/gamma) + alpha === nothing && (alpha = -gamma) + beta === nothing && (beta = -1/gamma) Mod.MWSingleLayer3D(gamma, alpha, beta) end + """ + weaklysingular(;wavenumber) + + Weakly singular part of the Maxwell 3D single layer operator. + + ``α = -\\mathrm{j} k`` is set with the wavenumber ``k``. + """ + weaklysingular(;wavenumber) = singlelayer(;wavenumber, alpha=-im*wavenumber, beta=zero(im*wavenumber)) + + """ + hypersingular(;wavenumber) + + Hyper singular part of the Maxwell 3D single layer operator. + + ``β = -1/\\mathrm{j} k`` is set with the wavenumber ``k``. + """ + hypersingular(;wavenumber) = singlelayer(; wavenumber, alpha=zero(im*wavenumber), beta=-1/(im*wavenumber)) + """ doublelayer(;gamma) - doublelaher(;wavenumber) + doublelayer(;wavenumber) - Bilinear form given by: + Maxwell double layer operator. - ```math - ∬_{Γ^2} k(x) ⋅ (∇G_γ(x-y) × j(y)) - ``` + Either gamma or the wavenumber must be provided. Optionally, also alpha can be provided. - with ``G_γ = e^{-γ|x-y|} / 4π|x-y|`` + If alpha is not provided explitly, it is set to ``α = 1``. """ function doublelayer(; + alpha=nothing, gamma=nothing, wavenumber=nothing) - if (gamma == nothing) && (wavenumber == nothing) - error("Supply one of (not both) gamma or wavenumber") - end - - if (gamma != nothing) && (wavenumber != nothing) - error("Supply one of (not both) gamma or wavenumber") - end + gamma, wavenumber = Mod.gamma_wavenumber_handler(gamma, wavenumber) - if gamma == nothing - if iszero(real(wavenumber)) - gamma = -imag(wavenumber) + if isnothing(alpha) + if Mod.isstatic(gamma) # static case + alpha = 1.0 # Default to double precision else - gamma = im*wavenumber + alpha = one(gamma) end end - @assert gamma != nothing - - Mod.MWDoubleLayer3D(gamma) + Mod.MWDoubleLayer3D(alpha, gamma) end + """ + planewave(; + direction = error("missing arguement `direction`"), + polarization = error("missing arguement `polarization`"), + wavenumber = error("missing arguement `wavenumber`"), + amplitude = one(real(typeof(wavenumber)))) + + Time-harmonic plane wave. + """ planewave(; direction = error("missing arguement `direction`"), polarization = error("missing arguement `polarization`"), wavenumber = error("missing arguement `wavenumber`"), amplitude = one(real(typeof(wavenumber)))) = - Mod.PlaneWaveMW(direction, polarization, wavenumber, amplitude) + Mod.PlaneWaveMW(direction, polarization, wavenumber*im, amplitude) + + + planewaveExtractedKernel(; + direction = error("missing arguement `direction`"), + polarization = error("missing arguement `polarization`"), + wavenumber = error("missing arguement `wavenumber`"), + amplitude = one(real(typeof(wavenumber)))) = + Mod.PlaneWaveExtractedKernelMW(direction, polarization, wavenumber*im, amplitude) farfield(; wavenumber = error("missing argument: `wavenumber`")) = diff --git a/src/maxwell/mwexc.jl b/src/maxwell/mwexc.jl index cb22aa47..0c38d89c 100644 --- a/src/maxwell/mwexc.jl +++ b/src/maxwell/mwexc.jl @@ -1,85 +1,189 @@ mutable struct PlaneWaveMW{T,P} direction::P polarisation::P - wavenumber::T + gamma::T amplitude::T end -function PlaneWaveMW(d,p,k,a = 1) - T = promote_type(eltype(d), eltype(p), typeof(k), typeof(a)) +function PlaneWaveMW(d,p,γ,a = 1) + T = promote_type(eltype(d), eltype(p), typeof(γ), typeof(a)) P = similar_type(typeof(d), T) - PlaneWaveMW{T,P}(d,p,k,a) + PlaneWaveMW{T,P}(d,p,γ,a) end +scalartype(x::PlaneWaveMW{T,P}) where {T,P} = promote_type(T, eltype(P)) + """ - planewavemw3d(;direction, polarization, wavenumber[, amplitude=1]) + planewavemw3d(;direction, polarization, wavenumber, gamma[, amplitude=1]) Create a plane wave solution to Maxwell's equations. """ -planewavemw3d(; +function planewavemw3d(; direction = error("missing arguement `direction`"), polarization = error("missing arguement `polarization`"), - wavenumber = error("missing arguement `wavenumber`"), + wavenumber = nothing, + gamma = nothing, amplitude = 1, - ) = PlaneWaveMW(direction, polarization, wavenumber, amplitude) + ) + + gamma, wavenumber = gamma_wavenumber_handler(gamma, wavenumber) + isstatic(gamma) && (gamma = zero(eltype(direction))) + + return PlaneWaveMW(direction, polarization, gamma, amplitude) + +end function (e::PlaneWaveMW)(x) - k = e.wavenumber + γ = e.gamma d = e.direction u = e.polarisation a = e.amplitude - a * exp(-im * k * dot(d, x)) * u + a * exp(-γ * dot(d, x)) * u end function curl(field::PlaneWaveMW) - k = field.wavenumber + γ = field.gamma d = field.direction u = field.polarisation a = field.amplitude v = d × u - b = -a * im * k - PlaneWaveMW(d, v, k, b) + b = -a * γ + PlaneWaveMW(d, v, γ, b) end -*(a::Number, e::PlaneWaveMW) = PlaneWaveMW(e.direction, e.polarisation, e.wavenumber, a*e.amplitude) +*(a::Number, e::PlaneWaveMW) = PlaneWaveMW(e.direction, e.polarisation, e.gamma, a*e.amplitude) -mutable struct CrossTraceMW{F} <: Functional - field::F + +mutable struct PlaneWaveExtractedKernelMW{T,P} + direction::P + polarisation::P + gamma::T + amplitude::T end -mutable struct TangTraceMW{F} <: Functional - field::F +function PlaneWaveExtractedKernelMW(d,p,γ,a = 1) + T = promote_type(eltype(d), eltype(p), typeof(γ), typeof(a)) + P = similar_type(typeof(d), T) + PlaneWaveExtractedKernelMW{T,P}(d,p,γ,a) end -cross(::NormalVector, p::Function) = CrossTraceMW(p) -cross(::NormalVector, p::PlaneWaveMW) = CrossTraceMW(p) -cross(t::CrossTraceMW, ::NormalVector) = TangTraceMW(t.field) +scalartype(x::PlaneWaveExtractedKernelMW{T,P}) where {T,P} = promote_type(T, eltype(P)) -function (ϕ::CrossTraceMW)(p) - F = ϕ.field - x = cartesian(p) - n = normal(p) - return n × F(x) + +function (e::PlaneWaveExtractedKernelMW)(x) + γ = e.gamma + d = e.direction + u = e.polarisation + a = e.amplitude + a * expm1(-γ * dot(d, x)) * u end -function (ϕ::TangTraceMW)(p) - F = ϕ.field - x = cartesian(p) - n = normal(p) - return (n × F(x)) × n +function curl(field::PlaneWaveExtractedKernelMW) + γ = field.gamma + d = field.direction + u = field.polarisation + a = field.amplitude + v = d × u + b = -a * γ + PlaneWaveExtractedKernelMW(d, v, γ, b) end -integrand(::TangTraceMW, gx, ϕx) = gx[1] ⋅ ϕx -integrand(::CrossTraceMW, test_vals, field_val) = test_vals[1] ⋅ field_val +*(a::Number, e::PlaneWaveExtractedKernelMW) = PlaneWaveExtractedKernelMW(e.direction, e.polarisation, e.gamma, a*e.amplitude) + + + +abstract type Dipole end -struct NDotTrace{F} <: Functional - field::F +mutable struct DipoleMW{T,P} <: Dipole + location::P + orientation::P + gamma::T end -(ϕ::NDotTrace)(p) = dot(normal(p), ϕ.field(cartesian(p))) -integrand(::NDotTrace, g, ϕ) = dot(g.value, ϕ) -LinearAlgebra.dot(::NormalVector, f) = NDotTrace(f) +function DipoleMW(l,o,γ) + T = promote_type(eltype(l), eltype(o), typeof(γ)) + P = similar_type(typeof(l), T) + DipoleMW{T,P}(l,o,γ) +end + +scalartype(x::DipoleMW{T,P}) where {T,P} = promote_type(T, eltype(P)) + +mutable struct curlDipoleMW{T,P} <: Dipole + location::P + orientation::P + gamma::T +end + +function curlDipoleMW(l,o,γ) + T = promote_type(eltype(l), eltype(o), typeof(γ)) + P = similar_type(typeof(l), T) + curlDipoleMW{T,P}(l,o,γ) +end + +scalartype(x::curlDipoleMW{T,P}) where {T,P} = promote_type(T, eltype(P)) + +""" + dipolemw3d(;location, orientation, wavenumber) + +Create an electric dipole solution to Maxwell's equations representing the electric +field part. Implementation is based on (9.18) of Jackson's “Classical electrodynamics”, +with the notable difference that the ``\exp(ikr)`` is used. +""" +function dipolemw3d(; + location = error("missing arguement `location`"), + orientation = error("missing arguement `orientation`"), + wavenumber = nothing, + gamma = nothing + ) + + gamma, wavenumber = gamma_wavenumber_handler(gamma, wavenumber) + isstatic(gamma) && (gamma = zero(eltype(orientation))) + + return DipoleMW(location, orientation, gamma) + +end + +function (d::DipoleMW)(x; isfarfield=false) + γ = d.gamma + x_0 = d.location + p = d.orientation + if isfarfield + # postfactor (4*π*im)/k = (-4*π)/γ to be consistent with BEAST far field computation + # and, of course, adapted phase factor exp(im*k*dot(n,x_0)) with + # respect to (9.19) of Jackson's Classical Electrodynamics + r = norm(x) + n = x/r + return cross(cross(n,1/(4*π)*(-γ^2*cross(cross(n,p),n))*exp(γ*dot(n,x_0))*(-4*π)/γ),n) + else + r = norm(x-x_0) + n = (x - x_0)/r + return 1/(4*π)*exp(-γ*r)*(-γ^2/r*cross(cross(n,p),n) + + (1/r^3 + γ/r^2)*(3*n*dot(n,p) - p)) + end +end + +function (d::curlDipoleMW)(x; isfarfield=false) + γ = d.gamma + x_0 = d.location + p = d.orientation + if isfarfield + # postfactor (4*π*im)/k to be consistent with BEAST far field computation + r = norm(x) + n = x/r + return (-γ)*(-γ^2)/(4*π)*cross(n,p)*(-4*π)/γ*exp(γ*dot(n,x_0)) + else + r = norm(x-x_0) + n = (x - x_0)/r + return γ^3/(4*π)*cross(n,p)*exp(-γ*r)/r*(1 + 1/(γ*r)) + end +end + +function curl(d::DipoleMW) + return curlDipoleMW(d.location, d.orientation, d.gamma) +end +*(a::Number, d::DipoleMW) = DipoleMW(d.location, a .* d.orientation, d.gamma) +*(a::Number, d::curlDipoleMW) = curlDipoleMW(d.location, a .* d.orientation, d.gamma) mutable struct CurlGreen{T,U,V} @@ -104,8 +208,8 @@ mutable struct CurlCurlGreen{T,U,V} position::V end -cross(::NormalVector, p::CurlGreen) = CrossTraceMW(p) -cross(::NormalVector, p::CurlCurlGreen) = CrossTraceMW(p) +# cross(::NormalVector, p::CurlGreen) = CrossTraceMW(p) +# cross(::NormalVector, p::CurlCurlGreen) = CrossTraceMW(p) function (f::CurlCurlGreen)(x) γ = im * f.wavenumber diff --git a/src/maxwell/mwops.jl b/src/maxwell/mwops.jl index b35f37bc..5380aed5 100644 --- a/src/maxwell/mwops.jl +++ b/src/maxwell/mwops.jl @@ -1,52 +1,24 @@ -abstract type MaxwellOperator3D <: IntegralOperator end -abstract type MaxwellOperator3DReg <: MaxwellOperator3D end - -struct KernelValsMaxwell3D{T,U,P,Q} - "gamma = im * wavenumber" - gamma::U - vect::P - dist::T - green::U - gradgreen::Q -end - -const inv_4pi = 1/(4pi) -function kernelvals(biop::MaxwellOperator3D, p, q) - - γ = biop.gamma - r = cartesian(p) - cartesian(q) - R = norm(r) - γR = γ*R - - inv_R = 1/R +abstract type MaxwellOperator3D{T,K} <: IntegralOperator end +abstract type MaxwellOperator3DReg{T,K} <: MaxwellOperator3D{T,K} end - expn = exp(-γR) - green = expn * inv_R * inv_4pi - gradgreen = -(γ + inv_R) * green * inv_R * r +scalartype(op::MaxwellOperator3D{T,K}) where {T, K <: Val{0}} = T +scalartype(op::MaxwellOperator3D{T,K}) where {T, K} = promote_type(T, K) - KernelValsMaxwell3D(γ, r, R, green, gradgreen) -end +gamma(op::MaxwellOperator3D{T,Val{0}}) where {T} = zero(T) +gamma(op::MaxwellOperator3D{T,K}) where {T, K} = op.gamma -function kernelvals(kernel::MaxwellOperator3DReg, p, q) - γ = kernel.gamma - r = p.cart - q.cart - R = norm(r) - γR = γ*R - - Exp = exp(-γ*R) - green = (Exp - 1 + γR - 0.5*γR^2) / (4pi*R) - gradgreen = ( - (γR + 1)*Exp + (1 - 0.5*γR^2) ) * (r/R^3) / (4π) - - KernelValsMaxwell3D(γ, r, R, green, gradgreen) -end - -struct MWSingleLayer3D{T,U} <: MaxwellOperator3D +struct MWSingleLayer3D{T,U} <: MaxwellOperator3D{T,U} gamma::T α::U β::U end +gamma(op::MWSingleLayer3D{Val{0}, U}) where {U} = zero(U) + +scalartype(op::MWSingleLayer3D{T,U}) where {T,U} = promote_type(T,U) +# sign_upon_permutation(op::MWSingleLayer3D, I, J) = 1 + MWSingleLayer3D(gamma) = MWSingleLayer3D(gamma, -gamma, -1/(gamma)) MWWeaklySingular(gamma) = MWSingleLayer3D(gamma, 1, 0) MWHyperSingular(gamma) = MWSingleLayer3D(gamma, 0, 1) @@ -55,20 +27,18 @@ MWHyperSingular(gamma) = MWSingleLayer3D(gamma, 0, 1) export Maxwell3D -struct MWSingleLayer3DReg{T,U} <: MaxwellOperator3DReg +struct MWSingleLayer3DReg{T,U} <: MaxwellOperator3DReg{T,U} gamma::T α::U β::U end -struct MWSingleLayer3DSng{T,U} <: MaxwellOperator3D +struct MWSingleLayer3DSng{T,U} <: MaxwellOperator3D{T,U} gamma::T α::U β::U end -scalartype(op::MaxwellOperator3D) = typeof(op.gamma) - regularpart(op::MWSingleLayer3D) = MWSingleLayer3DReg(op.gamma, op.α, op.β) singularpart(op::MWSingleLayer3D) = MWSingleLayer3DSng(op.gamma, op.α, op.β) @@ -80,180 +50,221 @@ function _legendre(n,a,b) collect(zip(x,w)) end -function quaddata(op::MaxwellOperator3D, - test_local_space::RefSpace, trial_local_space::RefSpace, - test_charts, trial_charts) +# defaultquadstrat(op::MaxwellOperator3D, tfs::Space, bfs::Space) = DoubleNumWiltonSauterQStrat(2,3,6,7,5,5,4,3) - a, b = 0.0, 1.0 - # CommonVertex, CommonEdge, CommonFace rules +defaultquadstrat(op::MaxwellOperator3D, tfs::RTRefSpace, bfs::RTRefSpace) = DoubleNumWiltonSauterQStrat(2,3,6,7,5,5,4,3) +# defaultquadstrat(op::MaxwellOperator3D, tfs::RefSpace, bfs::RefSpace) = DoubleNumWiltonSauterQStrat(2,3,6,7,5,5,4,3) - tqd = quadpoints(test_local_space, test_charts, (2,6)) - bqd = quadpoints(trial_local_space, trial_charts, (3,7)) - leg = (_legendre(3,a,b), _legendre(4,a,b), _legendre(5,a,b),) - # High accuracy rules (use them e.g. in LF MFIE scenarios) - # tqd = quadpoints(test_local_space, test_charts, (8,8)) - # bqd = quadpoints(trial_local_space, trial_charts, (8,9)) - # leg = (_legendre(8,a,b), _legendre(10,a,b), _legendre(5,a,b),) - return (tpoints=tqd, bpoints=bqd, gausslegendre=leg) +struct MWDoubleLayer3D{T,K} <: MaxwellOperator3D{T,K} + alpha::T + gamma::K end +# sign_upon_permutation(op::MWDoubleLayer3D, I, J) = 1 +struct MWDoubleLayer3DSng{T,K} <: MaxwellOperator3D{T,K} + alpha::T + gamma::K +end +struct MWDoubleLayer3DReg{T,K} <: MaxwellOperator3DReg{T,K} + alpha::T + gamma::K +end -# use Union type so this code can be shared between the operator -# and its regular part. -MWSL3DGen = Union{MWSingleLayer3D,MWSingleLayer3DReg} -function integrand(biop::MWSL3DGen, kerneldata, tvals, tgeo, bvals, bgeo) +MWDoubleLayer3D(gamma) = MWDoubleLayer3D(1.0, gamma) # For legacy purposes - gx = tvals[1] - fy = bvals[1] +regularpart(op::MWDoubleLayer3D) = MWDoubleLayer3DReg(op.alpha, op.gamma) +singularpart(op::MWDoubleLayer3D) = MWDoubleLayer3DSng(op.alpha, op.gamma) - dgx = tvals[2] - dfy = bvals[2] - G = kerneldata.green - γ = kerneldata.gamma +struct MWDoubleLayer3DLoop{T,K} <: MaxwellOperator3D{T,K} + alpha::T + gamma::K +end - α = biop.α - β = biop.β +MWDoubleLayer3DLoop(gamma) = MWDoubleLayer3DLoop(1.0, gamma) # For legacy purposes - t = (α * dot(gx, fy) + β * (dgx*dfy)) * G -end +################################################################################ +# +# Kernel definitions +# +################################################################################ -struct MWDoubleLayer3D{T} <: MaxwellOperator3D - gamma::T -end +const i4pi = 1 / (4pi) +function (igd::Integrand{<:MWSingleLayer3D})(x,y,f,g) + α = igd.operator.α + β = igd.operator.β + γ = igd.operator.gamma -struct MWDoubleLayer3DSng{T} <: MaxwellOperator3D - gamma::T -end + r = cartesian(x) - cartesian(y) + R = norm(r) + iR = 1 / R + green = exp(-γ*R)*(i4pi*iR) -struct MWDoubleLayer3DReg{T} <: MaxwellOperator3DReg - gamma::T + αG = α * green + βG = β * green + + _integrands(f,g) do fi,gj + αG * dot(fi.value, gj.value) + βG * dot(fi.divergence, gj.divergence) + end end -regularpart(op::MWDoubleLayer3D) = MWDoubleLayer3DReg(op.gamma) -singularpart(op::MWDoubleLayer3D) = MWDoubleLayer3DSng(op.gamma) -const MWDL3DGen = Union{MWDoubleLayer3D,MWDoubleLayer3DReg} -function integrand(biop::MWDL3DGen, kerneldata, tvals, tgeo, bvals, bgeo) - g = tvals[1] - f = bvals[1] - ∇G = kerneldata.gradgreen - (f × g) ⋅ ∇G -end +function (igd::Integrand{<:MWSingleLayer3DReg})(x,y,f,g) + α = igd.operator.α + β = igd.operator.β + γ = igd.operator.gamma -quadrule(op::MaxwellOperator3D, g::RTRefSpace, f::RTRefSpace, i, τ, j, σ, qd) = qrss(op, g, f, i, τ, j, σ, qd) + r = cartesian(x) - cartesian(y) + R = norm(r) + γR = γ*R + # iR = 1 / R + green = (expm1(-γR) + γR - 0.5*γR^2) / (4pi*R) -function qrss(op, g, f, i, τ, j, σ, qd) - # defines coincidence of points - dtol = 1.0e3 * eps(eltype(eltype(τ.vertices))) + αG = α * green + βG = β * green - hits = 0 - dmin2 = floatmax(eltype(eltype(τ.vertices))) - for t in τ.vertices - for s in σ.vertices - d2 = LinearAlgebra.norm_sqr(t-s) - dmin2 = min(dmin2, d2) - hits += (d2 < dtol) - end + _integrands(f,g) do fi,gj + αG * dot(fi.value, gj.value) + βG * dot(fi.divergence, gj.divergence) end +end - hits == 3 && return SauterSchwabQuadrature.CommonFace(qd.gausslegendre[3]) - hits == 2 && return SauterSchwabQuadrature.CommonEdge(qd.gausslegendre[2]) - hits == 1 && return SauterSchwabQuadrature.CommonVertex(qd.gausslegendre[1]) - - h2 = volume(σ) - xtol2 = 0.2 * 0.2 - k2 = abs2(op.gamma) - max(dmin2*k2, dmin2/16h2) < xtol2 && return WiltonSEStrategy( - qd.tpoints[2,i], - DoubleQuadStrategy( - qd.tpoints[2,i], - qd.bpoints[2,j],),) - return DoubleQuadStrategy( - qd.tpoints[1,i], - qd.bpoints[1,j],) + +function (igd::Integrand{<:MWDoubleLayer3D})(x,y,f,g) + + γ = igd.operator.gamma + + r = cartesian(x) - cartesian(y) + R = norm(r) + iR = 1/R + green = exp(-γ*R)*(iR*i4pi) + gradgreen = -(γ + iR) * green * (iR * r) + + fvalue = getvalue(f) + gvalue = getvalue(g) + G = cross.(Ref(gradgreen), gvalue) + return _krondot(fvalue, G) end -function qrib(op::MaxwellOperator3D, g::RTRefSpace, f::RTRefSpace, i, τ, j, σ, qd) - # defines coincidence of points - dtol = 1.0e3 * eps(eltype(eltype(τ.vertices))) +function (igd::Integrand{<:MWDoubleLayer3DReg})(x,y,f,g) - # decides on whether to use singularity extraction - xtol = 0.2 + γ = igd.operator.gamma + + r = cartesian(x) - cartesian(y) + R = norm(r) + γR = γ*R + iR = 1/R + expo = exp(-γR) + green = (expo - 1 + γR - 0.5*γR^2) * (i4pi*iR) + gradgreen = ( -(γR + 1)*expo + (1 - 0.5*γR^2) ) * (i4pi*iR^3) * r + + fvalue = getvalue(f) + gvalue = getvalue(g) + G = cross.(Ref(gradgreen), gvalue) + return _krondot(fvalue, G) +end - k = norm(op.gamma) - hits = 0 - xmin = xtol - for t in τ.vertices - for s in σ.vertices - d = norm(t-s) - xmin = min(xmin, k*d) - if d < dtol - hits +=1 - break - end +function (igd::Integrand{<:MWDoubleLayer3DLoop})(x,y,f,g) + + γ = igd.operator.gamma + + r = cartesian(x) - cartesian(y) + R = norm(r) + γR = γ*R + iR = 1/R + gradgreen = -(γ*exp(-γR)*iR+expm1(-γR)*iR^2)*iR/(4π)*r # -(expm1(γR)*(1+γR)+γR)*iR^3/(4pi) * r #-im*exp(-γR)*( im*γR + im*expm1(γR)) * iR^3/(4pi) * r + + fvalue = getvalue(f) + gvalue = getvalue(g) + G = cross.(Ref(gradgreen), gvalue) + return _krondot(fvalue, G) +end + +################################################################################ +# +# Handling of operator parameters (Helmholtz and Maxwell) +# +################################################################################ + +""" + gamma_wavenumber_handler(gamma, wavenumber) + +This function handles the input of `gamma` and `wavenumber`. It throws an error if both `gamma` and +`wavenumber` are provided. If neither is provided, it assumes a static problem and returns `Val(0)` +for `gamma` and `wavenumber`. + +# Arguments +- `gamma`: `im` * `wavenumber` or `nothing`. +- `wavenumber`: `wavenumber` or `nothing`. + +# Returns +- `gamma` and `wavenumber`: Appropriate pair `gamma` and `wavenumber`. +""" +function gamma_wavenumber_handler(gamma, wavenumber) + if !isnothing(gamma) && !isnothing(wavenumber) + error("Supplying both gamma and wavenumber is not supported.") + + elseif isnothing(gamma) && isnothing(wavenumber) + # if neither gamma nor wavenumber is supplied, we are assuming a static problem + return Val(0), Val(0) + end + + if isnothing(gamma) && !isnothing(wavenumber) + if iszero(real(wavenumber)) + gamma = -imag(wavenumber) + else + gamma = im*wavenumber + end + else + # gamma supplied, set wavenumber + if iszero(real(gamma)) + wavenumber = imag(gamma) + else + wavenumber = -im*gamma + end end - end - - hits == 3 && return BogaertSelfPatchStrategy(5) - hits == 2 && return BogaertEdgePatchStrategy(8, 4) - hits == 1 && return BogaertPointPatchStrategy(2, 3) - rmin = xmin/k - xmin < xtol && return WiltonSEStrategy( - qd.tpoints[1,i], - DoubleQuadStrategy( - qd.tpoints[2,i], - qd.bpoints[2,j], - ), - ) - return DoubleQuadStrategy( - qd.tpoints[1,i], - qd.bpoints[1,j], - ) + return gamma, wavenumber end +""" + isstatic(gamma) + +This function checks if the provided `gamma` value represents a static problem. +It returns true if `gamma` is of type `Val{0}` indicating a static problem. -function qrdf(op::MaxwellOperator3D, g::RTRefSpace, f::RTRefSpace, i, τ, j, σ, qd) - # defines coincidence of points - dtol = 1.0e3 * eps(eltype(eltype(τ.vertices))) +# Arguments +- `gamma`: `gamma` value. - # decides on whether to use singularity extraction - xtol = 0.2 +# Returns +- A boolean indicating whether the problem is static or not. +""" +function isstatic(gamma) + return typeof(gamma) == Val{0} +end - k = norm(op.gamma) +function isstatic(op::MaxwellOperator3D) + return isstatic(op.gamma) +end - hits = 0 - xmin = xtol - for t in τ.vertices - for s in σ.vertices - d = norm(t-s) - xmin = min(xmin, k*d) - if d < dtol - hits +=1 - break - end +function operator_parameter_handler(alpha, gamma, wavenumber) + + gamma, wavenumber = gamma_wavenumber_handler(gamma, wavenumber) + + if alpha === nothing + if isstatic(gamma) # static problem + alpha = 1.0 # default to double precision + else + alpha = one(real(typeof(gamma))) + end end - end - - xmin < xtol && return WiltonSEStrategy( - qd.tpoints[1,i], - DoubleQuadStrategy( - qd.tpoints[2,i], - qd.bpoints[2,j], - ), - ) - return DoubleQuadStrategy( - qd.tpoints[1,i], - qd.bpoints[1,j], - ) + return alpha, gamma end diff --git a/src/maxwell/nearfield.jl b/src/maxwell/nearfield.jl index 5d9fcabc..01297466 100644 --- a/src/maxwell/nearfield.jl +++ b/src/maxwell/nearfield.jl @@ -1,42 +1,77 @@ -mutable struct MWSingleLayerField3D{K} - wavenumber::K +mutable struct MWSingleLayerField3D{T, U} + gamma::T + α::U + β::U end -mutable struct MWDoubleLayerField3D{K} - wavenumber::K +mutable struct MWDoubleLayerField3D{T} + gamma::T end """ - MWSingleLayerField3D(wavenumber = error()) + MWSingleLayerField3D(;gamma, wavenumber, alpha, beta) Create the single layer near field operator, for use with `potential`. """ -MWSingleLayerField3D(;wavenumber = error("Missing arg: `wavenumber`")) = MWSingleLayerField3D(wavenumber) -MWDoubleLayerField3D(;wavenumber = error("Missing arg: `wavenumber`")) = MWDoubleLayerField3D(wavenumber) +function MWSingleLayerField3D(; + gamma=nothing, + wavenumber=nothing, + alpha=nothing, + beta=nothing +) + gamma, _ = gamma_wavenumber_handler(gamma, wavenumber) + @assert !isstatic(gamma) + + alpha === nothing && (alpha = -gamma) + beta === nothing && (beta = -1/gamma) + + MWSingleLayerField3D(gamma, alpha, beta) +end + +MWSingleLayerField3D(op::MWSingleLayer3D{T,U}) where {T,U} = MWSingleLayerField3D(op.gamma, op.α, op.β) + +""" + MWDoubleLayerField3D(; gamma, wavenumber) + +Create the double layer near field operator, for use with `potential`. +""" +function MWDoubleLayerField3D(; + gamma=nothing, + wavenumber=nothing +) + gamma, _ = gamma_wavenumber_handler(gamma, wavenumber) + @assert !isstatic(gamma) + + MWDoubleLayerField3D(gamma) +end + +MWDoubleLayerField3D(op::MWDoubleLayer3D) = MWDoubleLayerField3D(op.gamma) + +const MWField3D = Union{MWSingleLayerPotential3D, MWSingleLayerField3D,MWDoubleLayerField3D} +defaultquadstrat(op::MWField3D, basis) = SingleNumQStrat(2) +quaddata(op::MWField3D,rs,els,qs::SingleNumQStrat) = quadpoints(rs,els,(qs.quad_rule,)) +quadrule(op::MWField3D,refspace,p,y,q,el,qdata,qs::SingleNumQStrat) = qdata[1,q] -const MWField3D = Union{MWSingleLayerField3D,MWDoubleLayerField3D} -quaddata(op::MWField3D,rs,els) = quadpoints(rs,els,(2,)) -quadrule(op::MWField3D,refspace,p,y,q,el,qdata) = qdata[1,q] function kernelvals(op::MWField3D,y,p) - k = op.wavenumber + γ = op.gamma r = y - cartesian(p) R = norm(r) - kr = k * R - expn = exp(-im * k * R) + γR = γ*R + expn = exp(-γR) green = expn / (4pi*R) - gradgreen = -(im*k + 1/R) * green / R * r + gradgreen = -(γ + 1/R) * green / R * r krn = (trans=r, dist=R, green=green, gradgreen=gradgreen) end function integrand(op::MWSingleLayerField3D, krn, y, fp, p) - γ = im * op.wavenumber + γ = op.gamma j = fp.value ρ = fp.divergence @@ -44,7 +79,7 @@ function integrand(op::MWSingleLayerField3D, krn, y, fp, p) G = krn.green ∇G = krn.gradgreen - -γ*G*j + ∇G*ρ/γ + op.α*G*j - op.β*∇G*ρ end function integrand(op::MWDoubleLayerField3D, krn, y, fp, p) diff --git a/src/maxwell/nitsche.jl b/src/maxwell/nitsche.jl index d5499a8a..e159c2a8 100644 --- a/src/maxwell/nitsche.jl +++ b/src/maxwell/nitsche.jl @@ -8,18 +8,18 @@ Describe a single layer operator from the surface to a line. = ∫_γ dx v(x) ∫_Γ dy \frac{e^{-ikR}}{4πR} u(y) ``` """ -mutable struct SingleLayerTrace{T} <: MaxwellOperator3D +mutable struct SingleLayerTrace{T} <: MaxwellOperator3D{T,T} gamma::T end +defaultquadstrat(::SingleLayerTrace, ::LagrangeRefSpace, ::LagrangeRefSpace) = DoubleNumWiltonSauterQStrat(10,8,10,8,3,3,3,3) function quaddata(operator::SingleLayerTrace, localtestbasis::LagrangeRefSpace, localtrialbasis::LagrangeRefSpace, - testelements, - trialelements) + testelements, trialelements, qs::DoubleNumWiltonSauterQStrat) - tqd = quadpoints(localtestbasis, testelements, (10,)) - bqd = quadpoints(localtrialbasis, trialelements, (8,)) + tqd = quadpoints(localtestbasis, testelements, (qs.outer_rule_far,)) + bqd = quadpoints(localtrialbasis, trialelements, (qs.outer_rule_near,)) #return QuadData(tqd, bqd) return (tpoints=tqd, bpoints=bqd) @@ -27,8 +27,10 @@ end # Use numerical quadrature for now # Note: basis integral is over triangle, test over line -function quadrule(op::SingleLayerTrace, g::LagrangeRefSpace, f::LagrangeRefSpace, i, τ, j, σ, qd) - DoubleQuadStrategy( +function quadrule(op::SingleLayerTrace, g::LagrangeRefSpace, f::LagrangeRefSpace, i, τ, j, σ, qd, + qs::DoubleNumWiltonSauterQStrat) + + DoubleQuadRule( qd.tpoints[1,i], qd.bpoints[1,j] ) diff --git a/src/maxwell/nxdbllayer.jl b/src/maxwell/nxdbllayer.jl index b6ca769a..12358beb 100644 --- a/src/maxwell/nxdbllayer.jl +++ b/src/maxwell/nxdbllayer.jl @@ -1,46 +1,44 @@ +""" + struct DoubleLayerRotatedMW3D{T,K} <: MaxwellOperator3D{T,K} + Bilinear form given by: -mutable struct DoubleLayerRotatedMW3D{T} <: MaxwellOperator3D - "im times the wavenumber" - gamma::T -end - -LinearAlgebra.cross(::NormalVector, a::MWDoubleLayer3D) = DoubleLayerRotatedMW3D(a.gamma) + ```math + α ∬_{Γ^2} k(x) ⋅ [n̂(x) × (∇G_γ(x-y) × j(y))] + ``` -function quaddata(operator::DoubleLayerRotatedMW3D, - local_test_basis::RTRefSpace, - local_trial_basis::RTRefSpace, - test_elements, trial_elements) + with ``G_γ = e^{-γ|x-y|} / 4π|x-y|`` - test_quad_data = quadpoints(local_test_basis, test_elements, (2,)) - trial_quad_data = quadpoints(local_trial_basis, trial_elements, (3,)) +# Fields +- `alpha::T`: Factor in front of bilinear form. +- `gamma::K`: imaginary unit times the wavenumber. - return test_quad_data, trial_quad_data +""" +struct DoubleLayerRotatedMW3D{T,K} <: MaxwellOperator3D{T,K} + alpha::T + gamma::K end +# defaultquadstrat(op::DoubleLayerRotatedMW3D, tfs::Space, bfs::Space) = DoubleNumSauterQstrat(6,7,5,5,4,3) +defaultquadstrat(op::DoubleLayerRotatedMW3D, tfs::Space, bfs::Space) = DoubleNumQStrat(6,7) -function quadrule(operator::DoubleLayerRotatedMW3D, - local_test_basis::RTRefSpace, - local_trial_basis::RTRefSpace, - test_id, test_element, - trial_id, trial_element, - quad_data) +LinearAlgebra.cross(::NormalVector, a::MWDoubleLayer3D) = DoubleLayerRotatedMW3D(a.alpha, a.gamma) - test_quad_rules = quad_data[1] - trial_quad_rules = quad_data[2] +function (igd::Integrand{<:DoubleLayerRotatedMW3D})(x,y,f,g) - DoubleQuadStrategy( - test_quad_rules[1,test_id], - trial_quad_rules[1,trial_id] - ) -end + nx = normal(x) -function integrand(op::DoubleLayerRotatedMW3D, kernel_vals, test_vals, test_nbd, trial_vals, trial_nbd) + r = cartesian(x) - cartesian(y) + R = norm(r) + iR = 1/R + γ = gamma(igd.operator) + G = exp(-γ*R)/(4π*R) + K = -(γ + iR) * G * (iR * r) - n = normal(test_nbd) - g = test_vals[1] - f = trial_vals[1] - ∇G = kernel_vals.gradgreen + fvalue = getvalue(f) + gvalue = getvalue(g) - return g ⋅ (n × (∇G × f)) + Kg = cross.(Ref(K), gvalue) + nxKg = cross.(Ref(nx), Kg) + return _krondot(fvalue, nxKg) end diff --git a/src/maxwell/qlmwops.jl b/src/maxwell/qlmwops.jl new file mode 100644 index 00000000..77361f34 --- /dev/null +++ b/src/maxwell/qlmwops.jl @@ -0,0 +1,103 @@ +struct QuasiLocalSingleLayerMW{R<:Real,F} <: QuasiLocalOperator + gamma::R + alpha::R + beta::R + range::R + nominator::F +end + +scalartype(op::QuasiLocalSingleLayerMW{T}) where {T} = T +defaultquadstrat(op::QuasiLocalSingleLayerMW, + tfs::RTRefSpace, bfs::RTRefSpace) = DoubleNumSauterQstrat(4,5,5,5,4,3) + +oprange(op::QuasiLocalSingleLayerMW) = op.range +Base.:*(a::T, op::QuasiLocalSingleLayerMW) where {T<:Number} = + QuasiLocalSingleLayerMW(op.gamma, a*op.alpha, a*op.beta, op.range, op.nominator) + +struct ExponentialNominator{R} + gamma::R +end +function (f::ExponentialNominator)(x,y) + exp(-f.gamma * norm(x-y)) +end + +function QuasiLocalSingleLayerMW(gamma, range) + f = ExponentialNominator(gamma) + QuasiLocalSingleLayerMW(gamma, -gamma, -1/(gamma), range, f) +end + +function QuasiLocalSingleLayerMW(gamma, range, f) + QuasiLocalSingleLayerMW(gamma, -gamma, -1/(gamma), range, f) +end + +function (igd::Integrand{<:QuasiLocalSingleLayerMW})(x,y,f,g) + α = igd.operator.alpha + β = igd.operator.beta + γ = igd.operator.gamma + F = igd.operator.nominator + + cx = cartesian(x) + cy = cartesian(y) + r = cx - cy + R = norm(r) + # iR = 1 / R + # green = exp(-γ*R) / (4π*R) + green = F(cx,cy) / (4π*R) + + αG = α * green + βG = β * green + + _integrands(f,g) do fi,gj + αG * dot(fi.value, gj.value) + βG * dot(fi.divergence, gj.divergence) + end +end + +@testitem "quasi-local singlelayer MW" begin + using CompScienceMeshes, LinearAlgebra + using SparseArrays + + fn = joinpath(dirname(pathof(BEAST)),"../test","assets","sphere45.in") + Γ1 = readmesh(fn) + Γ2 = CompScienceMeshes.translate(Γ1, point(0,0,3.0)) + # Γ = meshsphere(radius=1.0, h=0.075) + + import Pkg + # @show pathof(BEAST.CollisionDetection) + # for d in Pkg.project().dependencies + # @show d + # end + + δ = 0.025 + γ = 1/δ + t1 = BEAST.QuasiLocalSingleLayerMW(γ, 12*δ) + t2 = BEAST.MWSingleLayer3D(γ) + X1 = raviartthomas(Γ1) + X2 = raviartthomas(Γ2) + + qs = BEAST.defaultquadstrat(t1, X1, X1) + # @show qs + + @time Z11 = assemble(t1, X1, X1) + @time W11 = assemble(t2, X1, X1) + @time Z12 = assemble(t1, X1, X2) + @time W12 = assemble(t2, X1, X2) + # @show norm(Z11-W11) + # @show norm(W11) + # @show norm(Z12-W12) + # @show norm(W12) + # @show norm(Z12) + @show length(nonzeros(Z11)) / length(Z11) + + @test norm(Z11-W11) < 1e-5 + @test norm(Z12-W12) < 1e-19 + @test norm(Z12) == 0 + @test length(nonzeros(Z11)) / length(Z11) < 0.86 + + @hilbertspace j + @hilbertspace k + Q1 = assemble(t1[k,j], j∈X1, k∈X1) + Q2 = assemble(t1[k,j] + t1[k,j], j∈X1, k∈X1) + + @test Q1.lmap.A.lmap isa SparseMatrixCSC + @test Q2.maps[1].lmap.A.lmap isa SparseMatrixCSC +end \ No newline at end of file diff --git a/src/maxwell/sauterschwabints_bdm.jl b/src/maxwell/sauterschwabints_bdm.jl deleted file mode 100644 index 3b89272f..00000000 --- a/src/maxwell/sauterschwabints_bdm.jl +++ /dev/null @@ -1,157 +0,0 @@ -struct MWSL3DIntegrand2{C,O,L} - test_triangular_element::C - trial_triangular_element::C - op::O - test_local_space::L - trial_local_space::L -end - -function (igd::MWSL3DIntegrand2)(u,v) - α = igd.op.α - β = igd.op.β - γ = igd.op.gamma - - x = neighborhood(igd.test_triangular_element,u) - y = neighborhood(igd.trial_triangular_element,v) - - r = cartesian(x) - cartesian(y) - R = norm(r) - G = exp(-γ*R)/(4π*R) - - f = igd.test_local_space(x) - g = igd.trial_local_space(y) - - j = jacobian(x) * jacobian(y) - - αjG = α*j*G - βjG = β*j*G - - G = @SVector[αjG*g[i].value for i in 1:6] - H = @SVector[βjG*g[i].divergence for i in 1:6] - - @SMatrix[dot(f[i].value,G[j])+f[i].divergence*H[j] for i in 1:6, j in 1:6] -end - -function momintegrals!(op::MWSingleLayer3D, - test_local_space::BDMRefSpace, trial_local_space::BDMRefSpace, - test_triangular_element, trial_triangular_element, out, strat::SauterSchwabStrategy) - - I, J, K, L = SauterSchwabQuadrature.reorder( - test_triangular_element.vertices, - trial_triangular_element.vertices, strat) - - test_triangular_element = simplex( - test_triangular_element.vertices[I[1]], - test_triangular_element.vertices[I[2]], - test_triangular_element.vertices[I[3]]) - - trial_triangular_element = simplex( - trial_triangular_element.vertices[J[1]], - trial_triangular_element.vertices[J[2]], - trial_triangular_element.vertices[J[3]]) - - igd = MWSL3DIntegrand2(test_triangular_element, trial_triangular_element, - op, test_local_space, trial_local_space) - G = sauterschwab_parameterized(igd, strat) - - A = levicivita(K) == 1 ? @SVector[1,2] : @SVector[2,1] - B = levicivita(L) == 1 ? @SVector[1,2] : @SVector[2,1] - for j ∈ 1:3 - q = L[j] - for i ∈ 1:3 - p = K[i] - for n in 1:2 - b = B[n] - for m in 1:2 - a = A[m] - out[2*(i-1)+m,2*(j-1)+n] += G[2*(p-1)+a,2*(q-1)+b] - end - end - end - end - - nothing -end - - -struct MWDL3DIntegrand2{C,O,L} - test_triangular_element::C - trial_triangular_element::C - op::O - test_local_space::L - trial_local_space::L -end - -function (igd::MWDL3DIntegrand2)(u,v) - - γ = igd.op.gamma - - x = neighborhood(igd.test_triangular_element,u) - y = neighborhood(igd.trial_triangular_element,v) - - r = cartesian(x) - cartesian(y) - R = norm(r) - G = exp(-γ*R)/(4π*R) - - GG = -(γ + 1/R) * G / R * r - T = @SMatrix [ - 0 -GG[3] GG[2] - GG[3] 0 -GG[1] - -GG[2] GG[1] 0 ] - - f = igd.test_local_space(x) - g = igd.trial_local_space(y) - - jx = jacobian(x) - jy = jacobian(y) - j = jx*jy - - G1 = j*T*g[1][1] - G2 = j*T*g[2][1] - G3 = j*T*g[3][1] - - f1 = f[1][1] - f2 = f[2][1] - f3 = f[3][1] - - SMatrix{3,3}( - dot(f1,G1), - dot(f2,G1), - dot(f3,G1), - dot(f1,G2), - dot(f2,G2), - dot(f3,G2), - dot(f1,G3), - dot(f2,G3), - dot(f3,G3),) -end - - -function momintegrals!(op::MWDoubleLayer3D, - test_local_space::BDMRefSpace, trial_local_space::BDMRefSpace, - test_triangular_element, trial_triangular_element, out, strat::SauterSchwabStrategy) - - I, J, K, L = SauterSchwabQuadrature.reorder( - test_triangular_element.vertices, - trial_triangular_element.vertices, strat) - - test_triangular_element = simplex( - test_triangular_element.vertices[I[1]], - test_triangular_element.vertices[I[2]], - test_triangular_element.vertices[I[3]]) - - trial_triangular_element = simplex( - trial_triangular_element.vertices[J[1]], - trial_triangular_element.vertices[J[2]], - trial_triangular_element.vertices[J[3]]) - - igd = MWDL3DIntegrand2(test_triangular_element, trial_triangular_element, - op, test_local_space, trial_local_space) - Q = sauterschwab_parameterized(igd, strat) - for j ∈ 1:3 - for i ∈ 1:3 - out[i,j] += Q[K[i],L[j]] - end - end - nothing -end diff --git a/src/maxwell/sauterschwabints_bdm_rt.jl b/src/maxwell/sauterschwabints_bdm_rt.jl new file mode 100644 index 00000000..b03d6ee8 --- /dev/null +++ b/src/maxwell/sauterschwabints_bdm_rt.jl @@ -0,0 +1,95 @@ + + +struct MWDL3DIntegrand3{C,O,L,M} + test_triangular_element::C + trial_triangular_element::C + op::O + test_local_space::L + trial_local_space::M +end + +function (igd::MWDL3DIntegrand3)(u,v) + + γ = igd.op.gamma + + x = neighborhood(igd.test_triangular_element,u) + y = neighborhood(igd.trial_triangular_element,v) + + r = cartesian(x) - cartesian(y) + R = norm(r) + G = exp(-γ*R)/(4π*R) + + GG = -(γ + 1/R) * G / R * r + + f = igd.test_local_space(x) #BDM + g = igd.trial_local_space(y) #RT + + j = jacobian(x) * jacobian(y) + + G = @SVector [cross(GG,(j*g[1].value)), cross(GG,(j*g[2].value)), cross(GG,(j*g[3].value))] + + + f1 = f[1].value + f2 = f[2].value + f3 = f[3].value + f4 = f[4].value + f5 = f[5].value + f6 = f[6].value + + SMatrix{6,3}( + dot(f1,G[1]), + dot(f2,G[1]), + dot(f3,G[1]), + dot(f4,G[1]), + dot(f5,G[1]), + dot(f6,G[1]), + dot(f1,G[2]), + dot(f2,G[2]), + dot(f3,G[2]), + dot(f4,G[2]), + dot(f5,G[2]), + dot(f6,G[2]), + dot(f1,G[3]), + dot(f2,G[3]), + dot(f3,G[3]), + dot(f4,G[3]), + dot(f5,G[3]), + dot(f6,G[3])) +end + + +function momintegrals!(op::MWDoubleLayer3D, + test_local_space::BDMRefSpace, trial_local_space::RTRefSpace, + test_triangular_element, trial_triangular_element, out, strat::SauterSchwabStrategy) + + I, J, K, L = SauterSchwabQuadrature.reorder( + test_triangular_element.vertices, + trial_triangular_element.vertices, strat) + + test_triangular_element = simplex( + test_triangular_element.vertices[I[1]], + test_triangular_element.vertices[I[2]], + test_triangular_element.vertices[I[3]]) + + trial_triangular_element = simplex( + trial_triangular_element.vertices[J[1]], + trial_triangular_element.vertices[J[2]], + trial_triangular_element.vertices[J[3]]) + + igd = MWDL3DIntegrand3(test_triangular_element, trial_triangular_element, + op, test_local_space, trial_local_space) + Q = SauterSchwabQuadrature.sauterschwab_parameterized(igd, strat) + + A = levicivita(K) == 1 ? @SVector[1,2] : @SVector[2,1] + + for j ∈ 1:3 + for i ∈ 1:3 + p = K[i] + for m in 1:2 + a = A[m] + out[2*(i-1)+m,j] += Q[2*(p-1)+a,L[j]] + end + end + end + nothing +end diff --git a/src/maxwell/sauterschwabints_rt.jl b/src/maxwell/sauterschwabints_rt.jl deleted file mode 100644 index 7095570f..00000000 --- a/src/maxwell/sauterschwabints_rt.jl +++ /dev/null @@ -1,173 +0,0 @@ -struct MWSL3DIntegrand{C,O,L} - test_triangular_element::C - trial_triangular_element::C - op::O - test_local_space::L - trial_local_space::L -end - -const i4pi = 1 / (4pi) -function (igd::MWSL3DIntegrand)(u,v) - α = igd.op.α - β = igd.op.β - γ = igd.op.gamma - - x = neighborhood(igd.test_triangular_element,u) - y = neighborhood(igd.trial_triangular_element,v) - - r = cartesian(x) - cartesian(y) - R = norm(r) - iR = 1 / R - G = exp(-γ*R)*(i4pi*iR) - - f = igd.test_local_space(x) - g = igd.trial_local_space(y) - - j = jacobian(x) * jacobian(y) - - jG = j*G - αjG = α*jG - βjG = β*jG - - G = @SVector [αjG*g[1].value, αjG*g[2].value, αjG*g[3].value] - H = @SVector [βjG*g[1].divergence, βjG*g[2].divergence, βjG*g[3].divergence] - - SMatrix{3,3}(( - dot(f[1].value,G[1]) + f[1].divergence*H[1], - dot(f[2].value,G[1]) + f[2].divergence*H[1], - dot(f[3].value,G[1]) + f[3].divergence*H[1], - dot(f[1].value,G[2]) + f[1].divergence*H[2], - dot(f[2].value,G[2]) + f[2].divergence*H[2], - dot(f[3].value,G[2]) + f[3].divergence*H[2], - dot(f[1].value,G[3]) + f[1].divergence*H[3], - dot(f[2].value,G[3]) + f[2].divergence*H[3], - dot(f[3].value,G[3]) + f[3].divergence*H[3])) -end - -struct MWDL3DIntegrand{C,O,L} - test_triangular_element::C - trial_triangular_element::C - op::O - test_local_space::L - trial_local_space::L -end - -function (igd::MWDL3DIntegrand)(u,v) - - γ = igd.op.gamma - - x = neighborhood(igd.test_triangular_element,u) - y = neighborhood(igd.trial_triangular_element,v) - - r = cartesian(x) - cartesian(y) - R = norm(r) - iR = 1/R - G = exp(-γ*R)*(iR*i4pi) - - f = igd.test_local_space(x) - g = igd.trial_local_space(y) - - j = jacobian(x) * jacobian(y) - - GG = -(γ + iR) * G * (iR * r) - - G = @SVector [cross(GG,(j*g[1].value)), cross(GG,(j*g[2].value)), cross(GG,(j*g[3].value))] - SMatrix{3,3}(( - dot(f[1].value, G[1]), - dot(f[2].value, G[1]), - dot(f[3].value, G[1]), - dot(f[1].value, G[2]), - dot(f[2].value, G[2]), - dot(f[3].value, G[2]), - dot(f[1].value, G[3]), - dot(f[2].value, G[3]), - dot(f[3].value, G[3]))) -end - -kernel_in_bary(op::MWSingleLayer3D, - test_local_space::RTRefSpace, trial_local_space::RTRefSpace, - test_chart, trial_chart) = MWSL3DIntegrand( - test_chart, trial_chart, op, test_local_space, trial_local_space) - -kernel_in_bary(op::MWDoubleLayer3D, - test_local_space::RTRefSpace, trial_local_space::RTRefSpace, - test_chart, trial_chart) = MWDL3DIntegrand( - test_chart, trial_chart, op, test_local_space, trial_local_space) - -const MWOperator3D = Union{MWSingleLayer3D, MWDoubleLayer3D} -function momintegrals!(op::MWOperator3D, - test_local_space::RTRefSpace, trial_local_space::RTRefSpace, - test_triangular_element, trial_triangular_element, out, strat::SauterSchwabStrategy) - - I, J, K, L = SauterSchwabQuadrature.reorder( - test_triangular_element.vertices, - trial_triangular_element.vertices, strat) - - test_triangular_element = simplex( - test_triangular_element.vertices[I[1]], - test_triangular_element.vertices[I[2]], - test_triangular_element.vertices[I[3]]) - - trial_triangular_element = simplex( - trial_triangular_element.vertices[J[1]], - trial_triangular_element.vertices[J[2]], - trial_triangular_element.vertices[J[3]]) - - # igd = MWSL3DIntegrand(test_triangular_element, trial_triangular_element, - # op, test_local_space, trial_local_space) - igd = kernel_in_bary(op, test_local_space, trial_local_space, - test_triangular_element, trial_triangular_element) - G = sauterschwab_parameterized(igd, strat) - for j ∈ 1:3, i ∈ 1:3 - out[i,j] += G[K[i],L[j]] - end - - nothing -end - -function momintegrals_nested!(op::MWOperator3D, - test_local_space::RTRefSpace, trial_local_space::RTRefSpace, - test_chart, trial_chart, out, strat::SauterSchwabStrategy) - - # 1. Refine the trial_chart - p1, p2, p3 = trial_chart.vertices - e1 = cartesian(neighborhood(trial_chart, (0,1/2))) - - e2 = cartesian(neighborhood(trial_chart, (1/2,0))) - e3 = cartesian(neighborhood(trial_chart, (1/2,1/2))) - ct = cartesian(center(trial_chart)) - refined_trial_chart = [ - simplex(ct, p1, e3), - simplex(ct, e3, p2), - simplex(ct, p2, e1), - simplex(ct, e1, p3), - simplex(ct, p3, e2), - simplex(ct, e2, p1)] - - qd = quaddata(op, test_local_space, trial_local_space, - [test_chart], refined_trial_chart) - - for (q,chart) in enumerate(refined_trial_chart) - qr = quadrule(op, test_local_space, trial_local_space, - 1, test_chart, q ,chart, qd) - - Q = restrict(trial_local_space, trial_chart, chart) - zlocal = zero(out) - momintegrals!(op, test_local_space, trial_local_space, - test_chart, chart, zlocal, qr) - - for j in 1:3 - for i in 1:3 - for k in 1:3 - out[i,j] += zlocal[i,k] * Q[j,k] - end end end - end -end - -function momintegrals_nested!(op::IntegralOperator, - test_local_space::RefSpace, trial_local_space::RefSpace, - test_chart, trial_chart, out, strat) - - momintegrals!(op, test_local_space, trial_local_space, - test_chart, trial_chart, out, strat) -end diff --git a/src/maxwell/sourcefield.jl b/src/maxwell/sourcefield.jl index 792e5d31..4520e89f 100644 --- a/src/maxwell/sourcefield.jl +++ b/src/maxwell/sourcefield.jl @@ -1,6 +1,6 @@ -struct SourceField{F} <: Functional - f::F -end +# struct SourceField{T,F} <: Functional{T} +# field::F +# end -(s::SourceField)(p) = s.f(cartesian(p)) -integrand(f::SourceField, tval, fval) = dot(fval, tval.value) +# (s::SourceField)(p) = s.f(cartesian(p)) +# integrand(f::SourceField, tval, fval) = dot(fval, tval.value) diff --git a/src/maxwell/timedomain/mwtdexc.jl b/src/maxwell/timedomain/mwtdexc.jl index b2f651f4..1ab074e6 100644 --- a/src/maxwell/timedomain/mwtdexc.jl +++ b/src/maxwell/timedomain/mwtdexc.jl @@ -5,7 +5,11 @@ mutable struct PlaneWaveMWTD{T,F,P} <: TDFunctional{T} amplitude::F end +""" + planewave(polarisation,direction,amplitude,speedoflight) +Time-domain plane wave. +""" function planewave(polarisation,direction,amplitude,speedoflight) PlaneWaveMWTD(direction,polarisation,speedoflight,amplitude) end @@ -31,6 +35,7 @@ cross(k, pw::PlaneWaveMWTD) = PlaneWaveMWTD( function (f::PlaneWaveMWTD)(r,t) t = cartesian(t)[1] + r = cartesian(r) dr = zero(typeof(t)) for i in 1 : 3 dr += r[i]*f.direction[i] diff --git a/src/maxwell/timedomain/mwtdops.jl b/src/maxwell/timedomain/mwtdops.jl index b0139e2b..dbc73aa1 100644 --- a/src/maxwell/timedomain/mwtdops.jl +++ b/src/maxwell/timedomain/mwtdops.jl @@ -14,7 +14,7 @@ mutable struct MWSingleLayerTDIO{T} <: RetardedPotential{T} end function Base.:*(a::Number, op::MWSingleLayerTDIO) - @info "scalar product a * op (SL)" + # @info "scalar product a * op (SL)" MWSingleLayerTDIO( op.speed_of_light, a * op.ws_weight, @@ -30,7 +30,7 @@ mutable struct MWDoubleLayerTDIO{T} <: RetardedPotential{T} end function Base.:*(a::Number, op::MWDoubleLayerTDIO) - @info "scalar product a * op (DL)" + # @info "scalar product a * op (DL)" MWDoubleLayerTDIO( op.speed_of_light, a * op.weight, @@ -44,7 +44,7 @@ mutable struct MWDoubleLayerTransposedTDIO{T} <: RetardedPotential{T} end function Base.:*(a::Number, op::MWDoubleLayerTransposedTDIO) - @info "scalar product a * op (DL)" + # @info "scalar product a * op (DL)" MWDoubleLayerTransposedTDIO( op.speed_of_light, a * op.weight, @@ -74,8 +74,10 @@ end # module TDMaxwell3D export TDMaxwell3D +defaultquadstrat(::MWSingleLayerTDIO, tfs, bfs) = OuterNumInnerAnalyticQStrat(3) + function quaddata(op::MWSingleLayerTDIO, testrefs, trialrefs, timerefs, - testels, trialels, timeels) + testels, trialels, timeels, quadstrat::OuterNumInnerAnalyticQStrat) dmax = numfunctions(timerefs)-1 bn = binomial.((0:dmax),(0:dmax)') @@ -83,12 +85,12 @@ function quaddata(op::MWSingleLayerTDIO, testrefs, trialrefs, timerefs, V = eltype(testels[1].vertices) ws = WiltonInts84.workspace(V) # quadpoints(testrefs, testels, (3,)), bn, ws - quadpoints(testrefs, testels, (3,)), bn, ws + quadpoints(testrefs, testels, (quadstrat.outer_rule,)), bn, ws end quadrule(op::MWSingleLayerTDIO, testrefs, trialrefs, timerefs, - p, testel, q, trialel, r, timeel, qd) = WiltonInts84Strat(qd[1][1,p],qd[2],qd[3]) + p, testel, q, trialel, r, timeel, qd, ::OuterNumInnerAnalyticQStrat) = WiltonInts84Strat(qd[1][1,p],qd[2],qd[3]) struct TransposedStorage{F} @@ -98,46 +100,10 @@ end @inline (f::TransposedStorage)(v,m,n,k) = f.store(v,n,m,k) -# function allocatestorage(op::MWDoubleLayerTDIO, testST, basisST, -# ::Type{Val{:bandedstorage}}, -# ::Type{LongDelays{:ignore}}) - -# # tfs = spatialbasis(testST) -# # bfs = spatialbasis(basisST) -# X, T = spatialbasis(testST), temporalbasis(testST) -# Y, U = spatialbasis(basisST), temporalbasis(basisST) - -# if CompScienceMeshes.refines(geometry(Y), geometry(X)) -# testST = Y⊗T -# basisST = X⊗U -# end - -# M = numfunctions(X) -# N = numfunctions(Y) - -# K0 = fill(typemax(Int), M, N) -# K1 = zeros(Int, M, N) - -# function store(v,m,n,k) -# K0[m,n] = min(K0[m,n],k) -# K1[m,n] = max(K1[m,n],k) -# end - -# aux = EmptyRP(op.speed_of_light) -# print("Allocating memory for convolution operator: ") -# assemble!(aux, testST, basisST, store) -# println("\nAllocated memory for convolution operator.") - -# maxk1 = maximum(K1) -# bandwidth = maximum(K1 .- K0 .+ 1) -# data = zeros(eltype(op), bandwidth, M, N) -# Z = SparseND.Banded3D(K0, data, maxk1) -# store1(v,m,n,k) = (Z[m,n,k] += v) -# return ()->Z, store1 -# end - function assemble!(dl::MWDoubleLayerTDIO, W::SpaceTimeBasis, V::SpaceTimeBasis, store, - threading=Threading{:multi}) + threading::Type{Threading{:multi}}; quadstrat=defaultquadstrat(dl,W,V)) + + quadstrat = quadstrat(dl, W, V) X, T = spatialbasis(W), temporalbasis(W) Y, U = spatialbasis(V), temporalbasis(V) @@ -154,48 +120,54 @@ function assemble!(dl::MWDoubleLayerTDIO, W::SpaceTimeBasis, V::SpaceTimeBasis, Y, S = spatialbasis(W), temporalbasis(W) splits = [round(Int,s) for s in range(0, stop=numfunctions(Y), length=P+1)] - @info "Starting assembly with $P threads:" + # @info "Starting assembly with $P threads:" Threads.@threads for i in 1:P lo, hi = splits[i]+1, splits[i+1] lo <= hi || continue Y_p = subset(Y, lo:hi) store2 = (v,m,n,k) -> store(v,lo+m-1,n,k) - assemble_chunk!(dl, Y_p ⊗ S, V, store2) + assemble_chunk!(dl, Y_p ⊗ S, V, store2; quadstrat) end # return assemble_chunk!(dl, W, V, store1) end +defaultquadstrat(::MWDoubleLayerTDIO, tfs, bfs) = OuterNumInnerAnalyticQStrat(3) + function quaddata(op::MWDoubleLayerTDIO, testrefs, trialrefs, timerefs, - testels, trialels, timeels) + testels, trialels, timeels, quadstrat::OuterNumInnerAnalyticQStrat) dmax = numfunctions(timerefs)-1 bn = binomial.((0:dmax),(0:dmax)') V = eltype(testels[1].vertices) ws = WiltonInts84.workspace(V) - # quadpoints(testrefs, testels, (3,)), bn, ws - quadpoints(testrefs, testels, (3,)), bn, ws + + quadpoints(testrefs, testels, (quadstrat.outer_rule,)), bn, ws end quadrule(op::MWDoubleLayerTDIO, testrefs, trialrefs, timerefs, - p, testel, q, trialel, r, timeel, qd) = WiltonInts84Strat(qd[1][1,p],qd[2],qd[3]) + p, testel, q, trialel, r, timeel, qd, quadstrat::OuterNumInnerAnalyticQStrat) = + WiltonInts84Strat(qd[1][1,p],qd[2],qd[3]) + +defaultquadstrat(::MWDoubleLayerTransposedTDIO, tfs, bfs) = OuterNumInnerAnalyticQStrat(3) function quaddata(op::MWDoubleLayerTransposedTDIO, testrefs, trialrefs, timerefs, - testels, trialels, timeels) + testels, trialels, timeels, quadstrat::OuterNumInnerAnalyticQStrat) dmax = numfunctions(timerefs)-1 bn = binomial.((0:dmax),(0:dmax)') V = eltype(testels[1].vertices) ws = WiltonInts84.workspace(V) - quadpoints(testrefs, testels, (3,)), bn, ws + quadpoints(testrefs, testels, (quadstrat.outer_rule,)), bn, ws end quadrule(op::MWDoubleLayerTransposedTDIO, testrefs, trialrefs, timerefs, - p, testel, q, trialel, r, timeel, qd) = WiltonInts84Strat(qd[1][1,p],qd[2],qd[3]) + p, testel, q, trialel, r, timeel, qd, quadstrat::OuterNumInnerAnalyticQStrat) = + WiltonInts84Strat(qd[1][1,p],qd[2],qd[3]) function momintegrals!(z, op::MWDoubleLayerTransposedTDIO, g, f, T, τ, σ, ι, qr::WiltonInts84Strat) @@ -296,10 +268,13 @@ function innerintegrals!(zl, op::MWSingleLayerTDIO, sol5 = sol4*sol solpowers = (one(sol), sol, sol2, sol3, sol4, sol5) - for i in 1 : numfunctions(U) + udim = numfunctions(U, domain(τ)) + vdim = numfunctions(V, domain(σ)) + + for i in 1 : udim a = τ[i] g = (x-a) - for j in 1 : numfunctions(V) + for j in 1 : vdim b = σ[j]; bξ = ξ-b for k in 1 : numfunctions(W) d = k-1 # ranges from 0 to numfunctions(W)-1 @@ -368,11 +343,14 @@ function innerintegrals!(z, op::MWDoubleLayerTDIO, Ux = U(p) Vx = αf * @SVector[(x-σ[1]), (x-σ[2]), (x-σ[3])] - for i in 1 : numfunctions(U) + udim = numfunctions(U, domain(τ)) + vdim = numfunctions(V, domain(σ)) + + for i in 1 : udim # a = τ[i] # g = αg * (x-τ[i]) g = Ux[i].value - for j in 1 : numfunctions(V) + for j in 1 : vdim # b = σ[j] # f = αf * (x-σ[j]) # f = Vx[j].value diff --git a/src/maxwell/wiltonints.jl b/src/maxwell/wiltonints.jl index 77287d76..9b715446 100644 --- a/src/maxwell/wiltonints.jl +++ b/src/maxwell/wiltonints.jl @@ -1,14 +1,15 @@ import WiltonInts84 -mutable struct WiltonSEStrategy{P,Q} <: SingularityExtractionStrategy +mutable struct WiltonSERule{P,Q} <: SingularityExtractionRule outer_quad_points::P - regularpart_quadrule::DoubleQuadStrategy{P,Q} + regularpart_quadrule::Q end function innerintegrals!(op::MWSingleLayer3DSng, p, g, f, t, s, z, - strat::WiltonSEStrategy, dx) + strat::WiltonSERule, dx) γ = op.gamma + T = typeof(γ) x = cartesian(p) n = cross(s[1]-s[3],s[2]-s[3]) n /= norm(n) @@ -28,13 +29,16 @@ function innerintegrals!(op::MWSingleLayer3DSng, p, g, f, t, s, z, c₁ = op.α c₂ = op.β + num_tshapes = numfunctions(g, domain(t)) + num_bshapes = numfunctions(f, domain(s)) + α = 1 / volume(t) / volume(s) / 4 - for i in 1 : numfunctions(g) + for i in 1 : num_bshapes a = t[i] g = x - a dg = 2 - for j in 1 : numfunctions(f) + for j in 1 : num_tshapes b = s[j] ∫Gf = SVector(∫Gy[1]-∫G*b[1], ∫Gy[2]-∫G*b[2], ∫Gy[3]-∫G*b[3]) @@ -48,9 +52,10 @@ function innerintegrals!(op::MWSingleLayer3DSng, p, g, f, t, s, z, end -function innerintegrals!(op::MWDoubleLayer3DSng, p, g, f, t, s, z, strat::WiltonSEStrategy, dx) +function innerintegrals!(op::MWDoubleLayer3DSng, p, g, f, t, s, z, strat::WiltonSERule, dx) γ = op.gamma + T=typeof(γ) x = cartesian(p) n = cross(s[1]-s[3],s[2]-s[3]) n /= norm(n) @@ -58,15 +63,17 @@ function innerintegrals!(op::MWDoubleLayer3DSng, p, g, f, t, s, z, strat::Wilton scal, vec, grad = WiltonInts84.wiltonints(s[1], s[2], s[3], x, Val{1}) - # \int \nabla G_s with G_s = \nabla (1/R + 0.5*γ^2*R) / (4\pi) - ∫∇G = (-grad[1] - 0.5*γ^2*grad[3]) / (4π) + num_tshapes = numfunctions(g, domain(t)) + num_bshapes = numfunctions(f, domain(s)) + # \int \nabla G_s with G_s = \nabla (1/R + 0.5*γ^2*R) / (4\pi) + ∫∇G = T.((-grad[1] - 0.5*γ^2*grad[3]) / (4π)) α = 1 / volume(t) / volume(s) / 4 - for i in 1 : numfunctions(g) + for i in 1 : num_tshapes a = t[i] g = (x - a) - for j in 1 : numfunctions(f) + for j in 1 : num_bshapes b = s[j] z[i,j] += ( α * ( (x-b) × g ) ⋅ ∫∇G ) * dx diff --git a/src/multiplicativeop.jl b/src/multiplicativeop.jl index 06920208..2834590d 100644 --- a/src/multiplicativeop.jl +++ b/src/multiplicativeop.jl @@ -2,6 +2,6 @@ struct Multiplicative{f} <: LocalOperator field::f end -kernelvals(biop::Multiplicative, x) = Nothing +kernelvals(biop::Multiplicative, x) = nothing integrand(op::Multiplicative, kernel, x, g, f) = dot(f[1], op.field(cartesian(x))*g[1]) scalartype(op::Multiplicative) = Float64 diff --git a/src/operator.jl b/src/operator.jl index d34d0a51..0b35aa46 100644 --- a/src/operator.jl +++ b/src/operator.jl @@ -6,20 +6,24 @@ struct Threading{T} end import Base: transpose, +, -, * abstract type AbstractOperator end - -#@linearspace AbstractOperator{T} T - """ *Atomic operator*: one that assemblechunk can deal with """ abstract type Operator <: AbstractOperator end +abstract type IntegralOperator <: Operator end -mutable struct TransposedOperator <: Operator - op::Operator +mutable struct TransposedOperator <: AbstractOperator + op::AbstractOperator end scalartype(op::TransposedOperator) = scalartype(op.op) +defaultquadstrat(op::TransposedOperator, tfs::Space, bfs::Space) = defaultquadstrat(op.op, tfs, bfs) + +""" + LinearCombinationOfOperators{T} <: AbstractOperator +A linear combination of operators. +""" mutable struct LinearCombinationOfOperators{T} <: AbstractOperator coeffs::Vector{T} ops::Vector @@ -74,42 +78,63 @@ end transpose(op::TransposedOperator) = op.op transpose(op::Operator) = TransposedOperator(op) +defaultquadstrat(lc::LinearCombinationOfOperators, tfs, bfs) = + [defaultquadstrat(op,tfs,bfs) for op in lc.ops] + +""" + assemble(operator, test_functions, trial_functions; + storage_policy = Val{:bandedstorage}, + threading = Threading{:multi}, + quadstrat=defaultquadstrat(operator, test_functions, trial_functions)) +Assemble the system matrix corresponding to the operator `operator` tested with the test functions `test_functions` and the trial functions `trial_functions`. +""" function assemble(operator::AbstractOperator, test_functions, trial_functions; storage_policy = Val{:bandedstorage}, - long_delays_policy = LongDelays{:compress}, - threading = Threading{:multi}) - # This is a convenience function whose only job is to allocate - # the storage for the interaction matrix. Further dispatch on - # operator and space types is handled by the 4-argument version + threading = Threading{:multi}, + quadstrat=defaultquadstrat) + Z, store = allocatestorage(operator, test_functions, trial_functions, - storage_policy, long_delays_policy) - assemble!(operator, test_functions, trial_functions, store, threading) + storage_policy) + # qs = quadstrat(operator, test_functions, trial_functions) + assemble!(operator, test_functions, trial_functions, + store, threading; quadstrat) return Z() end + +function assemble(A::AbstractMatrix, testfns, trialfns; kwargs...) + @assert numfunctions(testfns) == size(A,1) + @assert numfunctions(trialfns) == size(A,2) + return A +end + function assemblerow(operator::AbstractOperator, test_functions, trial_functions, storage_policy = Val{:bandedstorage}, - long_delays_policy = LongDelays{:ignore}) + long_delays_policy = LongDelays{:ignore}; + quadstrat=defaultquadstrat(operator, test_functions, trial_functions)) Z, store = allocatestorage(operator, test_functions, trial_functions, storage_policy, long_delays_policy) - assemblerow!(operator, test_functions, trial_functions, store) + assemblerow!(operator, test_functions, trial_functions, store; quadstrat) + Z() end function assemblecol(operator::AbstractOperator, test_functions, trial_functions, storage_policy = Val{:bandestorage}, - long_delays_policy = LongDelays{:ignore}) + long_delays_policy = LongDelays{:ignore}; + quadstrat=defaultquadstrat(operator, test_functions, trial_functions)) Z, store = allocatestorage(operator, test_functions, trial_functions, storage_policy, long_delays_policy) - assemblecol!(operator, test_functions, trial_functions, store) + assemblecol!(operator, test_functions, trial_functions, store; quadstrat) + Z() end function allocatestorage(operator::AbstractOperator, test_functions, trial_functions, - storage_trait, longdelays_trait) + storage_trait=nothing, longdelays_trait=nothing) T = promote_type( scalartype(operator) , @@ -126,15 +151,15 @@ function allocatestorage(operator::AbstractOperator, test_functions, trial_funct end -function allocatestorage(operator::LinearCombinationOfOperators, - test_functions::SpaceTimeBasis, trial_functions::SpaceTimeBasis, - storage_policy::Type{Val{:bandedstorage}}, - long_delays_policy::Type{LongDelays{:ignore}}) +# function allocatestorage(operator::LinearCombinationOfOperators, +# test_functions::SpaceTimeBasis, trial_functions::SpaceTimeBasis, +# storage_policy::Type{Val{:bandedstorage}}, +# long_delays_policy::Type{LongDelays{:ignore}}) - # TODO: remove this ugly, ugly patch - return allocatestorage(operator.ops[end], test_functions, trial_functions, - storage_policy, long_delays_policy) -end +# # This works when are terms in the LC can share storage +# return allocatestorage(operator.ops[end], test_functions, trial_functions, +# storage_policy, long_delays_policy) +# end function allocatestorage(operator::LinearCombinationOfOperators, @@ -142,16 +167,24 @@ function allocatestorage(operator::LinearCombinationOfOperators, storage_policy::Type{Val{S}}, long_delays_policy::Type{LongDelays{L}}) where {L,S} - # TODO: remove this ugly, ugly patch + # This works when are terms in the LC can share storage return allocatestorage(operator.ops[end], test_functions, trial_functions, storage_policy, long_delays_policy) end +struct _OffsetStore{F} + store::F + row_offset::Int + col_offset::Int +end + +(f::_OffsetStore)(v,m,n) = f.store(v,m + f.row_offset, n + f.col_offset) -function assemble!(operator::Operator, test_functions::Space, trial_functions::Space, store, - threading::Type{Threading{:multi}} = Threading{:multi}) +function assemble!(operator::Operator, test_functions::Space, trial_functions::Space, + store, threading::Type{Threading{:multi}}; + quadstrat=defaultquadstrat) - @info "Multi-threaded assembly:" + quadstrat = quadstrat(operator, test_functions, trial_functions) P = Threads.nthreads() numchunks = P @@ -162,63 +195,133 @@ function assemble!(operator::Operator, test_functions::Space, trial_functions::S lo, hi = splits[i]+1, splits[i+1] lo <= hi || continue test_functions_p = subset(test_functions, lo:hi) - store1 = (v,m,n) -> store(v,lo+m-1,n) - assemblechunk!(operator, test_functions_p, trial_functions, store1) - end - -end - -function assemble!(operator::Operator, test_functions::Space, trial_functions::Space, store, - threading::Type{Threading{:single}}) + store1 = _OffsetStore(store, lo-1, 0) + assemblechunk!(operator, test_functions_p, trial_functions, store1, quadstrat=quadstrat) +end end - @info "Single-threaded assembly" +function assemble!(operator::Operator, test_functions::Space, trial_functions::Space, + store, threading::Type{Threading{:single}}; + quadstrat=defaultquadstrat) - assemblechunk!(operator, test_functions, trial_functions, store) + quadstrat = quadstrat(operator, test_functions, trial_functions) + assemblechunk!(operator, test_functions, trial_functions, store; quadstrat) end -function assemble!(op::TransposedOperator, tfs::Space, bfs::Space, store) +function assemble!(op::TransposedOperator, tfs::Space, bfs::Space, store, + threading::Type{Threading{:multi}} = Threading{:multi}; + quadstrat=defaultquadstrat(op, tfs, bfs)) store1(v,m,n) = store(v,n,m) - assemble!(op.op, bfs, tfs, store1) + assemble!(op.op, bfs, tfs, store1, threading; quadstrat) end function assemble!(op::LinearCombinationOfOperators, tfs::AbstractSpace, bfs::AbstractSpace, - store, threading = Threading{:multi}) + store, threading = Threading{:multi}; + quadstrat=defaultquadstrat) + for (a,A) in zip(op.coeffs, op.ops) store1(v,m,n) = store(a*v,m,n) - assemble!(A, tfs, bfs, store1) + qs = quadstrat(A, tfs, bfs) + assemble!(A, tfs, bfs, store1, threading; quadstrat=qs) end end # Support for direct product spaces -function assemble!(op::Operator, tfs::DirectProductSpace, bfs::Space, store, threading = Threading{:multi}) +function assemble!(op::AbstractOperator, tfs::DirectProductSpace, bfs::Space, + store, threading = Threading{:multi}; + quadstrat=defaultquadstrat(op, tfs[1], bfs)) + I = Int[0] for s in tfs.factors push!(I, last(I) + numfunctions(s)) end for (i,s) in enumerate(tfs.factors) store1(v,m,n) = store(v,m + I[i], n) - assemble!(op, s, bfs, store1) + assemble!(op, s, bfs, store1, threading; quadstrat) end end -function assemble!(op::Operator, tfs::Space, bfs::DirectProductSpace, store, threading=Threading{:multi}) +function assemble!(op::AbstractOperator, tfs::Space, bfs::DirectProductSpace, + store, threading=Threading{:multi}; + quadstrat=defaultquadstrat(op, tfs, bfs[1])) + J = Int[0] for s in bfs.factors push!(J, last(J) + numfunctions(s)) end for (j,s) in enumerate(bfs.factors) store1(v,m,n) = store(v,m,n + J[j]) - assemble!(op, tfs, s, store1) + assemble!(op, tfs, s, store1, threading; quadstrat) end end -function assemble!(op::Operator, tfs::DirectProductSpace, bfs::DirectProductSpace, store, threading=Threading{:multi}) +function assemble!(op::AbstractOperator, tfs::DirectProductSpace, bfs::DirectProductSpace, + store, threading=Threading{:multi}; + quadstrat=defaultquadstrat(op, tfs[1], bfs[1])) + I = Int[0] for s in tfs.factors push!(I, last(I) + numfunctions(s)) end for (i,s) in enumerate(tfs.factors) store1(v,m,n) = store(v,m + I[i],n) - assemble!(op, s, bfs, store1) + assemble!(op, s, bfs, store1, threading; quadstrat) + end +end + +# TODO: Remove BlockDiagonalOperator in favour of manipulations +# on the level of bilinear forms. + +# Discretisation and assembly of these operators +# will respect the direct product structure of the +# HilbertSpace/FiniteElmeentSpace +struct BlockDiagonalOperator <: AbstractOperator + op::AbstractOperator +end + +diag(op::AbstractOperator) = BlockDiagonalOperator(op) + +scalartype(op::BlockDiagonalOperator) = scalartype(op.op) +defaultquadstrat(op::BlockDiagonalOperator, U::DirectProductSpace, V::DirectProductSpace) = defaultquadstrat(op.op, U, V) +allocatestorage(op::BlockDiagonalOperator, X, Y, storage_trait, longdelays_trait) = + allocatestorage(op.op, X, Y, storage_trait, longdelays_trait) + +function assemble!(op::BlockDiagonalOperator, U::DirectProductSpace, V::DirectProductSpace, + store, threading=Threading{:multi}; + quadstrat = defaultquadstrat(op, U, V)) + + @assert length(U.factors) == length(V.factors) + I = Int[0]; for u in U.factors push!(I, last(I) + numfunctions(u)) end + J = Int[0]; for v in V.factors push!(J, last(J) + numfunctions(v)) end + + for (k,(u,v)) in enumerate(zip(U.factors, V.factors)) + store1(v,m,n) = store(v, I[k] + m, J[k] + n) + assemble!(op.op, u, v, store1, threading; quadstrat) + end +end + +# BlockFull is default so not sure when this exists -> remove + +struct BlockFullOperators <: AbstractOperator + op::AbstractOperator +end + +blocks(op::AbstractOperator) = BlockFullOperators(op) + +scalartype(op::BlockFullOperators) = scalartype(op.op) +defaultquadstrat(op::BlockFullOperators, U::DirectProductSpace, V::DirectProductSpace) = defaultquadstrat(op.op, U, V) + + +function assemble!(op::BlockFullOperators, U::DirectProductSpace, V::DirectProductSpace, + store, threading; + quadstrat = defaultquadstrat(op, U, V)) + + I = Int[0]; for u in U.factors push!(I, last(I) + numfunctions(u)) end + J = Int[0]; for v in V.factors push!(J, last(J) + numfunctions(v)) end + + for (k,u) in enumerate(U.factors) + for (l,v) in enumerate(V.factors) + store1(x,m,n) = store(x, I[k]+m, J[l]+n) + assemble!(op.op, u, v, store1, threading; quadstrat) + end end end diff --git a/src/operators/quasilocalops.jl b/src/operators/quasilocalops.jl new file mode 100644 index 00000000..ca7565af --- /dev/null +++ b/src/operators/quasilocalops.jl @@ -0,0 +1,117 @@ +abstract type QuasiLocalOperator <: IntegralOperator end + +function allocatestorage(op::QuasiLocalOperator, + test_functions, trial_functions, + storage::Type{Val{:bandedstorage}}) + + T = scalartype(op, test_functions, trial_functions) + + nt = Threads.nthreads() + M = Vector{Vector{Int}}(undef, nt) + N = Vector{Vector{Int}}(undef, nt) + V = Vector{Vector{T}}(undef, nt) + for i in 1:nt + M[i] = Vector{Int}() + N[i] = Vector{Int}() + V[i] = Vector{T}() + end + # M = Int[] + # N = Int[] + # V = T[] + + # @show M + + function storeq2(v,m,n) + tid = Threads.threadid() + push!(M[tid],m) + # @show length(M) + push!(N[tid],n) + push!(V[tid],v) + end + + function freeze() + nrows = numfunctions(test_functions) + ncols = numfunctions(trial_functions) + Mall = reduce(vcat, M) + Nall = reduce(vcat, N) + Vall = reduce(vcat, V) + S = sparse(Mall,Nall,Vall, nrows, ncols) + @info "Compression rate: $(length(nonzeros(S)) / (nrows*ncols))" + return S + end + + return freeze, storeq2 +end + + +function assemblechunk!(op::QuasiLocalOperator, tfs::Space, bfs::Space, store321; + quadstrat=defaultquadstrat(op, tfs, bfs)) + + tr = assemblydata(tfs); tr == nothing && return + br = assemblydata(bfs); br == nothing && return + + T = scalartype(op, tfs, bfs) + tol = sqrt(eps(real(T))) + + trefs = refspace(tfs) + brefs = refspace(bfs) + + tgeo = geometry(tfs) + bgeo = geometry(bfs) + + num_trefs = numfunctions(trefs, domain(chart(tgeo, first(tgeo)))) + num_brefs = numfunctions(brefs, domain(chart(bgeo, first(bgeo)))) + + tels, tad, tact = tr + bels, bad, bact = br + + @assert length(tels) == size(tad.data, 3) + @assert length(bels) == size(bad.data, 3) + + qd = quaddata(op, trefs, brefs, tels, bels, quadstrat) + zlocal = zeros(T, num_trefs, num_brefs) + tree = elementstree(bels, 1.1) + + δ = oprange(op) + tid = Threads.threadid() + + tid == 1 && print("dots out of 10: ") + todo, done, pctg = length(tels), 0, 0 + for (p,(tcell,tptr)) in enumerate(zip(tels, tact)) + + tc, ts = boundingbox(tcell.vertices) + # pred = (c,s) -> boxesoverlap(c,s,tc,ts + δ/2) + + for box in boxes(tree, tc, ts + δ/2) + for q in box + bcell = bels[q] + bc, bs = boundingbox(bcell.vertices) + norm(tc-bc) - 2*(ts+bs)*sqrt(3) > δ && continue + + bptr = bact[q] + @assert q <= length(bels) + @assert q <= size(bad.data, 3) + + fill!(zlocal, 0) + qrule = quadrule(op, trefs, brefs, p, tcell, q, bcell, qd, quadstrat) + momintegrals!(zlocal, op, + tfs, tptr, tcell, + bfs, bptr, bcell, qrule) + + for j in 1 : length(bad[q]) + for i in 1 : length(tad[p]) + zij = zlocal[i,j] + for (n,b) in bad[q][j] + zb = zij*b + for (m,a) in tad[p][i] + store321(a*zb, m, n) + end end end end end end + + done += 1 + new_pctg = round(Int, done / todo * 100) + if new_pctg > pctg + 9 + tid == 1 && print(".") + pctg = new_pctg + end end + tid == 1 && println("") +end \ No newline at end of file diff --git a/src/postproc.jl b/src/postproc.jl index 3e09edc5..92901f2c 100644 --- a/src/postproc.jl +++ b/src/postproc.jl @@ -10,18 +10,22 @@ function facecurrents(coeffs, basis) T = eltype(coeffs) RT = real(T) + mesh = geometry(basis) + dom = domain(chart(mesh, first(mesh))) + refs = refspace(basis) - numrefs = numfunctions(refs) + numrefs = numfunctions(refs, dom) cells, tad, a2g = assemblydata(basis) - mesh = geometry(basis) D = dimension(mesh) # U = D+1 U = 3 - # TODO: express relative to input types - PT = SVector{U, T} + # TODO: remove ugliness + vals = refs(center(first(cells))) + PT = typeof(first(coeffs)*vals[1][1]) + fcr = zeros(PT, numcells(mesh)) for (t,cell) in enumerate(cells) @@ -98,12 +102,12 @@ function facecurrents(u, X1, Xs...) offset = 0 n = numfunctions(X1) - fcrs = [ facecurrents(u[offset + (1:n)], X1)[1] ] + fcrs = [ facecurrents(u[offset .+ (1:n)], X1)[1] ] offset += n for i in 1:length(Xs) n = numfunctions(Xs[i]) - push!(fcrs, facecurrents(u[offset + (1:n)], Xs[i])[1]) + push!(fcrs, facecurrents(u[offset .+ (1:n)], Xs[i])[1]) offset += n end @@ -125,50 +129,61 @@ function facecurrents(u, X::DirectProductSpace) fcr, m end -function potential(op, points, coeffs, basis) - T = SVector{3,ComplexF64} - ff = zeros(T, size(points)) + +""" + potential(op, points, coeffs, basis) + +Evaluate operator for a given bases and expansion coefficients at the given points. +""" +function potential(op, points, coeffs, basis; + type=SVector{3,ComplexF64}, + quadstrat=defaultquadstrat(op, basis)) + + ff = zeros(type, size(points)) store(v,m,n) = (ff[m] += v*coeffs[n]) - potential!(store, op, points, basis) + potential!(store, op, points, basis; type, quadstrat) return ff end function potential(op, points,coeffs, basis::SpaceTimeBasis) - T = SVector{3,ComplexF64} + T = SVector{3,eltype(coeffs)} ff = zeros(T, length(points), size(coeffs)[2]) store(v,m,n,k,o) = (ff[m,k] += v*coeffs[n,o]) potential!(store, op, points, basis) return ff end -function potential(op, points, coeffs, space::DirectProductSpace) - T = SVector{3,ComplexF64} - ff = zeros(T, length(points)) +function potential(op, points, coeffs, space::DirectProductSpace; + type=SVector{3,ComplexF64}, + quadstrat=defaultquadstrat(op,space)) + ff = zeros(type, size(points)) @assert length(coeffs) == numfunctions(space) offset = 0 for fct in space.factors store(v,m,n) = (ff[m] += v*coeffs[offset+n]) - potential!(store, op, points, fct) + potential!(store, op, points, fct; type, quadstrat) offset += numfunctions(fct) end ff end -function potential!(store, op, points, basis) +function potential!(store, op, points, basis; + type=SVector{3,ComplexF64}, + quadstrat=defaultquadstrat(op, basis)) - T = SVector{3,ComplexF64} - z = zeros(T,length(points)) + z = zeros(type,length(points)) els, ad = assemblydata(basis) rs = refspace(basis) - zlocal = Array{T}(undef,numfunctions(rs)) - qdata = quaddata(op,rs,els) + geo = geometry(basis) + nf = numfunctions(rs, domain(chart(geo, first(geo)))) + zlocal = Array{type}(undef, nf) + qdata = quaddata(op,rs,els,quadstrat) - #println("Computing nearfield.") print("dots out of 10: ") todo, done, pctg = length(points), 0, 0 @@ -176,8 +191,8 @@ function potential!(store, op, points, basis) for (p,y) in enumerate(points) for (q,el) in enumerate(els) - fill!(zlocal,zero(T)) - qr = quadrule(op,rs,p,y,q,el,qdata) + fill!(zlocal,zero(type)) + qr = quadrule(op,rs,p,y,q,el,qdata,quadstrat) farfieldlocal!(zlocal,op,rs,y,el,qr) # assemble from local contributions @@ -209,7 +224,7 @@ function potential!(store, op, points, basis::SpaceTimeBasis) Nt = numfunctions(time_basis) Δt = timestep(time_basis) - T = SVector{3,ComplexF64} + T = SVector{3,Complex{eltype(eltype(points))}} refs = refspace(space_basis) trefs = refspace(time_basis) diff --git a/src/postproc/farfield.jl b/src/postproc/farfield.jl new file mode 100644 index 00000000..4d8a7482 --- /dev/null +++ b/src/postproc/farfield.jl @@ -0,0 +1,7 @@ +abstract type FarField end + +defaultquadstrat(op::FarField, basis) = SingleNumQStrat(10) +defaultquadstrat(op::FarField, basis::DirectProductSpace) = defaultquadstrat(op, basis.factors[1]) + +quaddata(op::FarField,rs,els,qs::SingleNumQStrat) = quadpoints(rs,els,(qs.quad_rule,)) +quadrule(op::FarField,refspace,p,y,q,el,qdata,qs::SingleNumQStrat) = qdata[1,q] \ No newline at end of file diff --git a/src/postproc/segcurrents.jl b/src/postproc/segcurrents.jl index 88ac3b58..da905f06 100644 --- a/src/postproc/segcurrents.jl +++ b/src/postproc/segcurrents.jl @@ -17,9 +17,9 @@ function octree(charts::Vector{S} where {S <: CompScienceMeshes.Simplex}) end """ - Hi + grideval(points, coeffs, basis; type=nothing) """ -function grideval(points, coeffs, basis) +function grideval(points, coeffs, basis; type=nothing) # charts: active charts # ad: assembly data (active_cell_idx, local_shape_idx) -> [dof1, dfo2, ...] @@ -29,22 +29,30 @@ function grideval(points, coeffs, basis) V = valuetype(refs, eltype(charts)) T = promote_type(eltype(coeffs), eltype(V)) - P = similar_type(V, T) + if !(V <: SVector) + P = T + else + P = similar_type(V, T) + end + + type !== nothing && (P = type) - values = zeros(P, length(points)) + values = zeros(P, size(points)) chart_tree = BEAST.octree(charts) for (j,point) in enumerate(points) i = CompScienceMeshes.findchart(charts, chart_tree, point) - if i != nothing + if i !== nothing + # @show i chart = charts[i] u = carttobary(chart, point) vals = refs(neighborhood(chart,u)) - for r in 1 : numfunctions(refs) + for r in 1 : numfunctions(refs, domain(chart)) for (m,w) in ad[i, r] - values[j] += w * coeffs[m] * vals[r][1] + values[j] += w * coeffs[m] * P(vals[r][1]) end end + continue end end return values diff --git a/src/quaddata.jl b/src/quaddata.jl deleted file mode 100644 index 1b6e79f3..00000000 --- a/src/quaddata.jl +++ /dev/null @@ -1,65 +0,0 @@ - - -""" - quaddata(operator, test_refspace, trial_refspace, test_elements, trial_elements) - -Returns an object cashing data required for the computation of boundary element -interactions. It is up to the client programmer to decide what (if any) data is -cached. For double numberical quadrature, storing the integration points for -example can significantly speed up matrix assembly. - -- `operator` is an integration kernel. -- `test_refspace` and `trial_refspace` are reference space objects. `quadata` -is typically overloaded on the type of these local spaces of shape functions. -(See the implementation in `maxwell.jl` for an example). -- `test_elements` and `trial_elements` are iterable collections of the geometric -elements on which the finite element space are defined. These are provided to -allow computation of the actual integrations points - as opposed to only their -coordinates. -""" -function quaddata end - - -""" - quadrule(operator, test_refspace, trial_refspace, test_index, test_chart, trial_index, trial_chart, quad_data) - -Based on the operator kernel and the test and trial elements, this function builds -an object whose type and data fields specify the quadrature rule that needs to be -used to accurately compute the interaction integrals. The `quad_data` object created -by `quaddata` is passed to allow reuse of any precomputed data such as quadrature -points and weights, geometric quantities, etc. - -The type of the returned quadrature rule will help in deciding which method of -`momintegrals` to dispatch to. -""" -function quadrule(op::IntegralOperator, g::RefSpace, f::RefSpace, i, τ, j, σ, qd) - # defines coincidence of points - dtol = 1.0e3 * eps(eltype(eltype(τ.vertices))) - - # decides on whether to use singularity extraction - xtol = 0.2 - k = norm(op.gamma) - - hits = 0 - xmin = xtol - for t in τ.vertices - for s in σ.vertices - d = norm(t-s) - xmin = min(xmin, k*d) - if d < dtol - hits +=1 - break - end - end - end - - hits == 3 && return SauterSchwabQuadrature.CommonFace(qd.gausslegendre[3]) - hits == 2 && return SauterSchwabQuadrature.CommonEdge(qd.gausslegendre[2]) - hits == 1 && return SauterSchwabQuadrature.CommonVertex(qd.gausslegendre[1]) - xmin < xtol && return DoubleQuadStrategy( - qd.tpoints[2,i], - qd.bpoints[2,j],) - return DoubleQuadStrategy( - qd.tpoints[1,i], - qd.bpoints[1,j],) -end diff --git a/src/quadrature/SauterSchwabQuadrature1D.jl b/src/quadrature/SauterSchwabQuadrature1D.jl new file mode 100644 index 00000000..6193eae0 --- /dev/null +++ b/src/quadrature/SauterSchwabQuadrature1D.jl @@ -0,0 +1,14 @@ +module SauterSchwabQuadrature1D + +# -------- exportet parts +# types +export SauterSchwabStrategy1D +export CommonEdge, CommonVertex + +# functions +export sauterschwab_parameterized1D, _MRWrules, reorder + +# -------- included files +include("doublesauterschwabint.jl") + +end \ No newline at end of file diff --git a/src/quadrature/commonfaceoverlappingedgeqstrat.jl b/src/quadrature/commonfaceoverlappingedgeqstrat.jl new file mode 100644 index 00000000..183223ba --- /dev/null +++ b/src/quadrature/commonfaceoverlappingedgeqstrat.jl @@ -0,0 +1,26 @@ +struct CommonFaceOverlappingEdgeQStrat{S} <: AbstractQuadStrat + conforming_qstrat::S +end + +function quaddata(a, X, Y, tels, bels, qs::CommonFaceOverlappingEdgeQStrat) + return quaddata(a, X, Y, tels, bels, qs.conforming_qstrat) +end + + +function quadrule(a, 𝒳, 𝒴, i, τ, j, σ, qd, + qs::CommonFaceOverlappingEdgeQStrat) + + if CompScienceMeshes.overlap(τ, σ) + return quadrule(a, 𝒳, 𝒴, i, τ, j, σ, qd, qs.conforming_qstrat) + end + + for (i,λ) in pairs(faces(τ)) + for (j,μ) in pairs(faces(σ)) + if CompScienceMeshes.overlap(λ, μ) + return NonConformingTouchQRule(qs.conforming_qstrat, i, j) + end end end + + # Either positive distance, common face, or common vertex, which can + # be handled directly by the parent quadrature strategy + return quadrule(a, 𝒳, 𝒴, i, τ, j, σ, qd, qs.conforming_qstrat) +end \ No newline at end of file diff --git a/src/quadrature/double_quadrature.jl b/src/quadrature/double_quadrature.jl deleted file mode 100644 index 03da1cf6..00000000 --- a/src/quadrature/double_quadrature.jl +++ /dev/null @@ -1,48 +0,0 @@ -struct DoubleQuadStrategy{P,Q} - outer_quad_points::P - inner_quad_points::Q -end - - -""" - regularcellcellinteractions!(biop, tshs, bshs, tcell, bcell, interactions, strat) - -Function for the computation of moment integrals using simple double quadrature. -""" -function momintegrals!(biop, tshs, bshs, tcell, bcell, z, strat::DoubleQuadStrategy) - - # memory allocation here is a result from the type instability on strat - # which is on purpose, i.e. the momintegrals! method is chosen based - # on dynamic polymorphism. - womps = strat.outer_quad_points - wimps = strat.inner_quad_points - - - for womp in womps - tgeo = womp.point - tvals = womp.value - M = length(tvals) - jx = womp.weight - - for wimp in wimps - bgeo = wimp.point - bvals = wimp.value - N = length(bvals) - jy = wimp.weight - - j = jx * jy - kernel = kernelvals(biop, tgeo, bgeo) - - for n in 1 : N - bval = bvals[n] - for m in 1 : M - tval = tvals[m] - igd = integrand(biop, kernel, tval, tgeo, bval, bgeo) - z[m,n] += j * igd - end - end - end - end - - return z -end diff --git a/src/quadrature/doublenumints.jl b/src/quadrature/doublenumints.jl new file mode 100644 index 00000000..0efc2ccb --- /dev/null +++ b/src/quadrature/doublenumints.jl @@ -0,0 +1,43 @@ +struct DoubleQuadRule{P,Q} + outer_quad_points::P + inner_quad_points::Q +end + + +""" +momintegrals!(biop, tshs, bshs, tcell, bcell, interactions, strat) + +Function for the computation of moment integrals using simple double quadrature. +""" +function momintegrals!(biop, + tshs, bshs, tcell, bcell, z, strat::DoubleQuadRule) + + igd = Integrand(biop, tshs, bshs, tcell, bcell) + + womps = strat.outer_quad_points + wimps = strat.inner_quad_points + + for womp in womps + tgeo = womp.point + tvals = womp.value + M = length(tvals) + jx = womp.weight + + for wimp in wimps + bgeo = wimp.point + bvals = wimp.value + N = length(bvals) + jy = wimp.weight + + j = jx * jy + + z1 = j * igd(tgeo, bgeo, tvals, bvals) + for n in 1:N + for m in 1:M + z[m,n] += z1[m,n] + end end + end + end + + return z +end diff --git a/src/quadrature/doublenumqstrat.jl b/src/quadrature/doublenumqstrat.jl new file mode 100644 index 00000000..5282d619 --- /dev/null +++ b/src/quadrature/doublenumqstrat.jl @@ -0,0 +1,32 @@ +struct DoubleNumQStrat{R} <: AbstractQuadStrat + outer_rule::R + inner_rule::R +end + +function quaddata(operator::IntegralOperator, + local_test_basis, local_trial_basis, + test_elements, trial_elements, qs::DoubleNumQStrat) + + # local_test_basis = refspace(test_basis) + # local_trial_basis = refspace(trial_basis) + + test_quad_data = quadpoints(local_test_basis, test_elements, (qs.outer_rule,)) + trial_quad_data = quadpoints(local_trial_basis, trial_elements, (qs.inner_rule,)) + + return test_quad_data, trial_quad_data +end + + +function quadrule(operator::IntegralOperator, + local_test_basis, local_trial_basis, + test_id, test_element, trial_id, trial_element, + quad_data, qs::DoubleNumQStrat) + + test_quad_rules = quad_data[1] + trial_quad_rules = quad_data[2] + + DoubleQuadRule( + test_quad_rules[1,test_id], + trial_quad_rules[1,trial_id] + ) +end \ No newline at end of file diff --git a/src/quadrature/doublenumsauterqstrat.jl b/src/quadrature/doublenumsauterqstrat.jl new file mode 100644 index 00000000..02f2bafa --- /dev/null +++ b/src/quadrature/doublenumsauterqstrat.jl @@ -0,0 +1,80 @@ +struct DoubleNumSauterQstrat{R,S} <: AbstractQuadStrat + outer_rule::R + inner_rule::R + sauter_schwab_common_tetr::S + sauter_schwab_common_face::S + sauter_schwab_common_edge::S + sauter_schwab_common_vert::S +end + +function quaddata(op::IntegralOperator, + test_local_space::RefSpace, trial_local_space::RefSpace, + test_charts, trial_charts, qs::DoubleNumSauterQstrat) + + T = coordtype(test_charts[1]) + + tqd = quadpoints(test_local_space, test_charts, (qs.outer_rule,)) + bqd = quadpoints(trial_local_space, trial_charts, (qs.inner_rule,)) + + leg = ( + convert.(NTuple{2,T},_legendre(qs.sauter_schwab_common_vert,0,1)), + convert.(NTuple{2,T},_legendre(qs.sauter_schwab_common_edge,0,1)), + convert.(NTuple{2,T},_legendre(qs.sauter_schwab_common_face,0,1)), + convert.(NTuple{2,T},_legendre(qs.sauter_schwab_common_tetr,0,1)), + ) + + return (tpoints=tqd, bpoints=bqd, gausslegendre=leg) +end + + +function quadrule(op::IntegralOperator, g::RefSpace, f::RefSpace, + i, τ::CompScienceMeshes.Simplex{<:Any, 2}, + j, σ::CompScienceMeshes.Simplex{<:Any, 2}, + qd, qs::DoubleNumSauterQstrat) + + hits = _numhits(τ, σ) + @assert hits <= 3 + + hits == 3 && return SauterSchwabQuadrature.CommonFace(qd.gausslegendre[3]) + hits == 2 && return SauterSchwabQuadrature.CommonEdge(qd.gausslegendre[2]) + hits == 1 && return SauterSchwabQuadrature.CommonVertex(qd.gausslegendre[1]) + + return DoubleQuadRule( + qd.tpoints[1,i], + qd.bpoints[1,j],) +end + +function quadrule(op::IntegralOperator, g::RefSpace, f::RefSpace, + i, τ::CompScienceMeshes.Quadrilateral, + j, σ::CompScienceMeshes.Quadrilateral, + qd, qs::DoubleNumSauterQstrat) + + hits = _numhits(τ, σ) + @assert hits != 3 + @assert hits <= 4 + + hits == 4 && return SauterSchwabQuadrature.CommonFaceQuad(qd.gausslegendre[3]) + hits == 2 && return SauterSchwabQuadrature.CommonEdgeQuad(qd.gausslegendre[2]) + hits == 1 && return SauterSchwabQuadrature.CommonVertexQuad(qd.gausslegendre[1]) + + return DoubleQuadRule( + qd.tpoints[1,i], + qd.bpoints[1,j],) +end + + +function _numhits(τ, σ) + T = coordtype(τ) + hits = 0 + dtol = 1.0e3 * eps(T) + dmin2 = floatmax(T) + for t in vertices(τ) + for s in vertices(σ) + d2 = LinearAlgebra.norm_sqr(t-s) + d = norm(t-s) + dmin2 = min(dmin2, d2) + hits += (d < dtol) + end + end + return hits +end \ No newline at end of file diff --git a/src/quadrature/doublenumwiltonbogaertqstrat.jl b/src/quadrature/doublenumwiltonbogaertqstrat.jl new file mode 100644 index 00000000..f1aff891 --- /dev/null +++ b/src/quadrature/doublenumwiltonbogaertqstrat.jl @@ -0,0 +1,57 @@ +struct DoubleNumWiltonBogaertQStrat{R} <: AbstractQuadStrat + outer_rule_far::R + inner_rule_far::R + outer_rule_near::R + inner_rule_near::R +end + +function quaddata(op::IntegralOperator, + test_local_space::RefSpace, trial_local_space::RefSpace, + test_charts, trial_charts, qs::DoubleNumWiltonBogaertQStrat) + + T = coordtype(test_charts[1]) + + tqd = quadpoints(test_local_space, test_charts, (qs.outer_rule_far,qs.outer_rule_near)) + bqd = quadpoints(trial_local_space, trial_charts, (qs.inner_rule_far,qs.inner_rule_near)) + + return (tpoints=tqd, bpoints=bqd) +end + +function quadrule(op::IntegralOperator, g::RTRefSpace, f::RTRefSpace, i, τ, j, σ, qd, + qs::DoubleNumWiltonBogaertQStrat) + + dtol = 1.0e3 * eps(eltype(eltype(τ.vertices))) + xtol = 0.2 + + k = norm(gamma(op)) + + hits = 0 + xmin = xtol + for t in τ.vertices + for s in σ.vertices + d = norm(t-s) + xmin = min(xmin, k*d) + if d < dtol + hits +=1 + break + end + end + end + + hits == 3 && return BogaertSelfPatchStrategy(5) + hits == 2 && return BogaertEdgePatchStrategy(8, 4) + hits == 1 && return BogaertPointPatchStrategy(2, 3) + rmin = xmin/k + xmin < xtol && return WiltonSERule( + qd.tpoints[1,i], + DoubleQuadRule( + qd.tpoints[2,i], + qd.bpoints[2,j], + ), + ) + return DoubleQuadRule( + qd.tpoints[1,i], + qd.bpoints[1,j], + ) + + end \ No newline at end of file diff --git a/src/quadrature/doublenumwiltonsauterqstrat.jl b/src/quadrature/doublenumwiltonsauterqstrat.jl new file mode 100644 index 00000000..6853ce55 --- /dev/null +++ b/src/quadrature/doublenumwiltonsauterqstrat.jl @@ -0,0 +1,69 @@ +struct DoubleNumWiltonSauterQStrat{R,S} <: AbstractQuadStrat + outer_rule_far::R + inner_rule_far::R + outer_rule_near::R + inner_rule_near::R + sauter_schwab_common_tetr::S + sauter_schwab_common_face::S + sauter_schwab_common_edge::S + sauter_schwab_common_vert::S +end + +function quaddata(op::IntegralOperator, + test_local_space, trial_local_space, + test_charts, trial_charts, qs::DoubleNumWiltonSauterQStrat) + + T = coordtype(test_charts[1]) + # test_local_space = refspace(test_space) + # trial_local_space = refspace(trial_space) + + tqd = quadpoints(test_local_space, test_charts, (qs.outer_rule_far,qs.outer_rule_near)) + bqd = quadpoints(trial_local_space, trial_charts, (qs.inner_rule_far,qs.inner_rule_near)) + + leg = ( + convert.(NTuple{2,T},_legendre(qs.sauter_schwab_common_vert,0,1)), + convert.(NTuple{2,T},_legendre(qs.sauter_schwab_common_edge,0,1)), + convert.(NTuple{2,T},_legendre(qs.sauter_schwab_common_face,0,1)),) + + return (tpoints=tqd, bpoints=bqd, gausslegendre=leg) +end + + +function quadrule(op::IntegralOperator, g, f, i, τ, j, σ, qd, + qs::DoubleNumWiltonSauterQStrat) + + T = eltype(eltype(τ.vertices)) + hits = 0 + dtol = 1.0e3 * eps(T) + dmin2 = floatmax(T) + for t in τ.vertices + for s in σ.vertices + d2 = LinearAlgebra.norm_sqr(t-s) + d = norm(t-s) + dmin2 = min(dmin2, d2) + # hits += (d2 < dtol) + hits += (d < dtol) + end + end + + @assert hits <= 3 + + hits == 3 && return SauterSchwabQuadrature.CommonFace(qd.gausslegendre[3]) + hits == 2 && return SauterSchwabQuadrature.CommonEdge(qd.gausslegendre[2]) + hits == 1 && return SauterSchwabQuadrature.CommonVertex(qd.gausslegendre[1]) + + h2 = volume(σ) + xtol2 = 0.2 * 0.2 + k2 = abs2(gamma(op)) + if max(dmin2*k2, dmin2/16h2) < xtol2 + return WiltonSERule( + qd.tpoints[2,i], + DoubleQuadRule( + qd.tpoints[2,i], + qd.bpoints[2,j],),) + end + + return DoubleQuadRule( + qd.tpoints[1,i], + qd.bpoints[1,j],) +end \ No newline at end of file diff --git a/src/quadrature/doublesauterschwabint.jl b/src/quadrature/doublesauterschwabint.jl new file mode 100644 index 00000000..ae3a5cc6 --- /dev/null +++ b/src/quadrature/doublesauterschwabint.jl @@ -0,0 +1,183 @@ +# -------- used packages +using FastGaussQuadrature + +using LinearAlgebra +using StaticArrays + +# -------- included files +include("gqlog.jl") + + +abstract type SauterSchwabStrategy1D end + +struct CommonEdge{A} <: SauterSchwabStrategy1D + qpso::A # MRW quadrature for the outer integral + qpsi::A # GL quadrature for the inner integral +end +struct CommonVertex{A} <: SauterSchwabStrategy1D + qpso::A # MRW quadrature for the outer integral + qpsi::A # GL quadrature for the inner integral +end + + + +""" + (::CommonEdge)(f, η, ξ) + +Regularizing coordinate transform for parametrization on the unit line: [0,1] ↦ Γ. +based on Boundary Element Methods by Sauter and Schwab, example, 5.2.3, p.308 + +Common face case. +""" +function (::CommonEdge)(f, η, ξ) + + return (1-ξ) * + ( + f( (1 - η) * (1 - ξ), (1 - η) * (1 - ξ) + ξ ) + + f( 1 - (1 - η) * (1 - ξ), 1 - (1 - η) * (1 - ξ) - ξ) + ) +end + + + +""" + (::CommonVertex)(f, η, ξ) + +Regularizing coordinate transform for parametrization on the unit line: [0,1] ↦ Γ. +based on Boundary Element Methods by Sauter and Schwab, example, 5.2.3, p.308 + +Common vertex case. +""" + + +# We apply the Duffy trick at vertex (0,0) of the two triangles +# created by splitting along the diagonal (0,0)-(1,1) +function (::CommonVertex)(f, η, ξ) + + return ξ * ( + f( ξ, η * ξ) + + f( η * ξ, ξ) + ) +end + + +function _legendre(n, a, b) + x, w = FastGaussQuadrature.gausslegendre(n) + w .*= (b - a) / 2 + x = (x .+ 1) / 2 * (b - a) .+ a + collect(zip(x, w)) +end + +# MRWRules abbreviation of Ma-Rocklin-Wandzura rule +# Ma, J., Rocklin, D., & Wandzura, S. (1996). +#"Generalized Gaussian Quadrature Rules for Systems of Arbitrary Functions." +function _MRWrules(n,a,b) + + x, w = mrwquadrature(n) + return collect(zip(x,w)) +end + +function sauterschwab_parameterized1D(integrand, strategy::SauterSchwabStrategy1D) + return sum(w1 * w2 * strategy(integrand, η, ξ) for (η, w1) in strategy.qpsi, (ξ, w2) in strategy.qpso) +end + +# In the reference domain [0, 1] x [0,1], we assume +# that the singularity is at [0, 0] and then apply the +# Duffy trick at the triangles created by splitting along +# the diagonal (0,0)-(1,1). +# ==> Therefore, the vertices must be mapped that the segments +# with vertices [vt1, vt2] and [vs1, vs2] meet at vertices +# vt2 and vs2 (since the barycentric coordinate ξ = 0 is at vt2/vs2) +function reorder(t, s, strat::CommonVertex) + + T = eltype(t[1]) + tol = 1e3 * eps(T) + # tol = 1e5 * eps(T) + # tol = sqrt(eps(T)) + + # Find the permutation P of t and s that make + # Pt = [P, A1, A2] + # Ps = [P, B1, B2] + I = zeros(Int, 1) + J = zeros(Int, 1) + e = 1 + for i in 1:2 + v = t[i] + for j in 1:2 + w = s[j] + if norm(w - v) < tol + I[e] = i + J[e] = j + e += 1 + break + end + end + e == 2 && break + end + + prepend!(I, setdiff([1, 2], I)) + prepend!(J, setdiff([1, 2], J)) + + K = zeros(Int, 2) + for i in 1:2 + for j in 1:2 + if I[j] == i + K[i] = j + break + end + end + end + + L = zeros(Int, 2) + for i in 1:2 + for j in 1:2 + if J[j] == i + L[i] = j + break + end + end + end + + return I, J, K, L +end + +function reorder(t, s, strat::CommonEdge) + + T = eltype(t[1]) + tol = 1e3 * eps(T) + # tol = 1e5 * eps(T) + # tol = sqrt(eps(T)) + + I = [1, 2] + J = zeros(Int, 2) + v = t[1] + w = s[1] + if norm(w - v) < tol + J[:] = I[:] + else # If first vertices do not coincide -> swap + J[1] = 2 + J[2] = 1 + end + + K = zeros(Int, 2) + for i in 1:2 + for j in 1:2 + if I[j] == i + K[i] = j + break + end + end + end + + L = zeros(Int, 2) + for i in 1:2 + for j in 1:2 + if J[j] == i + L[i] = j + break + end + end + end + + return I, J, K, L +end \ No newline at end of file diff --git a/src/quadrature/gqlog.jl b/src/quadrature/gqlog.jl new file mode 100644 index 00000000..5ef8d2bc --- /dev/null +++ b/src/quadrature/gqlog.jl @@ -0,0 +1,366 @@ +## precision 1e-64 +const gq3logx = [ + big"0.0288116625309518311743284463058892943833785410148091127120685877756728991180956", + big"0.3040637296121376526108623586392372317949520269925648162447492695778139996408323", + big"0.8116692253440781168637051777605030761450290604667706879097761140167527022589782" +] +const gq3logw = [ + big"0.1033307079649286467692515926638152925117601886607103120667018372055221973825252", + big"0.4546365259700987088406911214255950929711433227560482628824643195989125765801328", + big"0.4420327660649726443900572859105896145170964885832414250508338431955652260378603" +] + +const gq4logx = [ + big"0.0118025909978449182649173011095277243792040203690474965703379785154526233875205", + big"0.1428256799774836951368513691763590994938532316254834466840646624072159114529217", + big"0.4892015226545744787190313056994040673995128251953935115454141480106812059217529", + big"0.8786799740691837028076889062651203170983114607803952562798953418334277261264303" +] +const gq4logw = [ + big"0.04339102877841439110189836963212064031500034973926060944228561441031014255089125", + big"0.2404520976594606759784500615666669970425863019151864377534566626947204831710365", + big"0.421403452259775931978815024210724368880313959764404972252008918197462697072954", + big"0.2947534213023490009408365445904879937620993885811479805522488046975066771977948" +] + +const gq5logx = [ + big"0.005652228205080097135927256196733224364461736662521571634310808301956553659434744", + big"0.07343037174265227340615889388832093647469715917834067035684484541883673822337442", + big"0.2849574044625581537145276019260509101628963210662763809477575315819894593888129", + big"0.6194822640847783814068089430513733271561626825848934609549153022972420903233235", + big"0.9157580830046983337846091809279726334110698024540233984987896162377435354971433" +] +const gq5logw = [ + big"0.02104694579185462911900268264212980706242530912584843774923279348541444162086709", + big"0.1307055407444466975910762549921867773164715864654795406970237184043603827930743", + big"0.289702301671314156841590351056571717447074684999630662451019615095549595934383", + big"0.3502203701203987102855468041352946381567577080211603909627150348339178095411739", + big"0.2083248416719858061627839071738170600172707113878809681400088381807577699303166" +] + +const gq6logx = [ + big"0.003025802137546258709729970375256268401779482992828101213315602517070676774568322", + big"0.04097825415595061505346596310891297982215214416543044471948088845952583162746435", + big"0.1708632955268772947251498297861305222272021210798376492086997941491912322656478", + big"0.4132557088447932476664814551636301801016057123765087288754062575075223818171789", + big"0.7090951467906285439500459170837777217505798833565268634341442960650946180757895", + big"0.9382395903771670913550205947159654750322534581742281653799581714380847089429558" +] +const gq6logw = [ + big"0.01135133881727260944049112382835582058174453200264770791905594141446248858716565", + big"0.07524106995491652291735628910920604024498865691978884727033600167691095492167005", + big"0.1887900416154163546095079437717888698034483559720734297158003205881410216356809", + big"0.2858207218272273119866834800845419826732913507876038823492520584906108457473145", + big"0.2844864278914088000451516698444240021352894282576606882526226532705898320941591", + big"0.1543103998937584010008094933616832845612376760602254444929330245592848866881613" +] + +const gq7logx = [ + big"0.001759652118465774280562642849488452238847882376829417161020566844898763296815622", + big"0.02446965071251336742764533734970449814707465617881458711466958936398767171520647", + big"0.1067480568587889541802597810831012639984493524877324427179411407375702636036147", + big"0.2758076412959173830778595120569401032015064743411215315346947545778810161812877", + big"0.5178551421518337161586689619818074537726293041441122507100974625383718453865105", + big"0.7718154853623849002746468694943818398820314642582719680233938471526845409035046", + big"0.9528413405810905589943065885030879911652507982696548514590485932036664026265743" +] +const gq7logw = [ + big"0.006632666319025705117839049890505451545085156065257921564980554577340348816003555", + big"0.04579970797847533412557673481204533079388958290463806525668605988795024448820841", + big"0.1238402080713181945504895649219393280879395309587789720141373794746235307041714", + big"0.2121019260238119301079148754555698567157309653785676929348714013330178452209891", + big"0.2613906456720077256465806068585123980383539106322107830995996640959468257240063", + big"0.2316361802909093843188155261040439969529839487681492485634359818123016339590676", + big"0.1185986656444517261327836419573836378660169052923973165662889588188190507751178" +] + +const gq8logx = [ + big"0.001090693941921822894165111895272961656889761270364157490698764319877404204397833", + big"0.01544065354637409035654803129696733338088233302185817085956683953435628581173319", + big"0.06943486210070215589606820183704622774352513760912341739401993180500014904198664", + big"0.1874432442554370314741850139770783912914836031762547096874553060230836873430399", + big"0.3733044213430930068774224196130946427983769812240799839889190905932509629106671", + big"0.6004940136993972160075727034500910768028300567728462727056609455469481882732827", + big"0.8168773397346666264599684042762922851882693053719296062937312826985616566562561", + big"0.9628397592694479679767293938117621595514008471737935702073567287533849959502929" +] +const gq8logw = [ + big"0.004124301185198343019856732777906332094734815195995821999647282928812400963714979", + big"0.02927037967468729611607963459474011124474094870767491048230999412065760132430261", + big"0.08311406745317000356959934495432356781311685459112761469028864096298503979443943", + big"0.1537216703422877404504605696868084750732921159913022489119351065174839736994873", + big"0.2134976095222608553772321005742849287594848638665672219708479691269085320689126", + big"0.2318702724435750456389973716207802644106398233930637097216879048919245919447895", + big"0.1905362390403677390075584696403609750435838347240190689367511770476690710464044", + big"0.09386546033845297682021577615079534556040674353024940328653192440354873593922193" +] + +const gq9logx = [ + big"0.0007110732887084292371271948114411427130596929286827033747571630375829895092934655", + big"0.01019453302625490675705263439721390757082070374909770360626863228804971969139422", + big"0.0468338672211245120800478827726438577871775553713352006780256391811070482319059", + big"0.1303678313651315279092725116746256616748483487812351497491905645734524393839669", + big"0.2704472571889117534115792396210086109828026312327005462427877165292572019618002", + big"0.4583194570951279555704788423104835576655393832207886739103153471011494009470139", + big"0.6654446330703511931141011568840343222343257021433467228651963799007886473741206", + big"0.8501167298492689862135218078117680443898748048201621390257943239647520356529031", + big"0.9699770448705806690488601014922724532077813279649453870772921791520938067334167" +] +const gq9logw = [ + big"0.002694891149020972537650818274669972064785863224366740658735088194859850376821306", + big"0.01949806475263514725048677490738025162488067888132757835094210266913341165923162", + big"0.05727368794913121851622494268319581220230302710787939286951364778040103463322235", + big"0.1115510143487581908445290090392732768292549693822402575676675123047847211910685", + big"0.1671748768632405941331694750536953632851337573319718468823265216730223402765603", + big"0.2036971186947111327284495789202735996350952027805427819858864230265916190717751", + big"0.2033824531641998599297042266271605769330084459619524205450522923748533235284438", + big"0.158655279830106274450813985467734586755046487348205019646993658645688017119264", + big"0.07607261324819660960897118902661656067049156798151396149288275333032421965258767" +] +const gq10logx = [ + big"0.0004829617106896294943183149272749175233248985914823525942928633132005676508123314", + big"0.006988629214315765291321415052330852703903885292050311462565292717699814705239717", + big"0.03261139659467762873636756333311119502903377871063522373995097575113617928003805", + big"0.09282575738916595754304180899809018017197868009727626181054204984045825798259768", + big"0.1983272568954037952451448876788239049325686339457225672360989187271669627070847", + big"0.3488801429793531934308727052079965952821380621199977107169557491389991483084391", + big"0.5304405557879560773593628930481504753741482469114746733537471295142940779164014", + big"0.7167646485116550851271524596080857897065882439999679965244791572396532440846242", + big"0.8752345575062335681899918401828609991184087254011484223626515130124980427858389", + big"0.9752456986843928703116413484567293450966284004578761398160192959831174182935528" +] +const gq10logw = [ + big"0.001833400073789844974343039244187088756939297229916101905004850946833688773766269", + big"0.01345312234599178938388397441120775272856711649003550514202802923403524514255926", + big"0.04049719431695833328218443450460775397747621201836680039657708236714280143083774", + big"0.08182236965890360616068411173063619462800676003125110086271887123013027996979842", + big"0.1291923427701375391426228447040414123310685703067390418217018174652510377316056", + big"0.1695453195472587471761867424266567039339458883985308141716254981178276286005089", + big"0.1891002165329956092241637321518972638740135835067914498094639132865254218185757", + big"0.1779657539614705508518683516871686460340560486233669980423658137698553596240428", + big"0.1337247706154615197642626403147324065464076855087306464470729665894779967882703", + big"0.06286551017703246003980012882486477718951883788627154140144115699614809281187774" +] + +const gq15logx = [ + big"0.0001057845484586292753493864924668380916251024074531216298515114307885975888182837077025", + big"0.007595218903207091883247444376284730435089898982134959893543137589162227513216067560734", + big"0.001566243836167816956655385777415432235256871358416825366132255779407797797063564074336", + big"0.0228310673939862320908871357311429238465611023426736927457085695758783454249166053234", + big"0.05238863015682000512815362893330259140205201964625249898277863454897904936380636336242", + big"0.1007586852012129667391004051136416390637516216000338429059475799900456473867694200564", + big"0.1707407688499432897166276448099318000084410205203915118544771821000765281005023511881", + big"0.2625912061189931044996158999566953474829984421658717933965928855192943179193219106092", + big"0.3735365051845580413656463497279392875704241296651853711319349601068756502026863034781", + big"0.4977463584145334387874316512677272094693748259201281232961985452805697138926236954657", + big"0.6267890313923734128862401704231215953332762536281211672089523277856861188842261626273", + big"0.7505161034614075810996828048439310865633264955639212529377408870151103751864345161899", + big"0.8582553352078605177984423047477266972702530005265449789606259388659161604670935837999", + big"0.9401412912123457712018243306013409987706249783593107354789051443834065655521367132188", + big"0.98840159598634178326441972546720013231209699249441888675085570941504454528126530438" +] +const gq15logw = [ + big"0.0004032177246484613108113868855719067131609139354902442780939481796966138315560952884750", + big"0.009784212118766145151161398889593401651383575047217047223872276721858634524108733830682", + big"0.003062978434787002192079338214803518648777190553257027589956117925535847239417067776433", + big"0.02155875222558125941055846177671850985290937096257277641281484956583132046253300979456", + big"0.03832306737088916459447923899346820916960051106457997764057575699361858087669673468580", + big"0.05889819902630037803354721437087112558399556998861012518316858072147312154129159956634", + big"0.08111702993925953014418613733560273596656944057632845507111787372353568675899055124403", + big"0.1021221019720686409827399402219384580411590819905097066492730049234426900752992643841", + big"0.1187890590304012903122863318580292804774040165420944681986724151227161900089339293267", + big"0.1282103164466939209716269453809265385177800805573983908408790332666576381949672776871", + big"0.1281633274170931946155990062722928672897994926781647759252725389773093642921684420955", + big"0.1174894658884916604705425997823953678879687624841131431788200137454728360609792520454", + big"0.09632301856959041974222565383861801802356754266825087678627376540093734939016915901427", + big"0.06613453983189341688459160887643830449298566566458033351875146195781511683399415051504", + big"0.0296207140035355151835647373027317576829387852868326515024583627740990099088947327451" +] + +const gq20logx =[ + big"3.52330453033399471667816984505212163385372867701241177054557432008560084854639227897305e-05", + big"0.00052609398251740622931185201230712336983446921505852417585026623624467681675527629213", + big"0.00258751954058139354982343346427624186380724620758427946400293435944533268347785475355", + big"0.00793447194838036690898675737551375268645848500751865408409873748481145848080538488209", + big"0.01868288813744561783653938764750025119630809002162018788255377919833387462704672359943", + big"0.03709767336975036321140295948415363410628316780781877636363314115648013425635225927340", + big"0.06531248867402127000203627397232005971094419594710515781581286966321679280710510277392", + big"0.10504850471155061286810146844991137213753086149525569391876624641826242856000857964041", + big"0.15735969181900194255764110855237616685370902165441514721181535970287673096005782068619", + big"0.22243006276745466795344010160142143129919223811088898974943778369604881916260678361773", + big"0.29944376565409990140139180056840606405847048366147916785308499946161590450020290372508", + big"0.38654244694388192611269692971909327833983859776432003583652778494397228807150436302099", + big"0.48087645382678963009875046547745208340402413105861821236744947129359489183930838809025", + big"0.57874793220550687780118957035777358141664078156681562597114176771293610958660152141124", + big"0.67583547584003749409689635673268871053832441601541226337158644402514605525463511344023", + big"0.76748246087256435235881593088168817030085019655264020473919169064531201055566527849563", + big"0.84902525397032003391717514950021749147719817851968628768372316422346888625639398588588", + big"0.91613370324166446526561782425398464194563663350874014749925948312513509742697660053806", + big"0.96513542790025568797284412200174800997093368204723690145127197708961367148386513479096", + big"0.99330353645695419477749224434889710143097251086524695494463434414747362227484890290069" +] +const gq20logw =[ + big"0.0001344996764677571785755869109998153753077125501697493791309017346370516233712556344", + big"0.0010347769229506137001964267002086113327273066783579727965914510659856637214605457741", + big"0.0033772636772332014304071807493684205732945358218422159622340103282499537881211088281", + big"0.0076735561935946429061968738147092460135554371267072145698890068024955734275871246431", + big"0.0142054962855419692230156082678233228034489481480776486190695719837525785785047977667", + big"0.0229844384632086133642926604803330825551668634385287894069098453272948919261594660325", + big"0.0337363605577136353630349538167293653140706844323453207110012764976530438528535058147", + big"0.0459147630734521805763787261700834101052513301612066180095957478645178171817632110455", + big"0.0587404799428039977139522416532948172566068384302970423568064199808158670985424149117", + big"0.0712650131611019951597126355449221280821946137970396226433000650095010279558501379770", + big"0.0824518089775831713766154049650768079393967900663760427250713849268578291550998231446", + big"0.0912682015163873671456134433980263438610572775942202418862625342739119798024940831677", + big"0.0967797159091613537245599424606049227671293198402166937066405170578512391472371710119", + big"0.0982381433400897170264204215678838446666990091981590951692574443184601626076371233194", + big"0.0951553030540296616805586810238431737748294263531686413205589409221933865160968370927", + big"0.0873556504104573967653727605061775985247662198036079972745116768635241054129859568447", + big"0.0750027772122717350558059607955531490075827440767261036945605828913202042466050580026", + big"0.0585972958082336954531999669843095642303012092555129152937077146462168365372613268424", + big"0.0389472505496114365437373129779855044193052969187765645674151605884742219863536023733", + big"0.0171372052681058586123532112120668713973084363086635099074857469162865654340154497718" +] + +const gq25logx =[ + big"1.488052056467244909625638598196564951876569065220877785868029725929257175702073e-05", + big"0.0002230911595769679150052824827061963553887308057912589297421430508322235593830149", + big"0.001104643649055818355966527940649085000685595160923981048336572605217909595781463", + big"0.003419469468885920108435532061507438449748930344249701792199240567760744981134533", + big"0.008150529295033886863001737760922833583972167939553544027949623676022634980540248", + big"0.01642893749479774357536914607010297463429235120115467227534160504792104112918616", + big"0.02944598355986495611729043551183351592537620021435726249691258773257858495243842", + big"0.04835756970793361992206318289677079171377067665053611124161178808395997974547869", + big"0.07418709391973235055161345105314559978006226850379538594909956584122958662110417", + big"0.1077329558835263957555273771944780860183523493640476006201185800123101693565726", + big"0.1494866382580873514029205773718203880809421017595584426091740310673042986930094", + big"0.1995667309592877334892849598527591611240819852994166926207587305100971208280098", + big"0.2576733558313253022937358067504523798314306274156044714730893469651697969374821", + big"0.3230662664067204776334892632593698903208493483364823778280717807027167910904648", + big"0.3945685120691864972423465462055174654475617953335967500929296325180965705827934", + big"0.4705960495534080911589738896473523490798329385681206138773790559940837999359339", + big"0.5492121464331800113338053478110828306174234808821422519671406282623117939769409", + big"0.62820394224342956742721788713433694930593440087226148756627291757420870460847", + big"0.705177201069315684009216075313764796668874317071394637193611443315058812652506", + big"0.7776641844158138725454459327579467862496790765378705168736570171130565114296634", + big"0.8432387621385732265330937708707768036353565992240126265713341165914802212256237", + big"0.8996324161061799578911370633997479446760397229530673482338851736387075804002855", + big"0.9448447334057146581716256758822538016902245031938619437645914717774015185865162", + big"0.9772425752266930964071101829574726836904699310716772379925450956228825958567725", + big"0.9956472154564406578393873465081762532052103442281190457464841657872264187993479" +] +const gq25logw =[ + big"5.68460660251698016879030856022417630001430609016622367404055301532420517518146e-05", + big"0.0004399975857693375973647244464678378656571747820875928868053402781833492973839444", + big"0.001450718904759957538678349120904065177343596226567549479016314001060341544670339", + big"0.003344018738173782930390142077859263786746242661488414521712207604655464684262988", + big"0.006308099547359185301394009297771731168039890753375951467515248755245728133502821", + big"0.01044887231035341693833085280730910931287694687370918949624926858979554546942906", + big"0.01577950366313618961381321505098910773186227729332837668009224396751136533141598", + big"0.02221579084737618976616974555580489592306146268420586418029213097641676732770199", + big"0.02957770241410115563354566497367172700221328972120378713034720074800286876842385", + big"0.03759704560718378026508260437372317265973035980591719381931293743645095154963036", + big"0.04593085159498192065795356091614634903045149175052029457054511209195320135850725", + big"0.05417972366570000073562979252398890641461333672904032950920405925127192012041869", + big"0.0619100915223072560034657428054791826935387118547807898511465447238853319719781", + big"0.06867907489284756060553676062557690702482539043342506486021238240403629718273571", + big"0.0740604961651022732248948486171917260340764931698174907073336916833564924542006", + big"0.07767050451276309473099166537844979688969030762299763172471541920363489078274423", + big"0.07919128776745480042852947234337603958242405415397338299700888106674317554728525", + big"0.0783914525088150148888334078694416997243579916181124400848147761113332917972084", + big"0.07514184161385324278653409971527606906237843712486869148047723092914634697649294", + big"0.06942582121576333636781033240187019134337158605139855903585929269150790978196787", + big"0.06134339190482063881362517452637817009569584955196664117532098895052422109096163", + big"0.05110885129800288147090741101894360205572976775703651201802794222866948111927817", + big"0.03904218956400919229641780441420384985429514672241964476559246224509873932540361", + big"0.02555547136263279771626117616472806907229536248219411749824155386571674026297952", + big"0.01115035472670782388615153988884628873172468911466282782341636466564633607871119" +] +const gq30logx = [ + big"7.3237974427259711014121960070261558027540416963138812613431263278327151190959804366e-06", + big"0.0001100447004577745007716984769879706584453254088480184618404891401583368805852606", + big"0.0005469183261839665800863499163609522643981329961208508247165296601075876520669445", + big"0.0017018575191016396996609998210211063372476794957637728051826599621512487361999522", + big"0.0040838636097143723718644912310460035773312700037827241100616448033337453278004756", + big"0.0083000411768823359286287204715113149951924068604220901716433344126671711618772075", + big"0.0150229781560799918407734738092689183169334797613696585255554437053296826559583688", + big"0.0249539236157545452623839559701354241297597712533297138906598587000838054214649009", + big"0.0387833861710629296537643873484755707882131324051356611114889508499039466807898273", + big"0.0571508984811763829294398275011739764422515015438295843301931490904109469284692630", + big"0.0806057414726551613156465122418320971118831782536207674741123918423202714028978615", + big"0.1095703942345179343611158071642564640247389773976704867002267074489662838861495058", + big"0.1443083730015008037356197789175449537749814887445783639406919211150498752312441063", + big"0.1848979494275319495770725900200967399299860317548604528456354192477147513110879917", + big"0.2312130015482551721163548462811451176341279422817911572631877314792390476096369565", + big"0.3394354811908526513550841586470308321278005121866896994848949413776526679876480729", + big"0.2829119601972077387080427035003151356127360118392446697437361910056006346697600011", + big"0.4000131131094501820250769897649776296380125851532828677483580844712844326518780426", + big"0.4636788569393062849191467490065869972502641315496346884536143223006897517940708194", + big"0.5292951427410362462923319470735170564284332778195315985758918131361310822398350604", + big"0.5955843953256450027825382220533229626567086395897337474045253978208215527348048846", + big"0.6611670403969151929139247690126765162243640607544294092814316869342595350201284609", + big"0.7246045281760327657790707990935709795002999206671464914538818667060410225576321978", + big"0.7844457347349415400509678017888811719495951273900367011694108038702325222630460459", + big"0.8392749514786630154000514251848497883439805962605613432967984751367075590980896528", + big"0.8877595976173431134701092007511246347018682591436474515354898615262600610351937045", + big"0.9286957959632275789862107959484967949713679877492330402104285186684398776724010128", + big"0.9610500591874097103222899330483108727864338896073734995030477023581225096095722360", + big"0.9839957035212889808179890593218683763658708185435852305227846725586056753688294546", + big"0.9969459586797630509861337066494387438585030158185224196851893029505421506025964264" +] +const gq30logw = [ + big"2.79892154309545405739723510489380069210793611655050550747711543479704102561861919685e-05", + big"0.00021736552650254116285124642012621259181832733064311295409672178173804747259446376", + big"0.00072070358653438901040194053761631875159806889598654616182904411596462505061716433", + big"0.00167446096505498902233281391415105911504274990708724661497346440039733067657261145", + big"0.00319128240641146441610066277996670825343132418947802492373263356129383541125471007", + big"0.00535378831352933473172631190251374346030494065364313342637259357587998203452787619", + big"0.00820962136808195471589891530215803621769361461546980563318888002909952552567724036", + big"0.01176802921308481152797096394244181125451658226184469697172509738148802153652282010", + big"0.01599814350489140149763603068428629666775039563032076452547616160487744529727224182", + big"0.02082904109362429386427713970365819913806790570243416235608770390517636839882038207", + big"0.02615159766130931255371398669267371160770236587219917317217286765695263056664607358", + big"0.03182206826945638096504346131337724704287534948009793135273588003504229344353078382", + big"0.03766725599980671697189382643523378842658467493644965516029104462279299436170505501", + big"0.04349106226322339672911771677370940528027127799845281803511333713210191659662041518", + big"0.04908215322840304030099174860840581446598241617157948889097724877574668651907173348", + big"0.05869594429097691387579357598711458627497606185797316466625163785697740760494015714", + big"0.05422242866126261587110150982953097239633598880605386130322259535988549142217642663", + big"0.06229791811493648011168304384832523168380265713682130944354943903684490153886454541", + big"0.06484344570723305525372721814737560442230745015510585776187183540111537894524832593", + big"0.06617555985125437859534613656176695554584622877929469097064774571451332612064623233", + big"0.06617229527720047913131104667640576429008335550213045345021005286440129176246678245", + big"0.06475245892292092403406452472768098104967651874004644271092850354821405524611080741", + big"0.06187985834995463849925309457706242097748883881928765598589247454949234397734149989", + big"0.05756580366344204424412375601782622950190169249457352156861756311335070073919719047", + big"0.05186976919246567846117295123649254581339360407011283296332312580588896440891173441", + big"0.04489817849556722268070380739886932332497986016922684846199394709407370500832867311", + big"0.03680136250036949402188921872896857566077811836438319898471514629234808804665804404", + big"0.02776887037741359260389115885664022791979235167927439041544240586106164189739881477", + big"0.01802387378416074345440757639679616037798750110594698516423528647063618940758975519", + big"0.00782767019549675715100064364777713048008869931291672091525079129829684057243125330" +] + +const gqlogx = [gq3logx, gq4logx, gq5logx, gq6logx, gq7logx, gq8logx, gq9logx, gq10logx, gq15logx, gq20logx, gq25logx, gq30logx] +const gqlogw = [gq3logw, gq4logw, gq5logw, gq6logw, gq7logw, gq8logw, gq9logw, gq10logw, gq15logw, gq20logw, gq25logw, gq30logw] + +mutable struct GQExceptionDegree <: Exception +end + +Base.showerror(io::IO, e::GQExceptionDegree) = print( + io, + "Only quadratures of degree 3, 4, 5, 6, 7, 8, 9, 10, 15, 20, 25 and 30 available." +) + +function mrwquadrature(n::Int) + rules = [15, 20, 25, 30] + if n >= 3 && n <= 10 + return Float64.(gqlogx[n-2]), Float64.(gqlogw[n-2]) + else + if n in rules + gqlogx[8+Int((n-10)/5)], gqlogw[8+Int((n-10)/5)] + else + throw(GQExceptionDegree()) + end + end +end \ No newline at end of file diff --git a/src/quadrature/nonconformingintegralopqstrat.jl b/src/quadrature/nonconformingintegralopqstrat.jl new file mode 100644 index 00000000..abd2c394 --- /dev/null +++ b/src/quadrature/nonconformingintegralopqstrat.jl @@ -0,0 +1,25 @@ +struct NonConformingIntegralOpQStrat{S} <: AbstractQuadStrat + conforming_qstrat::S +end + +function quaddata(a, X, Y, tels, bels, qs::NonConformingIntegralOpQStrat) + return quaddata(a, X, Y, tels, bels, qs.conforming_qstrat) +end + +function quadrule(a, 𝒳, 𝒴, i, τ, j, σ, qd, + qs::NonConformingIntegralOpQStrat) + + if CompScienceMeshes.overlap(τ, σ) + return NonConformingOverlapQRule(qs.conforming_qstrat) + end + + for (i,λ) in pairs(faces(τ)) + for (j,μ) in pairs(faces(σ)) + if CompScienceMeshes.overlap(λ, μ) + return NonConformingTouchQRule(qs.conforming_qstrat, i, j) + end end end + + # Either positive distance or common vertex, both can + # be handled directly by the parent quadrature strategy + return quadrule(a, 𝒳, 𝒴, i, τ, j, σ, qd, qs.conforming_qstrat) +end \ No newline at end of file diff --git a/src/quadrature/nonconformingoverlapqrule.jl b/src/quadrature/nonconformingoverlapqrule.jl new file mode 100644 index 00000000..c93dd267 --- /dev/null +++ b/src/quadrature/nonconformingoverlapqrule.jl @@ -0,0 +1,70 @@ +struct NonConformingOverlapQRule{S} + conforming_qstrat::S +end + + +function momintegrals!(op, + test_local_space, basis_local_space, + test_chart::CompScienceMeshes.Simplex, basis_chart::CompScienceMeshes.Simplex, + out, qrule::NonConformingOverlapQRule) + + num_tshapes = numfunctions(test_local_space, domain(test_chart)) + num_bshapes = numfunctions(basis_local_space, domain(basis_chart)) + + test_charts, tclps = CompScienceMeshes.intersection_keep_clippings(test_chart, basis_chart) + _, bclps = CompScienceMeshes.intersection_keep_clippings(basis_chart, test_chart) + bsis_charts = copy(test_charts) + + for tclp in tclps append!(test_charts, tclp) end + for bclp in bclps append!(bsis_charts, bclp) end + + T = coordtype(test_chart) + h = max(volume(test_chart), volume(test_chart)) + test_charts = [ch for ch in test_charts if volume(ch) .> 1e6 * eps(T) * h] + bsis_charts = [ch for ch in bsis_charts if volume(ch) .> 1e6 * eps(T) * h] + + test_charts = [ch for ch in test_charts if volume(ch) .> 1e6 * eps(T)] + bsis_charts = [ch for ch in bsis_charts if volume(ch) .> 1e6 * eps(T)] + + isempty(test_charts) && return + isempty(bsis_charts) && return + + test_overlaps = map(test_charts) do tchart + simplex( + carttobary(test_chart, tchart.vertices[1]), + carttobary(test_chart, tchart.vertices[2]), + carttobary(test_chart, tchart.vertices[3])) + end + + trial_overlaps = map(bsis_charts) do bchart + simplex( + carttobary(basis_chart, bchart.vertices[1]), + carttobary(basis_chart, bchart.vertices[2]), + carttobary(basis_chart, bchart.vertices[3])) + end + + qstrat = CommonFaceOverlappingEdgeQStrat(qrule.conforming_qstrat) + qdata = quaddata(op, test_local_space, basis_local_space, + test_charts, bsis_charts, qstrat) + + zlocal = zero(out) + P = zeros(T, num_tshapes, num_tshapes) + Q = zeros(T, num_bshapes, num_bshapes) + for (p,tchart) in enumerate(test_charts) + restrict!(P, test_local_space, test_chart, tchart, test_overlaps[p]) + for (q,bchart) in enumerate(bsis_charts) + restrict!(Q, basis_local_space, basis_chart, bchart, trial_overlaps[q]) + + qrule = quadrule(op, test_local_space, basis_local_space, + p, tchart, q, bchart, qdata, qstrat) + + fill!(zlocal, 0) + momintegrals!(op, test_local_space, basis_local_space, + tchart, bchart, zlocal, qrule) + + for i in axes(P,1) + for j in axes(Q,1) + for k in axes(P,2) + for l in axes(Q,2) + out[i,j] += P[i,k] * zlocal[k,l] * Q[j,l] +end end end end end end end \ No newline at end of file diff --git a/src/quadrature/nonconformingtouchqrule.jl b/src/quadrature/nonconformingtouchqrule.jl new file mode 100644 index 00000000..e2b63866 --- /dev/null +++ b/src/quadrature/nonconformingtouchqrule.jl @@ -0,0 +1,243 @@ +struct NonConformingTouchQRule{S} + conforming_qstrat::S + test_overlapping_edge_index::Int + bsis_overlapping_edge_index::Int +end + +function momintegrals!(op, + test_locspace, bsis_locspace, + τ::CompScienceMeshes.Simplex, σ::CompScienceMeshes.Simplex, + out, qrule::NonConformingTouchQRule) + + num_tshapes = numfunctions(test_locspace, domain(τ)) + num_bshapes = numfunctions(bsis_locspace, domain(σ)) + + T = coordtype(τ) + P = eltype(τ.vertices) + + i = qrule.test_overlapping_edge_index + j = qrule.bsis_overlapping_edge_index + + @assert volume(τ) > eps(T) * 1e3 + @assert volume(σ) > eps(T) * 1e3 + τs, σs = _conforming_refinement_touching_triangles(τ,σ,i,j) + + isempty(τs) && return + isempty(σs) && return + + @assert all(volume.(τs) .> 1e3 * eps(T) * (volume(τ))) + @assert all(volume.(σs) .> 1e3 * eps(T) * (volume(σ))) + + test_overlaps = map(τs) do tchart + simplex( + carttobary(τ, tchart.vertices[1]), + carttobary(τ, tchart.vertices[2]), + carttobary(τ, tchart.vertices[3])) + end + + trial_overlaps = map(σs) do bchart + simplex( + carttobary(σ, bchart.vertices[1]), + carttobary(σ, bchart.vertices[2]), + carttobary(σ, bchart.vertices[3])) + end + + qstrat = qrule.conforming_qstrat + qdata = quaddata(op, test_locspace, bsis_locspace, τs, σs, qstrat) + + @assert !any(volume.(τs) .< 1e-13) + @assert !any(volume.(σs) .< 1e-13) + + zlocal = zero(out) + P = zeros(T, num_tshapes, num_tshapes) + Q = zeros(T, num_bshapes, num_bshapes) + for (p,tchart) in enumerate(τs) + restrict!(P, test_locspace, τ, tchart, test_overlaps[p]) + for (q,bchart) in enumerate(σs) + restrict!(Q, bsis_locspace, σ, bchart, trial_overlaps[q]) + + qrule = quadrule(op, test_locspace, bsis_locspace, + p, tchart, q, bchart, qdata, qstrat) + + fill!(zlocal, 0) + momintegrals!(op, test_locspace, bsis_locspace, + tchart, bchart, zlocal, qrule) + + for i in axes(P,1) + for j in axes(Q,1) + for k in axes(P,2) + for l in axes(Q,2) + out[i,j] += P[i,k] * zlocal[k,l] * Q[j,l] +end end end end end end end + + +function _conforming_refinement_touching_triangles_bak(τ,σ,i,j) + λ = faces(τ)[i] + μ = faces(σ)[j] + ρ = CompScienceMeshes.intersection(λ,μ)[1] + + τ_verts = [τ[mod1(i+2,3)], τ[mod1(i,3)], τ[mod1(i+1,3)]] + σ_verts = [σ[mod1(j+2,3)], σ[mod1(j,3)], σ[mod1(j+1,3)]] + + T = coordtype(τ) + P = eltype(τ.vertices) + + U = T[] + V = P[] + for v in ρ.vertices + if CompScienceMeshes.isinside(λ,v) + push!(V,v) + push!(U, carttobary(λ,v)[1]) + end end + if length(U) == 2 + if U[1] < U[2] + temp = V[1] + V[1] = V[2] + V[2] = temp + end end + append!(τ_verts, V) + + U = T[] + V = P[] + for v in ρ.vertices + if CompScienceMeshes.isinside(μ,v) + push!(V,v) + push!(U, carttobary(μ,v)[1]) + end end + if length(U) == 2 + if U[1] < U[2] + temp = V[1] + V[1] = V[2] + V[2] = temp + end end + append!(σ_verts, V) + + τ_verts = push!(τ_verts[2:end], τ_verts[1]) + σ_verts = push!(σ_verts[2:end], σ_verts[1]) + + # @show τ_verts + + τ_charts = [ simplex(τ_verts[1], τ_verts[i], τ_verts[i+1]) for i in 2:length(τ_verts)-1 ] + σ_charts = [ simplex(σ_verts[1], σ_verts[i], σ_verts[i+1]) for i in 2:length(σ_verts)-1 ] + + signs = Int.(sign.(dot.(normal.(τ_charts),Ref(normal(τ))))) + τ_charts = flip_normal.(τ_charts,signs) + signs = Int.(sign.(dot.(normal.(σ_charts),Ref(normal(σ))))) + σ_charts = flip_normal.(σ_charts,signs) + + h = sqrt(volume(τ)) + τ_charts = τ_charts[volume.(τ_charts) .> 1e3 * eps(T) * h] + σ_charts = τ_charts[volume.(σ_charts) .> 1e3 * eps(T) * h] + + return τ_charts, σ_charts +end + + +function _conforming_refinement_touching_triangles(τ,σ,i,j) + λ = faces(τ)[i] + μ = faces(σ)[j] + ρ = CompScienceMeshes.intersection(λ,μ)[1] + + τ_verts = Array(τ.vertices) + σ_verts = Array(σ.vertices) + + T = coordtype(τ) + P = eltype(τ.vertices) + + U = T[] + V = P[] + for v in ρ.vertices + push!(V,v) + push!(U, carttobary(λ,v)[1]) + end + if U[1] < U[2] + temp = V[1] + V[1] = V[2] + V[2] = temp + end + p = mod1(i+2,3) + new_i = p <= i ? i+2 : i + insert!(τ_verts, p, V[2]) + insert!(τ_verts, p, V[1]) + + U = T[] + V = P[] + for v in ρ.vertices + push!(V,v) + push!(U, carttobary(μ,v)[1]) + end + if U[1] < U[2] + temp = V[1] + V[1] = V[2] + V[2] = temp + end + p = mod1(j+2,3) + new_j = p <= j ? j+2 : j + insert!(σ_verts, p, V[2]) + insert!(σ_verts, p, V[1]) + + τ_charts = [ simplex(τ_verts[mod1(new_i,5)], τ_verts[mod1(new_i+s,5)], τ_verts[mod1(new_i+s+1,5)]) for s in 1:3 ] + σ_charts = [ simplex(σ_verts[mod1(new_j,5)], σ_verts[mod1(new_j+s,5)], σ_verts[mod1(new_j+s+1,5)]) for s in 1:3 ] + + τ_charts = [ch for ch in τ_charts if volume(ch) .> 1e6 * eps(T) * volume(τ)] + σ_charts = [ch for ch in σ_charts if volume(ch) .> 1e6 * eps(T) * volume(σ)] + τ_charts = [ch for ch in τ_charts if volume(ch) .> 1e6 * eps(T)] + σ_charts = [ch for ch in σ_charts if volume(ch) .> 1e6 * eps(T)] + + # signs = Int.(sign.(dot.(normal.(τ_charts),Ref(normal(τ))))) + # @assert all(signs .== 1) + # τ_charts = flip_normal.(τ_charts,signs) + # signs = Int.(sign.(dot.(normal.(σ_charts),Ref(normal(σ))))) + # @assert all(signs .== 1) + # σ_charts = flip_normal.(σ_charts,signs) + + return τ_charts, σ_charts +end + + +function _num_common_vertices(τ, σ) + hits = 0 + T = coordtype(σ) + tol = eps(T) * 10^3 + for v in τ.vertices + for w in σ.vertices + if norm(v - w) < tol + hits += 1 + break + end end end + return hits +end + + +function _test_conformity(τ, σ) + if CompScienceMeshes.overlap(τ, σ) + # if _num_common_vertices(τ, σ) != 3 + # @infiltrate + # end + return _num_common_vertices(τ, σ) == 3 + end + + for eτ in faces(τ) + for eσ in faces(σ) + if CompScienceMeshes.overlap(eτ, eσ) + # if _num_common_vertices(τ, σ) != 2 + # @infiltrate + # end + return _num_common_vertices(τ, σ) == 2 + end + end end + + for u in τ.vertices + for v in σ.vertices + su = simplex([u], Val{0}) + sv = simplex([v], Val{0}) + if CompScienceMeshes.overlap(su, sv) + # if _num_common_vertices(τ, σ) != 1 + # @infiltrate + # end + return _num_common_vertices(τ, σ) == 1 + end end end + + @assert _num_common_vertices(τ, σ) == 0 + return _num_common_vertices(τ, σ) == 0 +end \ No newline at end of file diff --git a/src/quadrature/quadstrats.jl b/src/quadrature/quadstrats.jl new file mode 100644 index 00000000..ec9f2e58 --- /dev/null +++ b/src/quadrature/quadstrats.jl @@ -0,0 +1,104 @@ +using InteractiveUtils + + + + + + +struct SauterSchwab3DQStrat{R,S} <: AbstractQuadStrat + outer_rule::R + inner_rule::R + sauter_schwab_1D::S + sauter_schwab_2D::S + sauter_schwab_3D::S + sauter_schwab_4D::S +end + +struct OuterNumInnerAnalyticQStrat{R} <: AbstractQuadStrat + outer_rule::R +end + + +defaultquadstrat(op, tfs, bfs) = defaultquadstrat(op, refspace(tfs), refspace(bfs)) +macro defaultquadstrat(dop, body) + @assert dop.head == :tuple + if length(dop.args) == 3 + op = dop.args[1] + tfs = dop.args[2] + bfs = dop.args[3] + ex = quote + function BEAST.defaultquadstrat(::typeof($op), ::typeof($tfs), ::typeof($bfs)) + $body + end + end + return esc(ex) + elseif length(dop.args) == 2 + lin = dop.args[1] + tfs = dop.args[2] + ex = quote + function BEAST.defaultquadstrat(::typeof($lin), ::typeof($tfs)) + $body + end + end + return esc(ex) + end + error("@defaultquadstrat expects a first argument of the for (op,tfs,bfs) or (linform,tfs)") +end + +struct SingleNumQStrat{R} <: AbstractQuadStrat + quad_rule::R +end + +function quadinfo(op, tfs, bfs; quadstrat=defaultquadstrat(op, tfs, bfs)) + + tels, tad = assemblydata(tfs) + bels, bad = assemblydata(bfs) + + tref = refspace(tfs) + bref = refspace(bfs) + + i, τ = 1, first(tels) + j, σ = 1, first(bels) + + @show quadstrat + println(@which BEAST.quaddata(op,tref,bref,tels,bels,quadstrat)) + + qd = quaddata(op,tref,bref,tels,bels,quadstrat) + println(@which quadrule(op,tref,bref,i,τ,j,σ,qd,quadstrat)) + + nothing +end + +""" + quaddata(operator, test_refspace, trial_refspace, test_elements, trial_elements) + +Returns an object cashing data required for the computation of boundary element +interactions. It is up to the client programmer to decide what (if any) data is +cached. For double numberical quadrature, storing the integration points for +example can significantly speed up matrix assembly. + +- `operator` is an integration kernel. +- `test_refspace` and `trial_refspace` are reference space objects. `quadata` +is typically overloaded on the type of these local spaces of shape functions. +(See the implementation in `maxwell.jl` for an example). +- `test_elements` and `trial_elements` are iterable collections of the geometric +elements on which the finite element space are defined. These are provided to +allow computation of the actual integrations points - as opposed to only their +coordinates. +""" +function quaddata end + + +""" + quadrule(operator, test_refspace, trial_refspace, test_index, test_chart, trial_index, trial_chart, quad_data) + +Based on the operator kernel and the test and trial elements, this function builds +an object whose type and data fields specify the quadrature rule that needs to be +used to accurately compute the interaction integrals. The `quad_data` object created +by `quaddata` is passed to allow reuse of any precomputed data such as quadrature +points and weights, geometric quantities, etc. + +The type of the returned quadrature rule will help in deciding which method of +`momintegrals` to dispatch to. +""" +function quadrule end \ No newline at end of file diff --git a/src/quadrature/rules/momintegrals.jl b/src/quadrature/rules/momintegrals.jl new file mode 100644 index 00000000..a599c539 --- /dev/null +++ b/src/quadrature/rules/momintegrals.jl @@ -0,0 +1,13 @@ +function momintegrals!(out, op, + test_functions, test_cellptr, test_chart, + trial_functions, trial_cellptr, trial_chart, + quadrule) + + local_test_space = refspace(test_functions) + local_trial_space = refspace(trial_functions) + + momintegrals!(op, + local_test_space, local_trial_space, + test_chart, trial_chart, + out, quadrule) +end \ No newline at end of file diff --git a/src/quadrature/rules/testinbaryrefoftrialqrule.jl b/src/quadrature/rules/testinbaryrefoftrialqrule.jl new file mode 100644 index 00000000..6f5239b7 --- /dev/null +++ b/src/quadrature/rules/testinbaryrefoftrialqrule.jl @@ -0,0 +1,76 @@ +struct TestInBaryRefOfTrialQRule{S} + conforming_qstrat::S +end + +function BEAST.momintegrals!(out, op, + test_functions, test_cell, test_chart, + trial_functions, trial_cell, trial_chart, + qr::TestInBaryRefOfTrialQRule) + + test_local_space = refspace(test_functions) + trial_local_space = refspace(trial_functions) + + num_tshapes = numfunctions(test_local_space, domain(test_chart)) + num_bshapes = numfunctions(trial_local_space, domain(trial_chart)) + + T = coordtype(test_chart) + z, u, h, t = zero(T), one(T), T(1//2), T(1//3) + + c = CompScienceMeshes.point(T, t, t) + v = ( + CompScienceMeshes.point(T, u, z), + CompScienceMeshes.point(T, z, u), + CompScienceMeshes.point(T, z, z)) + e = ( + CompScienceMeshes.point(T, z, h), + CompScienceMeshes.point(T, h, z), + CompScienceMeshes.point(T, h, h)) + + X = ( + CompScienceMeshes.simplex(v[1], e[3], c), + CompScienceMeshes.simplex(v[2], c, e[3]), + CompScienceMeshes.simplex(v[2], e[1], c), + CompScienceMeshes.simplex(v[3], c, e[1]), + CompScienceMeshes.simplex(v[3], e[2], c), + CompScienceMeshes.simplex(v[1], c, e[2])) + + C = CompScienceMeshes.cartesian(trial_chart, c) + V = ( + CompScienceMeshes.cartesian(trial_chart, v[1]), + CompScienceMeshes.cartesian(trial_chart, v[2]), + CompScienceMeshes.cartesian(trial_chart, v[3])) + E = ( + CompScienceMeshes.cartesian(trial_chart, e[1]), + CompScienceMeshes.cartesian(trial_chart, e[2]), + CompScienceMeshes.cartesian(trial_chart, e[3])) + + trial_charts = ( + CompScienceMeshes.simplex(V[1], E[3], C), + CompScienceMeshes.simplex(V[2], C, E[3]), + CompScienceMeshes.simplex(V[2], E[1], C), + CompScienceMeshes.simplex(V[3], C, E[1]), + CompScienceMeshes.simplex(V[3], E[2], C), + CompScienceMeshes.simplex(V[1], C, E[2])) + + quadstrat = qr.conforming_qstrat + qd = BEAST.quaddata(op, test_local_space, trial_local_space, + (test_chart,), trial_charts, quadstrat) + + Q = zeros(T, num_tshapes, num_tshapes) + out1 = zero(out) + for (q,chart) in enumerate(trial_charts) + qr1 = BEAST.quadrule(op, test_local_space, trial_local_space, + 1, test_chart, q ,chart, qd, quadstrat) + + BEAST.restrict!(Q, trial_local_space, trial_chart, chart, X[q]) + + fill!(out1, 0) + BEAST.momintegrals!(out1, op, + test_functions, nothing, test_chart, + trial_functions, nothing, chart, qr1) + + for j in 1:num_bshapes + for i in 1:num_tshapes + for k in 1:size(Q, 2) + out[i,j] += out1[i,k] * Q[j,k] +end end end end end diff --git a/src/quadrature/rules/testrefinestrialqrule.jl b/src/quadrature/rules/testrefinestrialqrule.jl new file mode 100644 index 00000000..baf388f7 --- /dev/null +++ b/src/quadrature/rules/testrefinestrialqrule.jl @@ -0,0 +1,53 @@ +struct TestRefinesTrialQRule{S} + conforming_qstrat::S +end + +function momintegrals!(out, op, + test_functions::Space, test_cell, test_chart, + trial_functions::Space, trial_cell, trial_chart, + qr::TestRefinesTrialQRule) + + test_local_space = refspace(test_functions) + trial_local_space = refspace(trial_functions) + + test_mesh = geometry(test_functions) + trial_mesh = geometry(trial_functions) + + tdom = domain(test_chart) + bdom = domain(trial_chart) + + num_tshapes = numfunctions(test_local_space, tdom) + num_bshapes = numfunctions(trial_local_space, bdom) + + parent_mesh = CompScienceMeshes.parent(test_mesh) + trial_charts = [chart(test_mesh, p) for p in CompScienceMeshes.children(parent_mesh, trial_cell)] + + trial_overlaps = map(trial_charts) do chart + simplex( + carttobary(trial_chart, chart.vertices[1]), + carttobary(trial_chart, chart.vertices[2]), + carttobary(trial_chart, chart.vertices[3])) + end + + quadstrat = qr.conforming_qstrat + qd = quaddata(op, test_local_space, trial_local_space, + [test_chart], trial_charts, quadstrat) + + zlocal = zero(out) + Q = zeros(coordtype(trial_chart), num_bshapes, num_bshapes) + for (q,chart) in enumerate(trial_charts) + restrict!(Q, trial_local_space, trial_chart, chart, trial_overlaps[q]) + + qr = quadrule(op, test_local_space, trial_local_space, + 1, test_chart, q ,chart, qd, quadstrat) + + fill!(zlocal, 0) + momintegrals!(zlocal, op, + test_functions, nothing, test_chart, + trial_functions, nothing, chart, qr) + + for j in 1:num_bshapes + for i in 1:num_tshapes + for k in 1:size(Q, 2) + out[i,j] += zlocal[i,k] * Q[j,k] +end end end end end \ No newline at end of file diff --git a/src/quadrature/rules/timedomain/excitation/multiquadqrule.jl b/src/quadrature/rules/timedomain/excitation/multiquadqrule.jl new file mode 100644 index 00000000..53766782 --- /dev/null +++ b/src/quadrature/rules/timedomain/excitation/multiquadqrule.jl @@ -0,0 +1,22 @@ +mutable struct MultiQuadStrategy{P,R} #<: NumQuadStrategy + quad_points::P + inner_rule::R +end + + +timequadrule(qr::MultiQuadStrategy, p) = qr.inner_rule + +function momintegrals!(z, exc::TDFunctional, testrefs, timerefs, τ, ρ, qr::MultiQuadStrategy) + + for p in qr.quad_points + x = p.point + w = p.weight + f = p.value + dx = w + + tqr = timequadrule(qr,p) + timeintegrals!(z, exc, testrefs, timerefs, x, ρ, dx, tqr, f) + + end + +end \ No newline at end of file diff --git a/src/quadrature/rules/timedomain/excitation/singlequad2qrule.jl b/src/quadrature/rules/timedomain/excitation/singlequad2qrule.jl new file mode 100644 index 00000000..218f76d0 --- /dev/null +++ b/src/quadrature/rules/timedomain/excitation/singlequad2qrule.jl @@ -0,0 +1,42 @@ +# TODO: consolidate with the existing definition of SingleQuadStrategy +mutable struct SingleQuadStrategy2{P} #<: NumQuadStrategy + quad_points::P +end + + + +function timeintegrals!(z, exc::TDFunctional, + testrefs, timerefs, + testpoint, timeelement, dx, qr::SingleQuadStrategy2, f) + + num_tshapes = numfunctions(testrefs, domain(chart(testpoint))) + for p in qr.quad_points + t = p.point + w = p.weight + U = p.value + dt = w #* jacobian(t) # * volume(timeelement) + + for i in 1 : num_tshapes + for k in 1 : numfunctions(timerefs) + z[i,k] += dot(f[i][1]*U[k], exc(testpoint,t)) * dt * dx + end + end + end +end + + +function timeintegrals!(z, exc::TDFunctional, + spacerefs, timerefs::DiracBoundary, + testpoint, timeelement, + dx, qr::Nothing, testvals) + + num_tshapes = numfunctions(spacerefs, domain(chart(testpoint))) + # since timeelement uses barycentric coordinates, + # the first/left vertex has coords u = 1.0! + testtime = neighborhood(timeelement, point(0.0)) + @assert cartesian(testtime)[1] ≈ timeelement.vertices[2][1] + + for i in 1 : num_tshapes + z[i,1] += dot(testvals[i][1], exc(testpoint, testtime)) * dx + end +end \ No newline at end of file diff --git a/src/quadrature/rules/trialrefinestestqrule.jl b/src/quadrature/rules/trialrefinestestqrule.jl new file mode 100644 index 00000000..f0c74f54 --- /dev/null +++ b/src/quadrature/rules/trialrefinestestqrule.jl @@ -0,0 +1,43 @@ +struct TrialRefinesTestQRule{S} + conforming_qstrat::S +end + +function momintegrals!(out, op, + test_functions::Space, test_cell, test_chart, + trial_functions::Space, trial_cell, trial_chart, + qr::TrialRefinesTestQRule) + + test_local_space = refspace(test_functions) + trial_local_space = refspace(trial_functions) + + test_mesh = geometry(test_functions) + trial_mesh = geometry(trial_functions) + + tdom = domain(chart(test_mesh, first(test_mesh))) + bdom = domain(chart(trial_mesh, first(trial_mesh))) + + num_tshapes = numfunctions(test_local_space, tdom) + num_bshapes = numfunctions(trial_local_space, bdom) + + parent_mesh = CompScienceMeshes.parent(trial_mesh) + test_charts = [chart(trial_mesh, p) for p in CompScienceMeshes.children(parent_mesh, test_cell)] + + quadstrat = qr.conforming_qstrat + qd = quaddata(op, test_local_space, trial_local_space, + test_charts, [trial_chart], quadstrat) + + for (p,chart) in enumerate(test_charts) + qr = quadrule(op, test_local_space, trial_local_space, + p, chart, 1, trial_chart, qd, quadstrat) + + Q = restrict(test_local_space, test_chart, chart) + zlocal = zero(out) + momintegrals!(zlocal, op, + test_functions, nothing, chart, + trial_functions, trial_cell, trial_chart, qr) + + for j in 1:num_bshapes + for i in 1:num_tshapes + for k in 1:size(Q, 2) + out[i,j] += Q[i,k] * zlocal[k,j] +end end end end end \ No newline at end of file diff --git a/src/quadrature/sauterschwabints.jl b/src/quadrature/sauterschwabints.jl new file mode 100644 index 00000000..1688ab08 --- /dev/null +++ b/src/quadrature/sauterschwabints.jl @@ -0,0 +1,182 @@ +struct Integrand{Op,LSt,LSb,Elt,Elb} + operator::Op + local_test_space::LSt + local_trial_space::LSb + test_chart::Elt + trial_chart::Elb +end + + +function (igd::Integrand)(u,v) + + x = neighborhood(igd.test_chart,u) + y = neighborhood(igd.trial_chart,v) + + f = igd.local_test_space(x) + g = igd.local_trial_space(y) + + return jacobian(x) * jacobian(y) * igd(x,y,f,g) +end + +# For divergence conforming basis and trial functions, an alternative evaluation +# of the integrand is possible that avoids the computation of the chart jacobian +# determinants. +function (igd::Integrand{<:IntegralOperator,<:DivRefSpace,<:DivRefSpace})(u,v) + test_domain = CompScienceMeshes.domain(igd.test_chart) + bsis_domain = CompScienceMeshes.domain(igd.trial_chart) + + x = CompScienceMeshes.neighborhood_lazy(igd.test_chart,u) + y = CompScienceMeshes.neighborhood_lazy(igd.trial_chart,v) + + p = neighborhood(test_domain, u) + q = neighborhood(bsis_domain, v) + + f̂ = igd.local_test_space(p) + ĝ = igd.local_trial_space(q) + + Dx = tangents(x) + Dy = tangents(y) + + f = map(f̂) do fi + (value = Dx * fi.value, divergence = fi.divergence) end + g = map(ĝ) do gi + (value = Dy * gi.value, divergence = gi.divergence) end + + igd(x,y,f,g) +end + +getvalue(a::SVector{N}) where {N} = SVector{N}(getvalue(a.data)) +getvalue(a::NTuple{1}) = (a[1].value,) +getvalue(a::NTuple{N}) where {N} = tuple(a[1].value, getvalue(Base.tail(a))...) + +getdivergence(a::SVector{N}) where {N} = SVector{N}(getdivergence(a.data)) +getdivergence(a::NTuple{1}) = (a[1].divergence,) +getdivergence(a::NTuple{N}) where {N} = tuple(a[1].divergence, getdivergence(Base.tail(a))...) + +function _krondot_gen(a::Type{U}, b::Type{V}) where {U<:SVector{N}, V<:SVector{M}} where {M,N} + ex = :(SMatrix{N,M}(())) + for m in 1:M + for n in 1:N + push!(ex.args[2].args, :(dot(a[$n], b[$m]))) + end + end + return ex +end + +@generated function _krondot(a::SVector{N}, b::SVector{M}) where {M,N} + ex = _krondot_gen(a,b) + return ex +end + +function _integrands_gen(::Type{U}, ::Type{V}) where {U<:SVector{N}, V<:SVector{M}} where {M,N} + ex = :(SMatrix{N,M}(())) + for m in 1:M + for n in 1:N + # push!(ex.args[2].args, :(dot(a[$n], b[$m]))) + push!(ex.args[2].args, :(f(a[$n], b[$m]))) + end + end + return ex +end + +@generated function _integrands(f, a::SVector{N}, b::SVector{M}) where {M,N} + ex = _integrands_gen(a,b) + # println(ex) + return ex +end + +function _integrands_leg_gen(f::Type{U}, g::Type{V}) where {U<:SVector{N}, V<:SVector{M}} where {M,N} + ex = :(SMatrix{N,M}(())) + for m in 1:M + for n in 1:N + push!(ex.args[2].args, :(integrand(op, kervals, f[$n], x, g[$m], y))) + end + end + return ex +end +@generated function _integrands_leg(op, kervals, f::SVector{N}, x, g::SVector{M}, y) where {M,N} + _integrands_leg_gen(f, g) +end + + +# Support for legacy kernels +function (igd::Integrand)(x,y,f,g) + + op = igd.operator + kervals = kernelvals(op, x, y) + return _integrands_leg(op, kervals, f, x, g, y) + +end + +struct PulledBackIntegrand{I,C1,C2} + igd::I + chart1::C1 + chart2::C2 +end + +function (f::PulledBackIntegrand)(u,v) + # In general I think a Jacobian determinant needs to be included. For Simplical and + # Quadrilateral charts this is not needed because they are 1. + f.igd(cartesian(f.chart1,u), cartesian(f.chart2,v)) +end + +function pulledback_integrand(igd, + I, chart1, + J, chart2) + + dom1 = domain(chart1) + dom2 = domain(chart2) + + ichart1 = CompScienceMeshes.permute_vertices(dom1, I) + ichart2 = CompScienceMeshes.permute_vertices(dom2, J) + + PulledBackIntegrand(igd, ichart1, ichart2) +end + +function sauterschwab_parameterized(igdp, rule::SauterSchwabStrategy) + return SauterSchwabQuadrature.sauterschwab_parameterized(igdp, rule) +end + +function sauterschwab_parameterized(igdp, rule::SauterSchwabQuadrature1D.SauterSchwabStrategy1D) + return SauterSchwabQuadrature1D.sauterschwab_parameterized1D(igdp, rule) +end + +function sauterschwab_reorder(test_vertices, trial_vertices, rule::SauterSchwabStrategy) + I, J, _, _ = SauterSchwabQuadrature.reorder(test_vertices, trial_vertices, rule) + + return I, J +end + +function sauterschwab_reorder(test_vertices, trial_vertices, rule::SauterSchwabQuadrature1D.SauterSchwabStrategy1D) + I, J, _, _ = SauterSchwabQuadrature1D.reorder(test_vertices, trial_vertices, rule) + + return I, J +end + +function momintegrals!(op::Operator, + test_local_space, trial_local_space, + test_chart, trial_chart, + out, rule::Union{SauterSchwabStrategy,SauterSchwabQuadrature1D.SauterSchwabStrategy1D}) + + I, J = sauterschwab_reorder( + vertices(test_chart), + vertices(trial_chart), + rule + ) + + num_tshapes = numfunctions(test_local_space, domain(test_chart)) + num_bshapes = numfunctions(trial_local_space, domain(trial_chart)) + + igd = Integrand(op, test_local_space, trial_local_space, test_chart, trial_chart) + igdp = pulledback_integrand(igd, I, test_chart, J, trial_chart) + + G = sauterschwab_parameterized(igdp, rule) + + for j in 1:num_bshapes + for i in 1:num_tshapes + out[i,j] += G[i,j] + end + end + + nothing +end \ No newline at end of file diff --git a/src/quadrature/selfsauterdnumotherwiseqstrat.jl b/src/quadrature/selfsauterdnumotherwiseqstrat.jl new file mode 100644 index 00000000..1cbca3a4 --- /dev/null +++ b/src/quadrature/selfsauterdnumotherwiseqstrat.jl @@ -0,0 +1,48 @@ +struct SelfSauterOtherwiseDNumQStrat{R,S} <: AbstractQuadStrat + outer_rule::R + inner_rule::R + sauter_schwab_common::S +end + +function quaddata(op::IntegralOperator, + test_local_space::RefSpace, trial_local_space::RefSpace, + test_charts, trial_charts, qs::SelfSauterOtherwiseDNumQStrat) + + T = coordtype(test_charts[1]) + + tqd = quadpoints(test_local_space, test_charts, (qs.outer_rule,)) + bqd = quadpoints(trial_local_space, trial_charts, (qs.inner_rule,)) + + leg = ( + convert.(NTuple{2,T},_legendre(qs.sauter_schwab_common,0,1)), + ) + + return (tpoints=tqd, bpoints=bqd, gausslegendre=leg) +end + + +function quadrule(op::IntegralOperator, g::RefSpace, f::RefSpace, i, τ, j, σ, qd, + qs::SelfSauterOtherwiseDNumQStrat) + + T = eltype(eltype(τ.vertices)) + hits = 0 + dtol = 1.0e3 * eps(T) + dmin2 = floatmax(T) + for t in τ.vertices + for s in σ.vertices + d2 = LinearAlgebra.norm_sqr(t-s) + d = norm(t-s) + dmin2 = min(dmin2, d2) + # hits += (d2 < dtol) + hits += (d < dtol) + end + end + + @assert hits <= 3 + + hits == 3 && return SauterSchwabQuadrature.CommonFace(qd.gausslegendre[1]) + + return DoubleQuadRule( + qd.tpoints[1,i], + qd.bpoints[1,j],) +end \ No newline at end of file diff --git a/src/quadrature/singularity_extraction.jl b/src/quadrature/singularity_extraction.jl deleted file mode 100644 index debbdb4d..00000000 --- a/src/quadrature/singularity_extraction.jl +++ /dev/null @@ -1,23 +0,0 @@ -abstract type SingularityExtractionStrategy end -regularpart_quadrule(qr::SingularityExtractionStrategy) = qr.regularpart_quadrule - -function momintegrals!(op, g, f, t, s, z, strat::SingularityExtractionStrategy) - - - womps = strat.outer_quad_points - - sop = singularpart(op) - rop = regularpart(op) - - # compute the regular part - rstrat = regularpart_quadrule(strat) - momintegrals!(rop, g, f, t, s, z, rstrat) - - for p in 1 : length(womps) - x = womps[p].point - dx = womps[p].weight - - innerintegrals!(sop, x, g, f, t, s, z, strat, dx) - end # next quadrature point - -end diff --git a/src/quadrature/singularityextractionints.jl b/src/quadrature/singularityextractionints.jl new file mode 100644 index 00000000..6cb3377b --- /dev/null +++ b/src/quadrature/singularityextractionints.jl @@ -0,0 +1,22 @@ +abstract type SingularityExtractionRule end +regularpart_quadrule(qr::SingularityExtractionRule) = qr.regularpart_quadrule + +function momintegrals!(op, + g, f,t, s, + z, qrule::SingularityExtractionRule) + + womps = qrule.outer_quad_points + + sop = singularpart(op) + rop = regularpart(op) + + regqrule = regularpart_quadrule(qrule) + momintegrals!(rop, g, f, t, s, z, regqrule) + + for p in 1 : length(womps) + x = womps[p].point + dx = womps[p].weight + + innerintegrals!(sop, x, g, f, t, s, z, qrule, dx) + end +end diff --git a/src/quadrature/strategies/cfcvsautercewiltonpdnumqstrat.jl b/src/quadrature/strategies/cfcvsautercewiltonpdnumqstrat.jl new file mode 100644 index 00000000..5e86b67c --- /dev/null +++ b/src/quadrature/strategies/cfcvsautercewiltonpdnumqstrat.jl @@ -0,0 +1,69 @@ +struct CommonFaceVertexSauterCommonEdgeWiltonPostitiveDistanceNumQStrat{R,S} <: AbstractQuadStrat + outer_rule_far::R + inner_rule_far::R + outer_rule_near::R + inner_rule_near::R + sauter_schwab_common_tetr::S + sauter_schwab_common_face::S + sauter_schwab_common_edge::S + sauter_schwab_common_vert::S +end + +function quaddata(op::IntegralOperator, test_local_space, bsis_local_space, + test_charts, bsis_charts, qs::CommonFaceVertexSauterCommonEdgeWiltonPostitiveDistanceNumQStrat) + + return quaddata(op, test_local_space, bsis_local_space, + test_charts, bsis_charts, DoubleNumWiltonSauterQStrat( + qs.outer_rule_far, + qs.inner_rule_far, + qs.outer_rule_near, + qs.inner_rule_near, + qs.sauter_schwab_common_tetr, + qs.sauter_schwab_common_face, + qs.sauter_schwab_common_edge, + qs.sauter_schwab_common_vert,)) +end + +function quadrule(op::IntegralOperator, g, f, i, τ, j, σ, + qd, qs::CommonFaceVertexSauterCommonEdgeWiltonPostitiveDistanceNumQStrat) + + T = eltype(eltype(τ.vertices)) + hits = 0 + dtol = 1.0e3 * eps(T) + dmin2 = floatmax(T) + for t in τ.vertices + for s in σ.vertices + d2 = LinearAlgebra.norm_sqr(t-s) + d = norm(t-s) + dmin2 = min(dmin2, d2) + # hits += (d2 < dtol) + hits += (d < dtol) + end + end + + @assert hits <= 3 + + hits == 3 && return SauterSchwabQuadrature.CommonFace(qd.gausslegendre[3]) + # hits == 2 && return SauterSchwabQuadrature.CommonEdge(qd.gausslegendre[2]) + if hits == 2 + return WiltonSERule( + qd.tpoints[2,i], + SauterSchwabQuadrature.CommonEdge(qd.gausslegendre[2]),) + end + hits == 1 && return SauterSchwabQuadrature.CommonVertex(qd.gausslegendre[1]) + + h2 = volume(σ) + xtol2 = 0.2 * 0.2 + k2 = abs2(gamma(op)) + if max(dmin2*k2, dmin2/16h2) < xtol2 + return WiltonSERule( + qd.tpoints[2,i], + DoubleQuadRule( + qd.tpoints[2,i], + qd.bpoints[2,j],),) + end + + return DoubleQuadRule( + qd.tpoints[1,i], + qd.bpoints[1,j],) +end \ No newline at end of file diff --git a/src/quadrature/strategies/nonconftestbaryrefoftrialqstrat.jl b/src/quadrature/strategies/nonconftestbaryrefoftrialqstrat.jl new file mode 100644 index 00000000..51c1394d --- /dev/null +++ b/src/quadrature/strategies/nonconftestbaryrefoftrialqstrat.jl @@ -0,0 +1,48 @@ +struct NonConfTestBaryRefOfTrialQStrat{P} <: BEAST.AbstractQuadStrat + conforming_qstrat::P +end + +function BEAST.quaddata(a, X, Y, tels, bels, qs::NonConfTestBaryRefOfTrialQStrat) + return BEAST.quaddata(a, X, Y, tels, bels, qs.conforming_qstrat) +end + +function BEAST.quadrule(a, 𝒳, 𝒴, i, τ, j, σ, qd, + quadstrat::NonConfTestBaryRefOfTrialQStrat) + + # return TestInBaryRefOfTrialQRule(quadstrat.conforming_qstrat) + nh = BEAST._numhits(τ, σ) + nh > 0 && return TestInBaryRefOfTrialQRule(quadstrat.conforming_qstrat) + return BEAST.quadrule(a, 𝒳, 𝒴, i, τ, j, σ, qd, + quadstrat.conforming_qstrat) +end + +@testitem "NonConfTestBaryRefOfTrialQStrat" begin + using BEAST, Test + using CompScienceMeshes, LinearAlgebra + + # @show pathof(CompScienceMeshes) + + fnm = joinpath(dirname(pathof(BEAST)), "../test/assets/sphere45.in") + Γ1 = BEAST.readmesh(fnm) + Γ2 = deepcopy(Γ1) + + X = raviartthomas(Γ1) + Y1 = buffachristiansen(Γ1) + Y = buffachristiansen(Γ2) + + K = Maxwell3D.doublelayer(gamma=1.0) + qs1 = BEAST.DoubleNumWiltonSauterQStrat(2, 3, 6, 7, 5, 5, 4, 3) + qs2 = BEAST.NonConformingIntegralOpQStrat(qs1) + qs3 = BEAST.NonConfTestBaryRefOfTrialQStrat(qs1) + + @time Kyx1 = assemble(K, Y, X; quadstrat=qs1) + @time Kyx2 = assemble(K, Y, X; quadstrat=qs2) + @time Kyx3 = assemble(K, Y, X; quadstrat=qs3) + @time Kyx4 = assemble(K, Y1, X; quadstrat=qs1) + + @test norm(Kyx1 - Kyx2) < 0.05 + @test norm(Kyx1 - Kyx3) < 0.05 + @test norm(Kyx2 - Kyx3) < 0.002 + @test norm(Kyx2 - Kyx4) < 0.002 + @test norm(Kyx3 - Kyx4) < 1e-12 +end \ No newline at end of file diff --git a/src/quadrature/strategies/quadstrat.jl b/src/quadrature/strategies/quadstrat.jl new file mode 100644 index 00000000..38c88ca7 --- /dev/null +++ b/src/quadrature/strategies/quadstrat.jl @@ -0,0 +1,9 @@ +abstract type AbstractQuadStrat end + +function (qs::AbstractQuadStrat)(a, X, Y) + qs +end + +function (qs::AbstractQuadStrat)(l, X) + qs +end \ No newline at end of file diff --git a/src/quadrature/strategies/testrefinestrialqstrat.jl b/src/quadrature/strategies/testrefinestrialqstrat.jl new file mode 100644 index 00000000..e295c6e5 --- /dev/null +++ b/src/quadrature/strategies/testrefinestrialqstrat.jl @@ -0,0 +1,18 @@ +struct TestRefinesTrialQStrat{S} <: AbstractQuadStrat + conforming_qstrat::S +end + +function quaddata(a, X, Y, tels, bels, qs::TestRefinesTrialQStrat) + return quaddata(a, X, Y, tels, bels, qs.conforming_qstrat) +end + +function quadrule(a, 𝒳, 𝒴, i, τ, j, σ, qd, + qs::TestRefinesTrialQStrat) + + hits = _numhits(τ, σ) + if hits > 0 + return TestRefinesTrialQRule(qs.conforming_qstrat) + end + + return quadrule(a, 𝒳, 𝒴, i, τ, j, σ, qd, qs.conforming_qstrat) +end \ No newline at end of file diff --git a/src/quadrature/strategies/timedomain/excitation/numspacenumtimeqstrat.jl b/src/quadrature/strategies/timedomain/excitation/numspacenumtimeqstrat.jl new file mode 100644 index 00000000..a3655512 --- /dev/null +++ b/src/quadrature/strategies/timedomain/excitation/numspacenumtimeqstrat.jl @@ -0,0 +1,91 @@ +struct NumSpaceNumTimeQStrat{S,T} <: AbstractQuadStrat + space_rule::S + time_rule::T +end + + +function quaddata(exc::TDFunctional, + testrefs, timerefs, + testels, timeels, quadstrat::NumSpaceNumTimeQStrat) + + r = quadstrat.space_rule + s = quadstrat.time_rule + + testqd = quadpoints(testrefs, testels, (r,)) + timeqd = quadpoints(timerefs, timeels, (s,)) + + testqd, timeqd + +end + + +function quadrule(exc::TDFunctional, + testrefs, timerefs, + p, τ, r, ρ, + qd, quadstrat::NumSpaceNumTimeQStrat) + + MultiQuadStrategy( + qd[1][1,p], + SingleQuadStrategy2( + qd[2][1,r] + ) + ) + +end + + +function quaddata(excitation::TDFunctional, + test_refspace, time_refspace::DiracBoundary, + test_elements, time_elements, quadstrat::NumSpaceNumTimeQStrat) + + r = quadstrat.space_rule + test_quad_data = quadpoints(test_refspace, test_elements, (r,)) + + test_quad_data, nothing +end + + +function quadrule(exc::TDFunctional, + testrefs, timerefs::DiracBoundary, + p, τ, r, ρ, + qd, quadstrat::NumSpaceNumTimeQStrat) + + MultiQuadStrategy( + qd[1][1,p], + nothing + ) + +end + + +@testitem "tdexc: multiquadqrule" begin + using CompScienceMeshes + using LinearAlgebra + + fn = joinpath(dirname(pathof(BEAST)), "../test/assets/sphere45.in") + + mesh = readmesh(fn) + RT = raviartthomas(mesh) + + Δt = 0.1 + Nt = 200 + T = timebasisshiftedlagrange(Δt, Nt, 3) + U = timebasisdelta(Δt, Nt) + + X = RT ⊗ U + + duration = 2 * 20 * Δt + delay = 1.5 * duration + amplitude = 1.0 + gaussian = creategaussian(duration, delay, amplitude) + direction, polarisation = ẑ, x̂ + E = planewave(polarisation, direction, derive(gaussian), 1.0) + + qs1 = BEAST.NumSpaceNumTimeQStrat(2, 10) + qs2 = BEAST.NumSpaceNumTimeQStrat(6, 20) + + b1 = assemble(E, X; quadstrat=qs1) + b2 = assemble(E, X; quadstrat=qs2) + + @test norm(b1-b2, Inf) < 1e-4 +end \ No newline at end of file diff --git a/src/quadrature/strategies/timedomain/nothingqstrat.jl b/src/quadrature/strategies/timedomain/nothingqstrat.jl new file mode 100644 index 00000000..f6234dd9 --- /dev/null +++ b/src/quadrature/strategies/timedomain/nothingqstrat.jl @@ -0,0 +1,2 @@ +struct NothingQStrategy <: AbstractQuadStrat +end \ No newline at end of file diff --git a/src/quadrature/strategies/trialrefinestestqstrat.jl b/src/quadrature/strategies/trialrefinestestqstrat.jl new file mode 100644 index 00000000..7cd70e03 --- /dev/null +++ b/src/quadrature/strategies/trialrefinestestqstrat.jl @@ -0,0 +1,18 @@ +struct TrialRefinesTestQStrat{S} <: AbstractQuadStrat + conforming_qstrat::S +end + +function quaddata(a, X, Y, tels, bels, qs::TrialRefinesTestQStrat) + return quaddata(a, X, Y, tels, bels, qs.conforming_qstrat) +end + +function quadrule(a, 𝒳, 𝒴, i, τ, j, σ, qd, + qs::TrialRefinesTestQStrat) + + hits = _numhits(τ, σ) + if hits > 0 + return TrialRefinesTestQRule(qs.conforming_qstrat) + end + + return quadrule(a, 𝒳, 𝒴, i, τ, j, σ, qd, qs.conforming_qstrat) +end \ No newline at end of file diff --git a/src/solvers/gmres.jl b/src/solvers/gmres.jl new file mode 100644 index 00000000..81c5e578 --- /dev/null +++ b/src/solvers/gmres.jl @@ -0,0 +1,94 @@ +import Krylov +import Suppressor + +struct GMRES{T} <: LinearMap{T} + A + M + N + memory + restart + atol + rtol + itmax + verbose + workspace +end + +Base.axes(solver::GMRES) = reverse(axes(solver.A)) +Base.size(solver::GMRES) = reverse(size(solver.A)) + +function LinearAlgebra.mul!(y::AbstractVecOrMat, solver::GMRES, x::AbstractVector) + fill!(y,0) + y, ch = solve!(y, solver, x) + solver.verbose > 0 && println("Number of iterations: ", ch.niter) + ch.solved || error("Iterative solver did not converge.") + return y +end + + +function GMRES(A; + M = LinearAlgebra.I, + N = LinearAlgebra.I, + memory = 20, + restart = false, + atol = sqrt(eps(real(eltype(A)))), + rtol = sqrt(eps(real(eltype(A)))), + itmax = 2 * size(A,2), + verbose = 1) + + m, n = size(A) + T = eltype(A) + ws = Krylov.GmresWorkspace(m, n, Vector{T}; memory) + return GMRES{T}(A, M, N, memory, restart, atol, rtol, itmax, verbose, ws) +end + + +function solve!(x, solver::GMRES, b) + + m, n = size(solver.A) + T = promote_type(eltype(solver.A), eltype(b)) + # @show T + ws = Krylov.GmresWorkspace(m, n, Vector{T}; solver.memory) + # @show typeof(ws) + + Suppressor.@suppress_err begin + Krylov.gmres!(ws, solver.A, Vector(b); + M = solver.M, + N = solver.N, + # memory = solver.memory, + restart = solver.restart, + atol = solver.atol, + rtol = solver.rtol, + itmax = solver.itmax, + verbose = solver.verbose, + history = true) + end + x .= Krylov.solution(ws) + + return x, Krylov.statistics(ws) +end + +function solve(solver::GMRES, b) + T = promote_type(eltype(solver), eltype(b)) + x = similar(Vector{T}, size(solver)[2]) + fill!(x,0) + x, ch = solve!(x, solver, b) + # z = similar(Array{T}, axes(solver)[2]) + # copyto!(z,x) + return x, ch +end + +@testitem "GMRES" begin + using LinearAlgebra + + A = rand(10, 10) + b = rand(10) + + Ai = BEAST.GMRES(A; restart=true, atol=1e-8, rtol=1e-8, verbose=1, memory=200) + x, ch = solve(Ai, b) + + Ai2 = BEAST.GMRESSolver(A; restart=200, abstol=1e-8, reltol=1e-8, verbose=true) + x2, ch2 = solve(Ai2, b) + + @test norm(x - x2) < 1e-6 +end \ No newline at end of file diff --git a/src/solvers/itsolver.jl b/src/solvers/itsolver.jl index 77f2ecbb..63c64a9c 100644 --- a/src/solvers/itsolver.jl +++ b/src/solvers/itsolver.jl @@ -2,61 +2,218 @@ import IterativeSolvers - -struct GMRESSolver{L,R} +struct GMRESSolver{T,L,R,PL,PR} <: LinearMap{T} linear_operator::L maxiter::Int restart::Int - tol::R + abstol::R + reltol::R + verbose::Bool + left_preconditioner::PL + right_preconditioner::PR end -function GMRESSolver(op; maxiter=0, restart=0, tol=sqrt(eps(real(eltype(op))))) +Base.axes(A::GMRESSolver) = reverse(axes(A.linear_operator)) + + +function GMRESSolver(op::L; + left_preconditioner = nothing, + right_preconditioner = nothing, + Pl = nothing, + Pr = nothing, + maxiter=0, + restart=0, + abstol::R = zero(real(eltype(op))), + reltol::R = sqrt(eps(real(eltype(op)))), + verbose=true) where {L,R<:Real} + + if left_preconditioner == nothing + Pl == nothing && (Pl = IterativeSolvers.Identity()) + else + if Pl == nothing + Pl = BEAST.Preconditioner(left_preconditioner) + else + error("Either supply Pl or left_preconditioner, not both.") + end + end + @assert Pl != nothing + + if right_preconditioner == nothing + Pr == nothing && (Pr = IterativeSolvers.Identity()) + else + if Pr == nothing + Pr = BEAST.Preconditioner(right_preconditioner) + else + error("Either supply Pl or left_preconditioner, not both.") + end + end + @assert Pr != nothing m, n = size(op) @assert m == n - maxiter == 0 && (maxiter = div(n, 5)) + maxiter == 0 && (maxiter = n) restart == 0 && (restart = n) - GMRESSolver(op, maxiter, restart, tol) + PL = typeof(Pl) + PR = typeof(Pr) + T = eltype(op) + GMRESSolver{T,L,R,PL,PR}(op, maxiter, restart, abstol, reltol, verbose, Pl, Pr) end operator(solver::GMRESSolver) = solver.linear_operator -function solve(solver::GMRESSolver, b) +function solve(solver::GMRESSolver, b; abstol=solver.abstol, reltol=solver.reltol) + T = promote_type(eltype(solver), eltype(b)) + x = similar(Array{T}, axes(solver)[2]) + fill!(x,0) + x, ch = solve!(x, solver, b; abstol, reltol) +end + + +function solve!(x, solver::GMRESSolver, b; abstol=solver.abstol, reltol=solver.reltol) op = operator(solver) - x, ch = IterativeSolvers.gmres(op, b, log=true, maxiter=solver.maxiter, - restart=solver.restart, reltol=solver.tol) + x, ch = IterativeSolvers.gmres!(x, op, b; + log=true, + maxiter=solver.maxiter, + restart=solver.restart, + reltol=reltol, + abstol=abstol, + verbose=solver.verbose, + Pl=solver.left_preconditioner, + Pr=solver.right_preconditioner) return x, ch end -function Base.:*(solver::GMRESSolver, b) - x, ch = solve(solver, b) - println("Number of iterations: ", ch.iters) +function Base.:*(A::GMRESSolver, b::AbstractVector) + + T = promote_type(eltype(A), eltype(b)) + y = BlockedVector{T}(undef, (axes(A,2),)) + + mul!(y, A, b) +end + +Base.size(solver::GMRESSolver) = reverse(size(solver.linear_operator)) + +function LinearAlgebra.mul!(y::AbstractVecOrMat, solver::GMRESSolver, x::AbstractVector) + fill!(y,0) + y, ch = solve!(y, solver, x) + solver.verbose && println("Number of iterations: ", ch.iters) ch.isconverged || error("Iterative solver did not converge.") - return x + return y end +LinearAlgebra.adjoint(A::GMRESSolver) = GMRESSolver(adjoint(A.linear_operator); maxiter=A.maxiter, restart=A.restart, abstol=A.abstol, reltol=A.reltol, verbose=A.verbose, Pl=A.right_preconditioner, Pr=A.left_preconditioner) +LinearAlgebra.transpose(A::GMRESSolver) = GMRESSolver(transpose(A.linear_operator); maxiter=A.maxiter, restart=A.restart, abstol=A.abstol, reltol=A.reltol, verbose=A.verbose, Pl=A.right_preconditioner, Pr=A.left_preconditioner) + -function gmres(eq::DiscreteEquation; maxiter=0, restart=0, tol=0) - test_space_dict = eq.test_space_dict - trial_space_dict = eq.trial_space_dict +function gmres_ch(eq::DiscreteEquation; maxiter=0, restart=0, tol=0, verbose=true) lhs = eq.equation.lhs rhs = eq.equation.rhs - b = assemble(rhs, test_space_dict) - Z = assemble(lhs, test_space_dict, trial_space_dict) + X = _spacedict_to_directproductspace(eq.test_space_dict) + Y = _spacedict_to_directproductspace(eq.trial_space_dict) + + b = assemble(rhs, X) + Z = assemble(lhs, X, Y) if tol == 0 - invZ = GMRESSolver(Z, maxiter=maxiter, restart=restart) + invZ = GMRESSolver(Z; maxiter, restart, verbose) else - invZ = GMRESSolver(Z, maxiter=maxiter, restart=restart, tol=tol) + invZ = GMRESSolver(Z; maxiter, restart, reltol=tol, verbose) end - x = invZ * b + x, ch = solve(invZ, b) + # x = invZ * b + + ax = nestedrange(Y, 1, numfunctions) + return BlockedVector(x, (ax,)), ch end + +gmres(eq::DiscreteEquation; maxiter=0, restart=0, tol=0) = gmres_ch(eq; maxiter, restart, tol)[1] + + + +struct CGSolver{L,M,T} <: LinearMap{T} + A::L + Pl::M + abstol::T + reltol::T + maxiter::Int + verbose::Bool +end + + +Base.size(solver::CGSolver) = reverse(size(solver.A)) +Base.axes(solver::CGSolver) = reverse(axes(solver.A)) +operator(solver::CGSolver) = solver.A + + +cg(A; + Pl = IterativeSolvers.Identity(), + abstol::Real = zero(real(eltype(A))), + reltol::Real = sqrt(eps(real(eltype(A)))), + maxiter::Int = size(A,2), + verbose::Bool = false) = CGSolver(A, Pl, abstol, reltol, maxiter, verbose) + + +function solve(solver::CGSolver, b) + T = promote_type(eltype(solver), eltype(b)) + x = similar(Vector{T}, size(solver)[2]) + fill!(x,0) + x, ch = solve!(x, solver, b) + z = similar(Array{T}, axes(solver)[2]) + copyto!(z,x) + return z, ch +end + + +function solve!(x, solver::CGSolver, b) + op = operator(solver) + x, ch = IterativeSolvers.cg!(x, op, b; + Pl = solver.Pl, + abstol = solver.abstol, + reltol = solver.reltol, + maxiter = solver.maxiter, + verbose = solver.verbose, + log = true) + return x, ch +end + +function LinearAlgebra.mul!(y::AbstractVecOrMat, solver::CGSolver, x::AbstractVector) + fill!(y,0) + y, ch = solve!(y, solver, x) + return y +end + + +struct Preconditioner{L,T} <: LinearMap{T} + A::L +end + +Preconditioner(A::L) where {L} = Preconditioner{L,eltype(A)}(A) + +function LinearAlgebra.ldiv!(y, P::Preconditioner, x) + mul!(y, P.A, x) +end + +function LinearAlgebra.ldiv!(P::Preconditioner, b) + c = deepcopy(b) + mul!(b, P.A, c) +end + +function Base.size(p::Preconditioner) + reverse(size(p.A)) +end + + +function solve(iA::AbstractMatrix, b) + ch = nothing + x = iA * b + return x, ch +end \ No newline at end of file diff --git a/src/solvers/lusolver.jl b/src/solvers/lusolver.jl index c5de7991..4c3b8fdb 100644 --- a/src/solvers/lusolver.jl +++ b/src/solvers/lusolver.jl @@ -1,3 +1,7 @@ +using LinearAlgebra + +lusolve(eq) = solve(eq) + """ Solves a variational equation by simply creating the full system matrix and calling a traditional lu decomposition. @@ -5,7 +9,7 @@ and calling a traditional lu decomposition. function solve(eq) time_domain = isa(first(eq.trial_space_dict).second, BEAST.SpaceTimeBasis) - time_domain |= isa(first(eq.trial_space_dict).second, BEAST.StagedTimeStep) + if time_domain return td_solve(eq) end @@ -16,48 +20,20 @@ function solve(eq) lhs = eq.equation.lhs rhs = eq.equation.rhs - b = assemble(rhs, test_space_dict) - Z = assemble(lhs, test_space_dict, trial_space_dict) - - u = Z \ b - - return u -end - - -function td_solve(eq) - - V = eq.trial_space_dict[1] + X = _spacedict_to_directproductspace(eq.test_space_dict) + Y = _spacedict_to_directproductspace(eq.trial_space_dict) - # bilform = eq.equation.lhs + b = assemble(rhs, X) + Z = assemble(lhs, X, Y) - A = td_assemble(eq.equation.lhs, eq.test_space_dict, eq.trial_space_dict) - S = timeslice(A,1) - iS = inv(Array(S)) + print("Converting system to Matrix...") + M = Matrix(Z) + println("done.") - b = td_assemble(eq.equation.rhs, eq.test_space_dict) - - nt = numfunctions(temporalbasis(V)) - marchonintime(iS, A, b, nt) -end - -function timeslice(A::BlockArray, k) - - I = blocklengths(axes(A,1)) - J = blocklengths(axes(A,2)) - - T = eltype(eltype(A)) - S = PseudoBlockArray{T}(undef, I, J) - fill!(S,0) - - for i in 1:blocksize(A,1) - for j in 1:blocksize(A,2) - isassigned(A.blocks, i, j) || continue - A[Block(i,j)] isa Zeros && continue - A[Block(i,j)] isa Fill && continue - S[Block(i,j)] = A[Block(i,j)].convop[:,:,k] - end - end + print("LU solution of the linear system...") + u = M \ Vector(b) + println("done.") - return S + ax = nestedrange(Y, 1, numfunctions) + return BlockedVector(u, (ax,)) end diff --git a/src/solvers/solver.jl b/src/solvers/solver.jl index 4ab8d3c4..66ab362d 100644 --- a/src/solvers/solver.jl +++ b/src/solvers/solver.jl @@ -8,8 +8,81 @@ mutable struct DiscreteEquation test_space_dict # dictionary mapping indices into test space to FE spaces end +struct DiscreteBilform + bilform + trial_space_dict # dictionary mapping indices into trial space to FE spaces + test_space_dict # dictionary mapping indices into test space to FE spaces +end + +struct DiscreteLinform + linform + test_space_dict +end + +function _expand_space_mappings(sms) + esms = [] + for sm in sms + if first(sm) isa Vector + j = first(sm) + X = last(sm) + @assert X isa BEAST.DirectProductSpace + append!(esms, [(ji => Xi) for (ji,Xi) in zip(j,X.factors)]) + else + append!(esms, [sm]) + end + end + return esms +end + + +function discretise(bf::BilForm, space_mappings::Pair...) + + space_mappings = _expand_space_mappings(space_mappings) + + trial_space_dict = Dict() + test_space_dict = Dict() + for sm in space_mappings + + found = false + sm.first.space == bf.trial_space && (dict = trial_space_dict; found = true) + sm.first.space == bf.test_space && (dict = test_space_dict; found = true) + @assert found "Vector $(sm.first) neither in test nor in trial space" + + @assert !haskey(dict, sm.first.idx) "multiple mappings for $(sm.first)" + dict[sm.first.idx] = sm.second + end + + # check that all symbols where mapped + for p in eachindex(bf.trial_space) @assert haskey(trial_space_dict,p) end + for p in eachindex(bf.test_space) @assert haskey(test_space_dict, p) end + + DiscreteBilform(bf, trial_space_dict, test_space_dict) +end + + +function discretise(lf::LinForm, space_mappings::Pair...) + space_mappings = _expand_space_mappings(space_mappings) + test_space_dict = Dict() + for sm in space_mappings + + found = false + sm.first.space == lf.test_space && (dict = test_space_dict; found = true) + @assert found "Vector $(sm.first) not found in test space" + + @assert !haskey(dict, sm.first.idx) "multiple mappings for $(sm.first)" + dict[sm.first.idx] = sm.second + end + + # check that all symbols where mapped + for p in eachindex(lf.test_space) @assert haskey(test_space_dict, p) end + + DiscreteLinform(lf, test_space_dict) +end + function discretise(eq, space_mappings::Pair...) + space_mappings = _expand_space_mappings(space_mappings) + trial_space_dict = Dict() test_space_dict = Dict() for sm in space_mappings @@ -51,174 +124,179 @@ macro discretise(eq, pairs...) end -sysmatrix(eq::DiscreteEquation) = assemble(eq.equation.lhs, eq.test_space_dict, eq.trial_space_dict) +sysmatrix(eq::DiscreteEquation; materialize=BEAST.assemble) = + assemble(eq.equation.lhs, eq.test_space_dict, eq.trial_space_dict, materialize=materialize) rhs(eq::DiscreteEquation) = assemble(eq.equation.rhs, eq.test_space_dict) -function assemble(lform::LinForm, test_space_dict) - - terms = lform.terms - T = ComplexF64 - - # I = Int[1] - blocksizes1 = Int[] - for p in 1:length(lform.test_space) - X = test_space_dict[p] - # push!(I, last(I) + numfunctions(X)) - push!(blocksizes1, numfunctions(X)) - end - - Z = zeros(T, sum(blocksizes1)) - B = PseudoBlockArray{T}(Z, blocksizes1) - - for t in terms - - α = t.coeff - a = t.functional - m = t.test_id - X = test_space_dict[m] - o = t.test_ops - - # act with the various ops on X - for op in reverse(o) - Y = X; - X = op[end](op[1:end-1]..., Y) - end +assemble(dbf::DiscreteBilform; materialize=BEAST.assemble) = assemble(dbf.bilform, dbf.test_space_dict, dbf.trial_space_dict; materialize) +assemble(dlf::DiscreteLinform) = assemble(dlf.linform, dlf.test_space_dict) - b = assemble(a, X) - # B[I[m] : I[m+1]-1] = α * b - B[Block(m)] = α * b - end - return B +function _spacedict_to_directproductspace(spacedict) + xfactors = Vector{AbstractSpace}(undef, length(spacedict)) + for (p,x) in spacedict xfactors[p] = x end + X = DirectProductSpace(xfactors) end -function td_assemble(lform::LinForm, test_space_dict) - - terms = lform.terms +function assemble(lform::LinForm, test_space_dict) + X = _spacedict_to_directproductspace(test_space_dict) + return assemble(lform, X) +end - T = Float32 - for term in lform.terms - T = scalartype(T,term.coeff) - T = scalartype(T,term.functional) +scalartype(lf::LinForm) = scalartype(lf.terms...) +scalartype(lt::LinTerm) = scalartype(lt.coeff, lt.functional) + +function assemble(lform::LinForm, X::DirectProductSpace; + quadstrat=BEAST.defaultquadstrat) + + @assert !isempty(lform.terms) + + T = scalartype(lform, X) + x = first(AbstractTrees.Leaves(X)) + spaceTimeBasis = isa(x, BEAST.SpaceTimeBasis) + if spaceTimeBasis + stagedtimestep = isa(x.time, BEAST.StagedTimeStep) + if stagedtimestep + stages = numstages(x.time) + stagednumfunctions(X) = stages * numfunctions(X) + U = NestedUnitRanges.nestedrange(spatialbasis(X), 1, stagednumfunctions) + else + U = NestedUnitRanges.nestedrange(spatialbasis(X), 1, numfunctions) + end + else + U = NestedUnitRanges.nestedrange(spatialbasis(X), 1, numfunctions) end - for kv in test_space_dict; T = scalartype(T,kv[2]) end - # @show T - - I = [numfunctions(spatialbasis(test_space_dict[i])) for i in 1:length(lform.test_space)] + + N = Base.OneTo(tensordim(x,2)) + ax = _righthandside_axes(x, U, N) - BT = SparseND.SpaceTimeData{T} - B = BlockArray(undef_blocks, BT, I) + B = BlockArray{T}(undef, ax) + fill!(B, 0) - for t in terms + for t in lform.terms - α = t.coeff - a = t.functional m = t.test_id - X = test_space_dict[m] - o = t.test_ops + x = X.factors[m] - # act with the various ops on X - for op in reverse(o) - Y = X; - X = op[end](op[1:end-1]..., Y) - end - - b = assemble(a, X) - B[Block(m)] = SparseND.SpaceTimeData{T}(α*b) + for op in reverse(t.test_ops) x = op[end](op[1:end-1]..., x) end + b = assemble(t.functional, x; quadstrat) + B[Block(m),Block(1)] = t.coeff * b end return B end -function assemble(bilform::BilForm, test_space_dict, trial_space_dict) - - lhterms = bilform.terms - T = ComplexF64 # TDOD: Fix this +function assemble(lf::LinForm, X::Space) + @assert length(lf.terms) == 1 + assemble(lf, BEAST.DirectProductSpace([X])) +end - blocksizes1 = Int[] - for p in 1:length(bilform.test_space) - X = test_space_dict[p] - push!(blocksizes1, numfunctions(X)) - end +struct SpaceTimeData{T} <: AbstractArray{Vector{T},1} + data::Array{T,2} +end - blocksizes2 = Int[] - for q in 1:length(bilform.trial_space) - Y = trial_space_dict[q] - push!(blocksizes2, numfunctions(Y)) - end +Base.eltype(x::SpaceTimeData{T}) where {T} = Vector{T} +Base.size(x::SpaceTimeData) = (size(x.data)[1],) +Base.getindex(x::SpaceTimeData, i::Int) = x.data[i,:] - # allocate the memory for the matrices - A = zeros(T, sum(blocksizes1), sum(blocksizes2)) - Z = PseudoBlockArray{T}(A, blocksizes1, blocksizes2) - # For each block, compute the interaction matrix - for t in lhterms +function td_assemble(lform::LinForm, test_space_dict) + X = _spacedict_to_directproductspace(test_space_dict) + return td_assemble(lform, X) +end - α = t.coeff - a = t.kernel +_righthandside_axes(x::SpaceTimeBasis, U, N) = (U,N,) +_righthandside_axes(x, U, N) = (U,) - m = t.test_id - x = test_space_dict[m] - for op in reverse(t.test_ops) - x = op[end](op[1:end-1]..., x) - end +td_assemble(lform::LinForm, X::DirectProductSpace) = assemble(lform, X) - n = t.trial_id - y = trial_space_dict[n] - for op in reverse(t.trial_ops) - y = op[end](op[1:end-1]..., y) - end +function assemble(bilform::BilForm, test_space_dict, trial_space_dict; + materialize=BEAST.assemble) - z = assemble(a, x, y) - Z[Block(m,n)] += α * z - end + X = _spacedict_to_directproductspace(test_space_dict) + Y = _spacedict_to_directproductspace(trial_space_dict) - return Z + return assemble(bilform, X, Y; materialize) end +lift(a,I,J,U,V) = LiftedMaps.LiftedMap(a,I,J,U,V) +lift(a::ConvolutionOperators.AbstractConvOp ,I,J,U,V) = + ConvolutionOperators.LiftedConvOp(a, U, V, I, J) -function td_assemble(bilform::BilForm, test_space_dict, trial_space_dict) +function assemble(bf::BilForm, X::DirectProductSpace, Y::DirectProductSpace; + materialize=BEAST.assemble, quadstrat=BEAST.defaultquadstrat) - lhterms = bilform.terms + T = Int32 + @assert !isempty(bf.terms) - T = Float32 - for term in bilform.terms - T = scalartype(T,term.coeff) - T = scalartype(T,term.kernel) - end - for kv in test_space_dict; T = scalartype(T,kv[2]) end - for kv in trial_space_dict; T = scalartype(T,kv[2]) end + Q = X.factors[1] + while Q isa BEAST.DirectProductSpace + Q = Q.factors[1] + end + spaceTimeBasis = isa(Q, BEAST.SpaceTimeBasis) - I = [numfunctions(spatialbasis(test_space_dict[i])) for i in 1:length(bilform.test_space)] - J = [numfunctions(spatialbasis(trial_space_dict[i])) for i in 1:length(bilform.trial_space)] + if spaceTimeBasis + if X[1] isa DirectProductSpace + p = 1 + else + p = [numstages(temporalbasis(ch)) for ch in X.factors] + end + lincombv = ConvolutionOperators.AbstractConvOp[] + else + p = 1 + lincombv = LinearMap[] + end -# BT = SparseND.MatrixOfConvolutions{T} -# Z = BlockArray(undef_blocks, BT, I, J) + M = numfunctions.(spatialbasis(X).factors) .* p + N = numfunctions.(spatialbasis(Y).factors) .* p - Z = BlockArray{Vector{T}}(zero_blocks, I, J) + MN = numfunctions(X) + + U = BlockArrays.blockedrange(M) + V = BlockArrays.blockedrange(N) - # For each block, compute the interaction matrix - for t in lhterms + for term in bf.terms - @show (t.coeff,t.kernel) - a = t.coeff * t.kernel + x = X.factors[term.test_id] + for op in reverse(term.test_ops) + x = op[end](op[1:end-1]..., x) + end - m = t.test_id - x = test_space_dict[m] - for op in reverse(t.test_ops) - x = op[end](op[1:end-1]..., x) - end + y = Y.factors[term.trial_id] + for op in reverse(term.trial_ops) + y = op[end](op[1:end-1]..., y) + end + + a = term.kernel + # qs = quadstrat(a, x, y) + z = materialize(a, x, y; quadstrat) + + Smap = term.coeff * lift(z, Block(term.test_id), Block(term.trial_id), U, V) + T = promote_type(T, eltype(Smap)) + push!(lincombv, Smap) + end + if spaceTimeBasis + return sum(lincombv) + else + if length(lincombv) == 1 + return lincombv[1] + else + return LinearMaps.LinearCombination{T}(lincombv) + end + end +end - n = t.trial_id - y = trial_space_dict[n] - for op in reverse(t.trial_ops) - y = op[end](op[1:end-1]..., y) - end +function assemble(bf::BilForm, X::Space, Y::Space) + @assert length(bf.terms) == 1 + assemble(bf, BEAST.DirectProductSpace([X]), BEAST.DirectProductSpace([Y])) +end - z = assemble(a, x, y) - @warn "variation formulations where combinations of test and trial space recur multiple times are not supported!" - Z[Block(m,n)] = SparseND.MatrixOfConvolutions(z) - - end - return Z +function assemble(bf::BilForm, pairs::Pair...) + dbf = discretise(bf, pairs...) + assemble(dbf) end + +function assemble(bf::LinForm, pairs::Pair...) + dbf = discretise(bf, pairs...) + assemble(dbf) +end \ No newline at end of file diff --git a/src/timedomain/convop.jl b/src/timedomain/convop.jl deleted file mode 100644 index 25ae7740..00000000 --- a/src/timedomain/convop.jl +++ /dev/null @@ -1,96 +0,0 @@ -struct ConvOp{T} <: AbstractArray{T,3} - data::Array{T,3} - k0::Array{Int,2} - k1::Array{Int,2} - tail::Array{T,2} - length::Int -end - -function Base.size(obj) - return (size(obj.data)[2:3]...,obj.length) -end - -function Base.getindex(obj::ConvOp, m::Int, n::Int, k::Int) - if k < obj.k0[m,n] - return zero(eltype(obj.data)) - end - - if k > obj.k1[m,n] - return obj.tail[m,n] - end - - return obj.data[k-obj.k0[m,n]+1,m,n] -end - - -function convolve!(y, Z::ConvOp, x, X, j, k_start, k_stop=size(Z,3)) - for n in axes(x,1) - for m in axes(y,1) - k0 = Z.k0[m,n] - k1 = Z.k1[m,n] - for k in max(k0,k_start):min(k1,k_stop) - p = k - k0 + 1 - j-k < 0 && continue - y[m] += Z.data[p,m,n] * x[n,j-k+1] - end - - j-k1 > 0 || continue - y[m] += Z.tail[m,n] * X[n,j-k1] - end - end -end - - -function polyeig(Z::ConvOp) - kmax = maximum(Z.k1) - M = size(Z,1) - Q = zeros(eltype(Z), 2M, 2M, kmax+1) - for k in 1:kmax - Q[1:M,1:M,k] .= Z[:,:,k] - end - Id = Matrix{eltype(Z)}(LinearAlgebra.I,M,M) - Q[M+1:2M,1:M,1] .= -Id - Q[M+1:2M,M+1:2M,1] .= Id - Q[M+1:2M,M+1:2M,2] .= -Id - Q[1:M,M+1:2M,kmax+1] .= Z[:,:,kmax+1] - return eigvals(companion(Q)), Q - # return Q -end - - -function polyeig(Z) - return eigvals(companion(Z)) -end - - -function Base.:+(a::ConvOp, b::ConvOp) - - @assert size(a.data) == size(b.data) - M,N = size(a.data) - T = promote_type(eltype(a.data), eltype(b.data)) - - k0 = min.(a.k0, b.k0) - k1 = max.(a.k1, b.k1) - - bandwidth = maximum(k1 - k0) + 1 - data = zeros(T, bandwidth, M, N) - - k1max = maximum(k1) - tail = zeros(T,M,N) - for m in 1:M, n in 1:N - tail[m,n] = a[m,n,k1max+1] + b[m,n,k1max+1] - end - - for m in 1:M - for n in 1:N - for k in k0[m,n]:k1[m,n] - data[k,m,n] = a[m,n,k] + b[m,n,k] - end - end - end - - lgt = max(a.length, b.length) - return ConvOp(data, tail, k0, k1, lgt) -end - - diff --git a/src/timedomain/motlu.jl b/src/timedomain/motlu.jl index 4c65653d..fd82452f 100644 --- a/src/timedomain/motlu.jl +++ b/src/timedomain/motlu.jl @@ -1,98 +1,35 @@ +motsolve(eq) = td_solve(eq) +function td_solve(eq) + V = eq.trial_space_dict[1] + A = assemble(eq.equation.lhs, eq.test_space_dict, eq.trial_space_dict) + T = eltype(A) + S = zeros(T, size(A)[1:2]) + ConvolutionOperators.timeslice!(S, A, 1) + iS = inv(S) + b = assemble(eq.equation.rhs, eq.test_space_dict) + nt = numfunctions(temporalbasis(V)) + marchonintime(iS, A, b, nt) +end """ - marchonintime(W0,Z,B,I) + marchonintime(W0,Z,B,I; convhist=false) Solve by marching-on-in-time the causal convolution problem defined by `(W0,Z,B)` up to timestep `I`. Here, `Z` is an array of order 3 that contains a discretisation of a time translation invariant retarded potential operator. `W0` is the inverse of the slice `Z[:,:,1]`. -""" -function marchonintime(W0,Z,B,I) - T = eltype(W0) - M,N = size(Z) - @assert M == size(B,1) - x = zeros(T,N,I) - for i in 1:I - b = B[:,i] - convolve(Z,x,i,2) - x[:,i] += W0 * b - (i % 10 == 0) && print(i, "[", I, "] - ") - end - return x -end -function marchonintime(iZ0, Z::ConvOp, B, Nt) - - T = eltype(iZ0) - Ns = size(Z,1) - x = zeros(T,Ns,Nt) - csx = zeros(T,Ns,Nt) - y = zeros(T,Ns) - - todo, done, pct = Nt, 0, 0 - for i in 1:Nt - fill!(y,0) - convolve!(y, Z, x, csx, i, 2, Nt) - y .*= -1 - y .+= B[:,i] - # @show norm(B[:,i]) - - x[:,i] .+= iZ0 * y - if i > 1 - csx[:,i] .= csx[:,i-1] .+ x[:,i] - else - csx[:,i] .= x[:,i] - end - - done += 1 - new_pct = round(Int, done / todo * 100) - new_pct > pct+9 && (println("[$new_pct]"); pct=new_pct) - end - x -end - -function convolve(Z::BlockArray, x, i, j_start) - # ax1 = axes(Z,1) - ax2 = axes(Z,2) - T = eltype(eltype(Z)) - y = PseudoBlockVector{T}(undef,blocklengths(axes(Z,1))) - fill!(y,0) - for I in blockaxes(Z,1) - for J in blockaxes(Z,2) - xJ = view(x, ax2[J], :) - try - ZIJ = Z[I,J].banded - y[I] .+= convolve(ZIJ, xJ, i, j_start) - catch - @info "Skipping unassigned block." - continue - end - end - end - return y -end - -function convolve!(y,Z::BlockArray, x, csx, i, j_start, j_stop) - ax1 = axes(Z,1) - ax2 = axes(Z,2) - T = eltype(eltype(Z)) - fill!(y,0) - for I in blockaxes(Z,1) - for J in blockaxes(Z,2) - xJ = view(x, ax2[J], :) - csxJ = view(csx, ax2[J], :) - yI = view(y, ax1[I]) - convolve!(yI, Z[I,J], xJ, csxJ, i, j_start, j_stop) - end - end - return y -end - -function marchonintime(W0,Z::BlockArray,B,I) +Keyword arguments: + - 'convhist': when true, return in addition to the space-time data for the + solution also the vector of convergence histories as returned each time step + by the supplied solver `W0`. +""" +function marchonintime(W0,Z,B,I; convhist=false) T = eltype(W0) M,N = size(W0) @@ -102,16 +39,18 @@ function marchonintime(W0,Z::BlockArray,B,I) y = zeros(T,N) csx = zeros(T,N,I) + ch = [] for i in 1:I - R = [ B[j][i] for j in 1:N ] - # @show norm(R) + R = B[:,i] k_start = 2 k_stop = I fill!(y,0) - convolve!(y,Z,x,csx,i,k_start,k_stop) + ConvolutionOperators.convolve!(y,Z,x,csx,i,k_start,k_stop) b = R - y - x[:,i] .+= W0 * b + xi, chi = BEAST.solve(W0, b) + x[:,i] .+= xi + push!(ch, chi) if i > 1 csx[:,i] .= csx[:,i-1] .+ x[:,i] else @@ -120,5 +59,10 @@ function marchonintime(W0,Z::BlockArray,B,I) (i % 10 == 0) && print(i, "[", I, "] - ") end - return x + + if convhist + return x, ch + else + return x + end end diff --git a/src/timedomain/rkcq.jl b/src/timedomain/rkcq.jl index cda0a1ea..6dcfd3ec 100644 --- a/src/timedomain/rkcq.jl +++ b/src/timedomain/rkcq.jl @@ -16,15 +16,11 @@ A, b: Coefficient matrix and vectors from the Butcher tableau. zTransformedTermCount: Number of terms in the inverse Z-transform. contourRadius: radius of circle used as integration contour for the inverse Z-transform. """ -struct RungeKuttaConvolutionQuadrature{LK, T, N, NN} - laplaceKernel :: LK # function of s that returns an IntegralOperator - A :: SArray{Tuple{N,N},T,2,NN} - b :: SVector{N,T} - Δt :: T - zTransformedTermCount :: Int - contourRadius :: T +struct RungeKuttaConvolutionQuadrature{} + timedomainKernel :: AbstractSpaceTimeOperator # function of s that returns an IntegralOperator end -scalartype(rkcq::RungeKuttaConvolutionQuadrature{LK, T, N, NN}) where {LK, T, N, NN} = Complex{T}; +#scalartype(rkcq::RungeKuttaConvolutionQuadrature{LK}) where {LK} = Complex + # M = H*diagm(D)*invH struct DiagonalizedMatrix{T,N,NN} @@ -35,7 +31,7 @@ end # M = H*diagm(D)*invH function diagonalizedmatrix(M :: SArray{Tuple{N,N},Complex{T},2,NN}) where {T,N,NN} - ef = eigfact(Array{Complex{T},2}(M)); + ef = eigen(Array{Complex{T},2}(M)); efValues = SVector{N,Complex{T}}(ef.values) :: SVector{N,Complex{T}}; efVectors = SArray{Tuple{N,N},Complex{T},2,NN}(ef.vectors) :: SArray{Tuple{N,N},Complex{T},2,NN}; @@ -43,59 +39,142 @@ function diagonalizedmatrix(M :: SArray{Tuple{N,N},Complex{T},2,NN}) where {T,N, end function assemble(rkcq :: RungeKuttaConvolutionQuadrature, - testfns :: StagedTimeStep, - trialfns :: StagedTimeStep) + testfns :: SpaceTimeBasis, + trialfns :: SpaceTimeBasis) + + @warn "staged assemble of the left-hand side" + sol = rkcq.timedomainKernel.speed_of_light + numdiffweak = rkcq.timedomainKernel.ws_diffs + numdiffhyper = rkcq.timedomainKernel.hs_diffs - laplaceKernel = rkcq.laplaceKernel; - A = rkcq.A; - b = rkcq.b; - Δt = rkcq.Δt; - Q = rkcq.zTransformedTermCount; - rho = rkcq.contourRadius; - p = length(b); # stage count + @info "converting time-domain kernel to Laplace-domain kernel" + LaplaceEFIO(s::T) where {T}= MWSingleLayer3D(s/sol, -s^(numdiffweak)/sol, s^(numdiffhyper)*T(sol)) + laplaceKernel = LaplaceEFIO + + A = testfns.time.A + b = testfns.time.b + Δt = testfns.time.Δt + Q = testfns.time.zTransformedTermCount + rho = testfns.time.contourRadius + p = length(b) # stage count - test_spatial_basis = testfns.spatialBasis; - trial_spatial_basis = trialfns.spatialBasis; + test_spatial_basis = testfns.space + trial_spatial_basis = trialfns.space # Compute the Z transformed sequence. # Assume that the operator applied on the conjugate of s is the same as the # conjugate of the operator applied on s, # so that only half of the values are computed - Qmax = Q>>1+1; - M = numfunctions(test_spatial_basis); - N = numfunctions(trial_spatial_basis); - Tz = promote_type(scalartype(rkcq), scalartype(testfns), scalartype(trialfns)); - Zz = Vector{Array{Tz,2}}(undef,Qmax); - blocksEigenvalues = Vector{Array{Tz,2}}(undef,p); - tmpDiag = Vector{Tz}(undef,p); + Qmax = Q>>1+1 + M = numfunctions(test_spatial_basis) + N = numfunctions(trial_spatial_basis) + #Tz = promote_type(scalartype(rkcq), scalartype(testfns), scalartype(trialfns)) + #Tz = promote_type(scalartype(testfns), scalartype(trialfns)) + Tz = ComplexF64 + Zz = Vector{Array{Tz,2}}(undef,Qmax) + blocksEigenvalues = Vector{Array{Tz,2}}(undef,p) + tmpDiag = Vector{Tz}(undef,p) for q = 0:Qmax-1 # Build a temporary matrix for each eigenvalue - s = laplace_to_z(rho, q, Q, Δt, A, b); - sFactorized = diagonalizedmatrix(s); + s = laplace_to_z(rho, q, Q, Δt, A, b) + sFactorized = diagonalizedmatrix(s) for (i,sD) in enumerate(sFactorized.D) - blocksEigenvalues[i] = assemble(laplaceKernel(sD), test_spatial_basis, trial_spatial_basis); + blocksEigenvalues[i] = assemble(laplaceKernel(sD), test_spatial_basis, trial_spatial_basis) end # Compute the Z transformed matrix by block - Zz[q+1] = zeros(Tz, M*p, N*p); + Zz[q+1] = zeros(Tz, M*p, N*p) for m = 1:M for n = 1:N for i = 1:p - tmpDiag[i] = blocksEigenvalues[i][m,n]; + tmpDiag[i] = blocksEigenvalues[i][m,n] end D = SVector{p,Tz}(tmpDiag); - Zz[q+1][(m-1)*p+(1:p),(n-1)*p+(1:p)] = sFactorized.H * diagm(D) * sFactorized.invH; + Zz[q+1][(m-1)*p.+(1:p),(n-1)*p.+(1:p)] = sFactorized.H * diagm(D) * sFactorized.invH end end end # return the inverse Z transform - kmax = Q; - T = real(Tz); + kmax = Q + T = real(Tz) Z = zeros(T, M*p, N*p, kmax) for q = 0:kmax-1 - Z[:,:,q+1] = real_inverse_z_transform(q, rho, Q, Zz); + Z[:,:,q+1] = real_inverse_z_transform(q, rho, Q, Zz) end - return Z + ZC = ConvolutionOperators.DenseConvOp(Z) + return ZC + +end + +struct LaplaceDomainOperator + coeff::ComplexF64 + kernel::Any end + +scalartype(op::LaplaceDomainOperator) = ComplexF64 + +*(a::Number, op::LaplaceDomainOperator) = LaplaceDomainOperator(a*op.coeff, op.kernel) + +LaplaceDomainOperator(kernel) = LaplaceDomainOperator(Complex(1.0), kernel) + +defaultquadstrat(op::LaplaceDomainOperator, tfs::SpaceTimeBasis, bfs::SpaceTimeBasis) = DoubleNumWiltonSauterQStrat(2,3,6,7,5,5,4,3) + +function assemble(op::LaplaceDomainOperator, testfns::SpaceTimeBasis, trialfns::SpaceTimeBasis; quadstrat=defaultquadstrat(op, testfns, trialfns)) + laplaceKernel = op.kernel + + A = testfns.time.A + b = testfns.time.b + Δt = testfns.time.Δt + Q = testfns.time.zTransformedTermCount + rho = testfns.time.contourRadius + p = length(b) # stage count + + test_spatial_basis = testfns.space + trial_spatial_basis = trialfns.space + + # Compute the Z transformed sequence. + # Assume that the operator applied on the conjugate of s is the same as the + # conjugate of the operator applied on s, + # so that only half of the values are computed + Qmax = Q>>1+1 + M = numfunctions(test_spatial_basis) + N = numfunctions(trial_spatial_basis) + Tz = ComplexF64 + Zz = Vector{Array{Tz,2}}(undef,Qmax) + blocksEigenvalues = Vector{Array{Tz,2}}(undef,p) + tmpDiag = Vector{Tz}(undef,p) + + for q = 0:Qmax-1 + # Build a temporary matrix for each eigenvalue + s = laplace_to_z(rho, q, Q, Δt, A, b) + sFactorized = diagonalizedmatrix(s) + for (i,sD) in enumerate(sFactorized.D) + blocksEigenvalues[i] = op.coeff * assemble(laplaceKernel(sD), test_spatial_basis, trial_spatial_basis, quadstrat=quadstrat) + end + + # Compute the Z transformed matrix by block + Zz[q+1] = zeros(Tz, M*p, N*p) + for m = 1:M + for n = 1:N + for i = 1:p + tmpDiag[i] = blocksEigenvalues[i][m,n] + end + D = SVector{p,Tz}(tmpDiag); + Zz[q+1][(m-1)*p.+(1:p),(n-1)*p.+(1:p)] = sFactorized.H * diagm(D) * sFactorized.invH + end + end + end + + # return the inverse Z transform + kmax = Q + T = real(Tz) + Z = zeros(T, M*p, N*p, kmax) + for q = 0:kmax-1 + Z[:,:,q+1] = real_inverse_z_transform(q, rho, Q, Zz) + end + ZC = ConvolutionOperators.DenseConvOp(Z) + + return ZC +end \ No newline at end of file diff --git a/src/timedomain/td_symmetric_quadstrat.jl b/src/timedomain/td_symmetric_quadstrat.jl new file mode 100644 index 00000000..9f4d7045 --- /dev/null +++ b/src/timedomain/td_symmetric_quadstrat.jl @@ -0,0 +1,58 @@ +struct SymmetricQuadStrat{S} + quadstrat::S +end + +struct SymmetricQuadRule{R1,R2} + quadrule1::R1 + quadrule2::R2 +end + +function quaddata(op, + test_local_space, trial_local_space, time_local_space, + test_charts, trial_charts, time_charts, + quadstrat::SymmetricQuadStrat) + + qd1 = quaddata(op, + test_local_space, trial_local_space, time_local_space, + test_charts, trial_charts, time_charts, + quadstrat.quadstrat) + qd2 = quaddata(op, + trial_local_space, test_local_space, time_local_space, + trial_charts, test_charts, time_charts, + quadstrat.quadstrat) + + return qd1, qd2 +end + +function quadrule(op, + test_local_space, trial_local_space, time_local_space, + p, test_chart, q, trial_chart, r, time_chart, + qd, quadstrat::SymmetricQuadStrat) + + qd1 = qd[1] + qd2 = qd[2] + + qr1 = quadrule(op, test_local_space, trial_local_space, time_local_space, + p, test_chart, q, trial_chart, r, time_chart, + qd1, quadstrat.quadstrat) + qr2 = quadrule(op, trial_local_space, test_local_space, time_local_space, + q, trial_chart, p, test_chart, r, time_chart, + qd2, quadstrat.quadstrat) + + return SymmetricQuadRule(qr1, qr2) +end + +function momintegrals!(z, op, U, V, W, τ, σ, ι, qr::SymmetricQuadRule) + + qr1 = qr.quadrule1 + qr2 = qr.quadrule2 + + z1 = zero(z) + z2 = zero(z) + + momintegrals!(z1, op, U, V, W, τ, σ, ι, qr1) + momintegrals!(z2, op, V, U, W, σ, τ, ι, qr2) + z2 = permutedims(z2, (2,1,3)) + z .+= (z1+z2)/2 + return nothing +end \ No newline at end of file diff --git a/src/timedomain/tdexcitation.jl b/src/timedomain/tdexcitation.jl index 3b5a5122..a0e23a02 100644 --- a/src/timedomain/tdexcitation.jl +++ b/src/timedomain/tdexcitation.jl @@ -1,70 +1,41 @@ abstract type TDFunctional{T} end Base.eltype(x::TDFunctional{T}) where {T} = T +defaultquadstrat(exc::TDFunctional, testfns) = NumSpaceNumTimeQStrat(2, 10) -function quaddata(exc::TDFunctional, testrefs, timerefs, testels, timeels) - - testqd = quadpoints(testrefs, testels, (2,)) - timeqd = quadpoints(timerefs, timeels, (10,)) - - testqd, timeqd - -end - -function quaddata(excitation::TDFunctional, - test_refspace, time_refspace::DiracBoundary, - test_elements, time_elements) - - test_quad_data = quadpoints(test_refspace, test_elements, (2,)) - - test_quad_data, nothing -end - -function quadrule(exc::TDFunctional, testrefs, timerefs, p, τ, r, ρ, qd) - - MultiQuadStrategy( - qd[1][1,p], - SingleQuadStrategy2( - qd[2][1,r] - ) - ) - -end - -function quadrule(exc::TDFunctional, testrefs, timerefs::DiracBoundary, p, τ, r, ρ, qd) - - MultiQuadStrategy( - qd[1][1,p], - nothing - ) - -end - - -function assemble(exc::TDFunctional, testST) +function assemble(exc::TDFunctional, testST; quadstrat=defaultquadstrat) + + stagedtimestep = isa(temporalbasis(testST), BEAST.StagedTimeStep) + if stagedtimestep + return staged_assemble(exc, testST; quadstrat) + end + testfns = spatialbasis(testST) timefns = temporalbasis(testST) Z = zeros(eltype(exc), numfunctions(testfns), numfunctions(timefns)) store(v,m,k) = (Z[m,k] += v) - assemble!(exc, testST, store) + assemble!(exc, testST, store; quadstrat) return Z end -function assemble(exc::TDFunctional, testST::StagedTimeStep) - stageCount = length(testST.c); - spatialBasis = testST.spatialBasis; - Nt = testST.Nt; - Δt = testST.Δt; - Z = zeros(eltype(exc), numfunctions(spatialBasis) * stageCount, Nt); +function staged_assemble(exc::TDFunctional, testST::SpaceTimeBasis; quadstrat=defaultquadstrat) + + @warn "staged assemble of the right-hand side" + testfns = spatialbasis(testST) + timefns = temporalbasis(testST) + stageCount = numstages(timefns) + Nt = timefns.Nt + Δt = timefns.Δt + Z = zeros(eltype(exc), numfunctions(testfns) * stageCount, Nt) for i = 1:stageCount - store(v,m,k) = (Z[(m-1)*stageCount+i,k] += v); - tbsd = TimeBasisDeltaShifted(timebasisdelta(Δt, Nt), testST.c[i]); - assemble!(exc, spatialBasis ⊗ tbsd, store); + store(v,m,k) = (Z[(m-1)*stageCount+i,k] += v) + tbsd = TimeBasisDeltaShifted(timebasisdelta(Δt, Nt), timefns.c[i]) + assemble!(exc, testfns ⊗ tbsd, store; quadstrat) end - return Z; + return Z end -function assemble!(exc::TDFunctional, testST, store) +function assemble!(exc::TDFunctional, testST, store; quadstrat=defaultquadstrat) testfns = spatialbasis(testST) timefns = temporalbasis(testST) @@ -75,19 +46,23 @@ function assemble!(exc::TDFunctional, testST, store) testels, testad = assemblydata(testfns) timeels, timead = assemblydata(timefns) - qd = quaddata(exc, testrefs, timerefs, testels, timeels) - - z = zeros(eltype(exc), numfunctions(testrefs), numfunctions(timerefs)) + @show quadstrat + @assert quadstrat != nothing + qs = quadstrat(exc, testST) + qd = quaddata(exc, testrefs, timerefs, testels, timeels, qs) + + num_testshapes = numfunctions(testrefs, domain(first(testels))) + z = zeros(eltype(exc), num_testshapes, numfunctions(timerefs)) for p in eachindex(testels) τ = testels[p] for r in eachindex(timeels) ρ = timeels[r] fill!(z, 0) - qr = quadrule(exc, testrefs, timerefs, p, τ, r, ρ, qd) + qr = quadrule(exc, testrefs, timerefs, p, τ, r, ρ, qd, qs) momintegrals!(z, exc, testrefs, timerefs, τ, ρ, qr) - for i in 1 : numfunctions(testrefs) + for i in 1 : num_testshapes for d in 1 : numfunctions(timerefs) v = z[i,d] @@ -102,76 +77,3 @@ function assemble!(exc::TDFunctional, testST, store) end end end - - -abstract type NumQuadStrategy end - -mutable struct MultiQuadStrategy{P,R} <: NumQuadStrategy - quad_points::P - inner_rule::R -end - -# TODO: consolidate with the existing definition of SingleQuadStrategy -mutable struct SingleQuadStrategy2{P} <: NumQuadStrategy - quad_points::P -end - -timequadrule(qr::MultiQuadStrategy, p) = qr.inner_rule - -function momintegrals!(z, exc::TDFunctional, testrefs, timerefs, τ, ρ, qr) - - for p in qr.quad_points - x = p.point - w = p.weight - f = p.value - dx = w - - # try - # @assert ρ.vertices[1][1] <= cartesian(x)[1] <= ρ.vertices[2][1] - # catch - # @show ρ.vertices[1][1] - # @show cartesian(x)[1] - # @show ρ.vertices[2][1] - # error("") - # end - - tqr = timequadrule(qr,p) - timeintegrals!(z, exc, testrefs, timerefs, x, ρ, dx, tqr, f) - - end - -end - - -function timeintegrals!(z, exc::TDFunctional, testrefs, timerefs, testpoint, timeelement, dx, qr, f) - - for p in qr.quad_points - t = p.point - w = p.weight - U = p.value - dt = w #* jacobian(t) # * volume(timeelement) - - for i in 1 : numfunctions(testrefs) - for k in 1 : numfunctions(timerefs) - z[i,k] += dot(f[i][1]*U[k], exc(testpoint,t)) * dt * dx - end - end - end -end - - - -function timeintegrals!(z, exc::TDFunctional, - spacerefs, timerefs::DiracBoundary, - testpoint, timeelement, - dx, qr, testvals) - - # since timeelement uses barycentric coordinates, - # the first/left vertex has coords u = 1.0! - testtime = neighborhood(timeelement, point(0.0)) - @assert cartesian(testtime)[1] ≈ timeelement.vertices[2][1] - - for i in 1 : numfunctions(spacerefs) - z[i,1] += dot(testvals[i][1], exc(testpoint, testtime)) * dx - end -end diff --git a/src/timedomain/tdintegralop.jl b/src/timedomain/tdintegralop.jl index eaef6d95..878aeb81 100644 --- a/src/timedomain/tdintegralop.jl +++ b/src/timedomain/tdintegralop.jl @@ -1,13 +1,35 @@ using WiltonInts84 -abstract type RetardedPotential{T} <: Operator end -Base.eltype(::RetardedPotential{T}) where {T} = T +abstract type AbstractSpaceTimeOperator end +abstract type SpaceTimeOperator <: AbstractSpaceTimeOperator end # atomic operator + +#TODO RKCQ multithreading + +function assemble(operator::AbstractSpaceTimeOperator, test_functions, trial_functions; + storage_policy = Val{:bandedstorage}, + long_delays_policy = LongDelays{:compress}, + threading = Threading{:multi}, + quadstrat=defaultquadstrat(operator, test_functions, trial_functions)) + stagedtimestep = isa(test_functions.time, StagedTimeStep) + if stagedtimestep + return assemble(RungeKuttaConvolutionQuadrature(operator), test_functions, trial_functions) + end + freeze, store = allocatestorage(operator, test_functions, trial_functions, + storage_policy, long_delays_policy) + assemble!(operator, test_functions, trial_functions, store, threading; quadstrat) + return freeze() +end + + +abstract type RetardedPotential{T} <: SpaceTimeOperator end +# Base.eltype(::RetardedPotential{T}) where {T} = T scalartype(A::RetardedPotential{T}) where {T} = T mutable struct EmptyRP{T} <: RetardedPotential{T} speed_of_light::T end Base.eltype(::EmptyRP) = Int +defaultquadstrat(::EmptyRP, tfs, bfs) = NothingQStrategy() quaddata(op::EmptyRP, xs...) = nothing quadrule(op::EmptyRP, xs...) = nothing momintegrals!(z, op::EmptyRP, xs...) = nothing @@ -31,14 +53,16 @@ function allocatestorage(op::RetardedPotential, testST, basisST, end aux = EmptyRP(op.speed_of_light) - print("Allocating memory for convolution operator: ") + #print("Allocating memory for convolution operator: ") assemble!(aux, testST, basisST, store) - println("\nAllocated memory for convolution operator.") + #println("\nAllocated memory for convolution operator.") kmax = maximum(K1); - Z = zeros(eltype(op), M, N, kmax) + T = scalartype(op, testST, basisST) + Z = zeros(T, M, N, kmax) store1(v,m,n,k) = (Z[m,n,k] += v) - return ()->MatrixConvolution(Z), store1 + # return ()->MatrixConvolution(Z), store1 + return ()->ConvolutionOperators.DenseConvOp(Z), store1 end @@ -79,9 +103,9 @@ function allocatestorage(op::RetardedPotential, testST, basisST, ::Type{Val{:bandedstorage}}, ::Type{LongDelays{:compress}}) - @info "Allocating mem for RP op compressing the static tail..." + #@info "Allocating mem for RP op compressing the static tail..." - T = eltype(op) + T = scalartype(op, testST, basisST) tfs = spatialbasis(testST) bfs = spatialbasis(basisST) @@ -106,45 +130,89 @@ function allocatestorage(op::RetardedPotential, testST, basisST, op_alloc = EmptyRP(op.speed_of_light) tbf_trunc = truncatetail(tbf) δ = timebasisdelta(Δt, Nt) - print("Allocating memory for convolution operator: ") + #print("Allocating memory for convolution operator: ") assemble!(op_alloc, tfs⊗δ, bfs⊗tbf_trunc, store_alloc) - println("\nAllocated memory for convolution operator.") + #println("\nAllocated memory for convolution operator.") bandwidth = maximum(K1 .- K0 .+ 1) data = zeros(T, bandwidth, M, N) tail = zeros(T, M, N) # kmax = maximum(K1) len = has_tail ? Nt : maximum(K1) - Z = ConvOp(data, K0, K1, tail, len) + Z = ConvolutionOperators.ConvOp(data, K0, K1, tail, len) function store1(v,m,n,k) - if Z.k0[m,n] ≤ k ≤ Z.k1[m,n] - Z.data[k - Z.k0[m,n] + 1,m,n] += v - elseif k == Z.k1[m,n]+1 - Z.tail[m,n] += v - end + k0 = Z.k0[m,n] + k < k0 && return + k1 = Z.k1[m,n] + k > k1 + 1 && return + k > k1 && (Z.tail[m,n] += v; return) + Z.data[k - k0 + 1, m,n] += v + # if Z.k0[m,n] ≤ k ≤ Z.k1[m,n] + # Z.data[k - Z.k0[m,n] + 1,m,n] += v + # elseif k == Z.k1[m,n]+1 + # Z.tail[m,n] += v + # end end return ()->Z, store1 end function assemble!(op::LinearCombinationOfOperators, tfs::SpaceTimeBasis, bfs::SpaceTimeBasis, store, - threading=Threading{:multi}) + threading=Threading{:multi}; quadstrat=defaultquadstrat(op, tfs, bfs)) - for (a,A) in zip(op.coeffs, op.ops) + for (a,A,qs) in zip(op.coeffs, op.ops, quadstrat) store1(v,m,n,k) = store(a*v,m,n,k) - assemble!(A, tfs, bfs, store1) + assemble!(A, tfs, bfs, store1, threading; quadstrat=qs) end end -function assemble!(op::RetardedPotential, testST, trialST, store, - threading=Threading{:multi}) + +function assemble!(op::RetardedPotential, testST::Space, trialST::Space, store, + threading::Type{Threading{:single}}; quadstrat=defaultquadstrat(op, testST, trialST)) + + assemble_chunk!(op, testST, trialST, store; quadstrat=quadstrat) +end + +function assemble!(op::RetardedPotential, testST::Space, trialST::Space, store, + threading::Type{Threading{:multi}}=Threading{:multi}; quadstrat=defaultquadstrat(op, testST, trialST)) + + quadstrat = quadstrat(op, testST, trialST) + @show quadstrat Y, S = spatialbasis(testST), temporalbasis(testST) + X, R = spatialbasis(trialST), temporalbasis(trialST) - P = Threads.nthreads() - @assert P >= 1 - splits = [round(Int,s) for s in range(0, stop=numfunctions(Y), length=P+1)] + T = Threads.nthreads() + M = length(spatialbasis(testST)) + N = length(spatialbasis(trialST)) + + P = max(1, floor(Int, sqrt(M*T/N))) + Q = max(1, floor(Int, sqrt(N*T/M))) + + rowsplits = [round(Int,s) for s in range(0, stop=M, length=P+1)] + colsplits = [round(Int,s) for s in range(0, stop=N, length=Q+1)] + + idcs = CartesianIndices((1:P, 1:Q)) + Threads.@threads for idx in idcs + i = idx[1] + j = idx[2] + + rlo, rhi = rowsplits[i]+1, rowsplits[i+1] + rlo <= rhi || continue + clo, chi = colsplits[j]+1, colsplits[j+1] + clo <= chi || continue + + Y_p = subset(Y, rlo:rhi) + X_q = subset(X, clo:chi) + + store1 = (v,m,n,k) -> store(v,rlo+m-1,clo+n-1,k) + assemble_chunk!(op, Y_p ⊗ S, X_q ⊗ R, store1; quadstrat) + end + #println("") + + # P = Threads.nthreads() + # splits = [round(Int,s) for s in range(0, stop=numfunctions(Y), length=P+1)] # @info "Starting assembly with $P threads:" # Threads.@threads for i in 1:P @@ -155,10 +223,11 @@ function assemble!(op::RetardedPotential, testST, trialST, store, # assemble_chunk!(op, Y_p ⊗ S, trialST, store1) # end - assemble_chunk!(op, testST, trialST, store) + # assemble_chunk!(op, testST, trialST, store) end -function assemble_chunk!(op::RetardedPotential, testST, trialST, store) +function assemble_chunk!(op::RetardedPotential, testST, trialST, store; + quadstrat=defaultquadstrat(op, testST, trialST)) myid = Threads.threadid() @@ -188,17 +257,21 @@ function assemble_chunk!(op::RetardedPotential, testST, trialST, store) V = refspace(trialspace) W = refspace(timebasisfunction) - qd = quaddata(op, U, V, W, testels, trialels, nothing) + # qs = quadstrat(op, testST, trialST) + qd = quaddata(op, U, V, W, testels, trialels, nothing, quadstrat) + + ugeo = geometry(testspace) + vgeo = geometry(trialspace) - udim = numfunctions(U) - vdim = numfunctions(V) + udim = numfunctions(U, domain(chart(ugeo, first(ugeo)))) + vdim = numfunctions(V, domain(chart(vgeo, first(vgeo)))) wdim = numfunctions(W) - z = zeros(eltype(op), udim, vdim, wdim) + z = zeros(scalartype(op, testST, trialST), udim, vdim, wdim) - @show length(testels) length(trialels) + # @show length(testels) length(trialels) - myid == 1 && print("dots out of 10: ") - todo, done, pctg = length(testels), 0, 0 + #myid == 1 && print("dots out of 10: ") + #todo, done, pctg = length(testels), 0, 0 for p in eachindex(testels) τ = testels[p] for q in eachindex(trialels) @@ -209,13 +282,13 @@ function assemble_chunk!(op::RetardedPotential, testST, trialST, store) # compute interactions between reference shape functions fill!(z, 0) - qr = quadrule(op, U, V, W, p, τ, q, σ, r, ι, qd) + qr = quadrule(op, U, V, W, p, τ, q, σ, r, ι, qd, quadstrat) momintegrals!(z, op, U, V, W, τ, σ, ι, qr) # assemble in the global matrix - for i in 1 : udim - for j in 1 : vdim - for d in 1 : wdim + for d in 1 : wdim + for j in 1 : vdim + for i in 1 : udim v = z[i,j,d] @@ -229,21 +302,22 @@ function assemble_chunk!(op::RetardedPotential, testST, trialST, store) end # next κ end # next ν end # next μ - end # next d - end # next j - end #next i + end + end + end end # next r end # next q - done += 1 - new_pctg = round(Int, done / todo * 100) - if myid == 1 && new_pctg > pctg + 9 - print(".") - pctg = new_pctg - end + #done += 1 + + #new_pctg = round(Int, done / todo * 100) + #if myid == 1 && new_pctg > pctg + 9 + # print(".") + # pctg = new_pctg + #end end # next p - println("") + # println("") end diff --git a/src/timedomain/tdtimeops.jl b/src/timedomain/tdtimeops.jl index 2de1cb7b..cdb3e99e 100644 --- a/src/timedomain/tdtimeops.jl +++ b/src/timedomain/tdtimeops.jl @@ -32,11 +32,13 @@ function assemble(op::Identity, return z end -mutable struct TensorOperator <: Operator +mutable struct TensorOperator <: SpaceTimeOperator spatial_factor temporal_factor end +BEAST.defaultquadstrat(::TensorOperator, tfs, bfs) = nothing + ⊗(A::AbstractOperator, B::AbstractOperator) = TensorOperator(A, B) function scalartype(A::TensorOperator) promote_type( @@ -44,32 +46,13 @@ function scalartype(A::TensorOperator) scalartype(A.temporal_factor)) end - - -# function allocatestorage(op::TensorOperator, test_functions, trial_functions, -# ::Type{Val{:bandedstorage}}, ::Type{LongDelays{:ignore}},) - -# M = numfunctions(spatialbasis(test_functions)) -# N = numfunctions(spatialbasis(trial_functions)) - -# time_basis_function = BEAST.convolve( -# temporalbasis(test_functions), -# temporalbasis(trial_functions)) - -# space_operator = op.spatial_factor -# A = assemble(space_operator, spatialbasis(test_functions), spatialbasis(trial_functions)) - -# K0 = ones(M,N) -# bandwidth = numintervals(time_basis_function) - 1 -# data = zeros(scalartype(op), bandwidth, M, N) -# maxk1 = bandwidth -# Z = SparseND.Banded3D(K0, data, maxk1) -# return ()->Z, (v,m,n,k)->(Z[m,n,k] += v) -# end +function Base.:*(alpha::Number, A::TensorOperator) + return TensorOperator(alpha*A.spatial_factor, A.temporal_factor) +end function allocatestorage(op::TensorOperator, test_functions, trial_functions, - ::Type{Val{:bandedstorage}}, ::Type{LongDelays{:compress}},) + ::Type{Val{:bandedstorage}}, long_delay_traits::Any) M = numfunctions(spatialbasis(test_functions)) N = numfunctions(spatialbasis(trial_functions)) @@ -84,12 +67,12 @@ function allocatestorage(op::TensorOperator, test_functions, trial_functions, K0 = ones(Int, M, N) bandwidth = numintervals(time_basis_function) - 1 K1 = ones(Int,M,N) .+ (bandwidth - 1) - T = scalartype(op) + T = scalartype(op, test_functions, trial_functions) data = zeros(T, bandwidth, M, N) tail = zeros(T, M, N) Nt = numfunctions(temporalbasis(trial_functions)) - Z = ConvOp(data, K0, K1, tail, Nt) + Z = ConvolutionOperators.ConvOp(data, K0, K1, tail, bandwidth) function store1(v,m,n,k) if Z.k0[m,n] ≤ k ≤ Z.k1[m,n] Z.data[k - Z.k0[m,n] + 1,m,n] += v @@ -97,40 +80,14 @@ function allocatestorage(op::TensorOperator, test_functions, trial_functions, Z.tail[m,n] += v end end - return Z, store1 + return ()->Z, store1 end -# function allocatestorage(op::TensorOperator, test_functions, trial_functions) - -# M = numfunctions(spatialbasis(test_functions)) -# N = numfunctions(spatialbasis(trial_functions)) - -# time_basis_function = BEAST.convolve( -# temporalbasis(test_functions), -# temporalbasis(trial_functions)) - -# tbf = time_basis_function -# has_zero_tail = all(tbf.polys[end].data .== 0) -# @show has_zero_tail -# if has_zero_tail -# K = numintervals(time_basis_function)-1 -# else -# speedoflight = 1.0 -# @warn "Assuming speed of light to be equal to 1!" -# Δt = timestep(tbf) -# ct, hs = boundingbox(geometry(spatialbasis(trial_functions)).vertices) -# diam = 2 * sqrt(3) * hs -# K = ceil(Int, (numintervals(tbf)-1) + diam/speedoflight/Δt)+1 -# end -# @assert K > 0 -# Z = zeros(M, N, K) -# return MatrixConvolution(Z), (v,m,n,k)->(Z[m,n,k] += v) -# end - -function assemble!(operator::TensorOperator, testfns, trialfns, store, - threading = Threading{:multi}) +function assemble!(operator::TensorOperator, testfns::SpaceTimeBasis, trialfns::SpaceTimeBasis, + store, threading::Type{Threading{:multi}}; + quadstrat=defaultquadstrat(operator, testfns, trialfns)) space_operator = operator.spatial_factor time_operator = operator.temporal_factor @@ -162,7 +119,8 @@ function assemble!(operator::TensorOperator, testfns, trialfns, store, store(w*v,m,n,k) end end - assemble!(space_operator, space_testfns, space_trialfns, store1) + assemble!(space_operator, space_testfns, space_trialfns, + store1, threading) end @@ -175,6 +133,8 @@ derive(op::AbstractOperator) = TemporalDifferentiation(op) scalartype(op::TemporalDifferentiation) = scalartype(op.operator) Base.:*(a::Number, op::TemporalDifferentiation) = TemporalDifferentiation(a * op.operator) +defaultquadstrat(op::TemporalDifferentiation, tfs, bfs) = defaultquadstrat(op.operator, tfs, bfs) + function allocatestorage(op::TemporalDifferentiation, testfns, trialfns, storage_trait, longdelays_trait) @@ -189,7 +149,8 @@ function allocatestorage(op::TemporalDifferentiation, testfns, trialfns, return allocatestorage(op.operator, testfns, trialfns, storage_trait, longdelays_trait) end -function assemble!(operator::TemporalDifferentiation, testfns, trialfns, store, threading = Threading{:multi}) +function assemble!(operator::TemporalDifferentiation, testfns, trialfns, store, threading = Threading{:multi}; + quadstrat=defaultquadstrat(operator, testfns, trialfns)) trial_time_fns = temporalbasis(trialfns) trial_space_fns = spatialbasis(trialfns) @@ -199,15 +160,17 @@ function assemble!(operator::TemporalDifferentiation, testfns, trialfns, store, derive(trial_time_fns) ) - assemble!(operator.operator, testfns, trialfns, store, threading) + assemble!(operator.operator, testfns, trialfns, store, threading; quadstrat) end -struct TemporalIntegration <: AbstractOperator - operator::AbstractOperator +struct TemporalIntegration <: AbstractSpaceTimeOperator + operator::AbstractSpaceTimeOperator end -integrate(op::AbstractOperator) = TemporalIntegration(op) +defaultquadstrat(op::TemporalIntegration, tfs, bfs) = defaultquadstrat(op.operator, tfs, bfs) + +integrate(op::SpaceTimeOperator) = TemporalIntegration(op) derive(op::TemporalIntegration) = op.operator scalartype(op::TemporalIntegration) = scalartype(op.operator) Base.:*(a::Number, op::TemporalIntegration) = TemporalIntegration(a * op.operator) @@ -227,7 +190,7 @@ function allocatestorage(op::TemporalIntegration, testfns, trialfns, end function assemble!(operator::TemporalIntegration, testfns, trialfns, store, - threading = Threading{:multi}) + threading = Threading{:multi}; quadstrat=defaultquadstrat(operator, testfns, trialfns)) trial_time_fns = temporalbasis(trialfns) trial_space_fns = spatialbasis(trialfns) @@ -237,6 +200,6 @@ function assemble!(operator::TemporalIntegration, testfns, trialfns, store, integrate(trial_time_fns) ) - assemble!(operator.operator, testfns, trialfns, store) + assemble!(operator.operator, testfns, trialfns, store; quadstrat) end diff --git a/src/timedomain/zdomain.jl b/src/timedomain/zdomain.jl index e0ea789c..a44095c1 100644 --- a/src/timedomain/zdomain.jl +++ b/src/timedomain/zdomain.jl @@ -7,9 +7,9 @@ Returns the complex matrix valued Laplace variable s that correspond to the variable z = rho*exp(2*im*pi*n/N) for a given Butcher tableau (A,b,c) and a time step dt. """ function laplace_to_z(rho, n, N, dt, A, b) - z = rho * exp(2*im*pi*n/N); - s = inv(dt * (A + ones(b) * b' / (z-1))); - return s; + z = rho * exp(2*im*pi*n/N) + s = inv(dt * (A + ones(size(b)) * b' / (z-1))) + return s end """ @@ -19,7 +19,7 @@ Returns the k-th term of the inverse z-transform. X is an array of the z-transfo evaluated in the points z=rho*exp(2*im*pi*n/N) for n in 0:(N-1). """ function inverse_z_transform(k, rho, N, X::AbstractArray{T,1}) where T - return ((rho^k) / N) * sum(n -> X[n+1] * exp(2*im*pi*k*n/N), 0:(N-1)); + return ((rho^k) / N) * sum(n -> X[n+1] * exp(2*im*pi*k*n/N), 0:(N-1)) end """ @@ -32,7 +32,7 @@ X is an array of the z-transform evaluated in the points z=rho*exp(2*im*pi*n/N) for n in 0:(Nmax-1). """ function real_inverse_z_transform(k, rho, N, X::AbstractArray{T,1}) where T - Nmax = (N+1)>>1; - realTerms = (N%2==0) ? real(X[1]) + (-1)^k * real(X[Nmax+1]) : real(X[1]); - return ((rho^k) / N) * (realTerms + 2*sum(n -> real(X[n+1] * exp(2*im*pi*k*n/N)), 1:(Nmax-1))); + Nmax = (N+1)>>1 + realTerms = (N%2==0) ? real(X[1]) + (-1)^k * real(X[Nmax+1]) : real(X[1]) + return ((rho^k) / N) * (realTerms + 2*sum(n -> real(X[n+1] * exp(2*im*pi*k*n/N)), 1:(Nmax-1))) end diff --git a/src/utils/debug_tools.jl b/src/utils/debug_tools.jl deleted file mode 100644 index 87851767..00000000 --- a/src/utils/debug_tools.jl +++ /dev/null @@ -1,15 +0,0 @@ -function show_which_quadrule(op,X,Y) - - test_charts, test_ad = assemblydata(X) - trial_charts, trial_ad = assemblydata(Y) - - x = refspace(X) - y = refspace(Y) - - @show @which quaddata(op, x, y, test_charts, trial_charts) - qd = quaddata(op, x, y, test_charts, trial_charts) - @show @which quadrule(op, x, y, 1, first(test_charts), 1, first(trial_charts), qd) - - - nothing -end diff --git a/src/utils/lagpolys.jl b/src/utils/lagpolys.jl new file mode 100644 index 00000000..60620706 --- /dev/null +++ b/src/utils/lagpolys.jl @@ -0,0 +1,47 @@ +function _lagpoly(nodes, i, s, i0=1, i1=length(nodes)) + T = eltype(s) + r = one(T) + si = nodes[i] + for j in i0:i1 + j == i && continue + sj = nodes[j] + r *= (s - sj) / (si - sj) + end + return r +end + +function _lagpoly_diff(nodes, i, s, i0=1, i1=length(nodes)) + T = typeof(s) + r = zero(T) + si = nodes[i] + for p in i0:i1 + p == i && continue + sp = nodes[p] + rp = one(T) + for j in i0:i1 + j == i && continue + j == p && continue + sj = nodes[j] + rp *= (s - sj) / (si - sj) + end + rp *= 1 / (si - sp) + r += rp + end + return r +end + +function _sylpoly(nodes, i, s) + _lagpoly(nodes, i, s, 1, i) +end + +function _sylpoly_diff(nodes, i, s) + _lagpoly_diff(nodes, i, s, 1, i) +end + +function _sylpoly_shift(nodes, i, s) + _lagpoly(nodes, i, s, 2, i) +end + +function _sylpoly_shift_diff(nodes, i, s) + _lagpoly_diff(nodes, i, s, 2, i) +end \ No newline at end of file diff --git a/src/utils/matrixconv.jl b/src/utils/matrixconv.jl deleted file mode 100644 index 60648943..00000000 --- a/src/utils/matrixconv.jl +++ /dev/null @@ -1,129 +0,0 @@ -struct MatrixConvolution{T} <: AbstractArray{T,3} - arr::Array{T,3} -end - -function Base.:+(x::MatrixConvolution, y::MatrixConvolution) - - A = x.arr - B = y.arr - - T = promote_type(eltype(A), eltype(B)) - @assert axes(A)[1:2] == axes(B)[1:2] - - kmax = max(size(A,3), size(B,3)) - C = zeros(T,size(A)[1:2]...,kmax) - - for k in axes(A,3) - C[:,:,k] += A[:,:,k] - end - - for k in axes(B,3) - C[:,:,k] += B[:,:,k] - end - - return MatrixConvolution(C) -end - - -Base.size(x::MatrixConvolution) = size(x.arr) -Base.getindex(x::MatrixConvolution, i::Int) = x.arr[i] -Base.IndexStyle(x::MatrixConvolution) = IndexLinear() - - -function Base.:*(x::MatrixConvolution, y::Matrix) - - A = x.arr - - T = promote_type(eltype(A), eltype(y)) - @assert axes(A,2) == axes(y,1) - - C = zeros(T, size(A)...) - for k in axes(C,3) - C[:,:,k] = A[:,:,k]*y - end - - return MatrixConvolution(C) -end - -function Base.:*(y::Matrix, x::MatrixConvolution) - - A = x.arr - - T = promote_type(eltype(A), eltype(y)) - @assert axes(A,2) == axes(y,1) - - C = zeros(T, size(A)...) - for k in axes(C,3) - C[:,:,k] = y*A[:,:,k] - end - - return MatrixConvolution(C) -end - - -Base.:*(a::Number, x::MatrixConvolution) = MatrixConvolution(a * x.arr) -Base.:*(x::MatrixConvolution, a::Number) = MatrixConvolution(x.arr * a) -Base.:/(x::MatrixConvolution, a::Number) = MatrixConvolution(x.arr / a) -Base.:-(x::MatrixConvolution) = (-1) * x - -function convolve(Z::Array,x,j,k0) - M,N,K = size(Z) - y = similar(Z,M) - fill!(y,0) - for k ∈ k0 : min(j,K) - i = j - k + 1 - y += Z[:,:,k] * x[:,i] - end - return y -end - -convolve(x::MatrixConvolution, y::Matrix, i, j) = convolve(x.arr, y, i, j) - - -function Base.hvcat((M,N)::Tuple{Int,Int}, as::MatrixConvolution...) - kmax = maximum(size(a,3) for a in as) - - @assert length(as) == M*N - - li = LinearIndices((1:N,1:M)) - for m in 1:M - a = as[li[1,m]] - M1 = size(a,1) - for n in 2:N - a = as[li[n,m]] - @assert size(a,1) == M1 - end - end - - for n in 1:N - a = as[li[n,1]] - N1 = size(a,2) - for m in 2:M - a = as[li[n,m]] - @assert size(a,2) == N1 - end - end - - Ms = [size(as[li[1,i]],1) for i in 1:M] - Ns = [size(as[li[j,1]],2) for j in 1:N] - - cMs = pushfirst!(cumsum(Ms),0) - cNs = pushfirst!(cumsum(Ns),0) - T = promote_type(eltype.(as)...) - data = zeros(T, last(cMs), last(cNs), kmax) - - @show size(data) - @show eltype(data) - - for m in 1:M - I = cMs[m]+1 : cMs[m+1] - for n in 1:N - J = cNs[n]+1 : cNs[n+1] - a = as[li[n,m]] - K = 1:size(a,3) - data[I,J,K] .= a - end - end - - return MatrixConvolution(data) -end \ No newline at end of file diff --git a/src/utils/mixedblockarrays.jl b/src/utils/mixedblockarrays.jl deleted file mode 100644 index cccdcea8..00000000 --- a/src/utils/mixedblockarrays.jl +++ /dev/null @@ -1,39 +0,0 @@ -struct ZeroBlockInitializer end -const zero_blocks = ZeroBlockInitializer() - -import Base.Cartesian: @nloops, @ntuple -import BlockArrays: BlockArray, undef_blocks, blockaxes -import FillArrays: Zeros, Fill - -""" -Initialise a BlockArray where each block can have a different type. - - BlockArray{T}(zero_blocks, blocksize...) -""" -@generated function BlockArray{T}(::ZeroBlockInitializer, blocksizes::Vararg{AbstractVector{Int},N}) where {T,N} - return quote - B = BlockArray{T,N,Array{AbstractArray{T,N},N}}(undef_blocks, blocksizes...) - axs = axes(B) - @nloops $N block dim->blockaxes(B,dim) begin - block_indices = @ntuple $N block - indices = getindex.(axs, block_indices) - block_size = length.(indices) - B[block_indices...] = Zeros{T}(block_size...) - end - return B - end -end - -@generated function BlockArray{T}(::ZeroBlockInitializer, blocksizes::Vararg{AbstractVector{Int},N}) where {T <: Array,N} - return quote - B = BlockArray{T,N,Array{AbstractArray{T,N},N}}(undef_blocks, blocksizes...) - axs = axes(B) - @nloops $N block dim->blockaxes(B,dim) begin - block_indices = @ntuple $N block - indices = getindex.(axs, block_indices) - block_size = length.(indices) - B[block_indices...] = Fill{T}(T(), block_size...) - end - return B - end -end diff --git a/src/utils/plotlyglue.jl b/src/utils/plotlyglue.jl new file mode 100644 index 00000000..61aa84eb --- /dev/null +++ b/src/utils/plotlyglue.jl @@ -0,0 +1,18 @@ +using Requires + +export functionvals + +function __init__() + @require PlotlyJS="f0f68f2c-4968-5e81-91da-67840de0976a" begin + @eval function PlotlyJS.cone(xyz::Vector, uvw::Vector;kwargs...) + PlotlyJS.cone(; + x=getindex.(xyz,1), + y=getindex.(xyz,2), + z=getindex.(xyz,3), + u=getindex.(uvw,1), + v=getindex.(uvw,2), + w=getindex.(uvw,3), + kwargs...) + end + end +end \ No newline at end of file diff --git a/src/utils/polyeig.jl b/src/utils/polyeig.jl deleted file mode 100644 index 5de11de4..00000000 --- a/src/utils/polyeig.jl +++ /dev/null @@ -1,27 +0,0 @@ -export companion - -function companion(Z) - - T = eltype(Z) - K = size(Z,3) - @assert K > 1 - - M, N = size(Z)[1:2] - C = similar(Z, M*(K-1), N*(K-1)) - fill!(C,0) - - @assert M == N - Id = Matrix{T}(I, M, N) - for m in 2:K-1 - n = m-1 - C[(m-1)*M+1:m*M, (n-1)*N+1:n*N] = Id - end - - W = -inv(Z[:,:,1]) - for n in 1:K-1 - m = 1 - C[(m-1)*M+1:m*M, (n-1)*N+1:n*N] = W*Z[:,:,n+1] - end - - return C -end diff --git a/src/utils/polynomial.jl b/src/utils/polynomial.jl index 88e6d345..5a13cf00 100644 --- a/src/utils/polynomial.jl +++ b/src/utils/polynomial.jl @@ -111,13 +111,13 @@ function substitute(p::Polynomial, q::Polynomial) return r end -mutable struct PieceWisePolynomial -end +# mutable struct PieceWisePolynomial +# end -function Bernstein(n,u) - basis = zeros(n+1,1) - for i = 0:n - basis[i+1] = binomial(n,i) * u^i * (1.0-u)^(n-i) - end - basis -end +# function Bernstein(n,u) +# basis = zeros(n+1,1) +# for i = 0:n +# basis[i+1] = binomial(n,i) * u^i * (1.0-u)^(n-i) +# end +# basis +# end diff --git a/src/utils/rank1map.jl b/src/utils/rank1map.jl new file mode 100644 index 00000000..5a366a96 --- /dev/null +++ b/src/utils/rank1map.jl @@ -0,0 +1,23 @@ +import LinearMaps + +struct Rank1Map{T,U,V} <: LinearMap{T} + u::U + v::V +end + +Rank1Map{T}(u::U, v::V) where {T,U,V} = Rank1Map{T,U,V}(u,v) +LinearMaps.MulStyle(A::Rank1Map) = LinearMaps.FiveArg() + +Base.size(A::Rank1Map) = (length(A.u), length(A.v),) +Base.axes(A::Rank1Map) = (axes(A.u)..., axes(A.v)...) + +function LinearMaps._unsafe_mul!(y::AbstractVector, L::Rank1Map, x::AbstractVector, α::Number=true, β::Number=false) + y .*= β + y .+= α .* L.u .* dot(L.v, x) +end + +function LinearMaps._unsafe_mul!(Y::AbstractMatrix, L::Rank1Map, c::Number, α::Number=true, β::Number=false) + rmul!(Y, β) + Y .+= (L.u * L.v') .* c .* α + return Y +end diff --git a/src/utils/spacetimedyad.jl b/src/utils/spacetimedyad.jl deleted file mode 100644 index a692627e..00000000 --- a/src/utils/spacetimedyad.jl +++ /dev/null @@ -1,29 +0,0 @@ -""" -Array type of rank 3 (taking three scalar indices) that is used for the storage -of discrete convolution operators that happen to be the tensor product of a spatial -part and a temporal part. - -Using a dedicated storage type for these operators saves both on memory and computation -time for convolutions with space-time data. -""" -mutable struct SpaceTimeDyad{A,B,T} <: AbstractArray{3,T} - spatial_factor::A - temporal_factor::B -end - -# Implementation of a minimalistic array interface -# Note that setindex! is not supported. There is no way to enforce maintaining -# the dyadic structure during element modification. -import Base: size, getindex, linearindexing -size(X::SpaceTimeDyad) = (size(X.spatial_factor)..., length(X.temporal_factor)) -linearindexing(::Type{T}) where {T<:SpaceTimeDyad} = Base.LinearSlow() -function getindex(X::SpaceTimeDyad, m::Int, n::Int, k::Int) - T = promote_type(eltype(X.spatial_factor), eltype(X.temporal_factor)) - k > length(X.spacetial_factor) && return zero(T) - return X.spatial_factor[m,n] * X.temporal_factor[k] -end - - -function convolve(X::SpaceTimeDyad, x::Array{T,2}) - -end diff --git a/src/utils/sparsend.jl b/src/utils/sparsend.jl deleted file mode 100644 index ae2ee957..00000000 --- a/src/utils/sparsend.jl +++ /dev/null @@ -1,32 +0,0 @@ -module SparseND - -import ...BEAST -import FillArrays - -struct MatrixOfConvolutions{T} <: AbstractArray{Vector{T},2} - # banded::AbstractArray{T,3} - convop::BEAST.ConvOp{T} -end - -function Base.eltype(x::MatrixOfConvolutions{T}) where {T} - Vector{T} -end -Base.size(x::MatrixOfConvolutions) = size(x.convop)[1:2] -function Base.getindex(x::MatrixOfConvolutions, m, n) - return x.convop[m,n,:] -end - -BEAST.convolve!(y, Z::MatrixOfConvolutions, x, csx, i, k_start, k_stop) = BEAST.convolve!(y, Z.convop, x, csx, i, k_start, k_stop) -BEAST.convolve!(y, Z::FillArrays.Zeros, x, csx, i, k_start, k_stop) = nothing -BEAST.convolve!(y, Z::FillArrays.Fill, x, csx, i, k_start, k_stop) = nothing - - -struct SpaceTimeData{T} <: AbstractArray{Vector{T},1} - data::Array{T,2} -end - -Base.eltype(x::SpaceTimeData{T}) where {T} = Vector{T} -Base.size(x::SpaceTimeData) = (size(x.data)[1],) -Base.getindex(x::SpaceTimeData, i::Int) = x.data[i,:] - -end # module diff --git a/src/utils/specialfns.jl b/src/utils/specialfns.jl index 2605f593..18cda970 100644 --- a/src/utils/specialfns.jl +++ b/src/utils/specialfns.jl @@ -1,4 +1,4 @@ - +import SpecialFunctions: erf struct Gaussian{T} scaling::T @@ -6,8 +6,7 @@ struct Gaussian{T} delay::T end -Gaussian(;scaling=1.0, width, delay) = Gaussian(scaling, width, delay) - +Gaussian(;scaling=1.0, width, delay) = Gaussian(typeof(width)(scaling), width, delay) (g::Gaussian)(s::Real) = 4*g.scaling/(g.width*√π) * exp(-(4*(s-g.delay)/g.width)^2) @@ -18,11 +17,11 @@ function creategaussian(width,s0,scaling=one(typeof(width))) end -function fouriertransform(g::Gaussian) +function fouriertransform(g::Gaussian; numdiff=0) scaling = g.scaling width = g.width s0 = g.delay - ft(w) = scaling * exp(-im*w*s0 - (width*w/8)^2) / sqrt(2π) + ft(w) = (im*w)^numdiff * scaling * exp(-im*w*s0 - (width*w/8)^2) / sqrt(2π) end @@ -34,11 +33,21 @@ function fouriertransform(a::Array, dt, t0, dim=1) b, dω, ω0 end +function inversefouriertransform(a::Array, dω, ω0, dim=1) + n = size(a,dim) + dt = 2π/ (n*dω) + b = ifft(a,dim) * sqrt(2π) / dt + t0 = -dt * div(n,2) + b, dt, t0 +end + fouriertransform(a::Array; stepsize, offset, dim=1) = fouriertransform(a, stepsize, offset, dim) derive(g::Gaussian) = s -> g(s) * (-8 * (s-g.delay)/g.width) * (4/g.width) +derive2(g::Gaussian) = s -> -(8*4)/g.width^2 * (g(s) + (s-g.delay) * derive(g)(s)) + struct ErrorFunction{T} scaling::T diff --git a/src/utils/variational.jl b/src/utils/variational.jl index 9888ea2f..1a42b399 100644 --- a/src/utils/variational.jl +++ b/src/utils/variational.jl @@ -1,7 +1,7 @@ module Variational using BlockArrays - +import BEAST # import Base: start, done, next export transposecalls! @@ -108,12 +108,21 @@ end Base.Int(hv::HilbertVector) = hv.idx +function hilbertspace(s::Symbol, numcomponents::Int) + syms = [Symbol(s,i) for i in 1:numcomponents] + return [HilbertVector(i,syms,[]) for i in 1:numcomponents] +end + + Base.getindex(A::AbstractBlockArray, p::HilbertVector, q::HilbertVector) = A[Block(Int(p),Int(q))] Base.getindex(u::AbstractBlockArray, p::HilbertVector) = u[Block(Int(p))] Base.setindex!(A::AbstractBlockArray, v, p::HilbertVector, q::HilbertVector) = setindex!(A, v, Block(Int(p),Int(q))) Base.setindex!(A::AbstractBlockArray, v, p::HilbertVector) = setindex!(A, v, Block(Int(p))) +Base.view(A::AbstractBlockArray, p::HilbertVector, q::HilbertVector) = view(A, Block(Int(p), Int(q))) +Base.view(A::AbstractBlockArray, p::HilbertVector) = view(A, Block(Int(p))) + mutable struct LinForm test_space terms @@ -155,36 +164,89 @@ Build an equation from a left hand and right hand side -""" - hilbert_space(type, g1, g2, ...) - -Returns generators defining a Hilbert space of field `type` -""" -hilbertspace(vars::Symbol...) = [HilbertVector(i, [vars...], []) for i in 1:length(vars)] +# """ +# hilbert_space(type, g1, g2, ...) +# Returns generators defining a Hilbert space of field `type` +# """ +# hilbertspace(vars::Symbol...) = [HilbertVector(i, [vars...], []) for i in 1:length(vars)] -macro hilbertspace(syms...) +function genspace(syms...) + space = Vector{Symbol}() + lengths = Int[] + starts = Int[] + stops = Int[] for sym in syms - @assert isa(sym, Symbol) "@hilbertspace takes a list of Symbols" - end - rhs = :(hilbertspace()) - for sym in syms - push!(rhs.args, QuoteNode(sym)) + if sym isa Symbol + push!(space, sym) + push!(lengths,1) + push!(starts,1) + push!(stops,1) + elseif sym isa Expr && sym.head == :ref + base = sym.args[1] + start = sym.args[2].args[2] + stop = sym.args[2].args[3] + for k in start:stop + sym = Symbol(base,k) + push!(space, sym) + end + push!(lengths,stop-start+1) + push!(starts,start) + push!(stops,stop) + end end + + return space, starts, stops +end - vars = gensym() - xp = quote - $vars = $rhs - end - for (i,s) in enumerate(syms) - push!(xp.args, :(global $s = $vars[$i])) - end +macro hilbertspace(syms...) - xp + space, starts, stops = genspace(syms...) + + ex = quote end + k = 1 + for (s, (start,stop)) in enumerate(zip(starts,stops)) + + + len = stop-start+1 + if syms[s] isa Symbol + sym = syms[s] + push!(ex.args, :($(esc(sym)) = HilbertVector($k,$space,[]))) + k += 1 + else + sym = syms[s].args[1] + push!(ex.args, :($(esc(sym)) = [HilbertVector(i,$space,[]) for i in $k:$(k+len-1)])) + k += len + end + + end + return ex end +# macro hilbertspace(syms...) + +# for sym in syms +# @assert isa(sym, Symbol) "@hilbertspace takes a list of Symbols" +# end + +# rhs = :(hilbertspace()) +# for sym in syms +# push!(rhs.args, QuoteNode(sym)) +# end + +# vars = gensym() +# xp = quote +# $vars = $rhs +# end +# for (i,s) in enumerate(syms) +# push!(xp.args, :($(esc(s)) = $vars[$i])) +# end + +# xp +# end + """ call(u::HilbertVector, f, params...) @@ -205,6 +267,14 @@ Return a LinForm corresponding to f[v] """ getindex(f, v::HilbertVector) = LinForm(v.space, [LinTerm(v.idx, v.opstack, 1, f)]) +function getindex(f, V::Vector{HilbertVector}) + terms = Vector{LinTerm}() + for v in V + term = LinTerm(v.idx, v.opstack, 1, f) + push!(terms, term) + end + return LinForm(first(V).space, terms) +end """ getindex(A, v::HilbertVector, u::HilbertVector) @@ -216,6 +286,53 @@ function getindex(A, v::HilbertVector, u::HilbertVector) BilForm(v.space, u.space, terms) end +function getindex(A::AbstractMatrix, v::HilbertVector, u::HilbertVector) + terms = [ BilTerm(v.idx, u.idx, v.opstack, u.opstack, 1, A) ] + BilForm(v.space, u.space, terms) +end + + +# function getindex(A::BEAST.BlockDiagonalOperator, V::Vector{HilbertVector}, U::Vector{HilbertVector}) +# op = A.op +# terms = Vector{BilTerm}() +# @assert length(V) == length(U) +# for (v,u) in zip(V,U) +# term = BilTerm(v.idx, u.idx, v.opstack, u.opstack, 1, op) +# push!(terms, term) +# end +# return BilForm(first(V).space, first(U).space, terms) +# end + +# function getindex(A::BEAST.BlockFullOperators, V::Vector{HilbertVector}, U::Vector{HilbertVector}) +# op = A.op +# terms = Vector{BilTerm}() +# # @assert length(V) == length(U) +# for v in V +# for u in U +# term = BilTerm(v.idx, u.idx, v.opstack, u.opstack, 1, op) +# push!(terms, term) +# end +# end +# return BilForm(first(V).space, first(U).space, terms) +# end + + +function getindex(op::Any, V::Vector{HilbertVector}, U::Vector{HilbertVector}) + terms = Vector{BilTerm}() + for v in V + for u in U + term = BilTerm(v.idx, u.idx, v.opstack, u.opstack, 1, op) + push!(terms, term) + end + end + return BilForm(first(V).space, first(U).space, terms) +end + +function getindex(A::Matrix, v::HilbertVector, u::HilbertVector) + terms = [ BilTerm(v.idx, u.idx, v.opstack, u.opstack, 1, A) ] + BilForm(v.space, u.space, terms) +end + "Add two BilForms together" function +(a::BilForm, b::BilForm) @@ -315,4 +432,55 @@ macro varform(x) esc(y) end + +struct DirectProductKernel + bilforms +end + +function Base.getindex(A::DirectProductKernel, V::Vector{HilbertVector}, U::Vector{HilbertVector}) + terms = Vector{BilTerm}() + @assert length(V) == length(U) == length(A.bilforms) + + for (v,u,op) in zip(V,U, A.bilforms) + term = BilTerm(v.idx, u.idx, v.opstack, u.opstack, 1, op) + push!(terms, term) + end + + return BilForm(first(V).space, first(U).space, terms) +end + +struct BlockDiagKernel + bilform +end + +function Base.getindex(A::BlockDiagKernel, V::Vector{HilbertVector}, U::Vector{HilbertVector}) + terms = Vector{BilTerm}() + @assert length(V) == length(U) + + op = A.bilform + for (v,u) in zip(V,U) + term = BilTerm(v.idx, u.idx, v.opstack, u.opstack, 1, op) + push!(terms, term) + end + + return BilForm(first(V).space, first(U).space, terms) +end + + +struct OffDiagKernel + bilform +end + +function getindex(op::OffDiagKernel, V::Vector{HilbertVector}, U::Vector{HilbertVector}) + terms = Vector{BilTerm}() + for (i,v) in enumerate(V) + for (j,u) in enumerate(U) + i == j && continue + term = BilTerm(v.idx, u.idx, v.opstack, u.opstack, 1, op.bilform) + push!(terms, term) + end + end + return BilForm(first(V).space, first(U).space, terms) +end + end diff --git a/src/utils/zeromap.jl b/src/utils/zeromap.jl new file mode 100644 index 00000000..207d6fa4 --- /dev/null +++ b/src/utils/zeromap.jl @@ -0,0 +1,57 @@ +import LinearMaps + +struct ZeroMap{T,U,V} <: LinearMap{T} + range::U + domain::V +end + +ZeroMap{T}(range::U, domain::V) where {T,U,V} = ZeroMap{T,U,V}(range, domain) +LinearMaps.MulStyle(A::ZeroMap) = LinearMaps.FiveArg() + +Base.size(A::ZeroMap) = (length(A.range), length(A.domain),) +Base.axes(A::ZeroMap) = (A.range, A.domain) + +function LinearMaps._unsafe_mul!(y::AbstractVector, L::ZeroMap, x::AbstractVector, α::Number, β::Number) + y .*= β +end + + +function LinearMaps._unsafe_mul!(Y::AbstractMatrix, X::ZeroMap, c::Number, a::Number, b::Number) + rmul!(Y, b) + return Y +end + +function LinearMaps._unsafe_mul!(Y::AbstractMatrix, X::ZeroMap, c::Number) + fill!(Y, false) + return Y +end + +# function LinearAlgebra.mul!(y::AbstractVector, +# Lt::LinearMaps.TransposeMap{<:Any,<:ZeroMap}, +# x::AbstractVector, α::Number, β::Number) + +# y .*= β +# end + + +function LinearMaps._unsafe_mul!(y::AbstractVector, L::ZeroMap, x::AbstractVector) + y .= 0 +end + +# function LinearAlgebra.mul!(y::AbstractVector, +# Lt::LinearMaps.TransposeMap{<:Any,<:ZeroMap}, x::AbstractVector) + +# y .= 0 +# end + +LinearAlgebra.adjoint(A::ZeroMap{T}) where {T} = ZeroMap{T}(A.domain, A.range) +LinearAlgebra.transpose(A::ZeroMap{T}) where {T} = ZeroMap{T}(A.domain, A.range) + +# function Base.:(*)(A::ZeroMap, x::AbstractVector) +# T = eltype(A) +# y = similar(A.range, T) +# fill!(y, zero(T)) +# LinearAlgebra.mul!(y,A,x) +# end + +# Base.Matrix{T}(A::ZeroMap) where {T} = zeros(T, length(A.range), length(A.domain)) diff --git a/src/volumeintegral/farfield.jl b/src/volumeintegral/farfield.jl new file mode 100644 index 00000000..5b17b7d9 --- /dev/null +++ b/src/volumeintegral/farfield.jl @@ -0,0 +1,35 @@ +""" +Operator to compute the far field of a current distribution. In particular, given the current distribution ``j`` this operator allows for the computation of + +```math +A j = n × ∫_Ω j e^{γ ̂x ⋅ y} dy +``` + +where ``̂x`` is the unit vector in the direction of observation. Note that the assembly routing expects the observation directions to be normalised by the caller. +""" +struct VIEFarField3D{K,P} + gamma::K + tau::P +end + +function VIEFarField3D(;wavenumber=error("wavenumber is a required argument"), tau=nothing) + gamma = nothing + + if iszero(real(wavenumber)) + gamma = -imag(wavenumber) + else + gamma = wavenumber*im + end + + tau == nothing && (tau = x->1.0) + + VIEFarField3D(gamma, tau) +end + +quaddata(op::VIEFarField3D,rs,els) = quadpoints(rs,els,(3,)) +quadrule(op::VIEFarField3D,refspace,p,y,q,el,qdata) = qdata[1,q] + +kernelvals(op::VIEFarField3D,y,p) = exp(op.gamma*dot(y,cartesian(p))) +function integrand(op::VIEFarField3D,krn,y,f,p) + (y × (op.tau(cartesian(p)) * krn * f[1])) × y +end diff --git a/src/volumeintegral/sauterschwab_ints.jl b/src/volumeintegral/sauterschwab_ints.jl new file mode 100644 index 00000000..bb0050e4 --- /dev/null +++ b/src/volumeintegral/sauterschwab_ints.jl @@ -0,0 +1,102 @@ + + +# 6D integral: ∫∫∫_Ω ∫∫∫_Ω, 5D integral: ∫∫∫_Ω ∫∫_Γ +# function (f::PulledBackIntegrand)(u,v) <--- see quadrature/sauterschwabints.jl +# ... +# end + + +# 5D integral: ∫∫_Γ ∫∫∫_Ω +function (f::PulledBackIntegrand{<:Integrand{<:BoundaryOperator}, + <:CompScienceMeshes.Simplex{<:Any,2,<:Any,3}, <:CompScienceMeshes.Simplex{<:Any,3,<:Any,4} })(v,u) + # "In general I think a Jacobian determinant needs to be included. For Simplical and + # Quadrilateral charts this is not needed because they are 1." + f.igd(cartesian(f.chart1,u), cartesian(f.chart2,v)) +end + + + +function reorder(test_chart::CompScienceMeshes.Simplex{<:Any,2,<:Any,3}, trial_chart::CompScienceMeshes.Simplex{<:Any,3,<:Any,4}, + strat::SauterSchwab3DStrategy) + J, I = SauterSchwab3D.reorder(strat.sing) # 5D integral: ∫∫_Γ ∫∫∫_Ω + return I, J +end + +function reorder(test_chart::CompScienceMeshes.Simplex, trial_chart::CompScienceMeshes.Simplex, + strat::SauterSchwab3DStrategy) + I, J = SauterSchwab3D.reorder(strat.sing) # 6D integral: ∫∫∫_Ω ∫∫∫_Ω, 5D integral: ∫∫∫_Ω ∫∫_Γ + return I, J +end + + + +#TODO: use trial_ptr to get the cell material using material_array[trial_ptr] ... + +function momintegrals!(out, op::VIEOperator, + test_functions::Space, test_ptr, test_chart, + trial_functions::Space, trial_ptr, trial_chart, + strat::SauterSchwab3DStrategy) + + test_local_space = refspace(test_functions) + trial_local_space = refspace(trial_functions) + + num_tshapes = numfunctions(test_local_space, domain(test_chart)) + num_bshapes = numfunctions(trial_local_space, domain(trial_chart)) + + I, J = reorder(test_chart, trial_chart, strat) + + igd = Integrand(op, test_local_space, trial_local_space, test_chart, trial_chart) + igdp = pulledback_integrand(igd, I, test_chart, J, trial_chart) + Q = SauterSchwab3D.sauterschwab_parameterized(igdp, strat) + + + for j in 1:num_bshapes + for i in 1:num_tshapes + out[i,j] += Q[i,j] + end + end + + return nothing +end + + + +#TODO: use trial_ptr to get the cell material using material_array[trial_ptr] ... + +# function momintegrals!(z, op::VIEOperator, +# test_functions::Space, test_cellptr, test_chart, +# trial_functions::Space, trial_cellptr, trial_chart, +# strat::DoubleQuadRule) + +# tshs = refspace(test_functions) +# bshs = refspace(trial_functions) + +# igd = Integrand(op, tshs, bshs, test_chart, trial_chart) + +# womps = strat.outer_quad_points +# wimps = strat.inner_quad_points + +# for womp in womps +# tgeo = womp.point +# tvals = womp.value +# M = length(tvals) +# jx = womp.weight + +# for wimp in wimps +# bgeo = wimp.point +# bvals = wimp.value +# N = length(bvals) +# jy = wimp.weight + +# j = jx * jy + +# z1 = j * igd(tgeo, bgeo, tvals, bvals) +# for n in 1:N +# for m in 1:M +# z[m,n] += z1[m,n] +# end end +# end +# end + +# return z +# end \ No newline at end of file diff --git a/src/volumeintegral/vie.jl b/src/volumeintegral/vie.jl new file mode 100644 index 00000000..f1ebebb2 --- /dev/null +++ b/src/volumeintegral/vie.jl @@ -0,0 +1,401 @@ +module VIE + using ..BEAST + Mod = BEAST + + """ + singlelayer(;gamma, alpha, beta, tau) + singlelayer(;wavenumber, alpha, beta, tau) + + Bilinear form given by: + + ```math + α ∬_{Ω×Ω} j(x)⋅τ(y)⋅k(y) G_{γ}(x,y) + β ∬_{Ω×Ω} div j(x) τ(y)⋅k(y)⋅grad G_{γ}(x,y) + ``` + + with ``G_{γ} = e^{-γ|x-y|} / 4π|x-y|`` + and ``τ(y)`` contrast dyadic + """ + + function singlelayer(; + gamma=nothing, + wavenumber=nothing, + alpha=nothing, + beta=nothing, + tau=nothing) + + if (gamma == nothing) && (wavenumber == nothing) + error("Supply one of (not both) gamma or wavenumber") + end + + if (gamma != nothing) && (wavenumber != nothing) + error("Supply one of (not both) gamma or wavenumber") + end + + if gamma == nothing + if iszero(real(wavenumber)) + gamma = -imag(wavenumber) + else + gamma = im*wavenumber + end + end + + @assert gamma != nothing + + alpha == nothing && (alpha = wavenumber*wavenumber) + beta == nothing && (beta = 1.0) + tau == nothing && (tau = x->1.0) + + Mod.VIESingleLayer(gamma, alpha, beta, tau) + end + + + function singlelayer2(; + gamma=nothing, + wavenumber=nothing, + alpha=nothing, + beta=nothing, + tau=nothing) + + if (gamma == nothing) && (wavenumber == nothing) + error("Supply one of (not both) gamma or wavenumber") + end + + if (gamma != nothing) && (wavenumber != nothing) + error("Supply one of (not both) gamma or wavenumber") + end + + if gamma == nothing + if iszero(real(wavenumber)) + gamma = -imag(wavenumber) + else + gamma = im*wavenumber + end + end + + @assert gamma != nothing + + alpha == nothing && (alpha = 1.0) + beta == nothing && (beta = 1.0) + tau == nothing && (tau = x->1.0) + + Mod.VIESingleLayer2(gamma, alpha, beta, tau) + end + + """ + boundary(;gamma, alpha, tau) + boundary(;wavenumber, alpha, tau) + + Bilinear form given by: + + ```math + α ∬_{Ω×Ω} T_n j(x) grad G_{γ}(x,y)⋅τ(y)⋅k(y) + ``` + + with ``G_{γ} = e^{-γ|x-y|} / 4π|x-y|`` + and ``τ(y)`` contrast dyadic + """ + + function boundary(; + gamma=nothing, + wavenumber=nothing, + alpha=nothing, + tau=nothing) + + if (gamma == nothing) && (wavenumber == nothing) + error("Supply one of (not both) gamma or wavenumber") + end + + if (gamma != nothing) && (wavenumber != nothing) + error("Supply one of (not both) gamma or wavenumber") + end + + if gamma == nothing + if iszero(real(wavenumber)) + gamma = -imag(wavenumber) + else + gamma = im*wavenumber + end + end + + @assert gamma != nothing + + alpha == nothing && (alpha = 1.0) + tau == nothing && (tau = x->1.0) + + Mod.VIEBoundary(gamma, alpha, tau) + end + + function boundary2(; + gamma=nothing, + wavenumber=nothing, + alpha=nothing, + tau=nothing) + + if (gamma == nothing) && (wavenumber == nothing) + error("Supply one of (not both) gamma or wavenumber") + end + + if (gamma != nothing) && (wavenumber != nothing) + error("Supply one of (not both) gamma or wavenumber") + end + + if gamma == nothing + if iszero(real(wavenumber)) + gamma = -imag(wavenumber) + else + gamma = im*wavenumber + end + end + + @assert gamma != nothing + + alpha == nothing && (alpha = 1.0) + tau == nothing && (tau = x->1.0) + + Mod.VIEBoundary2(gamma, alpha, tau) + end + + """ + doublelayer(;gamma, alpha, beta, tau) + doublelayer(;wavenumber, alpha, beta, tau) + + Bilinear form given by: + + ```math + α ∬_{Ω×Ω} j(x) ⋅ grad G_{γ}(x,y) × τ(y)⋅k(y) + ``` + + with ``G_{γ} = e^{-γ|x-y|} / 4π|x-y|`` + and ``τ(y)`` contrast dyadic + """ + + function doublelayer(; + gamma=nothing, + wavenumber=nothing, + alpha=nothing, + beta=nothing, + tau=nothing) + + if (gamma == nothing) && (wavenumber == nothing) + error("Supply one of (not both) gamma or wavenumber") + end + + if (gamma != nothing) && (wavenumber != nothing) + error("Supply one of (not both) gamma or wavenumber") + end + + if gamma == nothing + if iszero(real(wavenumber)) + gamma = -imag(wavenumber) + else + gamma = im*wavenumber + end + end + + @assert gamma != nothing + + alpha == nothing && (alpha = 1.0) + tau == nothing && (tau = x->1.0) + + Mod.VIEDoubleLayer(gamma, alpha, tau) + end + + planewave(; + direction = error("missing arguement `direction`"), + polarization = error("missing arguement `polarization`"), + wavenumber = error("missing arguement `wavenumber`"), + amplitude = one(real(typeof(wavenumber)))) = + Mod.PlaneWaveVIE(direction, polarization, wavenumber, amplitude) + + farfield(; + wavenumber = error("missing argument: `wavenumber`"), + tau=error("missing argument: `tau`")) = + Mod.VIEFarField3D(wavenumber=wavenumber, tau= tau) + + + + # Operators of the Lippmann Schwinger Volume Integral Equation, + # which is based on the generalized Helmholtz Equation: + """ + hhvolume(;gamma, alpha, tau) + hhvolume(;wavenumber, alpha, tau) + + Bilinear form given by: + + ```math + α ∬_{Ω×Ω} (grad j(x)) ⋅ G_{γ}(x,y)τ(y) (grad k(y)) + ``` + + with ``G_{γ} = e^{-γ|x-y|} / 4π|x-y|`` + and ``τ(y)`` contrast function + """ + function hhvolume(; + gamma=nothing, + wavenumber=nothing, + alpha=nothing, + tau=nothing) + + if (gamma === nothing) && (wavenumber === nothing) + error("Supply one of (not both) gamma or wavenumber") + end + + if (gamma !== nothing) && (wavenumber !== nothing) + error("Supply one of (not both) gamma or wavenumber") + end + + if gamma === nothing + if iszero(real(wavenumber)) + gamma = -imag(wavenumber) + else + gamma = im*wavenumber + end + end + + @assert gamma !== nothing + + alpha === nothing && (alpha = -1.0) + tau === nothing && (tau = x->1.0) + + Mod.VIEhhVolume(gamma, alpha, tau) + end + + + """ + hhboundary(;gamma, alpha, tau) + hhboundary(;wavenumber, alpha, tau) + + Bilinear form given by: + + ```math + α ∬_{∂Ω×Ω} n̂(x) ⋅ j(x) G_{γ}(x,y) τ(y) (grad k(y)) + ``` + + with ``G_{γ} = e^{-γ|x-y|} / 4π|x-y|`` + and ``τ(y)`` contrast function + and ``n̂(x)`` normal vector + """ + function hhboundary(; + gamma=nothing, + wavenumber=nothing, + alpha=nothing, + tau=nothing) + + if (gamma === nothing) && (wavenumber === nothing) + error("Supply one of (not both) gamma or wavenumber") + end + + if (gamma !== nothing) && (wavenumber !== nothing) + error("Supply one of (not both) gamma or wavenumber") + end + + if gamma === nothing + if iszero(real(wavenumber)) + gamma = -imag(wavenumber) + else + gamma = im*wavenumber + end + end + + @assert gamma !== nothing + + alpha === nothing && (alpha = 1.0) + tau === nothing && (tau = x->1.0) + + Mod.VIEhhBoundary(gamma, alpha, tau) + end + + + """ + hhvolumek0(;gamma, alpha, tau) + hhvolumek0(;wavenumber, alpha, tau) + + Bilinear form given by: + + ```math + α ∬_{Ω×Ω} j(x) G_{γ}(x,y)τ(y) k(y) + ``` + + with ``G_{γ} = e^{-γ|x-y|} / 4π|x-y|`` + and ``τ(y)`` contrast function + """ + function hhvolumek0(; + gamma=nothing, + wavenumber=nothing, + alpha=nothing, + tau=nothing) + + if (gamma === nothing) && (wavenumber === nothing) + error("Supply one of (not both) gamma or wavenumber") + end + + if (gamma !== nothing) && (wavenumber !== nothing) + error("Supply one of (not both) gamma or wavenumber") + end + + if gamma === nothing + if iszero(real(wavenumber)) + gamma = -imag(wavenumber) + else + gamma = im*wavenumber + end + end + + @assert gamma !== nothing + + alpha === nothing && (alpha = wavenumber*wavenumber) + tau === nothing && (tau = x->1.0) + + Mod.VIEhhVolumek0(gamma, alpha, tau) + end + + + + """ + hhvolumegradG(;gamma, alpha, tau) + hhvolumegradG(;wavenumber, alpha, tau) + + Bilinear form given by: + + ```math + α ∬_{Ω×Ω} j(x) grad_y(G_{γ}(x,y)) τ(y) ⋅ (grad k(y)) + ``` + + with ``G_{γ} = e^{-γ|x-y|} / 4π|x-y|`` + and ``τ(y)`` constant function + """ + function hhvolumegradG(; + gamma=nothing, + wavenumber=nothing, + alpha=nothing, + tau=nothing) + + if (gamma === nothing) && (wavenumber === nothing) + error("Supply one of (not both) gamma or wavenumber") + end + + if (gamma !== nothing) && (wavenumber !== nothing) + error("Supply one of (not both) gamma or wavenumber") + end + + if gamma === nothing + if iszero(real(wavenumber)) + gamma = -imag(wavenumber) + else + gamma = im*wavenumber + end + end + + @assert gamma !== nothing + + alpha === nothing && (alpha = -1.0) + tau === nothing && (tau = x->1.0) + + Mod.VIEhhVolumegradG(gamma, alpha, tau) + end + + linearpotential(; + direction = error("missing arguement `direction`"), + amplitude = one(real(typeof(direction[1])))) = + Mod.LinearPotentialVIE(direction, amplitude) + +end \ No newline at end of file diff --git a/src/volumeintegral/vieexc.jl b/src/volumeintegral/vieexc.jl new file mode 100644 index 00000000..f83fb6d0 --- /dev/null +++ b/src/volumeintegral/vieexc.jl @@ -0,0 +1,102 @@ +mutable struct PlaneWaveVIE{T,P} <: Functional{T} + direction::P + polarisation::P + wavenumber::T + amplitude::T + end + + scalartype(x::PlaneWaveVIE{T,P}) where {T,P} = Complex{T} + + function PlaneWaveVIE(d,p,k,a = 1) + T = promote_type(eltype(d), eltype(p), typeof(k), typeof(a)) + P = similar_type(typeof(d), T) + PlaneWaveVIE{T,P}(d,p,k,a) +end + +""" + planewavevie(; + direction = error("missing arguement `direction`"), + polarization = error("missing arguement `polarization`"), + wavenumber = error("missing arguement `wavenumber`"), + amplitude = 1, + ) + +For volume integral equations +""" +planewavevie(; + direction = error("missing arguement `direction`"), + polarization = error("missing arguement `polarization`"), + wavenumber = error("missing arguement `wavenumber`"), + amplitude = 1, + ) = PlaneWaveVIE(direction, polarization, wavenumber, amplitude) + +function (e::PlaneWaveVIE)(p) + k = e.wavenumber + d = e.direction + u = e.polarisation + a = e.amplitude + x = p + a * exp(-im * k * dot(d, x)) * u + end + + function (e::PlaneWaveVIE)(p::CompScienceMeshes.MeshPointNM) + k = e.wavenumber + d = e.direction + u = e.polarisation + a = e.amplitude + x = cartesian(p) + a * exp(-im * k * dot(d, x)) * u + end + + function curl(field::PlaneWaveVIE) + k = field.wavenumber + d = field.direction + u = field.polarisation + a = field.amplitude + v = d × u + b = -a * im * k + PlaneWaveVIE(d, v, k, b) + end + +*(a::Number, e::PlaneWaveVIE) = PlaneWaveVIE(e.direction, e.polarisation, e.wavenumber, a*e.amplitude) + +integrand(::PlaneWaveVIE, test_vals, field_val) = test_vals[1] ⋅ field_val + + +# Excitation for Lippmann Schwinger Volume Integral Equation +mutable struct LinearPotentialVIE{T,P} <: Functional{T} + direction::P + amplitude::T +end + +scalartype(x::LinearPotentialVIE{T,P}) where {T,P} = T + +function LinearPotentialVIE_(d,a = 1) + T = promote_type(eltype(d), typeof(a)) + P = similar_type(typeof(d), T) #SVector{3,T} + return LinearPotentialVIE{T,P}(d,a) +end + +""" + linearpotentialvie(; + direction = error("missing argument `direction`"), + amplitude = 1, + ) + +Linear potential for volume integral equations. +""" +linearpotentialvie(; + direction = error("missing argument `direction`"), + amplitude = 1, +) = LinearPotentialVIE_(direction, amplitude) + +function (e::LinearPotentialVIE)(p) + d = e.direction + a = e.amplitude + x = cartesian(p) + return a * dot(d, x) +end + +*(a::Number, e::LinearPotentialVIE) = LinearPotentialVIE_(e.direction, a*e.amplitude) + +integrand(::LinearPotentialVIE, test_vals, field_val) = test_vals[1] ⋅ field_val \ No newline at end of file diff --git a/src/volumeintegral/vieops.jl b/src/volumeintegral/vieops.jl new file mode 100644 index 00000000..6be6c16d --- /dev/null +++ b/src/volumeintegral/vieops.jl @@ -0,0 +1,585 @@ +abstract type VIEOperator <: IntegralOperator end +abstract type VolumeOperator <: VIEOperator end # ∫∫∫_Ω ∫∫∫_Ω +abstract type BoundaryOperator <: VIEOperator end # ∫∫∫_Ω ∫∫_Γ or ∫∫_Γ ∫∫∫_Ω + +struct VIESingleLayer{T,U,P} <: VolumeOperator + gamma::T + α::U + β::U + tau::P +end + +struct VIEBoundary{T,U,P} <: BoundaryOperator + gamma::T + α::U + tau::P +end + +struct VIESingleLayer2{T,U,P} <: VolumeOperator + gamma::T + α::U + β::U + tau::P +end + +struct VIEBoundary2{T,U,P} <: BoundaryOperator + gamma::T + α::U + tau::P +end + + +struct VIEDoubleLayer{T,U,P} <: VolumeOperator + gamma::T + α::U + tau::P +end + + +struct VIEhhVolume{T,U,P} <: VolumeOperator + gamma::T + α::U + tau::P +end + +struct VIEhhBoundary{T,U,P} <: BoundaryOperator + gamma::T + α::U + tau::P +end + +struct VIEhhVolumegradG{T,U,P} <: VolumeOperator + gamma::T + α::U + tau::P +end + +struct VIEhhVolumek0{T,U,P} <: VolumeOperator + gamma::T + α::U + tau::P +end + + +""" +The following operators are for test purposes only (EVIE and DVIE) +""" + +struct DVIE_TestOp1{T,U,P} <: VolumeOperator + gamma::T + α::U + tau::P +end + +struct DVIE_TestOp2{T,U,P} <: BoundaryOperator + gamma::T + α::U + tau::P +end + +struct DVIE_TestOp3{T,U,P} <: VolumeOperator + gamma::T + α::U + tau::P +end + +struct EVIE_TestOp1{T,U,P} <: VolumeOperator + gamma::T + α::U + tau::P +end + +struct EVIE_TestOp2{T,U,P} <: BoundaryOperator + gamma::T + α::U + tau::P +end + +struct EVIE_TestOp3{T,U,P} <: VolumeOperator + gamma::T + α::U + tau::P +end + + + +#TODO: ... scalartype is also determined by the material function ... LSVIE quasistatic case => complex ...handler... +scalartype(op::VIEOperator) = typeof(op.gamma) + +export VIE + + + + +""" +Integrands for the operators of the DVIE +""" + +function (igd::Integrand{<:VIESingleLayer})(x,y,f,g) + α = igd.operator.α + β = igd.operator.β + γ = igd.operator.gamma + + r = cartesian(x) - cartesian(y) + R = norm(r) + iR = 1/R + green = exp(-γ*R)*(iR*i4pi) + gradgreen = -(γ + iR) * green * (iR * r) # Derivation after x (test variable) => "-" to get nablaG(r,r') + + Ty = igd.operator.tau(cartesian(y)) + + αgreenTy = α * green * Ty + βgradgreenTy = β * gradgreen * Ty + + + fx = @SVector[f[i].value for i in 1:4] + gy = @SVector[g[i].value for i in 1:4] + + dfx = @SVector[f[i].divergence for i in 1:4] + + @SMatrix[α * dot(fx[i],gy[j]) * αgreenTy - dot(dfx[i] * gy[j], βgradgreenTy) for i in 1:4, j in 1:4] +end + +function (igd::Integrand{<:VIEBoundary})(x,y,f,g) + α = igd.operator.α + γ = igd.operator.gamma + + r = cartesian(x) - cartesian(y) + R = norm(r) + iR = 1/R + green = exp(-γ*R)*(iR*i4pi) + gradgreen = -(γ + iR) * green * (iR * r) # Derivation after x (test variable) => "-" to get ∇_x G(x,y) + + Ty = igd.operator.tau(cartesian(y)) + + αgradgreenTy = α * gradgreen * Ty + + + fx = @SVector[f[i].value for i in 1:1] + gy = @SVector[g[i].value for i in 1:4] + + @SMatrix[fx[i] * dot(gy[j], αgradgreenTy) for i in 1:1, j in 1:4] +end + + + +""" +Integrands for the operators of the EVIE +""" + +function (igd::Integrand{<:VIESingleLayer2})(x,y,f,g) + α = igd.operator.α + γ = igd.operator.gamma + + r = cartesian(x) - cartesian(y) + R = norm(r) + iR = 1/R + green = exp(-γ*R)*(iR*i4pi) + gradgreen = -(γ + iR) * green * (iR * r) # Derivation after x (test variable) => "-" to get nablaG(r,r') + + Ty = igd.operator.tau(cartesian(y)) + + αgradgreenTy = α * gradgreen * Ty + + + gy = @SVector[g[i].value for i in 1:6] + dfx = @SVector[f[i].curl for i in 1:6] + + @SMatrix[dot(dfx[i], cross(αgradgreenTy, gy[j])) for i in 1:6, j in 1:6] +end + +function (igd::Integrand{<:VIEBoundary2})(x,y,f,g) + α = igd.operator.α + γ = igd.operator.gamma + + r = cartesian(x) - cartesian(y) + R = norm(r) + iR = 1/R + green = exp(-γ*R)*(iR*i4pi) + gradgreen = -(γ + iR) * green * (iR * r) # Derivation after x (test variable) => "-" to get ∇_x G(x,y) + + Ty = igd.operator.tau(cartesian(y)) + + αgradgreenTy = α * gradgreen * Ty + + + fx = @SVector[f[i].value for i in 1:3] + gy = @SVector[g[i].value for i in 1:6] + + @SMatrix[dot(fx[i], cross(αgradgreenTy, gy[j])) for i in 1:3, j in 1:6] +end + + + +""" +Other integrands +""" + +function integrand(viop::VIEDoubleLayer, kerneldata, tvals, tgeo, bvals, bgeo) + gx = tvals[1] + fy = bvals[1] + + gradG = kerneldata.gradgreen + + Ty = kerneldata.tau + + α = viop.α + + t = α * dot(gx, cross(gradG, Ty*fy)) +end + + + +""" +Integrands for the operators of the Lippmann Schwinger Volume Integral Equation (LSVIE) +""" + +function (igd::Integrand{<:VIEhhVolume})(x,y,f,g) + α = igd.operator.α + γ = igd.operator.gamma + + r = cartesian(x) - cartesian(y) + R = norm(r) + iR = 1 / R + green = exp(-γ*R)*(i4pi*iR) + + Ty = igd.operator.tau(cartesian(y)) + + αgreenTy = α * green * Ty + + + dfx = @SVector[f[i].gradient for i in 1:4] + dgy = @SVector[g[i].gradient for i in 1:4] + + return @SMatrix[dot(dfx[i], dgy[j]) * αgreenTy for i in 1:4, j in 1:4] +end + +function (igd::Integrand{<:VIEhhBoundary})(x,y,f,g) + α = igd.operator.α + γ = igd.operator.gamma + + r = cartesian(x) - cartesian(y) + R = norm(r) + iR = 1 / R + green = exp(-γ*R)*(i4pi*iR) + + Ty = igd.operator.tau(cartesian(y)) + + αgreenTy = α * green * Ty + + nx = x.patch.normals[1] + + + fx = @SVector[f[i].value for i in 1:3] + dgy = @SVector[g[i].gradient for i in 1:4] + + return @SMatrix[fx[i] * dot(nx, dgy[j]) * αgreenTy for i in 1:3, j in 1:4] +end + +function (igd::Integrand{<:VIEhhVolumek0})(x,y,f,g) + α = igd.operator.α + γ = igd.operator.gamma + + r = cartesian(x) - cartesian(y) + R = norm(r) + iR = 1/R + green = exp(-γ*R)*(iR*i4pi) + + Ty = igd.operator.tau(cartesian(y)) + + αgreenTy = α * green * Ty + + + fx = @SVector[f[i].value for i in 1:4] + gy = @SVector[g[i].value for i in 1:4] + + return @SMatrix[dot(fx[i], gy[j]) * αgreenTy for i in 1:4, j in 1:4] +end + +function (igd::Integrand{<:VIEhhVolumegradG})(x,y,f,g) + α = igd.operator.α + γ = igd.operator.gamma + + r = cartesian(x) - cartesian(y) + R = norm(r) + iR = 1/R + green = exp(-γ*R)*(iR*i4pi) + gradgreen = (γ + iR) * green * (iR * r) # Derivation after y (trial variable) => "+" to get ∇_y G(x,y) + + Ty = igd.operator.tau(cartesian(y)) + + αgradgreenTy = α * gradgreen * Ty + + fx = @SVector[f[i].value for i in 1:4] + dgy = @SVector[g[i].gradient for i in 1:4] + + return @SMatrix[fx[i] * dot(dgy[j], αgradgreenTy) for i in 1:4, j in 1:4] +end + + + +""" +Integrands for operators which are for test purposes only (DVIE and EVIE) +""" + +function (igd::Integrand{<:DVIE_TestOp1})(x,y,f,g) + α = igd.operator.α + γ = igd.operator.gamma + + + r = cartesian(x) - cartesian(y) + R = norm(r) + iR = 1/R + green = exp(-γ*R)*(iR*i4pi) + gradgreen = -(γ + iR) * green * (iR * r) # Derivation after x (test variable) => "-" to get ∇_x G(x,y) + + Ty = igd.operator.tau(cartesian(y)) + + αgradgreenTy = α * gradgreen * Ty + + + fx = @SVector[f[i].value for i in 1:4] + dgy = @SVector[g[i].divergence for i in 1:4] + + return @SMatrix[dot(fx[i], αgradgreenTy) * dgy[j] for i in 1:4, j in 1:4] +end + +function (igd::Integrand{<:DVIE_TestOp2})(x,y,f,g) + α = igd.operator.α + γ = igd.operator.gamma + + r = cartesian(x) - cartesian(y) + R = norm(r) + iR = 1/R + green = exp(-γ*R)*(iR*i4pi) + + Ty = igd.operator.tau(cartesian(y)) + + αgreenTy = α * green * Ty + + + fx = @SVector[f[i].value for i in 1:1] + dgy = @SVector[g[i].divergence for i in 1:4] + + return @SMatrix[fx[i] * dgy[j] * αgreenTy for i in 1:1, j in 1:4] +end + +function (igd::Integrand{<:DVIE_TestOp3})(x,y,f,g) + α = igd.operator.α + γ = igd.operator.gamma + + + r = cartesian(x) - cartesian(y) + R = norm(r) + iR = 1/R + green = exp(-γ*R)*(iR*i4pi) + + Ty = igd.operator.tau(cartesian(y)) + + αgreenTy = α * green * Ty + + + dfx = @SVector[f[i].divergence for i in 1:4] + dgy = @SVector[g[i].divergence for i in 1:4] + + return @SMatrix[dfx[i] * dgy[j] * αgreenTy for i in 1:4, j in 1:4] +end + +function (igd::Integrand{<:EVIE_TestOp1})(x,y,f,g) + α = igd.operator.α + γ = igd.operator.gamma + + r = cartesian(x) - cartesian(y) + R = norm(r) + iR = 1/R + green = exp(-γ*R)*(iR*i4pi) + + Ty = igd.operator.tau(cartesian(y)) + + αgreenTy = α * green * Ty + + + dfx = @SVector[f[i].curl for i in 1:6] + gy = @SVector[g[i].value for i in 1:6] + + return @SMatrix[dot(dfx[i], gy[j]) * αgreenTy for i in 1:6, j in 1:6] +end + +function (igd::Integrand{<:EVIE_TestOp2})(x,y,f,g) + α = igd.operator.α + γ = igd.operator.gamma + + r = cartesian(x) - cartesian(y) + R = norm(r) + iR = 1/R + green = exp(-γ*R)*(iR*i4pi) + + Ty = igd.operator.tau(cartesian(y)) + + αgreenTy = α * green * Ty + + + fx = @SVector[f[i].value for i in 1:3] # f[i].value is n̂ × NedeleccBasis for the EVIE, means ttrace(NedeleccBasis) + gy = @SVector[g[i].value for i in 1:6] + + return @SMatrix[dot(fx[i], gy[j]) * αgreenTy for i in 1:3, j in 1:6] +end + +function (igd::Integrand{<:EVIE_TestOp3})(x,y,f,g) + α = igd.operator.α + γ = igd.operator.gamma + + r = cartesian(x) - cartesian(y) + R = norm(r) + iR = 1/R + green = exp(-γ*R)*(iR*i4pi) + gradgreen = -(γ + iR) * green * (iR * r) # Derivation after x (test variable) => "-" to get ∇_x G(x,y) + + Ty = igd.operator.tau(cartesian(y)) + + αgradgreenTy = α * gradgreen * Ty + + fx = @SVector[f[i].value for i in 1:6] + gy = @SVector[g[i].value for i in 1:6] + + return @SMatrix[dot(fx[i], cross(αgradgreenTy, gy[j])) for i in 1:6, j in 1:6] # cross product is bilinear (important complex material functions Ty) +end + + + + + + + + + +defaultquadstrat(op::VIEOperator, tfs, bfs) = SauterSchwab3DQStrat(3,3,3,3,3,3) + + +function quaddata(op::VIEOperator, + test_local_space::RefSpace, trial_local_space::RefSpace, + test_charts, trial_charts, qs::SauterSchwab3DQStrat) + + #The combinations of rules (6,7) and (5,7 are) BAAAADDDD + # they result in many near singularity evaluations with any + # resemblence of accuracy going down the drain! Simply don't! + # (same for (5,7) btw...). + t_qp = quadpoints(test_local_space, test_charts, (qs.outer_rule,)) + b_qp = quadpoints(trial_local_space, trial_charts, (qs.inner_rule,)) + + + sing_qp = (SauterSchwab3D._legendre(qs.sauter_schwab_1D,0,1), + SauterSchwab3D._shunnham2D(qs.sauter_schwab_2D), + SauterSchwab3D._shunnham3D(qs.sauter_schwab_3D), + SauterSchwab3D._shunnham4D(qs.sauter_schwab_4D),) + + + return (tpoints=t_qp, bpoints=b_qp, sing_qp=sing_qp) +end + + +function _hits(τ, σ) + T = coordtype(τ) + hits = 0 + dtol = 1.0e3 * eps(T) + idx_t = Int64[] + idx_s = Int64[] + sizehint!(idx_t,4) + sizehint!(idx_s,4) + + for (i,t) in enumerate(τ.vertices) + for (j,s) in enumerate(σ.vertices) + d2 = LinearAlgebra.norm_sqr(t-s) + if d2 < dtol + push!(idx_t,i) + push!(idx_s,j) + hits +=1 + break + end + end + end + + return hits, idx_t, idx_s +end + + +""" +tetrahedron-tetrahedron quadrule for the 6D integral ∫∫∫_Ω ∫∫∫_Ω +""" +function quadrule(op::VolumeOperator, g::RefSpace, f::RefSpace, + i, τ::CompScienceMeshes.Simplex{<:Any, 3, <:Any, 4}, + j, σ::CompScienceMeshes.Simplex{<:Any, 3, <:Any, 4}, + qd, qs::SauterSchwab3DQStrat) + + hits, idx_t, idx_s = _hits(τ, σ) + + @assert hits <= 4 + + hits == 4 && return SauterSchwab3D.CommonVolume6D_S(SauterSchwab3D.Singularity6DVolume(idx_t,idx_s),(qd.sing_qp[1],qd.sing_qp[2],qd.sing_qp[4])) + hits == 3 && return SauterSchwab3D.CommonFace6D_S(SauterSchwab3D.Singularity6DFace(idx_t,idx_s),(qd.sing_qp[1],qd.sing_qp[2],qd.sing_qp[3])) + hits == 2 && return SauterSchwab3D.CommonEdge6D_S(SauterSchwab3D.Singularity6DEdge(idx_t,idx_s),(qd.sing_qp[1],qd.sing_qp[2],qd.sing_qp[3],qd.sing_qp[4])) + hits == 1 && return SauterSchwab3D.CommonVertex6D_S(SauterSchwab3D.Singularity6DPoint(idx_t,idx_s),qd.sing_qp[3]) + #= + #Classic tensor-product quadrature rules + hits == 4 && return SauterSchwab3D.CommonVolume6D(SauterSchwab3D.Singularity6DVolume(idx_t,idx_s),qd.sing_qp[1]) + hits == 3 && return SauterSchwab3D.CommonFace6D(SauterSchwab3D.Singularity6DFace(idx_t,idx_s),qd.sing_qp[1]) + hits == 2 && return SauterSchwab3D.CommonEdge6D(SauterSchwab3D.Singularity6DEdge(idx_t,idx_s),qd.sing_qp[1]) + hits == 1 && return SauterSchwab3D.CommonVertex6D(SauterSchwab3D.Singularity6DPoint(idx_t,idx_s),qd.sing_qp[1]) + =# + + return DoubleQuadRule(qd[1][1,i], qd[2][1,j]) +end + + +""" +triangle-tetrahedron quadrule for the 5D integral ∫∫_Γ ∫∫∫_Ω +""" +function quadrule(op::BoundaryOperator, g::RefSpace, f::RefSpace, + i, τ::CompScienceMeshes.Simplex{<:Any, 2, <:Any, 3}, + j, σ::CompScienceMeshes.Simplex{<:Any, 3, <:Any, 4}, + qd, qs::SauterSchwab3DQStrat) + + hits, idx_t, idx_s = _hits(τ, σ) + + @assert hits <= 3 + + hits == 3 && return SauterSchwab3D.CommonFace5D_S(SauterSchwab3D.Singularity5DFace(idx_s,idx_t),(qd.sing_qp[1],qd.sing_qp[2],qd.sing_qp[3])) + hits == 2 && return SauterSchwab3D.CommonEdge5D_S(SauterSchwab3D.Singularity5DEdge(idx_s,idx_t),(qd.sing_qp[1],qd.sing_qp[2],qd.sing_qp[3])) + hits == 1 && return SauterSchwab3D.CommonVertex5D_S(SauterSchwab3D.Singularity5DPoint(idx_s,idx_t),(qd.sing_qp[3],qd.sing_qp[2])) + #= + #Classic tensor-product quadrature rules + hits == 3 && return SauterSchwab3D.CommonFace5D(SauterSchwab3D.Singularity5DFace(idx_s,idx_t),qd.sing_qp[1]) + hits == 2 && return SauterSchwab3D.CommonEdge5D(SauterSchwab3D.Singularity5DEdge(idx_s,idx_t),qd.sing_qp[1]) + hits == 1 && return SauterSchwab3D.CommonVertex5D(SauterSchwab3D.Singularity5DPoint(idx_s,idx_t),qd.sing_qp[1]) + =# + + return DoubleQuadRule(qd[1][1,i], qd[2][1,j]) +end + + +""" +tetrahedron-triangle quadrule for the 5D integral ∫∫∫_Ω ∫∫_Γ +""" +function quadrule(op::BoundaryOperator, g::RefSpace, f::RefSpace, + i, τ::CompScienceMeshes.Simplex{<:Any, 3, <:Any, 4}, + j, σ::CompScienceMeshes.Simplex{<:Any, 2, <:Any, 3}, + qd, qs::SauterSchwab3DQStrat) + + hits, idx_t, idx_s = _hits(τ, σ) + + @assert hits <= 3 + + hits == 3 && return SauterSchwab3D.CommonFace5D_S(SauterSchwab3D.Singularity5DFace(idx_t,idx_s),(qd.sing_qp[1],qd.sing_qp[2],qd.sing_qp[3])) + hits == 2 && return SauterSchwab3D.CommonEdge5D_S(SauterSchwab3D.Singularity5DEdge(idx_t,idx_s),(qd.sing_qp[1],qd.sing_qp[2],qd.sing_qp[3])) + hits == 1 && return SauterSchwab3D.CommonVertex5D_S(SauterSchwab3D.Singularity5DPoint(idx_t,idx_s),(qd.sing_qp[3],qd.sing_qp[2])) + #= + #Classic tensor-product quadrature rules + hits == 3 && return SauterSchwab3D.CommonFace5D(SauterSchwab3D.Singularity5DFace(idx_t,idx_s),qd.sing_qp[1]) + hits == 2 && return SauterSchwab3D.CommonEdge5D(SauterSchwab3D.Singularity5DEdge(idx_t,idx_s),qd.sing_qp[1]) + hits == 1 && return SauterSchwab3D.CommonVertex5D(SauterSchwab3D.Singularity5DPoint(idx_t,idx_s),qd.sing_qp[1]) + =# + + return DoubleQuadRule(qd[1][1,i], qd[2][1,j]) +end + + diff --git a/src/volumeintegral/vsie.jl b/src/volumeintegral/vsie.jl new file mode 100644 index 00000000..9947f567 --- /dev/null +++ b/src/volumeintegral/vsie.jl @@ -0,0 +1,236 @@ +module VSIE + using ..BEAST + Mod = BEAST + + """ + singlelayer(;gamma, alpha, beta, tau) + singlelayer(;wavenumber, alpha, beta, tau) + + Bilinear form given by: + + ```math + α ∬_{Γ×Ω} j(x)⋅τ(y)⋅k(y) G_{γ}(x,y) + β ∬_{Γ×Ω} div j(x) τ(y) div k(y) G_{γ}(x,y) + ``` + + with ``G_{γ} = e^{-γ|x-y|} / 4π|x-y|`` + and ``τ(y)`` contrast dyadic + """ + + function singlelayer(; + gamma=nothing, + wavenumber=nothing, + alpha=nothing, + beta=nothing, + tau=nothing) + + if (gamma == nothing) && (wavenumber == nothing) + error("Supply one of (not both) gamma or wavenumber") + end + + if (gamma != nothing) && (wavenumber != nothing) + error("Supply one of (not both) gamma or wavenumber") + end + + if gamma == nothing + if iszero(real(wavenumber)) + gamma = -imag(wavenumber) + else + gamma = im*wavenumber + end + end + + @assert gamma != nothing + + alpha == nothing && (alpha = -gamma) + beta == nothing && (beta = -1/gamma) + tau == nothing && (tau = x->1.0) + + Mod.VSIESingleLayer(gamma, alpha, beta, tau) + end + + function singlelayer2(; + gamma=nothing, + wavenumber=nothing, + alpha=nothing, + beta=nothing, + tau=nothing) + + if (gamma == nothing) && (wavenumber == nothing) + error("Supply one of (not both) gamma or wavenumber") + end + + if (gamma != nothing) && (wavenumber != nothing) + error("Supply one of (not both) gamma or wavenumber") + end + + if gamma == nothing + if iszero(real(wavenumber)) + gamma = -imag(wavenumber) + else + gamma = im*wavenumber + end + end + + @assert gamma != nothing + + alpha == nothing && (alpha = -gamma) + beta == nothing && (beta = -1/gamma) + tau == nothing && (tau = x->1.0) + + Mod.VSIESingleLayer2(gamma, alpha, beta, tau) + end + + """ + boundary(;gamma, alpha, tau) + boundary(;wavenumber, alpha, tau) + + Bilinear form given by: + + ```math + α ∬_{Γ×Ω} T_n j(x) grad G_{γ}(x,y)⋅τ(y) div k(y) + ``` + + with ``G_{γ} = e^{-γ|x-y|} / 4π|x-y|`` + and ``τ(y)`` contrast dyadic + """ + + function boundary(; + gamma=nothing, + wavenumber=nothing, + alpha=nothing, + tau=nothing) + + if (gamma == nothing) && (wavenumber == nothing) + error("Supply one of (not both) gamma or wavenumber") + end + + if (gamma != nothing) && (wavenumber != nothing) + error("Supply one of (not both) gamma or wavenumber") + end + + if gamma == nothing + if iszero(real(wavenumber)) + gamma = -imag(wavenumber) + else + gamma = im*wavenumber + end + end + + @assert gamma != nothing + + alpha == nothing && (alpha = 1.0) + tau == nothing && (tau = x->1.0) + + Mod.VSIEBoundary(gamma, alpha, tau) + end + + function boundaryT(; + gamma=nothing, + wavenumber=nothing, + alpha=nothing, + tau=nothing) + + if (gamma == nothing) && (wavenumber == nothing) + error("Supply one of (not both) gamma or wavenumber") + end + + if (gamma != nothing) && (wavenumber != nothing) + error("Supply one of (not both) gamma or wavenumber") + end + + if gamma == nothing + if iszero(real(wavenumber)) + gamma = -imag(wavenumber) + else + gamma = im*wavenumber + end + end + + @assert gamma != nothing + + alpha == nothing && (alpha = 1.0) + tau == nothing && (tau = x->1.0) + + Mod.VSIEBoundaryT(gamma, alpha, tau) + end + + + + """ + doublelayer(;gamma, alpha, beta, tau) + doublelayer(;wavenumber, alpha, beta, tau) + + Bilinear form given by: + + ```math + α ∬_{Ω×Γ} j(x) ⋅ grad G_{γ}(x,y) × τ(y)⋅k(y) + ``` + + with ``G_{γ} = e^{-γ|x-y|} / 4π|x-y|`` + and ``τ(y)`` contrast dyadic + """ + + function doublelayer(; + gamma=nothing, + wavenumber=nothing, + alpha=nothing, + beta=nothing, + tau=nothing) + + if (gamma == nothing) && (wavenumber == nothing) + error("Supply one of (not both) gamma or wavenumber") + end + + if (gamma != nothing) && (wavenumber != nothing) + error("Supply one of (not both) gamma or wavenumber") + end + + if gamma == nothing + if iszero(real(wavenumber)) + gamma = -imag(wavenumber) + else + gamma = im*wavenumber + end + end + + @assert gamma != nothing + + alpha == nothing && (alpha = 1.0) + tau == nothing && (tau = x->1.0) + + Mod.VSIEDoubleLayer(gamma, alpha, tau) + end + + + function doublelayerT(; + gamma=nothing, + wavenumber=nothing, + alpha=nothing, + beta=nothing, + tau=nothing) + + if (gamma == nothing) && (wavenumber == nothing) + error("Supply one of (not both) gamma or wavenumber") + end + + if (gamma != nothing) && (wavenumber != nothing) + error("Supply one of (not both) gamma or wavenumber") + end + + if gamma == nothing + if iszero(real(wavenumber)) + gamma = -imag(wavenumber) + else + gamma = im*wavenumber + end + end + + @assert gamma != nothing + + alpha == nothing && (alpha = 1.0) + tau == nothing && (tau = x->1.0) + + Mod.VSIEDoubleLayerT(gamma, alpha, tau) + end + +end \ No newline at end of file diff --git a/src/volumeintegral/vsieops.jl b/src/volumeintegral/vsieops.jl new file mode 100644 index 00000000..7338e7d3 --- /dev/null +++ b/src/volumeintegral/vsieops.jl @@ -0,0 +1,338 @@ +abstract type VSIEOperator <: IntegralOperator end +abstract type VolumeSurfaceOperator <: VSIEOperator end +abstract type BoundarySurfaceOperator <: VSIEOperator end +abstract type VSIEOperatorT <: VSIEOperator end +abstract type VolumeSurfaceOperatorT <: VolumeSurfaceOperator end +abstract type BoundarySurfaceOperatorT <: BoundarySurfaceOperator end + +struct KernelValsVSIE{T,U,P,Q,K} + gamma::U + vect::P + dist::T + green::U + gradgreen::Q + tau::K +end + +function kernelvals(viop::VSIEOperator, p ,q) + Y = viop.gamma; + r = cartesian(p)-cartesian(q) + R = norm(r) + yR = Y*R + + expn = exp(-yR) + green = expn / (4*pi*R) + gradgreen = - (Y +1/R)*green/R*r + + tau = viop.tau(cartesian(q)) + + KernelValsVSIE(Y,r,R, green, gradgreen,tau) +end + +struct VSIESingleLayer{T,U,P} <: VolumeSurfaceOperator + gamma::T + α::U + β::U + tau::P +end + +struct VSIEBoundary{T,U,P} <: BoundarySurfaceOperator + gamma::T + α::U + tau::P +end + +struct VSIEDoubleLayer{T,U,P} <: VolumeSurfaceOperator + gamma::T + α::U + tau::P +end + +#= +struct VSIESingleLayer2{T,U,P} <: VolumeSurfaceOperator + gamma::T + α::U + β::U + tau::P +end + +struct VSIEBoundaryT{T,U,P} <: BoundarySurfaceOperatorT + gamma::T + α::U + tau::P +end + +struct VSIEDoubleLayerT{T,U,P} <: VolumeSurfaceOperatorT + gamma::T + α::U + tau::P +end +=# +scalartype(op::VSIEOperator) = typeof(op.gamma) + +export VSIE + +struct VSIEIntegrand{S,T,O,K,L} + test_tetrahedron_element::S + trial_triangle_element::T + op::O + test_local_space::K + trial_local_space::L +end + +#= +struct VSIEIntegrandT{S,T,O,K,L} + test_tetrahedron_element::S + trial_triangle_element::T + op::O + test_local_space::K + trial_local_space::L +end +=# + +function (igd::VSIEIntegrand)(u,v) + + #mesh points + tgeo = neighborhood(igd.test_tetrahedron_element,v) + bgeo = neighborhood(igd.trial_triangle_element,u) + + #kernel values + kerneldata = kernelvals(igd.op,tgeo,bgeo) + + #values & grad/div/curl of local shape functions + tval = igd.test_local_space(tgeo) + bval = igd.trial_local_space(bgeo) + + #jacobian + j = jacobian(tgeo) * jacobian(bgeo) + + integrand(igd.op, kerneldata,tval,tgeo,bval,tgeo) * j +end + + +#= +function (igd::VSIEIntegrandT)(u,v) + + #mesh points + tgeo = neighborhood(igd.test_tetrahedron_element,u) + bgeo = neighborhood(igd.trial_triangle_element,v) + + #kernel values + kerneldata = kernelvals(igd.op,tgeo,bgeo) + + #values & grad/div/curl of local shape functions + tval = igd.test_local_space(tgeo) + bval = igd.trial_local_space(bgeo) + + #jacobian + j = jacobian(tgeo) * jacobian(bgeo) + + integrand(igd.op, kerneldata,tval,tgeo,bval,tgeo) * j +end +=# + + +function integrand(viop::VSIESingleLayer, kerneldata, tvals, tgeo, bvals, bgeo) + + gx = @SVector[tvals[i].value for i in 1:3] + fy = @SVector[bvals[i].value for i in 1:4] + + dgx = @SVector[tvals[i].divergence for i in 1:3] + dfy = @SVector[bvals[i].divergence for i in 1:4] + + G = kerneldata.green + + Ty = kerneldata.tau + + α = viop.α + β = viop.β + + @SMatrix[α * dot(gx[i],Ty*fy[j]) * G + β * (dgx[i] * Ty*dfy[j])*G for i in 1:3, j in 1:4] +end + +function integrand(viop::VSIEBoundary, kerneldata, tvals, tgeo, bvals, bgeo) + + dgx = @SVector[tvals[i].divergence for i in 1:3] + fy = @SVector[bvals[i].value for i in 1:1] + + G = kerneldata.green + + Ty = kerneldata.tau + + α = viop.α + + @SMatrix[α * Ty * dgx[i] * fy[j] * G for i in 1:3, j in 1:1] +end + +#= +function integrand(viop::VSIESingleLayer2, kerneldata, tvals, tgeo, bvals, bgeo) + + gx = @SVector[tvals[i].value for i in 1:3] + fy = @SVector[bvals[i].value for i in 1:4] + + dgx = @SVector[tvals[i].divergence for i in 1:3] + dfy = @SVector[bvals[i].divergence for i in 1:4] + + G = kerneldata.green + + Ty = kerneldata.tau + + α = viop.α + β = viop.β + + @SMatrix[α * dot(gx[i],Ty*fy[j]) * G - β * (dgx[i] * Ty*dfy[j])*G for i in 1:3, j in 1:4] +end + + +function integrand(viop::VSIEBoundaryT, kerneldata, tvals, tgeo, bvals, bgeo) + + gx = @SVector[tvals[i].value for i in 1:1] + dfy = @SVector[bvals[i].divergence for i in 1:3] + + G = kerneldata.green + + Tx = kerneldata.tau + + α = viop.α + + @SMatrix[α * Tx * gx[i] * dfy[j] * G for i in 1:3, j in 1:1] +end +=# + +function integrand(viop::VSIEDoubleLayer, kerneldata, tvals, tgeo, bvals, bgeo) + gx = @SVector[tvals[i].value for i in 1:3] + fy = @SVector[bvals[i].value for i in 1:4] + + gradG = kerneldata.gradgreen + + Ty = kerneldata.tau + + α = viop.α + + @SMatrix[α * dot(cross(Ty*fy[j],gx[i]),gradG) for i in 1:3, j in 1:4] +end + +#= +function integrand(viop::VSIEDoubleLayerT, kerneldata, tvals, tgeo, bvals, bgeo) + gx = @SVector[tvals[i].value for i in 1:4] + fy = @SVector[bvals[i].value for i in 1:3] + + + gradG = kerneldata.gradgreen + + Ty = kerneldata.tau + + α = viop.α + + @SMatrix[α * transpose(cross(fy[j],gx[i])) *gradG for i in 1:4, j in 1:3] +end +=# + +defaultquadstrat(op::VSIEOperator, tfs, bfs) = SauterSchwab3DQStrat(3,3,3,3,3,3) + + +function quaddata(op::VSIEOperator, + test_local_space::RefSpace, trial_local_space::RefSpace, + test_charts, trial_charts, qs::SauterSchwab3DQStrat) + + #The combinations of rules (6,7) and (5,7 are) BAAAADDDD + # they result in many near singularity evaluations with any + # resemblence of accuracy going down the drain! Simply don't! + # (same for (5,7) btw...). + t_qp = quadpoints(test_local_space, test_charts, (qs.outer_rule)) + b_qp = quadpoints(trial_local_space, trial_charts, (qs.inner_rule)) + + + sing_qp = (SauterSchwab3D._legendre(qs.sauter_schwab_1D,0,1), + SauterSchwab3D._shunnham2D(qs.sauter_schwab_2D), + SauterSchwab3D._shunnham3D(qs.sauter_schwab_3D), + SauterSchwab3D._shunnham4D(qs.sauter_schwab_4D),) + + + return (tpoints=t_qp, bpoints=b_qp, sing_qp=sing_qp) +end + +quadrule(op::VolumeSurfaceOperator, g::RefSpace, f::RefSpace, i, τ, j, σ, qd, qs) = qr_volume(op, g, f, i, τ, j, σ, qd, qs) + + +function qr_volume(op::VolumeSurfaceOperator, g::RefSpace, f::RefSpace, i, τ, j, σ, qd, + qs::SauterSchwab3DQStrat) + + @assert (length(τ.vertices)==3 && length(σ.vertices)==4) "Expected simplex wrong" + + dtol = 1.0e3 * eps(eltype(eltype(τ.vertices))) + + hits = 0 + idx_t = Int64[] + idx_s = Int64[] + sizehint!(idx_t,4) + sizehint!(idx_s,4) + dmin2 = floatmax(eltype(eltype(τ.vertices))) + D = dimension(τ)+dimension(σ) + for (i,t) in enumerate(τ.vertices) + for (j,s) in enumerate(σ.vertices) + d2 = LinearAlgebra.norm_sqr(t-s) + dmin2 = min(dmin2, d2) + if d2 < dtol + push!(idx_t,j) + push!(idx_s,i) + hits +=1 + break + end + end + end + + #singData = SauterSchwab3D.Singularity{D,hits}(idx_t, idx_s ) + + hits == 3 && return SauterSchwab3D.CommonFace5D_S(SauterSchwab3D.Singularity5DFace(idx_t,idx_s),(qd.sing_qp[1],qd.sing_qp[2],qd.sing_qp[3])) + hits == 2 && return SauterSchwab3D.CommonEdge5D_S(SauterSchwab3D.Singularity5DEdge(idx_t,idx_s),(qd.sing_qp[1],qd.sing_qp[2],qd.sing_qp[3])) + hits == 1 && return SauterSchwab3D.CommonVertex5D_S(SauterSchwab3D.Singularity5DPoint(idx_t,idx_s),(qd.sing_qp[3],qd.sing_qp[2])) + #hits == 0 && return SauterSchwab3D.PositiveDistance5D_S(SauterSchwab3D.Singularity5DPositiveDistance(),(qd.sing_qp[3],qd.sing_qp[2])) + + return DoubleQuadRule( + qd[1][1,i], + qd[2][1,j]) + +end + +quadrule(op::BoundarySurfaceOperator, g::RefSpace, f::RefSpace, i, τ, j, σ, qd, qs) = qr_boundary(op, g, f, i, τ, j, σ, qd, qs) + +function qr_boundary(op::BoundarySurfaceOperator, g::RefSpace, f::RefSpace, i, τ, j, σ, qd, + qs::SauterSchwab3DQStrat) + + dtol = 1.0e3 * eps(eltype(eltype(τ.vertices))) + + hits = 0 + idx_t = Int64[] + idx_s = Int64[] + sizehint!(idx_t,4) + sizehint!(idx_s,4) + dmin2 = floatmax(eltype(eltype(τ.vertices))) + D = dimension(τ)+dimension(σ) + for (i,t) in enumerate(τ.vertices) + for (j,s) in enumerate(σ.vertices) + d2 = LinearAlgebra.norm_sqr(t-s) + dmin2 = min(dmin2, d2) + if d2 < dtol + push!(idx_t,i) + push!(idx_s,j) + hits +=1 + break + end + end + end + + #singData = SauterSchwab3D.Singularity{D,hits}(idx_t, idx_s ) + + + hits == 3 && return SauterSchwab3D.CommonFace4D_S(SauterSchwab3D.Singularity4DFace(idx_t,idx_s),(qd.sing_qp[1],qd.sing_qp[3])) + hits == 2 && return SauterSchwab3D.CommonEdge4D_S(SauterSchwab3D.Singularity4DEdge(idx_t,idx_s),(qd.sing_qp[1],qd.sing_qp[2])) + hits == 1 && return SauterSchwab3D.CommonVertex4D_S(SauterSchwab3D.Singularity4DPoint(idx_t,idx_s),(qd.sing_qp[2])) + + + return DoubleQuadRule( + qd[1][1,i], + qd[2][1,j]) + +end + diff --git a/test/Project.toml b/test/Project.toml new file mode 100644 index 00000000..406bc7ba --- /dev/null +++ b/test/Project.toml @@ -0,0 +1,19 @@ +[deps] +BlockArrays = "8e7c35d0-a365-5155-bbbb-fb81a777f24e" +CollisionDetection = "2b5bf9a6-f3f8-5352-af9c-82bb4af718d8" +Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa" +CompScienceMeshes = "3e66a162-7b8c-5da0-b8f8-124ecd2c3ae1" +DelimitedFiles = "8bb1440f-4735-579b-a4ab-409b98df4dab" +Distributed = "8ba89e20-285c-5b6f-9357-94700520ee1b" +LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" +LinearMaps = "7a12625a-238d-50fd-b39a-03d52299707e" +Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" +SauterSchwabQuadrature = "535c7bfe-2023-5c1d-b712-654ef9d93a38" +SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" +SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b" +SphericalScattering = "1a9ea918-b599-4f1f-bd9a-d681e8bb5b3e" +StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" +TestItemRunner = "f8b46487-2199-4994-9208-9a1283c18c0a" +TestItems = "1c621080-faea-4a02-84b6-bbd5e436b8fe" +WiltonInts84 = "a3e2863e-c0ee-5ff6-a523-307a4cdc8724" diff --git a/test/assets/sphere45.in b/test/assets/sphere45.in new file mode 100644 index 00000000..f1d63191 --- /dev/null +++ b/test/assets/sphere45.in @@ -0,0 +1,323 @@ +1 +109 212 +0.0 0.0 0.0 +1.0 0.0 0.0 +0.0 1.0 0.0 +-1.0 0.0 0.0 +0.0 -1.0 0.0 +0.0 0.0 1.0 +0.0 0.0 -1.0 +0.923879532082714 0.3826834333997559 0.0 +0.7071067795767625 0.7071067827963327 0.0 +0.3826834312295727 0.9238795329816333 0.0 +-0.3826834333997559 0.923879532082714 0.0 +-0.7071067827963327 0.7071067795767625 0.0 +-0.9238795329816333 0.3826834312295727 0.0 +-0.923879532082714 -0.3826834333997559 0.0 +-0.7071067795767625 -0.7071067827963327 0.0 +-0.3826834312295727 -0.9238795329816333 0.0 +0.3826834333997559 -0.923879532082714 0.0 +0.7071067827963327 -0.7071067795767625 0.0 +0.9238795329816333 -0.3826834312295727 0.0 +0.0 0.3826834333997559 0.923879532082714 +0.0 0.7071067827963327 0.7071067795767625 +0.0 0.9238795329816333 0.3826834312295727 +0.0 0.923879532082714 -0.3826834333997559 +0.0 0.7071067795767625 -0.7071067827963327 +0.0 0.3826834312295727 -0.9238795329816333 +0.0 -0.3826834333997559 -0.923879532082714 +0.0 -0.7071067827963327 -0.7071067795767625 +0.0 -0.9238795329816333 -0.3826834312295727 +0.0 -0.923879532082714 0.3826834333997559 +0.0 -0.7071067795767625 0.7071067827963327 +0.0 -0.3826834312295727 0.9238795329816333 +0.7712990324977224 -0.5261836316964337 -0.3580901956251096 +0.7832272477767324 0.5233357175207212 -0.3356706795464225 +0.3356706794722104 -0.183771065508847 -0.923879532419897 +0.3298749710844383 0.7884309806178305 -0.5191908052479812 +0.3364914460521806 -0.5356371469604048 -0.7745103960114611 +0.4943952293272733 -0.7818671890563442 -0.3798118690820019 +0.6320232753776562 -0.3163329681330331 -0.7074461341000068 +0.9179912585278028 0.1751715968730272 -0.3558187191752205 +0.7153911295286901 0.2972003361018707 -0.632366580404594 +0.9247531464560557 -0.2034408415038567 -0.3216262460183055 +0.3182620562185089 0.5161949160681655 -0.7951427998774474 +0.2034339032461793 0.191010725638466 -0.9602757675277923 +0.3318408257257821 -0.7290341394220538 -0.5986575731908655 +0.5143125430732531 0.08759839546161578 -0.8531173009323357 +0.5429324605256425 -0.5927866926067963 -0.5948346664207352 +0.7870222775749804 0.001326899353937282 -0.6169231507560687 +0.5709878661731511 0.6078735380892875 -0.5517813139041982 +0.5481592105556712 0.79368928263904 -0.2637779416609913 +0.2458184845179587 0.9376281485435064 -0.2458184853264101 +0.3356706784848729 -0.1837710644888045 0.9238795329815229 +0.3808964856541316 0.5323383179218478 0.7559985333904224 +0.9238795324198404 0.1837710655050317 0.3356706794744551 +0.7981217874283407 -0.4624241295859055 0.3862195448302793 +0.3239596594514724 -0.7578760046838178 0.5662809378502687 +0.3379819903774152 0.7843488125142359 0.5201587397016689 +0.7746503893874388 0.54404208556427 0.3223894901462653 +0.6762424833857329 0.3096909027609308 0.6684217593790739 +0.7952068118384303 -0.1011561765969377 0.5978407432937709 +0.9361182415423337 -0.1795770587008401 0.3023817419092033 +0.5777467983327468 -0.4173250611831379 0.7014616385268339 +0.2994166271545614 0.2150136179002493 0.9295799199105109 +0.5623828314586349 -0.7723464303375097 0.2953075387210339 +0.2757249103344169 -0.5179234431073846 0.809772240139701 +0.577313184287358 -0.02857822908601 0.8160225315947462 +0.5386170963264684 0.795865112582042 0.2765688813290775 +0.5745678182057902 0.6089374928036108 0.5468701419352591 +0.2640750467215406 0.9279472313776503 0.2630176904270309 +0.26349397886833 -0.9286880418750045 0.2609778610888406 +-0.7782808169164066 -0.5197822937894205 -0.3522858740894094 +-0.7832272498784564 0.5233357150134625 -0.335670678551431 +-0.3356706794739734 -0.1837710655065382 -0.9238795324197158 +-0.5233357175261585 0.7832272477702242 -0.3356706795531307 +-0.3223894903743268 -0.5440420859519188 -0.7746503890202773 +-0.54510520194119 0.5451051986321889 -0.636962040659266 +-0.7050491528145805 0.3255910057135039 -0.629996975479971 +-0.9313698262072214 -0.2067000597440215 -0.2997087455056128 +-0.769142567051783 -0.2521831216418175 -0.5872166420563893 +-0.4625858909497461 -0.7952774519371975 -0.3918520995662882 +-0.5066801882500737 0.3013212963140176 -0.8077627518167138 +-0.5595946567772138 -0.5549240882847868 -0.6155591574720422 +-0.2885513214281583 0.1343905017269157 -0.9479859323574646 +-0.6326688931838378 0.01593457962769268 -0.7742584586361497 +-0.294002442557577 0.5074470400059689 -0.8099753486121409 +-0.2206785158701794 -0.8109710613787934 -0.5418735371278878 +-0.9037335009357622 0.1116491840495477 -0.4132798313340016 +-0.2761954901451649 0.7695834710164464 -0.5757233123313235 +-0.2657595433294145 0.9261685617758262 -0.2675512293140113 +-0.5610283527690639 -0.3131152481702204 -0.7662936961456306 +-0.8986511462892972 -0.2234519520144048 0.3774855525896111 +-0.7832272498729063 0.5233357150155545 0.3356706785611194 +-0.4063938126531793 0.1675901986980527 0.898196857229839 +-0.3862195463776329 -0.4624241289548164 0.7981217870452071 +-0.5163412162948556 0.7883957432990804 0.3344008078684016 +-0.3223894901399288 0.5440420855535103 0.7746503893976325 +-0.5613920645927912 -0.7538101640409378 0.3414808726719812 +-0.6259579262136886 0.3574127511108542 0.6931325991133507 +-0.5654909040789513 -0.2106776780151426 0.7973925967740848 +-0.2733654792854793 -0.2010368837014314 0.9406675747184216 +-0.6948473160149551 -0.4285218225779893 0.5775432927506401 +-0.7787080013966404 -0.546208098467257 0.3086592971702507 +-0.9346316977779511 0.1510686554600859 0.3219345443505349 +-0.2953075384818359 -0.7723464300921336 0.5623828319212242 +-0.2762115444539761 0.7961810039040885 0.5383335320529707 +-0.788420074759952 0.01466700895224715 0.6149623277599258 +-0.5809154623227699 0.5832144663068862 0.5678011200452993 +-0.2626771544471102 0.9281258472912962 0.2637861333000328 +-0.2665923219327522 -0.9241423246462652 0.2736594556815108 +-0.8002939633135487 0.3205923101786786 0.5067051834530513 +72 89 83 +78 86 83 +83 89 78 +81 89 74 +78 89 81 +78 81 70 +77 78 70 +77 86 78 +4 86 77 +4 77 14 +14 77 70 +14 70 15 +70 81 79 +70 79 15 +15 79 16 +16 79 28 +79 85 28 +16 28 5 +26 72 7 +72 82 7 +72 83 82 +74 89 72 +26 74 72 +27 74 26 +27 85 74 +74 85 81 +81 85 79 +28 85 27 +26 36 27 +36 44 27 +44 46 37 +36 46 44 +27 44 28 +28 17 5 +28 44 37 +28 37 17 +37 46 32 +18 37 32 +17 37 18 +18 32 19 +32 46 38 +38 46 36 +34 45 38 +45 47 38 +38 47 41 +41 47 39 +38 41 32 +32 41 19 +19 41 2 +2 41 39 +34 36 26 +34 38 36 +7 34 26 +25 43 7 +7 43 34 +43 45 34 +42 45 43 +42 43 25 +2 39 8 +8 39 33 +39 40 33 +39 47 40 +40 47 45 +40 48 33 +42 48 40 +40 45 42 +8 33 9 +9 49 10 +33 49 9 +48 49 33 +35 49 48 +35 50 49 +49 50 10 +10 50 3 +3 50 23 +23 50 35 +23 35 24 +35 48 42 +35 42 24 +24 42 25 +25 84 24 +84 87 24 +24 87 23 +73 87 75 +75 87 84 +75 80 76 +75 76 71 +83 86 76 +80 83 76 +82 83 80 +7 82 25 +82 84 25 +80 84 82 +75 84 80 +13 86 4 +71 86 13 +76 86 71 +12 73 71 +73 75 71 +12 71 13 +11 73 12 +11 88 73 +73 88 87 +87 88 23 +3 88 11 +23 88 3 +3 107 22 +11 107 3 +22 107 104 +104 107 94 +94 107 11 +12 94 11 +13 91 12 +91 106 94 +91 94 12 +91 109 106 +102 109 91 +13 102 91 +4 102 13 +95 97 92 +20 95 92 +20 92 6 +97 105 92 +97 109 105 +105 109 102 +106 109 97 +95 106 97 +104 106 95 +94 106 104 +22 104 21 +21 104 95 +21 95 20 +20 52 21 +52 56 21 +52 67 56 +21 56 22 +56 68 22 +22 68 3 +3 68 10 +10 68 66 +66 68 56 +56 67 66 +66 67 57 +9 66 57 +10 66 9 +9 57 8 +57 67 58 +58 67 52 +58 65 59 +62 65 58 +58 59 53 +57 58 53 +8 57 53 +8 53 2 +20 62 52 +52 62 58 +51 62 6 +6 62 20 +31 51 6 +51 65 62 +51 64 61 +31 64 51 +53 60 2 +2 60 19 +19 60 54 +54 60 59 +59 60 53 +61 65 51 +59 65 61 +59 61 54 +55 63 61 +19 54 18 +18 63 17 +54 63 18 +61 63 54 +55 69 63 +63 69 17 +17 69 5 +5 69 29 +29 69 55 +29 55 30 +61 64 55 +55 64 30 +30 64 31 +30 103 29 +100 103 93 +93 103 30 +31 93 30 +31 99 93 +93 99 98 +98 99 92 +92 99 6 +6 99 31 +5 108 16 +29 108 5 +103 108 29 +96 108 103 +16 108 96 +96 101 15 +16 96 15 +100 101 96 +15 101 14 +14 101 90 +14 90 4 +90 102 4 +90 105 102 +90 101 100 +98 105 100 +100 105 90 +92 105 98 +98 100 93 +96 103 100 diff --git a/test/assets/sphere_rad=10_h=2.in b/test/assets/sphere_rad=10_h=2.in new file mode 100644 index 00000000..4190f2c8 --- /dev/null +++ b/test/assets/sphere_rad=10_h=2.in @@ -0,0 +1,1211 @@ +1 +405 804 +0.0 0.0 0.0 +10.0 0.0 0.0 +0.0 10.0 0.0 +-10.0 0.0 0.0 +0.0 -10.0 0.0 +0.0 0.0 10.0 +0.0 0.0 -10.0 +9.80785280299685 1.95090322536687 0.0 +9.238795320462739 3.8268343348773 0.0 +8.314696113681087 5.555702344180853 0.0 +7.071067795942936 7.071067827788014 0.0 +5.555702316559705 8.314696132136948 0.0 +3.826834312886675 9.238795329571554 0.0 +1.950903214109354 9.807852805236108 0.0 +-1.95090322536687 9.80785280299685 0.0 +-3.8268343348773 9.238795320462739 0.0 +-5.555702344180853 8.314696113681087 0.0 +-7.071067827788014 7.071067795942936 0.0 +-8.314696132136948 5.555702316559705 0.0 +-9.238795329571554 3.826834312886675 0.0 +-9.807852805236108 1.950903214109354 0.0 +-9.80785280299685 -1.95090322536687 0.0 +-9.238795320462739 -3.8268343348773 0.0 +-8.314696113681087 -5.555702344180853 0.0 +-7.071067795942936 -7.071067827788014 0.0 +-5.555702316559705 -8.314696132136948 0.0 +-3.826834312886675 -9.238795329571554 0.0 +-1.950903214109354 -9.807852805236108 0.0 +1.95090322536687 -9.80785280299685 0.0 +3.8268343348773 -9.238795320462739 0.0 +5.555702344180853 -8.314696113681087 0.0 +7.071067827788014 -7.071067795942936 0.0 +8.314696132136948 -5.555702316559705 0.0 +9.238795329571554 -3.826834312886675 0.0 +9.807852805236108 -1.950903214109354 0.0 +0.0 1.95090322536687 9.80785280299685 +0.0 3.8268343348773 9.238795320462739 +0.0 5.555702344180853 8.314696113681087 +0.0 7.071067827788014 7.071067795942936 +0.0 8.314696132136948 5.555702316559705 +0.0 9.238795329571554 3.826834312886675 +0.0 9.807852805236108 1.950903214109354 +0.0 9.80785280299685 -1.95090322536687 +0.0 9.238795320462739 -3.8268343348773 +0.0 8.314696113681087 -5.555702344180853 +0.0 7.071067795942936 -7.071067827788014 +0.0 5.555702316559705 -8.314696132136948 +0.0 3.826834312886675 -9.238795329571554 +0.0 1.950903214109354 -9.807852805236108 +0.0 -1.95090322536687 -9.80785280299685 +0.0 -3.8268343348773 -9.238795320462739 +0.0 -5.555702344180853 -8.314696113681087 +0.0 -7.071067827788014 -7.071067795942936 +0.0 -8.314696132136948 -5.555702316559705 +0.0 -9.238795329571554 -3.826834312886675 +0.0 -9.807852805236108 -1.950903214109354 +0.0 -9.80785280299685 1.95090322536687 +0.0 -9.238795320462739 3.8268343348773 +0.0 -8.314696113681087 5.555702344180853 +0.0 -7.071067795942936 7.071067827788014 +0.0 -5.555702316559705 8.314696132136948 +0.0 -3.826834312886675 9.238795329571554 +0.0 -1.950903214109354 9.807852805236108 +7.484525235800258 -3.884914731954154 -5.374878558638759 +8.640432252084782 4.616001386420454 -2.008845862105294 +1.694959659960681 2.860845128906918 -9.430942524451918 +7.576281374022892 -6.295881151996815 -1.721000017886417 +1.716699998323463 7.604323764034456 -6.263162237041051 +1.694959664352379 -6.252141737270096 -7.618256718784404 +9.80785280383656 0.9659891891984269 -1.69495966460181 +1.694959659721775 6.252141707960549 -7.618256743868375 +3.386120079414433 6.62080808349219 -6.685737889668639 +1.694959664602064 -0.9659891891983962 -9.80785280383652 +1.678517616006651 -2.888596932216308 -9.425422344698717 +4.628741954402159 -8.69433125572815 -1.727238239278489 +3.340752674331311 -1.866291263567569 -9.238848872477543 +6.458793765802624 7.399888684600718 -1.877666249988903 +3.191006637361629 7.970726703993889 -5.126889159183445 +4.884461856964811 6.810937280894969 -5.454646251002069 +4.874452501062273 5.398025373279759 -6.863019370824746 +6.34842119745199 5.426380392209262 -5.500176737050282 +6.179264675812918 3.958137776738934 -6.793366868247498 +7.491809585589161 3.899344360395977 -5.354241560886327 +7.229760271693815 2.349409388994967 -6.496987143033305 +5.778981370742834 2.350120932879847 -7.81538904453561 +6.714694636539958 0.6681765864766767 -7.380136583241931 +8.025539080989324 0.64983788898619 -5.93029789956213 +1.297918943169261 -8.693914874333723 -4.767730128142872 +9.373403397565864 -2.852202756983408 -2.001061763054043 +2.130400536751212 9.525628225140219 -2.173430576168048 +3.337980475624946 -0.04450701977387266 -9.426341043561786 +3.418835008903087 -3.651041314896926 -8.659183824057228 +5.088323258359434 -2.485065203142772 -8.2421730966152 +4.803831259521475 -4.408945460135892 -7.581847080994936 +2.216340265586997 -9.502447736670753 -2.188909052240096 +7.362819406824999 -1.032535330145154 -6.687507844816572 +8.50992197863405 -0.924511780444419 -5.169768455683545 +7.516214470580177 5.279466217989938 -3.953954537591974 +9.031229940426881 0.6274207606611665 -4.247732212867056 +4.703853507518662 7.942047134518326 -3.846771307589472 +9.295786629399853 -1.178488338118462 -3.492780579651362 +8.534912612782197 3.669564225687408 -3.699941227334102 +4.082949003212194 8.910403900783622 -1.983489289627897 +6.397535389615995 -3.404256264676478 -6.890760496710206 +6.050078815375487 -5.044946925739978 -6.159955912521735 +4.50679199659493 -5.996435354633118 -6.612986385675831 +5.547156310147217 -6.589328324745058 -5.080335530209238 +3.868205087304074 -7.418927582292483 -5.47690632850834 +4.865796494866782 -7.912727679021323 -3.703075093512411 +4.950237856754044 0.7245027537233326 -8.658535726172552 +4.384918808574644 2.55119594948837 -8.617649695219832 +6.581307863960296 -6.714431337460461 -3.406288099134203 +1.895741315324868 8.975632480878645 -3.980475654185891 +7.508645978365221 6.224342766675042 -2.208572546809337 +9.379360363764452 2.77280877972829 -2.083058001522305 +7.063701496654174 -5.452886021206812 -4.513330832754557 +8.276836523799368 -4.285653971100762 -3.623140515945948 +6.275549513143593 6.848453594682692 -3.703533673339683 +1.614017657052355 0.9926510606442275 -9.818838570550241 +1.963853131727953 -7.710740195274058 -6.057042720502084 +9.829594898705071 -0.8938976726943376 -1.606241288879998 +6.167552236300564 -7.6513143719413 -1.848969387044691 +1.474179217245597 4.637668624871222 -8.736064638115163 +4.623729262325734 3.853329286042339 -7.985798715347211 +8.530662103459711 -4.823434248502576 -1.990549202348118 +1.692419959116914 -4.65955796856348 -8.684712673403727 +3.298518568992726 -5.361089261570473 -7.770360170512301 +3.136448370998263 5.116171827811228 -7.999217301984805 +8.374065915881594 2.264035938667739 -4.974852892788996 +5.966980458298531 -0.8328576485417362 -7.98132146624536 +7.717753222218721 -5.518988501858001 -3.158647039048794 +8.582996958897814 -2.604452710607588 -4.421423897542474 +3.373606642630378 -8.944212118799896 -2.935957730403023 +9.511434314250431 0.5222788131548508 -3.042998870691376 +9.18874856174815 1.907123951500339 -3.453951085725598 +4.614340964629106 -0.8886535072375961 -8.827125942582901 +3.152371668898908 3.549517718820688 -8.801333809423518 +3.060499228520063 -6.659110884031874 -6.803645104383116 +7.661250523169691 -2.325725292532114 -5.991347284618756 +3.276073752854909 8.770281569229471 -3.514185817837325 +2.929169316224433 1.809101615716378 -9.38866968536553 +2.904121341587593 -8.462401679424488 -4.466971798591201 +3.213845291127491 -9.371847222130508 -1.356347333739524 +1.3074961364713 -9.327300160576597 -3.360346048790275 +5.301932935228361 8.106864802909042 -2.483596226783921 +1.265689599129985 -9.840279039602926 -1.251774045666988 +1.094723915244614 9.877211828949552 -1.114569888096105 +1.291192931326633 8.493646571743604 -5.117693692318861 +6.543258395339659 -2.004386637289248 -7.291653021097027 +7.589455411460426 -3.679143044266371 5.372529480354709 +8.640432252644828 4.616001390121554 2.008845851191897 +1.694959669238944 2.860845145080079 9.430942517878325 +7.618256743864916 -6.252141707968306 1.694959659708706 +6.450855885595064 7.40977662520076 1.865923017681049 +1.944258897729118 -6.224209280910443 7.581495641760236 +9.807852803836578 0.9659891891984049 1.694959664601724 +4.645760425563159 -8.69160639863923 1.694959668961131 +1.694959659664616 -0.9659891836526907 9.807852805235996 +1.678517608591775 -2.888596919755813 9.425422349837941 +1.694959664299138 6.252141737311508 7.618256718762266 +1.694959659646261 7.618256743894761 6.252141707948871 +3.348410836805147 6.662887710873175 6.662887678796713 +3.340752663294766 -1.866291257542965 9.238848877685339 +3.173818838401166 7.981791228781764 5.120340102097945 +4.884461853472326 6.81093731446191 5.454646212216159 +4.86179525369738 5.430594546821983 6.846282916966912 +6.34666572729495 5.431245799766588 5.497399676983425 +6.178698639502741 3.960147239733798 6.792710575450402 +7.493491623847692 3.885680101472035 5.361816243806642 +7.231225464333012 2.311082762545684 6.509091699203969 +5.778981392942154 2.350120976421075 7.815389015027597 +6.716669873758189 0.6629224560235365 7.37881288719594 +8.025539100298753 0.6498379366665192 5.930297868205684 +1.694959668959868 -8.691606398640305 4.645760425561607 +9.43094252445263 -2.860845128902828 1.694959659963622 +2.158837975923562 9.522543630113772 2.158837975923599 +1.655794917741912 -9.427778990068859 2.894015636584617 +3.372235135751129 0.1662498997649503 9.412778078762598 +3.312630296447432 -3.672115694066237 8.69149278596324 +4.857693151003588 -2.590754852425246 8.34810197262395 +4.688766808881241 -4.620681279858319 7.527600568700528 +7.342489751578588 -1.07042207993445 6.703882518268963 +8.539945127764579 -0.9881245848899494 5.108125587679505 +7.516325072258296 5.279000505085103 3.954366077572048 +9.031229952531442 0.6274208065770261 4.247732180349081 +4.618878064629182 7.997047975029581 3.835777510383348 +9.35401251923927 -1.102512716947698 3.359600496916807 +8.55807778731667 3.504077751378756 3.80535723665064 +3.630526799841694 -8.669484529393333 3.414573641062798 +3.309567566962856 -7.801040017605713 5.309476166574423 +5.050187332916818 -7.506677290556895 4.259742240779825 +4.706747804391623 -6.491576585321064 5.975446304901917 +6.262206837984788 -6.22968886421301 4.687829153605855 +4.08294896806108 8.91040392210775 1.983489266191214 +6.48635910743304 -2.909813141696293 7.032789845419241 +5.138054524935237 0.6512541194018134 8.554312583180176 +4.213519187994706 2.117033438066333 8.818413999949735 +6.501181305435489 -6.907052733859684 3.166585568958708 +1.895741304876226 8.975632489437164 3.980475639863435 +6.304304567705763 -4.832825007115868 6.074499680483962 +7.507276609571816 6.226747532196535 2.20644806819926 +9.379360361691266 2.772808793944486 2.083057991933696 +6.210920431397934 6.865624943702563 3.779902317157534 +1.582420592745625 -7.618157864853675 6.281688930042835 +1.614017652640312 0.9926510633828588 9.818838570998626 +9.80346529711746 -0.7536984109551983 1.823185913048167 +8.745924670883912 -2.920510881845144 3.87032528868676 +8.654891396865057 -4.195609807250448 2.73673404878426 +1.594928387346465 4.764488882519325 8.646146490060538 +4.623729279363438 3.853329325197461 7.985798686589247 +6.376427261889495 -7.538739773316453 1.583849110252942 +3.513915083648801 -5.937217793516878 7.238911911140516 +1.565327941910373 -4.700751607442105 8.686350370518397 +3.268353313412033 5.199765513252965 7.891787201001666 +8.380653272860494 2.203916657326835 4.990731618475391 +5.955560356493085 -1.002749327177809 7.970307059769556 +7.62236586211518 -5.545957317965076 3.337947886257473 +2.908322756154376 -9.423831201848772 1.65319818082894 +3.13597678594112 3.430458311375293 8.854242224603672 +7.243131812391905 -4.967762442408584 4.781043595713291 +9.228192322532836 1.770362317530412 3.421444683611643 +9.558087061835261 0.3010082260922371 2.924442812947828 +4.615138251433526 -0.9418995456048308 8.821186097466967 +5.388427711314114 -7.944601840809216 2.801454691932922 +7.813591740087231 -2.247876327543923 5.821927183957925 +3.258194796900417 8.780235813939173 3.505941487970237 +1.391501843979358 -9.804460476558825 1.391501843979356 +2.738173449283312 1.884002500997701 9.431486666367839 +8.093350344670409 -4.309767073241091 3.990437065388993 +5.301553274031107 8.118345286400222 2.446630886217478 +2.946926312522238 -5.021931343995751 8.129934248489166 +2.908276692001961 -7.000026053627124 6.522389280877033 +1.037480177520195 9.891761045438042 1.037640738024088 +1.316374247500741 8.491715151519735 5.114482635220011 +8.735480837212208 -4.72741157815794 1.158859056743549 +-5.374878556949718 3.884914724699531 -7.484525240778797 +-2.008845851620726 -4.616001387300988 -8.640432254051968 +-9.430942517878332 -2.860845145080152 -1.694959669238789 +-1.709555016031904 6.401141960527851 -7.490180454991041 +-6.252141707968359 -7.618256743864762 -1.694959659709205 +-7.618256743864769 6.252141707968363 -1.694959659709164 +-1.694959664601897 -0.9659891891983583 -9.807852803836553 +-1.694959668943105 8.691606398645341 -4.645760425558302 +-9.807852805236152 0.9659891836528632 -1.694959659663621 +-9.42542234983937 2.888596919751108 -1.678517608591853 +-7.618256718791448 -6.252141737261187 -1.69495966435359 +-6.662887678816417 -6.662887710757489 -3.348410836996143 +-9.23884887768555 1.866291257528204 -3.340752663302424 +-1.909220298133099 -7.648617081923373 -6.15252261981267 +-5.155177515825385 -7.911699573232785 -3.290768093202041 +-5.45464621211022 -6.810937314342715 -4.884461853756839 +-6.846282916917358 -5.430594546609798 -4.86179525400417 +-5.497399667441975 -5.43124579863651 -6.346665736526708 +-6.792710573928432 -3.96014723633121 -6.1786986433568 +-5.361816233406216 -3.885680094806932 -7.493491634745633 +-6.509091686476384 -2.311082751763193 -7.231225479235639 +-7.815389014874922 -2.350120976066626 -5.778981393292771 +-7.378812876720874 -0.6629224511400627 -6.716669885747898 +-5.930297867874392 -0.6498379362895301 -8.025539100574079 +-4.81249152290017 8.626415612974276 -1.557202303571266 +-2.135871451924229 2.782898569782521 -9.364482297017677 +-2.830871992426569 -9.45851564267541 -1.588283853648148 +-9.41277807919409 -0.1662498976392883 -3.372235134651516 +-8.659183837629666 3.651041298807182 -3.418834991709534 +-8.343800929731625 2.590267158576029 -4.865336791241911 +-7.59391435510929 4.365111730742949 -4.82475536637821 +-2.18641178137868 9.502440167744249 -2.218836267210626 +-6.703882501669495 1.07042208854975 -7.342489765478351 +-5.172651312904525 0.9304389119234084 -8.507523836363074 +-3.954366073916201 -5.279000500664388 -7.516325077286485 +-4.247732179956284 -0.6274208062224084 -9.031229952740826 +-3.795257787690934 -7.930211197407921 -4.765266906425845 +-3.338698267487406 1.077152084233651 -9.364445379525032 +-3.805357197840756 -3.50407774708571 -8.558077806331294 +-7.038553059469726 2.890760589124219 -6.488626514555723 +-6.193566453258985 4.92658005685032 -6.112981558332203 +-6.602033182364537 6.031000534397245 -4.476627124639455 +-5.086067527184662 6.574971429234599 -5.558926858096481 +-5.416125392709582 7.424104052501769 -3.943128801861953 +-3.68455843522508 7.911924204552873 -4.881135576771256 +-8.554312583008146 -0.6512541190076759 -5.138054525271608 +-8.818413995692211 -2.117033448829829 -4.213519191497224 +-3.390889515184185 6.734930818427901 -6.56830078230314 +-2.12167074310682 -6.29319756563519 -7.476240877454918 +-2.083057991425826 -2.772808793931354 -9.379360361807938 +-4.513330862451521 5.45288600419915 -7.063701490808601 +-3.60530195997109 4.269328628110605 -8.29304713857481 +-3.706521147702986 -6.796479171732051 -6.330053068485519 +-1.913660425762787 -8.748013728102624 -4.450860544635995 +-3.408096955687423 -8.843674175376385 -3.189718203919417 +-4.597190921082289 -8.7163979381629 -1.700071356945966 +-9.818838571001642 -0.9926510633839429 -1.614017652621297 +-1.89571991462049 0.6803462789504269 -9.795068909713171 +-8.646146483175038 -4.764488898128167 -1.594928378045049 +-7.985798686544285 -3.853329324894388 -4.623729279693669 +-1.930781601493908 7.65396537125016 -6.139128317850673 +-6.280417806791561 7.652711209047571 -1.411511007060566 +-1.995887730336125 4.840996886254995 -8.519458980191356 +-3.000396984770823 5.628306183414012 -7.701934006437716 +-8.684712687221438 4.659557948511561 -1.692419943417625 +-7.753397736237208 5.388924829647754 -3.293070409832998 +-7.891787212241205 -5.199765521593527 -3.268353273003606 +-4.990731574862538 -2.20391663976237 -8.380653303451266 +-7.970307060470303 1.002749316779279 -5.955560357306097 +-1.552135166463686 -9.462757236594953 -2.836741423939901 +-4.137957569339338 2.588283725704439 -8.728006330749356 +-8.854242223403944 -3.430458329637661 -3.135976769351201 +-2.914381321259444 8.946380502785924 -3.386540626312626 +-3.421444644315994 -1.770362310435668 -9.228192338463167 +-6.666706698525027 6.925671604111411 -2.755012636603763 +-2.927967445363524 -0.3123110829432801 -9.55664524958221 +-8.820585902110437 0.9413905837454912 -4.616389077225394 +-5.808140882539739 2.297003518379107 -7.809563004747737 +-1.324292291726 -9.823397907221668 -1.321780421427118 +-9.431486671181649 -1.884002505928987 -2.738173429309438 +-4.366546817555308 8.466696076018525 -3.041106122191419 +-1.307496135174042 9.3273001550238 -3.360346064707906 +-3.321772318766406 9.336591585814478 -1.339360377932518 +-5.572828560608213 7.880564016400061 -2.615395308072933 +-1.269530636930449 9.839706960241353 -1.252381291170076 +-5.374878571566752 -3.884914728589924 7.484525228262488 +-2.008845863581305 4.616001384934471 8.640432252535478 +-9.430942524451918 2.860845128906918 1.69495965996068 +-1.70955498874268 -6.401141969409561 7.490180453629172 +-6.252141737270282 7.618256718784315 1.694959664352093 +-7.618256718784404 -6.252141737270096 1.694959664352379 +-1.69495966460181 0.9659891891984264 9.80785280383656 +-7.618256743868375 6.252141707960551 1.694959659721778 +-6.654981233774063 6.647402589825604 3.394593287404972 +-9.80785280383652 -0.9659891891983954 1.694959664602063 +-9.42542234496626 -2.888596931563689 1.678517615627424 +-1.694959668945985 -8.6916063986437 4.645760425560322 +-9.238848873163763 -1.866291263694047 3.340752672362914 +-1.909220315490329 7.648617077812533 6.152522619536921 +-5.155177552511963 7.911699545659703 3.290768102021899 +-5.454646251271448 6.810937280957861 4.884461856576285 +-6.846282950747401 5.430594510499585 4.86179524670026 +-5.497399707889391 5.431245751808068 6.34666574156576 +-6.792710608820068 3.960147196370969 6.178698630609722 +-5.361816279700077 3.885680052190198 7.493491623719507 +-6.509091726741612 2.311082707663549 7.23122545708557 +-7.815389043480207 2.350120935846277 5.778981370963793 +-7.378812911152689 0.6629224079169684 6.71666985218776 +-5.930297901601751 0.6498378943651693 8.025539079046647 +-4.812491514407762 -8.626415617087982 1.557202307028167 +-2.135871455394768 -2.7828985752925 9.364482294588678 +-2.830872023726072 9.458515632972894 1.588283855641814 +-9.412778078830796 0.1662498889508181 3.372235136093906 +-8.659183828565725 -3.651041318719097 3.418834993402245 +-8.343800927351758 -2.590267176638686 4.865336785706843 +-7.593914345026667 -4.365111744718054 4.82475536960401 +-2.186411775616083 -9.502440171126622 2.218836258403577 +-6.703882533200341 -1.070422129426041 7.342489730730744 +-5.172651353822996 -0.9304389445121214 8.507523807920158 +-3.954366119405836 5.279000466702863 7.51632507720669 +-4.24773221861931 0.6274207654099319 9.031229937391469 +-3.795257830268208 7.930211169315793 4.765266919265583 +-3.338698303533497 -1.077152122803012 9.364445362237078 +-3.80535726344807 3.504077713141948 8.558077791057109 +-7.038553069424285 -2.890760612904486 6.4886264931631 +-6.193566447483134 -4.92658007174417 6.112981552180928 +-6.602033163698644 -6.031000545197974 4.476627137616557 +-5.086067507866664 -6.574971435576019 5.558926868270748 +-5.416125391182664 -7.424104045462543 3.94312881721269 +-3.684558432165985 -7.91192420829659 4.881135573012175 +-8.55431260448588 0.6512540795657944 5.138054494512772 +-8.82247724879782 2.153466988687883 4.186463319208277 +-3.39088948388111 -6.734930828101727 6.568300788544132 +-2.121670775456225 6.293197587071103 7.476240850230643 +-2.083058005690916 2.772808780871042 9.379360362500815 +-4.513330832681101 -5.452886029698087 7.063701490146192 +-3.605301956681974 -4.269328629989363 8.293047139037514 +-3.706521205278666 6.796479128819655 6.330053080846827 +-1.913660453382623 8.748013718462884 4.450860551707344 +-3.408096993934809 8.843674154769358 3.189718220187639 +-4.597190959661578 8.71639791701253 1.700071361062665 +-9.818838570529465 0.9926510585536407 1.614017658464505 +-1.895719920900001 -0.6803462995499572 9.795068907067042 +-6.280417771910004 -7.652711227604057 1.411511061656903 +-1.930781594809315 -7.653965376259936 6.13912831370706 +-8.780920392686157 4.516933059837277 1.578845397832683 +-7.978123647028463 3.849559062994188 4.64009031089316 +-1.995887711159481 -4.840996887387293 8.519458984040542 +-3.000396954110874 -5.628306185706372 7.701934016706556 +-8.684712675864203 -4.659557964904648 1.692419956564349 +-7.753397726555823 -5.388924836096233 3.293070422074845 +-7.902114708597709 5.124329387745007 3.361224696157135 +-4.990731637058082 2.203916605369499 8.380653275457954 +-7.970307083189042 -1.002749332266295 5.955560324294104 +-8.863829617063514 3.378892033896668 3.16474535198921 +-1.552135189646684 9.462757231740394 2.836741427448994 +-4.13795760386107 -2.588283712108062 8.728006318414558 +-2.914381323573151 -8.946380508409229 3.386540609466159 +-3.421444705361096 1.770362287897678 9.228192320153836 +-6.666706677133172 -6.925671598523495 2.755012702415913 +-2.927967481508825 0.3123110603476493 9.556645239246427 +-8.82058591347917 -0.9413906087727384 4.616389050399381 +-5.808140907850898 -2.297003546073666 7.809562977777562 +-1.324292307962778 9.823397904854742 1.321780422750342 +-9.432270542487016 1.879936637208693 2.738267819883979 +-4.366546831208668 -8.466696073387492 3.041106109912366 +-1.307496137571986 -9.327300155131484 3.360346063475979 +-3.321772290255145 -9.336591598485487 1.33936036031507 +-5.572828542137912 -7.880564021938099 2.615395330742148 +-1.269530632042144 -9.83970696226377 1.252381280235589 +252 253 251 +252 254 253 +256 303 255 +259 303 256 +269 271 259 +271 303 259 +257 282 281 +281 304 258 +257 281 258 +258 268 259 +258 259 256 +257 258 256 +254 257 256 +254 256 255 +254 255 253 +252 295 254 +254 295 257 +307 315 282 +238 315 307 +294 307 302 +302 307 295 +295 307 282 +257 295 282 +252 302 295 +281 282 263 +282 315 263 +263 315 292 +263 292 244 +292 315 238 +22 292 238 +4 292 22 +244 292 4 +238 307 294 +22 238 23 +238 294 23 +23 294 24 +294 302 246 +24 294 246 +24 246 25 +247 252 251 +247 302 252 +246 302 247 +247 250 240 +247 251 250 +250 291 240 +25 240 26 +240 291 26 +246 247 240 +25 246 240 +27 262 28 +26 291 27 +27 291 262 +262 291 290 +290 291 250 +290 305 262 +289 290 272 +272 290 250 +251 272 250 +55 289 54 +55 305 289 +56 305 55 +289 305 290 +305 314 262 +56 314 305 +5 314 56 +28 314 5 +262 314 28 +255 270 253 +285 309 242 +50 285 242 +50 242 7 +242 293 7 +242 311 293 +309 311 242 +271 311 309 +271 309 303 +274 285 237 +274 309 285 +255 303 274 +303 309 274 +255 274 270 +270 274 237 +237 285 51 +51 285 50 +52 237 51 +53 284 52 +52 284 237 +249 284 53 +284 288 270 +249 288 284 +251 288 272 +253 288 251 +270 288 253 +237 284 270 +249 289 272 +272 288 249 +54 289 249 +54 249 53 +52 69 53 +52 126 69 +126 127 69 +69 120 53 +69 138 120 +127 138 69 +106 138 127 +106 127 94 +107 108 106 +108 138 106 +120 138 108 +108 142 120 +120 142 88 +53 120 54 +54 120 88 +54 88 55 +88 144 55 +55 144 56 +95 144 133 +142 144 88 +5 146 29 +56 146 5 +144 146 56 +95 146 144 +143 146 95 +29 146 143 +29 143 30 +133 143 95 +75 143 133 +30 143 75 +30 75 31 +107 109 108 +109 142 108 +75 133 109 +133 142 109 +133 144 142 +112 122 109 +107 112 109 +107 116 112 +116 131 112 +112 131 67 +67 122 112 +32 122 67 +75 122 31 +109 122 75 +31 122 32 +32 67 33 +67 131 125 +67 125 33 +117 131 116 +105 116 107 +105 107 106 +105 106 94 +64 139 132 +93 149 104 +93 104 94 +104 105 94 +64 105 104 +64 116 105 +104 149 139 +104 139 64 +96 149 130 +139 149 96 +96 130 86 +130 136 110 +130 149 93 +93 136 130 +132 139 97 +97 139 96 +87 97 96 +99 101 97 +99 134 101 +101 134 121 +89 132 101 +101 132 97 +64 132 117 +64 117 116 +89 125 117 +125 131 117 +117 132 89 +34 125 89 +33 125 34 +34 89 35 +89 121 35 +35 121 2 +101 121 89 +50 74 51 +74 126 51 +51 126 52 +92 126 74 +92 127 126 +94 127 92 +76 93 92 +93 94 92 +76 92 74 +76 136 93 +91 136 76 +110 136 91 +73 91 76 +73 119 91 +73 76 74 +73 74 50 +7 73 50 +7 119 73 +49 119 7 +66 119 49 +66 141 119 +119 141 91 +91 141 110 +110 141 111 +111 141 137 +137 141 66 +111 137 124 +111 124 85 +124 128 80 +124 137 128 +128 137 123 +47 123 48 +48 123 66 +48 66 49 +123 137 66 +81 98 83 +70 115 8 +70 135 115 +121 134 70 +2 121 70 +2 70 8 +8 115 9 +9 65 10 +9 115 65 +65 115 102 +115 135 102 +65 102 98 +98 102 83 +102 129 83 +102 135 129 +129 135 99 +99 135 134 +134 135 70 +87 99 97 +87 129 99 +84 129 87 +110 111 85 +86 110 85 +86 130 110 +87 96 86 +84 87 86 +84 86 85 +84 85 82 +83 84 82 +81 83 82 +81 82 80 +82 124 80 +85 124 82 +83 129 84 +79 81 80 +98 114 65 +65 114 10 +10 114 11 +11 77 12 +77 145 12 +12 145 103 +11 114 77 +114 118 77 +98 118 114 +81 118 98 +79 118 81 +100 118 79 +100 145 118 +118 145 77 +103 140 90 +113 140 78 +100 140 103 +103 145 100 +78 140 100 +78 100 79 +12 103 13 +13 103 90 +13 90 14 +90 147 14 +43 147 90 +3 147 43 +14 147 3 +78 148 113 +90 140 113 +90 113 44 +43 90 44 +113 148 44 +44 148 45 +45 148 68 +45 68 46 +68 148 78 +68 78 72 +79 80 72 +78 79 72 +80 128 72 +68 72 71 +68 71 46 +71 128 123 +71 123 47 +46 71 47 +72 128 71 +298 299 239 +47 298 239 +47 239 46 +239 296 46 +283 296 239 +45 296 243 +46 296 45 +243 296 280 +280 296 283 +280 283 278 +283 299 286 +239 299 283 +283 286 278 +286 299 287 +266 277 276 +277 278 276 +278 286 276 +306 313 236 +276 286 236 +236 313 275 +266 276 275 +275 276 236 +266 275 265 +281 312 304 +304 312 265 +275 304 265 +258 304 268 +275 313 268 +268 304 275 +268 269 259 +268 313 269 +269 313 306 +269 273 271 +269 306 273 +273 306 261 +273 311 271 +293 311 273 +7 293 49 +261 293 273 +49 293 261 +49 261 48 +48 298 47 +261 298 48 +287 298 261 +261 306 287 +287 306 236 +286 287 236 +287 299 298 +263 312 281 +248 312 263 +265 312 248 +248 263 244 +21 244 4 +21 245 244 +245 248 244 +20 245 21 +20 300 245 +19 300 20 +245 300 264 +245 264 248 +264 265 248 +264 301 266 +264 266 265 +300 301 264 +18 297 241 +297 310 241 +310 319 279 +277 279 278 +266 301 277 +277 310 279 +241 310 301 +301 310 277 +241 300 19 +241 301 300 +18 241 19 +279 319 316 +297 319 310 +260 319 297 +260 297 17 +17 297 18 +16 260 17 +16 318 260 +260 318 316 +308 318 267 +15 318 16 +316 319 260 +279 280 278 +279 316 280 +316 318 308 +280 308 243 +280 316 308 +308 317 243 +267 317 308 +44 317 43 +243 317 44 +45 243 44 +15 320 318 +318 320 267 +267 320 317 +317 320 43 +43 320 3 +3 320 15 +15 399 3 +3 399 42 +42 399 391 +391 399 347 +347 399 15 +41 374 40 +41 391 374 +42 391 41 +374 391 375 +374 375 357 +375 391 347 +375 376 335 +357 375 335 +336 357 335 +16 347 15 +347 376 375 +16 376 347 +17 376 16 +18 325 17 +325 376 17 +335 376 325 +19 328 18 +381 387 328 +19 381 328 +328 387 329 +329 337 336 +329 387 337 +329 336 335 +329 335 325 +328 329 325 +18 328 325 +381 390 387 +382 390 367 +342 382 367 +337 387 382 +387 390 382 +323 400 390 +20 381 19 +323 381 20 +21 323 20 +323 390 381 +377 400 323 +21 377 323 +4 377 21 +330 377 4 +348 377 330 +348 400 377 +390 400 367 +367 400 348 +366 367 348 +340 355 338 +355 359 322 +340 359 355 +340 388 359 +359 394 370 +359 370 322 +388 394 359 +322 370 37 +38 322 37 +37 370 36 +36 370 327 +370 394 327 +36 327 6 +327 378 6 +327 396 378 +356 396 394 +394 396 327 +356 394 388 +344 388 341 +354 356 344 +356 388 344 +342 343 341 +343 344 341 +343 353 344 +342 367 366 +366 389 343 +342 366 343 +339 382 342 +339 341 340 +339 342 341 +337 382 339 +337 339 338 +339 340 338 +341 388 340 +337 338 336 +322 369 355 +334 373 369 +369 373 355 +355 373 338 +336 373 357 +338 373 336 +334 374 357 +357 373 334 +40 374 334 +40 334 39 +334 369 39 +39 369 38 +38 369 322 +38 160 39 +38 209 160 +209 214 160 +160 161 39 +160 162 161 +160 214 162 +162 214 166 +162 165 164 +162 166 165 +162 164 161 +164 234 161 +39 161 40 +161 234 40 +40 234 41 +41 234 199 +41 176 42 +41 199 176 +199 226 176 +199 234 164 +3 233 14 +42 233 3 +176 233 42 +14 233 176 +14 176 13 +176 194 13 +13 194 12 +165 186 164 +186 230 194 +194 226 186 +186 226 164 +164 226 199 +176 226 194 +154 230 203 +203 230 186 +165 203 186 +167 203 165 +184 203 167 +201 203 184 +154 203 201 +154 201 11 +194 230 12 +12 230 154 +12 154 11 +11 201 10 +10 201 151 +151 201 184 +166 167 165 +170 215 169 +168 210 171 +166 210 168 +166 168 167 +168 169 167 +168 170 169 +168 171 170 +171 172 170 +172 173 170 +172 182 173 +196 216 172 +171 196 172 +171 197 196 +173 215 170 +185 215 173 +183 185 173 +221 222 156 +185 222 221 +185 221 215 +215 221 188 +169 215 188 +169 188 184 +184 188 151 +188 202 151 +188 221 202 +151 202 9 +10 151 9 +9 202 8 +8 156 2 +156 206 2 +156 222 206 +202 221 156 +8 202 156 +169 184 167 +152 219 209 +36 152 37 +152 209 37 +37 209 38 +209 219 214 +214 219 210 +166 214 210 +171 210 197 +210 219 197 +152 228 219 +197 228 178 +219 228 197 +196 197 178 +178 228 205 +178 205 158 +205 228 152 +36 205 152 +6 205 36 +158 205 6 +63 158 6 +63 159 158 +159 163 158 +163 178 158 +178 223 196 +163 223 178 +180 223 163 +159 179 163 +179 181 180 +179 180 163 +179 231 181 +213 231 179 +159 213 179 +61 213 62 +62 213 159 +62 159 63 +175 206 187 +2 206 35 +35 206 175 +35 175 34 +34 235 33 +175 235 34 +208 235 175 +217 235 208 +207 208 175 +207 229 208 +208 229 217 +220 229 150 +150 229 207 +183 207 187 +187 207 175 +183 225 207 +206 222 187 +187 222 185 +183 187 185 +182 183 173 +182 225 183 +216 223 180 +195 216 180 +196 223 216 +172 216 182 +182 216 195 +195 225 182 +150 225 195 +200 220 150 +195 200 150 +181 200 195 +181 195 180 +207 225 150 +192 200 181 +193 200 192 +193 220 200 +191 193 192 +217 229 220 +153 235 217 +33 235 153 +33 153 32 +32 211 31 +211 224 157 +31 211 157 +153 211 32 +198 211 153 +153 217 198 +217 220 193 +198 217 193 +191 198 193 +198 224 211 +191 224 198 +177 218 189 +189 190 174 +157 224 189 +189 224 191 +189 191 190 +31 157 30 +157 218 30 +189 218 157 +30 218 29 +218 227 29 +177 227 218 +57 227 177 +5 227 57 +29 227 5 +177 189 174 +57 177 58 +58 177 174 +58 174 59 +174 204 59 +59 204 60 +190 204 174 +190 232 204 +192 232 190 +155 232 212 +212 232 192 +191 192 190 +181 212 192 +204 232 155 +60 204 155 +155 231 213 +155 213 61 +60 155 61 +212 231 155 +181 231 212 +60 380 59 +59 380 332 +332 380 365 +365 380 368 +371 384 372 +368 371 363 +365 368 363 +324 384 368 +368 384 371 +368 380 324 +324 380 60 +383 384 324 +61 383 324 +61 324 60 +62 383 61 +63 346 62 +346 383 62 +372 383 346 +372 384 383 +371 372 321 +372 392 321 +346 392 372 +358 392 346 +354 392 358 +354 358 356 +358 396 356 +378 396 358 +6 378 63 +346 378 358 +63 378 346 +28 405 403 +5 405 28 +57 405 5 +402 405 57 +352 405 402 +403 405 352 +352 402 393 +58 402 57 +332 402 58 +59 332 58 +393 402 332 +364 365 363 +364 401 365 +365 393 332 +365 401 393 +401 403 393 +401 404 345 +393 403 352 +345 403 401 +27 403 345 +27 345 26 +28 403 27 +25 379 326 +379 395 326 +345 379 26 +26 379 25 +379 404 395 +345 404 379 +364 404 401 +395 404 364 +362 364 363 +326 395 386 +386 395 362 +351 386 362 +362 395 364 +25 326 24 +326 385 24 +326 386 385 +24 385 23 +23 385 331 +23 331 22 +331 385 349 +22 330 4 +22 331 330 +331 333 330 +333 348 330 +350 397 333 +333 397 348 +348 397 366 +349 386 351 +349 351 350 +385 386 349 +331 349 333 +349 350 333 +351 360 350 +351 361 360 +360 361 321 +361 371 321 +321 398 360 +353 389 360 +360 398 353 +343 389 353 +366 397 389 +389 397 350 +360 389 350 +353 354 344 +354 398 392 +353 398 354 +392 398 321 +351 362 361 +362 363 361 +363 371 361 diff --git a/test/runtests.jl b/test/runtests.jl index 6bb6afe2..6062afed 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -4,6 +4,8 @@ using StaticArrays module PkgTests +using TestItemRunner + using Distributed using LinearAlgebra using SparseArrays @@ -14,7 +16,6 @@ import BEAST include("test_fourier.jl") include("test_specials.jl") -# include("test_sparsend.jl") include("test_basis.jl") include("test_directproduct.jl") @@ -22,6 +23,8 @@ include("test_raviartthomas.jl") include("test_rt.jl") include("test_rtx.jl") include("test_subd_basis.jl") +include("test_rt2.jl") +include("test_nd2.jl") include("test_dvg.jl") include("test_bcspace.jl") @@ -33,6 +36,7 @@ include("test_ndjunction.jl") include("test_ndspace.jl") include("test_restrict.jl") include("test_ndlcd_restrict.jl") +include("test_interpolate_and_restrict.jl") include("test_rt3d.jl") include("test_gradient.jl") include("test_mult.jl") @@ -41,32 +45,56 @@ include("test_gram.jl") include("test_vector_gram.jl") include("test_local_storage.jl") include("test_embedding.jl") -include("test_laminated.jl") include("test_assemblerow.jl") +# include("test_mixed_blkassm.jl") include("test_local_assembly.jl") +include("test_assemble_refinements.jl") + +include("test_dipole.jl") + +include("test_sauterschwabints1D.jl") +include("test_hh2d_exec.jl") +include("test_hh2d_mie.jl") +include("test_hh2d_nearfield.jl") include("test_wiltonints.jl") include("test_sauterschwabints.jl") +include("test_hh3dints.jl") include("test_ss_nested_meshes.jl") include("test_nitsche.jl") include("test_nitschehh3d.jl") include("test_curlcurlgreen.jl") include("test_hh3dtd_exc.jl") -include("test_hh3dexc.jl") +# include("test_hh3dexc.jl") +include("test_hh3d_nearfield.jl") include("test_tdassembly.jl") include("test_tdhhdbl.jl") include("test_tdmwdbl.jl") include("test_compressed_storage.jl") -include("test_matrixconv.jl") +include("test_tdefie_irk.jl") +include("test_dyadicop.jl") +# include("test_matrixconv.jl") include("test_tdop_scaling.jl") include("test_tdrhs_scaling.jl") +include("test_td_tensoroperator.jl") + +include("test_variational.jl") + +include("test_handlers.jl") +include("test_ncrossbdm.jl") +include("test_curl_lagc0d1_lagc0d2.jl") +include("test_gridfunction.jl") + +include("test_hh_lsvie.jl") +include("test_vie.jl") +include("test_evie_dvie.jl") +include("test_hh2d_mie.jl") -# include("test_farfield.jl") +@run_package_tests filter=ti->!(:example in ti.tags) verbose=true -#include("test_assemblerow.jl") try Pkg.installed("BogaertInts10") diff --git a/test/test_assemble_refinements.jl b/test/test_assemble_refinements.jl new file mode 100644 index 00000000..294b5a7f --- /dev/null +++ b/test/test_assemble_refinements.jl @@ -0,0 +1,79 @@ +using StaticArrays +using CompScienceMeshes +using BEAST +# using SauterSchwabQuadrature +using LinearAlgebra +using Test + +function generate_refpair(;angle=180) + vertices = [ + SVector(0.0, 1.0, 0.0), + SVector(-0.5, 0.0, 0.0), + SVector(+0.5, 0.0, 0.0), + SVector(0.0, 1.0*cos(angle/180*π), 1.0*sin(angle/180*π)) + ] + + triangles = [ + SVector(1, 2, 3), + SVector(2, 4, 3) + ] + return Mesh(vertices, triangles) +end +Γ = generate_refpair(angle=45) + +X = raviartthomas(Γ) +Y = buffachristiansen(Γ) + +Kop = Maxwell3D.doublelayer(wavenumber=0.0) + +qs = BEAST.defaultquadstrat(Kop, X, Y) +qs = BEAST.DoubleNumWiltonSauterQStrat(1, 1, 12, 13, 12, 12, 12, 12) +@show qs +@show qs(Kop, X, Y) +@show qs(Kop, Y, X) +K1 = assemble(Kop, X, Y; quadstrat=qs) +K2 = assemble(Kop, Y, X; quadstrat=qs) + +@show K1[1,1] +@show K2[1,1] + +K1[1,1] - K2[1,1] +@test K1[1,1] ≈ K2[1,1] rtol=1e-7 + +# Verify that quadrature strategy is passed through +qds(i) = BEAST.DoubleNumWiltonSauterQStrat(3,3,8,8,i,i,i,i) +Kxy_1 = (assemble(Kop, X, Y, quadstrat=qds(1)))[1,1] +Kxy_2 = (assemble(Kop, X, Y, quadstrat=qds(5)))[1,1] +Kxy_3 = (assemble(Kop, X, Y, quadstrat=qds(10)))[1,1] +# Kxy_4 = (assemble(Kop, X, Y, quadstrat=qds(15)))[1,1] + +@test 0.1 < abs(Kxy_1 - Kxy_3)/abs(Kxy_3) < 0.4 +@test 0.0008 < abs(Kxy_2 - Kxy_3)/abs(Kxy_3) < 0.002 + +Kyx_1 = (assemble(Kop, Y, X, quadstrat=qds(1)))[1,1] +Kyx_2 = (assemble(Kop, Y, X, quadstrat=qds(5)))[1,1] +Kyx_3 = (assemble(Kop, Y, X, quadstrat=qds(10)))[1,1] +# Kyx_4 = (assemble(Kop, Y, X, quadstrat=qds(15)))[1,1] + +@test 0.2 < abs(Kyx_1 - Kyx_3)/abs(Kyx_3) < 0.3 +@test 0.0006 < abs(Kyx_2 - Kyx_3)/abs(Kyx_3) < 0.0007 + +Γtr = CompScienceMeshes.translate(Γ, SVector(2.0, 0.0, 0.0)) + +Γ2 = weld(Γ, Γtr) + +X2 = raviartthomas(Γ2) +Y2 = buffachristiansen(Γ2) + +qds2(i) = BEAST.DoubleNumWiltonSauterQStrat(i,i,i,i,10,10,10,10) +K2xy_1 = (assemble(Kop, X2, Y2, quadstrat=qds2(1)))[1,2] +K2xy_2 = (assemble(Kop, X2, Y2, quadstrat=qds2(5)))[1,2] +K2xy_3 = (assemble(Kop, X2, Y2, quadstrat=qds2(10)))[1,2] +@test 0.06 < abs(K2xy_1 - K2xy_3)/abs(K2xy_3) < 0.07 +@test 1e-6 < abs(K2xy_2 - K2xy_3)/abs(K2xy_3) < 1e-5 + +K2yx_1 = (assemble(Kop, Y2, X2, quadstrat=qds2(1)))[1,2] +K2yx_2 = (assemble(Kop, Y2, X2, quadstrat=qds2(5)))[1,2] +K2yx_3 = (assemble(Kop, Y2, X2, quadstrat=qds2(10)))[1,2] +@test 0.06 < abs(K2yx_1 - K2yx_3)/abs(K2yx_3) < 0.07 +@test 1e-6 < abs(K2yx_2 - K2yx_3)/abs(K2yx_3) < 1e-5 \ No newline at end of file diff --git a/test/test_assemblerow.jl b/test/test_assemblerow.jl index de27e4e7..948e1b93 100644 --- a/test/test_assemblerow.jl +++ b/test/test_assemblerow.jl @@ -1,59 +1,65 @@ +@info "Executing test_assemblerow.jl" + using CompScienceMeshes, BEAST using Test fn = joinpath(dirname(@__FILE__),"assets","sphere35.in") -m = readmesh(fn) -t = Maxwell3D.singlelayer(wavenumber=1.0) -X = raviartthomas(m) -numfunctions(X) -## -X1 = subset(X,1:1) -numfunctions(X1) +for T in [Float32, Float64] + local m = readmesh(fn,T=T) + t = Maxwell3D.singlelayer(wavenumber=T(1.0)) + local X = raviartthomas(m) + numfunctions(X) + + ## + local X1 = subset(X,1:1) + numfunctions(X1) -T1 = assemble(t,X1,X) -T2 = BEAST.assemblerow(t,X1,X) -# -# T3 = assemble(t,X,X1) -# T4 = BEAST.assemblecol(t,X,X1) -# -# @test T1 == T2 + T1 = assemble(t,X1,X) + T2 = BEAST.assemblerow(t,X1,X) + # + # T3 = assemble(t,X,X1) + # T4 = BEAST.assemblecol(t,X,X1) + # + # @test T1 == T2 -# T2 = BEAST.assembleblock(t,X,X) -@test T1≈T2 atol=1e-8 + # T2 = BEAST.assembleblock(t,X,X) + @test T1≈T2 atol=sqrt(eps(T)) -I = [3,2,7] -X1 = subset(X,I) -T2 = zeros(scalartype(t,X1,X1),numfunctions(X1),numfunctions(X1)) -store(v,m,n) = (T2[m,n] += v) + local I = [3,2,7] + X1 = subset(X,I) + T2 = zeros(scalartype(t,X1,X1),numfunctions(X1),numfunctions(X1)) + store(v,m,n) = (T2[m,n] += v) -test_elements, test_assembly_data, - trial_elements, trial_assembly_data, - quadrature_data, zlocal = BEAST.assembleblock_primer(t,X,X) + qs = BEAST.defaultquadstrat(t,X,X) + test_elements, test_assembly_data, + trial_elements, trial_assembly_data, + quadrature_data, zlocal = BEAST.assembleblock_primer(t,X,X, quadstrat=qs) -BEAST.assembleblock_body!(t, - X, I, test_elements, test_assembly_data, - X, I, trial_elements, trial_assembly_data, - quadrature_data, zlocal, store) + BEAST.assembleblock_body!(t, + X, I, test_elements, test_assembly_data, + X, I, trial_elements, trial_assembly_data, + quadrature_data, zlocal, store, quadstrat=qs) -T1 = assemble(t,X1,X1) -@test T1 == T2 + T1 = assemble(t,X1,X1) + @test T1 == T2 -# @time BEAST.assembleblock_body!(t, -# X, I, test_elements, test_assembly_data, -# X, I, trial_elements, trial_assembly_data, -# quadrature_data, zlocal, store) -# @time assemble(t,X1,X1) + # @time BEAST.assembleblock_body!(t, + # X, I, test_elements, test_assembly_data, + # X, I, trial_elements, trial_assembly_data, + # quadrature_data, zlocal, store) + # @time assemble(t,X1,X1) -T3 = zeros(scalartype(t,X1,X1),numfunctions(X1),numfunctions(X1)) -store3(v,m,n) = (T3[m,n] += v) + T3 = zeros(scalartype(t,X1,X1),numfunctions(X1),numfunctions(X1)) + store3(v,m,n) = (T3[m,n] += v) -blkasm = BEAST.blockassembler(t,X,X) -blkasm(I,I,store3) + blkasm = BEAST.blockassembler(t,X,X) + blkasm(I,I,store3) -T4 = assemble(t,X,X) -@test T3 == T4[I,I] + T4 = assemble(t,X,X) + @test T3 == T4[I,I] -# @time blkasm(I,I) -# @time assemble(t,X1,X1) + # @time blkasm(I,I) + # @time assemble(t,X1,X1) +end \ No newline at end of file diff --git a/test/test_basis.jl b/test/test_basis.jl index e4ae3b82..cebb14e1 100644 --- a/test/test_basis.jl +++ b/test/test_basis.jl @@ -1,137 +1,176 @@ ## Preamble using Test +using LinearAlgebra using CompScienceMeshes using BEAST ## The actual tests +for T in [Float32, Float64] + κ = ω = T(1.0) + + Γ = meshsegment(T(1.0), T(0.5)) + X = lagrangec0d1(Γ) + @test numvertices(Γ)-2 == numfunctions(X) + + hypersingular = Helmholtz2D.hypersingular(; wavenumber=κ) + identityop = Identity() + doublelayer = Helmholtz2D.doublelayer(; wavenumber=κ) + + @show BEAST.defaultquadstrat(hypersingular, X, X) + # @show @which BEAST.defaultquadstrat(hypersingular, X, X) + @time N = assemble(hypersingular, X, X) + @time I = Matrix(assemble(identityop, X, X)) + + @test size(N) == (numfunctions(X), numfunctions(X)) + @test size(I) == (numfunctions(X), numfunctions(X)) + @test rank(I) == numfunctions(X) + + E = Helmholtz2D.planewave(wavenumber=κ, direction=point(1.0,0.0)) + @time e = assemble(BEAST.NormalDerivative(E), X) + #e = assemble(PlaneWaveNeumann(κ, point(0.0, 1.0)), X) + @test length(e) == numfunctions(X) + + x1 = N \ e; + + # Testing duallagrangec0d1 + if T == Float64 + Γ1 = meshcircle(T(1.0), T(2.5),3) # creating a triangle + Γ2 = barycentric_refinement(Γ1) # creating the refined mesh + + X1 =duallagrangec0d1(Γ1,Γ2) # creating the basis functions + X1 = duallagrangec0d1(Γ1, Γ2, x->false, Val{2}) + @test numcells(Γ1) == numfunctions(X1) # making sure it is assigned according to the coarse mesh segments + @test length(X1.fns[1])== 6 # making sure each segment represent 6 shapes inside it + @test length(X1.fns[numfunctions(X1)])== 6 # making sure the last segment functions contains 6 shapes as well + end +end -κ = ω = 1.0 - -Γ = meshsegment(1.0, 0.5) -X = lagrangec0d1(Γ) -@test numvertices(Γ)-2 == numfunctions(X) - -hypersingular = HyperSingular(κ) -identityop = Identity() -doublelayer = DoubleLayer(κ) - -@time N = assemble(hypersingular, X, X) -@time I = assemble(identityop, X, X) - -@test size(N) == (numfunctions(X), numfunctions(X)) -@test size(I) == (numfunctions(X), numfunctions(X)) -@test rank(I) == numfunctions(X) - -@time e = assemble(PlaneWaveNeumann(κ, point(0.0, 1.0)), X) -@test length(e) == numfunctions(X) - -x1 = N \ e; +## Test linear Lagrange elements on triangles and the computation of their curl +for T in [Float32, Float64] + Degr = 1 + Dim1 = 3 + NumF = 3 + f = BEAST.LagrangeRefSpace{T,Degr,Dim1,NumF}() + sphere = readmesh(joinpath(dirname(@__FILE__),"assets","sphere5.in"),T=T) + s = chart(sphere, first(sphere)) + t = neighborhood(s, T.([1,1]/3)) + # v = f(t, Val{:withcurl}) + v = f(t) + + A = volume(s) + @test v[1][2] == (s[3]-s[2])/2A + @test v[2][2] == (s[1]-s[3])/2A + @test v[3][2] == (s[2]-s[1])/2A + + @test v[1][1] ≈ 1/3 + @test v[2][1] ≈ 1/3 + @test v[3][1] ≈ 1/3 +end -# Testing duallagrangec0d1 -Γ1 = meshcircle(1.0, 2.5,3) # creating a triangle -Γ2 = barycentric_refinement(Γ1) # creating the refined mesh +## Test the construction of continuous linear Lagrange elements on 2D surfaces +using CompScienceMeshes +using BEAST +using Test -X1 =duallagrangec0d1(Γ1,Γ2) # creating the basis functions -X1 = duallagrangec0d1(Γ1, Γ2, x->false, Val{2}) -@test numcells(Γ1) == numfunctions(X1) # making sure it is assigned according to the coarse mesh segments -@test length(X1.fns[1])== 6 # making sure each segment represent 6 shapes inside it -@test length(X1.fns[numfunctions(X1)])== 6 # making sure the last segment functions contains 6 shapes as well +for T in [Float32, Float64] + m = meshrectangle(T(1.0), T(1.0), T(0.5), 3) + X = lagrangec0d1(m) + x = refspace(X) + dom = domain(chart(m, first(m))) + @test numfunctions(x, dom) == 3 -## Test linear Lagrange elements on triangles and the computation of their curl -T = Float64 -Degr = 1 -Dim1 = 3 -NumF = 3 -f = BEAST.LagrangeRefSpace{T,Degr,Dim1,NumF}() -sphere = readmesh(joinpath(dirname(@__FILE__),"assets","sphere5.in")) -s = chart(sphere, first(cells(sphere))) -t = neighborhood(s, [1,1]/3) -v = f(t, Val{:withcurl}) - -A = volume(s) -@test v[1][2] == (s[3]-s[2])/2A -@test v[2][2] == (s[1]-s[3])/2A -@test v[3][2] == (s[2]-s[1])/2A - -@test v[1][1] ≈ 1/3 -@test v[2][1] ≈ 1/3 -@test v[3][1] ≈ 1/3 + @test numfunctions(X) == 1 + @test length(X.fns[1]) == 6 +end -## Test the construction of continuous linear Lagrange elements on 2D surfaces +## Test unitfunction using CompScienceMeshes using BEAST using Test -m = meshrectangle(1.0, 1.0, 0.5, 3) -X = lagrangec0d1(m) -x = refspace(X) +for T in [Float32, Float64] + m = meshrectangle(T(1.0), T(1.0), T(0.5), 3) + X = unitfunctioncxd0(m) -@test numfunctions(x) == 3 + @test numfunctions(X) == 1 + @test length(X.fns[1]) == numcells(m) + @test assemble(Identity(), X, X) ≈ [1.0] -@test numfunctions(X) == 1 -@test length(X.fns[1]) == 6 + X1 = unitfunctionc0d1(m) + + @test numfunctions(X1) == 1 + @test length(X1.fns[1]) == 6 + @test assemble(Identity(), X1, X1) ≈ [0.125] + + X2 = unitfunctionc0d1(m; dirichlet=false) + + @test numfunctions(X2) == 1 + @test length(X2.fns[1]) == numcells(m) * 3 + @test assemble(Identity(), X2, X2) ≈ [1.0] +end ## test the scalar trace for Lagrange functions using CompScienceMeshes using BEAST using Test +for T in [Float64] + p1 = point(T,0,0,0) + p2 = point(T,1,0,0) + p3 = point(T,0,1,0) + p4 = point(T,1,1,1) + c1 = index(1,2,3) -p1 = point(0,0,0) -p2 = point(1,0,0) -p3 = point(0,1,0) -p4 = point(1,1,1) -c1 = index(1,2,3) - -m = Mesh([p1,p2,p3,p4],[c1]) -b = Mesh([p1,p2], [index(1,2)]) -X = lagrangec0d1(m, boundary(m)) -@test numfunctions(X) == 3 + m = Mesh([p1,p2,p3,p4],[c1]) + b = Mesh([p1,p2], [index(1,2)]) + X = lagrangec0d1(m, boundary(m)) + @test numfunctions(X) == 3 -Y = BEAST.strace(X, b) + Y = BEAST.strace(X, b) -@test numfunctions(X) == 3 -@test numfunctions(Y) == 3 + @test numfunctions(X) == 3 + @test numfunctions(Y) == 3 -@test length(Y.fns[1]) == 1 -@test length(Y.fns[2]) == 1 -@test length(Y.fns[3]) == 0 + @test length(Y.fns[1]) == 1 + @test length(Y.fns[2]) == 1 + @test length(Y.fns[3]) == 0 -sh = Y.fns[1][1]; @test (sh.cellid, sh.refid, sh.coeff) == (1, 1, 1.0) -sh = Y.fns[2][1]; @test (sh.cellid, sh.refid, sh.coeff) == (1, 2, 1.0) + sh = Y.fns[1][1]; @test (sh.cellid, sh.refid, sh.coeff) == (1, 1, 1.0) + sh = Y.fns[2][1]; @test (sh.cellid, sh.refid, sh.coeff) == (1, 2, 1.0) -x = refspace(X) - -cell = chart(m, first(cells(m))) -face = chart(b, first(cells(b))) -Q = BEAST.strace(x, cell, 3, face) -@test Q == [1 0 0; 0 1 0] + x = refspace(X) + cell = chart(m, first(m)) + face = chart(b, first(b)) + Q = BEAST.strace(x, cell, 3, face) + @test Q == [1 0 0; 0 1 0] +end ## test Lagrange construction on Junctions using CompScienceMeshes using BEAST using Test -m1 = meshrectangle(1.0, 0.5, 0.5) -m2 = CompScienceMeshes.rotate(m1, 0.5π*[1,0,0]) -m3 = CompScienceMeshes.rotate(m1, 1.0π*[1,0,0]) -m = weld(m1, m2, m3) - -X = lagrangec0d1(m) -x = refspace(X) -@test numfunctions(X) == 1 -@test length(X.fns[1]) == 9 - -p = point(0.5, 0.0, 0.0) -for _s in X.fns[1] - _cell = m.faces[_s.cellid] - patch = chart(m, _cell) - bary = carttobary(patch, p) - mp = neighborhood(patch, bary) +for T in [Float64] + m1 = meshrectangle(T(1.0), T(0.5), T(0.5)) + m2 = CompScienceMeshes.rotate(m1, T(0.5π)*[1,0,0]) + m3 = CompScienceMeshes.rotate(m1, T(1.0π)*[1,0,0]) + m = weld(m1, m2, m3) + + X = lagrangec0d1(m) + x = refspace(X) + @test numfunctions(X) == 1 + @test length(X.fns[1]) == 9 + + p = point(T, 0.5, 0.0, 0.0) + for _s in X.fns[1] + # _cell = m.faces[_s.cellid] + patch = chart(m, _s.cellid) + bary = carttobary(patch, p) + mp = neighborhood(patch, bary) + end end - ## Test the dual pieweise constant lagrange elemetns using CompScienceMeshes using BEAST @@ -156,8 +195,8 @@ for _fn in X.fns for _sh in _fn @test _sh.refid == 1 cellid = _sh.cellid - _cell = cells(fine)[cellid] - ptch = chart(fine, _cell) + # _cell = cells(fine)[cellid] + ptch = chart(fine, cellid) @test _sh.coeff * volume(ptch) ≈ 1/_n end end @@ -177,9 +216,10 @@ x = refspace(X) isonjunction = inclosure_gpredicate(j) els, ad = BEAST.assemblydata(X) +num_shapes = numfunctions(x, domain(chart(m, first(m)))) for _p in 1:numcells(m) el = els[_p] - for r in 1:numfunctions(x) + for r in 1:num_shapes vert = el[r] isonjunction(vert) || continue for (i,w) in ad[_p,r] @@ -188,6 +228,51 @@ for _p in 1:numcells(m) end end +## Test gradient and curl of continuous lagrange elements +m = meshrectangle(1.0, 1.0, 0.5, 3) +int_nodes = submesh(!in(skeleton(boundary(m),0)), skeleton(m,0)) +@test length(int_nodes) == 1 + +lag = lagrangec0d1(m, int_nodes) +@test numfunctions(lag) == 1 + +rs = refspace(lag) +# cl = cells(m)[2] +p = 2 +cl = CompScienceMeshes.indices(m,p) +ch = chart(m, p) +nbd = neighborhood(ch, carttobary(ch, [0.5, 0.5, 0])) +vals = getfield.(rs(nbd), :value) +@test vals ≈ [0, 1, 0] + +crl = BEAST.curl(lag) +# BEAST.gradient(lag) +nxgrad = BEAST.n × BEAST.gradient(lag) + +for i in eachindex(crl.fns) + crli = sort(crl.fns[i], by=sh->(sh.cellid, sh.refid)) + nxgradi = sort(nxgrad.fns[i], by=sh->(sh.cellid, sh.refid)) + for j in eachindex(crl.fns[i]) + # @test crl.fns[i][j] == nxgrad.fns[i][j] + @test crli[j].coeff == -nxgradi[j].coeff + end +end + + +m = Mesh([ + point(1,0,0), + point(0,1,0), + point(0,0,1), + point(0,0,0)], + [index(1,2,3,4)]) + +lag = lagrangec0d1(m, skeleton(m,0)) +@test numfunctions(lag) == 4 + +gradlag = gradient(lag) +edg = skeleton(m,1) +nd3d1 = BEAST.nedelec(m,edg) +nd3d2 = BEAST.nedelecc3d(m,edg) ## end of file diff --git a/test/test_bcspace.jl b/test/test_bcspace.jl index db82ffca..cecaec68 100644 --- a/test/test_bcspace.jl +++ b/test/test_bcspace.jl @@ -17,14 +17,14 @@ function isdivconforming(space) geo = geometry(space) mesh = geo - + T=eltype(vertextype(mesh)) edges = skeleton(mesh,1) D = connectivity(edges, mesh, abs) rows = rowvals(D) vals = nonzeros(D) - Flux = zeros(Float64, numcells(mesh),3) - TotalFlux = zeros(Float64, numcells(edges), numfunctions(space)) + Flux = zeros(T, numcells(mesh),3) + TotalFlux = zeros(T, numcells(edges), numfunctions(space)) for i in 1 : numfunctions(space) @@ -85,74 +85,76 @@ function interior(mesh::Mesh) end #meshfile = Pkg.dir("BEAST","test","sphere2.in") -meshfile = joinpath(dirname(@__FILE__),"assets","sphere316.in") -mesh = readmesh(meshfile) -@test numvertices(mesh) == 160 -@test numcells(mesh) == 316 - -rt = raviartthomas(mesh) -@test numfunctions(rt) == 316 * 3 / 2 - -fine = barycentric_refinement(mesh) -edges = skeleton(mesh, 1) - -bc = buffachristiansen(mesh) -@test numfunctions(bc) == 316 * 3 / 2 - -lc = isdivconforming(rt) -@test maximum(lc) < eps(Float64) * 1000 -println("RT space is div-conforming") - -lc = isdivconforming(bc); -@test maximum(lc) < eps(Float64) * 1000 -println("BC space is div-conforming") - -# Now repeat the exercise with an open mesh -mesh = meshrectangle(1.0, 1.0, 0.2); -fine = barycentric_refinement(mesh); - -rt = raviartthomas(mesh) -bc = buffachristiansen(mesh) - -@test numfunctions(rt) == 65 -@test numfunctions(bc) == 65 - -int_pred = interior_tpredicate(mesh) -bnd_pred(s) = !int_pred(s) - -leaky_edges = findall(sum(abs.(isdivconforming(rt)),dims=1) .!= 0) -@test length(leaky_edges) == 0 - -bnd = boundary(mesh) -bndtch_pred = touches_predicate(bnd) -edges = interior(mesh) -bndtch_edges = findall(bndtch_pred, cells(edges)) -leaky_edges = findall(vec(sum(abs.(isdivconforming(bc)),dims=1)) .!= 0) -@test bndtch_edges == leaky_edges - - -## Test the charge of BC functions -#meshfile = Pkg.dir("BEAST","test","sphere2.in") -meshfile = joinpath(dirname(@__FILE__),"assets","sphere316.in") -mesh = readmesh(meshfile) -bc = buffachristiansen(mesh) -fine = geometry(bc) -charges = zeros(numcells(fine)) - -for fn in bc.fns - abs_charge = 0.0 - net_charge = 0.0 - fill!(charges,0) - for _sh in fn - cellid = _sh.cellid - - #cell = simplex(vertices(fine, fine.faces[cellid])) - net_charge += _sh.coeff - charges[cellid] += _sh.coeff +for T in [Float32, Float64] + meshfile = joinpath(dirname(@__FILE__),"assets","sphere316.in") + mesh = readmesh(meshfile,T=T) + @test numvertices(mesh) == 160 + @test numcells(mesh) == 316 + + rt = raviartthomas(mesh) + @test numfunctions(rt) == 316 * 3 / 2 + + local fine = barycentric_refinement(mesh) + local edges = skeleton(mesh, 1) + + bc = buffachristiansen(mesh) + @test numfunctions(bc) == 316 * 3 / 2 + + lc = isdivconforming(rt) + @test maximum(lc) < eps(T) * 1000 + println("RT space is div-conforming") + + lc = isdivconforming(bc); + @test maximum(lc) < eps(T) * 1000 + println("BC space is div-conforming") + + # Now repeat the exercise with an open mesh + mesh = meshrectangle(T(1.0), T(1.0), T(0.2)); + fine = barycentric_refinement(mesh); + + rt = raviartthomas(mesh) + bc = buffachristiansen(mesh) + + @test numfunctions(rt) == 65 + @test numfunctions(bc) == 65 + + int_pred = interior_tpredicate(mesh) + bnd_pred(s) = !int_pred(s) + + leaky_edges = findall(sum(abs.(isdivconforming(rt)),dims=1) .!= 0) + @test length(leaky_edges) == 0 + + bnd = boundary(mesh) + bndtch_pred = touches_predicate(bnd) + edges = interior(mesh) + bndtch_edges = findall(bndtch_pred, cells(edges)) + leaky_edges = findall(vec(sum(abs.(isdivconforming(bc)),dims=1)) .!= 0) + @test bndtch_edges == leaky_edges + + + ## Test the charge of BC functions + #meshfile = Pkg.dir("BEAST","test","sphere2.in") + meshfile = joinpath(dirname(@__FILE__),"assets","sphere316.in") + mesh = readmesh(meshfile,T=T) + bc = buffachristiansen(mesh) + fine = geometry(bc) + charges = zeros(numcells(fine)) + + for fn in bc.fns + abs_charge = T(0.0) + net_charge = T(0.0) + fill!(charges,0) + for _sh in fn + cellid = _sh.cellid + + #cell = simplex(vertices(fine, fine.faces[cellid])) + net_charge += _sh.coeff + charges[cellid] += _sh.coeff + end + abs_charge = sum(abs.(charges)) + @test net_charge + 1 ≈ 1 + @test abs_charge ≈ 2 end - abs_charge = sum(abs.(charges)) - @test net_charge + 1 ≈ 1 - @test abs_charge ≈ 2 end # THe BC construction function should throw for non-oriented surfaces @@ -162,3 +164,39 @@ G2 = CompScienceMeshes.rotate(G1, 0.5π * x̂) G = CompScienceMeshes.weld(G1,G2) @test_throws AssertionError buffachristiansen(G) # G3 = CompScienceMeshes.rotate(G1, 1.0π * x̂) + +@testitem "Neumann BC" begin + using CompScienceMeshes + using LinearAlgebra + + Γ = meshrectangle(1.0, 1.0, 0.1, 3) + all_edges = skeleton(Γ, 1) + bnd_edges = boundary(Γ) + int_edges = setminus(all_edges, bnd_edges) + + X = raviartthomas(Γ, all_edges) + Y = Ydi = BEAST.buffachristiansen(Γ, bnd_edges) + + T = Maxwell3D.singlelayer(wavenumber=1.0) + Id = BEAST.NCross() + + Txx = assemble(T, X, X) + Tyy = assemble(T, Y, Y) + + Ixy = assemble(Id, X, Y) + IYX = BEAST.GMRES(Ixy) + IXY = BEAST.GMRES(Ixy') + + 𝗧 = Matrix(Txx) + 𝕋 = Matrix(Tyy) + 𝗜 = Matrix(Ixy) + 𝗜⁻¹ = inv(𝗜) + 𝗜⁻ᵀ = transpose(𝗜⁻¹) + 𝗣 = 𝗜⁻ᵀ * 𝕋 * 𝗜⁻¹ + 𝗣𝗧 = 𝗣*𝗧 + + @test cond(𝗧) > 1e3 + @test cond(𝕋) > 1e3 + @show cond(𝗜) < 20 + @show cond(𝗣*𝗧) < 20 +end \ No newline at end of file diff --git a/test/test_blockassembler.jl b/test/test_blockassembler.jl new file mode 100644 index 00000000..91fdb4fc --- /dev/null +++ b/test/test_blockassembler.jl @@ -0,0 +1,28 @@ +using BEAST +using CompScienceMeshes +using LinearAlgebra +using Test + +r = 10.0 +λ = 20 * r +k = 2 * π / λ + +sphere = readmesh(joinpath(dirname(@__FILE__),"assets","sphere5.in"), T=Float64) + +D = Maxwell3D.doublelayer(wavenumber=k) +X = raviartthomas(sphere) +Y = buffachristiansen(sphere) + +A = assemble(D, X, X) + +@views blkasm = BEAST.blockassembler(D, X, X) + +@views function assembler(Z, tdata, sdata) + @views store(v,m,n) = (Z[m,n] += v) + blkasm(tdata,sdata,store) +end + +A_blk = zeros(ComplexF64, length(X.fns), length(Y.fns)) +assembler(A_blk, [1:length(X.fns);], [1:length(Y.fns);]) + +@test norm(A - A_blk) ≈ 0 atol=eps(Float64) diff --git a/test/test_bogaertints.jl b/test/test_bogaertints.jl index 6da52012..e364025a 100644 --- a/test/test_bogaertints.jl +++ b/test/test_bogaertints.jl @@ -23,9 +23,9 @@ z2 = zeros(z1) tqd = BE.quadpoints(x, [t], (12,13)) bqd = BE.quadpoints(x, [t], (13,)) -SE_strategy = BE.WiltonSEStrategy( +SE_strategy = BE.WiltonSERule( tqd[2,1], - BE.DoubleQuadStrategy( + BE.DoubleQuadRule( tqd[1,1], bqd[1,1], ), @@ -47,9 +47,9 @@ BEAST.momintegrals!(op, x, x, t, s, z3, EE_strategy) z4 = zeros(ComplexF64, n, n) tqd = BE.quadpoints(x, [t], (12,13)) bqd = BE.quadpoints(x, [s], (13,)) -SE_strategy = BE.WiltonSEStrategy( +SE_strategy = BE.WiltonSERule( tqd[2,1], - BE.DoubleQuadStrategy( + BE.DoubleQuadRule( tqd[1,1], bqd[1,1], ), @@ -73,9 +73,9 @@ BEAST.momintegrals!(op, x, x, t, s, z5, EE_strategy) z6 = zeros(ComplexF64, n, n) tqd = BE.quadpoints(x, [t], (12,13)) bqd = BE.quadpoints(x, [s], (13,)) -SE_strategy = BE.WiltonSEStrategy( +SE_strategy = BE.WiltonSERule( tqd[2,1], - BE.DoubleQuadStrategy( + BE.DoubleQuadRule( tqd[1,1], bqd[1,1], ), @@ -115,14 +115,14 @@ z3 = zeros(ComplexF64, BE.numfunctions(x), BE.numfunctions(x)) s1 = BEAST.BogaertEdgePatchStrategy(13, 30) tqd = BE.quadpoints(x, [t1], (12,13)) bqd = BE.quadpoints(x, [t2], (13,)) -s2 = BE.WiltonSEStrategy( +s2 = BE.WiltonSERule( tqd[2,1], - BE.DoubleQuadStrategy( + BE.DoubleQuadRule( tqd[1,1], bqd[1,1], ), ) -s3 = BE.DoubleQuadStrategy( +s3 = BE.DoubleQuadRule( tqd[1,1], tqd[1,1], ) @@ -136,14 +136,14 @@ BEAST.momintegrals!(op, x, x, t1, t2, z3, s3) s1 = BEAST.BogaertPointPatchStrategy(9,10) tqd = BE.quadpoints(x, [t2], (12,13)) bqd = BE.quadpoints(x, [t3], (13,)) -s2 = BE.WiltonSEStrategy( +s2 = BE.WiltonSERule( tqd[2,1], - BE.DoubleQuadStrategy( + BE.DoubleQuadRule( tqd[1,1], bqd[1,1], ), ) -s3 = BE.DoubleQuadStrategy( +s3 = BE.DoubleQuadRule( tqd[1,1], tqd[1,1], ) @@ -157,14 +157,14 @@ fill!(z3, 0); BEAST.momintegrals!(op, x, x, t2, t3, z3, s3) s1 = BEAST.BogaertSelfPatchStrategy(20) tqd = BE.quadpoints(x, [t1], (12,13)) bqd = BE.quadpoints(x, [t1], (13,)) -s2 = BE.WiltonSEStrategy( +s2 = BE.WiltonSERule( tqd[2,1], - BE.DoubleQuadStrategy( + BE.DoubleQuadRule( tqd[1,1], bqd[1,1], ), ) -s3 = BE.DoubleQuadStrategy( +s3 = BE.DoubleQuadRule( tqd[1,1], tqd[1,1], ) diff --git a/test/test_compressed_storage.jl b/test/test_compressed_storage.jl index e20d393c..d101812b 100644 --- a/test/test_compressed_storage.jl +++ b/test/test_compressed_storage.jl @@ -46,7 +46,20 @@ fr2, store2 = BEAST.allocatestorage(SL0, X⊗δ, X⊗T1, BEAST.assemble!(SL0, X⊗δ, X⊗T1, store1); Z1 = fr1() BEAST.assemble!(SL0, X⊗δ, X⊗T1, store2); Z2 = fr2() -@test norm(Z1-Z2,Inf) < 1e-12 +for k in axes(Z1,3) + local T = scalartype(SL0, X⊗δ, X⊗T1) + Z1k = zeros(T, size(Z1)[1:2]) + Z2k = zeros(T, size(Z2)[1:2]) + BEAST.ConvolutionOperators.timeslice!(Z1k, Z1, k) + BEAST.ConvolutionOperators.timeslice!(Z2k, Z2, k) + @test norm(Z1k .- Z2k, Inf) < 1e-12 +end +# for m in axes(Z1,1) +# for n in axes(Z1,2) +# for k in axes(Z1,3) +# @test norm(Z1[m,n,k] - Z2[m,n,k]) < 1e-12 +# end end end +# @test norm(Z1-Z2,Inf) < 1e-12 fr3, store7 = BEAST.allocatestorage(SL1, X⊗δ, X⊗T2, BEAST.Val{:bandedstorage}, @@ -74,16 +87,33 @@ BEAST.assemble!(SL1, X⊗δ, X⊗T2, store8); Z4 = fr4() b = assemble(E, X⊗δ) b1 = assemble(E1, X⊗δ) -W1 = inv(Z1[:,:,1]) -W2 = inv(Z2[:,:,1]) +function timeslice(T,Z,k) + Zk = zeros(T, size(Z)[1:2]) + BEAST.ConvolutionOperators.timeslice!(Zk, Z, k) +end + +T = scalartype(SL0, X⊗δ, X⊗T1) +W1 = inv(timeslice(T,Z1,1)) +W2 = inv(timeslice(T,Z2,1)) + +# W1 = inv(AbstractArray(Z1)[:,:,1]) +# W2 = inv(AbstractArray(Z2)[:,:,1]) +# W1 = inv(BEAST.timeslice(Z1,1)) +# W2 = inv(BEAST.timeslice(Z2,1)) @test norm(W1-W2,Inf) < 1e-12 -W3 = inv(Z3[:,:,1]) -W4 = inv(Z4[:,:,1]) +T = scalartype(SL1, X⊗δ, X⊗T2) +W3 = inv(timeslice(T,Z3,1)) +W4 = inv(timeslice(T,Z4,1)) + +# W3 = inv(AbstractArray(Z3)[:,:,1]) +# W4 = inv(AbstractArray(Z4)[:,:,1]) +# W3 = inv(BEAST.timeslice(Z3,1)) +# W4 = inv(BEAST.timeslice(Z4,1)) @test norm(W3-W4) < 1e-12 x1 = marchonintime(W1, Z1, b, Nt) -x2 = marchonintime(W1, Z2, b, Nt) +x2 = marchonintime(W2, Z2, b, Nt) @test norm(x1-x2,Inf) < 1e-12 x3 = marchonintime(W3, Z3, b1, Nt) @@ -110,7 +140,21 @@ fr10, store10 = BEAST.allocatestorage(DLh..., Z9 = fr9() Z10 = fr10() -@test norm(Z9-Z10,Inf) < 1e-12 +for k in axes(Z9,3) + local T = scalartype(DLh...) + Z9k = zeros(T, size(Z9)[1:2]) + Z10k = zeros(T, size(Z10)[1:2]) + BEAST.ConvolutionOperators.timeslice!(Z9k, Z9, k) + BEAST.ConvolutionOperators.timeslice!(Z10k, Z10, k) + @test norm(Z9k .- Z10k, Inf) < 1e-12 +end + +# # @test norm(Z9-Z10,Inf) < 1e-12 +# for m in axes(Z9,1) +# for n in axes(Z9,2) +# for k in axes(Z9,3) +# @test norm(Z9[m,n,k] - Z10[m,n,k]) < 1e-12 +# end end end iDLh = (integrate(DL0), Y2⊗δ, X2⊗T1) fr11, store11 = BEAST.allocatestorage(iDLh..., BEAST.Val{:densestorage}, BEAST.LongDelays{:ignore}) @@ -122,5 +166,19 @@ BEAST.assemble!(iDLh..., store11); Z11 = fr11() BEAST.assemble!(iDLh..., store12); Z12 = fr12() # BEAST.assemble!(iDLh..., store13); Z13 = fr13() -@test norm(Z11-Z12,Inf) < 1e-8 -# @test norm(Z11-Z13,Inf) < 1e-8 \ No newline at end of file +# @test norm(Z11-Z12,Inf) < 1e-8 +# for m in axes(Z11,1) +# for n in axes(Z11,2) +# for k in axes(Z11,3) +# @test norm(Z11[m,n,k] - Z12[m,n,k]) < 1e-12 +# end end end +# @test norm(Z11-Z13,Inf) < 1e-8 + +for k in axes(Z11,3) + local T = scalartype(iDLh...) + Z11k = zeros(T, size(Z11)[1:2]) + Z12k = zeros(T, size(Z12)[1:2]) + BEAST.ConvolutionOperators.timeslice!(Z11k, Z11, k) + BEAST.ConvolutionOperators.timeslice!(Z12k, Z12, k) + @test norm(Z11k .- Z12k, Inf) < 1e-12 +end \ No newline at end of file diff --git a/test/test_curl_lagc0d1_lagc0d2.jl b/test/test_curl_lagc0d1_lagc0d2.jl new file mode 100644 index 00000000..8cb2a329 --- /dev/null +++ b/test/test_curl_lagc0d1_lagc0d2.jl @@ -0,0 +1,47 @@ +#test curl lagc0d2 + +using CompScienceMeshes +using BEAST + +using Test +for T in [Float32, Float64] + for j in [1,2,3] + local o, x, y, z = euclidianbasis(3,T) + triang = simplex(x,y,o) + ctr = center(triang) + + + + iref1 = BEAST.LagrangeRefSpace{T,2,3,6}() + oref1 = BEAST.BDMRefSpace{T}() + ishp1 = [BEAST.Shape{T}(1, j, 1.0), BEAST.Shape{T}(1, mod1(j+1,3)+3, 0.5), BEAST.Shape{T}(1, mod1(j+2,3)+3, 0.5)] + + # we use the property that a nodal lagc0d2 function plus 1/2 times the two adiacent edge lagc0d2 functions is equal to the nodal lagc0d1 function at the same nodes, and the same must be true for their surfcurl + + outputbdm = [BEAST.curl(iref1, ishp1[1], triang), BEAST.curl(iref1, ishp1[2], triang), BEAST.curl(iref1, ishp1[3], triang)] + + global obdm = point(T,0,0,0) + + for i in [1,2,3] + for oshp in outputbdm[i] + obdm += oref1(ctr)[oshp.refid].value * oshp.coeff + end + end + + + iref2 = BEAST.LagrangeRefSpace{T,1,3,3}() + oref2 = BEAST.RTRefSpace{T}() + ishp2 = BEAST.Shape{T}(1, j, 1.0) + + outputrt = BEAST.curl(iref2, ishp2, triang) + + global ort = point(T,0,0,0) + + for oshp in outputrt + ort += oref2(ctr)[oshp.refid].value * oshp.coeff + end + + + @test ort ≈ obdm + end +end diff --git a/test/test_curlcurlgreen.jl b/test/test_curlcurlgreen.jl index 01e099e3..3acc667a 100644 --- a/test/test_curlcurlgreen.jl +++ b/test/test_curlcurlgreen.jl @@ -3,42 +3,44 @@ using LinearAlgebra using BEAST using CompScienceMeshes using StaticArrays - -y = point(2,0,0) -j = ẑ -κ = 1.0 - -gj(x) = exp(-im*κ*norm(x-y))/(4*π*norm(x-y)) * j -ccg = BEAST.CurlCurlGreen(κ, j, y) - -function curlh(f,x,h) - e1 = point(1,0,0) - e2 = point(0,1,0) - e3 = point(0,0,1) - - d1f = (f(x+h*e1) - f(x-h*e1))/(2*h) - d2f = (f(x+h*e2) - f(x-h*e2))/(2*h) - d3f = (f(x+h*e3) - f(x-h*e3))/(2*h) - - return @SVector[ - d2f[3] - d3f[2], - d3f[1] - d1f[3], - d1f[2] - d2f[1]] -end - -cgh(x) = curlh(gj,x,h) -ccgh(x) = curlh(cgh,x,h) - -h = 0.01 -x = point(1,1,1) -a = ccg(x) -b = ccgh(x) -@show norm(x-y) -@test norm(a-b) < 1e-5 - -cccg = curl(ccg) -cccgh(x) = curlh(ccg,x,h) - -# @show a = cccg(x) -# @show b = cccgh(x) -@test norm(a-b) < 1e-5 +for T in [Float32, Float64] + local y = point(T,2,0,0) + local j = ẑ + local κ = T(1.0) + + gj(x) = exp(-im*κ*norm(x-y))/(4*π*norm(x-y)) * j + ccg = BEAST.CurlCurlGreen(κ, j, y) + + function curlh(f,x,h) + e1 = point(T,1,0,0) + e2 = point(T,0,1,0) + e3 = point(T,0,0,1) + + d1f = (f(x+h*e1) - f(x-h*e1))/(2*h) + d2f = (f(x+h*e2) - f(x-h*e2))/(2*h) + d3f = (f(x+h*e3) - f(x-h*e3))/(2*h) + + return @SVector[ + d2f[3] - d3f[2], + d3f[1] - d1f[3], + d1f[2] - d2f[1]] + end + + cgh(x) = curlh(gj,x,h) + ccgh(x) = curlh(cgh,x,h) + + local h = T(0.01) + local x = point(T,1,1,1) + a = ccg(x) + local b = ccgh(x) + @show norm(x-y) + T == Float64 ? atol=1e-5 : atol=1e-4 + @test norm(a-b) < atol + + cccg = curl(ccg) + cccgh(x) = curlh(ccg,x,h) + + # @show a = cccg(x) + # @show b = cccgh(x) + @test norm(a-b) < atol +end \ No newline at end of file diff --git a/test/test_dipole.jl b/test/test_dipole.jl new file mode 100644 index 00000000..fd2e6f07 --- /dev/null +++ b/test/test_dipole.jl @@ -0,0 +1,138 @@ +using Test + +using CompScienceMeshes +using BEAST +using StaticArrays +using LinearAlgebra +using BlockArrays + +# U = Float32 +for U in [Float32,Float64] + @show U + + c = U(3e8) + μ0 = U(4*π*1e-7) + μr = U(1.0) + μ = μ0*μr + ε0 = U(8.854187812e-12) + εr = U(5) + ε = ε0*εr + c = U(1)/sqrt(ε*μ) + local f = U(5e7) + λ = c/f + k = U(2*π/λ) + local ω = k*c + η = sqrt(μ/ε) + + a = U(1) + Γ_orig = CompScienceMeshes.meshcuboid(a,a,a,U(0.1); generator=:compsciencemeshes) + local Γ = translate(Γ_orig,SVector(U(-a/2),U(-a/2),U(-a/2))) + + Φ, Θ = U.([0.0]), range(U(0),stop=U(π),length=100) + pts = [point(U,cos(ϕ)*sin(θ), sin(ϕ)*sin(θ), cos(θ)) for ϕ in Φ for θ in Θ] + + # This is an electric dipole + # The pre-factor (1/ε) is used to resemble + # (9.18) in Jackson's Classical Electrodynamics + local E = U(1/ε) * dipolemw3d(location=SVector(U(0.4),U(0.2),U(0)), + orientation=U(1e-9).*SVector(U(0.5),U(0.5),U(0)), + wavenumber=k) + + local n = BEAST.NormalVector() + + 𝒆 = (n × E) × n + local H = (-1/(im*μ*ω))*curl(E) + 𝒉 = (n × H) × n + + 𝓣 = Maxwell3D.singlelayer(wavenumber=k, alpha=-im*ω*μ, beta=-1/(im*ω*ε)) + 𝓝 = BEAST.NCross() + 𝓚 = Maxwell3D.doublelayer(wavenumber=k) + + local X = raviartthomas(Γ) + local Y = buffachristiansen(Γ) + + local T = Matrix(assemble(𝓣,X,X)) + e = Vector(assemble(𝒆,X)) + j_EFIE = T\e + + nf_E_EFIE = potential(MWSingleLayerField3D(𝓣), pts, j_EFIE, X) + nf_H_EFIE = potential(BEAST.MWDoubleLayerField3D(𝓚), pts, j_EFIE, X) + ff_E_EFIE = potential(MWFarField3D(𝓣), pts, j_EFIE, X) + ff_H_EFIE = potential(BEAST.MWDoubleLayerFarField3D(𝓚), pts, j_EFIE, X) + ff_H_EFIE_rotated = potential(n × BEAST.MWDoubleLayerFarField3D(𝓚), pts, -j_EFIE, n × X) + ff_H_EFIE_doublerotated = potential(n × BEAST.MWDoubleLayerRotatedFarField3D(n × 𝓚), pts, -j_EFIE, X) + + + @test norm(nf_E_EFIE - E.(pts))/norm(E.(pts)) ≈ 0 atol=0.01 + @test norm(nf_H_EFIE - H.(pts))/norm(H.(pts)) ≈ 0 atol=0.01 + @test norm(ff_E_EFIE - E.(pts, isfarfield=true))/norm(E.(pts, isfarfield=true)) ≈ 0 atol=0.001 + @test norm(ff_H_EFIE - H.(pts, isfarfield=true))/norm(H.(pts, isfarfield=true)) ≈ 0 atol=0.001 + @test norm(ff_H_EFIE_rotated - H.(pts, isfarfield=true))/norm(H.(pts, isfarfield=true)) ≈ 0 atol=0.001 + @test norm(ff_H_EFIE_doublerotated - H.(pts, isfarfield=true))/norm(H.(pts, isfarfield=true)) ≈ 0 atol=0.001 + @test ff_H_EFIE ≈ ff_H_EFIE_rotated rtol=1e-7 + @test ff_H_EFIE_rotated ≈ ff_H_EFIE_doublerotated rtol=1e-7 + + + K_bc = Matrix(assemble(𝓚,Y,X)) + G_nxbc_rt = Matrix(assemble(𝓝,Y,X)) + h_bc = Vector(assemble(𝒉,Y)) + M_bc = -U(0.5)*G_nxbc_rt + K_bc + j_BCMFIE = M_bc\h_bc + + nf_E_BCMFIE = potential(MWSingleLayerField3D(𝓣), pts, j_BCMFIE, X) + nf_H_BCMFIE = potential(BEAST.MWDoubleLayerField3D(𝓚), pts, j_BCMFIE, X) + ff_E_BCMFIE = potential(MWFarField3D(𝓣), pts, j_BCMFIE, X) + + # @show length(pts) + # @show norm.(nf_E_BCMFIE - E.(pts)) ./ norm.(E.(pts)) + + @test norm(nf_E_BCMFIE - E.(pts))/norm(E.(pts)) ≈ 0 atol=0.01 + @test norm(nf_H_BCMFIE - H.(pts))/norm(H.(pts)) ≈ 0 atol=0.01 + @test norm(ff_E_BCMFIE - E.(pts, isfarfield=true))/norm(E.(pts, isfarfield=true)) ≈ 0 atol=0.01 + + H = dipolemw3d(location=SVector(U(0.0),U(0.0),U(0.3)), + orientation=U(1e-9).*SVector(U(0.5),U(0.5),U(0)), + wavenumber=k) + + # This time, we do not specify alpha and beta + # We include η in the magnetic RHS + 𝓣 = Maxwell3D.singlelayer(wavenumber=k) + + 𝒉 = (n × H) × n + E = (1/(im*ε*ω))*curl(H) + 𝒆 = (n × E) × n + + X = raviartthomas(Γ) + Y = buffachristiansen(Γ) + + T = Matrix(assemble(𝓣,X,X)) + e = Vector(assemble(𝒆,X)) + j_EFIE = T\e + + nf_E_EFIE = potential(MWSingleLayerField3D(wavenumber=k), pts, j_EFIE, X) + nf_H_EFIE = potential(BEAST.MWDoubleLayerField3D(wavenumber=k), pts, j_EFIE, X) ./ η + ff_E_EFIE = potential(MWFarField3D(wavenumber=k), pts, j_EFIE, X) + + @test norm(nf_E_EFIE - E.(pts))/norm(E.(pts)) ≈ 0 atol=0.01 + @test norm(nf_H_EFIE - H.(pts))/norm(H.(pts)) ≈ 0 atol=0.01 + @test norm(ff_E_EFIE - E.(pts, isfarfield=true))/norm(E.(pts, isfarfield=true)) ≈ 0 atol=0.01 + + @hilbertspace s + @hilbertspace t + + MFIE_discretebilform = @discretise( + -U(0.5)*𝓝[t, s] + 𝓚[t, s] == η*𝒉[t], + s∈X, t∈Y + ) + + j_BCMFIE = solve(MFIE_discretebilform)[Block(1)] + + nf_E_BCMFIE = potential(MWSingleLayerField3D(wavenumber=k), pts, j_BCMFIE, X) + nf_H_BCMFIE = potential(BEAST.MWDoubleLayerField3D(wavenumber=k), pts, j_BCMFIE, X) ./ η + ff_E_BCMFIE = potential(MWFarField3D(wavenumber=k), pts, j_BCMFIE, X) + + @test norm(j_BCMFIE - j_EFIE)/norm(j_EFIE) ≈ 0 atol=0.02 + @test norm(nf_E_BCMFIE - E.(pts))/norm(E.(pts)) ≈ 0 atol=0.01 + @test norm(nf_H_BCMFIE - H.(pts))/norm(H.(pts)) ≈ 0 atol=0.01 + @test norm(ff_E_BCMFIE - E.(pts, isfarfield=true))/norm(E.(pts, isfarfield=true)) ≈ 0 atol=0.01 +end diff --git a/test/test_directproduct.jl b/test/test_directproduct.jl index 7eba3395..2fff26f4 100644 --- a/test/test_directproduct.jl +++ b/test/test_directproduct.jl @@ -2,25 +2,35 @@ using CompScienceMeshes using BEAST using Test +import LinearMaps -m1 = meshrectangle(1.0, 1.0, 0.5) -m2 = translate!(meshrectangle(1.0, 1.0, 0.5), point(0,0,1)) -m3 = translate!(meshrectangle(1.0, 1.0, 0.5), point(0,0,2)) +for U in [Float32, Float64] + local m1 = meshrectangle(U(1.0), U(1.0), U(0.5)) + local m2 = CompScienceMeshes.translate!(meshrectangle(U(1.0), U(1.0), U(0.5)), point(U,0,0,1)) + local m3 = CompScienceMeshes.translate!(meshrectangle(U(1.0), U(1.0), U(0.5)), point(U,0,0,2)) -X1 = raviartthomas(m1) -X2 = raviartthomas(m2) -X3 = raviartthomas(m3) + local X1 = raviartthomas(m1) + local X2 = raviartthomas(m2) + local X3 = raviartthomas(m3) -X = X1 × X2 × X3 + local X = X1 × X2 × X3 -n1 = numfunctions(X1) -n2 = numfunctions(X2) -n3 = numfunctions(X3) + local n1 = numfunctions(X1) + local n2 = numfunctions(X2) + local n3 = numfunctions(X3) -@test numfunctions(X) == n1 + n2 + n3 -nt = numfunctions(X) + @test numfunctions(X) == n1 + n2 + n3 + local nt = numfunctions(X) -T = MWSingleLayer3D(1.0) -t = assemble(T, X, X) + local T = MWSingleLayer3D(U(1.0)) + local t = assemble(T, X, X) -@test size(t) == (nt,nt) + @test size(t) == (nt,nt) + + local bilterms = [BEAST.Variational.BilTerm(1,1,Any[],Any[],1,T)] + + local BilForm = BEAST.Variational.BilForm(:i, :j, bilterms) + local BXX = BEAST.assemble(BilForm, X, X) + # @assert BXX.lmap isa BEAST.LiftedMaps.LiftedMap + # @test typeof(assemble(BilForm, X, X)) == LinearMaps.LinearCombination{U, Vector{LinearMaps.LinearMap}} +end \ No newline at end of file diff --git a/test/test_dvg.jl b/test/test_dvg.jl index 262d650d..28a33505 100644 --- a/test/test_dvg.jl +++ b/test/test_dvg.jl @@ -1,11 +1,23 @@ using BEAST using CompScienceMeshes +using SparseArrays using Test Γ = meshrectangle(1.0,1.0,1.0) γ = meshsegment(1.0,1.0,3) -X = raviartthomas(Γ, γ) + +in_interior = CompScienceMeshes.interior_tpredicate(Γ) +on_junction = CompScienceMeshes.overlap_gpredicate(γ) +edges = submesh(skeleton(Γ,1)) do m,e + in_interior(m, e) ||on_junction(chart(m, e)) +end + +# edges = skeleton(Γ,1) do m, e +# in_interior(m, e) ||on_junction(chart(m, e)) +# end +length(edges) +X = raviartthomas(Γ, edges) x = divergence(X) @@ -15,7 +27,7 @@ for (_f,_g) in zip(x.fns, X.fns) @test length(_f) == length(_g) if length(_f) == 1 c = _f[1].cellid - _s = chart(Γ, Γ.faces[c]) + _s = chart(Γ, c) @test _f[1].coeff == 1 / volume(_s) else @test _f[1].coeff + _f[2].coeff == 0 diff --git a/test/test_dyadicop.jl b/test/test_dyadicop.jl new file mode 100644 index 00000000..04172341 --- /dev/null +++ b/test/test_dyadicop.jl @@ -0,0 +1,35 @@ +using Test + +using LinearAlgebra +using CompScienceMeshes +using BEAST + +@testset begin + m = meshsphere(radius=1.0, h=0.35) + X = lagrangec0d1(m) + + F = x -> 1.0 + f = BEAST.ScalarTrace{Float64}(F) + g = BEAST.ScalarTrace{Float64}(F) + + op = 2*BEAST.DyadicOp(f,g) + A = assemble(op, X, X) + + b = assemble(f,X) + c = assemble(f,X) + + M = Matrix(A) + @test norm(M - 2*b*c') ≤ 1e-8 + + @hilbertspace j[1:2] + @hilbertspace k[1:2] + + X2 = X × X + bf = sum(op[k[i],j[i]] for i in 1:2) + A2 = assemble(bf, X2, X2) + # A2 = assemble(@discretise(BEAST.diag(op)[k,j], k∈X2, j∈X2)) + M2 = Matrix(A2) + + nX = length(X) + @test size(M2) == (2*nX, 2*nX) +end diff --git a/test/test_evie_dvie.jl b/test/test_evie_dvie.jl new file mode 100644 index 00000000..f57020a3 --- /dev/null +++ b/test/test_evie_dvie.jl @@ -0,0 +1,128 @@ +using SphericalScattering +using BEAST, CompScienceMeshes +using StaticArrays +using LinearAlgebra + + +using Test + + +@testset "EVIE and DVIE - Scattering of plane waves on a sphere" begin + + ϵ0 = 1.0 + ϵ1 = 5.0 + μ0 = 1.0 + ω = 1.0 + κ0 = ω * √(ϵ0*μ0) + ϵ_r = ϵ1/ϵ0 + + amplitude = 1.0 + polarization = x̂ + direction = ẑ + + E_inc = VIE.planewave(amplitude = amplitude, direction = direction, polarization = polarization, wavenumber = κ0) + + r = 1.0 + h = 0.35 + mesh = CompScienceMeshes.tetmeshsphere(r,h) + bnd = boundary(mesh) + #using PlotlyJS + #PlotlyJS.plot(CompScienceMeshes.wireframe(mesh)) + + + # Analytical solution from SphericalScattering.jl + sp = DielectricSphere(; radius = r, filling = Medium(ϵ1, μ0)) + ex = planeWave( amplitude = amplitude, + polarization = polarization, + direction = direction, + embedding = Medium(ϵ0, μ0), + frequency = ω/(2*pi)) + + # Observation points + range_ = range(-1.0*r,stop=1.0*r,length=24) + points = [point(x,y,z) for x in range_ for y in range_ for z in range_] + points_inside = Vector{SVector{3,Float64}}() + for p in points + norm(p)<0.95*r && push!(points_inside, p) + end + + + E_inside = field(sp, ex, ElectricField(points_inside)) + + + # evie + function generate_tau2(ε_ins, ε_env) + function tau(x::SVector{U,T}) where {U,T} + return ε_ins/ε_env - 1.0 # (ϵ_r - 1) + end + return tau + end + tau2 = generate_tau2(ϵ1, ϵ0) + + ttrc = X2 -> ttrace(X2,bnd) + + X2 = nedelecc3d(mesh) + @show numfunctions(X2) + + I2 = Identity() + K2 = VIE.singlelayer2(wavenumber = κ0, tau = tau2) + B2 = VIE.boundary2(wavenumber = κ0, tau = tau2) + + M_I2 = assemble(I2, X2, X2) + M_K2 = assemble(-K2, X2, X2) + M_B2 = assemble(B2, ttrc(X2), X2) + + M_evie = ϵ_r*M_I2 + M_K2 + M_B2 + + b2 = assemble(E_inc, X2) + u_evie = M_evie \ b2 + + E_evie_inside = BEAST.grideval(points_inside, u_evie, X2) + + + # dvie + function generate_tau(ε_ins, ε_env) + function tau(x::SVector{U,T}) where {U,T} + return 1.0 - ε_env/ε_ins # (1 - 1/ϵ_r) + end + return tau + end + tau = generate_tau(ϵ1, ϵ0) + + + ntrc = X -> ntrace(X,bnd) + + X = nedelecd3d(mesh) + @show numfunctions(X) + + I = Identity() + K = VIE.singlelayer(wavenumber = κ0, tau = tau) + B = VIE.boundary(wavenumber = κ0, tau = tau) + + M_I = assemble(I, X, X) + M_K = assemble(-K, X, X) + M_B = assemble(-B, ntrc(X), X) + + M_dvie = (1.0/ϵ_r)*M_I + M_K/ϵ0 + M_B/ϵ0 # Greens function has the factor 1/(4πϵ₀), but ϵ₀ not in BEAST... => /ϵ0 + + b = ϵ0 * assemble(E_inc, X) + u_dvie = M_dvie \ b + + E_dvie_inside = (1/ϵ1)*(BEAST.grideval(points_inside, u_dvie, X)) # E_inside = (1/ϵ1) * D_inside + + + + + err_evie = norm(E_evie_inside - E_inside) / norm(E_inside) + err_dvie = norm(E_dvie_inside - E_inside) / norm(E_inside) + err_ = norm(E_dvie_inside - E_evie_inside) / norm(E_evie_inside) + + @show err_evie + @show err_dvie + @show err_ + + @test err_evie < 0.15 + @test err_dvie < 0.17 + @test err_ < 0.18 + +end \ No newline at end of file diff --git a/test/test_fastintegrnds.jl b/test/test_fastintegrnds.jl new file mode 100644 index 00000000..2affa6ea --- /dev/null +++ b/test/test_fastintegrnds.jl @@ -0,0 +1,31 @@ +using Test + +using CompScienceMeshes +using BEAST + +s = simplex( + point(5,1,0), + point(-1,3,0), + point(0,0,2), +) + +locspace = BEAST.RTRefSpace{Float64}() +t = Maxwell3D.singlelayer(wavenumber=1.0) + +igd = BEAST.Integrand(t, locspace, locspace, s, s) + +u = point(1/3,1/3) +v = point(1/4,2/3) + +igd(u,v) + +dom = CompScienceMeshes.domain(s) +p = CompScienceMeshes.neighborhood(dom, u) +f̂ = locspace(p) +Dx = tangents(s, u) +f = map(f̂) do fi + (value = Dx * fi.value, divergence = fi.divergence) end + +q = neighborhood(s, u) +g = locspace(q) +J = jacobian(q) \ No newline at end of file diff --git a/test/test_fourier.jl b/test/test_fourier.jl index 3700b301..783b816a 100644 --- a/test/test_fourier.jl +++ b/test/test_fourier.jl @@ -2,25 +2,26 @@ using BEAST using Test using LinearAlgebra -width = 4.0 -delay = 6.0 -g = BEAST.creategaussian(width, delay) -G = BEAST.fouriertransform(g) +for T in [Float32, Float64] + width = T(4.0) + delay = T(6.0) + g = BEAST.creategaussian(width, delay) + G = BEAST.fouriertransform(g) -dx = width / 15 -x0 = 0.0 -x = x0 : dx : 3*delay -n = length(x) -y = g.(x) + dx = width / 15 + x0 = T(0.0) + x = x0 : dx : 3*delay + n = length(x) + y = g.(x) -Y, dω, ω0 = BEAST.fouriertransform(y, dx, x0) -ω = collect(ω0 .+ (0:n-1)*dω) -Z = G.(ω) + Y, dω, ω0 = BEAST.fouriertransform(y, dx, x0) + ω = collect(ω0 .+ (0:n-1)*dω) + Z = G.(ω) -# using Plots -# plot(ω, real(Y)) -# scatter!(ω, real(Z)) -# plot!(ω, imag(Y)) -# scatter!(ω, imag(Z)) - -@test norm(Y-Z) < 1.e-10 + # using Plots + # plot(ω, real(Y)) + # scatter!(ω, real(Z)) + # plot!(ω, imag(Y)) + # scatter!(ω, imag(Z)) + @test norm(Y-Z) < sqrt(eps(T)) +end \ No newline at end of file diff --git a/test/test_gradient.jl b/test/test_gradient.jl index 58e2c6c3..6281f663 100644 --- a/test/test_gradient.jl +++ b/test/test_gradient.jl @@ -2,21 +2,22 @@ using CompScienceMeshes using BEAST using Test +for T in [Float32, Float64] + local o, x, y, z = euclidianbasis(3,T) + tet = simplex(x,y,z,o) + local ctr = center(tet) -o, x, y, z = euclidianbasis(3) -tet = simplex(x,y,z,o) -ctr = center(tet) -T = Float64 -iref = BEAST.LagrangeRefSpace{T,1,4,4}() -oref = BEAST.NDLCCRefSpace{T}() -ishp = BEAST.Shape{T}(1, 3, 1.0) + iref = BEAST.LagrangeRefSpace{T,1,4,4}() + oref = BEAST.NDLCCRefSpace{T}() + ishp = BEAST.Shape{T}(1, 3, 1.0) -output = BEAST.gradient(iref, ishp, tet) + output = BEAST.gradient(iref, ishp, tet) -oval = point(0,0,0) -for oshp in output - global oval += oref(ctr)[oshp.refid].value * oshp.coeff -end + global oval = point(T,0,0,0) + for oshp in output + oval += oref(ctr)[oshp.refid].value * oshp.coeff + end -@test oval ≈ point(0,0,1) + @test oval ≈ point(T,0,0,1) +end \ No newline at end of file diff --git a/test/test_gram.jl b/test/test_gram.jl index f421536e..3d50b93c 100644 --- a/test/test_gram.jl +++ b/test/test_gram.jl @@ -53,8 +53,8 @@ BEAST.assemble_local_mixed!(id, rt, rt, (v,m,n)->(G2[m,n]+=v)) # different but overlapping meshes m1 = meshrectangle(1.0,1.0,1.0) m2 = translate(m1, point(0.5,0.5,0.0)) -I = Identity() +Id = Identity() x1 = lagrangecxd0(m1) x2 = lagrangecxd0(m2) -G = assemble(I,x1,x2) +G = assemble(Id,x1,x2) @test sum(G) ≈ 1/4 diff --git a/test/test_gridfunction.jl b/test/test_gridfunction.jl new file mode 100644 index 00000000..d23e2ae8 --- /dev/null +++ b/test/test_gridfunction.jl @@ -0,0 +1,122 @@ +@testitem "gridfunction" begin + +# using BEAST +using CompScienceMeshes +using StaticArrays +using LinearAlgebra +# using Test +U = Float64 + +a = U(1) + +# Cube between (3,3,3) and (4,4,4) +Γ = translate( + CompScienceMeshes.meshcuboid(a,a,a,U(1.0); generator=:gmsh), + SVector(3.0,3.0,3.0)) + +C0 = lagrangec0d1(Γ) + +# We interpolate the function f(x, y, z) = z +coeffs = [v[3] for v in vertices(Γ)] + +gfc0 = BEAST.FEMFunction(coeffs, C0) + +# We integrate on a cube between the vertices (3,3,3) and (4,4,4) +# the function f(x, y, z) = z +# L1 result is = 4 + 3 + 4 * (16/2 - 9/2) = 21 +@test BEAST.Lp_integrate(gfc0; p=1) ≈ 21 + +# L2 result is sqrt(16 + 9 + 4 * (4^3/3 - 3^3/2)) +@test BEAST.Lp_integrate(gfc0) ≈ sqrt(16 + 9 + 4 * (4^3/3 - 3^3/3)) + +#lincombgfs = BEAST.LinearCombinationOfAbstractMeshFunctions([1.0; -0.5], [gfc0, gfc0]) +lincombgfs = -0.5*gfc0 + gfc0 + +# We integrate on a cube between the vertices (3,3,3) and (4,4,4) +# the function f(x, y, z) = z +# L1 result is = 4 + 3 + 4 * (16/2 - 9/2) = 21 +@test BEAST.Lp_integrate(lincombgfs; p=1) ≈ 21/2 + +# L2 result is sqrt(16 + 9 + 4 * (4^3/3 - 3^3/2)) +@test BEAST.Lp_integrate(lincombgfs) ≈ sqrt(16 + 9 + 4 * (4^3/3 - 3^3/3))/2 + +CX = lagrangecxd0(Γ) + +# We interpolate the function f(x, y, z) = 2 +# with piecewise constant lagrange elements +constgfcx = BEAST.FEMFunction([2.0 for c in cells(Γ)], CX) +@test BEAST.Lp_integrate(constgfcx; p=1) ≈ 12 +@test BEAST.Lp_integrate(constgfcx) ≈ sqrt(24) + +# We interpolate the function f(x, y, z) = z +# with piecewise constant lagrange elements +coeffsCx = [((Γ.vertices[c[1]] + Γ.vertices[c[2]] + Γ.vertices[c[3]])/3)[3] for c in cells(Γ)] + +gfcx = BEAST.FEMFunction(coeffsCx, CX) + +# Even though the function, should be able to interpolate it +@test BEAST.Lp_integrate(gfcx; p=1) ≈ 21 + +# Compute the difference in approximation between constant and linear elements +#difflincomb1 = BEAST.LinearCombinationOfAbstractMeshFunctions([1.0; -1.0], [gfcx, gfc0]) +difflincomb1 = gfcx - gfc0 + +@test BEAST.Lp_integrate(difflincomb1)/BEAST.Lp_integrate(gfc0) ≈ 0.03866223365135301 + +## Test GlobalFunction +f(x) = x[3] +glbf = BEAST.GlobalFunction(f, Γ, Vector(1:numcells(Γ))) + +@test BEAST.Lp_integrate(glbf; p=1) ≈ 21 + +g(x) = x[3]^2 +glbf2 = BEAST.GlobalFunction(g, Γ, Vector(1:numcells(Γ))) + +@test BEAST.Lp_integrate(glbf2; p=1) ≈ 74.33333333333334 + +## Test linear combination of global and fem functions +idxfem, idxglobal = BEAST.indices_splitfemglobal(gfc0 - glbf + 0.0gfcx) + +@test length(idxfem) == 2 +@test length(idxglobal) == 1 + +## +@test BEAST.Lp_integrate(gfc0 - glbf) / BEAST.Lp_integrate(glbf) ≈ 0 atol=1e-16 + +@test BEAST.Lp_integrate(gfcx - glbf) / BEAST.Lp_integrate(glbf) ≈ 0.038662233651353003 + +## Global function on limited supported +f(x) = x[3] + +idx_topplate = Int64[] + +for (i, face) in enumerate(cells(Γ)) + center = (Γ.vertices[face[1]] + Γ.vertices[face[2]] + Γ.vertices[face[3]]) / 3.0 + if center[3] > 3.99 + push!(idx_topplate, i) + end +end + +glbf_toppplate = BEAST.GlobalFunction(f, Γ, idx_topplate) +@test BEAST.Lp_integrate(glbf_toppplate, p=1) ≈ 4.000000000000003 + +@test BEAST.Lp_integrate(gfc0 - glbf_toppplate, p=1) ≈ 21 - 4 # Topplate accounts for 4 + +## + +Γ2 = CompScienceMeshes.meshcuboid(a,a,a,U(1.0); generator=:gmsh) + +coeffsΓ2 = [v[3] for v in vertices(Γ2)] + +C0Γ2 = lagrangec0d1(Γ2) + +gfc0Γ2 = BEAST.FEMFunction(coeffsΓ2, C0Γ2) + +@test_throws ErrorException("Functions must be defined over the same geometry.") gfc0 + gfc0Γ2 + +@test_throws ErrorException("Functions must be defined over the same geometry.") lincombgfs + 1.0gfc0Γ2 + +## Test mixed combinations +@test BEAST.Lp_integrate(gfc0 + 1im * gfc0; p=1) ≈ sqrt(2) * 21 + +end \ No newline at end of file diff --git a/test/test_gwp.jl b/test/test_gwp.jl new file mode 100644 index 00000000..f6bb6cad --- /dev/null +++ b/test/test_gwp.jl @@ -0,0 +1,71 @@ +@testitem "refspace: dimension" begin + using CompScienceMeshes + + T = Float64 + Dim, Nverts, Degree = 2, 3, 3 + + dom = CompScienceMeshes.ReferenceSimplex{Dim,T,Nverts}() + ϕ = BEAST.GWPCurlRefSpace{T,Degree}() + + u = (one(T)/3, one(T)/3) + vals = ϕ(dom, u) + + nf = (Degree+1)*(Degree+3) + @test length(vals) == nf +end + + +@testitem "refspace: self-interpolate" begin + using CompScienceMeshes + using LinearAlgebra + + T, Dim, Nverts, Degree = Float64, 2, 3, 2 + dom = CompScienceMeshes.ReferenceSimplex{Dim,T,Nverts}() + ϕ = BEAST.GWPCurlRefSpace{T,Degree}() + + fields(p) = [v.value for v in ϕ(dom, p)] + # fields(p) = [ϕ(dom,p)[1].value] + coeffs = BEAST.interpolate(fields, ϕ, dom) + + nf = numfunctions(ϕ, dom) + @test coeffs ≈ Matrix{T}(I,nf,nf) atol=sqrt(eps(T)) + # display(round.(coeffs, digits=3)) + +end + + +@testitem "confspace: interpolate lowest order" begin + using CompScienceMeshes + using LinearAlgebra + + T, Dim, Nverts, Degree = Float64, 2, 3, 2 + supp = simplex( + point(T,3,0,0), + point(T,0,2,0), + point(T,0,0,-1)) + + ϕ = BEAST.GWPCurlRefSpace{T,Degree}() + ψ = BEAST.NDRefSpace{T}() + + fields(p) = [v.value for v in ψ(p)] + coeffs = BEAST.interpolate(fields, ϕ, supp) + + nf1 = numfunctions(ψ, domain(supp)) + nf2 = numfunctions(ϕ, domain(supp)) + @test size(coeffs) == (nf1, nf2) + # display(round.(coeffs, digits=3)) + + pts = [ + point(T, 0.3, 0.1), + point(T, 0.1, 0.3), + point(T, 0.5, 0.0)] + + for (i,u) in enumerate(pts) + p = neighborhood(supp, u) + basisvals = ϕ(p) + fieldvals = ψ(p) + r = sum(c * v.value for (c,v) in zip(coeffs[i,:], basisvals)) + s = fieldvals[i].value + @test r ≈ s atol=sqrt(eps(T)) + end +end \ No newline at end of file diff --git a/test/test_handlers.jl b/test/test_handlers.jl new file mode 100644 index 00000000..62ed925b --- /dev/null +++ b/test/test_handlers.jl @@ -0,0 +1,106 @@ +using BEAST +using StaticArrays +using Test + +for T in [Float16, Float32, Float64] + @test_throws ErrorException BEAST.gamma_wavenumber_handler(T(1), T(2)) + + gamma, wavenumber = BEAST.gamma_wavenumber_handler(T(1), nothing) + @test gamma === T(1) + + gamma, wavenumber = BEAST.gamma_wavenumber_handler(nothing, T(2)) + @test gamma === T(2) * im + + gamma, wavenumber = BEAST.gamma_wavenumber_handler(nothing, nothing) + @test BEAST.isstatic(gamma) + @test gamma === Val(0) + + gamma, wavenumber = BEAST.gamma_wavenumber_handler(T(0), nothing) + @test gamma === T(0) + @test !BEAST.isstatic(gamma) + + gamma, wavenumber = BEAST.gamma_wavenumber_handler(nothing, T(1)) + @test gamma === T(1) * im + @test !BEAST.isstatic(gamma) + + @test_throws ErrorException BEAST.operator_parameter_handler(T(1), T(1), T(1)) + alpha, gamma = BEAST.operator_parameter_handler(nothing, T(1), nothing) + @test alpha === T(1) + @test gamma === T(1) + + alpha, gamma = BEAST.operator_parameter_handler(nothing, nothing, nothing) + @test alpha === 1.0 + @test BEAST.isstatic(gamma) + + alpha, gamma = BEAST.operator_parameter_handler(nothing, im * T(0), nothing) + @test alpha === T(1) + @test !BEAST.isstatic(gamma) + + # Helmholtz3D + operator = Helmholtz3D.hypersingular(; alpha=nothing, beta=nothing, gamma=nothing, + wavenumber=nothing + ) + @test BEAST.isstatic(operator) + @test operator.alpha === 0.0 + @test operator.beta === 1.0 + @test BEAST.gamma(operator) === 0.0 + + operator = Helmholtz3D.hypersingular(; alpha=T(1), beta=T(2)) + @test BEAST.isstatic(operator) + @test operator.alpha === T(1) + @test operator.beta === T(2) + @test BEAST.gamma(operator) === T(0) + @test scalartype(operator) == T + + pwave = Helmholtz3D.planewave(; direction=SVector(T(0), T(0), T(1))) + @test pwave.direction == SVector(T(0), T(0), T(1)) + @test pwave.gamma === T(0) + @test pwave.amplitude === T(1) + + mpol = Helmholtz3D.monopole(; position=SVector(T(0), T(0), T(0)), amplitude=T(1)) + @test mpol.position === SVector(T(0), T(0), T(0)) + @test mpol.gamma === T(0) + @test mpol.amplitude === T(1) + + gradmpol = Helmholtz3D.grad_monopole(; position=SVector(T(0), T(0), T(0)), + amplitude=T(1)) + @test gradmpol.position === SVector(T(0), T(0), T(0)) + @test gradmpol.gamma === T(0) + @test gradmpol.amplitude === T(1) + + # FarFields + @test_throws AssertionError BEAST.MWFarField3D() + @test_throws AssertionError BEAST.MWDoubleLayerFarField3D() + @test_throws AssertionError BEAST.MWDoubleLayerRotatedFarField3D() + + # Maxwell3D + operator = Maxwell3D.singlelayer(; alpha=T(1), beta=T(2)) + @test BEAST.isstatic(operator) + @test operator.α === T(1) + @test operator.β === T(2) + @test BEAST.gamma(operator) === T(0) + + operator = Maxwell3D.doublelayer() + @test BEAST.isstatic(operator) + @test BEAST.gamma(operator) === 0.0 + + operator = Maxwell3D.doublelayer(; alpha=T(1)) + @test BEAST.isstatic(operator) + @test operator.alpha === T(1) + @test BEAST.gamma(operator) === T(0) + + # BEAST + pwave = BEAST.planewavemw3d(; + direction=SVector(T(1), T(0), T(0)), + polarization=SVector(T(0), T(1), T(0)) + ) + @test pwave.direction === SVector(T(1), T(0), T(0)) + @test pwave.polarisation === SVector(T(0), T(1), T(0)) + @test pwave.gamma === T(0) + + dpole = BEAST.dipolemw3d(location=SVector(T(0), T(0), T(0)), + orientation=SVector(T(0), T(0), T(1))) + @test dpole.location === SVector(T(0), T(0), T(0)) + @test dpole.orientation === SVector(T(0), T(0), T(1)) + @test dpole.gamma === T(0) +end diff --git a/test/test_hh2d_exec.jl b/test/test_hh2d_exec.jl new file mode 100644 index 00000000..9ac820d2 --- /dev/null +++ b/test/test_hh2d_exec.jl @@ -0,0 +1,46 @@ +using BEAST +using CompScienceMeshes +using StaticArrays +using Test + +let + ε0 = 8.854187821e-12 + μ0 = 4π*1e-7 + c0 = 1/sqrt(ε0*μ0) + η0 = sqrt(μ0/ε0) + + f = 1e7 # 1 GHz + ω = 2π*f + λ = c0/f + k = 2π/λ + + Γ = CompScienceMeshes.meshsegment(1.0,1.0) + Γ = CompScienceMeshes.Mesh([SVector(0.0, 0.0), SVector(1.0, 1.0)], Γ.faces) + + X0 = lagrangecxd0(Γ) + + E0 = 1.0 # amplitude + Einc = Helmholtz2D.planewave(; amplitude=E0, wavenumber=k, direction=SVector(1.0, 0.0)) + + Hinc = -1.0 * curl(Einc) + #hTM = assemble(TangentTrace(Hinc), X0) + + h1 = assemble(TangentTrace(Hinc), X0) + h2 = assemble(BEAST.NormalDerivative(Einc), X0) + + @test norm(h1-h2) /norm(h2) ≈ 0 atol=1e-15 + + ## + Γ = CompScienceMeshes.meshsegment(1.0,0.5) + + X1 = lagrangec0d1(Γ) + + E0 = 1.0 # amplitude + Einc = Helmholtz2D.planewave(; amplitude=E0, wavenumber=k, direction=SVector(0.0, 1.0)) + Hinc = -1.0 * curl(Einc) + + h1 = assemble(TangentTrace(Hinc), X1) + h2 = assemble(BEAST.NormalDerivative(Einc), X1) + + @test norm(h1-h2) /norm(h2) ≈ 0 +end \ No newline at end of file diff --git a/test/test_hh2d_mie.jl b/test/test_hh2d_mie.jl new file mode 100644 index 00000000..657b0948 --- /dev/null +++ b/test/test_hh2d_mie.jl @@ -0,0 +1,288 @@ +using BEAST +using CompScienceMeshes +using StaticArrays +using LinearAlgebra +using Test +using SpecialFunctions + + +# This unit tests partially repeats hh2d_nearfield, but it +# tests the excitation part more in detail. +# Overall, it is rather an integration test and maybe should +# be part of an example file. +let + ε0 = 8.854187821e-12 + μ0 = 4π*1e-7 + c0 = 1/sqrt(ε0*μ0) + η0 = sqrt(μ0/ε0) + + f = 1e9 # 1 GHz + ω = 2π*f + λ = c0/f + k = 2π/λ + h = λ/10 + a = 1.0 # radius of the scatterer + + circle = CompScienceMeshes.meshcircle(a, h) + X0 = lagrangecxd0(circle) + X1 = lagrangec0d1(circle) + + # Computing the fields on a circle of radius rc + rc = 500.0*a + pts = meshcircle(rc, 0.3 * rc).vertices + + ## Analytical solutions + + dbesselj(n,x) = (besselj(n-1, x) - besselj(n+1, x)) / 2 + dhankelh2(n,x) = (hankelh2(n-1, x) - hankelh2(n+1, x)) / 2 + + cart2polar(x,y) = SVector(sqrt(x^2 + y^2), atan(y, x)) + + # TM planewave solution (based on Sec 6.4.2, Jin, Theory and Computation of Electromagnetic Fields]) + function TM_pec_planewave(E0, k, a, ρ, φ) + n = 0 + + # Jin (6.4.11) + a_n(n) = -(1.0im)^(-n)*(besselj(n, k*a) / hankelh2(n, k*a)) + valEz(n) = a_n(n) * hankelh2(n, k*ρ)*exp(im*n*φ) + valHφ(n) = 1/(im * ω * μ0) * k * a_n(n) * dhankelh2(n,k*ρ) * exp(im*n*φ) + valHρ(n) = -1/(im * ω * μ0) * (im * n / ρ) * a_n(n) * hankelh2(n, k*ρ) * exp(im*n*φ) + + retEz = valEz(0) + retHφ = valHφ(0) + retHρ = valHρ(0) + + while true + n += 1 + bufEz = valEz(n) + valEz(-n) + if abs(bufEz) >= abs(retEz) * eps(eltype(real(retEz))) * 1e3 + retEz += bufEz + else + break + end + end + + n=0 + while true + n += 1 + bufHφ = valHφ(n) + valHφ(-n) + if abs(bufHφ) >= abs(retHφ) * eps(eltype(real(retHφ))) * 1e3 + retHφ += bufHφ + else + break + end + end + + n=0 + while true + n += 1 + bufHρ = valHρ(n) + valHρ(-n) + if abs(bufHρ) >= abs(retHρ) * eps(eltype(real(retHρ))) * 1e3 + retHρ += bufHρ + else + break + end + end + + return E0 * retEz, E0*(retHρ*SVector(cos(φ), sin(φ)) + retHφ*SVector(-sin(φ), cos(φ))) + end + + function TM_pec_planewave_E(E0, k, a, pts) + return [TM_pec_planewave(E0, k, a, cart2polar(p[1], p[2])...)[1] for p in pts] + end + + function TM_pec_planewave_H(E0, k, a, pts) + return [TM_pec_planewave(E0, k, a, cart2polar(p[1], p[2])...)[2] for p in pts] + end + + + # TE planewave solution + function TE_pec_planewave(H0, k, a, ρ, φ) + n = 0 + + # Jin (6.4.19) + b(n) = -(1.0im)^(-n)*dbesselj(n,k*a)/dhankelh2(n,k*a) + + # Hz is not needed, but maybe we use it later for MFIE + valHz(n) = b(n) * hankelh2(n, k*ρ)*exp(im*n*φ) + valEφ(n) = -1/(im * ω * ε0) * k * b(n) * dhankelh2(n, k*ρ)*exp(im*n*φ) + valEρ(n) = 1/(im * ω * ε0) * (im * n / ρ) * b(n) * hankelh2(n, k*ρ)*exp(im*n*φ) + + retHz = valHz(0) + retEφ = valEφ(0) + retEρ = valEρ(0) + + while true + n += 1 + bufHz = valHz(n) + valHz(-n) + if abs(bufHz) >= abs(retHz) * eps(eltype(real(retHz))) * 1e3 + retHz += bufHz + else + break + end + end + + n = 0 + while true + n += 1 + bufEφ = valEφ(n) + valEφ(-n) + if abs(bufEφ) >= abs(retEφ) * eps(eltype(real(retEφ))) * 1e3 + retEφ += bufEφ + else + break + end + end + + n = 0 + while true + n += 1 + bufEρ = valEρ(n) + valEρ(-n) + if abs(bufEρ) >= abs(retEρ) * eps(eltype(real(retEρ))) * 1e3 + retEρ += bufEρ + else + break + end + end + + return H0*retHz, H0*(retEρ*SVector(cos(φ), sin(φ)) + retEφ*SVector(-sin(φ), cos(φ))) + end + + function TE_pec_planewave_H(H0, k, a, pts) + return [TE_pec_planewave(H0, k, a, cart2polar(p[1],p[2])...)[1] for p in pts] + end + + function TE_pec_planewave_E(H0, k, a, pts) + return [TE_pec_planewave(H0, k, a, cart2polar(p[1],p[2])...)[2] for p in pts] + end + + """ + Here, the line current is infinitely long along the z-axis, and is pointed towards the + z-axis as well. Thus, the potentials Ax, Ay will not be excited, leaving us only Az to + compute, which will then give us Ez. The monopole excitation in BEAST is used for this + excitation. + + Ref: Sec 6.5.3, Jin, Theory and Computation of Electromagnetic fields + Analytical expression used for scattered field: Eq 6.5.38 + """ + function TM_pec_line_curr(I, k, a, ρ, φ, ρp, φp) + n = 0 + A_d(n) = besselj(n,k*a)/hankelh2(n,k*a) + val(n) = A_d(n) * hankelh2(n,k*ρ) * hankelh2(n,k*ρp) * exp(im*n*(φ-φp)) + ret = val(0) + while true + n += 1 + buf = val(n) + val(-n) + if abs(buf) >= abs(ret) * eps(eltype(real(ret))) * 1e3 + ret += val(n) + val(-n) + else + break + end + end + return η0*k*I/4 * ret + end + + function TM_pec_line_curr_E(I, k, a, pts, spt) + cart2polar(x,y) = SVector(sqrt(x^2 + y^2), atan(y, x)) + return [TM_pec_line_curr(I,k,a,cart2polar(p[1],p[2])..., spt[1],spt[2]) for p in pts] + end + + + ## TM scattering + + # TM-EFIE system matrix + 𝒮 = Helmholtz2D.singlelayer(; alpha=im*k*η0, wavenumber=k) + M_TMEFIE = assemble(𝒮, X0, X0) + + # TM-MFIE system matrix + 𝒟ᵀ = Helmholtz2D.doublelayer_transposed(; wavenumber=k) + Dᵀ = assemble(𝒟ᵀ, X0, X0) + + I0 = assemble(BEAST.Identity(), X0, X0) + M_TMMFIE = +0.5*I0 + Dᵀ + + # 1. Excitation: Plane wave (The incoming planewave is along the x-axis) + E0 = 1.0 # Amplitude + Ez_pw_inc = Helmholtz2D.planewave(; amplitude=E0, wavenumber=k, direction=SVector(1.0, 0.0)) + + ez_pw_inc = assemble(DirichletTrace(Ez_pw_inc), X0) + j_TMEFIE_pw = M_TMEFIE \ ez_pw_inc + + # TM-EFIE: We compute the scattered Ez component (scalar) + Ez_pw_sca_num = -potential(HH2DSingleLayerNear(𝒮), pts, j_TMEFIE_pw, X0; type=ComplexF64) + Ez_pw_sca_ana = TM_pec_planewave_E(E0, k, a, pts) + + @test norm(Ez_pw_sca_ana - Ez_pw_sca_num) ./ norm(Ez_pw_sca_ana) < 0.002 + + Ht_pw_sca_num = -potential(HH2DDoubleLayerTransposedNear(𝒟ᵀ), pts, j_TMEFIE_pw, X0; type=SVector{2,ComplexF64}) + Ht_pw_sca_ana = TM_pec_planewave_H(E0, k, a, pts) + + @test norm(Ref(ẑ) .× Ht_pw_sca_num - Ht_pw_sca_ana) ./ norm(Ht_pw_sca_ana) < 0.002 + + # TM-MFIE + Ht_pw_inc = - 1.0 / (im * ω * μ0) * curl(Ez_pw_inc) + ht_pw_inc = assemble(TangentTrace(Ht_pw_inc), X0) + + j_TMMFIE_pw = M_TMMFIE \ ht_pw_inc + + @test norm(j_TMMFIE_pw - j_TMEFIE_pw) / norm(j_TMEFIE_pw) < 0.05 + + # 2. Excitation: Infinite line current + + I = 1.0 # 1 A + ρp = 2.0 # Source radial pos + φp = 0.0 # Source angular pos + xp = ρp * cos(φp) + yp = ρp * sin(φp) + + # Choosing the amplitude of Einc such that [Eq 6.5.11, Jin] is satisfied + Ez_lc_Einc = Helmholtz2D.monopole(;position = SVector(xp,yp), wavenumber=k, amplitude = -η0*k*I*im) + ez_lc_Einc = assemble(DirichletTrace(Ez_lc_Einc),X0) + j_TMEFIE_lc = M_TMEFIE \ ez_lc_Einc + + Ez_lc_sca_num = -potential(HH2DSingleLayerNear(𝒮),pts , j_TMEFIE_lc, X0;type=ComplexF64) + + Ez_lc_sca_ana = TM_pec_line_curr_E(I, k, a, pts, SVector(ρp,φp)) + + @test norm(Ez_lc_sca_num - Ez_lc_sca_ana) / norm(Ez_lc_sca_ana) < 0.003 + + + ## TE scattering + + # TE-MFIE + 𝒟 = Helmholtz2D.doublelayer(; wavenumber=k) + D = assemble(𝒟, X1, X1) + I1 = assemble(BEAST.Identity(), X1, X1) + M_TEMFIE = 0.5I1 - D + + # TE-EFIE + 𝒩 = Helmholtz2D.hypersingular(;alpha=im*ω*μ0, beta=1.0/(im*ω*ε0), wavenumber=k) + M_TEEFIE = assemble(𝒩, X1, X1) + + # 1. Excitation: Planewave + H0 = 1.0 + Hz_pw_inc = Helmholtz2D.planewave(; amplitude=H0, wavenumber=k, direction=SVector(1.0, 0.0)) + hz_pw_inc = -assemble(DirichletTrace(Hz_pw_inc), X1) + j_TEMFIE_pw = M_TEMFIE \ hz_pw_inc + + + Hz_pw_sca_num = -potential(HH2DDoubleLayerNear(𝒟), pts, j_TEMFIE_pw, X1; type=ComplexF64) + Hz_pw_sca_ana = TE_pec_planewave_H(H0, k, a, pts) + + @test norm(Hz_pw_sca_num - Hz_pw_sca_ana) /norm(Hz_pw_sca_ana) < 0.0015 + + Et_pw_inc = 1 / (im * ω * ε0) * curl(Hz_pw_inc) + @test Et_pw_inc.direction == Hz_pw_inc.direction + @test Et_pw_inc.polarization == SVector(0.0, -1.0) + @test Et_pw_inc.amplitude ≈ Et_pw_inc.gamma / (im * ω * ε0) atol=1e-15 + et_pw_inc = assemble(TangentTrace(Et_pw_inc), X1) + + j_TEEFIE_pw = M_TEEFIE \ et_pw_inc + + @test norm(j_TEEFIE_pw - j_TEMFIE_pw)/norm(j_TEMFIE_pw) < 0.007 + + Et_pw_sca_num = potential(HH2DHyperSingularNear(𝒩), pts, j_TEEFIE_pw, X1; type=SVector{2, ComplexF64}) + Et_pw_sca_ana = TE_pec_planewave_E(H0, k, a, pts) + + # We compute the scattered Ez component (scalar) + @test norm(Ref(ẑ) .× Et_pw_sca_num - Et_pw_sca_ana) / norm(Et_pw_sca_ana) < 0.003 +end \ No newline at end of file diff --git a/test/test_hh2d_nearfield.jl b/test/test_hh2d_nearfield.jl new file mode 100644 index 00000000..b3848796 --- /dev/null +++ b/test/test_hh2d_nearfield.jl @@ -0,0 +1,162 @@ +using BEAST +using CompScienceMeshes +using StaticArrays +using LinearAlgebra +using Test + +@testset "2D-Helmholtz potential operators" begin + # r = 10.0 + # λ = 20 * r + # k = 2 * π / λ + # sphere = meshsphere(r, 0.2 * r) + k = 0.031415926535897934 + r = 10.0 + circle = CompScienceMeshes.meshcircle(r, 2.0) + + X0 = lagrangecxd0(circle) + X1 = lagrangec0d1(circle) + + S = Helmholtz2D.singlelayer(; gamma=im * k) + D = Helmholtz2D.doublelayer(; gamma=im * k) + Dt = Helmholtz2D.doublelayer_transposed(; gamma=im * k) + N = Helmholtz2D.hypersingular(; gamma=im * k) + + q = 100.0 + ϵ = 1.0 + + # Interior problem + # Formulations from Sauter and Schwab, Boundary Element Methods(2011), Chapter 3.4.1.1 + + pos1 = SVector(r * 1.5, 0.0) # positioning of point charges + pos2 = SVector(-r * 1.5, 0.0) + + charge1 = Helmholtz2D.monopole(position=pos1, amplitude=q/(4*π*ϵ), wavenumber=k) + charge2 = Helmholtz2D.monopole(position=pos2, amplitude=-q/(4*π*ϵ), wavenumber=k) + + # Potential of point charges + + Φ_inc(x) = charge1(x) + charge2(x) + + gD0 = assemble(DirichletTrace(charge1), X0) + assemble(DirichletTrace(charge2), X0) + gD1 = assemble(DirichletTrace(charge1), X1) + assemble(DirichletTrace(charge2), X1) + gN = assemble(∂n(charge1), X1) + assemble(BEAST.n ⋅ grad(charge2), X1) + + G = assemble(Identity(), X1, X1) + o = ones(numfunctions(X1)) + + # Interior Dirichlet problem - compare Sauter & Schwab eqs. 3.81 + M_IDPSL = assemble(S, X0, X0) # Single layer (SL) + M_IDPDL = (-1 / 2 * assemble(Identity(), X1, X1) + assemble(D, X1, X1)) # Double layer (DL) + + # Interior Neumann problem + # Neumann derivative from DL potential with deflected nullspace + M_INPDL = assemble(N, X1, X1) + # Neumann derivative from SL potential with deflected nullspace + M_INPSL = (1 / 2 * assemble(Identity(), X1, X1) + assemble(Dt, X1, X1)) + + ρ_IDPSL = M_IDPSL \ (-gD0) + ρ_IDPDL = M_IDPDL \ (-gD1) + + ρ_INPDL = M_INPDL \ (gN) + ρ_INPSL = M_INPSL \ (-gN) + + pts = meshcircle(0.8 * r, 0.8 * 0.6 * r).vertices # sphere inside on which the potential and field are evaluated + + pot_IDPSL = potential(HH2DSingleLayerNear(S), pts, ρ_IDPSL, X0; type=ComplexF64) + pot_IDPDL = potential(HH2DDoubleLayerNear(D), pts, ρ_IDPDL, X1; type=ComplexF64) + + pot_INPSL = potential(HH2DSingleLayerNear(S), pts, ρ_INPSL, X1; type=ComplexF64) + pot_INPDL = potential(HH2DDoubleLayerNear(D), pts, ρ_INPDL, X1; type=ComplexF64) + + # Total field inside should be zero + err_IDPSL_pot = norm(pot_IDPSL + Φ_inc.(pts)) / norm(Φ_inc.(pts)) + err_IDPDL_pot = norm(pot_IDPDL + Φ_inc.(pts)) / norm(Φ_inc.(pts)) + err_INPSL_pot = norm(pot_INPSL + Φ_inc.(pts)) / norm(Φ_inc.(pts)) + err_INPDL_pot = norm(pot_INPDL + Φ_inc.(pts)) / norm(Φ_inc.(pts)) + + # Efield(x) = - grad Φ_inc(x) + Efield(x) = -grad(charge1)(x) + -grad(charge2)(x) + + field_IDPSL = -potential(HH2DDoubleLayerTransposedNear(Dt), pts, ρ_IDPSL, X0; type=SVector{2,ComplexF64}) + field_IDPDL = -potential(HH2DHyperSingularNear(N), pts, ρ_IDPDL, X1; type=SVector{2,ComplexF64}) + field_INPSL = -potential(HH2DDoubleLayerTransposedNear(Dt), pts, ρ_INPSL, X1; type=SVector{2,ComplexF64}) + field_INPDL = -potential(HH2DHyperSingularNear(N), pts, ρ_INPDL, X1; type=SVector{2,ComplexF64}) + + err_IDPSL_field = norm(field_IDPSL + Efield.(pts)) / norm(Efield.(pts)) + err_IDPDL_field = norm(field_IDPDL + Efield.(pts)) / norm(Efield.(pts)) + err_INPSL_field = norm(field_INPSL + Efield.(pts)) / norm(Efield.(pts)) + err_INPDL_field = norm(field_INPDL + Efield.(pts)) / norm(Efield.(pts)) + + # Exterior problem + # formulations from Sauter and Schwab, Boundary Element Methods(2011), Chapter 3.4.1.2 + + pos1 = SVector(r * 0.5, 0.0) + pos2 = SVector(-r * 0.5, 0.0) + + charge1 = Helmholtz2D.monopole(position=pos1, amplitude=q/(4*π*ϵ), wavenumber=k) + charge2 = Helmholtz2D.monopole(position=pos2, amplitude=-q/(4*π*ϵ), wavenumber=k) + + gD0 = assemble(DirichletTrace(charge1), X0) + assemble(DirichletTrace(charge2), X0) + gD1 = assemble(DirichletTrace(charge1), X1) + assemble(DirichletTrace(charge2), X1) + gN = assemble(∂n(charge1), X1) + assemble(∂n(charge2), X1) + + G = assemble(Identity(), X1, X1) + o = ones(numfunctions(X1)) + + M_EDPSL = assemble(S, X0, X0) + M_EDPDL = (1 / 2 * assemble(Identity(), X1, X1) + assemble(D, X1, X1)) + + M_ENPDL = assemble(N, X1, X1) + M_ENPSL = -1 / 2 * assemble(Identity(), X1, X1) + assemble(Dt, X1, X1) + + ρ_EDPSL = M_EDPSL \ (-gD0) + ρ_EDPDL = M_EDPDL \ (-gD1) + + ρ_ENPDL = M_ENPDL \ gN + ρ_ENPSL = M_ENPSL \ (-gN) + + testcircle = meshcircle(1.2 * r, 1.2 * 0.6 * r) + pts = testcircle.vertices[norm.(testcircle.vertices) .> r] + + pot_EDPSL = potential(HH2DSingleLayerNear(S), pts, ρ_EDPSL, X0; type=ComplexF64) + pot_EDPDL = potential(HH2DDoubleLayerNear(D), pts, ρ_EDPDL, X1; type=ComplexF64) + pot_ENPDL = potential(HH2DDoubleLayerNear(D), pts, ρ_ENPDL, X1; type=ComplexF64) + pot_ENPSL = potential(HH2DSingleLayerNear(S), pts, ρ_ENPSL, X1; type=ComplexF64) + + err_EDPSL_pot = norm(pot_EDPSL + Φ_inc.(pts)) ./ norm(Φ_inc.(pts)) + err_EDPDL_pot = norm(pot_EDPDL + Φ_inc.(pts)) ./ norm(Φ_inc.(pts)) + err_ENPSL_pot = norm(pot_ENPSL + Φ_inc.(pts)) ./ norm(Φ_inc.(pts)) + err_ENPDL_pot = norm(pot_ENPDL + Φ_inc.(pts)) ./ norm(Φ_inc.(pts)) + + field_EDPSL = -potential(HH2DDoubleLayerTransposedNear(Dt), pts, ρ_EDPSL, X0; type=SVector{2,ComplexF64}) + field_EDPDL = -potential(HH2DHyperSingularNear(N), pts, ρ_EDPDL, X1; type=SVector{2,ComplexF64}) + field_ENPSL = -potential(HH2DDoubleLayerTransposedNear(Dt), pts, ρ_ENPSL, X1; type=SVector{2,ComplexF64}) + field_ENPDL = -potential(HH2DHyperSingularNear(N), pts, ρ_ENPDL, X1; type=SVector{2,ComplexF64}) + + err_EDPSL_field = norm(field_EDPSL + Efield.(pts)) / norm(Efield.(pts)) + err_EDPDL_field = norm(field_EDPDL + Efield.(pts)) / norm(Efield.(pts)) + err_ENPSL_field = norm(field_ENPSL + Efield.(pts)) / norm(Efield.(pts)) + err_ENPDL_field = norm(field_ENPDL + Efield.(pts)) / norm(Efield.(pts)) + + # errors of interior problems + @test err_IDPSL_pot < 1e-3 + @test err_IDPDL_pot < 1e-3 + @test err_INPSL_pot < 1e-3 + @test err_INPDL_pot < 1e-3 + + @test err_IDPSL_field < 1e-2 + @test err_IDPDL_field < 1e-2 + @test err_INPSL_field < 1e-2 + @test err_INPDL_field < 1e-2 + + # errors of exterior problems + @test err_EDPSL_pot < 1e-3 + @test err_EDPDL_pot < 1e-3 + @test err_ENPSL_pot < 1e-3 + @test err_ENPDL_pot < 1e-3 + + @test err_EDPSL_field < 1e-2 + @test err_EDPDL_field < 1e-2 + @test err_ENPSL_field < 1e-2 + @test err_ENPDL_field < 1e-2 +end \ No newline at end of file diff --git a/test/test_hh3d_nearfield.jl b/test/test_hh3d_nearfield.jl new file mode 100644 index 00000000..ff55747e --- /dev/null +++ b/test/test_hh3d_nearfield.jl @@ -0,0 +1,314 @@ +using BEAST +using CompScienceMeshes +using StaticArrays +using LinearAlgebra +using Test + +@testset "Helmholtz potential operators" begin + # r = 10.0 + # λ = 20 * r + # k = 2 * π / λ + # sphere = meshsphere(r, 0.2 * r) + k = 0.031415926535897934 + sphere = readmesh(joinpath(dirname(pathof(BEAST)),"../test/assets/sphere_rad=10_h=2.in")) + + X0 = lagrangecxd0(sphere) + X1 = lagrangec0d1(sphere) + + S = Helmholtz3D.singlelayer(; gamma=im * k) + D = Helmholtz3D.doublelayer(; gamma=im * k) + Dt = Helmholtz3D.doublelayer_transposed(; gamma=im * k) + N = Helmholtz3D.hypersingular(; gamma=im * k) + + q = 100.0 + ϵ = 1.0 + + # Interior problem + # Formulations from Sauter and Schwab, Boundary Element Methods(2011), Chapter 3.4.1.1 + r = 10.0 + pos1 = SVector(r * 1.5, 0.0, 0.0) # positioning of point charges + pos2 = SVector(-r * 1.5, 0.0, 0.0) + + charge1 = Helmholtz3D.monopole(position=pos1, amplitude=q/(4*π*ϵ), wavenumber=k) + charge2 = Helmholtz3D.monopole(position=pos2, amplitude=-q/(4*π*ϵ), wavenumber=k) + + # Potential of point charges + + Φ_inc(x) = charge1(x) + charge2(x) + + gD0 = assemble(DirichletTrace(charge1), X0) + assemble(DirichletTrace(charge2), X0) + gD1 = assemble(DirichletTrace(charge1), X1) + assemble(DirichletTrace(charge2), X1) + gN = assemble(∂n(charge1), X1) + assemble(BEAST.n ⋅ grad(charge2), X1) + + G = assemble(Identity(), X1, X1) + o = ones(numfunctions(X1)) + + # Interior Dirichlet problem - compare Sauter & Schwab eqs. 3.81 + M_IDPSL = assemble(S, X0, X0) # Single layer (SL) + M_IDPDL = (-1 / 2 * assemble(Identity(), X1, X1) + assemble(D, X1, X1)) # Double layer (DL) + + # Interior Neumann problem + # Neumann derivative from DL potential with deflected nullspace + M_INPDL = assemble(N, X1, X1) + G * o * o' * G + # Neumann derivative from SL potential with deflected nullspace + M_INPSL = (1 / 2 * assemble(Identity(), X1, X1) + assemble(Dt, X1, X1)) + G * o * o' * G + + ρ_IDPSL = M_IDPSL \ (-gD0) + ρ_IDPDL = M_IDPDL \ (-gD1) + + ρ_INPDL = M_INPDL \ (gN) + ρ_INPSL = M_INPSL \ (-gN) + + pts = meshsphere(0.8 * r, 0.8 * 0.6 * r).vertices # sphere inside on which the potential and field are evaluated + + pot_IDPSL = potential(HH3DSingleLayerNear(im * k), pts, ρ_IDPSL, X0; type=ComplexF64) + pot_IDPDL = potential(HH3DDoubleLayerNear(im * k), pts, ρ_IDPDL, X1; type=ComplexF64) + + pot_INPSL = potential(HH3DSingleLayerNear(im * k), pts, ρ_INPSL, X1; type=ComplexF64) + pot_INPDL = potential(HH3DDoubleLayerNear(im * k), pts, ρ_INPDL, X1; type=ComplexF64) + + # Total field inside should be zero + err_IDPSL_pot = norm(pot_IDPSL + Φ_inc.(pts)) / norm(Φ_inc.(pts)) + err_IDPDL_pot = norm(pot_IDPDL + Φ_inc.(pts)) / norm(Φ_inc.(pts)) + err_INPSL_pot = norm(pot_INPSL + Φ_inc.(pts)) / norm(Φ_inc.(pts)) + err_INPDL_pot = norm(pot_INPDL + Φ_inc.(pts)) / norm(Φ_inc.(pts)) + + # Efield(x) = - grad Φ_inc(x) + Efield(x) = -grad(charge1)(x) + -grad(charge2)(x) + + field_IDPSL = -potential(HH3DDoubleLayerTransposedNear(im * k), pts, ρ_IDPSL, X0) + field_IDPDL = -potential(HH3DHyperSingularNear(im * k), pts, ρ_IDPDL, X1) + field_INPSL = -potential(HH3DDoubleLayerTransposedNear(im * k), pts, ρ_INPSL, X1) + field_INPDL = -potential(HH3DHyperSingularNear(im * k), pts, ρ_INPDL, X1) + + err_IDPSL_field = norm(field_IDPSL + Efield.(pts)) / norm(Efield.(pts)) + err_IDPDL_field = norm(field_IDPDL + Efield.(pts)) / norm(Efield.(pts)) + err_INPSL_field = norm(field_INPSL + Efield.(pts)) / norm(Efield.(pts)) + err_INPDL_field = norm(field_INPDL + Efield.(pts)) / norm(Efield.(pts)) + + # Exterior problem + # formulations from Sauter and Schwab, Boundary Element Methods(2011), Chapter 3.4.1.2 + + pos1 = SVector(r * 0.5, 0.0, 0.0) + pos2 = SVector(-r * 0.5, 0.0, 0.0) + + charge1 = Helmholtz3D.monopole(position=pos1, amplitude=q/(4*π*ϵ), wavenumber=k) + charge2 = Helmholtz3D.monopole(position=pos2, amplitude=-q/(4*π*ϵ), wavenumber=k) + + gD0 = assemble(DirichletTrace(charge1), X0) + assemble(DirichletTrace(charge2), X0) + gD1 = assemble(DirichletTrace(charge1), X1) + assemble(DirichletTrace(charge2), X1) + gN = assemble(∂n(charge1), X1) + assemble(∂n(charge2), X1) + + G = assemble(Identity(), X1, X1) + o = ones(numfunctions(X1)) + + M_EDPSL = assemble(S, X0, X0) + M_EDPDL = (1 / 2 * assemble(Identity(), X1, X1) + assemble(D, X1, X1)) + + M_ENPDL = assemble(N, X1, X1) + G * o * o' * G + M_ENPSL = -1 / 2 * assemble(Identity(), X1, X1) + assemble(Dt, X1, X1) + G * o * o' * G + + ρ_EDPSL = M_EDPSL \ (-gD0) + ρ_EDPDL = M_EDPDL \ (-gD1) + + ρ_ENPDL = M_ENPDL \ gN + ρ_ENPSL = M_ENPSL \ (-gN) + + testsphere = meshsphere(1.2 * r, 1.2 * 0.6 * r) + pts = testsphere.vertices[norm.(testsphere.vertices) .> r] + + pot_EDPSL = potential(HH3DSingleLayerNear(im * k), pts, ρ_EDPSL, X0; type=ComplexF64) + pot_EDPDL = potential(HH3DDoubleLayerNear(im * k), pts, ρ_EDPDL, X1; type=ComplexF64) + pot_ENPDL = potential(HH3DDoubleLayerNear(im * k), pts, ρ_ENPDL, X1; type=ComplexF64) + pot_ENPSL = potential(HH3DSingleLayerNear(im * k), pts, ρ_ENPSL, X1; type=ComplexF64) + + err_EDPSL_pot = norm(pot_EDPSL + Φ_inc.(pts)) ./ norm(Φ_inc.(pts)) + err_EDPDL_pot = norm(pot_EDPDL + Φ_inc.(pts)) ./ norm(Φ_inc.(pts)) + err_ENPSL_pot = norm(pot_ENPSL + Φ_inc.(pts)) ./ norm(Φ_inc.(pts)) + err_ENPDL_pot = norm(pot_ENPDL + Φ_inc.(pts)) ./ norm(Φ_inc.(pts)) + + field_EDPSL = -potential(HH3DDoubleLayerTransposedNear(im * k), pts, ρ_EDPSL, X0) + field_EDPDL = -potential(HH3DHyperSingularNear(im * k), pts, ρ_EDPDL, X1) + field_ENPSL = -potential(HH3DDoubleLayerTransposedNear(im * k), pts, ρ_ENPSL, X1) + field_ENPDL = -potential(HH3DHyperSingularNear(im * k), pts, ρ_ENPDL, X1) + + err_EDPSL_field = norm(field_EDPSL + Efield.(pts)) / norm(Efield.(pts)) + err_EDPDL_field = norm(field_EDPDL + Efield.(pts)) / norm(Efield.(pts)) + err_ENPSL_field = norm(field_ENPSL + Efield.(pts)) / norm(Efield.(pts)) + err_ENPDL_field = norm(field_ENPDL + Efield.(pts)) / norm(Efield.(pts)) + + # errors of interior problems + @test err_IDPSL_pot < 0.01 + @test err_IDPDL_pot < 0.01 + @test err_INPSL_pot < 0.02 + @test err_INPDL_pot < 0.01 + + @test err_IDPSL_field < 0.01 + @test err_IDPDL_field < 0.02 + @test err_INPSL_field < 0.02 + @test err_INPDL_field < 0.01 + + # errors of exterior problems + @test err_EDPSL_pot < 0.01 + @test err_EDPDL_pot < 0.02 + @test err_ENPSL_pot < 0.01 + @test err_ENPDL_pot < 0.01 + + @test err_EDPSL_field < 0.01 + @test err_EDPDL_field < 0.03 + @test err_ENPSL_field < 0.01 + @test err_ENPDL_field < 0.02 +end + +## Test here some of the mixed discretizatinos +#@testset "Helmholtz potential operators: mixed discretizations with duallagrangecxd0" begin + # r = 10.0 + # sphere = meshsphere(r, 0.2 * r) + # λ = 20 * r + # k = 2 * π / λ + k = 0.031415926535897934 + sphere = readmesh(joinpath(dirname(pathof(BEAST)),"../test/assets/sphere_rad=10_h=2.in")) + X0 = lagrangecxd0(sphere) + X1 = lagrangec0d1(sphere) + Y0 = duallagrangecxd0(sphere; interpolatory=true) + + S = Helmholtz3D.singlelayer(; gamma=im * k) + D = Helmholtz3D.doublelayer(; gamma=im * k) + Dt = Helmholtz3D.doublelayer_transposed(; gamma=im * k) + N = Helmholtz3D.hypersingular(; gamma=im * k) + + q = 100.0 + ϵ = 1.0 + + # Interior problem + # Formulations from Sauter and Schwab, Boundary Element Methods(2011), Chapter 3.4.1.1 + r = 10.0 + pos1 = SVector(r * 1.5, 0.0, 0.0) # positioning of point charges + pos2 = SVector(-r * 1.5, 0.0, 0.0) + + charge1 = Helmholtz3D.monopole(position=pos1, amplitude=q/(4*π*ϵ), wavenumber=k) + charge2 = Helmholtz3D.monopole(position=pos2, amplitude=-q/(4*π*ϵ), wavenumber=k) + + # Potential of point charges + + Φ_inc(x) = charge1(x) + charge2(x) + + gD1 = assemble(DirichletTrace(charge1), Y0) + assemble(DirichletTrace(charge2), Y0) + gN = assemble(∂n(charge1), X1) + assemble(BEAST.n ⋅ grad(charge2), X1) + + G = assemble(Identity(), X1, Y0) + o = ones(numfunctions(X1)) + + # Interior Dirichlet problem - compare Sauter & Schwab eqs. 3.81 + M_IDPDL = (-1 / 2 * assemble(Identity(), Y0, X1) + assemble(D, Y0, X1)) # Double layer (DL) + + # Interior Neumann problem + # Neumann derivative from SL potential with deflected nullspace + M_INPSL = (1 / 2 * assemble(Identity(), X1, Y0) + assemble(Dt, X1, Y0)) + G * o * o' * G + + ρ_IDPDL = M_IDPDL \ (-gD1) + ρ_INPSL = M_INPSL \ (-gN) + + pts = meshsphere(0.8 * r, 0.8 * 0.6 * r).vertices # sphere inside on which the potential and field are evaluated + @show length(pts) + + pot_IDPDL = potential(HH3DDoubleLayerNear(im * k), pts, ρ_IDPDL, X1; type=ComplexF64) + + pot_INPSL = potential(HH3DSingleLayerNear(im * k), pts, ρ_INPSL, Y0; type=ComplexF64) + + # Total field inside should be zero + err_IDPDL_pot = norm(pot_IDPDL + Φ_inc.(pts)) / norm(Φ_inc.(pts)) + err_INPSL_pot = norm(pot_INPSL + Φ_inc.(pts)) / norm(Φ_inc.(pts)) + + # Efield(x) = - grad Φ_inc(x) + Efield(x) = -grad(charge1)(x) + -grad(charge2)(x) + + field_IDPDL = -potential(HH3DHyperSingularNear(im * k), pts, ρ_IDPDL, X1) + field_INPSL = -potential(HH3DDoubleLayerTransposedNear(im * k), pts, ρ_INPSL, Y0) + + err_IDPDL_field = norm(field_IDPDL + Efield.(pts)) / norm(Efield.(pts)) + err_INPSL_field = norm(field_INPSL + Efield.(pts)) / norm(Efield.(pts)) + + # errors of interior problems + @test err_IDPDL_pot < 0.005 + @test err_INPSL_pot < 0.002 + + @test err_IDPDL_field < 0.0095 + @test err_INPSL_field < 0.002 +#end + +## Test here some of the mixed discretizatinos +#@testset "Helmholtz potential operators: mixed discretizations with duallagrangec0d1" begin + # r = 10.0 + # λ = 20 * r + # k = 2 * π / λ + # sphere = meshsphere(r, 0.2 * r) + k = 0.031415926535897934 + sphere = readmesh(joinpath(dirname(pathof(BEAST)),"../test/assets/sphere_rad=10_h=2.in")) + + X0 = lagrangecxd0(sphere) + Y1 = duallagrangec0d1(sphere) + + S = Helmholtz3D.singlelayer(; gamma=im * k) + D = Helmholtz3D.doublelayer(; gamma=im * k) + Dt = Helmholtz3D.doublelayer_transposed(; gamma=im * k) + N = Helmholtz3D.hypersingular(; gamma=im * k) + + q = 100.0 + ϵ = 1.0 + + # Interior problem + # Formulations from Sauter and Schwab, Boundary Element Methods(2011), Chapter 3.4.1.1 + r = 10.0 + pos1 = SVector(r * 1.5, 0.0, 0.0) # positioning of point charges + pos2 = SVector(-r * 1.5, 0.0, 0.0) + + charge1 = Helmholtz3D.monopole(position=pos1, amplitude=q/(4*π*ϵ), wavenumber=k) + charge2 = Helmholtz3D.monopole(position=pos2, amplitude=-q/(4*π*ϵ), wavenumber=k) + + # Potential of point charges + + Φ_inc(x) = charge1(x) + charge2(x) + + gD1 = assemble(DirichletTrace(charge1), X0) + assemble(DirichletTrace(charge2), X0) + gN = assemble(∂n(charge1), Y1) + assemble(BEAST.n ⋅ grad(charge2), Y1) + + G = assemble(Identity(), Y1, X0) + o = ones(numfunctions(X0)) + + # Interior Dirichlet problem - compare Sauter & Schwab eqs. 3.81 + M_IDPDL = (-1 / 2 * assemble(Identity(), X0, Y1) + assemble(D, X0, Y1)) # Double layer (DL) + + # Interior Neumann problem + # Neumann derivative from SL potential with deflected nullspace + M_INPSL = (1 / 2 * assemble(Identity(), Y1, X0) + assemble(Dt, Y1, X0)) + G * o * o' * G + + ρ_IDPDL = M_IDPDL \ (-gD1) + ρ_INPSL = M_INPSL \ (-gN) + + pts = meshsphere(0.8 * r, 0.8 * 0.6 * r).vertices # sphere inside on which the potential and field are evaluated + + pot_IDPDL = potential(HH3DDoubleLayerNear(im * k), pts, ρ_IDPDL, Y1; type=ComplexF64) + + pot_INPSL = potential(HH3DSingleLayerNear(im * k), pts, ρ_INPSL, X0; type=ComplexF64) + + # Total field inside should be zero + err_IDPDL_pot = norm(pot_IDPDL + Φ_inc.(pts)) / norm(Φ_inc.(pts)) + err_INPSL_pot = norm(pot_INPSL + Φ_inc.(pts)) / norm(Φ_inc.(pts)) + + # Efield(x) = - grad Φ_inc(x) + Efield(x) = -grad(charge1)(x) + -grad(charge2)(x) + + field_IDPDL = -potential(HH3DHyperSingularNear(im * k), pts, ρ_IDPDL, Y1) + field_INPSL = -potential(HH3DDoubleLayerTransposedNear(im * k), pts, ρ_INPSL, X0) + + err_IDPDL_field = norm(field_IDPDL + Efield.(pts)) / norm(Efield.(pts)) + err_INPSL_field = norm(field_INPSL + Efield.(pts)) / norm(Efield.(pts)) + + # errors of interior problems + @test err_IDPDL_pot < 0.02 + @test err_INPSL_pot < 0.025 + + @test err_IDPDL_field < 0.02 + @test err_INPSL_field < 0.025 +#end \ No newline at end of file diff --git a/test/test_hh3dexc.jl b/test/test_hh3dexc.jl index 0f04466a..cd67ef35 100644 --- a/test/test_hh3dexc.jl +++ b/test/test_hh3dexc.jl @@ -1,39 +1,54 @@ +@testitem "excitation: HH3D" begin using CompScienceMeshes -using BEAST -using Test +# using BEAST +# using Test -sphere = readmesh(joinpath(dirname(@__FILE__),"assets","sphere5.in")) -numcells(sphere) +for T in [Float32, Float64] + # sphere = readmesh(joinpath(dirname(@__FILE__),"assets","sphere5.in"), T=T) + sphere = readmesh(joinpath(dirname(pathof(BEAST)),"../test","assets","sphere5.in"), T=T) + # numcells(sphere) -κ = 2π -direction = point(0,0,1) -f = BEAST.HH3DPlaneWave(direction, κ) + κ = T(2π) + direction = point(T,0,0,1) + f = BEAST.HH3DPlaneWave(direction, im*κ, T(1.0)) -v1 = f(point(0,0,0)) -v2 = f(point(0,0,0.5)) + v1 = f(point(T,0,0,0)) + v2 = f(point(T,0,0,0.5)) -@test v1 ≈ +1 -@test v2 ≈ -1 + @test v1 ≈ +1 + @test v2 ≈ -1 -import BEAST.∂n -p = ∂n(f) + lp = HH3DLinearPotential(point(T,0,1,0), 2.0) + @test lp(point(T,1,1,0)) == T(2.0) -s = chart(m,first(cells(m))) -c = neighborhood(s, [1,1]/3) + gradlp = grad(lp) + @test gradlp(point(T,1,1,0)) == point(T, 0, 2, 0) -r = cartesian(c) -n = normal(s) + γnlp = dot(BEAST.NormalVector(), gradlp) -w1 = p(c) -w2 = -im*κ*dot(direction, n)*f(r) + # import BEAST.∂n + p = ∂n(f) -w1 ≈ w2 + s = chart(sphere,first(sphere)) + c = neighborhood(s, T.([1,1]/3)) -N = BEAST.HH3DHyperSingularFDBIO(im*κ) -X = BEAST.lagrangec0d1(sphere) + r = cartesian(c) + nr = normal(s) -numfunctions(X) + w1 = p(c) + w2 = -im*κ*dot(direction, nr)*f(r) -Nxx = assemble(N, X, X) + w1 ≈ w2 -@test size(Nxx) == (numfunctions(X), numfunctions(X)) + N = BEAST.HH3DHyperSingularFDBIO(im*κ) + X = BEAST.lagrangec0d1(sphere) + + # numfunctions(X) + + # BEAST.quadinfo(N, X, X) + Nxx = assemble(N, X, X) + + @test size(Nxx) == (numfunctions(X), numfunctions(X)) +end + +end \ No newline at end of file diff --git a/test/test_hh3dints.jl b/test/test_hh3dints.jl new file mode 100644 index 00000000..008ad7c8 --- /dev/null +++ b/test/test_hh3dints.jl @@ -0,0 +1,773 @@ +using Test +using LinearAlgebra + +using BEAST, CompScienceMeshes, SauterSchwabQuadrature, StaticArrays + +T=Float64 + + +# operators +op1 = Helmholtz3D.singlelayer(gamma=T(1.0)im+T(1.0)) +op2 = Helmholtz3D.doublelayer(gamma=T(1.0)im+T(1.0)) +op3 = Helmholtz3D.doublelayer_transposed(gamma=T(1.0)im+T(1.0)) +op4 = Helmholtz3D.hypersingular(gamma=T(1.0)im+T(1.0)) +## Common face case: +@testset "Common Face" begin + # triangles +t1 = simplex( + T.(@SVector [0.180878, -0.941848, -0.283207]), + T.(@SVector [0.0, -0.980785, -0.19509]), + T.(@SVector [0.0, -0.92388, -0.382683])) + + lag0 = BEAST.LagrangeRefSpace{T,0,3,1}() # patch basis + lag1 = BEAST.LagrangeRefSpace{T,1,3,3}() #pyramid basis + + tqd0 = BEAST.quadpoints(lag0, [t1], (13,)) #test quadrature data + tqd1 = BEAST.quadpoints(lag1, [t1], (13,)) + + bqd0 = BEAST.quadpoints(lag0, [t1], (12,)) #basis quadrature data + bqd1 = BEAST.quadpoints(lag1, [t1], (12,)) + + SE_strategy = BEAST.WiltonSERule( #wilton + tqd0[1,1], + BEAST.DoubleQuadRule( + tqd0[1,1], + bqd0[1,1], + ), + ) + + SS_strategy = SauterSchwabQuadrature.CommonFace(BEAST._legendre(12,T(0.0),T(1.0))) #sauter +#single layer with patch-patch + Double_strategy = BEAST.DoubleQuadRule( #doublequadstrat + tqd0[1,1], + bqd0[1,1] + ) + + z_se = [0.0im] + z_ss = [0.0im] + z_double = [0.0im] + + BEAST.momintegrals!(op1, lag0, lag0, t1, t1, z_se, SE_strategy) + BEAST.momintegrals!(op1, lag0, lag0, t1, t1, z_ss, SS_strategy) + BEAST.momintegrals!(op1, lag0, lag0, t1, t1, z_double, Double_strategy) + + @test z_se ≈ z_ss rtol=1e-5 + @test z_double ≈ z_ss rtol = 1e-1 + @test norm(z_se-z_ss) < norm(z_double-z_ss) + + #single layer with patch-pyramid + SE_strategy = BEAST.WiltonSERule( #wilton + tqd1[1,1], + BEAST.DoubleQuadRule( + tqd1[1,1], + bqd0[1,1], + ),) + Double_strategy = BEAST.DoubleQuadRule( + tqd1[1,1], + bqd0[1,1], + ) + + z_se = zeros(complex(T),3,1) + z_ss = zeros(complex(T),3,1) + z_double = zeros(complex(T),3,1) + BEAST.momintegrals!(op1, lag1, lag0, t1, t1, z_ss, SS_strategy) + BEAST.momintegrals!(op1, lag1, lag0, t1, t1, z_se, SE_strategy) + BEAST.momintegrals!(op1, lag1, lag0, t1, t1, z_double, Double_strategy) + + @test z_ss ≈ z_se rtol = 1e-5 + @test z_ss ≈ z_double rtol = 1e-1 + @test norm(z_ss-z_se) < norm(z_ss-z_double) + +#single layer with pyramid-patch + SE_strategy = BEAST.WiltonSERule( #wilton + tqd0[1,1], + BEAST.DoubleQuadRule( + tqd0[1,1], + bqd1[1,1], + ),) + Double_strategy = BEAST.DoubleQuadRule( + tqd0[1,1], + bqd1[1,1], + ) + + z_se = zeros(complex(T),1,3) + z_ss = zeros(complex(T),1,3) + z_double = zeros(complex(T),1,3) + BEAST.momintegrals!(op1, lag0, lag1, t1, t1, z_ss, SS_strategy) + BEAST.momintegrals!(op1, lag0, lag1, t1, t1, z_se, SE_strategy) + BEAST.momintegrals!(op1, lag0, lag1, t1, t1, z_double, Double_strategy) + + @test z_ss ≈ z_se rtol = 1e-5 + @test z_ss ≈ z_double rtol = 1e-1 + @test norm(z_ss-z_se) < norm(z_ss-z_double) + + #single layer with pyramid pyramid + SE_strategy = BEAST.WiltonSERule( #wilton + tqd1[1,1], + BEAST.DoubleQuadRule( + tqd1[1,1], + bqd1[1,1], + ),) + Double_strategy = BEAST.DoubleQuadRule( + tqd1[1,1], + bqd1[1,1], + ) + + z_se = zeros(complex(T),3,3) + z_ss = zeros(complex(T),3,3) + z_double = zeros(complex(T),3,3) + BEAST.momintegrals!(op1, lag1, lag1, t1, t1, z_ss, SS_strategy) + BEAST.momintegrals!(op1, lag1, lag1, t1, t1, z_se, SE_strategy) + BEAST.momintegrals!(op1, lag1, lag1, t1, t1, z_double, Double_strategy) + + @test z_ss ≈ z_se rtol = 1e-5 + @test z_ss ≈ z_double rtol = 1e-1 + @test norm(z_ss-z_se) < norm(z_ss-z_double) + +end +# common vertex case + +# In the common vertex case, for the single layer and hypersingular, +# the double quadrature apparently is better than the wilton method. +# It is not clear to me why this is. If someone knows why this happens +# I would be grateful for an explanation. + +@testset "Common vertex" begin + t1 = simplex( + T.(@SVector [0.180878, -0.941848, -0.283207]), + T.(@SVector [0.0, -0.980785, -0.19509]), + T.(@SVector [0.0, -0.92388, -0.382683])) + t2 = simplex( + T.(@SVector [0.373086, -0.881524, -0.289348]), + T.(@SVector [0.180878, -0.941848, -0.283207]), + T.(@SVector [0.294908, -0.944921, -0.141962])) + lag0 = BEAST.LagrangeRefSpace{T,0,3,1}() # patch basis + lag1 = BEAST.LagrangeRefSpace{T,1,3,3}() #pyramid basis + + tqd0 = BEAST.quadpoints(lag0, [t1,t2], (12,)) #test quadrature data + tqd1 = BEAST.quadpoints(lag1, [t1,t2], (12,)) + + bqd0 = BEAST.quadpoints(lag0, [t1,t2], (13,)) #basis quadrature data + bqd1 = BEAST.quadpoints(lag1, [t1,t2], (13,)) +#patch patch + SE_strategy = BEAST.WiltonSERule( + tqd0[1,1], + BEAST.DoubleQuadRule( + tqd0[1,1], + bqd0[1,2])) + + SS_strategy = SauterSchwabQuadrature.CommonVertex(BEAST._legendre(8,T(0.0),T(1.0))) + + Double_strategy = BEAST.DoubleQuadRule( #doublequadstrat + tqd0[1,1], + bqd0[1,2] + ) + #single layer + + z_cv_se = zeros(complex(T),1,1) + z_cv_ss = zeros(complex(T),1,1) + z_cv_double = zeros(complex(T),1,1) + + BEAST.momintegrals!(op1, lag0, lag0, t1, t2, z_cv_se, SE_strategy) + BEAST.momintegrals!(op1, lag0, lag0, t1, t2, z_cv_ss, SS_strategy) + BEAST.momintegrals!(op1, lag0, lag0, t1, t2, z_cv_double, Double_strategy) + + @test z_cv_double ≈ z_cv_ss rtol = 1e-4 + @test z_cv_se ≈ z_cv_ss rtol=1e-4 + @test_broken norm(z_cv_ss-z_cv_se)/norm(z_cv_ss) < norm(z_cv_ss-z_cv_double)/norm(z_cv_ss) + #doublelayer + z_cv_se = zeros(complex(T),1,1) + z_cv_ss = zeros(complex(T),1,1) + z_cv_double = zeros(complex(T),1,1) + + BEAST.momintegrals!(op2, lag0, lag0, t1, t2, z_cv_se, SE_strategy) + BEAST.momintegrals!(op2, lag0, lag0, t1, t2, z_cv_ss, SS_strategy) + BEAST.momintegrals!(op2, lag0, lag0, t1, t2, z_cv_double, Double_strategy) + + @test z_cv_double ≈ z_cv_ss rtol = 1e-4 + @test z_cv_se ≈ z_cv_double rtol=1e-4 + @test norm(z_cv_ss-z_cv_se) < norm(z_cv_ss-z_cv_double) + + # doublelayer_transposed + z_cv_se = zeros(complex(T),1,1) + z_cv_ss = zeros(complex(T),1,1) + z_cv_double = zeros(complex(T),1,1) + + BEAST.momintegrals!(op3, lag0, lag0, t1, t2, z_cv_se, SE_strategy) + BEAST.momintegrals!(op3, lag0, lag0, t1, t2, z_cv_ss, SS_strategy) + BEAST.momintegrals!(op3, lag0, lag0, t1, t2, z_cv_double, Double_strategy) + + @test z_cv_double ≈ z_cv_ss rtol = 1e-4 + @test z_cv_se ≈ z_cv_ss rtol=1e-4 + @test norm(z_cv_ss-z_cv_se) < norm(z_cv_ss-z_cv_double) + +#pyramid pyramid + # singlelayer + SE_strategy = BEAST.WiltonSERule( + tqd1[1,1], + BEAST.DoubleQuadRule( + tqd1[1,1], + bqd1[1,2])) + Double_strategy = BEAST.DoubleQuadRule( #doublequadstrat + tqd1[1,1], + bqd1[1,2] + ) + z_cv_se = zeros(complex(T),3,3) + z_cv_ss = zeros(complex(T),3,3) + z_cv_double = zeros(complex(T),3,3) + + BEAST.momintegrals!(op1, lag1, lag1, t1, t2, z_cv_se, SE_strategy) + BEAST.momintegrals!(op1, lag1, lag1, t1, t2, z_cv_ss, SS_strategy) + BEAST.momintegrals!(op1, lag1, lag1, t1, t2, z_cv_double, Double_strategy) + + @test z_cv_double ≈ z_cv_ss rtol = 1e-4 + @test z_cv_se ≈ z_cv_double rtol=1e-4 + @test_broken norm(z_cv_ss-z_cv_se) < norm(z_cv_ss-z_cv_double) + + #doublelayer + z_cv_se = zeros(complex(T),3,3) + z_cv_ss = zeros(complex(T),3,3) + z_cv_double = zeros(complex(T),3,3) + + BEAST.momintegrals!(op2, lag1, lag1, t1, t2, z_cv_se, SE_strategy) + BEAST.momintegrals!(op2, lag1, lag1, t1, t2, z_cv_ss, SS_strategy) + BEAST.momintegrals!(op2, lag1, lag1, t1, t2, z_cv_double, Double_strategy) + + @test z_cv_double ≈ z_cv_ss rtol = 1e-4 + @test z_cv_se ≈ z_cv_ss rtol=1e-4 + @test norm(z_cv_ss-z_cv_se) < norm(z_cv_ss-z_cv_double) + + #doublelayer_transposed + z_cv_se = zeros(complex(T),3,3) + z_cv_ss = zeros(complex(T),3,3) + z_cv_double = zeros(complex(T),3,3) + + BEAST.momintegrals!(op3, lag1, lag1, t1, t2, z_cv_se, SE_strategy) + BEAST.momintegrals!(op3, lag1, lag1, t1, t2, z_cv_ss, SS_strategy) + BEAST.momintegrals!(op3, lag1, lag1, t1, t2, z_cv_double, Double_strategy) + + @test z_cv_double ≈ z_cv_ss rtol = 1e-3 + @test z_cv_double ≈ z_cv_se rtol = 1e-5 + @test norm(z_cv_ss-z_cv_se) < norm(z_cv_ss-z_cv_double) + + #hypersingular + z_cv_se = zeros(complex(T),3,3) + z_cv_ss = zeros(complex(T),3,3) + z_cv_double = zeros(complex(T),3,3) + + BEAST.momintegrals!(op4, lag1, lag1, t1, t2, z_cv_se, SE_strategy) + BEAST.momintegrals!(op4, lag1, lag1, t1, t2, z_cv_ss, SS_strategy) + BEAST.momintegrals!(op4, lag1, lag1, t1, t2, z_cv_double, Double_strategy) + + # @show z_cv_double + # @show z_cv_se + # @show z_cv_ss + # @show 2 * norm(z_cv_double + z_cv_ss) / norm(z_cv_double - z_cv_ss) + @test z_cv_double ≈ z_cv_ss rtol = 1e-4 + @test z_cv_se ≈ z_cv_ss rtol=1e-4 + @test_broken norm(z_cv_ss-z_cv_se) < norm(z_cv_ss-z_cv_double) + +#pyramid test patch basis + SE_strategy = BEAST.WiltonSERule( + tqd1[1,1], + BEAST.DoubleQuadRule( + tqd1[1,1], + bqd0[1,2])) + + SS_strategy = SauterSchwabQuadrature.CommonVertex(BEAST._legendre(8,T(0.0),T(1.0))) + + Double_strategy = BEAST.DoubleQuadRule( #doublequadstrat + tqd1[1,1], + bqd0[1,2] + ) + #singlelayer + z_cv_se = zeros(complex(T),3,1) + z_cv_ss = zeros(complex(T),3,1) + z_cv_double = zeros(complex(T),3,1) + + BEAST.momintegrals!(op1, lag1, lag0, t1, t2, z_cv_se, SE_strategy) + BEAST.momintegrals!(op1, lag1, lag0, t1, t2, z_cv_ss, SS_strategy) + BEAST.momintegrals!(op1, lag1, lag0, t1, t2, z_cv_double, Double_strategy) + + @test z_cv_double ≈ z_cv_ss rtol = 1e-4 + @test z_cv_se ≈ z_cv_ss rtol=1e-4 + @test_broken norm(z_cv_ss-z_cv_se) < norm(z_cv_ss-z_cv_double) + + #doublelayer + z_cv_se = zeros(complex(T),3,1) + z_cv_ss = zeros(complex(T),3,1) + z_cv_double = zeros(complex(T),3,1) + + BEAST.momintegrals!(op2, lag1, lag0, t1, t2, z_cv_se, SE_strategy) + BEAST.momintegrals!(op2, lag1, lag0, t1, t2, z_cv_ss, SS_strategy) + BEAST.momintegrals!(op2, lag1, lag0, t1, t2, z_cv_double, Double_strategy) + + @test z_cv_double ≈ z_cv_ss rtol = 1e-4 + @test z_cv_se ≈ z_cv_ss rtol=1e-4 + @test norm(z_cv_ss-z_cv_se)/norm(z_cv_ss) < norm(z_cv_ss-z_cv_double)/norm(z_cv_ss) + #doublelayer_transposed + + z_cv_se = zeros(complex(T),3,1) + z_cv_ss = zeros(complex(T),3,1) + z_cv_double = zeros(complex(T),3,1) + + BEAST.momintegrals!(op3, lag1, lag0, t1, t2, z_cv_se, SE_strategy) + BEAST.momintegrals!(op3, lag1, lag0, t1, t2, z_cv_ss, SS_strategy) + BEAST.momintegrals!(op3, lag1, lag0, t1, t2, z_cv_double, Double_strategy) + + @test z_cv_double ≈ z_cv_ss rtol = 1e-4 + @test z_cv_se ≈ z_cv_ss rtol=1e-4 + @test norm(z_cv_ss-z_cv_se) < norm(z_cv_ss-z_cv_double) + + #pyramid basis patch test + SE_strategy = BEAST.WiltonSERule( + tqd0[1,1], + BEAST.DoubleQuadRule( + tqd0[1,1], + bqd1[1,2])) + + SS_strategy = SauterSchwabQuadrature.CommonVertex(BEAST._legendre(8,T(0.0),T(1.0))) + + Double_strategy = BEAST.DoubleQuadRule( #doublequadstrat + tqd0[1,1], + bqd1[1,2] + ) + #singlelayer + z_cv_se = zeros(complex(T),1,3) + z_cv_ss = zeros(complex(T),1,3) + z_cv_double = zeros(complex(T),1,3) + + BEAST.momintegrals!(op1, lag0, lag1, t1, t2, z_cv_se, SE_strategy) + BEAST.momintegrals!(op1, lag0, lag1, t1, t2, z_cv_ss, SS_strategy) + BEAST.momintegrals!(op1, lag0, lag1, t1, t2, z_cv_double, Double_strategy) + + @test z_cv_double ≈ z_cv_ss rtol = 1e-4 + @test z_cv_se ≈ z_cv_ss rtol=1e-4 + @test_broken norm(z_cv_ss-z_cv_se) < norm(z_cv_ss-z_cv_double) + + #double layer + z_cv_se = zeros(complex(T),1,3) + z_cv_ss = zeros(complex(T),1,3) + z_cv_double = zeros(complex(T),1,3) + + BEAST.momintegrals!(op2, lag0, lag1, t1, t2, z_cv_se, SE_strategy) + BEAST.momintegrals!(op2, lag0, lag1, t1, t2, z_cv_ss, SS_strategy) + BEAST.momintegrals!(op2, lag0, lag1, t1, t2, z_cv_double, Double_strategy) + + @test z_cv_double ≈ z_cv_ss rtol = 1e-4 + @test z_cv_se ≈ z_cv_ss rtol=1e-4 + @test norm(z_cv_ss-z_cv_se) < norm(z_cv_ss-z_cv_double) + + #double layer transposed + z_cv_se = zeros(complex(T),1,3) + z_cv_ss = zeros(complex(T),1,3) + z_cv_double = zeros(complex(T),1,3) + + BEAST.momintegrals!(op3, lag0, lag1, t1, t2, z_cv_se, SE_strategy) + BEAST.momintegrals!(op3, lag0, lag1, t1, t2, z_cv_ss, SS_strategy) + BEAST.momintegrals!(op3, lag0, lag1, t1, t2, z_cv_double, Double_strategy) + + @test z_cv_double ≈ z_cv_ss rtol = 1e-4 + @test z_cv_se ≈ z_cv_ss rtol=1e-4 + @test norm(z_cv_ss-z_cv_se) < norm(z_cv_ss-z_cv_double) + +end + +@testset "Common edge" begin + # common edge + t1 = simplex( + T.(@SVector [0.180878, -0.941848, -0.283207]), + T.(@SVector [0.0, -0.980785, -0.19509]), + T.(@SVector [0.0, -0.92388, -0.382683]) + ) + t2 = simplex( + T.(@SVector [0.158174, -0.881178, -0.44554]), + T.(@SVector [0.180878, -0.941848, -0.283207]), + T.(@SVector [0.0, -0.92388, -0.382683]) + ) + + lag0 = BEAST.LagrangeRefSpace{T,0,3,1}() # patch basis + lag1 = BEAST.LagrangeRefSpace{T,1,3,3}() #pyramid basis + + tqd0 = BEAST.quadpoints(lag0, [t1,t2], (12,)) #test quadrature data + tqd1 = BEAST.quadpoints(lag1, [t1,t2], (12,)) + + bqd0 = BEAST.quadpoints(lag0, [t1,t2], (13,)) #basis quadrature data + bqd1 = BEAST.quadpoints(lag1, [t1,t2], (13,)) + + #patch patch + SE_strategy = BEAST.WiltonSERule( + tqd0[1,1], + BEAST.DoubleQuadRule( + tqd0[1,1], + bqd0[1,2])) + + SS_strategy = SauterSchwabQuadrature.CommonEdge(BEAST._legendre(8,T(0.0),T(1.0))) + + Double_strategy = BEAST.DoubleQuadRule( #doublequadstrat + tqd0[1,1], + bqd0[1,2] + ) + #single layer + + z_ce_se = zeros(complex(T),1,1) + z_ce_ss = zeros(complex(T),1,1) + z_ce_double = zeros(complex(T),1,1) + + BEAST.momintegrals!(op1, lag0, lag0, t1, t2, z_ce_se, SE_strategy) + BEAST.momintegrals!(op1, lag0, lag0, t1, t2, z_ce_ss, SS_strategy) + BEAST.momintegrals!(op1, lag0, lag0, t1, t2, z_ce_double, Double_strategy) + + @test z_ce_double ≈ z_ce_ss rtol = 1e-3 + @test z_ce_se ≈ z_ce_ss rtol=1e-4 + @test norm(z_ce_ss-z_ce_se) < norm(z_ce_ss-z_ce_double) + + #doublelayer + z_ce_se = zeros(complex(T),1,1) + z_ce_ss = zeros(complex(T),1,1) + z_ce_double = zeros(complex(T),1,1) + + BEAST.momintegrals!(op2, lag0, lag0, t1, t2, z_ce_se, SE_strategy) + BEAST.momintegrals!(op2, lag0, lag0, t1, t2, z_ce_ss, SS_strategy) + BEAST.momintegrals!(op2, lag0, lag0, t1, t2, z_ce_double, Double_strategy) + + @test z_ce_ss ≈ z_ce_double rtol = 1e-1 + @test z_ce_se ≈ z_ce_ss rtol = 1e-2 + @test norm(z_ce_ss-z_ce_se) < norm(z_ce_ss-z_ce_double) + + # doublelayer_transposed + z_ce_se = zeros(complex(T),1,1) + z_ce_ss = zeros(complex(T),1,1) + z_ce_double = zeros(complex(T),1,1) + + BEAST.momintegrals!(op3, lag0, lag0, t1, t2, z_ce_se, SE_strategy) + BEAST.momintegrals!(op3, lag0, lag0, t1, t2, z_ce_ss, SS_strategy) + BEAST.momintegrals!(op3, lag0, lag0, t1, t2, z_ce_double, Double_strategy) + + @test z_ce_double ≈ z_ce_ss rtol = 1e-1 + @test z_ce_se ≈ z_ce_ss rtol=1e-2 + @test norm(z_ce_ss-z_ce_se) < norm(z_ce_ss-z_ce_double) + +#pyramid pyramid + # singlelayer + SE_strategy = BEAST.WiltonSERule( + tqd1[1,1], + BEAST.DoubleQuadRule( + tqd1[1,1], + bqd1[1,2])) + Double_strategy = BEAST.DoubleQuadRule( #doublequadstrat + tqd1[1,1], + bqd1[1,2] + ) + z_ce_se = zeros(complex(T),3,3) + z_ce_ss = zeros(complex(T),3,3) + z_ce_double = zeros(complex(T),3,3) + + BEAST.momintegrals!(op1, lag1, lag1, t1, t2, z_ce_se, SE_strategy) + BEAST.momintegrals!(op1, lag1, lag1, t1, t2, z_ce_ss, SS_strategy) + BEAST.momintegrals!(op1, lag1, lag1, t1, t2, z_ce_double, Double_strategy) + + @test z_ce_double ≈ z_ce_ss rtol = 1e-3 + @test z_ce_se ≈ z_ce_double rtol=1e-3 + @test norm(z_ce_ss-z_ce_se) < norm(z_ce_ss-z_ce_double) + + #doublelayer + z_ce_se = zeros(complex(T),3,3) + z_ce_ss = zeros(complex(T),3,3) + z_ce_double = zeros(complex(T),3,3) + + BEAST.momintegrals!(op2, lag1, lag1, t1, t2, z_ce_se, SE_strategy) + BEAST.momintegrals!(op2, lag1, lag1, t1, t2, z_ce_ss, SS_strategy) + BEAST.momintegrals!(op2, lag1, lag1, t1, t2, z_ce_double, Double_strategy) + + @test z_ce_double ≈ z_ce_ss rtol = 1e-1 + @test z_ce_se ≈ z_ce_ss rtol=1e-4 + @test norm(z_ce_ss-z_ce_se)/norm(z_ce_ss) < norm(z_ce_ss-z_ce_double)/norm(z_ce_ss) + + #doublelayer_transposed + z_ce_se = zeros(complex(T),3,3) + z_ce_ss = zeros(complex(T),3,3) + z_ce_double = zeros(complex(T),3,3) + + BEAST.momintegrals!(op3, lag1, lag1, t1, t2, z_ce_se, SE_strategy) + BEAST.momintegrals!(op3, lag1, lag1, t1, t2, z_ce_ss, SS_strategy) + BEAST.momintegrals!(op3, lag1, lag1, t1, t2, z_ce_double, Double_strategy) + + @test z_ce_double ≈ z_ce_ss rtol = 1e-1 + @test z_ce_ss ≈ z_ce_se rtol = 1e-2 + @test norm(z_ce_ss-z_ce_se) < norm(z_ce_ss-z_ce_double) + + #hypersingular + z_ce_se = zeros(complex(T),3,3) + z_ce_ss = zeros(complex(T),3,3) + z_ce_double = zeros(complex(T),3,3) + + BEAST.momintegrals!(op4, lag1, lag1, t1, t2, z_ce_se, SE_strategy) + BEAST.momintegrals!(op4, lag1, lag1, t1, t2, z_ce_ss, SS_strategy) + BEAST.momintegrals!(op4, lag1, lag1, t1, t2, z_ce_double, Double_strategy) + + @test z_ce_double ≈ z_ce_ss rtol = 1e-3 + @test z_ce_se ≈ z_ce_ss rtol=1e-4 + @test norm(z_ce_ss-z_ce_se) < norm(z_ce_ss-z_ce_double) + +#pyramid test patch basis + SE_strategy = BEAST.WiltonSERule( + tqd1[1,1], + BEAST.DoubleQuadRule( + tqd1[1,1], + bqd0[1,2])) + + SS_strategy = SauterSchwabQuadrature.CommonEdge(BEAST._legendre(8,T(0.0),T(1.0))) + + Double_strategy = BEAST.DoubleQuadRule( #doublequadstrat + tqd1[1,1], + bqd0[1,2] + ) + #singlelayer + z_ce_se = zeros(complex(T),3,1) + z_ce_ss = zeros(complex(T),3,1) + z_ce_double = zeros(complex(T),3,1) + + BEAST.momintegrals!(op1, lag1, lag0, t1, t2, z_ce_se, SE_strategy) + BEAST.momintegrals!(op1, lag1, lag0, t1, t2, z_ce_ss, SS_strategy) + BEAST.momintegrals!(op1, lag1, lag0, t1, t2, z_ce_double, Double_strategy) + + @test z_ce_double ≈ z_ce_ss rtol = 1e-3 + @test z_ce_se ≈ z_ce_ss rtol=1e-4 + @test norm(z_ce_ss-z_ce_se) < norm(z_ce_ss-z_ce_double) + + #doublelayer + z_ce_se = zeros(complex(T),3,1) + z_ce_ss = zeros(complex(T),3,1) + z_ce_double = zeros(complex(T),3,1) + + BEAST.momintegrals!(op2, lag1, lag0, t1, t2, z_ce_se, SE_strategy) + BEAST.momintegrals!(op2, lag1, lag0, t1, t2, z_ce_ss, SS_strategy) + BEAST.momintegrals!(op2, lag1, lag0, t1, t2, z_ce_double, Double_strategy) + + @test z_ce_double ≈ z_ce_ss rtol = 1e-1 + @test z_ce_se ≈ z_ce_ss rtol=1e-4 + @test norm(z_ce_ss-z_ce_se)/norm(z_ce_ss) < norm(z_ce_ss-z_ce_double)/norm(z_ce_ss) + #doublelayer_transposed + + z_ce_se = zeros(complex(T),3,1) + z_ce_ss = zeros(complex(T),3,1) + z_ce_double = zeros(complex(T),3,1) + + BEAST.momintegrals!(op3, lag1, lag0, t1, t2, z_ce_se, SE_strategy) + BEAST.momintegrals!(op3, lag1, lag0, t1, t2, z_ce_ss, SS_strategy) + BEAST.momintegrals!(op3, lag1, lag0, t1, t2, z_ce_double, Double_strategy) + + @test z_ce_double ≈ z_ce_ss rtol = 1e-1 + @test z_ce_se ≈ z_ce_ss rtol=1e-2 + @test norm(z_ce_ss-z_ce_se)/norm(z_ce_ss) < norm(z_ce_ss-z_ce_double)/norm(z_ce_ss) + + #pyramid basis patch test + SE_strategy = BEAST.WiltonSERule( + tqd0[1,1], + BEAST.DoubleQuadRule( + tqd0[1,1], + bqd1[1,2])) + + SS_strategy = SauterSchwabQuadrature.CommonEdge(BEAST._legendre(8,T(0.0),T(1.0))) + + Double_strategy = BEAST.DoubleQuadRule( #doublequadstrat + tqd0[1,1], + bqd1[1,2] + ) + #singlelayer + z_ce_se = zeros(complex(T),1,3) + z_ce_ss = zeros(complex(T),1,3) + z_ce_double = zeros(complex(T),1,3) + + BEAST.momintegrals!(op1, lag0, lag1, t1, t2, z_ce_se, SE_strategy) + BEAST.momintegrals!(op1, lag0, lag1, t1, t2, z_ce_ss, SS_strategy) + BEAST.momintegrals!(op1, lag0, lag1, t1, t2, z_ce_double, Double_strategy) + + @test z_ce_double ≈ z_ce_ss rtol = 1e-3 + @test z_ce_se ≈ z_ce_ss rtol=1e-4 + @test norm(z_ce_ss-z_ce_se)/norm(z_ce_ss) < norm(z_ce_ss-z_ce_double)/norm(z_ce_ss) + + #double layer + z_ce_se = zeros(complex(T),1,3) + z_ce_ss = zeros(complex(T),1,3) + z_ce_double = zeros(complex(T),1,3) + + BEAST.momintegrals!(op2, lag0, lag1, t1, t2, z_ce_se, SE_strategy) + BEAST.momintegrals!(op2, lag0, lag1, t1, t2, z_ce_ss, SS_strategy) + BEAST.momintegrals!(op2, lag0, lag1, t1, t2, z_ce_double, Double_strategy) + + @test z_ce_double ≈ z_ce_ss rtol = 1e-1 + @test z_ce_se ≈ z_ce_ss rtol=1e-4 + @test norm(z_ce_ss-z_ce_se)/norm(z_ce_ss) < norm(z_ce_ss-z_ce_double)/norm(z_ce_ss) + + #double layer transposed + z_ce_se = zeros(complex(T),1,3) + z_ce_ss = zeros(complex(T),1,3) + z_ce_double = zeros(complex(T),1,3) + + BEAST.momintegrals!(op3, lag0, lag1, t1, t2, z_ce_se, SE_strategy) + BEAST.momintegrals!(op3, lag0, lag1, t1, t2, z_ce_ss, SS_strategy) + BEAST.momintegrals!(op3, lag0, lag1, t1, t2, z_ce_double, Double_strategy) + + @test z_ce_double ≈ z_ce_ss rtol = 1e-1 + @test z_ce_se ≈ z_ce_ss rtol=1e-1 + @test norm(z_ce_ss-z_ce_se)/norm(z_ce_ss) < norm(z_ce_ss-z_ce_double)/norm(z_ce_ss) + +end + +@testset "Seperated triangles" begin + t1 = simplex( + T.(@SVector [0.0, -0.980785, -0.19509]), + T.(@SVector [0.180878, -0.941848, -0.283207]), + T.(@SVector [0.0, -0.92388, -0.382683])) + t2 = simplex( + T.(@SVector [0.0, -0.980785, -0.230102]), + T.(@SVector [0.180878, -0.941848, -0.383207]), + T.(@SVector [0.0, -0.92388, -0.411962])) + + lag0 = BEAST.LagrangeRefSpace{T,0,3,1}() # patch basis + lag1 = BEAST.LagrangeRefSpace{T,1,3,3}() #pyramid basis + + tqd0 = BEAST.quadpoints(lag0, [t1,t2], (12,)) #test quadrature data + tqd1 = BEAST.quadpoints(lag1, [t1,t2], (12,)) + + bqd0 = BEAST.quadpoints(lag0, [t1,t2], (13,)) #basis quadrature data + bqd1 = BEAST.quadpoints(lag1, [t1,t2], (13,)) + # singlelayer + SE_strategy = BEAST.WiltonSERule( + tqd0[1,1], + BEAST.DoubleQuadRule( + tqd0[1,1], + bqd0[1,2])) + + Double_strategy = BEAST.DoubleQuadRule( #doublequadstrat + tqd0[1,1], + bqd0[1,2] + ) + z_ce_se = zeros(complex(T),1,1) + z_ce_double = zeros(complex(T),1,1) + + BEAST.momintegrals!(op1, lag0, lag0, t1, t2, z_ce_se, SE_strategy) + BEAST.momintegrals!(op1, lag0, lag0, t1, t2, z_ce_double, Double_strategy) + + @test z_ce_se ≈ z_ce_double rtol=1e-6 + + # calculate a reference value with hcubature to desired accuracy + #=using HCubature + r(u,v,t) = (1-u)*t[1]+u*((1-v)*t[2]+v*t[3]) + gamma=1.0+1.0im + jac(u,v,t) = u * norm(cross(t[1],t[2])+cross(t[2],t[3])+cross(t[3],t[1])) + function outerf(x) + r2(x2) = r(x2[1],x2[2],t1) - r(x[1],x[2],t2) + innerf(x2) = exp(-gamma*norm(r2(x2)))/(4*pi*norm(r2(x2))) * jac(x2[1],x2[2],t1) + hcubature(innerf, (0.0,0.0), (1.0,1.0), rtol = 1e-8)[1] * jac(x[1],x[2],t2) + end + + @show refval = hcubature(outerf, (0.0,0.0), (1.0,1.0), rtol = 1e-8)[1] =# + refval = 0.00033563892481993545 - 2.22592609372769e-5im # can be calculated with above code + + @test z_ce_se[1] ≈ refval rtol = 1e-7 + @test z_ce_double[1] ≈ refval rtol = 1e-6 + + + SE_strategy = BEAST.WiltonSERule( + tqd1[1,1], + BEAST.DoubleQuadRule( + tqd1[1,1], + bqd1[1,2])) + + Double_strategy = BEAST.DoubleQuadRule( #doublequadstrat + tqd1[1,1], + bqd1[1,2] + ) + z_ce_se = zeros(complex(T),3,3) + z_ce_double = zeros(complex(T),3,3) + + BEAST.momintegrals!(op1, lag1, lag1, t1, t2, z_ce_se, SE_strategy) + BEAST.momintegrals!(op1, lag1, lag1, t1, t2, z_ce_double, Double_strategy) + + @test z_ce_se ≈ z_ce_double rtol=1e-4 + + # doublelayer + SE_strategy = BEAST.WiltonSERule( + tqd0[1,1], + BEAST.DoubleQuadRule( + tqd0[1,1], + bqd1[1,2])) + + Double_strategy = BEAST.DoubleQuadRule( + tqd0[1,1], + bqd1[1,2]) + z_ce_se = zeros(complex(T),1,3) + z_ce_double = zeros(complex(T),1,3) + BEAST.momintegrals!(op2, lag0, lag1, t1, t2, z_ce_se, SE_strategy) + BEAST.momintegrals!(op2, lag0, lag1, t1, t2, z_ce_double, Double_strategy) + + @test z_ce_se ≈ z_ce_double rtol = 1e-4 + + SE_strategy = BEAST.WiltonSERule( + tqd1[1,1], + BEAST.DoubleQuadRule( + tqd1[1,1], + bqd1[1,2])) + + Double_strategy = BEAST.DoubleQuadRule( + tqd1[1,1], + bqd1[1,2]) + z_ce_se = zeros(complex(T),3,3) + z_ce_double = zeros(complex(T),3,3) + BEAST.momintegrals!(op2, lag1, lag1, t1, t2, z_ce_se, SE_strategy) + BEAST.momintegrals!(op2, lag1, lag1, t1, t2, z_ce_double, Double_strategy) + + @test z_ce_se ≈ z_ce_double rtol = 1e-4 + # doublelayer transposed + SE_strategy = BEAST.WiltonSERule( + tqd1[1,1], + BEAST.DoubleQuadRule( + tqd1[1,1], + bqd0[1,2])) + Double_strategy = BEAST.DoubleQuadRule( + tqd1[1,1], + bqd0[1,2]) + z_ce_se = zeros(complex(T),3,1) + z_ce_double = zeros(complex(T),3,1) + + BEAST.momintegrals!(op3, lag1, lag0, t1, t2, z_ce_se, SE_strategy) + BEAST.momintegrals!(op3, lag1, lag0, t1, t2, z_ce_double, Double_strategy) + + @test z_ce_se ≈ z_ce_double rtol=1e-4 + + SE_strategy = BEAST.WiltonSERule( + tqd1[1,1], + BEAST.DoubleQuadRule( + tqd1[1,1], + bqd1[1,2])) + Double_strategy = BEAST.DoubleQuadRule( + tqd1[1,1], + bqd1[1,2]) + z_ce_se = zeros(complex(T),3,3) + z_ce_double = zeros(complex(T),3,3) + + BEAST.momintegrals!(op3, lag1, lag1, t1, t2, z_ce_se, SE_strategy) + BEAST.momintegrals!(op3, lag1, lag1, t1, t2, z_ce_double, Double_strategy) + + @test z_ce_se ≈ z_ce_double rtol=1e-4 + # hypersingular + SE_strategy = BEAST.WiltonSERule( + tqd1[1,1], + BEAST.DoubleQuadRule( + tqd1[1,1], + bqd1[1,2])) + Double_strategy = BEAST.DoubleQuadRule( + tqd1[1,1], + bqd1[1,2]) + + z_ce_se = zeros(complex(T),3,3) + z_ce_double = zeros(complex(T),3,3) + + BEAST.momintegrals!(op4, lag1, lag1, t1, t2, z_ce_se, SE_strategy) + BEAST.momintegrals!(op4, lag1, lag1, t1, t2, z_ce_double, Double_strategy) + @test z_ce_se ≈ z_ce_double rtol = 1e-5 + +end diff --git a/test/test_hh3dtd_exc.jl b/test/test_hh3dtd_exc.jl index 2b25f070..75432cbe 100644 --- a/test/test_hh3dtd_exc.jl +++ b/test/test_hh3dtd_exc.jl @@ -1,34 +1,37 @@ -using BEAST -using CompScienceMeshes -using Test +@testitem "excitation: TD Helmholtz 3D" begin + # using BEAST + using CompScienceMeshes + # using Test + for T in [Float32, Float64] + dir = point(T,0,0,1) + width = T(1.0) + delay = T(1.5) + scaling = T(1.0) + sig = creategaussian(width, delay, scaling) -dir = point(0,0,1) -width = 1.0 -delay = 1.5 -scaling = 1.0 -sig = creategaussian(width, delay, scaling) + pw = BEAST.planewave(dir, T(1.0), sig) -pw = BEAST.planewave(dir, 1.0, sig) + CompScienceMeshes.cartesian(p::typeof(dir)) = p + CompScienceMeshes.cartesian(p::Number) = p + x = point(T,1,0,0) + t = T(1.0) + @test pw(x,t) ≈ sig(t-dot(dir,x)) -CompScienceMeshes.cartesian(p::typeof(dir)) = p -CompScienceMeshes.cartesian(p::Number) = p -x = point(1,0,0) -t = 1.0 -@test pw(x,t) ≈ sig(t-dot(dir,x)) + pw2 = BEAST.gradient(pw) + @test pw2.direction ≈ dir + @test pw2.polarisation ≈ -dir + @test pw2.speedoflight ≈ pw.speed_of_light -pw2 = BEAST.gradient(pw) -@test pw2.direction ≈ dir -@test pw2.polarisation ≈ -dir -@test pw2.speedoflight ≈ pw.speed_of_light + dsig = derive(sig) + @test pw2(x,t) ≈ -dir*dsig(t-dot(dir,x)) -dsig = derive(sig) -@test pw2(x,t) ≈ -dir*dsig(t-dot(dir,x)) - -trc = dot(BEAST.n,pw2) -ch = simplex( - point(0,0,0), - point(1,0,0), - point(0,1,0)) -ctr = center(ch) -val = trc(ctr,t) -@test val ≈ -dsig(t-dot(dir,x)) + trc = dot(BEAST.n,pw2) + ch = simplex( + point(T,0,0,0), + point(T,1,0,0), + point(T,0,1,0)) + ctr = center(ch) + val = trc(ctr,t) + @test val ≈ -dsig(t-dot(dir,x)) + end +end \ No newline at end of file diff --git a/test/test_hh_lsvie.jl b/test/test_hh_lsvie.jl new file mode 100644 index 00000000..8f45f6c6 --- /dev/null +++ b/test/test_hh_lsvie.jl @@ -0,0 +1,101 @@ +using BEAST +using CompScienceMeshes +using StaticArrays +using LinearAlgebra +using SphericalScattering +using Test + + +# Homogeneous Dielectic Sphere Unit Test + +@testset "Lippmann Schwinger Volume Integral Equation" begin + + # Environment + ε1 = 1.0*SphericalScattering.ε0 + μ1 = SphericalScattering.μ0 + + # Dielectic Sphere + ε2 = 5.0*SphericalScattering.ε0 + μ2 = SphericalScattering.μ0 + r = 1.0 + sp = DielectricSphere(; radius = r, filling = Medium(ε2, μ2)) + + + # Mesh, Basis + h = 0.35 + mesh = CompScienceMeshes.tetmeshsphere(r,h) + bnd = boundary(mesh) + X = lagrangec0d1(mesh; dirichlet = false) + #@show numfunctions(X) + strc = X -> strace(X,bnd) + + # VIE operators + function generate_tau(ε_ins, ε_env) + contr = 1.0 - ε_ins/ε_env + function tau(x::SVector{U,T}) where {U,T} + return T(contr) + end + return tau + end + τ = generate_tau(ε2, ε1) + + I, V, B = Identity(), VIE.hhvolume(tau = τ, wavenumber = 0.0), VIE.hhboundary(tau = τ, wavenumber = 0.0) + Y = VIE.hhvolumegradG(tau = τ, wavenumber = 0.0) + + + # Exitation + dirE = SVector(1.0, 0.0, 0.0) + dirgradΦ = -dirE + amp = 1.0 + + # SphericalScattering exitation + ex = UniformField(direction = dirE, amplitude = amp, embedding = Medium(ε1, μ1)) + + # VIE exitation + Φ_inc = VIE.linearpotential(direction=dirgradΦ, amplitude=amp) + + + # Assembly + b = real.(assemble(Φ_inc, X)) + + Z_I = assemble(I, X, X) + + Z_V = assemble(V, X, X) + Z_B = assemble(B, strc(X), X) + + Z_Y = assemble(Y, X, X) + + Z_version1 = Z_I + Z_V + Z_B + Z_version2 = Z_I + Z_Y + + # MoM solution + u_version1 = Z_version1 \ b + u_version2 = Z_version2 \ b + + # Observation points + range_ = range(-1.0*r,stop=1.0*r,length=14) + points = [point(x,y,z) for x in range_ for y in range_ for z in range_] + points_sp=[] + for p in points + norm(p)<0.97*r && push!(points_sp,p) + end + + # SphericalScattering solution inside the dielectric sphere + Φ = field(sp, ex, ScalarPotential(points_sp)) + + # MoM solution inside the dielectric sphere + Φ_MoM_version1 = BEAST.grideval(points_sp, u_version1, X) + Φ_MoM_version2 = BEAST.grideval(points_sp, u_version2, X) + + + + err_Φ_version1 = norm(Φ - Φ_MoM_version1) / norm(Φ) + err_Φ_version2 = norm(Φ - Φ_MoM_version2) / norm(Φ) + + @show err_Φ_version1 + @show err_Φ_version2 + + @test err_Φ_version1 < 0.001 + @test err_Φ_version2 < 0.002 + +end \ No newline at end of file diff --git a/test/test_hhd2d_directedmonopole.jl b/test/test_hhd2d_directedmonopole.jl new file mode 100644 index 00000000..958d6fc7 --- /dev/null +++ b/test/test_hhd2d_directedmonopole.jl @@ -0,0 +1,63 @@ +using BEAST +using CompScienceMeshes +using StaticArrays +using LinearAlgebra +using Test +using SpecialFunctions + + + ε0 = 8.854187821e-12 + μ0 = 4π*1e-7 + c0 = 1/sqrt(ε0*μ0) + η0 = sqrt(μ0/ε0) + + f = 1e9 # 1 GHz + ω = 2π*f + λ = c0/f + k = 2π/λ + h = λ/10 + a = 1.0 # radius of the scatterer + + circle = CompScienceMeshes.meshcircle(a, h) + X0 = lagrangecxd0(circle) + X1 = lagrangec0d1(circle) + + # Computing the fields on a circle of radius rc + rc = 500.0*a + pts = meshcircle(rc, 0.3 * rc).vertices + + ## Analytical solutions + + dbesselj(n,x) = (besselj(n-1, x) - besselj(n+1, x)) / 2 + dhankelh2(n,x) = (hankelh2(n-1, x) - hankelh2(n+1, x)) / 2 + + cart2polar(x,y) = SVector(sqrt(x^2 + y^2), atan(y, x)) + + 𝒟 = Helmholtz2D.doublelayer(; wavenumber=k) + D = assemble(𝒟, X1, X1) + I1 = assemble(BEAST.Identity(), X1, X1) + M_TEMFIE = 0.5I1 - D + + # TE-EFIE + 𝒩 = Helmholtz2D.hypersingular(;alpha=im*ω*μ0, beta=1.0/(im*ω*ε0), wavenumber=k) + M_TEEFIE = assemble(𝒩, X1, X1) + + ## + # 1. Excitation: Planewave + H0 = 1.0 + Hz_dm_inc = Helmholtz2D.directedmonopole(; + amplitude=H0, + wavenumber=k, + position=SVector(2.0, 0.0), + direction=SVector(1.0, 0.0) + ) + + hz_dm_inc = -assemble(DirichletTrace(Hz_dm_inc), X1) + j_TEMFIE_dm = M_TEMFIE \ hz_dm_inc + + Et_dm_inc = 1 / (im * ω * ε0) * curl(Hz_dm_inc) + et_dm_inc = assemble(TangentTrace(Et_dm_inc), X1) + + j_TEEFIE_dm = M_TEEFIE \ et_dm_inc + + @test norm(j_TEEFIE_dm + j_TEMFIE_dm)/norm(j_TEMFIE_dm) < 0.01 \ No newline at end of file diff --git a/test/test_higher_order_lagrange_functions.jl b/test/test_higher_order_lagrange_functions.jl new file mode 100644 index 00000000..aec7f292 --- /dev/null +++ b/test/test_higher_order_lagrange_functions.jl @@ -0,0 +1,306 @@ +@testitem "lagrangecx order=3 - global" begin + using CompScienceMeshes + + projectdir = joinpath(dirname(pathof(BEAST)),"..") + m = readmesh(joinpath(projectdir, "test/assets/sphere2.in")) + + lagspace3 = BEAST.lagrangecx(m, order=3) + @test numfunctions(lagspace3) == 10 * length(m) + @test refspace(lagspace3) == BEAST.LagrangeRefSpace{Float64,3,3,10}() +end + +@testitem "lagrangecxd2: local, ref" begin + using CompScienceMeshes + + T = Float64 + s = simplex( + point(1,0,0), + point(0,1,0), + point(0,0,0), + ) + p = CompScienceMeshes.center(s) + + ϕ = BEAST.LagrangeRefSpace{Float64,2,3,6}() + A = ϕ(p) + + # test the dimension + @test length(A) == binomial(4,2) + + # test the partition of unity property + valp = sum(a.value for a in A) + crlp = sum(a.curl for a in A) + @test valp ≈ 1 + @test crlp ≈ point(0,0,0) atol=sqrt(eps(T)) + + u = T(0.2); du = eps(T) * 1000 + v = T(0.6); dv = eps(T) * 1000 + + p00 = neighborhood(s, (u,v)) + p10 = neighborhood(s, (u+du,v)) + p01 = neighborhood(s, (u, v+dv)) + + ϕ00 = ϕ(p00) + ϕ10 = ϕ(p10) + ϕ01 = ϕ(p01) + + # @show [x.value for x in ϕ(neighborhood(s, (0.0, 0.0)))] + + tu = tangents(p00,1) + tv = tangents(p00,2) + j = jacobian(p00) + + for (f00, f10, f01) in zip(ϕ00, ϕ10, ϕ01) + dfdu = (f10.value - f00.value)/du + dfdv = (f01.value - f00.value)/dv + curl_num = (dfdv * tu - dfdu * tv) / j + curl_ana = f00.curl + @test curl_num ≈ curl_ana atol=sqrt(eps(T))*100 + end +end + +@testitem "lagrangecxd3: local, ref" begin + using CompScienceMeshes + + T = Float64 + s = simplex( + point(1,0,0), + point(0,1,0), + point(0,0,0), + ) + p = center(s) + + ϕ = BEAST.LagrangeRefSpace{Float64,3,3,10}() + A = ϕ(p) + + # test the dimension + @test length(A) == binomial(5,2) + + # test the partition of unity property + valp = sum(a.value for a in A) + crlp = sum(a.curl for a in A) + @test valp ≈ 1 + @test crlp ≈ point(0,0,0) atol=sqrt(eps(T)) + + u = T(0.2); du = eps(T) * 1000 + v = T(0.6); dv = eps(T) * 1000 + + p00 = neighborhood(s, (u,v)) + p10 = neighborhood(s, (u+du,v)) + p01 = neighborhood(s, (u, v+dv)) + + ϕ00 = ϕ(p00) + ϕ10 = ϕ(p10) + ϕ01 = ϕ(p01) + + tu = tangents(p00,1) + tv = tangents(p00,2) + j = jacobian(p00) + + for (f00, f10, f01) in zip(ϕ00, ϕ10, ϕ01) + dfdu = (f10.value - f00.value)/du + dfdv = (f01.value - f00.value)/dv + curl_num = (-dfdv * tu + dfdu * tv) / j + curl_ana = f00.curl + @test curl_num ≈ curl_ana atol=sqrt(eps(T))*100 + end +end + + +@testitem "lagrangecxd3: local, generic simplex" begin + using CompScienceMeshes + + s = simplex( + point(3,0,0), + point(0,2,1), + point(-1,-1,-1), + ) + p = center(s) + + ϕ = BEAST.LagrangeRefSpace{Float64,3,3,10}() + A = ϕ(p) + + # test the dimension + @test length(A) == binomial(5,2) + + # test the partition of unity property + valp = sum(a.value for a in A) + @test valp ≈ 1 +end + +@testitem "lagrangecxd3: self-interpolate" begin + using CompScienceMeshes + using LinearAlgebra + + ϕ = BEAST.LagrangeRefSpace{Float64,3,3,10}() + ch = simplex( + point(3,0,0), + point(0,2,1), + point(-1,-1,-1)) + ch1toch2 = simplex( + point(1,0), + point(0,1), + point(0,0)) + + Q = BEAST.interpolate(ϕ, ch, ϕ, ch, ch1toch2) + # display(round.(Q, digits=3)) + Id = Matrix{Float64}(I, 10, 10) + @test Q ≈ Id +end + + +@testitem "lagrnacexcd3: interpolate generic poly" begin + + using CompScienceMeshes + using LinearAlgebra + + ϕ = BEAST.LagrangeRefSpace{Float64,3,3,10}() + ch = simplex( + point(3,0,0), + point(0,2,1), + point(-1,-1,-1)) + + function fields(p) + u, v = parametric(p) + return [ + 1 + u + v + u^2 + u*v + v^2 + u^3 + u^2*v + u*v^2 + v^3, + 1 + u + u^2 + u^3, + 1 + u + u^2, + 1 + u, + 1 + ] + end + + Q = BEAST.interpolate(fields, ϕ, ch) + nbds = [ + neighborhood(ch, (0.2, 0.2)), + neighborhood(ch, (0.2, 0.6)), + neighborhood(ch, (0.6, 0.2)), + neighborhood(ch, (1/3, 1/3)), + neighborhood(ch, (0.0, 0.0))] + + for p in nbds + basis = ϕ(p) + + vals = fields(p) + for j in eachindex(vals) + val1 = vals[j] + val2 = sum(Q[j,i] * b.value for (i,b) in enumerate(basis)) + @test val1≈val2 atol=1e-8 + end + # println() + end +end + + +@testitem "lagc0d2: support size" begin + using CompScienceMeshes + using SparseArrays + + order = 2 + projectdir = joinpath(dirname(pathof(BEAST)),"..") + m = readmesh(joinpath(projectdir, "test/assets/sphere2.in")) + + verts = skeleton(m,0) + edges = skeleton(m,1) + + println(pathof(CompScienceMeshes)) + + lagspace3 = BEAST.lagrangec0(m, order=order) + @test numfunctions(lagspace3) == + length(verts) + + length(edges)*(order-1) + + length(m)*div((order-1)*(order-2),2) + @test refspace(lagspace3) == BEAST.LagrangeRefSpace{Float64,2,3,6}() + + conn20 = connectivity(verts, m, x -> 1) + num_adjacent_faces = vec(sum(conn20, dims=1)) + + nv = length(verts) + ne = length(edges) * (order-1) + nf = length(m) * div((order-1) * (order-2),2) + @test length.(lagspace3.fns[1:length(verts)]) == num_adjacent_faces + @test all(length.(lagspace3.fns[nv+1:nv+ne]) .== 2) + @test all(length.(lagspace3.fns[nv+ne+1:nv+ne+nf]) .== 1) +end + + +@testitem "lagc0d3: support size" begin + using CompScienceMeshes + using SparseArrays + order = 3 + + projectdir = joinpath(dirname(pathof(BEAST)),"..") + m = readmesh(joinpath(projectdir, "test/assets/sphere2.in")) + + verts = skeleton(m,0) + edges = skeleton(m,1) + + println(pathof(CompScienceMeshes)) + + lagspace3 = BEAST.lagrangec0(m, order=3) + @test numfunctions(lagspace3) == + length(verts) + + length(edges)*(order-1) + + length(m)*div((order-1)*(order-2),2) + @test refspace(lagspace3) == BEAST.LagrangeRefSpace{Float64,3,3,10}() + + conn20 = connectivity(verts, m, x -> 1) + num_adjacent_faces = vec(sum(conn20, dims=1)) + + nv = length(verts) + ne = length(edges) * (order-1) + nf = length(m) * div((order-1) * (order-2),2) + @test length.(lagspace3.fns[1:nv]) == num_adjacent_faces + @test all(length.(lagspace3.fns[nv+1:nv+ne]) .== 2) + @test all(length.(lagspace3.fns[nv+ne+1:nv+ne+nf]) .== 1) +end + + +# @testitem "lagrangec0 order=3 - continuity" begin +# using CompScienceMeshes +# using SparseArrays +# order = 3 + +# projectdir = joinpath(dirname(pathof(BEAST)),"..") +# m = readmesh(joinpath(projectdir, "test/assets/sphere2.in")) + +# verts = skeleton(m,0) +# edges = skeleton(m,1) + +# println(pathof(CompScienceMeshes)) + +# lagspace3 = BEAST.lagrangec0(m, order=3) +# @test numfunctions(lagspace3) == +# length(verts) + +# length(edges)*(order-1) + +# length(m)*div((order-1)*(order-2),2) +# @test refspace(lagspace3) == BEAST.LagrangeRefSpace{Float64,3,3,10}() + +# conn20 = connectivity(verts, m) +# end + + +# @testitem "Lagc0d3: alternative construction" begin +# using CompScienceMeshes +# using SparseArrays +# import Main.InteractiveUtils +# order = 3 + +# projectdir = joinpath(dirname(pathof(BEAST)),"..") +# m = readmesh(joinpath(projectdir, "test/assets/sphere2.in")) + +# verts = skeleton(m,0) +# edges = skeleton(m,1) + +# println(pathof(CompScienceMeshes)) + +# lagspace3 = BEAST.lagrangec0(m, order=3) +# @test numfunctions(lagspace3) == +# length(verts) + +# length(edges)*(order-1) + +# length(m)*div((order-1)*(order-2),2) +# # @show InteractiveUtils.@which refspace(lagspace3) +# @test refspace(lagspace3) == BEAST.LagrangeRefSpace{Float64,3,3,10}() + +# conn20 = connectivity(verts, m) +# end \ No newline at end of file diff --git a/test/test_interpolate_and_restrict.jl b/test/test_interpolate_and_restrict.jl new file mode 100644 index 00000000..9671c0e6 --- /dev/null +++ b/test/test_interpolate_and_restrict.jl @@ -0,0 +1,61 @@ +using Test + +using BEAST +using CompScienceMeshes + +chart1 = simplex( + point(1,0,0), + point(0,1,0), + point(0,0,0)) + +chart2 = simplex( + point(1/2,0,0), + point(0,1/2,0), + point(1,1,0)) + +X = BEAST.RTRefSpace{Float64}() +@time Q1 = BEAST.restrict(X, chart1, chart2) +@time Q2 = BEAST.interpolate(X, chart2, X, chart1) +@test Q1 ≈ Q2 + +constant_vector_field = point(1,2,0) +Q3 = BEAST.interpolate(X, chart2) do p + return [constant_vector_field] +end + +ctr = center(chart2) +vals = [f.value for f in X(ctr)] +itpol = sum(w*val for (w,val) in zip(Q3,vals)) +@test itpol ≈ constant_vector_field + +# using TestItems +@testitem "restrict RT0" begin + using CompScienceMeshes + + ref_vertices = [ + point(1,0), + point(0,1), + point(0,0), + ] + vertices = [ + point(1,0,0), + point(0,1,0), + point(0,0,0), + ] + chart1 = simplex(vertices...) + for I in BEAST._dof_perms_rt + chart2 = simplex( + chart1.vertices[I[1]], + chart1.vertices[I[2]], + chart1.vertices[I[3]],) + chart2tochart1 = CompScienceMeshes.simplex(ref_vertices[collect(I)]...) + rs = BEAST.RTRefSpace{Float64}() + Q1 = BEAST.dof_perm_matrix(rs, I) + Q2 = BEAST.restrict(rs, chart1, chart2) + Q3 = BEAST.restrict(rs, chart1, chart2, chart2tochart1) + @test Q1 ≈ Q2 + @test Q1 ≈ Q3 + end +end + + diff --git a/test/test_interpolation.jl b/test/test_interpolation.jl new file mode 100644 index 00000000..3836521e --- /dev/null +++ b/test/test_interpolation.jl @@ -0,0 +1,28 @@ +using BEAST +using CompScienceMeshes +using Test + +Γ = meshrectangle(1.0, 1.0, 0.4) + +XN = lagrangecxd0(Γ) # lagrangec0d1(Γₑ₁; dirichlet=true) # Dirichlet=true -> no boundary function +XD = lagrangec0d1(Γ) + +mp = Helmholtz3D.monopole(position=SVector(0.0, 0.0, 3.0)) +gradmp = grad(mp) + +φ = DofInterpolate(XD, mp) + +@test eltype(φ) == Float64 + +for i in eachindex(φ) + @test mp(XD.pos[i]) == φ[i] +end + +σ = DofInterpolate(XN, gradmp) + +@test eltype(σ) == Float64 + +for i in eachindex(σ) + # Orientation of meshrectangle is in -z direction + @test dot(SVector(0.0, 0.0, -1.0), gradmp(XN.pos[i])) == σ[i] +end \ No newline at end of file diff --git a/test/test_local_assembly.jl b/test/test_local_assembly.jl index a99ca345..a7b5a2dd 100644 --- a/test/test_local_assembly.jl +++ b/test/test_local_assembly.jl @@ -1,3 +1,4 @@ +@info "Executing test_local_assembly.jl" using CompScienceMeshes using BEAST @@ -5,10 +6,11 @@ using Test m = meshrectangle(1.0, 1.0, 0.5, 3) nodes = skeleton(m,0) -srt_bnd_nodes = sort.(skeleton(boundary(m),0)) -int_nodes = submesh(nodes) do node - !(sort(node) in srt_bnd_nodes) -end +int_nodes = submesh(!in(skeleton(boundary(m),0)), nodes) +# srt_bnd_nodes = sort.(skeleton(boundary(m),0)) +# int_nodes = submesh(nodes) do node +# !(sort(node) in srt_bnd_nodes) +# end @test length(int_nodes) == 1 @@ -43,3 +45,30 @@ fr2, st2 = BEAST.allocatestorage(Id, BC, RT, Val{:bandedstorage}, BEAST.LongDela BEAST.assemble_local_mixed!(Id, BC, RT, st2) Q2 = fr2() @test isapprox(Q1, Q2, atol=1e-8) + + +@testitem "localop assembly returns sparse" begin + using CompScienceMeshes + using LinearAlgebra + using LinearMaps + using SparseArrays + + Γ = meshsphere(radius=1.0, h=0.35) + X = raviartthomas(Γ) + @show numfunctions(X) + + Id = BEAST.Identity() + + @hilbertspace k + @hilbertspace j + + + Idkj = Id[k,j] + term = Idkj.terms[1] + dpsX = BEAST.DirectProductSpace([X]) + + Z1 = assemble(Idkj, dpsX, dpsX) + # T = typeof(Z1.lmap.A.lmap) + + @test Z1.lmap.A.lmap isa SparseMatrixCSC +end \ No newline at end of file diff --git a/test/test_local_storage.jl b/test/test_local_storage.jl index 980baab3..a5cd8bb5 100644 --- a/test/test_local_storage.jl +++ b/test/test_local_storage.jl @@ -1,21 +1,22 @@ -using Test -using BEAST -using CompScienceMeshes -using SparseArrays +@testitem "storage: local operators" begin + using CompScienceMeshes + using SparseArrays + for T in [Float32, Float64] + fn = joinpath(@__DIR__, "assets/sphere5.in") + m = readmesh(fn, T=T) -fn = joinpath(@__DIR__, "assets/sphere5.in") -m = readmesh(fn) + Id = BEAST.Identity() + X = BEAST.raviartthomas(m) -Id = BEAST.Identity() -X = BEAST.raviartthomas(m) + Z1 = assemble(Id, X, X, storage_policy=Val{:densestorage}) + Z2 = assemble(Id, X, X, storage_policy=Val{:bandedstorage}) + Z3 = assemble(Id, X, X, storage_policy=Val{:sparsedicts}) -Z1 = assemble(Id, X, X, storage_policy=Val{:densestorage}) -Z2 = assemble(Id, X, X, storage_policy=Val{:bandedstorage}) -Z3 = assemble(Id, X, X, storage_policy=Val{:sparsedicts}) + @test Z1 isa DenseMatrix + @test Z2 isa SparseMatrixCSC + @test Z3 isa SparseMatrixCSC -@test Z1 isa DenseMatrix -@test Z2 isa SparseMatrixCSC -@test Z3 isa SparseMatrixCSC - -@test Z1 ≈ Z2 atol=1e-8 -@test Z1 ≈ Z3 atol=1e-8 \ No newline at end of file + @test Z1 ≈ Z2 atol=1e-8 + @test Z1 ≈ Z3 atol=1e-8 + end +end \ No newline at end of file diff --git a/test/test_mixed_blkassm.jl b/test/test_mixed_blkassm.jl new file mode 100644 index 00000000..dc7a6651 --- /dev/null +++ b/test/test_mixed_blkassm.jl @@ -0,0 +1,71 @@ +@testitem "Issue #66: block assembly dbl-layer" begin + # test resolutuion of #66 + + # using BEAST + # using Test + using CompScienceMeshes + using LinearAlgebra + + function hassemble2(operator::BEAST.AbstractOperator, + test_functions, + trial_functions) + + blkasm = BEAST.blockassembler(operator, test_functions, trial_functions) + + function assembler2(Z, tdata, sdata) + store(v,m,n) = (Z[m,n] += v) + blkasm(tdata,sdata,store) + end + + mat = zeros(scalartype(operator), + numfunctions(test_functions), + numfunctions(trial_functions)) + + assembler2(mat, 1:numfunctions(test_functions), 1:numfunctions(trial_functions)) + return mat + end + + for T in [Float32, Float64] + c = T(3e8) + μ = T(4π * 1e-7) + ε = T(1/(μ*c^2)) + f = T(1e8) + λ = T(c/f) + k = T(2π/λ) + ω = T(k*c) + η = T(sqrt(μ/ε)) + + a = T(1) + Γ = CompScienceMeshes.meshcuboid(a,a,a,T(0.2); generator=:compsciencemeshes) + + 𝓣 = Maxwell3D.singlelayer(wavenumber=k) + 𝓚 = Maxwell3D.doublelayer(wavenumber=k) + + X = raviartthomas(Γ) + Y = buffachristiansen(Γ) + + # println("Number of RWG functions: ", numfunctions(X)) + + T_blockassembler = hassemble2(𝓣, X, X) + T_standardassembler = assemble(𝓣, X, X) + + @test norm(T_blockassembler - T_standardassembler)/norm(T_standardassembler) ≈ 0.0 atol=100*eps(T) + + T_bc_blockassembler = hassemble2(𝓣, Y, Y) + T_bc_standardassembler = assemble(𝓣, Y, Y) + + @test norm(T_bc_blockassembler - T_bc_standardassembler)/norm(T_bc_standardassembler) ≈ 0.0 atol=100*eps(T) + + K_mix_blockassembler = hassemble2(𝓚,Y,X) + K_mix_standardassembler = assemble(𝓚,Y,X) + + T_mix_blockassembler = hassemble2(𝓣, Y, X) + T_mix_standardassembler = assemble(𝓣, Y, X) + + if T==Float64 + @test norm(K_mix_blockassembler - K_mix_standardassembler)/norm(K_mix_standardassembler) ≈ 0.0 atol=100*eps(T) + @test norm(T_mix_blockassembler - T_mix_standardassembler)/norm(T_mix_standardassembler) ≈ 0.0 atol=100*eps(T) + @info "Tests executed!" + end + end +end \ No newline at end of file diff --git a/test/test_mult.jl b/test/test_mult.jl index d8b2081d..420b30ae 100644 --- a/test/test_mult.jl +++ b/test/test_mult.jl @@ -1,32 +1,30 @@ -using CompScienceMeshes -using BEAST +@testitem "loops have div zero" begin + using CompScienceMeshes + using LinearAlgebra -using Test -using LinearAlgebra + for T in [Float32, Float64] + faces = meshrectangle(T(1.0), T(1.0), T(0.5), 3) -faces = meshrectangle(1.0, 1.0, 0.5, 3) -srt_bnd_faces = sort.(boundary(faces)) -edges = submesh(skeleton(faces,1)) do edge - !(sort(edge) in srt_bnd_faces) -end + bnd = boundary(faces) + edges = submesh(!in(bnd), skeleton(faces,1)) -srt_bnd_nodes = sort.(skeleton(boundary(faces),0)) -@test length(srt_bnd_nodes) == 8 -nodes = submesh(skeleton(faces,0)) do node - !(sort(node) in srt_bnd_nodes) -end -@test length(nodes) == 1 + bnd_nodes = skeleton(bnd, 0) + @test length(bnd_nodes) == 8 + nodes = submesh(!in(bnd_nodes), skeleton(faces,0)) + @test length(nodes) == 1 -Conn = connectivity(nodes, edges, sign) + Conn = connectivity(nodes, edges, sign) -X = raviartthomas(faces, cellpairs(faces,edges)) -@test numfunctions(X) == 8 + X = raviartthomas(faces, cellpairs(faces,edges)) + @test numfunctions(X) == 8 -divX = divergence(X) -Id = BEAST.Identity() -DD = assemble(Id, divX, divX) -@test rank(DD) == 7 -L = divX * Conn -for sh in L.fns[1] - @test isapprox(sh.coeff, 0, atol=1e-8) -end + divX = divergence(X) + Id = BEAST.Identity() + DD = Matrix(assemble(Id, divX, divX)) + @test rank(DD) == 7 + L = divX * Conn + for sh in L.fns[1] + @test isapprox(sh.coeff, 0, atol=1e-8) + end + end +end \ No newline at end of file diff --git a/test/test_ncrossbdm.jl b/test/test_ncrossbdm.jl new file mode 100644 index 00000000..1df7ec8a --- /dev/null +++ b/test/test_ncrossbdm.jl @@ -0,0 +1,40 @@ +@testitem "local basis: nxBDM and BDM consistency" begin + using CompScienceMeshes + #testing local value in the center of a triangle + for T in [Float64] + for j in [1,2,3,4,5,6] + o, x, y, z = euclidianbasis(3,T) + triang = simplex(x,y,o) + ctr = center(triang) + n = normal(triang) + oref = BEAST.NCrossBDMRefSpace{T}() + oref2= BEAST.BDMRefSpace{T}() + oshp=BEAST.Shape(1,j,1.0) + oshp2=BEAST.Shape(1,j,1.0) + + o1 = oref(ctr)[oshp.refid].value * oshp.coeff + o2=n × oref2(ctr)[oshp2.refid].value * oshp2.coeff + @test o1 ≈ o2 + end + end + + m=meshsphere(radius=1.0, h=1.0) + Z = BEAST.brezzidouglasmarini(m) + NZ = BEAST.ncrossbdm(m) + for i=(1,11,36,52,111) + for j=(1,2) + @test Z.fns[i][j].coeff ≈ NZ.fns[i][j].coeff + @test Z.fns[i][j].refid ≈ NZ.fns[i][j].refid + @test Z.fns[i][j].cellid ≈ NZ.fns[i][j].cellid + end + end + + #testing the values on a mesh through Gram matrices + Id = BEAST.Identity() + qs = BEAST.SingleNumQStrat(8) + + Gzz= assemble(Id,Z,Z,quadstrat=qs) + Gnznz= assemble(Id,NZ,NZ,quadstrat=qs) + @test Gzz ≈ Gnznz +end + diff --git a/test/test_nd2.jl b/test/test_nd2.jl new file mode 100644 index 00000000..e354973a --- /dev/null +++ b/test/test_nd2.jl @@ -0,0 +1,25 @@ +using CompScienceMeshes +using BEAST +using LinearAlgebra + +using Test + + +#= """ +This test is achieved by projecting the coefficients of 1st degree +curl conforming basis functions onto the 2nd degree basis functions +and then projecting back the obtained coefficients of the 2nd degree +basis functions onto the 1st degree basis functions. The resultant +operator should be an Identity operator. +""" =# + +m = meshrectangle(1.0,1.0,0.5) +tol = 1e-10 +X2 = BEAST.nedelec2(m) +X1 = BEAST.nedelec(m) +G11 = assemble(Identity(), X1, X1) +G12 = assemble(Identity(), X1, X2) +G21 = assemble(Identity(), X2, X1) +G22 = assemble(Identity(), X2, X2) +Id = Matrix{eltype(G11)}(LinearAlgebra.I, numfunctions(X1), numfunctions(X1)) +@test norm(inv(Matrix(G11))*G12*inv(Matrix(G22))*G21-Id) 1 && push!(interior_edges, edge) diff --git a/test/test_ndlcd_restrict.jl b/test/test_ndlcd_restrict.jl index 19f88e34..6b7d306e 100644 --- a/test/test_ndlcd_restrict.jl +++ b/test/test_ndlcd_restrict.jl @@ -1,36 +1,38 @@ -using CompScienceMeshes -using BEAST -using Test -using LinearAlgebra +@testitem "restrict for 3D Nedelec-curl" begin + using CompScienceMeshes + using LinearAlgebra -o, x, y, z = CompScienceMeshes.euclidianbasis(3) -tet = simplex(x,y,z,o) + for T in [Float32, Float64] + o, x, y, z = CompScienceMeshes.euclidianbasis(3,T) + tet = simplex(x,y,z,o) -rs = BEAST.NDLCDRefSpace{Float64}() -Q = BEAST.restrict(rs, tet, tet) -@test Q ≈ Matrix(1.0LinearAlgebra.I, 4, 4) + rs = BEAST.NDLCDRefSpace{T}() + Q = BEAST.restrict(rs, tet, tet) + @test Q ≈ Matrix(T(1.0)LinearAlgebra.I, 4, 4) -rs = BEAST.NDLCCRefSpace{Float64}() -Q = BEAST.restrict(rs, tet, tet) -@test Q ≈ Matrix(1.0LinearAlgebra.I, 6, 6) + rs = BEAST.NDLCCRefSpace{T}() + Q = BEAST.restrict(rs, tet, tet) + @test Q ≈ Matrix(T(1.0)LinearAlgebra.I, 6, 6) -c = cartesian(center(tet)) -smalltet = simplex(x,y,z,c) -p_smalltet = center(smalltet) -p_tet = neighborhood(tet, carttobary(tet, cartesian(p_smalltet))) -@show cartesian(p_smalltet) -@show cartesian(p_tet) -@assert cartesian(p_tet) ≈ cartesian(p_smalltet) -@assert volume(tet) / volume(smalltet) ≈ 4 + c = cartesian(center(tet)) + smalltet = simplex(x,y,z,c) + p_smalltet = center(smalltet) + p_tet = neighborhood(tet, carttobary(tet, cartesian(p_smalltet))) + # @show cartesian(p_smalltet) + # @show cartesian(p_tet) + @assert cartesian(p_tet) ≈ cartesian(p_smalltet) + @assert volume(tet) / volume(smalltet) ≈ 4 -Q = BEAST.restrict(rs, tet, smalltet) -Fp = rs(p_tet) -fp = rs(p_smalltet) + Q = BEAST.restrict(rs, tet, smalltet) + Fp = rs(p_tet) + fp = rs(p_smalltet) -for j in axes(Q,1) - x = Fp[j].value - y = sum(Q[j,i]*fp[i].value for i in axes(Q,2)) - @show x - @show y - @test x ≈ y -end + for j in axes(Q,1) + x = Fp[j].value + y = sum(Q[j,i]*fp[i].value for i in axes(Q,2)) + # @show x + # @show y + @test x ≈ y + end + end +end \ No newline at end of file diff --git a/test/test_ndspace.jl b/test/test_ndspace.jl index 64cbf85e..c400c039 100644 --- a/test/test_ndspace.jl +++ b/test/test_ndspace.jl @@ -1,34 +1,43 @@ using CompScienceMeshes using BEAST + using Test +using LinearAlgebra + + +for T in [Float32, Float64] + fn = joinpath(dirname(@__FILE__),"assets","rect1.in") -fn = joinpath(dirname(@__FILE__),"assets","rect1.in") + mesh = readmesh(fn, T=T) + edgs = skeleton(mesh,1) -mesh = readmesh(fn) -edgs = skeleton(mesh,1) + charts = [chart(edgs,edg) for edg in edgs] + ctrs = [cartesian(center(cht)) for cht in charts] -charts = [chart(edgs,edg) for edg in cells(edgs)] -ctrs = [cartesian(center(cht)) for cht in charts] + ND = BEAST.nedelec(mesh, edgs) -ND = BEAST.nedelec(mesh, edgs) + @test numfunctions(ND) == numcells(edgs) + @test length(ND.fns[1]) == 1 + @test length(ND.fns[2]) == 1 + @test length(ND.fns[3]) == 2 + @test length(ND.fns[4]) == 1 + @test length(ND.fns[5]) == 1 -@test numfunctions(ND) == numcells(edgs) -@test length(ND.fns[1]) == 1 -@test length(ND.fns[2]) == 1 -@test length(ND.fns[3]) == 2 -@test length(ND.fns[4]) == 1 -@test length(ND.fns[5]) == 1 + # center of the diagonal + ctr = center(charts[3]) + face_charts = [chart(mesh,fce) for fce in mesh] + nbd1 = neighborhood(face_charts[1], carttobary(face_charts[1], cartesian(ctr))) + nbd2 = neighborhood(face_charts[2], carttobary(face_charts[2], cartesian(ctr))) + t = tangents(ctr,1) + ut = t / norm(t) -# center of the diagonal -ctr = center(charts[3]) -face_charts = [chart(mesh,fce) for fce in cells(mesh)] -nbd1 = neighborhood(face_charts[1], carttobary(face_charts[1], ctr)) -nbd2 = neighborhood(face_charts[2], carttobary(face_charts[2], ctr)) -t = tangents(ctr,1) -ut = t / norm(t) + ndlocal = refspace(ND) + fn = ND.fns[3] + v1 = dot(fn[1].coeff*ndlocal(nbd1)[1][1],ut) + v2 = dot(fn[2].coeff*ndlocal(nbd2)[2][1],ut) + @test v1 ≈ v2 ≈ -1/√2 ≈ -1/volume(charts[3]) -ndlocal = refspace(ND) -fn = ND.fns[3] -v1 = dot(fn[1].coeff*ndlocal(nbd1)[1][1],ut) -v2 = dot(fn[2].coeff*ndlocal(nbd2)[2][1],ut) -@test v1 ≈ v2 ≈ -1/√2 ≈ -1/volume(charts[3]) + curlND = curl(ND) + @test curlND.fns[3][1].coeff ≈ +2 + @test curlND.fns[3][2].coeff ≈ -2 +end diff --git a/test/test_nitsche.jl b/test/test_nitsche.jl index d748d22a..3d620e8f 100644 --- a/test/test_nitsche.jl +++ b/test/test_nitsche.jl @@ -1,5 +1,7 @@ +@info "Executing test_nitsche.jl" + using Test -#using LinearForms +using LinearAlgebra using CompScienceMeshes using BEAST @@ -14,11 +16,20 @@ h = 0.25 Γ = meshrectangle(1.0,1.0,h) γ = meshsegment(1.0,1.0,3) -X = raviartthomas(Γ, γ) +in_interior = CompScienceMeshes.interior_tpredicate(Γ) +on_junction = CompScienceMeshes.overlap_gpredicate(γ) +edges = submesh(skeleton(Γ,1)) do m, edge + in_interior(m,edge) || on_junction(chart(m,edge)) +end + +# edges = skeleton(Γ,1) do edge +# in_interior(edge) ||on_junction(chart(Γ,edge)) +# end +X = raviartthomas(Γ, edges) x = divergence(X) y = ntrace(X,γ) -Z = assemble(S,y,x) +Z = assemble(S,y,x; threading = BEAST.Threading{:single}) # test for the correct sparsity pattern # I, J, V = findall(!iszero, Z) @@ -56,8 +67,8 @@ Nyx = assemble(N,Y,X) @test size(Nyx) == (1,1) -sx = chart(m, first(cells(m))) -sy = chart(n, first(cells(n))) +sx = chart(m, first(m)) +sy = chart(n, first(n)) cx = neighborhood(sx, [1,1]/3) cy = neighborhood(sy, [1]/2) @@ -91,7 +102,7 @@ end for _f in Y.fns @test length(_f) == 1 @test 0 < _f[1].cellid < 4 - seg = chart(Σ, cells(Σ)[_f[1].cellid]) + seg = chart(Σ, _f[1].cellid) @test _f[1].refid == 1 @test _f[1].coeff ≈ (1 / volume(seg)) end diff --git a/test/test_nitschehh3d.jl b/test/test_nitschehh3d.jl index 6b252dae..498eed1b 100644 --- a/test/test_nitschehh3d.jl +++ b/test/test_nitschehh3d.jl @@ -17,9 +17,9 @@ X = lagrangec0d1(m, boundary(m)) @test numfunctions(X) == 3 x = refspace(X) -s = chart(m, m.faces[X.fns[1][1].cellid]) +s = chart(m, X.fns[1][1].cellid) c = neighborhood(s, [1,1]/3) # get the barycenter of that patch -v = x(c, Val{:withcurl}) # evaluate the Lagrange elements in c, together with their curls +v = x(c) # evaluate the Lagrange elements in c, together with their curls @test (s[3] - s[2]) / (2 * volume(s)) ≈ v[1][2] @test (s[1] - s[3]) / (2 * volume(s)) ≈ v[2][2] @@ -29,7 +29,7 @@ Y = lagrangec0d1(n, boundary(n)) @test numfunctions(Y) == 2 y = refspace(Y) -t = chart(n, n.faces[Y.fns[1][1].cellid]) +t = chart(n, Y.fns[1][1].cellid) d = neighborhood(t, [1]/2) tg = normalize(tangents(d,1)) w = y(d) diff --git a/test/test_nonconf_quadrules.jl b/test/test_nonconf_quadrules.jl new file mode 100644 index 00000000..e81bb5a3 --- /dev/null +++ b/test/test_nonconf_quadrules.jl @@ -0,0 +1,42 @@ +@testitem "norm of constant field" begin + using CompScienceMeshes + using LinearAlgebra + + h1 = 0.2 + h2 = 0.176 + m1 = meshcuboid(1.0, 1.0, 1.0, h1; generator=:gmsh) + m2 = meshcuboid(1.0, 1.0, 1.0, h2; generator=:gmsh) + + E = Maxwell3D.planewave(;direction=point(0,0,1), polarization=point(1,0,0), wavenumber=0.0) + e = n × E + + cstrat = BEAST.DoubleNumWiltonSauterQStrat{Int64, Int64}(2, 3, 6, 7, 5, 5, 4, 3) + # cstrat = BEAST.DoubleNumWiltonSauterQStrat{Int64, Int64}(12, 13, 12, 13, 7, 7, 7, 7) + nstrat = BEAST.NonConformingIntegralOpQStrat(cstrat) + + Id = BEAST.Identity() + S = Maxwell3D.singlelayer(alpha=1.0, beta=1.0, gamma=1.0) + + X1 = raviartthomas(m1) + X2 = raviartthomas(m2) + + G11 = assemble(Id, X1, X1) + G22 = assemble(Id, X2, X2) + + u1 = G11 \ assemble(e, X1) + u2 = G22 \ assemble(e, X2) + + S11 = assemble(S, X1, X1; quadstrat=cstrat) + S12 = assemble(S, X1, X2; quadstrat=nstrat) + S22 = assemble(S, X2, X2; quadstrat=cstrat) + + term1 = real(u1' * S11 * u1) + term2 = real(u1' * S12 * u2) + term3 = real(u2' * S22 * u2) + + norm1 = sqrt(term1) + norm3 = sqrt(term2) + testval = norm(term1 - 2*term2 + term3) + @show testval / norm1 + @test testval / norm1 < 3e-2 +end \ No newline at end of file diff --git a/test/test_overlapping_edge_remeshing.jl b/test/test_overlapping_edge_remeshing.jl new file mode 100644 index 00000000..8384ac7f --- /dev/null +++ b/test/test_overlapping_edge_remeshing.jl @@ -0,0 +1,32 @@ +@testitem "conformity" begin + +using CompScienceMeshes + τ = simplex( + point(0.0624999999994547, 0.6856034446626162, 0.0), + point(0.06944444444542179, 0.6735753140519203, 0.0), + point(0.05555730739992443, 0.6735783483387338, 0.0)) + + σ = simplex( + point(0.07017543859225508, 0.6988976942759024, 0.0), + point(0.06249997795123046, 0.6856034064739717, 0.0), + point(0.06140343344926745, 0.6837043913625904, 0.0)) + + for (k,λ) in pairs(faces(τ)) + for (l,μ) in pairs(faces(σ)) + if CompScienceMeshes.overlap(λ, μ) + global i = k + global j = l + end + end end + + @test i == 2 + @test j == 3 + + τs, σs = BEAST._conforming_refinement_touching_triangles(τ,σ,i,j) + + # Ideally both should be true but the round-off causes CompScienceMeshes.overlap to + # incorrectly report. + @test BEAST._test_conformity(τs[1], σs[1])==true + @test BEAST._test_conformity(τs[2], σs[1])==false + +end \ No newline at end of file diff --git a/test/test_quadrature.jl b/test/test_quadrature.jl new file mode 100644 index 00000000..adf76fc8 --- /dev/null +++ b/test/test_quadrature.jl @@ -0,0 +1,90 @@ +using Test + +using CompScienceMeshes +using BEAST +using StaticArrays +using LinearAlgebra + +c = 3e8 +μ = 4*π*1e-7 +ε = 1/(μ*c^2) +f = 5e7 +λ = c/f +k = 2*π/λ +ω = k*c +η = sqrt(μ/ε) + +a = 1 +Γ_orig = CompScienceMeshes.meshcuboid(a,a,a,0.1; generator=:compsciencemeshes) +Γ = translate(Γ_orig,SVector(-a/2,-a/2,-a/2)) + +Φ, Θ = [0.0], range(0,stop=π,length=100) +pts = [point(cos(ϕ)*sin(θ), sin(ϕ)*sin(θ), cos(θ)) for ϕ in Φ for θ in Θ] + +# This is an electric dipole +# The pre-factor (1/ε) is used to resemble +# (9.18) in Jackson's Classical Electrodynamics +E = (1/ε) * dipolemw3d(location=SVector(0.4,0.2,0), + orientation=1e-9.*SVector(0.5,0.5,0), + wavenumber=k) + +n = BEAST.NormalVector() + +𝒆 = (n × E) × n +H = (-1/(im*μ*ω))*curl(E) +𝒉 = (n × H) × n + +𝓣 = Maxwell3D.singlelayer(wavenumber=k) + +X = raviartthomas(Γ) + +T = Matrix(assemble(𝓣,X,X)) +e = Vector(assemble(𝒆,X)) +j_EFIE = T\e + +nf_E_EFIE = potential(MWSingleLayerField3D(wavenumber=k), pts, j_EFIE, X) +nf_H_EFIE = potential(BEAST.MWDoubleLayerField3D(wavenumber=k), pts, j_EFIE, X) ./ η +ff_E_EFIE = potential(MWFarField3D(wavenumber=k), pts, j_EFIE, X) + +accuracy_default_quadrature_nf_e = norm(nf_E_EFIE - E.(pts))/norm(E.(pts)) +accuracy_default_quadrature_nf_h = norm(nf_H_EFIE - H.(pts))/norm(H.(pts)) +accuracy_default_quadrature_ff_e = + norm(ff_E_EFIE - E.(pts, isfarfield=true))/norm(E.(pts, isfarfield=true)) + +function fastquaddata(op::BEAST.MaxwellOperator3D, + test_local_space::BEAST.RefSpace, trial_local_space::BEAST.RefSpace, + test_charts, trial_charts) + + a, b = 0.0, 1.0 + # CommonVertex, CommonEdge, CommonFace rules + println("Fast quadrule is called") + tqd = quadpoints(test_local_space, test_charts, (1,2)) + bqd = quadpoints(trial_local_space, trial_charts, (1,2)) + leg = (BEAST._legendre(3,a,b), BEAST._legendre(4,a,b), BEAST._legendre(5,a,b),) + + return (tpoints=tqd, bpoints=bqd, gausslegendre=leg) +end + +function fastquaddata(fn::BEAST.Functional, refs, cells) + println("Fast RHS quadrature") + return quadpoints(refs, cells, [1]) +end + + +fastT = Matrix(assemble(𝓣,X,X, quaddata=fastquaddata)) +faste = Vector(assemble(𝒆,X, quaddata=fastquaddata)) +fastj_EFIE = fastT\faste + +fastnf_E_EFIE = potential(MWSingleLayerField3D(wavenumber=k), pts, fastj_EFIE, X) +fastnf_H_EFIE = potential(BEAST.MWDoubleLayerField3D(wavenumber=k), pts, fastj_EFIE, X) ./ η +fastff_E_EFIE = potential(MWFarField3D(wavenumber=k), pts, fastj_EFIE, X) + +accuracy_fast_quadrature_nf_e = norm(fastnf_E_EFIE - E.(pts))/norm(E.(pts)) +accuracy_fast_quadrature_nf_h = norm(fastnf_H_EFIE - H.(pts))/norm(H.(pts)) +accuracy_fast_quadrature_ff_e = + norm(fastff_E_EFIE - E.(pts, isfarfield=true))/norm(E.(pts, isfarfield=true)) + +@test accuracy_fast_quadrature_nf_e > accuracy_default_quadrature_nf_e +@test accuracy_fast_quadrature_nf_h > accuracy_default_quadrature_nf_h +@test accuracy_fast_quadrature_ff_e > accuracy_default_quadrature_ff_e + diff --git a/test/test_quadstrat_as_fn.jl b/test/test_quadstrat_as_fn.jl new file mode 100644 index 00000000..fc180c6d --- /dev/null +++ b/test/test_quadstrat_as_fn.jl @@ -0,0 +1,47 @@ +@testitem "quadstrat as function" begin + using CompScienceMeshes + using LinearAlgebra + + pd = dirname(pathof(BEAST)) + fn = joinpath(pd, "../test/assets/sphere45.in") + Γ = CompScienceMeshes.readmesh(fn) + # @show length(Γ) + + X = raviartthomas(Γ) + @show numfunctions(X) + t = Maxwell3D.singlelayer(wavenumber=1.0) + + qsp(p) = (op, X, Y) -> BEAST.DoubleNumWiltonSauterQStrat(2+p, 3+p, 6+p, 7+p, 5+p, 5+p, 4+p, 3+p) + Z = map(0:3) do p + assemble(t, X, X; quadstrat=qsp(p)) + end + + W = map(0:3) do p + qs = qsp(p)(t, X, X) + assemble(t, X, X; quadstrat=qs) + end + + @test all(Z .≈ W) +end + +@testitem "quadstrat for linear combinations" begin + using CompScienceMeshes + using LinearAlgebra + + pd = dirname(pathof(BEAST)) + fn = joinpath(pd, "../test/assets/sphere45.in") + Γ = CompScienceMeshes.readmesh(fn) + # @show length(Γ) + + X = raviartthomas(Γ) + @show numfunctions(X) + t = Maxwell3D.singlelayer(wavenumber=1.0) + k = Maxwell3D.doublelayer(wavenumber=1.0) + + qsp(p) = (op, X, Y) -> BEAST.DoubleNumWiltonSauterQStrat(2+p, 3+p, 6+p, 7+p, 5+p, 5+p, 4+p, 3+p) + + a = t + k + qsp0 = qsp(0) + Z = assemble(a, X, X; quadstrat=qsp0) + # @which assemble(a, X, X; quadstrat=qsp0) +end \ No newline at end of file diff --git a/test/test_raviartthomas.jl b/test/test_raviartthomas.jl index 32eacb5d..5a646a6a 100644 --- a/test/test_raviartthomas.jl +++ b/test/test_raviartthomas.jl @@ -2,39 +2,6 @@ using Test using BEAST using CompScienceMeshes -T = Float64 -tol = eps(T) * 10^3 - -mesh = meshrectangle(1.0, 1.0, 0.5) -@test numvertices(mesh) == 9 - -idcs = mesh.faces[1] -@test size(idcs) == (3,) - -verts = vertices(mesh, idcs) -@test size(verts) == (3,) - -faces = skeleton(mesh, 2) -idcs = faces.faces[1] -verts = vertices(mesh, idcs) -p = simplex(verts, Val{2}) -@test volume(p) == 1/8 - -edges = skeleton(mesh,1) -@test numcells(edges) == 16 - -cps = cellpairs(mesh, edges) -@test size(cps) == (2,16) - -# select only inner edges -I = findall(x->(x>0), cps[2,:]) -cps = cps[:,I] -@test size(cps) == (2,8) - -# build the Raviart-Thomas elements -rt = raviartthomas(mesh, cps) -@test numfunctions(rt) == 8 - function neighbortest(X) Γ = X.geo for (e,f) in enumerate(X.fns) @@ -55,4 +22,40 @@ function neighbortest(X) end end -neighbortest(rt) +for T in [Float32,Float64] + tol = eps(T) * 10^3 + + mesh = meshrectangle(T(1.0), T(1.0), T(0.5)) + @test numvertices(mesh) == 9 + + idcs = mesh.faces[1] + @test size(idcs) == (3,) + + verts = vertices(mesh, idcs) + @test size(verts) == (3,) + + faces = skeleton(mesh, 2) + idcs = faces.faces[1] + verts = vertices(mesh, idcs) + local p = simplex(verts, Val{2}) + @test volume(p) == T(1/8) + + local edges = skeleton(mesh,1) + @test numcells(edges) == 16 + + local cps = cellpairs(mesh, edges) + @test size(cps) == (2,16) + + # select only inner edges + I = findall(x->(x>0), cps[2,:]) + cps = cps[:,I] + @test size(cps) == (2,8) + + # build the Raviart-Thomas elements + rt = raviartthomas(mesh, cps) + @test numfunctions(rt) == 8 + + + + neighbortest(rt) +end \ No newline at end of file diff --git a/test/test_restrict.jl b/test/test_restrict.jl index 224d14bd..69d03137 100644 --- a/test/test_restrict.jl +++ b/test/test_restrict.jl @@ -1,64 +1,73 @@ -using Test +@testitem "restrict" begin + using LinearAlgebra + using CompScienceMeshes -using CompScienceMeshes -using BEAST + const e0 = point(0.0,0.0,0.0) + const e1 = point(1.0,0.0,0.0) + const e2 = point(0.0,1.0,0.0) + const e3 = point(0.0,0.0,1.0) -const e0 = point(0.0,0.0,0.0) -const e1 = point(1.0,0.0,0.0) -const e2 = point(0.0,1.0,0.0) -const e3 = point(0.0,0.0,1.0) + for T in [Float32, Float64] + p = simplex( + [ + point(T,0.0,0.0,0.0), + point(T,1.0,0.0,0.0) + ], + Val{1} + ) -p = simplex( - [ - point(0.0,0.0,0.0), - point(1.0,0.0,0.0) - ], - Val{1} -) + q = simplex( + [ + point(T,0.0,0.0,0.0), + point(T,0.5,0.0,0.0) + ], + Val{1} + ) -q = simplex( - [ - point(0.0,0.0,0.0), - point(0.5,0.0,0.0) - ], - Val{1} -) + f = BEAST.LagrangeRefSpace{T,1,2,2}() + x = neighborhood(p, T.([0.0])) + v = f(x) -f = BEAST.LagrangeRefSpace{Float64,1,2,2}() -x = neighborhood(p, [0.0]) -v = f(x) + @test v[1].value == 0 + @test v[2].value == 1 -@test v[1].value == 0 -@test v[2].value == 1 + @test v[1].derivative == -1 + @test v[2].derivative == +1 -@test v[1].derivative == -1 -@test v[2].derivative == +1 + Q = restrict(f, p, q) + @test Q == [ + T(1.0) T(0.5) + T(0.0) T(0.5)] -Q = restrict(f, p, q) -@test Q == [ - 1.0 0.5 - 0.0 0.5] + p = simplex([T.(2*e0),T.(2*e1),T.(2*e2)], Val{2}) + x = neighborhood(p,T.([0.5, 0.5])) + ϕ = BEAST.RTRefSpace{T}() + v = ϕ(x) + Q = restrict(ϕ, p, p) + if T==Float64 + @test Q == Matrix(LinearAlgebra.I, 3, 3) -# Test restriction of RT elements -ni, no = 6, 7; -ui = transpose([triangleGaussA[ni] triangleGaussB[ni] ]); -uo = transpose([triangleGaussA[no] triangleGaussB[no] ]); -wi = triangleGaussW[ni]; -wo = triangleGaussW[no]; -# universe = Universe(1.0, ui, wi, uo, wo); + q = simplex([T.(e0+e1), T.(2*e1), T.(e1+e2)], Val{2}) + Q = restrict(ϕ, p, q) + @test Q == [ + 2 -1 0 + 0 1 0 + 0 -1 2] // 4 + end -p = simplex([2*e0,2*e1,2*e2], Val{2}) -x = neighborhood(p,[0.5, 0.5]) -ϕ = BEAST.RTRefSpace{Float64}() -v = ϕ(x) + # Test restriction of Nedelec elements + ψ = BEAST.NDRefSpace{T}() + Q = restrict(ψ, p, p) + if T==Float64 + @test Q == Matrix(LinearAlgebra.I, 3, 3) -Q = restrict(ϕ, p, p) -@test Q == Matrix(LinearAlgebra.I, 3, 3) - -q = simplex([e0+e1, 2*e1, e1+e2], Val{2}) -Q = restrict(ϕ, p, q) -@test Q == [ - 2 -1 0 - 0 1 0 - 0 -1 2] // 4 + q = simplex([T.(e0+e1), T.(2*e1), T.(e1+e2)], Val{2}) + Q = restrict(ψ, p, q) + @test Q == [ + 2 -1 0 + 0 1 0 + 0 -1 2] // 4 + end + end +end \ No newline at end of file diff --git a/test/test_rt.jl b/test/test_rt.jl index aa0ac4ab..ea46e92c 100644 --- a/test/test_rt.jl +++ b/test/test_rt.jl @@ -4,7 +4,8 @@ using BEAST using Test using StaticArrays -T = Float64 +const third = one(Float64)/3 +for T in [Float32, Float64] P = SVector{3,T} tol = eps(T) * 10^3 @@ -12,7 +13,8 @@ tol = eps(T) * 10^3 function shapevals(ϕ, ts) numpoints = length(ts) - numshapes = numfunctions(ϕ) + dom = CompScienceMeshes.domain(CompScienceMeshes.chart(ts[1])) + numshapes = numfunctions(ϕ, dom) y = Array{P}(undef, numshapes, numpoints) for i in 1 : numpoints @@ -26,17 +28,17 @@ function shapevals(ϕ, ts) return y end -const third = one(T)/3 -m = meshrectangle(1.0,1.0,0.5); + +local m = meshrectangle(T(1.0),T(1.0),T(0.5)); rwg = raviartthomas(m) ref = refspace(rwg) # vrts = vertices(m, first(cells(m))) # ptch = simplex(vrts[1], vrts[2], vrts[3]) -ptch = chart(m, first(cells(m))) +ptch = chart(m, first(m)) -ctrd = neighborhood(ptch, [1,1]/3) -vals = shapevals(ref, [ctrd]) +ctrd = neighborhood(ptch, T.([1,1]/3)) +local vals = shapevals(ref, [ctrd]) # test edge detection edges = skeleton(m, 1) @@ -44,15 +46,15 @@ vp = cellpairs(m,edges) # test function evaluation v = [ - point(0.0, 0.0, 0.0), - point(2.0, 0.0, 0.0), - point(0.0, 3.0, 0.0)] + point(T, 0.0, 0.0, 0.0), + point(T, 2.0, 0.0, 0.0), + point(T, 0.0, 3.0, 0.0)] cell = simplex(v[1], v[2], v[3]) -mp = neighborhood(cell,[third, third]); -ϕ = BEAST.RTRefSpace{Float64}() +mp = neighborhood(cell,[T(third), T(third)]); +ϕ = BEAST.RTRefSpace{T}() fx = ϕ(mp)[2][1] A = volume(cell) @@ -65,10 +67,10 @@ gx = (r - v[2]) / 2A @test norm((r - v[3])/2A - ϕ(mp)[3][1]) < tol # repeat the test but now on a random cell -randpoint() = point(2*rand()-1, 2*rand()-1, 2*rand()-1) +randpoint() = point(2*rand(T)-1, 2*rand(T)-1, 2*rand(T)-1) v = [randpoint(), randpoint(), randpoint()] cell = simplex(v[1], v[2], v[3]) -mp = neighborhood(cell,[third, third]); +mp = neighborhood(cell,[T(third), T(third)]); fx = ϕ(mp)[2][1] #fx = evalfun(rs, mp) @@ -81,3 +83,4 @@ gx = (r-v[2]) / 2A @test norm((r - v[1])/2A - ϕ(mp)[1][1]) < tol @test norm((r - v[2])/2A - ϕ(mp)[2][1]) < tol @test norm((r - v[3])/2A - ϕ(mp)[3][1]) < tol +end \ No newline at end of file diff --git a/test/test_rt2.jl b/test/test_rt2.jl new file mode 100644 index 00000000..86c9e73b --- /dev/null +++ b/test/test_rt2.jl @@ -0,0 +1,25 @@ +using CompScienceMeshes +using BEAST +using LinearAlgebra + +using Test + +#= """ +This test is achieved by projecting the coefficients of 1st degree +div conforming basis functions onto the 2nd degree basis functions +and then projecting back the obtained coefficients of the 2nd degree +basis functions onto the 1st degree basis functions. The resultant +operator should be an Identity operator. +""" =# + +m = meshrectangle(1.0,1.0,0.5) +tol = 1e-10 +X2 = BEAST.raviartthomas2(m) +X1 = raviartthomas(m) +G11 = assemble(Identity(), X1, X1) +G12 = assemble(Identity(), X1, X2) +G21 = assemble(Identity(), X2, X1) +G22 = assemble(Identity(), X2, X2) +Id = Matrix{eltype(G11)}(LinearAlgebra.I, numfunctions(X1), numfunctions(X1)) +@test norm(inv(Matrix(G11))*G12*inv(Matrix(G22))*G21-Id) 0 -@test dot(rs(nbd2)[2].value, normal(fcs[2])) > 0 -@test dot(rs(nbd3)[3].value, normal(fcs[3])) > 0 -@test dot(rs(nbd4)[4].value, normal(fcs[4])) > 0 - -ctr = cartesian(center(tet)) -@test dot(cartesian(nbd1)-ctr, normal(fcs[1])) > 0 -@test dot(cartesian(nbd2)-ctr, normal(fcs[2])) > 0 -@test dot(cartesian(nbd3)-ctr, normal(fcs[3])) > 0 -@test dot(cartesian(nbd4)-ctr, normal(fcs[4])) > 0 +for T in [Float32, Float64] + local o, x, y, z = euclidianbasis(3,T) + tet = simplex(x,y,z,o) + + nbd1 = neighborhood(tet, T.([0,1,1]/3)) + nbd2 = neighborhood(tet, T.([1,0,1]/3)) + nbd3 = neighborhood(tet, T.([1,1,0]/3)) + nbd4 = neighborhood(tet, T.([1,1,1]/3)) + + local rs = BEAST.NDLCDRefSpace{T}() + local fcs = BEAST.faces(tet) + @test dot(rs(nbd1)[1].value, normal(fcs[1])) > 0 + @test dot(rs(nbd2)[2].value, normal(fcs[2])) > 0 + @test dot(rs(nbd3)[3].value, normal(fcs[3])) > 0 + @test dot(rs(nbd4)[4].value, normal(fcs[4])) > 0 + + dot(rs(nbd1)[1].value, normal(fcs[1])) * volume(fcs[1]) ≈ 1 + dot(rs(nbd2)[2].value, normal(fcs[2])) * volume(fcs[2]) ≈ 1 + dot(rs(nbd3)[3].value, normal(fcs[3])) * volume(fcs[2]) ≈ 1 + dot(rs(nbd4)[4].value, normal(fcs[4])) * volume(fcs[4]) ≈ 1 + + @test dot(rs(nbd1)[2].value, normal(fcs[1])) * volume(fcs[1]) ≈ 0 atol=eps(T) + @test dot(rs(nbd1)[3].value, normal(fcs[1])) * volume(fcs[1]) ≈ 0 atol=eps(T) + @test dot(rs(nbd1)[4].value, normal(fcs[1])) * volume(fcs[1]) ≈ 0 atol=eps(T) + + @test dot(rs(nbd2)[1].value, normal(fcs[2])) * volume(fcs[2]) ≈ 0 atol=eps(T) + @test dot(rs(nbd2)[3].value, normal(fcs[2])) * volume(fcs[2]) ≈ 0 atol=eps(T) + @test dot(rs(nbd2)[4].value, normal(fcs[2])) * volume(fcs[2]) ≈ 0 atol=eps(T) + + @test dot(rs(nbd3)[1].value, normal(fcs[3])) * volume(fcs[3]) ≈ 0 atol=eps(T) + @test dot(rs(nbd3)[2].value, normal(fcs[3])) * volume(fcs[3]) ≈ 0 atol=eps(T) + @test dot(rs(nbd3)[4].value, normal(fcs[3])) * volume(fcs[3]) ≈ 0 atol=eps(T) + + @test dot(rs(nbd4)[1].value, normal(fcs[4])) * volume(fcs[4]) ≈ 0 atol=eps(T) + @test dot(rs(nbd4)[2].value, normal(fcs[4])) * volume(fcs[4]) ≈ 0 atol=eps(T) + @test dot(rs(nbd4)[3].value, normal(fcs[4])) * volume(fcs[4]) ≈ 0 atol=eps(T) + + + local ctr = cartesian(center(tet)) + @test dot(cartesian(nbd1)-ctr, normal(fcs[1])) > 0 + @test dot(cartesian(nbd2)-ctr, normal(fcs[2])) > 0 + @test dot(cartesian(nbd3)-ctr, normal(fcs[3])) > 0 + @test dot(cartesian(nbd4)-ctr, normal(fcs[4])) > 0 +end \ No newline at end of file diff --git a/test/test_rtports.jl b/test/test_rtports.jl index 339ddef7..8e563c92 100644 --- a/test/test_rtports.jl +++ b/test/test_rtports.jl @@ -40,12 +40,14 @@ cps = cellpairs(sum_mesh, edges) # select only outer edges overlaps = overlap_gpredicate(Γ) -on_junction = c -> overlaps(simplex(vertices(meshZ,c))) - -pred = x -> on_junction(x) -edges = skeleton(pred, meshZ, 1) #Take only exterio -@test numcells(edges) == 2 -@test size(edges.faces[1]) == (2,) +on_junction = (m,c) -> overlaps(chart(m,c)) + +# pred = x -> on_junction(x) +edges = submesh(on_junction, skeleton(meshZ,1)) +# edges = skeleton(pred, meshZ, 1) +@test length(edges) == 2 +# @test size(edges.faces[1]) == (2,) +@test dimension(edges) == 1 cps = cellpairs(sum_mesh, edges) @test size(cps) == (2,2) diff --git a/test/test_rtx.jl b/test/test_rtx.jl index d9cb4a12..09425b7f 100644 --- a/test/test_rtx.jl +++ b/test/test_rtx.jl @@ -2,20 +2,22 @@ using CompScienceMeshes using BEAST using Test -m = meshrectangle(1.0, 1.0, 0.25) +for T in [Float32, Float64] + local m = meshrectangle(T(1.0), T(1.0), T(0.25)) -X = raviartthomas(m, BEAST.Continuity{:none}) -@test numfunctions(X) == 16*2*3 -@test all(length.(X.fns) .== 1) + local X = raviartthomas(m, BEAST.Continuity{:none}) + @test numfunctions(X) == 16*2*3 + @test all(length.(X.fns) .== 1) -p = positions(X) + local p = positions(X) -i = 12 -c = X.fns[i][1].cellid -ch = chart(m, cells(m)[c]) -ctr = cartesian(center(ch)) -@test ctr ≈ p[i] + i = 12 + c = X.fns[i][1].cellid + local ch = chart(m, c) + local ctr = cartesian(center(ch)) + @test ctr ≈ p[i] -for (i,f) in enumerate(X.fns) - @test f[1].coeff == 1.0 -end + for (i,f) in enumerate(X.fns) + @test f[1].coeff == T(1.0) + end +end \ No newline at end of file diff --git a/test/test_sauterschwabints.jl b/test/test_sauterschwabints.jl index 29d5ef2f..ef5d190a 100644 --- a/test/test_sauterschwabints.jl +++ b/test/test_sauterschwabints.jl @@ -3,14 +3,16 @@ using LinearAlgebra using BEAST, CompScienceMeshes, SauterSchwabQuadrature, StaticArrays +#for T in [Float64] +T=Float64 t1 = simplex( - @SVector[0.180878, -0.941848, -0.283207], - @SVector[0.0, -0.980785, -0.19509], - @SVector[0.0, -0.92388, -0.382683]) + T.(@SVector [0.180878, -0.941848, -0.283207]), + T.(@SVector [0.0, -0.980785, -0.19509]), + T.(@SVector [0.0, -0.92388, -0.382683])) t2 = simplex( - @SVector[0.373086, -0.881524, -0.289348], - @SVector[0.180878, -0.941848, -0.283207], - @SVector[0.294908, -0.944921, -0.141962]) + T.(@SVector [0.373086, -0.881524, -0.289348]), + T.(@SVector [0.180878, -0.941848, -0.283207]), + T.(@SVector [0.294908, -0.944921, -0.141962])) # s1 = simplex( @@ -23,22 +25,22 @@ t2 = simplex( # @SVector[0.294908, -0.944921, -0.141962]) # Common face case: -rt = BEAST.RTRefSpace{Float64}() +rt = BEAST.RTRefSpace{T}() tqd = BEAST.quadpoints(rt, [t1,t2], (12,)) bqd = BEAST.quadpoints(rt, [t1,t2], (13,)) -op1 = BEAST.MWSingleLayer3D(1.0, 250.0, 1.0) -op2 = BEAST.MWSingleLayer3D(1.0) -op3 = BEAST.MWDoubleLayer3D(0.0) +op1 = BEAST.MWSingleLayer3D(T(1.0), T(250.0), T(1.0)) +op2 = BEAST.MWSingleLayer3D(T(1.0)) +op3 = BEAST.MWDoubleLayer3D(T(0.0)) -SE_strategy = BEAST.WiltonSEStrategy( +SE_strategy = BEAST.WiltonSERule( tqd[1,1], - BEAST.DoubleQuadStrategy( + BEAST.DoubleQuadRule( tqd[1,1], bqd[1,1], ), ) -SS_strategy = SauterSchwabQuadrature.CommonFace(BEAST._legendre(8,0.0,1.0)) +SS_strategy = SauterSchwabQuadrature.CommonFace(BEAST._legendre(8,T(0.0),T(1.0))) z_se = zeros(3,3) z_ss = zeros(3,3) @@ -57,12 +59,12 @@ BEAST.momintegrals!(op2, rt, rt, t1, t1, z_ss2, SS_strategy) @test z_se2 ≈ z_ss2 atol=1e-4 ## Common Vertex Case -SE_strategy = BEAST.WiltonSEStrategy( +SE_strategy = BEAST.WiltonSERule( tqd[1,1], - BEAST.DoubleQuadStrategy( + BEAST.DoubleQuadRule( tqd[1,1], bqd[1,2])) -SS_strategy = SauterSchwabQuadrature.CommonVertex(BEAST._legendre(12,0.0,1.0)) +SS_strategy = SauterSchwabQuadrature.CommonVertex(BEAST._legendre(12,T(0.0),T(1.0))) z_cv_se_1 = zeros(3,3) z_cv_ss_1 = zeros(3,3) @@ -90,25 +92,25 @@ BEAST.momintegrals!(op3, rt, rt, t1, t2, z_cv_ss_3, SS_strategy) ## Common Edge Case: t1 = simplex( - @SVector[0.180878, -0.941848, -0.283207], - @SVector[0.0, -0.980785, -0.19509], - @SVector[0.0, -0.92388, -0.382683], + T.(@SVector [0.180878, -0.941848, -0.283207]), + T.(@SVector [0.0, -0.980785, -0.19509]), + T.(@SVector [0.0, -0.92388, -0.382683]) ) t2 = simplex( - @SVector[0.180878, -0.941848, -0.283207], - @SVector[0.158174, -0.881178, -0.44554], - @SVector[0.0, -0.92388, -0.382683], + T.(@SVector [0.180878, -0.941848, -0.283207]), + T.(@SVector [0.158174, -0.881178, -0.44554]), + T.(@SVector [0.0, -0.92388, -0.382683]) ) tqd = BEAST.quadpoints(rt, [t1,t2], (12,)) bqd = BEAST.quadpoints(rt, [t1,t2], (13,)) -SE_strategy = BEAST.WiltonSEStrategy( +SE_strategy = BEAST.WiltonSERule( tqd[1,1], - BEAST.DoubleQuadStrategy( + BEAST.DoubleQuadRule( tqd[1,1], bqd[1,2])) -SS_strategy = SauterSchwabQuadrature.CommonEdge(BEAST._legendre(12,0.0,1.0)) +SS_strategy = SauterSchwabQuadrature.CommonEdge(BEAST._legendre(12,T(0.0),T(1.0))) z_ce_se_1 = zeros(3,3) z_ce_ss_1 = zeros(3,3) @@ -134,8 +136,9 @@ BEAST.momintegrals!(op3, rt, rt, t1, t2, z_ce_ss_3, SS_strategy) @show norm(z_ce_se_3 - z_ce_ss_3) @test z_ce_se_3 ≈ z_ce_ss_3 atol=1e-5 -SS_strategy = SauterSchwabQuadrature.CommonEdge(BEAST._legendre(18,0.0,1.0)) +SS_strategy = SauterSchwabQuadrature.CommonEdge(BEAST._legendre(18,T(0.0),T(1.0))) z_ce_ss_3_18 = zeros(3,3) BEAST.momintegrals!(op3, rt, rt, t1, t2, z_ce_ss_3_18, SS_strategy) @show norm(z_ce_ss_3 - z_ce_ss_3_18) @test z_ce_ss_3 ≈ z_ce_ss_3_18 atol=1e-14 +#end \ No newline at end of file diff --git a/test/test_sauterschwabints1D.jl b/test/test_sauterschwabints1D.jl new file mode 100644 index 00000000..0cdb2427 --- /dev/null +++ b/test/test_sauterschwabints1D.jl @@ -0,0 +1,113 @@ +using Test +using LinearAlgebra + +using BEAST, CompScienceMeshes, StaticArrays + + +# Float32 not working since hankelh2 returns always F64 +for T in [Float64] + T = Float64 + Γto = meshsegment(T(1.0), T(0.5)) + Γt = Γto + Γs = Γt + + Xt = lagrangecxd0(Γt) + Xs = lagrangecxd0(Γs) + + λ = 10 + k = 2π/λ + + ops = [ + Helmholtz2D.singlelayer(; wavenumber=k) + ] + + refstrat = BEAST.DoubleNumSauterQstrat(3,3,0,4,30,30) + + for op in ops + Sref = assemble(op, Xt, Xs; quadstrat=refstrat) + ref = Sref[1, 1] + + n = 25 + + #Sgl = assemble(op, Xt, Xs; quadstrat=BEAST.DoubleNumQStrat(n, n+1)) + Sss = assemble(op, Xt, Xs; quadstrat=BEAST.DoubleNumSauterQstrat(3,3,0,4,n,n)) + + #gl = Sgl[1, 1] + ss = Sss[1, 1] + + #glrel = norm(gl - ref) / norm(ref) + ssrel = norm(ss - ref) / norm(ref) + + @test ssrel < 1e-14 + end + + Xt = lagrangec0d1(Γt) + Xs = lagrangec0d1(Γs) + + ops = [ + Helmholtz2D.singlelayer(; wavenumber=k) + Helmholtz2D.hypersingular(; wavenumber=k) + ] + 𝒮 = + + refstrat = BEAST.DoubleNumSauterQstrat(3,3,0,4,30,30) + + for op in ops + Sref = assemble(op, Xt, Xs; quadstrat=refstrat) + ref = Sref[1, 1] + + n = 25 + + #Sgl = assemble(op, Xt, Xs; quadstrat=BEAST.DoubleNumQStrat(n, n+1)) + Sss = assemble(op, Xt, Xs; quadstrat=BEAST.DoubleNumSauterQstrat(3,3,0,4,n,n)) + + #gl = Sgl[1, 1] + ss = Sss[1, 1] + + #glrel = norm(gl - ref) / norm(ref) + ssrel = norm(ss - ref) / norm(ref) + + @test ssrel < 1e-14 + end + + ## Test accuracy of vertex integrations + + T = Float64 + Γto = meshsegment(T(1.0), T(1.0)) + Γt = Γto + Γs = translate(Γto, SVector(1.0, 0.0)) + + Γs = Mesh([SVector(0.0, 0.0), SVector(0.0, 1.0)], [SVector(1, 2)]) + Xt = lagrangecxd0(Γt) + Xs = lagrangecxd0(Γs) + + λ = 10 + k = 2π/λ + + ops = [ + Helmholtz2D.singlelayer(; wavenumber=k) + Helmholtz2D.doublelayer(; wavenumber=k) + Helmholtz2D.doublelayer_transposed(; wavenumber=k) + ] + 𝒮 = + + refstrat = BEAST.DoubleNumSauterQstrat(3,3,0,4,30,30) + + for op in ops + Sref = assemble(op, Xt, Xs; quadstrat=refstrat) + ref = Sref[1, 1] + + n = 25 + + #Sgl = assemble(op, Xt, Xs; quadstrat=BEAST.DoubleNumQStrat(n, n+1)) + Sss = assemble(op, Xt, Xs; quadstrat=BEAST.DoubleNumSauterQstrat(3,3,0,4,n,n)) + + #gl = Sgl[1, 1] + ss = Sss[1, 1] + + #glrel = norm(gl - ref) / norm(ref) + ssrel = norm(ss - ref) / norm(ref) + + @test ssrel < 1e-14 + end +end \ No newline at end of file diff --git a/test/test_specials.jl b/test/test_specials.jl index 7f9637b3..63bc1d0d 100644 --- a/test/test_specials.jl +++ b/test/test_specials.jl @@ -1,15 +1,17 @@ using BEAST -width, delay = 4.0, 6.0 -f = BEAST.Gaussian(width=width, delay=delay) -g = BEAST.integrate(f) +for T in [Float32, Float64] + width, delay = T(4.0), T(6.0) + f = BEAST.Gaussian(width=width, delay=delay) + g = BEAST.integrate(f) -step = width/150 -x = range(delay-2*width, stop=delay+2*width, step=step) -# xc = 0.5*(x[1:end-1] + x[2:end]) + step = width/150 + x = range(delay-2*width, stop=delay+2*width, step=step) + # xc = 0.5*(x[1:end-1] + x[2:end]) -y1 = g.(x) -y2 = cumsum(f.(x))*step + y1 = g.(x) + y2 = cumsum(f.(x))*step -using LinearAlgebra -@assert norm(y1-y2, Inf) < 1e-2 + using LinearAlgebra + @assert norm(y1-y2, Inf) < T(1e-2) +end \ No newline at end of file diff --git a/test/test_ss_nested_meshes.jl b/test/test_ss_nested_meshes.jl index 8d1558b7..b23f725d 100644 --- a/test/test_ss_nested_meshes.jl +++ b/test/test_ss_nested_meshes.jl @@ -2,7 +2,7 @@ using Test # Write a test that compares the momintegrals_nested approach with applying the # Wilton singularity extraction to a non-conforming mesh. -# Conclusion: WiltonSEStrategy should not be applied to deal with overlapping +# Conclusion: WiltonSERule should not be applied to deal with overlapping # charts in the case of geometrically non-conforming meshes; the result of the # inner integral is not analytic, crippling the accuracy of the outer integration, # which is done with triangular Gauss-Legendre points. @@ -17,11 +17,25 @@ d = point(1/3,-1/3,0) # Relative weights chosen to make the two terms equally important 𝒜 = Maxwell3D.singlelayer(wavenumber=0.0, alpha=22.0, beta=1.0) -𝒳 = BEAST.RTRefSpace{Float64}() + +coarse = Mesh([o,x̂,ŷ], [CompScienceMeshes.index(1,2,3)]) +fine = barycentric_refinement(coarse) + +X = raviartthomas(coarse, skeleton(coarse,1)) +Y = raviartthomas(fine, skeleton(fine,1)) + +𝒳 = refspace(X) T = scalartype(𝒜, 𝒳, 𝒳) +@test 𝒳 == refspace(Y) +@test CompScienceMeshes.parent(geometry(Y)) == geometry(X) -test_chart = simplex(e,x̂,c) -trial_chart = simplex(o,x̂,ŷ) +p = 6 +trial_chart = chart(coarse,1) +test_chart = chart(fine,p) +# @show test_chart.vertices + +# test_chart_old = simplex(e,x̂,c) +# trial_chart_old = simplex(o,x̂,ŷ) test_quadpoints = BEAST.quadpoints(𝒳, [test_chart], (12,))[1,1] trial_quadpoints = BEAST.quadpoints(𝒳, [trial_chart], (13,))[1,1] @@ -37,13 +51,22 @@ function BEAST.quaddata(op::BEAST.MaxwellOperator3D, return (tpoints=tqd, bpoints=bqd, gausslegendre=leg) end -sauterschwab = BEAST.SauterSchwabQuadrature.CommonFace(nothing) -out_ss = zeros(T, numfunctions(𝒳), numfunctions(𝒳)) -BEAST.momintegrals_nested!(𝒜,𝒳,𝒳,test_chart,trial_chart,out_ss,sauterschwab) +qs_strat = BEAST.DoubleNumWiltonSauterQStrat(1,1,6,7,10,10,10,10) + +# sauterschwab = BEAST.SauterSchwabQuadrature.CommonFace(BEAST._legendre(10,0.0,1.0)) +out_ss = zeros(T, numfunctions(𝒳, domain(test_chart)), numfunctions(𝒳, domain(trial_chart))) +BEAST.momintegrals!(out_ss, 𝒜, + Y, p, test_chart, + X, 1, trial_chart, + BEAST.TestRefinesTrialQRule(qs_strat)) + -wiltonsingext = BEAST.WiltonSEStrategy(test_quadpoints, BEAST.DoubleQuadStrategy(test_quadpoints, trial_quadpoints)) -out_dw = zeros(T, numfunctions(𝒳), numfunctions(𝒳)) -BEAST.momintegrals_nested!(𝒜,𝒳,𝒳,test_chart,trial_chart,out_dw,wiltonsingext) +wiltonsingext = BEAST.WiltonSERule(test_quadpoints, BEAST.DoubleQuadRule(test_quadpoints, trial_quadpoints)) +out_dw = zeros(T, numfunctions(𝒳, domain(test_chart)), numfunctions(𝒳, domain(trial_chart))) +BEAST.momintegrals!(out_dw, 𝒜, + Y, p, test_chart, + X, 1, trial_chart, + wiltonsingext) @show norm(out_ss-out_dw) / norm(out_dw) @@ -54,11 +77,11 @@ test_quadpoints = BEAST.quadpoints(𝒳, [test_chart], (12,))[1,1] trial_quadpoints = BEAST.quadpoints(𝒳, [trial_chart], (13,))[1,1] sauterschwab = BEAST.SauterSchwabQuadrature.CommonFace(BEAST._legendre(10,0.0,1.0)) -out_ss1 = zeros(T, numfunctions(𝒳), numfunctions(𝒳)) +out_ss1 = zeros(T, numfunctions(𝒳, domain(test_chart)), numfunctions(𝒳, domain(trial_chart))) BEAST.momintegrals!(𝒜,𝒳,𝒳,test_chart,trial_chart,out_ss1,sauterschwab) -wiltonsingext = BEAST.WiltonSEStrategy(test_quadpoints, BEAST.DoubleQuadStrategy(test_quadpoints, trial_quadpoints)) -out_dw1 = zeros(T, numfunctions(𝒳), numfunctions(𝒳)) +wiltonsingext = BEAST.WiltonSERule(test_quadpoints, BEAST.DoubleQuadRule(test_quadpoints, trial_quadpoints)) +out_dw1 = zeros(T, numfunctions(𝒳, domain(test_chart)), numfunctions(𝒳, domain(trial_chart))) BEAST.momintegrals!(𝒜,𝒳,𝒳,test_chart,trial_chart,out_dw1,wiltonsingext) @test out_ss1 ≈ out_dw1 rtol=3e-6 @@ -71,11 +94,11 @@ test_quadpoints = BEAST.quadpoints(𝒳, [test_chart], (12,))[1,1] trial_quadpoints = BEAST.quadpoints(𝒳, [trial_chart], (13,))[1,1] sauterschwab = BEAST.SauterSchwabQuadrature.CommonEdge(BEAST._legendre(10,0.0,1.0)) -out_ss2 = zeros(T, numfunctions(𝒳), numfunctions(𝒳)) +out_ss2 = zeros(T, numfunctions(𝒳, domain(test_chart)), numfunctions(𝒳, domain(trial_chart))) BEAST.momintegrals!(𝒜,𝒳,𝒳,test_chart,trial_chart,out_ss2,sauterschwab) -wiltonsingext = BEAST.WiltonSEStrategy(test_quadpoints, BEAST.DoubleQuadStrategy(test_quadpoints, trial_quadpoints)) -out_dw2 = zeros(T, numfunctions(𝒳), numfunctions(𝒳)) +wiltonsingext = BEAST.WiltonSERule(test_quadpoints, BEAST.DoubleQuadRule(test_quadpoints, trial_quadpoints)) +out_dw2 = zeros(T, numfunctions(𝒳, domain(test_chart)), numfunctions(𝒳, domain(trial_chart))) BEAST.momintegrals!(𝒜,𝒳,𝒳,test_chart,trial_chart,out_dw2,wiltonsingext) @test out_ss2 ≈ out_dw2 rtol=4e-6 @@ -88,11 +111,11 @@ test_quadpoints = BEAST.quadpoints(𝒳, [test_chart], (12,))[1,1] trial_quadpoints = BEAST.quadpoints(𝒳, [trial_chart], (13,))[1,1] sauterschwab = BEAST.SauterSchwabQuadrature.CommonEdge(BEAST._legendre(10,0.0,1.0)) -out_ss3 = zeros(T, numfunctions(𝒳), numfunctions(𝒳)) +out_ss3 = zeros(T, numfunctions(𝒳, domain(test_chart)), numfunctions(𝒳, domain(trial_chart))) BEAST.momintegrals!(𝒜,𝒳,𝒳,test_chart,trial_chart,out_ss3,sauterschwab) -wiltonsingext = BEAST.WiltonSEStrategy(test_quadpoints, BEAST.DoubleQuadStrategy(test_quadpoints, trial_quadpoints)) -out_dw3 = zeros(T, numfunctions(𝒳), numfunctions(𝒳)) +wiltonsingext = BEAST.WiltonSERule(test_quadpoints, BEAST.DoubleQuadRule(test_quadpoints, trial_quadpoints)) +out_dw3 = zeros(T, numfunctions(𝒳, domain(test_chart)), numfunctions(𝒳, domain(trial_chart))) BEAST.momintegrals!(𝒜,𝒳,𝒳,test_chart,trial_chart,out_dw3,wiltonsingext) @test out_ss3 ≈ out_dw3 rtol=3e-6 @@ -106,11 +129,11 @@ test_quadpoints = BEAST.quadpoints(𝒳, [test_chart], (12,))[1,1] trial_quadpoints = BEAST.quadpoints(𝒳, [trial_chart], (13,))[1,1] sauterschwab = BEAST.SauterSchwabQuadrature.CommonVertex(BEAST._legendre(10,0.0,1.0)) -out_ss4 = zeros(T, numfunctions(𝒳), numfunctions(𝒳)) +out_ss4 = zeros(T, numfunctions(𝒳, domain(test_chart)), numfunctions(𝒳, domain(trial_chart))) BEAST.momintegrals!(𝒜,𝒳,𝒳,test_chart,trial_chart,out_ss4,sauterschwab) -wiltonsingext = BEAST.WiltonSEStrategy(test_quadpoints, BEAST.DoubleQuadStrategy(test_quadpoints, trial_quadpoints)) -out_dw4 = zeros(T, numfunctions(𝒳), numfunctions(𝒳)) +wiltonsingext = BEAST.WiltonSERule(test_quadpoints, BEAST.DoubleQuadRule(test_quadpoints, trial_quadpoints)) +out_dw4 = zeros(T, numfunctions(𝒳, domain(test_chart)), numfunctions(𝒳, domain(trial_chart))) BEAST.momintegrals!(𝒜,𝒳,𝒳,test_chart,trial_chart,out_dw4,wiltonsingext) @test out_ss4 ≈ out_dw4 rtol=2e-9 @@ -123,11 +146,11 @@ test_quadpoints = BEAST.quadpoints(𝒳, [test_chart], (12,))[1,1] trial_quadpoints = BEAST.quadpoints(𝒳, [trial_chart], (13,))[1,1] sauterschwab = BEAST.SauterSchwabQuadrature.CommonVertex(BEAST._legendre(10,0.0,1.0)) -out_ss5 = zeros(T, numfunctions(𝒳), numfunctions(𝒳)) +out_ss5 = zeros(T, numfunctions(𝒳, domain(test_chart)), numfunctions(𝒳, domain(trial_chart))) BEAST.momintegrals!(𝒜,𝒳,𝒳,test_chart,trial_chart,out_ss5,sauterschwab) -wiltonsingext = BEAST.WiltonSEStrategy(test_quadpoints, BEAST.DoubleQuadStrategy(test_quadpoints, trial_quadpoints)) -out_dw5 = zeros(T, numfunctions(𝒳), numfunctions(𝒳)) +wiltonsingext = BEAST.WiltonSERule(test_quadpoints, BEAST.DoubleQuadRule(test_quadpoints, trial_quadpoints)) +out_dw5 = zeros(T, numfunctions(𝒳, domain(test_chart)), numfunctions(𝒳, domain(trial_chart))) BEAST.momintegrals!(𝒜,𝒳,𝒳,test_chart,trial_chart,out_dw5,wiltonsingext) @test out_ss4 ≈ out_dw4 rtol=3e-8 @@ -140,11 +163,11 @@ test_quadpoints = BEAST.quadpoints(𝒳, [test_chart], (12,))[1,1] trial_quadpoints = BEAST.quadpoints(𝒳, [trial_chart], (13,))[1,1] sauterschwab = BEAST.SauterSchwabQuadrature.CommonVertex(BEAST._legendre(10,0.0,1.0)) -out_ss6 = zeros(T, numfunctions(𝒳), numfunctions(𝒳)) +out_ss6 = zeros(T, numfunctions(𝒳, domain(test_chart)), numfunctions(𝒳, domain(trial_chart))) BEAST.momintegrals!(𝒜,𝒳,𝒳,test_chart,trial_chart,out_ss6,sauterschwab) -wiltonsingext = BEAST.WiltonSEStrategy(test_quadpoints, BEAST.DoubleQuadStrategy(test_quadpoints, trial_quadpoints)) -out_dw6 = zeros(T, numfunctions(𝒳), numfunctions(𝒳)) +wiltonsingext = BEAST.WiltonSERule(test_quadpoints, BEAST.DoubleQuadRule(test_quadpoints, trial_quadpoints)) +out_dw6 = zeros(T, numfunctions(𝒳, domain(test_chart)), numfunctions(𝒳, domain(trial_chart))) BEAST.momintegrals!(𝒜,𝒳,𝒳,test_chart,trial_chart,out_dw6,wiltonsingext) @test out_ss4 ≈ out_dw4 rtol=6e-8 diff --git a/test/test_subd_basis.jl b/test/test_subd_basis.jl index ef54ffa2..33140011 100644 --- a/test/test_subd_basis.jl +++ b/test/test_subd_basis.jl @@ -18,20 +18,21 @@ function CompScienceMeshes.chart(Smesh::CompScienceMeshes.subdMesh,E) return chart = CompScienceMeshes.subd_chart(E,N,nodes,verticecoords) end - -G = readmesh(joinpath(dirname(@__FILE__),"assets","sphere872.in")) +for T in [Float32, Float64] +G = readmesh(joinpath(dirname(@__FILE__),"assets","sphere872.in"),T=T) # G0 = readmesh(fn) # G0 = meshsphere(1.0, 0.5) #G = readmesh("/Users/Benjamin/Documents/sphere.in") # G = Loop_subdivision(G0) -X = subdsurface(G) +local X = subdsurface(G) #els, ad = BEAST.assemblydata(X) identityop = Identity() -singlelayer = Helmholtz3D.singlelayer(gamma=1.0) +singlelayer = Helmholtz3D.singlelayer(gamma=T(1.0)) I = assemble(identityop, X, X) #S = assemble(singlelayer, X, X) ncd = cond(Matrix(I)) -@test ncd ≈ 64.50401358713235 rtol=1e-8 +@test ncd ≈ T(64.50401358713235) rtol=sqrt(eps(T)) +end \ No newline at end of file diff --git a/test/test_td_tensoroperator.jl b/test/test_td_tensoroperator.jl new file mode 100644 index 00000000..3bddb66d --- /dev/null +++ b/test/test_td_tensoroperator.jl @@ -0,0 +1,35 @@ +using CompScienceMeshes +using BEAST + +using Test + +@testset "Assembly of TensorOperator wrt SpaceTimeBasis" begin + m = Mesh( + [ + point(0,0,0), + point(1,0,0), + point(1,1,0), + point(0,1,0) + ], + [ + index(1,2,3), + index(1,3,4) + ], + ) + + X = raviartthomas(m) + δ = timebasisdelta(0.01234, 10) + T = timebasisshiftedlagrange(0.1234, 10, 1) + U = X ⊗ T + V = X ⊗ δ + + Id = BEAST.Identity() + # Nx = BEAST.NCross() + op = Id ⊗ Id + Z = assemble(op, V, U) + A = BEAST.ConvolutionOperators.ConvOpAsArray(Z) + @test size(A) == (1,1,2) + for i in 2:10 + @test A[1,1,i] ≈ 0 atol=sqrt(eps(eltype(A))) + end +end \ No newline at end of file diff --git a/test/test_tdassembly.jl b/test/test_tdassembly.jl index ca6e915b..00894b9f 100644 --- a/test/test_tdassembly.jl +++ b/test/test_tdassembly.jl @@ -31,8 +31,8 @@ q = refspace(Q) # Overwrite the default quadrature strategy for # this operator/space combination -τ1 = chart(S1, first(Iterators.drop(cells(S1),0))) -τ2 = chart(S2, first(Iterators.drop(cells(S2),0))) +τ1 = chart(S1, first(Iterators.drop(S1,0))) +τ2 = chart(S2, first(Iterators.drop(S2,0))) ι = BEAST.ring(first(BEAST.rings(τ1, τ2, ΔR)), ΔR) struct DoubleQuadTimeDomainRule end @@ -58,24 +58,25 @@ function momintegrals!(z, op::typeof(G), TR = [(-R)^d for d in 0:maxdegree] dxdy = wpv_τ.weight * wpv_σ.weight - for i in 1 : numfunctions(g) - for j in 1 : numfunctions(f) + for i in 1 : numfunctions(g, domain(τ)) + for j in 1 : numfunctions(f, domain(σ)) for k in 1 : numfunctions(T) z[i,j,k] += dxdy * gx[i] * fy[j] * TR[k] / (4π*R) end end end end end end # Compare results for a single monomial -z1 = zeros(numfunctions(x1), numfunctions(x2), numfunctions(q)) +z1 = zeros(numfunctions(x1, domain(τ1)), numfunctions(x2, domain(τ2)), numfunctions(q)) for r in BEAST.rings(τ1, τ2, ΔR) - ι = BEAST.ring(r, ΔR) + local ι = BEAST.ring(r, ΔR) momintegrals!(z1, G, x1, x2, q, τ1, τ2, ι, DoubleQuadTimeDomainRule()) end -qd = quaddata(G, x1, x2, q, [τ1], [τ2], nothing) -z2 = zeros(numfunctions(x1), numfunctions(x2), numfunctions(q)) +qs = BEAST.defaultquadstrat(G,X1,X2) +qd = quaddata(G, x1, x2, q, [τ1], [τ2], nothing, qs) +z2 = zeros(numfunctions(x1, domain(τ1)), numfunctions(x2, domain(τ2)), numfunctions(q)) for r in BEAST.rings(τ1, τ2, ΔR) - ι = BEAST.ring(r, ΔR) - quad_rule = quadrule(G, x1, x2, q, 1, τ1, 1, τ2, r, ι, qd) + local ι = BEAST.ring(r, ΔR) + quad_rule = quadrule(G, x1, x2, q, 1, τ1, 1, τ2, r, ι, qd, qs) BEAST.momintegrals!(z2, G, x1, x2, q, τ1, τ2, ι, quad_rule) end diff --git a/test/test_tdefie_irk.jl b/test/test_tdefie_irk.jl new file mode 100644 index 00000000..bfc5e809 --- /dev/null +++ b/test/test_tdefie_irk.jl @@ -0,0 +1,53 @@ +using CompScienceMeshes +using BEAST +using StaticArrays +using LinearAlgebra +using Test + +# Γ = meshsphere(radius=1.0, h=0.45) +Γ = readmesh(joinpath(dirname(pathof(BEAST)),"../test/assets/sphere45.in")) +X = raviartthomas(Γ) +sol = 1.0 # Speed of light (for sake of simplicity, set to one) + +# Note that certain choices of Δt, Nt, as well as the excitation +# can lead to late-time instabilities +Δt, Nt = 10.0, 200 + +(A, b, c) = butcher_tableau_radau_2stages() +T = StagedTimeStep(Δt, Nt, c, A, b, 5, 1.0001) +V = X ⊗ T + +duration = 2 * 20 * Δt +delay = 1.5 * duration +amplitude = 1.0 +gaussian = creategaussian(duration, delay, amplitude) + +direction, polarisation = ẑ , x̂ +E = planewave(polarisation, direction, derive(gaussian), sol) + +T = TDMaxwell3D.singlelayer(speedoflight=sol, numdiffs=1) + +@hilbertspace j +@hilbertspace j′ +tdefie_irk = @discretise T[j′,j] == -1E[j′] j∈V j′∈V +xefie_irk = solve(tdefie_irk) + +# Set up Space-Time-Galerkin MOT for comparison + +T = timebasisshiftedlagrange(Δt, Nt, 3) +U = timebasisdelta(Δt, Nt) + +V = X ⊗ T +W = X ⊗ U + +SL = TDMaxwell3D.singlelayer(speedoflight=1.0, numdiffs=1) + +tdefie = @discretise SL[j′,j] == -1.0E[j′] j∈V j′∈W +xefie = BEAST.motsolve(tdefie) + +xefie_irk = xefie_irk[1:size(c,1):end,:] + +diff_MOT_max = maximum((norm.(xefie_irk[:,1:end] - xefie[:,1:end])) ./ maximum(xefie[:,1:end])) + +# @test diff_MOT_max ≈ 0.137252874891817 atol=1e-8 +@test diff_MOT_max < 0.16 \ No newline at end of file diff --git a/test/test_tdhhdbl.jl b/test/test_tdhhdbl.jl index 2103d4e1..864fd295 100644 --- a/test/test_tdhhdbl.jl +++ b/test/test_tdhhdbl.jl @@ -13,11 +13,13 @@ c = 1.0 K = BEAST.HH3DDoubleLayerTDBIO(speed_of_light=c) X = lagrangecxd0(G) -@test numfunctions(refspace(X)) == 1 +@test numfunctions(refspace(X), domain(chart(G, first(G)))) == 1 Y = duallagrangec0d1(G) @test numfunctions(Y) == 1 -@test numfunctions(refspace(Y)) == 3 + +Gref = geometry(Y) +@test numfunctions(refspace(Y), domain(chart(Gref, first(Gref)))) == 3 # X = duallagrangecxd0(G, boundary(G)) # Y = lagrangec0d1(G, dirichlet=false) @@ -33,7 +35,11 @@ W = Y ⊗ T fr2, store2 = BEAST.allocatestorage(K, V, V, Val{:densestorage}, BEAST.LongDelays{:ignore}) BEAST.assemble!(K, W, V, store2) Z = fr2() -@test all(==(0), Z[:,:,1]) + +T = scalartype(K,V,V) +Z1 = zeros(T, size(Z)[1:2]) +BEAST.ConvolutionOperators.timeslice!(Z1, Z, 1) +@test all(==(0), Z1) # import WiltonInts84 # trial_element = chart(G, first(cells(G))) diff --git a/test/test_tdmwdbl.jl b/test/test_tdmwdbl.jl index 2c65935e..60d6c453 100644 --- a/test/test_tdmwdbl.jl +++ b/test/test_tdmwdbl.jl @@ -29,7 +29,11 @@ fr1, store1 = BEAST.allocatestorage(K, W, V, BEAST.assemble!(K, W, V, store1) Z = fr1() -@test all(==(0), Z[:,:,1]) + +T = scalartype(K,W,V) +Z1 = zeros(T, size(Z)[1:2]) +BEAST.ConvolutionOperators.timeslice!(Z1, Z, 1) +@test all(==(0), Z1) W = X⊗δ V = Y⊗T2 @@ -38,10 +42,15 @@ K = TDMaxwell3D.doublelayer(speedoflight=1.0, numdiffs=1) fr2, store2 = BEAST.allocatestorage(K, W, V, Val{:densestorage}, BEAST.LongDelays{:ignore}) BEAST.assemble!(K, W, V, store2) Z2 = fr2() -@test all(==(0), Z2[:,:,1]) + +T = scalartype(K,W,V) +Z21 = zeros(T, size(Z)[1:2]) +BEAST.ConvolutionOperators.timeslice!(Z21, Z2, 1) +@test all(==(0), Z21) +# @test all(==(0), Z2[:,:,1]) γ = geometry(Y) -ch1 = chart(G, first(cells(G))) +ch1 = chart(G, first(G)) qps = quadpoints(ch1, 3) x = qps[1][1] @@ -51,13 +60,13 @@ test_els = BEAST.elements(G) trial_els = BEAST.elements(γ) time_els = [(0.0, 20.0)] -qdt = BEAST.quaddata(K, refspace(X), refspace(Y), refspace(T2), test_els, trial_els, nothing) -qrl = BEAST.quadrule(K, - refspace(X), refspace(Y), refspace(T2), - 1, test_els[1], 1, trial_els[1], 1, time_els[1], qdt) +qs = BEAST.defaultquadstrat(K,X,Y) +qdt = BEAST.quaddata(K, refspace(X), refspace(Y), refspace(T2), test_els, trial_els, nothing, qs) +qrl = BEAST.quadrule(K, refspace(X), refspace(Y), refspace(T2), + 1, test_els[1], 1, trial_els[1], 1, time_els[1], qdt, qs) z = zeros(Float64, 3, 3, 10) BEAST.innerintegrals!(z, K, x, refspace(X), refspace(Y), refspace(T2), test_els[1], trial_els[1], (0.0, 20.0), qrl, w) -@test_broken !any(isnan.(z)) +@test !any(isnan.(z)) verts = trial_els[1].vertices import WiltonInts84 @@ -76,4 +85,4 @@ h = 0.0 using StaticArrays m = SVector(-0.7071067811865474, 0.7071067811865478, -0.0) iG, vG = WiltonInts84.segintsg(a, b, p, h, m, Val{2}) -@test_broken !any(isnan.(iG)) +@test !any(isnan.(iG)) diff --git a/test/test_tdop_scaling.jl b/test/test_tdop_scaling.jl index becab824..6ced9036 100644 --- a/test/test_tdop_scaling.jl +++ b/test/test_tdop_scaling.jl @@ -39,7 +39,10 @@ t = MWSingleLayerTDIO(sol,-1/sol,-sol,2,0) Z3 = assemble(t, W, V) m = n = 1 -@test findall(Z1[m,n,:] .!= 0) == findall(Z3[m,n,:] .!= 0) +M1 = AbstractArray(Z1) +M3 = AbstractArray(Z3) -I = findall(Z1[m,n,:] .!= 0) -@test all(sol*Z1[m,n,I] .≈ Z3[m,n,I]) +@test findall(M1[m,n,:] .!= 0) == findall(M3[m,n,:] .!= 0) + +I = findall(M1[m,n,:] .!= 0) +@test all(sol*M1[m,n,I] .≈ M3[m,n,I]) diff --git a/test/test_tensor_identityop.jl b/test/test_tensor_identityop.jl new file mode 100644 index 00000000..4ccc64f2 --- /dev/null +++ b/test/test_tensor_identityop.jl @@ -0,0 +1,16 @@ +using Test +using BEAST +using CompScienceMeshes + +Γ = meshrectangle(1.0, 1.0, 0.5, 3) +X = raviartthomas(Γ) +fns = numfunctions(X) +id = Identity()⊗Identity() + +Δt, Nt = 1.01, 10 +δ = timebasisdelta(Δt,Nt) +h = timebasisc0d1(Δt,Nt) + +idop = (id, X⊗δ, X⊗h) +G = assemble(idop...) +@test size(G) == (fns, fns, Nt) \ No newline at end of file diff --git a/test/test_trace.jl b/test/test_trace.jl index 43d65b11..f963d78c 100644 --- a/test/test_trace.jl +++ b/test/test_trace.jl @@ -21,7 +21,7 @@ for i in eachindex(rwg.fns) length(nt.fns[i]) == 1 || continue global n += 1 c = nt.fns[i][1].cellid - edge = chart(Σ, cells(Σ)[c]) + edge = chart(Σ, c) @test on_bnd(edge) @test nt.fns[i][1].refid == 1 @test nt.fns[i][1].coeff == 2.0 diff --git a/test/test_ttrace.jl b/test/test_ttrace.jl index cc97e7a1..067f27ea 100644 --- a/test/test_ttrace.jl +++ b/test/test_ttrace.jl @@ -4,123 +4,125 @@ using LinearAlgebra using Test using StaticArrays -o, x, y, z = euclidianbasis(3) +for T in [Float32, Float64] + local o, x, y, z = euclidianbasis(3, T) -p1 = 2x -p2 = y -p3 = 3z -p4 = o -tet = simplex(p1, p2, p3, p4) + p1 = 2x + p2 = y + p3 = 3z + p4 = o + tet = simplex(p1, p2, p3, p4) -q = abs(CompScienceMeshes.relorientation([1,2,3],[1,2,3,4])) -tri = simplex(p1, p2, p3) -n = normal(tri) + q = abs(CompScienceMeshes.relorientation([1,2,3],[1,2,3,4])) + tri = simplex(p1, p2, p3) + local n = normal(tri) -i = 3 -j = 3 -edg = simplex(p1, p2) + i = 3 + local j = 3 + local edg = simplex(p1, p2) -T = Float64 -x = BEAST.NDLCDRefSpace{T}() -y = BEAST.NDRefSpace{T}() -p = neighborhood(tet, [0.5, 0.5, 0.0]) -r = neighborhood(tri, [0.5, 0.5]) + x = BEAST.NDLCDRefSpace{T}() + y = BEAST.NDRefSpace{T}() -@test carttobary(edg, cartesian(p)) ≈ [0.5] -@test carttobary(edg, cartesian(r)) ≈ [0.5] + local p = neighborhood(tet, T.([0.5, 0.5, 0.0])) + r = neighborhood(tri, T.([0.5, 0.5])) -xp = x(p)[j].value -yr = y(r)[i].value + @test carttobary(edg, cartesian(p)) ≈ T.([0.5]) + @test carttobary(edg, cartesian(r)) ≈ T.([0.5]) -a, b = extrema((n × xp) ./ yr) -@test a ≈ b + xp = x(p)[j].value + yr = y(r)[i].value -tgt = p2 - p1 -a2 = dot(n × xp, tgt) / dot(yr, tgt) -@test a ≈ a2 + local a, b = extrema((n × xp) ./ yr) + @test a ≈ b -z = a * yr -@test z ≈ n × xp + tgt = p2 - p1 + a2 = dot(n × xp, tgt) / dot(yr, tgt) + @test a ≈ a2 -volume(simplex(p1,p2,p4)) -volume(simplex(p1,p2)) + z = a * yr + @test z ≈ n × xp -Q = BEAST.ttrace(x, tet, q, tri) + volume(simplex(p1,p2,p4)) + volume(simplex(p1,p2)) -p = neighborhood(tet, [1/3, 1/3, 1/3]) -r = neighborhood(tri, [1/3, 1/3]) -@test cartesian(p) ≈ cartesian(r) + Q = BEAST.ttrace(x, tet, q, tri) -xp = n × x(p)[j].value -yr = y(r)[i].value -@test xp ≈ yr * Q[i,j] + p = neighborhood(tet, T.([1/3, 1/3, 1/3])) + r = neighborhood(tri, T.([1/3, 1/3])) + @test cartesian(p) ≈ cartesian(r) + xp = n × x(p)[j].value + yr = y(r)[i].value + @test xp ≈ yr * Q[i,j] -x = BEAST.NDLCCRefSpace{Float64}() -y = BEAST.RTRefSpace{Float64}() -for q in 1:4 - q = 3 - fc = BEAST.faces(tet)[q] - Q = BEAST.ttrace(x, tet, q, fc) + + x = BEAST.NDLCCRefSpace{T}() + y = BEAST.RTRefSpace{T}() + for q in 1:4 + q = 3 + fc = BEAST.faces(tet)[q] + Q = BEAST.ttrace(x, tet, q, fc) + + nbdi = CompScienceMeshes.center(fc) + nbdj = neighborhood(tet, carttobary(tet, cartesian(nbdi))) + + xvals = x(nbdj) + yvals = y(nbdi) + + for j in 1:6 + trc = sum(Q[i,j]*yvals[i].value for i in 1:3) + @test isapprox(trc, normal(fc) × xvals[j].value, atol=1e-4) + end + end + + # test the case where intrinsic and extrinsic orientations differ + o, x, y, z = euclidianbasis(3, T) + fc = simplex(z,o,y) + x = BEAST.NDLCCRefSpace{T}() + y = BEAST.RTRefSpace{T}() + Q = BEAST.ttrace(x, tet, 3000, fc) nbdi = CompScienceMeshes.center(fc) nbdj = neighborhood(tet, carttobary(tet, cartesian(nbdi))) + @test cartesian(nbdi) ≈ cartesian(nbdj) + xvals = x(nbdj) yvals = y(nbdi) for j in 1:6 trc = sum(Q[i,j]*yvals[i].value for i in 1:3) - @test isapprox(trc, normal(fc) × xvals[j].value, atol=1e-4) + @test isapprox(trc, -normal(fc) × xvals[j].value, atol=1e-4) end -end - -# test the case where intrinsic and extrinsic orientations differ -o, x, y, z = euclidianbasis(3) -fc = simplex(z,o,y) -x = BEAST.NDLCCRefSpace{Float64}() -y = BEAST.RTRefSpace{Float64}() -Q = BEAST.ttrace(x, tet, 3000, fc) - -nbdi = CompScienceMeshes.center(fc) -nbdj = neighborhood(tet, carttobary(tet, cartesian(nbdi))) - -@test cartesian(nbdi) ≈ cartesian(nbdj) - -xvals = x(nbdj) -yvals = y(nbdi) - -for j in 1:6 - trc = sum(Q[i,j]*yvals[i].value for i in 1:3) - @test isapprox(trc, -normal(fc) × xvals[j].value, atol=1e-4) -end -o, x, y, z = euclidianbasis(3) -m = Mesh([x,y,z,o], [@SVector[1,2,3,4]]) + o, x, y, z = euclidianbasis(3) + local m = Mesh([x,y,z,o], [@SVector[1,2,3,4]]) -m1 = skeleton(m,1) -X = BEAST.nedelecc3d(m, m1) -@test numfunctions(X) == 6 + m1 = skeleton(m,1) + local X = BEAST.nedelecc3d(m, m1) + @test numfunctions(X) == 6 -# m2 = skeleton(m,2) -m2 = boundary(m) -Y = BEAST.ttrace(X,m2) -@test numfunctions(Y) == 6 + # m2 = skeleton(m,2) + m2 = boundary(m) + local Y = BEAST.ttrace(X,m2) + @test numfunctions(Y) == 6 -pa = Y.fns[1][1].cellid -pb = Y.fns[1][2].cellid + pa = Y.fns[1][1].cellid + pb = Y.fns[1][2].cellid -tria = chart(m2, cells(m2)[pa]) -trib = chart(m2, cells(m2)[pb]) + tria = chart(m2, pa) + trib = chart(m2, pb) -ra = Y.fns[1][1].refid -rb = Y.fns[1][2].refid + ra = Y.fns[1][1].refid + rb = Y.fns[1][2].refid -ctra = cartesian(CompScienceMeshes.center(CompScienceMeshes.edges(tria)[ra])) -ctrb = cartesian(CompScienceMeshes.center(CompScienceMeshes.edges(trib)[rb])) -@test ctra ≈ ctrb -# tri1 = chart(m, cells(m)[Y.fns[1][1].cellid]) + ctra = cartesian(CompScienceMeshes.center(CompScienceMeshes.edges(tria)[ra])) + ctrb = cartesian(CompScienceMeshes.center(CompScienceMeshes.edges(trib)[rb])) + @test ctra ≈ ctrb + # tri1 = chart(m, cells(m)[Y.fns[1][1].cellid]) -# ctr1 = cartesian(center(CompScienceMeshes.edges(tri)[])) + # ctr1 = cartesian(center(CompScienceMeshes.edges(tri)[])) +end \ No newline at end of file diff --git a/test/test_variational.jl b/test/test_variational.jl new file mode 100644 index 00000000..1368b787 --- /dev/null +++ b/test/test_variational.jl @@ -0,0 +1,63 @@ +using BEAST +using CompScienceMeshes +using Test + +@hilbertspace j[1:3] +@hilbertspace k[1:3] + +n = BEAST.n + +SL = BEAST.Maxwell3D.singlelayer(wavenumber=1.0) +EF = BEAST.Maxwell3D.planewave(polarization=point(1,0,0), direction=point(0,0,1), wavenumber=1.0) +ef = (n × EF) × n + +# bf = SL[j,k] +bf = sum(SL[j[i],k[i]] for i in 1:3) +# lf = ef[j] +lf = sum(ef[j[i]] for i in 1:3) + +@test length(bf.terms) == 3 +for term in bf.terms + @test term.test_id == term.trial_id +end + +mesh1 = Mesh( + [ + point(0,0,0), + point(1,0,0), + point(1,1,0), + point(0,1,0), + ], + [ + index(1,2,3), + index(1,3,4) + ] +) + +mesh2 = CompScienceMeshes.translate(mesh1, point(0,0,1)) +mesh3 = CompScienceMeshes.translate(mesh1, point(0,0,2)) + +X1 = raviartthomas(mesh1) +X2 = raviartthomas(mesh2) +X3 = raviartthomas(mesh3) + +X = X1 × X2 × X3 + +dbf = BEAST.discretise(bf, j=>X, k=>X) +dlf = BEAST.discretise(lf, j=>X) + +space_mappings = (j=>X, k=>X,) +# for sm in space_mappings +# @show sm +# end + +M = assemble(dbf) +A = Matrix(M) + +@test A[1,2] == A[1,3] == A[2,1] == A[2,3] == A[3,1] == A[3,2] == 0 +@test A[1,1] ≈ A[2,2] ≈ A[3,3] + +# N = assemble(SL, X, X) +N = assemble(bf, X, X) +B = Matrix(N) +@test A == B \ No newline at end of file diff --git a/test/test_vie.jl b/test/test_vie.jl new file mode 100644 index 00000000..9c5dc414 --- /dev/null +++ b/test/test_vie.jl @@ -0,0 +1,76 @@ +using BEAST +using CompScienceMeshes +using LinearAlgebra + +using Test + + +""" +Tests the structure of the VIE implementation (function spaces, numerical integration, traces, ...) +by using the divergence theorem: ∫∫∫_Ω ∫∫∫_Ω ... = ∫∫∫_Ω ∫∫∫_Ω ... + ∫∫_Γ ∫∫∫_Ω ... +This test does not include the Operators of the EVIE, DVIE and one operator of the LSVIE. +""" + + +@testset "Volume Integral Equations" begin + + qs = BEAST.SauterSchwab3DQStrat(3,3,3,3,3,3) + + r = 1.0 + h = 0.4 + mesh = CompScienceMeshes.tetmeshsphere(r,h) + bnd = boundary(mesh) + + function tau(x) + return 1.0 + 2.0*x[1]^2*(x[2] - 1.0) + (x[3] - 0.5)^2 - 7.0*x[1]*x[2]*x[3] + end + + #using PlotlyJS + #PlotlyJS.plot(CompScienceMeshes.wireframe(mesh)) + + ## LSVIE + X = lagrangec0d1(mesh; dirichlet = false) + strc = X -> strace(X,bnd) + + V, B = VIE.hhvolume(tau = tau, wavenumber = 1.0), VIE.hhboundary(tau = tau, wavenumber = 1.0) + Y = VIE.hhvolumegradG(tau = tau, wavenumber = 1.0) + + M_a = assemble(Y, X, X, quadstrat = qs) + M_b = assemble(B, strc(X), X, quadstrat = qs) + assemble(V, X, X, quadstrat = qs) + err = norm(M_a - M_b)/norm(M_b) + @show err + @test err < 0.005 + + + ## DVIE + X = nedelecd3d(mesh) + ntrc = X -> ntrace(X,bnd) + + TestOp1 = BEAST.DVIE_TestOp1(1.0, 1.0, tau) + TestOp2 = BEAST.DVIE_TestOp2(1.0, 1.0, tau) + TestOp3 = BEAST.DVIE_TestOp3(1.0, -1.0, tau) + + M_a = assemble(TestOp1, X, X, quadstrat = qs) + M_b = assemble(TestOp2, ntrc(X), X, quadstrat = qs) + assemble(TestOp3, X, X, quadstrat = qs) + + err = norm(M_a - M_b)/norm(M_b) + @show err + @test err < 0.02 + + + ## EVIE + X = nedelecc3d(mesh) + ttrc = X -> ttrace(X,bnd) + + TestOp1 = BEAST.EVIE_TestOp1(1.0, 1.0, tau) + TestOp2 = BEAST.EVIE_TestOp2(1.0, 1.0, tau) + TestOp3 = BEAST.EVIE_TestOp3(1.0, 1.0, tau) + + M_a = assemble(TestOp1, X, X, quadstrat = qs) + M_b = assemble(TestOp2, ttrc(X), X, quadstrat = qs) + assemble(TestOp3, X, X, quadstrat = qs) + + err = norm(M_a - M_b)/norm(M_b) + @show err + @test err < 0.01 + +end \ No newline at end of file diff --git a/test/test_wiltonints.jl b/test/test_wiltonints.jl index 04842375..1f1469b7 100644 --- a/test/test_wiltonints.jl +++ b/test/test_wiltonints.jl @@ -1,5 +1,6 @@ import BEAST; BE = BEAST; using CompScienceMeshes +using StaticArrays using Test T = Float64 @@ -323,8 +324,8 @@ end Γ = readmesh(joinpath(dirname(@__FILE__),"assets","sphere2.in")) nc = numcells(Γ) -t = chart(Γ, first(cells(Γ))) -s = chart(Γ, last(cells(Γ))) +t = chart(Γ, first(Γ)) +s = chart(Γ, nc) X = BEAST.raviartthomas(Γ) x = BEAST.refspace(X) @@ -332,7 +333,7 @@ x = BEAST.refspace(X) κ = 1.0 op = BEAST.MWSingleLayer3D(κ) -n = BE.numfunctions(x) +n = BE.numfunctions(x, domain(s)) z1 = zeros(ComplexF64, n, n) z2 = zeros(ComplexF64, n, n) @@ -341,16 +342,20 @@ BE = BEAST tqd = BE.quadpoints(x, [t], (12,)) bqd = BE.quadpoints(x, [s], (13,)) -DQ_strategy = BE.DoubleQuadStrategy(tqd[1,1], bqd[1,1]) -BEAST.momintegrals!(op, x, x, t, s, z1, DQ_strategy) +DQ_strategy = BE.DoubleQuadRule(tqd[1,1], bqd[1,1]) +BEAST.momintegrals!(z1, op, + X, nothing, t, + X, nothing, s, DQ_strategy) -SE_strategy = BE.WiltonSEStrategy( +SE_strategy = BE.WiltonSERule( tqd[1,1], - BE.DoubleQuadStrategy( + BE.DoubleQuadRule( tqd[1,1], bqd[1,1], ), ) -BEAST.momintegrals!(op, x, x, t, s, z2, SE_strategy) +BEAST.momintegrals!(z2, op, + X, nothing, t, + X, nothing, s, SE_strategy) @test norm(z1-z2) < 1.0e-7 diff --git a/test/test_laminated.jl b/test/untested/test_laminated.jl similarity index 97% rename from test/test_laminated.jl rename to test/untested/test_laminated.jl index ee1cd672..1db6191d 100644 --- a/test/test_laminated.jl +++ b/test/untested/test_laminated.jl @@ -27,7 +27,7 @@ G1 = weld(-G01, -G12, seam=b) G2 = weld(-G02, G12, seam=b) @test CompScienceMeshes.isoriented(G2) -isclosed(m) = (numcells(boundary(m)) == 0) +isclosed(m) = (length(boundary(m)) == 0) @test isclosed(G1) @test isclosed(G2) diff --git a/test/untested/test_mot.jl b/test/untested/test_mot.jl index 847a15e8..929fc87a 100644 --- a/test/untested/test_mot.jl +++ b/test/untested/test_mot.jl @@ -18,7 +18,7 @@ W0 = inv(Z0) x = BEAST.mot(W0,Z,B,Nt) n = 20 -@show x[n] +# @show x[n] @show exp(-n*Δt) @show norm(x[n]-exp(-n*Δt)) diff --git a/test/untested/test_mwops.jl b/test/untested/test_mwops.jl index ce428b6b..b2ec8586 100644 --- a/test/untested/test_mwops.jl +++ b/test/untested/test_mwops.jl @@ -62,6 +62,6 @@ z = assemble(K, T, S) qd = quaddata(K, rt, rt, elements(τ), elements(σ)) zlocal = zeros(promote_type(scalartype(K), scalartype(S), scalartype(T)), 3, 3) strat = BEAST.quadrule(K, rt, rt, 1, t, 1, s, qd) -strat = BEAST.DoubleQuadStrategy(qd.tpoints[1,1], qd.bpoints[1,1]) +strat = BEAST.DoubleQuadRule(qd.tpoints[1,1], qd.bpoints[1,1]) BEAST.momintegrals!(K, rt, rt, t, s, zlocal, strat) z = zlocal[3,3]