├── .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 |
--------------------------------------------------------------------------------