#!/usr/bin/env python
# -*- coding: latin-1 -*-
##
# Copyright 2012-2013 Ghent University
#
# This file is part of vsc-administration,
# originally created by the HPC team of Ghent University (http://ugent.be/hpc/en),
# with support of Ghent University (http://ugent.be/hpc),
# the Flemish Supercomputer Centre (VSC) (https://vscentrum.be/nl/en),
# the Hercules foundation (http://www.herculesstichting.be/in_English)
# and the Department of Economy, Science and Innovation (EWI) (http://www.ewi-vlaanderen.be/en).
#
# All rights reserved.
#
##
"""
This file contains the tools for automated administration w.r.t. the VSC
Original Perl code by Stijn De Weirdt

@author: Andy Georges (Ghent University)
"""

import logging
import os
import stat

from lockfile.pidlockfile import PIDLockFile
from vsc.utils.mail import VscMail

mailer = VscMail()

def create_stat_directory(path, permissions, uid, gid, posix):
    """
    Create a new directory if it does not exist and set permissions, ownership. Otherwise,
    check the permissions and ownership and change if needed.
    """

    created = None
    try:
        statinfo = os.stat(path)
        logging.debug("Path %s found.", path)
    except OSError:
        created = posix.make_dir(path)
        logging.info("Created directory at %s" % (path,))

    if created or stat.S_IMODE(statinfo.st_mode) != permissions:
        posix.chmod(permissions, path)
        logging.info("Permissions changed for path %s to %s", path, permissions)
    else:
        logging.debug("Path %s already exists with correct permissions" % (path,))

    if created or statinfo.st_uid != uid or statinfo.st_gid != gid:
        posix.chown(uid, gid, path)
        logging.info("Ownership changed for path %s to %d, %d", path, uid, gid)
    else:
        logging.debug("Path %s already exists with correct ownership" % (path,))

    return created


#!/usr/bin/env python3

# File: fan_set_alarms.py
# Date: 2017-12-12
# Author: Wayne Lewis
#
# Description:
# Set alarms for cooling unit fans. This script will query the MCH to
# get the fan sensor IDs, then use the IDs to set the thresholds.

import argparse
import subprocess

fan_ids = []

def main():
    # Get the arguments
    parser = argparse.ArgumentParser(description = 'Set power module alarms')
    parser.add_argument('--mch',required=True, help='MCH host name or IP address')
    parser.add_argument('--lnr',default=250,required=False, help='Channel lower non-recoverable thresold')
    parser.add_argument('--lcr',default=500,required=False, help='Channel lower critical thresold')
    parser.add_argument('--lnc',default=1000,required=False, help='Channel lower non-critical thresold')
    parser.add_argument('--unc',default=3500,required=False, help='Channel upper non-critical thresold')
    parser.add_argument('--ucr',default=4000,required=False, help='Channel upper critical thresold')
    parser.add_argument('--unr',default=4500,required=False, help='Channel upper non-recoverable thresold')

    args = parser.parse_args()

    # Build the command to read the sensors
    command = []
    command.append("ipmiutil")
    command.append("sensor")
    command.append("-N")
    command.append(args.mch)

    result = subprocess.check_output(command, universal_newlines=False)

    # Get the list of fan sensor IDs
    for line in result.splitlines():
        line = line.decode('utf8')
        if "Fan" in line:
            fan_ids.append((line.split("snum")[1]).split()[0])

    # Remove duplicate fan IDs. For some reason, the above command repeats each sensor twice.
    unique_fan_ids = list(set(fan_ids))

    # Set the alarm thresholds
    for fan_id in unique_fan_ids:
        command.append("-n")
        command.append(fan_id)
        command.append("-u")
        # Alarm thresholds are lnc:lcr:lnr:unc:ucr:unr
        command.append("{}:{}:{}:{}:{}:{}".format(
            args.lnc,
            args.lcr,
            args.lnr,
            args.unc,
            args.ucr,
            args.unr))
        print ("Setting thresholds for fan ID {}".format(fan_id))
        subprocess.check_output(command)
        del command[-4:]

if __name__ == '__main__':
    main()


import numpy as np
from scipy import sparse as sp
import warnings
from discretize.utils import (
    sdiag,
    speye,
    kron3,
    spzeros,
    ddx,
    av,
    av_extrap,
    make_boundary_bool,
)
from discretize.utils.code_utils import deprecate_method, deprecate_property

def _validate_BC(bc):
    """Checks if boundary condition 'bc' is valid.

    Each bc must be either 'dirichlet' or 'neumann'

    """
    if isinstance(bc, str):
        bc = [bc, bc]
    if not isinstance(bc, list):
        raise TypeError("bc must be a single string or list of strings")
    if not len(bc) == 2:
        raise TypeError("bc list must have two elements, one for each side")

    for bc_i in bc:
        if not isinstance(bc_i, str):
            raise TypeError("each bc must be a string")
        if bc_i not in ["dirichlet", "neumann"]:
            raise ValueError("each bc must be either, 'dirichlet' or 'neumann'")
    return bc

def _ddxCellGrad(n, bc):
    """
    Create 1D derivative operator from cell-centers to nodes this means we
    go from n to n+1

    For Cell-Centered **Dirichlet**, use a ghost point::

        (u_1 - u_g)/hf = grad

            u_g       u_1      u_2
             *    |    *   |    *     ...
                  ^
                  0

        u_g = - u_1
        grad = 2*u1/dx
        negitive on the other side.

    For Cell-Centered **Neumann**, use a ghost point::

        (u_1 - u_g)/hf = 0

            u_g       u_1      u_2
             *    |    *   |    *     ...

        u_g = u_1
        grad = 0;  put a zero in.

    """
    bc = _validate_BC(bc)

    D = sp.spdiags((np.ones((n + 1, 1)) * [-1, 1]).T, [-1, 0], n + 1, n, format="csr")
    # Set the first side
    if bc[0] == "dirichlet":
        D[0, 0] = 2
    elif bc[0] == "neumann":
        D[0, 0] = 0
    # Set the second side
    if bc[1] == "dirichlet":
        D[-1, -1] = -2
    elif bc[1] == "neumann":
        D[-1, -1] = 0
    return D

def _ddxCellGradBC(n, bc):
    """
    Create 1D derivative operator from cell-centers to nodes this means we
    go from n to n+1

    For Cell-Centered **Dirichlet**, use a ghost point::

        (u_1 - u_g)/hf = grad

         u_g       u_1      u_2
          *    |    *   |    *     ...
               ^
              u_b

    We know the value at the boundary (u_b)::

        (u_g+u_1)/2 = u_b               (the average)
        u_g = 2*u_b - u_1

        So plug in to gradient:

        (u_1 - (2*u_b - u_1))/hf = grad
        2*(u_1-u_b)/hf = grad

    Separate, because BC are known (and can move to RHS later)::

        ( 2/hf )*u_1 + ( -2/hf )*u_b = grad

                       (   ^   ) JUST RETURN THIS

    """
    bc = _validate_BC(bc)

    ij = (np.array([0, n]), np.array([0, 1]))
    vals = np.zeros(2)

    # Set the first side
    if bc[0] == "dirichlet":
        vals[0] = -2
    elif bc[0] == "neumann":
        vals[0] = 0
    # Set the second side
    if bc[1] == "dirichlet":
        vals[1] = 2
    elif bc[1] == "neumann":
        vals[1] = 0
    D = sp.csr_matrix((vals, ij), shape=(n + 1, 2))
    return D

class DiffOperators(object):
    """
    Class creates the differential operators that you need!
    """

    _aliases = {
        "aveF2CC": "average_face_to_cell",
        "aveF2CCV": "average_face_to_cell_vector",
        "aveFx2CC": "average_face_x_to_cell",
        "aveFy2CC": "average_face_y_to_cell",
        "aveFz2CC": "average_face_z_to_cell",
        "aveCC2F": "average_cell_to_face",
        "aveCCV2F": "average_cell_vector_to_face",
        "aveE2CC": "average_edge_to_cell",
        "aveE2CCV": "average_edge_to_cell_vector",
        "aveEx2CC": "average_edge_x_to_cell",
        "aveEy2CC": "average_edge_y_to_cell",
        "aveEz2CC": "average_edge_z_to_cell",
        "aveN2CC": "average_node_to_cell",
        "aveN2E": "average_node_to_edge",
        "aveN2F": "average_node_to_face",
    }

    ###########################################################################
    #                                                                         #
    #                             Face Divergence                             #
    #                                                                         #
    ###########################################################################
    @property
    def _face_x_divergence_stencil(self):
        """
        Face divergence operator in the x-direction (x-faces to cell centers)
        """
        if self.dim == 1:
            Dx = ddx(self.shape_cells[0])
        elif self.dim == 2:
            Dx = sp.kron(speye(self.shape_cells[1]), ddx(self.shape_cells[0]))
        elif self.dim == 3:
            Dx = kron3(
                speye(self.shape_cells[2]),
                speye(self.shape_cells[1]),
                ddx(self.shape_cells[0]),
            )
        return Dx

    @property
    def _face_y_divergence_stencil(self):
        """
        Face divergence operator in the y-direction (y-faces to cell centers)
        """
        if self.dim == 1:
            return None
        elif self.dim == 2:
            Dy = sp.kron(ddx(self.shape_cells[1]), speye(self.shape_cells[0]))
        elif self.dim == 3:
            Dy = kron3(
                speye(self.shape_cells[2]),
                ddx(self.shape_cells[1]),
                speye(self.shape_cells[0]),
            )
        return Dy

    @property
    def _face_z_divergence_stencil(self):
        """
        Face divergence operator in the z-direction (z-faces to cell centers)
        """
        if self.dim == 1 or self.dim == 2:
            return None
        elif self.dim == 3:
            Dz = kron3(
                ddx(self.shape_cells[2]),
                speye(self.shape_cells[1]),
                speye(self.shape_cells[0]),
            )
        return Dz

    @property
    def _face_divergence_stencil(self):
        # Compute faceDivergence stencil on faces
        if self.dim == 1:
            D = self._face_x_divergence_stencil
        elif self.dim == 2:
            D = sp.hstack(
                (self._face_x_divergence_stencil, self._face_y_divergence_stencil),
                format="csr",
            )
        elif self.dim == 3:
            D = sp.hstack(
                (
                    self._face_x_divergence_stencil,
                    self._face_y_divergence_stencil,
                    self._face_z_divergence_stencil,
                ),
                format="csr",
            )
        return D

    @property
    def face_divergence(self):
        """
        Construct divergence operator (face-stg to cell-centres).
        """
        if getattr(self, "_face_divergence", None) is None:
            # Get the stencil of +1, -1's
            D = self._face_divergence_stencil
            # Compute areas of cell faces & volumes
            S = self.face_areas
            V = self.cell_volumes
            self._face_divergence = sdiag(1 / V) * D * sdiag(S)
        return self._face_divergence

    @property
    def face_x_divergence(self):
        """
        Construct divergence operator in the x component (face-stg to
        cell-centres).
        """
        # Compute areas of cell faces & volumes
        S = self.reshape(self.face_areas, "F", "Fx", "V")
        V = self.cell_volumes
        return sdiag(1 / V) * self._face_x_divergence_stencil * sdiag(S)

    @property
    def face_y_divergence(self):
        if self.dim < 2:
            return None
        # Compute areas of cell faces & volumes
        S = self.reshape(self.face_areas, "F", "Fy", "V")
        V = self.cell_volumes
        return sdiag(1 / V) * self._face_y_divergence_stencil * sdiag(S)

    @property
    def face_z_divergence(self):
        """
        Construct divergence operator in the z component (face-stg to
        cell-centers).
        """
        if self.dim < 3:
            return None
        # Compute areas of cell faces & volumes
        S = self.reshape(self.face_areas, "F", "Fz", "V")
        V = self.cell_volumes
        return sdiag(1 / V) * self._face_z_divergence_stencil * sdiag(S)

    ###########################################################################
    #                                                                         #
    #                          Nodal Diff Operators                           #
    #                                                                         #
    ###########################################################################

    @property
    def _nodal_gradient_x_stencil(self):
        """
        Stencil for the nodal grad in the x-direction (nodes to x-edges)
        """
        if self.dim == 1:
            Gx = ddx(self.shape_cells[0])
        elif self.dim == 2:
            Gx = sp.kron(speye(self.shape_nodes[1]), ddx(self.shape_cells[0]))
        elif self.dim == 3:
            Gx = kron3(
                speye(self.shape_nodes[2]),
                speye(self.shape_nodes[1]),
                ddx(self.shape_cells[0]),
            )
        return Gx

    @property
    def _nodal_gradient_y_stencil(self):
        """
        Stencil for the nodal grad in the y-direction (nodes to y-edges)
        """
        if self.dim == 1:
            return None
        elif self.dim == 2:
            Gy = sp.kron(ddx(self.shape_cells[1]), speye(self.shape_nodes[0]))
        elif self.dim == 3:
            Gy = kron3(
                speye(self.shape_nodes[2]),
                ddx(self.shape_cells[1]),
                speye(self.shape_nodes[0]),
            )
        return Gy

    @property
    def _nodal_gradient_z_stencil(self):
        """
        Stencil for the nodal grad in the z-direction (nodes to z- edges)
        """
        if self.dim == 1 or self.dim == 2:
            return None
        else:
            Gz = kron3(
                ddx(self.shape_cells[2]),
                speye(self.shape_nodes[1]),
                speye(self.shape_nodes[0]),
            )
        return Gz

    @property
    def _nodal_gradient_stencil(self):
        """
        Stencil for the nodal grad
        """
        # Compute divergence operator on faces
        if self.dim == 1:
            G = self._nodal_gradient_x_stencil
        elif self.dim == 2:
            G = sp.vstack(
                (self._nodal_gradient_x_stencil, self._nodal_gradient_y_stencil),
                format="csr",
            )
        elif self.dim == 3:
            G = sp.vstack(
                (
                    self._nodal_gradient_x_stencil,
                    self._nodal_gradient_y_stencil,
                    self._nodal_gradient_z_stencil,
                ),
                format="csr",
            )
        return G

    @property
    def nodal_gradient(self):
        """
        Construct gradient operator (nodes to edges).
        """
        if getattr(self, "_nodal_gradient", None) is None:
            G = self._nodal_gradient_stencil
            L = self.edge_lengths
            self._nodal_gradient = sdiag(1 / L) * G
        return self._nodal_gradient

    @property
    def _nodal_laplacian_x_stencil(self):
        warnings.warn("Laplacian has not been tested rigorously.")

        Dx = ddx(self.shape_cells[0])
        Lx = -Dx.T * Dx

        if self.dim == 2:
            Lx = sp.kron(speye(self.shape_nodes[1]), Lx)
        elif self.dim == 3:
            Lx = kron3(speye(self.shape_nodes[2]), speye(self.shape_nodes[1]), Lx)
        return Lx

    @property
    def _nodal_laplacian_y_stencil(self):
        warnings.warn("Laplacian has not been tested rigorously.")

        if self.dim == 1:
            return None

        Dy = ddx(self.shape_cells[1])
        Ly = -Dy.T * Dy

        if self.dim == 2:
            Ly = sp.kron(Ly, speye(self.shape_nodes[0]))
        elif self.dim == 3:
            Ly = kron3(speye(self.shape_nodes[2]), Ly, speye(self.shape_nodes[0]))
        return Ly

    @property
    def _nodal_laplacian_z_stencil(self):
        warnings.warn("Laplacian has not been tested rigorously.")

        if self.dim == 1 or self.dim == 2:
            return None

        Dz = ddx(self.shape_cells[2])
        Lz = -Dz.T * Dz
        return kron3(Lz, speye(self.shape_nodes[1]), speye(self.shape_nodes[0]))

    @property
    def _nodal_laplacian_x(self):
        Hx = sdiag(1.0 / self.h[0])
        if self.dim == 2:
            Hx = sp.kron(speye(self.shape_nodes[1]), Hx)
        elif self.dim == 3:
            Hx = kron3(speye(self.shape_nodes[2]), speye(self.shape_nodes[1]), Hx)
        return Hx.T * self._nodal_gradient_x_stencil * Hx

    @property
    def _nodal_laplacian_y(self):
        Hy = sdiag(1.0 / self.h[1])
        if self.dim == 1:
            return None
        elif self.dim == 2:
            Hy = sp.kron(Hy, speye(self.shape_nodes[0]))
        elif self.dim == 3:
            Hy = kron3(speye(self.shape_nodes[2]), Hy, speye(self.shape_nodes[0]))
        return Hy.T * self._nodal_gradient_y_stencil * Hy

    @property
    def _nodal_laplacian_z(self):
        if self.dim == 1 or self.dim == 2:
            return None
        Hz = sdiag(1.0 / self.h[2])
        Hz = kron3(Hz, speye(self.shape_nodes[1]), speye(self.shape_nodes[0]))
        return Hz.T * self._nodal_laplacian_z_stencil * Hz

    @property
    def nodal_laplacian(self):
        """
        Construct laplacian operator (nodes to edges).
        """
        if getattr(self, "_nodal_laplacian", None) is None:
            warnings.warn("Laplacian has not been tested rigorously.")
            # Compute divergence operator on faces
            if self.dim == 1:
                self._nodal_laplacian = self._nodal_laplacian_x
            elif self.dim == 2:
                self._nodal_laplacian = (
                    self._nodal_laplacian_x + self._nodal_laplacian_y
                )
            elif self.dim == 3:
                self._nodal_laplacian = (
                    self._nodal_laplacian_x
                    + self._nodal_laplacian_y
                    + self._nodal_laplacian_z
                )
        return self._nodal_laplacian

    def edge_divergence_weak_form_robin(self, alpha=0.0, beta=1.0, gamma=0.0):
        r"""Robin boundary condition for the weak formulation of the edge divergence

        This function returns the necessary parts to form the full weak form of the
        edge divergence using the nodal gradient with appropriate boundary conditions.

        The `alpha`, `beta`, and `gamma` parameters can be scalars, or arrays. If they
        are arrays, they can either be the same length as the number of boundary faces,
        or boundary nodes. If multiple parameters are arrays, they must all be the same
        length.

        `beta` can not be 0.

        It is assumed here that quantity that is approximated on the boundary is the
        gradient of another quantity. See the Notes section for explicit details.

        Parameters
        ----------
        alpha, beta : scalar or array_like
            Parameters for the Robin boundary condition. array_like must be defined on
            either boundary faces or boundary nodes.
        gamma: scalar or array_like
            right hand side boundary conditions. If this parameter is array like, it can
            be fed either a (n_boundary_XXX,) shape array or an (n_boundary_XXX, n_rhs)
            shape array if multiple systems have the same alpha and beta parameters.

        Notes
        -----
        For these returned operators, it is assumed that the quantity on the boundary is
        related to the gradient of some other quantity.

        The weak form is obtained by multiplying the divergence by a (piecewise-constant)
        test function, and integrating over the cell, i.e.

        .. math:: \int_V y \nabla \cdot \vec{u} \partial V
            :label: weak_form

        This equation can be transformed to reduce the differentiability requirement on
        :math:`\vec{u}` to be,

        .. math:: -\int_V \vec{u} \cdot (\nabla y) \partial V + \int_{dV} y \vec{u} \cdot \hat{n} \partial S.
            :label: transformed

        Furthermore, when applying these types of transformations, the unknown vector
        :math:`\vec{u}` is usually related to some scalar potential as:

        .. math:: \vec{u} = \nabla \phi
            :label: gradient of potential

        Thus the robin conditions returned by these matrices apply to the quantity of
        :math:`\phi`.

        .. math::
            \alpha \phi + \beta \nabla \phi \cdot \hat{n} = \gamma

            \alpha \phi + \beta \vec{u} \cdot \hat{n} = \gamma

        The returned operators cannot be used to impose a Dirichlet condition on
        :math:`\phi`.

        """
        alpha = np.atleast_1d(alpha)
        beta = np.atleast_1d(beta)
        gamma = np.atleast_1d(gamma)

        if np.any(beta == 0.0):
            raise ValueError("beta cannot have a zero value")

        Pbn = self.project_node_to_boundary_node
        Pbf = self.project_face_to_boundary_face

        n_boundary_faces = Pbf.shape[0]
        n_boundary_nodes = Pbn.shape[0]

        if len(alpha) == 1:
            if len(beta) != 1:
                alpha = np.full(len(beta), alpha[0])
            elif len(gamma) != 1:
                alpha = np.full(len(gamma), alpha[0])
            else:
                alpha = np.full(n_boundary_faces, alpha[0])
        if len(beta) == 1:
            if len(alpha) != 1:
                beta = np.full(len(alpha), beta[0])
        if len(gamma) == 1:
            if len(alpha) != 1:
                gamma = np.full(len(alpha), gamma[0])

        if len(alpha) != len(beta) or len(beta) != len(gamma):
            raise ValueError("alpha, beta, and gamma must have the same length")

        if len(alpha) not in [n_boundary_faces, n_boundary_nodes]:
            raise ValueError(
                "The arrays must be of length n_boundary_faces or n_boundary_nodes"
            )

        AveN2F = self.average_node_to_face
        boundary_areas = Pbf @ self.face_areas
        AveBN2Bf = Pbf @ AveN2F @ Pbn.T

        # at the boundary, we have that u dot n = (gamma - alpha * phi)/beta
        if len(alpha) == n_boundary_faces:
            if gamma.ndim == 2:
                b = Pbn.T @ (
                    AveBN2Bf.T @ (gamma / beta[:, None] * boundary_areas[:, None])
                )
            else:
                b = Pbn.T @ (AveBN2Bf.T @ (gamma / beta * boundary_areas))
            B = sp.diags(Pbn.T @ (AveBN2Bf.T @ (-alpha / beta * boundary_areas)))
        else:
            if gamma.ndim == 2:
                b = Pbn.T @ (
                    gamma / beta[:, None] * (AveBN2Bf.T @ boundary_areas)[:, None]
                )
            else:
                b = Pbn.T @ (gamma / beta * (AveBN2Bf.T @ boundary_areas))
            B = sp.diags(Pbn.T @ (-alpha / beta * (AveBN2Bf.T @ boundary_areas)))
        return B, b

    ###########################################################################
    #                                                                         #
    #                                Cell Grad                                #
    #                                                                         #
    ###########################################################################

    _cell_gradient_BC_list = "neumann"

    def set_cell_gradient_BC(self, BC):
        """
        Function that sets the boundary conditions for cell-centred derivative
        operators.

        Examples
        --------
        ..code:: python

            # Neumann in all directions
            BC = 'neumann'

            # 3D, Dirichlet in y Neumann else
            BC = ['neumann', 'dirichlet', 'neumann']

            # 3D, Neumann in x on bottom of domain,  Dirichlet else
            BC = [['neumann', 'dirichlet'], 'dirichlet', 'dirichlet']
        """

        if isinstance(BC, str):
            BC = [BC] * self.dim
        if isinstance(BC, list):
            if len(BC) != self.dim:
                raise ValueError("BC list must be the size of your mesh")
        else:
            raise TypeError("BC must be a str or a list.")

        for i, bc_i in enumerate(BC):
            BC[i] = _validate_BC(bc_i)

        # ensure we create a new gradient next time we call it
        self._cell_gradient = None
        self._cell_gradient_BC = None
        self._cell_gradient_BC_list = BC
        return BC

    @property
    def stencil_cell_gradient_x(self):
        # TODO: remove this hard-coding
        BC = ["neumann", "neumann"]
        if self.dim == 1:
            G1 = _ddxCellGrad(self.shape_cells[0], BC)
        elif self.dim == 2:
            G1 = sp.kron(
                speye(self.shape_cells[1]), _ddxCellGrad(self.shape_cells[0], BC)
            )
        elif self.dim == 3:
            G1 = kron3(
                speye(self.shape_cells[2]),
                speye(self.shape_cells[1]),
                _ddxCellGrad(self.shape_cells[0], BC),
            )
        return G1

    @property
    def stencil_cell_gradient_y(self):
        if self.dim < 2:
            return None
        BC = ["neumann", "neumann"]  # TODO: remove this hard-coding
        n = self.vnC
        if self.dim == 2:
            G2 = sp.kron(_ddxCellGrad(n[1], BC), speye(n[0]))
        elif self.dim == 3:
            G2 = kron3(speye(n[2]), _ddxCellGrad(n[1], BC), speye(n[0]))
        return G2

    @property
    def stencil_cell_gradient_z(self):
        if self.dim < 3:
            return None
        BC = ["neumann", "neumann"]  # TODO: remove this hard-coding
        n = self.vnC
        G3 = kron3(_ddxCellGrad(n[2], BC), speye(n[1]), speye(n[0]))
        return G3

    @property
    def stencil_cell_gradient(self):
        BC = self.set_cell_gradient_BC(self._cell_gradient_BC_list)
        if self.dim == 1:
            G = _ddxCellGrad(self.shape_cells[0], BC[0])
        elif self.dim == 2:
            G1 = sp.kron(
                speye(self.shape_cells[1]), _ddxCellGrad(self.shape_cells[0], BC[0])
            )
            G2 = sp.kron(
                _ddxCellGrad(self.shape_cells[1], BC[1]), speye(self.shape_cells[0])
            )
            G = sp.vstack((G1, G2), format="csr")
        elif self.dim == 3:
            G1 = kron3(
                speye(self.shape_cells[2]),
                speye(self.shape_cells[1]),
                _ddxCellGrad(self.shape_cells[0], BC[0]),
            )
            G2 = kron3(
                speye(self.shape_cells[2]),
                _ddxCellGrad(self.shape_cells[1], BC[1]),
                speye(self.shape_cells[0]),
            )
            G3 = kron3(
                _ddxCellGrad(self.shape_cells[2], BC[2]),
                speye(self.shape_cells[1]),
                speye(self.shape_cells[0]),
            )
            G = sp.vstack((G1, G2, G3), format="csr")
        return G

    @property
    def cell_gradient(self):
        """
        The cell centered Gradient, takes you to cell faces.
        """
        if getattr(self, "_cell_gradient", None) is None:
            G = self.stencil_cell_gradient
            S = self.face_areas  # Compute areas of cell faces & volumes
            V = (
                self.aveCC2F * self.cell_volumes
            )  # Average volume between adjacent cells
            self._cell_gradient = sdiag(S / V) * G
        return self._cell_gradient

    def cell_gradient_weak_form_robin(self, alpha=1.0, beta=0.0, gamma=0.0):
        r"""Robin boundary condition for the weak formulation of the cell gradient

        This function returns the necessary parts for the weak form of the cell gradient
        operator to represent the Robin boundary conditions.

        The implementation assumes a ghost cell that mirrors the boundary cells across the boundary faces, with a
        piecewise linear approximation to the values at the ghost cell centers.

        The parameters can either be defined as a constant applied to the entire boundary,
        or as arrays that represent those values on the :meth:`discretize.base.BaseTensorMesh.boundary_faces`.

        The returned arrays represent the proper boundary conditions on a solution ``u``
        such that the inner product of the gradient of ``u`` with a test function y would
        be <``y, gradient*u``> ``= y.dot((-face_divergence.T*cell_volumes + A)*u + y.dot(b)``.

        The default values will produce a zero-dirichlet boundary condition.

        Parameters
        ----------
        alpha, beta : scalar or array_like
            Parameters for the Robin boundary condition. array_like must be defined on
            each boundary face.
        gamma: scalar or array_like
            right hand side boundary conditions. If this parameter is array like, it can
            be fed either a (n_boundary_faces,) shape array or an (n_boundary_faces, n_rhs)
            shape array if multiple systems have the same alpha and beta parameters.

        Returns
        -------
        A : scipy.sparse.csr_matrix
            Matrix to add to (-face_divergence.T * cell_volumes)
        b : numpy.ndarray
            Array to add to the result of the (-face_divergence.T * cell_volumes + A) @ u.

        Examples
        --------
        We first create a very simple 2D tensor mesh on the [0, 1] boundary:

        >>> import matplotlib.pyplot as plt
        >>> import scipy.sparse as sp
        >>> import discretize
        >>> mesh = discretize.TensorMesh([32, 32])

        Define the `alpha`, `beta`, and `gamma` parameters for a zero - Dirichlet
        condition on the boundary, this corresponds to setting:

        >>> alpha = 1.0
        >>> beta = 0.0
        >>> gamma = 0.0
        >>> A, b = mesh.cell_gradient_weak_form_robin(alpha, beta, gamma)

        We can then represent the operation of taking the weak form of the gradient of a
        function defined on cell centers with appropriate robin boundary conditions as:

        >>> V = sp.diags(mesh.cell_volumes)
        >>> D = mesh.face_divergence
        >>> phi = np.sin(np.pi * mesh.cell_centers[:, 0]) * np.sin(np.pi * mesh.cell_centers[:, 1])
        >>> phi_grad = (-D.T @ V + A) @ phi + b

        Notes
        -----
        The weak form is obtained by multiplying the gradient by a (piecewise-constant)
        test function, and integrating over the cell, i.e.

        .. math:: \int_V \vec{y} \cdot \nabla u \partial V
            :label: weak_form

        This equation can be transformed to reduce the differentiability requirement on `u`
        to be,

        .. math:: -\int_V u (\nabla \cdot \vec{y}) \partial V + \int_{dV} u \vec{y} \partial V.
            :label: transformed

        The first term in equation :eq:transformed is constructed using the matrix
        operators defined on this mesh as ``D`` = :meth:`discretize.operators.DiffOperators.face_divergence` and
        ``V``, a diagonal matrix of :meth:`discretize.base.BaseMesh.cell_volumes`, as

        .. math:: -(D*y)^T*V*u.

        This function returns the necessary matrices to complete the transformation of
        equation :eq:transformed. The second part of equation :eq:transformed becomes,

        .. math:: \int_V \nabla \cdot (\phi u) \partial V = \int_{\partial\Omega} \phi\vec{u}\cdot\hat{n} \partial a
            :label: boundary_conditions

        which is then approximated with the matrices returned here such that the full
        form of the weak formulation in a discrete form would be.

        .. math:: y^T(-D^T V + B)u + y^Tb
        """

        # get length between boundary cell_centers and boundary_faces
        Pf = self.project_face_to_boundary_face
        aveC2BF = Pf @ self.average_cell_to_face
        # distance from cell centers to ghost point on boundary faces
        if self.dim == 1:
            h = np.abs(self.boundary_faces - aveC2BF @ self.cell_centers)
        else:
            h = np.linalg.norm(
                self.boundary_faces - aveC2BF @ self.cell_centers, axis=1
            )

        # for the ghost point u_k = a*u_i + b where
        a = beta / h / (alpha + beta / h)
        A = sp.diags(a) @ aveC2BF

        gamma = np.asarray(gamma)
        if gamma.ndim > 1:
            b = (gamma) / (alpha + beta / h)[:, None]
        else:
            b = (gamma) / (alpha + beta / h)

        # value at boundary = A*cells + b
        M = self.boundary_face_scalar_integral
        A = M @ A
        b = M @ b

        return A, b

    @property
    def cell_gradient_BC(self):
        """
        The cell centered Gradient boundary condition matrix
        """
        if getattr(self, "_cell_gradient_BC", None) is None:
            BC = self.set_cell_gradient_BC(self._cell_gradient_BC_list)
            n = self.vnC
            if self.dim == 1:
                G = _ddxCellGradBC(n[0], BC[0])
            elif self.dim == 2:
                G1 = sp.kron(speye(n[1]), _ddxCellGradBC(n[0], BC[0]))
                G2 = sp.kron(_ddxCellGradBC(n[1], BC[1]), speye(n[0]))
                G = sp.block_diag((G1, G2), format="csr")
            elif self.dim == 3:
                G1 = kron3(speye(n[2]), speye(n[1]), _ddxCellGradBC(n[0], BC[0]))
                G2 = kron3(speye(n[2]), _ddxCellGradBC(n[1], BC[1]), speye(n[0]))
                G3 = kron3(_ddxCellGradBC(n[2], BC[2]), speye(n[1]), speye(n[0]))
                G = sp.block_diag((G1, G2, G3), format="csr")
            # Compute areas of cell faces & volumes
            S = self.face_areas
            V = (
                self.aveCC2F * self.cell_volumes
            )  # Average volume between adjacent cells
            self._cell_gradient_BC = sdiag(S / V) * G
        return self._cell_gradient_BC

    @property
    def cell_gradient_x(self):
        """
        Cell centered Gradient in the x dimension. Has neumann boundary
        conditions.
        """
        if getattr(self, "_cell_gradient_x", None) is None:
            G1 = self.stencil_cell_gradient_x
            # Compute areas of cell faces & volumes
            V = self.aveCC2F * self.cell_volumes
            L = self.reshape(self.face_areas / V, "F", "Fx", "V")
            self._cell_gradient_x = sdiag(L) * G1
        return self._cell_gradient_x

    @property
    def cell_gradient_y(self):
        if self.dim < 2:
            return None
        if getattr(self, "_cell_gradient_y", None) is None:
            G2 = self.stencil_cell_gradient_y
            # Compute areas of cell faces & volumes
            V = self.aveCC2F * self.cell_volumes
            L = self.reshape(self.face_areas / V, "F", "Fy", "V")
            self._cell_gradient_y = sdiag(L) * G2
        return self._cell_gradient_y

    @property
    def cell_gradient_z(self):
        """
        Cell centered Gradient in the x dimension. Has neumann boundary
        conditions.
        """
        if self.dim < 3:
            return None
        if getattr(self, "_cell_gradient_z", None) is None:
            G3 = self.stencil_cell_gradient_z
            # Compute areas of cell faces & volumes
            V = self.aveCC2F * self.cell_volumes
            L = self.reshape(self.face_areas / V, "F", "Fz", "V")
            self._cell_gradient_z = sdiag(L) * G3
        return self._cell_gradient_z

    ###########################################################################
    #                                                                         #
    #                                Edge Curl                                #
    #                                                                         #
    ###########################################################################

    @property
    def _edge_x_curl_stencil(self):
        n = self.vnC  # The number of cell centers in each direction

        D32 = kron3(ddx(n[2]), speye(n[1]), speye(n[0] + 1))
        D23 = kron3(speye(n[2]), ddx(n[1]), speye(n[0] + 1))
        # O1 = spzeros(np.shape(D32)[0], np.shape(D31)[1])
        O1 = spzeros((n[0] + 1) * n[1] * n[2], n[0] * (n[1] + 1) * (n[2] + 1))

        return sp.hstack((O1, -D32, D23))

    @property
    def _edge_y_curl_stencil(self):
        n = self.vnC  # The number of cell centers in each direction

        D31 = kron3(ddx(n[2]), speye(n[1] + 1), speye(n[0]))
        D13 = kron3(speye(n[2]), speye(n[1] + 1), ddx(n[0]))
        # O2 = spzeros(np.shape(D31)[0], np.shape(D32)[1])
        O2 = spzeros(n[0] * (n[1] + 1) * n[2], (n[0] + 1) * n[1] * (n[2] + 1))

        return sp.hstack((D31, O2, -D13))

    @property
    def _edge_z_curl_stencil(self):
        n = self.vnC  # The number of cell centers in each direction

        D21 = kron3(speye(n[2] + 1), ddx(n[1]), speye(n[0]))
        D12 = kron3(speye(n[2] + 1), speye(n[1]), ddx(n[0]))
        # O3 = spzeros(np.shape(D21)[0], np.shape(D13)[1])
        O3 = spzeros(n[0] * n[1] * (n[2] + 1), (n[0] + 1) * (n[1] + 1) * n[2])

        return sp.hstack((-D21, D12, O3))

    @property
    def _edge_curl_stencil(self):
        if self.dim <= 1:
            raise NotImplementedError("Edge Curl only programed for 2 or 3D.")

        # Compute divergence operator on faces
        if self.dim == 2:
            n = self.vnC  # The number of cell centers in each direction

            D21 = sp.kron(ddx(n[1]), speye(n[0]))
            D12 = sp.kron(speye(n[1]), ddx(n[0]))
            C = sp.hstack((-D21, D12), format="csr")
            return C

        elif self.dim == 3:

            # D32 = kron3(ddx(n[2]), speye(n[1]), speye(n[0]+1))
            # D23 = kron3(speye(n[2]), ddx(n[1]), speye(n[0]+1))
            # D31 = kron3(ddx(n[2]), speye(n[1]+1), speye(n[0]))
            # D13 = kron3(speye(n[2]), speye(n[1]+1), ddx(n[0]))
            # D21 = kron3(speye(n[2]+1), ddx(n[1]), speye(n[0]))
            # D12 = kron3(speye(n[2]+1), speye(n[1]), ddx(n[0]))

            # O1 = spzeros(np.shape(D32)[0], np.shape(D31)[1])
            # O2 = spzeros(np.shape(D31)[0], np.shape(D32)[1])
            # O3 = spzeros(np.shape(D21)[0], np.shape(D13)[1])

            # C = sp.vstack((sp.hstack((O1, -D32, D23)),
            #                sp.hstack((D31, O2, -D13)),
            #                sp.hstack((-D21, D12, O3))), format="csr")

            C = sp.vstack(
                (
                    self._edge_x_curl_stencil,
                    self._edge_y_curl_stencil,
                    self._edge_z_curl_stencil,
                ),
                format="csr",
            )

            return C

    @property
    def edge_curl(self):
        """
        Construct the 3D curl operator.
        """
        L = self.edge_lengths  # Compute lengths of cell edges
        S = self.face_areas  # Compute areas of cell faces

        if getattr(self, "_edge_curl", None) is None:

            if self.dim <= 1:
                raise NotImplementedError("Edge Curl only programed for 2 or 3D.")

            if self.dim == 2:
                self._edge_curl = self._edge_curl_stencil * sdiag(1 / S)
            elif self.dim == 3:
                self._edge_curl = sdiag(1 / S) * (self._edge_curl_stencil * sdiag(L))

        return self._edge_curl

    @property
    def boundary_face_scalar_integral(self):
        r"""Represents the operation of integrating a scalar function on the boundary

        This matrix represents the boundary surface integral of a scalar function
        multiplied with a finite volume test function on the mesh.

        Returns
        -------
        scipy.sparse.csr_matrix
            Sparse matrix of shape (n_faces, n_boundary_faces)

        Notes
        -----
        The integral we are representing on the boundary of the mesh is

        .. math:: \int_{\Omega} u\vec{w} \cdot \hat{n} \partial \Omega

        In discrete form this is:

        .. math:: w^T * P * u_b

        where `w` is defined on all faces, and `u_b` is defined on boundary faces.
        """
        if self.dim == 1:
            return sp.csr_matrix(
                ([-1, 1], ([0, self.n_faces_x - 1], [0, 1])), shape=(self.n_faces_x, 2)
            )
        P = self.project_face_to_boundary_face

        w_h_dot_normal = np.sum(
            (P @ self.face_normals) * self.boundary_face_outward_normals, axis=-1
        )
        A = sp.diags(self.face_areas) @ P.T @ sp.diags(w_h_dot_normal)
        return A

    @property
    def boundary_edge_vector_integral(self):
        r"""Represents the operation of integrating a vector function on the boundary

        This matrix represents the boundary surface integral of a vector function
        multiplied with a finite volume test function on the mesh.

        In 1D and 2D, the operation assumes that the right array contains only a single
        component of the vector ``u``. In 3D, however, we must assume that ``u`` will
        contain each of the three vector components, and it must be ordered as,
        ``[edges_1_x, ... ,edge_N_x, edge_1_y, ..., edge_N_y, edge_1_z, ..., edge_N_z]``
        , where ``N`` is the number of boundary edges.

        Returns
        -------
        scipy.sparse.csr_matrix
            Sparse matrix of shape (n_edges, n_boundary_edges) for 1D or 2D mesh,
            (n_edges, 3*n_boundary_edges) for a 3D mesh.

        Notes
        -----
        The integral we are representing on the boundary of the mesh is

        .. math:: \int_{\Omega} \vec{w} \cdot (\vec{u} \times \hat{n}) \partial \Omega

        In discrete form this is:

        .. math:: w^T * P * u_b

        where `w` is defined on all edges, and `u_b` is all three components defined on
        boundary edges.
        """
        Pe = self.project_edge_to_boundary_edge
        Pf = self.project_face_to_boundary_face
        dA = self.boundary_face_outward_normals * (Pf @ self.face_areas)[:, None]
        w = Pe @ self.edge_tangents

        n_boundary_edges = len(w)

        Av = Pf @ self.average_edge_to_face_vector @ Pe.T

        w_cross_n = np.cross(-w, Av.T @ dA)

        if self.dim == 2:
            return Pe.T @ sp.diags(w_cross_n, format="csr")
        return Pe.T @ sp.diags(
            w_cross_n.T,
            n_boundary_edges * np.arange(3),
            shape=(n_boundary_edges, 3 * n_boundary_edges),
        )

    @property
    def boundary_node_vector_integral(self):
        r"""Represents the operation of integrating a vector function dotted with the boundary normal

        This matrix represents the boundary surface integral of a vector function
        dotted with the boundary normal and multiplied with a scalar finite volume
        test function on the mesh.

        Returns
        -------
        scipy.sparse.csr_matrix
            Sparse matrix of shape (n_nodes, ndim * n_boundary_nodes).

        Notes
        -----
        The integral we are representing on the boundary of the mesh is

        .. math:: \int_{\Omega} (w \vec{u}) \cdot \hat{n} \partial \Omega

        In discrete form this is:

        .. math:: w^T * P * u_b

        where `w` is defined on all nodes, and `u_b` is all three components defined on
        boundary nodes.
        """
        if self.dim == 1:
            return sp.csr_matrix(
                ([-1, 1], ([0, self.shape_nodes[0] - 1], [0, 1])),
                shape=(self.shape_nodes[0], 2),
            )
        Pn = self.project_node_to_boundary_node
        Pf = self.project_face_to_boundary_face
        n_boundary_nodes = Pn.shape[0]

        dA = self.boundary_face_outward_normals * (Pf @ self.face_areas)[:, None]

        Av = Pf @ self.average_node_to_face @ Pn.T

        u_dot_ds = Av.T @ dA
        diags = u_dot_ds.T
        offsets = n_boundary_nodes * np.arange(self.dim)

        return Pn.T @ sp.diags(
            diags, offsets, shape=(n_boundary_nodes, self.dim * n_boundary_nodes)
        )

    def get_BC_projections(self, BC, discretization="CC"):
        """
        The weak form boundary condition projection matrices.

        Examples
        --------

        .. code:: python

            # Neumann in all directions
            BC = 'neumann'

            # 3D, Dirichlet in y Neumann else
            BC = ['neumann', 'dirichlet', 'neumann']

            # 3D, Neumann in x on bottom of domain, Dirichlet else
            BC = [['neumann', 'dirichlet'], 'dirichlet', 'dirichlet']
        """

        if discretization != "CC":
            raise NotImplementedError(
                "Boundary conditions only implemented" "for CC discretization."
            )

        if isinstance(BC, str):
            BC = [BC for _ in self.vnC]  # Repeat the str self.dim times
        elif isinstance(BC, list):
            if len(BC) != self.dim:
                raise ValueError("BC list must be the size of your mesh")
        else:
            raise TypeError("BC must be a str or a list.")

        for i, bc_i in enumerate(BC):
            BC[i] = _validate_BC(bc_i)

        def projDirichlet(n, bc):
            bc = _validate_BC(bc)
            ij = ([0, n], [0, 1])
            vals = [0, 0]
            if bc[0] == "dirichlet":
                vals[0] = -1
            if bc[1] == "dirichlet":
                vals[1] = 1
            return sp.csr_matrix((vals, ij), shape=(n + 1, 2))

        def projNeumannIn(n, bc):
            bc = _validate_BC(bc)
            P = sp.identity(n + 1).tocsr()
            if bc[0] == "neumann":
                P = P[1:, :]
            if bc[1] == "neumann":
                P = P[:-1, :]
            return P

        def projNeumannOut(n, bc):
            bc = _validate_BC(bc)
            ij = ([0, 1], [0, n])
            vals = [0, 0]
            if bc[0] == "neumann":
                vals[0] = 1
            if bc[1] == "neumann":
                vals[1] = 1
            return sp.csr_matrix((vals, ij), shape=(2, n + 1))

        n = self.vnC
        indF = self.face_boundary_indices
        if self.dim == 1:
            Pbc = projDirichlet(n[0], BC[0])
            indF = indF[0] | indF[1]
            Pbc = Pbc * sdiag(self.face_areas[indF])

            Pin = projNeumannIn(n[0], BC[0])

            Pout = projNeumannOut(n[0], BC[0])

        elif self.dim == 2:
            Pbc1 = sp.kron(speye(n[1]), projDirichlet(n[0], BC[0]))
            Pbc2 = sp.kron(projDirichlet(n[1], BC[1]), speye(n[0]))
            Pbc = sp.block_diag((Pbc1, Pbc2), format="csr")
            indF = np.r_[(indF[0] | indF[1]), (indF[2] | indF[3])]
            Pbc = Pbc * sdiag(self.face_areas[indF])

            P1 = sp.kron(speye(n[1]), projNeumannIn(n[0], BC[0]))
            P2 = sp.kron(projNeumannIn(n[1], BC[1]), speye(n[0]))
            Pin = sp.block_diag((P1, P2), format="csr")

            P1 = sp.kron(speye(n[1]), projNeumannOut(n[0], BC[0]))
            P2 = sp.kron(projNeumannOut(n[1], BC[1]), speye(n[0]))
            Pout = sp.block_diag((P1, P2), format="csr")

        elif self.dim == 3:
            Pbc1 = kron3(speye(n[2]), speye(n[1]), projDirichlet(n[0], BC[0]))
            Pbc2 = kron3(speye(n[2]), projDirichlet(n[1], BC[1]), speye(n[0]))
            Pbc3 = kron3(projDirichlet(n[2], BC[2]), speye(n[1]), speye(n[0]))
            Pbc = sp.block_diag((Pbc1, Pbc2, Pbc3), format="csr")
            indF = np.r_[(indF[0] | indF[1]), (indF[2] | indF[3]), (indF[4] | indF[5])]
            Pbc = Pbc * sdiag(self.face_areas[indF])

            P1 = kron3(speye(n[2]), speye(n[1]), projNeumannIn(n[0], BC[0]))
            P2 = kron3(speye(n[2]), projNeumannIn(n[1], BC[1]), speye(n[0]))
            P3 = kron3(projNeumannIn(n[2], BC[2]), speye(n[1]), speye(n[0]))
            Pin = sp.block_diag((P1, P2, P3), format="csr")

            P1 = kron3(speye(n[2]), speye(n[1]), projNeumannOut(n[0], BC[0]))
            P2 = kron3(speye(n[2]), projNeumannOut(n[1], BC[1]), speye(n[0]))
            P3 = kron3(projNeumannOut(n[2], BC[2]), speye(n[1]), speye(n[0]))
            Pout = sp.block_diag((P1, P2, P3), format="csr")

        return Pbc, Pin, Pout

    def get_BC_projections_simple(self, discretization="CC"):
        """The weak form boundary condition projection matrices
        when mixed boundary condition is used
        """

        if discretization != "CC":
            raise NotImplementedError(
                "Boundary conditions only implemented" "for CC discretization."
            )

        def projBC(n):
            ij = ([0, n], [0, 1])
            vals = [0, 0]
            vals[0] = 1
            vals[1] = 1
            return sp.csr_matrix((vals, ij), shape=(n + 1, 2))

        def projDirichlet(n, bc):
            bc = _validate_BC(bc)
            ij = ([0, n], [0, 1])
            vals = [0, 0]
            if bc[0] == "dirichlet":
                vals[0] = -1
            if bc[1] == "dirichlet":
                vals[1] = 1
            return sp.csr_matrix((vals, ij), shape=(n + 1, 2))

        BC = [
            ["dirichlet", "dirichlet"],
            ["dirichlet", "dirichlet"],
            ["dirichlet", "dirichlet"],
        ]
        n = self.vnC
        indF = self.face_boundary_indices

        if self.dim == 1:
            Pbc = projDirichlet(n[0], BC[0])
            B = projBC(n[0])
            indF = indF[0] | indF[1]
            Pbc = Pbc * sdiag(self.face_areas[indF])

        elif self.dim == 2:
            Pbc1 = sp.kron(speye(n[1]), projDirichlet(n[0], BC[0]))
            Pbc2 = sp.kron(projDirichlet(n[1], BC[1]), speye(n[0]))
            Pbc = sp.block_diag((Pbc1, Pbc2), format="csr")
            B1 = sp.kron(speye(n[1]), projBC(n[0]))
            B2 = sp.kron(projBC(n[1]), speye(n[0]))
            B = sp.block_diag((B1, B2), format="csr")
            indF = np.r_[(indF[0] | indF[1]), (indF[2] | indF[3])]
            Pbc = Pbc * sdiag(self.face_areas[indF])

        elif self.dim == 3:
            Pbc1 = kron3(speye(n[2]), speye(n[1]), projDirichlet(n[0], BC[0]))
            Pbc2 = kron3(speye(n[2]), projDirichlet(n[1], BC[1]), speye(n[0]))
            Pbc3 = kron3(projDirichlet(n[2], BC[2]), speye(n[1]), speye(n[0]))
            Pbc = sp.block_diag((Pbc1, Pbc2, Pbc3), format="csr")
            B1 = kron3(speye(n[2]), speye(n[1]), projBC(n[0]))
            B2 = kron3(speye(n[2]), projBC(n[1]), speye(n[0]))
            B3 = kron3(projBC(n[2]), speye(n[1]), speye(n[0]))
            B = sp.block_diag((B1, B2, B3), format="csr")
            indF = np.r_[(indF[0] | indF[1]), (indF[2] | indF[3]), (indF[4] | indF[5])]
            Pbc = Pbc * sdiag(self.face_areas[indF])

        return Pbc, B.T

    ###########################################################################
    #                                                                         #
    #                                Averaging                                #
    #                                                                         #
    ###########################################################################

    @property
    def average_face_to_cell(self):
        "Construct the averaging operator on cell faces to cell centers."
        if getattr(self, "_average_face_to_cell", None) is None:
            if self.dim == 1:
                self._average_face_to_cell = self.aveFx2CC
            elif self.dim == 2:
                self._average_face_to_cell = (0.5) * sp.hstack(
                    (self.aveFx2CC, self.aveFy2CC), format="csr"
                )
            elif self.dim == 3:
                self._average_face_to_cell = (1.0 / 3.0) * sp.hstack(
                    (self.aveFx2CC, self.aveFy2CC, self.aveFz2CC), format="csr"
                )
        return self._average_face_to_cell

    @property
    def average_face_to_cell_vector(self):
        "Construct the averaging operator on cell faces to cell centers."
        if getattr(self, "_average_face_to_cell_vector", None) is None:
            if self.dim == 1:
                self._average_face_to_cell_vector = self.aveFx2CC
            elif self.dim == 2:
                self._average_face_to_cell_vector = sp.block_diag(
                    (self.aveFx2CC, self.aveFy2CC), format="csr"
                )
            elif self.dim == 3:
                self._average_face_to_cell_vector = sp.block_diag(
                    (self.aveFx2CC, self.aveFy2CC, self.aveFz2CC), format="csr"
                )
        return self._average_face_to_cell_vector

    @property
    def average_face_x_to_cell(self):
        """
        Construct the averaging operator on cell faces in the x direction to
        cell centers.
        """

        if getattr(self, "_average_face_x_to_cell", None) is None:
            n = self.vnC
            if self.dim == 1:
                self._average_face_x_to_cell = av(n[0])
            elif self.dim == 2:
                self._average_face_x_to_cell = sp.kron(speye(n[1]), av(n[0]))
            elif self.dim == 3:
                self._average_face_x_to_cell = kron3(speye(n[2]), speye(n[1]), av(n[0]))
        return self._average_face_x_to_cell

    @property
    def average_face_y_to_cell(self):
        """
        Construct the averaging operator on cell faces in the y direction to
        cell centers.
        """
        if self.dim < 2:
            return None
        if getattr(self, "_average_face_y_to_cell", None) is None:
            n = self.vnC
            if self.dim == 2:
                self._average_face_y_to_cell = sp.kron(av(n[1]), speye(n[0]))
            elif self.dim == 3:
                self._average_face_y_to_cell = kron3(speye(n[2]), av(n[1]), speye(n[0]))
        return self._average_face_y_to_cell

    @property
    def average_face_z_to_cell(self):
        """
        Construct the averaging operator on cell faces in the z direction to
        cell centers.
        """
        if self.dim < 3:
            return None
        if getattr(self, "_average_face_z_to_cell", None) is None:
            n = self.vnC
            if self.dim == 3:
                self._average_face_z_to_cell = kron3(av(n[2]), speye(n[1]), speye(n[0]))
        return self._average_face_z_to_cell

    @property
    def average_cell_to_face(self):
        "Construct the averaging operator on cell centers to faces."
        if getattr(self, "_average_cell_to_face", None) is None:
            if self.dim == 1:
                self._average_cell_to_face = av_extrap(self.shape_cells[0])
            elif self.dim == 2:
                self._average_cell_to_face = sp.vstack(
                    (
                        sp.kron(
                            speye(self.shape_cells[1]), av_extrap(self.shape_cells[0])
                        ),
                        sp.kron(
                            av_extrap(self.shape_cells[1]), speye(self.shape_cells[0])
                        ),
                    ),
                    format="csr",
                )
            elif self.dim == 3:
                self._average_cell_to_face = sp.vstack(
                    (
                        kron3(
                            speye(self.shape_cells[2]),
                            speye(self.shape_cells[1]),
                            av_extrap(self.shape_cells[0]),
                        ),
                        kron3(
                            speye(self.shape_cells[2]),
                            av_extrap(self.shape_cells[1]),
                            speye(self.shape_cells[0]),
                        ),
                        kron3(
                            av_extrap(self.shape_cells[2]),
                            speye(self.shape_cells[1]),
                            speye(self.shape_cells[0]),
                        ),
                    ),
                    format="csr",
                )
        return self._average_cell_to_face

    @property
    def average_cell_vector_to_face(self):
        """
        Construct the averaging operator on cell centers to
        faces as a vector.
        """
        if getattr(self, "_average_cell_vector_to_face", None) is None:
            if self.dim == 1:
                self._average_cell_vector_to_face = self.aveCC2F
            elif self.dim == 2:
                aveCCV2Fx = sp.kron(
                    speye(self.shape_cells[1]), av_extrap(self.shape_cells[0])
                )
                aveCC2VFy = sp.kron(
                    av_extrap(self.shape_cells[1]), speye(self.shape_cells[0])
                )
                self._average_cell_vector_to_face = sp.block_diag(
                    (aveCCV2Fx, aveCC2VFy), format="csr"
                )
            elif self.dim == 3:
                aveCCV2Fx = kron3(
                    speye(self.shape_cells[2]),
                    speye(self.shape_cells[1]),
                    av_extrap(self.shape_cells[0]),
                )
                aveCC2VFy = kron3(
                    speye(self.shape_cells[2]),
                    av_extrap(self.shape_cells[1]),
                    speye(self.shape_cells[0]),
                )
                aveCC2BFz = kron3(
                    av_extrap(self.shape_cells[2]),
                    speye(self.shape_cells[1]),
                    speye(self.shape_cells[0]),
                )
                self._average_cell_vector_to_face = sp.block_diag(
                    (aveCCV2Fx, aveCC2VFy, aveCC2BFz), format="csr"
                )
        return self._average_cell_vector_to_face

    @property
    def average_cell_to_edge(self):
        if getattr(self, "_average_cell_to_edge", None) is None:
            n = self.shape_cells
            if self.dim == 1:
                avg = sp.eye(n[0])
            elif self.dim == 2:
                avg = sp.vstack(
                    (
                        sp.kron(av_extrap(n[1]), speye(n[0])),
                        sp.kron(speye(n[1]), av_extrap(n[0])),
                    ),
                    format="csr",
                )
            elif self.dim == 3:
                avg = sp.vstack(
                    (
                        kron3(av_extrap(n[2]), av_extrap(n[1]), speye(n[0])),
                        kron3(av_extrap(n[2]), speye(n[1]), av_extrap(n[0])),
                        kron3(speye(n[2]), av_extrap(n[1]), av_extrap(n[0])),
                    ),
                    format="csr",
                )
            self._average_cell_to_edge = avg
        return self._average_cell_to_edge

    @property
    def average_edge_to_cell(self):
        "Construct the averaging operator on cell edges to cell centers."
        if getattr(self, "_average_edge_to_cell", None) is None:
            if self.dim == 1:
                self._avE2CC = self.aveEx2CC
            elif self.dim == 2:
                self._avE2CC = 0.5 * sp.hstack(
                    (self.aveEx2CC, self.aveEy2CC), format="csr"
                )
            elif self.dim == 3:
                self._avE2CC = (1.0 / 3) * sp.hstack(
                    (self.aveEx2CC, self.aveEy2CC, self.aveEz2CC), format="csr"
                )
        return self._avE2CC

    @property
    def average_edge_to_cell_vector(self):
        "Construct the averaging operator on cell edges to cell centers."
        if getattr(self, "_average_edge_to_cell_vector", None) is None:
            if self.dim == 1:
                self._average_edge_to_cell_vector = self.aveEx2CC
            elif self.dim == 2:
                self._average_edge_to_cell_vector = sp.block_diag(
                    (self.aveEx2CC, self.aveEy2CC), format="csr"
                )
            elif self.dim == 3:
                self._average_edge_to_cell_vector = sp.block_diag(
                    (self.aveEx2CC, self.aveEy2CC, self.aveEz2CC), format="csr"
                )
        return self._average_edge_to_cell_vector

    @property
    def average_edge_x_to_cell(self):
        """
        Construct the averaging operator on cell edges in the x direction to
        cell centers.
        """
        if getattr(self, "_average_edge_x_to_cell", None) is None:
            # The number of cell centers in each direction
            n = self.vnC
            if self.dim == 1:
                self._average_edge_x_to_cell = speye(n[0])
            elif self.dim == 2:
                self._average_edge_x_to_cell = sp.kron(av(n[1]), speye(n[0]))
            elif self.dim == 3:
                self._average_edge_x_to_cell = kron3(av(n[2]), av(n[1]), speye(n[0]))
        return self._average_edge_x_to_cell

    @property
    def average_edge_y_to_cell(self):
        """
        Construct the averaging operator on cell edges in the y direction to
        cell centers.
        """
        if self.dim < 2:
            return None
        if getattr(self, "_average_edge_y_to_cell", None) is None:
            # The number of cell centers in each direction
            n = self.vnC
            if self.dim == 2:
                self._average_edge_y_to_cell = sp.kron(speye(n[1]), av(n[0]))
            elif self.dim == 3:
                self._average_edge_y_to_cell = kron3(av(n[2]), speye(n[1]), av(n[0]))
        return self._average_edge_y_to_cell

    @property
    def average_edge_z_to_cell(self):
        """
        Construct the averaging operator on cell edges in the z direction to
        cell centers.
        """
        if self.dim < 3:
            return None
        if getattr(self, "_average_edge_z_to_cell", None) is None:
            # The number of cell centers in each direction
            n = self.vnC
            if self.dim == 3:
                self._average_edge_z_to_cell = kron3(speye(n[2]), av(n[1]), av(n[0]))
        return self._average_edge_z_to_cell

    @property
    def average_edge_to_face_vector(self):
        if self.dim == 1:
            return self.average_cell_to_face
        elif self.dim == 2:
            return sp.diags(
                [1, 1],
                [-self.n_faces_x, self.n_faces_y],
                shape=(self.n_faces, self.n_edges),
            )
        n1, n2, n3 = self.shape_cells
        ex_to_fy = kron3(av(n3), speye(n2 + 1), speye(n1))
        ex_to_fz = kron3(speye(n3 + 1), av(n2), speye(n1))

        ey_to_fx = kron3(av(n3), speye(n2), speye(n1 + 1))
        ey_to_fz = kron3(speye(n3 + 1), speye(n2), av(n1))

        ez_to_fx = kron3(speye(n3), av(n2), speye(n1 + 1))
        ez_to_fy = kron3(speye(n3), speye(n2 + 1), av(n1))

        e_to_f = sp.bmat(
            [
                [None, ey_to_fx, ez_to_fx],
                [ex_to_fy, None, ez_to_fy],
                [ex_to_fz, ey_to_fz, None],
            ],
            format="csr",
        )
        return e_to_f

    @property
    def average_node_to_cell(self):
        "Construct the averaging operator on cell nodes to cell centers."
        if getattr(self, "_average_node_to_cell", None) is None:
            # The number of cell centers in each direction
            if self.dim == 1:
                self._average_node_to_cell = av(self.shape_cells[0])
            elif self.dim == 2:
                self._average_node_to_cell = sp.kron(
                    av(self.shape_cells[1]), av(self.shape_cells[0])
                ).tocsr()
            elif self.dim == 3:
                self._average_node_to_cell = kron3(
                    av(self.shape_cells[2]),
                    av(self.shape_cells[1]),
                    av(self.shape_cells[0]),
                ).tocsr()
        return self._average_node_to_cell

    @property
    def _average_node_to_edge_x(self):
        """
        Averaging operator on cell nodes to x-edges
        """
        if self.dim == 1:
            aveN2Ex = av(self.shape_cells[0])
        elif self.dim == 2:
            aveN2Ex = sp.kron(speye(self.shape_nodes[1]), av(self.shape_cells[0]))
        elif self.dim == 3:
            aveN2Ex = kron3(
                speye(self.shape_nodes[2]),
                speye(self.shape_nodes[1]),
                av(self.shape_cells[0]),
            )
        return aveN2Ex

    @property
    def _average_node_to_edge_y(self):
        """
        Averaging operator on cell nodes to y-edges
        """
        if self.dim == 1:
            return None
        elif self.dim == 2:
            aveN2Ey = sp.kron(av(self.shape_cells[1]), speye(self.shape_nodes[0]))
        elif self.dim == 3:
            aveN2Ey = kron3(
                speye(self.shape_nodes[2]),
                av(self.shape_cells[1]),
                speye(self.shape_nodes[0]),
            )
        return aveN2Ey

    @property
    def _average_node_to_edge_z(self):
        if self.dim == 1 or self.dim == 2:
            return None
        elif self.dim == 3:
            aveN2Ez = kron3(
                av(self.shape_cells[2]),
                speye(self.shape_nodes[1]),
                speye(self.shape_nodes[0]),
            )
        return aveN2Ez

    @property
    def average_node_to_edge(self):
        """
        Construct the averaging operator on cell nodes to cell edges, keeping
        each dimension separate.
        """
        if getattr(self, "_average_node_to_edge", None) is None:
            # The number of cell centers in each direction
            if self.dim == 1:
                self._average_node_to_edge = self._average_node_to_edge_x
            elif self.dim == 2:
                self._average_node_to_edge = sp.vstack(
                    (self._average_node_to_edge_x, self._average_node_to_edge_y),
                    format="csr",
                )
            elif self.dim == 3:
                self._average_node_to_edge = sp.vstack(
                    (
                        self._average_node_to_edge_x,
                        self._average_node_to_edge_y,
                        self._average_node_to_edge_z,
                    ),
                    format="csr",
                )
        return self._average_node_to_edge

    @property
    def _average_node_to_face_x(self):
        if self.dim == 1:
            aveN2Fx = speye(self.shape_nodes[0])
        elif self.dim == 2:
            aveN2Fx = sp.kron(av(self.shape_cells[1]), speye(self.shape_nodes[0]))
        elif self.dim == 3:
            aveN2Fx = kron3(
                av(self.shape_cells[2]),
                av(self.shape_cells[1]),
                speye(self.shape_nodes[0]),
            )
        return aveN2Fx

    @property
    def _average_node_to_face_y(self):
        if self.dim == 1:
            return None
        elif self.dim == 2:
            aveN2Fy = sp.kron(speye(self.shape_nodes[1]), av(self.shape_cells[0]))
        elif self.dim == 3:
            aveN2Fy = kron3(
                av(self.shape_cells[2]),
                speye(self.shape_nodes[1]),
                av(self.shape_cells[0]),
            )
        return aveN2Fy

    @property
    def _average_node_to_face_z(self):
        if self.dim == 1 or self.dim == 2:
            return None
        else:
            aveN2Fz = kron3(
                speye(self.shape_nodes[2]),
                av(self.shape_cells[1]),
                av(self.shape_cells[0]),
            )
        return aveN2Fz

    @property
    def average_node_to_face(self):
        """
        Construct the averaging operator on cell nodes to cell faces, keeping
        each dimension separate.
        """
        if getattr(self, "_average_node_to_face", None) is None:
            # The number of cell centers in each direction
            if self.dim == 1:
                self._average_node_to_face = self._average_node_to_face_x
            elif self.dim == 2:
                self._average_node_to_face = sp.vstack(
                    (self._average_node_to_face_x, self._average_node_to_face_y),
                    format="csr",
                )
            elif self.dim == 3:
                self._average_node_to_face = sp.vstack(
                    (
                        self._average_node_to_face_x,
                        self._average_node_to_face_y,
                        self._average_node_to_face_z,
                    ),
                    format="csr",
                )
        return self._average_node_to_face

    @property
    def project_face_to_boundary_face(self):
        """Projects values defined on all faces to the boundary faces

        Returns
        -------
        scipy.sparse.csr_matrix
            Projection matrix with shape (n_boundary_faces, n_faces)
        """
        # Simple matrix which projects the values of the faces onto the boundary faces
        # Can also be used to "select" the boundary faces

        # Create a matrix that projects all faces onto boundary faces
        # The below should work for a regular structured mesh
        is_b = make_boundary_bool(self.shape_faces_x, dir="x")
        if self.dim > 1:
            is_b = np.r_[is_b, make_boundary_bool(self.shape_faces_y, dir="y")]
        if self.dim == 3:
            is_b = np.r_[is_b, make_boundary_bool(self.shape_faces_z, dir="z")]
        return sp.eye(self.n_faces, format="csr")[is_b]

    @property
    def project_edge_to_boundary_edge(self):
        """Projects values defined on all edges to the boundary edges

        Returns
        -------
        scipy.sparse.csr_matrix
            Projection matrix with shape (n_boundary_edges, n_edges)
        """
        # Simple matrix which projects the values of the faces onto the boundary faces
        # Can also be used to "select" the boundary faces

        # Create a matrix that projects all edges onto boundary edges
        # The below should work for a regular structured mesh
        if self.dim == 1:
            return None  # No edges are on the boundary in 1D

        is_b = np.r_[
            make_boundary_bool(self.shape_edges_x, dir="yz"),
            make_boundary_bool(self.shape_edges_y, dir="xz"),
        ]
        if self.dim == 3:
            is_b = np.r_[is_b, make_boundary_bool(self.shape_edges_z, dir="xy")]
        return sp.eye(self.n_edges, format="csr")[is_b]

    @property
    def project_node_to_boundary_node(self):
        """Projects values defined on all edges to the boundary edges

        Returns
        -------
        scipy.sparse.csr_matrix
            Projection matrix with shape (n_boundary_nodes, n_nodes)
        """
        # Simple matrix which projects the values of the nodes onto the boundary nodes
        # Can also be used to "select" the boundary nodes

        # Create a matrix that projects all nodes onto boundary nodes
        # The below should work for a regular structured mesh

        is_b = make_boundary_bool(self.shape_nodes)
        return sp.eye(self.n_nodes, format="csr")[is_b]

    # DEPRECATED
    cellGrad = deprecate_property("cell_gradient", "cellGrad", removal_version="1.0.0")
    cellGradBC = deprecate_property(
        "cell_gradient_BC", "cellGradBC", removal_version="1.0.0"
    )
    cellGradx = deprecate_property(
        "cell_gradient_x", "cellGradx", removal_version="1.0.0"
    )
    cellGrady = deprecate_property(
        "cell_gradient_y", "cellGrady", removal_version="1.0.0"
    )
    cellGradz = deprecate_property(
        "cell_gradient_z", "cellGradz", removal_version="1.0.0"
    )
    nodalGrad = deprecate_property(
        "nodal_gradient", "nodalGrad", removal_version="1.0.0"
    )
    nodalLaplacian = deprecate_property(
        "nodal_laplacian", "nodalLaplacian", removal_version="1.0.0"
    )
    faceDiv = deprecate_property("face_divergence", "faceDiv", removal_version="1.0.0")
    faceDivx = deprecate_property(
        "face_x_divergence", "faceDivx", removal_version="1.0.0"
    )
    faceDivy = deprecate_property(
        "face_y_divergence", "faceDivy", removal_version="1.0.0"
    )
    faceDivz = deprecate_property(
        "face_z_divergence", "faceDivz", removal_version="1.0.0"
    )
    edgeCurl = deprecate_property("edge_curl", "edgeCurl", removal_version="1.0.0")
    _cellGradStencil = deprecate_property(
        "stencil_cell_gradient", "_cellGradStencil", removal_version="1.0.0"
    )
    _cellGradxStencil = deprecate_property(
        "stencil_cell_gradient_x", "_cellGradxStencil", removal_version="1.0.0"
    )
    _cellGradyStencil = deprecate_property(
        "stencil_cell_gradient_y", "_cellGradyStencil", removal_version="1.0.0"
    )
    _cellGradzStencil = deprecate_property(
        "stencil_cell_gradient_z", "_cellGradzStencil", removal_version="1.0.0"
    )

    setCellGradBC = deprecate_method(
        "set_cell_gradient_BC", "setCellGradBC", removal_version="1.0.0"
    )
    getBCProjWF = deprecate_method(
        "get_BC_projections", "getBCProjWF", removal_version="1.0.0"
    )
    getBCProjWF_simple = deprecate_method(
        "get_BC_projections_simple", "getBCProjWF_simple", removal_version="1.0.0"
    )

DiffOperators.__module__ = "discretize.operators"


from typing import Callable, Any, Dict, Optional, Union

from pyspark.sql import DataFrame, Column

from spark_auto_mapper.data_types.array_base import AutoMapperArrayLikeBase
from spark_auto_mapper.data_types.data_type_base import AutoMapperDataTypeBase
from spark_auto_mapper.helpers.spark_higher_order_functions import filter
from spark_auto_mapper.type_definitions.wrapper_types import AutoMapperColumnOrColumnLikeType

class AutoMapperFilterDataType(AutoMapperArrayLikeBase):
    def __init__(
        self, column: Union[AutoMapperDataTypeBase,
                            AutoMapperColumnOrColumnLikeType],
        func: Callable[[Dict[str, Any]], Any]
    ) -> None:
        super().__init__()

        self.column: Union[AutoMapperDataTypeBase,
                           AutoMapperColumnOrColumnLikeType] = column
        self.func: Callable[[Dict[str, Any]], Any] = func

    def include_null_properties(self, include_null_properties: bool) -> None:
        self.column.include_null_properties(
            include_null_properties=include_null_properties
        )

    def get_column_spec(
        self, source_df: Optional[DataFrame], current_column: Optional[Column]
    ) -> Column:
        return filter(
            self.column.get_column_spec(
                source_df=source_df, current_column=current_column
            ), self.func
        )


"""Convert helper functions for rfsoc driver."""
from dataclasses import asdict
from functools import singledispatch

import numpy as np
import qibosoq.components.base as rfsoc
import qibosoq.components.pulses as rfsoc_pulses

from qibolab.platform import Qubit
from qibolab.pulses import Pulse, PulseSequence, PulseShape
from qibolab.sweeper import BIAS, DURATION, START, Parameter, Sweeper

HZ_TO_MHZ = 1e-6
NS_TO_US = 1e-3

def replace_pulse_shape(rfsoc_pulse: rfsoc_pulses.Pulse, shape: PulseShape) -> rfsoc_pulses.Pulse:
    """Set pulse shape parameters in rfsoc_pulses pulse object."""
    if shape.name not in {"Gaussian", "Drag", "Rectangular", "Exponential"}:
        new_pulse = rfsoc_pulses.Arbitrary(
            **asdict(rfsoc_pulse), i_values=shape.envelope_waveform_i, q_values=shape.envelope_waveform_q
        )
        return new_pulse
    new_pulse_cls = getattr(rfsoc_pulses, shape.name)
    if shape.name == "Rectangular":
        return new_pulse_cls(**asdict(rfsoc_pulse))
    if shape.name == "Gaussian":
        return new_pulse_cls(**asdict(rfsoc_pulse), rel_sigma=shape.rel_sigma)
    if shape.name == "Drag":
        return new_pulse_cls(**asdict(rfsoc_pulse), rel_sigma=shape.rel_sigma, beta=shape.beta)
    if shape.name == "Exponential":
        return new_pulse_cls(**asdict(rfsoc_pulse), tau=shape.tau, upsilon=shape.upsilon, weight=shape.g)

def pulse_lo_frequency(pulse: Pulse, qubits: dict[int, Qubit]) -> int:
    """Return local_oscillator frequency (HZ) of a pulse."""
    pulse_type = pulse.type.name.lower()
    try:
        lo_frequency = getattr(qubits[pulse.qubit], pulse_type).local_oscillator._frequency
    except AttributeError:
        lo_frequency = 0
    return lo_frequency

def convert_units_sweeper(sweeper: rfsoc.Sweeper, sequence: PulseSequence, qubits: dict[int, Qubit]):
    """Convert units for `qibosoq.abstract.Sweeper` considering also LOs."""
    for idx, jdx in enumerate(sweeper.indexes):
        parameter = sweeper.parameters[idx]
        if parameter is rfsoc.Parameter.FREQUENCY:
            pulse = sequence[jdx]
            lo_frequency = pulse_lo_frequency(pulse, qubits)
            sweeper.starts[idx] = (sweeper.starts[idx] - lo_frequency) * HZ_TO_MHZ
            sweeper.stops[idx] = (sweeper.stops[idx] - lo_frequency) * HZ_TO_MHZ
        elif parameter is rfsoc.Parameter.DELAY:
            sweeper.starts[idx] *= NS_TO_US
            sweeper.stops[idx] *= NS_TO_US
        elif parameter is rfsoc.Parameter.RELATIVE_PHASE:
            sweeper.starts[idx] = np.degrees(sweeper.starts[idx])
            sweeper.stops[idx] = np.degrees(sweeper.stops[idx])

@singledispatch
def convert(*args):
    """Convert from qibolab obj to qibosoq obj, overloaded."""
    raise ValueError(f"Convert function received bad parameters ({type(args[0])}).")

@convert.register
def _(qubit: Qubit) -> rfsoc.Qubit:
    """Convert `qibolab.platforms.abstract.Qubit` to `qibosoq.abstract.Qubit`."""
    if qubit.flux:
        return rfsoc.Qubit(qubit.flux.offset, qubit.flux.port.name)
    return rfsoc.Qubit(0.0, None)

@convert.register
def _(sequence: PulseSequence, qubits: dict[int, Qubit]) -> list[rfsoc_pulses.Pulse]:
    """Convert PulseSequence to list of rfosc pulses with relative time."""
    last_pulse_start = 0
    list_sequence = []
    for pulse in sorted(sequence.pulses, key=lambda item: item.start):
        start_delay = (pulse.start - last_pulse_start) * NS_TO_US
        pulse_dict = asdict(convert(pulse, qubits, start_delay))
        list_sequence.append(pulse_dict)

        last_pulse_start = pulse.start
    return list_sequence

@convert.register
def _(pulse: Pulse, qubits: dict[int, Qubit], start_delay: float) -> rfsoc_pulses.Pulse:
    """Convert `qibolab.pulses.pulse` to `qibosoq.abstract.Pulse`."""
    pulse_type = pulse.type.name.lower()
    dac = getattr(qubits[pulse.qubit], pulse_type).port.name
    adc = qubits[pulse.qubit].feedback.port.name if pulse_type == "readout" else None
    lo_frequency = pulse_lo_frequency(pulse, qubits)

    rfsoc_pulse = rfsoc_pulses.Pulse(
        frequency=(pulse.frequency - lo_frequency) * HZ_TO_MHZ,
        amplitude=pulse.amplitude,
        relative_phase=np.degrees(pulse.relative_phase),
        start_delay=start_delay,
        duration=pulse.duration * NS_TO_US,
        dac=dac,
        adc=adc,
        name=pulse.serial,
        type=pulse_type,
    )
    return replace_pulse_shape(rfsoc_pulse, pulse.shape)

@convert.register
def _(par: Parameter) -> rfsoc.Parameter:
    """Convert a qibolab sweeper.Parameter into a qibosoq.Parameter."""
    return getattr(rfsoc.Parameter, par.name.upper())

@convert.register
def _(sweeper: Sweeper, sequence: PulseSequence, qubits: dict[int, Qubit]) -> rfsoc.Sweeper:
    """Convert `qibolab.sweeper.Sweeper` to `qibosoq.abstract.Sweeper`.

    Note that any unit conversion is not done in this function (to avoid to do it multiple times).
    Conversion will be done in `convert_units_sweeper`.
    """
    parameters = []
    starts = []
    stops = []
    indexes = []

    if sweeper.parameter is BIAS:
        for qubit in sweeper.qubits:
            parameters.append(rfsoc.Parameter.BIAS)
            indexes.append(list(qubits.values()).index(qubit))

            base_value = qubit.flux.offset
            values = sweeper.get_values(base_value)
            starts.append(values[0])
            stops.append(values[-1])

        if max(np.abs(starts)) > 1 or max(np.abs(stops)) > 1:
            raise ValueError("Sweeper amplitude is set to reach values higher than 1")
    else:
        for pulse in sweeper.pulses:
            idx_sweep = sequence.index(pulse)
            indexes.append(idx_sweep)
            base_value = getattr(pulse, sweeper.parameter.name)
            if idx_sweep != 0 and sweeper.parameter is START:
                # do the conversion from start to delay
                base_value = base_value - sequence[idx_sweep - 1].start
            values = sweeper.get_values(base_value)
            starts.append(values[0])
            stops.append(values[-1])

            if sweeper.parameter is START:
                parameters.append(rfsoc.Parameter.DELAY)
            elif sweeper.parameter is DURATION:
                parameters.append(rfsoc.Parameter.DURATION)
                delta_start = values[0] - base_value
                delta_stop = values[-1] - base_value

                if len(sequence) > idx_sweep + 1:
                    # if duration-swept pulse is not last
                    indexes.append(idx_sweep + 1)
                    t_start = sequence[idx_sweep + 1].start - sequence[idx_sweep].start
                    parameters.append(rfsoc.Parameter.DELAY)
                    starts.append(t_start + delta_start)
                    stops.append(t_start + delta_stop)
            else:
                parameters.append(convert(sweeper.parameter))

    return rfsoc.Sweeper(
        parameters=parameters,
        indexes=indexes,
        starts=starts,
        stops=stops,
        expts=len(sweeper.values),
    )


import os
from tqdm import tqdm
import numpy as np
import re

not_chinese = '[^\u4e00-\u9fff]'
stop_words = {
    '有', '所', '啊', '都', '來', '啦',
    '也', '好', '到', '的', '樣', '會',
    '在', '什', '就', '那', '\u3000',
    '個', '是', '以', '要', '怎', '麼',
    '子', '可', '人', '嗎', '這', '了'
}

""" max len
3 sentences: 270
1 sentence:
"""

class Dataset:

    def load(fname):
        import pickle
        with open(fname, 'rb') as f:
            return pickle.load(f)

    def __init__(self,
                 root='./dataset/training_data/subtitle_no_TC',
                 use_stop_words=False):
        self.use_stop_words = use_stop_words
        self.flist = []
        for path, dirs, files in os.walk(root):
            for f in filter(lambda s: s.endswith('.txt'), files):
                self.flist += [path + '/' + f]
        self.data = []
        self.max_seq_len = 0
        self.top_seq_lens = []
        chars = set()
        for fname in tqdm(self.flist, desc='Parsing words'):
            with open(fname, 'rt') as f:
                l1 = lambda s: (s != '')
                l2 = lambda s: self.filt(s)
                lines = list(filter(l1, map(l2, f.readlines())))
                m = max(len(line) for line in lines)
                if m > self.max_seq_len:
                    self.max_seq_len = m
                    self.top_seq_lens += [m]
                chars |= set(''.join(lines))
                self.data += lines
        self.data = np.array(self.data)
        self.charset = chars
        self.chars = list(chars)
        self.vsize = len(self.chars)
        self.dict = dict((c, i) for i, c in enumerate(self.chars))

    # def dialog_and_next(self, dialog_len, padding=False):
    #     n_dialog = len(self.data)
    #     while True:
    #         ss = self.data[np.random.randint(n_dialog)]
    #         ss = [self.encode_sentence(s) for s in ss]
    #         for i in range(len(ss) - dialog_len):
    #             dialog = ss[i:i+dialog_len]
    #             dialog = [c for s in dialog for c in s]
    #             s_next = ss[i+dialog_len]
    #             if padding:
    #                 dialog, l1 = self.pad_sequence(dialog, padding, return_len=True)
    #                 s_next, l2 = self.pad_sequence(s_next, padding, return_len=True)
    #                 yield (dialog, s_next), [l1, l2]
    #             else:
    #                 yield dialog, s_next

    # def batch_dialog_and_next(self, batch_size, dialog_len):
    #     g = self.dialog_and_next(dialog_len, padding=False)
    #     D, S, D_lens, S_lens, max_len = [], [], [], [], 0
    #     for i, (dialog, s_next) in enumerate(g):
    #         D += [dialog]
    #         S += [s_next]
    #         D_lens += [len(dialog)]
    #         S_lens += [len(s_next)]
    #         k = max(len(dialog), len(s_next))
    #         max_len = k if k > max_len else max_len
    #         if i % batch_size == batch_size - 1:
    #             D = [self.pad_sequence(s, max_len) for s in D]
    #             S = [self.pad_sequence(s, max_len) for s in S]
    #             yield (D, D_lens), (S, S_lens), max_len
    #             D, S, D_lens, S_lens, max_len = [], [], [], [], 0

    def random_batch_generator(self, batch_size, dialog_len):
        data_size = self.data.shape[0]
        while True:
            indexes = np.random.randint(data_size - dialog_len - 1, size=batch_size)
            dialogs, s_nexts, dialog_lens, s_next_lens, max_len = [], [], [], [], 0
            for i in indexes:
                *dialog, s_next = self.data[np.arange(i, i+dialog_len+1)]
                dialog = self.encode_many(dialog)
                s_next = self.encode(s_next)
                k = max(len(dialog), len(s_next))
                max_len = k if k > max_len else max_len
                dialogs.append(dialog)
                s_nexts.append(s_next)
                dialog_lens.append(len(dialog))
                s_next_lens.append(len(s_next))
            dialogs = [self.pad_sequence(dialog, max_len) for dialog in dialogs]
            s_nexts = [self.pad_sequence(s_next, max_len) for s_next in s_nexts]
            yield (dialogs, dialog_lens), (s_nexts, s_next_lens)

    def random_batch_generator_neg(self, batch_size, dialog_len):
        data_size = self.data.shape[0]
        while True:
            indexes = np.random.randint(data_size - dialog_len, size=batch_size)
            neg_idx = np.random.randint(data_size, size=batch_size)
            dialogs, s_nexts, dialog_lens, s_next_lens, max_len = [], [], [], [], 0
            for i, j in zip(indexes, neg_idx):
                dialog = self.encode_many(self.data[np.arange(i, i+dialog_len+1)])
                s_next = self.encode(self.data[j])
                k = max(len(dialog), len(s_next))
                max_len = k if k > max_len else max_len
                dialogs.append(dialog)
                s_nexts.append(s_next)
                dialog_lens.append(len(dialog))
                s_next_lens.append(len(s_next))
            dialogs = [self.pad_sequence(dialog, max_len) for dialog in dialogs]
            s_nexts = [self.pad_sequence(s_next, max_len) for s_next in s_nexts]
            yield (dialogs, dialog_lens), (s_nexts, s_next_lens)

    def pad_sequence(self, s, max_len, pad=1, return_len=False):
        l = len(s)
        if max_len < l:
            print('Error: padding to %d but seq_len is %d' % (max_len, l))
        if return_len:
            return s + [pad] * (max_len - l), l
        else:
            return s + [pad] * (max_len - l)

    def encode_many(self, ss):
        return [c for s in ss for c in self.encode(s)]

    def encode(self, s):
        valid = self.filt(s, only_seen=True)
        indeces = map(lambda c: self.dict[c], valid)
        return list(indeces)

    def decode(self, m):
        m = filter(lambda n: n >= 0 and n < self.vsize, m)
        return ''.join([self.chars[n] for n in m])

    def save(self, fname):
        import pickle
        with open(fname, 'wb') as f:
            pickle.dump(self, f)

    def filt(self, s, only_seen=False):
        if only_seen:
            s = filter(lambda c: c in self.charset, s)
        else:
            s = re.sub(not_chinese, '', s)
        if self.use_stop_words:
            s = ''.join(filter(lambda c: c not in stop_words, s))
        else:
            s = ''.join(s)
        return s

def load(path):
    try:
        d = Dataset.load(path)
        print('Loaded from ' + path)
    except:
        d = Dataset()
        d.save(path)
    return d


# 手写数字识别
import torch
from torch import nn, optim
from torch.utils.data import DataLoader
from torchvision import datasets, transforms
import matplotlib.pyplot as plt
from net import LeNet

batch_size = 128
learning_rate = 1e-3
num_epoches = 5

# 通过transforms.Compose对PIL Image进行变换
# transforms:由多个transforms构成的列表
# ToTensor():把一个取值范围是[0,255]的PIL.Image或者shape为(H,W,C)的numpy.ndarray，转换成形状为[C,H,W]，取值范围是[0,1.0]的torch.FloadTensor
# Normalize():正则化，这里均值为0.5，方差为0.5，将数据归一化到-1-1之间
data_tf = transforms.Compose(
    [transforms.ToTensor(),
     transforms.Normalize([0.5], [0.5])]
)

# 所有数据集都是torch.utils.data.Dataset的子类
train_dataset = datasets.MNIST(root='../data', train=True, transform=data_tf, download=True)
test_dataset = datasets.MNIST(root='../data', train=False, transform=data_tf, download=True)

# torch.utils.data.DataLoader()
# 数据加载器，集合了数据集和取样器，并且可以提供多个线程处理数据集
# 在训练模型时使用到此函数，用来把训练数据分为多个小组，此函数每次抛出一组数据，直至把所有的数据都抛出
# num_workers表示使用多少个子进程来用于数据加载，0表示数据默认被加载入子进程，默认为0
# shuffle表示是否在每个epoch前打乱数据
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)

# 模型
model = LeNet()
if torch.cuda.is_available():
    model = model.cuda()

# 定义损失函数和优化方法
loss_fn = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr=learning_rate)

# 训练
for epoch in range(num_epoches):
    model.train()
    for data in train_loader:  # 每次读取一个batch_size张图片
        img, label = data
        if torch.cuda.is_available():
            img = img.cuda()
            label = label.cuda()
        # 前向传播求出预测的值
        output = model(img)
        # 求损失
        loss = loss_fn(output, label)
        # 梯度清零，每个batch至少有一张图片，在每个batch进行计算的时候都需要将梯度清零
        optimizer.zero_grad()
        # 反向传播求梯度
        loss.backward()
        # 更新所有参数
        optimizer.step()
    # print("epoch:", epoch, " | loss:", loss.item())

    model = model.eval()
    eval_loss = 0
    eval_acc = 0
    for data in test_loader:
        img, label = data
        if torch.cuda.is_available():
            img = img.cuda()
            label = label.cuda()
        output = model(img)
        loss = loss_fn(output, label)
        eval_loss += loss.item()
        _, pred = torch.max(output, 1)
        num_correct = (pred == label).sum()
        eval_acc += num_correct.item()
    print('Epoch:{}, Test loss:{:.6f}, Acc:{:.6f}'.format(epoch, eval_loss / (len(test_dataset)),
                                                              eval_acc / (len(test_dataset))))


from datetime import datetime
from NotaFiscal_Cliente import Cliente
from NotaFiscal_Item import ItemNotaFiscal

class NotaFiscal():
    def __init__(self, Id, codigo, cliente):
        self._Id = Id
        self._codigo = codigo
        self._cliente = cliente
        self._data = datetime.today()
        self._itens = []
        self._valorNota = 0.0

    def setCliente(self, cliente):
        if isinstance(cliente, Cliente):
            self._cliente = cliente
            return self._cliente

    def adicionarItem(self, item):
        if isinstance(item, ItemNotaFiscal):
            self._itens.append(item)

    def calcularNotaFiscal(self):
        valor = 0.0
        for item in self._itens:
            valor +=  item._valorItem
        self.valorNota = valor

    def imprimirNotaFiscal(self):
        print(f'''---------------------------------------------------------------------------------------------------
NOTA FISCAL \t\t\t\t {self._data}
Cliente: {self._cliente._codigo}\t\t\t\t Nome: {self._cliente._nome}
CPF/CNPJ: {self._cliente._cnpjcpf}
---------------------------------------------------------------------------------------------------
ITENS
---------------------------------------------------------------------------------------------------
Seq                        Descrição                           QTD   Valor Unit         Preço
---- -------------------------------------------------------- ----- ------------ ------------------''')
        for c in range(0, 3):
            print('{0:<17}{1:^34}{2:^28}{3:<8}{4}'.format(self._itens[c].getSequencial(),  self._itens[c].getDescricao(), self._itens[c].getQuantidade(), self._itens[c].getValorUnitario(), self._itens[c].getvalorItem()))

        print('Valor Total:', self.valorNota)


from Exceptions import *
from NewIntervalFunctions import *

class interval(object):
    '''
    This class handle the range of integers between a lower bound and an upper bound

    '''
    def __init__(self,interval_string):
        '''
        Construct an interval object given a string representing an integer interval.
        If interval_string is not a valid interval, raise exception.
        '''
        if isValidInterval(interval_string):
            self.interval_string = interval_string
            self.left, self.right, self.lower, self.upper = splitIntervalString(interval_string)
            self.lower_inclusive, self.upper_inclusive = getTrueValue(self.left, self.right, self.lower, self.upper)
            self.left_inclusive = True if self.left == '[' else False
            self.right_inclusive = True if self.right == ']' else False
        else:
            raise invalidInterval('The interval is invalid.')

    def __repr__(self):
        return self.interval_string

def mergeIntervals(int1,int2):
    '''
    merge two intervals if they overlap, or an exception will be thrown
    '''
    if int1.lower_inclusive > int2.upper_inclusive +1 or int1.upper_inclusive < int2.lower_inclusive -1:
        raise notOverlappingException('Intervals do not overlap.')
    else:
        if int1.lower_inclusive < int2.lower_inclusive:
            merge_left = int1.left
            merge_lower = int1.lower
        elif int1.lower_inclusive == int2.lower_inclusive:
            merge_left = int1.left if int1.lower==int2.lower else '('
            merge_lower=min(int1.lower, int2.lower)
        else:
            merge_left = int2.left
            merge_lower = int2.lower
        if int1.upper_inclusive < int2.upper_inclusive:
            merge_right = int2.right
            merge_upper = int2.upper
        elif int1.upper_inclusive == int2.upper_inclusive:
            merge_right = int1.right if int1.upper==int2.upper else ')'
            merge_upper = max(int1.upper, int2.upper)
        else:
            merge_right = int1.right
            merge_upper = int1.upper
        merge_string = ''
        merge_string = merge_left + str(merge_lower) + ',' + str(merge_upper) + merge_right
        new_interval = interval(merge_string)
        return new_interval

def  mergeOverlapping(intlist):
    '''
    merge all overlapping intervals
    '''
    sorted_list = sorted(intlist, key=lambda x: x.lower)
    current_interval=sorted_list[0]
    current_interval_list=[]
    for next_interval in sorted_list[1:]:
        try:
            current_interval = mergeIntervals(current_interval, next_interval)
        except:
            current_interval_list.append(current_interval)
            current_interval = next_interval
    current_interval_list.append(current_interval)
    return current_interval_list

def insert(intlist, newint):
    '''
    insert a new interval into an interval list and merge them if possible
    '''
    intlist.append(newint)
    return mergeOverlapping(intlist)


from flask import Flask, send_from_directory, request
app = Flask(__name__)

@app.route('/reciepts', methods=['POST'])
def send_reciept():

    return """<pre>
        The length of the htmlContent that should be rendered as a pdf is {}.

        The text content is
        ---------------
        {}
        ---------------

        and we're sending it to {} with a subject of "{}".

        The sender will look like {}.
        </pre>
           """.format(len(request.form['htmlContent']),
           request.form['textContent'], request.form['emailTo'], request.form['subject'],
           request.form['emailFrom'])

# Provide some example requests
@app.route('/examples/<path:path>')
def get_example (path):
    return send_from_directory('examples', path)

if __name__ == '__main__':
    app.debug = True
    app.run()


from django.core.mail import send_mail
from django.template import RequestContext
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import render_to_response
from datetime import date

def index(request):

	context = RequestContext(request)
	years_working = date.today().year - 2007
	context_dict = {'activepage': 'index', 'years_working': years_working, 'page_title': 'Matt Healy - Web Developer - Perth, Western Australia'}

	return render_to_response('healysite/index.html', context_dict, context)

def contact(request):

	context = RequestContext(request)
	context_dict = {'activepage': 'contact', 'page_title': 'Contact - Matt Healy - Web Developer - Perth, Western Australia'}

	if request.method == 'POST':
		if (request.POST.get('email') and request.POST.get('subject') and request.POST.get('message')):
			send_mail(
				request.POST['subject'],
				request.POST['message'],
				request.POST.get('email', 'noreply@healy.net.au'),
				['healmatt@gmail.com'],
            		)
			return HttpResponseRedirect('/healysite/thanks/')

	return render_to_response('healysite/contact.html', context_dict, context)

def thanks(request):

	context = RequestContext(request)
	context_dict = {'activepage': 'contact', 'page_title': 'Contact - Matt Healy - Web Developer - Perth, Western Australia'}

	return render_to_response('healysite/thanks.html', context_dict, context)