├── .gitignore ├── LICENSE ├── README.md ├── assets ├── boundary.png ├── plots │ ├── u_ld_l.png │ ├── u_sd_nc.png │ ├── u_sd_s.png │ ├── u_sd_sv.png │ ├── u_sd_t.png │ └── u_sd_v.png ├── problem.png ├── rhs.png └── scratch_work │ └── vem.ipynb ├── meshes ├── L-domain.mat ├── non-convex.mat ├── smoothed-voronoi.mat ├── squares.mat ├── triangles.mat └── voronoi.mat ├── requirements.txt └── vem.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | .DS_Store 106 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Aditya Chetan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Virtual Element Methods 2 | 3 | This repository contains a Python translation of the code provided in: 4 | 5 | >[The virtual element method in 50 lines of MATLAB. *Oliver J. Sutton*. *Numerical Algorithms*](https://dl.acm.org/doi/10.1007/s11075-016-0235-3) 6 | 7 | It solves a toy problem, a 2-D poisson equation on generalized polygonal meshes, using the lowest order Virtual Element Method. 8 | 9 | 10 | ## Usage 11 | 12 | ``` 13 | $ python3 vem.py --help 14 | usage: vem.py [-h] [-d D] [-o O] [--save_plot] [--title TITLE] i 15 | 16 | This script solves 2-D Poisson Equation on general polygonal meshes using 17 | Virtual Element Method of the lowest order. 18 | 19 | positional arguments: 20 | i Path to input mesh 21 | 22 | optional arguments: 23 | -h, --help show this help message and exit 24 | -d D Specifies the shape of the 2D domain. 25 | Possible values are: 26 | - s: Square Domain 27 | - l: L-Shaped Domain 28 | -o O Path to output file 29 | --save_plot Flag for saving the plot 30 | --title TITLE Title of plot 31 | ``` 32 | 33 | The meshes can be downloaded from [here](http://www.netlib.org/numeralgo/) (available in the na45 package). A copy of the meshes is also provided in this repository in the `meshes` directory. 34 | 35 | 36 | ### Example Usage 37 | 38 | ``` 39 | $ # Computing the solution of a 2-D poisson equation on a square mesh and square domain 40 | $ python3 vem.py -d s meshes/square -o solution.npy --save_plot --title plot.png 41 | ``` 42 | 43 | ## Some Results 44 | 45 | Since this is a translation of the paper, this repository solved the exact toy problem that the paper has taken up, that is, 46 | 47 | $$ 48 | \begin{align*} 49 | -\Delta u &= f \text{ in } \Omega \\ 50 | u &= g \text{ in } \partial \Omega \\ 51 | \end{align*} 52 | $$ 53 | 54 | Here $f = 15\sin (\pi x) \sin (\pi y)$ and $u = (1 - x) y \sin (\pi x)$ 55 | on $\partial \Omega $ 56 | 57 | 60 | 61 | The solutions to this problem on different meshes in the square domain are shown below. 62 | 63 | | **Mesh** | Square | Triangle | Voronoi | Smoothed Voronoi | Non-convex | 64 | | -------- | ------------- | ------------- | ------------- | ------------- | ------------- | 65 | | **Solution** | | | | | | 66 | 67 | 68 | ## Custom boundary conditions and RHS 69 | 70 | You can customise the code to run it with your own boundary condition and RHS too! 71 | 72 | Just take a look at `square_domain_boundary_condition` and `square_domain_rhs`, you can write similar boundary condition functions and RHS function definitions. 73 | 74 | Basically, the template is as follows: 75 | 76 | ``` 77 | # for boundary condition 78 | def my_boundary_condition(points): 79 | # points is a list of 2-lists, containing mesh points 80 | 81 | results = do_something(points) 82 | return results 83 | 84 | # for RHS 85 | def my_rhs(point): 86 | # here we have a single 2-list as input 87 | 88 | result_rhs = do_something_else(point) 89 | return result_rhs 90 | ``` 91 | 92 | Now, in `vem.py`, go to the main function where the `vem` function is called and change it to: 93 | 94 | ``` 95 | u = vem(mesh_file, my_rhs, my_boundary_condition) 96 | ``` 97 | 98 | And voilà! You should be good to go! 99 | 100 | ## Report 101 | 102 | My understanding of the paper is documented in a report available [here](https://justachetan.github.io/proposts/NPDE_report.pdf). 103 | 104 | 105 | - - - 106 | 107 | This code was written as a part of my course project in **MTH598 Numerical Partial Differential Equations** with [Dr. Kaushik Kalyanaraman](https://www.iiitd.ac.in/kaushik) at IIIT Delhi during Winter 2019 Semester. 108 | 109 | For bugs in the code, please write to: aditya16217 [at] iiitd [dot] ac [dot] in 110 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /assets/boundary.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justachetan/VirtualElementMethods/a8c222f126802643a4372c26bb0346ce4d241389/assets/boundary.png -------------------------------------------------------------------------------- /assets/plots/u_ld_l.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justachetan/VirtualElementMethods/a8c222f126802643a4372c26bb0346ce4d241389/assets/plots/u_ld_l.png -------------------------------------------------------------------------------- /assets/plots/u_sd_nc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justachetan/VirtualElementMethods/a8c222f126802643a4372c26bb0346ce4d241389/assets/plots/u_sd_nc.png -------------------------------------------------------------------------------- /assets/plots/u_sd_s.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justachetan/VirtualElementMethods/a8c222f126802643a4372c26bb0346ce4d241389/assets/plots/u_sd_s.png -------------------------------------------------------------------------------- /assets/plots/u_sd_sv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justachetan/VirtualElementMethods/a8c222f126802643a4372c26bb0346ce4d241389/assets/plots/u_sd_sv.png -------------------------------------------------------------------------------- /assets/plots/u_sd_t.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justachetan/VirtualElementMethods/a8c222f126802643a4372c26bb0346ce4d241389/assets/plots/u_sd_t.png -------------------------------------------------------------------------------- /assets/plots/u_sd_v.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justachetan/VirtualElementMethods/a8c222f126802643a4372c26bb0346ce4d241389/assets/plots/u_sd_v.png -------------------------------------------------------------------------------- /assets/problem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justachetan/VirtualElementMethods/a8c222f126802643a4372c26bb0346ce4d241389/assets/problem.png -------------------------------------------------------------------------------- /assets/rhs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justachetan/VirtualElementMethods/a8c222f126802643a4372c26bb0346ce4d241389/assets/rhs.png -------------------------------------------------------------------------------- /meshes/L-domain.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justachetan/VirtualElementMethods/a8c222f126802643a4372c26bb0346ce4d241389/meshes/L-domain.mat -------------------------------------------------------------------------------- /meshes/non-convex.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justachetan/VirtualElementMethods/a8c222f126802643a4372c26bb0346ce4d241389/meshes/non-convex.mat -------------------------------------------------------------------------------- /meshes/smoothed-voronoi.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justachetan/VirtualElementMethods/a8c222f126802643a4372c26bb0346ce4d241389/meshes/smoothed-voronoi.mat -------------------------------------------------------------------------------- /meshes/squares.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justachetan/VirtualElementMethods/a8c222f126802643a4372c26bb0346ce4d241389/meshes/squares.mat -------------------------------------------------------------------------------- /meshes/triangles.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justachetan/VirtualElementMethods/a8c222f126802643a4372c26bb0346ce4d241389/meshes/triangles.mat -------------------------------------------------------------------------------- /meshes/voronoi.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justachetan/VirtualElementMethods/a8c222f126802643a4372c26bb0346ce4d241389/meshes/voronoi.mat -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | appnope==0.1.0 2 | attrs==19.1.0 3 | backcall==0.1.0 4 | bleach==3.3.0 5 | cycler==0.10.0 6 | decorator==4.4.0 7 | defusedxml==0.6.0 8 | entrypoints==0.3 9 | ipykernel==5.1.0 10 | ipython==7.16.3 11 | ipython-genutils==0.2.0 12 | ipywidgets==7.4.2 13 | jedi==0.13.3 14 | Jinja2==2.11.3 15 | jsonschema==3.0.1 16 | jupyter==1.0.0 17 | jupyter-client==5.2.4 18 | jupyter-console==6.0.0 19 | jupyter-core==4.4.0 20 | kiwisolver==1.1.0 21 | MarkupSafe==1.1.1 22 | matplotlib==3.0.3 23 | mistune==0.8.4 24 | nbconvert==6.5.1 25 | nbformat==4.4.0 26 | notebook==6.4.12 27 | numpy==1.22.0 28 | pandocfilters==1.4.2 29 | parso==0.4.0 30 | pexpect==4.7.0 31 | pickleshare==0.7.5 32 | prometheus-client==0.6.0 33 | prompt-toolkit==2.0.9 34 | ptyprocess==0.6.0 35 | Pygments==2.7.4 36 | pyparsing==2.4.0 37 | pyrsistent==0.15.1 38 | python-dateutil==2.8.0 39 | pyzmq==18.0.1 40 | qtconsole==4.4.3 41 | scipy==1.2.1 42 | Send2Trash==1.5.0 43 | six==1.12.0 44 | terminado==0.8.2 45 | testpath==0.4.2 46 | tornado==6.0.2 47 | traitlets==4.3.2 48 | wcwidth==0.1.7 49 | webencodings==0.5.1 50 | widgetsnbextension==3.4.2 51 | -------------------------------------------------------------------------------- /vem.py: -------------------------------------------------------------------------------- 1 | import scipy.io 2 | import numpy as np 3 | import matplotlib.pyplot as plt 4 | from scipy.interpolate import griddata 5 | from mpl_toolkits.axes_grid1 import make_axes_locatable 6 | 7 | 8 | def mod_wrap(x, a): 9 | return (x % a) 10 | 11 | 12 | def square_domain_boundary_condition(points): 13 | x = points[:, 0] 14 | y = points[:, 1] 15 | 16 | g = (1 - x) * y * np.sin(np.pi * x) 17 | 18 | return g 19 | 20 | 21 | def square_domain_rhs(point): 22 | x = point[0] 23 | y = point[1] 24 | 25 | f = 15 * np.sin(np.pi * x) * np.sin(np.pi * y) 26 | return f 27 | 28 | 29 | def L_domain_rhs(point): 30 | return 0 31 | 32 | 33 | def L_domain_boundary_condition(points): 34 | x = points[:, 0] 35 | y = points[:, 1] 36 | 37 | r = np.sqrt(x**2 + y**2) 38 | theta = np.arctan2(y, x) 39 | theta = ((theta >= 0) * theta) + ((theta < 0) * (theta + (2 * np.pi))) 40 | g = (r**(2 / 3)) * np.sin(2 * (theta - np.pi / 2) / 3) 41 | return g 42 | 43 | 44 | def vem(mesh_file, rhs, boundary_condition): 45 | 46 | mesh = scipy.io.loadmat(mesh_file) 47 | 48 | vertices = mesh['vertices'] 49 | 50 | elements = np.array([i[0].reshape(-1) - 1 for i in mesh['elements']]) 51 | 52 | boundary = mesh['boundary'].T[0] - 1 53 | 54 | n_dofs = vertices.shape[0] 55 | n_polys = 3 56 | 57 | K = np.zeros((n_dofs, n_dofs)) 58 | F = np.zeros(n_dofs) 59 | u = np.zeros(n_dofs) 60 | 61 | linear_polynomials = [[0, 0], [1, 0], [0, 1]] 62 | 63 | for el_id in range(elements.shape[0]): 64 | 65 | vert_ids = elements[el_id] 66 | 67 | verts = vertices[vert_ids] 68 | 69 | n_sides = vert_ids.shape[0] 70 | 71 | area_components = verts[ 72 | :, 0] * np.roll(verts[:, 1], -1) - np.roll(verts[:, 0], -1) * verts[:, 1] 73 | 74 | area = 0.5 * np.abs(np.sum(area_components)) 75 | 76 | centroid = np.sum((np.roll(verts, -1, axis=0) + verts) 77 | * area_components.reshape(-1, 1), axis=0) / (6 * area) 78 | 79 | diameter = np.max(np.linalg.norm( 80 | verts - np.roll(verts, -1, axis=0), ord=2)) 81 | 82 | D = np.zeros((n_sides, n_polys)) 83 | D[:, 0] = 1 84 | 85 | B = np.zeros((n_polys, n_sides)) 86 | B[0, :] = 1 / n_sides 87 | 88 | for vertex_id in range(n_sides): 89 | 90 | vert = verts[vertex_id, :] 91 | 92 | prevv = verts[mod_wrap(vertex_id - 1, n_sides), :] 93 | 94 | nextv = verts[mod_wrap(vertex_id + 1, n_sides), :] 95 | 96 | vertex_normal = np.array( 97 | [nextv[1] - prevv[1], prevv[0] - nextv[0]]) 98 | 99 | for poly_id in range(1, n_polys): # Looping over non-constant polynomials 100 | 101 | poly_degree = linear_polynomials[poly_id] 102 | 103 | monomial_grad = poly_degree / diameter 104 | 105 | D[vertex_id, poly_id] = np.dot( 106 | vert - centroid, poly_degree) / diameter 107 | B[poly_id, vertex_id] = np.dot( 108 | monomial_grad, vertex_normal) / 2 109 | 110 | projector = np.dot(np.linalg.inv(np.dot(B, D)), B) 111 | 112 | stabilising_term = np.dot( 113 | (np.eye(n_sides) - np.dot(D, projector)).T, (np.eye(n_sides) - np.dot(D, projector))) 114 | 115 | G = np.dot(B, D) 116 | G[0, :] = 0 117 | 118 | local_stiffness = np.dot( 119 | np.dot(projector.T, G), projector) + stabilising_term 120 | 121 | # Global indices 122 | gis = np.array(np.meshgrid(vert_ids, vert_ids) 123 | ).T.reshape(-1, 2).tolist() 124 | 125 | lsr = local_stiffness.ravel() 126 | counter = 0 127 | for i in range(len(gis)): 128 | x = gis[i][0] 129 | y = gis[i][1] 130 | K[x, y] = K[x, y] + lsr[counter] 131 | counter += 1 132 | 133 | F[vert_ids] = F[vert_ids] + (rhs(centroid) * (area / n_sides)) 134 | 135 | boundary_vals = boundary_condition(vertices[boundary]) 136 | internal_dofs = np.array( 137 | [i for i in np.arange(n_dofs) if i not in boundary]) 138 | 139 | F = F - np.dot(K[:, boundary], boundary_vals) 140 | 141 | num_idof = internal_dofs.shape[0] 142 | gid_of_idof = np.array(np.meshgrid( 143 | internal_dofs, internal_dofs)).T.reshape(-1, 2).tolist() 144 | K_II = np.zeros((num_idof, num_idof)).ravel() 145 | counter = 0 146 | for i in range(len(gid_of_idof)): 147 | x = gid_of_idof[i][0] 148 | y = gid_of_idof[i][1] 149 | 150 | K_II[counter] = K[x, y] 151 | counter += 1 152 | 153 | K_II = K_II.reshape(num_idof, num_idof) 154 | u[internal_dofs] = np.linalg.solve(K_II, F[internal_dofs]) 155 | u[boundary] = boundary_vals 156 | 157 | return u 158 | 159 | 160 | def plot_solution(mesh_file, u, save=False, plot_name=None): 161 | 162 | mesh = scipy.io.loadmat(mesh_file) 163 | 164 | vertices = mesh['vertices'] 165 | elements = np.array([i[0].reshape(-1) - 1 for i in mesh['elements']]) 166 | boundary = mesh['boundary'].T[0] - 1 167 | 168 | x = vertices[:, 0] 169 | y = vertices[:, 1] 170 | v = u 171 | 172 | plt.figure(figsize=(5, 5)) 173 | ax = plt.subplot(111) 174 | xi = np.linspace(min(x) - 0.01, max(x) + 0.001, 100) 175 | yi = np.linspace(min(y) - 0.01, max(y) + 0.001, 100) 176 | 177 | zi = griddata((x, y), v, (xi[None, :], yi[:, None]), method='linear') 178 | for i in range(len(elements)): 179 | 180 | for j in range(len(elements[i])): 181 | 182 | x = [vertices[elements[i][j % len(elements[i])]][0], vertices[ 183 | elements[i][(j + 1) % len(elements[i])]][0]] 184 | y = [vertices[elements[i][j % len(elements[i])]][1], vertices[ 185 | elements[i][(j + 1) % len(elements[i])]][1]] 186 | plt.plot(x, y, "k", linewidth=0.5) 187 | 188 | im = plt.pcolormesh(xi, yi, zi) 189 | 190 | divider = make_axes_locatable(ax) 191 | cax = divider.append_axes("right", size="5%", pad=0.05) 192 | plt.colorbar(im, cax=cax) 193 | ax.set_title("Approximate Solution (u)") 194 | ax.set_xlabel('x') 195 | ax.set_ylabel('y') 196 | 197 | if save and plot_name is not None: 198 | plt.savefig(plot_name) 199 | elif save and plot_name is None: 200 | plt.savefig("sol.png") 201 | 202 | plt.show() 203 | 204 | 205 | def main(): 206 | import argparse 207 | from argparse import RawTextHelpFormatter 208 | 209 | parser = argparse.ArgumentParser( 210 | description='This script solves 2-D Poisson Equation on general polygonal meshes using \nVirtual Element Method of the lowest order.', formatter_class=RawTextHelpFormatter) 211 | parser.add_argument( 212 | "-d", help="Specifies the shape of the 2D domain. \nPossible values are:\n- s: Square Domain\n- l: L-Shaped Domain", type=str) 213 | parser.add_argument("-o", help="Path to output file", 214 | type=str, default="./sol.npy") 215 | parser.add_argument("i", help="Path to input mesh", type=str) 216 | parser.add_argument( 217 | "--save_plot", help="Flag for saving the plot", action="store_true") 218 | parser.add_argument("--title", help="Title of plot", 219 | default="./plot.png", type=str) 220 | 221 | args = parser.parse_args() 222 | 223 | mesh_file = args.i 224 | 225 | u = None 226 | 227 | if args.d == "s": 228 | u = vem(mesh_file, square_domain_rhs, square_domain_boundary_condition) 229 | elif args.d == "l": 230 | u = vem(mesh_file, L_domain_rhs, L_domain_boundary_condition) 231 | else: 232 | raise RuntimeError("Shape of domain not supported!") 233 | 234 | np.save(args.o, u) 235 | 236 | plot_solution(mesh_file, u, args.save_plot, args.title) 237 | 238 | 239 | if __name__ == '__main__': 240 | main() 241 | --------------------------------------------------------------------------------