Boundary Conditions

One of the key advantages of using this solver is that boundary conditions can be defined using a simple dictionary on either side of the domain.

The sparse Jacobian is automatically generated according to the boundary condition specified.

apply_BC(d, v, bc={'left': 'periodic', 'right': 'periodic'}, xmin=0.0, xmax=1.0)

Apply boundary conditions to the given domain.

Parameters:
  • d (Domain) –

    The domain to apply the boundary conditions to.

  • v (str) –

    The name of the variable to apply the boundary conditions to.

  • bc (str, default: {'left': 'periodic', 'right': 'periodic'} ) –

    The type of boundary condition to apply. Acceptable values are "periodic" and "outflow". Default is "periodic".

  • xmin (float, default: 0.0 ) –

    The minimum x-value of the domain. Default is 0.0.

  • xmax (float, default: 1.0 ) –

    The maximum x-value of the domain. Default is 1.0.

Raises:
  • NotImplementedError

    If an unsupported boundary condition is specified.

Source code in splitfxm/bc.py
def apply_BC(d: Domain, v: str, bc: dict = {"left": "periodic", "right": "periodic"}, xmin=0.0, xmax=1.0):
    """
    Apply boundary conditions to the given domain.

    Parameters
    ----------
    d : Domain
        The domain to apply the boundary conditions to.
    v : str
        The name of the variable to apply the boundary conditions to.
    bc : str, optional
        The type of boundary condition to apply. Acceptable values are "periodic" and "outflow". Default is "periodic".
    xmin : float, optional
        The minimum x-value of the domain. Default is 0.0.
    xmax : float, optional
        The maximum x-value of the domain. Default is 1.0.

    Raises
    ------
    NotImplementedError
        If an unsupported boundary condition is specified.
    """

    # Get cells
    cells = d.cells()

    # Find index of component
    idx = d.component_index(v)

    # Common values used in all types
    ilo = d.ilo()
    ihi = d.ihi()

    lb, rb = d.boundaries()

    # Check only required directions specified
    bc_keys = sorted(list(bc.keys()))
    if bc_keys != [btype_map[btype.LEFT], btype_map[btype.RIGHT]]:
        raise SFXM("Incorrect boundary directions specified")

    # Iterate over left and right BCs
    for dir in bc_keys:
        bc_type = bc[dir]

        if bc_type == "periodic":
            if dir == btype_map[btype.LEFT]:
                # left boundary
                # Ghost cells are (right to left) the rightmost elements (same order)
                for i, b in enumerate(lb):
                    shift = xmax - cells[ihi - (i + 1)].x()
                    b.set_x(xmin - shift)
                    b.set_value(idx, cells[ihi - i].value(idx))

            elif dir == btype_map[btype.RIGHT]:
                # right boundary
                # Ghost cells are (left to right) the leftmost elements (same order)
                for i, b in enumerate(rb):
                    shift = cells[(i + 1) + ilo].x() - xmin
                    b.set_x(xmax + shift)
                    b.set_value(idx, cells[i + ilo].value(idx))

        elif bc_type == "outflow":
            if dir == btype_map[btype.LEFT]:
                # left boundary
                for i, b in enumerate(lb):
                    # The shift mirrors the interior on the same side
                    shift = cells[(i + 1) + ilo].x() - xmin
                    b.set_x(xmin - shift)
                    # Value same as extrapolated from interior
                    dy = cells[ilo + 1].value(idx) - cells[ilo].value(idx)
                    dx = cells[ilo + 1].x() - cells[ilo].x()
                    delta_x = cells[ilo - i].x() - cells[ilo - (i + 1)].x()

                    b.set_value(
                        idx, cells[ilo - i].value(idx) - (dy/dx) * delta_x)

            elif dir == btype_map[btype.RIGHT]:
                # right boundary
                for i, b in enumerate(rb):
                    # The shift mirrors the interior on the same side
                    shift = xmax - cells[ihi - (i + 1)].x()
                    b.set_x(xmax + shift)
                    # Value same as extrapolated from interior
                    dy = cells[ihi].value(idx) - cells[ihi - 1].value(idx)
                    dx = cells[ihi].x() - cells[ihi - 1].x()
                    delta_x = cells[ihi + (i + 1)].x() - cells[ihi + i].x()

                    b.set_value(
                        idx, cells[ihi + i].value(idx) + (dy/dx) * delta_x)

        # Dictionary-based boundary conditions
        # Dirichlet and Neumann BCs require additional values also
        elif isinstance(bc_type, dict) and len(bc_type.keys()) == 1:
            bc_data = list(bc_type.values())[0]
            bc_type = list(bc_type.keys())[0]

            # Check if BC data is valid
            if not isinstance(bc_data, (float, int)):
                raise SFXM(
                    "Incorrect data specified for dictionary-type boundaries")

            if bc_type == "neumann":
                if dir == btype_map[btype.LEFT]:
                    # left boundary
                    neumann_value = bc_data
                    for i, b in enumerate(lb):
                        # The shift mirrors the interior on the same side
                        shift = cells[(i + 1) + ilo].x() - xmin
                        b.set_x(xmin - shift)

                        # Cell width to the left
                        dx = cells[ilo - i].x() - cells[ilo - (i + 1)].x()
                        b.set_value(
                            idx, cells[ilo - i].value(idx) - neumann_value * dx)

                elif dir == btype_map[btype.RIGHT]:
                    # right boundary
                    neumann_value = bc_data
                    for i, b in enumerate(rb):
                        # The shift mirrors the interior on the same side
                        shift = xmax - cells[ihi - (i + 1)].x()
                        b.set_x(xmax + shift)

                        # Cell width to the right
                        dx = cells[ihi + (i + 1)].x() - cells[ihi + i].x()
                        b.set_value(
                            idx, cells[ihi + i].value(idx) + neumann_value * dx)

            elif bc_type == "dirichlet":
                if dir == btype_map[btype.LEFT]:
                    # left boundary
                    dirichlet_value = bc_data
                    for i, b in enumerate(lb):
                        # The shift mirrors the interior on the same side
                        shift = cells[(i + 1) + ilo].x() - xmin
                        b.set_x(xmin - shift)

                        b.set_value(idx, dirichlet_value)

                elif dir == btype_map[btype.RIGHT]:
                    # right boundary
                    dirichlet_value = bc_data
                    for i, b in enumerate(rb):
                        # The shift mirrors the interior on the same side
                        shift = xmax - cells[ihi - (i + 1)].x()
                        b.set_x(xmax + shift)

                        b.set_value(idx, dirichlet_value)

            else:
                raise SFXM(
                    "Incorrect data specified for dictionary-type boundaries")
        else:
            raise SFXM("Boundary type not implemented")

get_periodic_bcs(bc_dict, d)

Extracts periodic boundary conditions from a boundary condition dictionary.

Parameters:
  • bc_dict (dict) –

    A dictionary where keys are component names and values are dictionaries of boundary conditions.

  • d (Domain) –

    An instance of the Domain class used to get the component index.

Returns:
  • dict

    A dictionary where keys are component indices and values are lists of boundary sides ('left', 'right') that have periodic boundary conditions. Only components with periodic conditions are included in the output dictionary.

Source code in splitfxm/bc.py
def get_periodic_bcs(bc_dict: dict, d: Domain):
    """
    Extracts periodic boundary conditions from a boundary condition dictionary.

    Parameters
    ----------
    bc_dict : dict
        A dictionary where keys are component names and values are dictionaries of boundary conditions. 
    d : Domain
        An instance of the Domain class used to get the component index.

    Returns
    -------
    dict
        A dictionary where keys are component indices and values are lists of boundary sides 
        ('left', 'right') that have periodic boundary conditions. Only components with periodic 
        conditions are included in the output dictionary.
    """
    periodic_bcs = {}

    for component, bcs in bc_dict.items():
        idx = d.component_index(component)
        sides = []
        for side, bc_type in bcs.items():
            if bc_type == "periodic":
                sides.append(side)

        if sides:
            periodic_bcs[idx] = sides

    return periodic_bcs

extend_band(band, dirs, i, d)

Extends a list of band indices to include points affected by periodic boundary conditions. It handles overflow points for periodic boundaries by adding indices from the left or right domain as needed.

Parameters:
  • band (list) –

    A list of current band indices to be extended.

  • dirs (list) –

    A list of boundary directions ('left', 'right') that determine how to extend the band based on periodic boundary conditions.

  • i (int) –

    The current point index within the domain where the band is being extended.

  • d (Domain) –

    An instance of the Domain class used to obtain boundary parameters such as nb, ilo, and ihi

Returns:
  • list

    A new list containing the union of the original band and additional indices affected by periodic boundary conditions.

Source code in splitfxm/bc.py
def extend_band(band, dirs: list, i: int, d: Domain):
    """
    Extends a list of band indices to include points affected by periodic boundary conditions.
    It handles overflow points for periodic boundaries by adding indices from the left or right domain as needed.

    Parameters
    ----------
    band : list
        A list of current band indices to be extended.
    dirs : list
        A list of boundary directions ('left', 'right') that determine how to extend the band 
        based on periodic boundary conditions.
    i : int
        The current point index within the domain where the band is being extended.
    d : Domain
        An instance of the Domain class used to obtain boundary parameters such as `nb`, `ilo`, and `ihi`

    Returns
    -------
    list
        A new list containing the union of the original band and additional indices 
        affected by periodic boundary conditions.
    """
    nb_left = d.nb(btype.LEFT)
    nb_right = d.nb(btype.RIGHT)

    ilo = d.ilo()
    ihi = d.ihi()

    # Iterate over left and right BCs
    addl_points = []
    for dir in dirs:
        # If right is periodic, check how much overflow there is
        # and add the left domain points to the band
        if dir == btype_map[btype.RIGHT]:
            overflow = max(0, nb_right - (ihi - i))
            addl_points.extend(list(range(ilo, ilo + overflow)))
        # If left is periodic, check how much overflow there is
        # and add the right domain points to the band
        elif dir == btype_map[btype.LEFT]:
            overflow = max(0, nb_left - (i - ilo))
            addl_points.extend(list(range(ihi - overflow + 1, ihi + 1)))
        else:
            raise SFXM("Incorrect boundary direction encountered")

    return list(set(band).union(set(addl_points)))