15a21df39Svaleriabarra# Copyright (c) 2017, Lawrence Livermore National Security, LLC. Produced at 25a21df39Svaleriabarra# the Lawrence Livermore National Laboratory. LLNL-CODE-734707. All Rights 35a21df39Svaleriabarra# reserved. See files LICENSE and NOTICE for details. 45a21df39Svaleriabarra# 55a21df39Svaleriabarra# This file is part of CEED, a collection of benchmarks, miniapps, software 65a21df39Svaleriabarra# libraries and APIs for efficient high-order finite element and spectral 75a21df39Svaleriabarra# element discretizations for exascale applications. For more information and 85a21df39Svaleriabarra# source code availability see http://github.com/ceed. 95a21df39Svaleriabarra# 105a21df39Svaleriabarra# The CEED research is supported by the Exascale Computing Project 17-SC-20-SC, 115a21df39Svaleriabarra# a collaborative effort of two U.S. Department of Energy organizations (Office 125a21df39Svaleriabarra# of Science and the National Nuclear Security Administration) responsible for 135a21df39Svaleriabarra# the planning and preparation of a capable exascale ecosystem, including 145a21df39Svaleriabarra# software, applications, hardware, advanced system engineering and early 155a21df39Svaleriabarra# testbed platforms, in support of the nation's exascale computing imperative. 165a21df39Svaleriabarra 175a21df39Svaleriabarrafrom _ceed_cffi import ffi, lib 185a21df39Svaleriabarraimport tempfile 195a21df39Svaleriabarraimport numpy as np 205a21df39Svaleriabarrafrom abc import ABC 215a21df39Svaleriabarrafrom .ceed_constants import TRANSPOSE, NOTRANSPOSE 225a21df39Svaleriabarra 235a21df39Svaleriabarra# ------------------------------------------------------------------------------ 245a21df39Svaleriabarraclass Basis(ABC): 255a21df39Svaleriabarra """Ceed Basis: finite element basis objects.""" 265a21df39Svaleriabarra 275a21df39Svaleriabarra # Attributes 285a21df39Svaleriabarra _ceed = ffi.NULL 295a21df39Svaleriabarra _pointer = ffi.NULL 305a21df39Svaleriabarra 315a21df39Svaleriabarra # Representation 325a21df39Svaleriabarra def __repr__(self): 335a21df39Svaleriabarra return "<CeedBasis instance at " + hex(id(self)) + ">" 345a21df39Svaleriabarra 355a21df39Svaleriabarra # String conversion for print() to stdout 365a21df39Svaleriabarra def __str__(self): 375a21df39Svaleriabarra """View a Basis via print().""" 385a21df39Svaleriabarra 395a21df39Svaleriabarra # libCEED call 405a21df39Svaleriabarra with tempfile.NamedTemporaryFile() as key_file: 415a21df39Svaleriabarra with open(key_file.name, 'r+') as stream_file: 425a21df39Svaleriabarra stream = ffi.cast("FILE *", stream_file) 435a21df39Svaleriabarra 445a21df39Svaleriabarra lib.CeedBasisView(self._pointer[0], stream) 455a21df39Svaleriabarra 465a21df39Svaleriabarra stream_file.seek(0) 475a21df39Svaleriabarra out_string = stream_file.read() 485a21df39Svaleriabarra 495a21df39Svaleriabarra return out_string 505a21df39Svaleriabarra 515a21df39Svaleriabarra # Apply Basis 525a21df39Svaleriabarra def apply(self, nelem, emode, u, v, tmode=NOTRANSPOSE): 535a21df39Svaleriabarra """Apply basis evaluation from nodes to quadrature points or vice versa. 545a21df39Svaleriabarra 555a21df39Svaleriabarra Args: 565a21df39Svaleriabarra nelem: the number of elements to apply the basis evaluation to; 575a21df39Svaleriabarra the backend will specify the ordering in a 585a21df39Svaleriabarra BlockedElemRestriction 595a21df39Svaleriabarra emode: basis evaluation mode 605a21df39Svaleriabarra u: input vector 615a21df39Svaleriabarra v: output vector 625a21df39Svaleriabarra **tmode: CEED_NOTRANSPOSE to evaluate from nodes to quadrature 635a21df39Svaleriabarra points, CEED_TRANSPOSE to apply the transpose, mapping 645a21df39Svaleriabarra from quadrature points to nodes; default CEED_NOTRANSPOSE""" 655a21df39Svaleriabarra 665a21df39Svaleriabarra # libCEED call 675a21df39Svaleriabarra lib.CeedBasisApply(self._pointer[0], nelem, tmode, emode, 685a21df39Svaleriabarra u._pointer[0], v._pointer[0]) 695a21df39Svaleriabarra 705a21df39Svaleriabarra # Transpose a Basis 715a21df39Svaleriabarra @property 725a21df39Svaleriabarra def T(self): 735a21df39Svaleriabarra """Transpose a Basis.""" 745a21df39Svaleriabarra 755a21df39Svaleriabarra return TransposeBasis(self) 765a21df39Svaleriabarra 775a21df39Svaleriabarra # Transpose a Basis 785a21df39Svaleriabarra @property 795a21df39Svaleriabarra def transpose(self): 805a21df39Svaleriabarra """Transpose a Basis.""" 815a21df39Svaleriabarra 825a21df39Svaleriabarra return TransposeBasis(self) 835a21df39Svaleriabarra 845a21df39Svaleriabarra # Get number of nodes 855a21df39Svaleriabarra def get_num_nodes(self): 865a21df39Svaleriabarra """Get total number of nodes (in dim dimensions) of a Basis. 875a21df39Svaleriabarra 885a21df39Svaleriabarra Returns: 895a21df39Svaleriabarra num_nodes: total number of nodes""" 905a21df39Svaleriabarra 915a21df39Svaleriabarra # Setup argument 925a21df39Svaleriabarra p_pointer = ffi.new("CeedInt *") 935a21df39Svaleriabarra 945a21df39Svaleriabarra # libCEED call 955a21df39Svaleriabarra lib.CeedBasisGetNumNodes(self._pointer[0], p_pointer) 965a21df39Svaleriabarra 975a21df39Svaleriabarra return p_pointer[0] 985a21df39Svaleriabarra 995a21df39Svaleriabarra # Get number of quadrature points 1005a21df39Svaleriabarra def get_num_quadrature_points(self): 1015a21df39Svaleriabarra """Get total number of quadrature points (in dim dimensions) of a Basis. 1025a21df39Svaleriabarra 1035a21df39Svaleriabarra Returns: 1045a21df39Svaleriabarra num_qpts: total number of quadrature points""" 1055a21df39Svaleriabarra 1065a21df39Svaleriabarra # Setup argument 1075a21df39Svaleriabarra q_pointer = ffi.new("CeedInt *") 1085a21df39Svaleriabarra 1095a21df39Svaleriabarra # libCEED call 1105a21df39Svaleriabarra lib.CeedBasisGetNumQuadraturePoints(self._pointer[0], q_pointer) 1115a21df39Svaleriabarra 1125a21df39Svaleriabarra return q_pointer[0] 1135a21df39Svaleriabarra 1145a21df39Svaleriabarra # Gauss quadrature 1155a21df39Svaleriabarra @staticmethod 1165a21df39Svaleriabarra def gauss_quadrature(q): 1175a21df39Svaleriabarra """Construct a Gauss-Legendre quadrature. 1185a21df39Svaleriabarra 1195a21df39Svaleriabarra Args: 1205a21df39Svaleriabarra Q: number of quadrature points (integrates polynomials of 1215a21df39Svaleriabarra degree 2*Q-1 exactly) 1225a21df39Svaleriabarra 1235a21df39Svaleriabarra Returns: 1245a21df39Svaleriabarra (qref1d, qweight1d): array of length Q to hold the abscissa on [-1, 1] 1255a21df39Svaleriabarra and array of length Q to hold the weights""" 1265a21df39Svaleriabarra 1275a21df39Svaleriabarra # Setup arguments 1285a21df39Svaleriabarra qref1d = np.empty(q, dtype="float64") 129*fc2a3161SJed Brown qweight1d = np.empty(q, dtype="float64") 1305a21df39Svaleriabarra 1315a21df39Svaleriabarra qref1d_pointer = ffi.new("CeedScalar *") 1325a21df39Svaleriabarra qref1d_pointer = ffi.cast("CeedScalar *", qref1d.__array_interface__['data'][0]) 1335a21df39Svaleriabarra 1345a21df39Svaleriabarra qweight1d_pointer = ffi.new("CeedScalar *") 1355a21df39Svaleriabarra qweight1d_pointer = ffi.cast("CeedScalar *", qweight1d.__array_interface__['data'][0]) 1365a21df39Svaleriabarra 1375a21df39Svaleriabarra # libCEED call 1385a21df39Svaleriabarra lib.CeedGaussQuadrature(q, qref1d_pointer, qweight1d_pointer) 1395a21df39Svaleriabarra 1405a21df39Svaleriabarra return qref1d, qweight1d 1415a21df39Svaleriabarra 1425a21df39Svaleriabarra # Lobatto quadrature 1435a21df39Svaleriabarra @staticmethod 1445a21df39Svaleriabarra def lobatto_quadrature(q): 1455a21df39Svaleriabarra """Construct a Gauss-Legendre-Lobatto quadrature. 1465a21df39Svaleriabarra 1475a21df39Svaleriabarra Args: 1485a21df39Svaleriabarra q: number of quadrature points (integrates polynomials of 1495a21df39Svaleriabarra degree 2*Q-3 exactly) 1505a21df39Svaleriabarra 1515a21df39Svaleriabarra Returns: 1525a21df39Svaleriabarra (qref1d, qweight1d): array of length Q to hold the abscissa on [-1, 1] 1535a21df39Svaleriabarra and array of length Q to hold the weights""" 1545a21df39Svaleriabarra 1555a21df39Svaleriabarra # Setup arguments 1565a21df39Svaleriabarra qref1d = np.empty(q, dtype="float64") 1575a21df39Svaleriabarra qref1d_pointer = ffi.new("CeedScalar *") 1585a21df39Svaleriabarra qref1d_pointer = ffi.cast("CeedScalar *", qref1d.__array_interface__['data'][0]) 1595a21df39Svaleriabarra 160*fc2a3161SJed Brown qweight1d = np.empty(q, dtype="float64") 1615a21df39Svaleriabarra qweight1d_pointer = ffi.new("CeedScalar *") 1625a21df39Svaleriabarra qweight1d_pointer = ffi.cast("CeedScalar *", qweight1d.__array_interface__['data'][0]) 1635a21df39Svaleriabarra 1645a21df39Svaleriabarra # libCEED call 1655a21df39Svaleriabarra lib.CeedLobattoQuadrature(q, qref1d_pointer, qweight1d_pointer) 1665a21df39Svaleriabarra 1675a21df39Svaleriabarra return qref1d, qweight1d 1685a21df39Svaleriabarra 1695a21df39Svaleriabarra # QR factorization 1705a21df39Svaleriabarra @staticmethod 1715a21df39Svaleriabarra def qr_factorization(ceed, mat, tau, m, n): 1725a21df39Svaleriabarra """Return QR Factorization of a matrix. 1735a21df39Svaleriabarra 1745a21df39Svaleriabarra Args: 1755a21df39Svaleriabarra ceed: Ceed context currently in use 1765a21df39Svaleriabarra *mat: Numpy array holding the row-major matrix to be factorized in place 1775a21df39Svaleriabarra *tau: Numpy array to hold the vector of lengt m of scaling factors 1785a21df39Svaleriabarra m: number of rows 1795a21df39Svaleriabarra n: numbef of columns""" 1805a21df39Svaleriabarra 1815a21df39Svaleriabarra # Setup arguments 1825a21df39Svaleriabarra mat_pointer = ffi.new("CeedScalar *") 1835a21df39Svaleriabarra mat_pointer = ffi.cast("CeedScalar *", mat.__array_interface__['data'][0]) 1845a21df39Svaleriabarra 1855a21df39Svaleriabarra tau_pointer = ffi.new("CeedScalar *") 1865a21df39Svaleriabarra tau_pointer = ffi.cast("CeedScalar *", tau.__array_interface__['data'][0]) 1875a21df39Svaleriabarra 1885a21df39Svaleriabarra # libCEED call 1895a21df39Svaleriabarra lib.CeedQRFactorization(ceed._pointer[0], mat_pointer, tau_pointer, m, n) 1905a21df39Svaleriabarra 1915a21df39Svaleriabarra return mat, tau 1925a21df39Svaleriabarra 1935a21df39Svaleriabarra # Symmetric Schur decomposition 1945a21df39Svaleriabarra @staticmethod 1955a21df39Svaleriabarra def symmetric_schur_decomposition(ceed, mat, n): 1965a21df39Svaleriabarra """Return symmetric Schur decomposition of a symmetric matrix 1975a21df39Svaleriabarra via symmetric QR factorization. 1985a21df39Svaleriabarra 1995a21df39Svaleriabarra Args: 2005a21df39Svaleriabarra ceed: Ceed context currently in use 2015a21df39Svaleriabarra *mat: Numpy array holding the row-major matrix to be factorized in place 2025a21df39Svaleriabarra n: number of rows/columns 2035a21df39Svaleriabarra 2045a21df39Svaleriabarra Returns: 2055a21df39Svaleriabarra lbda: Numpy array of length n holding eigenvalues""" 2065a21df39Svaleriabarra 2075a21df39Svaleriabarra # Setup arguments 2085a21df39Svaleriabarra mat_pointer = ffi.new("CeedScalar *") 2095a21df39Svaleriabarra mat_pointer = ffi.cast("CeedScalar *", mat.__array_interface__['data'][0]) 2105a21df39Svaleriabarra 2115a21df39Svaleriabarra lbda = np.empty(n, dtype="float64") 2125a21df39Svaleriabarra l_pointer = ffi.new("CeedScalar *") 2135a21df39Svaleriabarra l_pointer = ffi.cast("CeedScalar *", lbda.__array_interface__['data'][0]) 2145a21df39Svaleriabarra 2155a21df39Svaleriabarra # libCEED call 2165a21df39Svaleriabarra lib.CeedSymmetricSchurDecomposition(ceed._pointer[0], mat_pointer, l_pointer, n) 2175a21df39Svaleriabarra 2185a21df39Svaleriabarra return lbda 2195a21df39Svaleriabarra 2205a21df39Svaleriabarra # Simultaneous Diagonalization 2215a21df39Svaleriabarra @staticmethod 2225a21df39Svaleriabarra def simultaneous_diagonalization(ceed, matA, matB, n): 2235a21df39Svaleriabarra """Return Simultaneous Diagonalization of two matrices. 2245a21df39Svaleriabarra 2255a21df39Svaleriabarra Args: 2265a21df39Svaleriabarra ceed: Ceed context currently in use 2275a21df39Svaleriabarra *matA: Numpy array holding the row-major matrix to be factorized with 2285a21df39Svaleriabarra eigenvalues 2295a21df39Svaleriabarra *matB: Numpy array holding the row-major matrix to be factorized to identity 2305a21df39Svaleriabarra n: number of rows/columns 2315a21df39Svaleriabarra 2325a21df39Svaleriabarra Returns: 2335a21df39Svaleriabarra (x, lbda): Numpy array holding the row-major orthogonal matrix and 2345a21df39Svaleriabarra Numpy array holding the vector of length n of generalized 2355a21df39Svaleriabarra eigenvalues""" 2365a21df39Svaleriabarra 2375a21df39Svaleriabarra # Setup arguments 2385a21df39Svaleriabarra matA_pointer = ffi.new("CeedScalar *") 2395a21df39Svaleriabarra matA_pointer = ffi.cast("CeedScalar *", matA.__array_interface__['data'][0]) 2405a21df39Svaleriabarra 2415a21df39Svaleriabarra matB_pointer = ffi.new("CeedScalar *") 2425a21df39Svaleriabarra matB_pointer = ffi.cast("CeedScalar *", matB.__array_interface__['data'][0]) 2435a21df39Svaleriabarra 2445a21df39Svaleriabarra lbda = np.empty(n, dtype="float64") 2455a21df39Svaleriabarra l_pointer = ffi.new("CeedScalar *") 2465a21df39Svaleriabarra l_pointer = ffi.cast("CeedScalar *", lbda.__array_interface__['data'][0]) 2475a21df39Svaleriabarra 2485a21df39Svaleriabarra x = np.empty(n*n, dtype="float64") 2495a21df39Svaleriabarra x_pointer = ffi.new("CeedScalar *") 2505a21df39Svaleriabarra x_pointer = ffi.cast("CeedScalar *", x.__array_interface__['data'][0]) 2515a21df39Svaleriabarra 2525a21df39Svaleriabarra # libCEED call 2535a21df39Svaleriabarra lib.CeedSimultaneousDiagonalization(ceed._pointer[0], matA_pointer, matB_pointer, 2545a21df39Svaleriabarra x_pointer, l_pointer, n) 2555a21df39Svaleriabarra 2565a21df39Svaleriabarra return x, lbda 2575a21df39Svaleriabarra 2585a21df39Svaleriabarra # Destructor 2595a21df39Svaleriabarra def __del__(self): 2605a21df39Svaleriabarra # libCEED call 2615a21df39Svaleriabarra lib.CeedBasisDestroy(self._pointer) 2625a21df39Svaleriabarra 2635a21df39Svaleriabarra# ------------------------------------------------------------------------------ 2645a21df39Svaleriabarraclass BasisTensorH1(Basis): 2655a21df39Svaleriabarra """Ceed Tensor H1 Basis: finite element tensor-product basis objects for 2665a21df39Svaleriabarra H^1 discretizations.""" 2675a21df39Svaleriabarra 2685a21df39Svaleriabarra # Constructor 2695a21df39Svaleriabarra def __init__(self, ceed, dim, ncomp, P1d, Q1d, interp1d, grad1d, 2705a21df39Svaleriabarra qref1d, qweight1d): 2715a21df39Svaleriabarra 2725a21df39Svaleriabarra # Setup arguments 2735a21df39Svaleriabarra self._pointer = ffi.new("CeedBasis *") 2745a21df39Svaleriabarra 2755a21df39Svaleriabarra self._ceed = ceed 2765a21df39Svaleriabarra 2775a21df39Svaleriabarra interp1d_pointer = ffi.new("CeedScalar *") 2785a21df39Svaleriabarra interp1d_pointer = ffi.cast("CeedScalar *", interp1d.__array_interface__['data'][0]) 2795a21df39Svaleriabarra 2805a21df39Svaleriabarra grad1d_pointer = ffi.new("CeedScalar *") 2815a21df39Svaleriabarra grad1d_pointer = ffi.cast("CeedScalar *", grad1d.__array_interface__['data'][0]) 2825a21df39Svaleriabarra 2835a21df39Svaleriabarra qref1d_pointer = ffi.new("CeedScalar *") 2845a21df39Svaleriabarra qref1d_pointer = ffi.cast("CeedScalar *", qref1d.__array_interface__['data'][0]) 2855a21df39Svaleriabarra 2865a21df39Svaleriabarra qweight1d_pointer = ffi.new("CeedScalar *") 2875a21df39Svaleriabarra qweight1d_pointer = ffi.cast("CeedScalar *", qweight1d.__array_interface__['data'][0]) 2885a21df39Svaleriabarra 2895a21df39Svaleriabarra # libCEED call 2905a21df39Svaleriabarra lib.CeedBasisCreateTensorH1(self._ceed._pointer[0], dim, ncomp, P1d, Q1d, 2915a21df39Svaleriabarra interp1d_pointer, grad1d_pointer, qref1d_pointer, 2925a21df39Svaleriabarra qweight1d_pointer, self._pointer) 2935a21df39Svaleriabarra 2945a21df39Svaleriabarra# ------------------------------------------------------------------------------ 2955a21df39Svaleriabarraclass BasisTensorH1Lagrange(Basis): 2965a21df39Svaleriabarra """Ceed Tensor H1 Lagrange Basis: finite element tensor-product Lagrange basis 2975a21df39Svaleriabarra objects for H^1 discretizations.""" 2985a21df39Svaleriabarra 2995a21df39Svaleriabarra # Constructor 3005a21df39Svaleriabarra def __init__(self, ceed, dim, ncomp, P, Q, qmode): 3015a21df39Svaleriabarra 3025a21df39Svaleriabarra # Setup arguments 3035a21df39Svaleriabarra self._pointer = ffi.new("CeedBasis *") 3045a21df39Svaleriabarra 3055a21df39Svaleriabarra self._ceed = ceed 3065a21df39Svaleriabarra 3075a21df39Svaleriabarra # libCEED call 3085a21df39Svaleriabarra lib.CeedBasisCreateTensorH1Lagrange(self._ceed._pointer[0], dim, ncomp, P, 3095a21df39Svaleriabarra Q, qmode, self._pointer) 3105a21df39Svaleriabarra 3115a21df39Svaleriabarra# ------------------------------------------------------------------------------ 3125a21df39Svaleriabarraclass BasisH1(Basis): 3135a21df39Svaleriabarra """Ceed H1 Basis: finite element non tensor-product basis for H^1 discretizations.""" 3145a21df39Svaleriabarra 3155a21df39Svaleriabarra # Constructor 3165a21df39Svaleriabarra def __init__(self, ceed, topo, ncomp, nnodes, nqpts, interp, grad, qref, qweight): 3175a21df39Svaleriabarra 3185a21df39Svaleriabarra # Setup arguments 3195a21df39Svaleriabarra self._pointer = ffi.new("CeedBasis *") 3205a21df39Svaleriabarra 3215a21df39Svaleriabarra self._ceed = ceed 3225a21df39Svaleriabarra 3235a21df39Svaleriabarra interp_pointer = ffi.new("CeedScalar *") 3245a21df39Svaleriabarra interp_pointer = ffi.cast("CeedScalar *", interp.__array_interface__['data'][0]) 3255a21df39Svaleriabarra 3265a21df39Svaleriabarra grad_pointer = ffi.new("CeedScalar *") 3275a21df39Svaleriabarra grad_pointer = ffi.cast("CeedScalar *", grad.__array_interface__['data'][0]) 3285a21df39Svaleriabarra 3295a21df39Svaleriabarra qref_pointer = ffi.new("CeedScalar *") 3305a21df39Svaleriabarra qref_pointer = ffi.cast("CeedScalar *", qref.__array_interface__['data'][0]) 3315a21df39Svaleriabarra 3325a21df39Svaleriabarra qweight_pointer = ffi.new("CeedScalar *") 3335a21df39Svaleriabarra qweight_pointer = ffi.cast("CeedScalar *", qweight.__array_interface__['data'][0]) 3345a21df39Svaleriabarra 3355a21df39Svaleriabarra # libCEED call 3365a21df39Svaleriabarra lib.CeedBasisCreateH1(self._ceed._pointer[0], topo, ncomp, nnodes, nqpts, 3375a21df39Svaleriabarra interp_pointer, grad_pointer, qref_pointer, 3385a21df39Svaleriabarra qweight_pointer, self._pointer) 3395a21df39Svaleriabarra 3405a21df39Svaleriabarra# ------------------------------------------------------------------------------ 3415a21df39Svaleriabarraclass TransposeBasis(): 3425a21df39Svaleriabarra """Transpose Ceed Basis: transpose of finite element tensor-product basis objects.""" 3435a21df39Svaleriabarra 3445a21df39Svaleriabarra # Attributes 3455a21df39Svaleriabarra _basis = None 3465a21df39Svaleriabarra 3475a21df39Svaleriabarra # Constructor 3485a21df39Svaleriabarra def __init__(self, basis): 3495a21df39Svaleriabarra 3505a21df39Svaleriabarra # Reference basis 3515a21df39Svaleriabarra self._basis = basis 3525a21df39Svaleriabarra 3535a21df39Svaleriabarra # Representation 3545a21df39Svaleriabarra def __repr__(self): 3555a21df39Svaleriabarra return "<Transpose CeedBasis instance at " + hex(id(self)) + ">" 3565a21df39Svaleriabarra 3575a21df39Svaleriabarra # Apply Transpose Basis 3585a21df39Svaleriabarra def apply(self, nelem, emode, u, v): 3595a21df39Svaleriabarra """Apply basis evaluation from quadrature points to nodes. 3605a21df39Svaleriabarra 3615a21df39Svaleriabarra Args: 3625a21df39Svaleriabarra nelem: the number of elements to apply the basis evaluation to; 3635a21df39Svaleriabarra the backend will specify the ordering in a 3645a21df39Svaleriabarra Blocked ElemRestriction 3655a21df39Svaleriabarra **emode: basis evaluation mode 3665a21df39Svaleriabarra u: input vector 3675a21df39Svaleriabarra v: output vector""" 3685a21df39Svaleriabarra 3695a21df39Svaleriabarra # libCEED call 3705a21df39Svaleriabarra self._basis.apply(nelem, emode, u, v, tmode=TRANSPOSE) 3715a21df39Svaleriabarra 3725a21df39Svaleriabarra# ------------------------------------------------------------------------------ 373