diff --git a/DESCRIPTION.rst b/DESCRIPTION.rst old mode 100644 new mode 100755 index 2ff45fb..a778309 --- a/DESCRIPTION.rst +++ b/DESCRIPTION.rst @@ -1,9 +1,9 @@ -PETGEM +petgem ====== PETGEM is a parallel python code for 3D Controlled-Source -Electromagnetic Method (3D CSEM) in geophysics using edge finite -elements (Nedelec finite elements). +Electromagnetic Method (3D CSEM) in geophysics using high-order edge finite +elements (Nédélec finite elements). Requirements @@ -29,15 +29,15 @@ Install * Following commands may require root privileges -* Download `PETSc `__ (PETSc 3.7 and 3.8 have been tested) +* Download `PETSc `__ (PETSc 3.7, 3.8, and 3.9 have been tested) -* Uncompress the PETSc archive (in this example, using PETSc 3.8.3):: +* Uncompress the PETSc archive (in this example, using PETSc 3.9.3):: - $ tar zxvf petsc-3.8.3.tar.gz + $ tar zxvf petsc-3.9.3.tar.gz * Configure and build PETSc. The configuration options depend on the calculations you want to perform (complex- or real-valued) as well as your compiler/MPI/Blas/Lapack setup. For PETGEM executions, **PETSC MUST BE BUILD FOR COMPLEX-VALUED NUMBERS**. In order to avoid incompatibilities between PETSC, petsc4py and PETGEM, we highly recommend the following configuration lines. Please, visit PETSc website for advanced configuration options. If you have a clean environment (not working MPI/Blas/Lapack), then run:: - $ cd petsc-3.8.3 + $ cd petsc-3.9.3 $ export PETSC_DIR=$PWD $ export PETSC_ARCH=arch-linux2-c-debug @@ -49,6 +49,12 @@ Install $ --download-mumps --download-scalapack --download-parmetis --download-metis --download-ptscotch --download-cmake +* Further, to activate GPUs support, please add following options to previous configure line:: + + $ --with-cuda=1 --with_cuda_dir=PATH + + where ``PATH`` is the directory of your CUDA libraries. + * Then, build and install PETSc:: $ make $PETSC_DIR $PETSC_ARCH all @@ -79,8 +85,8 @@ publication, please acknowledge that fact by citing the project: elements*. Computers & Geosciences, vol 119: 123-136. ISSN 0098-3004, Elsevier. https://doi.org/10.1016/j.cageo.2018.07.005 -* Castillo-Reyes, O., de la Puente, J., Cela, J.M. (2017). - *Three-Dimensional CSEM Modelling on Unstructured Tetrahedral Meshes - Using Edge Finite Elements*. Communications in Computer and - Information Science, vol 697: 247-256. ISBN 978-3-319-57971-9, - Springer, Cham. https://doi.org/10.1007/978-3-319-57972-6_18 +* Castillo-Reyes, O., de la Puente, J., Cela, J.M. (2017). + *Three-Dimensional CSEM Modelling on Unstructured Tetrahedral Meshes + Using Edge Finite Elements*, Communications in Computer and + Information Science, vol 697: 247-256. ISBN 978-3-319-57971-9 + Springer, Cham. https://doi.org/10.1007/978-3-319-57972-6_18 diff --git a/README.rst b/README.rst old mode 100644 new mode 100755 index 9360fe7..233d630 --- a/README.rst +++ b/README.rst @@ -4,9 +4,9 @@ petgem Overview ======== -Welcome to petgem. This code is a parallel python modelling tool for 3D +Welcome to PETGEM. This code is a parallel python modelling tool for 3D Controlled-Source Electromagnetic Method (3D CSEM) in geophysics using -edge finite elements (Nedelec finite elements). +high-order edge finite elements (Nédélec finite elements). Dependencies ------------ @@ -34,7 +34,7 @@ License PETGEM is developed as open-source under GPLv3.0 license at Computer Applications in Science & Engineering of the Barcelona Supercomputing Center - Centro Nacional de Supercomputación. Please, see the CONDITIONS -OF USE described in the LICENSE.rst file. +OF USE described in the LICENSE.rst file. Documentation diff --git a/doc/Makefile b/doc/Makefile old mode 100644 new mode 100755 diff --git a/doc/source/CSEMEdges.rst b/doc/source/CSEMEdges.rst old mode 100644 new mode 100755 index 235037b..d476755 --- a/doc/source/CSEMEdges.rst +++ b/doc/source/CSEMEdges.rst @@ -19,8 +19,8 @@ of freedom, e.g. size of the problem. Furthermore, its divergence-free basis is very well suited for solving Maxwell’s equation. On top of that, we choose to support tetrahedral meshes, as these are the easiest to use for very large domains or complex geometries. We present the numerical formulation and -results of 3D CSEM forward modelling (FM) using tetrahedral EFEM on -unstructured meshes. +results of 3D CSEM forward modelling (FM) using EFEM of high-order on +unstructured tetrahedral meshes. .. _CSEM problem: @@ -33,11 +33,11 @@ CSEM surveys generally work with low frequency electromagnetic fields (:math:`\sim` 1 Hz to :math:`\sim` 3 Hz) because the electric conductivity of the geological structures is much larger than their dielectric permittivity. As a consequence, in an unbound domain :math:`\Gamma`, the electric field -can be obtained by solving Maxwell’s equations in their diffusive form: +can be obtained by solving Maxwell’s equations in their diffusive form .. math:: - \nabla \times \mathbf{E} &= i\omega \mu_{0}\mathbf H \\ - \nabla \times \mathbf{H} &= \mathbf J_{s} + \tilde{\sigma}\mathbf E + \nabla \times \mathbf{E} &= i\omega \mu_{0}\mathbf H, \\ + \nabla \times \mathbf{H} &= \mathbf J_{s} + \tilde{\sigma}\mathbf E, :label: maxwellDiffusive where the harmonic time dependence :math:`e^{-i \omega t}` is omitted, @@ -52,21 +52,21 @@ The first one is the inevitable spatial singularity at the source. The second is the grid refinement requirements in order to capture the rapid change of the primary field [1]_. In order to mitigate these issues, PETGEM used a secondary field approach where the total electric -field :math:`\mathbf E` is obtained as: +field :math:`\mathbf E` is obtained as .. math:: - \mathbf E &= \mathbf E_{p} + \mathbf E_{s} \\ - \tilde{\sigma} &= \tilde{\sigma_{s}} + \Delta \tilde{\sigma} + \mathbf E &= \mathbf E_{p} + \mathbf E_{s}, \\ + \tilde{\sigma} &= \tilde{\sigma_{s}} + \Delta \tilde{\sigma}, :label: total_electric_field where subscripts :math:`p` and :math:`s` represent a primary field and secondary field respectively. For a general layered Earth model, :math:`\mathbf E_{p}` can be computed semi-analytically by using Hankel transform filters. Based on this decomposition and following the work by -[3]_ the equation system to solve :math:`\mathbf E_{s}` is: +[3]_ the equation system to solve :math:`\mathbf E_{s}` is .. math:: - \nabla \times \nabla \times \mathbf E_{s} + i \omega \mu \tilde{\sigma} \mathbf E_{s} = -i \omega \mu \Delta \sigma \mathbf E_{p} + \nabla \times \nabla \times \mathbf E_{s} + i \omega \mu \tilde{\sigma} \mathbf E_{s} = -i \omega \mu \Delta \sigma \mathbf E_{p}, :label: electric_field_weak_form1 where the electrical conductivity :math:`\sigma` is a function of position @@ -81,60 +81,60 @@ based on the skin depth of the electric field [4]_. Edge finite element formulation ------------------------------- For the computation of :math:`\mathbf E_{s}`, PETGEM implemented the -Nédélec EFEM which uses vector basis functions defined on the edges of -the corresponding elements. Its basis functions are divergence-free but +high-order Nédélec EFEM which uses vector basis functions defined on the edges, +faces and volume of the corresponding elements. Its basis functions are divergence-free but not curl-free [2]_. Thus, EFEM naturally ensures tangential continuity and allows normal discontinuity of :math:`\mathbf E_{s}` at material interfaces. PETGEM used unstructured tetrahedral meshes because of their ability to represent complex geological structures such as bathymetry or reservoirs as well as the local refinement capability in order to improve the solution -accuracy. `Figure 4.1`_ shows the tetrahedral Nédélec elements (lowest order) +accuracy. `Figure 4.1`_ shows the tetrahedral Nédélec elements (first-, second-, and third-order) together with their node and edge indexing. .. _Figure 4.1: .. figure:: _static/figures/edgeElement.jpg - :scale: 12% - :alt: Tetrahedral Nédélec edge element with node/edge indexing. + :scale: 25% + :alt: Tetrahedral Nédélec edge element with node/edge/face indexing. :align: center - Figure 4.1. Tetrahedral Nédélec edge element with node/edge indexing. + Figure 4.1. Reference tetrahedral element showing numeration of DOFs for each order :math:`p`. For :math:`p=1` there are 6 DOFs (1 per edge). For :math:`p=2` there are 20 DOFs (2 per edge and 2 per face). Finally, for :math:`p=3` there are 45 DOFs (3 per edge, 6 per face and 3 per element's volume). In PETGEM workflow, the tangential component of the secondary electric field is assigned to the edges in the mesh. Therefore, all components of the electric field at a point :math:`\mathbf x` located inside a tetrahedral -element :math:`e` can be obtained as follows: +element :math:`e` can be obtained as follows .. math:: - \mathbf E^{e}(\mathbf x) = \sum_{i=1}^{6} \mathbf N^{e}_{i}(\mathbf x) E^{e}_{i} + \mathbf E^{e}(\mathbf x) = \sum_{i=1}^{ndofs} \mathbf N^{e}_{i}(\mathbf x) E^{e}_{i}, :label: field_components_edge_element -where :math:`\mathbf N^{e}_{i}` are the vector basis functions associated -to each edge :math:`i` and :math:`E^{e}_{i}` their degrees of freedom. -Considering the node and edge indexing in `Figure 4.1`_, the vector basis -functions can be expressed as follows: +where :math:`\mathbf N^{e}_{i}` are the vector basis functions and :math:`E^{e}_{i}` their degrees of freedom. +For instance, considering the node and edge indexing in `Figure 4.1`_, the vector basis +functions for :math:`p=1` (first-order) can be expressed as follows .. math:: - \mathbf N^{e}_{i} &= (\lambda^{e}_{i1} \nabla \lambda^{e}_{i2} - \lambda^{e}_{i2} \nabla \lambda^{e}_{i1}) \ell^{e}_{i} + \mathbf N^{e}_{i} &= (\lambda^{e}_{i1} \nabla \lambda^{e}_{i2} - \lambda^{e}_{i2} \nabla \lambda^{e}_{i1}) \ell^{e}_{i}, :label: nedelec_basis where subscripts :math:`i1` and :math:`i2` are the first and second nodes linked to the :math:`i`-th edge, :math:`\lambda^{e}_{i}` are the linear nodal basis functions, and :math:`\ell^{e}_{i}` is the length of the -:math:`i`-th edge of the element :math:`e`. +:math:`i`-th edge of the element :math:`e`. A systematic approach for obtaining +mixed-order curl-conforming basis functions may be seen in [4]_, [5]_. By substituting equation :eq:`field_components_edge_element` into :eq:`electric_field_weak_form1`, and using Galerkin's approach, the weak -form of the original differential equation becomes: +form of the original differential equation becomes .. math:: - Q_{i} = \int_{\Omega} \mathbf N_{i} \cdot [ \nabla \times \nabla \times \mathbf E_{s} -i \omega \mu \tilde{\sigma} \mathbf E_{s} + i \omega \mu \Delta \tilde{\sigma} \mathbf E_{p} ] dV + Q_{i} = \int_{\Omega} \mathbf N_{i} \cdot [ \nabla \times \nabla \times \mathbf E_{s} -i \omega \mu \tilde{\sigma} \mathbf E_{s} + i \omega \mu \Delta \tilde{\sigma} \mathbf E_{p} ] dV, :label: electric_field_weak_form2 The compact discretized form of :eq:`electric_field_weak_form2` is -obtained after applying the Green's theorem: +obtained after applying the Green's theorem .. math:: - [K^{e}_{jk} + i \omega \tilde{\sigma}_{e} M^{e}_{jk}] \cdot \{ E_{sk} \} = - i \omega \mu \Delta \tilde{\sigma}_{e} R^{e}_k + [K^{e}_{jk} + i \omega \tilde{\sigma}_{e} M^{e}_{jk}] \cdot \{ E_{sk} \} = - i \omega \mu \Delta \tilde{\sigma}_{e} R^{e}_k, :label: system_eq_edge_electric where :math:`K^{e}` and :math:`M^{e}` are the elemental stiffness @@ -146,3 +146,5 @@ side which requires numerical integration. .. [2] Jin, J. (2002). The Finite Element Method in Electromagnetics. Wiley, New York, second edn. .. [3] Newman, G.A. and Alumbaugh, D.L. (2002). Three-dimensional induction logging problems, Part 2: A finite difference solution. Geophysics, 67(2), 484–491. .. [4] Puzyrev, V., Koldan, J., de la Puente, J., Houzeaux, G., Vázquez, M. and Cela, J.M. (2013). A parallel finite-element method for three-dimensional controlled-source electromagnetic forward modelling. Geophysical Journal International, ggt027. +.. [5] Garcia-Castillo, L.E., Salazar-Palma, M., 2000. Second-order Nédélec tetrahedral element for computational electromagnetics. International Journal of Numerical Modelling: Electronic Networks, Devices and Fields (John Wiley & Sons, Inc.) 13, 261–287. +.. [6] Garcia-Castillo, L.E., Ruiz-Genovés, A.J., Gómez-Revuelto, I., Salazar-Palma, M., Sarkar, T.K., 2002. Third-order Nédélec curl-conforming finite element. IEEE Transactions on Magnetics 38, 2370–2372. diff --git a/doc/source/Contact.rst b/doc/source/Contact.rst old mode 100644 new mode 100755 diff --git a/doc/source/Download.rst b/doc/source/Download.rst old mode 100644 new mode 100755 index dab324d..6ffdc84 --- a/doc/source/Download.rst +++ b/doc/source/Download.rst @@ -184,7 +184,7 @@ Downloads Scripts ******* -* PETGEM is available for download at the project website generously hosted by PyPi. Download `here `__. +* PETGEM is available for download at the project website generously hosted by PyPi and GitHub. Download `here `__ or `here `__. * ``kernel.py``: python script that manages the PETGEM work-flow. Download :download:`here <_downloads/kernel.py>`. @@ -201,6 +201,8 @@ Examples * Example 1: Dataset for Pre-processing stage within PETGEM. Download :download:`here <_downloads/Example1.zip>`. -* Example 2: Dataset for the Canonical model of an off-shore hydrocarbon reservoir. Download :download:`here <_downloads/Example2.zip>`. +* Example 2: Dataset for the Canonical model of an off-shore hydrocarbon reservoir (Nédélec elements of first-order). Download :download:`here <_downloads/Example2.zip>`. -* Example 3: Dataset for the use of PETSc solvers. Download :download:`here <_downloads/Example3.zip>`. +* Example 3: Dataset for the Canonical model of an off-shore hydrocarbon reservoir (Nédélec elements of second-order). Download :download:`here <_downloads/Example3.zip>`. + +* Example 4: Dataset for the use of PETSc solvers. Download :download:`here <_downloads/Example4.zip>`. diff --git a/doc/source/Features.rst b/doc/source/Features.rst old mode 100644 new mode 100755 index e1abb45..3b2e1f7 --- a/doc/source/Features.rst +++ b/doc/source/Features.rst @@ -3,7 +3,7 @@ Features =============== -PETGEM use a code structure for the Nédélec FE algorithm that emphasizes +PETGEM use a code structure for the high-order Nédélec FE algorithm that emphasizes good parallel scalability, which is crucial in the multi-core era. Furthermore, it’s modularity should simplify the process of reaching the best possible performance in terms of percentage of the peak amount of @@ -30,7 +30,7 @@ A more detailed explanation is the following: * Independent of problem formulation, numerical solution, and data storage: The kernel provides the independent abstractions for modeling, numerical methods, data storage and analysis. -* Parallel processing support: Based on distributed-memory parallelism (`petsc4py `__) and static load balancing. +* Parallel processing support: Based on distributed-memory parallelism (`petsc4py `__) and static load balancing. Further, GPUs architectures are supported. * Confidence and performance monitoring: Based on an simple and automatic profiling module. @@ -38,7 +38,7 @@ A more detailed explanation is the following: * Interface to mesh generator: Simple and light library to export nodal finite element meshes to edge elements data structures. Current version support `Gmsh `__ meshes. -* Edge FEM library: Edge-based discretisations, vector basis functions, their geometry description, and generalized integration rules provides a generic support for implementing edge-based solution algorithms. +* Edge FEM library: High-order Edge-based discretisations, vector basis functions, their geometry description, and generalized integration rules provides a generic support for implementing edge-based solution algorithms. * Linear systems library: Support to Compressed Row Storage (CSR) format for sparse matrices and their easy and efficient parallel assembly on distributed-memory platforms. @@ -80,8 +80,9 @@ Target architecture -------------------- The HPC goal of the PETGEM involves using cutting-edge architectures. To that goal, the code is implemented in current state-of-the-art -platforms such as Intel Xeon Platinum, Intel Haswell and Intel Xeon Phi processors, which offer -high performance, flexibility, and power efficiency. Nevertheless, +platforms such as Intel Xeon Platinum processors from the Skylake generation, +Intel Haswell and Intel Xeon Phi processors, which offer +high-performance, flexibility, and power efficiency. Nevertheless, PETGEM support older architectures such as SandyBridge, for the sake of usability and to be able to compare performance. diff --git a/doc/source/Installation.rst b/doc/source/Installation.rst old mode 100644 new mode 100755 index 5a093c0..6f110de --- a/doc/source/Installation.rst +++ b/doc/source/Installation.rst @@ -31,19 +31,19 @@ Install PETGEM * Following commands may require root privileges -* Download `PETSc `__ (PETSc 3.7 and 3.8 have been tested) +* Download `PETSc `__ (PETSc 3.7, 3.8 and 3.9 have been tested) -* Uncompress the `PETSc `__ archive (in this example, using PETSc 3.8.3): +* Uncompress the `PETSc `__ archive (in this example, using PETSc 3.9.3): .. code-block:: bash - $ tar zxvf petsc-3.8.3.tar.gz + $ tar zxvf petsc-3.9.3.tar.gz -* Configure and build `PETSc `__. The configuration options depend on the calculations you want to perform (complex- or real-valued) as well as your compiler/MPI/Blas/Lapack setup. For PETGEM executions, **PETSC MUST BE BUILD FOR COMPLEX-VALUED NUMBERS**. In order to avoid incompatibilities between PETSC, petsc4py and PETGEM, we highly recommend the following configuration lines. Please, visit PETSc website for advanced configuration options. If you have a clean environment (not working MPI/Blas/Lapack), then run: +* Configure and build `PETSc `__. The configuration options depend on the calculations you want to perform (complex- or real-valued) as well as your compiler/MPI/Blas/Lapack setup. For PETGEM executions, **PETSC MUST BE BUILD FOR COMPLEX-VALUED NUMBERS**. In order to avoid incompatibilities between PETSC, petsc4py and PETGEM, we highly recommend the following configuration lines. Please, visit `PETSc `__ website for advanced configuration options. If you have a clean environment (not working MPI/Blas/Lapack), then run: .. code-block:: bash - $ cd petsc-3.8.3 + $ cd petsc-3.9.3 $ export PETSC_DIR=$PWD $ export PETSC_ARCH=arch-linux2-c-debug @@ -59,6 +59,14 @@ Install PETGEM $ --download-mumps --download-scalapack --download-parmetis --download-metis --download-ptscotch --download-cmake +* Further, to activate GPUs support, please add following options to previous configure line: + + .. code-block:: bash + + $ --with-cuda=1 --with_cuda_dir=PATH + + where ``PATH`` is the directory of your CUDA libraries. + * Then, build and install `PETSc `__: .. code-block:: bash @@ -91,7 +99,8 @@ Downloading and building PETGEM ------------------------------- The PETGEM package is available for download at -`Python Package Index (PyPI) `__ +`Python Package Index (PyPI) `__, at +`GitHub `__, and the :ref:`Download` section of this project website. * Configure and install `PETSc `__ (see :ref:`Install` section) @@ -111,7 +120,7 @@ and the :ref:`Download` section of this project website. $ tar zxvf petgem-1.1.tar.gz $ cd petgem-1.1 -* After unpacking the release tarball, the distribution is ready for building. Some environment configuration is needed to inform the location `PETSc `__. As in :ref:`Install` section, you can set the environment variables ``PETSC_DIR`` and ``PETSC_ARCH`` indicating where you have built/installed `PETSc `__: +* After unpacking the release tarball, the distribution is ready for building. Some environment configuration is needed to inform the `PETSc `__ location. As in :ref:`Install` section, you can set the environment variables ``PETSC_DIR`` and ``PETSC_ARCH`` indicating where you have built/installed `PETSc `__: .. code-block:: bash diff --git a/doc/source/Manual.rst b/doc/source/Manual.rst old mode 100644 new mode 100755 index 0737f48..e7a959d --- a/doc/source/Manual.rst +++ b/doc/source/Manual.rst @@ -82,7 +82,7 @@ guidelines are the following: PETGEM design ------------- -PETGEM use a code structure for the Nédelec FE algorithm that emphasizes +PETGEM use a code structure for the high-order Nédelec FE algorithm that emphasizes good parallel scalability, which is crucial in the multi-core era. Furthermore, it’s modularity should simplify the process of reaching the best possible performance in terms of percentage of the peak amount of @@ -188,7 +188,7 @@ The PETGEM source code is `petgem/`, which has the following contents: - Common classes and functions for init process. * - `efem/` - General modules and classes for describing a 3D CSEM survey by using EFEM of - lowest order in tetrahedral meshes + high-order (:math:`p=1,2,3`) in tetrahedral meshes * - `mesh/` - Interface to import mesh files * - `monitoring/` @@ -230,25 +230,35 @@ task. A glance of this file is the following: # name and his key names MUST NOT BE changed. All file paths should consider as absolute. preprocessing = { + # ---------- Nedelec element order ---------- + # 1 = First Nédélec order (6 DOFS per element) + # 2 = Second Nédélec order (20 DOFS per element) + # 3 = Third Nédélec order (45 DOFS per element) + # Type: int + # Optional: NO + 'NEDELEC_ORDER': 1, + # ---------- Mesh file ---------- - 'MESH_FILE': 'DIPOLE1D/Input_preprocessing/DIPOLE1D.msh', + 'MESH_FILE': 'PATH_TO_FILE', # ---------- Material conductivities ---------- 'MATERIAL_CONDUCTIVITIES': [1.0, 1./100., 1., 1./.3], - # ---------- Receivers positions file ---------- - 'RECEIVERS_FILE': 'DIPOLE1D/Input_preprocessing/RECEIVER_POSITIONS.txt', + # ---------- Receivers position file ---------- + 'RECEIVERS_FILE': 'PATH_TO_FILE', # ---------- Path for Output ---------- - 'OUT_DIR': 'DIPOLE1D/Input_model/', - } + 'OUT_DIR': 'PATH_TO_FILE', + } As you can see, in the ``preprocessingParams.py`` are defined the main parameters, -namely, mesh file path, material conductivities, receivers positions file path and the +namely, Nédélec element order, mesh file path, material conductivities, receivers positions file path and the pre-processing output path. For this phase, the supported/expected formats are the following: +* ``NEDELEC_ORDER``: nédélec element order for the discretization. Supported orders are :math:`p=1,2,3` + * ``MESH_FILE``: path to tetrahedral mesh file in MSH ASCII format from `Gmsh `__ * ``MATERIAL_CONDUCTIVITIES``: numpy array where each value represents the conductivity for each material defined in ``MESH_FILE``. Therefore, size of ``MATERIAL_CONDUCTIVITIES`` must match with the number of materials in ``MESH_FILE`` @@ -272,15 +282,19 @@ following: * ``meshConnectivity.dat``: list of the node number of the n-th tetrahedral element in the mesh. The dimensions of ``meshConnectivity.dat`` are given by (number-of-elements, 4) -* ``dofs.dat``: list of degrees of freedom of the n-th tetrahedral element in the mesh. Since PETGEM is based on linear Nédelec FE, the dimensions of ``dofs.dat`` are given by (number-of-elements, 6) +* ``dofs.dat``: list of degrees of freedom of the n-th tetrahedral element in the mesh. Since PETGEM is based on high-order Nédelec FE, the dimensions of ``dofs.dat`` are given by (number-of-elements, 6) for :math:`p=1`, by (number-of-elements, 20) for :math:`p=2`, and by (number-of-elements, 45) for :math:`p=3`. + +* ``edgesNodes.dat``: list of the two nodes that make up each edge in the mesh. The dimensions of ``edgesNodes.dat`` are given by (number-of-edges, 2) -* ``dofsNodes.dat``: list of the two nodes that make up each degrees-of-freedom (DOFs) in the mesh. The dimensions of ``dofsNodes.dat`` are given by (number-of-dofs, 2) +* ``faces.dat``: list of the four faces that make up each tetrahedral element in the mesh. The dimensions of ``faces.dat`` are given by (number-of-elements, 4) -* ``boundaries.dat``: list of the DOFs number that belongs on domain boundaries. The dimensions of ``boundaries.dat`` are given by (number-of-boundaries, 1) +* ``facesNodes.dat``: list of the three nodes that make up each tetrahedral face in the mesh. The dimensions of ``facesNodes.dat`` are given by (number-of-faces, 3) + +* ``boundaries.dat``: list of the DOFs indexes that belongs on domain boundaries. The dimensions of ``boundaries.dat`` are given by (number-of-boundaries, 1) * ``conductivityModel.dat``: floating point values giving the conductivity to which the n-th tetrahedral element belongs. The dimensions of ``conductivityModel.dat`` are given by (number-of-elements, 1) -* ``receivers.dat``: floating point values giving the X, Y and Z coordinates of the receivers. Additionally, this file includes information about the tetrahedral element that contains the n-th receiver, namely, its X, Y and Z coordinates of the four nodes, its list of the node number and its list of DOFs. The dimensions of ``receivers.dat`` are given by (number-of-receivers, 25) +* ``receivers.dat``: floating point values giving the X, Y and Z coordinates of the receivers. Additionally, this file includes information about the tetrahedral element that contains the n-th receiver, namely, its X, Y and Z coordinates of the four nodes, its list of the node number and its list of DOFs. The dimensions of ``receivers.dat`` are given by (number-of-receivers, 25) for :math:`p=1`, by (number-of-receivers, 39) for :math:`p=2`, and by (number-of-receivers, 64) for :math:`p=3`. * ``nnz.dat``: list containing the number of nonzeros in the various matrix rows, namely, the sparsity pattern. According to the `PETSc `__ documentation, using the ``nnz.dat`` list to preallocate memory is especially important for efficient matrix assembly if the number of nonzeros varies considerably among the rows. The dimensions of ``nnz.dat`` are given by (number-of-DOFs, 1) @@ -333,7 +347,8 @@ details about available options. A template of this file is included in ``examples/`` of the PETGEM source tree. Additionally, a freely available copy of this file is located at :ref:`Download` section. -On the other hand, any 3D CSEM survey should include: physical parameters, +On the other hand, any 3D CSEM survey should include: order for Nédélec elements +discretizations, physical parameters, a mesh file, source and receivers files. These data are included in the ``modelParams.py`` file. Here all the files resulting from the pre-processing phase are included. In order to avoid a specific parser, ``modelParams.py`` @@ -344,18 +359,33 @@ A glance of ``modelParams.py`` file is the following: .. code-block:: python - # Parameters file template for 3D CSEM modelling. - # By definition, any 3D CSEM survey should include: physical parameters, a mesh file, source and receivers files. These data - # are included in the modelParams.py file. Additionally, options for PETSc solvers are defined in a petsc.opts file. - # In order to avoid a specific parser, modelParams.py file is imported by PETGEM as a Python dictionary. As consequence, - # the dictionary name and his key names MUST NOT BE changed. All file paths should consider as absolute. +# Parameters file template for 3D CSEM modelling. +# By definition, any 3D CSEM survey should include: physical parameters, a mesh file, source and receivers files. These data +# are included in the modelParams.py file. Additionally, options for PETSc solvers are defined in a petsc.opts file. +# In order to avoid a specific parser, modelParams.py file is imported by PETGEM as a Python dictionary. As consequence, +# the dictionary name and his key names MUST NOT BE changed. All file paths should consider as absolute. + +modelling = { + # ---------- Nedelec element order ---------- + # 1 = First nedelec order (6 DOFS per element) + # 2 = Second nedelec order (20 DOFS per element) + # 3 = Third nedelec order (45 DOFS per element) + # Type: int + # Optional: NO + 'NEDELEC_ORDER': 1, - modelling = { - # ----- Pyshical parameters ----- + # ---------- CUDA SUPPORT ---------- + # 0 = No + # 1 = Yes + # Type: int + # Optional: NO + 'CUDA': 0, + + # ----- Physical parameters ----- # Source # Source frequency. Type: float # Optional: NO - 'FREQ': 2.0, + 'FREQ': 1.0, # Source position(x, y, z). Type: float # Optional: NO 'SRC_POS': [1750.0, 1750.0, -975.0], @@ -373,7 +403,7 @@ A glance of ``modelParams.py`` file is the following: 'SRC_LENGTH': 1.0, # Conductivity model. Type: str # Optional: NO - 'CONDUCTIVITY_MODEL_FILE': 'DIPOLE1D/Input_model/conductivityModel.dat', + 'CONDUCTIVITY_MODEL_FILE': 'PATH_TO_FILE', # Background conductivity. Type: float # Optional: NO 'CONDUCTIVITY_BACKGROUND': 3.33, @@ -382,34 +412,41 @@ A glance of ``modelParams.py`` file is the following: # Mesh files # Nodes spatial coordinates. Type: str # Optional: NO - 'NODES_FILE': 'DIPOLE1D/Input_model/nodes.dat', + 'NODES_FILE': 'PATH_TO_FILE', # Elements-nodes connectivity. Type: str # Optional: NO - 'MESH_CONNECTIVITY_FILE': 'DIPOLE1D/Input_model/meshConnectivity.dat', + 'MESH_CONNECTIVITY_FILE': 'PATH_TO_FILE', + # Elements-faces connectivity. Type: str + # Optional: NO + 'FACES_CONNECTIVITY_FILE': 'PATH_TO_FILE', + # Faces-nodes connectivity. Type: str + # Optional: NO + 'FACES_NODES_FILE': 'PATH_TO_FILE', # Elements-edges connectivity. Type: str # Optional: NO - 'DOFS_CONNECTIVITY_FILE': 'DIPOLE1D/Input_model/dofs.dat', + 'EDGES_CONNECTIVITY_FILE': 'PATH_TO_FILE', # Edges-nodes connectivity. Type: str # Optional: NO - 'DOFS_NODES_FILE': 'DIPOLE1D/Input_model/dofsNodes.dat', + 'EDGES_NODES_FILE': 'PATH_TO_FILE', # Sparsity pattern for matrix allocation (PETSc) - 'NNZ_FILE': 'DIPOLE1D/Input_model/nnz.dat', + 'NNZ_FILE': 'PATH_TO_FILE', # Boundaries. Type: str # Optional: NO - 'BOUNDARIES_FILE': 'DIPOLE1D/Input_model/boundaries.dat', + 'BOUNDARIES_FILE': 'PATH_TO_FILE', # ------------ Solver ----------- # Solver options must be set in - # petsc.opts file + # petsc_solver.opts # ------------ Receivers file ----------- # Name of the file that contains the receivers position. Type: str # Optional: NO - 'RECEIVERS_FILE': 'DIPOLE1D/Input_model/receivers.dat', - } + 'RECEIVERS_FILE': 'PATH_TO_FILE', -The ``modelParams.py`` file is divided into 4 sections, namely, physical parameters -(frequency, source position, source current, source length, conductivity model, +} + +The ``modelParams.py`` file is divided into 5 sections, namely, Nédélec element order, +physical parameters (frequency, source position, source current, source length, conductivity model, background conductivity), mesh information (file path of nodal spatial coordinates, mesh connectivity, DOFs connectivity, edges-nodes connectivity, sparsity structure for `PETSc `__ @@ -436,8 +473,8 @@ is the modelling parameters file for PETGEM. PETGEM solves the problem and outputs the solution to the ``Output/`` path. By default PETGEM will create the ``Output`` directory at same level where the ``modelParams.py`` file is located. The output files will be in three formats: -`PETSc `__, Matlab and ASCII. Therefore, the -``Output`` directory will contain three subdirectories: ``Ascii``, ``Matlab`` +`PETSc `__, MATLAB and ASCII. Therefore, the +``Output`` directory will contain three subdirectories: ``Ascii``, ``MATLAB`` and ``Petsc``. By default, and for each format, PETGEM save the electric field responses in different files: @@ -464,8 +501,8 @@ the next work-flow: #. The problem sets up its domain, sub-domains, source, solver. This stage include the computation of the main data structures #. Parallel assembling of :math:`Ax=b`. See :ref:`CSEM problem` and :ref:`Edge finite element formulation` sections for a detail mathematical background of this equation system #. The solution is obtained in parallel by calling a ``ksp()`` `PETSc `__ object. -#. Interpolation of electromagnetic responses & post-processing parallel stage -#. Finally the solution can be stored by calling ``postProcessingFields()`` function. Current version support `PETSc `__, Matlab and ASCII formats. +#. Interpolation of electromagnetic responses and post-processing parallel stage +#. Finally the solution can be stored by calling ``postProcessingFields()`` function. Current version support `PETSc `__, MATLAB and ASCII formats Based on previous work-flow, any 3D CSEM modelling requires the following input files: @@ -483,7 +520,7 @@ input files: Mesh formats ------------ -Current PETGEM version supports mesh files in MSH ASCII from +Current PETGEM version supports mesh files in MSH ASCII (version 2.2) from `Gmsh `__. Aforementioned format must be pre-processed using the ``run_preprocessing.py`` script. The ``run_preprocessing.py`` script is included in the top-level directory of the PETGEM source tree. @@ -590,7 +627,7 @@ Visualization of results Once a solution of a 3D CSEM survey has been obtained, it should be post-processed by using a visualization program. PETGEM does not do the visualization by itself, but it generates output files (ASCII, -`PETSc `__ and Matlab formats are supported) +`PETSc `__ and MATLAB formats are supported) with the electric responses at receivers positions. It also gives timing values in order to evaluate the performance. @@ -658,7 +695,7 @@ which content is the following: * Visit http://gmsh.info/ for details about mesh scripting with Gmsh * * by Octavio Castillo-Reyes, BSC-CASE (octavio.castillo@bsc.es) - * Latest update: January 30th, 2018 + * Latest update: September 19th, 2018 *********************************************************************/ // ################################################################# // # Parameters # @@ -752,6 +789,9 @@ which content is the following: // ################################################################# Characteristic Length {1, 2, 3, 4, 5, 6, 7, 8, 9 , 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20} = dg; + // Mesh file format supported by PETGEM + Mesh.MshFileVersion = 2.2; + Once the mesh have been created, the PETGEM pre-processing is invoked as follows: @@ -769,18 +809,30 @@ task. A glance of this script is the following: # name and his key names MUST NOT BE changed. All file paths should consider as absolute. preprocessing = { + # ---------- Nedelec element order ---------- + # 1 = First Nédélec order (6 DOFS per element) + # 2 = Second Nédélec order (20 DOFS per element) + # 3 = Third Nédélec order (45 DOFS per element) + # Type: int + # Optional: NO + 'NEDELEC_ORDER': 1, + # ---------- Mesh file ---------- - 'MESH_FILE': 'PREPROCESSING/Input_preprocessing/PREPROCESSING.msh', + # Type: str + 'MESH_FILE': 'Example1/PREPROCESSING/Input_preprocessing/PREPROCESSING.msh', # ---------- Material conductivities ---------- + # Type: float 'MATERIAL_CONDUCTIVITIES': [1., 1./.3], - # ---------- Receivers positions file ---------- - 'RECEIVERS_FILE': 'PREPROCESSING/Input_preprocessing/RECEIVER_POSITIONS.txt', + # ---------- Receivers position file ---------- + # Type: str + 'RECEIVERS_FILE': 'Example1/PREPROCESSING/Input_preprocessing/RECEIVER_POSITIONS.txt', # ---------- Path for Output ---------- - 'OUT_DIR': 'Input_model/', - } + # Type: str + 'OUT_DIR': 'Example1/PREPROCESSING/Input_model/', + } As you can see, in the ``preprocessingParams.py`` are defined the main parameters, namely, mesh file path, material conductivities, receivers positions file path and the @@ -790,7 +842,7 @@ The expected output of this phase is show in `Figure 7.7`_. .. _Figure 7.7: .. figure:: _static/figures/output_preprocessing.png - :scale: 35% + :scale: 65% :alt: PETGEM output for pre-processing stage :align: center @@ -814,11 +866,12 @@ consists in four-layers: 1000 m thick seawater (3.3 :math:`S/m`), 1000 m thick s (1 :math:`S/m`). The computational domain is a :math:`[0,3500]^3` m cube. For this model, a 2 Hz x-directed dipole source is located at :math:`z=975`, :math:`x=1750`, :math:`y=1750`. The receivers are placed in-line to the source position and along its -orientation, directly above the seafloor (:math:`z = 990`). +orientation, directly above the seafloor (:math:`z = 990`). Further, the +Nédélec element order is p=1 (first order) Meshing ####### -PETGEM V1.0 is based on tetrahedral meshes of lowest order. Therefore, `Figure 7.8`_ +PETGEM V1.0 is based on tetrahedral meshes. Therefore, `Figure 7.8`_ shows a 3D view of the model with its unstructured tetrahedral mesh for the halfspace :math:`y>1750`, with the color scale representing the electrical conductivity :math:`\sigma` for each layer. Mesh in `Figure 7.8`_ have been @@ -866,61 +919,82 @@ this example follows: # the dictionary name and his key names MUST NOT BE changed. All file paths should consider as absolute. modelling = { - # ----- Pyshical parameters ----- - # Source - # Source frequency. Type: float - # Optional: NO - 'FREQ': 2.0, - # Source position(x, y, z). Type: float - # Optional: NO - 'SRC_POS': [1750.0, 1750.0, -975.0], - # Source orientarion. Type: int - # 1 = X-directed source - # 2 = Y-directed source - # 3 = Z-directed source - # Optional: NO - 'SRC_DIREC': 1, - # Source current. Type: float - # Optional: NO - 'SRC_CURRENT': 1.0, - # Source length. Type: float - # Optional: NO - 'SRC_LENGTH': 1.0, - # Conductivity model. Type: str - # Optional: NO - 'CONDUCTIVITY_MODEL_FILE': 'DIPOLE1D/Input_model/conductivityModel.dat', - # Background conductivity. Type: float - # Optional: NO - 'CONDUCTIVITY_BACKGROUND': 3.33, - - # ------- Mesh and conductivity model files ------ - # Mesh files - # Nodes spatial coordinates. Type: str - # Optional: NO - 'NODES_FILE': 'DIPOLE1D/Input_model/nodes.dat', - # Elements-nodes connectivity. Type: str - # Optional: NO - 'MESH_CONNECTIVITY_FILE': 'DIPOLE1D/Input_model/meshConnectivity.dat', - # Elements-edges connectivity. Type: str - # Optional: NO - 'DOFS_CONNECTIVITY_FILE': 'DIPOLE1D/Input_model/dofs.dat', - # Edges-nodes connectivity. Type: str - # Optional: NO - 'DOFS_NODES_FILE': 'DIPOLE1D/Input_model/dofsNodes.dat', - # Sparsity pattern for matrix allocation (PETSc) - 'NNZ_FILE': 'DIPOLE1D/Input_model/nnz.dat', - # Boundaries. Type: str - # Optional: NO - 'BOUNDARIES_FILE': 'DIPOLE1D/Input_model/boundaries.dat', - - # ------------ Solver ----------- - # Solver options must be set in - # petsc.opts file - - # ------------ Receivers file ----------- - # Name of the file that contains the receivers position. Type: str - # Optional: NO - 'RECEIVERS_FILE': 'DIPOLE1D/Input_model/receivers.dat', + # ---------- Nedelec element order ---------- + # 1 = First nedelec order (6 DOFS per element) + # 2 = Second nedelec order (20 DOFS per element) + # 3 = Third nedelec order (45 DOFS per element) + # Type: int + # Optional: NO + 'NEDELEC_ORDER': 1, + + # ---------- CUDA SUPPORT ---------- + # 0 = No + # 1 = Yes + # Type: int + # Optional: NO + 'CUDA': 0, + + # ----- Physical parameters ----- + # Source + # Source frequency. Type: float + # Optional: NO + 'FREQ': 2.0, + # Source position(x, y, z). Type: float + # Optional: NO + 'SRC_POS': [1750.0, 1750.0, -975.0], + # Source orientarion. Type: int + # 1 = X-directed source + # 2 = Y-directed source + # 3 = Z-directed source + # Optional: NO + 'SRC_DIREC': 1, + # Source current. Type: float + # Optional: NO + 'SRC_CURRENT': 1.0, + # Source length. Type: float + # Optional: NO + 'SRC_LENGTH': 1.0, + # Conductivity model. Type: str + # Optional: NO + 'CONDUCTIVITY_MODEL_FILE': 'Example2/DIPOLE1D/Input_model/conductivityModel.dat', + # Background conductivity. Type: float + # Optional: NO + 'CONDUCTIVITY_BACKGROUND': 3.33, + + # ------- Mesh and conductivity model files ------ + # Mesh files + # Nodes spatial coordinates. Type: str + # Optional: NO + 'NODES_FILE': 'Example2/DIPOLE1D/Input_model/nodes.dat', + # Elements-nodes connectivity. Type: str + # Optional: NO + 'MESH_CONNECTIVITY_FILE': 'Example2/DIPOLE1D/Input_model/meshConnectivity.dat', + # Elements-faces connectivity. Type: str + # Optional: NO + 'FACES_CONNECTIVITY_FILE': 'Example2/DIPOLE1D/Input_model/faces.dat', + # Faces-nodes connectivity. Type: str + # Optional: NO + 'FACES_NODES_FILE': 'Example2/DIPOLE1D/Input_model/facesNodes.dat', + # Elements-edges connectivity. Type: str + # Optional: NO + 'EDGES_CONNECTIVITY_FILE': 'Example2/DIPOLE1D/Input_model/edges.dat', + # Edges-nodes connectivity. Type: str + # Optional: NO + 'EDGES_NODES_FILE': 'Example2/DIPOLE1D/Input_model/edgesNodes.dat', + # Sparsity pattern for matrix allocation (PETSc) + 'NNZ_FILE': 'Example2/DIPOLE1D/Input_model/nnz.dat', + # Boundaries. Type: str + # Optional: NO + 'BOUNDARIES_FILE': 'Example2/DIPOLE1D/Input_model/boundaries.dat', + + # ------------ Solver ----------- + # Solver options must be set in + # petsc_solver.opts + + # ------------ Receivers file ----------- + # Name of the file that contains the receivers position. Type: str + # Optional: NO + 'RECEIVERS_FILE': 'Example2/DIPOLE1D/Input_model/receivers.dat', } Note that you may wish to change the location of the input files to @@ -931,12 +1005,12 @@ the solver options have been configured in the ``petsc.opts`` file as follows: .. code-block:: python - # Solver options for PETSc - -ksp_type gmres - -pc_type sor - -ksp_rtol 1e-8 - -ksp_converged_reason - -log_summary + # Solver options for + -ksp_type gmres + -pc_type sor + -ksp_rtol 1e-8 + -ksp_converged_reason + -log_summary That's it, we are now ready to solve the modelling. @@ -959,8 +1033,8 @@ directory of the PETGEM source tree: and outputs the solution to the output path (``DIPOLE1D/Output/``). The output files will be in three formats: -`PETSc `__, Matlab and ASCII. Therefore, the -``Output`` directory will contain three subdirectories: ``Ascii``, ``Matlab`` +`PETSc `__, MATLAB and ASCII. Therefore, the +``Output`` directory will contain three subdirectories: ``Ascii``, ``MATLAB`` and ``Petsc``. By default, and for each format, PETGEM save the electric field responses in different files: @@ -975,7 +1049,7 @@ PETGEM post-processing Once a solution of a 3D CSEM survey has been obtained, it should be post-processed by using a visualization program. PETGEM does not do the visualization by itself, but it generates output files (ASCII, -`PTSc `__ and Matlab formats are supported) +`PETSc `__ and MATLAB formats are supported) with the electric responses at receivers positions. It also gives timing values in order to evaluate the performance. @@ -996,7 +1070,159 @@ mesh with :math:`\approx2` millions of DOFs. Figure 7.9. Total electric field comparative for x-component between PETGEM V1.0 and DIPOLE1D. -Example 3: Use of PETSc solvers +Example 3: Use of high-order Nédélec elements +********************************************* +In order to show the potential of high-order Nédélec elements, here is +considered the same model of previous example. In this case, the +Nédélec element order is p=2 (second order). + +Meshing +########################## +As in previous example, the mesh is composed by four conductivity materials. The +resulting `Gmsh `__ script can be found in :ref:`Download` +section. It is worth to mention that the use a Nédélec element order p=2 helps +to reduce the number of tetrahedral elements to achieved a certain error level. + +Parameters file for PETGEM +########################## +The parameters file used for this example follows: + +.. code-block:: python + + # Parameters file template for 3D CSEM modelling. + # By definition, any 3D CSEM survey should include: physical parameters, a mesh file, source and receivers files. These data + # are included in the modelParams.py file. Additionally, options for PETSc solvers are defined in a petsc.opts file. + # In order to avoid a specific parser, modelParams.py file is imported by PETGEM as a Python dictionary. As consequence, + # the dictionary name and his key names MUST NOT BE changed. All file paths should consider as absolute. + + modelling = { + # ---------- Nedelec element order ---------- + # 1 = First nedelec order (6 DOFS per element) + # 2 = Second nedelec order (20 DOFS per element) + # 3 = Third nedelec order (45 DOFS per element) + # Type: int + # Optional: NO + 'NEDELEC_ORDER': 2, + + # ---------- CUDA SUPPORT ---------- + # 0 = No + # 1 = Yes + # Type: int + # Optional: NO + 'CUDA': 0, + + # ----- Physical parameters ----- + # Source + # Source frequency. Type: float + # Optional: NO + 'FREQ': 2.0, + # Source position(x, y, z). Type: float + # Optional: NO + 'SRC_POS': [1750.0, 1750.0, -975.0], + # Source orientarion. Type: int + # 1 = X-directed source + # 2 = Y-directed source + # 3 = Z-directed source + # Optional: NO + 'SRC_DIREC': 1, + # Source current. Type: float + # Optional: NO + 'SRC_CURRENT': 1.0, + # Source length. Type: float + # Optional: NO + 'SRC_LENGTH': 1.0, + # Conductivity model. Type: str + # Optional: NO + 'CONDUCTIVITY_MODEL_FILE': 'Example2/DIPOLE1D/Input_model/conductivityModel.dat', + # Background conductivity. Type: float + # Optional: NO + 'CONDUCTIVITY_BACKGROUND': 3.33, + + # ------- Mesh and conductivity model files ------ + # Mesh files + # Nodes spatial coordinates. Type: str + # Optional: NO + 'NODES_FILE': 'Example2/DIPOLE1D/Input_model/nodes.dat', + # Elements-nodes connectivity. Type: str + # Optional: NO + 'MESH_CONNECTIVITY_FILE': 'Example2/DIPOLE1D/Input_model/meshConnectivity.dat', + # Elements-faces connectivity. Type: str + # Optional: NO + 'FACES_CONNECTIVITY_FILE': 'Example2/DIPOLE1D/Input_model/faces.dat', + # Faces-nodes connectivity. Type: str + # Optional: NO + 'FACES_NODES_FILE': 'Example2/DIPOLE1D/Input_model/facesNodes.dat', + # Elements-edges connectivity. Type: str + # Optional: NO + 'EDGES_CONNECTIVITY_FILE': 'Example2/DIPOLE1D/Input_model/edges.dat', + # Edges-nodes connectivity. Type: str + # Optional: NO + 'EDGES_NODES_FILE': 'Example2/DIPOLE1D/Input_model/edgesNodes.dat', + # Sparsity pattern for matrix allocation (PETSc) + 'NNZ_FILE': 'Example2/DIPOLE1D/Input_model/nnz.dat', + # Boundaries. Type: str + # Optional: NO + 'BOUNDARIES_FILE': 'Example2/DIPOLE1D/Input_model/boundaries.dat', + + # ------------ Solver ----------- + # Solver options must be set in + # petsc_solver.opts + + # ------------ Receivers file ----------- + # Name of the file that contains the receivers position. Type: str + # Optional: NO + 'RECEIVERS_FILE': 'Example2/DIPOLE1D/Input_model/receivers.dat', + } + +Running PETGEM +############## +To run the simulation, the following command should be run in the top-level +directory of the PETGEM source tree: + +.. code-block:: bash + + $ mpirun -n 48 python3 kernel.py -options_file DIPOLE1D/petsc.opts DIPOLE1D/modelParams.py + +``kernel.py`` solves the problem as follows: + + #. Problem initialization + #. Import files + #. Parallel assembly system + #. Parallel solution system + #. Post-processing of electric responses + +and outputs the solution to the output path +(``DIPOLE1D/Output/``). The output files will be in three formats: +`PETSc `__, MATLAB and ASCII. Therefore, the +``Output`` directory will contain three subdirectories: ``Ascii``, ``MATLAB`` +and ``Petsc``. By default, and for each format, PETGEM save the electric +field responses in different files: + + * ``Ep.xx``: primary electric field components + * ``Es.xx``: secondary electric field components + * ``Et.xx``: total electric field components + +where ``.xx`` is the file extension that depends of the format file. + +Nédélec order comparison +######################## +`Figure 7.10`_ shows a comparison of the x-component of total electric field between p=1 +and p=2 against the quasi-analytical solution obtained with the +`DIPOLE1D `_ tool. The total +electric field in `Figure 7.10`_ was calculated using a +mesh with :math:`\approx2` millions of DOFs for p=1 and a mesh with +:math:`\approx600` thousands of DOFs. + +.. _Figure 7.10: +.. figure:: _static/figures/total_field_X_model2.png + :scale: 40% + :alt: Nédélec order comparison for an in-line canonical off-shore hydrocarbon model with its unstructured tetrahedral mesh for :math:`y>150` + :align: center + + Figure 7.10. Nédélec order comparative within PETGEM V1.0 + + +Example 4: Use of PETSc solvers ******************************* In PETGEM, options for `PETSc `__ solvers/preconditioners diff --git a/doc/source/Publications.rst b/doc/source/Publications.rst old mode 100644 new mode 100755 index 0911455..77f1033 --- a/doc/source/Publications.rst +++ b/doc/source/Publications.rst @@ -4,6 +4,8 @@ Publications ============ Papers: +* Castillo-Reyes, O., de la Puente, Cela, J. M. `PETGEM: A parallel code for 3D CSEM forward modeling using edge finite elements `_. Computers & Geosciences, vol 119: 123-136. ISSN 0098-3004. Elsevier. +* Castillo-Reyes, O., de la Puente, Cela, J. M. `Three-Dimensional CSEM modelling on unstructured tetrahedral meshes using edge finite elements `_. In: Barrios Hernández C., Gitler I., Klapp J. (eds) High Performance Computing. CARLA 2016. Communications in Computer and Information Science, vol 697: 247-256. ISBN 978-3-319-57971-9 Springer, Cham. 2017. * Castillo-Reyes, O., de la Puente, Cela, J. M. `Improving edge finite element assembly for geophysical electromagnetic modelling on shared-memory architectures `_. 7th Annual Ubiquitous Computing, Electronics & Mobile Communication Conference – UEMCON. 2016. * Castillo-Reyes, O., de la Puente, J., Puzyrev, V., and Cela, J. M., `Edge-based parallel framework for the simulation of 3D CSEM surveys `_. ICE Barcelona-AAPG/SEG International Conference & Exhibition. 2016. * Castillo-Reyes, O., de la Puente, J., Barucq, H., Diaz, J., and Cela, J. M., `Parallel and vectorized code for CSEM surveys in geophysics: An edge-based approach `_. ECCOMAS. 2016. @@ -17,6 +19,8 @@ Papers: Conferences: +* Castillo-Reyes, O. HPC geophysical electromagnetic modeling. Fifth International Congress on Multiphysics, Multiscale, and Optimization problems. University of the Basque Country. Bilbao, Spain. May 2018. +* Castillo-Reyes, O. Python performance for HPC geophysical applications. 9th International Supercomputing Conference in Mexico – ISUM 2018. Red Mexicana de Supercómputo. Mérida, Yucatán, Mexico. March 2018. * Castillo-Reyes, O. Overview of numerical techniques and applications for CSEM/MT geophysical surveys. SIAM Conference on Mathematical and Computational Issues in the Geosciences. University Erlangen-Nürnberg. Erlangen, Germany. September 2017. * Castillo-Reyes, O. PETGEM: Parallel Edge-based Tool for Geophysical Electromagnetic Modelling. Congress on Numerical Methods in Engineering. Technical University of Valencia. Valencia, Spain. July 2017. * Castillo-Reyes, O. PETGEM: potential of 3D CSEM modelling using a new HPC tool for exploration geophysics. 10th International Marine Electromagnetics conference – MARELEC. University of Liverpool. Liverpool, United Kingdom. June 2017. @@ -48,6 +52,7 @@ Work on PETGEM has received funding from the European Union's Horizon 2020 resea the Marie Sklodowska-Curie grant agreement No. 644202. The research leading to these results has received funding from the European Union's Horizon 2020 Programme (2014-2020) and from Brazilian Ministry of Science, Technology and Innovation through Rede Nacional de Pesquisa (RNP) under the `HPC4E Project `_ , grant agreement -No. 689772. +No. 689772. Further, this project has received funding from the European Union's Horizon 2020 research and innovation +programme under the Marie Sklodowska-Curie grant agreement No. 777778. Octavio Castillo-Reyes expresses his gratitude to the Mexican National Council for Science and Technology (`CONACyT `_) for his support. diff --git a/doc/source/Tutorial.rst b/doc/source/Tutorial.rst old mode 100644 new mode 100755 index f6fbf15..e851912 --- a/doc/source/Tutorial.rst +++ b/doc/source/Tutorial.rst @@ -97,18 +97,26 @@ A glance of this file is the following: # name and his key names MUST NOT BE changed. All file paths should consider as absolute. preprocessing = { + # ---------- Nedelec element order ---------- + # 1 = First Nédélec order (6 DOFS per element) + # 2 = Second Nédélec order (20 DOFS per element) + # 3 = Third Nédélec order (45 DOFS per element) + # Type: int + # Optional: NO + 'NEDELEC_ORDER': 1, + # ---------- Mesh file ---------- - 'MESH_FILE': 'DIPOLE1D/Input_preprocessing/DIPOLE1D.msh', + 'MESH_FILE': 'PATH_TO_FILE', # ---------- Material conductivities ---------- 'MATERIAL_CONDUCTIVITIES': [1.0, 1./100., 1., 1./.3], - # ---------- Receivers positions file ---------- - 'RECEIVERS_FILE': 'DIPOLE1D/Input_preprocessing/RECEIVER_POSITIONS.txt', + # ---------- Receivers position file ---------- + 'RECEIVERS_FILE': 'PATH_TO_FILE', # ---------- Path for Output ---------- - 'OUT_DIR': 'DIPOLE1D/Input_model/', - } + 'OUT_DIR': 'PATH_TO_FILE', + } A template of this file is included in ``examples/`` of the PETGEM source tree. Additionally, a freely available copy of this file @@ -169,11 +177,26 @@ A glance of ``modelParams.py`` file is the following: # the dictionary name and his key names MUST NOT BE changed. All file paths should consider as absolute. modelling = { - # ----- Pyshical parameters ----- + # ---------- Nedelec element order ---------- + # 1 = First nedelec order (6 DOFS per element) + # 2 = Second nedelec order (20 DOFS per element) + # 3 = Third nedelec order (45 DOFS per element) + # Type: int + # Optional: NO + 'NEDELEC_ORDER': 2, + + # ---------- CUDA SUPPORT ---------- + # 0 = No + # 1 = Yes + # Type: int + # Optional: NO + 'CUDA': 0, + + # ----- Physical parameters ----- # Source # Source frequency. Type: float # Optional: NO - 'FREQ': 2.0, + 'FREQ': 1.0, # Source position(x, y, z). Type: float # Optional: NO 'SRC_POS': [1750.0, 1750.0, -975.0], @@ -191,7 +214,7 @@ A glance of ``modelParams.py`` file is the following: 'SRC_LENGTH': 1.0, # Conductivity model. Type: str # Optional: NO - 'CONDUCTIVITY_MODEL_FILE': 'DIPOLE1D/Input_model/conductivityModel.dat', + 'CONDUCTIVITY_MODEL_FILE': 'PATH_TO_FILE', # Background conductivity. Type: float # Optional: NO 'CONDUCTIVITY_BACKGROUND': 3.33, @@ -200,21 +223,27 @@ A glance of ``modelParams.py`` file is the following: # Mesh files # Nodes spatial coordinates. Type: str # Optional: NO - 'NODES_FILE': 'DIPOLE1D/Input_model/nodes.dat', + 'NODES_FILE': 'PATH_TO_FILE', # Elements-nodes connectivity. Type: str # Optional: NO - 'MESH_CONNECTIVITY_FILE': 'DIPOLE1D/Input_model/meshConnectivity.dat', + 'MESH_CONNECTIVITY_FILE': 'PATH_TO_FILE', + # Elements-faces connectivity. Type: str + # Optional: NO + 'FACES_CONNECTIVITY_FILE': 'PATH_TO_FILE', + # Faces-nodes connectivity. Type: str + # Optional: NO + 'FACES_NODES_FILE': 'PATH_TO_FILE', # Elements-edges connectivity. Type: str # Optional: NO - 'DOFS_CONNECTIVITY_FILE': 'DIPOLE1D/Input_model/dofs.dat', + 'EDGES_CONNECTIVITY_FILE': 'PATH_TO_FILE', # Edges-nodes connectivity. Type: str # Optional: NO - 'DOFS_NODES_FILE': 'DIPOLE1D/Input_model/dofsNodes.dat', + 'EDGES_NODES_FILE': 'PATH_TO_FILE', # Sparsity pattern for matrix allocation (PETSc) - 'NNZ_FILE': 'DIPOLE1D/Input_model/nnz.dat', + 'NNZ_FILE': 'PATH_TO_FILE', # Boundaries. Type: str # Optional: NO - 'BOUNDARIES_FILE': 'DIPOLE1D/Input_model/boundaries.dat', + 'BOUNDARIES_FILE': 'PATH_TO_FILE', # ------------ Solver ----------- # Solver options must be set in @@ -223,7 +252,7 @@ A glance of ``modelParams.py`` file is the following: # ------------ Receivers file ----------- # Name of the file that contains the receivers position. Type: str # Optional: NO - 'RECEIVERS_FILE': 'DIPOLE1D/Input_model/receivers.dat', + 'RECEIVERS_FILE': 'PATH_TO_FILE', } A template of this file is included in ``examples/`` diff --git a/doc/source/WhatIs.rst b/doc/source/WhatIs.rst old mode 100644 new mode 100755 index e72ead1..633bfb1 --- a/doc/source/WhatIs.rst +++ b/doc/source/WhatIs.rst @@ -31,7 +31,7 @@ easiest to scale-up to very large domains or arbitrary shape. It supports distributed-memory paralelism through `petsc4py `__ package. -As a result, PETGEM tool allow users to specify edge-based +As a result, PETGEM tool allow users to specify high-order edge-based variational forms of H(curl) for the simulation of electromagnetic fields in realistic 3D CSEM surveys with accuracy, reliability and efficiency. diff --git a/doc/source/_downloads/Example1.zip b/doc/source/_downloads/Example1.zip new file mode 100644 index 0000000..27f1b8a Binary files /dev/null and b/doc/source/_downloads/Example1.zip differ diff --git a/doc/source/_downloads/Example2.zip b/doc/source/_downloads/Example2.zip new file mode 100644 index 0000000..2191f34 Binary files /dev/null and b/doc/source/_downloads/Example2.zip differ diff --git a/doc/source/_downloads/Example3.zip b/doc/source/_downloads/Example3.zip new file mode 100644 index 0000000..288ba26 Binary files /dev/null and b/doc/source/_downloads/Example3.zip differ diff --git a/doc/source/_downloads/Example4.zip b/doc/source/_downloads/Example4.zip new file mode 100644 index 0000000..070fba0 Binary files /dev/null and b/doc/source/_downloads/Example4.zip differ diff --git a/doc/source/_downloads/GPLv3_T_C.pdf b/doc/source/_downloads/GPLv3_T_C.pdf old mode 100644 new mode 100755 diff --git a/doc/source/_downloads/kernel.py b/doc/source/_downloads/kernel.py old mode 100644 new mode 100755 index 7184b0f..cfde18f --- a/doc/source/_downloads/kernel.py +++ b/doc/source/_downloads/kernel.py @@ -42,10 +42,6 @@ # -- Get rank, size and MASTER of the MPI pool -- MASTER, rank, size = getRankSize() - # ############################################### - # ----- Define geometry and EFEM constants ----- - edgeOrder, nodalOrder, numDimensions = defineEFEMConstants() - # ############################################### # ----------- Print header (Master) ------------- printPetgemHeader(rank) @@ -60,6 +56,10 @@ # ------- Check and read parameters file -------- modelling = readUserParams(input_params, rank) + # ############################################### + # ----- Define geometry and EFEM constants ----- + edgeOrder, nodalOrder, numDimensions = defineEFEMConstants(modelling['NEDELEC_ORDER']) + # ############################################### # ---------------- Read mesh -------------------- printMessage('\nImport files', rank) @@ -72,23 +72,34 @@ nodes = readPetscMatrix(modelling['NODES_FILE'], communicator=None) # elements-nodes connectivity printMessage(' Elements-nodes connectivity', rank) - elemsN = readPetscMatrix(modelling['MESH_CONNECTIVITY_FILE'], communicator=None) + elemsN = readPetscMatrix(modelling['MESH_CONNECTIVITY_FILE'], + communicator=None) + # elements-faces connectivity + printMessage(' Elements-faces connectivity', rank) + elemsF = readPetscMatrix(modelling['FACES_CONNECTIVITY_FILE'], + communicator=None) + # facesN connectivity + printMessage(' Faces-nodes connectivity', rank) + facesN = readPetscMatrix(modelling['FACES_NODES_FILE'], + communicator=None) # elements-edges connectivity printMessage(' Elements-edges connectivity', rank) - elemsE = readPetscMatrix(modelling['DOFS_CONNECTIVITY_FILE'], communicator=None) + elemsE = readPetscMatrix(modelling['EDGES_CONNECTIVITY_FILE'], + communicator=None) # edgesN connectivity printMessage(' Edges-nodes connectivity', rank) - edgesN = readPetscMatrix(modelling['DOFS_NODES_FILE'], communicator=None) - # Boundary-Edges + edgesN = readPetscMatrix(modelling['EDGES_NODES_FILE'], communicator=None) + # Boundaries printMessage(' Boundary-Edges', rank) - bEdges = readPetscVector(modelling['BOUNDARIES_FILE'], communicator=None) + boundaries = readPetscVector(modelling['BOUNDARIES_FILE'], communicator=None) # Sparsity pattern (NNZ) for matrix allocation printMessage(' Vector for matrix allocation', rank) Q = readPetscVector(modelling['NNZ_FILE'], communicator=None) nnz = (Q.getArray().real).astype(PETSc.IntType) # Conductivity model printMessage(' Conductivity model', rank) - elemsSigma = readPetscVector(modelling['CONDUCTIVITY_MODEL_FILE'], communicator=None) + elemsSigma = readPetscVector(modelling['CONDUCTIVITY_MODEL_FILE'], + communicator=None) # Receivers data printMessage(' Receivers data', rank) receivers = readPetscMatrix(modelling['RECEIVERS_FILE'], communicator=None) @@ -97,25 +108,30 @@ # ############################################### # -------------- Mesh information --------------- - [nElems, nEdges, nBoundaries, ndofs] = getMeshInfo(elemsN, edgesN, bEdges, rank) + [nElems, nFaces, nEdges, ndofs, nBoundaries] = getMeshInfo(modelling['NEDELEC_ORDER'], + elemsN, elemsF, facesN, + edgesN, boundaries, + rank) + + # ############################################### # --------- Information of parallel pool -------- printMessage('\nParallel information', rank) printMessage('='*75, rank) [Istart_elemsE, Iend_elemsE, - Istart_bEdges, Iend_bEdges, - Istart_receivers, Iend_receivers] = getRanges(elemsE, bEdges, receivers, - size, rank) + Istart_boundaries, Iend_boundaries, + Istart_receivers, Iend_receivers] = getRanges(elemsE, boundaries, + receivers, size, rank) # ############################################### # ----- Create and setup parallel structures ---- # Left-hand side - A = createParallelMatrix(nEdges, nEdges, nnz, communicator=None) + A = createParallelMatrix(ndofs, ndofs, nnz, communicator=None) # Right-hand side - b = createParallelVector(nEdges, communicator=None) + b = createParallelVector(ndofs, communicator=None) # X vector - x = createParallelVector(nEdges, communicator=None) + x = createParallelVector(ndofs, communicator=None) # ############################################### # -------------- Parallel assembly -------------- @@ -125,7 +141,11 @@ assemblerLog = startLogEvent("Assembler") assemblerLog.begin() # System assembly - [A, b, elapsedTimeAssembly] = parallelAssembler(modelling, A, b, nodes, elemsE, elemsN, elemsSigma, Istart_elemsE, Iend_elemsE, rank) + [A, b, elapsedTimeAssembly] = parallelAssembler(modelling, A, b, nodes, + elemsE, elemsN, elemsF, + elemsSigma, Istart_elemsE, + Iend_elemsE, nEdges, nFaces, + rank) # End log event for assembly task assemblerLog.end() @@ -137,9 +157,9 @@ boundariesLog = startLogEvent("Boundaries") boundariesLog.begin() # Set boundary conditions - [A, b, elapsedTimeBoundaries] = setBoundaryConditions(A, b, bEdges, - Istart_bEdges, - Iend_bEdges, rank) + [A, b, elapsedTimeBoundaries] = setBoundaryConditions(A, b, boundaries, + Istart_boundaries, + Iend_boundaries, rank) # End log event for setting boundary conditions task boundariesLog.end() @@ -162,14 +182,20 @@ # Create and start log event for assembly task postProcessingLog = startLogEvent("Postprocessing") postProcessingLog.begin() - elapsedTimepostProcessing = postProcessingFields(receivers, modelling, x, Iend_receivers, Istart_receivers, edgeOrder, nodalOrder, numDimensions, rank) + elapsedTimepostProcessing = postProcessingFields(receivers, modelling, x, + Iend_receivers, + Istart_receivers, + modelling['NEDELEC_ORDER'], + nodalOrder, + numDimensions, rank) postProcessingLog.end() # ############################################### # -------------- Print elapsed times------------- printMessage('\nElapsed times (seconds)', rank) printMessage('='*75, rank) - printElapsedTimes(elapsedTimeAssembly, elapsedTimeSolver, elapsedTimepostProcessing, iterationNumber, rank) + printElapsedTimes(elapsedTimeAssembly, elapsedTimeSolver, + elapsedTimepostProcessing, iterationNumber, rank) # ############################################### # ----------- Print footer (Master) ------------- diff --git a/doc/source/_downloads/modelParams.py b/doc/source/_downloads/modelParams.py old mode 100644 new mode 100755 index 8627f37..7b08a6c --- a/doc/source/_downloads/modelParams.py +++ b/doc/source/_downloads/modelParams.py @@ -13,7 +13,15 @@ Next, each key is described. ''' modelling = { - # ----- Pyshical parameters ----- + # ---------- Nedelec element order ---------- + # 1 = First Nédélec order (6 DOFS per element) + # 2 = Second Nédélec order (20 DOFS per element) + # 3 = Third Nédélec order (45 DOFS per element) + # Type: int + # Optional: NO + 'NEDELEC_ORDER': 2, + + # ----- Physical parameters ----- # Source # Source frequency. Type: float # Optional: NO @@ -50,10 +58,10 @@ 'MESH_CONNECTIVITY_FILE': 'examples/DIPOLE1D/Input_model/meshConnectivity.dat', # Elements-edges connectivity. Type: str # Optional: NO - 'DOFS_CONNECTIVITY_FILE': 'examples/DIPOLE1D/Input_model/dofs.dat', + 'EDGES_CONNECTIVITY_FILE': 'examples/DIPOLE1D/Input_model/edges.dat', # Edges-nodes connectivity. Type: str # Optional: NO - 'DOFS_NODES_FILE': 'examples/DIPOLE1D/Input_model/dofsNodes.dat', + 'EDGES_NODES_FILE': 'examples/DIPOLE1D/Input_model/edgesNodes.dat', # Sparsity pattern for matrix allocation (PETSc) 'NNZ_FILE': 'examples/DIPOLE1D/Input_model/nnz.dat', # Boundaries. Type: str diff --git a/doc/source/_downloads/petsc.opts b/doc/source/_downloads/petsc.opts old mode 100644 new mode 100755 diff --git a/doc/source/_downloads/preprocessingParams.py b/doc/source/_downloads/preprocessingParams.py old mode 100644 new mode 100755 index f443ee0..feb242e --- a/doc/source/_downloads/preprocessingParams.py +++ b/doc/source/_downloads/preprocessingParams.py @@ -10,16 +10,28 @@ All file paths should consider as absolute. ''' preprocessing = { + # ---------- Nedelec element order ---------- + # 1 = First Nédélec order (6 DOFS per element) + # 2 = Second Nédélec order (20 DOFS per element) + # 3 = Third Nédélec order (45 DOFS per element) + # Type: int + # Optional: NO + 'NEDELEC_ORDER': 3, + # ---------- Mesh file ---------- + # Type: str 'MESH_FILE': 'examples/DIPOLE1D/Input_preprocessing/DIPOLE1D.msh', # ---------- Material conductivities ---------- + # Type: float 'MATERIAL_CONDUCTIVITIES': [1.0, 1./100., 1., 1./.3], # ---------- Receivers position file ---------- + # Type: str 'RECEIVERS_FILE': 'examples/DIPOLE1D/Input_preprocessing/RECEIVER_POSITIONS.txt', # ---------- Path for Output ---------- + # Type: str 'OUT_DIR': 'examples/DIPOLE1D/Input_model/', } diff --git a/doc/source/_downloads/run_preprocessing.py b/doc/source/_downloads/run_preprocessing.py old mode 100644 new mode 100755 index 5db2ef5..b59631f --- a/doc/source/_downloads/run_preprocessing.py +++ b/doc/source/_downloads/run_preprocessing.py @@ -18,7 +18,8 @@ from petgem.preprocessing.preprocessing import readPreprocessingParams from petgem.preprocessing.preprocessing import preprocessNodes from petgem.preprocessing.preprocessing import preprocessingNodalConnectivity -from petgem.preprocessing.preprocessing import preprocessingDOF +from petgem.preprocessing.preprocessing import preprocessingEdges +from petgem.preprocessing.preprocessing import preprocessingFaces from petgem.preprocessing.preprocessing import preprocessingConductivityModel from petgem.preprocessing.preprocessing import preprocessingDataReceivers from petgem.preprocessing.preprocessing import preprocessingNNZ @@ -49,31 +50,43 @@ # ############################################### # ----------- Data preprocessing --------- # Nodal coordinates preprocessing (nodes) -nNodes = preprocessNodes(preprocessing['MESH_FILE'], preprocessing['OUT_DIR'], rank) +nNodes = preprocessNodes(preprocessing['MESH_FILE'], + preprocessing['OUT_DIR'], rank) # Nodal connectivity preprocessing (elemsN) -nElems = preprocessingNodalConnectivity(preprocessing['MESH_FILE'], preprocessing['OUT_DIR'], rank) +nElems = preprocessingNodalConnectivity(preprocessing['MESH_FILE'], + preprocessing['OUT_DIR'], rank) + +# Edges connectivity and edge boundaries preprocessing (edges) +nEdges, nDofs = preprocessingEdges(preprocessing['NEDELEC_ORDER'], + preprocessing['MESH_FILE'], + preprocessing['OUT_DIR'], rank) -# Degrees of freedom preprocessing (dofs) -nDofs = preprocessingDOF(preprocessing['MESH_FILE'], preprocessing['OUT_DIR'], rank) +# Faces connectivity and faces boundaries preprocessing (faces) +nFaces = preprocessingFaces(preprocessing['NEDELEC_ORDER'], + preprocessing['MESH_FILE'], + preprocessing['OUT_DIR'], rank) # Conductivity model preprocessingConductivityModel(preprocessing['MESH_FILE'], preprocessing['MATERIAL_CONDUCTIVITIES'], preprocessing['OUT_DIR'], rank) # Receiver positions -nReceivers = preprocessingDataReceivers(preprocessing['MESH_FILE'], +nReceivers = preprocessingDataReceivers(preprocessing['NEDELEC_ORDER'], + preprocessing['MESH_FILE'], preprocessing['RECEIVERS_FILE'], preprocessing['OUT_DIR'], rank) # Sparsity pattern for parallel matrix allocation -preprocessingNNZ(preprocessing['MESH_FILE'], preprocessing['OUT_DIR'], rank) +preprocessingNNZ(preprocessing['NEDELEC_ORDER'], + preprocessing['MESH_FILE'], + preprocessing['OUT_DIR'], rank) # ############################################### # ------------------ Summary -------------------- printMessage('\nSummary', rank) printMessage('='*75, rank) -printPreprocessingSummary(nNodes, nElems, nDofs, nReceivers, rank) +printPreprocessingSummary(nElems, nNodes, nFaces, nDofs, nReceivers, rank) # ############################################### # ----------- Print footer (Master) ------------- diff --git a/doc/source/_static/Documentation.rst~ b/doc/source/_static/Documentation.rst~ old mode 100644 new mode 100755 diff --git a/doc/source/_static/Features.rst~ b/doc/source/_static/Features.rst~ old mode 100644 new mode 100755 diff --git a/doc/source/_static/Tutorial.rst~ b/doc/source/_static/Tutorial.rst~ old mode 100644 new mode 100755 diff --git a/doc/source/_static/figures/PETGEM_assembly.png b/doc/source/_static/figures/PETGEM_assembly.png old mode 100644 new mode 100755 diff --git a/doc/source/_static/figures/PETGEM_parallel_scheme.png b/doc/source/_static/figures/PETGEM_parallel_scheme.png old mode 100644 new mode 100755 diff --git a/doc/source/_static/figures/class_diagram.png b/doc/source/_static/figures/class_diagram.png old mode 100644 new mode 100755 diff --git a/doc/source/_static/figures/component_diagram.png b/doc/source/_static/figures/component_diagram.png old mode 100644 new mode 100755 diff --git a/doc/source/_static/figures/edgeElement.jpg b/doc/source/_static/figures/edgeElement.jpg old mode 100644 new mode 100755 index ee60462..0e7846c Binary files a/doc/source/_static/figures/edgeElement.jpg and b/doc/source/_static/figures/edgeElement.jpg differ diff --git a/doc/source/_static/figures/logo_bsc.jpeg b/doc/source/_static/figures/logo_bsc.jpeg old mode 100644 new mode 100755 diff --git a/doc/source/_static/figures/model1.png b/doc/source/_static/figures/model1.png old mode 100644 new mode 100755 diff --git a/doc/source/_static/figures/model_preprocessing.png b/doc/source/_static/figures/model_preprocessing.png old mode 100644 new mode 100755 diff --git a/doc/source/_static/figures/output_preprocessing.png b/doc/source/_static/figures/output_preprocessing.png index 59f9975..79276e2 100644 Binary files a/doc/source/_static/figures/output_preprocessing.png and b/doc/source/_static/figures/output_preprocessing.png differ diff --git a/doc/source/_static/figures/petgem_logo.png b/doc/source/_static/figures/petgem_logo.png old mode 100644 new mode 100755 diff --git a/doc/source/_static/figures/sequence_diagram.png b/doc/source/_static/figures/sequence_diagram.png old mode 100644 new mode 100755 diff --git a/doc/source/_static/figures/softwareStack.jpg b/doc/source/_static/figures/softwareStack.jpg old mode 100644 new mode 100755 diff --git a/doc/source/_static/figures/totalFieldVTK.png b/doc/source/_static/figures/totalFieldVTK.png old mode 100644 new mode 100755 diff --git a/doc/source/_static/figures/total_field_X_model1.png b/doc/source/_static/figures/total_field_X_model1.png old mode 100644 new mode 100755 diff --git a/doc/source/_static/figures/total_field_X_model2.png b/doc/source/_static/figures/total_field_X_model2.png new file mode 100644 index 0000000..352b24b Binary files /dev/null and b/doc/source/_static/figures/total_field_X_model2.png differ diff --git a/doc/source/_static/petgem.css b/doc/source/_static/petgem.css old mode 100644 new mode 100755 diff --git a/doc/source/_static/petgem.css~ b/doc/source/_static/petgem.css~ old mode 100644 new mode 100755 diff --git a/doc/source/_static/setup.cfg b/doc/source/_static/setup.cfg old mode 100644 new mode 100755 diff --git a/doc/source/_templates/layout.html b/doc/source/_templates/layout.html old mode 100644 new mode 100755 diff --git a/doc/source/conf.py b/doc/source/conf.py old mode 100644 new mode 100755 diff --git a/doc/source/examples/modelParams.rst b/doc/source/examples/modelParams.rst old mode 100644 new mode 100755 diff --git a/doc/source/examples/preprocessingParams.rst b/doc/source/examples/preprocessingParams.rst old mode 100644 new mode 100755 diff --git a/doc/source/index.rst b/doc/source/index.rst old mode 100644 new mode 100755 diff --git a/doc/source/petgem/base/basis_scripts.rst b/doc/source/petgem/base/basis_scripts.rst old mode 100644 new mode 100755 diff --git a/doc/source/petgem/base/modelling.rst b/doc/source/petgem/base/modelling.rst old mode 100644 new mode 100755 diff --git a/doc/source/petgem/base/modelling.rst~ b/doc/source/petgem/base/modelling.rst~ old mode 100644 new mode 100755 diff --git a/doc/source/petgem/base/styles.rst b/doc/source/petgem/base/styles.rst old mode 100644 new mode 100755 diff --git a/doc/source/petgem/base/styles.rst~ b/doc/source/petgem/base/styles.rst~ old mode 100644 new mode 100755 diff --git a/doc/source/petgem/efem/efem.rst b/doc/source/petgem/efem/efem.rst old mode 100644 new mode 100755 diff --git a/doc/source/petgem/efem/efem.rst~ b/doc/source/petgem/efem/efem.rst~ old mode 100644 new mode 100755 diff --git a/doc/source/petgem/efem/fem.rst b/doc/source/petgem/efem/fem.rst old mode 100644 new mode 100755 diff --git a/doc/source/petgem/efem/fem.rst~ b/doc/source/petgem/efem/fem.rst~ old mode 100644 new mode 100755 diff --git a/doc/source/petgem/efem/general.rst~ b/doc/source/petgem/efem/general.rst~ old mode 100644 new mode 100755 diff --git a/doc/source/petgem/efem/mesh.rst~ b/doc/source/petgem/efem/mesh.rst~ old mode 100644 new mode 100755 diff --git a/doc/source/petgem/efem/nedelec_elements.rst~ b/doc/source/petgem/efem/nedelec_elements.rst~ old mode 100644 new mode 100755 diff --git a/doc/source/petgem/efem/vectorMatrixFunctions.rst b/doc/source/petgem/efem/vectorMatrixFunctions.rst old mode 100644 new mode 100755 diff --git a/doc/source/petgem/kernel/kernel.rst b/doc/source/petgem/kernel/kernel.rst old mode 100644 new mode 100755 diff --git a/doc/source/petgem/kernel/kernel.rst~ b/doc/source/petgem/kernel/kernel.rst~ old mode 100644 new mode 100755 diff --git a/doc/source/petgem/mesh/mesh.rst b/doc/source/petgem/mesh/mesh.rst old mode 100644 new mode 100755 diff --git a/doc/source/petgem/mesh/mesh.rst~ b/doc/source/petgem/mesh/mesh.rst~ old mode 100644 new mode 100755 diff --git a/doc/source/petgem/monitoring/monitoring.rst b/doc/source/petgem/monitoring/monitoring.rst old mode 100644 new mode 100755 diff --git a/doc/source/petgem/monitoring/monitoring.rst~ b/doc/source/petgem/monitoring/monitoring.rst~ old mode 100644 new mode 100755 diff --git a/doc/source/petgem/parallel/mesh.rst~ b/doc/source/petgem/parallel/mesh.rst~ old mode 100644 new mode 100755 diff --git a/doc/source/petgem/parallel/parallel.rst b/doc/source/petgem/parallel/parallel.rst old mode 100644 new mode 100755 diff --git a/doc/source/petgem/parallel/parallel.rst~ b/doc/source/petgem/parallel/parallel.rst~ old mode 100644 new mode 100755 diff --git a/doc/source/petgem/postprocessing/assembler.rst~ b/doc/source/petgem/postprocessing/assembler.rst~ old mode 100644 new mode 100755 diff --git a/doc/source/petgem/postprocessing/postprocessing.rst b/doc/source/petgem/postprocessing/postprocessing.rst old mode 100644 new mode 100755 diff --git a/doc/source/petgem/postprocessing/postprocessing.rst~ b/doc/source/petgem/postprocessing/postprocessing.rst~ old mode 100644 new mode 100755 diff --git a/doc/source/petgem/postprocessing/solver.rst~ b/doc/source/petgem/postprocessing/solver.rst~ old mode 100644 new mode 100755 diff --git a/doc/source/petgem/preprocessing/assembler.rst~ b/doc/source/petgem/preprocessing/assembler.rst~ old mode 100644 new mode 100755 diff --git a/doc/source/petgem/preprocessing/postprocessing.rst~ b/doc/source/petgem/preprocessing/postprocessing.rst~ old mode 100644 new mode 100755 diff --git a/doc/source/petgem/preprocessing/preprocessing.rst b/doc/source/petgem/preprocessing/preprocessing.rst old mode 100644 new mode 100755 diff --git a/doc/source/petgem/preprocessing/solver.rst~ b/doc/source/petgem/preprocessing/solver.rst~ old mode 100644 new mode 100755 diff --git a/doc/source/petgem/solver/assembler.rst b/doc/source/petgem/solver/assembler.rst old mode 100644 new mode 100755 diff --git a/doc/source/petgem/solver/assembler.rst~ b/doc/source/petgem/solver/assembler.rst~ old mode 100644 new mode 100755 diff --git a/doc/source/petgem/solver/solver.rst b/doc/source/petgem/solver/solver.rst old mode 100644 new mode 100755 diff --git a/doc/source/petgem/solver/solver.rst~ b/doc/source/petgem/solver/solver.rst~ old mode 100644 new mode 100755 diff --git a/doc/source/petgem_theme/theme.conf b/doc/source/petgem_theme/theme.conf old mode 100644 new mode 100755 diff --git a/doc/source/petgem_theme/theme.conf~ b/doc/source/petgem_theme/theme.conf~ old mode 100644 new mode 100755 diff --git a/doc/source/setup/config.rst~ b/doc/source/setup/config.rst~ old mode 100644 new mode 100755 diff --git a/doc/source/setup/init.rst~ b/doc/source/setup/init.rst~ old mode 100644 new mode 100755 diff --git a/doc/source/setup/setup-base.rst~ b/doc/source/setup/setup-base.rst~ old mode 100644 new mode 100755 diff --git a/doc/source/setup/setup-decomposition.rst~ b/doc/source/setup/setup-decomposition.rst~ old mode 100644 new mode 100755 diff --git a/doc/source/setup/setup-efem.rst~ b/doc/source/setup/setup-efem.rst~ old mode 100644 new mode 100755 diff --git a/doc/source/setup/setup-mesh.rst~ b/doc/source/setup/setup-mesh.rst~ old mode 100644 new mode 100755 diff --git a/doc/source/setup/setup-monitor.rst~ b/doc/source/setup/setup-monitor.rst~ old mode 100644 new mode 100755 diff --git a/doc/source/setup/setup-monitoring.rst~ b/doc/source/setup/setup-monitoring.rst~ old mode 100644 new mode 100755 diff --git a/doc/source/setup/setup-parallel.rst~ b/doc/source/setup/setup-parallel.rst~ old mode 100644 new mode 100755 diff --git a/doc/source/setup/setup-petgem.rst~ b/doc/source/setup/setup-petgem.rst~ old mode 100644 new mode 100755 diff --git a/doc/source/setup/setup-postprocessing.rst~ b/doc/source/setup/setup-postprocessing.rst~ old mode 100644 new mode 100755 diff --git a/doc/source/setup/setup-solver.rst~ b/doc/source/setup/setup-solver.rst~ old mode 100644 new mode 100755 diff --git a/doc/source/setup/setup.rst b/doc/source/setup/setup.rst old mode 100644 new mode 100755 diff --git a/doc/source/setup/setup.rst~ b/doc/source/setup/setup.rst~ old mode 100644 new mode 100755 diff --git a/doc/source/setup/version.rst~ b/doc/source/setup/version.rst~ old mode 100644 new mode 100755 diff --git a/examples/Example1.zip b/examples/Example1.zip index 5788c9c..27f1b8a 100644 Binary files a/examples/Example1.zip and b/examples/Example1.zip differ diff --git a/examples/Example2.zip b/examples/Example2.zip index cc0efd9..2191f34 100644 Binary files a/examples/Example2.zip and b/examples/Example2.zip differ diff --git a/examples/Example3.zip b/examples/Example3.zip index bae1369..288ba26 100644 Binary files a/examples/Example3.zip and b/examples/Example3.zip differ diff --git a/examples/Example4.zip b/examples/Example4.zip new file mode 100644 index 0000000..070fba0 Binary files /dev/null and b/examples/Example4.zip differ diff --git a/examples/modelParams.py b/examples/modelParams.py index 8627f37..711ccf4 100644 --- a/examples/modelParams.py +++ b/examples/modelParams.py @@ -13,11 +13,26 @@ Next, each key is described. ''' modelling = { - # ----- Pyshical parameters ----- + # ---------- Nedelec element order ---------- + # 1 = First nedelec order (6 DOFS per element) + # 2 = Second nedelec order (20 DOFS per element) + # 3 = Third nedelec order (45 DOFS per element) + # Type: int + # Optional: NO + 'NEDELEC_ORDER': 2, + + # ---------- CUDA SUPPORT ---------- + # 0 = No + # 1 = Yes + # Type: int + # Optional: NO + 'CUDA': 0, + + # ----- Physical parameters ----- # Source # Source frequency. Type: float # Optional: NO - 'FREQ': 2.0, + 'FREQ': 1.0, # Source position(x, y, z). Type: float # Optional: NO 'SRC_POS': [1750.0, 1750.0, -975.0], @@ -35,7 +50,7 @@ 'SRC_LENGTH': 1.0, # Conductivity model. Type: str # Optional: NO - 'CONDUCTIVITY_MODEL_FILE': 'examples/DIPOLE1D/Input_model/conductivityModel.dat', + 'CONDUCTIVITY_MODEL_FILE': 'PATH_TO_FILE', # Background conductivity. Type: float # Optional: NO 'CONDUCTIVITY_BACKGROUND': 3.33, @@ -44,21 +59,27 @@ # Mesh files # Nodes spatial coordinates. Type: str # Optional: NO - 'NODES_FILE': 'examples/DIPOLE1D/Input_model/nodes.dat', + 'NODES_FILE': 'PATH_TO_FILE', # Elements-nodes connectivity. Type: str # Optional: NO - 'MESH_CONNECTIVITY_FILE': 'examples/DIPOLE1D/Input_model/meshConnectivity.dat', + 'MESH_CONNECTIVITY_FILE': 'PATH_TO_FILE', + # Elements-faces connectivity. Type: str + # Optional: NO + 'FACES_CONNECTIVITY_FILE': 'PATH_TO_FILE', + # Faces-nodes connectivity. Type: str + # Optional: NO + 'FACES_NODES_FILE': 'PATH_TO_FILE', # Elements-edges connectivity. Type: str # Optional: NO - 'DOFS_CONNECTIVITY_FILE': 'examples/DIPOLE1D/Input_model/dofs.dat', + 'EDGES_CONNECTIVITY_FILE': 'PATH_TO_FILE', # Edges-nodes connectivity. Type: str # Optional: NO - 'DOFS_NODES_FILE': 'examples/DIPOLE1D/Input_model/dofsNodes.dat', + 'EDGES_NODES_FILE': 'PATH_TO_FILE', # Sparsity pattern for matrix allocation (PETSc) - 'NNZ_FILE': 'examples/DIPOLE1D/Input_model/nnz.dat', + 'NNZ_FILE': 'PATH_TO_FILE', # Boundaries. Type: str # Optional: NO - 'BOUNDARIES_FILE': 'examples/DIPOLE1D/Input_model/boundaries.dat', + 'BOUNDARIES_FILE': 'PATH_TO_FILE', # ------------ Solver ----------- # Solver options must be set in @@ -67,5 +88,5 @@ # ------------ Receivers file ----------- # Name of the file that contains the receivers position. Type: str # Optional: NO - 'RECEIVERS_FILE': 'examples/DIPOLE1D/Input_model/receivers.dat', + 'RECEIVERS_FILE': 'PATH_TO_FILE', } diff --git a/examples/petsc.opts b/examples/petsc.opts old mode 100644 new mode 100755 diff --git a/examples/preprocessingParams.py b/examples/preprocessingParams.py index f443ee0..c7b461f 100644 --- a/examples/preprocessingParams.py +++ b/examples/preprocessingParams.py @@ -10,16 +10,24 @@ All file paths should consider as absolute. ''' preprocessing = { + # ---------- Nedelec element order ---------- + # 1 = First Nédélec order (6 DOFS per element) + # 2 = Second Nédélec order (20 DOFS per element) + # 3 = Third Nédélec order (45 DOFS per element) + # Type: int + # Optional: NO + 'NEDELEC_ORDER': 1, + # ---------- Mesh file ---------- - 'MESH_FILE': 'examples/DIPOLE1D/Input_preprocessing/DIPOLE1D.msh', + 'MESH_FILE': 'PATH_TO_FILE', # ---------- Material conductivities ---------- 'MATERIAL_CONDUCTIVITIES': [1.0, 1./100., 1., 1./.3], # ---------- Receivers position file ---------- - 'RECEIVERS_FILE': 'examples/DIPOLE1D/Input_preprocessing/RECEIVER_POSITIONS.txt', + 'RECEIVERS_FILE': 'PATH_TO_FILE', # ---------- Path for Output ---------- - 'OUT_DIR': 'examples/DIPOLE1D/Input_model/', + 'OUT_DIR': 'PATH_TO_FILE', } diff --git a/kernel.py b/kernel.py old mode 100644 new mode 100755 index 571648e..4fc3110 --- a/kernel.py +++ b/kernel.py @@ -42,10 +42,6 @@ # -- Get rank, size and MASTER of the MPI pool -- MASTER, rank, size = getRankSize() - # ############################################### - # ----- Define geometry and EFEM constants ----- - edgeOrder, nodalOrder, numDimensions = defineEFEMConstants() - # ############################################### # ----------- Print header (Master) ------------- printPetgemHeader(rank) @@ -60,6 +56,11 @@ # ------- Check and read parameters file -------- modelling = readUserParams(input_params, rank) + # ############################################### + # ----- Define geometry and EFEM constants ----- + [edgeOrder, nodalOrder, + numDimensions] = defineEFEMConstants(modelling['NEDELEC_ORDER']) + # ############################################### # ---------------- Read mesh -------------------- printMessage('\nImport files', rank) @@ -74,16 +75,25 @@ printMessage(' Elements-nodes connectivity', rank) elemsN = readPetscMatrix(modelling['MESH_CONNECTIVITY_FILE'], communicator=None) + # elements-faces connectivity + printMessage(' Elements-faces connectivity', rank) + elemsF = readPetscMatrix(modelling['FACES_CONNECTIVITY_FILE'], + communicator=None) + # facesN connectivity + printMessage(' Faces-nodes connectivity', rank) + facesN = readPetscMatrix(modelling['FACES_NODES_FILE'], + communicator=None) # elements-edges connectivity printMessage(' Elements-edges connectivity', rank) - elemsE = readPetscMatrix(modelling['DOFS_CONNECTIVITY_FILE'], + elemsE = readPetscMatrix(modelling['EDGES_CONNECTIVITY_FILE'], communicator=None) # edgesN connectivity printMessage(' Edges-nodes connectivity', rank) - edgesN = readPetscMatrix(modelling['DOFS_NODES_FILE'], communicator=None) - # Boundary-Edges + edgesN = readPetscMatrix(modelling['EDGES_NODES_FILE'], communicator=None) + # Boundaries printMessage(' Boundary-Edges', rank) - bEdges = readPetscVector(modelling['BOUNDARIES_FILE'], communicator=None) + boundaries = readPetscVector(modelling['BOUNDARIES_FILE'], + communicator=None) # Sparsity pattern (NNZ) for matrix allocation printMessage(' Vector for matrix allocation', rank) Q = readPetscVector(modelling['NNZ_FILE'], communicator=None) @@ -100,26 +110,28 @@ # ############################################### # -------------- Mesh information --------------- - [nElems, nEdges, nBoundaries, ndofs] = getMeshInfo(elemsN, edgesN, - bEdges, rank) + [nElems, nFaces, nEdges, ndofs, + nBoundaries] = getMeshInfo(modelling['NEDELEC_ORDER'], elemsN, elemsF, + facesN, edgesN, boundaries, rank) # ############################################### # --------- Information of parallel pool -------- printMessage('\nParallel information', rank) printMessage('='*75, rank) [Istart_elemsE, Iend_elemsE, - Istart_bEdges, Iend_bEdges, - Istart_receivers, Iend_receivers] = getRanges(elemsE, bEdges, receivers, - size, rank) + Istart_boundaries, Iend_boundaries, + Istart_receivers, Iend_receivers] = getRanges(elemsE, boundaries, + receivers, size, rank) # ############################################### # ----- Create and setup parallel structures ---- # Left-hand side - A = createParallelMatrix(nEdges, nEdges, nnz, communicator=None) + A = createParallelMatrix(ndofs, ndofs, nnz, modelling['CUDA'], + communicator=None) # Right-hand side - b = createParallelVector(nEdges, communicator=None) + b = createParallelVector(ndofs, modelling['CUDA'], communicator=None) # X vector - x = createParallelVector(nEdges, communicator=None) + x = createParallelVector(ndofs, modelling['CUDA'], communicator=None) # ############################################### # -------------- Parallel assembly -------------- @@ -130,9 +142,10 @@ assemblerLog.begin() # System assembly [A, b, elapsedTimeAssembly] = parallelAssembler(modelling, A, b, nodes, - elemsE, elemsN, elemsSigma, + elemsE, elemsN, elemsF, + facesN, elemsSigma, Istart_elemsE, Iend_elemsE, - rank) + nEdges, nFaces, rank) # End log event for assembly task assemblerLog.end() @@ -144,9 +157,10 @@ boundariesLog = startLogEvent("Boundaries") boundariesLog.begin() # Set boundary conditions - [A, b, elapsedTimeBoundaries] = setBoundaryConditions(A, b, bEdges, - Istart_bEdges, - Iend_bEdges, rank) + [A, b, elapsedTimeBoundaries] = setBoundaryConditions(A, b, boundaries, + Istart_boundaries, + Iend_boundaries, + rank) # End log event for setting boundary conditions task boundariesLog.end() @@ -166,14 +180,16 @@ # --------------- Post-processing --------------- printMessage('\nPost-processing', rank) printMessage('='*75, rank) - # Create and start log event for assembly task + # # Create and start log event for assembly task postProcessingLog = startLogEvent("Postprocessing") postProcessingLog.begin() - elapsedTimepostProcessing = postProcessingFields(receivers, modelling, x, - Iend_receivers, - Istart_receivers, - edgeOrder, nodalOrder, - numDimensions, rank) + elapsedTimepostProcess = postProcessingFields(receivers, modelling, x, + Iend_receivers, + Istart_receivers, + modelling['NEDELEC_ORDER'], + modelling['CUDA'], + nodalOrder, + numDimensions, rank) postProcessingLog.end() # ############################################### @@ -181,7 +197,7 @@ printMessage('\nElapsed times (seconds)', rank) printMessage('='*75, rank) printElapsedTimes(elapsedTimeAssembly, elapsedTimeSolver, - elapsedTimepostProcessing, iterationNumber, rank) + elapsedTimepostProcess, iterationNumber, rank) # ############################################### # ----------- Print footer (Master) ------------- diff --git a/petgem.egg-info/PKG-INFO b/petgem.egg-info/PKG-INFO old mode 100644 new mode 100755 index d0ae852..38ef9ff --- a/petgem.egg-info/PKG-INFO +++ b/petgem.egg-info/PKG-INFO @@ -1,19 +1,18 @@ -Metadata-Version: 1.1 +Metadata-Version: 1.2 Name: petgem -Version: 0.30.48 +Version: 0.5 Summary: Parallel python code for electromagnetic modeling in geophysics Home-page: https://www.bsc.es/castillo-octavio -Author: Octavio Castillo Reyes -Author-email: octavio.castillo@bsc.es +Maintainer: Octavio Castillo Reyes +Maintainer-email: octavio.castillo@bsc.es License: GPLv3.0 Download-URL: http://petgem.bsc.es -Description-Content-Type: UNKNOWN Description: petgem ====== - petgem is a parallel python code for 3D Controlled-Source - Electromagnetic Method (3D CSEM) in geophysics using edge finite - elements (Nedelec finite elements). + PETGEM is a parallel python code for 3D Controlled-Source + Electromagnetic Method (3D CSEM) in geophysics using high-order edge finite + elements (Nédélec finite elements). Requirements @@ -39,15 +38,15 @@ Description: petgem * Following commands may require root privileges - * Download `PETSc `__ (PETSc 3.7 and 3.8 have been tested) + * Download `PETSc `__ (PETSc 3.7, 3.8, and 3.9 have been tested) - * Uncompress the PETSc archive (in this example, using PETSc 3.8.3):: + * Uncompress the PETSc archive (in this example, using PETSc 3.9.3):: - $ tar zxvf petsc-3.8.3.tar.gz + $ tar zxvf petsc-3.9.3.tar.gz * Configure and build PETSc. The configuration options depend on the calculations you want to perform (complex- or real-valued) as well as your compiler/MPI/Blas/Lapack setup. For PETGEM executions, **PETSC MUST BE BUILD FOR COMPLEX-VALUED NUMBERS**. In order to avoid incompatibilities between PETSC, petsc4py and PETGEM, we highly recommend the following configuration lines. Please, visit PETSc website for advanced configuration options. If you have a clean environment (not working MPI/Blas/Lapack), then run:: - $ cd petsc-3.8.3 + $ cd petsc-3.9.3 $ export PETSC_DIR=$PWD $ export PETSC_ARCH=arch-linux2-c-debug @@ -59,6 +58,12 @@ Description: petgem $ --download-mumps --download-scalapack --download-parmetis --download-metis --download-ptscotch --download-cmake + * Further, to activate GPUs support, please add following options to previous configure line:: + + $ --with-cuda=1 --with_cuda_dir=PATH + + where ``PATH`` is the directory of your CUDA libraries. + * Then, build and install PETSc:: $ make $PETSC_DIR $PETSC_ARCH all @@ -81,14 +86,19 @@ Description: petgem Citations --------- - If petgem been significant to a project that leads to an academic + If PETGEM been significant to a project that leads to an academic publication, please acknowledge that fact by citing the project: - * Castillo-Reyes, O., de la Puente, J., Cela, J.M. (2017). - *Three-Dimensional CSEM Modelling on Unstructured Tetrahedral Meshes - Using Edge Finite Elements*, Communications in Computer and - Information Science, vol 697: 247-256. ISBN 978-3-319-57971-9 - Springer, Cham. https://doi.org/10.1007/978-3-319-57972-6_18 + * Castillo-Reyes, O., de la Puente, J., Cela, J. M. (2018). + *PETGEM: A parallel code for 3D CSEM forward modeling using edge finite + elements*. Computers & Geosciences, vol 119: 123-136. ISSN 0098-3004, + Elsevier. https://doi.org/10.1016/j.cageo.2018.07.005 + + * Castillo-Reyes, O., de la Puente, J., Cela, J.M. (2017). + *Three-Dimensional CSEM Modelling on Unstructured Tetrahedral Meshes + Using Edge Finite Elements*, Communications in Computer and + Information Science, vol 697: 247-256. ISBN 978-3-319-57971-9 + Springer, Cham. https://doi.org/10.1007/978-3-319-57972-6_18 Keywords: 3D CSEM, edge finite elements, HPC, petsc, petsc4py. Platform: Linux diff --git a/petgem.egg-info/SOURCES.txt b/petgem.egg-info/SOURCES.txt old mode 100644 new mode 100755 index 8f79187..95fdd66 --- a/petgem.egg-info/SOURCES.txt +++ b/petgem.egg-info/SOURCES.txt @@ -7,243 +7,6 @@ run_preprocessing.py setup.cfg setup.py doc/Makefile -doc/build/doctrees/CSEMEdges.doctree -doc/build/doctrees/Contact.doctree -doc/build/doctrees/Download.doctree -doc/build/doctrees/Features.doctree -doc/build/doctrees/Installation.doctree -doc/build/doctrees/Manual.doctree -doc/build/doctrees/Publications.doctree -doc/build/doctrees/Tutorial.doctree -doc/build/doctrees/WhatIs.doctree -doc/build/doctrees/environment.pickle -doc/build/doctrees/index.doctree -doc/build/doctrees/examples/modelParams.doctree -doc/build/doctrees/examples/preprocessingParams.doctree -doc/build/doctrees/petgem/base/basis_scripts.doctree -doc/build/doctrees/petgem/base/modelling.doctree -doc/build/doctrees/petgem/base/styles.doctree -doc/build/doctrees/petgem/efem/efem.doctree -doc/build/doctrees/petgem/efem/fem.doctree -doc/build/doctrees/petgem/efem/vectorMatrixFunctions.doctree -doc/build/doctrees/petgem/kernel/kernel.doctree -doc/build/doctrees/petgem/mesh/mesh.doctree -doc/build/doctrees/petgem/monitoring/monitoring.doctree -doc/build/doctrees/petgem/parallel/parallel.doctree -doc/build/doctrees/petgem/postprocessing/postprocessing.doctree -doc/build/doctrees/petgem/preprocessing/preprocessing.doctree -doc/build/doctrees/petgem/solver/assembler.doctree -doc/build/doctrees/petgem/solver/solver.doctree -doc/build/doctrees/setup/setup.doctree -doc/build/html/.buildinfo -doc/build/html/CSEMEdges.html -doc/build/html/Contact.html -doc/build/html/Download.html -doc/build/html/Features.html -doc/build/html/Installation.html -doc/build/html/Manual.html -doc/build/html/Publications.html -doc/build/html/Tutorial.html -doc/build/html/WhatIs.html -doc/build/html/genindex.html -doc/build/html/index.html -doc/build/html/objects.inv -doc/build/html/py-modindex.html -doc/build/html/search.html -doc/build/html/searchindex.js -doc/build/html/_downloads/GPLv3_T_C.pdf -doc/build/html/_downloads/kernel.py -doc/build/html/_downloads/modelParams.py -doc/build/html/_downloads/petsc.opts -doc/build/html/_downloads/preprocessingParams.py -doc/build/html/_downloads/run_preprocessing.py -doc/build/html/_images/PETGEM_parallel_scheme.png -doc/build/html/_images/class_diagram.png -doc/build/html/_images/component_diagram.png -doc/build/html/_images/edgeElement.jpg -doc/build/html/_images/model1.png -doc/build/html/_images/model_preprocessing.png -doc/build/html/_images/output_preprocessing.png -doc/build/html/_images/sequence_diagram.png -doc/build/html/_images/softwareStack.jpg -doc/build/html/_images/totalFieldVTK.png -doc/build/html/_images/total_field_X_model1.png -doc/build/html/_images/math/011e5790a6c33043ceadca81d9657dde6c61d769.png -doc/build/html/_images/math/04615352c253577db465254c8472a56a22f929e3.png -doc/build/html/_images/math/0637d483fd4572928af77d351880fa5d99de0639.png -doc/build/html/_images/math/0f61209c74e91af59effb0034a18c9e20842b9ad.png -doc/build/html/_images/math/108b261d0fb9e1fc5ef05bca89d81f9536276306.png -doc/build/html/_images/math/128cb7613c50e091d5ed0c4c631f94c14b2208ea.png -doc/build/html/_images/math/12de4daabfac3d325423a62d25e1c644d7aff1c5.png -doc/build/html/_images/math/18fbb1ca63534927f6185e7da753a45d89e8a6bb.png -doc/build/html/_images/math/1ab98880e07cc102c8b27c778be235fa35447d60.png -doc/build/html/_images/math/1b1751f7c08678ec02839c883370e1f485bf1cfe.png -doc/build/html/_images/math/1b23b00cdc487a2b00a9d479e09947c43e8bf66b.png -doc/build/html/_images/math/26a8dcae237b2cc1c4af9341b5ad47b43501f34f.png -doc/build/html/_images/math/27d463da4622be5b3ef1d4176ced7d7a323c6425.png -doc/build/html/_images/math/302f31b1a918dd143d53dab4649346d85a8fa792.png -doc/build/html/_images/math/382bac1ca802500dfe8557b2cc59091ce27a7357.png -doc/build/html/_images/math/43e5f6f8941e97dad900f219e8296c1ead98fae7.png -doc/build/html/_images/math/4494e9b5e147f29c6cd47e00ad0805d3c13c0d99.png -doc/build/html/_images/math/54a34fd8abde26fa0013111971eccd6158094e8b.png -doc/build/html/_images/math/5ecfb5efd13556512083272088a2baa0839e3c66.png -doc/build/html/_images/math/63751cb2e98ba393b0f22e45ca127c3cebb61487.png -doc/build/html/_images/math/66c954fa19dbde81a9ef695623119ca386b322a8.png -doc/build/html/_images/math/78a06cf7a34f39fde71fbba0d5539baae7c72d4e.png -doc/build/html/_images/math/7e2fc44f1822d652ad5eec7fb3a4091404b2bae9.png -doc/build/html/_images/math/7f940eafc240052d58ba29449f1b42f362d728a5.png -doc/build/html/_images/math/85bab93280badb33fa5af4e02512594786b8b96d.png -doc/build/html/_images/math/8b5bfe447077684d8941d5813a7eac322b809de9.png -doc/build/html/_images/math/8c77da2488bbc1f116151218ea2db969e41278a9.png -doc/build/html/_images/math/8dad0dea5e39bdc613bcdbee3df2aa308c4bfb36.png -doc/build/html/_images/math/92d87ddbc8556091823ce39983632b51a9017155.png -doc/build/html/_images/math/949aeae100d4cedce0cdac415b623cb45987a610.png -doc/build/html/_images/math/a6a3f8d12297ed2927849fdb3781c0b2a102a10f.png -doc/build/html/_images/math/a83d027b4a941b54bdc83c9dd2f8aa3c17632757.png -doc/build/html/_images/math/b33ce3703c911a1378e4daee36821363ac79623a.png -doc/build/html/_images/math/b51aa99e0bc8cc8c3d17c0b9cf822db73e13293e.png -doc/build/html/_images/math/b9040d52d78d98aea6867a562560cc34dd30b780.png -doc/build/html/_images/math/ca92360eea377f62866449804d0938704a76f5bf.png -doc/build/html/_images/math/cb5fc5bdb973c86de9def73a1a02062126b06948.png -doc/build/html/_images/math/d275af031e98b6eb55e1438bf891a34a840441f7.png -doc/build/html/_images/math/d79e8a2c7ce54906c2b25549da38bdbe02cf40d6.png -doc/build/html/_images/math/d872993ed0c7c420e73e77a1d285ac100c516806.png -doc/build/html/_images/math/dad48cd4c5cab157b7c283308b11d4ba1bbb037c.png -doc/build/html/_images/math/df0deb143e5ac127f00bd248ee8001ecae572adc.png -doc/build/html/_images/math/e7e68cfb93d1957dc2310ad17a25221b7e397f18.png -doc/build/html/_images/math/eaa3cc725b3b1d2897f997f93c7b7a058130eb58.png -doc/build/html/_images/math/eeb09f98a36d7f95bf06fa6b39ec78f9718c39ed.png -doc/build/html/_modules/index.html -doc/build/html/_modules/setup.html -doc/build/html/_modules/petgem/base/base.html -doc/build/html/_modules/petgem/base/modelling.html -doc/build/html/_modules/petgem/base/styles.html -doc/build/html/_modules/petgem/efem/efem.html -doc/build/html/_modules/petgem/efem/fem.html -doc/build/html/_modules/petgem/efem/vectorMatrixFunctions.html -doc/build/html/_modules/petgem/mesh/mesh.html -doc/build/html/_modules/petgem/monitoring/monitoring.html -doc/build/html/_modules/petgem/parallel/parallel.html -doc/build/html/_modules/petgem/postprocessing/postprocessing.html -doc/build/html/_modules/petgem/preprocessing/preprocessing.html -doc/build/html/_modules/petgem/solver/assembler.html -doc/build/html/_modules/petgem/solver/solver.html -doc/build/html/_sources/CSEMEdges.rst.txt -doc/build/html/_sources/Contact.rst.txt -doc/build/html/_sources/Download.rst.txt -doc/build/html/_sources/Features.rst.txt -doc/build/html/_sources/Installation.rst.txt -doc/build/html/_sources/Manual.rst.txt -doc/build/html/_sources/Publications.rst.txt -doc/build/html/_sources/Tutorial.rst.txt -doc/build/html/_sources/WhatIs.rst.txt -doc/build/html/_sources/index.rst.txt -doc/build/html/_sources/examples/modelParams.rst.txt -doc/build/html/_sources/examples/preprocessingParams.rst.txt -doc/build/html/_sources/petgem/base/basis_scripts.rst.txt -doc/build/html/_sources/petgem/base/modelling.rst.txt -doc/build/html/_sources/petgem/base/styles.rst.txt -doc/build/html/_sources/petgem/efem/efem.rst.txt -doc/build/html/_sources/petgem/efem/fem.rst.txt -doc/build/html/_sources/petgem/efem/vectorMatrixFunctions.rst.txt -doc/build/html/_sources/petgem/kernel/kernel.rst.txt -doc/build/html/_sources/petgem/mesh/mesh.rst.txt -doc/build/html/_sources/petgem/monitoring/monitoring.rst.txt -doc/build/html/_sources/petgem/parallel/parallel.rst.txt -doc/build/html/_sources/petgem/postprocessing/postprocessing.rst.txt -doc/build/html/_sources/petgem/preprocessing/preprocessing.rst.txt -doc/build/html/_sources/petgem/solver/assembler.rst.txt -doc/build/html/_sources/petgem/solver/solver.rst.txt -doc/build/html/_sources/setup/setup.rst.txt -doc/build/html/_static/Documentation.rst~ -doc/build/html/_static/Features.rst~ -doc/build/html/_static/Tutorial.rst~ -doc/build/html/_static/ajax-loader.gif -doc/build/html/_static/basic.css -doc/build/html/_static/comment-bright.png -doc/build/html/_static/comment-close.png -doc/build/html/_static/comment.png -doc/build/html/_static/doctools.js -doc/build/html/_static/down-pressed.png -doc/build/html/_static/down.png -doc/build/html/_static/file.png -doc/build/html/_static/jquery-3.1.0.js -doc/build/html/_static/jquery.js -doc/build/html/_static/minus.png -doc/build/html/_static/petgem.css -doc/build/html/_static/petgem.css~ -doc/build/html/_static/plus.png -doc/build/html/_static/pygments.css -doc/build/html/_static/searchtools.js -doc/build/html/_static/setup.cfg -doc/build/html/_static/underscore-1.3.1.js -doc/build/html/_static/underscore.js -doc/build/html/_static/up-pressed.png -doc/build/html/_static/up.png -doc/build/html/_static/websupport.js -doc/build/html/_static/figures/PETGEM_assembly.png -doc/build/html/_static/figures/PETGEM_parallel_scheme.png -doc/build/html/_static/figures/class_diagram.png -doc/build/html/_static/figures/component_diagram.png -doc/build/html/_static/figures/edgeElement.jpg -doc/build/html/_static/figures/logo_bsc.jpeg -doc/build/html/_static/figures/model1.png -doc/build/html/_static/figures/model_preprocessing.png -doc/build/html/_static/figures/output_preprocessing.png -doc/build/html/_static/figures/petgem_logo.png -doc/build/html/_static/figures/sequence_diagram.png -doc/build/html/_static/figures/softwareStack.jpg -doc/build/html/_static/figures/totalFieldVTK.png -doc/build/html/_static/figures/total_field_X_model1.png -doc/build/html/examples/modelParams.html -doc/build/html/examples/preprocessingParams.html -doc/build/html/petgem/base/basis_scripts.html -doc/build/html/petgem/base/modelling.html -doc/build/html/petgem/base/styles.html -doc/build/html/petgem/efem/efem.html -doc/build/html/petgem/efem/fem.html -doc/build/html/petgem/efem/vectorMatrixFunctions.html -doc/build/html/petgem/kernel/kernel.html -doc/build/html/petgem/mesh/mesh.html -doc/build/html/petgem/monitoring/monitoring.html -doc/build/html/petgem/parallel/parallel.html -doc/build/html/petgem/postprocessing/postprocessing.html -doc/build/html/petgem/preprocessing/preprocessing.html -doc/build/html/petgem/solver/assembler.html -doc/build/html/petgem/solver/solver.html -doc/build/html/setup/setup.html -doc/build/latex/Makefile -doc/build/latex/PETGEM.aux -doc/build/latex/PETGEM.fdb_latexmk -doc/build/latex/PETGEM.fls -doc/build/latex/PETGEM.idx -doc/build/latex/PETGEM.ilg -doc/build/latex/PETGEM.ind -doc/build/latex/PETGEM.log -doc/build/latex/PETGEM.out -doc/build/latex/PETGEM.pdf -doc/build/latex/PETGEM.tex -doc/build/latex/PETGEM.toc -doc/build/latex/PETGEM_parallel_scheme.png -doc/build/latex/class_diagram.png -doc/build/latex/component_diagram.png -doc/build/latex/edgeElement.jpg -doc/build/latex/footnotehyper-sphinx.sty -doc/build/latex/latexmkjarc -doc/build/latex/latexmkrc -doc/build/latex/model1.png -doc/build/latex/model_preprocessing.png -doc/build/latex/output_preprocessing.png -doc/build/latex/python.ist -doc/build/latex/sequence_diagram.png -doc/build/latex/softwareStack.jpg -doc/build/latex/sphinx.sty -doc/build/latex/sphinxhighlight.sty -doc/build/latex/sphinxhowto.cls -doc/build/latex/sphinxmanual.cls -doc/build/latex/sphinxmulticell.sty -doc/build/latex/totalFieldVTK.png -doc/build/latex/total_field_X_model1.png doc/source/CSEMEdges.rst doc/source/Contact.rst doc/source/Download.rst @@ -255,6 +18,10 @@ doc/source/Tutorial.rst doc/source/WhatIs.rst doc/source/conf.py doc/source/index.rst +doc/source/_downloads/Example1.zip +doc/source/_downloads/Example2.zip +doc/source/_downloads/Example3.zip +doc/source/_downloads/Example4.zip doc/source/_downloads/GPLv3_T_C.pdf doc/source/_downloads/kernel.py doc/source/_downloads/modelParams.py @@ -281,6 +48,7 @@ doc/source/_static/figures/sequence_diagram.png doc/source/_static/figures/softwareStack.jpg doc/source/_static/figures/totalFieldVTK.png doc/source/_static/figures/total_field_X_model1.png +doc/source/_static/figures/total_field_X_model2.png doc/source/_templates/layout.html doc/source/examples/modelParams.rst doc/source/examples/preprocessingParams.rst @@ -338,11 +106,12 @@ doc/source/setup/version.rst~ examples/Example1.zip examples/Example2.zip examples/Example3.zip +examples/Example4.zip examples/modelParams.py examples/petsc.opts examples/preprocessingParams.py -examples/__pycache__/modelParams.cpython-35.pyc -examples/__pycache__/preprocessingParams.cpython-35.pyc +examples/__pycache__/modelParams.cpython-36.pyc +examples/__pycache__/preprocessingParams.cpython-36.pyc petgem.egg-info/PKG-INFO petgem.egg-info/SOURCES.txt petgem.egg-info/dependency_links.txt diff --git a/petgem.egg-info/dependency_links.txt b/petgem.egg-info/dependency_links.txt old mode 100644 new mode 100755 diff --git a/petgem.egg-info/requires.txt b/petgem.egg-info/requires.txt old mode 100644 new mode 100755 diff --git a/petgem.egg-info/top_level.txt b/petgem.egg-info/top_level.txt old mode 100644 new mode 100755 diff --git a/petgem/base/__init__.py b/petgem/base/__init__.py old mode 100644 new mode 100755 diff --git a/petgem/base/__pycache__/__init__.cpython-35.pyc b/petgem/base/__pycache__/__init__.cpython-35.pyc new file mode 100755 index 0000000..add1d76 Binary files /dev/null and b/petgem/base/__pycache__/__init__.cpython-35.pyc differ diff --git a/petgem/base/__pycache__/__init__.cpython-36.pyc b/petgem/base/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000..591ae33 Binary files /dev/null and b/petgem/base/__pycache__/__init__.cpython-36.pyc differ diff --git a/petgem/base/__pycache__/base.cpython-35.pyc b/petgem/base/__pycache__/base.cpython-35.pyc new file mode 100755 index 0000000..3fb3b37 Binary files /dev/null and b/petgem/base/__pycache__/base.cpython-35.pyc differ diff --git a/petgem/base/__pycache__/base.cpython-36.pyc b/petgem/base/__pycache__/base.cpython-36.pyc new file mode 100644 index 0000000..281bb9e Binary files /dev/null and b/petgem/base/__pycache__/base.cpython-36.pyc differ diff --git a/petgem/base/__pycache__/modelling.cpython-35.pyc b/petgem/base/__pycache__/modelling.cpython-35.pyc new file mode 100755 index 0000000..5a4439f Binary files /dev/null and b/petgem/base/__pycache__/modelling.cpython-35.pyc differ diff --git a/petgem/base/__pycache__/modelling.cpython-36.pyc b/petgem/base/__pycache__/modelling.cpython-36.pyc new file mode 100644 index 0000000..c91ad7a Binary files /dev/null and b/petgem/base/__pycache__/modelling.cpython-36.pyc differ diff --git a/petgem/base/__pycache__/preprocessing.cpython-35.pyc b/petgem/base/__pycache__/preprocessing.cpython-35.pyc new file mode 100755 index 0000000..1947483 Binary files /dev/null and b/petgem/base/__pycache__/preprocessing.cpython-35.pyc differ diff --git a/petgem/base/__pycache__/styles.cpython-35.pyc b/petgem/base/__pycache__/styles.cpython-35.pyc new file mode 100755 index 0000000..afd11e0 Binary files /dev/null and b/petgem/base/__pycache__/styles.cpython-35.pyc differ diff --git a/petgem/base/__pycache__/styles.cpython-36.pyc b/petgem/base/__pycache__/styles.cpython-36.pyc new file mode 100644 index 0000000..7885d5b Binary files /dev/null and b/petgem/base/__pycache__/styles.cpython-36.pyc differ diff --git a/petgem/base/base.py b/petgem/base/base.py old mode 100644 new mode 100755 index 0878c8d..26dbc08 --- a/petgem/base/base.py +++ b/petgem/base/base.py @@ -107,6 +107,23 @@ def checkDictionaryConsistencyMaster(rank, in_dict, file_name, dir_name): PETSc.Sys.Print(' Checking parameters consistency...') + # NEDELEC_ORDER parameter + msg = msg1 + 'NEDELEC_ORDER' + msg2 + assert 'NEDELEC_ORDER' in in_dict, msg + msg = msg3 + 'NEDELEC_ORDER' + msg4 + nedelec_order = in_dict['NEDELEC_ORDER'] + assert type(nedelec_order) is int, msg + PETSc.Sys.Print(' checkDictionaryConsistency(): NEDELEC_ORDER ' + + 'consistency OK.') + + # CUDA parameter + msg = msg1 + 'CUDA' + msg2 + assert 'CUDA' in in_dict, msg + msg = msg3 + 'CUDA' + msg4 + cuda = in_dict['CUDA'] + assert type(cuda) is int, msg + PETSc.Sys.Print(' checkDictionaryConsistency(): CUDA consistency OK.') + # freq parameter msg = msg1 + 'FREQ' + msg2 assert 'FREQ' in in_dict, msg @@ -196,22 +213,40 @@ def checkDictionaryConsistencyMaster(rank, in_dict, file_name, dir_name): PETSc.Sys.Print(' checkDictionaryConsistency(): MESH_CONNECTIVITY_FILE ' 'consistency OK.') + # elemsF_file parameter + msg = msg1 + 'FACES_CONNECTIVITY_FILE' + msg2 + assert 'FACES_CONNECTIVITY_FILE' in in_dict, msg + msg = msg3 + 'FACES_CONNECTIVITY_FILE' + msg4 + elemsF_file = in_dict['FACES_CONNECTIVITY_FILE'] + assert type(elemsF_file) is str, msg + PETSc.Sys.Print(' checkDictionaryConsistency(): FACES_CONNECTIVITY_FILE ' + 'consistency OK.') + + # facesN_file parameter + msg = msg1 + 'FACES_NODES_FILE' + msg2 + assert 'FACES_NODES_FILE' in in_dict, msg + msg = msg3 + 'FACES_NODES_FILE' + msg4 + facesN_file = in_dict['FACES_NODES_FILE'] + assert type(facesN_file) is str, msg + PETSc.Sys.Print(' checkDictionaryConsistency(): FACES_NODES_FILE ' + 'consistency OK.') + # elemsE_file parameter - msg = msg1 + 'DOFS_CONNECTIVITY_FILE' + msg2 - assert 'DOFS_CONNECTIVITY_FILE' in in_dict, msg - msg = msg3 + 'DOFS_CONNECTIVITY_FILE' + msg4 - elemsE_file = in_dict['DOFS_CONNECTIVITY_FILE'] + msg = msg1 + 'EDGES_CONNECTIVITY_FILE' + msg2 + assert 'EDGES_CONNECTIVITY_FILE' in in_dict, msg + msg = msg3 + 'EDGES_CONNECTIVITY_FILE' + msg4 + elemsE_file = in_dict['EDGES_CONNECTIVITY_FILE'] assert type(elemsE_file) is str, msg - PETSc.Sys.Print(' checkDictionaryConsistency(): DOFS_CONNECTIVITY_FILE ' + PETSc.Sys.Print(' checkDictionaryConsistency(): EDGES_CONNECTIVITY_FILE ' 'consistency OK.') # edgesN_file parameter - msg = msg1 + 'DOFS_NODES_FILE' + msg2 - assert 'DOFS_NODES_FILE' in in_dict, msg - msg = msg3 + 'DOFS_NODES_FILE' + msg4 - edgesN_file = in_dict['DOFS_NODES_FILE'] + msg = msg1 + 'EDGES_NODES_FILE' + msg2 + assert 'EDGES_NODES_FILE' in in_dict, msg + msg = msg3 + 'EDGES_NODES_FILE' + msg4 + edgesN_file = in_dict['EDGES_NODES_FILE'] assert type(edgesN_file) is str, msg - PETSc.Sys.Print(' checkDictionaryConsistency(): DOFS_NODES_FILE ' + PETSc.Sys.Print(' checkDictionaryConsistency(): EDGES_NODES_FILE ' 'consistency OK.') # nnz_file parameter @@ -222,12 +257,12 @@ def checkDictionaryConsistencyMaster(rank, in_dict, file_name, dir_name): assert type(nnz_file) is str, msg PETSc.Sys.Print(' checkDictionaryConsistency(): NNZ_FILE consistency OK.') - # bEedges_file parameter + # boundaries_file parameter msg = msg1 + 'BOUNDARIES_FILE' + msg2 assert 'BOUNDARIES_FILE' in in_dict, msg msg = msg3 + 'BOUNDARIES_FILE' + msg4 - bEdges_file = in_dict['BOUNDARIES_FILE'] - assert type(bEdges_file) is str, msg + boundaries_file = in_dict['BOUNDARIES_FILE'] + assert type(boundaries_file) is str, msg PETSc.Sys.Print(' checkDictionaryConsistency(): BOUNDARIES_FILE ' 'consistency OK.') @@ -277,6 +312,26 @@ def checkDictionaryConsistencyMaster(rank, in_dict, file_name, dir_name): ' does not exist.') raise ValueError(msg) + # check elemsF_file path + success = checkFilePath(elemsF_file) + if success: + PETSc.Sys.Print(' checkDictionaryConsistency(): "' + elemsF_file + + '" exists.') + else: + msg = (' checkDictionaryConsistency(): file ' + elemsF_file + + ' does not exist.') + raise ValueError(msg) + + # check facesN_file path + success = checkFilePath(facesN_file) + if success: + PETSc.Sys.Print(' checkDictionaryConsistency(): "' + facesN_file + + '" exists.') + else: + msg = (' checkDictionaryConsistency(): file ' + facesN_file + + ' does not exist.') + raise ValueError(msg) + # check elemsE_file path success = checkFilePath(elemsE_file) if success: @@ -297,6 +352,16 @@ def checkDictionaryConsistencyMaster(rank, in_dict, file_name, dir_name): ' does not exist.') raise ValueError(msg) + # check boundaries_file path + success = checkFilePath(boundaries_file) + if success: + PETSc.Sys.Print(' checkDictionaryConsistency(): "' + + boundaries_file + '" exists.') + else: + msg = (' checkDictionaryConsistency(): file ' + boundaries_file + + ' does not exist.') + raise ValueError(msg) + # check receivers_file path success = checkFilePath(receivers_file) if success: @@ -312,11 +377,12 @@ def checkDictionaryConsistencyMaster(rank, in_dict, file_name, dir_name): file_name + '" are consistent.') # Create csem_modelling dictionary - out_model = CSEM_MODELLING(rank, freq, src_pos, src_direc, src_current, - src_length, sigma_background, sigma_file, - nodes_file, elemsN_file, elemsE_file, - edgesN_file, nnz_file, bEdges_file, - receivers_file, dir_name) + out_model = CSEM_MODELLING(rank, nedelec_order, cuda, freq, src_pos, + src_direc, src_current, src_length, + sigma_background, sigma_file, nodes_file, + elemsN_file, elemsF_file, facesN_file, + elemsE_file, edgesN_file, nnz_file, + boundaries_file, receivers_file, dir_name) PETSc.Sys.Print(' A new CSEM_MODELLING dictionary has been ' 'successfully created.') @@ -335,6 +401,14 @@ def checkDictionaryConsistencySlave(rank, in_dict, file_name, dir_name): :return: csem modelling dictionary after test. :rtype: csem_modelling dictionary. ''' + # nedelec_order parameter + nedelec_order = in_dict['NEDELEC_ORDER'] + nedelec_order = np.int(nedelec_order) + + # cuda parameter + cuda = in_dict['CUDA'] + cuda = np.int(cuda) + # freq parameter freq = in_dict['FREQ'] freq = np.float(in_dict['FREQ']) @@ -344,7 +418,7 @@ def checkDictionaryConsistencySlave(rank, in_dict, file_name, dir_name): # src_direc parameter src_direc = in_dict['SRC_DIREC'] - src_direc = int(src_direc) + src_direc = np.int(src_direc) # src_current parameter src_current = in_dict['SRC_CURRENT'] @@ -367,27 +441,34 @@ def checkDictionaryConsistencySlave(rank, in_dict, file_name, dir_name): # elemsN_file parameter elemsN_file = in_dict['MESH_CONNECTIVITY_FILE'] + # elemsF_file parameter + elemsF_file = in_dict['FACES_CONNECTIVITY_FILE'] + + # facesN_file parameter + facesN_file = in_dict['FACES_NODES_FILE'] + # elemsE_file parameter - elemsE_file = in_dict['DOFS_CONNECTIVITY_FILE'] + elemsE_file = in_dict['EDGES_CONNECTIVITY_FILE'] # edgesN_file parameter - edgesN_file = in_dict['DOFS_NODES_FILE'] + edgesN_file = in_dict['EDGES_NODES_FILE'] # nnz_file parameter nnz_file = in_dict['NNZ_FILE'] # edgesN_file parameter - bEdges_file = in_dict['BOUNDARIES_FILE'] + boundaries_file = in_dict['BOUNDARIES_FILE'] # receivers_file parameter receivers_file = in_dict['RECEIVERS_FILE'] # Create csem_modelling dictionary - out_model = CSEM_MODELLING(rank, freq, src_pos, src_direc, src_current, - src_length, sigma_background, sigma_file, - nodes_file, elemsN_file, elemsE_file, - edgesN_file, nnz_file, bEdges_file, - receivers_file, dir_name) + out_model = CSEM_MODELLING(rank, nedelec_order, cuda, freq, src_pos, + src_direc, src_current, src_length, + sigma_background, sigma_file, nodes_file, + elemsN_file, elemsF_file, facesN_file, + elemsE_file, edgesN_file, nnz_file, + boundaries_file, receivers_file, dir_name) return out_model diff --git a/petgem/base/modelling.py b/petgem/base/modelling.py old mode 100644 new mode 100755 index 9cc9856..5070168 --- a/petgem/base/modelling.py +++ b/petgem/base/modelling.py @@ -7,13 +7,16 @@ ''' -def CSEM_MODELLING(rank, freq, src_pos, src_direc, src_current, - src_length, sigma_background, sigma_file, - nodes_file, elemsN_file, elemsE_file, edgesN_file, - nnz_file, bEdges_file, receivers_file, dir_name): +def CSEM_MODELLING(rank, nedelec_order, cuda, freq, src_pos, src_direc, + src_current, src_length, sigma_background, sigma_file, + nodes_file, elemsN_file, elemsF_file, facesN_file, + elemsE_file, edgesN_file, nnz_file, boundaries_file, + receivers_file, dir_name): ''' csem_modelling dictionary with main parameters for CSEM FM. :param int rank: MPI rank. + :param int nedelec_order: nedelec element order. + :param int cuda: flag for cuda support. :param int,float freq: frequency. :param list src_pos: source position. :param int,float src_dir: source orientation. @@ -23,10 +26,12 @@ def CSEM_MODELLING(rank, freq, src_pos, src_direc, src_current, :param str sigma_file: file name of conductivity model. :param str nodes_file: file name of node spatial coordinates. :param str elemsN_file: file name of elements-nodes connectivity. + :param str elemsF_file: file name of elements-faces connectivity. + :param str facesN_file: file name of faces-nodes connectivity. :param str elemsE_file: file name of elements-edges connectivity. :param str edgesN_file: file name of edges-nodes connectivity. :param str nnz_file: file name of nnz for matrix allocation. - :param str bEdges_file: file name of boundary edges. + :param str boundaries_file: file name of boundary edges. :param str receivers_file: file name or receivers position. :param str dir_name: parent directory of sigma_file, nodes_file and elemsN_file. @@ -38,7 +43,9 @@ def CSEM_MODELLING(rank, freq, src_pos, src_direc, src_current, PETSc.Sys.Print(' CSEM_MODELLING(): Creating a ' 'CSEM_MODELLING dictionary.') - modelling = {'FREQ': freq, + modelling = {'NEDELEC_ORDER': nedelec_order, + 'CUDA': cuda, + 'FREQ': freq, 'SRC_POS': src_pos, 'SRC_DIREC': src_direc, 'SRC_CURRENT': src_current, @@ -47,10 +54,12 @@ def CSEM_MODELLING(rank, freq, src_pos, src_direc, src_current, 'CONDUCTIVITY_MODEL_FILE': sigma_file, 'NODES_FILE': nodes_file, 'MESH_CONNECTIVITY_FILE': elemsN_file, - 'DOFS_CONNECTIVITY_FILE': elemsE_file, - 'DOFS_NODES_FILE': edgesN_file, + 'FACES_CONNECTIVITY_FILE': elemsF_file, + 'FACES_NODES_FILE': facesN_file, + 'EDGES_CONNECTIVITY_FILE': elemsE_file, + 'EDGES_NODES_FILE': edgesN_file, 'NNZ_FILE': nnz_file, - 'BOUNDARIES_FILE': bEdges_file, + 'BOUNDARIES_FILE': boundaries_file, 'RECEIVERS_FILE': receivers_file, 'DIR_NAME': dir_name} @@ -63,39 +72,50 @@ def printModellingData(input_modelling): :return: None. ''' - A = str(input_modelling['FREQ']) - B = str(input_modelling['SRC_POS']) - C = str(input_modelling['SRC_DIREC']) - D = str(input_modelling['SRC_CURRENT']) - E = str(input_modelling['SRC_LENGTH']) - F = str(input_modelling['CONDUCTIVITY_BACKGROUND']) - G = input_modelling['CONDUCTIVITY_MODEL_FILE'] - H = input_modelling['NODES_FILE'] - I = input_modelling['MESH_CONNECTIVITY_FILE'] - J = input_modelling['DOFS_CONNECTIVITY_FILE'] - K = input_modelling['DOFS_NODES_FILE'] - L = input_modelling['NNZ_FILE'] - M = input_modelling['BOUNDARIES_FILE'] - N = input_modelling['RECEIVERS_FILE'] - O = input_modelling['DIR_NAME'] + A = str(input_modelling['NEDELEC_ORDER']) + if input_modelling['CUDA'] == 0: + B = str('0 (No)') + else: + B = str('1 (Yes)') + C = str(input_modelling['FREQ']) + D = str(input_modelling['SRC_POS']) + E = str(input_modelling['SRC_DIREC']) + F = str(input_modelling['SRC_CURRENT']) + G = str(input_modelling['SRC_LENGTH']) + H = str(input_modelling['CONDUCTIVITY_BACKGROUND']) + II = input_modelling['CONDUCTIVITY_MODEL_FILE'] + J = input_modelling['NODES_FILE'] + K = input_modelling['MESH_CONNECTIVITY_FILE'] + L = input_modelling['FACES_CONNECTIVITY_FILE'] + M = input_modelling['FACES_NODES_FILE'] + N = input_modelling['EDGES_CONNECTIVITY_FILE'] + OO = input_modelling['EDGES_NODES_FILE'] + P = input_modelling['NNZ_FILE'] + Q = input_modelling['BOUNDARIES_FILE'] + R = input_modelling['RECEIVERS_FILE'] + S = input_modelling['DIR_NAME'] PETSc.Sys.Print('\nData for CSEM_MODELLING') PETSc.Sys.Print('='*75) - PETSc.Sys.Print(' Frequency: {0:50}'.format(A)) - PETSc.Sys.Print(' Source position: {0:50}'.format(B)) - PETSc.Sys.Print(' Source orientation: {0:50}'.format(C)) - PETSc.Sys.Print(' Source current: {0:50}'.format(D)) - PETSc.Sys.Print(' Source length: {0:50}'.format(E)) - PETSc.Sys.Print(' Sigma background: {0:50}'.format(F)) - PETSc.Sys.Print(' Sigma file: {0:50}'.format(G)) - PETSc.Sys.Print(' Nodes file: {0:50}'.format(H)) - PETSc.Sys.Print(' Elements-nodes file: {0:50}'.format(I)) - PETSc.Sys.Print(' Elements-edges file: {0:50}'.format(J)) - PETSc.Sys.Print(' Edges-nodes file: {0:50}'.format(K)) - PETSc.Sys.Print(' nnz file: {0:50}'.format(L)) - PETSc.Sys.Print(' Boundary edges file: {0:50}'.format(M)) - PETSc.Sys.Print(' Receivers file: {0:50}'.format(N)) - PETSc.Sys.Print(' Parent directory: {0:50}'.format(O)) + PETSc.Sys.Print(' Nedelec order: {0:50}'.format(A)) + PETSc.Sys.Print(' CUDA support: {0:50}'.format(B)) + PETSc.Sys.Print(' Frequency: {0:50}'.format(C)) + PETSc.Sys.Print(' Source position: {0:50}'.format(D)) + PETSc.Sys.Print(' Source orientation: {0:50}'.format(E)) + PETSc.Sys.Print(' Source current: {0:50}'.format(F)) + PETSc.Sys.Print(' Source length: {0:50}'.format(G)) + PETSc.Sys.Print(' Sigma background: {0:50}'.format(H)) + PETSc.Sys.Print(' Sigma file: {0:50}'.format(II)) + PETSc.Sys.Print(' Nodes file: {0:50}'.format(J)) + PETSc.Sys.Print(' Elements-nodes file: {0:50}'.format(K)) + PETSc.Sys.Print(' Elements-faces file: {0:50}'.format(L)) + PETSc.Sys.Print(' Faces-nodes file: {0:50}'.format(M)) + PETSc.Sys.Print(' Elements-edges file: {0:50}'.format(N)) + PETSc.Sys.Print(' Edges-nodes file: {0:50}'.format(OO)) + PETSc.Sys.Print(' nnz file: {0:50}'.format(P)) + PETSc.Sys.Print(' Boundaries file: {0:50}'.format(Q)) + PETSc.Sys.Print(' Receivers file: {0:50}'.format(R)) + PETSc.Sys.Print(' Parent directory: {0:50}'.format(S)) return @@ -103,22 +123,23 @@ def printModellingData(input_modelling): def unitary_test(): ''' Unitary test for modelling.py script. ''' - from styles import (petgemHeader, test_header, - petgemFooter, test_footer) + from styles import (printPetgemHeader, test_header, + printPetgemFooter, test_footer) - printPetgemHeader() + rank = 0 # MASTER + printPetgemHeader(rank) test_header('modelling.py') PETSc.Sys.Print('Testing csem_modelling creation.') PETSc.Sys.Print('Creating modelling...') - # sample data: [rank, freq, src_pos, src_direc, src_current, - # src_length, sigma_background, sigma_file, + # sample data: [rank, nedelec_order, cuda, freq, src_pos, src_direc, + # src_current, src_length, sigma_background, sigma_file, # nodes_file, elemsN_file, elemsE_file, edgesN_file, - # bEdges_file, receivers_file, out_prefix, dir_name) - sample_data = [0, 1.0, [100, 100, 100], 1, 2.5, 2.5, + # boundaries_file, receivers_file, out_prefix, dir_name) + sample_data = [0, 2, 1, 1.0, [100, 100, 100], 1, 2.5, 2.5, 3.3, 'sigma_file.data', 'nodes_file.data', - 'elemsN_file.data', 'elemsE_file.data', 'edgesN_file.data', - 'nnz_file', 'bEdges_file.dat', 'receivers.data', - 'parent/path'] + 'elemsN_file.data', 'elemsF_file.data', 'facesN_file.data', + 'elemsE_file.data', 'edgesN_file.data', 'nnz_file', + 'boundaries_file.dat', 'receivers.data', 'parent/path'] sample_csem = CSEM_MODELLING(*sample_data) @@ -127,7 +148,7 @@ def unitary_test(): pass_test = True test_footer(pass_test) - printPetgemFooter() + printPetgemFooter(rank) if __name__ == '__main__': diff --git a/petgem/base/styles.py b/petgem/base/styles.py old mode 100644 new mode 100755 diff --git a/petgem/efem/__init__.py b/petgem/efem/__init__.py old mode 100644 new mode 100755 index 47b2a45..fa85256 --- a/petgem/efem/__init__.py +++ b/petgem/efem/__init__.py @@ -1,11 +1,29 @@ from .efem import unitary_test from .efem import defineEFEMConstants from .efem import nedelecBasisIterative -from .efem import computeDofs +from .efem import computeEdges from .efem import computeFaces from .efem import computeBoundaryFaces -from .efem import computeBoundaryDofs +from .efem import computeBoundaryEdges +from .efem import computeBoundaries +from .efem import computeElementDOFs +from .efem import edgeFaceVerticesInit +from .efem import computeSignsJacobLegth +from .efem import definitionHighOrderTet +from .efem import computeCoefficientsSecondOrder +from .efem import nedelecBasisSecondOrder +from .efem import computeMassMatrixSecondOrder +from .efem import computeStiffnessMatrixSecondOrder +from .efem import computeCoefficientsThirdOrder +from .efem import nedelecBasisThirdOrder +from .efem import computeMassMatrixThirdOrder +from .efem import computeStiffnessMatrixThirdOrder from .fem import tetraXiEtaZeta2XYZ from .fem import gauss_points_tetrahedron +from .fem import gauss_points_reference_tetrahedron +from .fem import volumetricToCartesianCoordinates +from .fem import cartesianToVolumetricCoordinates from .vectorMatrixFunctions import deleteDuplicateRows from .vectorMatrixFunctions import findUniqueRows +from .vectorMatrixFunctions import crossprod +from .vectorMatrixFunctions import is_duplicate_entry diff --git a/petgem/efem/__pycache__/__init__.cpython-35.pyc b/petgem/efem/__pycache__/__init__.cpython-35.pyc new file mode 100755 index 0000000..8bdfd20 Binary files /dev/null and b/petgem/efem/__pycache__/__init__.cpython-35.pyc differ diff --git a/petgem/efem/__pycache__/__init__.cpython-36.pyc b/petgem/efem/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000..afa3529 Binary files /dev/null and b/petgem/efem/__pycache__/__init__.cpython-36.pyc differ diff --git a/petgem/efem/__pycache__/efem.cpython-35.pyc b/petgem/efem/__pycache__/efem.cpython-35.pyc new file mode 100755 index 0000000..7565b06 Binary files /dev/null and b/petgem/efem/__pycache__/efem.cpython-35.pyc differ diff --git a/petgem/efem/__pycache__/efem.cpython-36.pyc b/petgem/efem/__pycache__/efem.cpython-36.pyc new file mode 100644 index 0000000..ff73d2a Binary files /dev/null and b/petgem/efem/__pycache__/efem.cpython-36.pyc differ diff --git a/petgem/efem/__pycache__/fem.cpython-35.pyc b/petgem/efem/__pycache__/fem.cpython-35.pyc new file mode 100755 index 0000000..a3e9aef Binary files /dev/null and b/petgem/efem/__pycache__/fem.cpython-35.pyc differ diff --git a/petgem/efem/__pycache__/fem.cpython-36.pyc b/petgem/efem/__pycache__/fem.cpython-36.pyc new file mode 100644 index 0000000..29c75cd Binary files /dev/null and b/petgem/efem/__pycache__/fem.cpython-36.pyc differ diff --git a/petgem/efem/__pycache__/vectorMatrixFunctions.cpython-35.pyc b/petgem/efem/__pycache__/vectorMatrixFunctions.cpython-35.pyc new file mode 100755 index 0000000..818cccb Binary files /dev/null and b/petgem/efem/__pycache__/vectorMatrixFunctions.cpython-35.pyc differ diff --git a/petgem/efem/__pycache__/vectorMatrixFunctions.cpython-36.pyc b/petgem/efem/__pycache__/vectorMatrixFunctions.cpython-36.pyc new file mode 100644 index 0000000..cdfe1fa Binary files /dev/null and b/petgem/efem/__pycache__/vectorMatrixFunctions.cpython-36.pyc differ diff --git a/petgem/efem/efem.py b/petgem/efem/efem.py old mode 100644 new mode 100755 index f8bcdd8..85f8871 --- a/petgem/efem/efem.py +++ b/petgem/efem/efem.py @@ -7,23 +7,40 @@ ''' -def computeDofs(elemsN, nElems): - ''' Compute degrees of freedom (DOF) of a 3D tetrahedral mesh. Here, dofs - are defined for edge finite element computations. +def computeEdges(elemsN, nElems, nedelec_order): + ''' Compute edges of a 3D tetrahedral mesh. For edge finite element of linear + order the edges are the dofs. For edge finite element of second order + the dofs are computed in runtime based on edges and faces on each + tetrahedral element. :param ndarray elemsN: elements-nodes connectivity. :param int nElems: number of tetrahedral elements in the mesh. - :return: dof connectivity and dofsNodes connectivity. + :param int nedelec_order: nedelec element order. + :return: edges connectivity and edgesNodes connectivity. :rtype: ndarray ''' - # Extracts sets of edges - edges1 = elemsN[:, [0, 1]] - edges2 = elemsN[:, [0, 2]] - edges3 = elemsN[:, [0, 3]] - edges4 = elemsN[:, [1, 2]] - edges5 = elemsN[:, [3, 1]] - edges6 = elemsN[:, [2, 3]] + # First order edge element + if nedelec_order == 1: + # Extracts sets of edges + edges1 = elemsN[:, [0, 1]] + edges2 = elemsN[:, [0, 2]] + edges3 = elemsN[:, [0, 3]] + edges4 = elemsN[:, [1, 2]] + edges5 = elemsN[:, [3, 1]] + edges6 = elemsN[:, [2, 3]] + # Second and third order edge element + elif nedelec_order == 2 or nedelec_order == 3: + # Extracts sets of edges + edges1 = elemsN[:, [0, 1]] + edges2 = elemsN[:, [1, 2]] + edges3 = elemsN[:, [2, 0]] + edges4 = elemsN[:, [0, 3]] + edges5 = elemsN[:, [1, 3]] + edges6 = elemsN[:, [2, 3]] + else: + raise ValueError('Edge element order=', + nedelec_order, ' not supported.') # Edges as sets of their nodes (vertices) vertices = np.zeros([nElems*6, 2]) @@ -35,22 +52,24 @@ def computeDofs(elemsN, nElems): vertices[5::6] = edges6 # Delete duplicate rows - [dofsNodes, dofs] = deleteDuplicateRows(vertices) + [edgesNodes, edges] = deleteDuplicateRows(vertices) # Build dofs matrix - dofs = np.array(np.reshape(dofs, (nElems, 6)), dtype=np.int) + edges = np.array(np.reshape(edges, (nElems, 6)), dtype=np.int) # Build dofs to nodes connectivity - dofsNodes.sort(axis=1) - dofsNodes = np.array(dofsNodes, dtype=np.int) + edgesNodes.sort(axis=1) + edgesNodes = np.array(edgesNodes, dtype=np.int) - return dofs, dofsNodes + return edges, edgesNodes -def computeFaces(elemsN, nElems): +def computeFaces(elemsN, nElems, nedelec_order): ''' Compute the element\'s faces of a 3D tetrahedral mesh. :param ndarray matrix: elements-nodes connectivity. + :param int nElems: number of elements in the mesh. + :param int nedelec_order: nedelec element order. :return: element/faces connectivity. :rtype: ndarray @@ -60,11 +79,21 @@ def computeFaces(elemsN, nElems): SIAM Journal on Scientific Computing 31.6 (2009): 4130-4151. ''' - # Extracts sets of faces - faces1 = elemsN[:, [0, 2, 1]] - faces2 = elemsN[:, [0, 1, 3]] - faces3 = elemsN[:, [0, 3, 2]] - faces4 = elemsN[:, [1, 2, 3]] + # Extracts sets of faces for each nedelec element order + if nedelec_order == 1: # First order edge element + faces1 = elemsN[:, [0, 2, 1]] + faces2 = elemsN[:, [0, 1, 3]] + faces3 = elemsN[:, [0, 3, 2]] + faces4 = elemsN[:, [1, 2, 3]] + elif nedelec_order == 2 or nedelec_order == 3: # Second and third order + # edge element + faces1 = elemsN[:, [0, 1, 2]] + faces2 = elemsN[:, [0, 2, 3]] + faces3 = elemsN[:, [0, 3, 1]] + faces4 = elemsN[:, [3, 1, 2]] + else: + raise ValueError('Edge element order=', + nedelec_order, ' not supported.') # Faces as sets of their nodes (vertices) vertices = np.zeros([nElems*4, 3]) @@ -88,7 +117,7 @@ def computeBoundaryFaces(elemsF, facesN): :param ndarray elemsF: elements-face connectivity. :param ndarray facesN: faces-nodes connectivity. - :return: boundary-faces connectivity. + :return: nodal-connectivity and indexes of boundary-faces. :rtype: ndarray ''' @@ -145,16 +174,22 @@ def computeBoundaryFaces(elemsF, facesN): # related to only one single tetra, which means it is on the # boundary of the domain. Since faces are defined by their nodes, # we have the boundary nodes too. - tmp = (E[:, 1] == 0) - ind = np.where(tmp)[False] - + # Get boundary faces to nodes + ind = (E[:, 1] == 0) bfacesN = np.array(np.transpose(facesN[ind, :]), dtype=np.int) - return bfacesN + # Get indexes of boundary faces + ind = np.where(ind == True) + bFaces = np.array(np.transpose(ind), dtype=np.int) + size = bFaces.shape + nBoundaryFaces = size[0] + bFaces = bFaces.reshape((nBoundaryFaces)) + return bfacesN, bFaces -def computeBoundaryDofs(edgesN, bfacesN): - ''' Compute boundary dofs of a tetrahedral mesh. + +def computeBoundaryEdges(edgesN, bfacesN): + ''' Compute boundary edges of a tetrahedral mesh. :param ndarray edgesN: edges-nodes connectivity. :param ndarray bfacesN: boundary-faces-nodes connectivity. @@ -209,17 +244,157 @@ def computeBoundaryDofs(edgesN, bfacesN): return bEdges -def defineEFEMConstants(): +def computeBoundaries(elemsN, nElems, edgesNodes, nedelec_order): + ''' Compute boundaries of the domain for edge finite element computations. + + :param ndarray elemsN: elements-nodes connectivity. + :param int nElems: number of elements in the mesh. + :param ndarray edgesN: edges-nodes connectivity. + :param int nedelec_order: nedelec element order. + :return: boundaries, ndofs + :rtype: ndarray + ''' + + # Compute faces + elemsF, facesN = computeFaces(elemsN, nElems, nedelec_order) + + # Compute boundary faces + boundaryFacesN, boundaryFaces = computeBoundaryFaces(elemsF, facesN) + + # Compute boundary edges + boundaryEdges = computeBoundaryEdges(edgesNodes, boundaryFacesN) + + # Number of edges + size = edgesNodes.shape + nEdges = size[0] + + # Number of faces + size = facesN.shape + nFaces = size[0] + + # Number of boundary edges + size = boundaryEdges.shape + nBoundaryEdges = size[1] + + # Number of boundary faces + size = boundaryFaces.shape + nBoundaryFaces = size[0] + + # Compute boundaries + if nedelec_order == 1: # First order edge element + # Boundaries correspond to boundary edges + boundaries = boundaryEdges + # Number of DOFs correspond to edges in the mesh + size = edgesNodes.shape + ndofs = size[0] + + elif nedelec_order == 2: # Second order edge element + # Number of DOFS + ndofs = nEdges*2 + nFaces*2 + + # Total number of boundaries + nBoundaries = nBoundaryEdges*2 + nBoundaryFaces*2 + + # Allocate + boundaries = np.zeros((nBoundaries), dtype=np.int) + newEdgesNumb = np.zeros((nBoundaryEdges, 2), dtype=np.int) + newFacesNumb = np.zeros((nBoundaryFaces, 2), dtype=np.int) + + # Compute boundaries on edges + # Use 1-based indexing in boundaryEdges and boundaryFaces + boundaryEdges += np.int(1) + boundaryFaces += np.int(1) + + # Get boundaries on edges + newEdgesNumb[:, 0] = (boundaryEdges*2)-1 + newEdgesNumb[:, 1] = boundaryEdges*2 + newEdgesNumb = newEdgesNumb.reshape((nBoundaryEdges*2), order='F') + + # Insert edge boundary indexes in boundary array + boundaries[0:nBoundaryEdges*2] = newEdgesNumb + + # Get boundaries on faces + newFacesNumb[:, 0] = (boundaryFaces*2+nEdges*2)-1 + newFacesNumb[:, 1] = boundaryFaces*2+nEdges*2 + newFacesNumb = newFacesNumb.reshape((nBoundaryFaces*2), order='F') + + # Insert face boundary indexes in boundary array + boundaries[nBoundaryEdges*2:] = newFacesNumb + + # Use 0-based indexing in boundaries + boundaries -= np.int(1) + + elif nedelec_order == 3: # Third order edge element + # Number of DOFS + ndofs = nEdges*3 + nFaces*6 + nElems*3 + + # Total number of boundaries + nBoundaries = nBoundaryEdges*3 + nBoundaryFaces*6 + + # Allocate + boundaries = np.zeros((nBoundaries), dtype=np.int) + newEdgesNumb = np.zeros((nBoundaryEdges, 3), dtype=np.int) + newFacesNumb = np.zeros((nBoundaryFaces, 6), dtype=np.int) + + # Compute boundaries on edges + # Use 1-based indexing in boundaryEdges and boundaryFaces + boundaryEdges += np.int(1) + boundaryFaces += np.int(1) + + # Get boundaries on edges + newEdgesNumb[:, 0] = (boundaryEdges*3)-2 + newEdgesNumb[:, 1] = (boundaryEdges*3)-1 + newEdgesNumb[:, 2] = boundaryEdges*3 + newEdgesNumb = newEdgesNumb.reshape((nBoundaryEdges*3), order='F') + + # Insert edge boundary indexes in boundary array + boundaries[0:nBoundaryEdges*3] = newEdgesNumb + + # Get boundaries on faces + newFacesNumb[:, 0] = (boundaryFaces*6+nEdges*3)-5 + newFacesNumb[:, 1] = (boundaryFaces*6+nEdges*3)-4 + newFacesNumb[:, 2] = (boundaryFaces*6+nEdges*3)-3 + newFacesNumb[:, 3] = (boundaryFaces*6+nEdges*3)-2 + newFacesNumb[:, 4] = (boundaryFaces*6+nEdges*3)-1 + newFacesNumb[:, 5] = boundaryFaces*6+nEdges*3 + newFacesNumb = newFacesNumb.reshape((nBoundaryFaces*6), order='F') + + # Insert face boundary indexes in boundary array + boundaries[nBoundaryEdges*3:] = newFacesNumb + + # Use 0-based indexing in boundaries + boundaries -= np.int(1) + + else: + raise ValueError('Edge element order=', + nedelec_order, ' not supported.') + + return boundaries, ndofs + + +def defineEFEMConstants(nedelec_order): ''' Set constants for edge finite element computations, namely, - order linear edge finite elements (edgeOrder), order linear nodal finite + order of edge finite elements (edgeOrder), order of nodal finite elements (nodalOrder) and number of dimensions (numDimensions). - :param: None. + :param int nedelec_order: nedelec element order. :return: edgeOrder, nodalOrder and numDimensions. :rtype: integer. ''' - # Set order of linear edge finite elements - edgeOrder = 6 + # Set order of edge finite elements + if nedelec_order == 1: # First order edge element + # Nedelec order + edgeOrder = 6 + elif nedelec_order == 2: # Second order edge element + # Nedelec order + edgeOrder = 20 + elif nedelec_order == 3: # Third order edge element + # Nedelec order + edgeOrder = 45 + else: + raise ValueError('Edge element order=', + nedelec_order, ' not supported.') + # Set order of linear nodal finite lements nodalOrder = 4 # Set number of dimensions @@ -245,13 +420,13 @@ def nedelecBasisIterative(eleNodes, points, eleVol, lengthEdges, edgeOrder): John Wiley & Sons, 2002. ''' # Coefficients computation. Running in a cycling way - a = np.zeros([4], dtype=np.float64) - b = np.zeros([4], dtype=np.float64) - c = np.zeros([4], dtype=np.float64) - d = np.zeros([4], dtype=np.float64) + a = np.zeros([4], dtype=np.float) + b = np.zeros([4], dtype=np.float) + c = np.zeros([4], dtype=np.float) + d = np.zeros([4], dtype=np.float) tmp = np.array([0, 1, 2, 3, 0, 1, 2], dtype=np.int) - temp_ones = np.ones([3], dtype=np.float64) + temp_ones = np.ones([3], dtype=np.float) for iCoeff in np.arange(4): a[iCoeff] = det([[eleNodes[tmp[iCoeff+1], 0], @@ -286,7 +461,7 @@ def nedelecBasisIterative(eleNodes, points, eleVol, lengthEdges, edgeOrder): eleNodes[tmp[iCoeff+3], 1]]]) # Add signs - sign = np.float64(-1.0) + sign = np.float(-1.0) a[1] = a[1] * sign a[3] = a[3] * sign b[0] = b[0] * sign @@ -304,7 +479,7 @@ def nedelecBasisIterative(eleNodes, points, eleVol, lengthEdges, edgeOrder): # Nedelec basis for all points if nPoints == 1: - AA = np.float64(1.0) / ((np.float64(6.0)*eleVol)**2) + AA = np.float(1.0) / ((np.float(6.0)*eleVol)**2) # To reduce number of multiplications b1x = b[0]*points[0] b2x = b[1]*points[0] @@ -348,11 +523,11 @@ def nedelecBasisIterative(eleNodes, points, eleVol, lengthEdges, edgeOrder): d[3]*A3-d[2]*A4], lengthEdges[5]) basis = np.array(np.vstack((b1, b2, b3, b4, b5, b6)) * AA, - dtype=np.float64) + dtype=np.float) # If not else: - basis = np.zeros((edgeOrder, 3, nPoints), dtype=np.float64) - AA = np.float64(1.0) / ((np.float64(6.0)*eleVol)**2) + basis = np.zeros((edgeOrder, 3, nPoints), dtype=np.float) + AA = np.float(1.0) / ((np.float(6.0)*eleVol)**2) # Compute basis for each point for iP in np.arange(nPoints): # To reduce number of multiplications @@ -402,16 +577,3100 @@ def nedelecBasisIterative(eleNodes, points, eleVol, lengthEdges, edgeOrder): return basis +def computeElementDOFs(iEle, nodesEle, edgesEle, facesEle, nodesFace, + nEdges, nFaces, nedelec_order): + ''' Compute global DOFs for tetrahedral element. + + :param int iEle: id of the tetrahedral element-th. + :param ndarray nodesEle: array with nodal indexes of element. + :param ndarray edgesEle: array with edge indexes of element. + :param ndarray facesEle: array with face indexes of element. + :param ndarray nodesFace: array with node indexes of faces. + :param ndarray nEdges: total number of edges in the mesh. + :param ndarray nFaces: total number of faces in the mesh. + :param int nedelec_order: nedelec element order. + :return: DOFs indexes. + :rtype: ndarray + ''' + if nedelec_order == 1: # First order edge element + # DOFs correspond to element's edges in the mesh (6 DOFs) + dofsEle = edgesEle + + elif nedelec_order == 2: # Second order edge element + # First order ele + firstOrderEdgeElement = 6 + + # Second order ele + secondOrderEdgeElement = 20 + + # Number of faces per tetrahedral element + nFacesEle = 4 + + # Edge's signs computation + idx_signs1 = np.array([1, 2, 0, 3, 3, 3], dtype=np.int) + idx_signs2 = np.array([0, 1, 2, 0, 1, 2], dtype=np.int) + tmp = nodesEle + tmp = tmp[idx_signs1] - tmp[idx_signs2] + signsEle = tmp / np.abs(tmp) + + # Allocate + dofsEle = np.zeros((secondOrderEdgeElement), dtype=np.int) + newEdgesNumb = np.zeros((firstOrderEdgeElement, 2), dtype=np.int) + newFacesNumb = np.zeros((nFacesEle, 2), dtype=np.int) + + # Use 1-based indexing in edgesEle and facesEle + edgesEle += np.int(1) + facesEle += np.int(1) + + # Get dofs on edges + newEdgesNumb[:, 0] = (edgesEle*2)-1 + newEdgesNumb[:, 1] = edgesEle*2 + + # Reverse dofs in case they are negative + for iEdge in np.arange(firstOrderEdgeElement): + if (signsEle[iEdge] < 0): + newEdgesNumb[iEdge, [0, 1]] = newEdgesNumb[iEdge, [1, 0]] + + newEdgesNumb = newEdgesNumb.reshape((firstOrderEdgeElement * 2)) + + # Get dofs on faces + newFacesNumb[:, 0] = (facesEle*2+nEdges*2)-1 + newFacesNumb[:, 1] = facesEle*2+nEdges*2 + newFacesNumb = newFacesNumb.reshape((nFacesEle*2)) + + # Insert dofs on edges in dofsEle array + dofsEle[0:firstOrderEdgeElement*2] = newEdgesNumb + + # Insert dofs on faces in dofsEle array + dofsEle[firstOrderEdgeElement*2:] = newFacesNumb + + # Use 0-based indexing in boundaries + dofsEle -= np.int(1) + + elif nedelec_order == 3: # Third order edge element + # First order ele + firstOrderEdgeElement = 6 + + # Third order ele + thirdOrderEdgeElement = 45 + + # Number of faces per tetrahedral element + nFacesEle = 4 + + # Edge's signs computation + idx_signs1 = np.array([1, 2, 0, 3, 3, 3], dtype=np.int) + idx_signs2 = np.array([0, 1, 2, 0, 1, 2], dtype=np.int) + tmp = nodesEle + tmp = tmp[idx_signs1] - tmp[idx_signs2] + signsEle = tmp / np.abs(tmp) + + # Allocate + dofsEle = np.zeros((thirdOrderEdgeElement), dtype=np.int) + newEdgesNumb = np.zeros((firstOrderEdgeElement, 3), dtype=np.int) + newFacesNumb = np.zeros((nFacesEle, 6), dtype=np.int) + newVolumeNumb = np.zeros([3], dtype=np.int) + + # Use 1-based indexing in edgesEle and facesEle + edgesEle += np.int(1) + facesEle += np.int(1) + iEle += np.int(1) + + # Get dofs on edges + newEdgesNumb[:, 0] = (edgesEle*3)-2 + newEdgesNumb[:, 1] = (edgesEle*3)-1 + newEdgesNumb[:, 2] = edgesEle*3 + + # Reverse dofs in case they are negative + for iEdge in np.arange(firstOrderEdgeElement): + if (signsEle[iEdge] < 0): + newEdgesNumb[iEdge, [0, 1, 2]] = newEdgesNumb[iEdge, [2, 1, 0]] + + newEdgesNumb = newEdgesNumb.reshape((firstOrderEdgeElement*3)) + + # Get dofs on faces + # Definition of faces (global ordering) + global_faces = np.array([[0, 1, 2], [0, 2, 3], + [0, 3, 1], [3, 1, 2]], dtype=np.int) + + tmp_dofs = np.zeros((3, 2), dtype=np.int) + + for iFace in np.arange(nFacesEle): + tmp_dofs[0, :] = [(facesEle[iFace]*6+nEdges*3)-5, + (facesEle[iFace]*6+nEdges*3)-4] + tmp_dofs[1, :] = [(facesEle[iFace]*6+nEdges*3)-3, + (facesEle[iFace]*6+nEdges*3)-2] + tmp_dofs[2, :] = [(facesEle[iFace]*6+nEdges*3)-1, + (facesEle[iFace]*6+nEdges*3)] + + if not np.all(nodesFace[iFace, :] == + nodesEle[global_faces[iFace, :]]): + a = nodesFace[iFace, :] + b = nodesEle[global_faces[iFace, :]] + idx = np.where(a[:, None] == b[None, :])[1] + tmp_dofs_idx = tmp_dofs[idx, :] + newFacesNumb[iFace, :] = tmp_dofs_idx.reshape(1, 6) + else: + newFacesNumb[iFace, :] = tmp_dofs.reshape(1, 6) + + newFacesNumb = newFacesNumb.reshape((nFacesEle*6)) + + # Get dofs on element volume + newVolumeNumb[0] = (((nEdges*3) + (nFaces*6)) + + (iEle-1) * 3 + np.int(1)) + newVolumeNumb[1] = (((nEdges*3) + (nFaces*6)) + + (iEle-1)*3 + np.int(2)) + newVolumeNumb[2] = (((nEdges*3) + (nFaces*6)) + + (iEle-1) * 3 + np.int(3)) + + # Insert dofs on edges in dofsEle array + dofsEle[0:firstOrderEdgeElement*3] = newEdgesNumb + + # Insert dofs on faces in dofsEle array + dofsEle[firstOrderEdgeElement*3: + firstOrderEdgeElement*3+nFacesEle*6] = newFacesNumb + + # Insert dofs on volume in dofsEle array + dofsEle[firstOrderEdgeElement*3+nFacesEle*6:] = newVolumeNumb + + # Use 0-based indexing in boundaries + dofsEle -= np.int(1) + iEle -= np.int(1) + edgesEle -= np.int(1) + facesEle -= np.int(1) + + else: + raise ValueError('Edge element order=', + nedelec_order, ' not supported.') + + return dofsEle + + +def edgeFaceVerticesInit(): + ''' Initialization of arrays that define edges and faces of tetrahedral + elements. Vector basis functions are based on these arrays. + + :param: None. + :return: edge_vertices and face_vertices. + :rtype: ndarray + + .. note: References:\n + Amor-Martin, A., Garcia-Castillo, L. E., & Garcia-Doñoro, D. D. + (2016). Second-order Nédélec curl-conforming prismatic element + for computational electromagnetics. IEEE Transactions on + Antennas and Propagation, 64(10), 4384-4395. + ''' + + # Edges definition + edge_vertices = np.array([[0, 1], + [1, 2], + [2, 0], + [0, 3], + [1, 3], + [2, 3]], dtype=np.int) + + # Faces definition + face_vertices = np.array([[0, 1, 2], + [1, 3, 2], + [2, 3, 0], + [3, 1, 0]], dtype=np.int) + + # Reference element + ref_ele = np.array([[0, 1, 0, 0], + [0, 0, 1, 0], + [0, 0, 0, 1]], dtype=np.int) + + return edge_vertices, face_vertices, ref_ele + + +def isigno(xx, yy, zz): + ''' Compute signs for edges in a given tetrahedral element. + + :param float xx: x-coordinates of the nodes. + :param float yy: x-coordinates of the nodes. + :param float zz: x-coordinates of the nodes. + :return: signs of edges. + :rtype: ndarray + ''' + isign = -1 + + if ((xx == 0) and (yy == 0) and (zz == 0)): + print('Warning: Null sign in edge') + isign = 0 + elif ((xx > 0) or ((xx == 0) and (yy > 0)) or ((xx == 0) + and (yy == 0) and (zz > 0))): + isign = 1 + + return isign + + +def computeSignsJacobLegth(eleNodes, edge_vertices, face_vertices, + nedelec_order): + ''' Compute signs, edges length and jacobian for mapping. + + :param ndarray nodesEle: array with nodal indexes of element. + :param ndarray edge_vertices: edges connectivity based on vertices. + :param ndarray face_vertices: . + :param int nedelec_order: nedelec element order. + :return: tangential unitary vectors for each edge, edges length, + dofs signs, normal face vectors, jacobiano, determinant + of jacobian, faces area. + :rtype: ndarray + + .. note: References:\n + Amor-Martin, A., Garcia-Castillo, L. E., & Garcia-Doñoro, D. D. + (2016). Second-order Nédélec curl-conforming prismatic element + for computational electromagnetics. IEEE Transactions on + Antennas and Propagation, 64(10), 4384-4395. + ''' + # First order ele + firstOrderEdgeElement = 6 + + # Second order ele + secondOrderEdgeElement = 20 + + # Third order ele + thirdOrderEdgeElement = 45 + + if nedelec_order == 2: + orderEle = secondOrderEdgeElement + elif nedelec_order == 3: + orderEle = thirdOrderEdgeElement + else: + raise ValueError('Edge element order=', + nedelec_order, ' not supported.') + + # Absolute values for edges and faces numbering + edge_vertices_abs = np.abs(edge_vertices) + face_vertices_abs = np.abs(face_vertices) + + # Jacobian computation + jacob = np.zeros((3, 3), dtype=np.float) + jacob[0, :] = eleNodes[:, 1] - eleNodes[:, 0] + jacob[1, :] = eleNodes[:, 2] - eleNodes[:, 0] + jacob[2, :] = eleNodes[:, 3] - eleNodes[:, 0] + + # Compute jacobian determinant + detJacob = (jacob[0, 0]*jacob[1, 1]*jacob[2, 2] + + jacob[0, 1]*jacob[1, 2]*jacob[2, 0] + + jacob[0, 2]*jacob[1, 0]*jacob[2, 1] - + jacob[0, 2]*jacob[1, 1]*jacob[2, 0] - + jacob[0, 1]*jacob[1, 0]*jacob[2, 2] - + jacob[0, 0]*jacob[1, 2]*jacob[2, 1]) + + # Definition of tangetial unitary vectors for each edge + taui = np.zeros((3, firstOrderEdgeElement), dtype=np.float) + taui[0:3, :] = (eleNodes[:, edge_vertices_abs[:, 1]] - + eleNodes[:, edge_vertices_abs[:, 0]]) + + # Computation of normal face vectors, signs and edge length computation + nreal = np.zeros((3, 4), dtype=np.float) + area_faces = np.zeros(4, dtype=np.float) + length = np.zeros(firstOrderEdgeElement, dtype=np.float) + signs = np.zeros(orderEle, dtype=np.float) + + if nedelec_order == 2: + for iedge in np.arange(firstOrderEdgeElement): + length[iedge] = norm(taui[:, iedge]) + taui[:, iedge] = taui[:, iedge]/length[iedge] + + if np.sum(np.sign(edge_vertices[iedge, :]+np.int(1))) == -2: + taui[:, iedge] = -taui[:, iedge] + elif np.sum(np.sign(edge_vertices[iedge, :]+np.int(1))) == 0: + raise ValueError('The signs of ', iedge, + ' are incorrect.') + + dof = np.linspace(2*iedge-1, 2*iedge, 2, + dtype=np.int) + np.int(1) + + signs[dof] = isigno(taui[0, iedge], taui[1, iedge], + taui[2, iedge]) + + for facenumber in np.arange(4): + aux1 = (eleNodes[:, face_vertices_abs[facenumber, 1]] - + eleNodes[:, face_vertices_abs[facenumber, 0]]) + aux2 = (eleNodes[:, face_vertices_abs[facenumber, 2]] - + eleNodes[:, face_vertices_abs[facenumber, 0]]) + + nreal[:, facenumber] = -np.cross(aux1, aux2) + + area_faces[facenumber] = norm(nreal[:, facenumber]) + nreal[:, facenumber] = nreal[:, facenumber]/area_faces[facenumber] + + dof = np.linspace(2*facenumber-1, 2*facenumber, 2, + dtype=np.int) + np.int(13) + + signs[dof] = isigno(nreal[0, facenumber], nreal[1, facenumber], + nreal[2, facenumber]) + + elif nedelec_order == 3: + # Compute components for xx, yy, zz for the dofs associated + # to edges (18 dofs, 3 per edge) + # Allocate + xx = np.zeros(18, dtype=np.float) + yy = np.zeros(18, dtype=np.float) + zz = np.zeros(18, dtype=np.float) + + xyz21 = eleNodes[:, 1] - eleNodes[:, 0] + xyz31 = eleNodes[:, 2] - eleNodes[:, 0] + xyz41 = eleNodes[:, 3] - eleNodes[:, 0] + + # Node 1,2,3 + xx[[0, 1, 2]] = xyz21[0] + yy[[0, 1, 2]] = xyz21[1] + zz[[0, 1, 2]] = xyz21[2] + + # Node 4,5,6 + xx[[3, 4, 5]] = eleNodes[0, 2]-eleNodes[0, 1] + yy[[3, 4, 5]] = eleNodes[1, 2]-eleNodes[1, 1] + zz[[3, 4, 5]] = eleNodes[2, 2]-eleNodes[2, 1] + + # Node 7,8,9 + xx[[6, 7, 8]] = -xyz31[0] + yy[[6, 7, 8]] = -xyz31[1] + zz[[6, 7, 8]] = -xyz31[2] + + # Node 10,11,12 + xx[[9, 10, 11]] = xyz41[0] + yy[[9, 10, 11]] = xyz41[1] + zz[[9, 10, 11]] = xyz41[2] + + # Node 13,14,15 + xx[[12, 13, 14]] = eleNodes[0, 3]-eleNodes[0, 1] + yy[[12, 13, 14]] = eleNodes[1, 3]-eleNodes[1, 1] + zz[[12, 13, 14]] = eleNodes[2, 3]-eleNodes[2, 1] + + # Node 16,17,18 + xx[[15, 16, 17]] = eleNodes[0, 3]-eleNodes[0, 2] + yy[[15, 16, 17]] = eleNodes[1, 3]-eleNodes[1, 2] + zz[[15, 16, 17]] = eleNodes[2, 3]-eleNodes[2, 2] + + nreal[:, 0] = np.cross(np.hstack((xx[0], yy[0], zz[0])), + np.hstack((xx[6], yy[6], zz[6]))) + nreal[:, 1] = np.cross(np.hstack((xx[6], yy[6], zz[6])), + np.hstack((xx[9], yy[9], zz[9]))) + nreal[:, 2] = np.cross(np.hstack((xx[0], yy[0], zz[0])), + np.hstack((xx[9], yy[9], zz[9]))) + nreal[:, 3] = np.cross(np.hstack((xx[3], yy[3], zz[3])), + np.hstack((xx[12], yy[12], zz[12]))) + + for facenumber in np.arange(4): + nreal[:, facenumber] = (nreal[:, facenumber] / + norm(nreal[:, facenumber])) + + # Sign for dofs on edges + for idof in np.arange(18): + signs[idof] = isigno(xx[idof], yy[idof], zz[idof]) + + # Sign for dofs on faces + signs[18:24] = isigno(nreal[0, 0], nreal[1, 0], nreal[2, 0]) + signs[24:30] = isigno(nreal[0, 1], nreal[1, 1], nreal[2, 1]) + signs[30:36] = isigno(nreal[0, 2], nreal[1, 2], nreal[2, 2]) + signs[36:42] = isigno(nreal[0, 3], nreal[1, 3], nreal[2, 3]) + + # Sign for dofs on volume + signs[42:45] = 1 + + else: + raise ValueError('Edge element order=', + nedelec_order, ' not supported.') + + area_faces = area_faces/2. + + return taui, length, signs, nreal, jacob, detJacob, area_faces + + +def definitionHighOrderTet(nreal, signs, jacob, nedelec_order): + ''' Compute "q" vectors on faces -dof definition of edge tetrahedral + element of second and third order. + + :param float-array nreal: normal face vectors. + :param float-array signs: dofs signs. + :param float jacob: jacobian. + :param int nedelec_order: nedelec element order. + :return: q vectors on faces, inverse jacobian, 3x3 tensor. + :rtype: ndarray. + + .. note: References:\n + Amor-Martin, A., Garcia-Castillo, L. E., & Garcia-Doñoro, D. D. + (2016). Second-order Nédélec curl-conforming prismatic element + for computational electromagnetics. IEEE Transactions on + Antennas and Propagation, 64(10), 4384-4395. + ''' + # q vectors general + q_genera = np.eye(3, dtype=np.float) + + # Add sign to face + n_faces = np.zeros((3, 4), dtype=np.float) + if nedelec_order == 2: + n_faces[:, 0] = nreal[:, 0]*signs[12] + n_faces[:, 1] = nreal[:, 1]*signs[14] + n_faces[:, 2] = nreal[:, 2]*signs[16] + n_faces[:, 3] = nreal[:, 3]*signs[18] + elif nedelec_order == 3: + n_faces[:, 0] = nreal[:, 0]*signs[18] + n_faces[:, 1] = nreal[:, 1]*signs[24] + n_faces[:, 2] = nreal[:, 2]*signs[30] + n_faces[:, 3] = nreal[:, 3]*signs[36] + else: + raise ValueError('Edge element order=', + nedelec_order, ' not supported.') + + qface = np.zeros((3, 8), dtype=np.float) + + for nface in np.arange(4): + aux1 = crossprod(n_faces[:, nface], q_genera[:, 0]) + aux2 = crossprod(n_faces[:, nface], q_genera[:, 1]) + aux3 = crossprod(n_faces[:, nface], q_genera[:, 2]) + + if ((norm(aux1) >= norm(aux2)) and (norm(aux1) >= norm(aux3))): + qface[:, nface*2] = aux1[:, 0] + elif ((norm(aux2) >= norm(aux1)) and (norm(aux2) >= norm(aux3))): + qface[:, nface*2] = aux2[:, 0] + elif ((norm(aux3) >= norm(aux1)) and (norm(aux3) >= norm(aux2))): + qface[:, nface*2] = aux3[:, 0] + + qface[:, nface*2] = qface[:, nface*2]/norm(qface[:, nface*2]) + + qface[:, (nface*2)+1] = crossprod(n_faces[:, nface], + qface[:, nface*2])[:, 0] + + qface[:, (nface*2)+1] = (qface[:, (nface*2)+1] / + norm(qface[:, (nface*2)+1])) + + # Computation of jacob^-1 + gr = np.eye(3, dtype=np.float) + invjj = inv(jacob) + GR = np.matmul(invjj.transpose(), np.matmul(gr, invjj)) + + qface1_ref = np.matmul(jacob, qface[:, [0, 1]]) + qface2_ref = np.matmul(jacob, qface[:, [2, 3]]) + qface3_ref = np.matmul(jacob, qface[:, [4, 5]]) + qface4_ref = np.matmul(jacob, qface[:, [6, 7]]) + + return qface1_ref, qface2_ref, qface3_ref, qface4_ref, invjj, GR + + +def aristaMappingTetrahedral(edge_vertices_old, edge_vertices_new): + ''' Mapping edges of a real element defined by edge_vertices to a reference + element por edge_vertices_ref. Furthermore, compute relations between + "tau" tangential vectors to edges. + + :param int-array edge_vertices_old: edges definition based on + vertices (old). + :param int-array edge_vertices_new: edges definition based on + vertices (new). + :return: signs of unitary tangential vectors for each edge and + edges mapping. + + .. note: References:\n + Amor-Martin, A., Garcia-Castillo, L. E., & Garcia-Doñoro, D. D. + (2016). Second-order Nédélec curl-conforming prismatic element + for computational electromagnetics. IEEE Transactions on + Antennas and Propagation, 64(10), 4384-4395. + + Example: + tau_sign = [-1 1 1 1 1 1] sign of tau of (old) edge 1 is on opposite + direction. Note that old edge 1 will be + new edge 3 (see arista_mapping below) + + + arista_mapping=[-3 2 1 4 5 6] new edge 1 is old edge 3 The dof of + edge are interchanged, i.e., the dof + of the old edge 3 are placed + interchanged in new edge 1. + ''' + # Number of edges + size = edge_vertices_old.shape + numaristas = size[0] + size = edge_vertices_new.shape + numaristas2 = size[0] + + # Use 1-based indexes on edge_vertices_new + edge_vertices_new += np.int(1) + + # Allocate + tau_sign = np.ones(numaristas, dtype=np.int) + arista_mapping = np.zeros(numaristas, dtype=np.int) + + edge_vertices_old_abs = np.abs(edge_vertices_old) + edge_vertices_new_abs = np.abs(edge_vertices_new) + + # Previous validations + # Dimensional coherence + if numaristas != numaristas2: + raise ValueError('Dimensions of edge_vertices_old and ' + + 'edge_vertices_new are incoherence') + + # Signs coherence + if not (np.array_equal(np.sign(edge_vertices_old[:, 0]), + np.sign(edge_vertices_old[:, 1]))): + raise ValueError('Signs of edge_vertices_old are incoherence') + + if not (np.array_equal(np.sign(edge_vertices_new[:, 0]), + np.sign(edge_vertices_new[:, 1]))): + raise ValueError('Signs of edge_vertices_new are incoherence') + + for new_iedge in np.arange(numaristas): + new = edge_vertices_new[new_iedge, :] + new_abs = edge_vertices_new_abs[new_iedge, :] + new_sign = np.sign(new[0]) + + for old_iedge in np.arange(numaristas): + old = edge_vertices_old[old_iedge, :] + old_abs = edge_vertices_old_abs[old_iedge, :] + old_sign = np.sign(old[0]) + + # Check vertices coincidence (regardless of order or sign) + if (np.array_equal(new_abs, old_abs) or + np.array_equal(new_abs, np.flip(old_abs, axis=0))): + if (new_sign != old_sign): + tau_sign[old_iedge] = -tau_sign[old_iedge] + + if (np.array_equal(new_abs, old_abs)): + # Coincidence in order too + arista_mapping[new_iedge] = old_iedge + else: + # vertices ordering changed => sign '-' in arista_mapping + # => change sign in tau of + # old edge + arista_mapping[new_iedge] = -old_iedge + tau_sign[old_iedge] = -tau_sign[old_iedge] + + # Subsequent validations + # In arista_mapping must be all edge numbers and no repetitions + for iedge in np.arange(numaristas): + if np.where(np.abs(arista_mapping) == iedge)[0].size == 0: + raise ValueError('arista_mapping does not contain arista ' + + iedge) + + if is_duplicate_entry(np.abs(arista_mapping)) != 0: + raise ValueError('arista_mapping has duplicated values') + + return tau_sign, arista_mapping + + +def faceMappingTetrahedral(face_vertices_old, face_vertices_new, stage): + ''' Compute mapping between faces of a tetrahedron with face_vertices to a + tetrahedron with faces_vertices_ref. + + :param int-array face_vertices_old: faces definition based on + vertices (old). + :param int-array face_vertices_new: faces definition based on + vertices (new). + :param int face: number of stage of mapping (1 or 2). + :return: signs of unitary tangential vectors for each face and + faces mapping. + + .. note: References:\n + Amor-Martin, A., Garcia-Castillo, L. E., & Garcia-Doñoro, D. D. + (2016). Second-order Nédélec curl-conforming prismatic element + for computational electromagnetics. IEEE Transactions on + Antennas and Propagation, 64(10), 4384-4395. + ''' + # Number of faces + size = face_vertices_old.shape + numfaces = size[0] + size = face_vertices_new.shape + numfaces2 = size[0] + + # Use 1-based indexes on face_vertices_old or face_vertices_new + if stage == 1: # Mapping of first stage + face_vertices_old += np.int(1) + elif stage == 2: # Mapping of second stage + pass + else: + raise ValueError('Mapping stage = ', stage, ' not supported.') + + # Allocate + face_mapping = np.zeros(numfaces, dtype=np.int) + vertex_shift = np.zeros(numfaces, dtype=np.int) + + face_vertices_old_abs = np.abs(face_vertices_old) + face_vertices_new_abs = np.abs(face_vertices_new) + + # Previous validations + # Dimensional coherence + if numfaces != numfaces2: + raise ValueError('Dimensions of face_vertices_old and ' + + 'face_vertices_new are incoherence') + + # Mapping + nvertices_face = 3 + + for new_facenumber in np.arange(numfaces): + new = face_vertices_new[new_facenumber, :] + new_abs = face_vertices_new_abs[new_facenumber, :] + + for old_facenumber in np.arange(numfaces): + old = face_vertices_old[old_facenumber, :] + old_abs = face_vertices_old_abs[old_facenumber, :] + + old_abs_reversed = np.roll(np.flip(old_abs, axis=0), 1) + + # Ciclic shift until match + for cont_vertex_shift in np.arange(nvertices_face): + if (np.array_equal(new_abs, old_abs)): + face_mapping[new_facenumber] = old_facenumber + vertex_shift[new_facenumber] = cont_vertex_shift + break + if (np.array_equal(new_abs, old_abs_reversed)): + face_mapping[new_facenumber] = -old_facenumber + vertex_shift[new_facenumber] = cont_vertex_shift + break + + old_abs = np.roll(old_abs, 1) + old_abs_reversed = np.roll(old_abs_reversed, -1) + + # Subsequent validations + # In face_mapping must be all face numbers and no repetitions + for facenumber in np.arange(numfaces): + if np.where(np.abs(face_mapping) == facenumber)[0].size == 0: + raise ValueError('face_mapping does not contain arista ' + + facenumber) + + if is_duplicate_entry(np.abs(face_mapping)) != 0: + raise ValueError('face_mapping has duplicated values') + + return vertex_shift, face_mapping + + +def computeCoefficientsSecondOrder(qface1, qface2, qface3, qface4, + r_vertices_ref, edge_vertices, + face_vertices): + ''' Compute the coefficients for edge basis functions of second order + on reference element. + + :param float-array qface1: vector q on face 1. + :param float-array qface2: vector q on face 2. + :param float-array qface3: vector q on face 3. + :param float-array qface4: vector q on face 4. + :param float-array r_vertices: vertices of reference element. + :param int-array edge_vertices: initialization of edges connectivity. + :param int-array face_vertices: initialization of faces connectivity. + :return: coefficients. + :rtype: float. + + .. note: References:\n + Amor-Martin, A., Garcia-Castillo, L. E., & Garcia-Doñoro, D. D. + (2016). Second-order Nédélec curl-conforming prismatic element + for computational electromagnetics. IEEE Transactions on + Antennas and Propagation, 64(10), 4384-4395. + ''' + # First order ele + firstOrderEdgeElement = 6 + + # Second order ele + secondOrderEdgeElement = 20 + + # Absolute values for edges numbering + edge_vertices_abs = np.abs(edge_vertices) + + # Definition of tangetial unitary vectors for each edge (2 dofs per edge) + taui = np.zeros((3, firstOrderEdgeElement*2), dtype=np.float) + + for iedge in np.arange(firstOrderEdgeElement): + taui[:, iedge*2] = (r_vertices_ref[:, edge_vertices_abs[iedge, 1]] - + r_vertices_ref[:, edge_vertices_abs[iedge, 0]]) + taui[:, iedge*2+1] = taui[:, 2*iedge] + + # Normalization of taui by using edges length + length = np.zeros(firstOrderEdgeElement, dtype=np.float) + + for iedge in np.arange(firstOrderEdgeElement): + length[iedge] = norm(taui[:, iedge*2+1]) + taui[:, iedge*2: 2*iedge+2] = taui[:, iedge*2: 2*iedge+2]/length[iedge] + if (np.sum(np.sign(edge_vertices[iedge, :]+1)) == -2): + taui[:, iedge*2: 2*iedge+2] = -taui[:, iedge*2: 2*iedge+2] + elif (np.sum(np.sign(edge_vertices[iedge, :]+1)) == 0): + raise ValueError('The signs of ', iedge, ' are incorrect.') + + # Definition of normal vectors to faces + n1 = np.array([0, 0, -1], dtype=np.float) + n1 = n1/norm(n1) + n2 = np.array([-1, 0, 0], dtype=np.float) + n2 = n2/norm(n2) + n3 = np.array([0, -1, 0], dtype=np.float) + n3 = n3/norm(n3) + n4 = np.array([1, 1, 1], dtype=np.float) + n4 = n4/norm(n4) + + # Allocate + matrix = np.zeros((secondOrderEdgeElement, secondOrderEdgeElement), + dtype=np.float) + + # ----- Auxiliar definition of edges ----- + # We define the edges and their dof according to a predefined direction + # given by edge_vertices_aux. Then at the end the necessary changes are + # made so that the dof of the edges adhere to the definition given by + # edge_vertices. Here we use a 1-based indexes. + edge_vertices_aux = np.array([[1, 2], + [2, 3], + [-3, -1], + [1, 4], + [2, 4], + [3, 4]], dtype=np.int) + # Edges length + LengthAris = length + + # Integrals + # Int edge 1 (node 1 --> 2) + aux_x_L1 = LengthAris[0]*np.array([1./2., 1./6., 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + dtype=np.float) + aux_x_L2 = LengthAris[0]*np.array([1./2., 1./3., 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + dtype=np.float) + matrix[0, :] = aux_x_L1 # Dof 1 associated to L1 + matrix[1, :] = aux_x_L2 # Dof 2 associated to L2 + + del aux_x_L1, aux_x_L2 + + aux_x_L2 = LengthAris[1]*np.array([1./2., 1./3., 1./6., 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1./12., 0, -1./12., 0, 0, 0, + 0, 0], dtype=np.float) + aux_y_L2 = LengthAris[1]*np.array([0, 0, 0, 0, 1./2., 1./3., 1./6., 0, 0, + 0, 0, 0, -1./12., 0, 1./4., 0, 0, 0, + 0, 0], dtype=np.float) + aux_x_L3 = LengthAris[1]*np.array([1./2., 1./6., 1./3., 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1./4., 0, -1./12., 0, 0, 0, + 0, 0], dtype=np.float) + aux_y_L3 = LengthAris[1]*np.array([0, 0, 0, 0, 1./2., 1./6., 1./3., 0, 0, + 0, 0, 0, -1./12., 0, 1./12., 0, 0, 0, + 0, 0], dtype=np.float) + + # Dof 3 associated to L2 + matrix[2, :] = (1./LengthAris[1])*(-aux_x_L2+aux_y_L2) + # Dof 4 associated to L3 + matrix[3, :] = (1./LengthAris[1])*(-aux_x_L3+aux_y_L3) + + del aux_x_L2, aux_y_L2, aux_x_L3, aux_y_L3 + + # Int edge 3 (node 1 --> 3) + aux_y_L1 = LengthAris[2]*np.array([0, 0, 0, 0, 1./2., 0, 1./6., 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0], + dtype=np.float) + aux_y_L3 = LengthAris[2]*np.array([0, 0, 0, 0, 1./2., 0, 1./3., 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0], + dtype=np.float) + matrix[4, :] = aux_y_L3 # Dof 5 associated to L3 + matrix[5, :] = aux_y_L1 # Dof 6 associated to L1 + + del aux_y_L1, aux_y_L3 + + # Int edge 4 (node 1 --> 4) + aux_z_L1 = LengthAris[3]*np.array([0, 0, 0, 0, 0, 0, 0, 0, 1./2., 0, 0, + 1./6., 0, 0, 0, 0, 0, 0, 0, 0], + dtype=np.float) + aux_z_L4 = LengthAris[3]*np.array([0, 0, 0, 0, 0, 0, 0, 0, 1./2., 0, 0, + 1./3., 0, 0, 0, 0, 0, 0, 0, 0], + dtype=np.float) + matrix[6, :] = aux_z_L1 # Dof 7 associated to L1 + matrix[7, :] = aux_z_L4 # Dof 8 associated to L4 + + del aux_z_L1, aux_z_L4 + + # Int edge 5 (vertices 2 --> 4) + aux_x_L2 = LengthAris[4]*np.array([1./2., 1./3., 0, 1./6., 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, -1./12., 1./12., 0, + 0, 0], dtype=np.float) + aux_z_L2 = LengthAris[4]*np.array([0, 0, 0, 0, 0, 0, 0, 0, 1./2., 1./3., + 0, 1./6., 0, 0, 0, 1./4., -1./12., 0, + 0, 0], dtype=np.float) + aux_x_L4 = LengthAris[4]*np.array([1./2., 1./6., 0, 1./3., 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, -1./12., 1./4., 0, 0, + 0], dtype=np.float) + aux_z_L4 = LengthAris[4]*np.array([0, 0, 0, 0, 0, 0, 0, 0, 1./2., 1./6., + 0, 1./3., 0, 0, 0, 1./12., -1./12., 0, + 0, 0], dtype=np.float) + + # Dof 9 associated to L2 + matrix[8, :] = (1./np.sqrt(2.))*(-aux_x_L2+aux_z_L2) + # Dof 10 associated to L4 + matrix[9, :] = (1./np.sqrt(2.))*(-aux_x_L4+aux_z_L4) + + del aux_x_L2, aux_z_L2, aux_x_L4, aux_z_L4 + + # Int edge 6 (nodes 3 --> 4) + aux_y_L3 = LengthAris[5]*np.array([0, 0, 0, 0, 1./2., 0, 1./3., 1./6., 0, + 0, 0, 0, 0, -1./12., 0, 0, 0, 1./12., + 0, 0], dtype=np.float) + aux_z_L3 = LengthAris[5]*np.array([0, 0, 0, 0, 0, 0, 0, 0, 1./2., 0, 1./3., + 1./6., 0., 1./4., 0, 0, 0, -1./12., 0, + 0], dtype=np.float) + aux_y_L4 = LengthAris[5]*np.array([0, 0, 0, 0, 1./2., 0, 1./6., 1./3., 0, + 0, 0, 0, 0, -1./12., 0, 0, 0, 1./4., + 0, 0], dtype=np.float) + aux_z_L4 = LengthAris[5]*np.array([0, 0, 0, 0, 0, 0, 0, 0, 1./2., 0, 1./6., + 1./3., 0, 1./12., 0, 0, 0, -1./12., 0, + 0], dtype=np.float) + + # Dof 11 associated to L3 + matrix[10, :] = (1./np.sqrt(2))*(-aux_y_L3+aux_z_L3) + # Dof 12 associated to L4 + matrix[11, :] = (1./np.sqrt(2))*(-aux_y_L4+aux_z_L4) + + del aux_y_L3, aux_z_L3, aux_y_L4, aux_z_L4 + + # Mapping DOF order + # A change of order of the dof (functions) of the edge is made to + # fit the given in edge_vertices + tau_sign, arista_mapping = aristaMappingTetrahedral(edge_vertices_aux, + edge_vertices) + + # Change sign 'tau' of edges + nodes_sign = np.reshape(np.ones((2, 1))*tau_sign, (12, 1), order='F') + tmp1 = nodes_sign*np.ones((1, secondOrderEdgeElement), dtype=np.float) + matrix[0:12, :] = np.multiply(tmp1, matrix[0:12, :]) + + # Change sign 'tau' of faces + nodes_mapping = np.reshape(np.array([[2*np.abs(arista_mapping)], + [2*np.abs(arista_mapping)+1]]), (12), order='F') + matrix[0:12, :] = matrix[nodes_mapping, :] + + # Once ordered by edges, sort the dof of each edge according to the + # sign of arista_mapping + for iedge in np.arange(firstOrderEdgeElement): + if np.sign(arista_mapping[iedge]) == -1: + matrix[[2*iedge+1, 2*iedge], :] = matrix[[2*iedge, 2*iedge+1], :] + + del edge_vertices_aux, arista_mapping, tau_sign, nodes_sign, nodes_mapping + + # Conditions on faces int {n x Ni) .q} + # We define the faces and their dof according to a predefined sign given + # by face_vertices_aux. Then, the necessary changes are made so that the + # dof of the faces adhere to the definition given by edge_vertices. In + # this case, the dof are all associated to the barycenter of the face + # with what there is no such numbering sense as such and the only + # thing we will do is map the dof of some faces in the dof of other faces. + face_vertices_aux = np.array([[1, 2, 3], + [1, 3, 4], + [1, 4, 2], + [2, 4, 3]], dtype=np.int) + + # The qfacei that enter as a parameter of entry to the routine are + # referred to face numbering given by face_vertices. You have to map + # them to the number given by face_verttices_aux, which is what the + # calculations are programmed to do. + + # For second order, vertex_shift can be ignored + stage = 1 + _, face_mapping = faceMappingTetrahedral(face_vertices, face_vertices_aux, + stage) + + tmp1 = np.array([[2*np.abs(face_mapping)], [2*np.abs(face_mapping)+1]]) + q_mapping_aux = np.reshape(tmp1, (8), order='F') + + qface_all = np.hstack((qface1, qface2, qface3, qface4)) + qface_all = qface_all[:, q_mapping_aux] + qface1 = qface_all[:, [0, 1]] + qface2 = qface_all[:, [2, 3]] + qface3 = qface_all[:, [4, 5]] + qface4 = qface_all[:, [6, 7]] + + # The variables aux_x (i, :), aux_y (i, :), aux_z (i, :) are the + # integrals of Nx, Ny and Nz on the i side of the tetrahedron. The + # integral is a function of the coefficients in such a way that + # aux_x (ii,:)*coeff is the integral coefficient Nix on face ii. + + # Int face1 + aux_x = np.array([1./2., 1./6., 1./6., 0, 0, 0, 0, 0, 0, 0, 0, 0, 1./12., + 0, -1./24., 0, 0, 0, 0, 0], dtype=np.float) + aux_y = np.array([0, 0, 0, 0, 1./2., 1./6., 1./6., 0, 0, 0, 0, 0, -1./24., + 0, 1./12., 0, 0, 0, 0, 0], dtype=np.float) + aux_z = np.zeros(secondOrderEdgeElement) # No considered + + q1 = qface1[:, 0] + q2 = qface1[:, 1] + matrix[12, :] = ((q1[1]*n1[2]-q1[2]*n1[1])*aux_x + + (q1[2]*n1[0]-q1[0]*n1[2])*aux_y + + (q1[0]*n1[1]-q1[1]*n1[0])*aux_z) + matrix[13, :] = ((q2[1]*n1[2]-q2[2]*n1[1])*aux_x + + (q2[2]*n1[0]-q2[0]*n1[2])*aux_y + + (q2[0]*n1[1]-q2[1]*n1[0])*aux_z) + + del aux_x, aux_y, aux_z + + # Int face2 + aux_x = np.zeros(secondOrderEdgeElement) # No considered + aux_y = np.array([0, 0, 0, 0, 1./2., 0, 1./6., 1./6., 0, 0, 0, 0, 0, + -1./24., 0, 0, 0, 1./12., 0, 0], dtype=np.float) + aux_z = np.array([0, 0, 0, 0, 0, 0, 0, 0, 1./2., 0, 1./6., 1./6., 0, + 1./12., 0, 0, 0, -1./24., 0, 0], dtype=np.float) + + q1 = qface2[:, 0] + q2 = qface2[:, 1] + matrix[14, :] = ((q1[1]*n2[2]-q1[2]*n2[1])*aux_x + + (q1[2]*n2[0]-q1[0]*n2[2])*aux_y + + (q1[0]*n2[1]-q1[1]*n2[0])*aux_z) + matrix[15, :] = ((q2[1]*n2[2]-q2[2]*n2[1])*aux_x + + (q2[2]*n2[0]-q2[0]*n2[2])*aux_y + + (q2[0]*n2[1]-q2[1]*n2[0])*aux_z) + + del aux_x, aux_y, aux_z + + # Int face3 + aux_x = np.array([1./2., 1./6., 0, 1./6., 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, -1./24., 1./12., 0, 0, 0], dtype=np.float) + aux_y = np.zeros(secondOrderEdgeElement) # No considered + aux_z = np.array([0, 0, 0, 0, 0, 0, 0, 0, 1./2., 1./6., 0, 1./6., 0, 0, 0, + 1./12., -1./24., 0, 0, 0], dtype=np.float) + + q1 = qface3[:, 0] + q2 = qface3[:, 1] + matrix[16, :] = ((q1[1]*n3[2]-q1[2]*n3[1])*aux_x + + (q1[2]*n3[0]-q1[0]*n3[2])*aux_y + + (q1[0]*n3[1]-q1[1]*n3[0])*aux_z) + matrix[17, :] = ((q2[1]*n3[2]-q2[2]*n3[1])*aux_x + + (q2[2]*n3[0]-q2[0]*n3[2])*aux_y + + (q2[0]*n3[1]-q2[1]*n3[0])*aux_z) + + del aux_x, aux_y, aux_z + + # Int face4 + # Here (x,y,z) are L1, L2, L3 + area_face4 = np.sqrt(3.)/2. + + aux_x = 2.*area_face4*np.array([1./2., 1./6., 1./6., 1./6., 0, 0, 0, 0, + 0, 0, 0, 0, 1./12., 0, -1./24., -1./24., + 1./12., 0, 1./24., 0], dtype=np.float) + aux_y = 2.*area_face4*np.array([0, 0, 0, 0, 1./2., 1./6., 1./6., 1./6., 0, + 0, 0, 0, -1./24., -1./24., 1./12., 0, 0, + 1./12., -1./24., 1./24.], dtype=np.float) + aux_z = 2.*area_face4*np.array([0, 0, 0, 0, 0, 0, 0, 0, 1./2., 1./6., + 1./6., 1./6., 0, 1./12., 0, 1./12., + -1./24., -1./24., 0, -1./24], + dtype=np.float) + + q1 = qface4[:, 0] + q2 = qface4[:, 1] + matrix[18, :] = ((q1[1]*n4[2]-q1[2]*n4[1])*aux_x + + (q1[2]*n4[0]-q1[0]*n4[2])*aux_y + + (q1[0]*n4[1]-q1[1]*n4[0])*aux_z) + matrix[19, :] = ((q2[1]*n4[2]-q2[2]*n4[1])*aux_x + + (q2[2]*n4[0]-q2[0]*n4[2])*aux_y + + (q2[0]*n4[1]-q2[1]*n4[0])*aux_z) + + del aux_x, aux_y, aux_z + + # vertex_shift can be ignored for the case of second order since the + # dof are associated with barycenter of the face and not a certain + # face vertice + stage = 2 + _, face_mapping = faceMappingTetrahedral(face_vertices_aux, face_vertices, + stage) + + tmp1 = np.array([[2*np.abs(face_mapping)], [2*np.abs(face_mapping)+1]]) + nodes_mapping = np.int(12) + np.reshape(tmp1, (8), order='F') + + matrix[12:20, :] = matrix[nodes_mapping, :] + + del face_vertices_aux, face_mapping, nodes_mapping, qface_all + + # computation of the second member: one column for each Ni + secm = np.zeros((secondOrderEdgeElement, secondOrderEdgeElement), + dtype=np.float) + for ii in np.arange(secondOrderEdgeElement): + if ii <= 11: + secm[ii, ii] = 1. + elif ii > 11: + secm[ii, ii] = 1. + + # Dual basis computation. Obtaining polynomial coefficients by resolution + # of the system: matrix * {coef} = {secm} + coef = lstsq(matrix, secm, rcond=None)[0] + + # Coefficients EPS --> 0 + EPS = 1.e-12 + aux1, aux2 = np.where(np.abs(coef) < EPS) + size = aux1.shape + n = size[0] + + for kk in np.arange(n): + coef[aux1[kk], aux2[kk]] = 0. + + a1 = coef[0, :] + a2 = coef[1, :] + a3 = coef[2, :] + a4 = coef[3, :] + b1 = coef[4, :] + b2 = coef[5, :] + b3 = coef[6, :] + b4 = coef[7, :] + c1 = coef[8, :] + c2 = coef[9, :] + c3 = coef[10, :] + c4 = coef[11, :] + D = coef[12, :] + E = coef[13, :] + F = coef[14, :] + G = coef[15, :] + H = coef[16, :] + II = coef[17, :] + JJ = coef[18, :] + K = coef[19, :] + + return (a1, a2, a3, a4, b1, b2, b3, b4, c1, + c2, c3, c4, D, E, F, G, H, II, JJ, K) + + +def NiTetrahedralSecondOrder(a1, a2, a3, a4, b1, b2, b3, b4, c1, c2, c3, c4, + D, E, F, G, H, II, JJ, K, x, y, z, r): + ''' Computation of Ni (Nedelec basis functions of second order) in a + tetrahedral element with vertices (x,y,z) for point r. + + :param float coefficients: a1,a2,a3,a4,b1,b2,b3,b4,c1,c2,c3,c4,D,E, + F,G,H,II,JJ,K. + :param float-array x: x-coordinates of reference element. + :param float-array y: y-coordinates of reference element. + :param float-array z: z-coordinates of reference element. + :param float-array r: xyz coordinates of the evaluation point. + :return: basis nedelec funcions of second order. + :rtype: ndarray. + + .. note: References:\n + Amor-Martin, A., Garcia-Castillo, L. E., & Garcia-Doñoro, D. D. + (2016). Second-order Nédélec curl-conforming prismatic element + for computational electromagnetics. IEEE Transactions on + Antennas and Propagation, 64(10), 4384-4395. + ''' + # Number of dimensions + nDimensions = 3 + + # Second order ele + secondOrderEdgeElement = 20 + + # Initialization + coef = np.zeros((secondOrderEdgeElement, secondOrderEdgeElement), + dtype=np.float) + + coef[0, :] = a1 + coef[1, :] = a2 + coef[2, :] = a3 + coef[3, :] = a4 + coef[4, :] = b1 + coef[5, :] = b2 + coef[6, :] = b3 + coef[7, :] = b4 + coef[8, :] = c1 + coef[9, :] = c2 + coef[10, :] = c3 + coef[11, :] = c4 + coef[12, :] = D + coef[13, :] = E + coef[14, :] = F + coef[15, :] = G + coef[16, :] = H + coef[17, :] = II + coef[18, :] = JJ + coef[19, :] = K + + # Computation on reference element + L = cartesianToVolumetricCoordinates(x, y, z, r) + + xref = np.array([0, 1, 0, 0], dtype=np.float) + yref = np.array([0, 0, 1, 0], dtype=np.float) + zref = np.array([0, 0, 0, 1], dtype=np.float) + + rref = np.zeros(3, dtype=np.float) + rref[0] = np.dot(L, xref) + rref[1] = np.dot(L, yref) + rref[2] = np.dot(L, zref) + + aux_x = np.array([1, rref[0], rref[1], rref[2], 0, 0, 0, 0, 0, 0, 0, + 0, rref[1]**2, 0, -rref[0]*rref[1], -rref[0]*rref[2], + rref[2]**2, 0, rref[1]*rref[2], 0], dtype=np.float) + + aux_y = np.array([0, 0, 0, 0, 1, rref[0], rref[1], rref[2], 0, 0, 0, 0, + -rref[0]*rref[1], -rref[1]*rref[2], rref[0]**2, 0, 0, + rref[2]**2, -rref[0]*rref[2], rref[0]*rref[2]], + dtype=np.float) + + aux_z = np.array([0, 0, 0, 0, 0, 0, 0, 0, 1, rref[0], rref[1], rref[2], + 0, rref[1]**2, 0, rref[0]**2, -rref[0]*rref[2], + -rref[1]*rref[2], 0, -rref[0]*rref[1]], dtype=np.float) + + # Allocate + Niref = np.zeros((nDimensions, secondOrderEdgeElement), dtype=np.float) + + Niref[0, :] = np.matmul(aux_x, coef) + Niref[1, :] = np.matmul(aux_y, coef) + Niref[2, :] = np.matmul(aux_z, coef) + + del aux_x, aux_y, aux_z + + # Vector element reference to the real element through the Jacobian + # Ni_real=([J]^-1)*Niref + + x21 = x[1]-x[0] + y21 = y[1]-y[0] + z21 = z[1]-z[0] + x31 = x[2]-x[0] + y31 = y[2]-y[0] + z31 = z[2]-z[0] + x41 = x[3]-x[0] + y41 = y[3]-y[0] + z41 = z[3]-z[0] + + jacob = np.array([[x21, y21, z21], [x31, y31, z31], + [x41, y41, z41]], dtype=np.float) + + Ni = lstsq(jacob, Niref, rcond=None)[0] + + return Ni + + +def nedelecBasisSecondOrder(a1, a2, a3, a4, b1, b2, b3, b4, c1, c2, c3, c4, D, + E, F, G, H, II, JJ, K, ref_ele, points): + ''' This function computes the basis nedelec functions of second order + for a set of points in a given tetrahedral element. + + :param float coefficients: a1,a2,a3,a4,b1,b2,b3,b4,c1,c2,c3,c4, + D,E,F,G,H,II,JJ,K. + :param float-array ref_ele: nodal coordinates of reference element. + :param float-array points: spatial coordinates of the evaluation points. + :return: basis nedelec functions of second order. + :rtype: ndarray. + + .. note: References:\n + Amor-Martin, A., Garcia-Castillo, L. E., & Garcia-Doñoro, D. D. + (2016). Second-order Nédélec curl-conforming prismatic element + for computational electromagnetics. IEEE Transactions on + Antennas and Propagation, 64(10), 4384-4395. + ''' + # Number of points + size = points.shape + if len(size) == 2: # More than one point + nPoints = size[1] + elif len(size) == 1: # One point + nPoints = 1 + + # Number of dimensions + nDimensions = 3 + + # Second order ele + secondOrderEdgeElement = 20 + + # Allocate + basis = np.zeros((nDimensions, secondOrderEdgeElement, nPoints), + dtype=np.float) + + # Get reference element coordinates + xref = ref_ele[0, :] + yref = ref_ele[1, :] + zref = ref_ele[2, :] + + # Compute nedelec basis functions of second order for all points + if nPoints == 1: # One point + for iPoint in np.arange(nPoints): + r = points + + # Basis funtions for iPoint| + basis[:, :, iPoint] = NiTetrahedralSecondOrder(a1, a2, a3, a4, + b1, b2, b3, b4, + c1, c2, c3, c4, + D, E, F, G, H, + II, JJ, K, xref, + yref, zref, r) + else: # More than one point + for iPoint in np.arange(nPoints): + r = points[:, iPoint] + + # Basis funtions for iPoint| + basis[:, :, iPoint] = NiTetrahedralSecondOrder(a1, a2, a3, a4, + b1, b2, b3, b4, + c1, c2, c3, c4, + D, E, F, G, H, + II, JJ, K, xref, + yref, zref, r) + + return basis + + +def computeMassMatrixSecondOrder(a1, a2, a3, a4, b1, b2, b3, b4, c1, c2, c3, + c4, D, E, F, G, H, II, JJ, K, ref_ele, GR, + signs, DetJacob, Wi, rx, ry, rz, ngaussP): + ''' Compute mass matrix for tetrahedral edge elements of second order. + + :param float coefficients: a1,a2,a3,a4,b1,b2,b3,b4,c1,c2,c3,c4,D,E,F,G, + H,II,JJ,K. + :param float-array ref_ele: nodal coordinates of reference element. + :param float-array GR: tensor. + :param int-array signs: dofs signs. + :param float DetJacob: determinant of the jacobian. + :param float Wi: gauss weigths. + :param float-array rx: x-coordinates of gauss points. + :param float-array ry: y-coordinates of gauss points. + :param float-array rz: z-coordinates of gauss points. + :param int ngaussP: number of gauss points. + :return: mass matrix for edge element of second order. + :rtype: ndarray. + + .. note: References:\n + Amor-Martin, A., Garcia-Castillo, L. E., & Garcia-Doñoro, D. D. + (2016). Second-order Nédélec curl-conforming prismatic element + for computational electromagnetics. IEEE Transactions on + Antennas and Propagation, 64(10), 4384-4395. + ''' + # Second order ele + secondOrderEdgeElement = 20 + + # Get reference element coordinates + xref = ref_ele[0, :] + yref = ref_ele[1, :] + zref = ref_ele[2, :] + + # Allocate + Me = np.zeros((secondOrderEdgeElement, secondOrderEdgeElement), + dtype=np.float) + + # Mass matrix computation + r = np.zeros(3, dtype=np.float) + for iPoint in np.arange(ngaussP): + r[0] = rx[iPoint] + r[1] = ry[iPoint] + r[2] = rz[iPoint] + + Ni = NiTetrahedralSecondOrder(a1, a2, a3, a4, b1, b2, b3, b4, + c1, c2, c3, c4, D, E, F, G, H, + II, JJ, K, xref, yref, zref, r) + nix = Ni[0, :] + niy = Ni[1, :] + niz = Ni[2, :] + + for ii in np.arange(secondOrderEdgeElement): + for jj in np.arange(secondOrderEdgeElement): + Me[ii, jj] += Wi[iPoint]*((GR[0, 0] * nix[ii]*nix[jj] + + GR[0, 1] * (nix[ii]*niy[jj] + + niy[ii]*nix[jj]) + + GR[1, 1] * niy[ii]*niy[jj] + + GR[0, 2] * (nix[ii]*niz[jj] + + niz[ii]*nix[jj]) + + GR[1, 2] * (niy[ii]*niz[jj] + + niz[ii]*niy[jj]) + + GR[2, 2] * niz[ii]*niz[jj]) * + signs[ii]*signs[jj]) + + Me = Me*DetJacob*(1./6.) + + return Me + + +def computeDerivativesSecondOrder(a2, a3, a4, b2, b3, b4, c2, c3, c4, + D, E, F, G, H, II, JJ, K, point): + ''' Compute partial derivatives of basis functions for tetrahedral edge + elements of second order (reference element) + + :param float coefficients: a2,a3,a4,b2,b3,b4,c2,c3,c4,D,E,F,G,H,II,JJ,K. + :param float-array point: coordinates of the gaussian point. + :return: partial derivatives for edge element of second order. + :rtype: ndarray. + + .. note: References:\n + Amor-Martin, A., Garcia-Castillo, L. E., & Garcia-Doñoro, D. D. + (2016). Second-order Nédélec curl-conforming prismatic element + for computational electromagnetics. IEEE Transactions on + Antennas and Propagation, 64(10), 4384-4395. + ''' + # Point coordinates + x = point[0] + y = point[1] + z = point[2] + + # dxNix + dxNix = a2 - F*y - G*z + + # dxNiy + dxNiy = b2 + 2.*F*x - D*y - JJ*z + K*z + + # dxNiz + dxNiz = c2 + 2.*G*x - K*y - H*z + + # dyNix + dyNix = a3 - F*x + 2.*D*y + JJ*z + + # dyNiy + dyNiy = b3 - D*x - E*z + + # dyNiz + dyNiz = c3 - K*x + 2.*E*y - II*z + + # dzNix + dzNix = a4 - G*x + JJ*y + 2.*H*z + + # dzNiy + dzNiy = b4 - JJ*x + K*x - E*y + 2.*II*z + + # dzNiz + dzNiz = c4 - H*x - II*y + + return dxNix, dxNiy, dxNiz, dyNix, dyNiy, dyNiz, dzNix, dzNiy, dzNiz + + +def computeStiffnessMatrixSecondOrder(a2, a3, a4, b2, b3, b4, c2, c3, c4, + D, E, F, G, H, II, JJ, K, invjj, signs, + DetJacob, Wi, rx, ry, rz, ngaussP): + ''' Compute stiffness matrix for tetrahedral edge elements of second order. + + :param float coefficients: a2,a3,a4,b2,b3,b4,c2,c3,c4,D,E,F,G,H,II,JJ,K. + :param float-array invjj: inverse jacobian. + :param int-array signs: dofs signs. + :param float DetJacob: determinant of the jacobian. + :param float Wi: gauss weigths. + :param float-array rx: x-coordinates of gauss points. + :param float-array ry: y-coordinates of gauss points. + :param float-array rz: z-coordinates of gauss points. + :param int ngaussP: number of gauss points. + :return: stiffness matrix for edge element of second order. + :rtype: ndarray. + + .. note: References:\n + Amor-Martin, A., Garcia-Castillo, L. E., & Garcia-Doñoro, D. D. + (2016). Second-order Nédélec curl-conforming prismatic element + for computational electromagnetics. IEEE Transactions on + Antennas and Propagation, 64(10), 4384-4395. + ''' + # Second order element + secondOrderEdgeElement = 20 + + # Allocate + Ke = np.zeros((secondOrderEdgeElement, secondOrderEdgeElement), + dtype=np.float) + + # Tensor computation + fr = np.eye(3, dtype=np.float) + invfr = inv(fr) + + tmp1 = np.array([[0, 0, 0], [0, 0, 1], [0, -1, 0]], dtype=np.float) + A = np.matmul(invjj.transpose(), np.matmul(tmp1, invjj)) + + tmp1 = np.array([[0, 0, -1], [0, 0, 0], [1, 0, 0]], dtype=np.float) + B = np.matmul(invjj.transpose(), np.matmul(tmp1, invjj)) + + tmp1 = np.array([[0, 1, 0], [-1, 0, 0], [0, 0, 0]], dtype=np.float) + C = np.matmul(invjj.transpose(), np.matmul(tmp1, invjj)) + + # Stiffness matrix computation + r = np.zeros(3, dtype=np.float) + for iPoint in np.arange(ngaussP): + r[0] = rx[iPoint] + r[1] = ry[iPoint] + r[2] = rz[iPoint] + + [_, dxNiy, dxNiz, + dyNix, _, dyNiz, + dzNix, dzNiy, _] = computeDerivativesSecondOrder(a2, a3, a4, b2, b3, + b4, c2, c3, c4, D, + E, F, G, H, + II, JJ, K, r) + + rotNix = (A[0, 1]*dxNiy + A[0, 2]*dxNiz - + A[0, 1]*dyNix + A[1, 2]*dyNiz - + A[0, 2]*dzNix - A[1, 2]*dzNiy) + rotNiy = (B[0, 1]*dxNiy + B[0, 2]*dxNiz - + B[0, 1]*dyNix + B[1, 2]*dyNiz - + B[0, 2]*dzNix - B[1, 2]*dzNiy) + rotNiz = (C[0, 1]*dxNiy + C[0, 2]*dxNiz - + C[0, 1]*dyNix + C[1, 2]*dyNiz - + C[0, 2]*dzNix - C[1, 2]*dzNiy) + + for ii in np.arange(secondOrderEdgeElement): + for jj in np.arange(secondOrderEdgeElement): + Ke[ii, jj] += Wi[iPoint]*((invfr[0, 0]*rotNix[ii]*rotNix[jj] + + invfr[0, 1]*(rotNix[ii] * + rotNiy[jj] + + rotNiy[ii] * + rotNix[jj]) + + invfr[1, 1]*rotNiy[ii]*rotNiy[jj] + + invfr[0, 2]*(rotNix[ii] * + rotNiz[jj] + + rotNiz[ii] * + rotNix[jj]) + + invfr[1, 2]*(rotNiy[ii] * + rotNiz[jj] + + rotNiz[ii] * + rotNiy[jj]) + + invfr[2, 2]*rotNiz[ii]*rotNiz[jj]) * + signs[ii]*signs[jj]) + + Ke = Ke*DetJacob*(1./6.) + + return Ke + + +def polynomialProduct(poly1, poly2): + ''' Compute the product of two polynomials according to convention for + tetrahedral edge element of third order. + + :param float-array poly1: first polynomial to be computed. + :param float-array poly2: second polynomial to be computed. + :return: polynomial product. + :rtype: ndarray + ''' + + # Allocate + polProd = np.zeros((5, 5), dtype=np.float) + + for abs1 in np.arange(5): + for ord1 in np.arange(5-abs1): + for abs2 in np.arange(5): + for ord2 in np.arange(5-abs2): + if (poly1[abs1, ord1] != 0): + if (poly2[abs2, ord2] != 0): + indx1 = abs1+abs2 + indx2 = ord1+ord2 + polProd[indx1, indx2] = (polProd[indx1, indx2] + + poly1[abs1, ord1] * + poly2[abs2, ord2]) + return polProd + + +def componentProductPolynomial(nxNi_term, function_n, component_q, face): + ''' Compute the product for a given component of nxNi. + + :param float-array nxNi_term: matrix of coefficients. + :param float-array function_n: matrix function. + :param float-array component_q: id of the q component. + :param float-array face: face of the aforementioned parameters. + :return: product of nxNi, face and function_q. + :rtype: ndarray + ''' + + # Third order element + thirdOrderEdgeElement = 45 + + # Copy data as float + data_temp = nxNi_term.astype(np.float) + + if (component_q != 0): + for coefficient in np.arange(1, thirdOrderEdgeElement+1): + indx1 = (coefficient-1)*5+1 + indx2 = coefficient*5 + coeff_Ni = data_temp[:, indx1-1:indx2] + + coeff_aux_indep = np.zeros((5, 5), dtype=np.float) + if (function_n[0, 0] != 0): + output = 0 + for abs in np.arange(5): + for ord in np.arange(5-abs): + if (coeff_Ni[abs, ord] != 0): + coeff_aux_indep[abs, ord] = (function_n[0, 0] * + coeff_Ni[abs, ord] * + component_q) + if (face != 4): + output = 1 + break + if (output == 1): + break + + coeff_aux_abs = np.zeros((5, 5), dtype=np.float) + if (function_n[1, 0] != 0): + output = 0 + for abs in np.arange(5): + for ord in np.arange(5-abs): + if (coeff_Ni[abs, ord] != 0): + coeff_aux_abs[abs+1, ord] = (function_n[1, 0] * + coeff_Ni[abs, ord] * + component_q) + if (face != 4): + output = 1 + break + if (output == 1): + break + + coeff_aux_ord = np.zeros((5, 5), dtype=np.float) + if (function_n[0, 1] != 0): + output = 0 + for abs in np.arange(5): + for ord in np.arange(5-abs): + if (coeff_Ni[abs, ord] != 0): + coeff_aux_ord[abs, ord+1] = (function_n[0, 1] * + coeff_Ni[abs, ord] * + component_q) + if (face != 4): + output = 1 + break + if (output == 1): + break + + tmp = coeff_aux_indep + coeff_aux_abs + coeff_aux_ord + indx1_1 = ((coefficient-1)*5+1)-1 + indx2_1 = coefficient*5 + data_temp[:, indx1_1:indx2_1] = tmp + + product_result = data_temp + else: + product_result = np.zeros((5, 225), dtype=np.float) + + return product_result + + +def analyticIntegral(input_functions): + ''' Compute the integrals of the product of monomials in the reference + triangle. + + :param float-array input_functions: matrix of functions with coefficients + of Ni before integration process. + :return: integrals of the product of monomials in the reference triangle. + :rtype: ndarray + ''' + + # Third order element + thirdOrderEdgeElement = 45 + + integral_tab = np.array([[1/2, 1/6, 1/12, 1/20, 1/30], + [1/6, 1/24, 1/60, 1/120, 0], + [1/12, 1/60, 1/180, 0, 0], + [1/20, 1/120, 0, 0, 0], + [1/30, 0, 0, 0, 0]], dtype=np.float) + # Allocate + integral_res = np.zeros((thirdOrderEdgeElement), dtype=np.float) + + for coefficient in np.arange(1, thirdOrderEdgeElement+1): + indx1 = (coefficient-1)*5+1 + indx2 = coefficient*5 + aux = input_functions[:, indx1-1:indx2] + + for abs in np.arange(5): + for ord in np.arange(5-abs): + aux[abs, ord] = aux[abs, ord]*integral_tab[abs, ord] + + integral_res[coefficient-1] = 0 + + for abs in np.arange(5): + for ord in np.arange(5-abs): + integral_res[coefficient-1] = (integral_res[coefficient-1] + + aux[abs, ord]) + return integral_res + + +def computeCoefficientsThirdOrder(qface1, qface2, qface3, qface4): + ''' Compute the coefficients for edge basis functions of third order + on reference element. + + :param float-array qface1: vector q on face 1. + :param float-array qface2: vector q on face 2. + :param float-array qface3: vector q on face 3. + :param float-array qface4: vector q on face 4. + :return: coefficients. + :rtype: float. + + .. note: References:\n + Garcia-Castillo, L. E., Ruiz-Genovés, A. J., Gómez-Revuelto, I., + Salazar-Palma, M., & Sarkar, T. K. (2002). Third-order Nédélec + curl-conforming finite element. IEEE transactions on magnetics, + 38(5), 2370-2372. + ''' + # Third order element + thirdOrderEdgeElement = 45 + + # Definition of q functions + q_functions = np.array([[1, -1, 0, 0, 0, 1], + [-1, 0, 1, 0, 0, 0]], dtype=np.float) + + # Definition of q functions on faces + q_faces_ref = np.hstack((qface1, qface2, qface3, qface4)) + + # Definition of q functions on volume + q_volumen_ref = np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]], dtype=np.float) + + # Tangential unitary vectors for each edge (18 dofs) + Taui = np.array([[1, 1, 1, -1, -1, -1, 0, 0, 0, + 0, 0, 0, -1, -1, -1, 0, 0, 0], + [0, 0, 0, 1, 1, 1, -1, -1, -1, + 0, 0, 0, 0, 0, 0, -1, -1, -1], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + 1, 1, 1, 1, 1, 1, 1, 1]], dtype=np.float) + + # Normalization to unitary module + for idof in np.arange(18): + Taui[:, idof] = Taui[:, idof]/norm(Taui[:, idof]) + + # Edge length on reference element + length = np.array([1, np.sqrt(2), 1, 1, np.sqrt(2), np.sqrt(2)], + dtype=np.float) + + # Definition of the position of the 18 nodes of the edges. "a" is the + # distance to the center of the edge of the integration points for the + # interval [-1,1]. For our length edge Li => points located at + # "(Li / 2) * a" from the center of the edge. In the case that concerns + # us, tetrahedron of order 3, the third point of integration is the center + # of the edge. a = sqrt (3/5) in the case of third-order Gauss integration. + a = 0.7745966692 + + # Auxiliar varaible for spatial coordinates of the nodes + aux1 = (1.-a)/2. + aux2 = 1./2. + aux3 = (1.+a)/2. + + # Nodei contains coordinates of the 18 nodes on edges + Nodei = np.array([[aux1, aux2, aux3, aux3, aux2, aux1, + 0, 0, 0, 0, 0, 0, aux3, aux2, aux1, 0, 0, 0], + [0, 0, 0, aux1, aux2, aux3, aux3, aux2, aux1, + 0, 0, 0, 0, 0, 0, aux3, aux2, aux1], + [0, 0, 0, 0, 0, 0, 0, 0, 0, aux1, aux2, aux3, + aux1, aux2, aux3, aux1, aux2, aux3]], dtype=np.float) + + # Clear variables + del aux1, aux2 + + # Definition of the first 18 rows of the coefficient matrix of the system + # of equations to be presented. These equations are those that come from + # the imposition of the dofs of the edges. + matrix = np.zeros((thirdOrderEdgeElement, thirdOrderEdgeElement), + dtype=np.float) + + # Integrals over edges + for idof in np.arange(18): + tmp = np.hstack((Taui[0, idof], Nodei[0, idof]*Taui[0, idof], + Nodei[1, idof]*Taui[0, idof], + Nodei[2, idof]*Taui[0, idof], + (Nodei[0, idof]**2)*Taui[0, idof], + (Nodei[1, idof]**2)*Taui[0, idof], + (Nodei[2, idof]**2)*Taui[0, idof], + Nodei[0, idof]*Nodei[1, idof]*Taui[0, idof], + Nodei[0, idof]*Nodei[2, idof]*Taui[0, idof], + Nodei[1, idof]*Nodei[2, idof]*Taui[0, idof], + Taui[1, idof], Nodei[0, idof]*Taui[1, idof], + Nodei[1, idof]*Taui[1, idof], + Nodei[2, idof]*Taui[1, idof], + (Nodei[0, idof]**2)*Taui[1, idof], + (Nodei[1, idof]**2)*Taui[1, idof], + (Nodei[2, idof]**2)*Taui[1, idof], + Nodei[0, idof]*Nodei[1, idof]*Taui[1, idof], + Nodei[0, idof]*Nodei[2, idof]*Taui[1, idof], + Nodei[1, idof]*Nodei[2, idof]*Taui[1, idof], + Taui[2, idof], Nodei[0, idof]*Taui[2, idof], + Nodei[1, idof]*Taui[2, idof], + Nodei[2, idof]*Taui[2, idof], + (Nodei[0, idof]**2)*Taui[2, idof], + (Nodei[1, idof]**2)*Taui[2, idof], + (Nodei[2, idof]**2)*Taui[2, idof], + Nodei[0, idof]*Nodei[1, idof]*Taui[2, idof], + Nodei[0, idof]*Nodei[2, idof]*Taui[2, idof], + Nodei[1, idof]*Nodei[2, idof]*Taui[2, idof], + (Nodei[0, idof]**2)*Nodei[1, idof]*Taui[0, idof] - + (Nodei[0, idof]**3)*Taui[1, idof], + (Nodei[1, idof]**2)*Nodei[0, idof]*Taui[1, idof] - + (Nodei[1, idof]**3)*Taui[0, idof], + (Nodei[2, idof]**2)*Nodei[0, idof]*Taui[2, idof] - + (Nodei[2, idof]**3)*Taui[0, idof], + (Nodei[0, idof]**2)*Nodei[2, idof]*Taui[0, idof] - + (Nodei[0, idof]**3)*Taui[2, idof], + (Nodei[2, idof]**2)*Nodei[1, idof]*Taui[2, idof] - + (Nodei[2, idof]**3)*Taui[1, idof], + (Nodei[1, idof]**2)*Nodei[2, idof]*Taui[1, idof] - + (Nodei[1, idof]**3)*Taui[2, idof], + (Nodei[1, idof]**2)*Nodei[0, idof]*Taui[0, idof] - + (Nodei[0, idof]**2)*Nodei[1, idof]*Taui[1, idof], + (Nodei[2, idof]**2)*Nodei[0, idof]*Taui[0, idof] - + (Nodei[0, idof]**2)*Nodei[2, idof]*Taui[2, idof], + (Nodei[2, idof]**2)*Nodei[1, idof]*Taui[1, idof] - + (Nodei[1, idof]**2)*Nodei[2, idof]*Taui[2, idof], + Nodei[0, idof]*Nodei[1, idof] * + Nodei[2, idof]*Taui[0, idof] - + (Nodei[0, idof]**2)*Nodei[2, idof]*Taui[1, idof], + (Nodei[0, idof]**2)*Nodei[2, idof]*Taui[1, idof] - + (Nodei[0, idof]**2)*Nodei[1, idof]*Taui[2, idof], + Nodei[0, idof]*Nodei[1, idof] * + Nodei[2, idof]*Taui[1, idof] - + (Nodei[1, idof]**2)*Nodei[2, idof]*Taui[0, idof], + (Nodei[1, idof]**2)*Nodei[2, idof]*Taui[0, idof] - + (Nodei[1, idof]**2)*Nodei[0, idof]*Taui[2, idof], + Nodei[0, idof]*Nodei[1, idof] * + Nodei[2, idof]*Taui[2, idof] - + (Nodei[2, idof]**2)*Nodei[1, idof]*Taui[0, idof], + (Nodei[2, idof]**2)*Nodei[1, idof]*Taui[0, idof] - + (Nodei[2, idof]**2)*Nodei[0, idof]*Taui[1, idof])) + + matrix[idof, :] = tmp + + # Integrals over faces + # Definition of a matrix zeros (5,5) for the coefficients that do not + # appear in the definition of Ni particularized in each one of the faces + # and that can not be ignored so that when stacking matrices all result + # with the same size. + null_values = np.zeros((5, 5), dtype=np.float) + + # Definition of x-component for Ni on face 1 + Ni_x_face1_a1 = np.zeros((5, 5), dtype=np.float) + Ni_x_face1_a1[0, 0] = 1 + Ni_x_face1_a2 = np.zeros((5, 5), dtype=np.float) + Ni_x_face1_a2[1, 0] = 1 + Ni_x_face1_a3 = np.zeros((5, 5), dtype=np.float) + Ni_x_face1_a3[0, 1] = 1 + Ni_x_face1_a5 = np.zeros((5, 5), dtype=np.float) + Ni_x_face1_a5[2, 0] = 1 + Ni_x_face1_a6 = np.zeros((5, 5), dtype=np.float) + Ni_x_face1_a6[0, 2] = 1 + Ni_x_face1_a8 = np.zeros((5, 5), dtype=np.float) + Ni_x_face1_a8[1, 1] = 1 + + Ni_x_face1_D = np.zeros((5, 5), dtype=np.float) + Ni_x_face1_D[2, 1] = 1 + Ni_x_face1_E = np.zeros((5, 5), dtype=np.float) + Ni_x_face1_E[0, 3] = -1 + Ni_x_face1_J = np.zeros((5, 5), dtype=np.float) + Ni_x_face1_J[1, 2] = 1 + + Ni_x_face1 = np.hstack((Ni_x_face1_a1, Ni_x_face1_a2, Ni_x_face1_a3, + null_values, Ni_x_face1_a5, Ni_x_face1_a6, + null_values, Ni_x_face1_a8, null_values, + null_values, null_values, null_values, null_values, + null_values, null_values, null_values, null_values, + null_values, null_values, null_values, null_values, + null_values, null_values, null_values, null_values, + null_values, null_values, null_values, null_values, + null_values, Ni_x_face1_D, Ni_x_face1_E, + null_values, null_values, null_values, null_values, + Ni_x_face1_J, null_values, null_values, + null_values, null_values, null_values, null_values, + null_values, null_values)) + + # Definition of y-component for Ni on face 1 + Ni_y_face1_b1 = np.zeros((5, 5), dtype=np.float) + Ni_y_face1_b1[0, 0] = 1 + Ni_y_face1_b2 = np.zeros((5, 5), dtype=np.float) + Ni_y_face1_b2[1, 0] = 1 + Ni_y_face1_b3 = np.zeros((5, 5), dtype=np.float) + Ni_y_face1_b3[0, 1] = 1 + Ni_y_face1_b5 = np.zeros((5, 5), dtype=np.float) + Ni_y_face1_b5[2, 0] = 1 + Ni_y_face1_b6 = np.zeros((5, 5), dtype=np.float) + Ni_y_face1_b6[0, 2] = 1 + Ni_y_face1_b8 = np.zeros((5, 5), dtype=np.float) + Ni_y_face1_b8[1, 1] = 1 + + Ni_y_face1_D = np.zeros((5, 5), dtype=np.float) + Ni_y_face1_D[3, 0] = -1 + Ni_y_face1_E = np.zeros((5, 5), dtype=np.float) + Ni_y_face1_E[1, 2] = 1 + Ni_y_face1_J = np.zeros((5, 5), dtype=np.float) + Ni_y_face1_J[2, 1] = -1 + + Ni_y_face1 = np.hstack((null_values, null_values, null_values, null_values, + null_values, null_values, null_values, null_values, + null_values, null_values, Ni_y_face1_b1, + Ni_y_face1_b2, Ni_y_face1_b3, null_values, + Ni_y_face1_b5, Ni_y_face1_b6, null_values, + Ni_y_face1_b8, null_values, null_values, + null_values, null_values, null_values, null_values, + null_values, null_values, null_values, null_values, + null_values, null_values, Ni_y_face1_D, + Ni_y_face1_E, null_values, null_values, + null_values, null_values, Ni_y_face1_J, + null_values, null_values, null_values, null_values, + null_values, null_values, null_values, + null_values)) + + # Definition of y-component for Ni on face 2 + Ni_y_face2_b1 = np.zeros((5, 5), dtype=np.float) + Ni_y_face2_b1[0, 0] = 1 + Ni_y_face2_b3 = np.zeros((5, 5), dtype=np.float) + Ni_y_face2_b3[1, 0] = 1 + Ni_y_face2_b4 = np.zeros((5, 5), dtype=np.float) + Ni_y_face2_b4[0, 1] = 1 + Ni_y_face2_b6 = np.zeros((5, 5), dtype=np.float) + Ni_y_face2_b6[2, 0] = 1 + Ni_y_face2_b7 = np.zeros((5, 5), dtype=np.float) + Ni_y_face2_b7[0, 2] = 1 + Ni_y_face2_b10 = np.zeros((5, 5), dtype=np.float) + Ni_y_face2_b10[1, 1] = 1 + + Ni_y_face2_H = np.zeros((5, 5), dtype=np.float) + Ni_y_face2_H[0, 3] = -1 + Ni_y_face2_I = np.zeros((5, 5), dtype=np.float) + Ni_y_face2_I[2, 1] = 1 + Ni_y_face2_L = np.zeros((5, 5), dtype=np.float) + Ni_y_face2_L[1, 2] = 1 + + Ni_y_face2 = np.hstack((null_values, null_values, null_values, null_values, + null_values, null_values, null_values, null_values, + null_values, null_values, Ni_y_face2_b1, + null_values, Ni_y_face2_b3, Ni_y_face2_b4, + null_values, Ni_y_face2_b6, Ni_y_face2_b7, + null_values, null_values, Ni_y_face2_b10, + null_values, null_values, null_values, null_values, + null_values, null_values, null_values, null_values, + null_values, null_values, null_values, null_values, + null_values, null_values, Ni_y_face2_H, + Ni_y_face2_I, null_values, null_values, + Ni_y_face2_L, null_values, null_values, + null_values, null_values, null_values, + null_values)) + + # Definition of z-component for Ni on face 2 + Ni_z_face2_c1 = np.zeros((5, 5), dtype=np.float) + Ni_z_face2_c1[0, 0] = 1 + Ni_z_face2_c3 = np.zeros((5, 5), dtype=np.float) + Ni_z_face2_c3[1, 0] = 1 + Ni_z_face2_c4 = np.zeros((5, 5), dtype=np.float) + Ni_z_face2_c4[0, 1] = 1 + Ni_z_face2_c6 = np.zeros((5, 5), dtype=np.float) + Ni_z_face2_c6[2, 0] = 1 + Ni_z_face2_c7 = np.zeros((5, 5), dtype=np.float) + Ni_z_face2_c7[0, 2] = 1 + Ni_z_face2_c10 = np.zeros((5, 5), dtype=np.float) + Ni_z_face2_c10[1, 1] = 1 + + Ni_z_face2_H = np.zeros((5, 5), dtype=np.float) + Ni_z_face2_H[1, 2] = 1 + Ni_z_face2_I = np.zeros((5, 5), dtype=np.float) + Ni_z_face2_I[3, 0] = -1 + Ni_z_face2_L = np.zeros((5, 5), dtype=np.float) + Ni_z_face2_L[2, 1] = -1 + + Ni_z_face2 = np.hstack((null_values, null_values, null_values, null_values, + null_values, null_values, null_values, null_values, + null_values, null_values, null_values, null_values, + null_values, null_values, null_values, null_values, + null_values, null_values, null_values, null_values, + Ni_z_face2_c1, null_values, Ni_z_face2_c3, + Ni_z_face2_c4, null_values, Ni_z_face2_c6, + Ni_z_face2_c7, null_values, null_values, + Ni_z_face2_c10, null_values, null_values, + null_values, null_values, Ni_z_face2_H, + Ni_z_face2_I, null_values, null_values, + Ni_z_face2_L, null_values, null_values, + null_values, null_values, null_values, + null_values)) + + # Definition of x-component for Ni on face 3 + Ni_x_face3_a1 = np.zeros((5, 5), dtype=np.float) + Ni_x_face3_a1[0, 0] = 1 + Ni_x_face3_a2 = np.zeros((5, 5), dtype=np.float) + Ni_x_face3_a2[0, 1] = 1 + Ni_x_face3_a4 = np.zeros((5, 5), dtype=np.float) + Ni_x_face3_a4[1, 0] = 1 + Ni_x_face3_a5 = np.zeros((5, 5), dtype=np.float) + Ni_x_face3_a5[0, 2] = 1 + Ni_x_face3_a7 = np.zeros((5, 5), dtype=np.float) + Ni_x_face3_a7[2, 0] = 1 + Ni_x_face3_a9 = np.zeros((5, 5), dtype=np.float) + Ni_x_face3_a9[1, 1] = 1 + + Ni_x_face3_F = np.zeros((5, 5), dtype=np.float) + Ni_x_face3_F[3, 0] = -1 + Ni_x_face3_G = np.zeros((5, 5), dtype=np.float) + Ni_x_face3_G[1, 2] = 1 + Ni_x_face3_K = np.zeros((5, 5), dtype=np.float) + Ni_x_face3_K[2, 1] = 1 + + Ni_x_face3 = np.hstack((Ni_x_face3_a1, Ni_x_face3_a2, null_values, + Ni_x_face3_a4, Ni_x_face3_a5, null_values, + Ni_x_face3_a7, null_values, Ni_x_face3_a9, + null_values, null_values, null_values, null_values, + null_values, null_values, null_values, null_values, + null_values, null_values, null_values, null_values, + null_values, null_values, null_values, null_values, + null_values, null_values, null_values, null_values, + null_values, null_values, null_values, + Ni_x_face3_F, Ni_x_face3_G, null_values, + null_values, null_values, Ni_x_face3_K, + null_values, null_values, null_values, null_values, + null_values, null_values, null_values)) + + # Definition of z-component for Ni on face 3 + Ni_z_face3_c1 = np.zeros((5, 5), dtype=np.float) + Ni_z_face3_c1[0, 0] = 1 + Ni_z_face3_c2 = np.zeros((5, 5), dtype=np.float) + Ni_z_face3_c2[0, 1] = 1 + Ni_z_face3_c4 = np.zeros((5, 5), dtype=np.float) + Ni_z_face3_c4[1, 0] = 1 + Ni_z_face3_c5 = np.zeros((5, 5), dtype=np.float) + Ni_z_face3_c5[0, 2] = 1 + Ni_z_face3_c7 = np.zeros((5, 5), dtype=np.float) + Ni_z_face3_c7[2, 0] = 1 + Ni_z_face3_c9 = np.zeros((5, 5), dtype=np.float) + Ni_z_face3_c9[1, 1] = 1 + + Ni_z_face3_F = np.zeros((5, 5), dtype=np.float) + Ni_z_face3_F[2, 1] = 1 + Ni_z_face3_G = np.zeros((5, 5), dtype=np.float) + Ni_z_face3_G[0, 3] = -1 + Ni_z_face3_K = np.zeros((5, 5), dtype=np.float) + Ni_z_face3_K[1, 2] = -1 + + Ni_z_face3 = np.hstack((null_values, null_values, null_values, null_values, + null_values, null_values, null_values, null_values, + null_values, null_values, null_values, null_values, + null_values, null_values, null_values, null_values, + null_values, null_values, null_values, null_values, + Ni_z_face3_c1, Ni_z_face3_c2, null_values, + Ni_z_face3_c4, Ni_z_face3_c5, null_values, + Ni_z_face3_c7, null_values, Ni_z_face3_c9, + null_values, null_values, null_values, + Ni_z_face3_F, Ni_z_face3_G, null_values, + null_values, null_values, Ni_z_face3_K, + null_values, null_values, null_values, + null_values, null_values, + null_values, null_values)) + + # For the computation of the surface integral in the face 4, we are + # going to do it translating it to the calculation of the integral on the + # domain of its projection in the XY plane, and substituting z for the + # value in the surface, i.e., z = 1-xy, taking into account that the + # surface differential ds, is transformed into dxdy/cos (gamma), where + # gamma is the director cosine with the z axis. For z = 1-x-y + # cos(gamma)= 1/sqrt(3); With the help of the routine polynomialProduct + # we will develop all the terms. We will define first the equivalent + # terms of z(z), z^2 (z_2) and z^3 (z_3) that we will need. + z = np.zeros((5, 5), dtype=np.float) + z[0, 0] = 1 + z[1, 0] = -1 + z[0, 1] = -1 + z_2 = polynomialProduct(z, z) + z_3 = polynomialProduct(z_2, z) + + # Definition of x-component for Ni on face 4 + Ni_x_face4_a1 = np.zeros((5, 5), dtype=np.float) + Ni_x_face4_a1[0, 0] = 1 + Ni_x_face4_a2 = np.zeros((5, 5), dtype=np.float) + Ni_x_face4_a2[1, 0] = 1 + Ni_x_face4_a3 = np.zeros((5, 5), dtype=np.float) + Ni_x_face4_a3[0, 1] = 1 + Ni_x_face4_a4 = z + Ni_x_face4_a5 = np.zeros((5, 5), dtype=np.float) + Ni_x_face4_a5[2, 0] = 1 + Ni_x_face4_a6 = np.zeros((5, 5), dtype=np.float) + Ni_x_face4_a6[0, 2] = 1 + Ni_x_face4_a7 = z_2 + Ni_x_face4_a8 = np.zeros((5, 5), dtype=np.float) + Ni_x_face4_a8[1, 1] = 1 + + Ni_x_face4_a9 = np.zeros((5, 5), dtype=np.float) + Ni_x_face4_a9[1, 0] = 1 + Ni_x_face4_a9 = polynomialProduct(Ni_x_face4_a9, z) + + Ni_x_face4_a10 = np.zeros((5, 5), dtype=np.float) + Ni_x_face4_a10[0, 1] = 1 + Ni_x_face4_a10 = polynomialProduct(Ni_x_face4_a10, z) + + Ni_x_face4_D = np.zeros((5, 5), dtype=np.float) + Ni_x_face4_D[2, 1] = 1 + Ni_x_face4_E = np.zeros((5, 5), dtype=np.float) + Ni_x_face4_E[0, 3] = -1 + Ni_x_face4_F = -z_3 + + Ni_x_face4_G = np.zeros((5, 5), dtype=np.float) + Ni_x_face4_G[2, 0] = 1 + Ni_x_face4_G = polynomialProduct(Ni_x_face4_G, z) + + Ni_x_face4_J = np.zeros((5, 5), dtype=np.float) + Ni_x_face4_J[1, 2] = 1 + + Ni_x_face4_K = np.zeros((5, 5), dtype=np.float) + Ni_x_face4_K[1, 0] = 1 + Ni_x_face4_K = polynomialProduct(Ni_x_face4_K, z_2) + + Ni_x_face4_M = np.zeros((5, 5), dtype=np.float) + Ni_x_face4_M[1, 1] = 1 + Ni_x_face4_M = polynomialProduct(Ni_x_face4_M, z) + + Ni_x_face4_O = np.zeros((5, 5), dtype=np.float) + Ni_x_face4_O[0, 2] = 1 + Ni_x_face4_O = -polynomialProduct(Ni_x_face4_O, z) + + Ni_x_face4_P = np.zeros((5, 5), dtype=np.float) + Ni_x_face4_P[0, 2] = 1 + Ni_x_face4_P = polynomialProduct(Ni_x_face4_P, z) + + Ni_x_face4_Q = np.zeros((5, 5), dtype=np.float) + Ni_x_face4_Q[0, 1] = 1 + Ni_x_face4_Q = -polynomialProduct(Ni_x_face4_Q, z_2) + + Ni_x_face4_R = np.zeros((5, 5), dtype=np.float) + Ni_x_face4_R[0, 1] = 1 + Ni_x_face4_R = polynomialProduct(Ni_x_face4_R, z_2) + + Ni_x_face4 = np.hstack((Ni_x_face4_a1, Ni_x_face4_a2, Ni_x_face4_a3, + Ni_x_face4_a4, Ni_x_face4_a5, Ni_x_face4_a6, + Ni_x_face4_a7, Ni_x_face4_a8, Ni_x_face4_a9, + Ni_x_face4_a10, null_values, null_values, + null_values, null_values, null_values, null_values, + null_values, null_values, null_values, null_values, + null_values, null_values, null_values, null_values, + null_values, null_values, null_values, null_values, + null_values, null_values, Ni_x_face4_D, + Ni_x_face4_E, Ni_x_face4_F, Ni_x_face4_G, + null_values, null_values, Ni_x_face4_J, + Ni_x_face4_K, null_values, Ni_x_face4_M, + null_values, Ni_x_face4_O, Ni_x_face4_P, + Ni_x_face4_Q, Ni_x_face4_R)) + + # Definition of y-component for Ni on face 4 + Ni_y_face4_b1 = np.zeros((5, 5), dtype=np.float) + Ni_y_face4_b1[0, 0] = 1 + Ni_y_face4_b2 = np.zeros((5, 5), dtype=np.float) + Ni_y_face4_b2[1, 0] = 1 + Ni_y_face4_b3 = np.zeros((5, 5), dtype=np.float) + Ni_y_face4_b3[0, 1] = 1 + Ni_y_face4_b4 = z + Ni_y_face4_b5 = np.zeros((5, 5), dtype=np.float) + Ni_y_face4_b5[2, 0] = 1 + Ni_y_face4_b6 = np.zeros((5, 5), dtype=np.float) + Ni_y_face4_b6[0, 2] = 1 + Ni_y_face4_b7 = z_2 + Ni_y_face4_b8 = np.zeros((5, 5), dtype=np.float) + Ni_y_face4_b8[1, 1] = 1 + + Ni_y_face4_b9 = np.zeros((5, 5), dtype=np.float) + Ni_y_face4_b9[1, 0] = 1 + Ni_y_face4_b9 = polynomialProduct(Ni_y_face4_b9, z) + + Ni_y_face4_b10 = np.zeros((5, 5), dtype=np.float) + Ni_y_face4_b10[0, 1] = 1 + Ni_y_face4_b10 = polynomialProduct(Ni_y_face4_b10, z) + + Ni_y_face4_D = np.zeros((5, 5), dtype=np.float) + Ni_y_face4_D[3, 0] = -1 + Ni_y_face4_E = np.zeros((5, 5), dtype=np.float) + Ni_y_face4_E[1, 2] = 1 + Ni_y_face4_H = -z_3 + + Ni_y_face4_I = np.zeros((5, 5), dtype=np.float) + Ni_y_face4_I[0, 2] = 1 + Ni_y_face4_I = polynomialProduct(Ni_y_face4_I, z) + + Ni_y_face4_J = np.zeros((5, 5), dtype=np.float) + Ni_y_face4_J[2, 1] = -1 + + Ni_y_face4_L = np.zeros((5, 5), dtype=np.float) + Ni_y_face4_L[0, 1] = 1 + Ni_y_face4_L = polynomialProduct(Ni_y_face4_L, z_2) + + Ni_y_face4_M = np.zeros((5, 5), dtype=np.float) + Ni_y_face4_M[2, 0] = 1 + Ni_y_face4_M = -polynomialProduct(Ni_y_face4_M, z) + + Ni_y_face4_N = np.zeros((5, 5), dtype=np.float) + Ni_y_face4_N[2, 0] = 1 + Ni_y_face4_N = polynomialProduct(Ni_y_face4_N, z) + + Ni_y_face4_O = np.zeros((5, 5), dtype=np.float) + Ni_y_face4_O[1, 1] = 1 + Ni_y_face4_O = polynomialProduct(Ni_y_face4_O, z) + + Ni_y_face4_R = np.zeros((5, 5), dtype=np.float) + Ni_y_face4_R[1, 0] = 1 + Ni_y_face4_R = -polynomialProduct(Ni_y_face4_R, z_2) + + Ni_y_face4 = np.hstack((null_values, null_values, null_values, null_values, + null_values, null_values, null_values, null_values, + null_values, null_values, Ni_y_face4_b1, + Ni_y_face4_b2, Ni_y_face4_b3, Ni_y_face4_b4, + Ni_y_face4_b5, Ni_y_face4_b6, Ni_y_face4_b7, + Ni_y_face4_b8, Ni_y_face4_b9, Ni_y_face4_b10, + null_values, null_values, null_values, null_values, + null_values, null_values, null_values, null_values, + null_values, null_values, Ni_y_face4_D, + Ni_y_face4_E, null_values, null_values, + Ni_y_face4_H, Ni_y_face4_I, Ni_y_face4_J, + null_values, Ni_y_face4_L, Ni_y_face4_M, + Ni_y_face4_N, Ni_y_face4_O, null_values, + null_values, Ni_y_face4_R)) + + # Definition of z-component for Ni on face 4 + Ni_z_face4_c1 = np.zeros((5, 5), dtype=np.float) + Ni_z_face4_c1[0, 0] = 1 + Ni_z_face4_c2 = np.zeros((5, 5), dtype=np.float) + Ni_z_face4_c2[1, 0] = 1 + Ni_z_face4_c3 = np.zeros((5, 5), dtype=np.float) + Ni_z_face4_c3[0, 1] = 1 + Ni_z_face4_c4 = z + Ni_z_face4_c5 = np.zeros((5, 5), dtype=np.float) + Ni_z_face4_c5[2, 0] = 1 + Ni_z_face4_c6 = np.zeros((5, 5), dtype=np.float) + Ni_z_face4_c6[0, 2] = 1 + Ni_z_face4_c7 = z_2 + Ni_z_face4_c8 = np.zeros((5, 5), dtype=np.float) + Ni_z_face4_c8[1, 1] = 1 + + Ni_z_face4_c9 = np.zeros((5, 5), dtype=np.float) + Ni_z_face4_c9[1, 0] = 1 + Ni_z_face4_c9 = polynomialProduct(Ni_z_face4_c9, z) + + Ni_z_face4_c10 = np.zeros((5, 5), dtype=np.float) + Ni_z_face4_c10[0, 1] = 1 + Ni_z_face4_c10 = polynomialProduct(Ni_z_face4_c10, z) + + Ni_z_face4_F = np.zeros((5, 5), dtype=np.float) + Ni_z_face4_F[1, 0] = 1 + Ni_z_face4_F = polynomialProduct(Ni_z_face4_F, z_2) + + Ni_z_face4_G = np.zeros((5, 5), dtype=np.float) + Ni_z_face4_G[3, 0] = -1 + + Ni_z_face4_H = np.zeros((5, 5), dtype=np.float) + Ni_z_face4_H[0, 1] = 1 + Ni_z_face4_H = polynomialProduct(Ni_z_face4_H, z_2) + + Ni_z_face4_I = np.zeros((5, 5), dtype=np.float) + Ni_z_face4_I[0, 3] = -1 + + Ni_z_face4_K = np.zeros((5, 5), dtype=np.float) + Ni_z_face4_K[2, 0] = 1 + Ni_z_face4_K = -polynomialProduct(Ni_z_face4_K, z) + + Ni_z_face4_L = np.zeros((5, 5), dtype=np.float) + Ni_z_face4_L[0, 2] = 1 + Ni_z_face4_L = -polynomialProduct(Ni_z_face4_L, z) + + Ni_z_face4_N = np.zeros((5, 5), dtype=np.float) + Ni_z_face4_N[2, 1] = -1 + Ni_z_face4_P = np.zeros((5, 5), dtype=np.float) + Ni_z_face4_P[1, 2] = -1 + + Ni_z_face4_Q = np.zeros((5, 5), dtype=np.float) + Ni_z_face4_Q[1, 1] = 1 + Ni_z_face4_Q = polynomialProduct(Ni_z_face4_Q, z) + + Ni_z_face4 = np.hstack((null_values, null_values, null_values, null_values, + null_values, null_values, null_values, null_values, + null_values, null_values, null_values, null_values, + null_values, null_values, null_values, null_values, + null_values, null_values, null_values, null_values, + Ni_z_face4_c1, Ni_z_face4_c2, Ni_z_face4_c3, + Ni_z_face4_c4, Ni_z_face4_c5, Ni_z_face4_c6, + Ni_z_face4_c7, Ni_z_face4_c8, Ni_z_face4_c9, + Ni_z_face4_c10, null_values, null_values, + Ni_z_face4_F, Ni_z_face4_G, Ni_z_face4_H, + Ni_z_face4_I, null_values, Ni_z_face4_K, + Ni_z_face4_L, null_values, Ni_z_face4_N, + null_values, Ni_z_face4_P, Ni_z_face4_Q, + null_values)) + + # As a previous step to the integration, we must obtain the matrices + # of: n(normal to the face) x Ni(particularized in the face). Then, we + # can make the scalar product with the corresponding q's. + null_values2 = np.zeros((5, 225), dtype=np.float) + + n_face1xNi_face1 = np.vstack((Ni_y_face1, -Ni_x_face1, null_values2)) + n_face2xNi_face2 = np.vstack((null_values2, Ni_z_face2, -Ni_y_face2)) + n_face3xNi_face3 = np.vstack((-Ni_z_face3, null_values2, Ni_x_face3)) + n_face4xNi_face4 = np.vstack((Ni_z_face4 - Ni_y_face4, + Ni_x_face4 - Ni_z_face4, + Ni_y_face4 - Ni_x_face4)) + + n_facesxNi = np.vstack((n_face1xNi_face1, n_face2xNi_face2, + n_face3xNi_face3, n_face4xNi_face4)) + + for face in np.arange(1, 5): + for q in np.arange(1, 3): + for function_N in np.arange(1, 4): + # Three components of n_face(face)xNi particularized to face + indx1 = (face-1)*15+1 + indx2 = (face*15) + n_facexNi_face = n_facesxNi[indx1-1:indx2, :] + + # Auxiliar for integral + integral_aux = np.zeros((5, 225), dtype=np.float) + + for icomponent in np.arange(1, 4): + # Development of what I have to integrate in each face + # for each row of the matrix of the system of equations. + # n_facexNi_x*q_x+n_facexNi_y*q_y+n_facexNi_z*q_z + + # Each of the parameters and counters involved in the + # call to componentProductPolynomail are: + # n_facexNi (((icomponent-1)*5+1):(5*icomponent),:): One + # of the 3 components of n_facexNi in the face that + # concerns us. + # Functions_q(:,2*function-1:2*function): The function + # (2x2 matrix) that at that moment is multiplying to q. + # q_faces_ref(icomponent,2*(face-1)+q): One of the 3 + # components of the vector q. + v1 = n_facexNi_face[((icomponent-1)*5+1)-1: + (5*icomponent), :] + v2 = q_functions[:, (2*function_N-1)-1:2*function_N] + v3 = q_faces_ref[icomponent-1, (2*(face-1)+q)-1] + integral_aux += componentProductPolynomial(v1, v2, + v3, face) + + # Get row of the coefficient matrix of the system of equations. + # These equations are those that come from the imposition of + # the dofs of the edges. + row_matrix = 19+(face-1)*6+(function_N-1)*2+(q-1) + matrix[row_matrix-1, :] = analyticIntegral(integral_aux) + + # Integrals over volume + # Rows 43,44, and 45 of the coefficient matrix of the system of equations + # are linked to the definition of the dofs associated with the volume. + # We need again a matrix zeros(5,5) for the coefficients that do not + # appear in the definition of Ni and that can not be ignored so that when + # stacking matrices all result in the same size. We use "null_values" + # already defined for face integrals. + # For the computation of the volume integral, we perform the integral of + # Ni = f(x,y,z) between z(x,y)=0 and z(x,y)=1-xy, face 4 in the + # reference element. For this, we must integrate in z, which we do + # manually and then with the help of "polynomialProduct" + # develop all the terms. We will define first the term z^4(z_4) + # that we will need. The terms z(z), z^2(z_2) and z^3(z_3) have been + # defined previously for the integrals on face 4. + z_4 = polynomialProduct(z_3, z) + + # Definition of x-component of z-integral in Ni + Int_en_z_Ni_x_a1 = z + + Int_en_z_Ni_x_a2 = np.zeros((5, 5), dtype=np.float) + Int_en_z_Ni_x_a2[1, 0] = 1 + Int_en_z_Ni_x_a2 = polynomialProduct(Int_en_z_Ni_x_a2, z) + + Int_en_z_Ni_x_a3 = np.zeros((5, 5), dtype=np.float) + Int_en_z_Ni_x_a3[0, 1] = 1 + Int_en_z_Ni_x_a3 = polynomialProduct(Int_en_z_Ni_x_a3, z) + + Int_en_z_Ni_x_a4 = 1/2*z_2 + + Int_en_z_Ni_x_a5 = np.zeros((5, 5), dtype=np.float) + Int_en_z_Ni_x_a5[2, 0] = 1 + Int_en_z_Ni_x_a5 = polynomialProduct(Int_en_z_Ni_x_a5, z) + + Int_en_z_Ni_x_a6 = np.zeros((5, 5), dtype=np.float) + Int_en_z_Ni_x_a6[0, 2] = 1 + Int_en_z_Ni_x_a6 = polynomialProduct(Int_en_z_Ni_x_a6, z) + + Int_en_z_Ni_x_a7 = 1/3*z_3 + + Int_en_z_Ni_x_a8 = np.zeros((5, 5), dtype=np.float) + Int_en_z_Ni_x_a8[1, 1] = 1 + Int_en_z_Ni_x_a8 = polynomialProduct(Int_en_z_Ni_x_a8, z) + + Int_en_z_Ni_x_a9 = np.zeros((5, 5), dtype=np.float) + Int_en_z_Ni_x_a9[1, 0] = 1 + Int_en_z_Ni_x_a9 = 1/2*polynomialProduct(Int_en_z_Ni_x_a9, z_2) + + Int_en_z_Ni_x_a10 = np.zeros((5, 5), dtype=np.float) + Int_en_z_Ni_x_a10[0, 1] = 1 + Int_en_z_Ni_x_a10 = 1/2*polynomialProduct(Int_en_z_Ni_x_a10, z_2) + + Int_en_z_Ni_x_D = np.zeros((5, 5), dtype=np.float) + Int_en_z_Ni_x_D[2, 1] = 1 + Int_en_z_Ni_x_D = polynomialProduct(Int_en_z_Ni_x_D, z) + + Int_en_z_Ni_x_E = np.zeros((5, 5), dtype=np.float) + Int_en_z_Ni_x_E[0, 3] = 1 + Int_en_z_Ni_x_E = -polynomialProduct(Int_en_z_Ni_x_E, z) + + Int_en_z_Ni_x_F = -1/4*z_4 + + Int_en_z_Ni_x_G = np.zeros((5, 5), dtype=np.float) + Int_en_z_Ni_x_G[2, 0] = 1 + Int_en_z_Ni_x_G = 1/2*polynomialProduct(Int_en_z_Ni_x_G, z_2) + + Int_en_z_Ni_x_J = np.zeros((5, 5), dtype=np.float) + Int_en_z_Ni_x_J[1, 2] = 1 + Int_en_z_Ni_x_J = polynomialProduct(Int_en_z_Ni_x_J, z) + + Int_en_z_Ni_x_K = np.zeros((5, 5), dtype=np.float) + Int_en_z_Ni_x_K[1, 0] = 1 + Int_en_z_Ni_x_K = 1/3*polynomialProduct(Int_en_z_Ni_x_K, z_3) + + Int_en_z_Ni_x_M = np.zeros((5, 5), dtype=np.float) + Int_en_z_Ni_x_M[1, 1] = 1 + Int_en_z_Ni_x_M = 1/2*polynomialProduct(Int_en_z_Ni_x_M, z_2) + + Int_en_z_Ni_x_O = np.zeros((5, 5), dtype=np.float) + Int_en_z_Ni_x_O[0, 2] = 1 + Int_en_z_Ni_x_O = -1/2*polynomialProduct(Int_en_z_Ni_x_O, z_2) + + Int_en_z_Ni_x_P = np.zeros((5, 5), dtype=np.float) + Int_en_z_Ni_x_P[0, 2] = 1 + Int_en_z_Ni_x_P = 1/2*polynomialProduct(Int_en_z_Ni_x_P, z_2) + + Int_en_z_Ni_x_Q = np.zeros((5, 5), dtype=np.float) + Int_en_z_Ni_x_Q[0, 1] = 1 + Int_en_z_Ni_x_Q = -1/3*polynomialProduct(Int_en_z_Ni_x_Q, z_3) + + Int_en_z_Ni_x_R = np.zeros((5, 5), dtype=np.float) + Int_en_z_Ni_x_R[0, 1] = 1 + Int_en_z_Ni_x_R = 1/3*polynomialProduct(Int_en_z_Ni_x_R, z_3) + + Int_en_z_Ni_x = np.hstack((Int_en_z_Ni_x_a1, Int_en_z_Ni_x_a2, + Int_en_z_Ni_x_a3, Int_en_z_Ni_x_a4, + Int_en_z_Ni_x_a5, Int_en_z_Ni_x_a6, + Int_en_z_Ni_x_a7, Int_en_z_Ni_x_a8, + Int_en_z_Ni_x_a9, Int_en_z_Ni_x_a10, + null_values, null_values, null_values, + null_values, null_values, null_values, + null_values, null_values, null_values, + null_values, null_values, null_values, + null_values, null_values, null_values, + null_values, null_values, null_values, + null_values, null_values, Int_en_z_Ni_x_D, + Int_en_z_Ni_x_E, Int_en_z_Ni_x_F, + Int_en_z_Ni_x_G, null_values, null_values, + Int_en_z_Ni_x_J, Int_en_z_Ni_x_K, null_values, + Int_en_z_Ni_x_M, null_values, Int_en_z_Ni_x_O, + Int_en_z_Ni_x_P, Int_en_z_Ni_x_Q, + Int_en_z_Ni_x_R)) + + # Definition of y-component of z-integral in Ni + Int_en_z_Ni_y_b1 = z + + Int_en_z_Ni_y_b2 = np.zeros((5, 5), dtype=np.float) + Int_en_z_Ni_y_b2[1, 0] = 1 + Int_en_z_Ni_y_b2 = polynomialProduct(Int_en_z_Ni_y_b2, z) + + Int_en_z_Ni_y_b3 = np.zeros((5, 5), dtype=np.float) + Int_en_z_Ni_y_b3[0, 1] = 1 + Int_en_z_Ni_y_b3 = polynomialProduct(Int_en_z_Ni_y_b3, z) + + Int_en_z_Ni_y_b4 = 1/2*z_2 + + Int_en_z_Ni_y_b5 = np.zeros((5, 5), dtype=np.float) + Int_en_z_Ni_y_b5[2, 0] = 1 + Int_en_z_Ni_y_b5 = polynomialProduct(Int_en_z_Ni_y_b5, z) + + Int_en_z_Ni_y_b6 = np.zeros((5, 5), dtype=np.float) + Int_en_z_Ni_y_b6[0, 2] = 1 + Int_en_z_Ni_y_b6 = polynomialProduct(Int_en_z_Ni_y_b6, z) + + Int_en_z_Ni_y_b7 = 1/3*z_3 + + Int_en_z_Ni_y_b8 = np.zeros((5, 5), dtype=np.float) + Int_en_z_Ni_y_b8[1, 1] = 1 + Int_en_z_Ni_y_b8 = polynomialProduct(Int_en_z_Ni_y_b8, z) + + Int_en_z_Ni_y_b9 = np.zeros((5, 5), dtype=np.float) + Int_en_z_Ni_y_b9[1, 0] = 1 + Int_en_z_Ni_y_b9 = 1/2*polynomialProduct(Int_en_z_Ni_y_b9, z_2) + + Int_en_z_Ni_y_b10 = np.zeros((5, 5), dtype=np.float) + Int_en_z_Ni_y_b10[0, 1] = 1 + Int_en_z_Ni_y_b10 = 1/2*polynomialProduct(Int_en_z_Ni_y_b10, z_2) + + Int_en_z_Ni_y_D = np.zeros((5, 5), dtype=np.float) + Int_en_z_Ni_y_D[3, 0] = 1 + Int_en_z_Ni_y_D = -polynomialProduct(Int_en_z_Ni_y_D, z) + + Int_en_z_Ni_y_E = np.zeros((5, 5), dtype=np.float) + Int_en_z_Ni_y_E[1, 2] = 1 + Int_en_z_Ni_y_E = polynomialProduct(Int_en_z_Ni_y_E, z) + + Int_en_z_Ni_y_H = -1/4*z_4 + + Int_en_z_Ni_y_I = np.zeros((5, 5), dtype=np.float) + Int_en_z_Ni_y_I[0, 2] = 1 + Int_en_z_Ni_y_I = 1/2*polynomialProduct(Int_en_z_Ni_y_I, z_2) + + Int_en_z_Ni_y_J = np.zeros((5, 5), dtype=np.float) + Int_en_z_Ni_y_J[2, 1] = 1 + Int_en_z_Ni_y_J = -polynomialProduct(Int_en_z_Ni_y_J, z) + + Int_en_z_Ni_y_L = np.zeros((5, 5), dtype=np.float) + Int_en_z_Ni_y_L[0, 1] = 1 + Int_en_z_Ni_y_L = 1/3*polynomialProduct(Int_en_z_Ni_y_L, z_3) + + Int_en_z_Ni_y_M = np.zeros((5, 5), dtype=np.float) + Int_en_z_Ni_y_M[2, 0] = 1 + Int_en_z_Ni_y_M = -1/2*polynomialProduct(Int_en_z_Ni_y_M, z_2) + + Int_en_z_Ni_y_N = np.zeros((5, 5), dtype=np.float) + Int_en_z_Ni_y_N[2, 0] = 1 + Int_en_z_Ni_y_N = 1/2*polynomialProduct(Int_en_z_Ni_y_N, z_2) + + Int_en_z_Ni_y_O = np.zeros((5, 5), dtype=np.float) + Int_en_z_Ni_y_O[1, 1] = 1 + Int_en_z_Ni_y_O = 1/2*polynomialProduct(Int_en_z_Ni_y_O, z_2) + + Int_en_z_Ni_y_R = np.zeros((5, 5), dtype=np.float) + Int_en_z_Ni_y_R[1, 0] = 1 + Int_en_z_Ni_y_R = -1/3*polynomialProduct(Int_en_z_Ni_y_R, z_3) + + Int_en_z_Ni_y = np.hstack((null_values, null_values, null_values, + null_values, null_values, null_values, + null_values, null_values, null_values, + null_values, Int_en_z_Ni_y_b1, Int_en_z_Ni_y_b2, + Int_en_z_Ni_y_b3, Int_en_z_Ni_y_b4, + Int_en_z_Ni_y_b5, Int_en_z_Ni_y_b6, + Int_en_z_Ni_y_b7, Int_en_z_Ni_y_b8, + Int_en_z_Ni_y_b9, Int_en_z_Ni_y_b10, + null_values, null_values, null_values, + null_values, null_values, null_values, + null_values, null_values, null_values, + null_values, Int_en_z_Ni_y_D, Int_en_z_Ni_y_E, + null_values, null_values, Int_en_z_Ni_y_H, + Int_en_z_Ni_y_I, Int_en_z_Ni_y_J, null_values, + Int_en_z_Ni_y_L, Int_en_z_Ni_y_M, + Int_en_z_Ni_y_N, Int_en_z_Ni_y_O, null_values, + null_values, Int_en_z_Ni_y_R)) + + # Definition of z-component of z-integral in Ni + Int_en_z_Ni_z_c1 = z + + Int_en_z_Ni_z_c2 = np.zeros((5, 5), dtype=np.float) + Int_en_z_Ni_z_c2[1, 0] = 1 + Int_en_z_Ni_z_c2 = polynomialProduct(Int_en_z_Ni_z_c2, z) + + Int_en_z_Ni_z_c3 = np.zeros((5, 5), dtype=np.float) + Int_en_z_Ni_z_c3[0, 1] = 1 + Int_en_z_Ni_z_c3 = polynomialProduct(Int_en_z_Ni_z_c3, z) + + Int_en_z_Ni_z_c4 = 1/2*z_2 + + Int_en_z_Ni_z_c5 = np.zeros((5, 5), dtype=np.float) + Int_en_z_Ni_z_c5[2, 0] = 1 + Int_en_z_Ni_z_c5 = polynomialProduct(Int_en_z_Ni_z_c5, z) + + Int_en_z_Ni_z_c6 = np.zeros((5, 5), dtype=np.float) + Int_en_z_Ni_z_c6[0, 2] = 1 + Int_en_z_Ni_z_c6 = polynomialProduct(Int_en_z_Ni_z_c6, z) + + Int_en_z_Ni_z_c7 = 1/3*z_3 + + Int_en_z_Ni_z_c8 = np.zeros((5, 5), dtype=np.float) + Int_en_z_Ni_z_c8[1, 1] = 1 + Int_en_z_Ni_z_c8 = polynomialProduct(Int_en_z_Ni_z_c8, z) + + Int_en_z_Ni_z_c9 = np.zeros((5, 5), dtype=np.float) + Int_en_z_Ni_z_c9[1, 0] = 1 + Int_en_z_Ni_z_c9 = 1/2*polynomialProduct(Int_en_z_Ni_z_c9, z_2) + + Int_en_z_Ni_z_c10 = np.zeros((5, 5), dtype=np.float) + Int_en_z_Ni_z_c10[0, 1] = 1 + Int_en_z_Ni_z_c10 = 1/2*polynomialProduct(Int_en_z_Ni_z_c10, z_2) + + Int_en_z_Ni_z_F = np.zeros((5, 5), dtype=np.float) + Int_en_z_Ni_z_F[1, 0] = 1 + Int_en_z_Ni_z_F = 1/3*polynomialProduct(Int_en_z_Ni_z_F, z_3) + + Int_en_z_Ni_z_G = np.zeros((5, 5), dtype=np.float) + Int_en_z_Ni_z_G[3, 0] = 1 + Int_en_z_Ni_z_G = -polynomialProduct(Int_en_z_Ni_z_G, z) + + Int_en_z_Ni_z_H = np.zeros((5, 5), dtype=np.float) + Int_en_z_Ni_z_H[0, 1] = 1 + Int_en_z_Ni_z_H = 1/3*polynomialProduct(Int_en_z_Ni_z_H, z_3) + + Int_en_z_Ni_z_I = np.zeros((5, 5), dtype=np.float) + Int_en_z_Ni_z_I[0, 3] = 1 + Int_en_z_Ni_z_I = -polynomialProduct(Int_en_z_Ni_z_I, z) + + Int_en_z_Ni_z_K = np.zeros((5, 5), dtype=np.float) + Int_en_z_Ni_z_K[2, 0] = 1 + Int_en_z_Ni_z_K = -1/2*polynomialProduct(Int_en_z_Ni_z_K, z_2) + + Int_en_z_Ni_z_L = np.zeros((5, 5), dtype=np.float) + Int_en_z_Ni_z_L[0, 2] = 1 + Int_en_z_Ni_z_L = -1/2*polynomialProduct(Int_en_z_Ni_z_L, z_2) + + Int_en_z_Ni_z_N = np.zeros((5, 5), dtype=np.float) + Int_en_z_Ni_z_N[2, 1] = 1 + Int_en_z_Ni_z_N = -polynomialProduct(Int_en_z_Ni_z_N, z) + + Int_en_z_Ni_z_P = np.zeros((5, 5), dtype=np.float) + Int_en_z_Ni_z_P[1, 2] = 1 + Int_en_z_Ni_z_P = -polynomialProduct(Int_en_z_Ni_z_P, z) + + Int_en_z_Ni_z_Q = np.zeros((5, 5), dtype=np.float) + Int_en_z_Ni_z_Q[1, 1] = 1 + Int_en_z_Ni_z_Q = 1/2*polynomialProduct(Int_en_z_Ni_z_Q, z_2) + + Int_en_z_Ni_z = np.hstack((null_values, null_values, null_values, + null_values, null_values, null_values, + null_values, null_values, null_values, + null_values, null_values, null_values, + null_values, null_values, null_values, + null_values, null_values, null_values, + null_values, null_values, Int_en_z_Ni_z_c1, + Int_en_z_Ni_z_c2, Int_en_z_Ni_z_c3, + Int_en_z_Ni_z_c4, Int_en_z_Ni_z_c5, + Int_en_z_Ni_z_c6, Int_en_z_Ni_z_c7, + Int_en_z_Ni_z_c8, Int_en_z_Ni_z_c9, + Int_en_z_Ni_z_c10, null_values, null_values, + Int_en_z_Ni_z_F, Int_en_z_Ni_z_G, + Int_en_z_Ni_z_H, Int_en_z_Ni_z_I, null_values, + Int_en_z_Ni_z_K, Int_en_z_Ni_z_L, null_values, + Int_en_z_Ni_z_N, null_values, Int_en_z_Ni_z_P, + Int_en_z_Ni_z_Q, null_values)) + + # Build matrix of volume + Int_en_z_Ni = np.vstack((Int_en_z_Ni_x, Int_en_z_Ni_y, Int_en_z_Ni_z)) + + # Volumetric integral + for q in np.arange(1, 4): + integral_aux = np.zeros((5, 225), dtype=np.float) + for idof in np.arange(1, 4): + idx1 = ((idof-1)*5+1) + idx2 = (5*idof) + integral_aux = (integral_aux+Int_en_z_Ni[idx1-1:idx2, :] * + q_volumen_ref[idof-1, q-1]) + + # Get row of the coefficient matrix of the system of equations. + row_matrix = 43+(q-1) + matrix[row_matrix-1, :] = analyticIntegral(integral_aux) + + # Computation of the second member of the total equation system. Each + # column represents the independent terms in the second member of the + # system of equations proposed for each Ni function. + rhs = np.zeros((thirdOrderEdgeElement, thirdOrderEdgeElement), + dtype=np.float) + + for idof in np.arange(1, thirdOrderEdgeElement+1): + if (idof <= 18): + idx1 = np.int(np.round((idof+1)/3)) - np.int(1) + rhs[idof-1, idof-1] = 1/length[idx1] + elif (idof > 18): + rhs[idof-1, idof-1] = 1 + + # Obtaining the coefficients of the Ni by solving the system of equations: + # matrix * {Coefficients} = rhs + # We call "Coefficients" the matrix that hosts each of the 45 coefficients + # a1, ... R of the Ni functions. Each of the 45 columns of said matrix + # therefore represents each of the functions. + coef = lstsq(matrix, rhs, rcond=None)[0] + + # Coefficients EPS --> 0 + EPS = 1.e-14 + aux1, aux2 = np.where(np.abs(coef) < EPS) + size = aux1.shape + n = size[0] + + for kk in np.arange(n): + coef[aux1[kk], aux2[kk]] = 0. + + a1 = coef[0, :] + a2 = coef[1, :] + a3 = coef[2, :] + a4 = coef[3, :] + a5 = coef[4, :] + a6 = coef[5, :] + a7 = coef[6, :] + a8 = coef[7, :] + a9 = coef[8, :] + a10 = coef[9, :] + + b1 = coef[10, :] + b2 = coef[11, :] + b3 = coef[12, :] + b4 = coef[13, :] + b5 = coef[14, :] + b6 = coef[15, :] + b7 = coef[16, :] + b8 = coef[17, :] + b9 = coef[18, :] + b10 = coef[19, :] + + c1 = coef[20, :] + c2 = coef[21, :] + c3 = coef[22, :] + c4 = coef[23, :] + c5 = coef[24, :] + c6 = coef[25, :] + c7 = coef[26, :] + c8 = coef[27, :] + c9 = coef[28, :] + c10 = coef[29, :] + + D = coef[30, :] + E = coef[31, :] + F = coef[32, :] + G = coef[33, :] + H = coef[34, :] + II = coef[35, :] + JJ = coef[36, :] + K = coef[37, :] + L = coef[38, :] + M = coef[39, :] + N = coef[40, :] + OO = coef[41, :] + P = coef[42, :] + Q = coef[43, :] + R = coef[44, :] + + return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, b1, b2, b3, b4, b5, b6, + b7, b8, b9, b10, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, D, E, + F, G, H, II, JJ, K, L, M, N, OO, P, Q, R) + + +def NiTetrahedralThirdOrder(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, + b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, + c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, + D, E, F, G, H, II, JJ, K, L, M, N, OO, P, Q, + R, x, y, z, r): + ''' Computation of Ni (Nedelec basis functions of second order) in a + tetrahedral element with vertices (x,y,z) for point r. + + :param float coefficients: a1,a2,a3,a4,b1,b2,b3,b4,c1,c2,c3,c4,D,E,F, + G,H,II,JJ,K. + :param float-array x: x-coordinates of reference element. + :param float-array y: y-coordinates of reference element. + :param float-array z: z-coordinates of reference element. + :param float-array r: xyz coordinates of the evaluation point. + :return: basis nedelec funcions of second order. + :rtype: ndarray. + + .. note: References:\n + Amor-Martin, A., Garcia-Castillo, L. E., & Garcia-Doñoro, D. D. + (2016). Second-order Nédélec curl-conforming prismatic element + for computational electromagnetics. IEEE Transactions on + Antennas and Propagation, 64(10), 4384-4395. + ''' + # Number of dimensions + nDimensions = 3 + + # Third order ele + thirdOrderEdgeElement = 45 + + # Initialization + coef = np.zeros((thirdOrderEdgeElement, thirdOrderEdgeElement), + dtype=np.float) + + coef[0, :] = a1 + coef[1, :] = a2 + coef[2, :] = a3 + coef[3, :] = a4 + coef[4, :] = a5 + coef[5, :] = a6 + coef[6, :] = a7 + coef[7, :] = a8 + coef[8, :] = a9 + coef[9, :] = a10 + coef[10, :] = b1 + coef[11, :] = b2 + coef[12, :] = b3 + coef[13, :] = b4 + coef[14, :] = b5 + coef[15, :] = b6 + coef[16, :] = b7 + coef[17, :] = b8 + coef[18, :] = b9 + coef[19, :] = b10 + coef[20, :] = c1 + coef[21, :] = c2 + coef[22, :] = c3 + coef[23, :] = c4 + coef[24, :] = c5 + coef[25, :] = c6 + coef[26, :] = c7 + coef[27, :] = c8 + coef[28, :] = c9 + coef[29, :] = c10 + coef[30, :] = D + coef[31, :] = E + coef[32, :] = F + coef[33, :] = G + coef[34, :] = H + coef[35, :] = II + coef[36, :] = JJ + coef[37, :] = K + coef[38, :] = L + coef[39, :] = M + coef[40, :] = N + coef[41, :] = OO + coef[42, :] = P + coef[43, :] = Q + coef[44, :] = R + + # Computation on reference element + L = cartesianToVolumetricCoordinates(x, y, z, r) + + xref = np.array([0, 1, 0, 0], dtype=np.float) + yref = np.array([0, 0, 1, 0], dtype=np.float) + zref = np.array([0, 0, 0, 1], dtype=np.float) + + rref = np.zeros(3, dtype=np.float) + rref[0] = np.dot(L, xref) + rref[1] = np.dot(L, yref) + rref[2] = np.dot(L, zref) + + aux_x = np.array([1, rref[0], rref[1], rref[2], rref[0]**2, rref[1]**2, + rref[2]**2, rref[0]*rref[1], rref[0]*rref[2], + rref[1]*rref[2], 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, (rref[0]**2)*rref[1], -rref[1]**3, + -rref[2]**3, (rref[0]**2)*rref[2], 0, 0, + (rref[1]**2)*rref[0], (rref[2]**2)*rref[0], 0, + rref[0]*rref[1]*rref[2], 0, -(rref[1]**2)*rref[2], + (rref[1]**2)*rref[2], -(rref[2]**2)*rref[1], + (rref[2]**2)*rref[1]], dtype=np.float) + + aux_y = np.array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, rref[0], rref[1], + rref[2], rref[0]**2, rref[1]**2, rref[2]**2, + rref[0]*rref[1], rref[0]*rref[2], rref[1]*rref[2], + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -rref[0]**3, + (rref[1]**2)*rref[0], 0, 0, -rref[2]**3, + (rref[1]**2)*rref[2], -(rref[0]**2)*rref[1], 0, + (rref[2]**2)*rref[1], -(rref[0]**2)*rref[2], + (rref[0]**2)*rref[2], rref[0]*rref[1]*rref[2], 0, 0, + -(rref[2]**2)*rref[0]], dtype=np.float) + + aux_z = np.array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 1, rref[0], rref[1], rref[2], rref[0]**2, + rref[1]**2, rref[2]**2, rref[0]*rref[1], rref[0]*rref[2], + rref[1]*rref[2], 0, 0, (rref[2]**2)*rref[0], + -rref[0]**3, (rref[2]**2)*rref[1], -rref[1]**3, 0, + -(rref[0]**2)*rref[2], -(rref[1]**2)*rref[2], 0, + -(rref[0]**2)*rref[1], 0, -(rref[1]**2)*rref[0], + rref[0]*rref[1]*rref[2], 0], dtype=np.float) + + # Allocate + Niref = np.zeros((nDimensions, thirdOrderEdgeElement), dtype=np.float) + + Niref[0, :] = np.matmul(aux_x, coef) + Niref[1, :] = np.matmul(aux_y, coef) + Niref[2, :] = np.matmul(aux_z, coef) + + del aux_x, aux_y, aux_z + + # Vector element reference to the real element through the Jacobian + # Ni_real=([J]^-1)*Niref + x21 = x[1]-x[0] + y21 = y[1]-y[0] + z21 = z[1]-z[0] + x31 = x[2]-x[0] + y31 = y[2]-y[0] + z31 = z[2]-z[0] + x41 = x[3]-x[0] + y41 = y[3]-y[0] + z41 = z[3]-z[0] + + jacob = np.array([[x21, y21, z21], [x31, y31, z31], + [x41, y41, z41]], dtype=np.float) + + Ni = lstsq(jacob, Niref, rcond=None)[0] + + return Ni + + +def nedelecBasisThirdOrder(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, + b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, + c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, + D, E, F, G, H, II, JJ, K, L, M, N, + O, P, Q, R, ref_ele, points): + ''' This function computes the basis nedelec functions of third order + for a set of points in a given tetrahedral element. + + :param float coefficients: a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,b1,b2,b3,b4,b5, + b6,b7,b8,b9,b10,c1,c2,c3,c4,c5,c6,c7,c8,c9,c10,D,E,F,G,H,II,JJ,K,L,M,N, + O,P,Q,R. + :param float-array ref_ele: nodal coordinates of reference element. + :param float-array points: spatial coordinates of the evaluation points. + :return: basis nedelec functions of second order. + :rtype: ndarray. + + .. note: References:\n + Garcia-Castillo, L. E., Ruiz-Genovés, A. J., Gómez-Revuelto, I., + Salazar-Palma, M., & Sarkar, T. K. (2002). Third-order Nédélec + curl-conforming finite element. IEEE transactions on magnetics, + 38(5), 2370-2372. + ''' + # Number of points + size = points.shape + # More than one point + if len(size) == 2: + nPoints = size[1] + # One point + elif len(size) == 1: + nPoints = 1 + + # Number of dimensions + nDimensions = 3 + + # Third order ele + thirdOrderEdgeElement = 45 + + # Allocate + basis = np.zeros((nDimensions, thirdOrderEdgeElement, nPoints), + dtype=np.float) + + # Get reference element coordinates + xref = ref_ele[0, :] + yref = ref_ele[1, :] + zref = ref_ele[2, :] + + # Compute nedelec basis functions of second order for all points + if nPoints == 1: # One point + for iPoint in np.arange(nPoints): + r = points + + # Basis funtions for iPoint| + basis[:, :, iPoint] = NiTetrahedralThirdOrder(a1, a2, a3, a4, a5, + a6, a7, a8, a9, a10, + b1, b2, b3, b4, b5, + b6, b7, b8, b9, b10, + c1, c2, c3, c4, c5, + c6, c7, c8, c9, c10, + D, E, F, G, H, II, + JJ, K, L, M, N, O, + P, Q, R, xref, yref, + zref, r) + else: # More than one point + for iPoint in np.arange(nPoints): + r = points[:, iPoint] + + # Basis funtions for iPoint| + basis[:, :, iPoint] = NiTetrahedralThirdOrder(a1, a2, a3, a4, a5, + a6, a7, a8, a9, a10, + b1, b2, b3, b4, b5, + b6, b7, b8, b9, b10, + c1, c2, c3, c4, c5, + c6, c7, c8, c9, c10, + D, E, F, G, H, II, + JJ, K, L, M, N, O, + P, Q, R, xref, yref, + zref, r) + + return basis + + +def computeMassMatrixThirdOrder(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, + b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, + c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, + D, E, F, G, H, II, JJ, K, L, M, N, O, P, + Q, R, ref_ele, GR, signs, DetJacob): + ''' Compute mass matrix for tetrahedral edge elements of third order. + + :param float coefficients: a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,b1,b2,b3,b4,b5, + b6,b7,b8,b9,b10,c1,c2,c3,c4,c5,c6,c7,c8,c9,c10,D,E,F,G,H,II,JJ,K,L,M,N, + O,P,Q,R + :param float-array ref_ele: nodal coordinates of reference element. + :param float-array GR: tensor. + :param int-array signs: dofs signs. + :param float DetJacob: determinant of the jacobian. + :return: mass matrix for edge element of third order. + :rtype: ndarray. + + .. note: References:\n + Garcia-Castillo, L. E., Ruiz-Genovés, A. J., Gómez-Revuelto, I., + Salazar-Palma, M., & Sarkar, T. K. (2002). Third-order Nédélec + curl-conforming finite element. IEEE transactions on magnetics, + 38(5), 2370-2372. + ''' + # Third order ele + nedelecOrder = 3 + thirdOrderEdgeElement = 45 + + # Gaussian points for the unit reference tetrahedron + ngaussP = 24 + [Wi, rx, ry, rz] = gauss_points_reference_tetrahedron(ngaussP, + nedelecOrder) + + # Get reference element coordinates + xref = ref_ele[0, :] + yref = ref_ele[1, :] + zref = ref_ele[2, :] + + # Allocate + Me = np.zeros((thirdOrderEdgeElement, thirdOrderEdgeElement), + dtype=np.float) + + # Mass matrix computation + r = np.zeros(3, dtype=np.float) + for iPoint in np.arange(ngaussP): + r[0] = rx[iPoint] + r[1] = ry[iPoint] + r[2] = rz[iPoint] + + Ni = NiTetrahedralThirdOrder(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, + b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, + c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, + D, E, F, G, H, II, JJ, K, L, M, N, O, + P, Q, R, xref, yref, zref, r) + nix = Ni[0, :] + niy = Ni[1, :] + niz = Ni[2, :] + + for ii in np.arange(thirdOrderEdgeElement): + for jj in np.arange(thirdOrderEdgeElement): + Me[ii, jj] += Wi[iPoint]*((GR[0, 0]*nix[ii]*nix[jj] + + GR[0, 1]*(nix[ii]*niy[jj] + + niy[ii]*nix[jj]) + + GR[1, 1]*niy[ii]*niy[jj] + + GR[0, 2]*(nix[ii]*niz[jj] + + niz[ii]*nix[jj]) + + GR[1, 2]*(niy[ii]*niz[jj] + + niz[ii]*niy[jj]) + + GR[2, 2]*niz[ii]*niz[jj]) * + signs[ii]*signs[jj]) + + Me = Me*DetJacob + + return Me + + +def computeDerivativesThirdOrder(a2, a3, a4, a5, a6, a7, a8, a9, a10, b2, b3, + b4, b5, b6, b7, b8, b9, b10, c2, c3, c4, c5, + c6, c7, c8, c9, c10, D, E, F, G, H, II, JJ, + K, L, M, N, O, P, Q, R, point): + ''' Compute partial derivatives of basis functions for tetrahedral edge + elements of third order (reference element) + + :param float coefficients: a2,a3,a4,a5,a6,a7,a8,a9,a10,b2,b3,b4,b5,b6, + b7,b8,b9,b10,c2,c3,c4,c5,c6,c7,c8,c9,c10,D,E,F,G,H,II,JJ,K,L,M,N,O,P,Q,R. + :param float-array point: coordinates of the gaussian point. + :return: partial derivatives for edge element of third order. + :rtype: ndarray. + + .. note: References:\n + Garcia-Castillo, L. E., Ruiz-Genovés, A. J., Gómez-Revuelto, I., + Salazar-Palma, M., & Sarkar, T. K. (2002). Third-order Nédélec + curl-conforming finite element. IEEE transactions on magnetics, + 38(5), 2370-2372. + ''' + # Point coordinates + x = point[0] + y = point[1] + z = point[2] + + # dxNix + dxNix = (a2 + 2*a5*x + a8*y + a9*z + 2*D*x*y + + 2*G*x*z + JJ*y**2 + K*z**2 + M*y*z) + + # dxNiy + dxNiy = (b2 + 2*b5*x + b8*y + b9*z - 2*JJ*x*y - 2*M*x*z + + 2*N*x*z + E*y**2 - R*z**2 + O*y*z - 3*D*x**2) + + # dxNiz + dxNiz = (c2 + 2*c5*x + c8*y + c9*z - 2*N*x*y - + 2*K*x*z - P*y**2 + F*z**2 + Q*y*z - 3*G*x**2) + + # dyNix + dyNix = (a3 + 2*a6*y + a8*x + a10*z + D*x**2 + 2*JJ*y*x - + 2*O*y*z + 2*P*y*z - Q*z**2 + R*z**2 + M*x*z - 3*E*y**2) + + # dyNiy + dyNiy = (b3 + 2*b6*y + b8*x + b10*z - JJ*x**2 + + 2*E*y*x + 2*II*y*z + L*z**2 + O*x*z) + + # dyNiz + dyNiz = (c3 + 2*c6*y + c8*x + c10*z - N*x**2 - + 2*P*y*x - 2*L*y*z + H*z**2 + Q*x*z - 3*II*y**2) + + # dzNix + dzNix = (a4 + 2*a7*z + a9*x + a10*y + G*x**2 - O*y**2 + + P*y**2 + 2*K*z*x - 2*Q*z*y + 2*R*z*y + M*x*y - 3*F*z**2) + + # dzNiy + dzNiy = (b4 + 2*b7*z + b9*x + b10*y - M*x**2 + + N*x**2 + II*y**2 - 2*R*z*x + 2*L*z*y + O*x*y - 3*H*z**2) + + # dzNiz + dzNiz = (c4 + 2*c7*z + c9*x + c10*y - K*x**2 - + L*y**2 + 2*F*z*x + 2*H*z*y + Q*x*y) + + return dxNix, dxNiy, dxNiz, dyNix, dyNiy, dyNiz, dzNix, dzNiy, dzNiz + + +def computeStiffnessMatrixThirdOrder(a2, a3, a4, a5, a6, a7, a8, a9, a10, + b2, b3, b4, b5, b6, b7, b8, b9, b10, + c2, c3, c4, c5, c6, c7, c8, c9, c10, + D, E, F, G, H, II, JJ, K, L, M, N, + O, P, Q, R, invjj, signs, DetJacob): + ''' Compute stiffness matrix for tetrahedral edge elements of third order. + + :param float coefficients: a2,a3,a4,a5,a6,a7,a8,a9,a10,b2,b3,b4,b5,b6,b7, + b8,b9,b10,c2,c3,c4,c5,c6,c7,c8,c9,c10,D,E,F,G,H,II,JJ,K,L,M,N,O,P,Q,R. + :param float-array invjj: inverse jacobian. + :param int-array signs: dofs signs. + :param float DetJacob: determinant of the jacobian. + :return: stiffness matrix for edge element of third order. + :rtype: ndarray. + + .. note: References:\n + Garcia-Castillo, L. E., Ruiz-Genovés, A. J., Gómez-Revuelto, I., + Salazar-Palma, M., & Sarkar, T. K. (2002). Third-order Nédélec + curl-conforming finite element. IEEE transactions on magnetics, + 38(5), 2370-2372. + ''' + # Third order ele + nedelecOrder = 3 + thirdOrderEdgeElement = 45 + + # Gaussian points for the unit reference tetrahedron + ngaussP = 15 + [Wi, rx, ry, rz] = gauss_points_reference_tetrahedron(ngaussP, + nedelecOrder) + + # Allocate + Ke = np.zeros((thirdOrderEdgeElement, thirdOrderEdgeElement), + dtype=np.float) + + # Tensor computation + fr = np.eye(3, dtype=np.float) + invfr = inv(fr) + + tmp1 = np.array([[0, 0, 0], [0, 0, 1], [0, -1, 0]], dtype=np.float) + tmp2 = np.matmul(tmp1, invjj) + A = np.matmul(invjj.transpose(), tmp2) + + tmp1 = np.array([[0, 0, -1], [0, 0, 0], [1, 0, 0]], dtype=np.float) + tmp2 = np.matmul(tmp1, invjj) + B = np.matmul(invjj.transpose(), tmp2) + + tmp1 = np.array([[0, 1, 0], [-1, 0, 0], [0, 0, 0]], dtype=np.float) + tmp2 = np.matmul(tmp1, invjj) + C = np.matmul(invjj.transpose(), tmp2) + + # Stiffness matrix computation + r = np.zeros(3, dtype=np.float) + for iPoint in np.arange(ngaussP): + r[0] = rx[iPoint] + r[1] = ry[iPoint] + r[2] = rz[iPoint] + + [_, dxNiy, dxNiz, + dyNix, _, dyNiz, + dzNix, dzNiy, _] = computeDerivativesThirdOrder(a2, a3, a4, a5, a6, + a7, a8, a9, a10, b2, + b3, b4, b5, b6, b7, + b8, b9, b10, c2, c3, + c4, c5, c6, c7, c8, + c9, c10, D, E, F, G, + H, II, JJ, K, L, M, N, + O, P, Q, R, r) + + rotNix = (A[0, 1]*dxNiy + A[0, 2]*dxNiz - + A[0, 1]*dyNix + A[1, 2]*dyNiz - + A[0, 2]*dzNix - A[1, 2]*dzNiy) + rotNiy = (B[0, 1]*dxNiy + B[0, 2]*dxNiz - + B[0, 1]*dyNix + B[1, 2]*dyNiz - + B[0, 2]*dzNix - B[1, 2]*dzNiy) + rotNiz = (C[0, 1]*dxNiy + C[0, 2]*dxNiz - + C[0, 1]*dyNix + C[1, 2]*dyNiz - + C[0, 2]*dzNix - C[1, 2]*dzNiy) + + for ii in np.arange(thirdOrderEdgeElement): + for jj in np.arange(thirdOrderEdgeElement): + Ke[ii, jj] += Wi[iPoint]*((invfr[0, 0]*rotNix[ii]*rotNix[jj] + + invfr[0, 1]*(rotNix[ii] * + rotNiy[jj] + + rotNiy[ii] * + rotNix[jj]) + + invfr[1, 1]*rotNiy[ii]*rotNiy[jj] + + invfr[0, 2]*(rotNix[ii] * + rotNiz[jj] + + rotNiz[ii] * + rotNix[jj]) + + invfr[1, 2]*(rotNiy[ii] * + rotNiz[jj] + + rotNiz[ii] * + rotNiy[jj]) + + invfr[2, 2]*rotNiz[ii] * + rotNiz[jj])*signs[ii]*signs[jj]) + + Ke = Ke*DetJacob + + return Ke + + def unitary_test(): ''' Unitary test for efem.py script. ''' + if __name__ == '__main__': # Standard module import unitary_test() else: # Standard module import import numpy as np + from numpy.linalg import lstsq from scipy.linalg import det + from scipy.linalg import norm + from scipy.linalg import inv # PETGEM module import from petgem.efem.vectorMatrixFunctions import deleteDuplicateRows + from petgem.efem.vectorMatrixFunctions import crossprod + from petgem.efem.vectorMatrixFunctions import is_duplicate_entry + from petgem.efem.fem import cartesianToVolumetricCoordinates + from petgem.efem.fem import gauss_points_reference_tetrahedron diff --git a/petgem/efem/fem.py b/petgem/efem/fem.py old mode 100644 new mode 100755 index a3fe220..f41274c --- a/petgem/efem/fem.py +++ b/petgem/efem/fem.py @@ -7,12 +7,12 @@ def tetraXiEtaZeta2XYZ(eleNodes, XiEtaZetaPoints): - ''' Map a set of points in XiEtaZeta coordinates to XYZ coordinates. + ''' Map a set of points in XiEtaZeta coord to XYZ coord. - :param ndarray eleNodes: nodal spatial coordinates of the + :param ndarray eleNodes: nodal spatial coord of the tetrahedral element. - :param ndarray XiEtaZetaPoints: set of points in XiEtaZeta coordinates. - :return: new spatial coordinates of XiEtaZetaPoints. + :param ndarray XiEtaZetaPoints: set of points in XiEtaZeta coord. + :return: new spatial coord of XiEtaZetaPoints. :rtype: ndarray. ''' # Get number of points @@ -21,17 +21,17 @@ def tetraXiEtaZeta2XYZ(eleNodes, XiEtaZetaPoints): # Allocate xyzPoints = np.zeros((3), dtype=np.float64) # Mapping all points - # x-coordinates + # x-coord xyzPoints[0] = eleNodes[0][0] + \ (eleNodes[1][0]-eleNodes[0][0])*XiEtaZetaPoints[0] + \ (eleNodes[2][0]-eleNodes[0][0])*XiEtaZetaPoints[1] + \ (eleNodes[3][0]-eleNodes[0][0])*XiEtaZetaPoints[2] - # y-coordinates + # y-coord xyzPoints[1] = eleNodes[0][1] + \ (eleNodes[1][1]-eleNodes[0][1])*XiEtaZetaPoints[0] + \ (eleNodes[2][1]-eleNodes[0][1])*XiEtaZetaPoints[1] + \ (eleNodes[3][1]-eleNodes[0][1])*XiEtaZetaPoints[2] - # z-coordinates + # z-coord xyzPoints[2] = eleNodes[0][2] + \ (eleNodes[1][2]-eleNodes[0][2])*XiEtaZetaPoints[0] + \ (eleNodes[2][2]-eleNodes[0][2])*XiEtaZetaPoints[1] + \ @@ -41,17 +41,17 @@ def tetraXiEtaZeta2XYZ(eleNodes, XiEtaZetaPoints): # Allocate xyzPoints = np.zeros((nPoints, 3), dtype=np.float64) # Mapping all points - # x-coordinates + # x-coord xyzPoints[:, 0] = eleNodes[0][0] + \ ([eleNodes[1][0]-eleNodes[0][0]])*XiEtaZetaPoints[:, 0] + \ ([eleNodes[2][0]-eleNodes[0][0]])*XiEtaZetaPoints[:, 1] + \ ([eleNodes[3][0]-eleNodes[0][0]])*XiEtaZetaPoints[:, 2] - # y-coordinates + # y-coord xyzPoints[:, 1] = eleNodes[0][1] + \ ([eleNodes[1][1]-eleNodes[0][1]])*XiEtaZetaPoints[:, 0] + \ ([eleNodes[2][1]-eleNodes[0][1]])*XiEtaZetaPoints[:, 1] + \ ([eleNodes[3][1]-eleNodes[0][1]])*XiEtaZetaPoints[:, 2] - # z-coordinates + # z-coord xyzPoints[:, 2] = eleNodes[0][2] + \ ([eleNodes[1][2]-eleNodes[0][2]])*XiEtaZetaPoints[:, 0] + \ ([eleNodes[2][2]-eleNodes[0][2]])*XiEtaZetaPoints[:, 1] + \ @@ -807,9 +807,9 @@ def s4(w): return (X, W) def s31(a, w): - ''' First star: Compute the barycentric coordinates, which + ''' First star: Compute the barycentric coord, which contain 4 dimensions. The points are obtained by taking all - the unique 3 dimensional permutations from the barycentric coordinates. + the unique 3 dimensional permutations from the barycentric coord. ''' baryc = [a, a, a, (1.0-3.0*a)] temp = np.array(list(itertools.permutations(baryc)), dtype=np.float64) @@ -822,9 +822,9 @@ def s31(a, w): return (X, W) def s22(a, w): - ''' Second star: Compute the barycentric coordinates, which + ''' Second star: Compute the barycentric coord, which contain 4 dimensions. The points are obtained by taking all - the unique 3 dimensional permutations from the barycentric coordinates. + the unique 3 dimensional permutations from the barycentric coord. ''' baryc = [a, a, 0.5-a, 0.5-a] temp = np.array(list(itertools.permutations(baryc)), dtype=np.float64) @@ -837,9 +837,9 @@ def s22(a, w): return (X, W) def s211(a, b, w): - ''' Second star: Compute the barycentric coordinates, which + ''' Second star: Compute the barycentric coord, which contain 4 dimensions. The points are obtained by taking all - the unique 3 dimensional permutations from the barycentric coordinates. + the unique 3 dimensional permutations from the barycentric coord. ''' baryc = [a, a, b, (1.0-2.0*a-b)] temp = np.array(list(itertools.permutations(baryc)), dtype=np.float64) @@ -852,9 +852,9 @@ def s211(a, b, w): return (X, W) def s1111(a, b, c, w): - ''' Fourth star: Compute the barycentric coordinates, which + ''' Fourth star: Compute the barycentric coord, which contain 4 dimensions. The points are obtained by taking all - the unique 3 dimensional permutations from the barycentric coordinates. + the unique 3 dimensional permutations from the barycentric coord. ''' baryc = [a, b, c, (1.0-a-b-c)] temp = np.array(list(itertools.permutations(baryc)), dtype=np.float64) @@ -890,10 +890,462 @@ def s1111(a, b, c, w): return (X, W) +def gauss_points_reference_tetrahedron(npointeg, nedelec_order): + ''' Compute the quadrature points X,Y,Z and the weights Wi for the + integration over the reference tetrahedral. + + :param int npointeg: number of gauss points to be computed. + :param int nedelec_order: nedelec element order. + :return: quadrature Gauss points (X, Y, Z) and Gauss weights. + :rtype: ndarray. + + .. note:: References:\n + Amor-Martin, A., Garcia-Castillo, L. E., & Garcia-Doñoro, D. D. + (2016). Second-order Nédélec curl-conforming prismatic element + for computational electromagnetics. IEEE Transactions on + Antennas and Propagation, 64(10), 4384-4395. + ''' + + # ----- Gaussian points computation for unit tetrahedron ---- + # Allocate + Wi = np.zeros(npointeg, dtype=np.float) + x = np.zeros(npointeg, dtype=np.float) + y = np.zeros(npointeg, dtype=np.float) + z = np.zeros(npointeg, dtype=np.float) + + if npointeg == 5 and nedelec_order == 2: + # Compute weigths + Wi[0] = -2./15. + Wi[1] = 3./40. + Wi[2] = 3./40. + Wi[3] = 3./40. + Wi[4] = 3./40. + # Unit tetrahedron (Volume) + Wi = Wi*6. + + # Compute coord + x[0] = 1./4. + x[1] = 1./6. + x[2] = 1./6. + x[3] = 1./6. + x[4] = 1./2. + y[0] = 1./4. + y[1] = 1./6. + y[2] = 1./6. + y[3] = 1./2. + y[4] = 1./6. + z[0] = 1./4. + z[1] = 1./6. + z[2] = 1./2. + z[3] = 1./6. + z[4] = 1./6. + + elif npointeg == 15 and nedelec_order == 2: + # Compute weigths + a = 1./4. + b1 = (7.+np.sqrt(15.))/34. + b2 = (7.-np.sqrt(15.))/34. + c1 = (13.-3.*np.sqrt(15.))/34. + c2 = (13.+3.*np.sqrt(15.))/34. + d = (5.-np.sqrt(15.))/20. + ee = (5.+np.sqrt(15.))/20. + + Wi[0] = 8./405. + Wi[1] = (2665.-14.*np.sqrt(15.))/226800. + Wi[2] = (2665.-14.*np.sqrt(15.))/226800. + Wi[3] = (2665.-14.*np.sqrt(15.))/226800. + Wi[4] = (2665.-14.*np.sqrt(15.))/226800. + Wi[5] = (2665.+14.*np.sqrt(15.))/226800. + Wi[6] = (2665.+14.*np.sqrt(15.))/226800. + Wi[7] = (2665.+14.*np.sqrt(15.))/226800. + Wi[8] = (2665.+14.*np.sqrt(15.))/226800. + Wi[9] = 5./567. + Wi[10] = 5./567. + Wi[11] = 5./567. + Wi[12] = 5./567. + Wi[13] = 5./567. + Wi[14] = 5./567. + + # Unit tetrahedron (Volume) + Wi = Wi*6. + + # Compute coord + x[0] = a + y[0] = a + z[0] = a + + x[1] = b1 + x[2] = b1 + x[3] = b1 + x[4] = c1 + x[5] = b2 + x[6] = b2 + x[7] = b2 + x[8] = c2 + y[1] = b1 + y[2] = b1 + y[3] = c1 + y[4] = b1 + y[5] = b2 + y[6] = b2 + y[7] = c2 + y[8] = b2 + z[1] = b1 + z[2] = c1 + z[3] = b1 + z[4] = b1 + z[5] = b2 + z[6] = c2 + z[7] = b2 + z[8] = b2 + + x[9] = d + x[10] = d + x[11] = ee + y[9] = d + y[10] = ee + y[11] = d + z[9] = ee + z[10] = d + z[11] = d + + x[12] = d + x[13] = ee + x[14] = ee + y[12] = ee + y[13] = d + y[14] = ee + z[12] = ee + z[13] = ee + z[14] = d + + elif npointeg == 15 and nedelec_order == 3: + # Compute weigths + W0 = 8/405 + W1 = (2665-14*np.sqrt(15))/226800 + W2 = (2665+14*np.sqrt(15))/226800 + W3 = 5/567 + + a = 1/4 + b1 = (7+np.sqrt(15))/34 + b2 = (7-np.sqrt(15))/34 + c1 = (13-3*np.sqrt(15))/34 + c2 = (13+3*np.sqrt(15))/34 + d = (5-np.sqrt(15))/20 + ee = (5+np.sqrt(15))/20 + + Wi[0] = W0 + Wi[1] = W1 + Wi[2] = W1 + Wi[3] = W1 + Wi[4] = W1 + Wi[5] = W2 + Wi[6] = W2 + Wi[7] = W2 + Wi[8] = W2 + Wi[9] = W3 + Wi[10] = W3 + Wi[11] = W3 + Wi[12] = W3 + Wi[13] = W3 + Wi[14] = W3 + + # Compute coordinates + x[0] = a + y[0] = a + z[0] = a + + x[1] = b1 + x[2] = b1 + x[3] = b1 + x[4] = c1 + y[1] = b1 + y[2] = b1 + y[3] = c1 + y[4] = b1 + z[1] = b1 + z[2] = c1 + z[3] = b1 + z[4] = b1 + + x[5] = b2 + x[6] = b2 + x[7] = b2 + x[8] = c2 + y[5] = b2 + y[6] = b2 + y[7] = c2 + y[8] = b2 + z[5] = b2 + z[6] = c2 + z[7] = b2 + z[8] = b2 + + x[9] = d + x[10] = d + x[11] = ee + y[9] = d + y[10] = ee + y[11] = d + z[9] = ee + z[10] = d + z[11] = d + + x[12] = d + x[13] = ee + x[14] = ee + y[12] = ee + y[13] = d + y[14] = ee + z[12] = ee + z[13] = ee + z[14] = d + + elif npointeg == 24 and nedelec_order == 3: + # Definition of reference element (volumetric coord) + x_tet_ref = np.array([0, 1, 0, 0], dtype=np.float) + y_tet_ref = np.array([0, 0, 1, 0], dtype=np.float) + z_tet_ref = np.array([0, 0, 0, 1], dtype=np.float) + + # Allocate array for cartesian coord + cartesian_coord = np.zeros((3, npointeg), dtype=np.float) + + # First group K[3,1]. 4 points + W1 = 0.665379170969464506e-2 + Wi[0] = W1 + Wi[1] = W1 + Wi[2] = W1 + Wi[3] = W1 + + L1A = 0.214602871259151684 + L1B = 0.356191386222544953 + + # Volumetric coord to cartesian coord. 4 points + L1_1 = np.vstack((L1A, L1A, L1A, L1B)) + tmp = volumetricToCartesianCoordinates(x_tet_ref, y_tet_ref, + z_tet_ref, L1_1) + cartesian_coord[:, 0] = tmp[:, 0] + + L1_2 = np.vstack((L1A, L1A, L1B, L1A)) + tmp = volumetricToCartesianCoordinates(x_tet_ref, y_tet_ref, + z_tet_ref, L1_2) + cartesian_coord[:, 1] = tmp[:, 0] + + L1_3 = np.vstack((L1A, L1B, L1A, L1A)) + tmp = volumetricToCartesianCoordinates(x_tet_ref, y_tet_ref, + z_tet_ref, L1_3) + cartesian_coord[:, 2] = tmp[:, 0] + + L1_4 = np.vstack((L1B, L1A, L1A, L1A)) + tmp = volumetricToCartesianCoordinates(x_tet_ref, y_tet_ref, + z_tet_ref, L1_4) + cartesian_coord[:, 3] = tmp[:, 0] + + # Second group K[3,1]. 4 points + W2 = 0.167953517588677620e-2 + Wi[4] = W2 + Wi[5] = W2 + Wi[6] = W2 + Wi[7] = W2 + + L2A = 0.406739585346113397e-1 + L2B = 0.877978124396165982 + + # Volumetric coord to cartesian coord. 4 points + L2_1 = np.vstack((L2A, L2A, L2A, L2B)) + tmp = volumetricToCartesianCoordinates(x_tet_ref, y_tet_ref, + z_tet_ref, L2_1) + cartesian_coord[:, 4] = tmp[:, 0] + + L2_2 = np.vstack((L2A, L2A, L2B, L2A)) + tmp = volumetricToCartesianCoordinates(x_tet_ref, y_tet_ref, + z_tet_ref, L2_2) + cartesian_coord[:, 5] = tmp[:, 0] + + L2_3 = np.vstack((L2A, L2B, L2A, L2A)) + tmp = volumetricToCartesianCoordinates(x_tet_ref, y_tet_ref, + z_tet_ref, L2_3) + cartesian_coord[:, 6] = tmp[:, 0] + + L2_4 = np.vstack((L2B, L2A, L2A, L2A)) + tmp = volumetricToCartesianCoordinates(x_tet_ref, y_tet_ref, + z_tet_ref, L2_4) + cartesian_coord[:, 7] = tmp[:, 0] + + # Third group K[3,1]. 4 points + W3 = 0.922619692394239843e-2 + Wi[8] = W3 + Wi[9] = W3 + Wi[10] = W3 + Wi[11] = W3 + + L3A = 0.322337890142275646 + L3B = 0.329863295731730594e-1 + + # Volumetric coord to cartesian coord. 4 points + L3_1 = np.vstack((L3A, L3A, L3A, L3B)) + tmp = volumetricToCartesianCoordinates(x_tet_ref, y_tet_ref, + z_tet_ref, L3_1) + cartesian_coord[:, 8] = tmp[:, 0] + + L3_2 = np.vstack((L3A, L3A, L3B, L3A)) + tmp = volumetricToCartesianCoordinates(x_tet_ref, y_tet_ref, + z_tet_ref, L3_2) + cartesian_coord[:, 9] = tmp[:, 0] + + L3_3 = np.vstack((L3A, L3B, L3A, L3A)) + tmp = volumetricToCartesianCoordinates(x_tet_ref, y_tet_ref, + z_tet_ref, L3_3) + cartesian_coord[:, 10] = tmp[:, 0] + + L3_4 = np.vstack((L3B, L3A, L3A, L3A)) + tmp = volumetricToCartesianCoordinates(x_tet_ref, y_tet_ref, + z_tet_ref, L3_4) + cartesian_coord[:, 11] = tmp[:, 0] + + # GroupK[2,1,1]. 12 points. + W4 = 0.803571428571428248e-2 + + for iPoint in np.arange(12, npointeg): + Wi[iPoint] = W4 + + L4A = 0.636610018750175299e-1 + L4B = 0.269672331458315867 + L4C = 0.603005664791649076 + + # Volumetric coord to cartesian coord. 12 points. + L4_1 = np.vstack((L4A, L4A, L4B, L4C)) + tmp = volumetricToCartesianCoordinates(x_tet_ref, y_tet_ref, + z_tet_ref, L4_1) + cartesian_coord[:, 12] = tmp[:, 0] + + L4_2 = np.vstack((L4A, L4A, L4C, L4B)) + tmp = volumetricToCartesianCoordinates(x_tet_ref, y_tet_ref, + z_tet_ref, L4_2) + cartesian_coord[:, 13] = tmp[:, 0] + + L4_3 = np.vstack((L4B, L4A, L4A, L4C)) + tmp = volumetricToCartesianCoordinates(x_tet_ref, y_tet_ref, + z_tet_ref, L4_3) + cartesian_coord[:, 14] = tmp[:, 0] + + L4_4 = np.vstack((L4C, L4A, L4A, L4B)) + tmp = volumetricToCartesianCoordinates(x_tet_ref, y_tet_ref, + z_tet_ref, L4_4) + cartesian_coord[:, 15] = tmp[:, 0] + + L4_5 = np.vstack((L4B, L4C, L4A, L4A)) + tmp = volumetricToCartesianCoordinates(x_tet_ref, y_tet_ref, + z_tet_ref, L4_5) + cartesian_coord[:, 16] = tmp[:, 0] + + L4_6 = np.vstack((L4C, L4B, L4A, L4A)) + tmp = volumetricToCartesianCoordinates(x_tet_ref, y_tet_ref, + z_tet_ref, L4_6) + cartesian_coord[:, 17] = tmp[:, 0] + + L4_7 = np.vstack((L4A, L4B, L4A, L4C)) + tmp = volumetricToCartesianCoordinates(x_tet_ref, y_tet_ref, + z_tet_ref, L4_7) + cartesian_coord[:, 18] = tmp[:, 0] + + L4_8 = np.vstack((L4A, L4C, L4A, L4B)) + tmp = volumetricToCartesianCoordinates(x_tet_ref, y_tet_ref, + z_tet_ref, L4_8) + cartesian_coord[:, 19] = tmp[:, 0] + + L4_9 = np.vstack((L4A, L4B, L4C, L4A)) + tmp = volumetricToCartesianCoordinates(x_tet_ref, y_tet_ref, + z_tet_ref, L4_9) + cartesian_coord[:, 20] = tmp[:, 0] + + L4_10 = np.vstack((L4A, L4C, L4B, L4A)) + tmp = volumetricToCartesianCoordinates(x_tet_ref, y_tet_ref, + z_tet_ref, L4_10) + cartesian_coord[:, 21] = tmp[:, 0] + + L4_11 = np.vstack((L4B, L4A, L4C, L4A)) + tmp = volumetricToCartesianCoordinates(x_tet_ref, y_tet_ref, + z_tet_ref, L4_11) + cartesian_coord[:, 22] = tmp[:, 0] + + L4_12 = np.vstack((L4C, L4A, L4B, L4A)) + tmp = volumetricToCartesianCoordinates(x_tet_ref, y_tet_ref, + z_tet_ref, L4_12) + cartesian_coord[:, 23] = tmp[:, 0] + + for iPoint in np.arange(npointeg): + x[iPoint] = cartesian_coord[0, iPoint] + y[iPoint] = cartesian_coord[1, iPoint] + z[iPoint] = cartesian_coord[2, iPoint] + else: + disp('Number of gauss points not supported') + + return Wi, x, y, z + + +def volumetricToCartesianCoordinates(x, y, z, L): + ''' Change of volumetric coord to cartesian coord. + + :param float-array x: x-coord of real element. + :param float-array y: y-coord of real element. + :param float-array z: z-coord of real element. + :param float-array L: xyz-coord of reference element. + :return: points in cartesian coord. + :rtype: ndarray + ''' + # Number of points + size = L.shape + nPoints = size[1] + + # Number of dimensions + nDimensions = 3 + + # Allocate + points = np.zeros((nDimensions, nPoints), dtype=np.float) + + # Change of volumetric coord to cartesian coord + LL = np.transpose(L) + + points[0, :] = np.matmul(LL, x) + points[1, :] = np.matmul(LL, y) + points[2, :] = np.matmul(LL, z) + + return points + + +def cartesianToVolumetricCoordinates(x, y, z, r): + ''' Change of cartesian coord to volumetric coord. + + :param float-array x: x-coord of real element. + :param float-array y: y-coord of real element. + :param float-array z: z-coord of real element. + :param float-array r: xyz-coord of reference element. + :return: points in volumetric coord. + :rtype: ndarray + ''' + + # Initialization + matrix = np.vstack((x, y, z, np.ones(4, dtype=np.float))) + + # Coord transformation + matrix_inv = inv(matrix) + + L = np.zeros(4, dtype=np.float) + tmp = np.hstack((r[0], r[1], r[2], np.float(1))) + L[0] = np.dot(matrix_inv[0, :], tmp) + L[1] = np.dot(matrix_inv[1, :], tmp) + L[2] = np.dot(matrix_inv[2, :], tmp) + L[3] = np.dot(matrix_inv[3, :], tmp) + + return L + + def unitary_test(): ''' Unitary test for fem.py script. ''' + if __name__ == '__main__': # Standard module import unitary_test() @@ -901,5 +1353,6 @@ def unitary_test(): # Standard module import import itertools import numpy as np + from scipy.linalg import inv # PETGEM module import from petgem.efem.vectorMatrixFunctions import findUniqueRows diff --git a/petgem/efem/vectorMatrixFunctions.py b/petgem/efem/vectorMatrixFunctions.py old mode 100644 new mode 100755 index 16ba751..aeefc58 --- a/petgem/efem/vectorMatrixFunctions.py +++ b/petgem/efem/vectorMatrixFunctions.py @@ -30,6 +30,8 @@ def findUniqueRows(array, return_index=False, return_inverse=False): unique array. :param bool return_inverse: indices of the unique array that can be used to reconstruct array. + :return: unique rows. + :rtype: ndarray. ''' array = np.ascontiguousarray(array) @@ -55,10 +57,43 @@ def findUniqueRows(array, return_index=False, return_inverse=False): return out +def crossprod(x, y): + ''' Compute cross product of two arrays. + + :param float-array x: array1. + :param float-array y: array2. + :return: cross product. + :rtype: ndarray. + ''' + # Cross product + compx = x[1]*y[2] - x[2]*y[1] + compy = -(x[0]*y[2] - x[2]*y[0]) + compz = x[0]*y[1] - x[1]*y[0] + + z = np.vstack((compx, compy, compz)) + + return z + + +def is_duplicate_entry(x): + ''' Compute number of duplicate entries in a vector. + + :param int-array x: matrix. + :return: number of duplicate entries. + :rtype: int. + ''' + counts = np.bincount(x) + duplicate_entries = np.where(counts > 1)[0] + num_duplicate = duplicate_entries.size + + return num_duplicate + + def unitary_test(): ''' Unitary test for vector_matrix_functions.py script. ''' + if __name__ == '__main__': # Standard module import import sys diff --git a/petgem/mesh/__init__.py b/petgem/mesh/__init__.py old mode 100644 new mode 100755 diff --git a/petgem/mesh/__pycache__/__init__.cpython-35.pyc b/petgem/mesh/__pycache__/__init__.cpython-35.pyc new file mode 100755 index 0000000..a0dff85 Binary files /dev/null and b/petgem/mesh/__pycache__/__init__.cpython-35.pyc differ diff --git a/petgem/mesh/__pycache__/__init__.cpython-36.pyc b/petgem/mesh/__pycache__/__init__.cpython-36.pyc new file mode 100755 index 0000000..c21eb9e Binary files /dev/null and b/petgem/mesh/__pycache__/__init__.cpython-36.pyc differ diff --git a/petgem/mesh/__pycache__/mesh.cpython-35.pyc b/petgem/mesh/__pycache__/mesh.cpython-35.pyc new file mode 100755 index 0000000..a9c4edc Binary files /dev/null and b/petgem/mesh/__pycache__/mesh.cpython-35.pyc differ diff --git a/petgem/mesh/__pycache__/mesh.cpython-36.pyc b/petgem/mesh/__pycache__/mesh.cpython-36.pyc new file mode 100644 index 0000000..ce8e019 Binary files /dev/null and b/petgem/mesh/__pycache__/mesh.cpython-36.pyc differ diff --git a/petgem/mesh/mesh.py b/petgem/mesh/mesh.py old mode 100644 new mode 100755 index cbd0bd7..9907c12 --- a/petgem/mesh/mesh.py +++ b/petgem/mesh/mesh.py @@ -105,6 +105,8 @@ def __init__(self, mshfilename): # Close file self.mshfID.close() + return + def __gmshPrint__(self, msg): ''' Print a message related with a gmshObject. @@ -122,6 +124,8 @@ def __gmshError__(self, msg): ''' sys.stderr.write(' gmshObject: Error! -> ' + msg + '\n') + return + def __destroy__(self): ''' Destroy a gmshObject. @@ -129,6 +133,8 @@ def __destroy__(self): ''' self.mshfID.close() + return + def printNumberNodes(self): ''' Print number of nodes of a gmshObject. @@ -373,12 +379,16 @@ def gmshParser(self): hexahedron_125_node = np.int32(93) -def getMeshInfo(elemsN, edgesN, bEdges, rank): +def getMeshInfo(nedelec_order, elemsN, elemsF, + facesN, edgesN, boundaries, rank): ''' Get and print data mesh. + :param int nedelec_order: nedelec element order. :param matrix elemsN: elements-nodes connectivity. + :param matrix elemsF: elements-faces connectivity. + :param matrix facesN: faces-nodes connectivity. :param matrix edgesN: edges-nodes connectivity. - :param vector bEdges: boundary edges. + :param vector boundaries: boundaries. :return: number of elements, number of edges, number of boundaries and number of degrees of freedom :rtype: int @@ -388,23 +398,42 @@ def getMeshInfo(elemsN, edgesN, bEdges, rank): # Number of edges nEdges = edgesN.getSize()[0] # Number of boundaries - nBoundaries = bEdges.getSize() - # Number of dofs - ndofs = nEdges - nBoundaries + nBoundaries = boundaries.getSize() + # Number of faces + Istart_facesN, Iend_facesN = facesN.getOwnershipRange() + tmp = (facesN.getRow(Istart_facesN)[1].real).astype(PETSc.IntType) + nFaces = tmp[0] + + # Compute number of DOFS + if nedelec_order == 1: # First order edge element + # Number of DOFs correspond to edges in the mesh + ndofs = nEdges + elif nedelec_order == 2: # Second order edge element + # Number of dofs + ndofs = nEdges*2 + nFaces*2 + elif nedelec_order == 3: # Third order edge element + # Number of dofs + ndofs = nEdges*3 + nFaces*6 + nElems*3 + else: + raise ValueError('Edge element order=', + nedelec_order, ' not supported.') + # Print mesh information if rank == 0: PETSc.Sys.Print('\nMesh information') PETSc.Sys.Print('='*75) PETSc.Sys.Print(' Number of elements: {0:12}'. format(str(nElems))) + PETSc.Sys.Print(' Number of faces: {0:12}'. + format(str(nFaces))) PETSc.Sys.Print(' Number of edges: {0:12}'. format(str(nEdges))) PETSc.Sys.Print(' Number of dofs: {0:12}'. format(str(ndofs))) - PETSc.Sys.Print(' Number of boundary edges: {0:12}'. + PETSc.Sys.Print(' Number of boundaries: {0:12}'. format(str(nBoundaries))) - return nElems, nEdges, nBoundaries, ndofs + return nElems, nFaces, nEdges, ndofs, nBoundaries def readGmshNodes(mesh_file): @@ -580,6 +609,7 @@ def unitary_test(): ''' Unitary test for mesh.py script. ''' + if __name__ == '__main__': # Standard module import import numpy as np diff --git a/petgem/monitoring/__init__.py b/petgem/monitoring/__init__.py old mode 100644 new mode 100755 diff --git a/petgem/monitoring/__pycache__/__init__.cpython-35.pyc b/petgem/monitoring/__pycache__/__init__.cpython-35.pyc new file mode 100755 index 0000000..da97d85 Binary files /dev/null and b/petgem/monitoring/__pycache__/__init__.cpython-35.pyc differ diff --git a/petgem/monitoring/__pycache__/__init__.cpython-36.pyc b/petgem/monitoring/__pycache__/__init__.cpython-36.pyc new file mode 100755 index 0000000..bf69f48 Binary files /dev/null and b/petgem/monitoring/__pycache__/__init__.cpython-36.pyc differ diff --git a/petgem/monitoring/__pycache__/monitoring.cpython-35.pyc b/petgem/monitoring/__pycache__/monitoring.cpython-35.pyc new file mode 100755 index 0000000..b0a2aee Binary files /dev/null and b/petgem/monitoring/__pycache__/monitoring.cpython-35.pyc differ diff --git a/petgem/monitoring/__pycache__/monitoring.cpython-36.pyc b/petgem/monitoring/__pycache__/monitoring.cpython-36.pyc new file mode 100644 index 0000000..406e007 Binary files /dev/null and b/petgem/monitoring/__pycache__/monitoring.cpython-36.pyc differ diff --git a/petgem/monitoring/monitoring.py b/petgem/monitoring/monitoring.py old mode 100644 new mode 100755 diff --git a/petgem/parallel/__init__.py b/petgem/parallel/__init__.py old mode 100644 new mode 100755 diff --git a/petgem/parallel/__pycache__/__init__.cpython-35.pyc b/petgem/parallel/__pycache__/__init__.cpython-35.pyc new file mode 100755 index 0000000..8e53774 Binary files /dev/null and b/petgem/parallel/__pycache__/__init__.cpython-35.pyc differ diff --git a/petgem/parallel/__pycache__/__init__.cpython-36.pyc b/petgem/parallel/__pycache__/__init__.cpython-36.pyc new file mode 100755 index 0000000..0462201 Binary files /dev/null and b/petgem/parallel/__pycache__/__init__.cpython-36.pyc differ diff --git a/petgem/parallel/__pycache__/parallel.cpython-35.pyc b/petgem/parallel/__pycache__/parallel.cpython-35.pyc new file mode 100755 index 0000000..09a4973 Binary files /dev/null and b/petgem/parallel/__pycache__/parallel.cpython-35.pyc differ diff --git a/petgem/parallel/__pycache__/parallel.cpython-36.pyc b/petgem/parallel/__pycache__/parallel.cpython-36.pyc new file mode 100644 index 0000000..6e7e9e6 Binary files /dev/null and b/petgem/parallel/__pycache__/parallel.cpython-36.pyc differ diff --git a/petgem/parallel/parallel.py b/petgem/parallel/parallel.py old mode 100644 new mode 100755 index 8239123..1a337a6 --- a/petgem/parallel/parallel.py +++ b/petgem/parallel/parallel.py @@ -36,21 +36,31 @@ def printMessage(msg, rank): return -def createParallelMatrix(dimension1, dimension2, nnz, communicator=None): +def createParallelMatrix(dimension1, dimension2, nnz, + matrix_type, communicator=None): ''' Create a parallel sparse matrix in petsc format. - :param int dimension1: matrix dimension (rows) - :param int dimension2: matrix dimension (columns) - :param int nnz: not zero pattern for allocation - :param str communicator: mpi communicator - :return: parallel matrix - :rtype: petsc AIJ parallel matrix + :param int dimension1: matrix dimension (rows). + :param int dimension2: matrix dimension (columns). + :param int nnz: not zero pattern for allocation. + :param int matrix_type: matrix type for parallel computations. + :param str communicator: mpi communicator. + :return: parallel matrix. + :rtype: petsc AIJ parallel matrix. ''' if communicator is None: communicator = PETSc.COMM_WORLD - parallel_matrix = PETSc.Mat().createAIJ([dimension1, dimension2], - comm=communicator) + if matrix_type == 0: # No cuda support + parallel_matrix = PETSc.Mat().createAIJ([dimension1, dimension2], + comm=communicator) + elif matrix_type == 1: # Cuda support + parallel_matrix = PETSc.Mat().create(comm=communicator) + parallel_matrix.setType('aijcusparse') + parallel_matrix.setSizes([dimension1, dimension2]) + else: + raise ValueError('Cuda option=', matrix_type, ' not supported.') + parallel_matrix.setPreallocationNNZ((nnz, nnz)) parallel_matrix.setFromOptions() parallel_matrix.setUp() @@ -58,18 +68,26 @@ def createParallelMatrix(dimension1, dimension2, nnz, communicator=None): return parallel_matrix -def createParallelVector(size, communicator=None): +def createParallelVector(size, vector_type, communicator=None): ''' Create a parallel vector in petsc format. - :param int size: vector size - :param str communicator: mpi communicator - :return: parallel vector - :rtype: petsc parallel vector + :param int size: vector size. + :param int vector_type: vector type for parallel computations. + :param str communicator: mpi communicator. + :return: parallel vector. + :rtype: petsc parallel vector. ''' if communicator is None: communicator = PETSc.COMM_WORLD - parallel_vector = PETSc.Vec().createMPI(size, comm=communicator) + if vector_type == 0: # No cuda support + parallel_vector = PETSc.Vec().createMPI(size, comm=communicator) + elif vector_type == 1: # Cuda support + parallel_vector = PETSc.Vec().create(comm=communicator) + parallel_vector.setType('cuda') + parallel_vector.setSizes(size) + else: + raise ValueError('Cuda option=', vector_type, ' not supported.') parallel_vector.setUp() return parallel_vector @@ -78,11 +96,11 @@ def createParallelVector(size, communicator=None): def createParallelDenseMatrix(dimension1, dimension2, communicator=None): ''' Create a parallel dense matrix in petsc format. - :param int dimension1: matrix dimension (rows) - :param int dimension2: matrix dimension (columns) - :param str communicator: mpi communicator - :return: parallel matrix - :rtype: petsc parallel and dense matrix + :param int dimension1: matrix dimension (rows). + :param int dimension2: matrix dimension (columns). + :param str communicator: mpi communicator. + :return: parallel matrix. + :rtype: petsc parallel and dense matrix. ''' if communicator is None: communicator = PETSc.COMM_WORLD @@ -98,11 +116,11 @@ def createParallelDenseMatrix(dimension1, dimension2, communicator=None): def createSequentialDenseMatrixWithArray(dimension1, dimension2, data): ''' Given an input array, create a sequential dense matrix in petsc format. - :param int dimension1: matrix dimension (rows) - :param int dimension2: matrix dimension (columns) - :param ndarray data: data to be exported - :return: parallel matrix - :rtype: petsc parallel and dense matrix + :param int dimension1: matrix dimension (rows). + :param int dimension2: matrix dimension (columns). + :param ndarray data: data to be exported. + :return: parallel matrix. + :rtype: petsc parallel and dense matrix. ''' parallel_matrix = PETSc.Mat().createDense([dimension1, dimension2], @@ -117,9 +135,9 @@ def createSequentialDenseMatrixWithArray(dimension1, dimension2, data): def createSequentialVectorWithArray(data): ''' Given an input array, create a sequential vector in petsc format. - :param ndarray data: data to be exported - :return: parallel matrix - :rtype: petsc parallel and dense matrix + :param ndarray data: data to be exported. + :return: parallel matrix. + :rtype: petsc parallel and dense matrix. ''' parallel_vector = PETSc.Vec().createWithArray(data, comm=PETSc.COMM_SELF) @@ -129,18 +147,29 @@ def createSequentialVectorWithArray(data): return parallel_vector -def createSequentialVector(size, communicator=None): +def createSequentialVector(size, vector_type, communicator=None): ''' Create a sequential vector in petsc format. - :param int size: vector size - :param str communicator: mpi communicator - :return: sequential vector - :rtype: petsc sequential vector + :param int size: vector size. + :param int vector_type: vector type for parallel computations. + :param str communicator: mpi communicator. + :return: sequential vector. + :rtype: petsc sequential vector. ''' if communicator is None: communicator = PETSc.COMM_SELF - sequential_vector = PETSc.Vec().createSeq(size, comm=communicator) + #sequential_vector = PETSc.Vec().createSeq(size, comm=communicator) + #sequential_vector.setUp() + + + if vector_type == 0: # No cuda support + sequential_vector = PETSc.Vec().createSeq(size, comm=communicator) + elif vector_type == 1: # Cuda support + sequential_vector = PETSc.Vec().create(comm=communicator) + sequential_vector.setType('cuda') + sequential_vector.setSizes(size) + sequential_vector.setUp() return sequential_vector @@ -150,10 +179,10 @@ def readPetscMatrix(input_file, communicator=None): ''' Read a Petsc matrix which format is defined by two files: input_file.dat and input_file.info - :param str input_file: file name to be readed - :param str communicator: mpi communicator - :return: petsc_matrix - :rtype: petsc sparse matrix + :param str input_file: file name to be readed. + :param str communicator: mpi communicator. + :return: petsc_matrix. + :rtype: petsc sparse matrix. ''' if communicator is None: communicator = PETSc.COMM_WORLD @@ -169,10 +198,10 @@ def readPetscVector(input_file, communicator=None): ''' Read a Petsc vector which format is defined by two files: input_file.dat and input_file.info. - :param str input_file: file name to be readed - :param str communicator: mpi communicator - :return: petsc_vector - :rtype: petsc vector + :param str input_file: file name to be readed. + :param str communicator: mpi communicator. + :return: petsc_vector. + :rtype: petsc vector. ''' if communicator is None: communicator = PETSc.COMM_WORLD @@ -188,10 +217,10 @@ def writePetscVector(output_file, data, communicator=None): ''' Write a Petsc vector which format is defined by two files: output_file.dat and output_file.info. - :param str output_file: file name to be saved - :param petsc vector data: array to be saved - :param str communicator: mpi communicator - :return: None + :param str output_file: file name to be saved. + :param petsc vector data: array to be saved. + :param str communicator: mpi communicator. + :return: None. ''' if communicator is None: communicator = PETSc.COMM_WORLD @@ -207,10 +236,10 @@ def writeDenseMatrix(output_file, data, communicator=None): ''' Write a Petsc dense matrix which format is defined by two files: output_file.dat and output_file.info. - :param str output_file: file name to be saved - :param petsc matrix data: dense matrix to be saved - :param str communicator: mpi communicator - :return: None + :param str output_file: file name to be saved. + :param petsc matrix data: dense matrix to be saved. + :param str communicator: mpi communicator. + :return: None. ''' if communicator is None: communicator = PETSc.COMM_WORLD @@ -228,10 +257,10 @@ def writeParallelDenseMatrix(output_file, data, communicator=None): ''' Write a Petsc parallel dense matrix which format is defined by two files: output_file.dat and output_file.info. - :param str output_file: file name to be saved - :param petsc matrix data: dense matrix to be saved - :param str communicator: mpi communicator - :return: None + :param str output_file: file name to be saved. + :param petsc matrix data: dense matrix to be saved. + :param str communicator: mpi communicator. + :return: None. ''' if communicator is None: communicator = PETSc.COMM_WORLD @@ -243,26 +272,27 @@ def writeParallelDenseMatrix(output_file, data, communicator=None): return -def getRanges(elemsE, bEdges, receivers, size, rank): +def getRanges(elemsE, boundaries, receivers, size, rank): ''' Get owner ship ranges of matrices and vectors for parallel computations. - :param petsc matrix elemsE: elements-edges connectivity - :param petsc vector bEdges: boundary-edges indexes - :param petsc matrix receivers: receivers matrix - :param int size: size of the parallel pool - :para int rank: MPI rank + :param petsc matrix elemsE: elements-edges connectivity. + :param petsc vector boundaries: boundary indexes. + :param petsc matrix receivers: receivers matrix. + :param int size: size of the parallel pool. + :para int rank: MPI rank. :return: Istart_elemsE, Iend_elemsE, Istart_bEdges, - Iend_bEdges, Istart_bEdges, Iend_bEdges, - Istart_receivers, Iend_receivers + Iend_bEdges, Istart_boundaries, Iend_boundaries, + Istart_receivers, Iend_receivers. :rtype: int ''' # Get global indices # elemsE Istart_elemsE, Iend_elemsE = elemsE.getOwnershipRange() # bEdges - Istart_bEdges, Iend_bEdges = bEdges.getOwnershipRange() + Istart_boundaries, Iend_boundaries = boundaries.getOwnershipRange() # receivers Istart_receivers, Iend_receivers = receivers.getOwnershipRange() + if rank == 0: # Print size of MPI pool PETSc.Sys.Print(' Number of MPI tasks: ', size) @@ -273,53 +303,104 @@ def getRanges(elemsE, bEdges, receivers, size, rank): Istart_elemsE, Iend_elemsE) PETSc.Sys.syncFlush() - return (Istart_elemsE, Iend_elemsE, Istart_bEdges, - Iend_bEdges, Istart_receivers, Iend_receivers) + return (Istart_elemsE, Iend_elemsE, Istart_boundaries, + Iend_boundaries, Istart_receivers, Iend_receivers) -def parallelAssembler(modelling, A, b, nodes, elemsE, elemsN, elemsSigma, - Istart_elemsE, Iend_elemsE, rank): +def parallelAssembler(modelling, A, b, nodes, elemsE, elemsN, elemsF, facesN, + elemsSigma, Istart_elemsE, Iend_elemsE, nEdges, nFaces, + rank): ''' Assembly matrix A and vector b for 3D CSEM in parallel. - :param dictionary modelling: CSEM modelling with physical parameters - :param petsc matrix A: left-hand side - :param petsc vector b: right-hand side - :param petsc matrix nodes: nodal coordinates - :param petsc matrix elemsE: elements-edges connectivity - :param petsc matrix elemsN: elements-nodes connectivity - :param petsc vector elemsSigma: elements-conductivity array - :param int Istart_elemsE: init range for assembly - :param int Iend_elemsE: last range for assembly - :para int rank: MPI rank - :return: matrix A, vector b assembled, elapsedTimeAssembly - :rtype: petsc matrix, petsc vector and float + :param dictionary modelling: CSEM modelling with physical parameters. + :param petsc matrix A: left-hand side. + :param petsc vector b: right-hand side. + :param petsc matrix nodes: nodal coordinates. + :param petsc matrix elemsE: elements-edges connectivity. + :param petsc matrix elemsN: elements-nodes connectivity. + :param petsc matrix elemsF: elements-faces connectivity. + :param petsc matrix facesN: faces-nodes connectivity. + :param petsc vector elemsSigma: elements-conductivity array. + :param int Istart_elemsE: init range for assembly. + :param int Iend_elemsE: last range for assembly. + :param int nEdges: total number of edges in the mesh. + :para int rank: MPI rank. + :return: matrix A, vector b assembled, elapsedTimeAssembly. + :rtype: petsc matrix, petsc vector and float. ''' # Print information of assembly PETSc.Sys.syncPrint(' Rank: ', rank, ' is assembling ', Iend_elemsE-Istart_elemsE, ' elements') PETSc.Sys.syncFlush() - # Start timer - Init_assembly = getTime() - - # Compute contributions for all local elements - for iEle in np.arange(Istart_elemsE, Iend_elemsE): - # Get coordinates of iEle - coordEle = nodes.getRow(iEle)[1].real - # Get edges of iEle - edgesEle = (elemsE.getRow(iEle)[1].real).astype(PETSc.IntType) - # Get nodal indexes of iEle - nodesEle = elemsN.getRow(iEle)[1].real - # Get sigma of iEle - sigmaEle = elemsSigma.getValue(iEle).real - # Compute elemental contributions for iEle - # Elemental matrix (Ae) and elemental vector (be) - [Ae, be] = computeElementalContributionsMPI(modelling, coordEle, - nodesEle, sigmaEle) - # Add local contributions to global matrix - A.setValues(edgesEle, edgesEle, Ae, addv=PETSc.InsertMode.ADD_VALUES) - # Add local contributions to global vector - b.setValues(edgesEle, be, addv=PETSc.InsertMode.ADD_VALUES) + # Get order of edge elements + nedelec_order = modelling['NEDELEC_ORDER'] + + if nedelec_order == 1: # First order edge element + + # Start timer + Init_assembly = getTime() + + # Compute contributions for all local elements + for iEle in np.arange(Istart_elemsE, Iend_elemsE): + # Get coordinates of iEle + coordEle = nodes.getRow(iEle)[1].real + # Get nodal indexes of iEle + nodesEle = (elemsN.getRow(iEle)[1].real).astype(PETSc.IntType) + # Get sigma of iEle + sigmaEle = elemsSigma.getValue(iEle).real + # Get dofs of iEle + dofsEle = (elemsE.getRow(iEle)[1].real).astype(PETSc.IntType) + # Compute elemental contributions for iEle + # Elemental matrix (Ae) and elemental vector (be) + [Ae, be] = computeElementalContributionsMPI_FirstOrder(modelling, + coordEle, + nodesEle, + sigmaEle) + # Add local contributions to global matrix + A.setValues(dofsEle, dofsEle, Ae, addv=PETSc.InsertMode.ADD_VALUES) + # Add local contributions to global vector + b.setValues(dofsEle, be, addv=PETSc.InsertMode.ADD_VALUES) + + # Second or third order edge element + elif nedelec_order == 2 or nedelec_order == 3: + + # Start timer + Init_assembly = getTime() + + # Compute contributions for all local elements + for iEle in np.arange(Istart_elemsE, Iend_elemsE): + # Get coordinates of iEle + coordEle = nodes.getRow(iEle)[1].real + # Get edges indexes of iEle + edgesEle = (elemsE.getRow(iEle)[1].real).astype(PETSc.IntType) + # Get nodal indexes of iEle + nodesEle = (elemsN.getRow(iEle)[1].real).astype(PETSc.IntType) + # Get faces indexes of iEle + facesEle = (elemsF.getRow(iEle)[1].real).astype(PETSc.IntType) + # Get nodes indexes for each face + nodesFaceEle = (facesN.getRow(iEle)[1].real).astype(PETSc.IntType) + nodesFaceEle = np.reshape(np.delete(nodesFaceEle, 0), (4, 3)) + # Get sigma of iEle + sigmaEle = elemsSigma.getValue(iEle).real + + # Compute dofs of iEle + dofsEle = computeElementDOFs(iEle, nodesEle, edgesEle, facesEle, + nodesFaceEle, nEdges, nFaces, + nedelec_order) + dofsEle = dofsEle.astype(PETSc.IntType) + # Compute elemental contributions for iEle + # Elemental matrix (Ae) and elemental vector (be) + [Ae, + be] = computeElementalContributionsMPI_HighOrder(modelling, + coordEle, + nodesEle, + sigmaEle, + nedelec_order) + # Add local contributions to global matrix + A.setValues(dofsEle, dofsEle, Ae, addv=PETSc.InsertMode.ADD_VALUES) + # Add local contributions to global vector + b.setValues(dofsEle, be, addv=PETSc.InsertMode.ADD_VALUES) # Start global system assembly A.assemblyBegin() @@ -341,6 +422,7 @@ def unitary_test(): ''' Unitary test for parallel.py script. ''' + if __name__ == '__main__': # Standard module import unitary_test() @@ -350,4 +432,6 @@ def unitary_test(): from petsc4py import PETSc # PETGEM module import from petgem.monitoring.monitoring import getTime - from petgem.solver.assembler import computeElementalContributionsMPI + from petgem.efem.efem import computeElementDOFs + from petgem.solver.assembler import computeElementalContributionsMPI_FirstOrder + from petgem.solver.assembler import computeElementalContributionsMPI_HighOrder diff --git a/petgem/postprocessing/__init__.py b/petgem/postprocessing/__init__.py old mode 100644 new mode 100755 diff --git a/petgem/postprocessing/__pycache__/__init__.cpython-35.pyc b/petgem/postprocessing/__pycache__/__init__.cpython-35.pyc new file mode 100755 index 0000000..a6e1b46 Binary files /dev/null and b/petgem/postprocessing/__pycache__/__init__.cpython-35.pyc differ diff --git a/petgem/postprocessing/__pycache__/__init__.cpython-36.pyc b/petgem/postprocessing/__pycache__/__init__.cpython-36.pyc new file mode 100755 index 0000000..c1ea915 Binary files /dev/null and b/petgem/postprocessing/__pycache__/__init__.cpython-36.pyc differ diff --git a/petgem/postprocessing/__pycache__/postprocessing.cpython-35.pyc b/petgem/postprocessing/__pycache__/postprocessing.cpython-35.pyc new file mode 100755 index 0000000..bc4c258 Binary files /dev/null and b/petgem/postprocessing/__pycache__/postprocessing.cpython-35.pyc differ diff --git a/petgem/postprocessing/__pycache__/postprocessing.cpython-36.pyc b/petgem/postprocessing/__pycache__/postprocessing.cpython-36.pyc new file mode 100644 index 0000000..0a3e791 Binary files /dev/null and b/petgem/postprocessing/__pycache__/postprocessing.cpython-36.pyc differ diff --git a/petgem/postprocessing/postprocessing.py b/petgem/postprocessing/postprocessing.py old mode 100644 new mode 100755 index bb4f8d7..1fa7a3b --- a/petgem/postprocessing/postprocessing.py +++ b/petgem/postprocessing/postprocessing.py @@ -103,13 +103,13 @@ def EpReceiverComputation(model, point, numDimensions): SIGMA_BGROUND = np.float(model['CONDUCTIVITY_BACKGROUND']) SRC_POS = np.asarray(model['SRC_POS'], dtype=np.float) SRC_DIREC = np.int(model['SRC_DIREC']) - I = np.float(model['SRC_CURRENT']) + II = np.float(model['SRC_CURRENT']) dS = np.float(model['SRC_LENGTH']) # Source position as independent variables - X0 = np.float64(SRC_POS[0]) - Y0 = np.float64(SRC_POS[1]) - Z0 = np.float64(SRC_POS[2]) + X0 = np.float(SRC_POS[0]) + Y0 = np.float(SRC_POS[1]) + Z0 = np.float(SRC_POS[2]) # Compute constants # Imaginary part for complex numbers @@ -128,37 +128,37 @@ def EpReceiverComputation(model, point, numDimensions): xx = point[0] - X0 yy = point[1] - Y0 zz = point[2] - Z0 - r = np.float64(np.sqrt(xx**2 + yy**2 + zz**2)) + r = np.float(np.sqrt(xx**2 + yy**2 + zz**2)) - if(r < np.float64(1.0e0)): - r = np.float64(1.0e0) + if(r < np.float(1.0e0)): + r = np.float(1.0e0) # E = AA [ BB + (wavenumber^2 * r^2 -1i * wavenumber * r-1)] - AA = I * dS * \ - (np.float64(4.0) * np.pi * SIGMA_BGROUND * r**3)**-1 * \ + AA = II * dS * \ + (np.float(4.0) * np.pi * SIGMA_BGROUND * r**3)**-1 * \ np.exp((-imag_part) * WAVENUMBER * r) BB = -WAVENUMBER**2 * r**2 + \ - (np.float64(3.0) * imag_part * WAVENUMBER * r) + \ - np.float64(3.0) + (np.float(3.0) * imag_part * WAVENUMBER * r) + \ + np.float(3.0) if SRC_DIREC == 1: # X-directed Ep[0] = AA * ((xx**2/r**2)*BB + (WAVENUMBER**2 * r**2 - imag_part * - WAVENUMBER * r - np.float64(1.0))) + WAVENUMBER * r - np.float(1.0))) Ep[1] = AA * (xx*yy/r**2)*BB Ep[2] = AA * (xx*zz/r**2)*BB elif SRC_DIREC == 2: # Y-directed Ep[0] = AA * (xx*yy/r**2)*BB Ep[1] = AA * ((yy**2/r**2)*BB + (WAVENUMBER**2 * r**2 - imag_part * - WAVENUMBER*r-np.float64(1.0))) + WAVENUMBER*r-np.float(1.0))) Ep[2] = AA * (yy*zz/r**2)*BB else: # Z-directed Ep[0] = AA * (xx*zz/r**2)*BB Ep[1] = AA * (zz*yy/r**2)*BB Ep[2] = AA * ((zz**2/r**2)*BB + (WAVENUMBER**2 * r**2 - imag_part * - WAVENUMBER*r-np.float64(1.0))) + WAVENUMBER*r-np.float(1.0))) return Ep @@ -181,14 +181,14 @@ def EtReceiverComputation(primary_field, secondary_field, numDimensions): def EsReceiverComputation(field, coordEle, coordReceiver, nodesEle, - edgeOrder, numDimensions): + nedelec_order, numDimensions): ''' Compute the secondary electric field on receivers. :param ndarray field: secondary field to be interpolated :param ndarray coordElement: element spatial coordinates :param ndarray coordReceiver: receiver spatial coordinates :param ndarray nodesEle: nodal indices of element (element container) - :param int edgeOrder: order of tetrahedral edge element + :param int nedelec_order: order of tetrahedral edge element :param int numDimensions: number of dimensions :return: secondary electric field on receivers :rtype: ndarray. @@ -197,66 +197,186 @@ def EsReceiverComputation(field, coordEle, coordReceiver, nodesEle, # Imaginary part for complex numbers imag_part = np.complex(0.0 + 1.0j) - # ----------- Edge definition for the tetrahedral elements ----------- - # ----- Table 8.2 of Jin's book. Here is consider as an 1D-array ----- - edgesN = np.array([0, 1, 0, 2, 0, 3, 1, 2, 3, 1, 2, 3], dtype=np.int) - - # ----------- Definition of arrays for vector operations ----------- - # Signs computation - idx_signs1 = np.array([1, 2, 3, 2, 1, 3], dtype=np.int) - idx_signs2 = np.array([0, 0, 0, 1, 3, 2], dtype=np.int) - - # Allocate - Es = np.zeros(numDimensions, dtype=np.complex) - - # ----------- Compute edges's length of element ----------- - tmp = coordEle.reshape(4, 3) - tmp = tmp[edgesN, :] - edges = tmp[1::2, :] - tmp[0::2, :] - lengthEle = np.sqrt(np.sum(np.square(edges), axis=1)) - - # ----------- Compute element's volume ----------- - CONST_VOL1 = np.float(1.0)/np.float(6.0) - eleVol = (((coordEle[3]-coordEle[0])*(coordEle[7]-coordEle[1]) * - (coordEle[11]-coordEle[2]) + - (coordEle[4]-coordEle[1])*(coordEle[8]-coordEle[2]) * - (coordEle[9]-coordEle[0]) + - (coordEle[6]-coordEle[0])*(coordEle[10]-coordEle[1]) * - (coordEle[5]-coordEle[2])) - - ((coordEle[5]-coordEle[2])*(coordEle[7]-coordEle[1]) * - (coordEle[9]-coordEle[0]) + - (coordEle[6]-coordEle[0])*(coordEle[4]-coordEle[1]) * - (coordEle[11]-coordEle[2]) + - (coordEle[10]-coordEle[1])*(coordEle[8]-coordEle[2]) * - (coordEle[3]-coordEle[0]))) * CONST_VOL1 - - # ----------- Edge's signs ----------- - tmp = nodesEle - tmp = tmp[idx_signs1] - tmp[idx_signs2] - signsEle = tmp / np.abs(tmp) - - # Nedelec basis computation - basis = nedelecBasisIterative(coordEle.reshape(4, 3), - coordReceiver, eleVol, lengthEle, edgeOrder) - - rField = np.real(field) - iField = np.imag(field) - - # Compute secondary field - for kedge in np.arange(edgeOrder): - # Add contributions - Es[0] += (rField[kedge]*basis[kedge, 0]*signsEle[kedge]) + \ - (imag_part*iField[kedge]*basis[kedge, 0]*signsEle[kedge]) - Es[1] += (rField[kedge]*basis[kedge, 1]*signsEle[kedge]) + \ - (imag_part*iField[kedge]*basis[kedge, 1]*signsEle[kedge]) - Es[2] += (rField[kedge]*basis[kedge, 2]*signsEle[kedge]) + \ - (imag_part*iField[kedge]*basis[kedge, 2]*signsEle[kedge]) + if nedelec_order == 1: # First order edge element + # ----------- Edge element of first order ----------- + edgeOrder = 6 + + # ----------- Edge definition for the tetrahedral elements ----------- + # ----- Table 8.2 of Jin's book. Here is consider as an 1D-array ----- + edgesN = np.array([0, 1, 0, 2, 0, 3, 1, 2, 3, 1, 2, 3], dtype=np.int) + + # ----------- Definition of arrays for vector operations ----------- + # Signs computation + idx_signs1 = np.array([1, 2, 3, 2, 1, 3], dtype=np.int) + idx_signs2 = np.array([0, 0, 0, 1, 3, 2], dtype=np.int) + + # Allocate + Es = np.zeros(numDimensions, dtype=np.complex) + + # ----------- Compute edges's length of element ----------- + tmp = coordEle.reshape(4, 3) + tmp = tmp[edgesN, :] + edges = tmp[1::2, :] - tmp[0::2, :] + lengthEle = np.sqrt(np.sum(np.square(edges), axis=1)) + + # ----------- Compute element's volume ----------- + CONST_VOL1 = np.float(1.0)/np.float(6.0) + eleVol = (((coordEle[3]-coordEle[0])*(coordEle[7]-coordEle[1]) * + (coordEle[11]-coordEle[2]) + + (coordEle[4]-coordEle[1])*(coordEle[8]-coordEle[2]) * + (coordEle[9]-coordEle[0]) + + (coordEle[6]-coordEle[0])*(coordEle[10]-coordEle[1]) * + (coordEle[5]-coordEle[2])) - + ((coordEle[5]-coordEle[2])*(coordEle[7]-coordEle[1]) * + (coordEle[9]-coordEle[0]) + + (coordEle[6]-coordEle[0])*(coordEle[4]-coordEle[1]) * + (coordEle[11]-coordEle[2]) + + (coordEle[10]-coordEle[1])*(coordEle[8]-coordEle[2]) * + (coordEle[3]-coordEle[0]))) * CONST_VOL1 + + # ----------- Edge's signs ----------- + tmp = nodesEle + tmp = tmp[idx_signs1] - tmp[idx_signs2] + signsEle = tmp / np.abs(tmp) + + # Nedelec basis computation + basis = nedelecBasisIterative(coordEle.reshape(4, 3), + coordReceiver, eleVol, + lengthEle, edgeOrder) + + rField = np.real(field) + iField = np.imag(field) + + # Compute secondary field + for kedge in np.arange(edgeOrder): + # Add contributions + Es[0] += (rField[kedge]*basis[kedge, 0]*signsEle[kedge]) + \ + (imag_part*iField[kedge]*basis[kedge, 0]*signsEle[kedge]) + Es[1] += (rField[kedge]*basis[kedge, 1]*signsEle[kedge]) + \ + (imag_part*iField[kedge]*basis[kedge, 1]*signsEle[kedge]) + Es[2] += (rField[kedge]*basis[kedge, 2]*signsEle[kedge]) + \ + (imag_part*iField[kedge]*basis[kedge, 2]*signsEle[kedge]) + + elif nedelec_order == 2: # Second edge element + # ----------- Nodal order ----------- + nodalOrder = 4 + + # ----------- Edge element of second order ----------- + edgeOrder = 20 + + # Allocate + Es = np.zeros(numDimensions, dtype=np.complex) + + # ----- Initialization of edges/vertices/faces numbering ------ + [edge_vertices, face_vertices, ref_ele] = edgeFaceVerticesInit() + + # Element's nodes coordinates + coordEle = coordEle.reshape((numDimensions, nodalOrder), order='F') + + # Elemental computations (Signs, jacobian, edges length, area faces) + [_, _, signs, nreal, + jacob, DetJacob, _] = computeSignsJacobLegth(coordEle, edge_vertices, + face_vertices, + nedelec_order) + # Definition of q vectors on faces + [qface1, qface2, + qface3, qface4, + invjj, GR] = definitionHighOrderTet(nreal, signs, jacob, + nedelec_order) + + # Computation coefficients for basis functions + [a1, a2, a3, a4, + b1, b2, b3, b4, + c1, c2, c3, c4, + D, E, F, G, H, + II, JJ, K] = computeCoefficientsSecondOrder(qface1, qface2, qface3, + qface4, ref_ele, + edge_vertices, + face_vertices) + # Computation of basis functions + basis = nedelecBasisSecondOrder(a1, a2, a3, a4, b1, b2, b3, b4, c1, + c2, c3, c4, D, E, F, G, H, II, JJ, + K, coordEle, coordReceiver) + # Reshape basis array (from 3D to 2D) + basis = np.reshape(basis, (numDimensions, edgeOrder)) + + rField = np.real(field) + iField = np.imag(field) + + # Compute secondary field + for kedge in np.arange(edgeOrder): + # Add contributions + Es[0] += (rField[kedge]*basis[0, kedge]*signs[kedge]) + \ + (imag_part*iField[kedge]*basis[0, kedge]*signs[kedge]) + Es[1] += (rField[kedge]*basis[1, kedge]*signs[kedge]) + \ + (imag_part*iField[kedge]*basis[1, kedge]*signs[kedge]) + Es[2] += (rField[kedge]*basis[2, kedge]*signs[kedge]) + \ + (imag_part*iField[kedge]*basis[2, kedge]*signs[kedge]) + + elif nedelec_order == 3: # Third edge element + # ----------- Nodal order ----------- + nodalOrder = 4 + + # ----------- Edge element of third order ----------- + edgeOrder = 45 + + # Allocate + Es = np.zeros(numDimensions, dtype=np.complex) + + # ----- Initialization of edges/vertices/faces numbering ------ + [edge_vertices, face_vertices, ref_ele] = edgeFaceVerticesInit() + + # Element's nodes coordinates + coordEle = coordEle.reshape((numDimensions, nodalOrder), order='F') + + # Elemental computations (Signs, jacobian, edges length, area faces) + [_, _, signs, nreal, + jacob, _, _] = computeSignsJacobLegth(coordEle, edge_vertices, + face_vertices, nedelec_order) + # Definition of q vectors on faces + [qface1, qface2, + qface3, qface4, + _, _] = definitionHighOrderTet(nreal, signs, jacob, nedelec_order) + + # Computation of coefficients + [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, + b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, + c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, + D, E, F, G, H, II, JJ, K, L, M, N, O, + P, Q, R] = computeCoefficientsThirdOrder(qface1, qface2, + qface3, qface4) + + # Computation of basis functions + basis = nedelecBasisThirdOrder(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, + b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, + c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, + D, E, F, G, H, II, JJ, K, L, M, N, + O, P, Q, R, coordEle, coordReceiver) + + # Reshape basis array (from 3D to 2D) + basis = np.reshape(basis, (numDimensions, edgeOrder)) + + rField = np.real(field) + iField = np.imag(field) + + # Compute secondary field + for kedge in np.arange(edgeOrder): + # Add contributions + Es[0] += (rField[kedge]*basis[0, kedge]*signs[kedge]) + \ + (imag_part*iField[kedge]*basis[0, kedge]*signs[kedge]) + Es[1] += (rField[kedge]*basis[1, kedge]*signs[kedge]) + \ + (imag_part*iField[kedge]*basis[1, kedge]*signs[kedge]) + Es[2] += (rField[kedge]*basis[2, kedge]*signs[kedge]) + \ + (imag_part*iField[kedge]*basis[2, kedge]*signs[kedge]) + else: + raise ValueError('Edge element order=', + nedelec_order, ' not supported.') return Es def computeFieldsReceiver(modelling, coordReceiver, coordElement, - nodesElement, x_field, edgeOrder, numDimensions): + nodesElement, x_field, nedelec_order, numDimensions): ''' Compute the CSEM modelling output: primary electric field, secondary electric field and total electric field on receivers position. @@ -265,7 +385,7 @@ def computeFieldsReceiver(modelling, coordReceiver, coordElement, :param ndarray coordElement: element spatial coordinates :param ndarray nodesElement: nodal indices of element (element container) :param ndarray x_field: vector solution for receiver - :param int edgeOrder: order of tetrahedral edge element + :param int nedelec_order: order of tetrahedral edge element :param int numDimensions: number of dimensions :return: primary, secondary and total electric field :rtype: ndarray @@ -275,7 +395,7 @@ def computeFieldsReceiver(modelling, coordReceiver, coordElement, # ----- Secondary field computation ----- Es = EsReceiverComputation(x_field, coordElement, coordReceiver, - nodesElement, edgeOrder, numDimensions) + nodesElement, nedelec_order, numDimensions) # ----- Total field computation ----- Et = EtReceiverComputation(Ep, Es, numDimensions) @@ -284,8 +404,8 @@ def computeFieldsReceiver(modelling, coordReceiver, coordElement, def postProcessingFields(receivers, modelling, x, Iend_receivers, - Istart_receivers, edgeOrder, nodalOrder, - numDimensions, rank): + Istart_receivers, nedelec_order, vector_type, + nodalOrder, numDimensions, rank): ''' Compute the CSEM modelling output: primary electric field, secondary electric field and total electric field on receivers position. @@ -294,7 +414,7 @@ def postProcessingFields(receivers, modelling, x, Iend_receivers, :param petsc vector x: solution vector :param int Iend_receivers: last range for receivers :param int Istart_receivers: init range for receivers - :param int edgeOrder: order of tetrahedral edge element + :param int nedelec_order: order of tetrahedral edge element :param int nodalOrder: order of tetrahedral nodal element :param int numDimensions: number of dimensions :param int rank: MPI rank @@ -305,6 +425,16 @@ def postProcessingFields(receivers, modelling, x, Iend_receivers, # Start timer Init_postprocessing = getTime() + if nedelec_order == 1: # First order edge element + edgeOrder = 6 + elif nedelec_order == 2: # Second edge element + edgeOrder = 20 + elif nedelec_order == 3: # Third edge element + edgeOrder = 45 + else: + raise ValueError('Edge element order=', + nedelec_order, ' not supported.') + # Number of receivers nReceivers = receivers.getSize()[0] nReceiversLocal = Iend_receivers-Istart_receivers @@ -315,44 +445,45 @@ def postProcessingFields(receivers, modelling, x, Iend_receivers, nReceiversLocal, ' receivers') PETSc.Sys.syncFlush() - # Read edges-connectivity for receivers + # Read dofs-connectivity for receivers # Auxiliar arrays dataRecv = np.zeros(edgeOrder, dtype=np.float) - edgesIdxRecv = np.zeros((nReceiversLocal, edgeOrder), dtype=PETSc.IntType) + dofsIdxRecv = np.zeros((nReceiversLocal, edgeOrder), dtype=PETSc.IntType) idx = 0 for iRecv in np.arange(Istart_receivers, Iend_receivers): # Get data of iRecv temp = np.asarray(receivers.getRow(iRecv)) - dataRecv[:] = np.real(temp[1, 19:25]) + dataRecv[:] = np.real(temp[1, 19:]) # Edge-indexes for iRecv - edgesIdxRecv[idx, :] = (dataRecv).astype(PETSc.IntType) + dofsIdxRecv[idx, :] = (dataRecv).astype(PETSc.IntType) idx += 1 # Gather global solution of x to local vector # Sequential vector for gather tasks x_local = createSequentialVector(edgeOrder*nReceiversLocal, + modelling['CUDA'], communicator=None) # Build Index set in PETSc format - IS_edges = PETSc.IS().createGeneral(edgesIdxRecv.flatten(), - comm=PETSc.COMM_WORLD) + IS_dofs = PETSc.IS().createGeneral(dofsIdxRecv.flatten(), + comm=PETSc.COMM_WORLD) # Build gather vector - gatherVector = PETSc.Scatter().create(x, IS_edges, x_local, None) + gatherVector = PETSc.Scatter().create(x, IS_dofs, x_local, None) # Ghater values gatherVector.scatter(x, x_local, PETSc.InsertMode.INSERT_VALUES, PETSc.ScatterMode.FORWARD) # Post-processing electric fields # Create parallel structures - EpX = createParallelVector(nReceivers, communicator=None) - EpY = createParallelVector(nReceivers, communicator=None) - EpZ = createParallelVector(nReceivers, communicator=None) - EsX = createParallelVector(nReceivers, communicator=None) - EsY = createParallelVector(nReceivers, communicator=None) - EsZ = createParallelVector(nReceivers, communicator=None) - EtX = createParallelVector(nReceivers, communicator=None) - EtY = createParallelVector(nReceivers, communicator=None) - EtZ = createParallelVector(nReceivers, communicator=None) + EpX = createParallelVector(nReceivers, vector_type, communicator=None) + EpY = createParallelVector(nReceivers, vector_type, communicator=None) + EpZ = createParallelVector(nReceivers, vector_type, communicator=None) + EsX = createParallelVector(nReceivers, vector_type, communicator=None) + EsY = createParallelVector(nReceivers, vector_type, communicator=None) + EsZ = createParallelVector(nReceivers, vector_type, communicator=None) + EtX = createParallelVector(nReceivers, vector_type, communicator=None) + EtY = createParallelVector(nReceivers, vector_type, communicator=None) + EtZ = createParallelVector(nReceivers, vector_type, communicator=None) EpDense = createParallelDenseMatrix(nReceivers, numDimensions, communicator=None) EsDense = createParallelDenseMatrix(nReceivers, numDimensions, @@ -385,7 +516,7 @@ def postProcessingFields(receivers, modelling, x, Iend_receivers, (idx * edgeOrder) + edgeOrder], - edgeOrder, + nedelec_order, numDimensions) idx += 1 # Set primary field components @@ -553,6 +684,7 @@ def unitary_test(): ''' Unitary test for post_processing.py script. ''' + if __name__ == '__main__': # Standard module import unitary_test() @@ -574,4 +706,11 @@ def unitary_test(): from petgem.parallel.parallel import printMessage from petgem.parallel.parallel import writeDenseMatrix from petgem.efem.efem import nedelecBasisIterative + from petgem.efem.efem import edgeFaceVerticesInit + from petgem.efem.efem import computeSignsJacobLegth + from petgem.efem.efem import definitionHighOrderTet + from petgem.efem.efem import computeCoefficientsSecondOrder + from petgem.efem.efem import nedelecBasisSecondOrder + from petgem.efem.efem import computeCoefficientsThirdOrder + from petgem.efem.efem import nedelecBasisThirdOrder from petgem.monitoring.monitoring import getTime diff --git a/petgem/preprocessing/__init__.py b/petgem/preprocessing/__init__.py old mode 100644 new mode 100755 index 14e5fec..740363b --- a/petgem/preprocessing/__init__.py +++ b/petgem/preprocessing/__init__.py @@ -3,7 +3,8 @@ from .preprocessing import checkNumberParamsPreprocessing from .preprocessing import preprocessNodes from .preprocessing import preprocessingNodalConnectivity -from .preprocessing import preprocessingDOF +from .preprocessing import preprocessingEdges +from .preprocessing import preprocessingFaces from .preprocessing import preprocessingConductivityModel from .preprocessing import preprocessingDataReceivers from .preprocessing import preprocessingNNZ diff --git a/petgem/preprocessing/__pycache__/__init__.cpython-35.pyc b/petgem/preprocessing/__pycache__/__init__.cpython-35.pyc new file mode 100755 index 0000000..6b054c9 Binary files /dev/null and b/petgem/preprocessing/__pycache__/__init__.cpython-35.pyc differ diff --git a/petgem/preprocessing/__pycache__/__init__.cpython-36.pyc b/petgem/preprocessing/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000..50600e9 Binary files /dev/null and b/petgem/preprocessing/__pycache__/__init__.cpython-36.pyc differ diff --git a/petgem/preprocessing/__pycache__/preprocessing.cpython-35.pyc b/petgem/preprocessing/__pycache__/preprocessing.cpython-35.pyc new file mode 100755 index 0000000..3352d52 Binary files /dev/null and b/petgem/preprocessing/__pycache__/preprocessing.cpython-35.pyc differ diff --git a/petgem/preprocessing/__pycache__/preprocessing.cpython-36.pyc b/petgem/preprocessing/__pycache__/preprocessing.cpython-36.pyc new file mode 100644 index 0000000..d259901 Binary files /dev/null and b/petgem/preprocessing/__pycache__/preprocessing.cpython-36.pyc differ diff --git a/petgem/preprocessing/preprocessing.py b/petgem/preprocessing/preprocessing.py old mode 100644 new mode 100755 index d12037b..577af9c --- a/petgem/preprocessing/preprocessing.py +++ b/petgem/preprocessing/preprocessing.py @@ -32,12 +32,13 @@ def checkNumberParamsPreprocessing(init_params): return num_params -def buildPreprocessing(rank, MESH_FILE, MATERIAL_CONDUCTIVITIES, +def buildPreprocessing(rank, NEDELEC_ORDER, MESH_FILE, MATERIAL_CONDUCTIVITIES, RECEIVERS_FILE, OUT_DIR): ''' Build a dictionary with main parameters for PETGEM preprocessing. :param int rank: MPI rank. + :param in NEDELEC_ORDER: nedelec element order. :param str MESH_FILE: file name of mesh. :param ndarray MATERIAL_CONDUCTIVITIES: conductivity values of materials in the mesh. @@ -51,7 +52,8 @@ def buildPreprocessing(rank, MESH_FILE, MATERIAL_CONDUCTIVITIES, PETSc.Sys.Print(' buildPreprocessing(): Creating a ' + 'preprocessing dictionary.') - preprocessing = {'MESH_FILE': MESH_FILE, + preprocessing = {'NEDELEC_ORDER': NEDELEC_ORDER, + 'MESH_FILE': MESH_FILE, 'MATERIAL_CONDUCTIVITIES': MATERIAL_CONDUCTIVITIES, 'RECEIVERS_FILE': RECEIVERS_FILE, 'OUT_DIR': OUT_DIR @@ -69,18 +71,20 @@ def printPreprocessingData(input_preprocessing, file_name): ''' A = str(file_name) - B = str(input_preprocessing['MESH_FILE']) - C = str(input_preprocessing['MATERIAL_CONDUCTIVITIES']) - D = str(input_preprocessing['RECEIVERS_FILE']) - E = str(input_preprocessing['OUT_DIR']) + B = str(input_preprocessing['NEDELEC_ORDER']) + C = str(input_preprocessing['MESH_FILE']) + D = str(input_preprocessing['MATERIAL_CONDUCTIVITIES']) + E = str(input_preprocessing['RECEIVERS_FILE']) + F = str(input_preprocessing['OUT_DIR']) PETSc.Sys.Print('\nData for preprocessing') PETSc.Sys.Print('='*75) PETSc.Sys.Print(' Preprocessing file name: {0:50}'.format(A)) - PETSc.Sys.Print(' Mesh file: {0:50}'.format(B)) - PETSc.Sys.Print(' Material conductivities: {0:50}'.format(C)) - PETSc.Sys.Print(' Receiver positions file: {0:50}'.format(D)) - PETSc.Sys.Print(' Out directory: {0:50}'.format(E)) + PETSc.Sys.Print(' Nedelec element order: {0:50}'.format(B)) + PETSc.Sys.Print(' Mesh file: {0:50}'.format(C)) + PETSc.Sys.Print(' Material conductivities: {0:50}'.format(D)) + PETSc.Sys.Print(' Receiver positions file: {0:50}'.format(E)) + PETSc.Sys.Print(' Out directory: {0:50}'.format(F)) return @@ -104,6 +108,15 @@ def checkPreprocessingConsistency(rank, in_dict, file_name): PETSc.Sys.Print(' Checking preprocessing parameters consistency.') + # NEDELEC_ORDER parameter + msg = msg1 + 'NEDELEC_ORDER' + msg2 + assert 'NEDELEC_ORDER' in in_dict, msg + msg = msg3 + 'NEDELEC_ORDER' + msg4 + NEDELEC_ORDER = in_dict['NEDELEC_ORDER'] + assert type(NEDELEC_ORDER) is int, msg + PETSc.Sys.Print(' checkPreprocessingConsistency(): NEDELEC_ORDER ' + + 'consistency OK.') + # NODES_FILE parameter msg = msg1 + 'MESH_FILE' + msg2 assert 'MESH_FILE' in in_dict, msg @@ -141,7 +154,8 @@ def checkPreprocessingConsistency(rank, in_dict, file_name): 'OUT_DIR consistency OK.') # Create preprocessing dictionary - out_model = buildPreprocessing(rank, MESH_FILE, MATERIAL_CONDUCTIVITIES, + out_model = buildPreprocessing(rank, NEDELEC_ORDER, MESH_FILE, + MATERIAL_CONDUCTIVITIES, RECEIVERS_FILE, OUT_DIR) return out_model @@ -213,7 +227,6 @@ def readPreprocessingParams(input_params, rank): preprocessing_temp. preprocessing, PARAMS_FILE_NAME) - # Print modelling content if rank == 0: printPreprocessingData(preprocessing, input_path) @@ -321,121 +334,202 @@ def preprocessingNodalConnectivity(mesh_file, out_dir, rank): return nElems -def preprocessingDOF(mesh_file, out_dir, rank): - ''' Preprocess degrees of freedom (DOF) and its associated data structures - of a given mesh in Gmsh format. Here, dofs are defined for edge - finite element computations. +def preprocessingEdges(nedelec_order, mesh_file, out_dir, rank): + ''' Preprocess edges, edge boundaries and its associated data structures + of a given mesh in Gmsh format. For edge finite element of linear + order the edges are the dofs. For edge finite element of second order + the dofs are computed in runtime based on edges and faces on each + tetrahedral element. + :param int nedelec_order: nedelec element order. :param str mesh_file: mesh file name to be preprocess. :param str out_dir: path for output. :param int rank: MPI rank. - :return: number of DOFS. + :return: number of edges. :rtype: int ''' + # ---------- Export Edges ---------- if rank == 0: - PETSc.Sys.Print(' Degrees of freedom (dofs.dat)') + PETSc.Sys.Print(' Edges (edges.dat)') # Check if mesh_file exist success = checkFilePath(mesh_file) if rank == 0: if not success: - msg = (' preprocessingDOF(): file ' + mesh_file + + msg = (' preprocessingEdges(): file ' + mesh_file + ' does not exist.') raise ValueError(msg) # Read connectivity elemsN, nElems = readGmshConnectivity(mesh_file) - # Compute dofs - dofs, dofsNodes = computeDofs(elemsN, nElems) - nDofs = dofsNodes.shape[0] + # Compute edges + elemsE, edgesNodes = computeEdges(elemsN, nElems, nedelec_order) + nEdges = edgesNodes.shape[0] - # Compute faces - elemsF, facesN = computeFaces(elemsN, nElems) + # Compute boundaries + boundaries, nDofs = computeBoundaries(elemsN, nElems, edgesNodes, + nedelec_order) - # Compute boundary faces - boundaryFacesN = computeBoundaryFaces(elemsF, facesN) + # ---------- Export Edges ---------- + # Get matrix dimensions + size = elemsE.shape + # Build PETSc structures + matrix = createSequentialDenseMatrixWithArray(size[0], size[1], elemsE) # Delete unnecesary arrays - del elemsN - del elemsF - del facesN + del elemsE - # Compute boundary dofs - boundaryDofs = computeBoundaryDofs(dofsNodes, boundaryFacesN) + # Verify if OUT_DIR exists + checkIfDirectoryExist(out_dir) - # Delete unnecesary arrays - del boundaryFacesN + # Build path to save the file + out_path = out_dir + 'edges.dat' + + # Write PETGEM edges in PETSc format + writeParallelDenseMatrix(out_path, matrix, communicator=PETSc.COMM_SELF) + + # ---------- Export Edges to nodes ---------- + if rank == 0: + PETSc.Sys.Print(' Edges connectivity (edgesNodes.dat)') - # ---------- DOFS ---------- # Get matrix dimensions - size = dofs.shape + size = edgesNodes.shape # Build PETSc structures - matrix = createSequentialDenseMatrixWithArray(size[0], size[1], dofs) + matrix = createSequentialDenseMatrixWithArray(size[0], size[1], edgesNodes) # Delete unnecesary arrays - del dofs + del edgesNodes # Verify if OUT_DIR exists checkIfDirectoryExist(out_dir) # Build path to save the file - out_path = out_dir + 'dofs.dat' + out_path = out_dir + 'edgesNodes.dat' - # Write PETGEM nodes in PETSc format + # Write PETGEM edgesNodes in PETSc format writeParallelDenseMatrix(out_path, matrix, communicator=PETSc.COMM_SELF) - # ---------- DOFS TO NODES ---------- + # ---------- Export boundaries ---------- if rank == 0: - PETSc.Sys.Print(' Dofs connectivity (dofsNodes.dat)') - - # Get matrix dimensions - size = dofsNodes.shape + PETSc.Sys.Print(' Boundaries (boundaries.dat)') # Build PETSc structures - matrix = createSequentialDenseMatrixWithArray(size[0], size[1], dofsNodes) + vector = createSequentialVectorWithArray(boundaries) # Delete unnecesary arrays - del dofsNodes + del boundaries # Verify if OUT_DIR exists checkIfDirectoryExist(out_dir) # Build path to save the file - out_path = out_dir + 'dofsNodes.dat' + out_path = out_dir + 'boundaries.dat' # Write PETGEM nodes in PETSc format + writePetscVector(out_path, vector, communicator=PETSc.COMM_SELF) + + return nEdges, nDofs + + +def preprocessingFaces(nedelec_order, mesh_file, out_dir, rank): + ''' Preprocess faces, face boundaries and its associated data structures + of a given mesh in Gmsh format. + + :param int nedelec_order: nedelec element order. + :param str mesh_file: mesh file name to be preprocess. + :param str out_dir: path for output. + :param int rank: MPI rank. + :return: number of edges. + :rtype: int + ''' + + # Check if mesh_file exist + success = checkFilePath(mesh_file) + + if rank == 0: + if not success: + msg = (' preprocessingFaces(): file ' + mesh_file + + ' does not exist.') + raise ValueError(msg) + + # Read connectivity + elemsN, nElems = readGmshConnectivity(mesh_file) + + # Compute faces + elemsF, facesN = computeFaces(elemsN, nElems, nedelec_order) + + # Number of faces + size = facesN.shape + nFaces = size[0] + + # Delete unnecesary arrays + del elemsN + + # ---------- Export Faces connectivity ---------- + if rank == 0: + PETSc.Sys.Print(' Faces connectivity (faces.dat)') + + # Get matrix dimensions + size = elemsF.shape + # Build PETSc structures + matrix = createSequentialDenseMatrixWithArray(size[0], size[1], elemsF) + + # Verify if OUT_DIR exists + checkIfDirectoryExist(out_dir) + + # Build path to save the file + out_path = out_dir + 'faces.dat' + + # Write PETGEM edges in PETSc format writeParallelDenseMatrix(out_path, matrix, communicator=PETSc.COMM_SELF) - # ---------- BOUNDARY DOFs ---------- + # ---------- Export Faces connectivity ---------- if rank == 0: - PETSc.Sys.Print(' Boundaries (boundaries.dat)') + PETSc.Sys.Print(' Faces-nodes connectivity (facesN.dat)') + + # Build facesN connectivity for each element + nNodes_face = 3 + nFaces_element = 4 + # Allocate matrix with an additional position to store the total number of + # faces in the mesh + facesN_tmp = np.zeros((nElems, nNodes_face*nFaces_element+1)) + for iEle in np.arange(nElems): + facesElement = elemsF[iEle, :] + nodesFace = facesN[facesElement, :] + facesN_tmp[iEle, 0] = nFaces + facesN_tmp[iEle, 1:] = nodesFace.reshape(1, nNodes_face*nFaces_element) + + # Get matrix dimensions + size = facesN_tmp.shape # Build PETSc structures - vector = createSequentialVectorWithArray(boundaryDofs) + matrix = createSequentialDenseMatrixWithArray(size[0], size[1], facesN_tmp) # Delete unnecesary arrays - del boundaryDofs + del elemsF + del facesN + del facesN_tmp # Verify if OUT_DIR exists checkIfDirectoryExist(out_dir) # Build path to save the file - out_path = out_dir + 'boundaries.dat' + out_path = out_dir + 'facesNodes.dat' - # Write PETGEM nodes in PETSc format - writePetscVector(out_path, vector, communicator=PETSc.COMM_SELF) + # Write PETGEM edges in PETSc format + writeParallelDenseMatrix(out_path, matrix, communicator=PETSc.COMM_SELF) - return nDofs + return nFaces def preprocessingConductivityModel(mesh_file, material_conductivities, out_dir, rank): ''' Preprocess conductivity model associated to a given mesh in Gmsh - format. Here, dofs are defined for edge finite element computations. + format. :param str mesh_file: mesh file name to be preprocess. :param ndarray material_conductivities: conductivity values @@ -495,11 +589,11 @@ def preprocessingConductivityModel(mesh_file, material_conductivities, return -def preprocessingDataReceivers(mesh_file, receivers_file, +def preprocessingDataReceivers(nedelec_order, mesh_file, receivers_file, out_dir, rank): - ''' Preprocess conductivity model associated to a given mesh in Gmsh - format. Here, dofs are defined for edge finite element computations. + ''' Preprocess receivers and its data for a given 3D CSEM model. + :param int nedelec_order: nedelec element order. :param str mesh_file: mesh file name to be preprocess. :param str receivers_file: receiver positions file name to be preprocess. :param str out_dir: path for output. @@ -551,17 +645,26 @@ def preprocessingDataReceivers(mesh_file, receivers_file, # Read connectivity elemsN, nElems = readGmshConnectivity(mesh_file) - # Compute dofs - dofs, _ = computeDofs(elemsN, nElems) + # Compute edges + elemsE, _ = computeEdges(elemsN, nElems, nedelec_order) + + # Number of edges + numberEdges = np.max(elemsE) + np.int(1) - # Overwrite Delaunay structure with mesh_file connectivity + # Compute faces + elemsF, facesN = computeFaces(elemsN, nElems, nedelec_order) + + # Number of faces + numberFaces = np.max(elemsF) + np.int(1) + + # Overwrite Delaunay structure with mesh_file connectivity and points tri.simplices = elemsN.astype(np.int32) # Delete unnecesary arrays del elemsN # Find out which tetrahedral element points are in - recvElems = tri.find_simplex(receivers) + recvElems = tri.find_simplex(receivers, tol=np.spacing(1)) # Determine if all receiver points were found idx = np.where(recvElems < 0)[0] @@ -592,9 +695,22 @@ def preprocessingDataReceivers(mesh_file, receivers_file, np.savetxt(out_path, receivers, fmt='%1.8e') # Allocate space for receives in PETGEM format - numDimensions = 3 - nodalOrder = 4 - edgeOrder = 6 + if nedelec_order == 1: # First order edge element + numDimensions = 3 + nodalOrder = 4 + edgeOrder = 6 + elif nedelec_order == 2: # Second order edge element + numDimensions = 3 + nodalOrder = 4 + edgeOrder = 20 + elif nedelec_order == 3: # Third order edge element + numDimensions = 3 + nodalOrder = 4 + edgeOrder = 45 + else: + raise ValueError('Edge element order=', + nedelec_order, ' not supported.') + allocate = numDimensions+nodalOrder*numDimensions+nodalOrder+edgeOrder tmp = np.zeros((nReceivers, allocate), dtype=np.float) @@ -603,26 +719,46 @@ def preprocessingDataReceivers(mesh_file, receivers_file, for iReceiver in np.arange(nReceivers): # If there is one receiver if nReceivers == 1: + # Get index of tetrahedral element (receiver container) + iEle = recvElems # Get receiver coordinates coordiReceiver = receivers[0:] # Get element coordinates (container element) - coordElement = tri.points[tri.simplices[recvElems, :]] + coordElement = tri.points[tri.simplices[iEle, :]] coordElement = coordElement.flatten() # Get nodal indexes (container element) - nodesElement = tri.simplices[recvElems, :] + nodesElement = tri.simplices[iEle, :] # Get element-dofs indices (container element) - dofsElement = dofs[recvElems, :] + edgesElement = elemsE[iEle, :] + facesElement = elemsF[iEle, :] + nodesFace = facesN[facesElement, :] + + dofsElement = computeElementDOFs(iEle, nodesElement, edgesElement, + facesElement, nodesFace, + numberEdges, numberFaces, + nedelec_order) # If there are more than one receivers else: + # Get index of tetrahedral element (receiver container) + iEle = recvElems[iReceiver] # Get receiver coordinates coordiReceiver = receivers[iReceiver, :] # Get element coordinates (container element) - coordElement = tri.points[tri.simplices[recvElems[iReceiver], :]] + coordElement = tri.points[tri.simplices[iEle, :]] coordElement = coordElement.flatten() # Get nodal indexes (container element) - nodesElement = tri.simplices[recvElems[iReceiver], :] + nodesElement = tri.simplices[iEle, :] # Get element-dofs indices (container element) - dofsElement = dofs[recvElems[iReceiver], :] + edgesElement = elemsE[iEle, :] + facesElement = elemsF[iEle, :] + nodesFace = facesN[facesElement, :] + + # Compute element-dofs indices (container element) + dofsElement = computeElementDOFs(iEle, nodesElement, edgesElement, + facesElement, nodesFace, + numberEdges, numberFaces, + nedelec_order) + # Insert data for iReceiver tmp[iReceiver, 0:3] = coordiReceiver tmp[iReceiver, 3:15] = coordElement @@ -631,7 +767,7 @@ def preprocessingDataReceivers(mesh_file, receivers_file, # Delete unnecesary arrays del tri - del dofs + del elemsE # Get matrix dimensions size = tmp.shape @@ -654,11 +790,26 @@ def preprocessingDataReceivers(mesh_file, receivers_file, return nReceivers -def preprocessingNNZ(mesh_file, out_dir, rank): +def preprocessingNNZ(nedelec_order, mesh_file, out_dir, rank): ''' Preprocess sparsity pattern (NNZ) for parallel matrix allocation - of a given mesh in Gmsh format. Here, dofs are defined for edge - finite element computations. + of a given mesh in Gmsh format. + + Since PETGEM parallelism is based on PETSc, computation of the matrix + sparsity pattern is critical in sake of performance. Furthermore, PETGEM + is based on tetrahedral edge finite elements of first, second and third + order which produces: + + * 6 DOFs per element in first order discretizations + * 20 DOFs per element in second order discretizations + * 45 DOFs per element in third order discretizations + + Hence, the tetrahedral valence is equal to: + * 34 in first order discretizations + * 134 in second order discretizations + * 363 in third order discretizations + + :param int nedelec_order: nedelec element order. :param str mesh_file: mesh file name to be preprocess. :param int rank: MPI rank. :return: None @@ -679,18 +830,34 @@ def preprocessingNNZ(mesh_file, out_dir, rank): # Read connectivity elemsN, nElems = readGmshConnectivity(mesh_file) - # Compute dofs - _, dofsNodes = computeDofs(elemsN, nElems) - nDofs = dofsNodes.shape[0] - - # Since PETGEM parallelism is based on PETSc, computation of the matrix - # sparsity pattern is critical in sake of performance. Furthermore, PETGEM - # V1.0 is based on linear edge finite elements which produces six dofs per - # tetrahedral element. Hence, the tetrahedral valence is equal to 34. based - # on this information we build the NNZ vector. + # Compute number of edges + _, edgesNodes = computeEdges(elemsN, nElems, nedelec_order) + nEdges = edgesNodes.shape[0] + + # Compute number of faces + elemsF, facesN = computeFaces(elemsN, nElems, nedelec_order) + nFaces = facesN.shape[0] + + if nedelec_order == 1: # First order edge element + # Number of DOFs correspond to the number of edges in the mesh + nDofs = nEdges + # In order to avoid memory performance issues, add 20% to valence + valence = 41 + elif nedelec_order == 2: # Second order edge element + # Number of DOFs + nDofs = nEdges*np.int(2) + nFaces*np.int(2) + # In order to avoid memory performance issues, add 20% to valence + valence = 161 + elif nedelec_order == 3: # Third order edge element + # Number of DOFs + nDofs = nEdges*np.int(3) + nFaces*np.int(6) + nElems*np.int(3) + # In order to avoid memory performance issues, add 20% to valence + valence = 436 + else: + raise ValueError('Edge element order=', + nedelec_order, ' not supported.') - # In order to avoid memory performance issues, add 40% to valence - valence = 50 + # Build nnz pattern for each row nnz = np.full((nDofs), valence, dtype=np.int) # Build PETSc structures @@ -711,27 +878,30 @@ def preprocessingNNZ(mesh_file, out_dir, rank): return -def printPreprocessingSummary(nNodes, nElems, nDofs, nReceivers, rank): +def printPreprocessingSummary(nElems, nNodes, nFaces, nDofs, nReceivers, rank): ''' Print a summary of data preprocessed. - :param int nNodes: number of nodes in the mesh. :param int nElems: number of tetrahedral elements in the mesh. + :param int nNodes: number of nodes in the mesh. + :param int nFaces: number of faces in the mesh. :param int nDofs: number of DOFS in the mesh. :param int nReceivers: number of receivers. :param int rank: MPI rank. :return: None. ''' - A = str(nNodes) - B = str(nElems) - C = str(nDofs) - D = str(nReceivers) + A = str(nElems) + B = str(nNodes) + C = str(nFaces) + D = str(nDofs) + E = str(nReceivers) if rank == 0: - PETSc.Sys.Print(' Number of nodes: {0:50}'.format(A)) - PETSc.Sys.Print(' Number of elements: {0:50}'.format(B)) - PETSc.Sys.Print(' Number of DOFS: {0:50}'.format(C)) - PETSc.Sys.Print(' Number of receivers: {0:50}'.format(D)) + PETSc.Sys.Print(' Number of elements: {0:50}'.format(A)) + PETSc.Sys.Print(' Number of nodes: {0:50}'.format(B)) + PETSc.Sys.Print(' Number of faces: {0:50}'.format(C)) + PETSc.Sys.Print(' Number of DOFS: {0:50}'.format(D)) + PETSc.Sys.Print(' Number of receivers: {0:50}'.format(E)) return @@ -765,7 +935,10 @@ def printPreprocessingSummary(nNodes, nElems, nDofs, nReceivers, rank): from petgem.parallel.parallel import createSequentialVectorWithArray from petgem.parallel.parallel import writeParallelDenseMatrix from petgem.parallel.parallel import writePetscVector - from petgem.efem.efem import computeDofs + from petgem.efem.efem import computeEdges from petgem.efem.efem import computeFaces from petgem.efem.efem import computeBoundaryFaces - from petgem.efem.efem import computeBoundaryDofs + from petgem.efem.efem import computeBoundaryEdges + from petgem.efem.efem import computeBoundaries + from petgem.efem.efem import computeElementDOFs + from scipy.io import savemat diff --git a/petgem/solver/__init__.py b/petgem/solver/__init__.py old mode 100644 new mode 100755 index 8f6f11b..2e5dc42 --- a/petgem/solver/__init__.py +++ b/petgem/solver/__init__.py @@ -1,3 +1,4 @@ -from .assembler import computeElementalContributionsMPI +from .assembler import computeElementalContributionsMPI_FirstOrder +from .assembler import computeElementalContributionsMPI_HighOrder from .solver import setBoundaryConditions from .solver import solveSystem diff --git a/petgem/solver/__pycache__/__init__.cpython-35.pyc b/petgem/solver/__pycache__/__init__.cpython-35.pyc new file mode 100755 index 0000000..f1161ce Binary files /dev/null and b/petgem/solver/__pycache__/__init__.cpython-35.pyc differ diff --git a/petgem/solver/__pycache__/__init__.cpython-36.pyc b/petgem/solver/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000..69a7515 Binary files /dev/null and b/petgem/solver/__pycache__/__init__.cpython-36.pyc differ diff --git a/petgem/solver/__pycache__/assembler.cpython-35.pyc b/petgem/solver/__pycache__/assembler.cpython-35.pyc new file mode 100755 index 0000000..2002224 Binary files /dev/null and b/petgem/solver/__pycache__/assembler.cpython-35.pyc differ diff --git a/petgem/solver/__pycache__/assembler.cpython-36.pyc b/petgem/solver/__pycache__/assembler.cpython-36.pyc new file mode 100644 index 0000000..f0fbc80 Binary files /dev/null and b/petgem/solver/__pycache__/assembler.cpython-36.pyc differ diff --git a/petgem/solver/__pycache__/solver.cpython-35.pyc b/petgem/solver/__pycache__/solver.cpython-35.pyc new file mode 100755 index 0000000..8ac898e Binary files /dev/null and b/petgem/solver/__pycache__/solver.cpython-35.pyc differ diff --git a/petgem/solver/__pycache__/solver.cpython-36.pyc b/petgem/solver/__pycache__/solver.cpython-36.pyc new file mode 100644 index 0000000..6924320 Binary files /dev/null and b/petgem/solver/__pycache__/solver.cpython-36.pyc differ diff --git a/petgem/solver/assembler.py b/petgem/solver/assembler.py old mode 100644 new mode 100755 index c589be6..b6ecf8d --- a/petgem/solver/assembler.py +++ b/petgem/solver/assembler.py @@ -6,10 +6,12 @@ ''' -def computeElementalContributionsMPI(modelling, coordEle, nodesEle, sigmaEle): - '''Compute the elemental contributions of matrix A (LHS) and right hand - side (RHS) in a parallel-vectorized manner for CSEM surveys by EFEM. Here, - all necessary arrays are populated (Distributed-memory approach). +def computeElementalContributionsMPI_FirstOrder(modelling, coordEle, + nodesEle, sigmaEle): + '''Compute the first order elemental contributions of matrix A (LHS) and + right hand side (RHS) in a parallel-vectorized manner for CSEM surveys by + EFEM. Here, all necessary arrays are populated (Distributed-memory + approach). :param dictionary modelling: CSEM modelling with physical parameters. :param ndarray coordEle: array with nodal coordinates of element. @@ -23,17 +25,17 @@ def computeElementalContributionsMPI(modelling, coordEle, nodesEle, sigmaEle): SIGMA_BGROUND = np.float(modelling['CONDUCTIVITY_BACKGROUND']) SRC_POS = np.asarray(modelling['SRC_POS'], dtype=np.float) SRC_DIREC = np.int(modelling['SRC_DIREC']) - I = np.float(modelling['SRC_CURRENT']) + II = np.float(modelling['SRC_CURRENT']) dS = np.float(modelling['SRC_LENGTH']) # ----------- Edge order ----------- - edgeOrder = 6 + firstOrderEdgeElement = 6 # ----------- Nodal order ----------- nodalOrder = 4 # ----------- Number of dimensions ----------- nDimensions = 3 - # ----------- Definition of constants----------- + # ----------- Definition of constants-|---------- ZERO = np.float(0.0) ONE = np.float(1.0) TWO = np.float(2.0) @@ -53,7 +55,7 @@ def computeElementalContributionsMPI(modelling, coordEle, nodesEle, sigmaEle): # Propagation parameter WAVENUMBER = np.complex(np.sqrt(-IMAG_PART*MU*OMEGA*SIGMA_BGROUND)) # Physical constants - CONST_PHY1 = I * dS + CONST_PHY1 = II * dS CONST_PHY2 = FOUR * np.pi * SIGMA_BGROUND CONST_PHY3 = -IMAG_PART * WAVENUMBER CONST_PHY4 = -WAVENUMBER**2 @@ -97,7 +99,7 @@ def computeElementalContributionsMPI(modelling, coordEle, nodesEle, sigmaEle): pEvalY = np.zeros(allocate, dtype=np.float) pEvalZ = np.zeros(allocate, dtype=np.float) # Nedelec basis functions - allocate = ngaussP*edgeOrder + allocate = ngaussP*firstOrderEdgeElement temp_A1 = np.zeros(allocate, dtype=np.float) temp_A2 = np.zeros(allocate, dtype=np.float) temp_b1 = np.zeros(allocate, dtype=np.float) @@ -105,24 +107,24 @@ def computeElementalContributionsMPI(modelling, coordEle, nodesEle, sigmaEle): basis = np.zeros((nDimensions, allocate), dtype=np.float) # Vector operations over edges rep_edges1 = np.repeat((0, 1, 2, 3, 4, 5), ngaussP) - rep_edges2 = np.repeat((0, 1, 2, 3, 4, 5), edgeOrder) - rep_edges3 = np.tile((0, 1, 2, 3, 4, 5), edgeOrder) + rep_edges2 = np.repeat((0, 1, 2, 3, 4, 5), firstOrderEdgeElement) + rep_edges3 = np.tile((0, 1, 2, 3, 4, 5), firstOrderEdgeElement) # Vector operations over gauss points idxbx1 = np.repeat((1, 2, 3, 2, 1, 3), ngaussP) idxbx2 = np.repeat((0, 0, 0, 1, 3, 2), ngaussP) # Mass matrix - allocate = edgeOrder**2 + allocate = firstOrderEdgeElement**2 Me = np.zeros(allocate, dtype=np.float) # Stiffness matrix - rep_edges_stiff1 = np.tile((0, 0, 0, 1, 3, 2), edgeOrder) - rep_edges_stiff2 = np.tile((1, 2, 3, 2, 1, 3), edgeOrder) + rep_edges_stiff1 = np.tile((0, 0, 0, 1, 3, 2), firstOrderEdgeElement) + rep_edges_stiff2 = np.tile((1, 2, 3, 2, 1, 3), firstOrderEdgeElement) rep_edges_stiff3 = np.array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2], dtype=np.int) - rep_edges_stiff4 = np.repeat((1, 2, 3, 2, 1, 3), edgeOrder) + rep_edges_stiff4 = np.repeat((1, 2, 3, 2, 1, 3), firstOrderEdgeElement) # Stiffness matrix Ke = np.zeros(allocate, dtype=np.float) # Auxiliar vectors @@ -132,7 +134,7 @@ def computeElementalContributionsMPI(modelling, coordEle, nodesEle, sigmaEle): f2 = np.zeros(allocate, dtype=np.float) f3 = np.zeros(allocate, dtype=np.float) f4 = np.zeros(allocate, dtype=np.float) - allocate = edgeOrder**2 + allocate = firstOrderEdgeElement**2 std_v1 = np.zeros(allocate, dtype=np.float) std_v2 = np.zeros(allocate, dtype=np.float) std_v3 = np.zeros(allocate, dtype=np.float) @@ -166,13 +168,13 @@ def computeElementalContributionsMPI(modelling, coordEle, nodesEle, sigmaEle): distance = np.zeros(allocate, dtype=np.float) # Electric field field = np.zeros((nDimensions, allocate), dtype=np.complex) - indx_field = np.tile((np.arange(0, ngaussP)), edgeOrder) - allocate = edgeOrder*ngaussP + indx_field = np.tile((np.arange(0, ngaussP)), firstOrderEdgeElement) + allocate = firstOrderEdgeElement*ngaussP temp_field = np.zeros((nDimensions, allocate), dtype=np.complex) # Elemental matrix and elemental vector - allocate = edgeOrder**2 + allocate = firstOrderEdgeElement**2 Ae = np.zeros(allocate, dtype=np.complex) - allocate = edgeOrder + allocate = firstOrderEdgeElement be = np.zeros(allocate, dtype=np.complex) # Indexes within dimensions of gauss points and basis functions idx_gaussP_1 = ngaussP @@ -477,7 +479,7 @@ def computeElementalContributionsMPI(modelling, coordEle, nodesEle, sigmaEle): temp = np.sum(temp_field*basis, 0) * signsEle * weightEle * deltaSigma # Integral over edges - for iBasis in np.arange(edgeOrder): + for iBasis in np.arange(firstOrderEdgeElement): for iPoint in np.arange(ngaussP): be[iBasis] = be[iBasis] + temp[(iBasis*ngaussP)+iPoint] @@ -487,6 +489,235 @@ def computeElementalContributionsMPI(modelling, coordEle, nodesEle, sigmaEle): return Ae, be +def computeElementalContributionsMPI_HighOrder(modelling, coordEle, nodesEle, + sigmaEle, nedelec_order): + '''Compute the second or third order elemental contributions of matrix A + (LHS) and right hand side (RHS) in a parallel-vectorized manner for CSEM + surveys by EFEM. Here, all necessary arrays are populated + (Distributed-memory approach). + + :param dictionary modelling: CSEM modelling with physical parameters. + :param ndarray coordEle: array with nodal coordinates of element. + :param ndarray nodesEle: array with nodal indexes of element. + :param float sigmaEle: element conductiviy. + :param int nedelec_order: nedelec element order. + :return: Ae, be. + :rtype: complex. + ''' + # ----------- Nodal order ----------- + nodalOrder = 4 + + # ----------- Edge element order ----------- + if nedelec_order == 2: + orderEle = 20 + ngaussP = 15 + elif nedelec_order == 3: + orderEle = 45 + ngaussP = 24 + + # ----------- Number of dimensions ----------- + nDimensions = 3 + + # ----------- Read physical parameters ----------- + FREQ = np.float(modelling['FREQ']) + SIGMA_BGROUND = np.float(modelling['CONDUCTIVITY_BACKGROUND']) + SRC_POS = np.asarray(modelling['SRC_POS'], dtype=np.float) + SRC_DIREC = np.int(modelling['SRC_DIREC']) + II = np.float(modelling['SRC_CURRENT']) + dS = np.float(modelling['SRC_LENGTH']) + + # ----------- Definition of constants ----------- + ZERO = np.float(0.0) + ONE = np.float(1.0) + TWO = np.float(2.0) + THREE = np.float(3.0) + FOUR = np.float(4.0) + SIX = np.float(6.0) + # Imaginary part for complex numbers + IMAG_PART = np.complex128(0.0 + 1.0j) + # Vacuum permeability + MU = np.float(FOUR*np.pi*np.float(1.0e-7)) + # Angular frequency + OMEGA = np.float(FREQ*TWO*np.pi) + # Propagation parameter + WAVENUMBER = np.complex(np.sqrt(-IMAG_PART*MU*OMEGA*SIGMA_BGROUND)) + # Physical constants + CONST_PHY1 = II * dS + CONST_PHY2 = FOUR * np.pi * SIGMA_BGROUND + CONST_PHY3 = -IMAG_PART * WAVENUMBER + CONST_PHY4 = -WAVENUMBER**2 + CONST_PHY5 = THREE * IMAG_PART * WAVENUMBER + CONST_PHY6 = IMAG_PART*OMEGA*MU + + # Gaussian points for the unit reference tetrahedron + [Wi, rx, ry, rz] = gauss_points_reference_tetrahedron(ngaussP, + nedelec_order) + + # ----- Initialization of edges/vertices/faces numbering ------ + [edge_vertices, face_vertices, ref_ele] = edgeFaceVerticesInit() + + # ----------- Delta sigma of element ----------- + deltaSigma = sigmaEle - SIGMA_BGROUND + + # Element's nodes coordinates + coordEle = coordEle.reshape((nDimensions, nodalOrder), order='F') + + # Elemental computations (Signs, jacobian, edges length, area faces) + [_, _, signs, nreal, + jacob, DetJacob, _] = computeSignsJacobLegth(coordEle, edge_vertices, + face_vertices, + nedelec_order) + # Definition of q vectors on faces + [qface1, qface2, + qface3, qface4, + invjj, GR] = definitionHighOrderTet(nreal, signs, jacob, nedelec_order) + + # Traslation of gauss points to real element + L = np.vstack((np.float(1)-rx-ry-rz, rx, ry, rz)) + pEval = volumetricToCartesianCoordinates(coordEle[0, :], coordEle[1, :], + coordEle[2, :], L) + + # Computation coefficients for basis functions + if nedelec_order == 2: + # Gaussian points weigths (normalization) + weightEle = Wi*DetJacob*1./6. + + # Computation of coefficients + [a1, a2, a3, a4, + b1, b2, b3, b4, + c1, c2, c3, c4, + D, E, F, G, H, + II, JJ, K] = computeCoefficientsSecondOrder(qface1, qface2, qface3, + qface4, ref_ele, + edge_vertices, + face_vertices) + # Computation of basis functions + basis = nedelecBasisSecondOrder(a1, a2, a3, a4, b1, b2, b3, b4, c1, + c2, c3, c4, D, E, F, G, H, II, JJ, + K, coordEle, pEval) + + # Compute mass matrix + Me = computeMassMatrixSecondOrder(a1, a2, a3, a4, b1, b2, b3, b4, + c1, c2, c3, c4, D, E, F, G, H, + II, JJ, K, ref_ele, GR, signs, + DetJacob, Wi, rx, ry, rz, ngaussP) + + # Compute stiffness matrix + Ke = computeStiffnessMatrixSecondOrder(a2, a3, a4, b2, b3, b4, c2, c3, + c4, D, E, F, G, H, II, JJ, K, + invjj, signs, DetJacob, Wi, + rx, ry, rz, ngaussP) + + # Compute elemental matrix + Ae = Ke + CONST_PHY6*sigmaEle*Me + Ae = Ae.flatten() + + # Allocate RHS + be = np.zeros(orderEle, dtype=np.complex) + + elif nedelec_order == 3: + # Gaussian points weigths (normalization) + weightEle = Wi*DetJacob + + # Computation of coefficients + [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, + b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, + c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, + D, E, F, G, H, II, JJ, K, L, M, N, O, + P, Q, R] = computeCoefficientsThirdOrder(qface1, qface2, + qface3, qface4) + + # Computation of basis functions + basis = nedelecBasisThirdOrder(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, + b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, + c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, + D, E, F, G, H, II, JJ, K, L, M, N, + O, P, Q, R, coordEle, pEval) + + # Compute mass matrix + Me = computeMassMatrixThirdOrder(a1, a2, a3, a4, a5, a6, a7, a8, a9, + a10, b1, b2, b3, b4, b5, b6, b7, b8, + b9, b10, c1, c2, c3, c4, c5, c6, c7, + c8, c9, c10, D, E, F, G, H, II, JJ, + K, L, M, N, O, P, Q, R, ref_ele, + GR, signs, DetJacob) + + # Compute stiffness matrix + Ke = computeStiffnessMatrixThirdOrder(a2, a3, a4, a5, a6, a7, a8, a9, + a10, b2, b3, b4, b5, b6, b7, b8, + b9, b10, c2, c3, c4, c5, c6, c7, + c8, c9, c10, D, E, F, G, H, II, + JJ, K, L, M, N, O, P, Q, R, + invjj, signs, DetJacob) + + # Compute elemental matrix + Ae = Ke + CONST_PHY6*sigmaEle*Me + Ae = Ae.flatten() + + # Allocate RHS + be = np.zeros(orderEle, dtype=np.complex) + + # Compute primary field for all gauss points + # Allocate + field = np.zeros(nDimensions, dtype=np.complex) + for iPoint in np.arange(ngaussP): + # Basis functions + Ni = basis[:, :, iPoint] + + # Evaluation point + pEvalX = pEval[0, iPoint] + pEvalY = pEval[1, iPoint] + pEvalZ = pEval[2, iPoint] + + # Compute distance to the source for all Gaussian points + distX = pEvalX - SRC_POS[0] # X-component + distY = pEvalY - SRC_POS[1] # Y-component + distZ = pEvalZ - SRC_POS[2] # Z-component + distance = np.sqrt(distX**2 + distY**2 + distZ**2) + + # To avoid very large or small numbers + if distance < 1.e0: + distance = ONE + # Compute the primary field for all Gaussian points + # E = AA [ BB + (wavenumber^2*distance^2 -1i*wavenumber*distance-1)] + SQUARE_DISTANCE = distance**2 + AA = CONST_PHY1 / (CONST_PHY2 * distance**3) * np.exp(CONST_PHY3 * + distance) + BB = CONST_PHY4 * SQUARE_DISTANCE + (CONST_PHY5 * distance) + THREE + RR = ONE/SQUARE_DISTANCE + + # Compute primary field in function of source direction + if SRC_DIREC == 1: + # X-directed + field[0] = AA * ((distX**2 * RR)*BB + + (WAVENUMBER**2 * SQUARE_DISTANCE - IMAG_PART * + WAVENUMBER * distance - ONE)) + field[1] = AA * (distX*distY*RR)*BB + field[2] = AA * (distX*distZ*RR)*BB + elif SRC_DIREC == 2: + # Y-directed + field[0] = AA * (distX*distY*RR)*BB + field[1] = AA * ((distY**2 * RR)*BB + + (WAVENUMBER**2 * SQUARE_DISTANCE - IMAG_PART * + WAVENUMBER * distance - ONE)) + field[2] = AA * (distY*distZ*RR)*BB + else: + # Z-directed + field[0] = AA * (distX*distZ*RR)*BB + field[1] = AA * (distZ*distY*RR)*BB + field[2] = AA * ((distZ**2 * RR)*BB + + (WAVENUMBER**2 * SQUARE_DISTANCE - IMAG_PART * + WAVENUMBER * distance - ONE)) + + be += (deltaSigma*weightEle[iPoint] * + np.multiply(np.matmul(Ni.transpose(), field), signs)) + + # Scale vector by constant -1i*OMEGA*MU + be = be*-CONST_PHY6 + + return Ae, be + + def unitary_test(): ''' Unitary test for assembler.py script. ''' @@ -502,3 +733,17 @@ def unitary_test(): from petsc4py import PETSc # PETGEM module import from petgem.efem.fem import gauss_points_tetrahedron + from petgem.efem.fem import gauss_points_reference_tetrahedron + from petgem.efem.fem import volumetricToCartesianCoordinates + from petgem.efem.efem import edgeFaceVerticesInit + from petgem.efem.efem import computeSignsJacobLegth + from petgem.efem.efem import definitionHighOrderTet + from petgem.efem.efem import computeCoefficientsSecondOrder + from petgem.efem.efem import nedelecBasisSecondOrder + from petgem.efem.efem import computeMassMatrixSecondOrder + from petgem.efem.efem import computeStiffnessMatrixSecondOrder + from petgem.efem.efem import computeCoefficientsThirdOrder + from petgem.efem.efem import nedelecBasisThirdOrder + from petgem.efem.efem import computeMassMatrixThirdOrder + from petgem.efem.efem import computeStiffnessMatrixThirdOrder + from scipy.io import savemat diff --git a/petgem/solver/solver.py b/petgem/solver/solver.py old mode 100644 new mode 100755 index 10a9b18..030a8a9 --- a/petgem/solver/solver.py +++ b/petgem/solver/solver.py @@ -7,16 +7,17 @@ ''' -def setBoundaryConditions(A, b, bEdges, Istart_bEdges, Iend_bEdges, rank): +def setBoundaryConditions(A, b, boundaries, Istart_boundaries, Iend_boundaries, + rank): ''' Given a parallel matrix and a parallel vector, set Dirichlet boundary conditions. :param petsc matrix A: sparse and complex coefficients matrix in petsc format :param petsc vector b: parallel right hand side - :param petsc vector bEdges: array of boundary indexes - :param int Istart_bEdges: init range for boundaries - :param int Iend_bEdges: last range for boundaries + :param petsc vector boundaries: array of boundary indexes + :param int Istart_boundaries: init range for boundaries + :param int Iend_boundaries: last range for boundaries :para int rank: MPI rank :return: equation system after applied Dirichlet boundary conditions and elapsed time @@ -30,10 +31,10 @@ def setBoundaryConditions(A, b, bEdges, Istart_bEdges, Iend_bEdges, rank): Init_boundaries = getTime() # Boundaries for LHS - A.zeroRowsColumns(np.real(bEdges).astype(PETSc.IntType)) + A.zeroRowsColumns(np.real(boundaries).astype(PETSc.IntType)) # Boundaries for RHS - numLocalBoundaries = Iend_bEdges - Istart_bEdges - b.setValues(np.real(bEdges).astype(PETSc.IntType), + numLocalBoundaries = Iend_boundaries - Istart_boundaries + b.setValues(np.real(boundaries).astype(PETSc.IntType), np.zeros(numLocalBoundaries, dtype=np.complex), addv=PETSc.InsertMode.INSERT_VALUES) @@ -93,6 +94,7 @@ def unitary_test(): ''' Unitary test for solver.py script. ''' + if __name__ == '__main__': # Standard module import unitary_test() diff --git a/run_preprocessing.py b/run_preprocessing.py old mode 100644 new mode 100755 index cfe9277..b59631f --- a/run_preprocessing.py +++ b/run_preprocessing.py @@ -18,7 +18,8 @@ from petgem.preprocessing.preprocessing import readPreprocessingParams from petgem.preprocessing.preprocessing import preprocessNodes from petgem.preprocessing.preprocessing import preprocessingNodalConnectivity -from petgem.preprocessing.preprocessing import preprocessingDOF +from petgem.preprocessing.preprocessing import preprocessingEdges +from petgem.preprocessing.preprocessing import preprocessingFaces from petgem.preprocessing.preprocessing import preprocessingConductivityModel from petgem.preprocessing.preprocessing import preprocessingDataReceivers from petgem.preprocessing.preprocessing import preprocessingNNZ @@ -56,27 +57,36 @@ nElems = preprocessingNodalConnectivity(preprocessing['MESH_FILE'], preprocessing['OUT_DIR'], rank) -# Degrees of freedom preprocessing (dofs) -nDofs = preprocessingDOF(preprocessing['MESH_FILE'], - preprocessing['OUT_DIR'], rank) +# Edges connectivity and edge boundaries preprocessing (edges) +nEdges, nDofs = preprocessingEdges(preprocessing['NEDELEC_ORDER'], + preprocessing['MESH_FILE'], + preprocessing['OUT_DIR'], rank) + +# Faces connectivity and faces boundaries preprocessing (faces) +nFaces = preprocessingFaces(preprocessing['NEDELEC_ORDER'], + preprocessing['MESH_FILE'], + preprocessing['OUT_DIR'], rank) # Conductivity model preprocessingConductivityModel(preprocessing['MESH_FILE'], preprocessing['MATERIAL_CONDUCTIVITIES'], preprocessing['OUT_DIR'], rank) # Receiver positions -nReceivers = preprocessingDataReceivers(preprocessing['MESH_FILE'], +nReceivers = preprocessingDataReceivers(preprocessing['NEDELEC_ORDER'], + preprocessing['MESH_FILE'], preprocessing['RECEIVERS_FILE'], preprocessing['OUT_DIR'], rank) # Sparsity pattern for parallel matrix allocation -preprocessingNNZ(preprocessing['MESH_FILE'], preprocessing['OUT_DIR'], rank) +preprocessingNNZ(preprocessing['NEDELEC_ORDER'], + preprocessing['MESH_FILE'], + preprocessing['OUT_DIR'], rank) # ############################################### # ------------------ Summary -------------------- printMessage('\nSummary', rank) printMessage('='*75, rank) -printPreprocessingSummary(nNodes, nElems, nDofs, nReceivers, rank) +printPreprocessingSummary(nElems, nNodes, nFaces, nDofs, nReceivers, rank) # ############################################### # ----------- Print footer (Master) ------------- diff --git a/setup.py b/setup.py old mode 100644 new mode 100755 index 5b07575..34bba8c --- a/setup.py +++ b/setup.py @@ -50,6 +50,7 @@ def get_ext_modules(): return numpy_includes + if __name__ == '__main__': from setuptools import setup import os @@ -57,7 +58,7 @@ def get_ext_modules(): setup(name=name(), maintainer="Octavio Castillo Reyes", maintainer_email="octavio.castillo@bsc.es", - version='0.30.48', + version='0.5', long_description=long_description(), description=description(), url="https://www.bsc.es/castillo-octavio",