diff --git a/EU-ERC.png b/EU-ERC.png new file mode 100644 index 0000000..e7633ee Binary files /dev/null and b/EU-ERC.png differ diff --git a/README.md b/README.md index 19c567f..f8732b8 100644 --- a/README.md +++ b/README.md @@ -208,7 +208,9 @@ Pyelmer is published under the [GPLv3 license](https://www.gnu.org/licenses/gpl- ## Acknowledgements -This package was developed in the [Nemocrys project](https://www.researchgate.net/project/NEMOCRYS-Next-Generation-Multiphysical-Models-for-Crystal-Growth-Processes) which is founded by the [European Research Council](https://erc.europa.eu/). +This project has received funding from the European Research Council (ERC) under the European Union’s Horizon 2020 research and innovation programme ([grant agreement No 851768](https://cordis.europa.eu/project/id/851768/)). + +![European Union, European Research Council](./EU-ERC.png) ## Contribution diff --git a/pyelmer/__init__.py b/pyelmer/__init__.py index 68b60f6..d4f75de 100644 --- a/pyelmer/__init__.py +++ b/pyelmer/__init__.py @@ -1,4 +1,4 @@ -import pyelmer.elements +"""A python interface to Elmer.""" import pyelmer.elmer import pyelmer.execute import pyelmer.gmsh_utils diff --git a/pyelmer/elements.py b/pyelmer/elements.py deleted file mode 100644 index 2f66270..0000000 --- a/pyelmer/elements.py +++ /dev/null @@ -1,314 +0,0 @@ -"""Element shape functions, used for post processing.""" - -import numpy as np -from dataclasses import dataclass - - -@dataclass -class Node: - """Node, contains its coordinates and Temperature.""" - x: float - y: float - z: float = 0 - T: float = 0 - - @property - def X(self): - """Coordinate vector""" - return np.array([self.x, self.y]) - - -class Triangle: - """Base class for triangle elements. - """ - def __init__(self, nodes): - self.n1 = nodes[0] - self.n2 = nodes[1] - self.n3 = nodes[2] - - @property - def A(self): - """Surface of the triangle""" - return 0.5 * ((self.n2.x * self.n3.y - self.n3.x * self.n2.y) - - (self.n1.x * self.n3.y - self.n3.x * self.n1.y) - + (self.n1.x * self.n2.y - self.n2.x * self.n1.y)) - - -class Triangle1st(Triangle): - """Triangle first order. - Node numbering: - n3 - |`\ - | `\ - | `\ - | `\ - | `\ - n1---------n2 - """ - def B_e(self, x, y): - """Derivative of shape functions. - - Args: - x (float): coordinate (unused for elements of this order) - y (float): coordinate (unused for elements of this order) - - Returns: - numpy.array: derivative matrix - """ - del x, y - # according to Ottosen1992 p.124 eqn 7.99 - B_e = np.array([[self.n2.y - self.n3.y, self.n3.y - self.n1.y, self.n1.y - self.n2.y], - [self.n3.x - self.n2.x, self.n1.x - self.n3.x, self.n2.x - self.n1.x]]) - B_e /= 2 * self.A - return B_e - - @property - def T(self): - """Temperature vector""" - T = [self.n1.T, self.n2.T, self.n3.T] - return np.array(T) - - -class Triangle2nd(Triangle): - """Triangle second order. - Node numbering: - n3 - |`\ - | `\ - n6 n5 - | `\ - | `\ - n1---n4----n2 - - Formulas according to Zienkiewicz2005 p.116 ff. - """ - def __init__(self, nodes): - self.n1 = nodes[0] - self.n2 = nodes[1] - self.n3 = nodes[2] - self.n4 = nodes[3] - self.n5 = nodes[4] - self.n6 = nodes[5] - - @property - def a1(self): - return self.n2.x * self.n3.y - self.n3.x * self.n2.y - - @property - def a2(self): - return self.n3.x * self.n1.y - self.n1.x * self.n3.y - - @property - def a3(self): - return self.n1.x * self.n2.y - self.n2.x * self.n1.y - - @property - def b1(self): - return self.n2.y - self.n3.y - - @property - def b2(self): - return self.n3.y - self.n1.y - - @property - def b3(self): - return self.n1.y - self.n2.y - - @property - def c1(self): - return self.n3.x - self.n2.x - - @property - def c2(self): - return self.n1.x - self.n3.x - - @property - def c3(self): - return self.n2.x - self.n1.x - - def L1(self, x, y): - return (self.a1 + self.b1 * x + self.c1 * y) / (2 * self.A) - - def L2(self, x, y): - return (self.a2 + self.b2 * x + self.c2 * y) / (2 * self.A) - - def L3(self, x, y): - return (self.a3 + self.b3 * x + self.c3 * y) / (2 * self.A) - - def N1(self, x, y): - L1 = self.L1(x, y) - return (2*L1 - 1) * L1 - - def N2(self, x, y): - L2 = self.L2(x, y) - return (2*L2 - 1) * L2 - - def N3(self, x, y): - L3 = self.L3(x, y) - return (2*L3 - 1) * L3 - - def N4(self, x, y): - L1 = self.L1(x, y) - L2 = self.L2(x, y) - return 4 * L1 * L2 - - def N5(self, x, y): - L2 = self.L2(x, y) - L3 = self.L3(x, y) - return 4 * L2 * L3 - - def N6(self, x, y): - L3 = self.L3(x, y) - L1 = self.L1(x, y) - return 4 * L3 * L1 - - @property - def L1_x(self): - return self.b1 / (2 * self.A) - - @property - def L1_y(self): - return self.c1 / (2 * self.A) - - @property - def L2_x(self): - return self.b2 / (2 * self.A) - - @property - def L2_y(self): - return self.c2 / (2 * self.A) - - @property - def L3_x(self): - return self.b3 / (2 * self.A) - - @property - def L3_y(self): - return self.c3 / (2 * self.A) - - def N1_x(self, x, y): - L1 = self.L1(x, y) - L1_x = self.L1_x - return 4 * L1 * L1_x - L1_x - - def N1_y(self, x, y): - L1 = self.L1(x, y) - L1_y = self.L1_y - return 4 * L1 * L1_y - L1_y - - def N2_x(self, x, y): - L2 = self.L2(x, y) - L2_x = self.L2_x - return 4 * L2 * L2_x - L2_x - - def N2_y(self, x, y): - L2 = self.L2(x, y) - L2_y = self.L2_y - return 4 * L2 * L2_y - L2_y - - def N3_x(self, x, y): - L3 = self.L3(x, y) - L3_x = self.L3_x - return 4 * L3 * L3_x - L3_x - - def N3_y(self, x, y): - L3 = self.L3(x, y) - L3_y = self.L3_y - return 4 * L3 * L3_y - L3_y - - def N4_x(self, x, y): - L1 = self.L1(x, y) - L1_x = self.L1_x - L2 = self.L2(x, y) - L2_x = self.L2_x - return 4 * (L1_x * L2 + L1 * L2_x) - - def N4_y(self, x, y): - L1 = self.L1(x, y) - L1_y = self.L1_y - L2 = self.L2(x, y) - L2_y = self.L2_y - return 4 * (L1_y * L2 + L1 * L2_y) - - def N5_x(self, x, y): - L2 = self.L2(x, y) - L2_x = self.L2_x - L3 = self.L3(x, y) - L3_x = self.L3_x - return 4 * (L2_x * L3 + L2 * L3_x) - - def N5_y(self, x, y): - L2 = self.L2(x, y) - L2_y = self.L2_y - L3 = self.L3(x, y) - L3_y = self.L3_y - return 4 * (L2_y * L3 + L2 * L3_y) - - def N6_x(self, x, y): - L3 = self.L3(x, y) - L3_x = self.L3_x - L1 = self.L1(x, y) - L1_x = self.L1_x - return 4 * (L3_x * L1 + L3 * L1_x) - - def N6_y(self, x, y): - L3 = self.L3(x, y) - L3_y = self.L3_y - L1 = self.L1(x, y) - L1_y = self.L1_y - return 4 * (L3_y * L1 + L3 * L1_y) - - def B_e(self, x, y): - """Derivative of shape functions. - - Args: - x (float): coordinate - y (float): coordinate - - Returns: - numpy array: Derivative matrix at (x, y) - """ - B_e = [ - [self.N1_x(x, y), self.N2_x(x, y), self.N3_x(x, y), self.N4_x(x, y), self.N5_x(x, y), self.N6_x(x, y)], - [self.N1_y(x, y), self.N2_y(x, y), self.N3_y(x, y), self.N4_y(x, y), self.N5_y(x, y), self.N6_y(x, y)] - ] - return np.array(B_e) - - @property - def T(self): - """Temperature vector""" - T = [self.n1.T, self.n2.T, self.n3.T, self.n4.T, self.n5.T, self.n6.T] - return np.array(T) - - @T.setter - def T(self, T): - """Temperature vector""" - self.n1.T = T[0] - self.n2.T = T[1] - self.n3.T = T[2] - self.n4.T = T[3] - self.n5.T = T[4] - self.n6.T = T[5] - - -class Line1st: - """Line element of first order: - n1----n2 - """ - def __init__(self, nodes): - self.n1 = nodes[0] - self.n2 = nodes[1] - n = np.array([self.n2.y - self.n1.y, -(self.n2.x - self.n1.x)]) - self.normal = n / np.linalg.norm(n) - - def invert_normal(self): - self.normal *= -1 - - -class Line2nd(Line1st): - """Line element of second order: - n1----n3----n2 - """ - def __init__(self, nodes): - super().__init__(nodes[:-1]) - self.n3 = nodes[2] diff --git a/pyelmer/elmer.py b/pyelmer/elmer.py index a4ccd62..91a0d19 100644 --- a/pyelmer/elmer.py +++ b/pyelmer/elmer.py @@ -14,7 +14,7 @@ import yaml -DATA_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'data') +DATA_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'data') class Simulation: @@ -36,22 +36,6 @@ def __init__(self): } self.settings = {} - @property - def transient(self): - """Returns information wether simulation is transient or not. - - Returns: bool - """ - try: - if self.settings['Simulation Type'].lower() == 'transient': - return True - else: - return False - except KeyError: - print('Warning: Simulation type not set.') - return False - - def write_sif(self, simulation_dir): """Write sif file. @@ -118,8 +102,7 @@ def write_startinfo(self, simulation_dir): """ with open(simulation_dir + '/ELMERSOLVER_STARTINFO', 'w') as f: f.write('case.sif\n') - f.write('1\n') - + f.write('1\n') def write_boundary_ids(self, simulation_dir): """Write yaml-file containing the boundary names and the diff --git a/pyelmer/execute.py b/pyelmer/execute.py index 581cc36..b442730 100644 --- a/pyelmer/execute.py +++ b/pyelmer/execute.py @@ -1,3 +1,4 @@ +"""Utility functions for the execution of ElmerSolver and ElmerGrid.""" import os import shutil import subprocess @@ -36,7 +37,7 @@ def run_elmer_grid(sim_dir, meshfile, elmergrid=None): def run_elmer_solver(sim_dir, elmersolver=None): - """Run ElmerSolver with input file case.sif + """Run ElmerSolver with input file case.sif. Args: sim_dir (str): Simulation directory @@ -54,9 +55,9 @@ def run_elmer_solver(sim_dir, elmersolver=None): with open(sim_dir + '/elmersolver.log', 'w') as f: subprocess.run(args, cwd=sim_dir, stdout=f, stderr=f) + def run_multicore(count, sim_dirs, meshfiles, elmergrid=None, elmersolver=None): - """Run multiple instances of ElmerGrid, ElmerSolver on multiple - cores. + """Run multiple instances of ElmerGrid, ElmerSolver. Args: count (int): Number of processes diff --git a/pyelmer/gmsh_objects.py b/pyelmer/gmsh_objects.py index e81e1fd..879145b 100644 --- a/pyelmer/gmsh_objects.py +++ b/pyelmer/gmsh_objects.py @@ -62,8 +62,7 @@ def __getitem__(self, name): def show(self): - """Run gmsh GUI. - """ + """Run gmsh GUI.""" gmsh.fltk.run() @@ -167,7 +166,8 @@ def __init__(self, model, dim, name, geo_ids=[]): @property def dimtags(self): - """ + """Gmsh dimension tags. + Returns: list: Gmsh dim-tags of entities in shape. """ @@ -191,7 +191,7 @@ def boundaries(self): @property def bounding_box(self): - """Get the bounding box of this shape + """Get the bounding box of this shape. Returns: list[float]: [x_min, y_min, z_min, x_max, y_max, z_max] @@ -281,7 +281,7 @@ def right_boundary(self): def set_characteristic_length(self, char_length): """Set caracteristic length recursively on all boundaries and - their boudaries. + their boundaries. Args: char_length (float): Characteristic length for the mesh @@ -291,14 +291,13 @@ def set_characteristic_length(self, char_length): gmsh.model.mesh.setSize(boundary, char_length) def _make_physical(self): - """Convert shape into physical group. - """ + """Convert shape into physical group.""" self.ph_id = gmsh_utils.add_physical_group(self.dim, self.geo_ids, self.name) class MeshControl: - """Base class for mesh restrictions. - """ + """Base class for mesh restrictions.""" + def __init__(self, model): self._field = -1 self._restricted_field = -1 diff --git a/pyelmer/post.py b/pyelmer/post.py index 0afee01..6515aa8 100644 --- a/pyelmer/post.py +++ b/pyelmer/post.py @@ -83,7 +83,7 @@ def scan_logfile(sim_dir): def plot_residuals(sim_dir, solvers, save=False): - """Plot residuals in log file + """Plot residuals in log file. Does not work very well. Args: sim_dir (str): Simulation directory diff --git a/pyelmer/test/test_elements.py b/pyelmer/test/test_elements.py deleted file mode 100644 index 54f3126..0000000 --- a/pyelmer/test/test_elements.py +++ /dev/null @@ -1,59 +0,0 @@ -# created by Arved Enders-Seidlitz on 07.09.2020 -# -# tests for elements.py - -import copy -from pyelmer.elements import Node, Triangle2nd, Line2nd - -# simple input data -n1 = Node(0, 0, 0, 0) -n2 = Node(2, 0, 0, 4) -n3 = Node(0, 2, 0, 1) -n4 = Node(1, 0, 0, 1) -n5 = Node(1, 1, 0, 1) -n6 = Node(0, 1, 0, 1) -tr = Triangle2nd([n1, n2, n3, n4, n5, n6]) - -def values_at_nodes(N, nodes): - values = [] - for n in nodes: - values.append(N(n.x, n.y)) - return values - -def test_shape_functions(): - assert tr.N1(n1.x, n1.y) == 1 - assert tr.N2(n2.x, n2.y) == 1 - assert tr.N3(n3.x, n3.y) == 1 - assert tr.N4(n4.x, n4.y) == 1 - assert tr.N5(n5.x, n5.y) == 1 - assert tr.N6(n6.x, n6.y) == 1 - - assert values_at_nodes(tr.N1, [n2, n3, n4, n5, n6]) == [0, 0, 0, 0, 0] - assert values_at_nodes(tr.N2, [n1, n3, n4, n5, n6]) == [0, 0, 0, 0, 0] - assert values_at_nodes(tr.N3, [n1, n2, n4, n5, n6]) == [0, 0, 0, 0, 0] - assert values_at_nodes(tr.N4, [n1, n2, n3, n5, n6]) == [0, 0, 0, 0, 0] - assert values_at_nodes(tr.N5, [n1, n2, n3, n4, n6]) == [0, 0, 0, 0, 0] - assert values_at_nodes(tr.N6, [n1, n2, n3, n4, n5]) == [0, 0, 0, 0, 0] - -def test_derivatives(): - assert tr.N1_x(n1.x, n1.y) == tr.N1_y(n1.x, n1.y) - assert tr.N2_x(n2.x, n2.x) == tr.N3_y(n3.x, n3.y) - assert tr.N2_y(n2.x, n2.x) == tr.N3_x(n3.x, n3.y) - assert tr.N4_x(n4.x, n4.y) == 0 - assert tr.N6_y(n6.x, n6.y) == 0 - assert tr.N4_y(n4.x, n4.y) == tr.N6_x(n6.x, n6.y) - assert tr.N5_x(n5.x, n5.y) == tr.N5_y(n5.x, n5.y) - -def test_line(): - n1 = Node(0, 0, 0) - n2 = Node(1, 0, 0) - n3 = Node(2, 0, 0) - l = Line2nd([n1, n2, n3]) - normal = copy.deepcopy(l.normal) - l.invert_normal() - assert list(l.normal) == list(-1* normal) - -if __name__ == "__main__": - test_shape_functions() - test_derivatives() - test_line() diff --git a/pyelmer/test/test_gmsh_objects.py b/pyelmer/test/test_gmsh_objects.py index 973ec9c..5b60a42 100644 --- a/pyelmer/test/test_gmsh_objects.py +++ b/pyelmer/test/test_gmsh_objects.py @@ -3,6 +3,7 @@ import gmsh from pyelmer.gmsh_objects import * + @dataclass class Rects: r1: Shape @@ -11,16 +12,16 @@ class Rects: @pytest.fixture(scope='module') def rectangles(): - with Model() as model: - r1 = factory.addRectangle(0, 0, 0, 1, 1) - r2 = factory.addRectangle(1, 0, 0, 1, 1) - factory.fragment([(2, r1)], [(2, r2)]) - factory.synchronize() - rect1 = Shape(model, 2, 'r1', [r1]) - rect2 = Shape(model, 2, 'r2', [r2]) - rect3 = Shape(model, 2, 'r3', [r1, r2]) - # gmsh.fltk.run() - yield Rects(rect1, rect2, rect3) + model = Model() + r1 = factory.addRectangle(0, 0, 0, 1, 1) + r2 = factory.addRectangle(1, 0, 0, 1, 1) + factory.fragment([(2, r1)], [(2, r2)]) + factory.synchronize() + rect1 = Shape(model, 2, 'r1', [r1]) + rect2 = Shape(model, 2, 'r2', [r2]) + rect3 = Shape(model, 2, 'r3', [r1, r2]) + gmsh.fltk.run() + yield Rects(rect1, rect2, rect3) def test_geo_ids(rectangles): @@ -39,3 +40,15 @@ def test_get_boundaries_in_box(rectangles): assert sorted(rectangles.r1.get_boundaries_in_box([-0.5, 1.5], [-0.5, 1.5])) == [1, 2, 3, 4] assert rectangles.r1.get_boundaries_in_box([0.5, 1.5], [-0.5, 1.5]) == [2] assert rectangles.r1.get_boundaries_in_box([0.5, 1.5], [-0.5, 1.5], one_only=True) == 2 + + +def test_top_bottom_left_right_boundary(rectangles): + assert rectangles.r1.top_boundary == 3 + assert rectangles.r1.bottom_boundary == 1 + assert rectangles.r1.right_boundary == 2 + assert rectangles.r1.left_boundary == 4 + + assert rectangles.r2.top_boundary == 7 + assert rectangles.r2.bottom_boundary == 5 + assert rectangles.r2.right_boundary == 6 + assert rectangles.r2.left_boundary == 2 diff --git a/setup.py b/setup.py index 3e24648..d63eb46 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,3 @@ -# created by Arved Enders-Seidlitz on 07.09.2020 # https://packaging.python.org/tutorials/packaging-projects/ import setuptools @@ -10,7 +9,7 @@ version='0.1', author='Arved Enders-Seidlitz', author_email='arved.enders-seidlitz@ikz-berlin.de', - description='A python interface to Elmer', + description='A python interface to Elmer.', long_description=long_description, long_description_content_type='text/markdown', url='https://github.com/nemocrys/pyelmer',