├── .cargo └── config.toml ├── .gitattributes ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── errata.pdf ├── errata └── tex │ ├── .gitignore │ └── errata.tex ├── experiments ├── armadillo_slingshot │ └── run_experiments.py ├── convergence_rate │ └── run_experiments.py ├── cylinder_shell │ └── run_experiments.py └── hollow_ball │ └── run_experiments.py ├── extern ├── mkl-corrode │ ├── .github │ │ └── workflows │ │ │ └── build_and_test.yml │ ├── .gitignore │ ├── Cargo.toml │ ├── README.md │ ├── src │ │ ├── dss │ │ │ ├── mod.rs │ │ │ ├── solver.rs │ │ │ └── sparse_matrix.rs │ │ ├── extended_eigensolver.rs │ │ ├── lib.rs │ │ ├── sparse.rs │ │ └── util.rs │ └── tests │ │ └── integration.rs └── mkl-sys │ ├── .github │ └── workflows │ │ └── build_and_run.yml │ ├── .gitignore │ ├── Cargo.toml │ ├── LICENSE │ ├── README.md │ ├── build.rs │ ├── src │ └── lib.rs │ ├── tests │ └── basic.rs │ └── wrapper.h ├── fcm_convergence ├── Cargo.toml └── src │ └── main.rs ├── fenris ├── Cargo.toml ├── benches │ └── assembly.rs ├── examples │ ├── embed_mesh_3d.rs │ ├── meshgen.rs │ ├── poisson.rs │ ├── poisson_common │ │ └── mod.rs │ ├── poisson_mms.rs │ └── polymesh_intersection.rs ├── src │ ├── allocators.rs │ ├── assembly.rs │ ├── cg.rs │ ├── connectivity.rs │ ├── element.rs │ ├── embedding │ │ ├── embedding2d.rs │ │ ├── embedding3d.rs │ │ ├── mod.rs │ │ └── quadrature_reduction.rs │ ├── error.rs │ ├── geometry │ │ ├── mod.rs │ │ ├── polygon.rs │ │ ├── polymesh.rs │ │ ├── polytope.rs │ │ ├── procedural.rs │ │ ├── proptest_strategies.rs │ │ ├── sdf.rs │ │ └── vtk.rs │ ├── lib.rs │ ├── lp_solvers.rs │ ├── mesh.rs │ ├── mesh_convert.rs │ ├── model.rs │ ├── proptest.rs │ ├── quadrature.rs │ ├── reorder.rs │ ├── rtree.rs │ ├── solid │ │ ├── assembly.rs │ │ ├── impl_model.rs │ │ ├── materials.rs │ │ └── mod.rs │ ├── space.rs │ ├── space_impl.rs │ ├── sparse.rs │ └── util.rs └── tests │ ├── integration.rs │ ├── integration_tests │ ├── assembly.rs │ ├── cg_fem.rs │ ├── embedding.rs │ └── mod.rs │ ├── unit.rs │ ├── unit_tests │ ├── assembly.proptest-regressions │ ├── assembly.rs │ ├── basis.proptest-regressions │ ├── basis.rs │ ├── cg.rs │ ├── conversion_formulas.rs │ ├── element.proptest-regressions │ ├── element.rs │ ├── embedding.rs │ ├── fe_mesh.rs │ ├── geometry.rs │ ├── materials.rs │ ├── mesh.proptest-regressions │ ├── mesh.rs │ ├── mod.rs │ ├── polygon.rs │ ├── polymesh.rs │ ├── polytope.rs │ ├── reorder.rs │ ├── sparse.proptest-regressions │ └── sparse.rs │ └── utils │ └── mod.rs ├── global_stash ├── Cargo.toml └── src │ └── lib.rs ├── hamilton ├── .gitignore ├── Cargo.toml ├── examples │ └── basic.rs ├── src │ ├── container.rs │ ├── container │ │ └── container_serialize.rs │ ├── entity.rs │ ├── generic_factory.rs │ ├── lib.rs │ ├── storages.rs │ └── systems.rs └── tests │ ├── registration.rs │ ├── serialization │ └── mod.rs │ └── unit.rs ├── hamilton2 ├── Cargo.toml ├── src │ ├── calculus.rs │ ├── dynamic_system │ │ ├── mod.rs │ │ ├── mutable.rs │ │ ├── stateful.rs │ │ ├── stateless.rs │ │ └── views.rs │ ├── integrators │ │ ├── euler.rs │ │ └── mod.rs │ ├── lib.rs │ └── newton.rs └── tests │ ├── unit.rs │ ├── unit_tests │ ├── calculus.rs │ ├── integrators.rs │ ├── mod.rs │ └── newton.rs │ └── utils │ └── mod.rs ├── intel-mkl-src-patched ├── Cargo.toml └── src │ └── lib.rs ├── lp-bfp ├── Cargo.toml ├── README.md ├── build.rs ├── cpp │ ├── CMakeLists.txt │ ├── lp-bfp.cpp │ └── lp-bfp.h └── src │ └── lib.rs ├── nested-vec ├── Cargo.toml ├── src │ └── lib.rs └── tests │ └── test.rs ├── notebooks ├── condition_number_experiment │ ├── condition_number_experiment.ipynb │ └── condition_numbers.json ├── convergence_rate │ ├── convergence_rate.ipynb │ ├── convergence_rate_output.txt │ └── hex_results.json ├── mesh_reorder.ipynb ├── quad_plots │ ├── quad_reduc_results.json │ ├── quad_reduc_results_monomials.json │ └── quadrature_plots.ipynb ├── quadrature-simplification.ipynb ├── quadrature_lp.ipynb ├── slingshot_iterations │ └── iterations.ipynb └── unit_tests_analytic_solutions.ipynb ├── paradis ├── Cargo.toml ├── src │ ├── adapter.rs │ ├── coloring.rs │ ├── lib.rs │ └── slice.rs ├── test_sanitized.sh └── tsan_suppression.txt ├── rustfmt.toml ├── scene_runner ├── Cargo.toml ├── build.rs └── src │ ├── bin │ └── dynamic_runner.rs │ ├── lib.rs │ ├── meshes.rs │ └── scenes │ ├── armadillo_slingshot.rs │ ├── cantilever3d.rs │ ├── cylinder_shell.rs │ ├── helpers.rs │ ├── hollow_ball.rs │ ├── mod.rs │ ├── quad_reduc.rs │ └── rotating_bicycle.rs ├── simulation_toolbox ├── Cargo.toml └── src │ ├── components │ ├── mesh.rs │ └── mod.rs │ ├── fem │ ├── bcs.rs │ ├── deformer.rs │ ├── fe_model.rs │ ├── integrator.rs │ ├── mod.rs │ ├── newton_cg.rs │ ├── schwarz_precond.rs │ └── system_assembly.rs │ ├── io │ ├── json_helper.rs │ ├── mod.rs │ ├── msh.rs │ ├── obj.rs │ ├── ply.rs │ └── vtk.rs │ ├── lib.rs │ └── util.rs └── teaser.png /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | # Make sure we always build for local CPU in order to maximize performance: there's rarely if ever a case where 3 | # we want to transfer the binary to a different computer (in which case this would have to be overridden) 4 | rustflags = ["-C", "target-cpu=native"] 5 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | assets/**/*.msh filter=lfs diff=lfs merge=lfs -text 2 | assets/**/*.vtk filter=lfs diff=lfs merge=lfs -text 3 | assets/**/*.svg filter=lfs diff=lfs merge=lfs -text 4 | *.blend filter=lfs diff=lfs merge=lfs -text 5 | *.mp4 filter=lfs diff=lfs merge=lfs -text 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /data 2 | /target 3 | /.idea 4 | .ipynb_checkpoints/ 5 | **/*.rs.bk 6 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | 3 | members = [ 4 | "simulation_toolbox", 5 | "scene_runner", 6 | "fenris", 7 | "hamilton", 8 | "lp-bfp", 9 | "nested-vec", 10 | "paradis", 11 | "intel-mkl-src-patched", 12 | "hamilton2", 13 | "global_stash", 14 | "fcm_convergence", 15 | ] 16 | 17 | [profile.release] 18 | # Enable debug information if it becomes necessary to debug a release build. Otherwise, disable it, 19 | # as it contributes significantly to compile times. 20 | #debug = true 21 | incremental = true 22 | 23 | [profile.bench] 24 | debug=true 25 | 26 | # Patch intel-mkl-src to use our own version, which uses `mkl-sys` under the hood. 27 | # This lets us use both mkl-corrode and MKL LAPACK bindings through nalgebra in the same code base 28 | [patch.crates-io] 29 | intel-mkl-src = { path = "intel-mkl-src-patched" } 30 | 31 | # Override mkl-sys and mkl-corrode dependencies with local code in order to ensure 32 | # that we don't depend on external git repositories 33 | [patch."https://github.com/Andlon/mkl-sys.git"] 34 | mkl-sys = { path = "extern/mkl-sys" } 35 | 36 | [patch."https://github.com/Andlon/mkl-corrode.git"] 37 | mkl-corrode = { path = "extern/mkl-corrode" } 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021 Andreas Longva, Fabian Löschner, Jan Bender 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. -------------------------------------------------------------------------------- /errata.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InteractiveComputerGraphics/higher_order_embedded_fem/868fbc25f93cae32aa3caaa41a60987d4192cf1b/errata.pdf -------------------------------------------------------------------------------- /errata/tex/.gitignore: -------------------------------------------------------------------------------- 1 | errata.pdf 2 | 3 | 4 | #### TeX ignores below 5 | 6 | ## Core latex/pdflatex auxiliary files: 7 | *.aux 8 | *.lof 9 | *.log 10 | *.lot 11 | *.fls 12 | *.out 13 | *.toc 14 | *.fmt 15 | *.fot 16 | *.cb 17 | *.cb2 18 | .*.lb 19 | 20 | ## Intermediate documents: 21 | *.dvi 22 | *.xdv 23 | *-converted-to.* 24 | # these rules might exclude image files for figures etc. 25 | # *.ps 26 | # *.eps 27 | # *.pdf 28 | 29 | ## Generated if empty string is given at "Please type another file name for output:" 30 | .pdf 31 | 32 | ## Bibliography auxiliary files (bibtex/biblatex/biber): 33 | *.bbl 34 | *.bcf 35 | *.blg 36 | *-blx.aux 37 | *-blx.bib 38 | *.run.xml 39 | 40 | ## Build tool auxiliary files: 41 | *.fdb_latexmk 42 | *.synctex 43 | *.synctex(busy) 44 | *.synctex.gz 45 | *.synctex.gz(busy) 46 | *.pdfsync 47 | 48 | ## Build tool directories for auxiliary files 49 | # latexrun 50 | latex.out/ 51 | 52 | ## Auxiliary and intermediate files from other packages: 53 | # algorithms 54 | *.alg 55 | *.loa 56 | 57 | # achemso 58 | acs-*.bib 59 | 60 | # amsthm 61 | *.thm 62 | 63 | # beamer 64 | *.nav 65 | *.pre 66 | *.snm 67 | *.vrb 68 | 69 | # changes 70 | *.soc 71 | 72 | # cprotect 73 | *.cpt 74 | 75 | # elsarticle (documentclass of Elsevier journals) 76 | *.spl 77 | 78 | # endnotes 79 | *.ent 80 | 81 | # fixme 82 | *.lox 83 | 84 | # feynmf/feynmp 85 | *.mf 86 | *.mp 87 | *.t[1-9] 88 | *.t[1-9][0-9] 89 | *.tfm 90 | 91 | #(r)(e)ledmac/(r)(e)ledpar 92 | *.end 93 | *.?end 94 | *.[1-9] 95 | *.[1-9][0-9] 96 | *.[1-9][0-9][0-9] 97 | *.[1-9]R 98 | *.[1-9][0-9]R 99 | *.[1-9][0-9][0-9]R 100 | *.eledsec[1-9] 101 | *.eledsec[1-9]R 102 | *.eledsec[1-9][0-9] 103 | *.eledsec[1-9][0-9]R 104 | *.eledsec[1-9][0-9][0-9] 105 | *.eledsec[1-9][0-9][0-9]R 106 | 107 | # glossaries 108 | *.acn 109 | *.acr 110 | *.glg 111 | *.glo 112 | *.gls 113 | *.glsdefs 114 | 115 | # gnuplottex 116 | *-gnuplottex-* 117 | 118 | # gregoriotex 119 | *.gaux 120 | *.gtex 121 | 122 | # htlatex 123 | *.4ct 124 | *.4tc 125 | *.idv 126 | *.lg 127 | *.trc 128 | *.xref 129 | 130 | # hyperref 131 | *.brf 132 | 133 | # knitr 134 | *-concordance.tex 135 | # TODO Comment the next line if you want to keep your tikz graphics files 136 | *.tikz 137 | *-tikzDictionary 138 | 139 | # listings 140 | *.lol 141 | 142 | # makeidx 143 | *.idx 144 | *.ilg 145 | *.ind 146 | *.ist 147 | 148 | # minitoc 149 | *.maf 150 | *.mlf 151 | *.mlt 152 | *.mtc[0-9]* 153 | *.slf[0-9]* 154 | *.slt[0-9]* 155 | *.stc[0-9]* 156 | 157 | # minted 158 | _minted* 159 | *.pyg 160 | 161 | # morewrites 162 | *.mw 163 | 164 | # nomencl 165 | *.nlg 166 | *.nlo 167 | *.nls 168 | 169 | # pax 170 | *.pax 171 | 172 | # pdfpcnotes 173 | *.pdfpc 174 | 175 | # sagetex 176 | *.sagetex.sage 177 | *.sagetex.py 178 | *.sagetex.scmd 179 | 180 | # scrwfile 181 | *.wrt 182 | 183 | # sympy 184 | *.sout 185 | *.sympy 186 | sympy-plots-for-*.tex/ 187 | 188 | # pdfcomment 189 | *.upa 190 | *.upb 191 | 192 | # pythontex 193 | *.pytxcode 194 | pythontex-files-*/ 195 | 196 | # tcolorbox 197 | *.listing 198 | 199 | # thmtools 200 | *.loe 201 | 202 | # TikZ & PGF 203 | *.dpth 204 | *.md5 205 | *.auxlock 206 | 207 | # todonotes 208 | *.tdo 209 | 210 | # easy-todo 211 | *.lod 212 | 213 | # xmpincl 214 | *.xmpi 215 | 216 | # xindy 217 | *.xdy 218 | 219 | # xypic precompiled matrices 220 | *.xyc 221 | 222 | # endfloat 223 | *.ttt 224 | *.fff 225 | 226 | # Latexian 227 | TSWLatexianTemp* 228 | 229 | ## Editors: 230 | # WinEdt 231 | *.bak 232 | *.sav 233 | 234 | # Texpad 235 | .texpadtmp 236 | 237 | # LyX 238 | *.lyx~ 239 | 240 | # Kile 241 | *.backup 242 | 243 | # KBibTeX 244 | *~[0-9]* 245 | 246 | # auto folder when using emacs and auctex 247 | ./auto/* 248 | *.el 249 | 250 | # expex forward references with \gathertags 251 | *-tags.tex 252 | 253 | # standalone packages 254 | *.sta 255 | -------------------------------------------------------------------------------- /errata/tex/errata.tex: -------------------------------------------------------------------------------- 1 | \documentclass{scrartcl} 2 | 3 | \usepackage{amsmath} 4 | \usepackage{hyperref} 5 | \usepackage{siunitx} 6 | 7 | \title{Higher-order finite elements for embedded simulation} 8 | \subtitle{Errata} 9 | \date{October 2021} 10 | \author{Andreas Longva, RWTH Aachen University} 11 | 12 | \begin{document} 13 | \maketitle 14 | 15 | This document details some errors discovered in our source code after the publication of our paper. 16 | 17 | The code used to produce most of the results for our paper is available at the following URL: 18 | 19 | \begin{center} 20 | \url{https://github.com/InteractiveComputerGraphics/higher_order_embedded_fem} 21 | \end{center} 22 | 23 | \section*{Material parameters} 24 | While rewriting and transfering some code to a new project, we discovered that our utility function for converting material parameters from Young's modulus and Poisson's ratio to corresponding Lamé parameters was off by a factor 4. 25 | 26 | In short, when converting to the Lamé parameters $\lambda$ and $\mu$, our code used an incorrect formula for computing $\lambda$, whereas the formula used for $\mu$ was correct. The formulas we had implemented were: 27 | \begin{align*} 28 | \mu = \frac{E}{2 (1 + \nu)}, \qquad 29 | \lambda^\text{bad} = \frac{1}{2} \cdot \frac{\mu \nu}{1 - 2 \nu}. \\ 30 | \end{align*} 31 | The correct formula for $\lambda$ is 32 | \begin{align*} 33 | \lambda = 2 \cdot \frac{\mu \nu}{1 - 2 \nu} = 4 \lambda^\text{bad}. 34 | \end{align*} 35 | 36 | As a result, the parameters we give in the paper are not the \emph{true} parameters that were used for the simulation. With the incorrect formulas we can compute the Lamé parameters that were used for the simulation, and use the \emph{correct} conversion formulas the other way to compute corresponding \emph{effective} values for the Young's modulus and Poisson's ratio that would lead to the same Lamé parameters, and therefore the same simulation results. We obtain: 37 | \begin{align*} 38 | \nu^\text{eff} = \frac{\nu^\text{paper}}{4 - 6 \, \nu^\text{paper}}, 39 | \qquad 40 | E^\text{eff} = E^\text{paper} \frac{1 + \nu^\text{eff}}{1 + \nu^\text{paper}}. 41 | \end{align*} 42 | 43 | The following table gives the original material parameters (as presented in the paper) and the corresponding effective values (up to 4 significant digits) for the experiments in which these material parameters were provided. 44 | 45 | \begin{center} 46 | \begin{tabular}{l | c | c} 47 | \textbf{Experiment} & \textbf{Paper parameters} & \textbf{Effective parameters} \\ 48 | \hline 49 | Quadrature verification (Section 5.4) 50 | & 51 | \begin{tabular}{l} 52 | $E = \SI{3e6}{\pascal}$ \\ $\nu = 0.4$ 53 | \end{tabular} 54 | & \begin{tabular}{l} 55 | $E = \SI{2.679e6}{\pascal}$ \\ $\nu = 0.25$ 56 | \end{tabular} 57 | \\ 58 | \hline 59 | Twisting cylinder (Section 6.2) 60 | & 61 | \begin{tabular}{l} 62 | $E = \SI{5e6}{\pascal}$ \\ $\nu = 0.48$ 63 | \end{tabular} 64 | & \begin{tabular}{l} 65 | $E = \SI{4.826e6}{\pascal}$ \\ $\nu = 0.4286$ 66 | \end{tabular} 67 | \\ 68 | \hline 69 | Armadillo slingshot (Section 6.5) 70 | & 71 | \begin{tabular}{l} 72 | $E = \SI{5e5}{\pascal}$ \\ $\nu = 0.4$ 73 | \end{tabular} 74 | & \begin{tabular}{l} 75 | $E = \SI{4.464e5}{\pascal}$ \\ $\nu = 0.25$ 76 | \end{tabular} 77 | \\ 78 | \hline 79 | \end{tabular} 80 | \end{center} 81 | 82 | We see that the resulting effective parameters are (unfortunately) noticably different from the intended parameters. However, these parameter choices were more or less arbitrary to begin with, so it seems fair to say that it does not significantly change any of the conclusions made in the paper. 83 | 84 | 85 | 86 | \section*{Twisting cylinder boundary conditions} 87 | 88 | The twisting cylinder (Section 6.2) is reported in the paper to be 16 meters long. Recently, upon reviewing the code, we realized that the Dirichlet boundary conditions at the end were enforced for nodes with $y$-coordinate $|y| \geq 6.99$ instead of $|y| \geq 7.99$. Therefore the motion of the last meter on each side of the cylinder is prescribed. The experiment therefore effectively simulates a 14 meter long cylinder as opposed to the 16 meters described. 89 | 90 | 91 | \section*{} 92 | \end{document} -------------------------------------------------------------------------------- /experiments/armadillo_slingshot/run_experiments.py: -------------------------------------------------------------------------------- 1 | import os 2 | import subprocess 3 | 4 | # Values are intended for Suttner 5 | # You might want to set this to the number of *physical* cores on your computer 6 | # os.environ['RAYON_NUM_THREADS'] = "28" 7 | # os.environ['OMP_NUM_THREADS'] = "28" 8 | os.environ['RUSTFLAGS'] = "-C target-cpu=native" 9 | 10 | data_dir = "data_armadillo_slingshot" 11 | 12 | # We assume that the script is called from the root project directory 13 | args = [ 14 | "cargo", 15 | "run", 16 | "--", 17 | "--list-scenes" 18 | ] 19 | 20 | subprocess.run(["cargo build --release"], shell=True) 21 | 22 | scenes = [ 23 | "armadillo_slingshot_embedded_tet4_500", 24 | "armadillo_slingshot_embedded_tet4_1500", 25 | "armadillo_slingshot_embedded_tet4_3000", 26 | "armadillo_slingshot_embedded_tet10_500", 27 | "armadillo_slingshot_embedded_tet10_1000", 28 | "armadillo_slingshot_embedded_tet10_1500", 29 | "armadillo_slingshot_embedded_tet10_3000", 30 | "armadillo_slingshot_fem_tet4_500", 31 | "armadillo_slingshot_fem_tet4_1500", 32 | "armadillo_slingshot_fem_tet4_3000", 33 | "armadillo_slingshot_fem_tet10_500", 34 | "armadillo_slingshot_fem_tet10_1000", 35 | "armadillo_slingshot_fem_tet10_3000", 36 | 37 | # Save the more expensive sims for last 38 | "armadillo_slingshot_fem_tet4_5000", 39 | "armadillo_slingshot_embedded_tet4_5000", 40 | "armadillo_slingshot_fem_tet10_5000", 41 | "armadillo_slingshot_embedded_tet10_5000", 42 | "armadillo_slingshot_fem_tet4_full", 43 | ] 44 | 45 | for scene in scenes: 46 | print("\n\n\nRunning scene {}\n\n\n".format(scene)) 47 | cmd = "target/release/dynamic_runner --scene {} --print-timings-output --output-dir {} --output-ply" \ 48 | " --output-fps 100 --dump-stash"\ 49 | .format(scene, data_dir) 50 | subprocess.run([cmd], shell=True) 51 | 52 | -------------------------------------------------------------------------------- /experiments/convergence_rate/run_experiments.py: -------------------------------------------------------------------------------- 1 | import os 2 | import subprocess 3 | 4 | # Values are intended for Suttner 5 | # You might want to set this to the number of *physical* cores on your computer 6 | # os.environ['RAYON_NUM_THREADS'] = "28" 7 | # os.environ['OMP_NUM_THREADS'] = "28" 8 | os.environ['RUSTFLAGS'] = "-C target-cpu=native" 9 | 10 | data_dir = "data_cylinder_shell" 11 | 12 | # We assume that the script is called from the root project directory 13 | command = "cargo run --release --bin fcm_convergence -- --resolutions 1 2 4 8 16 24 32 --reference-mesh hemisphere_50_uniform_refined2.msh" 14 | subprocess.run([command], shell=True).check_returncode() 15 | -------------------------------------------------------------------------------- /experiments/cylinder_shell/run_experiments.py: -------------------------------------------------------------------------------- 1 | import os 2 | import subprocess 3 | 4 | # Values are intended for Suttner 5 | # You might want to set this to the number of *physical* cores on your computer 6 | # os.environ['RAYON_NUM_THREADS'] = "28" 7 | # os.environ['OMP_NUM_THREADS'] = "28" 8 | os.environ['RUSTFLAGS'] = "-C target-cpu=native" 9 | 10 | data_dir = "data_cylinder_shell" 11 | 12 | # We assume that the script is called from the root project directory 13 | args = [ 14 | "cargo", 15 | "run", 16 | "--", 17 | "--list-scenes" 18 | ] 19 | 20 | subprocess.run(["cargo build --release"], shell=True).check_returncode() 21 | 22 | scenes = [ 23 | # "cylinder_shell_embedded_hex20_res1", 24 | # "cylinder_shell_embedded_hex20_res2", 25 | # "cylinder_shell_embedded_hex20_res3", 26 | # "cylinder_shell_embedded_hex20_res4", 27 | # "cylinder_shell_embedded_hex20_res5", 28 | # "cylinder_shell_embedded_hex20_res6_strength3", 29 | # "cylinder_shell_embedded_hex20_res6_strength5", 30 | # "cylinder_shell_embedded_hex20_res6_strength5_no_simp", 31 | "cylinder_shell_embedded_hex20_res7_strength3", 32 | "cylinder_shell_embedded_hex20_res7_strength5", 33 | # "cylinder_shell_embedded_hex20_res8", 34 | # "cylinder_shell_embedded_hex20_res10", 35 | # "cylinder_shell_embedded_hex8_res2", 36 | # "cylinder_shell_embedded_hex8_res3", 37 | # "cylinder_shell_embedded_hex8_res5", 38 | # "cylinder_shell_embedded_hex8_res10", 39 | # "cylinder_shell_embedded_hex8_res14", 40 | # "cylinder_shell_embedded_hex8_res16", 41 | # "cylinder_shell_embedded_hex8_res18", 42 | # "cylinder_shell_embedded_hex8_res20", 43 | # "cylinder_shell_embedded_hex8_res22", 44 | # "cylinder_shell_embedded_hex8_res24", 45 | # "cylinder_shell_embedded_hex8_res26", 46 | "cylinder_shell_embedded_hex8_res28", 47 | "cylinder_shell_embedded_hex8_res29", 48 | "cylinder_shell_embedded_hex8_res30", 49 | # "cylinder_shell_embedded_hex8_res32", 50 | # "cylinder_shell_fem_tet4_5k", 51 | # "cylinder_shell_fem_tet4_10k", 52 | # "cylinder_shell_fem_tet4_20k", 53 | # "cylinder_shell_fem_tet4_40k", 54 | "cylinder_shell_fem_tet10_5k_strength2", 55 | "cylinder_shell_fem_tet10_5k_strength3", 56 | "cylinder_shell_fem_tet10_5k_strength5", 57 | # "cylinder_shell_fem_tet10_10k", 58 | # # Run these denser meshes last 59 | # "cylinder_shell_fem_tet4_80k", 60 | "cylinder_shell_fem_tet10_20k", 61 | "cylinder_shell_fem_tet4_160k", 62 | "cylinder_shell_embedded_hex20_res7_strength5_no_simp", 63 | # "cylinder_shell_fem_tet4_320k", 64 | ] 65 | 66 | for scene in scenes: 67 | print("\n\n\nRunning scene {}\n\n\n".format(scene)) 68 | cmd = "target/release/dynamic_runner --scene {} --print-timings-output --output-dir {} --output-ply " \ 69 | "--dump-stash"\ 70 | .format(scene, data_dir) 71 | subprocess.run([cmd], shell=True) 72 | -------------------------------------------------------------------------------- /experiments/hollow_ball/run_experiments.py: -------------------------------------------------------------------------------- 1 | import os 2 | import subprocess 3 | 4 | # Values are intended for Suttner 5 | # You might want to set this to the number of *physical* cores on your computer 6 | # os.environ['RAYON_NUM_THREADS'] = "28" 7 | # os.environ['OMP_NUM_THREADS'] = "28" 8 | os.environ['RUSTFLAGS'] = "-C target-cpu=native" 9 | 10 | data_dir = "data_hollow_ball" 11 | 12 | # We assume that the script is called from the root project directory 13 | args = [ 14 | "cargo", 15 | "run", 16 | "--", 17 | "--list-scenes" 18 | ] 19 | 20 | subprocess.run(["cargo build --release"], shell=True) 21 | 22 | scenes = [ 23 | "hollow_ball_embedded_tet4_coarse", 24 | "hollow_ball_embedded_tet10_coarse", 25 | 26 | "hollow_ball_embedded_tet4_medium", 27 | "hollow_ball_embedded_tet10_medium", 28 | "hollow_ball_fem_tet4_medium", 29 | 30 | "hollow_ball_fem_tet4_fine", 31 | "hollow_ball_fem_tet10_medium", 32 | "hollow_ball_fem_tet4_coarse", 33 | "hollow_ball_fem_tet10_coarse", 34 | 35 | "hollow_ball_fem_tet10_fine", 36 | ] 37 | 38 | for scene in scenes: 39 | print("\n\n\nRunning scene {}\n\n\n".format(scene)) 40 | cmd = "target/release/dynamic_runner --scene {} --print-timings-output --output-dir {} --output-ply " \ 41 | "--dump-stash --output-fps 100"\ 42 | .format(scene, data_dir) 43 | subprocess.run([cmd], shell=True) 44 | 45 | -------------------------------------------------------------------------------- /extern/mkl-corrode/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /extern/mkl-corrode/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mkl-corrode" 3 | version = "0.1.0" 4 | authors = ["Andreas Longva "] 5 | edition = "2018" 6 | 7 | [features] 8 | ilp64 = [ "mkl-sys/ilp64" ] 9 | openmp = [ "mkl-sys/openmp" ] 10 | 11 | [dependencies.mkl-sys] 12 | git = "https://github.com/Andlon/mkl-sys" 13 | rev = "c197c97319f0784caae26012968c9c7bd76d494b" 14 | features = [ "dss", "inspector-executor" ] 15 | 16 | [dev-dependencies] 17 | approx = "0.3" 18 | 19 | # Make sure that mkl-sys compiles faster by compiling bindgen in release mode 20 | [profile.dev.package.bindgen] 21 | opt-level = 2 22 | -------------------------------------------------------------------------------- /extern/mkl-corrode/README.md: -------------------------------------------------------------------------------- 1 | # mkl-corrode 2 | [![Build Status](https://github.com/Andlon/mkl-corrode/workflows/Build%20and%20run%20tests/badge.svg)](https://github.com/Andlon/mkl-sys/actions) 3 | 4 | A lightweight and pleasant Rust wrapper for Intel MKL. Tested on Ubuntu 18.04 and Windows Server 2019 with MKL 2019 Update 5 and MKL 2020 Update 1. 5 | -------------------------------------------------------------------------------- /extern/mkl-corrode/src/dss/mod.rs: -------------------------------------------------------------------------------- 1 | use mkl_sys::{MKL_DSS_NON_SYMMETRIC, MKL_DSS_SYMMETRIC, MKL_DSS_SYMMETRIC_STRUCTURE, MKL_INT}; 2 | 3 | mod solver; 4 | mod sparse_matrix; 5 | pub use solver::*; 6 | pub use sparse_matrix::*; 7 | 8 | // TODO: Support complex numbers 9 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 10 | pub enum MatrixStructure { 11 | StructurallySymmetric, 12 | Symmetric, 13 | NonSymmetric, 14 | } 15 | 16 | impl MatrixStructure { 17 | fn to_mkl_opt(&self) -> MKL_INT { 18 | use MatrixStructure::*; 19 | match self { 20 | StructurallySymmetric => MKL_DSS_SYMMETRIC_STRUCTURE, 21 | Symmetric => MKL_DSS_SYMMETRIC, 22 | NonSymmetric => MKL_DSS_NON_SYMMETRIC, 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /extern/mkl-corrode/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub extern crate mkl_sys; 2 | 3 | pub mod dss; 4 | pub mod extended_eigensolver; 5 | pub mod sparse; 6 | 7 | mod util; 8 | 9 | mod internal { 10 | pub trait InternalScalar { 11 | fn zero_element() -> Self; 12 | fn try_as_f64(&self) -> Option; 13 | } 14 | } 15 | 16 | /// Marker trait for supported scalar types. 17 | /// 18 | /// Can not be implemented by dependent crates. 19 | pub unsafe trait SupportedScalar: 'static + Copy + internal::InternalScalar {} 20 | 21 | // TODO: To support f32 we need to pass appropriate options during handle creation 22 | // Can have the sealed trait provide us with the appropriate option for this! 23 | //impl private::Sealed for f32 {} 24 | impl internal::InternalScalar for f64 { 25 | fn zero_element() -> Self { 26 | 0.0 27 | } 28 | 29 | fn try_as_f64(&self) -> Option { 30 | Some(*self) 31 | } 32 | } 33 | //unsafe impl SupportedScalar for f32 {} 34 | unsafe impl SupportedScalar for f64 {} 35 | -------------------------------------------------------------------------------- /extern/mkl-corrode/src/util.rs: -------------------------------------------------------------------------------- 1 | use std::any::TypeId; 2 | use std::mem::transmute; 3 | 4 | pub fn is_same_type() -> bool 5 | where 6 | T: 'static, 7 | U: 'static, 8 | { 9 | TypeId::of::() == TypeId::of::() 10 | } 11 | 12 | pub fn transmute_identical_slice(slice: &[T]) -> Option<&[U]> 13 | where 14 | T: 'static, 15 | U: 'static, 16 | { 17 | if is_same_type::() { 18 | Some(unsafe { transmute(slice) }) 19 | } else { 20 | None 21 | } 22 | } 23 | 24 | pub fn transmute_identical_slice_mut(slice: &mut [T]) -> Option<&mut [U]> 25 | where 26 | T: 'static, 27 | U: 'static, 28 | { 29 | if is_same_type::() { 30 | Some(unsafe { transmute(slice) }) 31 | } else { 32 | None 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /extern/mkl-sys/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /extern/mkl-sys/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mkl-sys" 3 | version = "0.1.0" 4 | authors = ["Andreas Longva "] 5 | edition = "2018" 6 | links = "mkl" 7 | build = "build.rs" 8 | 9 | [features] 10 | # Intel MKL module selection 11 | all = [] 12 | dss = [] 13 | sparse-matrix-checker = [] 14 | extended-eigensolver = [] 15 | inspector-executor = [] 16 | 17 | # Configurations 18 | openmp = [] 19 | ilp64 = [] 20 | 21 | [dependencies] 22 | 23 | [build-dependencies] 24 | bindgen = "0.54" 25 | 26 | # Need opt-level = 2 for `bindgen` to be able to finish in reasonable time (i.e. 30 secs instead of 10 minutes) 27 | [profile.dev.package.bindgen] 28 | opt-level = 2 -------------------------------------------------------------------------------- /extern/mkl-sys/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 Andreas Longva 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. -------------------------------------------------------------------------------- /extern/mkl-sys/README.md: -------------------------------------------------------------------------------- 1 | # mkl-sys 2 | 3 | [![Build Status](https://github.com/Andlon/mkl-sys/workflows/Build%20and%20run%20tests/badge.svg)](https://github.com/Andlon/mkl-sys/actions) 4 | 5 | Auto-generated bindings to Intel MKL. Currently only supports Linux and Windows, and not considered stable/ready for production use. Only tested with Intel MKL 2019 and 2020. 6 | 7 | This crate relies on Intel MKL having been installed on the target system, 8 | and that the environment is set up for use with MKL. 9 | The easiest way to make it work is to run the provided `mklvars.sh` setup script that is bundled with MKL. 10 | This sets up the environment for use with MKL. This crate then detects the correct Intel MKL installation 11 | by inspecting the value of the `MKLROOT` environment variable. 12 | 13 | Note that we used to support `pkg-config`, but as of Intel MKL 2020, Intel is shipping broken 14 | configurations. Therefore we instead directly rely on the value of `MKLROOT`. 15 | 16 | ## Windows support 17 | 18 | ### Compile time requirements 19 | To compiled this create on Windows, the following requirements have to be met: 20 | 1. To run `bindgen` a Clang (`libclang`) installation is required. According to the `bindgen` [documentation](https://rust-lang.github.io/rust-bindgen/requirements.html#clang) version 3.9 should suffice. A recent pre-built version of Clang can be downloaded on the [LLVM release page](https://releases.llvm.org/download.html). To ensure that `bindgen` can find Clang, the environment variable `LIBCLANG_PATH` used by `bindgen` has to be set to point to the `bin` folder of the Clang installation. 21 | 2. On Windows, Clang uses the MSVC standard library. Therefore, the build process should be started from a Visual Studio or Build Tools command prompt. The command prompt can be started from a start menu shortcut created by the Visual Studio installer or by running a `vcvars` script (e.g. `C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvars64.bat`) in an open command prompt. An IDE such as Clion with a configured MSVC toolchain should already provide this configuration for targets inside of the IDE. 22 | 3. The environment variable `MKLROOT` has to be configured properly to point to the path containing the `bin`, `lib`, `include`, etc. folders of MKL (e.g. `C:\Program Files (x86)\IntelSWTools\compilers_and_libraries\windows\mkl`). This can also be done by running the `mklvars.bat` script in the `bin` folder of MKL. 23 | 24 | A script to build the library and run all tests on Windows might then look like this: 25 | ``` 26 | call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvars64.bat" 27 | call "C:\Program Files (x86)\IntelSWTools\compilers_and_libraries\windows\mkl\bin\mklvars.bat intel64" 28 | set LIBCLANG_PATH=C:\Program Files\LLVM\bin 29 | cargo test --release --features "all" 30 | ``` 31 | 32 | ### Run time requirements 33 | During runtime the corresponding redistributable DLLs of MKL (e.g. located in `C:\Program Files (x86)\IntelSWTools\compilers_and_libraries\windows\redist\intel64_win\mkl`) have to be in `PATH`. 34 | 35 | ## Known issues 36 | - `bindgen` does not seem to be able to properly handle many preprocessor macros, such as e.g. `dss_create`. 37 | This appears to be related to [this issue](https://github.com/rust-lang/rust-bindgen/issues/753). 38 | - Generating bindings for the entire MKL library might take a lot of time. To circumvent this, you should use features 39 | to enable binding generation only for the parts of the library that you will need. For example, the `dss` feature 40 | generates bindings for the Direct Sparse Solver (DSS) interface. 41 | 42 | A second approach that alleviates long build times due to `bindgen` is to use the following profile override 43 | in your application's TOML file: 44 | 45 | ```toml 46 | [profile.dev.package.bindgen] 47 | opt-level = 2 48 | ``` 49 | 50 | This ensures that bindgen is compiled with optimizations on, significantly improving its runtime when 51 | invoked by the build script in `mkl-sys`. 52 | 53 | ## License 54 | Intel MKL is provided by Intel and licensed separately. 55 | 56 | This crate is licensed under the MIT license. See `LICENSE` for details. 57 | 58 | -------------------------------------------------------------------------------- /extern/mkl-sys/src/lib.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | 3 | # mkl-sys 4 | 5 | Auto-generated bindings to Intel MKL. 6 | 7 | Currently only tested on Linux, and should be **considered experimental and unstable** 8 | (in an API sense). 9 | 10 | This crate relies on Intel MKL having been installed on the target system, 11 | and that the environment is set up for use with MKL. 12 | It uses `pkg-config` to determine library paths. The easiest way to make it work is to run the provided 13 | `mklvars.sh` setup script that is bundled with MKL. 14 | 15 | The library can generate bindings for only selected modules of MKL, or for the entire library. 16 | By default, no modules are selected, and compilation will fail with an error message. To use 17 | this library, enable the features corresponding to the desired MKL modules, or enable the 18 | "all" feature if you want to generate code for all of MKL. At the moment, the currently available 19 | features corresponding to MKL modules are: 20 | 21 | - `all`: Create bindings for all modules. 22 | - `dss`: The Direct Sparse Solver (DSS) interface. 23 | - `sparse-matrix-checker`: Routines for checking validity of sparse matrix storage. 24 | - `extended-eigensolver`: Routines for the Extended Eigensolver functionality. 25 | - `inspector-executor`: The Inspector-Executor API for sparse matrices. 26 | 27 | It is strongly recommended to only enable the modules that you need, otherwise the effects 28 | on compilation time may be severe. See "Known issues" below. 29 | 30 | By default, the sequential version of MKL is used. To enable OpenMP support, enable the 31 | `openmp` feature. 32 | 33 | By default, 32-bit integers are used for indexing. This corresponds to the `lp64` configuration 34 | of MKL. To use 64-bit integers with the `ilp64` configuration, enable the `ilp64` feature. 35 | 36 | Please refer to the Intel MKL documentation for how to use the functions exposed by this crate. 37 | 38 | ## Contributions 39 | Contributions are very welcome. I am generally only adding features to this library as I need them. 40 | If you require something that is not yet available, please file an issue on GitHub 41 | and consider contributing the changes yourself through a pull request. 42 | 43 | ## Known issues 44 | - `bindgen` does not handle many preprocessor macros used by MKL, such as e.g. `dss_create`. 45 | It also does not generate type aliases for #define-based type aliases, such as e.g. `MKL_INT`. 46 | Some of these types are manually added to this library, but they do not appear in the 47 | function arguments. 48 | - Generating bindings for the entire MKL library takes a lot of time. This is a significant issue for debug 49 | builds, as we currently have no way of forcing optimizations for bindgen when dependent projects are 50 | built without optimizations. To circumvent this, you should use features to enable binding generation 51 | only for the parts of the library that you will need. For example, the `dss` feature generates bindings for the 52 | Direct Sparse Solver (DSS) interface. 53 | 54 | */ 55 | 56 | #![allow(non_upper_case_globals)] 57 | #![allow(non_camel_case_types)] 58 | #![allow(non_snake_case)] 59 | 60 | include!(concat!(env!("OUT_DIR"), "/bindings.rs")); -------------------------------------------------------------------------------- /extern/mkl-sys/tests/basic.rs: -------------------------------------------------------------------------------- 1 | use mkl_sys::{ 2 | _MKL_DSS_HANDLE_t, dss_create_, dss_delete_, MKL_DSS_DEFAULTS, MKL_DSS_ZERO_BASED_INDEXING, 3 | }; 4 | use std::ptr::null_mut; 5 | 6 | #[test] 7 | /// Calls some arbitrary MKL functions to ensure that linking and running an executable works 8 | fn does_link_and_run() { 9 | let create_opts = MKL_DSS_DEFAULTS + MKL_DSS_ZERO_BASED_INDEXING; 10 | let mut handle: _MKL_DSS_HANDLE_t = null_mut(); 11 | unsafe { 12 | let error = dss_create_(&mut handle, &create_opts); 13 | if error != 0 { 14 | panic!("dss_create error: {}", error); 15 | } 16 | } 17 | 18 | let delete_opts = MKL_DSS_DEFAULTS; 19 | unsafe { 20 | let error = dss_delete_(&mut handle, &delete_opts); 21 | if error != 0 { 22 | panic!("dss_delete error: {}", error); 23 | } 24 | } 25 | 26 | assert!(true); 27 | } 28 | -------------------------------------------------------------------------------- /extern/mkl-sys/wrapper.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // For some types, MKL uses preprocessor macros as a preprocessor alternative to 4 | // typedefs. Unfortunately, this makes it just about impossible for `bindgen` 5 | // to understand that it's actually a typedef. To remedy the situation, 6 | // we replace the preprocessor macros with actual, proper typedefs. 7 | 8 | /// Underlying MKL_INT type. This is an intermediate type alias introduced by `mkl-sys`, 9 | /// and should never be directly referenced. 10 | typedef MKL_INT ____MKL_SYS_UNDERLYING_MKL_INT; 11 | 12 | /// Underlying MKL_UINT type. This is an intermediate type alias introduced by `mkl-sys`, 13 | /// and should never be directly referenced. 14 | typedef MKL_UINT ____MKL_SYS_UNDERLYING_MKL_UINT; 15 | 16 | #undef MKL_INT 17 | #undef MKL_UINT 18 | typedef ____MKL_SYS_UNDERLYING_MKL_INT MKL_INT; 19 | typedef ____MKL_SYS_UNDERLYING_MKL_UINT MKL_UINT; 20 | 21 | #include -------------------------------------------------------------------------------- /fcm_convergence/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fcm_convergence" 3 | version = "0.1.0" 4 | authors = ["Andreas Longva "] 5 | edition = "2018" 6 | publish = false 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [dependencies] 11 | structopt = "0.3" 12 | simulation_toolbox = { path = "../simulation_toolbox" } 13 | fenris = { path = "../fenris" } 14 | hamilton2 = { path = "../hamilton2" } 15 | mkl-corrode = { git = "https://github.com/Andlon/mkl-corrode.git", rev="0843a0b46234cd88d7a0e7489720514624207ad9", features = [ "openmp" ] } 16 | serde = { version="1.0", features = [ "derive" ] } 17 | serde_json = "1.0" 18 | rayon = "1.3" 19 | chrono = "0.4" -------------------------------------------------------------------------------- /fenris/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fenris" 3 | version = "0.1.0" 4 | authors = ["Andreas Longva "] 5 | edition = "2018" 6 | publish = false 7 | 8 | [features] 9 | default = [ "proptest" ] 10 | 11 | [dependencies] 12 | nalgebra = { version = "0.21", features = [ "serde-serialize" ] } 13 | alga = { version = "0.9", default-features = false } 14 | vtkio = "0.3" 15 | num = "0.2" 16 | numeric_literals = "0.2.0" 17 | itertools = "0.9" 18 | ordered-float = "1.0" 19 | proptest = { version = "0.9", optional = true } 20 | rstar = "0.9.1" 21 | rayon = "1.3" 22 | lp-bfp = { path = "../lp-bfp" } 23 | nested-vec = { path="../nested-vec" } 24 | hamilton2 = { path="../hamilton2" } 25 | # TODO: Make serde optional 26 | serde = { version="1.0", features = [ "derive" ] } 27 | arrayvec = "0.5.1" 28 | log = "0.4" 29 | paradis = { path = "../paradis" } 30 | rustc-hash = "1.1.0" 31 | thread_local = "1.*" 32 | delegate = "0.6.1" 33 | 34 | [dev-dependencies] 35 | proptest = "0.9" 36 | prettytable-rs = "^0.8" 37 | matrixcompare = "0.3.0" 38 | mkl-corrode = { git = "https://github.com/Andlon/mkl-corrode.git", rev="0843a0b46234cd88d7a0e7489720514624207ad9" } 39 | paste = "0.1.7" 40 | criterion = "0.3.2" 41 | 42 | [[bench]] 43 | name = "assembly" 44 | harness = false 45 | -------------------------------------------------------------------------------- /fenris/examples/embed_mesh_3d.rs: -------------------------------------------------------------------------------- 1 | use fenris::embedding::embed_mesh_3d; 2 | use fenris::geometry::polymesh::PolyMesh3d; 3 | use fenris::geometry::procedural::create_rectangular_uniform_hex_mesh; 4 | 5 | use fenris::geometry::vtk::write_vtk; 6 | use nalgebra::{Rotation3, Unit, Vector3}; 7 | 8 | use std::error::Error; 9 | use std::time::Instant; 10 | 11 | fn main() -> Result<(), Box> { 12 | let bg_mesh = create_rectangular_uniform_hex_mesh(2.0, 2, 1, 1, 8); 13 | 14 | let embed_mesh = { 15 | let mut embed_mesh = create_rectangular_uniform_hex_mesh(0.5, 1, 1, 1, 4); 16 | let rotation = Rotation3::from_axis_angle(&Unit::new_normalize(Vector3::new(1.0, 1.0, 1.0)), 0.45); 17 | let t = Vector3::new(0.50, 0.50, 0.50); 18 | embed_mesh.transform_vertices(|v| *v = rotation * v.clone() + t); 19 | PolyMesh3d::from(&embed_mesh) 20 | }; 21 | 22 | println!( 23 | "Embedded mesh: {} cells, {} vertices.", 24 | embed_mesh.num_cells(), 25 | embed_mesh.vertices().len() 26 | ); 27 | println!( 28 | "Background mesh: {} cells, {} vertices.", 29 | bg_mesh.connectivity().len(), 30 | bg_mesh.vertices().len() 31 | ); 32 | 33 | write_vtk(&bg_mesh, "data/embed_mesh_3d/bg_mesh.vtk", "bg mesh")?; 34 | write_vtk(&embed_mesh, "data/embed_mesh_3d/embed_mesh.vtk", "embedded mesh")?; 35 | 36 | println!("Embedding..."); 37 | let now = Instant::now(); 38 | let embedding = embed_mesh_3d(&bg_mesh, &embed_mesh); 39 | let elapsed = now.elapsed().as_secs_f64(); 40 | println!("Completed embedding in {:2.2} seconds.", elapsed); 41 | 42 | let exterior_mesh = bg_mesh.keep_cells(&embedding.exterior_cells); 43 | let interior_mesh = bg_mesh.keep_cells(&embedding.interior_cells); 44 | let interface_mesh = bg_mesh.keep_cells(&embedding.interface_cells); 45 | 46 | let mut keep_cells = embedding.interior_cells.clone(); 47 | keep_cells.extend(embedding.interface_cells.iter().copied()); 48 | keep_cells.sort_unstable(); 49 | let adapted_bg_mesh = bg_mesh.keep_cells(&keep_cells); 50 | println!( 51 | "Adapted bg mesh: {} cells, {} vertices.", 52 | adapted_bg_mesh.connectivity().len(), 53 | adapted_bg_mesh.vertices().len() 54 | ); 55 | 56 | write_vtk(&exterior_mesh, "data/embed_mesh_3d/exterior.vtk", "exterior mesh")?; 57 | write_vtk(&interior_mesh, "data/embed_mesh_3d/interior.vtk", "interior mesh")?; 58 | write_vtk(&interface_mesh, "data/embed_mesh_3d/interface.vtk", "interface mesh")?; 59 | 60 | let aggregate_interface_mesh = PolyMesh3d::concatenate(&embedding.interface_cell_embeddings); 61 | write_vtk( 62 | &aggregate_interface_mesh, 63 | "data/embed_mesh_3d/aggregate_interface.vtk", 64 | "aggregate interface", 65 | )?; 66 | 67 | Ok(()) 68 | } 69 | -------------------------------------------------------------------------------- /fenris/examples/meshgen.rs: -------------------------------------------------------------------------------- 1 | use fenris::geometry::procedural::{ 2 | approximate_quad_mesh_for_sdf_2d, approximate_triangle_mesh_for_sdf_2d, voxelize_sdf_2d, 3 | }; 4 | use fenris::geometry::sdf::SdfCircle; 5 | use fenris::geometry::vtk::{ 6 | create_vtk_data_set_from_polygons, create_vtk_data_set_from_quad_mesh, create_vtk_data_set_from_triangle_mesh, 7 | write_vtk, 8 | }; 9 | 10 | use fenris::embedding::embed_mesh_2d; 11 | use nalgebra::Vector2; 12 | use vtkio::Error; 13 | 14 | pub fn main() -> Result<(), Error> { 15 | let voxelize_resolution = 0.23; 16 | let fitted_resolution = 0.1; 17 | let sdf = SdfCircle { 18 | radius: 1.0, 19 | center: Vector2::zeros(), 20 | }; 21 | 22 | let voxelized_mesh = voxelize_sdf_2d(&sdf, voxelize_resolution); 23 | let voxelized_data_set = create_vtk_data_set_from_quad_mesh(&voxelized_mesh); 24 | write_vtk( 25 | voxelized_data_set, 26 | "data/circle_mesh_voxelized.vtk", 27 | "data/voxelized circle", 28 | )?; 29 | 30 | let fitted_quad_mesh = approximate_quad_mesh_for_sdf_2d(&sdf, fitted_resolution); 31 | let fitted_quad_mesh_data_set = create_vtk_data_set_from_quad_mesh(&fitted_quad_mesh); 32 | write_vtk( 33 | fitted_quad_mesh_data_set, 34 | "data/circle_mesh_fitted.vtk", 35 | "data/fitted circle quad mesh", 36 | )?; 37 | 38 | let fitted_triangle_mesh = approximate_triangle_mesh_for_sdf_2d(&sdf, fitted_resolution); 39 | let fitted_triangle_mesh_data_set = create_vtk_data_set_from_triangle_mesh(&fitted_triangle_mesh); 40 | write_vtk( 41 | fitted_triangle_mesh_data_set, 42 | "data/circle_mesh_triangle_fitted.vtk", 43 | "data/fitted circle triangle mesh", 44 | )?; 45 | 46 | let embedding = embed_mesh_2d(&voxelized_mesh, &fitted_triangle_mesh).unwrap(); 47 | let polygons = embedding 48 | .into_iter() 49 | .map(|(_, polygons)| polygons) 50 | .flatten() 51 | .collect::>(); 52 | let embedded_data_set = create_vtk_data_set_from_polygons(&polygons); 53 | write_vtk( 54 | embedded_data_set, 55 | "data/embedded_mesh.vtk", 56 | "data/triangle mesh embedded in quad mesh", 57 | )?; 58 | 59 | Ok(()) 60 | } 61 | -------------------------------------------------------------------------------- /fenris/examples/poisson_common/mod.rs: -------------------------------------------------------------------------------- 1 | use fenris::assembly::{GeneralizedEllipticContraction, GeneralizedEllipticOperator}; 2 | use nalgebra::allocator::Allocator; 3 | use nalgebra::{DefaultAllocator, DimName, Matrix1, RealField, Scalar, VectorN, U1}; 4 | 5 | pub struct PoissonEllipticOperator; 6 | 7 | impl GeneralizedEllipticOperator for PoissonEllipticOperator 8 | where 9 | T: Scalar, 10 | GeometryDim: DimName, 11 | DefaultAllocator: Allocator, 12 | { 13 | fn compute_elliptic_term(&self, gradient: &VectorN) -> VectorN { 14 | gradient.clone_owned() 15 | } 16 | } 17 | 18 | impl GeneralizedEllipticContraction for PoissonEllipticOperator 19 | where 20 | T: RealField, 21 | GeometryDim: DimName, 22 | DefaultAllocator: 23 | Allocator + Allocator + Allocator, 24 | { 25 | fn contract( 26 | &self, 27 | _gradient: &VectorN, 28 | a: &VectorN, 29 | b: &VectorN, 30 | ) -> Matrix1 { 31 | Matrix1::new(a.dot(&b)) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /fenris/examples/poisson_mms.rs: -------------------------------------------------------------------------------- 1 | mod poisson_common; 2 | use poisson_common::*; 3 | 4 | use fenris::assembly::{ 5 | apply_homogeneous_dirichlet_bc_csr, apply_homogeneous_dirichlet_bc_rhs, assemble_generalized_stiffness, 6 | assemble_source_term_into, 7 | }; 8 | use fenris::element::ElementConnectivity; 9 | use fenris::error::estimate_element_L2_error_squared; 10 | use fenris::geometry::procedural::create_rectangular_uniform_hex_mesh; 11 | use fenris::geometry::vtk::write_vtk; 12 | use fenris::mesh::Hex20Mesh; 13 | use fenris::quadrature::hex_quadrature_strength_5; 14 | use fenris::vtkio::model::Attribute; 15 | use fenris::vtkio::IOBuffer; 16 | use mkl_corrode::dss; 17 | use mkl_corrode::dss::Definiteness; 18 | use mkl_corrode::dss::MatrixStructure::Symmetric; 19 | use nalgebra::storage::Storage; 20 | use nalgebra::{DVector, DVectorSlice, DVectorSliceMut, Vector1, Vector3, U1}; 21 | use std::error::Error; 22 | use std::ops::Add; 23 | use vtkio::model::DataSet; 24 | 25 | #[allow(non_snake_case)] 26 | fn main() -> Result<(), Box> { 27 | use std::f64::consts::PI; 28 | let sin = |x| f64::sin(x); 29 | 30 | let u_exact_xyz = |x, y, z| sin(PI * x) * sin(PI * y) * sin(PI * z); 31 | let u_exact = |x: &Vector3| -> Vector1 { Vector1::new(u_exact_xyz(x.x, x.y, x.z)) }; 32 | let f_xyz = { |x, y, z| 3.0 * PI * PI * u_exact_xyz(x, y, z) }; 33 | let f = |x: &Vector3| Vector1::new(f_xyz(x.x, x.y, x.z)); 34 | let resolutions = vec![1, 2, 4, 8, 16]; 35 | 36 | for res in resolutions { 37 | let mesh = create_rectangular_uniform_hex_mesh(1.0, 1, 1, 1, res); 38 | // let mesh = Tet4Mesh::try_from(&PolyMesh3d::from(&mesh).triangulate()?)?; 39 | // let mesh = Tet20Mesh::from(&mesh); 40 | // let mesh = Tet10Mesh::from(&mesh); 41 | let mesh = Hex20Mesh::from(&mesh); 42 | let quadrature = hex_quadrature_strength_5(); 43 | // let quadrature = tet_quadrature_strength_5(); 44 | let ndof = mesh.vertices().len(); 45 | 46 | // Assemble system matrix and right hand side 47 | let qtable = |_| &quadrature; 48 | let u = DVector::zeros(ndof); 49 | let operator = PoissonEllipticOperator; 50 | let mut A = assemble_generalized_stiffness(mesh.vertices(), mesh.connectivity(), &operator, &u, &qtable) 51 | .to_csr(Add::add); 52 | let boundary_vertices: Vec<_> = mesh 53 | .vertices() 54 | .iter() 55 | .enumerate() 56 | .filter(|(_, v)| (v.coords - Vector3::new(0.5, 0.5, 0.5)).amax() >= 0.499) 57 | .map(|(i, _)| i) 58 | .collect(); 59 | 60 | let mut rhs = DVector::zeros(ndof); 61 | assemble_source_term_into(DVectorSliceMut::from(&mut rhs), &mesh, &f, &qtable); 62 | 63 | apply_homogeneous_dirichlet_bc_csr::<_, U1>(&mut A, &boundary_vertices); 64 | apply_homogeneous_dirichlet_bc_rhs(&mut rhs, &boundary_vertices, 1); 65 | 66 | let A_dss = 67 | dss::SparseMatrix::try_convert_from_csr(A.row_offsets(), A.column_indices(), A.values(), Symmetric)?; 68 | let options = dss::SolverOptions::default().parallel_reorder(true); 69 | let mut solver = dss::Solver::try_factor_with_opts(&A_dss, Definiteness::PositiveDefinite, &options)?; 70 | let u_solution = solver.solve(rhs.data.as_slice()).unwrap(); 71 | 72 | { 73 | let mut dataset = DataSet::from(&mesh); 74 | if let DataSet::UnstructuredGrid { ref mut data, .. } = dataset { 75 | let u_buffer = IOBuffer::from_slice(u_solution.as_slice()); 76 | let attribute = Attribute::Scalars { 77 | num_comp: 1, 78 | lookup_table: None, 79 | data: u_buffer, 80 | }; 81 | data.point.push((format!("u"), attribute)); 82 | } else { 83 | panic!("Unexpected data"); 84 | } 85 | 86 | write_vtk(dataset, format!("poisson_sol_{}.vtk", res), "Poisson solution")?; 87 | } 88 | 89 | // Write nodal interpolated data 90 | { 91 | let mut dataset = DataSet::from(&mesh); 92 | let u_nodal_interpolation: Vec<_> = mesh 93 | .vertices() 94 | .iter() 95 | .map(|v| u_exact(&v.coords).x) 96 | .collect(); 97 | if let DataSet::UnstructuredGrid { ref mut data, .. } = dataset { 98 | let u_buffer = IOBuffer::from_slice(u_nodal_interpolation.as_slice()); 99 | let attribute = Attribute::Scalars { 100 | num_comp: 1, 101 | lookup_table: None, 102 | data: u_buffer, 103 | }; 104 | data.point.push((format!("u"), attribute)); 105 | } else { 106 | panic!("Unexpected data"); 107 | } 108 | 109 | write_vtk( 110 | dataset, 111 | format!("poisson_nodal_interpolation_{}.vtk", res), 112 | "Poisson solution", 113 | )?; 114 | } 115 | 116 | let l2_error_squared = mesh 117 | .connectivity() 118 | .iter() 119 | .map(|conn| { 120 | let u_weights = 121 | conn.element_variables(DVectorSlice::from_slice(u_solution.as_slice(), u_solution.len())); 122 | let element = conn.element(mesh.vertices()).unwrap(); 123 | estimate_element_L2_error_squared(&element, |x, _| u_exact(&x.coords), &u_weights, &quadrature) 124 | }) 125 | .sum::(); 126 | let l2_error = l2_error_squared.sqrt(); 127 | 128 | println!("L2 error: {:3.3e}", l2_error); 129 | } 130 | 131 | Ok(()) 132 | } 133 | -------------------------------------------------------------------------------- /fenris/examples/polymesh_intersection.rs: -------------------------------------------------------------------------------- 1 | use fenris::geometry::polymesh::PolyMesh3d; 2 | use fenris::geometry::procedural::create_rectangular_uniform_hex_mesh; 3 | use fenris::geometry::vtk::write_vtk; 4 | use fenris::geometry::{ConvexPolyhedron, HalfSpace, Tetrahedron}; 5 | use fenris::mesh::Tet4Mesh; 6 | use nalgebra::{Point3, Unit, Vector3}; 7 | use nested_vec::NestedVec; 8 | use std::convert::TryFrom; 9 | use std::error::Error; 10 | 11 | fn tetrahedron_vertices() -> [Point3; 4] { 12 | [ 13 | Point3::new(0.1, 0.1, 0.1), 14 | Point3::new(0.9, 0.1, 0.1), 15 | Point3::new(0.1, 0.9, 0.1), 16 | Point3::new(0.1, 0.1, 0.9), 17 | ] 18 | } 19 | 20 | fn tetrahedron() -> PolyMesh3d { 21 | let vertices = tetrahedron_vertices().to_vec(); 22 | let faces = vec![vec![0, 2, 1], vec![0, 1, 3], vec![1, 2, 3], vec![0, 3, 2]]; 23 | let cells = vec![vec![0, 1, 2, 3]]; 24 | PolyMesh3d::from_poly_data(vertices, NestedVec::from(&faces), NestedVec::from(&cells)) 25 | } 26 | 27 | fn intersect_meshes_with_single_half_space() -> Result<(), Box> { 28 | let cube = create_rectangular_uniform_hex_mesh(1.0, 2, 1, 1, 1); 29 | let cube = PolyMesh3d::from(&cube); 30 | 31 | // Intersect meshes with half space 32 | let meshes = vec![("cube", cube), ("tetrahedron", tetrahedron())]; 33 | 34 | for (name, mesh) in meshes { 35 | let half_space = HalfSpace::from_point_and_normal( 36 | Point3::new(0.0, 0.0, 0.3), 37 | Unit::new_normalize(Vector3::new(0.0, 0.0, 1.0)), 38 | ); 39 | 40 | let intersection = mesh.intersect_half_space(&half_space); 41 | 42 | let base_path = "data/polymesh_intersection/halfspace"; 43 | let original_mesh_file_name = format!("{}/{}.vtk", base_path, name); 44 | let intersection_file_name = format!("{}/{}_intersection.vtk", base_path, name); 45 | 46 | write_vtk(&mesh, original_mesh_file_name, "polymesh intersection")?; 47 | write_vtk(&intersection, intersection_file_name, "polymesh intersection")?; 48 | 49 | println!("Original mesh: {}", mesh); 50 | println!("Intersection: {}", intersection); 51 | } 52 | Ok(()) 53 | } 54 | 55 | fn intersect_polyhedron_with_mesh<'a>( 56 | mesh_name: &str, 57 | polyhedron_name: &str, 58 | polyhedron: &impl ConvexPolyhedron<'a, f64>, 59 | mesh: &PolyMesh3d, 60 | ) -> Result<(), Box> { 61 | let intersection = mesh.intersect_convex_polyhedron(polyhedron); 62 | 63 | let base_path = "data/polymesh_intersection/polyhedra"; 64 | let original_mesh_file_name = format!("{}/{}.vtk", base_path, mesh_name); 65 | let intersection_file_name = format!("{}/{}_{}_intersection.vtk", base_path, mesh_name, polyhedron_name); 66 | let tet_mesh_file_name = format!( 67 | "{}/{}_{}_intersection_tet_mesh.vtk", 68 | base_path, mesh_name, polyhedron_name 69 | ); 70 | 71 | write_vtk(mesh, original_mesh_file_name, "polymesh intersection")?; 72 | write_vtk(&intersection, intersection_file_name, "polymesh intersection")?; 73 | 74 | println!("Original mesh: {}", mesh); 75 | println!("Intersection: {}", intersection); 76 | 77 | let tet_mesh = Tet4Mesh::try_from(&intersection.triangulate()?)?; 78 | write_vtk(&tet_mesh, tet_mesh_file_name, "tet mesh")?; 79 | 80 | Ok(()) 81 | } 82 | 83 | fn intersect_meshes_with_polyhedra() -> Result<(), Box> { 84 | let cube = create_rectangular_uniform_hex_mesh(1.0, 1, 1, 1, 2); 85 | let cube = PolyMesh3d::from(&cube); 86 | 87 | // Intersect meshes with half space 88 | let meshes = vec![("cube", cube), ("tetrahedron", tetrahedron())]; 89 | 90 | { 91 | let tet = Tetrahedron::from_vertices(tetrahedron_vertices()); 92 | let name = "tet"; 93 | for (mesh_name, mesh) in &meshes { 94 | intersect_polyhedron_with_mesh(mesh_name, name, &tet, mesh)?; 95 | } 96 | } 97 | 98 | Ok(()) 99 | } 100 | 101 | fn main() -> Result<(), Box> { 102 | intersect_meshes_with_single_half_space()?; 103 | intersect_meshes_with_polyhedra()?; 104 | 105 | Ok(()) 106 | } 107 | -------------------------------------------------------------------------------- /fenris/src/error.rs: -------------------------------------------------------------------------------- 1 | //! Functionality for error estimation. 2 | 3 | use crate::allocators::VolumeFiniteElementAllocator; 4 | use crate::element::FiniteElement; 5 | use crate::quadrature::{Quadrature, Quadrature2d}; 6 | use nalgebra::allocator::Allocator; 7 | use nalgebra::{DefaultAllocator, DimMin, DimName, DimNameMul, MatrixMN, Point, RealField, VectorN, U1, U2}; 8 | 9 | /// Estimate the squared L^2 error of `u_h - u` on the given element with the given basis 10 | /// weights and quadrature points. 11 | /// 12 | /// `u(x, i)` represents the value of `u` at physical coordinate `x`. `i` is the index of the 13 | /// quadrature point. 14 | /// 15 | /// More precisely, estimate the integral of `dot(u_h - u, u_h - u)`, where `u_h = u_i N_i`, 16 | /// with `u_i` the `i`-th column in `u` denoting the `m`-dimensional weight associated with node `i`, 17 | /// and `N_i` is the basis function associated with node `i`. 18 | #[allow(non_snake_case)] 19 | pub fn estimate_element_L2_error_squared( 20 | element: &Element, 21 | u: impl Fn(&Point, usize) -> VectorN, 22 | u_weights: &MatrixMN, 23 | quadrature: impl Quadrature, 24 | ) -> T 25 | where 26 | T: RealField, 27 | Element: FiniteElement, 28 | GeometryDim: DimName + DimMin, 29 | SolutionDim: DimNameMul, 30 | DefaultAllocator: VolumeFiniteElementAllocator 31 | + Allocator 32 | + Allocator, 33 | { 34 | let weights = quadrature.weights(); 35 | let points = quadrature.points(); 36 | 37 | use itertools::izip; 38 | 39 | let mut result = T::zero(); 40 | for (i, (w, xi)) in izip!(weights, points).enumerate() { 41 | let x = element.map_reference_coords(xi); 42 | let j = element.reference_jacobian(xi); 43 | let g = element.evaluate_basis(xi); 44 | let u_h = u_weights * g.transpose(); 45 | let u_at_x = u(&Point::from(x), i); 46 | let error = u_h - u_at_x; 47 | let error2 = error.dot(&error); 48 | result += error2 * *w * j.determinant().abs(); 49 | } 50 | result 51 | } 52 | 53 | #[allow(non_snake_case)] 54 | pub fn estimate_element_L2_error( 55 | element: &Element, 56 | u: impl Fn(&Point, usize) -> VectorN, 57 | u_weights: &MatrixMN, 58 | quadrature: impl Quadrature, 59 | ) -> T 60 | where 61 | T: RealField, 62 | Element: FiniteElement, 63 | SolutionDim: DimNameMul, 64 | GeometryDim: DimName + DimMin, 65 | DefaultAllocator: VolumeFiniteElementAllocator 66 | + Allocator 67 | + Allocator, 68 | { 69 | estimate_element_L2_error_squared(element, u, u_weights, quadrature).sqrt() 70 | } 71 | 72 | /// Estimate the squared L^2 norm on the given element with the given basis weights and quadrature 73 | /// points. 74 | /// 75 | /// More precisely, compute the integral of `dot(u_h, u_h)`, where `u_h = u_i N_i`, with `u_i`, 76 | /// the `i`-th column in `u`, denoting the `m`-dimensional weight associated with node `i`, 77 | /// and `N_i` is the basis function associated with node `i`. 78 | #[allow(non_snake_case)] 79 | pub fn estimate_element_L2_norm_squared( 80 | element: &Element, 81 | u_weights: &MatrixMN, 82 | quadrature: impl Quadrature2d, 83 | ) -> T 84 | where 85 | T: RealField, 86 | Element: FiniteElement, 87 | SolutionDim: DimNameMul, 88 | DefaultAllocator: VolumeFiniteElementAllocator 89 | + Allocator 90 | + Allocator, 91 | { 92 | estimate_element_L2_error_squared( 93 | element, 94 | |_, _| VectorN::::repeat(T::zero()), 95 | u_weights, 96 | quadrature, 97 | ) 98 | } 99 | 100 | #[allow(non_snake_case)] 101 | pub fn estimate_element_L2_norm( 102 | element: &Element, 103 | u: &MatrixMN, 104 | quadrature: impl Quadrature2d, 105 | ) -> T 106 | where 107 | T: RealField, 108 | Element: FiniteElement, 109 | SolutionDim: DimNameMul, 110 | DefaultAllocator: VolumeFiniteElementAllocator 111 | + Allocator 112 | + Allocator, 113 | { 114 | estimate_element_L2_norm_squared(element, u, quadrature).sqrt() 115 | } 116 | -------------------------------------------------------------------------------- /fenris/src/geometry/sdf.rs: -------------------------------------------------------------------------------- 1 | use nalgebra::{Point2, RealField, Scalar, Vector2, U2}; 2 | 3 | use crate::geometry::{AxisAlignedBoundingBox2d, BoundedGeometry}; 4 | use numeric_literals::replace_float_literals; 5 | 6 | pub trait SignedDistanceFunction2d 7 | where 8 | T: Scalar, 9 | { 10 | fn eval(&self, x: &Point2) -> T; 11 | fn gradient(&self, x: &Point2) -> Option>; 12 | 13 | fn union(self, other: Other) -> SdfUnion 14 | where 15 | Self: Sized, 16 | Other: Sized + SignedDistanceFunction2d, 17 | { 18 | SdfUnion { 19 | left: self, 20 | right: other, 21 | } 22 | } 23 | } 24 | 25 | pub trait BoundedSdf: SignedDistanceFunction2d + BoundedGeometry 26 | where 27 | T: Scalar, 28 | { 29 | } 30 | 31 | impl BoundedSdf for X 32 | where 33 | T: Scalar, 34 | X: SignedDistanceFunction2d + BoundedGeometry, 35 | { 36 | } 37 | 38 | #[derive(Copy, Clone, Debug)] 39 | pub struct SdfCircle 40 | where 41 | T: Scalar, 42 | { 43 | pub radius: T, 44 | pub center: Vector2, 45 | } 46 | 47 | #[derive(Copy, Clone, Debug)] 48 | pub struct SdfUnion { 49 | pub left: Left, 50 | pub right: Right, 51 | } 52 | 53 | #[derive(Copy, Clone, Debug)] 54 | pub struct SdfAxisAlignedBox 55 | where 56 | T: Scalar, 57 | { 58 | pub aabb: AxisAlignedBoundingBox2d, 59 | } 60 | 61 | impl BoundedGeometry for SdfCircle 62 | where 63 | T: RealField, 64 | { 65 | type Dimension = U2; 66 | 67 | fn bounding_box(&self) -> AxisAlignedBoundingBox2d { 68 | let eps = self.radius * T::from_f64(0.01).unwrap(); 69 | AxisAlignedBoundingBox2d::new( 70 | self.center - Vector2::repeat(T::one()) * (self.radius + eps), 71 | self.center + Vector2::repeat(T::one()) * (self.radius + eps), 72 | ) 73 | } 74 | } 75 | 76 | impl SignedDistanceFunction2d for SdfCircle 77 | where 78 | T: RealField, 79 | { 80 | fn eval(&self, x: &Point2) -> T { 81 | let y = x - self.center; 82 | y.coords.norm() - self.radius 83 | } 84 | 85 | fn gradient(&self, x: &Point2) -> Option> { 86 | let y = x - self.center; 87 | let y_norm = y.coords.norm(); 88 | 89 | if y_norm == T::zero() { 90 | None 91 | } else { 92 | Some(y.coords / y_norm) 93 | } 94 | } 95 | } 96 | 97 | impl BoundedGeometry for SdfUnion 98 | where 99 | T: RealField, 100 | Left: BoundedGeometry, 101 | Right: BoundedGeometry, 102 | { 103 | type Dimension = U2; 104 | 105 | fn bounding_box(&self) -> AxisAlignedBoundingBox2d { 106 | self.left.bounding_box().enclose(&self.right.bounding_box()) 107 | } 108 | } 109 | 110 | impl SignedDistanceFunction2d for SdfUnion 111 | where 112 | T: RealField, 113 | Left: SignedDistanceFunction2d, 114 | Right: SignedDistanceFunction2d, 115 | { 116 | fn eval(&self, x: &Point2) -> T { 117 | self.left.eval(x).min(self.right.eval(x)) 118 | } 119 | 120 | fn gradient(&self, x: &Point2) -> Option> { 121 | // TODO: Is this actually correct? It might give funky results if exactly 122 | // at points where either SDF is non-differentiable 123 | if self.left.eval(x) < self.right.eval(x) { 124 | self.left.gradient(x) 125 | } else { 126 | self.right.gradient(x) 127 | } 128 | } 129 | } 130 | 131 | impl BoundedGeometry for SdfAxisAlignedBox 132 | where 133 | T: RealField, 134 | { 135 | type Dimension = U2; 136 | 137 | fn bounding_box(&self) -> AxisAlignedBoundingBox2d { 138 | self.aabb 139 | } 140 | } 141 | 142 | impl SignedDistanceFunction2d for SdfAxisAlignedBox 143 | where 144 | T: RealField, 145 | { 146 | #[replace_float_literals(T::from_f64(literal).unwrap())] 147 | fn eval(&self, x: &Point2) -> T { 148 | let b = self.aabb.extents() / 2.0; 149 | let p = x - self.aabb.center(); 150 | let d = p.abs() - b; 151 | 152 | // TODO: Use d.max() when fixed. See https://github.com/rustsim/nalgebra/issues/620 153 | d.sup(&Vector2::zeros()).norm() + T::min(T::zero(), d[d.imax()]) 154 | } 155 | 156 | #[replace_float_literals(T::from_f64(literal).expect("Literal must fit in T"))] 157 | fn gradient(&self, x: &Point2) -> Option> { 158 | // TODO: Replace finite differences with "proper" gradient 159 | // Note: arbitrary "step"/resolution h 160 | let h = 1e-4; 161 | let mut gradient = Vector2::zeros(); 162 | for i in 0..2 { 163 | let mut dx = Vector2::zeros(); 164 | dx[i] = h; 165 | gradient[i] = (self.eval(&(x + dx)) - self.eval(&(x - dx))) / (2.0 * h) 166 | } 167 | gradient.normalize_mut(); 168 | Some(gradient) 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /fenris/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod allocators; 2 | pub mod assembly; 3 | pub mod cg; 4 | pub mod connectivity; 5 | pub mod element; 6 | pub mod embedding; 7 | pub mod error; 8 | pub mod geometry; 9 | pub mod lp_solvers; 10 | pub mod model; 11 | pub mod quadrature; 12 | pub mod reorder; 13 | pub mod rtree; 14 | pub mod solid; 15 | pub mod space; 16 | pub mod sparse; 17 | pub mod util; 18 | 19 | #[cfg(feature = "proptest")] 20 | pub mod proptest; 21 | 22 | // TODO: Don't export 23 | pub use sparse::CooMatrix; 24 | pub use sparse::CsrMatrix; 25 | 26 | pub mod mesh; 27 | 28 | mod mesh_convert; 29 | mod space_impl; 30 | 31 | pub extern crate nalgebra; 32 | pub extern crate nested_vec; 33 | pub extern crate vtkio; 34 | -------------------------------------------------------------------------------- /fenris/src/lp_solvers.rs: -------------------------------------------------------------------------------- 1 | use crate::embedding::LpSolver; 2 | use crate::nalgebra::{DMatrix, DVector}; 3 | use lp_bfp::{solve_lp, Verbosity}; 4 | use std::error::Error; 5 | use std::f64; 6 | 7 | /// A basic feasible point solver powered by Google's GLOP LP solver. 8 | #[derive(Debug, Clone)] 9 | pub struct GlopSolver { 10 | verbosity: Verbosity, 11 | } 12 | 13 | impl GlopSolver { 14 | pub fn new() -> Self { 15 | Self { 16 | verbosity: Verbosity::NoVerbose, 17 | } 18 | } 19 | 20 | pub fn new_verbose() -> Self { 21 | Self { 22 | verbosity: Verbosity::Verbose, 23 | } 24 | } 25 | } 26 | 27 | impl LpSolver for GlopSolver { 28 | fn solve_lp( 29 | &self, 30 | c: &DVector, 31 | a: &DMatrix, 32 | b: &DVector, 33 | lb: &[Option], 34 | ub: &[Option], 35 | ) -> Result, Box> { 36 | let a_elements_row_major: Vec<_> = a.transpose().iter().copied().collect(); 37 | let lb: Vec<_> = lb 38 | .iter() 39 | .copied() 40 | .map(|lb_i| lb_i.unwrap_or(-f64::INFINITY)) 41 | .collect(); 42 | let ub: Vec<_> = ub 43 | .iter() 44 | .copied() 45 | .map(|ub_i| ub_i.unwrap_or(f64::INFINITY)) 46 | .collect(); 47 | let bfp = solve_lp( 48 | c.as_slice(), 49 | &a_elements_row_major, 50 | b.as_slice(), 51 | &lb, 52 | &ub, 53 | self.verbosity, 54 | )?; 55 | Ok(DVector::from_column_slice(&bfp)) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /fenris/src/proptest.rs: -------------------------------------------------------------------------------- 1 | use proptest::prelude::*; 2 | 3 | use crate::element::Tet4Element; 4 | use crate::geometry::Orientation::Counterclockwise; 5 | use crate::geometry::{Orientation, Triangle, Triangle2d, Triangle3d}; 6 | use nalgebra::{Point2, Point3}; 7 | 8 | pub fn point2() -> impl Strategy> { 9 | // Pick a reasonably small range to pick coordinates from, 10 | // otherwise we can easily get floating point numbers that are 11 | // so ridiculously large as to break anything we might want to do with them 12 | let range = -10.0..10.0; 13 | [range.clone(), range.clone()].prop_map(|[x, y]| Point2::new(x, y)) 14 | } 15 | 16 | pub fn point3() -> impl Strategy> { 17 | // Pick a reasonably small range to pick coordinates from, 18 | // otherwise we can easily get floating point numbers that are 19 | // so ridiculously large as to break anything we might want to do with them 20 | let range = -10.0..10.0; 21 | [range.clone(), range.clone(), range.clone()].prop_map(|[x, y, z]| Point3::new(x, y, z)) 22 | } 23 | 24 | #[derive(Debug, Clone)] 25 | pub struct Triangle3dParams { 26 | orientation: Orientation, 27 | } 28 | 29 | impl Triangle3dParams { 30 | pub fn with_orientation(self, orientation: Orientation) -> Self { 31 | Self { orientation, ..self } 32 | } 33 | } 34 | 35 | impl Default for Triangle3dParams { 36 | fn default() -> Self { 37 | Self { 38 | orientation: Counterclockwise, 39 | } 40 | } 41 | } 42 | 43 | #[derive(Debug, Clone)] 44 | pub struct Triangle2dParams { 45 | orientation: Orientation, 46 | } 47 | 48 | impl Triangle2dParams { 49 | pub fn with_orientation(self, orientation: Orientation) -> Self { 50 | Self { orientation, ..self } 51 | } 52 | } 53 | 54 | impl Default for Triangle2dParams { 55 | fn default() -> Self { 56 | Self { 57 | orientation: Counterclockwise, 58 | } 59 | } 60 | } 61 | 62 | impl Arbitrary for Triangle3d { 63 | // TODO: Parameter for extents (i.e. bounding box or so) 64 | type Parameters = Triangle3dParams; // TODO: Avoid boxing for performance...? 65 | type Strategy = BoxedStrategy; 66 | 67 | fn arbitrary_with(args: Self::Parameters) -> Self::Strategy { 68 | let points = [point3(), point3(), point3()]; 69 | points 70 | .prop_map(|points| Triangle(points)) 71 | .prop_map(move |mut triangle| { 72 | if triangle.orientation() != args.orientation { 73 | triangle.swap_vertices(0, 1); 74 | } 75 | triangle 76 | }) 77 | .boxed() 78 | } 79 | } 80 | 81 | impl Arbitrary for Triangle2d { 82 | // TODO: Parameter for extents (i.e. bounding box or so) 83 | type Parameters = Triangle2dParams; // TODO: Avoid boxing for performance...? 84 | type Strategy = BoxedStrategy; 85 | 86 | fn arbitrary_with(args: Self::Parameters) -> Self::Strategy { 87 | let points = [point2(), point2(), point2()]; 88 | points 89 | .prop_map(|points| Triangle(points)) 90 | .prop_map(move |mut triangle| { 91 | if triangle.orientation() != args.orientation { 92 | triangle.swap_vertices(0, 1); 93 | } 94 | triangle 95 | }) 96 | .boxed() 97 | } 98 | } 99 | 100 | impl Arbitrary for Tet4Element { 101 | // TODO: Reasonable parameters? 102 | type Parameters = (); 103 | type Strategy = BoxedStrategy; 104 | 105 | fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { 106 | any_with::>(Triangle3dParams::default().with_orientation(Counterclockwise)) 107 | .prop_flat_map(|triangle| { 108 | // To create an arbitrary tetrahedron element, we take a counter-clockwise oriented 109 | // triangle, and pick a point somewhere on the "positive" side of the 110 | // triangle plane. We do this by associating a parameter with each 111 | // tangent vector defined by the sides of the triangle, 112 | // plus a non-negative parameter that scales along the normal direction 113 | let range = -10.0..10.0; 114 | let tangent_params = [range.clone(), range.clone(), range.clone()]; 115 | let normal_param = 0.0..=10.0; 116 | (Just(triangle), tangent_params, normal_param) 117 | }) 118 | .prop_map(|(triangle, tangent_params, normal_param)| { 119 | let mut tangent_pos = triangle.centroid(); 120 | 121 | for (side, param) in triangle.sides().iter().zip(&tangent_params) { 122 | tangent_pos.coords += *param * side; 123 | } 124 | let coord = tangent_pos + normal_param * triangle.normal_dir().normalize(); 125 | Tet4Element::from_vertices([triangle.0[0], triangle.0[1], triangle.0[2], coord]) 126 | }) 127 | .boxed() 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /fenris/src/space.rs: -------------------------------------------------------------------------------- 1 | use crate::allocators::ElementConnectivityAllocator; 2 | use crate::element::{ConnectivityGeometryDim, ElementConnectivity, ElementForSpace}; 3 | use crate::geometry::GeometryCollection; 4 | use nalgebra::{DefaultAllocator, Point, Scalar}; 5 | 6 | pub trait FiniteElementSpace 7 | where 8 | T: Scalar, 9 | DefaultAllocator: ElementConnectivityAllocator, 10 | { 11 | type Connectivity: ElementConnectivity; 12 | 13 | fn vertices(&self) -> &[Point>]; 14 | 15 | fn num_connectivities(&self) -> usize; 16 | 17 | fn get_connectivity(&self, index: usize) -> Option<&Self::Connectivity>; 18 | 19 | fn get_element(&self, index: usize) -> Option> { 20 | self.get_connectivity(index) 21 | .map(|conn| conn.element(self.vertices()).unwrap()) 22 | } 23 | } 24 | 25 | /// A finite element space whose elements can be seen as a collection of geometric entities. 26 | /// 27 | /// This trait essentially functions as a marker trait for finite element spaces which can 28 | /// also be interpreted as a collection of geometry objects, with a 1:1 correspondence between 29 | /// elements and geometries. 30 | pub trait GeometricFiniteElementSpace<'a, T>: FiniteElementSpace + GeometryCollection<'a> 31 | where 32 | T: Scalar, 33 | DefaultAllocator: ElementConnectivityAllocator, 34 | { 35 | } 36 | -------------------------------------------------------------------------------- /fenris/src/space_impl.rs: -------------------------------------------------------------------------------- 1 | use crate::allocators::ElementConnectivityAllocator; 2 | use crate::connectivity::CellConnectivity; 3 | use crate::element::ElementConnectivity; 4 | use crate::embedding::EmbeddedModel; 5 | use crate::mesh::{ClosedSurfaceMesh2d, Mesh, Mesh2d}; 6 | use crate::model::NodalModel; 7 | use crate::space::{FiniteElementSpace, GeometricFiniteElementSpace}; 8 | use nalgebra::{DefaultAllocator, DimName, Point, Point2, RealField, Scalar, U2}; 9 | 10 | impl FiniteElementSpace for Mesh 11 | where 12 | T: Scalar, 13 | D: DimName, 14 | C: ElementConnectivity, 15 | DefaultAllocator: ElementConnectivityAllocator, 16 | { 17 | type Connectivity = C; 18 | 19 | fn vertices(&self) -> &[Point] { 20 | self.vertices() 21 | } 22 | 23 | fn num_connectivities(&self) -> usize { 24 | self.connectivity().len() 25 | } 26 | 27 | fn get_connectivity(&self, index: usize) -> Option<&Self::Connectivity> { 28 | self.connectivity().get(index) 29 | } 30 | } 31 | 32 | impl<'a, T, D, C> GeometricFiniteElementSpace<'a, T> for Mesh 33 | where 34 | T: Scalar, 35 | D: DimName, 36 | C: CellConnectivity + ElementConnectivity, 37 | DefaultAllocator: ElementConnectivityAllocator, 38 | { 39 | } 40 | 41 | impl FiniteElementSpace for ClosedSurfaceMesh2d 42 | where 43 | T: RealField, 44 | Connectivity: ElementConnectivity, 45 | DefaultAllocator: ElementConnectivityAllocator, 46 | { 47 | type Connectivity = as FiniteElementSpace>::Connectivity; 48 | 49 | fn vertices(&self) -> &[Point2] { 50 | self.mesh().vertices() 51 | } 52 | 53 | fn num_connectivities(&self) -> usize { 54 | self.mesh().connectivity().len() 55 | } 56 | 57 | fn get_connectivity(&self, index: usize) -> Option<&Self::Connectivity> { 58 | self.mesh().connectivity().get(index) 59 | } 60 | } 61 | 62 | impl<'a, T, C> GeometricFiniteElementSpace<'a, T> for ClosedSurfaceMesh2d 63 | where 64 | T: RealField, 65 | C: CellConnectivity + ElementConnectivity, 66 | DefaultAllocator: ElementConnectivityAllocator, 67 | { 68 | } 69 | 70 | impl FiniteElementSpace for NodalModel 71 | where 72 | T: Scalar, 73 | D: DimName, 74 | C: ElementConnectivity, 75 | DefaultAllocator: ElementConnectivityAllocator, 76 | { 77 | type Connectivity = C; 78 | 79 | fn vertices(&self) -> &[Point] { 80 | self.vertices() 81 | } 82 | 83 | fn num_connectivities(&self) -> usize { 84 | self.connectivity().len() 85 | } 86 | 87 | fn get_connectivity(&self, index: usize) -> Option<&Self::Connectivity> { 88 | self.connectivity().get(index) 89 | } 90 | } 91 | 92 | impl<'a, T, D, C> GeometricFiniteElementSpace<'a, T> for NodalModel 93 | where 94 | T: Scalar, 95 | D: DimName, 96 | C: CellConnectivity + ElementConnectivity, 97 | DefaultAllocator: ElementConnectivityAllocator, 98 | { 99 | } 100 | 101 | impl FiniteElementSpace for EmbeddedModel 102 | where 103 | T: Scalar, 104 | D: DimName, 105 | C: ElementConnectivity, 106 | DefaultAllocator: ElementConnectivityAllocator, 107 | { 108 | type Connectivity = C; 109 | 110 | fn vertices(&self) -> &[Point] { 111 | self.vertices() 112 | } 113 | 114 | fn num_connectivities(&self) -> usize { 115 | self.interior_connectivity().len() + self.interface_connectivity().len() 116 | } 117 | 118 | fn get_connectivity(&self, index: usize) -> Option<&Self::Connectivity> { 119 | let num_interior_connectivity = self.interior_connectivity().len(); 120 | 121 | if index >= num_interior_connectivity { 122 | // Interface connectivity 123 | let interface_index = index - num_interior_connectivity; 124 | self.interface_connectivity().get(interface_index) 125 | } else { 126 | let interior_index = index; 127 | self.interior_connectivity().get(interior_index) 128 | } 129 | } 130 | } 131 | 132 | impl<'a, T, D, C> GeometricFiniteElementSpace<'a, T> for EmbeddedModel 133 | where 134 | T: Scalar, 135 | D: DimName, 136 | C: CellConnectivity + ElementConnectivity, 137 | DefaultAllocator: ElementConnectivityAllocator, 138 | { 139 | } 140 | -------------------------------------------------------------------------------- /fenris/tests/integration.rs: -------------------------------------------------------------------------------- 1 | mod integration_tests; 2 | 3 | #[macro_use] 4 | pub mod utils; 5 | -------------------------------------------------------------------------------- /fenris/tests/integration_tests/cg_fem.rs: -------------------------------------------------------------------------------- 1 | //! Tests for CG applied to finite element matrices. 2 | 3 | use crate::assert_approx_matrix_eq; 4 | use fenris::cg::{CgWorkspace, ConjugateGradient, RelativeResidualCriterion}; 5 | use fenris::geometry::procedural::create_rectangular_uniform_hex_mesh; 6 | use fenris::model::NodalModel3d; 7 | use fenris::nalgebra::DVector; 8 | use fenris::quadrature::hex_quadrature_strength_5; 9 | use fenris::solid::materials::{LinearElasticMaterial, YoungPoisson}; 10 | use fenris::solid::ElasticityModel; 11 | use fenris::CsrMatrix; 12 | use std::ops::Add; 13 | 14 | #[test] 15 | fn cg_linear_elasticity_dynamic_regular_grid() { 16 | let mesh = create_rectangular_uniform_hex_mesh(1.0f64, 1, 1, 1, 5); 17 | let model = NodalModel3d::from_mesh_and_quadrature(mesh, hex_quadrature_strength_5()); 18 | 19 | // Use linear elastic material since it is guaranteed to give positive semi-definite 20 | // stiffness matrices 21 | let material = LinearElasticMaterial::from(YoungPoisson { 22 | young: 1e4, 23 | poisson: 0.48, 24 | }); 25 | 26 | let u = DVector::zeros(model.ndof()); 27 | let mass = model.assemble_mass(1.0).to_csr(Add::add); 28 | let mut stiffness = CsrMatrix::from_pattern_and_values(mass.sparsity_pattern(), vec![0.0; mass.nnz()]); 29 | model.assemble_stiffness_into(&mut stiffness, &u, &material); 30 | 31 | let dt = 0.1; 32 | let system_matrix = mass + stiffness * (dt * dt); 33 | 34 | let solutions = vec![ 35 | DVector::repeat(model.ndof(), 1.0), 36 | DVector::repeat(model.ndof(), 2.0), 37 | DVector::repeat(model.ndof(), 3.0), 38 | ]; 39 | 40 | // Use a workspace and solve several systems to test that the internal state of the workspace 41 | // does not impact the solutions 42 | let mut cg_workspace = CgWorkspace::default(); 43 | 44 | for x0 in solutions { 45 | let b = &system_matrix * &x0; 46 | 47 | let mut x_workspace = DVector::repeat(model.ndof(), 0.0); 48 | let output_workspace = ConjugateGradient::with_workspace(&mut cg_workspace) 49 | .with_operator(&system_matrix) 50 | .with_stopping_criterion(RelativeResidualCriterion::new(1e-8)) 51 | .solve_with_guess(&b, &mut x_workspace) 52 | .unwrap(); 53 | 54 | let mut x_no_workspace = DVector::repeat(model.ndof(), 0.0); 55 | let output_no_workspace = ConjugateGradient::new() 56 | .with_operator(&system_matrix) 57 | .with_stopping_criterion(RelativeResidualCriterion::new(1e-8)) 58 | .solve_with_guess(&b, &mut x_no_workspace) 59 | .unwrap(); 60 | 61 | // Results should be exactly the same regardless of whether we use a workspace or not. 62 | assert_eq!(x_no_workspace, x_workspace); 63 | assert_eq!(output_workspace.num_iterations, output_no_workspace.num_iterations); 64 | let x = x_no_workspace; 65 | assert_approx_matrix_eq!(&x, &x0, abstol = 1e-7); 66 | 67 | println!( 68 | "Matrix size: {}x{} ({} nnz)", 69 | system_matrix.nrows(), 70 | system_matrix.ncols(), 71 | system_matrix.nnz() 72 | ); 73 | println!("Num CG iterations: {}", output_no_workspace.num_iterations); 74 | 75 | let system_diagonal = DVector::from_iterator(model.ndof(), system_matrix.diag_iter().map(|d_i| d_i.recip())); 76 | let diagonal_preconditioner = CsrMatrix::from_diagonal(&system_diagonal); 77 | let mut x = DVector::repeat(model.ndof(), 0.0); 78 | let output_preconditioned = ConjugateGradient::new() 79 | .with_operator(&system_matrix) 80 | .with_preconditioner(&diagonal_preconditioner) 81 | .with_stopping_criterion(RelativeResidualCriterion::new(1e-8)) 82 | .solve_with_guess(&b, &mut x) 83 | .unwrap(); 84 | assert_approx_matrix_eq!(&x, &x0, abstol = 1e-7); 85 | 86 | println!( 87 | "Num CG iterations (diagonal precond): {}", 88 | output_preconditioned.num_iterations 89 | ); 90 | 91 | // For these particular matrices we expect the diagonal preconditioner to 92 | // yield less iterations 93 | assert!(output_preconditioned.num_iterations < output_workspace.num_iterations); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /fenris/tests/integration_tests/embedding.rs: -------------------------------------------------------------------------------- 1 | use fenris::element::{Hex8Element, Quad4d2Element}; 2 | use fenris::embedding::{ 3 | compute_element_embedded_quadrature, construct_embedded_quadrature_for_element_2d, embed_cell_2d, 4 | optimize_quadrature, QuadratureOptions, 5 | }; 6 | use fenris::geometry::procedural::{approximate_triangle_mesh_for_sdf_2d, create_simple_stupid_sphere}; 7 | use fenris::geometry::sdf::SdfCircle; 8 | use fenris::geometry::{ConvexPolygon, Hexahedron, Quad2d}; 9 | use fenris::lp_solvers::GlopSolver; 10 | use fenris::quadrature::{tet_quadrature_strength_10, tri_quadrature_strength_11, Quadrature}; 11 | use matrixcompare::assert_scalar_eq; 12 | use nalgebra::{Point2, Point3, Vector2, Vector3}; 13 | 14 | use std::convert::TryFrom; 15 | 16 | #[test] 17 | fn optimize_quadrature_2d() { 18 | let quad = Quad2d([ 19 | Point2::new(0.0, 2.0), 20 | Point2::new(2.0, 2.0), 21 | Point2::new(2.0, 3.0), 22 | Point2::new(0.0, 3.0), 23 | ]); 24 | 25 | let circle = SdfCircle { 26 | radius: 1.0, 27 | center: Vector2::new(2.0, 3.0), 28 | }; 29 | let quad_polygon = ConvexPolygon::try_from(quad).unwrap(); 30 | 31 | let element = Quad4d2Element::from(quad); 32 | let triangle_mesh = approximate_triangle_mesh_for_sdf_2d(&circle, 0.05); 33 | let embedded_polygons = embed_cell_2d(&quad_polygon, &triangle_mesh).unwrap(); 34 | let quadrature = 35 | construct_embedded_quadrature_for_element_2d(&element, &embedded_polygons, tri_quadrature_strength_11()); 36 | let quadrature_opt = optimize_quadrature(&quadrature, 9, &GlopSolver::new()).unwrap(); 37 | 38 | println!( 39 | "Num quadrature points before optimization: {}", 40 | quadrature.points().len() 41 | ); 42 | println!( 43 | "Num quadrature points after optimization: {}", 44 | quadrature_opt.points().len() 45 | ); 46 | 47 | let f = |x: f64, y: f64| { 48 | -1.0 * x.powi(5) * y.powi(4) + 2.0 * x * y - 5.0 * x.powi(4) * y.powi(5) 49 | + 2.5 * x.powi(1) * y.powi(8) 50 | + x * y 51 | + 2.0 52 | }; 53 | 54 | let f = |p: &Vector2| f(p.x, p.y); 55 | 56 | let original_integral: f64 = quadrature.integrate(f); 57 | let optimized_integral: f64 = quadrature_opt.integrate(f); 58 | 59 | assert_scalar_eq!(original_integral, optimized_integral, comp = abs, tol = 1e-9); 60 | } 61 | 62 | #[test] 63 | fn optimize_quadrature_3d() { 64 | let element = Hex8Element::from_vertices([ 65 | Point3::new(0.0, 2.0, 0.0), 66 | Point3::new(2.0, 2.0, 0.0), 67 | Point3::new(2.0, 3.0, 0.0), 68 | Point3::new(0.0, 3.0, 0.0), 69 | Point3::new(0.0, 2.0, 2.0), 70 | Point3::new(2.0, 2.0, 2.0), 71 | Point3::new(2.0, 3.0, 2.0), 72 | Point3::new(0.0, 3.0, 2.0), 73 | ]); 74 | let hex = Hexahedron::from_vertices(element.vertices().clone()); 75 | let embedded_mesh = 76 | create_simple_stupid_sphere(&Point3::new(2.0, 3.0, 2.0), 1.0, 5).intersect_convex_polyhedron(&hex); 77 | let quadrature = compute_element_embedded_quadrature( 78 | &element, 79 | &embedded_mesh, 80 | tet_quadrature_strength_10(), 81 | &QuadratureOptions::default(), 82 | ) 83 | .unwrap(); 84 | let quadrature_opt = optimize_quadrature(&quadrature, 8, &GlopSolver::new()).unwrap(); 85 | 86 | println!( 87 | "Num quadrature points before optimization: {}", 88 | quadrature.points().len() 89 | ); 90 | println!( 91 | "Num quadrature points after optimization: {}", 92 | quadrature_opt.points().len() 93 | ); 94 | 95 | let f = |x: f64, y: f64, z: f64| { 96 | -1.0 * x.powi(4) * y.powi(4) + 2.0 * x * y * z.powi(3) - 5.0 * x.powi(4) * y.powi(4) 97 | + 2.5 * x.powi(1) * y.powi(7) 98 | + 3.5 * z.powi(8) 99 | - 9.0 * x.powi(3) * y.powi(3) * z.powi(2) 100 | + z 101 | + x * y 102 | + 2.0 103 | }; 104 | 105 | let f = |p: &Vector3| f(p.x, p.y, p.z); 106 | 107 | let original_integral: f64 = quadrature.integrate(f); 108 | let optimized_integral: f64 = quadrature_opt.integrate(f); 109 | 110 | assert_scalar_eq!(original_integral, optimized_integral, comp = abs, tol = 1e-9); 111 | } 112 | 113 | #[test] 114 | fn zeroth_order_simplification() { 115 | // Points are arbitrary 116 | let points = vec![ 117 | Vector3::new(-0.5, 0.3, 0.5), 118 | Vector3::new(0.3, 0.2, -0.2), 119 | Vector3::new(0.4, 0.1, -0.5), 120 | Vector3::new(0.5, -0.2, 0.1), 121 | ]; 122 | let weights = vec![0.1, 0.5, 0.8, 3.0]; 123 | let quadrature = (weights.clone(), points.clone()); 124 | let quadrature_opt = optimize_quadrature(&quadrature, 0, &GlopSolver::new()).unwrap(); 125 | let opt_weights = quadrature_opt.0; 126 | let opt_points = quadrature_opt.1; 127 | 128 | assert_eq!(opt_weights.len(), 1); 129 | assert_eq!(opt_points.len(), 1); 130 | let expected_weight: f64 = weights.iter().sum(); 131 | assert_scalar_eq!(opt_weights[0], expected_weight, comp = abs, tol = 1e-9); 132 | 133 | assert!( 134 | opt_points[0] == points[0] 135 | || opt_points[0] == points[1] 136 | || opt_points[0] == points[2] 137 | || opt_points[0] == points[3] 138 | ) 139 | } 140 | -------------------------------------------------------------------------------- /fenris/tests/integration_tests/mod.rs: -------------------------------------------------------------------------------- 1 | mod assembly; 2 | mod cg_fem; 3 | mod embedding; 4 | -------------------------------------------------------------------------------- /fenris/tests/unit.rs: -------------------------------------------------------------------------------- 1 | mod unit_tests; 2 | 3 | #[macro_use] 4 | pub mod utils; 5 | -------------------------------------------------------------------------------- /fenris/tests/unit_tests/assembly.proptest-regressions: -------------------------------------------------------------------------------- 1 | # Seeds for failure cases proptest has generated in the past. It is 2 | # automatically read and these particular cases re-run before any 3 | # novel cases are generated. 4 | # 5 | # It is recommended to check this file in to source control so that 6 | # everyone who runs the test benefits from these saved cases. 7 | cc 3bf0a1bc67c2ee2b1c4d5e4b432a87cd194d9ff40a125f2882609ba13849b561 # shrinks to mesh = Mesh2d { vertices: [Matrix { data: [0.0, 0.0] }, Matrix { data: [1.0, 0.0] }, Matrix { data: [0.0, -1.0] }, Matrix { data: [1.0, -1.0] }], cells: [QuadIndices([2, 3, 1, 0])] } 8 | -------------------------------------------------------------------------------- /fenris/tests/unit_tests/basis.proptest-regressions: -------------------------------------------------------------------------------- 1 | # Seeds for failure cases proptest has generated in the past. It is 2 | # automatically read and these particular cases re-run before any 3 | # novel cases are generated. 4 | # 5 | # It is recommended to check this file in to source control so that 6 | # everyone who runs the test benefits from these saved cases. 7 | cc 925f952fe3aaca31b20a02512e467f1e4f6b1034d856bf8366a6bdc7e166ad8c # shrinks to mesh = Mesh2d { vertices: [Point { coords: Matrix { data: [0.0, 0.0] } }, Point { coords: Matrix { data: [1.0, 0.0] } }, Point { coords: Matrix { data: [0.0, -1.0] } }, Point { coords: Matrix { data: [1.0, -1.0] } }, Point { coords: Matrix { data: [0.0, -2.0] } }, Point { coords: Matrix { data: [1.0, -2.0] } }], cells: [QuadIndices([2, 3, 1, 0]), QuadIndices([4, 5, 3, 2])] } 8 | -------------------------------------------------------------------------------- /fenris/tests/unit_tests/cg.rs: -------------------------------------------------------------------------------- 1 | use crate::assert_approx_matrix_eq; 2 | use fenris::cg::{ConjugateGradient, IdentityOperator, RelativeResidualCriterion}; 3 | use fenris::nalgebra::{DMatrix, DVector}; 4 | 5 | #[test] 6 | fn solve_identity() { 7 | let operator = IdentityOperator; 8 | let mut x = DVector::zeros(4); 9 | let b = DVector::from_element(4, 5.0); 10 | ConjugateGradient::new() 11 | .with_operator(operator) 12 | .with_stopping_criterion(RelativeResidualCriterion::default()) 13 | .solve_with_guess(&b, &mut x) 14 | .unwrap(); 15 | assert_eq!(x, b); 16 | } 17 | 18 | #[test] 19 | fn solve_arbitrary() { 20 | // Use an arbitrary symmetric, positive definite matrix (diagonally dominant in this case) 21 | // and the identity preconditioner 22 | let a = DMatrix::from_fn(3, 3, |r, c| if r == c { 7.0 } else { 2.0 }); 23 | 24 | let x0 = DVector::from_row_slice(&[1.0, 2.0, 3.0]); 25 | let b = &a * &x0; 26 | let mut x = DVector::zeros(3); 27 | let output = ConjugateGradient::new() 28 | .with_operator(&a) 29 | .with_stopping_criterion(RelativeResidualCriterion::new(1e-14)) 30 | .solve_with_guess(&b, &mut x) 31 | .unwrap(); 32 | 33 | assert!(output.num_iterations > 0 && output.num_iterations <= 3); 34 | assert_approx_matrix_eq!(&x, &x0, abstol = 1e-12); 35 | } 36 | 37 | #[test] 38 | fn solve_arbitrary_preconditioned() { 39 | // Take some arbitrary positive definite matrices as system matrix and preconditioner 40 | let a = DMatrix::from_row_slice(3, 3, &[21.0, -1.0, -5.0, -1.0, 11.0, -4.0, -5.0, -4.0, 26.0]); 41 | let p = DMatrix::from_row_slice(3, 3, &[17.0, 6.0, 3.0, 6.0, 14.0, 9.0, 3.0, 9.0, 10.0]); 42 | let x0 = DVector::from_column_slice(&[1.0, 3.0, 2.0]); 43 | let b = &a * &x0; 44 | 45 | // Arbitrary initial guess 46 | let mut x = DVector::from_column_slice(&[2.0, 1.0, 0.0]); 47 | let output = ConjugateGradient::new() 48 | .with_operator(&a) 49 | .with_preconditioner(&p) 50 | .with_stopping_criterion(RelativeResidualCriterion::new(1e-14)) 51 | .solve_with_guess(&b, &mut x) 52 | .unwrap(); 53 | 54 | // We use the fact that CG converges in exact arithmetic in at most n iterations 55 | // for an n x n matrix. For such a small matrix, we should reach high precision immediately. 56 | assert_eq!(output.num_iterations, 3); 57 | assert_approx_matrix_eq!(&x, &x0, abstol = 1e-12); 58 | } 59 | -------------------------------------------------------------------------------- /fenris/tests/unit_tests/conversion_formulas.rs: -------------------------------------------------------------------------------- 1 | use fenris::solid::materials::{YoungPoisson, LameParameters}; 2 | use matrixcompare::assert_scalar_eq; 3 | 4 | fn check_effective_parameters(effective_params: YoungPoisson, paper_params: YoungPoisson) { 5 | let LameParameters { mu: mu_eff, .. } = effective_params.into(); 6 | let LameParameters { lambda: lambda_bad, mu } = paper_params.into(); 7 | 8 | let lambda_eff = 2.0 * (mu_eff * effective_params.poisson) / (1.0 - 2.0 * effective_params.poisson); 9 | assert_scalar_eq!(mu_eff, mu, comp = abs, tol = mu_eff * 1e-12); 10 | assert_scalar_eq!(lambda_eff, lambda_bad, comp=abs, tol = lambda_eff * 1e-9); 11 | } 12 | 13 | #[test] 14 | fn errata_conversion_formulas() { 15 | // Test that the formulas for the "effective" material parameters given in the errata are correct. 16 | // We do this by checking that, using these formulas, we obtain the same Lame parameters as our 17 | // incorrect implementation 18 | 19 | // Numerical verification (Section 5.4) 20 | { 21 | let effective_params = YoungPoisson { 22 | young: 2.67857142857e6, 23 | poisson: 0.25 24 | }; 25 | let paper_params = YoungPoisson { 26 | young: 3e6, 27 | poisson: 0.4 28 | }; 29 | check_effective_parameters(effective_params, paper_params); 30 | } 31 | 32 | // Twisting cylinder 33 | { 34 | let effective_params = YoungPoisson { 35 | young: 4.82625482625e6, 36 | poisson: 0.42857142857 37 | }; 38 | let paper_params = YoungPoisson { 39 | young: 5e6, 40 | poisson: 0.48 41 | }; 42 | check_effective_parameters(effective_params, paper_params); 43 | } 44 | 45 | // Armadillo slingshot 46 | { 47 | let effective_params = YoungPoisson { 48 | young: 4.46428571429e5, 49 | poisson: 0.25 50 | }; 51 | let paper_params = YoungPoisson { 52 | young: 5e5, 53 | poisson: 0.4 54 | }; 55 | check_effective_parameters(effective_params, paper_params); 56 | } 57 | } -------------------------------------------------------------------------------- /fenris/tests/unit_tests/element.proptest-regressions: -------------------------------------------------------------------------------- 1 | # Seeds for failure cases proptest has generated in the past. It is 2 | # automatically read and these particular cases re-run before any 3 | # novel cases are generated. 4 | # 5 | # It is recommended to check this file in to source control so that 6 | # everyone who runs the test benefits from these saved cases. 7 | cc 98a33f1ef95de867c023436d05254517ded8f1dd9aea56a3d0c38fc170be11a7 # shrinks to (x, y) = (0.0, 0.3795635332390195) 8 | cc de24d8bff42796cd3a7e927c1d894874fc96a143cd766409f20a983c3de2acdb # shrinks to tri = Triangle2d([Point { coords: Matrix { data: [0.0, 0.0] } }, Point { coords: Matrix { data: [0.0, -99.65933887502784] } }, Point { coords: Matrix { data: [57.997960082136984, -92.59510743817927] } }]) 9 | cc e3931d1eff663bf05135714b7d16b90b92803e3ca5866e2fd6df92a550efa684 # shrinks to (a, b, xi) = (Point { coords: Matrix { data: [0.0, 0.20939344981708885] } }, Point { coords: Matrix { data: [-37.52723953068697, 6.034261117385057] } }, -0.8591033571234763) 10 | cc 170e251a7fba0d179a7ea0383f4c16ce3acfb0fc96309e0f14ad0bb8d580d8b1 # shrinks to (tet, xi) = (Tet4Element { vertices: [Point { coords: Matrix { data: [0.0, 0.0, -9.82566465994083] } }, Point { coords: Matrix { data: [0.0, -7.794835280149801, 0.0] } }, Point { coords: Matrix { data: [-4.6558498908784385, 0.0, 0.0] } }, Point { coords: Matrix { data: [34.43724993104183, 0.0, 0.0] } }] }, Point { coords: Matrix { data: [0.0, -0.0, -0.0] } }) 11 | cc 999d392d7796f81363dae05aa2816004ba4e764101e31a4886d204d836dd32d8 # shrinks to tri = Triangle([Point { coords: Matrix { data: [-0.5350053492532321, 0.0] } }, Point { coords: Matrix { data: [-82.85181263386065, 90.14126143890536] } }, Point { coords: Matrix { data: [-98.76535656554171, 0.0] } }]) 12 | -------------------------------------------------------------------------------- /fenris/tests/unit_tests/mesh.proptest-regressions: -------------------------------------------------------------------------------- 1 | # Seeds for failure cases proptest has generated in the past. It is 2 | # automatically read and these particular cases re-run before any 3 | # novel cases are generated. 4 | # 5 | # It is recommended to check this file in to source control so that 6 | # everyone who runs the test benefits from these saved cases. 7 | cc 30fceec597d68f4be3af06a9661f6d26c791c150a5818382f8eb24fac730766f # shrinks to (mesh, cell_indices) = (Mesh2d { vertices: [], cells: [] }, [0]) 8 | -------------------------------------------------------------------------------- /fenris/tests/unit_tests/mod.rs: -------------------------------------------------------------------------------- 1 | mod assembly; 2 | mod basis; 3 | mod conversion_formulas; 4 | mod cg; 5 | mod element; 6 | mod embedding; 7 | mod fe_mesh; 8 | mod geometry; 9 | mod materials; 10 | mod mesh; 11 | mod polygon; 12 | mod polymesh; 13 | mod polytope; 14 | mod reorder; 15 | mod sparse; 16 | -------------------------------------------------------------------------------- /fenris/tests/unit_tests/polymesh.rs: -------------------------------------------------------------------------------- 1 | use fenris::geometry::polymesh::{PolyMesh, PolyMesh3d}; 2 | use fenris::geometry::procedural::create_rectangular_uniform_hex_mesh; 3 | use nested_vec::NestedVec; 4 | 5 | use itertools::iproduct; 6 | use matrixcompare::assert_scalar_eq; 7 | use nalgebra::Point3; 8 | 9 | fn create_single_tetrahedron_polymesh() -> PolyMesh3d { 10 | let vertices = vec![ 11 | Point3::new(0.0, 0.0, 0.0), 12 | Point3::new(1.0, 0.0, 0.0), 13 | Point3::new(0.0, 1.0, 0.0), 14 | Point3::new(0.0, 0.0, 1.0), 15 | ]; 16 | let faces = NestedVec::from(&vec![ 17 | // TODO: This isn't consistent with respect to winding order etc. 18 | // We need to introduce the concept of half faces or something similar to 19 | // make this stuff consistent per cell 20 | vec![0, 1, 2], 21 | vec![0, 1, 3], 22 | vec![1, 2, 3], 23 | vec![2, 0, 3], 24 | ]); 25 | let cells = NestedVec::from(&vec![vec![0, 1, 2, 3]]); 26 | PolyMesh::from_poly_data(vertices, faces, cells) 27 | } 28 | 29 | #[test] 30 | fn triangulate_single_tetrahedron_is_unchanged() { 31 | let mesh = create_single_tetrahedron_polymesh(); 32 | 33 | let triangulated = mesh.triangulate().unwrap(); 34 | 35 | assert_eq!(triangulated.num_cells(), 1); 36 | assert_eq!(triangulated.num_faces(), 4); 37 | 38 | // TODO: Further tests! 39 | } 40 | 41 | #[test] 42 | fn compute_volume() { 43 | { 44 | // Single cube, multiple resolutions 45 | let unit_lengths = [1.0, 0.5, 1.5]; 46 | let nx = [1, 2, 3]; 47 | let ny = [1, 2, 3]; 48 | let nz = [1, 2, 3]; 49 | let resolutions = [1, 2]; 50 | 51 | for (u, nx, ny, nz, res) in iproduct!(&unit_lengths, &nx, &ny, &nz, &resolutions) { 52 | let cube = create_rectangular_uniform_hex_mesh(*u, *nx, *ny, *nz, *res); 53 | let cube = PolyMesh3d::from(&cube); 54 | let expected_volume: f64 = u * u * u * (nx * ny * nz) as f64; 55 | dbg!(u, nx, ny, nz, res); 56 | assert_scalar_eq!(cube.compute_volume(), expected_volume, comp = abs, tol = 1e-12); 57 | } 58 | } 59 | } 60 | 61 | #[test] 62 | fn keep_cells() { 63 | { 64 | // Single tetrahedron 65 | let mesh = create_single_tetrahedron_polymesh(); 66 | 67 | // Keep no cells, should give empty mesh 68 | { 69 | let kept = mesh.keep_cells(&[]); 70 | assert_eq!(kept.vertices().len(), 0); 71 | assert_eq!(kept.num_faces(), 0); 72 | assert_eq!(kept.num_cells(), 0); 73 | } 74 | 75 | // Keep cell 0, should give unchanged mesh back 76 | { 77 | let kept = mesh.keep_cells(&[0]); 78 | assert_eq!(mesh, kept); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /fenris/tests/unit_tests/reorder.rs: -------------------------------------------------------------------------------- 1 | use fenris::reorder::{cuthill_mckee, reverse_cuthill_mckee}; 2 | use fenris::sparse::CsrMatrix; 3 | use nalgebra::DMatrix; 4 | 5 | #[test] 6 | fn cuthill_mckee_basic_examples() { 7 | // Basic example 8 | { 9 | let matrix = DMatrix::from_row_slice(4, 4, &[1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1]); 10 | let pattern = CsrMatrix::from(&matrix).sparsity_pattern(); 11 | let perm = cuthill_mckee(&pattern); 12 | 13 | assert_eq!(perm.perm(), &[1, 3, 0, 2]); 14 | 15 | let mut rcm_expected_perm = perm.clone(); 16 | rcm_expected_perm.reverse(); 17 | assert_eq!(&reverse_cuthill_mckee(&pattern), &rcm_expected_perm); 18 | } 19 | 20 | // Diagonal pattern 21 | // Note that the "standard" CM algorithm 22 | { 23 | let matrix = DMatrix::from_row_slice(4, 4, &[1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]); 24 | let pattern = CsrMatrix::from(&matrix).sparsity_pattern(); 25 | let perm = cuthill_mckee(&pattern); 26 | assert_eq!(perm.perm(), &[0, 1, 2, 3]); 27 | } 28 | 29 | // TODO: Property-based tests 30 | } 31 | -------------------------------------------------------------------------------- /fenris/tests/unit_tests/sparse.proptest-regressions: -------------------------------------------------------------------------------- 1 | # Seeds for failure cases proptest has generated in the past. It is 2 | # automatically read and these particular cases re-run before any 3 | # novel cases are generated. 4 | # 5 | # It is recommended to check this file in to source control so that 6 | # everyone who runs the test benefits from these saved cases. 7 | cc eb3d0383b97096d2736865250aa2a1e353e78fcde28ede14d02aa9a9a663cb11 # shrinks to coo = CooMatrix { nrows: 1, ncols: 2, i: [0, 0, 0], j: [1, 0, 0], v: [0, 0, 1] } 8 | cc 6cbf6d9b828de0df85a172e2f926a8663b6443d3261b7c9467cfbf7aded8d761 # shrinks to coo = CooMatrix { nrows: 3, ncols: 3, i: [0, 0, 0], j: [0, 0, 0], v: [0, 0, 0] } 9 | cc 4f059c9ff0161935917bc36eb6493be2a373ca223561aaaf1893d190f96fd902 # shrinks to (coo, x, y, alpha, beta) = (CooMatrix { nrows: 1, ncols: 0, i: [], j: [], v: [] }, [-5, -4, 0, -1, -5, -2], [-1, -3, -5, -3, 0, -3], 1, 4) 10 | cc 6840b2b458601adddbafc37004e0b2703cea45f96e1a6fb387f0b0b88f2ae24c # shrinks to matrices = [CsrMatrix { sparsity_pattern: SparsityPattern { major_offsets: [0], minor_indices: [], minor_dim: 0 }, v: [] }] 11 | cc b4d2d7ee3defa0a79c1bcf1cebe4b12224e827d4f1d7ce248ceb0e9c40797f89 # shrinks to (_, [a, b]) = (CsrMatrix { sparsity_pattern: SparsityPattern { major_offsets: [0, 4, 7], minor_indices: [0, 1, 2, 3, 0, 2, 3], minor_dim: 4 }, v: [0, 0, 0, 0, 0, 0, 0] }, [CsrMatrix { sparsity_pattern: SparsityPattern { major_offsets: [0, 2, 4], minor_indices: [2, 3, 1, 3], minor_dim: 4 }, v: [5, 4, 5, 7] }, CsrMatrix { sparsity_pattern: SparsityPattern { major_offsets: [0, 4, 7, 11, 11], minor_indices: [0, 1, 2, 3, 0, 2, 3, 0, 1, 2, 3], minor_dim: 4 }, v: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }]) 12 | -------------------------------------------------------------------------------- /fenris/tests/utils/mod.rs: -------------------------------------------------------------------------------- 1 | /// Poor man's approx assertion for matrices 2 | #[macro_export] 3 | macro_rules! assert_approx_matrix_eq { 4 | ($x:expr, $y:expr, abstol = $tol:expr) => {{ 5 | let diff = $x - $y; 6 | 7 | let max_absdiff = diff.abs().max(); 8 | let approx_eq = max_absdiff <= $tol; 9 | 10 | if !approx_eq { 11 | println!("abstol: {}", $tol); 12 | println!("left: {}", $x); 13 | println!("right: {}", $y); 14 | println!("diff: {:e}", diff); 15 | } 16 | assert!(approx_eq); 17 | }}; 18 | } 19 | 20 | #[macro_export] 21 | macro_rules! assert_panics { 22 | ($e:expr) => {{ 23 | use std::panic::catch_unwind; 24 | use std::stringify; 25 | let expr_string = stringify!($e); 26 | let result = catch_unwind(|| $e); 27 | if result.is_ok() { 28 | panic!("assert_panics!({}) failed.", expr_string); 29 | } 30 | }}; 31 | } 32 | -------------------------------------------------------------------------------- /global_stash/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "global_stash" 3 | version = "0.1.0" 4 | authors = ["Fabian Löschner "] 5 | edition = "2018" 6 | publish = false 7 | 8 | [dependencies] 9 | serde = "1.0" 10 | serde_json = "1.0" 11 | thiserror = "1.0" 12 | -------------------------------------------------------------------------------- /hamilton/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock -------------------------------------------------------------------------------- /hamilton/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hamilton" 3 | version = "0.1.0" 4 | authors = ["Andreas Longva "] 5 | edition = "2018" 6 | publish = false 7 | 8 | [dependencies] 9 | serde = { version="1.0", features=["derive"] } 10 | erased-serde = { version="0.3" } 11 | once_cell = "1.2" 12 | 13 | [dev-dependencies] 14 | serde_json = "1.0" -------------------------------------------------------------------------------- /hamilton/examples/basic.rs: -------------------------------------------------------------------------------- 1 | use hamilton::storages::VecStorage; 2 | use hamilton::{register_component, Component, Entity, StorageContainer}; 3 | 4 | use std::error::Error; 5 | 6 | use serde::{Deserialize, Serialize}; 7 | 8 | use serde_json; 9 | 10 | #[derive(Debug, Copy, Clone, Serialize, Deserialize)] 11 | pub struct TestComponent(pub usize); 12 | 13 | impl Component for TestComponent { 14 | type Storage = VecStorage; 15 | } 16 | 17 | fn main() -> Result<(), Box> { 18 | register_component::()?; 19 | 20 | let container = StorageContainer::new(); 21 | 22 | { 23 | let storage = container.get_storage::>(); 24 | storage.borrow_mut().insert(Entity::new(), TestComponent(0)); 25 | storage.borrow_mut().insert(Entity::new(), TestComponent(1)); 26 | 27 | dbg!(storage.borrow()); 28 | } 29 | 30 | let json = serde_json::to_string_pretty(&container)?; 31 | 32 | println!("{}", json); 33 | 34 | let deserialized_container: StorageContainer = serde_json::from_str(&json)?; 35 | 36 | { 37 | let storage = deserialized_container.get_storage::>(); 38 | dbg!(storage.borrow()); 39 | } 40 | 41 | Ok(()) 42 | } 43 | -------------------------------------------------------------------------------- /hamilton/src/container.rs: -------------------------------------------------------------------------------- 1 | use crate::{BijectiveStorageMut, Component, Entity}; 2 | use std::any::Any; 3 | use std::cell::RefCell; 4 | use std::ops::Deref; 5 | use std::pin::Pin; 6 | 7 | // Make container_serialize a submodule of this module, so that it can still 8 | // access private members of `StorageContainer`, without exposing this to the rest of the 9 | // crate (using e.g. `pub(crate)`). 10 | mod container_serialize; 11 | 12 | pub use container_serialize::{register_factory, register_storage, RegistrationStatus}; 13 | 14 | #[derive(Debug, Default)] 15 | pub struct StorageContainer { 16 | // Note: The "dyn Any" actually contains instances of "RefCell" 17 | storages: RefCell>)>>, 18 | } 19 | 20 | impl StorageContainer { 21 | pub fn new() -> Self { 22 | Self { 23 | storages: RefCell::new(Vec::new()), 24 | } 25 | } 26 | 27 | pub fn get_component_storage(&self) -> &RefCell 28 | where 29 | C: Component, 30 | C::Storage: 'static + Default, 31 | { 32 | self.get_storage::() 33 | } 34 | 35 | pub fn try_get_component_storage(&self) -> Option<&RefCell> 36 | where 37 | C: Component, 38 | C::Storage: 'static, 39 | { 40 | self.try_get_storage::() 41 | } 42 | 43 | pub fn get_storage(&self) -> &RefCell 44 | where 45 | Storage: 'static + Default, 46 | { 47 | if let Some(storage) = self.try_get_storage() { 48 | return storage; 49 | } 50 | 51 | // We didn't find the storage, so make a new one based on default value 52 | let new_storage = Box::pin(RefCell::new(Storage::default())); 53 | let new_storage_ptr = { 54 | // Make sure that we take a pointer to the right type by first explicitly 55 | // generating a reference to it 56 | let new_storage_ref: &RefCell = new_storage.deref(); 57 | new_storage_ref as *const RefCell 58 | }; 59 | self.storages 60 | .borrow_mut() 61 | .push((String::from(std::any::type_name::()), new_storage)); 62 | 63 | // See the above comment for why this is valid, as the same argument applies. 64 | unsafe { &*new_storage_ptr } 65 | } 66 | 67 | pub fn try_get_storage(&self) -> Option<&RefCell> 68 | where 69 | Storage: 'static, 70 | { 71 | for (_, untyped_storage) in self.storages.borrow().iter() { 72 | if let Some(typed) = untyped_storage.downcast_ref::>() { 73 | let typed_ptr = typed as *const RefCell<_>; 74 | 75 | // This is valid because the RefCell is contained inside Pin, 76 | // so as long as the pinned box is never deallocated, the pointer will remain 77 | // valid. We never remove any storages from the vector, so this could only 78 | // happen upon deallocation of the StorageContainer. However, the 79 | // returned reference is scoped to the lifetime of &self, so 80 | // it cannot outlive the container itself. 81 | return Some(unsafe { &*typed_ptr }); 82 | } 83 | } 84 | 85 | // Did not find storage 86 | None 87 | } 88 | 89 | pub fn replace_storage(&mut self, storage: Storage) -> bool 90 | where 91 | Storage: 'static, 92 | { 93 | // Note: Because we take &mut self here, we know that there cannot be any outstanding 94 | // references given out by `get_storage`, and so it's safe to replace 95 | // (and therefore destroy) the given storage 96 | let mut storages = self.storages.borrow_mut(); 97 | 98 | let tag = std::any::type_name::(); 99 | let pinned_storage = Box::pin(RefCell::new(storage)); 100 | 101 | for (existing_tag, existing_storage) in storages.iter_mut() { 102 | if *existing_tag == tag { 103 | *existing_storage = pinned_storage; 104 | return true; 105 | } 106 | } 107 | 108 | // No storage with the same tag found, so simply add it to existing storages 109 | storages.push((String::from(tag), pinned_storage)); 110 | false 111 | } 112 | 113 | pub fn insert_component(&self, id: Entity, component: C) 114 | where 115 | C: Component, 116 | C::Storage: 'static + Default + BijectiveStorageMut, 117 | { 118 | let mut storage = self.get_component_storage::().borrow_mut(); 119 | storage.insert_component(id, component); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /hamilton/src/entity.rs: -------------------------------------------------------------------------------- 1 | use crate::{EntityDeserialize, EntitySerializationMap}; 2 | use serde::de::Deserialize; 3 | use std::sync::atomic::{AtomicU64, Ordering}; 4 | 5 | static NEXT_ENTITY: AtomicU64 = AtomicU64::new(0); 6 | 7 | #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, serde::Serialize)] 8 | pub struct Entity(u64); 9 | 10 | impl Entity { 11 | #[allow(clippy::new_without_default)] 12 | pub fn new() -> Self { 13 | Entity(NEXT_ENTITY.fetch_add(1, Ordering::SeqCst)) 14 | } 15 | } 16 | 17 | #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)] 18 | pub struct SerializableEntity(u64); 19 | 20 | impl From for SerializableEntity { 21 | fn from(id: Entity) -> Self { 22 | Self(id.0) 23 | } 24 | } 25 | 26 | impl<'de> EntityDeserialize<'de> for Entity { 27 | fn entity_deserialize(deserializer: D, id_map: &mut EntitySerializationMap) -> Result 28 | where 29 | D: serde::Deserializer<'de>, 30 | { 31 | let deserializable = SerializableEntity::deserialize(deserializer)?; 32 | let entity = id_map.deserialize_entity(deserializable); 33 | Ok(entity) 34 | } 35 | } 36 | 37 | impl<'a, 'de> serde::de::DeserializeSeed<'de> for &'a mut EntitySerializationMap { 38 | type Value = Entity; 39 | 40 | fn deserialize(self, deserializer: D) -> Result 41 | where 42 | D: serde::Deserializer<'de>, 43 | { 44 | let deserializable = SerializableEntity::deserialize(deserializer)?; 45 | let entity = self.deserialize_entity(deserializable); 46 | Ok(entity) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /hamilton/src/generic_factory.rs: -------------------------------------------------------------------------------- 1 | use crate::{EntityDeserialize, EntitySerializationMap, StorageFactory}; 2 | use erased_serde::Serialize; 3 | use std::any::Any; 4 | use std::cell::RefCell; 5 | use std::error::Error; 6 | use std::marker::PhantomData; 7 | 8 | #[derive(Debug, Default)] 9 | pub struct GenericFactory { 10 | marker: PhantomData, 11 | } 12 | 13 | impl GenericFactory { 14 | pub fn new() -> Self { 15 | Self { marker: PhantomData } 16 | } 17 | } 18 | 19 | // Factory contains no data whatsoever and is therefore entirely safe to pass around across threads 20 | unsafe impl Sync for GenericFactory {} 21 | unsafe impl Send for GenericFactory {} 22 | 23 | impl StorageFactory for GenericFactory 24 | where 25 | for<'de> Storage: 'static + serde::Serialize + EntityDeserialize<'de>, 26 | { 27 | fn storage_tag(&self) -> String { 28 | std::any::type_name::().to_string() 29 | } 30 | 31 | fn serializable_storage(&self, storage: &dyn Any) -> Result<&dyn Serialize, Box> { 32 | // TODO: Is this actually valid? 33 | storage 34 | .downcast_ref::>() 35 | .map(|storage| storage as *const RefCell) 36 | // I'm not sure if Any actually provides the necessary guarantees for this to be valid. 37 | // In particular, we are extending the lifetime of the returned reference to 38 | // beyond that of the `downcast_ref` method. However, `Any` can only give a reference 39 | // to a concrete object, and the lifetime of this object must be at least as long 40 | // as the shared reference we have to the trait object, and so by converting our 41 | // reference into a pointer, this points to the contained item, which we know 42 | // has the required lifetime. 43 | .map(|ptr| unsafe { &*ptr } as &dyn Serialize) 44 | .ok_or_else(|| Box::from("provided storage is not known to factory")) 45 | } 46 | 47 | fn deserialize_storage( 48 | &self, 49 | deserializer: &mut dyn erased_serde::Deserializer, 50 | id_map: &mut EntitySerializationMap, 51 | ) -> Result, Box> { 52 | let storage = Storage::entity_deserialize(deserializer, id_map)?; 53 | Ok(Box::new(RefCell::new(storage))) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /hamilton/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::any::Any; 2 | use std::collections::HashMap; 3 | use std::error::Error; 4 | 5 | mod entity; 6 | pub use entity::*; 7 | 8 | mod container; 9 | pub use container::*; 10 | 11 | pub mod storages; 12 | 13 | mod generic_factory; 14 | pub use generic_factory::*; 15 | 16 | mod systems; 17 | pub use systems::*; 18 | 19 | pub struct EntitySerializationMap { 20 | map: HashMap, 21 | } 22 | 23 | impl EntitySerializationMap { 24 | fn new() -> Self { 25 | Self { map: HashMap::new() } 26 | } 27 | 28 | pub fn deserialize_entity(&mut self, id: SerializableEntity) -> Entity { 29 | *self.map.entry(id).or_insert_with(Entity::new) 30 | } 31 | } 32 | 33 | pub trait StorageFactory: Send + Sync { 34 | fn storage_tag(&self) -> String; 35 | 36 | fn serializable_storage(&self, storage: &dyn Any) -> Result<&dyn erased_serde::Serialize, Box>; 37 | 38 | fn deserialize_storage( 39 | &self, 40 | deserializer: &mut dyn erased_serde::Deserializer, 41 | id_map: &mut EntitySerializationMap, 42 | ) -> Result, Box>; 43 | } 44 | 45 | pub trait Storage { 46 | fn new_factory() -> Box; 47 | } 48 | 49 | /// Storage that represents a one-to-one (bijective) correspondence between entities and components. 50 | pub trait BijectiveStorage { 51 | // TODO: Move associated type to `Storage`? 52 | type Component; 53 | 54 | fn get_component_for_entity(&self, id: Entity) -> Option<&Self::Component>; 55 | } 56 | 57 | pub trait BijectiveStorageMut: BijectiveStorage { 58 | /// Inserts a component associated with the entity, overwriting any existing component 59 | /// that may already be associated with the given entity. 60 | fn insert_component(&mut self, id: Entity, component: Self::Component); 61 | 62 | fn get_component_for_entity_mut(&mut self, id: Entity) -> Option<&mut Self::Component>; 63 | } 64 | 65 | /// An extension of serde's `Deserialize` that allows deserialization of types containing 66 | /// instances `Entity` (which are not deserializable) 67 | pub trait EntityDeserialize<'de>: Sized { 68 | fn entity_deserialize(deserializer: D, id_map: &mut EntitySerializationMap) -> Result 69 | where 70 | D: serde::Deserializer<'de>; 71 | } 72 | 73 | impl<'de, T> EntityDeserialize<'de> for T 74 | where 75 | T: serde::Deserialize<'de>, 76 | { 77 | fn entity_deserialize(deserializer: D, _: &mut EntitySerializationMap) -> Result 78 | where 79 | D: serde::Deserializer<'de>, 80 | { 81 | T::deserialize(deserializer) 82 | } 83 | } 84 | 85 | pub trait Component { 86 | type Storage: Storage; 87 | } 88 | 89 | pub fn register_component() -> Result> 90 | where 91 | C: Component, 92 | { 93 | register_storage::() 94 | } 95 | -------------------------------------------------------------------------------- /hamilton/src/systems.rs: -------------------------------------------------------------------------------- 1 | use crate::StorageContainer; 2 | use std::error::Error; 3 | use std::fmt; 4 | use std::fmt::{Debug, Display}; 5 | 6 | pub trait System: Debug + Display { 7 | fn run(&mut self, data: &StorageContainer) -> Result<(), Box>; 8 | } 9 | 10 | /// A system that runs only once and executes its contained closure 11 | pub struct RunOnceSystem 12 | where 13 | F: FnOnce(&StorageContainer) -> Result<(), Box>, 14 | { 15 | pub closure: Option, 16 | has_run: bool, 17 | } 18 | 19 | /// System that uses a closure to determine if a system should be run 20 | pub struct FilterSystem 21 | where 22 | P: FnMut(&StorageContainer) -> Result>, 23 | S: System, 24 | { 25 | pub predicate: P, 26 | pub system: S, 27 | } 28 | 29 | /// Wrapper to store a vector of systems that are run in sequence 30 | pub struct SystemCollection(pub Vec>); 31 | 32 | impl Debug for RunOnceSystem 33 | where 34 | F: FnOnce(&StorageContainer) -> Result<(), Box>, 35 | { 36 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 37 | write!(f, "RunOnceSystem(has_run: {})", self.has_run) 38 | } 39 | } 40 | 41 | impl RunOnceSystem 42 | where 43 | F: FnOnce(&StorageContainer) -> Result<(), Box>, 44 | { 45 | pub fn new(closure: F) -> Self { 46 | RunOnceSystem { 47 | closure: Some(closure), 48 | has_run: false, 49 | } 50 | } 51 | } 52 | 53 | impl Display for RunOnceSystem 54 | where 55 | F: FnOnce(&StorageContainer) -> Result<(), Box>, 56 | { 57 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 58 | write!(f, "RunOnceSystem(has_run: {})", self.has_run) 59 | } 60 | } 61 | 62 | impl System for RunOnceSystem 63 | where 64 | F: FnOnce(&StorageContainer) -> Result<(), Box>, 65 | { 66 | fn run(&mut self, data: &StorageContainer) -> Result<(), Box> { 67 | if !self.has_run { 68 | let ret = (self 69 | .closure 70 | .take() 71 | .ok_or_else(|| Box::::from("Closure gone"))?)(data)?; 72 | self.has_run = true; 73 | Ok(ret) 74 | } else { 75 | Ok(()) 76 | } 77 | } 78 | } 79 | 80 | impl Debug for FilterSystem 81 | where 82 | P: FnMut(&StorageContainer) -> Result>, 83 | S: System, 84 | { 85 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 86 | write!(f, "Filter({:?})", self.system) 87 | } 88 | } 89 | 90 | impl Display for FilterSystem 91 | where 92 | P: FnMut(&StorageContainer) -> Result>, 93 | S: System, 94 | { 95 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 96 | write!(f, "Filter({})", self.system) 97 | } 98 | } 99 | 100 | impl System for FilterSystem 101 | where 102 | P: FnMut(&StorageContainer) -> Result>, 103 | S: System, 104 | { 105 | fn run(&mut self, data: &StorageContainer) -> Result<(), Box> { 106 | if (self.predicate)(data)? { 107 | self.system.run(data) 108 | } else { 109 | Ok(()) 110 | } 111 | } 112 | } 113 | 114 | impl Debug for SystemCollection { 115 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 116 | write!(f, "SystemCollection({:?})", self.0) 117 | } 118 | } 119 | 120 | impl Display for SystemCollection { 121 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 122 | let mut systems = String::new(); 123 | self.0.iter().for_each(|s| { 124 | systems.push_str(&format!("{}, ", s)); 125 | }); 126 | if systems.len() > 2 { 127 | write!(f, "SystemCollection({})", &systems[..systems.len() - 2]) 128 | } else { 129 | write!(f, "SystemCollection()") 130 | } 131 | } 132 | } 133 | 134 | impl System for SystemCollection { 135 | fn run(&mut self, data: &StorageContainer) -> Result<(), Box> { 136 | for s in self.0.iter_mut() { 137 | s.run(data)?; 138 | } 139 | Ok(()) 140 | } 141 | } 142 | 143 | #[derive(Debug, Default)] 144 | pub struct Systems { 145 | systems: Vec>, 146 | } 147 | 148 | impl Systems { 149 | pub fn add_system(&mut self, system: Box) { 150 | self.systems.push(system); 151 | } 152 | 153 | pub fn run_all(&mut self, data: &StorageContainer) -> Result<(), Box> { 154 | for system in &mut self.systems { 155 | system.run(data)?; 156 | } 157 | Ok(()) 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /hamilton/tests/registration.rs: -------------------------------------------------------------------------------- 1 | use hamilton::{register_factory, GenericFactory, RegistrationStatus}; 2 | 3 | #[test] 4 | fn register() { 5 | // Important: registration is global, so we must run this test in a separate binary, 6 | // which we do when we make it a separate integration test 7 | let make_factory = || Box::new(GenericFactory::::default()); 8 | let make_factory2 = || Box::new(GenericFactory::::default()); 9 | 10 | assert_eq!(register_factory(make_factory()).unwrap(), RegistrationStatus::Inserted); 11 | assert_eq!(register_factory(make_factory()).unwrap(), RegistrationStatus::Replaced); 12 | assert_eq!(register_factory(make_factory()).unwrap(), RegistrationStatus::Replaced); 13 | 14 | assert_eq!(register_factory(make_factory2()).unwrap(), RegistrationStatus::Inserted); 15 | assert_eq!(register_factory(make_factory2()).unwrap(), RegistrationStatus::Replaced); 16 | 17 | assert_eq!(register_factory(make_factory()).unwrap(), RegistrationStatus::Replaced); 18 | } 19 | -------------------------------------------------------------------------------- /hamilton/tests/serialization/mod.rs: -------------------------------------------------------------------------------- 1 | use hamilton::storages::VecStorage; 2 | use hamilton::{register_component, Component, Entity, StorageContainer}; 3 | 4 | use serde::{Deserialize, Serialize}; 5 | 6 | #[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)] 7 | pub struct Foo(i32); 8 | 9 | #[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)] 10 | pub struct Bar(i32); 11 | 12 | impl Component for Foo { 13 | type Storage = VecStorage; 14 | } 15 | 16 | impl Component for Bar { 17 | type Storage = VecStorage; 18 | } 19 | 20 | #[test] 21 | fn json_roundtrip() { 22 | register_component::().unwrap(); 23 | register_component::().unwrap(); 24 | 25 | let container = StorageContainer::default(); 26 | 27 | let id1 = Entity::new(); 28 | let id2 = Entity::new(); 29 | let id3 = Entity::new(); 30 | 31 | { 32 | let mut foo_storage = container.get_component_storage::().borrow_mut(); 33 | foo_storage.insert(id2, Foo(1)); 34 | foo_storage.insert(id1, Foo(2)); 35 | 36 | let mut bar_storage = container.get_component_storage::().borrow_mut(); 37 | bar_storage.insert(id2, Bar(3)); 38 | bar_storage.insert(id3, Bar(4)); 39 | bar_storage.insert(id1, Bar(5)); 40 | } 41 | 42 | let json = serde_json::to_string_pretty(&container).unwrap(); 43 | 44 | // Drop container so that we make sure we don't accidentally reference it later 45 | drop(container); 46 | 47 | let deserialized_container: StorageContainer = serde_json::from_str(&json).unwrap(); 48 | 49 | let foo_storage = deserialized_container 50 | .get_component_storage::() 51 | .borrow(); 52 | let bar_storage = deserialized_container 53 | .get_component_storage::() 54 | .borrow(); 55 | 56 | let foos = foo_storage.components(); 57 | let bars = bar_storage.components(); 58 | 59 | assert_eq!(foos, &[Foo(1), Foo(2)]); 60 | assert_eq!(bars, &[Bar(3), Bar(4), Bar(5)]); 61 | 62 | // We can not directly compare the entities with expected values, since we cannot predict 63 | // what they should be. However, entities only describe relations, and we can therefore 64 | // instead check that the components that shared the same entities still do after 65 | // serialization and deserialization. 66 | let foo_ids = foo_storage.entities(); 67 | let bar_ids = bar_storage.entities(); 68 | 69 | assert_eq!(foo_ids[0], bar_ids[0]); 70 | assert_eq!(foo_ids[1], bar_ids[2]); 71 | 72 | // Assert that the remaining entity is not equal to any of the others 73 | assert_ne!(bar_ids[1], bar_ids[0]); 74 | assert_ne!(bar_ids[1], bar_ids[2]); 75 | } 76 | -------------------------------------------------------------------------------- /hamilton/tests/unit.rs: -------------------------------------------------------------------------------- 1 | //! Unit tests are grouped into a single binary by way of directories that serve 2 | //! as modules. 3 | 4 | mod serialization; 5 | -------------------------------------------------------------------------------- /hamilton2/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hamilton2" 3 | version = "0.1.0" 4 | authors = ["Fabian Löschner "] 5 | edition = "2018" 6 | publish = false 7 | 8 | [dependencies] 9 | log = "0.4" 10 | itertools = "0.9" 11 | alga = "0.9" 12 | nalgebra = "0.21" 13 | numeric_literals = "0.2" 14 | approx = "0.3" 15 | coarse-prof = "0.2" 16 | -------------------------------------------------------------------------------- /hamilton2/src/dynamic_system/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod mutable; 2 | pub(crate) mod stateless; 3 | pub(crate) mod views; 4 | 5 | pub use mutable::MutableDifferentiableDynamicSystem as DifferentiableDynamicSystem; 6 | pub use mutable::MutableDynamicSystem as DynamicSystem; 7 | pub use stateless::{StatelessDifferentiableDynamicSystem, StatelessDynamicSystem}; 8 | pub use views::IntoStateful; 9 | 10 | // This is old code from exploring an operator based approach to the DynamicSystem trait 11 | /* 12 | mod stateless; 13 | mod stateful; 14 | mod views; 15 | 16 | pub use stateful::{ 17 | DifferentiableDynamicSystemSnapshot, DynamicSystemSnapshot, StatefulDynamicSystem, 18 | }; 19 | pub use stateless::{StatelessDifferentiableDynamicSystem, StatelessDynamicSystem}; 20 | //pub use views::{AsStateful, AsStateless, StatelessView}; 21 | pub use views::StatefulView; 22 | 23 | use nalgebra::{DVectorSlice, DVectorSliceMut, Scalar}; 24 | use std::error::Error; 25 | 26 | pub trait Operator { 27 | /// Applies this operator to `x` and stores or accumulates the result into `y`. 28 | /// 29 | /// It depends on the concrete implementation if the result overwrites the content of `y` 30 | /// or is added to it. 31 | fn apply(&self, y: DVectorSliceMut, x: DVectorSlice) -> Result<(), Box>; 32 | } 33 | */ 34 | -------------------------------------------------------------------------------- /hamilton2/src/dynamic_system/mutable.rs: -------------------------------------------------------------------------------- 1 | use nalgebra::{DVectorSlice, DVectorSliceMut, Scalar}; 2 | use std::error::Error; 3 | 4 | /// An abstract dynamic system represented by a second-order ODE. 5 | /// 6 | /// A dynamic system is represented by the second-order system of ODEs 7 | /// 8 | /// ```ignore 9 | /// M dv/dt = f(t, u, v), 10 | /// du/dt = v. 11 | /// ``` 12 | /// 13 | /// where M is constant. 14 | pub trait MutableDynamicSystem { 15 | /// Apply the mass matrix `M` to the vector `x` and accumulate the result in `y`, 16 | /// yielding `y = y + Mx`. 17 | fn apply_mass_matrix(&mut self, y: DVectorSliceMut, x: DVectorSlice); 18 | 19 | fn apply_inverse_mass_matrix( 20 | &mut self, 21 | sol: DVectorSliceMut, 22 | rhs: DVectorSlice, 23 | ) -> Result<(), Box>; 24 | 25 | fn eval_f( 26 | &mut self, 27 | f: DVectorSliceMut, 28 | t: T, 29 | u: DVectorSlice, 30 | v: DVectorSlice, 31 | ) -> Result<(), Box>; 32 | } 33 | 34 | /// An abstract dynamic system that allows for differentiation of the functions involved. 35 | /// 36 | /// This enables implicit integrators to work with an abstract representation of the dynamic 37 | /// system. 38 | pub trait MutableDifferentiableDynamicSystem: MutableDynamicSystem { 39 | /// Internally stores the state, pre-computes stiffness matrix, etc. for calls to Jacobian combination functions 40 | fn set_state(&mut self, t: T, u: DVectorSlice, v: DVectorSlice) -> Result<(), Box>; 41 | 42 | /// Pre-computes the Jacobian linear combination to apply it to any vector using `apply_jacobian_combination` 43 | fn init_apply_jacobian_combination( 44 | &mut self, 45 | alpha: Option, 46 | beta: Option, 47 | gamma: Option, 48 | ) -> Result<(), Box>; 49 | 50 | /// Performs factorization for subsequent calls to `solve_jacobian_combination` 51 | fn init_solve_jacobian_combination(&mut self, alpha: Option, beta: Option) -> Result<(), Box>; 52 | 53 | /// Apply a linear combination of the system Jacobians to a vector. 54 | /// 55 | /// Computes `y = y + (alpha * df/du + beta * df/dv + gamma * M) * x`, 56 | /// with the Jacobians evaluated at time `t` and the provided `u` and `v` variables 57 | /// that were set using `set_state`. 58 | fn apply_jacobian_combination(&mut self, y: DVectorSliceMut, x: DVectorSlice) -> Result<(), Box>; 59 | 60 | /// Solve a system consisting of linear combinations of Jacobians. 61 | /// 62 | /// Specifically, solve a linear system 63 | /// 64 | /// ```ignore 65 | /// H x = rhs, 66 | /// ``` 67 | /// 68 | /// where `H = M + alpha * df/du + beta * df/dv`, 69 | /// with `H` evaluated at time `t` and the provided `u` and `v` variables that were set 70 | /// using `set_state`. 71 | fn solve_jacobian_combination( 72 | &mut self, 73 | sol: DVectorSliceMut, 74 | rhs: DVectorSlice, 75 | ) -> Result<(), Box>; 76 | } 77 | -------------------------------------------------------------------------------- /hamilton2/src/dynamic_system/stateful.rs: -------------------------------------------------------------------------------- 1 | /* 2 | use nalgebra::{DVectorSlice, DVectorSliceMut, Scalar}; 3 | use std::error::Error; 4 | 5 | use crate::dynamic_system::Operator; 6 | 7 | pub trait StatefulDynamicSystem<'snap, T: Scalar> { 8 | type Snapshot: DynamicSystemSnapshot + 'snap; 9 | 10 | fn apply_mass_matrix(&self, y: DVectorSliceMut, x: DVectorSlice); 11 | 12 | fn apply_inverse_mass_matrix( 13 | &self, 14 | sol: DVectorSliceMut, 15 | rhs: DVectorSlice, 16 | ) -> Result<(), Box>; 17 | 18 | fn with_state( 19 | &self, 20 | t: T, 21 | u: DVectorSlice, 22 | v: DVectorSlice, 23 | ) -> Result>; 24 | } 25 | 26 | pub trait DynamicSystemSnapshot { 27 | fn eval_f(&self, f: DVectorSliceMut); 28 | } 29 | 30 | pub trait DifferentiableDynamicSystemSnapshot<'op, T: Scalar>: DynamicSystemSnapshot { 31 | type ApplyJacobianCombinationOperator: Operator + 'op; 32 | type SolveJacobianCombinationOperator: Operator + 'op; 33 | 34 | fn build_apply_jacobian_combination_operator( 35 | &'op self, 36 | alpha: T, 37 | beta: T, 38 | gamma: T, 39 | ) -> Result>; 40 | 41 | fn build_solve_jacobian_combination_operator( 42 | &'op self, 43 | alpha: T, 44 | beta: T, 45 | ) -> Result>; 46 | } 47 | */ 48 | -------------------------------------------------------------------------------- /hamilton2/src/dynamic_system/stateless.rs: -------------------------------------------------------------------------------- 1 | use nalgebra::{DVectorSlice, DVectorSliceMut, Scalar}; 2 | use std::error::Error; 3 | 4 | /// An abstract dynamic system represented by a second-order ODE. 5 | /// 6 | /// A dynamic system is represented by the second-order system of ODEs 7 | /// 8 | /// ```ignore 9 | /// M dv/dt = f(t, u, v), 10 | /// du/dt = v. 11 | /// ``` 12 | /// 13 | /// where M is constant. 14 | pub trait StatelessDynamicSystem { 15 | /// Apply the mass matrix `M` to the vector `x` and accumulate the result in `y`, 16 | /// yielding `y = y + Mx`. 17 | fn apply_mass_matrix(&self, y: DVectorSliceMut, x: DVectorSlice); 18 | 19 | fn apply_inverse_mass_matrix(&self, sol: DVectorSliceMut, rhs: DVectorSlice) -> Result<(), Box>; 20 | 21 | fn eval_f(&self, f: DVectorSliceMut, t: T, u: DVectorSlice, v: DVectorSlice); 22 | } 23 | 24 | /// An abstract dynamic system that allows for differentiation of the functions involved. 25 | /// 26 | /// This enables implicit integrators to work with an abstract representation of the dynamic 27 | /// system. 28 | pub trait StatelessDifferentiableDynamicSystem: StatelessDynamicSystem { 29 | /// Apply a linear combination of Jacobians to a vector. 30 | /// 31 | /// Computes `y = y + (alpha * df/du + beta * df/dv + gamma * M) * x`, 32 | /// with the Jacobians evaluated at time `t` and the provided `u` and `v` variables. 33 | fn apply_jacobian_combination( 34 | &self, 35 | y: DVectorSliceMut, 36 | x: DVectorSlice, 37 | t: T, 38 | u: DVectorSlice, 39 | v: DVectorSlice, 40 | alpha: Option, 41 | beta: Option, 42 | gamma: Option, 43 | ) -> Result<(), Box>; 44 | 45 | /// Solve a system consisting of linear combinations of Jacobians. 46 | /// 47 | /// Specifically, solve a linear system 48 | /// 49 | /// ```ignore 50 | /// H x = rhs, 51 | /// ``` 52 | /// 53 | /// where `H = M + alpha * df/du + beta * df/dv`, 54 | /// with `H` evaluated at time `t` and the provided `u` and `v` variables. 55 | fn solve_jacobian_combination( 56 | &self, 57 | sol: DVectorSliceMut, 58 | rhs: DVectorSlice, 59 | t: T, 60 | u: DVectorSlice, 61 | v: DVectorSlice, 62 | alpha: Option, 63 | beta: Option, 64 | ) -> Result<(), Box>; 65 | } 66 | -------------------------------------------------------------------------------- /hamilton2/src/integrators/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod euler; 2 | 3 | pub use euler::{backward_euler_step, symplectic_euler_step, BackwardEulerSettings}; 4 | -------------------------------------------------------------------------------- /hamilton2/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::excessive_precision)] 2 | #![allow(clippy::too_many_arguments)] 3 | 4 | /// Traits to model dynamic systems that can be integrated by this crate's integrators. 5 | pub mod dynamic_system; 6 | /// Implementations of various integration schemes for dynamic systems. 7 | pub mod integrators; 8 | 9 | /// Calculus helper traits and numerical differentiation 10 | pub mod calculus; 11 | /// Implementations of the Newton method with different line search strategies 12 | pub mod newton; 13 | -------------------------------------------------------------------------------- /hamilton2/tests/unit.rs: -------------------------------------------------------------------------------- 1 | mod unit_tests; 2 | 3 | #[macro_use] 4 | pub mod utils; 5 | -------------------------------------------------------------------------------- /hamilton2/tests/unit_tests/calculus.rs: -------------------------------------------------------------------------------- 1 | use hamilton2::calculus::*; 2 | use nalgebra::{DMatrix, DVector, DVectorSlice, DVectorSliceMut}; 3 | 4 | #[test] 5 | fn approximate_jacobian_simple_function() { 6 | struct SimpleTwoDimensionalPolynomial; 7 | 8 | // TODO: Rewrite with VectorFunctionBuilder 9 | 10 | impl VectorFunction for SimpleTwoDimensionalPolynomial { 11 | fn dimension(&self) -> usize { 12 | 2 13 | } 14 | 15 | fn eval_into(&mut self, f: &mut DVectorSliceMut, x: &DVectorSlice) { 16 | assert_eq!(x.len(), 2); 17 | assert_eq!(f.len(), x.len()); 18 | let x1 = x[0]; 19 | let x2 = x[1]; 20 | f[0] = x1 * x2 + 3.0; 21 | f[1] = x1 * x1 + x2 * x2 + x1 + 5.0; 22 | } 23 | } 24 | 25 | let h = 1e-6; 26 | let x = DVector::from_column_slice(&[3.0, 4.0]); 27 | let j = approximate_jacobian(SimpleTwoDimensionalPolynomial, &x, &h); 28 | 29 | // J = [ x2 x1 ] 30 | // [ 2*x1 + 1 2*x2 ] 31 | 32 | #[rustfmt::skip] 33 | let expected = DMatrix::from_row_slice(2, 2, 34 | &[4.0, 3.0, 35 | 7.0, 8.0]); 36 | 37 | let diff = expected - j; 38 | assert!(diff.norm() < 1e-6); 39 | } 40 | -------------------------------------------------------------------------------- /hamilton2/tests/unit_tests/integrators.rs: -------------------------------------------------------------------------------- 1 | use hamilton2::dynamic_system::{IntoStateful, StatelessDifferentiableDynamicSystem, StatelessDynamicSystem}; 2 | use hamilton2::integrators::{backward_euler_step, symplectic_euler_step, BackwardEulerSettings}; 3 | use nalgebra::{DVector, DVectorSlice, DVectorSliceMut, Vector3}; 4 | use std::error::Error; 5 | 6 | use crate::assert_approx_matrix_eq; 7 | 8 | /// Define the (mock) dynamic system 9 | /// M dv/dt + f(t, u, v) = 0 10 | /// du/dt - v = 0, 11 | /// with 12 | /// M = a * I 13 | /// f(t, u, v) = b * t * ones + c * u + d * v 14 | pub struct MockDynamicSystem { 15 | a: f64, 16 | b: f64, 17 | c: f64, 18 | d: f64, 19 | } 20 | 21 | impl StatelessDynamicSystem for MockDynamicSystem { 22 | fn apply_mass_matrix(&self, mut y: DVectorSliceMut, x: DVectorSlice) { 23 | y.axpy(self.a, &x, 1.0); 24 | } 25 | 26 | fn apply_inverse_mass_matrix( 27 | &self, 28 | mut y: DVectorSliceMut, 29 | x: DVectorSlice, 30 | ) -> Result<(), Box> { 31 | y.copy_from(&(x / self.a)); 32 | Ok(()) 33 | } 34 | 35 | fn eval_f(&self, mut f: DVectorSliceMut, t: f64, u: DVectorSlice, v: DVectorSlice) { 36 | let ones = DVector::repeat(u.len(), 1.0); 37 | f.copy_from(&(self.b * t * ones + self.c * u + self.d * v)); 38 | } 39 | } 40 | 41 | impl StatelessDifferentiableDynamicSystem for MockDynamicSystem { 42 | fn apply_jacobian_combination( 43 | &self, 44 | _y: DVectorSliceMut, 45 | _x: DVectorSlice, 46 | _t: f64, 47 | _u: DVectorSlice, 48 | _v: DVectorSlice, 49 | _alpha: Option, 50 | _beta: Option, 51 | _gamma: Option, 52 | ) -> Result<(), Box> { 53 | unimplemented!(); 54 | } 55 | 56 | fn solve_jacobian_combination( 57 | &self, 58 | mut sol: DVectorSliceMut, 59 | rhs: DVectorSlice, 60 | _t: f64, 61 | _u: DVectorSlice, 62 | _v: DVectorSlice, 63 | alpha: Option, 64 | beta: Option, 65 | ) -> Result<(), Box> { 66 | let alpha = alpha.unwrap_or(0.0); 67 | let beta = beta.unwrap_or(0.0); 68 | let g = self.a + alpha * self.c + beta * self.d; 69 | sol.copy_from(&(rhs / g)); 70 | Ok(()) 71 | } 72 | } 73 | 74 | #[test] 75 | fn symplectic_euler_step_mock() { 76 | let (a, b, c, d) = (2.0, 3.0, 4.0, 5.0); 77 | let mut system = MockDynamicSystem { a, b, c, d }.into_stateful(); 78 | let t0 = 2.0; 79 | let dt = 0.5; 80 | 81 | let mut u = Vector3::new(1.0, 2.0, 3.0); 82 | let mut v = Vector3::new(4.0, 5.0, 6.0); 83 | 84 | let mut f = Vector3::zeros(); 85 | let mut dv = Vector3::zeros(); 86 | 87 | // Compute expected values 88 | let v_next_expected = &v + (dt / a) * (b * t0 * Vector3::repeat(1.0) + c * &u + d * &v); 89 | let u_next_expected = &u + dt * &v_next_expected; 90 | 91 | symplectic_euler_step(&mut system, &mut u, &mut v, &mut f, &mut dv, t0, dt).unwrap(); 92 | 93 | assert_approx_matrix_eq!(v, v_next_expected, abstol = 1e-8); 94 | assert_approx_matrix_eq!(u, u_next_expected, abstol = 1e-8); 95 | } 96 | 97 | #[test] 98 | fn backward_euler_step_mock() { 99 | let (a, b, c, d) = (2.0, 3.0, 4.0, 5.0); 100 | let mut system = MockDynamicSystem { a, b, c, d }.into_stateful(); 101 | let t0 = 2.0; 102 | let dt = 0.5; 103 | 104 | let mut u = Vector3::new(1.0, 2.0, 3.0); 105 | let mut v = Vector3::new(4.0, 5.0, 6.0); 106 | 107 | let settings = BackwardEulerSettings { 108 | max_newton_iter: Some(100), 109 | tolerance: 1e-8, 110 | }; 111 | 112 | let (u_next_expected, v_next_expected) = { 113 | let h = a - dt * d - dt * dt * c; 114 | let t = t0 + dt; 115 | let ones = Vector3::repeat(1.0); 116 | let v_next_expected = (a * &v + dt * (b * t * ones + c * &u)) / h; 117 | let u_next_expected = &u + dt * &v_next_expected; 118 | (u_next_expected, v_next_expected) 119 | }; 120 | 121 | backward_euler_step(&mut system, &mut u, &mut v, t0, dt, settings).unwrap(); 122 | 123 | assert_approx_matrix_eq!(v, v_next_expected, abstol = 1e-6); 124 | assert_approx_matrix_eq!(u, u_next_expected, abstol = 1e-6); 125 | } 126 | -------------------------------------------------------------------------------- /hamilton2/tests/unit_tests/mod.rs: -------------------------------------------------------------------------------- 1 | mod calculus; 2 | mod integrators; 3 | mod newton; 4 | -------------------------------------------------------------------------------- /hamilton2/tests/unit_tests/newton.rs: -------------------------------------------------------------------------------- 1 | use hamilton2::calculus::{DifferentiableVectorFunction, VectorFunction}; 2 | use hamilton2::newton::*; 3 | use nalgebra::{DVector, DVectorSlice, DVectorSliceMut, Matrix3, Vector3}; 4 | use numeric_literals::replace_numeric_literals; 5 | use std::error::Error; 6 | 7 | struct MockLinearVectorFunction; 8 | 9 | impl VectorFunction for MockLinearVectorFunction { 10 | fn dimension(&self) -> usize { 11 | 3 12 | } 13 | 14 | #[replace_numeric_literals(f64::from(literal))] 15 | fn eval_into(&mut self, f: &mut DVectorSliceMut, x: &DVectorSlice) { 16 | let a = Matrix3::new(5, 1, 2, 1, 4, 2, 2, 2, 4); 17 | let b = Vector3::new(1, 2, 3); 18 | let r = a * x - b; 19 | f.copy_from(&r); 20 | } 21 | } 22 | 23 | impl DifferentiableVectorFunction for MockLinearVectorFunction { 24 | #[replace_numeric_literals(f64::from(literal))] 25 | fn solve_jacobian_system( 26 | &mut self, 27 | sol: &mut DVectorSliceMut, 28 | _x: &DVectorSlice, 29 | rhs: &DVectorSlice, 30 | ) -> Result<(), Box> { 31 | let a = Matrix3::new(5, 1, 2, 1, 4, 2, 2, 2, 4); 32 | let a_inv = a.try_inverse().unwrap(); 33 | sol.copy_from(&(a_inv * rhs)); 34 | Ok(()) 35 | } 36 | } 37 | 38 | #[test] 39 | fn newton_converges_in_single_iteration_for_linear_system() { 40 | // TODO: Use VectorFunctionBuilder or a mock from mockiato 41 | 42 | let expected_solution = Vector3::new(-0.125, 0.16666667, 0.72916667); 43 | 44 | let settings = NewtonSettings { 45 | max_iterations: Some(2), 46 | tolerance: Vector3::new(1.0, 2.0, 3.0).norm() * 1e-6, 47 | }; 48 | 49 | let mut f = DVector::zeros(3); 50 | let mut x = DVector::zeros(3); 51 | let mut dx = DVector::zeros(3); 52 | 53 | let iterations = 54 | newton(MockLinearVectorFunction, &mut x, &mut f, &mut dx, settings).expect("Newton iterations must succeed"); 55 | let diff = x - expected_solution; 56 | assert!(diff.norm() < 1e-6); 57 | assert_eq!(iterations, 1); 58 | } 59 | -------------------------------------------------------------------------------- /hamilton2/tests/utils/mod.rs: -------------------------------------------------------------------------------- 1 | /// Poor man's approx assertion for matrices 2 | #[macro_export] 3 | macro_rules! assert_approx_matrix_eq { 4 | ($x:expr, $y:expr, abstol = $tol:expr) => {{ 5 | let diff = $x - $y; 6 | 7 | let max_absdiff = diff.abs().max(); 8 | let approx_eq = max_absdiff <= $tol; 9 | 10 | if !approx_eq { 11 | println!("abstol: {}", $tol); 12 | println!("left: {}", $x); 13 | println!("right: {}", $y); 14 | println!("diff: {:e}", diff); 15 | } 16 | assert!(approx_eq); 17 | }}; 18 | } 19 | 20 | #[macro_export] 21 | macro_rules! assert_panics { 22 | ($e:expr) => {{ 23 | use std::panic::catch_unwind; 24 | use std::stringify; 25 | let expr_string = stringify!($e); 26 | let result = catch_unwind(|| $e); 27 | if result.is_ok() { 28 | panic!("assert_panics!({}) failed.", expr_string); 29 | } 30 | }}; 31 | } 32 | -------------------------------------------------------------------------------- /intel-mkl-src-patched/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "intel-mkl-src" 3 | version = "0.5.0" 4 | authors = ["Andreas Longva "] 5 | edition = "2018" 6 | publish = false 7 | 8 | # This is a dummy package that circumvents the intel-mkl-src package, which attempts to download MKL as part of the 9 | # build project. We replace it in-tree by this dummy package, which instead uses mkl-sys to link 10 | # to MKL. 11 | 12 | [features] 13 | use-shared = [] 14 | 15 | [dependencies] 16 | # Note: This should match the same version as mkl-corrode is using 17 | [dependencies.mkl-sys] 18 | git = "https://github.com/Andlon/mkl-sys" 19 | rev = "e144301e42bf984e28b53026d366cfacbefa6d0f" -------------------------------------------------------------------------------- /intel-mkl-src-patched/src/lib.rs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /lp-bfp/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lp-bfp" 3 | version = "0.1.0" 4 | authors = ["Andreas Longva "] 5 | edition = "2018" 6 | links = "ortools" 7 | publish = false 8 | 9 | [dependencies] 10 | libc = "0.2.66" 11 | 12 | [dev-dependencies] 13 | nalgebra = "0.21" 14 | 15 | [build-dependencies] 16 | cmake = "0.1" 17 | -------------------------------------------------------------------------------- /lp-bfp/README.md: -------------------------------------------------------------------------------- 1 | lp-bfp 2 | ====== 3 | 4 | A tiny Rust library written as a wrapper around a small C++ project that relies on an external LP solver to find a basic feasible point of a Linear Program. 5 | 6 | To use, you need: 7 | 8 | - To install Google OR-Tools. Get it here: https://developers.google.com/optimization. 9 | - To set the environment variable `ORTOOLS_ROOT` to the root directory of your OR-Tools installation. 10 | The root directory is the folder containing `lib`, `bin` etc. 11 | - To make sure that the `lib` directory in `ORTOOLS_ROOT` is available in your library search path. 12 | For Linux platforms, this can be accomplished by e.g. including `$ORTOOLS_ROOT/lib` in the `$LD_LIBRARY_PATH` environment 13 | variable. For Windows, this can be accomplished by including the lib directory in `PATH` (TODO: Is this correct?). 14 | -------------------------------------------------------------------------------- /lp-bfp/build.rs: -------------------------------------------------------------------------------- 1 | use cmake; 2 | use std::env::vars; 3 | 4 | fn main() { 5 | let cpp = cmake::build("cpp"); 6 | 7 | let or_tools_root = vars() 8 | .find(|(var, _)| var == "ORTOOLS_ROOT") 9 | .map(|(_, value)| value) 10 | .expect("Could not find ORTOOLS_ROOT"); 11 | 12 | let or_tools_lib_path = format!("{}/lib", or_tools_root); 13 | 14 | // Link static shim library 15 | println!("cargo:rustc-link-search=native={}/lib", cpp.display()); 16 | println!("cargo:rustc-link-lib=static=lp-bfp"); 17 | 18 | // Link OR-tools 19 | println!("cargo:rustc-link-search=native={}", &or_tools_lib_path); 20 | println!("cargo:rustc-link-lib=dylib=ortools"); 21 | 22 | // TODO: Come up with something more general? 23 | if cfg!(target_env = "gnu") { 24 | // GCC-specific, link to C++ stdlib 25 | println!("cargo:rustc-link-lib=dylib=stdc++"); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lp-bfp/cpp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | project(cpp) 3 | 4 | set(CMAKE_CXX_STANDARD 14) 5 | set(WINDOWS_EXPORT_ALL_SYMBOLS true) 6 | 7 | SET(ORTOOLS_ROOT $ENV{ORTOOLS_ROOT}) 8 | set(ORTOOLS_INCLUDE ${ORTOOLS_ROOT}/include) 9 | set(ORTOOLS_LIB ${ORTOOLS_ROOT}/lib) 10 | 11 | add_library(lp-bfp STATIC lp-bfp.cpp lp-bfp.h) 12 | target_include_directories(lp-bfp SYSTEM PUBLIC ${ORTOOLS_INCLUDE}) 13 | target_link_libraries(lp-bfp libortools) 14 | 15 | # Ideally we'd use target_link_directories, but it is only supported by fairly recent versions of CMake 16 | link_directories(${ORTOOLS_LIB}) 17 | 18 | IF (WIN32) 19 | # OR-Tools wants to define min/max functions but also includes windows.h which defines min/max macros itself 20 | # Therefore add a flag that disables the min/max macros from windows.h 21 | target_compile_definitions(lp-bfp PUBLIC NOMINMAX) 22 | ENDIF() 23 | 24 | install(TARGETS lp-bfp 25 | ARCHIVE DESTINATION lib 26 | LIBRARY DESTINATION lib 27 | RUNTIME DESTINATION bin) -------------------------------------------------------------------------------- /lp-bfp/cpp/lp-bfp.cpp: -------------------------------------------------------------------------------- 1 | #include "lp-bfp.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | int lp_bfp_solve_lp(double * x, 8 | const double * c, 9 | const double * A, 10 | const double * b, 11 | const double * lb, 12 | const double * ub, 13 | int num_constraints, 14 | int num_variables, 15 | bool verbose) 16 | { 17 | using operations_research::glop::LinearProgram; 18 | using operations_research::glop::ColIndex; 19 | using operations_research::glop::ProblemStatus; 20 | using operations_research::glop::LPSolver; 21 | using operations_research::glop::GlopParameters; 22 | using operations_research::glop::GlopParameters_SolverBehavior; 23 | using std::cout; 24 | 25 | LinearProgram lp; 26 | 27 | std::vector variables; 28 | for (int i = 0; i < num_variables; ++i) 29 | { 30 | const auto variable = lp.CreateNewVariable(); 31 | lp.SetVariableBounds(variable, lb[i], ub[i]); 32 | lp.SetObjectiveCoefficient(variable, c[i]); 33 | variables.push_back(variable); 34 | } 35 | 36 | for (int i = 0; i < num_constraints; ++i) 37 | { 38 | const auto constraint_row = lp.CreateNewConstraint(); 39 | lp.SetConstraintBounds(constraint_row, b[i], b[i]); 40 | 41 | for (int j = 0; j < num_variables; ++j) 42 | { 43 | const auto linear_matrix_index = num_variables * i + j; 44 | const auto a_ij = A[linear_matrix_index]; 45 | // GLOP uses sparse matrices under the hood, and it does not like explicit zeros. 46 | if (a_ij != 0.0) { 47 | lp.SetCoefficient(constraint_row, variables[j], a_ij); 48 | } 49 | } 50 | } 51 | 52 | if (verbose) { 53 | cout << "Constructed LP with " << num_variables << " variables and " << num_constraints << " constraints." 54 | << std::endl; 55 | } 56 | 57 | auto params = GlopParameters(); 58 | // Try to make GLOP change the problem as little as possible, 59 | // so that we hopefully get a more precise solution 60 | params.set_primal_feasibility_tolerance(1e-14); 61 | params.set_drop_tolerance(0.0); 62 | params.set_solve_dual_problem(GlopParameters_SolverBehavior::GlopParameters_SolverBehavior_NEVER_DO); 63 | params.set_use_dual_simplex(false); 64 | 65 | LPSolver glop_solver; 66 | glop_solver.SetParameters(params); 67 | const auto status = glop_solver.Solve(lp); 68 | 69 | if (verbose) { 70 | std::cout << lp.GetPrettyProblemStats() << std::endl; 71 | std::cout << "Status: " << status << std::endl; 72 | } 73 | 74 | if (status == ProblemStatus::OPTIMAL) { 75 | for (int i = 0; i < num_variables; ++i) { 76 | x[i] = glop_solver.variable_values().at(variables.at(i)); 77 | } 78 | 79 | return 0; 80 | } else { 81 | // TODO: Handle error conditions 82 | return 1; 83 | } 84 | } -------------------------------------------------------------------------------- /lp-bfp/cpp/lp-bfp.h: -------------------------------------------------------------------------------- 1 | #ifndef LP_BFP_H 2 | #define LP_BFP_H 3 | 4 | extern "C" { 5 | /// Attempts to solve the given Linear Program, storing the result in `x`. 6 | /// 7 | /// The LP is given by 8 | /// min c^T x 9 | /// s.t. Ax = b 10 | /// lb <= x <= ub 11 | /// Let A be an m x n matrix. Then the LP has m equality constraints and 12 | /// n (possibly bounded) variables. 13 | /// 14 | /// \param x Output array of length n. 15 | /// \param c Array of length n. 16 | /// \param A Row-major m x n matrix stored as array of length mxn. 17 | /// \param b Array of length m. 18 | /// \param lb Array of length n. 19 | /// \param ub Array of length n. 20 | /// \param num_constraints m. 21 | /// \param num_variables n. 22 | /// \param verbose Whether or not to print debug output to stdout. 23 | /// \return An error code. 0 denotes success. 24 | int lp_bfp_solve_lp(double * x, 25 | const double * c, 26 | const double * A, 27 | const double * b, 28 | const double * lb, 29 | const double * ub, 30 | int num_constraints, 31 | int num_variables, 32 | bool verbose); 33 | }; 34 | 35 | #endif // LP_BFP_H 36 | -------------------------------------------------------------------------------- /lp-bfp/src/lib.rs: -------------------------------------------------------------------------------- 1 | use libc::{c_double, c_int}; 2 | use std::convert::TryFrom; 3 | use std::error::Error; 4 | use std::fmt; 5 | use std::fmt::{Display, Formatter}; 6 | 7 | extern "C" { 8 | fn lp_bfp_solve_lp( 9 | x: *mut c_double, 10 | c: *const c_double, 11 | a: *const c_double, 12 | b: *const c_double, 13 | lb: *const c_double, 14 | ub: *const c_double, 15 | num_constraints: c_int, 16 | num_variables: c_int, 17 | verbose: bool, 18 | ) -> c_int; 19 | } 20 | 21 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 22 | pub enum Verbosity { 23 | NoVerbose, 24 | Verbose, 25 | } 26 | 27 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 28 | pub struct BfpError { 29 | // Private construction outside of library 30 | private: (), 31 | } 32 | 33 | impl Display for BfpError { 34 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 35 | write!(f, "Unspecified error during solve.") 36 | } 37 | } 38 | 39 | impl Error for BfpError {} 40 | 41 | pub fn solve_lp( 42 | c: &[f64], 43 | a: &[f64], 44 | b: &[f64], 45 | lb: &[f64], 46 | ub: &[f64], 47 | verbosity: Verbosity, 48 | ) -> Result, BfpError> { 49 | assert_eq!(lb.len(), ub.len(), "Lower and upper bounds must have same length."); 50 | let num_constraints = b.len(); 51 | let num_variables = lb.len(); 52 | assert_eq!( 53 | a.len(), 54 | num_constraints * num_variables, 55 | "Number of elements in a must be consistent with number of \ 56 | variables and constraints." 57 | ); 58 | assert_eq!( 59 | c.len(), 60 | num_variables, 61 | "Length of c must be equal to number of variables." 62 | ); 63 | 64 | let verbose = verbosity == Verbosity::Verbose; 65 | let mut solution = vec![0.0; num_variables]; 66 | 67 | // TODO: Make error? 68 | let num_constraints = 69 | c_int::try_from(num_constraints).expect("Number of constraints is too large to fit in `int`."); 70 | let num_variables = c_int::try_from(num_variables).expect("Number of variables is too large to fit in `int`."); 71 | 72 | let error_code = unsafe { 73 | lp_bfp_solve_lp( 74 | solution.as_mut_ptr(), 75 | c.as_ptr(), 76 | a.as_ptr(), 77 | b.as_ptr(), 78 | lb.as_ptr(), 79 | ub.as_ptr(), 80 | num_constraints, 81 | num_variables, 82 | verbose, 83 | ) 84 | }; 85 | 86 | if error_code == 0 { 87 | Ok(solution) 88 | } else { 89 | Err(BfpError { private: () }) 90 | } 91 | } 92 | 93 | #[cfg(test)] 94 | mod tests { 95 | use super::{solve_lp, Verbosity}; 96 | use nalgebra::{DMatrix, DVector}; 97 | 98 | #[test] 99 | fn solve_basic_lp() { 100 | // Problem is constructed by constructing b such that A * x0 = b 101 | let x0 = vec![0.0, 3.0, 7.0, 0.0, 1.0, 0.0]; 102 | let b = vec![28.0, -15.0, 58.0]; 103 | let a = vec![ 104 | 1.0, -1.0, 4.0, 2.0, 3.0, 3.0, -5.0, 2.0, -3.0, 2.0, 0.0, 4.0, 3.0, 1.0, 7.0, -3.0, 6.0, 0.0, 105 | ]; 106 | let lb = vec![0.0, 0.0, 0.0, 0.0, 0.0, 0.0]; 107 | let ub = vec![2.0, 4.0, 8.0, 9.0, 2.0, 3.0]; 108 | let c = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]; 109 | 110 | let x = solve_lp(&c, &a, &b, &lb, &ub, Verbosity::NoVerbose).unwrap(); 111 | 112 | // Check bounds 113 | for i in 0..x.len() { 114 | assert!(lb[i] <= x[i] && x[i] <= ub[i]); 115 | } 116 | 117 | // Check equality constraints 118 | let a = DMatrix::from_row_slice(3, 6, &a); 119 | let x = DVector::from_column_slice(&x); 120 | let b = DVector::from_column_slice(&b); 121 | let c = DVector::from_column_slice(&c); 122 | let x0 = DVector::from_column_slice(&x0); 123 | let r = a * &x - b; 124 | 125 | let objective_val = c.dot(&x); 126 | assert!(r.norm() < 1e-12); 127 | // TODO: Have a better, more "complete" test? 128 | assert!(objective_val <= c.dot(&x0) + 1e-6); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /nested-vec/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nested-vec" 3 | version = "0.1.0" 4 | authors = ["Andreas Longva "] 5 | edition = "2018" 6 | publish = false 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [dependencies] 11 | # TODO: Make optional feature 12 | serde = { version="1.0", features = [ "derive" ] } -------------------------------------------------------------------------------- /nested-vec/src/lib.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use std::fmt; 3 | use std::fmt::Debug; 4 | use std::ops::Range; 5 | 6 | #[derive(Clone, PartialEq, Eq, Serialize, Deserialize)] 7 | pub struct NestedVec { 8 | data: Vec, 9 | offsets_begin: Vec, 10 | offsets_end: Vec, 11 | } 12 | 13 | impl Debug for NestedVec { 14 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 15 | f.debug_list().entries(self.iter()).finish() 16 | } 17 | } 18 | 19 | impl Default for NestedVec { 20 | fn default() -> Self { 21 | Self::new() 22 | } 23 | } 24 | 25 | impl NestedVec { 26 | pub fn new() -> Self { 27 | Self { 28 | data: Vec::new(), 29 | offsets_begin: Vec::new(), 30 | offsets_end: Vec::new(), 31 | } 32 | } 33 | 34 | /// Return a data structure that can be used for appending single elements to the same array. 35 | /// When the returned data structure is dropped, the result is equivalent to 36 | /// adding the array at once with `CompactArrayStorage::push`. 37 | /// 38 | /// TODO: Need better name 39 | pub fn begin_array<'a>(&'a mut self) -> ArrayAppender<'a, T> { 40 | let initial_count = self.data.len(); 41 | ArrayAppender { 42 | initial_count, 43 | data: &mut self.data, 44 | offsets_begin: &mut self.offsets_begin, 45 | offsets_end: &mut self.offsets_end, 46 | } 47 | } 48 | 49 | pub fn iter<'a>(&'a self) -> impl 'a + Iterator { 50 | (0..self.len()).map(move |i| self.get(i).unwrap()) 51 | } 52 | 53 | pub fn len(&self) -> usize { 54 | self.offsets_begin.len() 55 | } 56 | 57 | /// Returns an iterator over all elements inside all arrays. 58 | pub fn iter_array_elements<'a>(&'a self) -> impl 'a + Iterator { 59 | self.iter().flatten() 60 | } 61 | 62 | pub fn total_num_elements(&self) -> usize { 63 | self.offsets_begin 64 | .iter() 65 | .zip(&self.offsets_end) 66 | .map(|(begin, end)| end - begin) 67 | .sum() 68 | } 69 | 70 | pub fn get(&self, index: usize) -> Option<&[T]> { 71 | let range = self.get_index_range(index)?; 72 | self.data.get(range) 73 | } 74 | 75 | pub fn get_mut(&mut self, index: usize) -> Option<&mut [T]> { 76 | let range = self.get_index_range(index)?; 77 | self.data.get_mut(range) 78 | } 79 | 80 | fn get_index_range(&self, index: usize) -> Option> { 81 | let begin = *self.offsets_begin.get(index)?; 82 | let end = *self.offsets_end.get(index)?; 83 | Some(begin..end) 84 | } 85 | 86 | pub fn first(&self) -> Option<&[T]> { 87 | self.get(0) 88 | } 89 | 90 | pub fn first_mut(&mut self) -> Option<&mut [T]> { 91 | self.get_mut(0) 92 | } 93 | 94 | fn get_last_range(&self) -> Option> { 95 | let begin = *self.offsets_begin.last()?; 96 | let end = *self.offsets_end.last()?; 97 | Some(begin..end) 98 | } 99 | 100 | pub fn last(&self) -> Option<&[T]> { 101 | let range = self.get_last_range()?; 102 | self.data.get(range) 103 | } 104 | 105 | pub fn last_mut(&mut self) -> Option<&mut [T]> { 106 | let range = self.get_last_range()?; 107 | self.data.get_mut(range) 108 | } 109 | 110 | pub fn clear(&mut self) { 111 | self.offsets_end.clear(); 112 | self.offsets_begin.clear(); 113 | self.data.clear(); 114 | } 115 | } 116 | 117 | #[derive(Debug)] 118 | pub struct ArrayAppender<'a, T> { 119 | data: &'a mut Vec, 120 | offsets_begin: &'a mut Vec, 121 | offsets_end: &'a mut Vec, 122 | initial_count: usize, 123 | } 124 | 125 | impl<'a, T> ArrayAppender<'a, T> { 126 | pub fn push_single(&mut self, element: T) -> &mut Self { 127 | self.data.push(element); 128 | self 129 | } 130 | 131 | pub fn count(&self) -> usize { 132 | self.data.len() - self.initial_count 133 | } 134 | } 135 | 136 | impl<'a, T> Drop for ArrayAppender<'a, T> { 137 | fn drop(&mut self) { 138 | self.offsets_begin.push(self.initial_count); 139 | self.offsets_end.push(self.data.len()); 140 | } 141 | } 142 | 143 | impl NestedVec { 144 | pub fn push(&mut self, array: &[T]) { 145 | self.offsets_begin.push(self.data.len()); 146 | self.data.extend_from_slice(array); 147 | self.offsets_end.push(self.data.len()); 148 | } 149 | } 150 | 151 | impl<'a, T: Clone> From<&'a Vec>> for NestedVec { 152 | fn from(nested_vec: &'a Vec>) -> Self { 153 | let mut result = Self::new(); 154 | for vec in nested_vec { 155 | result.push(vec); 156 | } 157 | result 158 | } 159 | } 160 | 161 | impl From>> for NestedVec { 162 | fn from(vec_vec: Vec>) -> Self { 163 | Self::from(&vec_vec) 164 | } 165 | } 166 | 167 | impl<'a, T: Clone> From<&'a NestedVec> for Vec> { 168 | fn from(nested: &NestedVec) -> Self { 169 | nested.iter().map(|slice| slice.to_vec()).collect() 170 | } 171 | } 172 | 173 | impl<'a, T: Clone> From> for Vec> { 174 | fn from(nested: NestedVec) -> Self { 175 | Self::from(&nested) 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /nested-vec/tests/test.rs: -------------------------------------------------------------------------------- 1 | use nested_vec::NestedVec; 2 | 3 | #[test] 4 | fn begin_array() { 5 | let mut storage = NestedVec::::new(); 6 | 7 | { 8 | // Add empty array 9 | storage.begin_array(); 10 | } 11 | 12 | assert_eq!(storage.len(), 1); 13 | assert_eq!(storage.get(0).unwrap(), []); 14 | assert!(storage.get(1).is_none()); 15 | assert_eq!( 16 | Vec::::new(), 17 | storage.iter().flatten().cloned().collect::>() 18 | ); 19 | 20 | { 21 | storage.begin_array().push_single(5).push_single(9); 22 | } 23 | 24 | assert_eq!(storage.len(), 2); 25 | assert_eq!(storage.get(0).unwrap(), []); 26 | assert_eq!(storage.get(1).unwrap(), [5, 9]); 27 | assert!(storage.get(2).is_none()); 28 | 29 | { 30 | // Add empty array 31 | storage.begin_array(); 32 | } 33 | 34 | assert_eq!(storage.len(), 3); 35 | assert_eq!(storage.get(0).unwrap(), []); 36 | assert_eq!(storage.get(1).unwrap(), [5, 9]); 37 | assert_eq!(storage.get(2).unwrap(), []); 38 | assert!(storage.get(3).is_none()); 39 | 40 | { 41 | storage.begin_array().push_single(3); 42 | } 43 | 44 | assert_eq!(storage.len(), 4); 45 | assert_eq!(storage.get(0).unwrap(), []); 46 | assert_eq!(storage.get(1).unwrap(), [5, 9]); 47 | assert_eq!(storage.get(2).unwrap(), []); 48 | assert_eq!(storage.get(3).unwrap(), [3]); 49 | assert!(storage.get(4).is_none()); 50 | } 51 | -------------------------------------------------------------------------------- /notebooks/convergence_rate/hex_results.json: -------------------------------------------------------------------------------- 1 | { 2 | "methods": { 3 | "fcm_hex20": [ 4 | { 5 | "resolution": 1, 6 | "mesh_size": 1.0, 7 | "l2_error": 0.015568217362350112 8 | }, 9 | { 10 | "resolution": 2, 11 | "mesh_size": 0.5, 12 | "l2_error": 0.006600855273375664 13 | }, 14 | { 15 | "resolution": 4, 16 | "mesh_size": 0.25, 17 | "l2_error": 0.0008224766685536088 18 | }, 19 | { 20 | "resolution": 8, 21 | "mesh_size": 0.125, 22 | "l2_error": 0.00010777672812763918 23 | }, 24 | { 25 | "resolution": 16, 26 | "mesh_size": 0.0625, 27 | "l2_error": 0.000016211783973513926 28 | }, 29 | { 30 | "resolution": 24, 31 | "mesh_size": 0.041666666666666664, 32 | "l2_error": 5.385588663732758e-6 33 | }, 34 | { 35 | "resolution": 32, 36 | "mesh_size": 0.03125, 37 | "l2_error": 2.4413064065673753e-6 38 | } 39 | ], 40 | "fcm_hex8": [ 41 | { 42 | "resolution": 1, 43 | "mesh_size": 1.0, 44 | "l2_error": 0.027304043294701312 45 | }, 46 | { 47 | "resolution": 2, 48 | "mesh_size": 0.5, 49 | "l2_error": 0.015045633757349396 50 | }, 51 | { 52 | "resolution": 4, 53 | "mesh_size": 0.25, 54 | "l2_error": 0.00825306942680628 55 | }, 56 | { 57 | "resolution": 8, 58 | "mesh_size": 0.125, 59 | "l2_error": 0.0024339772404086937 60 | }, 61 | { 62 | "resolution": 16, 63 | "mesh_size": 0.0625, 64 | "l2_error": 0.0006553969625189122 65 | }, 66 | { 67 | "resolution": 24, 68 | "mesh_size": 0.041666666666666664, 69 | "l2_error": 0.0002991481072869819 70 | }, 71 | { 72 | "resolution": 32, 73 | "mesh_size": 0.03125, 74 | "l2_error": 0.0001701522535675172 75 | } 76 | ], 77 | "fem_hex20": [ 78 | { 79 | "resolution": 1, 80 | "mesh_size": 1.0, 81 | "l2_error": 0.028003418360481765 82 | }, 83 | { 84 | "resolution": 2, 85 | "mesh_size": 0.5, 86 | "l2_error": 0.012527029765963862 87 | }, 88 | { 89 | "resolution": 4, 90 | "mesh_size": 0.25, 91 | "l2_error": 0.007409369481656567 92 | }, 93 | { 94 | "resolution": 8, 95 | "mesh_size": 0.125, 96 | "l2_error": 0.004934755066080688 97 | }, 98 | { 99 | "resolution": 16, 100 | "mesh_size": 0.0625, 101 | "l2_error": 0.0032585811774176543 102 | }, 103 | { 104 | "resolution": 24, 105 | "mesh_size": 0.041666666666666664, 106 | "l2_error": 0.002568843221155484 107 | }, 108 | { 109 | "resolution": 32, 110 | "mesh_size": 0.03125, 111 | "l2_error": 0.0022498423793336476 112 | } 113 | ], 114 | "fem_hex8": [ 115 | { 116 | "resolution": 1, 117 | "mesh_size": 1.0, 118 | "l2_error": 0.038817442703279845 119 | }, 120 | { 121 | "resolution": 2, 122 | "mesh_size": 0.5, 123 | "l2_error": 0.021271582206626553 124 | }, 125 | { 126 | "resolution": 4, 127 | "mesh_size": 0.25, 128 | "l2_error": 0.013676506185528967 129 | }, 130 | { 131 | "resolution": 8, 132 | "mesh_size": 0.125, 133 | "l2_error": 0.00647020351067994 134 | }, 135 | { 136 | "resolution": 16, 137 | "mesh_size": 0.0625, 138 | "l2_error": 0.0035661597210581005 139 | }, 140 | { 141 | "resolution": 24, 142 | "mesh_size": 0.041666666666666664, 143 | "l2_error": 0.002606527931782387 144 | }, 145 | { 146 | "resolution": 32, 147 | "mesh_size": 0.03125, 148 | "l2_error": 0.0022402534392801606 149 | } 150 | ] 151 | } 152 | } -------------------------------------------------------------------------------- /notebooks/mesh_reorder.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import scipy\n", 10 | "import scipy.io\n", 11 | "import numpy as np\n", 12 | "import matplotlib.pylab as plt\n", 13 | "from scipy.sparse import csr_matrix" 14 | ] 15 | }, 16 | { 17 | "cell_type": "code", 18 | "execution_count": null, 19 | "metadata": {}, 20 | "outputs": [], 21 | "source": [] 22 | }, 23 | { 24 | "cell_type": "code", 25 | "execution_count": null, 26 | "metadata": {}, 27 | "outputs": [], 28 | "source": [ 29 | "original_node_matrix = scipy.io.mmread(\"../data/cylinder_shell_fine_fem_quadratic/pattern_original_nodes.mm\")\n", 30 | "original_element_matrix = scipy.io.mmread(\"../data/cylinder_shell_fine_fem_quadratic/pattern_original_elements.mm\")\n", 31 | "plt.figure(figsize=(20, 20))\n", 32 | "plt.spy(original_node_matrix)\n", 33 | "plt.figure(figsize=(20, 20))\n", 34 | "plt.spy(original_element_matrix)" 35 | ] 36 | }, 37 | { 38 | "cell_type": "code", 39 | "execution_count": null, 40 | "metadata": { 41 | "scrolled": false 42 | }, 43 | "outputs": [], 44 | "source": [ 45 | "reordered_node_matrix = scipy.io.mmread(\"../data/cylinder_shell_fine_fem_quadratic/pattern_reordered_nodes.mm\")\n", 46 | "reordered_element_matrix = scipy.io.mmread(\"../data/cylinder_shell_fine_fem_quadratic/pattern_reordered_elements.mm\")\n", 47 | "plt.figure(figsize=(20, 20))\n", 48 | "plt.spy(reordered_node_matrix)\n", 49 | "plt.figure(figsize=(20, 20))\n", 50 | "plt.spy(reordered_element_matrix)" 51 | ] 52 | }, 53 | { 54 | "cell_type": "code", 55 | "execution_count": null, 56 | "metadata": {}, 57 | "outputs": [], 58 | "source": [] 59 | } 60 | ], 61 | "metadata": { 62 | "kernelspec": { 63 | "display_name": "Python 3", 64 | "language": "python", 65 | "name": "python3" 66 | }, 67 | "language_info": { 68 | "codemirror_mode": { 69 | "name": "ipython", 70 | "version": 3 71 | }, 72 | "file_extension": ".py", 73 | "mimetype": "text/x-python", 74 | "name": "python", 75 | "nbconvert_exporter": "python", 76 | "pygments_lexer": "ipython3", 77 | "version": "3.7.4" 78 | } 79 | }, 80 | "nbformat": 4, 81 | "nbformat_minor": 4 82 | } 83 | -------------------------------------------------------------------------------- /notebooks/quad_plots/quad_reduc_results.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "original_strength": 1, 4 | "reduced_strength": 1, 5 | "original_points": 6, 6 | "reduced_points": 4, 7 | "exact_integral": 0.3580185082735656, 8 | "original_integral": 0.20417013839299797, 9 | "original_abs_error": 0.15384836988056766, 10 | "original_rel_error": 0.4297218337187488, 11 | "reduced_integral": 0.19141791746779674, 12 | "reduced_abs_error": 0.1666005908057689, 13 | "reduced_rel_error": 0.4653407210960939 14 | }, 15 | { 16 | "original_strength": 2, 17 | "reduced_strength": 2, 18 | "original_points": 24, 19 | "reduced_points": 10, 20 | "exact_integral": 0.3580185082735656, 21 | "original_integral": 0.34388536794373625, 22 | "original_abs_error": 0.014133140329829375, 23 | "original_rel_error": 0.039476004740598765, 24 | "reduced_integral": 0.3669910406936639, 25 | "reduced_abs_error": 0.008972532420098267, 26 | "reduced_rel_error": 0.02506164405679905 27 | }, 28 | { 29 | "original_strength": 3, 30 | "reduced_strength": 3, 31 | "original_points": 48, 32 | "reduced_points": 20, 33 | "exact_integral": 0.3580185082735656, 34 | "original_integral": 0.3469088822812661, 35 | "original_abs_error": 0.011109625992299532, 36 | "original_rel_error": 0.031030870571111795, 37 | "reduced_integral": 0.2954676719799693, 38 | "reduced_abs_error": 0.06255083629359631, 39 | "reduced_rel_error": 0.17471397385355444 40 | }, 41 | { 42 | "original_strength": 5, 43 | "reduced_strength": 4, 44 | "original_points": 84, 45 | "reduced_points": 35, 46 | "exact_integral": 0.3580185082735656, 47 | "original_integral": 0.3474000591285993, 48 | "original_abs_error": 0.010618449144966347, 49 | "original_rel_error": 0.029658939131863765, 50 | "reduced_integral": 0.3268091724753725, 51 | "reduced_abs_error": 0.031209335798193127, 52 | "reduced_rel_error": 0.0871724089033569 53 | }, 54 | { 55 | "original_strength": 5, 56 | "reduced_strength": 5, 57 | "original_points": 84, 58 | "reduced_points": 56, 59 | "exact_integral": 0.3580185082735656, 60 | "original_integral": 0.3474000591285993, 61 | "original_abs_error": 0.010618449144966347, 62 | "original_rel_error": 0.029658939131863765, 63 | "reduced_integral": 0.3670838192098959, 64 | "reduced_abs_error": 0.009065310936330284, 65 | "reduced_rel_error": 0.025320788525836174 66 | }, 67 | { 68 | "original_strength": 10, 69 | "reduced_strength": 6, 70 | "original_points": 486, 71 | "reduced_points": 84, 72 | "exact_integral": 0.3580185082735656, 73 | "original_integral": 0.35801850827356546, 74 | "original_abs_error": 1.6653345369377348e-16, 75 | "original_rel_error": 4.651531969585314e-16, 76 | "reduced_integral": 0.36382279317395266, 77 | "reduced_abs_error": 0.005804284900387036, 78 | "reduced_rel_error": 0.01621224815548341 79 | }, 80 | { 81 | "original_strength": 10, 82 | "reduced_strength": 7, 83 | "original_points": 486, 84 | "reduced_points": 120, 85 | "exact_integral": 0.3580185082735656, 86 | "original_integral": 0.35801850827356546, 87 | "original_abs_error": 1.6653345369377348e-16, 88 | "original_rel_error": 4.651531969585314e-16, 89 | "reduced_integral": 0.3558862644080259, 90 | "reduced_abs_error": 0.002132243865539729, 91 | "reduced_rel_error": 0.005955680547974519 92 | }, 93 | { 94 | "original_strength": 10, 95 | "reduced_strength": 8, 96 | "original_points": 486, 97 | "reduced_points": 165, 98 | "exact_integral": 0.3580185082735656, 99 | "original_integral": 0.35801850827356546, 100 | "original_abs_error": 1.6653345369377348e-16, 101 | "original_rel_error": 4.651531969585314e-16, 102 | "reduced_integral": 0.3606206093341824, 103 | "reduced_abs_error": 0.002602101060616757, 104 | "reduced_rel_error": 0.007268062964578538 105 | }, 106 | { 107 | "original_strength": 10, 108 | "reduced_strength": 9, 109 | "original_points": 486, 110 | "reduced_points": 220, 111 | "exact_integral": 0.3580185082735656, 112 | "original_integral": 0.35801850827356546, 113 | "original_abs_error": 1.6653345369377348e-16, 114 | "original_rel_error": 4.651531969585314e-16, 115 | "reduced_integral": 0.3590001836612742, 116 | "reduced_abs_error": 0.0009816753877085471, 117 | "reduced_rel_error": 0.0027419682642731945 118 | }, 119 | { 120 | "original_strength": 10, 121 | "reduced_strength": 10, 122 | "original_points": 486, 123 | "reduced_points": 286, 124 | "exact_integral": 0.3580185082735656, 125 | "original_integral": 0.35801850827356546, 126 | "original_abs_error": 1.6653345369377348e-16, 127 | "original_rel_error": 4.651531969585314e-16, 128 | "reduced_integral": 0.3580185082735364, 129 | "reduced_abs_error": 2.9254376698872875e-14, 130 | "reduced_rel_error": 8.171191159904868e-14 131 | } 132 | ] -------------------------------------------------------------------------------- /notebooks/slingshot_iterations/iterations.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 9, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import re\n", 10 | "import numpy as np\n", 11 | "import matplotlib.pyplot as plt\n", 12 | "from matplotlib import rc\n", 13 | "from matplotlib.ticker import MultipleLocator, LogLocator\n", 14 | "\n", 15 | "%matplotlib inline\n", 16 | "plt.rcParams['figure.figsize'] = [15, 15]\n", 17 | "plt.rcParams.update({'font.size': 45})\n", 18 | "rc('text', usetex=True)" 19 | ] 20 | }, 21 | { 22 | "cell_type": "code", 23 | "execution_count": 10, 24 | "metadata": {}, 25 | "outputs": [], 26 | "source": [ 27 | "def read_iterations(filename):\n", 28 | " cg_iters = []\n", 29 | " newt_iters = []\n", 30 | "\n", 31 | " with open(filename) as logfile:\n", 32 | " for line in logfile:\n", 33 | " match = re.match(\".*CG iterations: (\\d+).*\", line)\n", 34 | " if match:\n", 35 | " cg_iters.append(match.group(1))\n", 36 | " continue\n", 37 | " \n", 38 | " match = re.match(\".*Number of newton iterations in Backward Euler step: (\\d+).*\", line)\n", 39 | " if match:\n", 40 | " newt_iters.append(match.group(1))\n", 41 | " continue\n", 42 | "\n", 43 | " cg_iters = np.array(cg_iters, dtype=np.int32)\n", 44 | " newt_iters = np.array(newt_iters, dtype=np.int32)\n", 45 | "\n", 46 | " return (cg_iters, newt_iters)\n", 47 | " " 48 | ] 49 | }, 50 | { 51 | "cell_type": "code", 52 | "execution_count": 11, 53 | "metadata": {}, 54 | "outputs": [], 55 | "source": [ 56 | "def get_stats(filename):\n", 57 | " (cg_iters, newt_iters) = read_iterations(filename)\n", 58 | "\n", 59 | " timesteps = len(newt_iters)\n", 60 | " total_cg_iters = cg_iters.sum()\n", 61 | " total_newt_iters = newt_iters.sum()\n", 62 | " avg_cg_iters = total_cg_iters / timesteps\n", 63 | " avg_newt_iters = total_newt_iters / timesteps\n", 64 | "\n", 65 | " print(f\"Filename: {filename}\")\n", 66 | " print(f\"Timesteps: {timesteps}\")\n", 67 | " print(f\"Total CG iters: {total_cg_iters}\")\n", 68 | " print(f\"Total Newt iters: {total_newt_iters}\")\n", 69 | " print(f\"Avg CG iters per timestep: {avg_cg_iters}\")\n", 70 | " print(f\"Avg Newt iters per timestep: {avg_newt_iters}\")" 71 | ] 72 | }, 73 | { 74 | "cell_type": "code", 75 | "execution_count": 12, 76 | "metadata": {}, 77 | "outputs": [ 78 | { 79 | "output_type": "stream", 80 | "name": "stdout", 81 | "text": "Filename: armadillo_slingshot_embedded_tet10_5000_femproto2_2020-05-21_11-35-43-891374.log\nTimesteps: 5000\nTotal CG iters: 1463478\nTotal Newt iters: 5809\nAvg CG iters per timestep: 292.6956\nAvg Newt iters per timestep: 1.1618\n" 82 | } 83 | ], 84 | "source": [ 85 | "get_stats(\"armadillo_slingshot_embedded_tet10_5000_femproto2_2020-05-21_11-35-43-891374.log\")" 86 | ] 87 | }, 88 | { 89 | "cell_type": "code", 90 | "execution_count": 13, 91 | "metadata": {}, 92 | "outputs": [ 93 | { 94 | "output_type": "stream", 95 | "name": "stdout", 96 | "text": "Filename: armadillo_slingshot_fem_tet10_5000_femproto2_2020-05-21_09-08-23-084417.log\nTimesteps: 5000\nTotal CG iters: 1155671\nTotal Newt iters: 5803\nAvg CG iters per timestep: 231.1342\nAvg Newt iters per timestep: 1.1606\n" 97 | } 98 | ], 99 | "source": [ 100 | "get_stats(\"armadillo_slingshot_fem_tet10_5000_femproto2_2020-05-21_09-08-23-084417.log\")" 101 | ] 102 | }, 103 | { 104 | "cell_type": "code", 105 | "execution_count": 14, 106 | "metadata": {}, 107 | "outputs": [ 108 | { 109 | "output_type": "stream", 110 | "name": "stdout", 111 | "text": "Filename: armadillo_slingshot_embedded_tet10_500_femproto2_2020-05-21_14-50-43-484014.log\nTimesteps: 5000\nTotal CG iters: 736848\nTotal Newt iters: 5659\nAvg CG iters per timestep: 147.3696\nAvg Newt iters per timestep: 1.1318\n" 112 | } 113 | ], 114 | "source": [ 115 | "get_stats(\"armadillo_slingshot_embedded_tet10_500_femproto2_2020-05-21_14-50-43-484014.log\")" 116 | ] 117 | }, 118 | { 119 | "cell_type": "code", 120 | "execution_count": 16, 121 | "metadata": {}, 122 | "outputs": [ 123 | { 124 | "output_type": "stream", 125 | "name": "stdout", 126 | "text": "Filename: armadillo_slingshot_fem_tet10_500_femproto2_2020-05-22_13-50-41-106940.log\nTimesteps: 5000\nTotal CG iters: 508117\nTotal Newt iters: 5856\nAvg CG iters per timestep: 101.6234\nAvg Newt iters per timestep: 1.1712\n" 127 | } 128 | ], 129 | "source": [ 130 | "get_stats(\"armadillo_slingshot_fem_tet10_500_femproto2_2020-05-22_13-50-41-106940.log\")" 131 | ] 132 | }, 133 | { 134 | "cell_type": "code", 135 | "execution_count": null, 136 | "metadata": {}, 137 | "outputs": [], 138 | "source": [] 139 | } 140 | ], 141 | "metadata": { 142 | "kernelspec": { 143 | "display_name": "Python 3", 144 | "language": "python", 145 | "name": "python3" 146 | }, 147 | "language_info": { 148 | "codemirror_mode": { 149 | "name": "ipython", 150 | "version": 3 151 | }, 152 | "file_extension": ".py", 153 | "mimetype": "text/x-python", 154 | "name": "python", 155 | "nbconvert_exporter": "python", 156 | "pygments_lexer": "ipython3", 157 | "version": "3.7.6-final" 158 | } 159 | }, 160 | "nbformat": 4, 161 | "nbformat_minor": 2 162 | } -------------------------------------------------------------------------------- /notebooks/unit_tests_analytic_solutions.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "L2 error squared estimation\n", 8 | "----------------------------\n", 9 | "\n", 10 | "\n", 11 | "### Bilinear quad" 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": 13, 17 | "metadata": {}, 18 | "outputs": [], 19 | "source": [ 20 | "from sympy import *\n", 21 | "from sympy.integrals.intpoly import polytope_integrate\n", 22 | "from sympy.abc import x, y" 23 | ] 24 | }, 25 | { 26 | "cell_type": "code", 27 | "execution_count": 14, 28 | "metadata": {}, 29 | "outputs": [], 30 | "source": [ 31 | "points = [ Point2D(-1, -1), Point2D(2, -2), Point2D(4, 1), Point2D(-2, 3)]\n", 32 | "\n", 33 | "def phi_alpha_beta(alpha, beta, x, y):\n", 34 | " return (1 + alpha * x) * (1 + beta * y) / 4\n", 35 | "\n", 36 | "# Define basis functions phi(x, y)\n", 37 | "def phi_local(x, y):\n", 38 | " return [\n", 39 | " phi_alpha_beta(-1, -1, x, y),\n", 40 | " phi_alpha_beta(1, -1, x, y),\n", 41 | " phi_alpha_beta(1, 1, x, y),\n", 42 | " phi_alpha_beta(-1, 1, x, y)\n", 43 | " ]\n", 44 | "\n", 45 | "# Define transformation from reference element T: K_hat -> K,\n", 46 | "# with K being the element defined by quad.\n", 47 | "def T(x, y):\n", 48 | " p = phi_local(x, y)\n", 49 | " return points[0] * p[0] + points[1] * p[1] + points[2] * p[2] + points[3] * p[3]\n", 50 | " \n", 51 | "def u(x, y):\n", 52 | " return 5 * x * y + 3 * x - 2 * y - 5\n", 53 | "\n", 54 | "def u_local(xi, eta):\n", 55 | " (x, y) = T(xi, eta)\n", 56 | " return u(x, y)\n", 57 | "\n", 58 | "u_h_weights = [u(p[0], p[1]) for p in points]\n", 59 | "\n", 60 | "def u_h_local(xi, eta):\n", 61 | " p = phi_local(xi, eta)\n", 62 | " u = u_h_weights\n", 63 | " return u[0] * p[0] + u[1] * p[1] + u[2] * p[2] + u[3] * p[3]" 64 | ] 65 | }, 66 | { 67 | "cell_type": "code", 68 | "execution_count": 15, 69 | "metadata": {}, 70 | "outputs": [ 71 | { 72 | "data": { 73 | "text/latex": [ 74 | "$\\displaystyle \\frac{9955}{12}$" 75 | ], 76 | "text/plain": [ 77 | "9955/12" 78 | ] 79 | }, 80 | "execution_count": 15, 81 | "metadata": {}, 82 | "output_type": "execute_result" 83 | } 84 | ], 85 | "source": [ 86 | "det_J_K = Matrix(T(x, y)).jacobian(Matrix([x, y])).det()\n", 87 | "\n", 88 | "integrand = expand(det_J_K * (u_h_local(x, y) - u_local(x, y))**2)\n", 89 | "\n", 90 | "# Note: It may be necessary to expand the polynomial for use with polytope_integrate\n", 91 | "#integral = polytope_integrate(reference_quad, 1)\n", 92 | "# Note: polytope_integrate did not seem to work so well. Since we anyway integrate in the reference domain,\n", 93 | "# which is a simple square, we can just integrate normally with simple limits\n", 94 | "integral = integrate(integrand, (x, -1, 1), (y, -1, 1))\n", 95 | "integral" 96 | ] 97 | }, 98 | { 99 | "cell_type": "code", 100 | "execution_count": 16, 101 | "metadata": {}, 102 | "outputs": [ 103 | { 104 | "data": { 105 | "text/latex": [ 106 | "$\\displaystyle \\frac{43 x y}{2} + \\frac{29 x}{2} - \\frac{3 y}{2} - \\frac{19}{2}$" 107 | ], 108 | "text/plain": [ 109 | "43*x*y/2 + 29*x/2 - 3*y/2 - 19/2" 110 | ] 111 | }, 112 | "execution_count": 16, 113 | "metadata": {}, 114 | "output_type": "execute_result" 115 | } 116 | ], 117 | "source": [ 118 | "expand(u_h_local(x, y))" 119 | ] 120 | }, 121 | { 122 | "cell_type": "code", 123 | "execution_count": 17, 124 | "metadata": {}, 125 | "outputs": [ 126 | { 127 | "data": { 128 | "text/latex": [ 129 | "$\\displaystyle - \\frac{15 x^{2} y^{2}}{16} - \\frac{45 x^{2} y}{8} - \\frac{135 x^{2}}{16} + \\frac{25 x y^{2}}{4} + \\frac{43 x y}{2} + \\frac{33 x}{4} + \\frac{35 y^{2}}{16} + \\frac{33 y}{8} - \\frac{37}{16}$" 130 | ], 131 | "text/plain": [ 132 | "-15*x**2*y**2/16 - 45*x**2*y/8 - 135*x**2/16 + 25*x*y**2/4 + 43*x*y/2 + 33*x/4 + 35*y**2/16 + 33*y/8 - 37/16" 133 | ] 134 | }, 135 | "execution_count": 17, 136 | "metadata": {}, 137 | "output_type": "execute_result" 138 | } 139 | ], 140 | "source": [ 141 | "expand(u_local(x, y))" 142 | ] 143 | }, 144 | { 145 | "cell_type": "code", 146 | "execution_count": null, 147 | "metadata": {}, 148 | "outputs": [], 149 | "source": [] 150 | }, 151 | { 152 | "cell_type": "code", 153 | "execution_count": null, 154 | "metadata": {}, 155 | "outputs": [], 156 | "source": [] 157 | } 158 | ], 159 | "metadata": { 160 | "kernelspec": { 161 | "display_name": "Python 3", 162 | "language": "python", 163 | "name": "python3" 164 | }, 165 | "language_info": { 166 | "codemirror_mode": { 167 | "name": "ipython", 168 | "version": 3 169 | }, 170 | "file_extension": ".py", 171 | "mimetype": "text/x-python", 172 | "name": "python", 173 | "nbconvert_exporter": "python", 174 | "pygments_lexer": "ipython3", 175 | "version": "3.6.9" 176 | } 177 | }, 178 | "nbformat": 4, 179 | "nbformat_minor": 2 180 | } 181 | -------------------------------------------------------------------------------- /paradis/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "paradis" 3 | version = "0.1.0" 4 | authors = ["Andreas Longva "] 5 | edition = "2018" 6 | publish = false 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [dependencies] 11 | rayon = "1.3" 12 | nested-vec = { path = "../nested-vec" } 13 | # TODO: Make serde optional 14 | serde = { version = "1.0", features = [ "derive" ] } 15 | 16 | [dev-dependencies] 17 | rand = "0.7" 18 | proptest = "0.9" 19 | -------------------------------------------------------------------------------- /paradis/src/coloring.rs: -------------------------------------------------------------------------------- 1 | use crate::DisjointSubsets; 2 | use nested_vec::NestedVec; 3 | use std::collections::BTreeSet; 4 | 5 | #[derive(Debug)] 6 | struct Color { 7 | subsets: NestedVec, 8 | labels: Vec, 9 | indices: BTreeSet, 10 | } 11 | 12 | impl Color { 13 | fn new_with_subset(subset: &[usize], label: usize) -> Self { 14 | let mut subsets = NestedVec::new(); 15 | subsets.push(subset); 16 | Self { 17 | subsets, 18 | labels: vec![label], 19 | indices: subset.iter().copied().collect(), 20 | } 21 | } 22 | 23 | fn try_add_subset(&mut self, subset: &[usize], label: usize, local_workspace_set: &mut BTreeSet) -> bool { 24 | local_workspace_set.clear(); 25 | for idx in subset { 26 | local_workspace_set.insert(*idx); 27 | } 28 | 29 | if self.indices.is_disjoint(&local_workspace_set) { 30 | self.subsets.push(subset); 31 | self.labels.push(label); 32 | 33 | for &idx in local_workspace_set.iter() { 34 | self.indices.insert(idx); 35 | } 36 | true 37 | } else { 38 | false 39 | } 40 | } 41 | 42 | fn max_index(&self) -> Option { 43 | // Use the fact that the last element in a BTreeSet is the largest value in the set 44 | self.indices.iter().copied().last() 45 | } 46 | } 47 | 48 | pub fn sequential_greedy_coloring(subsets: &NestedVec) -> Vec { 49 | let mut colors = Vec::::new(); 50 | let mut workspace_set = BTreeSet::new(); 51 | 52 | 'subset_loop: for (label, subset) in subsets.iter().enumerate() { 53 | for color in &mut colors { 54 | if color.try_add_subset(subset, label, &mut workspace_set) { 55 | continue 'subset_loop; 56 | } 57 | } 58 | 59 | // We did not succeed in adding the subset to an existing color, 60 | // so create a new one instead 61 | colors.push(Color::new_with_subset(subset, label)); 62 | } 63 | 64 | colors 65 | .into_iter() 66 | .map(|color| { 67 | let max_index = color.max_index(); 68 | // Subsets must be disjoint by construction, so skip checks 69 | unsafe { DisjointSubsets::from_disjoint_subsets_unchecked(color.subsets, color.labels, max_index) } 70 | }) 71 | .collect() 72 | } 73 | 74 | #[cfg(test)] 75 | mod tests { 76 | use super::sequential_greedy_coloring; 77 | use crate::DisjointSubsets; 78 | use nested_vec::NestedVec; 79 | use proptest::collection::vec; 80 | use proptest::prelude::*; 81 | use proptest::proptest; 82 | 83 | proptest! { 84 | #[test] 85 | fn sequential_greedy_coloring_produces_disjoint_subsets( 86 | integer_subsets in vec(vec(0 .. 100usize, 0 .. 10), 0 .. 10) 87 | ) { 88 | // Generate a random Vec>, which can be interpreted as a set of 89 | // subsets, which we then color 90 | let subsets = NestedVec::from(&integer_subsets); 91 | let colors = sequential_greedy_coloring(&subsets); 92 | 93 | // There can not be more colors than there are original subsets 94 | prop_assert!(colors.len() <= subsets.len()); 95 | 96 | let num_subsets_across_colors: usize = colors 97 | .iter() 98 | .map(|disjoint_subsets| disjoint_subsets.subsets().len()) 99 | .sum(); 100 | 101 | prop_assert_eq!(num_subsets_across_colors, subsets.len()); 102 | 103 | // Actually assert that each color has disjoint subsets by running it through 104 | // the `try...` constructor for DisjointSubsets 105 | for subset in colors { 106 | let checked_subsets = DisjointSubsets::try_from_disjoint_subsets( 107 | subset.subsets().clone(), subset.labels().to_vec()); 108 | prop_assert_eq!(checked_subsets, Ok(subset.clone())); 109 | } 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /paradis/src/slice.rs: -------------------------------------------------------------------------------- 1 | use crate::{ParallelAccess, ParallelStorage}; 2 | use std::marker::PhantomData; 3 | 4 | #[derive(Copy)] 5 | pub struct ParallelSliceAccess<'a, T> { 6 | ptr: *mut T, 7 | marker: PhantomData<&'a mut T>, 8 | } 9 | 10 | impl<'a, T> Clone for ParallelSliceAccess<'a, T> { 11 | fn clone(&self) -> Self { 12 | Self { 13 | ptr: self.ptr, 14 | marker: PhantomData, 15 | } 16 | } 17 | } 18 | 19 | unsafe impl<'a, T: Sync> Sync for ParallelSliceAccess<'a, T> {} 20 | unsafe impl<'a, T: Send> Send for ParallelSliceAccess<'a, T> {} 21 | 22 | unsafe impl<'a, 'b, T: 'b + Sync + Send> ParallelAccess<'b> for ParallelSliceAccess<'a, T> 23 | where 24 | 'a: 'b, 25 | { 26 | type Record = &'b T; 27 | type RecordMut = &'b mut T; 28 | 29 | unsafe fn get_unchecked(&self, global_index: usize) -> &T { 30 | // TODO: This might technically be unsound. Should we use .wrapping_add, or something else? 31 | &*self.ptr.add(global_index) 32 | } 33 | 34 | unsafe fn get_unchecked_mut(&self, global_index: usize) -> &mut T { 35 | // TODO: This might technically be unsound. Should we use .wrapping_add, or something else? 36 | &mut *self.ptr.add(global_index) 37 | } 38 | } 39 | 40 | unsafe impl<'a, T: 'a + Sync + Send> ParallelStorage<'a> for [T] { 41 | type Access = ParallelSliceAccess<'a, T>; 42 | 43 | fn create_access(&'a mut self) -> Self::Access { 44 | ParallelSliceAccess { 45 | ptr: self.as_mut_ptr(), 46 | marker: PhantomData, 47 | } 48 | } 49 | 50 | fn len(&self) -> usize { 51 | <[T]>::len(&self) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /paradis/test_sanitized.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | SCRIPTPATH="$( cd "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" 3 | export TSAN_OPTIONS="suppressions=$SCRIPTPATH/tsan_suppression.txt" 4 | export RUST_TEST_THREADS=1 5 | export CARGO_INCREMENTAL=0 6 | export RUSTFLAGS="-Z sanitizer=thread" 7 | export RAYON_NUM_THREADS=4 8 | cargo +nightly test -p paradis --target x86_64-unknown-linux-gnu --tests 9 | -------------------------------------------------------------------------------- /paradis/tsan_suppression.txt: -------------------------------------------------------------------------------- 1 | race:core::sync::atomic::atomic 2 | race:lazy_static::lazy::Lazy 3 | race:alloc::sync::Arc 4 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 120 2 | 3 | # Increasing max width to 120 causes many method chains to become single-line and hard to read, 4 | # so we set the chain width to the equivalent default value for the standard max width setting 5 | chain_width = 60 6 | -------------------------------------------------------------------------------- /scene_runner/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "scene_runner" 3 | version = "0.1.0" 4 | authors = ["Andreas Longva"] 5 | edition = "2018" 6 | build = "build.rs" 7 | default-run = "dynamic_runner" 8 | publish = false 9 | 10 | [dependencies] 11 | simulation_toolbox = { path = "../simulation_toolbox" } 12 | fenris = { path = "../fenris" } 13 | hamilton = { path = "../hamilton" } 14 | itertools = "0.9" 15 | numeric_literals = "0.2" 16 | ply-rs = "0.1.2" 17 | structopt = "0.3" 18 | once_cell = "1.3" 19 | petgraph = { version = "0.5", default-features=false } 20 | coarse-prof = "0.2" 21 | log = "0.4" 22 | fern = "0.6" 23 | rand = "0.7" 24 | serde = { version = "1.0", features = ["derive"] } 25 | statrs = "0.12" 26 | chrono = "0.4" 27 | hostname = "0.3" 28 | typetag = "0.1" 29 | rayon = "1.3" 30 | gnuplot = "0.0.35" 31 | nalgebra-lapack= { version="0.13", default-features=false, features = ["intel-mkl"] } 32 | mkl-corrode = { git = "https://github.com/Andlon/mkl-corrode.git", rev="0843a0b46234cd88d7a0e7489720514624207ad9", features = [ "openmp" ] } 33 | global_stash = { path = "../global_stash" } 34 | 35 | [build-dependencies] 36 | chrono = "0.4" 37 | hostname = "0.3" 38 | ignore = "0.4" 39 | -------------------------------------------------------------------------------- /scene_runner/build.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | use std::ffi::OsStr; 3 | use std::fs; 4 | use std::process::Command; 5 | 6 | use ignore::WalkBuilder; 7 | 8 | fn git_command_stdout, S: AsRef>(args: I) -> Result> { 9 | Ok( 10 | String::from_utf8_lossy(Command::new("git").args(args).output()?.stdout.as_slice()) 11 | .trim_end_matches("\n") 12 | .replace("\n", ";"), 13 | ) 14 | } 15 | 16 | fn main() -> Result<(), Box> { 17 | // Prepare environment variables that will be baked into the binary 18 | { 19 | let last_commit = git_command_stdout(&["show", "-s", "--format=Commit: %H%nAuthor: %an, %aI%nTitle: '%s'"])?; 20 | 21 | let current_changes = git_command_stdout(&["status", "-b", "--porcelain"])?; 22 | 23 | let hostname = hostname::get()?.to_string_lossy().into_owned(); 24 | let timestamp = chrono::Local::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, false); 25 | 26 | println!("cargo:rustc-env=FEMPROTO2_BUILD_TIMESTAMP={}", timestamp); 27 | println!("cargo:rustc-env=FEMPROTO2_BUILD_HOSTNAME={}", hostname); 28 | println!("cargo:rustc-env=FEMPROTO2_GIT_LAST_COMMIT={}", last_commit); 29 | println!("cargo:rustc-env=FEMPROTO2_GIT_CHANGES={}", current_changes); 30 | } 31 | 32 | // List directories that should be watched by cargo for changes for a rebuild. 33 | // Using the ignore crate allows to skip folders mentioned in .gitignore files, 34 | // e.g. changes in the target or data output directory should not lead to a rebuild. 35 | // 36 | // The .git folder has to be treated separately as every git command (even read only commands) 37 | // `touch` the .git folder. So we only want to rebuild on changes inside of the .git folder 38 | // (to cause a rebuild on a local commit). 39 | { 40 | // Add all top-level files and folders in the ../.git directory to watch 41 | for entry in fs::read_dir("../.git")? 42 | // Skip access errors 43 | .filter_map(|e| e.ok()) 44 | // Skip symlinks 45 | .filter(|e| e.metadata().is_ok()) 46 | { 47 | println!("cargo:rerun-if-changed={}", entry.path().to_str().unwrap()); 48 | } 49 | 50 | // Add all other folders recursively from ../ that are not excluded per .gitignore etc. 51 | for entry in WalkBuilder::new("../") 52 | .add_custom_ignore_filename(".git") 53 | .follow_links(false) 54 | .build() 55 | .filter_map(|e| e.ok()) 56 | { 57 | if entry.path().is_dir() { 58 | println!("cargo:rerun-if-changed={}", entry.path().to_str().unwrap()); 59 | } 60 | } 61 | } 62 | 63 | Ok(()) 64 | } 65 | -------------------------------------------------------------------------------- /scene_runner/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[allow(dead_code)] 2 | pub(crate) mod meshes; 3 | pub mod scenes; 4 | -------------------------------------------------------------------------------- /scene_runner/src/meshes.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | use std::fs::OpenOptions; 3 | use std::io::{BufReader, Read}; 4 | use std::path::Path; 5 | 6 | use fenris::connectivity::Connectivity; 7 | use fenris::mesh::{Mesh, TriangleMesh2d}; 8 | use fenris::nalgebra::allocator::Allocator; 9 | use fenris::nalgebra::{DefaultAllocator, DimName, Point}; 10 | use once_cell::sync::Lazy; 11 | use simulation_toolbox::io::msh::{try_mesh_from_bytes, TryConnectivityFromMshElement, TryVertexFromMshNode}; 12 | 13 | fn read_bytes

(path: P) -> Result, Box> 14 | where 15 | P: AsRef, 16 | { 17 | let file = OpenOptions::new() 18 | .read(true) 19 | .write(false) 20 | .create(false) 21 | .open(path)?; 22 | let mut buf_reader = BufReader::new(file); 23 | 24 | let mut data = Vec::new(); 25 | buf_reader.read_to_end(&mut data)?; 26 | Ok(data) 27 | } 28 | 29 | fn load_mesh_from_file_internal(asset_dir: P, file_name: &str) -> Result, Box> 30 | where 31 | P: AsRef, 32 | D: DimName, 33 | C: Connectivity + TryConnectivityFromMshElement, 34 | Point: TryVertexFromMshNode, 35 | DefaultAllocator: Allocator, 36 | { 37 | let file_path = asset_dir.as_ref().join(file_name); 38 | let msh_bytes = read_bytes(file_path)?; 39 | try_mesh_from_bytes(&msh_bytes) 40 | } 41 | 42 | pub fn load_mesh_from_file(asset_dir: P, file_name: &str) -> Result, Box> 43 | where 44 | P: AsRef, 45 | D: DimName, 46 | C: Connectivity + TryConnectivityFromMshElement, 47 | Point: TryVertexFromMshNode, 48 | DefaultAllocator: Allocator, 49 | { 50 | load_mesh_from_file_internal(asset_dir, file_name) 51 | .map_err(|e| Box::from(format!("Error occured during mesh loading of `{}`: {}", file_name, e))) 52 | } 53 | 54 | pub(crate) static BIKE_TRI2D_MESH_COARSE: Lazy> = Lazy::new(|| { 55 | load_mesh_from_file("assets", "meshes/bike_coarse.obj_linear.msh").expect("Unable to load 'BIKE_MSH_COARSE'") 56 | }); 57 | 58 | pub(crate) static BIKE_TRI2D_MESH_FINE: Lazy> = Lazy::new(|| { 59 | load_mesh_from_file("assets", "meshes/bike_fine.obj_linear.msh").expect("Unable to load 'BIKE_MSH_FINE'") 60 | }); 61 | 62 | pub(crate) static ELEPHANT_TRI2D_MESH_COARSE: Lazy> = Lazy::new(|| { 63 | load_mesh_from_file("assets", "meshes/elephant_base.obj_coarse.msh").expect("Unable to load 'ELEPHANT_MSH_COARSE'") 64 | }); 65 | 66 | pub(crate) static ELEPHANT_TRI2D_MESH_FINE: Lazy> = Lazy::new(|| { 67 | load_mesh_from_file("assets", "meshes/elephant_base.obj_fine.msh").expect("Unable to load 'ELEPHANT_MSH_FINE'") 68 | }); 69 | 70 | pub(crate) static ELEPHANT_TRI2D_MESH_SUPER_FINE: Lazy> = Lazy::new(|| { 71 | load_mesh_from_file("assets", "meshes/elephant_base.obj_super_fine.msh") 72 | .expect("Unable to load 'ELEPHANT_MSH_SUPER_FINE'") 73 | }); 74 | 75 | pub(crate) static ELEPHANT_CAGE_TRI2D_MESH_FINE: Lazy> = Lazy::new(|| { 76 | load_mesh_from_file("assets", "meshes/elephant_cage.obj_coarse.msh").expect("Unable to load 'ELEPHANT_MSH_CAGE'") 77 | }); 78 | -------------------------------------------------------------------------------- /scene_runner/src/scenes/cantilever3d.rs: -------------------------------------------------------------------------------- 1 | use std::convert::TryFrom; 2 | use std::error::Error; 3 | 4 | use crate::scenes::{filtered_vertex_indices, Scene, SceneParameters}; 5 | 6 | use simulation_toolbox::components::Name; 7 | use simulation_toolbox::fem::{FiniteElementIntegrator, FiniteElementMeshDeformer, Material}; 8 | 9 | use crate::scenes::helpers::BodyInitializer3d; 10 | use fenris::geometry::polymesh::PolyMesh3d; 11 | use fenris::geometry::procedural::create_rectangular_uniform_hex_mesh; 12 | use fenris::mesh::Tet4Mesh; 13 | use fenris::model::NodalModel; 14 | use fenris::nalgebra::Vector3; 15 | use fenris::quadrature::{hex_quadrature_strength_5, tet_quadrature_strength_5}; 16 | use fenris::solid::materials::{StableNeoHookeanMaterial, YoungPoisson}; 17 | 18 | pub fn cantilever3d(_params: &SceneParameters) -> Result> { 19 | let mut scene = Scene { 20 | initial_state: Default::default(), 21 | simulation_systems: Default::default(), 22 | analysis_systems: Default::default(), 23 | duration: 10.0, 24 | name: String::from("cantilever3d"), 25 | }; 26 | 27 | let volume_mesh = create_rectangular_uniform_hex_mesh(1.0, 4, 1, 1, 1); 28 | let volume_poly_mesh = PolyMesh3d::from(&volume_mesh); 29 | 30 | let material = Material { 31 | density: 1000.0, 32 | mass_damping_coefficient: None, 33 | stiffness_damping_coefficient: None, 34 | elastic_model: StableNeoHookeanMaterial::from(YoungPoisson { 35 | young: 3e6, 36 | poisson: 0.4, 37 | }) 38 | .into(), 39 | }; 40 | 41 | // Tet model 42 | { 43 | let tet_poly_mesh = volume_poly_mesh.triangulate()?; 44 | let tet_mesh = Tet4Mesh::try_from(&tet_poly_mesh).unwrap(); 45 | 46 | let quadrature = tet_quadrature_strength_5(); 47 | let fe_model = NodalModel::from_mesh_and_quadrature(tet_mesh.clone(), quadrature); 48 | BodyInitializer3d::initialize_in_state(&scene.initial_state) 49 | .add_name(Name("cantilever_tet4".to_string())) 50 | .add_finite_element_model(fe_model, volume_poly_mesh.clone())? 51 | .set_static_nodes(filtered_vertex_indices(tet_mesh.vertices(), |v| v.x < 1e-6)) 52 | .set_material(material.clone()); 53 | } 54 | 55 | // Hex model 56 | { 57 | let mut volume_mesh_hex = volume_mesh.clone(); 58 | let static_nodes = filtered_vertex_indices(volume_mesh_hex.vertices(), |v| v.x < 1e-6); 59 | volume_mesh_hex.translate(&Vector3::new(0.0, 2.0, 0.0)); 60 | let quadrature = hex_quadrature_strength_5(); 61 | 62 | let fe_model = NodalModel::from_mesh_and_quadrature(volume_mesh_hex.clone(), quadrature); 63 | 64 | BodyInitializer3d::initialize_in_state(&scene.initial_state) 65 | .add_name(Name("cantilever_hex8".to_string())) 66 | .add_finite_element_model(fe_model, &volume_mesh_hex)? 67 | .set_static_nodes(static_nodes) 68 | .set_material(material.clone()); 69 | } 70 | 71 | scene 72 | .simulation_systems 73 | .add_system(Box::new(FiniteElementIntegrator::default())); 74 | scene 75 | .simulation_systems 76 | .add_system(Box::new(FiniteElementMeshDeformer)); 77 | 78 | Ok(scene) 79 | } 80 | -------------------------------------------------------------------------------- /scene_runner/src/scenes/mod.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | use std::path::PathBuf; 3 | 4 | use fenris::nalgebra::allocator::Allocator; 5 | use fenris::nalgebra::{DefaultAllocator, DimName, Point, RealField}; 6 | use hamilton::{StorageContainer, Systems}; 7 | use once_cell::sync::Lazy; 8 | use simulation_toolbox::io::json_helper::JsonWrapper; 9 | 10 | // Single scenes 11 | mod cantilever3d; 12 | mod hollow_ball; 13 | mod rotating_bicycle; 14 | 15 | // Multiple scene modules 16 | mod armadillo_slingshot; 17 | mod cylinder_shell; 18 | 19 | // Special scenes 20 | mod quad_reduc; 21 | 22 | // Other modules 23 | mod helpers; 24 | 25 | static SCENE_REGISTRY: Lazy> = Lazy::new(|| { 26 | let mut scenes = Vec::new(); 27 | scenes.push(SceneConstructor { 28 | name: "cantilever3d".to_string(), 29 | constructor: cantilever3d::cantilever3d, 30 | }); 31 | scenes.push(SceneConstructor { 32 | name: "bicycle_embedded_super_coarse".to_string(), 33 | constructor: rotating_bicycle::build_bicycle_scene_embedded_super_coarse, 34 | }); 35 | scenes.push(SceneConstructor { 36 | name: "bicycle_embedded_coarse".to_string(), 37 | constructor: rotating_bicycle::build_bicycle_scene_embedded_coarse, 38 | }); 39 | scenes.push(SceneConstructor { 40 | name: "bicycle_embedded_fine".to_string(), 41 | constructor: rotating_bicycle::build_bicycle_scene_embedded_fine, 42 | }); 43 | scenes.push(SceneConstructor { 44 | name: "bicycle_fem_coarse".to_string(), 45 | constructor: rotating_bicycle::build_bicycle_scene_fem_coarse, 46 | }); 47 | scenes.push(SceneConstructor { 48 | name: "bicycle_fem_fine".to_string(), 49 | constructor: rotating_bicycle::build_bicycle_scene_fem_fine, 50 | }); 51 | 52 | scenes.extend(cylinder_shell::scenes()); 53 | scenes.extend(armadillo_slingshot::scenes()); 54 | scenes.extend(hollow_ball::scenes()); 55 | scenes.extend(quad_reduc::scenes()); 56 | 57 | scenes.sort_by_key(|constructor| constructor.name.clone()); 58 | scenes 59 | }); 60 | 61 | #[derive(Debug)] 62 | pub struct Scene { 63 | pub initial_state: StorageContainer, 64 | pub simulation_systems: Systems, 65 | pub analysis_systems: Systems, 66 | pub name: String, 67 | pub duration: f64, 68 | } 69 | 70 | #[derive(Debug, Clone)] 71 | pub struct SceneParameters { 72 | pub output_dir: PathBuf, 73 | pub asset_dir: PathBuf, 74 | pub config_file: Option>, 75 | } 76 | 77 | #[doc(hidden)] 78 | pub struct SceneConstructor { 79 | name: String, 80 | constructor: fn(&SceneParameters) -> Result>, 81 | } 82 | 83 | pub fn available_scenes() -> Vec { 84 | let mut names = Vec::new(); 85 | for scene in SCENE_REGISTRY.iter() { 86 | names.push(scene.name.clone()); 87 | } 88 | names 89 | } 90 | 91 | pub fn load_scene(name: &str, params: &SceneParameters) -> Result> { 92 | for scene in SCENE_REGISTRY.iter() { 93 | if scene.name == name { 94 | return (scene.constructor)(params); 95 | } 96 | } 97 | 98 | Err(Box::from(format!("Could not find scene {}", name))) 99 | } 100 | 101 | fn filtered_vertex_indices(vertices: &[Point], filter: F) -> Vec 102 | where 103 | T: RealField, 104 | D: DimName, 105 | F: Fn(&Point) -> bool, 106 | DefaultAllocator: Allocator, 107 | { 108 | vertices 109 | .iter() 110 | .enumerate() 111 | .filter(|(_, v)| filter(v)) 112 | .map(|(i, _)| i) 113 | .collect() 114 | } 115 | -------------------------------------------------------------------------------- /simulation_toolbox/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "simulation_toolbox" 3 | version = "0.1.0" 4 | authors = ["Andreas Longva"] 5 | edition = "2018" 6 | publish = false 7 | 8 | [dependencies] 9 | fenris = { path = "../fenris" } 10 | hamilton = { path = "../hamilton" } 11 | hamilton2 = { path = "../hamilton2" } 12 | serde = "1.0" 13 | itertools = "0.9" 14 | ply-rs = "0.1.2" 15 | obj = "0.10.0" 16 | osqp = "0.6.0" 17 | mshio = "0.4.2" 18 | mkl-corrode = { git = "https://github.com/Andlon/mkl-corrode.git", rev="0843a0b46234cd88d7a0e7489720514624207ad9", features = [ "openmp" ] } 19 | rstar = { version = "0.9.1", features = [ "serde" ] } 20 | coarse-prof = "0.2" 21 | log = "0.4" 22 | num = "0.2" 23 | serde_json = "1.0" 24 | typetag = "0.1" 25 | numeric_literals = "0.2" 26 | rayon = "1.3" 27 | paradis = { path = "../paradis" } 28 | global_stash = { path = "../global_stash" } 29 | nalgebra-lapack= { version="0.13", default-features=false, features = ["intel-mkl"] } 30 | lapack-src = {version="0.5", features = ["intel-mkl"]} 31 | # Make sure that 32 | intel-mkl-src = { version="0.5", features = ["use-shared"]} 33 | # This is a (temporary?) hack to force the openblas implementation used by nalgebra to use the system library 34 | # rather than the bundled one 35 | #openblas-src={version="0.8", features=["system"]} 36 | -------------------------------------------------------------------------------- /simulation_toolbox/src/fem/deformer.rs: -------------------------------------------------------------------------------- 1 | use crate::components::{SurfaceMesh2d, VolumeMesh2d, VolumeMesh3d}; 2 | use crate::fem::{FiniteElementElasticModel2d, FiniteElementElasticModel3d}; 3 | use crate::util::apply_displacements; 4 | use fenris::nalgebra::{U2, U3}; 5 | use fenris::space::FiniteElementSpace; 6 | use hamilton::{StorageContainer, System}; 7 | use std::error::Error; 8 | use std::fmt; 9 | use std::fmt::Display; 10 | 11 | #[derive(Debug)] 12 | pub struct FiniteElementMeshDeformer; 13 | 14 | impl Display for FiniteElementMeshDeformer { 15 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 16 | write!(f, "FiniteElementMeshDeformer") 17 | } 18 | } 19 | 20 | impl System for FiniteElementMeshDeformer { 21 | fn run(&mut self, data: &StorageContainer) -> Result<(), Box> { 22 | // 2D 23 | { 24 | let fe_models = data 25 | .get_component_storage::() 26 | .borrow(); 27 | let mut volume_meshes = data.get_component_storage::().borrow_mut(); 28 | let mut surface_meshes = data.get_component_storage::().borrow_mut(); 29 | 30 | for (id, model) in fe_models.entity_component_iter() { 31 | // Volume mesh 32 | if let Some(mesh) = volume_meshes.get_component_mut(*id) { 33 | let interpolator = &model.material_volume_interpolator; 34 | let n_vertices = model.material_volume_mesh.vertices().len(); 35 | 36 | if n_vertices == mesh.vertices().len() { 37 | // TODO: Reuse vector instead of re-allocating on every `run` invocation 38 | let displacements = interpolator.interpolate::(&model.u); 39 | apply_displacements( 40 | mesh.vertices_mut(), 41 | model.material_volume_mesh.vertices(), 42 | &displacements, 43 | ); 44 | } else { 45 | return Err(Box::from( 46 | "Reference mesh and deformed mesh have incompatible vertex counts.", 47 | )); 48 | } 49 | } 50 | 51 | // Surface mesh 52 | if let Some(surface_mesh) = surface_meshes.get_component_mut(*id) { 53 | let ref_surface = &model.material_surface; 54 | let interpolator = &model.material_surface_interpolator; 55 | 56 | if let (Some(ref_surface), Some(interpolator)) = (ref_surface, interpolator) { 57 | let n_vertices = FiniteElementSpace::vertices(ref_surface).len(); 58 | if n_vertices == surface_mesh.vertices().len() { 59 | // TODO: Reuse vector instead of re-allocating on every `run` invocation 60 | let displacements = interpolator.interpolate::(&model.u); 61 | surface_mesh.transform_all_vertices(|vertices_mut| { 62 | apply_displacements( 63 | vertices_mut, 64 | FiniteElementSpace::vertices(ref_surface), 65 | &displacements, 66 | ); 67 | }); 68 | } else { 69 | return Err(Box::from( 70 | "Reference and deformed surface meshes have incompatible vertex counts", 71 | )); 72 | } 73 | } 74 | } 75 | } 76 | } 77 | 78 | // 3D 79 | { 80 | let fe_models = data 81 | .get_component_storage::() 82 | .borrow(); 83 | let mut volume_meshes = data.get_component_storage::().borrow_mut(); 84 | 85 | for (id, model) in fe_models.entity_component_iter() { 86 | // Volume mesh 87 | if let Some(mesh) = volume_meshes.get_component_mut(*id) { 88 | let interpolator = &model.material_volume_interpolator; 89 | let n_vertices = model.material_volume_mesh.vertices().len(); 90 | 91 | if n_vertices == mesh.vertices().len() { 92 | // TODO: Reuse vector instead of re-allocating on every `run` invocation 93 | let displacements = interpolator.interpolate::(&model.u); 94 | apply_displacements( 95 | mesh.vertices_mut(), 96 | model.material_volume_mesh.vertices(), 97 | &displacements, 98 | ); 99 | } else { 100 | return Err(Box::from( 101 | "Reference mesh and deformed mesh have incompatible vertex counts.", 102 | )); 103 | } 104 | } 105 | } 106 | } 107 | 108 | Ok(()) 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /simulation_toolbox/src/fem/mod.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | mod fe_model; 3 | pub use fe_model::*; 4 | 5 | mod deformer; 6 | pub use deformer::*; 7 | 8 | mod system_assembly; 9 | 10 | mod integrator; 11 | pub use integrator::*; 12 | 13 | pub mod newton_cg; 14 | 15 | pub mod bcs; 16 | pub use bcs::{DirichletBoundaryConditionComponent, DirichletBoundaryConditions, OptionalDirichletBoundaryConditions}; 17 | 18 | pub mod schwarz_precond; 19 | 20 | use serde::{Deserialize, Serialize}; 21 | 22 | #[derive(Copy, Clone, Debug, Serialize, Deserialize)] 23 | pub enum IntegrationMethod { 24 | SymplecticEuler, 25 | BackwardEuler, 26 | } 27 | 28 | impl Default for IntegrationMethod { 29 | fn default() -> Self { 30 | IntegrationMethod::BackwardEuler 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /simulation_toolbox/src/io/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod json_helper; 2 | pub mod msh; 3 | pub mod obj; 4 | pub mod ply; 5 | pub mod vtk; 6 | -------------------------------------------------------------------------------- /simulation_toolbox/src/io/obj.rs: -------------------------------------------------------------------------------- 1 | use fenris::geometry::polymesh::PolyMesh3d; 2 | use fenris::nalgebra::Point3; 3 | use fenris::nested_vec::NestedVec; 4 | use obj::{IndexTuple, Obj, SimplePolygon}; 5 | use std::error::Error; 6 | use std::path::Path; 7 | 8 | pub fn load_single_surface_polymesh3d_obj(path: impl AsRef) -> Result, Box> { 9 | load_single_surface_polymesh3d_obj_(path.as_ref()) 10 | } 11 | 12 | fn load_single_surface_polymesh3d_obj_(path: &Path) -> Result, Box> { 13 | let obj_file = Obj::load(path)?; 14 | 15 | let vertices: Vec<_> = obj_file 16 | .data 17 | .position 18 | .iter() 19 | .map(|v| [v[0] as f64, v[1] as f64, v[2] as f64]) 20 | .map(Point3::from) 21 | .collect(); 22 | 23 | if obj_file.data.objects.len() != 1 { 24 | return Err(Box::from("Obj file must contain exactly one object")); 25 | } 26 | 27 | let object = obj_file.data.objects.first().unwrap(); 28 | let mut faces = NestedVec::new(); 29 | for group in &object.groups { 30 | for SimplePolygon(ref index_tuples) in &group.polys { 31 | let mut appender = faces.begin_array(); 32 | for IndexTuple(vertex_idx, _, _) in index_tuples { 33 | appender.push_single(*vertex_idx); 34 | } 35 | } 36 | } 37 | 38 | Ok(PolyMesh3d::from_poly_data(vertices, faces, NestedVec::new())) 39 | } 40 | -------------------------------------------------------------------------------- /simulation_toolbox/src/lib.rs: -------------------------------------------------------------------------------- 1 | // TODO: Instead of organizing things into modules for components, systems etc., 2 | // instead group related things together, such as VTK export components and systems together 3 | 4 | #[macro_use] 5 | pub mod fem; 6 | pub mod components; 7 | pub mod io; 8 | pub mod util; 9 | -------------------------------------------------------------------------------- /simulation_toolbox/src/util.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{BTreeSet, HashMap, HashSet}; 2 | use std::hash::Hash; 3 | 4 | use fenris::nalgebra::allocator::Allocator; 5 | use fenris::nalgebra::{DefaultAllocator, DimName, Point, RealField, VectorN}; 6 | use itertools::izip; 7 | 8 | pub trait IfTrue { 9 | fn if_true(&self, then_some: T) -> Option; 10 | } 11 | 12 | impl IfTrue for bool { 13 | /// Maps `true` to a `Some(then_some)` or `false` to `None`. 14 | fn if_true(&self, then_some: T) -> Option { 15 | if *self { 16 | Some(then_some) 17 | } else { 18 | None 19 | } 20 | } 21 | } 22 | 23 | // TODO: Move this somewhere 24 | pub fn apply_displacements(x: &mut [Point], x0: &[Point], displacements: &[VectorN]) 25 | where 26 | T: RealField, 27 | D: DimName, 28 | DefaultAllocator: Allocator, 29 | { 30 | assert_eq!(x.len(), x0.len()); 31 | assert_eq!(x0.len(), displacements.len()); 32 | for (v, v0, d) in izip!(x, x0, displacements) { 33 | *v = v0 + d; 34 | } 35 | } 36 | 37 | /// Takes an iterator of indices that yields unsorted, possibly duplicate indices 38 | /// and maps the indices to a new set of indices [0, N), where `N` is the number of 39 | /// unique original indices. 40 | /// 41 | /// Returns a tuple consisting of the number of indices in the new index set and 42 | /// a mapping from old to new indices. 43 | pub fn relabel_indices(original_indices: impl IntoIterator) -> (usize, HashMap) { 44 | let iter = original_indices.into_iter(); 45 | let ordered_indices: BTreeSet<_> = iter.collect(); 46 | let num_new_indices = ordered_indices.len(); 47 | let mapping = ordered_indices 48 | .into_iter() 49 | .enumerate() 50 | .map(|(new_idx, old_idx)| (old_idx, new_idx)) 51 | .collect(); 52 | 53 | (num_new_indices, mapping) 54 | } 55 | 56 | pub fn difference(a: impl Iterator, b: impl Iterator) -> Vec { 57 | let set_a: HashSet<_> = a.collect(); 58 | let set_b: HashSet<_> = b.collect(); 59 | set_a.difference(&set_b).map(|v| (*v).clone()).collect() 60 | } 61 | 62 | pub fn intersection(a: impl Iterator, b: impl Iterator) -> Vec { 63 | let set_a: HashSet<_> = a.collect(); 64 | let set_b: HashSet<_> = b.collect(); 65 | set_a.intersection(&set_b).map(|v| (*v).clone()).collect() 66 | } 67 | 68 | pub fn all_items_unique(iter: impl IntoIterator) -> bool { 69 | let mut uniq = HashSet::new(); 70 | iter.into_iter().all(move |x| uniq.insert(x)) 71 | } 72 | -------------------------------------------------------------------------------- /teaser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InteractiveComputerGraphics/higher_order_embedded_fem/868fbc25f93cae32aa3caaa41a60987d4192cf1b/teaser.png --------------------------------------------------------------------------------