import logging
import math
import sys
from dataclasses import dataclass
from enum import Enum, auto
from itertools import permutations
from typing import TYPE_CHECKING, Any, Iterable, Self, Sequence, get_args

import pytest

import faebryk.core.faebrykpy as fbrk
import faebryk.core.graph as graph
import faebryk.core.node as fabll
import faebryk.library._F as F
from faebryk.library import Collections, Literals
from faebryk.libs.util import (
    crosswise,
    groupby,
    indented_container,
    not_none,
    once,
    zip_dicts_by_key,
)

logger = logging.getLogger(__name__)

# TODO complete signatures
# TODO consider moving to zig
# TODO handle constrained attribute

# TODO strategy with traits:
# just make everything an instance trait,
# and later when we performance optimize reconsider

if TYPE_CHECKING:
    import faebryk.library._F as F


def _retrieve_operands(node: fabll.NodeT, identifier: str | None) -> list[fabll.NodeT]:
    class Ctx:
        operands: list[fabll.NodeT] = []
        _identifier = identifier

    def visit(ctx: type[Ctx], edge: graph.BoundEdge):
        if ctx._identifier is not None and edge.edge().name() != ctx._identifier:
            return
        ctx.operands.append(
            fabll.Node.bind_instance(edge.g().bind(node=edge.edge().target()))
        )

    fbrk.EdgeOperand.visit_operand_edges(bound_node=node.instance, ctx=Ctx, f=visit)
    return Ctx.operands


def _retrieve_single(node: fabll.NodeT) -> fabll.NodeT:
    operands = _retrieve_operands(node, None)
    if len(operands) != 1:
        raise ValueError(f"Expected 1 operand, got {len(operands)} on {node}")
    return operands[0]


OperandPointer = Collections.AbstractPointer(
    edge_factory=lambda identifier: fbrk.EdgeOperand.build(
        operand_identifier=identifier
    ),
    retrieval_function=_retrieve_single,
    typename="OperandPointer",
)

OperandSequence = Collections.AbstractSequence(
    edge_factory=lambda identifier, index: fbrk.EdgeOperand.build(
        operand_identifier=identifier
    ),
    retrieval_function=_retrieve_operands,
    typename="OperandSequence",
)

OperandSet = Collections.AbstractSet(
    edge_factory=lambda identifier, index: fbrk.EdgeOperand.build(
        operand_identifier=identifier
    ),
    retrieval_function=_retrieve_operands,
    typename="OperandSet",
)


def get_operand_path(owner_ref: fabll.RefPath) -> fabll.RefPath:
    # TODO: relying on a string identifier to connect to the correct
    # trait is nasty
    return [*owner_ref, "can_be_operand"]


class is_expression_type(fabll.Node):
    is_trait = fabll.Traits.MakeEdge(fabll.ImplementsTrait.MakeChild().put_on_type())
    repr_placement = F.Collections.Pointer.MakeChild()
    repr_symbol = F.Collections.Pointer.MakeChild()

    @dataclass
    class ReprStyle(fabll.NodeAttributes):
        symbol: str | None = None

        class Placement(Enum):
            INFIX = auto()
            """
            A + B + C
            """
            INFIX_FIRST = auto()
            """
            A > (B, C)
            """
            PREFIX = auto()
            """
            ¬A
            """
            POSTFIX = auto()
            """
            A!
            """
            EMBRACE = auto()
            """
            |A|
            """

        placement: Placement = Placement.INFIX

    _repr_enum = F.Literals.EnumsFactory(ReprStyle.Placement)

    @classmethod
    def MakeChild(cls, repr_style: ReprStyle) -> fabll._ChildField[Self]:
        out = fabll._ChildField(cls)
        cls._MakeReprStyle(out, repr_style)
        return out

    @classmethod
    def _MakeReprStyle(cls, out: fabll._ChildField[Self], repr_style: ReprStyle):
        Collections.Pointer.MakeEdgeForField(
            out,
            [out, cls.repr_placement],
            cls._repr_enum.MakeChild(repr_style.placement),
        )
        Collections.Pointer.MakeEdgeForField(
            out,
            [out, cls.repr_symbol],
            Literals.Strings.MakeChild(repr_style.symbol or "<NONE>"),
        )

    def get_repr_style(self) -> ReprStyle:
        placement = not_none(
            self.repr_placement.get()
            .deref()
            .cast(type(self)._repr_enum)
            .get_single_value_typed(is_expression_type.ReprStyle.Placement)
        )
        symbol = not_none(
            self.repr_symbol.get().deref().cast(F.Literals.Strings)
        ).get_values()[0]
        if symbol == "<NONE>":
            symbol = None
        return is_expression_type.ReprStyle(placement=placement, symbol=symbol)


class is_expression(fabll.Node):
    is_trait = fabll.Traits.MakeEdge(fabll.ImplementsTrait.MakeChild().put_on_type())

    as_parameter_operatable = fabll.Traits.ImpliedTrait(
        F.Parameters.is_parameter_operatable
    )
    as_operand = fabll.Traits.ImpliedTrait(F.Parameters.can_be_operand)

    as_canonical: fabll.Traits.OptionalImpliedTrait["is_canonical"] = (
        fabll.Traits.OptionalImpliedTrait(lambda: is_canonical)
    )
    as_assertable: fabll.Traits.OptionalImpliedTrait["is_assertable"] = (
        fabll.Traits.OptionalImpliedTrait(lambda: is_assertable)
    )

    def get_repr_style(self) -> is_expression_type.ReprStyle:
        type_node = not_none(fabll.Traits(self).get_obj_raw().get_type_node())
        is_expr_type = fabll.TypeNodeBoundTG.try_get_trait_of_type(
            is_expression_type, type_node
        )
        if is_expr_type is None:
            raise ValueError(f"No is_expression_type trait for {self}")
        return is_expr_type.get_repr_style()

    @once
    def get_operands(self) -> list["F.Parameters.can_be_operand"]:
        from faebryk.library.Collections import PointerProtocol

        node = fabll.Traits(self).get_obj_raw()
        operands: list[F.Parameters.can_be_operand] = []
        pointers: list[PointerProtocol] = node.get_children(
            direct_only=True,
            types=(OperandPointer, OperandSequence, OperandSet),  # type: ignore
        )
        assert pointers, f"No operand pointers for {node}"

        for pointer in pointers:
            li = pointer.as_list()
            li_op = [c.cast(F.Parameters.can_be_operand, check=False) for c in li]
            operands.extend(li_op)

        return operands

    def get_operand_operatables(self) -> set["F.Parameters.is_parameter_operatable"]:
        return self.get_operands_with_trait(F.Parameters.is_parameter_operatable)

    def get_operands_with_trait[T: fabll.NodeT](
        self, trait: type[T], recursive: bool = False
    ) -> set[T]:
        return {
            t for op in self.get_operands() if (t := op.try_get_sibling_trait(trait))
        } | (
            {
                inner
                for t_e in self.get_operands_with_trait(is_expression)
                for inner in t_e.get_operands_with_trait(trait, recursive=recursive)
            }
            if recursive
            else set()
        )

    def get_operand_literals(self) -> dict[int, "F.Literals.is_literal"]:
        return {
            i: t
            for i, op in enumerate(self.get_operands())
            if (t := op.as_literal.try_get())
        }

    def get_operand_leaves(
        self,
    ) -> set["F.Parameters.can_be_operand"]:
        """
        Recursively get all leaf operatables (parameters that are not expressions).
        For expressions, descends into their operands until reaching parameters.

        Example:
        ```
        (A + B) * C + 10 -> {A, B, C, 10}
        ```
        """
        # TODO merge with get_operand_leaves_operatable
        result: set[F.Parameters.can_be_operand] = set()
        for operand in self.get_operands():
            if (po := operand.as_parameter_operatable.try_get()) and (
                expr := po.as_expression.try_get()
            ):
                # Operand is an expression - recurse into it
                result.update(expr.get_operand_leaves())
            elif operand.as_parameter_operatable.try_get():
                # Operand is a parameter leaf
                result.add(operand)
            elif operand.as_literal.try_get():
                # Operand is a literal leaf
                result.add(operand)
        return result

    def get_operand_leaves_operatable(
        self,
    ) -> set["F.Parameters.is_parameter_operatable"]:
        """
        Recursively get all leaf operatables (parameters that are not expressions).
        For expressions, descends into their operands until reaching parameters.

        Example:
        ```
        (A + B) * C -> {A, B, C}
        ```
        """
        result: set["F.Parameters.is_parameter_operatable"] = set()
        for operand in self.get_operands():
            if (po := operand.as_parameter_operatable.try_get()) and (
                expr := po.as_expression.try_get()
            ):
                # Operand is an expression - recurse into it
                result.update(expr.get_operand_leaves_operatable())
            elif operand_po := operand.as_parameter_operatable.try_get():
                # Operand is a leaf (parameter or literal with is_parameter_operatable)
                result.add(operand_po)
        return result

    @staticmethod
    def _compact_repr(
        style: is_expression_type.ReprStyle,
        symbol: str,
        is_predicate: bool,
        is_terminated: bool,
        relevant: bool | None,
        lit_suffix: str,
        use_full_name: bool,
        class_suffix: str,
        expr_name: str,
        operands: Sequence["F.Parameters.can_be_operand"],
        no_lit_suffix: bool,
        no_class_suffix: bool,
    ):
        """Return compact math repr with symbols (+, *, ≥, ¬, ∧) and precedence."""
        symbol_suffix = ""
        if is_predicate:
            symbol_suffix += "!"

        if is_terminated:
            symbol_suffix += "$"

        if relevant is not None:
            symbol_suffix += "★" if relevant else "⊘"

        if not no_lit_suffix:
            symbol_suffix += lit_suffix

        if not no_class_suffix:
            symbol_suffix += class_suffix

        symbol += symbol_suffix

        def format_operand(op: "F.Parameters.can_be_operand"):
            if lit := op.as_literal.try_get():
                return lit.pretty_str()
            if po := op.as_parameter_operatable.try_get():
                if op_expr := po.as_expression.try_get():
                    op_out = po.compact_repr(
                        use_full_name=use_full_name, no_lit_suffix=no_lit_suffix
                    )
                    if len(op_expr.get_operands()) > 1:
                        op_out = f"({op_out})"
                    return op_out
                elif op_p := po.as_parameter.try_get():
                    return op_p.compact_repr(
                        use_full_name=use_full_name,
                        no_lit_suffix=no_lit_suffix,
                        with_detail=False,
                    )
            return str(op)

        formatted_operands = [format_operand(op) for op in operands]
        out = ""
        if style.placement == is_expression_type.ReprStyle.Placement.PREFIX:
            if len(formatted_operands) == 1:
                out = f"{symbol}{formatted_operands[0]}"
            else:
                out = f"{symbol}({', '.join(formatted_operands)})"
        elif style.placement == is_expression_type.ReprStyle.Placement.EMBRACE:
            out = f"{symbol}{', '.join(formatted_operands)}{style.symbol}"
        elif len(formatted_operands) == 0:
            out = f"{expr_name}{symbol_suffix}()"
        elif style.placement == is_expression_type.ReprStyle.Placement.POSTFIX:
            if len(formatted_operands) == 1:
                out = f"{formatted_operands[0]}{symbol}"
            else:
                out = f"({', '.join(formatted_operands)}){symbol}"
        elif len(formatted_operands) == 1:
            out = f"{expr_name}{symbol_suffix}({formatted_operands[0]})"
        elif lit_suffix and len(formatted_operands) > 2:
            out = f"{expr_name}{symbol_suffix}({', '.join(formatted_operands)})"
        elif style.placement == is_expression_type.ReprStyle.Placement.INFIX:
            symbol = f" {symbol} "
            out = f"{symbol.join(formatted_operands)}"
        elif style.placement == is_expression_type.ReprStyle.Placement.INFIX_FIRST:
            if len(formatted_operands) == 2:
                out = f"{formatted_operands[0]} {symbol} {formatted_operands[1]}"
            else:
                out = (
                    f"{formatted_operands[0]}{symbol}("
                    f"{', '.join(formatted_operands[1:])})"
                )
        else:
            assert False
        assert out

        # out += self._get_lit_suffix()

        return out

    def compact_repr(
        self,
        use_full_name: bool = False,
        no_lit_suffix: bool = False,
        no_class_suffix: bool = False,
    ) -> str:
        """Return compact math repr with symbols (+, *, ≥, ¬, ∧) and precedence."""
        from faebryk.core.solver.mutator import is_irrelevant, is_terminated

        aliases = self.as_operand.get().get_operations(Is, predicates_only=True)
        ps = [
            p
            for alias in aliases
            for p in alias.is_expression.get().get_operands_with_trait(
                F.Parameters.is_parameter
            )
        ]
        alias_suffix = ",".join(
            [
                p.compact_repr(use_full_name=use_full_name, with_detail=False)
                for p in ps
                if not p.try_get_sibling_trait(is_irrelevant)
            ]
        )
        if alias_suffix:
            alias_suffix = f"[{alias_suffix}]"

        from faebryk.core.solver.mutator import is_irrelevant, is_relevant

        isrelevant = self.try_get_sibling_trait(is_relevant) is not None
        irrelevant = self.try_get_sibling_trait(is_irrelevant) is not None
        relevant = True if isrelevant else False if irrelevant else None

        style = self.get_repr_style()
        return self._compact_repr(
            style=style,
            symbol=style.symbol if style.symbol is not None else type(self).__name__,
            is_predicate=bool(self.try_get_sibling_trait(is_predicate)),
            is_terminated=bool(self.try_get_sibling_trait(is_terminated)),
            relevant=relevant,
            lit_suffix=(
                self.as_parameter_operatable.get()._get_lit_suffix()
                if not no_lit_suffix
                else ""
            ),
            use_full_name=use_full_name,
            class_suffix=alias_suffix,
            expr_name=not_none(fabll.Traits(self).get_obj_raw().get_type_name()),
            operands=self.get_operands(),
            no_lit_suffix=no_lit_suffix,
            no_class_suffix=no_class_suffix,
        )

    def is_congruent_to_factory(
        self,
        other_factory: "type[fabll.NodeT]",
        other_operands: Sequence["F.Parameters.can_be_operand"],
        g: graph.GraphView,
        tg: fbrk.TypeGraph,
        allow_uncorrelated: bool = False,
        check_constrained: bool = True,
        recursive: bool = True,
    ) -> bool:
        """
        Check if this expression is congruent to an expression that would be
        created from the given factory with the given operands.

        This is useful for checking if creating a new expression would be
        redundant because an equivalent one already exists.

        Args:
            other_factory: The expression type (e.g., Add, Multiply)
            other_operands: The operands that would be used
            allow_uncorrelated: If True, non-singleton literals can match
            check_constrained: If True, also check constrained literals

        Returns:
            True if this expression is congruent to what the factory would create
        """
        # Get the underlying expression node
        self_obj = fabll.Traits(self).get_obj_raw()

        # Check if this expression is an instance of the factory type
        if not self_obj.isinstance(other_factory):
            return False

        # Check if the factory type is commutative
        type_node = not_none(self_obj.get_type_node())
        commutative = is_commutative.is_commutative_type(type_node)

        # Check operand congruence
        out = is_expression.are_pos_congruent(
            self.get_operands(),
            list(other_operands),
            commutative=commutative,
            g=g,
            tg=tg,
            allow_uncorrelated=allow_uncorrelated,
            check_constrained=check_constrained,
            recursive=recursive,
        )
        return out

    @staticmethod
    def sort_by_depth[T: fabll.NodeT](exprs: Iterable[T], ascending: bool) -> list[T]:
        """
        Ascending:
        ```
        (A + B) + (C + D)
        -> [A, B, C, D, (A+B), (C+D), (A+B)+(C+D)]
        ```
        """
        return sorted(
            exprs,
            key=lambda e: e.get_trait(F.Parameters.is_parameter_operatable).get_depth(),
            reverse=not ascending,
        )

    @staticmethod
    def sort_by_depth_po(
        exprs: Iterable["F.Parameters.is_parameter_operatable"],
        ascending: bool,
    ) -> list["F.Parameters.is_parameter_operatable"]:
        return sorted(
            exprs,
            key=F.Parameters.is_parameter_operatable.get_depth,
            reverse=not ascending,
        )

    @staticmethod
    def sort_by_depth_expr(
        exprs: Iterable["is_expression"], ascending: bool = True
    ) -> list["is_expression"]:
        return sorted(
            exprs,
            key=lambda e: e.as_parameter_operatable.get().get_depth(),
            reverse=not ascending,
        )

    def get_obj_type_node(self) -> graph.BoundNode:
        return not_none(fabll.Traits(self).get_obj_raw().get_type_node())

    def obj_has_trait[T: fabll.NodeT](self, trait: type[T]) -> T | None:
        return fabll.Traits(self).get_obj_raw().try_get_trait(trait)

    def obj_type_has_trait[T: fabll.NodeT](self, trait: type[T]) -> T | None:
        return fabll.Traits(self).get_obj_raw().try_get_trait_of_type(trait)

    def is_non_constraining(self) -> bool:
        """
        Check if this expression is non-constraining, i.e. does not create a dependency
        between operands.

        E.g. Anticorrelated(...), Not(Anticorrelated(...)) — these indicate statistical
        correlation, not constraint dependence.
        """
        # Anticorrelated(...)
        if self.obj_type_has_trait(has_independent_operands):
            return True

        # e.g. Not(Anticorrelated(...))
        return all(
            (is_expr := op.try_get_sibling_trait(is_expression)) is not None
            and is_expr.obj_type_has_trait(has_independent_operands)
            for op in self.get_operands()
        )

        # TODO: recurse to find deeper nesting, e.g. Not(Not(Anticorrelated(...)))

    def get_uncorrelatable_literals(self) -> list[Literals.is_literal]:
        """
        Get all literals in this expression's operands that cannot be correlated.

        Uncorrelatable literals are those that are neither singleton nor empty,
        meaning they represent a range or set of values that cannot be uniquely
        identified for congruence matching.

        Returns:
            List of uncorrelatable literal traits from this expression's operands
        """
        return [
            lit
            for lit in self.get_operand_literals().values()
            if lit.is_not_correlatable()
        ]

    def expr_isinstance(self, *expr_types: type[fabll.NodeT]) -> bool:
        return fabll.Traits(self).get_obj_raw().isinstance(*expr_types)

    def expr_try_cast[T: fabll.NodeT](self, t: type[T]) -> T | None:
        return fabll.Traits(self).get_obj_raw().try_cast(t)

    def expr_cast[T: fabll.NodeT](self, t: type[T], check: bool = True) -> T:
        return fabll.Traits(self).get_obj_raw().cast(t, check=check)

    def get_sorted_operands(self) -> list["F.Parameters.can_be_operand"]:
        return is_expression._sorted_operands(self.get_operands())

    @staticmethod
    def _sorted_operands(
        operands: Sequence["F.Parameters.can_be_operand"],
    ) -> list["F.Parameters.can_be_operand"]:
        # TODO not sure this still works the same way as back in the day
        return sorted(operands, key=hash)

    def is_congruent_to(
        self,
        other: "is_expression",
        g: graph.GraphView,
        tg: fbrk.TypeGraph,
        recursive: bool = False,
        allow_uncorrelated: bool = False,
        check_constrained: bool = True,
    ) -> bool:
        """
        Check if this expression is congruent to another expression.

        Two expressions are congruent if:
        - They are the same type
        - They have congruent operands (same nodes or equal correlatable literals)

        Args:
            other: The other expression (or its is_expression trait)
            recursive: If True, recursively check sub-expression congruence
            allow_uncorrelated: If True, non-singleton literals can match
            check_constrained: If True, also check constrained literals

        Returns:
            True if the expressions are congruent
        """
        if self.is_same(other):
            return True

        # TODO handle non-operands

        self_obj = fabll.Traits(self).get_obj_raw()
        other_obj = fabll.Traits(other).get_obj_raw()

        # Must be same type
        if not self_obj.has_same_type_as(other_obj):
            return False

        # if lit is non-single/empty set we can't correlate thus can't be congruent
        #  in general
        if not allow_uncorrelated and (
            self.get_uncorrelatable_literals() or other.get_uncorrelatable_literals()
        ):
            return False

        if check_constrained and (
            self_obj.has_trait(is_predicate) != other_obj.has_trait(is_predicate)
        ):
            return False

        # Check if the expression is commutative
        commutative = self_obj.try_get_trait(is_commutative) is not None
        self_operands = self.get_operands()
        other_operands = other.get_operands()

        if self_operands == other_operands:
            return True
        if commutative and self.get_sorted_operands() == other.get_sorted_operands():
            return True

        # Check operand congruence
        return recursive and is_expression.are_pos_congruent(
            self_operands,
            other_operands,
            g=g,
            tg=tg,
            commutative=commutative,
            allow_uncorrelated=allow_uncorrelated,
            check_constrained=check_constrained,
        )

    def in_operands(self, operand: "F.Parameters.can_be_operand") -> bool:
        return operand in self.get_operands()

    @staticmethod
    def are_pos_congruent(
        left: Sequence["F.Parameters.can_be_operand"],
        right: Sequence["F.Parameters.can_be_operand"],
        g: graph.GraphView,
        tg: fbrk.TypeGraph,
        commutative: bool = False,
        allow_uncorrelated: bool = False,
        check_constrained: bool = True,
        recursive: bool = True,
    ) -> bool:
        """
        Check if two sequences of operands are positionally congruent.

        Two operands are congruent if:
        - They are the same node (by reference)
        - They are both correlatable literals with equal values

        Args:
            left: First operand sequence
            right: Second operand sequence
            commutative: If True, order doesn't matter (for commutative operations)
            allow_uncorrelated: If True, non-singleton/non-empty literals can match
            check_constrained: If True, also check constrained literals

        Returns:
            True if the operand sequences are congruent
        """
        # TODO: this doesn't work anymore, not sure it ever did really well
        # if commutative:
        #    left = is_expression._sorted_operands(left)
        #    right = is_expression._sorted_operands(right)

        if len(left) != len(right):
            return False

        def operands_congruent(
            op1: F.Parameters.can_be_operand,
            op2: F.Parameters.can_be_operand,
        ) -> bool:
            # Same node - congruent
            if op1.is_same(op2):
                return True

            op1_obj = fabll.Traits(op1).get_obj_raw()
            op2_obj = fabll.Traits(op2).get_obj_raw()
            if not op1_obj.has_same_type_as(op2_obj):
                return False

            if lit1 := op1_obj.try_get_trait(Literals.is_literal):
                lit2 = op2_obj.get_trait(Literals.is_literal)
                if not allow_uncorrelated and (
                    lit1.is_not_correlatable() or lit2.is_not_correlatable()
                ):
                    return False
                return lit1.op_setic_equals(lit2, g=g, tg=tg)

            if recursive and (expr1 := op1_obj.try_get_trait(is_expression)):
                expr2 = op2_obj.get_trait(is_expression)
                return expr1.is_congruent_to(
                    expr2,
                    g=g,
                    tg=tg,
                    recursive=True,
                    allow_uncorrelated=allow_uncorrelated,
                    check_constrained=check_constrained,
                )

            # params only congruent if same node i guess?

            return False

        if commutative:
            # group operands by type
            # then check all combinations of operands in each group
            left_type_grouped = groupby(
                left, lambda x: not_none(x.get_type_node()).node().get_uuid()
            )
            right_type_grouped = groupby(
                right, lambda x: not_none(x.get_type_node()).node().get_uuid()
            )
            zipped_groups: dict[
                int,
                tuple[
                    list[F.Parameters.can_be_operand], list[F.Parameters.can_be_operand]
                ],
            ] = zip_dicts_by_key(left_type_grouped, right_type_grouped)
            if not all(
                len(left_group) == len(right_group)
                for left_group, right_group in zipped_groups.values()
            ):
                return False

            for left_group, right_group in zipped_groups.values():
                found_congruent_permutation_for_group = False
                for l_perm, r_perm in crosswise(
                    permutations(left_group, len(left_group)),
                    permutations(right_group, len(right_group)),
                ):
                    if all(
                        operands_congruent(l_op, r_op)
                        for l_op, r_op in zip(l_perm, r_perm)
                    ):
                        found_congruent_permutation_for_group = True
                        break
                if not found_congruent_permutation_for_group:
                    return False
            return True

        else:
            return all(operands_congruent(le, ri) for le, ri in zip(left, right))

    def get_depth(self) -> float:
        """
        Returns depth of longest expression tree from this expression.
        ```
        ((A + B) + (C + D)) * 5
            ^    ^    ^     ^
            0    1    0     2

        a = (X + (Y + Z))
        (a + 1) + a
         ^ ^    ^ ^
         1 2    3 1
        ```
        """
        return 1 + max([op.get_depth() for op in self.get_operands()] + [0])

    def switch_cast(self) -> "ExpressionNodes":
        """
        WARNING: very slow, do not use this!
        """
        types = get_args(ExpressionNodes)
        obj = fabll.Traits(self).get_obj_raw()
        for t in types:
            if obj.isinstance(t):
                return obj.cast(t)

        raise ValueError(
            f"Cannot cast expression {self} of type {obj} to any of {types}"
        )

    def get_parameter_type(
        self,
    ) -> type["F.Parameters.BooleanParameter | F.Parameters.NumericParameter"]:
        from faebryk.library.Parameters import BooleanParameter, NumericParameter

        # is_arithmetic -> NumericParameter
        if self.try_get_sibling_trait(is_arithmetic) is not None:
            return NumericParameter
        # is_logical -> BooleanParameter
        if self.try_get_sibling_trait(is_logic) is not None:
            return BooleanParameter

        raise ValueError(f"Unknown parameter type for expression {self}")

    def create_representative(
        self,
        g: graph.GraphView | None = None,
        tg: fbrk.TypeGraph | None = None,
        alias: bool = True,
    ) -> F.Parameters.is_parameter:
        """
        Warning: Strips unit!
        """
        if alias:
            assert g is None and tg is None

        g = g or self.g
        tg = tg or self.tg
        p_type = self.get_parameter_type()
        p_instance = p_type.bind_typegraph(tg=tg).create_instance(g=g)
        if isinstance(p_instance, F.Parameters.NumericParameter):
            p_instance.setup(
                domain=F.Parameters.NumericParameter.DOMAIN_SKIP, is_unit=None
            )
        elif isinstance(p_instance, F.Parameters.BooleanParameter):
            p_instance.setup()
        else:
            assert False

        p = p_instance.is_parameter.get()
        e_compact = self.compact_repr(no_lit_suffix=True, no_class_suffix=True)
        # calculcate ANSII color from uuid
        uuid = p.instance.node().get_uuid()
        color = f"\033[38;5;{uuid % 256}m"
        color_end = "\033[0m"
        p.set_name(
            f"{color}R0x{uuid:X}{color_end}",
            detail=f"[{e_compact}])",
        )
        if alias:
            Is.c(self.as_operand.get(), p.as_operand.get(), assert_=True)
        return p

    def __rich_repr__(self):
        try:
            yield self.compact_repr()
        except Exception as e:
            yield f"Error in repr: {e}"
        yield "on " + fabll.Traits(self).get_obj_raw().get_full_name(types=True)


# TODO
class has_implicit_constraints(fabll.Node):
    is_trait = fabll.Traits.MakeEdge(fabll.ImplementsTrait.MakeChild().put_on_type())


class is_assertable(fabll.Node):
    is_trait = fabll.Traits.MakeEdge(fabll.ImplementsTrait.MakeChild().put_on_type())
    as_expression = fabll.Traits.ImpliedTrait(is_expression)
    as_parameter_operatable = fabll.Traits.ImpliedTrait(
        F.Parameters.is_parameter_operatable
    )
    as_operand = fabll.Traits.ImpliedTrait(F.Parameters.can_be_operand)
    as_predicate = fabll.Traits.OptionalImpliedTrait["is_predicate"](
        lambda: is_predicate
    )

    # TODO: solver_terminated flag, has to be attr

    def assert_(self):
        obj = fabll.Traits(self).get_obj_raw()
        if obj.has_trait(is_predicate):
            return
        return fabll.Traits.create_and_add_instance_to(node=obj, trait=is_predicate)

    def is_asserted(self) -> bool:
        return self.try_get_sibling_trait(is_predicate) is not None


class is_predicate(fabll.Node):
    """
    Assertable and participates in constraining predicate semantics.
    """

    is_trait = fabll.Traits.MakeEdge(fabll.ImplementsTrait.MakeChild().put_on_type())
    as_expression = fabll.Traits.ImpliedTrait(is_expression)

    def unassert(self):
        # TODO
        pass


def _make_instance_from_operand_instance[T: fabll.NodeT](
    expr_factory: type[T],
    operand_instances: "tuple[F.Parameters.can_be_operand, ...]",
    g: graph.GraphView | None,
    tg: fbrk.TypeGraph | None,
) -> T:
    if not operand_instances and (not g or not tg):
        raise ValueError("At least one operand is required if no graph is provided")
    g = g or operand_instances[0].instance.g()
    tg = tg or operand_instances[0].tg
    return expr_factory.bind_typegraph(tg=tg).create_instance(g=g)


def _op(expr: fabll.NodeT) -> "F.Parameters.can_be_operand":
    from faebryk.library.Parameters import can_be_operand

    return expr.get_trait(can_be_operand)


# --------------------------------------------------------------------------------------

# TODO distribute
# TODO implement functions

# parameter type


class is_arithmetic(fabll.Node):
    is_trait = fabll.Traits.MakeEdge(fabll.ImplementsTrait.MakeChild().put_on_type())


class is_logic(fabll.Node):
    is_trait = fabll.Traits.MakeEdge(fabll.ImplementsTrait.MakeChild().put_on_type())


# operand type
class is_setic(fabll.Node):
    is_trait = fabll.Traits.MakeEdge(fabll.ImplementsTrait.MakeChild().put_on_type())


class has_side_effects(fabll.Node):
    is_trait = fabll.Traits.MakeEdge(fabll.ImplementsTrait.MakeChild().put_on_type())


# solver specific


class is_canonical(fabll.Node):
    is_trait = fabll.Traits.MakeEdge(fabll.ImplementsTrait.MakeChild().put_on_type())
    as_expression = fabll.Traits.ImpliedTrait(is_expression)
    as_parameter_operatable = fabll.Traits.ImpliedTrait(
        F.Parameters.is_parameter_operatable
    )
    as_operand = fabll.Traits.ImpliedTrait(F.Parameters.can_be_operand)


class is_reflexive(fabll.Node):
    """
    f(x,x) == true
    """

    is_trait = fabll.Traits.MakeEdge(fabll.ImplementsTrait.MakeChild().put_on_type())


class is_idempotent(fabll.Node):
    """
    f^n(x) == f(x) | n>0
    """

    is_trait = fabll.Traits.MakeEdge(fabll.ImplementsTrait.MakeChild().put_on_type())


class has_idempotent_operands(fabll.Node):
    """
    f(x,x) == f(x)
    """

    is_trait = fabll.Traits.MakeEdge(fabll.ImplementsTrait.MakeChild().put_on_type())


class is_commutative(fabll.Node):
    """
    f(x,y) == f(y,x)
    """

    is_trait = fabll.Traits.MakeEdge(fabll.ImplementsTrait.MakeChild().put_on_type())

    @classmethod
    def is_commutative_type(cls, typenode: graph.BoundNode) -> bool:
        return fabll.TypeNodeBoundTG.has_instance_of_type_has_trait(
            typenode, is_commutative
        )


class has_unary_identity(fabll.Node):
    """
    f(x,𝜖) == x
    """

    is_trait = fabll.Traits.MakeEdge(fabll.ImplementsTrait.MakeChild().put_on_type())


class is_associative(fabll.Node):
    """
    f(f(x,y),z) == f(x,f(y,z))
    """

    is_trait = fabll.Traits.MakeEdge(fabll.ImplementsTrait.MakeChild().put_on_type())


class is_flattenable(fabll.Node):
    """
    f(f(x,y),z) == f(x,y,z)
    """

    is_trait = fabll.Traits.MakeEdge(fabll.ImplementsTrait.MakeChild().put_on_type())


class is_involutory(fabll.Node):
    """
    f(f(x)) == x
    """

    is_trait = fabll.Traits.MakeEdge(fabll.ImplementsTrait.MakeChild().put_on_type())


class has_independent_operands(fabll.Node):
    """
    f(x, y) does not create a dependency between x and y when used in a predicate
    """

    is_trait = fabll.Traits.MakeEdge(fabll.ImplementsTrait.MakeChild().put_on_type())


# TODO expression class that captures not(f(x,y)) == f(y, x)

# --------------------------------------------------------------------------------------


class Add(fabll.Node):
    can_be_operand = fabll.Traits.MakeEdge(F.Parameters.can_be_operand.MakeChild())
    is_parameter_operatable = fabll.Traits.MakeEdge(
        F.Parameters.is_parameter_operatable.MakeChild()
    )
    is_expression_type = fabll.Traits.MakeEdge(
        is_expression_type.MakeChild(
            repr_style=is_expression_type.ReprStyle(
                symbol="+", placement=is_expression_type.ReprStyle.Placement.INFIX
            )
        ).put_on_type()
    )
    is_expression = fabll.Traits.MakeEdge(is_expression.MakeChild())
    is_arithmetic = fabll.Traits.MakeEdge(is_arithmetic.MakeChild())
    is_canonical = fabll.Traits.MakeEdge(is_canonical.MakeChild())
    is_commutative = fabll.Traits.MakeEdge(is_commutative.MakeChild())
    has_unary_identity = fabll.Traits.MakeEdge(has_unary_identity.MakeChild())
    is_associative = fabll.Traits.MakeEdge(is_associative.MakeChild())
    is_flattenable = fabll.Traits.MakeEdge(is_flattenable.MakeChild())

    operands = OperandSequence.MakeChild()

    def setup(self, *operands: "F.Parameters.can_be_operand") -> Self:
        self.operands.get().append(*operands)
        return self

    @classmethod
    def MakeChild(cls, *operands: fabll.RefPath) -> fabll._ChildField[Self]:
        out = fabll._ChildField(cls)
        for i, operand in enumerate(operands):
            out.add_dependant(
                OperandSequence.MakeEdge(
                    [out, cls.operands], get_operand_path(operand), i
                ),
            )
        return out

    @classmethod
    def from_operands(
        cls,
        *operands: "F.Parameters.can_be_operand",
        g: graph.GraphView | None = None,
        tg: fbrk.TypeGraph | None = None,
    ) -> Self:
        instance = _make_instance_from_operand_instance(cls, operands, g=g, tg=tg)
        return instance.setup(*operands)

    @classmethod
    def c(
        cls,
        *operands: "F.Parameters.can_be_operand",
        g: graph.GraphView | None = None,
        tg: fbrk.TypeGraph | None = None,
    ) -> "F.Parameters.can_be_operand":
        return _op(cls.from_operands(*operands, g=g, tg=tg))


class Subtract(fabll.Node):
    can_be_operand = fabll.Traits.MakeEdge(F.Parameters.can_be_operand.MakeChild())
    is_parameter_operatable = fabll.Traits.MakeEdge(
        F.Parameters.is_parameter_operatable.MakeChild()
    )
    is_expression_type = fabll.Traits.MakeEdge(
        is_expression_type.MakeChild(
            repr_style=is_expression_type.ReprStyle(
                symbol="-", placement=is_expression_type.ReprStyle.Placement.INFIX
            )
        ).put_on_type()
    )
    is_expression = fabll.Traits.MakeEdge(is_expression.MakeChild())
    is_arithmetic = fabll.Traits.MakeEdge(is_arithmetic.MakeChild())
    is_flattenable = fabll.Traits.MakeEdge(is_flattenable.MakeChild())
    minuend = OperandPointer.MakeChild()
    subtrahends = OperandSequence.MakeChild()

    def setup(
        self,
        minuend: "F.Parameters.can_be_operand",
        *subtrahends: "F.Parameters.can_be_operand",
    ) -> Self:
        self.minuend.get().point(minuend)
        for subtrahend in subtrahends:
            self.subtrahends.get().append(subtrahend)
        return self

    @classmethod
    def MakeChild(
        cls, minuend: fabll.RefPath, *subtrahends: fabll.RefPath
    ) -> fabll._ChildField[Self]:
        out = fabll._ChildField(cls)
        out.add_dependant(
            OperandPointer.MakeEdge([out, cls.minuend], get_operand_path(minuend)),
        )
        for i, subtrahend in enumerate(subtrahends):
            out.add_dependant(
                OperandSequence.MakeEdge(
                    [out, cls.subtrahends], get_operand_path(subtrahend), i
                ),
            )
        return out

    @classmethod
    def from_operands(
        cls,
        minuend: "F.Parameters.can_be_operand",
        *subtrahends: "F.Parameters.can_be_operand",
        g: graph.GraphView | None = None,
        tg: fbrk.TypeGraph | None = None,
    ) -> Self:
        operands = (minuend, *subtrahends)
        instance = _make_instance_from_operand_instance(cls, operands, g=g, tg=tg)
        return instance.setup(minuend, *subtrahends)

    @classmethod
    def c(
        cls,
        minuend: "F.Parameters.can_be_operand",
        *subtrahends: "F.Parameters.can_be_operand",
        g: graph.GraphView | None = None,
        tg: fbrk.TypeGraph | None = None,
    ) -> "F.Parameters.can_be_operand":
        return _op(cls.from_operands(minuend, *subtrahends, g=g, tg=tg))


class Multiply(fabll.Node):
    can_be_operand = fabll.Traits.MakeEdge(F.Parameters.can_be_operand.MakeChild())
    is_parameter_operatable = fabll.Traits.MakeEdge(
        F.Parameters.is_parameter_operatable.MakeChild()
    )
    is_expression_type = fabll.Traits.MakeEdge(
        is_expression_type.MakeChild(
            repr_style=is_expression_type.ReprStyle(
                symbol="*", placement=is_expression_type.ReprStyle.Placement.INFIX
            )
        ).put_on_type()
    )
    is_expression = fabll.Traits.MakeEdge(is_expression.MakeChild())
    is_arithmetic = fabll.Traits.MakeEdge(is_arithmetic.MakeChild())
    is_canonical = fabll.Traits.MakeEdge(is_canonical.MakeChild())
    is_commutative = fabll.Traits.MakeEdge(is_commutative.MakeChild())
    has_unary_identity = fabll.Traits.MakeEdge(has_unary_identity.MakeChild())
    is_associative = fabll.Traits.MakeEdge(is_associative.MakeChild())
    is_flattenable = fabll.Traits.MakeEdge(is_flattenable.MakeChild())

    operands = OperandSequence.MakeChild()

    @classmethod
    def MakeChild(cls, *operands: fabll.RefPath) -> fabll._ChildField[Self]:
        out = fabll._ChildField(cls)

        for i, operand_field in enumerate(operands):
            out.add_dependant(
                OperandSequence.MakeEdge(
                    [out, cls.operands], get_operand_path(operand_field), i
                )
            )

        return out

    def setup(self, *operands: "F.Parameters.can_be_operand") -> Self:
        for operand in operands:
            if (
                self.g.get_self_node().node().get_uuid()
                != operand.g.get_self_node().node().get_uuid()
            ):
                raise Exception("operand graph mismatch")
        self.operands.get().append(*operands)
        return self

    @classmethod
    def from_operands(
        cls,
        *operands: "F.Parameters.can_be_operand",
        g: graph.GraphView | None = None,
        tg: fbrk.TypeGraph | None = None,
    ) -> Self:
        instance = _make_instance_from_operand_instance(cls, operands, g=g, tg=tg)
        return instance.setup(*operands)

    @classmethod
    def c(
        cls,
        *operands: "F.Parameters.can_be_operand",
        g: graph.GraphView | None = None,
        tg: fbrk.TypeGraph | None = None,
    ) -> "F.Parameters.can_be_operand":
        return _op(cls.from_operands(*operands, g=g, tg=tg))


class Divide(fabll.Node):
    can_be_operand = fabll.Traits.MakeEdge(F.Parameters.can_be_operand.MakeChild())
    is_parameter_operatable = fabll.Traits.MakeEdge(
        F.Parameters.is_parameter_operatable.MakeChild()
    )
    is_expression_type = fabll.Traits.MakeEdge(
        is_expression_type.MakeChild(
            repr_style=is_expression_type.ReprStyle(
                symbol="/", placement=is_expression_type.ReprStyle.Placement.INFIX
            )
        ).put_on_type()
    )
    is_expression = fabll.Traits.MakeEdge(is_expression.MakeChild())
    is_arithmetic = fabll.Traits.MakeEdge(is_arithmetic.MakeChild())
    # denominator not zero
    has_implicit_constraints = fabll.Traits.MakeEdge(
        has_implicit_constraints.MakeChild()
    )
    is_flattenable = fabll.Traits.MakeEdge(is_flattenable.MakeChild())

    numerator = OperandPointer.MakeChild()
    zdenominator = OperandSequence.MakeChild()

    def setup(
        self,
        numerator: "F.Parameters.can_be_operand",
        *denominators: "F.Parameters.can_be_operand",
    ) -> Self:
        self.numerator.get().point(numerator)
        for denominator in denominators:
            self.zdenominator.get().append(denominator)
        return self

    @classmethod
    def MakeChild(
        cls, numerator: fabll.RefPath, *denominators: fabll.RefPath
    ) -> fabll._ChildField[Self]:
        out = fabll._ChildField(cls)
        out.add_dependant(
            OperandPointer.MakeEdge([out, cls.numerator], get_operand_path(numerator))
        )
        for i, denominator in enumerate(denominators):
            out.add_dependant(
                OperandSequence.MakeEdge(
                    [out, cls.zdenominator], get_operand_path(denominator), i
                )
            )
        return out

    @classmethod
    def from_operands(
        cls,
        numerator: "F.Parameters.can_be_operand",
        *denominators: "F.Parameters.can_be_operand",
        g: graph.GraphView | None = None,
        tg: fbrk.TypeGraph | None = None,
    ) -> Self:
        operands = (numerator, *denominators)
        instance = _make_instance_from_operand_instance(cls, operands, g=g, tg=tg)
        return instance.setup(numerator, *denominators)

    @classmethod
    def c(
        cls,
        numerator: "F.Parameters.can_be_operand",
        *denominators: "F.Parameters.can_be_operand",
        g: graph.GraphView | None = None,
        tg: fbrk.TypeGraph | None = None,
    ) -> "F.Parameters.can_be_operand":
        return _op(cls.from_operands(numerator, *denominators, g=g, tg=tg))


class Sqrt(fabll.Node):
    can_be_operand = fabll.Traits.MakeEdge(F.Parameters.can_be_operand.MakeChild())
    is_parameter_operatable = fabll.Traits.MakeEdge(
        F.Parameters.is_parameter_operatable.MakeChild()
    )
    is_expression_type = fabll.Traits.MakeEdge(
        is_expression_type.MakeChild(
            repr_style=is_expression_type.ReprStyle(
                symbol="√", placement=is_expression_type.ReprStyle.Placement.PREFIX
            )
        ).put_on_type()
    )
    is_expression = fabll.Traits.MakeEdge(is_expression.MakeChild())
    is_arithmetic = fabll.Traits.MakeEdge(is_arithmetic.MakeChild())
    # non-negative
    has_implicit_constraints = fabll.Traits.MakeEdge(
        has_implicit_constraints.MakeChild()
    )

    operand = OperandPointer.MakeChild()

    def setup(self, operand: "F.Parameters.can_be_operand") -> Self:
        self.operand.get().point(operand)
        return self

    @classmethod
    def MakeChild(cls, operand: fabll.RefPath) -> fabll._ChildField[Self]:
        out = fabll._ChildField(cls)
        out.add_dependant(
            OperandPointer.MakeEdge([out, cls.operand], get_operand_path(operand))
        )
        return out

    @classmethod
    def from_operands(
        cls,
        operand: "F.Parameters.can_be_operand",
        g: graph.GraphView | None = None,
        tg: fbrk.TypeGraph | None = None,
    ) -> Self:
        instance = _make_instance_from_operand_instance(cls, (operand,), g=g, tg=tg)
        return instance.setup(operand)

    @classmethod
    def c(
        cls,
        operand: "F.Parameters.can_be_operand",
        g: graph.GraphView | None = None,
        tg: fbrk.TypeGraph | None = None,
    ) -> "F.Parameters.can_be_operand":
        return _op(cls.from_operands(operand, g=g, tg=tg))


class Power(fabll.Node):
    can_be_operand = fabll.Traits.MakeEdge(F.Parameters.can_be_operand.MakeChild())
    is_parameter_operatable = fabll.Traits.MakeEdge(
        F.Parameters.is_parameter_operatable.MakeChild()
    )
    is_expression_type = fabll.Traits.MakeEdge(
        is_expression_type.MakeChild(
            repr_style=is_expression_type.ReprStyle(
                symbol="^", placement=is_expression_type.ReprStyle.Placement.INFIX_FIRST
            )
        ).put_on_type()
    )
    is_expression = fabll.Traits.MakeEdge(is_expression.MakeChild())
    is_arithmetic = fabll.Traits.MakeEdge(is_arithmetic.MakeChild())
    is_canonical = fabll.Traits.MakeEdge(is_canonical.MakeChild())
    # TODO
    # is_flattenable = fabll.Traits.MakeEdge(is_flattenable.MakeChild())

    base = OperandPointer.MakeChild()
    exponent = OperandPointer.MakeChild()

    @classmethod
    def MakeChild(
        cls, base: fabll.RefPath, exponent: fabll.RefPath
    ) -> fabll._ChildField[Self]:
        out = fabll._ChildField(cls)
        out.add_dependant(
            OperandPointer.MakeEdge([out, cls.base], get_operand_path(base))
        )
        out.add_dependant(
            OperandPointer.MakeEdge([out, cls.exponent], get_operand_path(exponent))
        )
        return out

    def setup(
        self,
        base: "F.Parameters.can_be_operand",
        exponent: "F.Parameters.can_be_operand",
    ) -> Self:
        if (
            self.g.get_self_node().node().get_uuid()
            != base.g.get_self_node().node().get_uuid()
        ):
            raise Exception("Base graph mismatch")
        if (
            self.g.get_self_node().node().get_uuid()
            != exponent.g.get_self_node().node().get_uuid()
        ):
            raise Exception("exponent graph mismatch")
        self.base.get().point(base)
        self.exponent.get().point(exponent)
        return self

    @classmethod
    def from_operands(
        cls,
        base: "F.Parameters.can_be_operand",
        exponent: "F.Parameters.can_be_operand",
        g: graph.GraphView | None = None,
        tg: fbrk.TypeGraph | None = None,
    ) -> Self:
        instance = _make_instance_from_operand_instance(
            cls, (base, exponent), g=g, tg=tg
        )
        return instance.setup(base, exponent)

    @classmethod
    def c(
        cls,
        base: "F.Parameters.can_be_operand",
        exponent: "F.Parameters.can_be_operand",
        g: graph.GraphView | None = None,
        tg: fbrk.TypeGraph | None = None,
    ) -> "F.Parameters.can_be_operand":
        return _op(cls.from_operands(base, exponent, g=g, tg=tg))


class Log(fabll.Node):
    can_be_operand = fabll.Traits.MakeEdge(F.Parameters.can_be_operand.MakeChild())
    is_parameter_operatable = fabll.Traits.MakeEdge(
        F.Parameters.is_parameter_operatable.MakeChild()
    )
    is_expression_type = fabll.Traits.MakeEdge(
        is_expression_type.MakeChild(
            repr_style=is_expression_type.ReprStyle(
                symbol="log", placement=is_expression_type.ReprStyle.Placement.PREFIX
            )
        ).put_on_type()
    )
    is_expression = fabll.Traits.MakeEdge(is_expression.MakeChild())
    is_arithmetic = fabll.Traits.MakeEdge(is_arithmetic.MakeChild())
    is_canonical = fabll.Traits.MakeEdge(is_canonical.MakeChild())

    # non-negative
    has_implicit_constraints = fabll.Traits.MakeEdge(
        has_implicit_constraints.MakeChild()
    )

    operand = OperandPointer.MakeChild()
    # z because alphabetic sort
    zbase = OperandPointer.MakeChild()

    def setup(
        self,
        operand: "F.Parameters.can_be_operand",
        base: "F.Parameters.can_be_operand | None" = None,
    ) -> Self:
        self.operand.get().point(operand)
        if base is None:
            base = (
                F.Literals.make_singleton(g=self.g, tg=self.tg, value=math.e)
                .is_literal.get()
                .as_operand.get()
            )
        self.zbase.get().point(base)
        return self

    @classmethod
    def MakeChild(
        cls, operand: fabll.RefPath, base: fabll.RefPath | None = None
    ) -> fabll._ChildField[Self]:
        out = fabll._ChildField(cls)
        out.add_dependant(
            OperandPointer.MakeEdge([out, cls.operand], get_operand_path(operand))
        )
        if base is not None:
            out.add_dependant(
                OperandPointer.MakeEdge([out, cls.zbase], get_operand_path(base))
            )
        return out

    @classmethod
    def from_operands(
        cls,
        operand: "F.Parameters.can_be_operand",
        base: "F.Parameters.can_be_operand | None" = None,
        g: graph.GraphView | None = None,
        tg: fbrk.TypeGraph | None = None,
    ) -> Self:
        instance = _make_instance_from_operand_instance(cls, (operand,), g=g, tg=tg)
        return instance.setup(operand, base)

    @classmethod
    def c(
        cls,
        operand: "F.Parameters.can_be_operand",
        base: "F.Parameters.can_be_operand | None" = None,
        g: graph.GraphView | None = None,
        tg: fbrk.TypeGraph | None = None,
    ) -> "F.Parameters.can_be_operand":
        return _op(cls.from_operands(operand, base, g=g, tg=tg))


class Sin(fabll.Node):
    can_be_operand = fabll.Traits.MakeEdge(F.Parameters.can_be_operand.MakeChild())
    is_parameter_operatable = fabll.Traits.MakeEdge(
        F.Parameters.is_parameter_operatable.MakeChild()
    )
    is_expression_type = fabll.Traits.MakeEdge(
        is_expression_type.MakeChild(
            repr_style=is_expression_type.ReprStyle(
                symbol="sin", placement=is_expression_type.ReprStyle.Placement.PREFIX
            )
        ).put_on_type()
    )
    is_expression = fabll.Traits.MakeEdge(is_expression.MakeChild())
    is_arithmetic = fabll.Traits.MakeEdge(is_arithmetic.MakeChild())
    is_canonical = fabll.Traits.MakeEdge(is_canonical.MakeChild())

    operand = OperandPointer.MakeChild()

    def setup(self, operand: "F.Parameters.can_be_operand") -> Self:
        self.operand.get().point(operand)
        return self

    @classmethod
    def MakeChild(cls, operand: fabll.RefPath) -> fabll._ChildField[Self]:
        out = fabll._ChildField(cls)
        out.add_dependant(
            OperandPointer.MakeEdge([out, cls.operand], get_operand_path(operand))
        )
        return out

    @classmethod
    def from_operands(
        cls,
        operand: "F.Parameters.can_be_operand",
        g: graph.GraphView | None = None,
        tg: fbrk.TypeGraph | None = None,
    ) -> Self:
        instance = _make_instance_from_operand_instance(cls, (operand,), g=g, tg=tg)
        return instance.setup(operand)

    @classmethod
    def c(
        cls,
        operand: "F.Parameters.can_be_operand",
        g: graph.GraphView | None = None,
        tg: fbrk.TypeGraph | None = None,
    ) -> "F.Parameters.can_be_operand":
        return _op(cls.from_operands(operand, g=g, tg=tg))


class Cos(fabll.Node):
    can_be_operand = fabll.Traits.MakeEdge(F.Parameters.can_be_operand.MakeChild())
    is_parameter_operatable = fabll.Traits.MakeEdge(
        F.Parameters.is_parameter_operatable.MakeChild()
    )
    is_expression_type = fabll.Traits.MakeEdge(
        is_expression_type.MakeChild(
            repr_style=is_expression_type.ReprStyle(
                symbol="cos", placement=is_expression_type.ReprStyle.Placement.PREFIX
            )
        ).put_on_type()
    )
    is_expression = fabll.Traits.MakeEdge(is_expression.MakeChild())
    is_arithmetic = fabll.Traits.MakeEdge(is_arithmetic.MakeChild())
    operand = OperandPointer.MakeChild()

    def setup(self, operand: "F.Parameters.can_be_operand") -> Self:
        self.operand.get().point(operand)
        return self

    @classmethod
    def MakeChild(cls, operand: fabll.RefPath) -> fabll._ChildField[Self]:
        out = fabll._ChildField(cls)
        out.add_dependant(
            OperandPointer.MakeEdge([out, cls.operand], get_operand_path(operand))
        )
        return out

    @classmethod
    def from_operands(
        cls,
        operand: "F.Parameters.can_be_operand",
        g: graph.GraphView | None = None,
        tg: fbrk.TypeGraph | None = None,
    ) -> Self:
        instance = _make_instance_from_operand_instance(cls, (operand,), g=g, tg=tg)
        return instance.setup(operand)

    @classmethod
    def c(
        cls,
        operand: "F.Parameters.can_be_operand",
        g: graph.GraphView | None = None,
        tg: fbrk.TypeGraph | None = None,
    ) -> "F.Parameters.can_be_operand":
        return _op(cls.from_operands(operand, g=g, tg=tg))


class Abs(fabll.Node):
    can_be_operand = fabll.Traits.MakeEdge(F.Parameters.can_be_operand.MakeChild())
    is_parameter_operatable = fabll.Traits.MakeEdge(
        F.Parameters.is_parameter_operatable.MakeChild()
    )
    is_expression_type = fabll.Traits.MakeEdge(
        is_expression_type.MakeChild(
            repr_style=is_expression_type.ReprStyle(
                symbol="|", placement=is_expression_type.ReprStyle.Placement.EMBRACE
            )
        ).put_on_type()
    )
    is_expression = fabll.Traits.MakeEdge(is_expression.MakeChild())
    is_arithmetic = fabll.Traits.MakeEdge(is_arithmetic.MakeChild())
    is_canonical = fabll.Traits.MakeEdge(is_canonical.MakeChild())
    is_idempotent = fabll.Traits.MakeEdge(is_idempotent.MakeChild())

    operand = OperandPointer.MakeChild()

    def setup(self, operand: "F.Parameters.can_be_operand") -> Self:
        self.operand.get().point(operand)
        return self

    @classmethod
    def MakeChild(cls, operand: fabll.RefPath) -> fabll._ChildField[Self]:
        out = fabll._ChildField(cls)
        out.add_dependant(
            OperandPointer.MakeEdge([out, cls.operand], get_operand_path(operand))
        )
        return out

    @classmethod
    def from_operands(
        cls,
        operand: "F.Parameters.can_be_operand",
        g: graph.GraphView | None = None,
        tg: fbrk.TypeGraph | None = None,
    ) -> Self:
        instance = _make_instance_from_operand_instance(cls, (operand,), g=g, tg=tg)
        return instance.setup(operand)

    @classmethod
    def c(
        cls,
        operand: "F.Parameters.can_be_operand",
        g: graph.GraphView | None = None,
        tg: fbrk.TypeGraph | None = None,
    ) -> "F.Parameters.can_be_operand":
        return _op(cls.from_operands(operand, g=g, tg=tg))


class Round(fabll.Node):
    can_be_operand = fabll.Traits.MakeEdge(F.Parameters.can_be_operand.MakeChild())
    is_parameter_operatable = fabll.Traits.MakeEdge(
        F.Parameters.is_parameter_operatable.MakeChild()
    )
    is_expression_type = fabll.Traits.MakeEdge(
        is_expression_type.MakeChild(
            repr_style=is_expression_type.ReprStyle(
                symbol="round", placement=is_expression_type.ReprStyle.Placement.PREFIX
            )
        ).put_on_type()
    )
    is_expression = fabll.Traits.MakeEdge(is_expression.MakeChild())
    is_arithmetic = fabll.Traits.MakeEdge(is_arithmetic.MakeChild())
    is_canonical = fabll.Traits.MakeEdge(is_canonical.MakeChild())
    is_idempotent = fabll.Traits.MakeEdge(is_idempotent.MakeChild())

    operand = OperandPointer.MakeChild()

    def setup(self, operand: "F.Parameters.can_be_operand") -> Self:
        self.operand.get().point(operand)
        return self

    @classmethod
    def MakeChild(cls, operand: fabll.RefPath) -> fabll._ChildField[Self]:
        out = fabll._ChildField(cls)
        out.add_dependant(
            OperandPointer.MakeEdge([out, cls.operand], get_operand_path(operand))
        )
        return out

    @classmethod
    def from_operands(
        cls,
        operand: "F.Parameters.can_be_operand",
        g: graph.GraphView | None = None,
        tg: fbrk.TypeGraph | None = None,
    ) -> Self:
        instance = _make_instance_from_operand_instance(cls, (operand,), g=g, tg=tg)
        return instance.setup(operand)

    @classmethod
    def c(
        cls,
        operand: "F.Parameters.can_be_operand",
        g: graph.GraphView | None = None,
        tg: fbrk.TypeGraph | None = None,
    ) -> "F.Parameters.can_be_operand":
        return _op(cls.from_operands(operand, g=g, tg=tg))


class Floor(fabll.Node):
    can_be_operand = fabll.Traits.MakeEdge(F.Parameters.can_be_operand.MakeChild())
    is_parameter_operatable = fabll.Traits.MakeEdge(
        F.Parameters.is_parameter_operatable.MakeChild()
    )
    is_expression_type = fabll.Traits.MakeEdge(
        is_expression_type.MakeChild(
            repr_style=is_expression_type.ReprStyle(
                symbol="⌊", placement=is_expression_type.ReprStyle.Placement.PREFIX
            )
        ).put_on_type()
    )
    is_expression = fabll.Traits.MakeEdge(is_expression.MakeChild())
    is_arithmetic = fabll.Traits.MakeEdge(is_arithmetic.MakeChild())

    operand = OperandPointer.MakeChild()

    def setup(self, operand: "F.Parameters.can_be_operand") -> Self:
        self.operand.get().point(operand)
        return self

    @classmethod
    def MakeChild(cls, operand: fabll.RefPath) -> fabll._ChildField[Self]:
        out = fabll._ChildField(cls)
        out.add_dependant(
            OperandPointer.MakeEdge([out, cls.operand], get_operand_path(operand))
        )
        return out

    @classmethod
    def from_operands(
        cls,
        operand: "F.Parameters.can_be_operand",
        g: graph.GraphView | None = None,
        tg: fbrk.TypeGraph | None = None,
    ) -> Self:
        instance = _make_instance_from_operand_instance(cls, (operand,), g=g, tg=tg)
        return instance.setup(operand)

    @classmethod
    def c(
        cls,
        operand: "F.Parameters.can_be_operand",
        g: graph.GraphView | None = None,
        tg: fbrk.TypeGraph | None = None,
    ) -> "F.Parameters.can_be_operand":
        return _op(cls.from_operands(operand, g=g, tg=tg))


class Ceil(fabll.Node):
    can_be_operand = fabll.Traits.MakeEdge(F.Parameters.can_be_operand.MakeChild())
    is_parameter_operatable = fabll.Traits.MakeEdge(
        F.Parameters.is_parameter_operatable.MakeChild()
    )
    is_expression_type = fabll.Traits.MakeEdge(
        is_expression_type.MakeChild(
            repr_style=is_expression_type.ReprStyle(
                symbol="⌈", placement=is_expression_type.ReprStyle.Placement.PREFIX
            )
        ).put_on_type()
    )
    is_expression = fabll.Traits.MakeEdge(is_expression.MakeChild())
    is_arithmetic = fabll.Traits.MakeEdge(is_arithmetic.MakeChild())

    operand = OperandPointer.MakeChild()

    def setup(self, operand: "F.Parameters.can_be_operand") -> Self:
        self.operand.get().point(operand)
        return self

    @classmethod
    def MakeChild(cls, operand: fabll.RefPath) -> fabll._ChildField[Self]:
        out = fabll._ChildField(cls)
        out.add_dependant(
            OperandPointer.MakeEdge([out, cls.operand], get_operand_path(operand))
        )
        return out

    @classmethod
    def from_operands(
        cls,
        operand: "F.Parameters.can_be_operand",
        g: graph.GraphView | None = None,
        tg: fbrk.TypeGraph | None = None,
    ) -> Self:
        instance = _make_instance_from_operand_instance(cls, (operand,), g=g, tg=tg)
        return instance.setup(operand)

    @classmethod
    def c(
        cls,
        operand: "F.Parameters.can_be_operand",
        g: graph.GraphView | None = None,
        tg: fbrk.TypeGraph | None = None,
    ) -> "F.Parameters.can_be_operand":
        return _op(cls.from_operands(operand, g=g, tg=tg))


class Integrate(fabll.Node):
    can_be_operand = fabll.Traits.MakeEdge(F.Parameters.can_be_operand.MakeChild())
    is_parameter_operatable = fabll.Traits.MakeEdge(
        F.Parameters.is_parameter_operatable.MakeChild()
    )
    is_expression_type = fabll.Traits.MakeEdge(
        is_expression_type.MakeChild(
            repr_style=is_expression_type.ReprStyle(
                symbol="∫", placement=is_expression_type.ReprStyle.Placement.PREFIX
            )
        ).put_on_type()
    )
    is_expression = fabll.Traits.MakeEdge(is_expression.MakeChild())
    is_arithmetic = fabll.Traits.MakeEdge(is_arithmetic.MakeChild())

    function = OperandPointer.MakeChild()
    variable = OperandPointer.MakeChild()  # Variable to integrate with respect to

    def setup(
        self,
        operand: "F.Parameters.can_be_operand",
        variable: "F.Parameters.can_be_operand",
    ) -> Self:
        self.function.get().point(operand)
        self.variable.get().point(variable)
        return self

    @classmethod
    def MakeChild(
        cls, operand: fabll.RefPath, variable: fabll.RefPath
    ) -> fabll._ChildField[Self]:
        out = fabll._ChildField(cls)
        out.add_dependant(
            OperandPointer.MakeEdge([out, cls.function], get_operand_path(operand))
        )
        out.add_dependant(
            OperandPointer.MakeEdge([out, cls.variable], get_operand_path(variable))
        )
        return out

    @classmethod
    def from_operands(
        cls,
        operand: "F.Parameters.can_be_operand",
        variable: "F.Parameters.can_be_operand",
        g: graph.GraphView | None = None,
        tg: fbrk.TypeGraph | None = None,
    ) -> Self:
        instance = _make_instance_from_operand_instance(
            cls, (operand, variable), g=g, tg=tg
        )
        return instance.setup(operand, variable)

    @classmethod
    def c(
        cls,
        operand: "F.Parameters.can_be_operand",
        variable: "F.Parameters.can_be_operand",
        g: graph.GraphView | None = None,
        tg: fbrk.TypeGraph | None = None,
    ) -> "F.Parameters.can_be_operand":
        return _op(cls.from_operands(operand, variable, g=g, tg=tg))


class Differentiate(fabll.Node):
    can_be_operand = fabll.Traits.MakeEdge(F.Parameters.can_be_operand.MakeChild())
    is_parameter_operatable = fabll.Traits.MakeEdge(
        F.Parameters.is_parameter_operatable.MakeChild()
    )
    is_expression_type = fabll.Traits.MakeEdge(
        is_expression_type.MakeChild(
            repr_style=is_expression_type.ReprStyle(
                symbol="d", placement=is_expression_type.ReprStyle.Placement.PREFIX
            )
        ).put_on_type()
    )
    is_expression = fabll.Traits.MakeEdge(is_expression.MakeChild())
    is_arithmetic = fabll.Traits.MakeEdge(is_arithmetic.MakeChild())

    function = OperandPointer.MakeChild()
    variable = OperandPointer.MakeChild()  # Variable to differentiate with respect to

    def setup(
        self,
        operand: "F.Parameters.can_be_operand",
        variable: "F.Parameters.can_be_operand",
    ) -> Self:
        self.function.get().point(operand)
        self.variable.get().point(variable)
        return self

    @classmethod
    def MakeChild(
        cls, operand: fabll.RefPath, variable: fabll.RefPath
    ) -> fabll._ChildField[Self]:
        out = fabll._ChildField(cls)
        out.add_dependant(
            OperandPointer.MakeEdge([out, cls.function], get_operand_path(operand))
        )
        out.add_dependant(
            OperandPointer.MakeEdge([out, cls.variable], get_operand_path(variable))
        )
        return out

    @classmethod
    def from_operands(
        cls,
        operand: "F.Parameters.can_be_operand",
        variable: "F.Parameters.can_be_operand",
        g: graph.GraphView | None = None,
        tg: fbrk.TypeGraph | None = None,
    ) -> Self:
        instance = _make_instance_from_operand_instance(
            cls, (operand, variable), g=g, tg=tg
        )
        return instance.setup(operand, variable)

    @classmethod
    def c(
        cls,
        operand: "F.Parameters.can_be_operand",
        variable: "F.Parameters.can_be_operand",
        g: graph.GraphView | None = None,
        tg: fbrk.TypeGraph | None = None,
    ) -> "F.Parameters.can_be_operand":
        return _op(cls.from_operands(operand, variable, g=g, tg=tg))


class And(fabll.Node):
    can_be_operand = fabll.Traits.MakeEdge(F.Parameters.can_be_operand.MakeChild())
    is_parameter_operatable = fabll.Traits.MakeEdge(
        F.Parameters.is_parameter_operatable.MakeChild()
    )
    is_assertable = fabll.Traits.MakeEdge(is_assertable.MakeChild())
    is_expression_type = fabll.Traits.MakeEdge(
        is_expression_type.MakeChild(
            repr_style=is_expression_type.ReprStyle(
                symbol="∧", placement=is_expression_type.ReprStyle.Placement.INFIX
            )
        ).put_on_type()
    )
    is_expression = fabll.Traits.MakeEdge(is_expression.MakeChild())
    is_logic = fabll.Traits.MakeEdge(is_logic.MakeChild())
    is_associative = fabll.Traits.MakeEdge(is_associative.MakeChild())
    is_flattenable = fabll.Traits.MakeEdge(is_flattenable.MakeChild())

    operands = OperandSequence.MakeChild()

    def setup(
        self,
        *operands: "F.Parameters.can_be_operand",
        assert_: bool = False,
    ) -> Self:
        self.operands.get().append(*operands)
        if assert_:
            self.is_assertable.get().assert_()
        return self

    @classmethod
    def MakeChild(cls, *operands: fabll.RefPath) -> fabll._ChildField[Self]:
        out = fabll._ChildField(cls)
        for i, operand in enumerate(operands):
            out.add_dependant(
                OperandSequence.MakeEdge(
                    [out, cls.operands], get_operand_path(operand), i
                )
            )
        return out

    @classmethod
    def from_operands(
        cls,
        *operands: "F.Parameters.can_be_operand",
        g: graph.GraphView | None = None,
        tg: fbrk.TypeGraph | None = None,
        assert_: bool = False,
    ) -> Self:
        instance = _make_instance_from_operand_instance(cls, operands, g=g, tg=tg)
        return instance.setup(*operands, assert_=assert_)

    @classmethod
    def c(
        cls,
        *operands: "F.Parameters.can_be_operand",
        g: graph.GraphView | None = None,
        tg: fbrk.TypeGraph | None = None,
        assert_: bool = False,
    ) -> "F.Parameters.can_be_operand":
        return _op(cls.from_operands(*operands, g=g, tg=tg, assert_=assert_))


class Or(fabll.Node):
    can_be_operand = fabll.Traits.MakeEdge(F.Parameters.can_be_operand.MakeChild())
    is_parameter_operatable = fabll.Traits.MakeEdge(
        F.Parameters.is_parameter_operatable.MakeChild()
    )
    is_assertable = fabll.Traits.MakeEdge(is_assertable.MakeChild())
    is_expression_type = fabll.Traits.MakeEdge(
        is_expression_type.MakeChild(
            repr_style=is_expression_type.ReprStyle(
                symbol="∨", placement=is_expression_type.ReprStyle.Placement.INFIX
            )
        ).put_on_type()
    )
    is_expression = fabll.Traits.MakeEdge(is_expression.MakeChild())
    is_logic = fabll.Traits.MakeEdge(is_logic.MakeChild())
    is_canonical = fabll.Traits.MakeEdge(is_canonical.MakeChild())
    has_idempotent_operands = fabll.Traits.MakeEdge(has_idempotent_operands.MakeChild())
    is_commutative = fabll.Traits.MakeEdge(is_commutative.MakeChild())
    has_unary_identity = fabll.Traits.MakeEdge(has_unary_identity.MakeChild())
    is_associative = fabll.Traits.MakeEdge(is_associative.MakeChild())
    is_flattenable = fabll.Traits.MakeEdge(is_flattenable.MakeChild())

    operands = OperandSet.MakeChild()

    def setup(
        self,
        *operands: "F.Parameters.can_be_operand",
        assert_: bool = False,
    ) -> Self:
        self.operands.get().append(*operands)
        if assert_:
            self.is_assertable.get().assert_()
        return self

    @classmethod
    def MakeChild(cls, *operands: fabll.RefPath) -> fabll._ChildField[Self]:
        out = fabll._ChildField(cls)
        for operand in operands:
            out.add_dependant(
                OperandSet.MakeEdge([out, cls.operands], get_operand_path(operand))
            )
        return out

    @classmethod
    def from_operands(
        cls,
        *operands: "F.Parameters.can_be_operand",
        g: graph.GraphView | None = None,
        tg: fbrk.TypeGraph | None = None,
        assert_: bool = False,
    ) -> Self:
        instance = _make_instance_from_operand_instance(cls, operands, g=g, tg=tg)
        return instance.setup(*operands, assert_=assert_)

    @classmethod
    def c(
        cls,
        *operands: "F.Parameters.can_be_operand",
        g: graph.GraphView | None = None,
        tg: fbrk.TypeGraph | None = None,
        assert_: bool = False,
    ) -> "F.Parameters.can_be_operand":
        return _op(cls.from_operands(*operands, g=g, tg=tg, assert_=assert_))


class Not(fabll.Node):
    can_be_operand = fabll.Traits.MakeEdge(F.Parameters.can_be_operand.MakeChild())
    is_parameter_operatable = fabll.Traits.MakeEdge(
        F.Parameters.is_parameter_operatable.MakeChild()
    )
    is_assertable = fabll.Traits.MakeEdge(is_assertable.MakeChild())
    is_expression_type = fabll.Traits.MakeEdge(
        is_expression_type.MakeChild(
            repr_style=is_expression_type.ReprStyle(
                symbol="¬", placement=is_expression_type.ReprStyle.Placement.PREFIX
            )
        ).put_on_type()
    )
    is_expression = fabll.Traits.MakeEdge(is_expression.MakeChild())
    is_logic = fabll.Traits.MakeEdge(is_logic.MakeChild())
    is_canonical = fabll.Traits.MakeEdge(is_canonical.MakeChild())
    is_involutory = fabll.Traits.MakeEdge(is_involutory.MakeChild())

    operand = OperandPointer.MakeChild()

    def setup(
        self, operand: "F.Parameters.can_be_operand", assert_: bool = False
    ) -> Self:
        self.operand.get().point(operand)
        if assert_:
            self.is_assertable.get().assert_()
        return self

    @classmethod
    def MakeChild(cls, operand: fabll.RefPath) -> fabll._ChildField[Self]:
        out = fabll._ChildField(cls)
        out.add_dependant(
            OperandPointer.MakeEdge([out, cls.operand], get_operand_path(operand))
        )
        return out

    @classmethod
    def from_operands(
        cls,
        operand: "F.Parameters.can_be_operand",
        g: graph.GraphView | None = None,
        tg: fbrk.TypeGraph | None = None,
        assert_: bool = False,
    ) -> Self:
        instance = _make_instance_from_operand_instance(cls, (operand,), g=g, tg=tg)
        return instance.setup(operand, assert_=assert_)

    @classmethod
    def c(
        cls,
        operand: "F.Parameters.can_be_operand",
        g: graph.GraphView | None = None,
        tg: fbrk.TypeGraph | None = None,
        assert_: bool = False,
    ) -> "F.Parameters.can_be_operand":
        return _op(cls.from_operands(operand, g=g, tg=tg, assert_=assert_))


class Xor(fabll.Node):
    can_be_operand = fabll.Traits.MakeEdge(F.Parameters.can_be_operand.MakeChild())
    is_parameter_operatable = fabll.Traits.MakeEdge(
        F.Parameters.is_parameter_operatable.MakeChild()
    )
    is_assertable = fabll.Traits.MakeEdge(is_assertable.MakeChild())
    is_expression_type = fabll.Traits.MakeEdge(
        is_expression_type.MakeChild(
            repr_style=is_expression_type.ReprStyle(
                symbol="⊕", placement=is_expression_type.ReprStyle.Placement.INFIX
            )
        ).put_on_type()
    )
    is_expression = fabll.Traits.MakeEdge(is_expression.MakeChild())
    is_logic = fabll.Traits.MakeEdge(is_logic.MakeChild())
    is_flattenable = fabll.Traits.MakeEdge(is_flattenable.MakeChild())
    is_associative = fabll.Traits.MakeEdge(is_associative.MakeChild())

    operands = OperandSequence.MakeChild()

    def setup(
        self,
        *operands: "F.Parameters.can_be_operand",
        assert_: bool = False,
    ) -> Self:
        self.operands.get().append(*operands)
        if assert_:
            self.is_assertable.get().assert_()
        return self

    @classmethod
    def MakeChild(cls, *operands: fabll.RefPath) -> fabll._ChildField[Self]:
        out = fabll._ChildField(cls)
        for i, operand in enumerate(operands):
            out.add_dependant(
                OperandSequence.MakeEdge(
                    [out, cls.operands], get_operand_path(operand), i
                )
            )
        return out

    @classmethod
    def from_operands(
        cls,
        *operands: "F.Parameters.can_be_operand",
        g: graph.GraphView | None = None,
        tg: fbrk.TypeGraph | None = None,
        assert_: bool = False,
    ) -> Self:
        instance = _make_instance_from_operand_instance(cls, operands, g=g, tg=tg)
        return instance.setup(*operands, assert_=assert_)

    @classmethod
    def c(
        cls,
        *operands: "F.Parameters.can_be_operand",
        g: graph.GraphView | None = None,
        tg: fbrk.TypeGraph | None = None,
        assert_: bool = False,
    ) -> "F.Parameters.can_be_operand":
        return _op(cls.from_operands(*operands, g=g, tg=tg, assert_=assert_))


class Implies(fabll.Node):
    can_be_operand = fabll.Traits.MakeEdge(F.Parameters.can_be_operand.MakeChild())
    is_parameter_operatable = fabll.Traits.MakeEdge(
        F.Parameters.is_parameter_operatable.MakeChild()
    )
    is_assertable = fabll.Traits.MakeEdge(is_assertable.MakeChild())
    is_expression_type = fabll.Traits.MakeEdge(
        is_expression_type.MakeChild(
            repr_style=is_expression_type.ReprStyle(
                symbol="⇒", placement=is_expression_type.ReprStyle.Placement.INFIX_FIRST
            )
        ).put_on_type()
    )
    is_expression = fabll.Traits.MakeEdge(is_expression.MakeChild())
    is_logic = fabll.Traits.MakeEdge(is_logic.MakeChild())

    antecedent = OperandPointer.MakeChild()
    consequent = OperandPointer.MakeChild()

    def setup(
        self,
        antecedent: "F.Parameters.can_be_operand",
        consequent: "F.Parameters.can_be_operand",
        assert_: bool = False,
    ) -> Self:
        self.antecedent.get().point(antecedent)
        self.consequent.get().point(consequent)
        if assert_:
            self.is_assertable.get().assert_()
        return self

    @classmethod
    def from_operands(
        cls,
        antecedent: "F.Parameters.can_be_operand",
        consequent: "F.Parameters.can_be_operand",
        g: graph.GraphView | None = None,
        tg: fbrk.TypeGraph | None = None,
        assert_: bool = False,
    ) -> Self:
        instance = _make_instance_from_operand_instance(
            cls, (antecedent, consequent), g=g, tg=tg
        )
        return instance.setup(antecedent, consequent, assert_=assert_)

    @classmethod
    def MakeChild(
        cls, antecedent: fabll.RefPath, consequent: fabll.RefPath
    ) -> fabll._ChildField[Self]:
        out = fabll._ChildField(cls)
        out.add_dependant(
            OperandPointer.MakeEdge([out, cls.antecedent], get_operand_path(antecedent))
        )
        out.add_dependant(
            OperandPointer.MakeEdge([out, cls.consequent], get_operand_path(consequent))
        )
        return out

    @classmethod
    def c(
        cls,
        antecedent: "F.Parameters.can_be_operand",
        consequent: "F.Parameters.can_be_operand",
        g: graph.GraphView | None = None,
        tg: fbrk.TypeGraph | None = None,
        assert_: bool = False,
    ) -> "F.Parameters.can_be_operand":
        return _op(
            cls.from_operands(antecedent, consequent, g=g, tg=tg, assert_=assert_)
        )


class IfThenElse(fabll.Node):
    can_be_operand = fabll.Traits.MakeEdge(F.Parameters.can_be_operand.MakeChild())
    is_parameter_operatable = fabll.Traits.MakeEdge(
        F.Parameters.is_parameter_operatable.MakeChild()
    )
    has_side_effects = fabll.Traits.MakeEdge(has_side_effects.MakeChild())
    is_expression_type = fabll.Traits.MakeEdge(
        is_expression_type.MakeChild(
            repr_style=is_expression_type.ReprStyle(
                symbol="?:", placement=is_expression_type.ReprStyle.Placement.INFIX
            )
        ).put_on_type()
    )
    is_expression = fabll.Traits.MakeEdge(is_expression.MakeChild())

    condition = OperandPointer.MakeChild()
    then_value = OperandPointer.MakeChild()
    else_value = OperandPointer.MakeChild()

    def setup(
        self,
        condition: "F.Parameters.can_be_operand",
        then_value: "F.Parameters.can_be_operand",
        else_value: "F.Parameters.can_be_operand",
    ) -> Self:
        self.condition.get().point(condition)
        self.then_value.get().point(then_value)
        self.else_value.get().point(else_value)
        return self

    @classmethod
    def from_operands(
        cls,
        condition: "F.Parameters.can_be_operand",
        then_value: "F.Parameters.can_be_operand",
        else_value: "F.Parameters.can_be_operand",
        g: graph.GraphView | None = None,
        tg: fbrk.TypeGraph | None = None,
    ) -> Self:
        instance = _make_instance_from_operand_instance(
            cls, (condition, then_value, else_value), g=g, tg=tg
        )
        return instance.setup(condition, then_value, else_value)

    @classmethod
    def c(
        cls,
        condition: "F.Parameters.can_be_operand",
        then_value: "F.Parameters.can_be_operand",
        else_value: "F.Parameters.can_be_operand",
        g: graph.GraphView | None = None,
        tg: fbrk.TypeGraph | None = None,
    ) -> "F.Parameters.can_be_operand":
        return _op(cls.from_operands(condition, then_value, else_value, g=g, tg=tg))

    @classmethod
    def MakeChild(
        cls,
        condition: fabll.RefPath,
        then_value: fabll.RefPath,
        else_value: fabll.RefPath,
    ) -> fabll._ChildField[Self]:
        out = fabll._ChildField(cls)
        out.add_dependant(
            OperandPointer.MakeEdge([out, cls.condition], get_operand_path(condition))
        )
        out.add_dependant(
            OperandPointer.MakeEdge([out, cls.then_value], get_operand_path(then_value))
        )
        out.add_dependant(
            OperandPointer.MakeEdge([out, cls.else_value], get_operand_path(else_value))
        )
        return out

    def try_run(self):
        # TODO
        pass


class LessThan(fabll.Node):
    can_be_operand = fabll.Traits.MakeEdge(F.Parameters.can_be_operand.MakeChild())
    is_parameter_operatable = fabll.Traits.MakeEdge(
        F.Parameters.is_parameter_operatable.MakeChild()
    )
    is_assertable = fabll.Traits.MakeEdge(is_assertable.MakeChild())
    is_expression_type = fabll.Traits.MakeEdge(
        is_expression_type.MakeChild(
            repr_style=is_expression_type.ReprStyle(
                symbol="<", placement=is_expression_type.ReprStyle.Placement.INFIX_FIRST
            )
        ).put_on_type()
    )
    is_expression = fabll.Traits.MakeEdge(is_expression.MakeChild())
    is_logic = fabll.Traits.MakeEdge(is_logic.MakeChild())

    left = OperandPointer.MakeChild()
    right = OperandPointer.MakeChild()

    @classmethod
    def MakeChild(
        cls, lhs: "fabll.RefPath", rhs: "fabll.RefPath", assert_: bool = False
    ) -> fabll._ChildField[Any]:
        out = fabll._ChildField(cls)
        if assert_:
            out.add_dependant(
                fabll.Traits.MakeEdge(is_predicate.MakeChild(), [out]),
            )
        out.add_dependant(
            OperandPointer.MakeEdge([out, cls.left], get_operand_path(lhs))
        )
        out.add_dependant(
            OperandPointer.MakeEdge([out, cls.right], get_operand_path(rhs))
        )
        return out

    def setup(
        self,
        left: "F.Parameters.can_be_operand",
        right: "F.Parameters.can_be_operand",
        assert_: bool = False,
    ) -> Self:
        self.left.get().point(left)
        self.right.get().point(right)
        if assert_:
            self.is_assertable.get().assert_()
        return self

    @classmethod
    def from_operands(
        cls,
        left: "F.Parameters.can_be_operand",
        right: "F.Parameters.can_be_operand",
        g: graph.GraphView | None = None,
        tg: fbrk.TypeGraph | None = None,
        assert_: bool = False,
    ) -> Self:
        instance = _make_instance_from_operand_instance(cls, (left, right), g=g, tg=tg)
        return instance.setup(left, right, assert_=assert_)

    @classmethod
    def c(
        cls,
        left: "F.Parameters.can_be_operand",
        right: "F.Parameters.can_be_operand",
        g: graph.GraphView | None = None,
        tg: fbrk.TypeGraph | None = None,
        assert_: bool = False,
    ) -> "F.Parameters.can_be_operand":
        return _op(cls.from_operands(left, right, g=g, tg=tg, assert_=assert_))


class GreaterThan(fabll.Node):
    can_be_operand = fabll.Traits.MakeEdge(F.Parameters.can_be_operand.MakeChild())
    is_parameter_operatable = fabll.Traits.MakeEdge(
        F.Parameters.is_parameter_operatable.MakeChild()
    )
    is_assertable = fabll.Traits.MakeEdge(is_assertable.MakeChild())
    is_expression_type = fabll.Traits.MakeEdge(
        is_expression_type.MakeChild(
            repr_style=is_expression_type.ReprStyle(
                symbol=">", placement=is_expression_type.ReprStyle.Placement.INFIX_FIRST
            )
        ).put_on_type()
    )
    is_expression = fabll.Traits.MakeEdge(is_expression.MakeChild())
    is_logic = fabll.Traits.MakeEdge(is_logic.MakeChild())
    is_canonical = fabll.Traits.MakeEdge(is_canonical.MakeChild())

    left = OperandPointer.MakeChild()
    right = OperandPointer.MakeChild()

    @classmethod
    def MakeChild(
        cls, lhs: "fabll.RefPath", rhs: "fabll.RefPath", assert_: bool = False
    ) -> fabll._ChildField[Any]:
        out = fabll._ChildField(cls)
        if assert_:
            out.add_dependant(
                fabll.Traits.MakeEdge(is_predicate.MakeChild(), [out]),
            )
        out.add_dependant(
            OperandPointer.MakeEdge([out, cls.left], get_operand_path(lhs))
        )
        out.add_dependant(
            OperandPointer.MakeEdge([out, cls.right], get_operand_path(rhs))
        )
        return out

    def setup(
        self,
        left: "F.Parameters.can_be_operand",
        right: "F.Parameters.can_be_operand",
        assert_: bool = False,
    ) -> Self:
        self.left.get().point(left)
        self.right.get().point(right)
        if assert_:
            self.is_assertable.get().assert_()
        return self

    @classmethod
    def from_operands(
        cls,
        left: "F.Parameters.can_be_operand",
        right: "F.Parameters.can_be_operand",
        g: graph.GraphView | None = None,
        tg: fbrk.TypeGraph | None = None,
        assert_: bool = False,
    ) -> Self:
        instance = _make_instance_from_operand_instance(cls, (left, right), g=g, tg=tg)
        return instance.setup(left, right, assert_=assert_)

    @classmethod
    def c(
        cls,
        left: "F.Parameters.can_be_operand",
        right: "F.Parameters.can_be_operand",
        g: graph.GraphView | None = None,
        tg: fbrk.TypeGraph | None = None,
        assert_: bool = False,
    ) -> "F.Parameters.can_be_operand":
        return _op(cls.from_operands(left, right, g=g, tg=tg, assert_=assert_))


class LessOrEqual(fabll.Node):
    can_be_operand = fabll.Traits.MakeEdge(F.Parameters.can_be_operand.MakeChild())
    is_parameter_operatable = fabll.Traits.MakeEdge(
        F.Parameters.is_parameter_operatable.MakeChild()
    )
    is_assertable = fabll.Traits.MakeEdge(is_assertable.MakeChild())
    is_expression_type = fabll.Traits.MakeEdge(
        is_expression_type.MakeChild(
            repr_style=is_expression_type.ReprStyle(
                symbol="≤", placement=is_expression_type.ReprStyle.Placement.INFIX_FIRST
            )
        ).put_on_type()
    )
    is_expression = fabll.Traits.MakeEdge(is_expression.MakeChild())
    is_logic = fabll.Traits.MakeEdge(is_logic.MakeChild())

    left = OperandPointer.MakeChild()
    right = OperandPointer.MakeChild()

    @classmethod
    def MakeChild(
        cls, lhs: "fabll.RefPath", rhs: "fabll.RefPath", assert_: bool = False
    ) -> fabll._ChildField[Any]:
        out = fabll._ChildField(cls)
        if assert_:
            out.add_dependant(
                fabll.Traits.MakeEdge(is_predicate.MakeChild(), [out]),
            )
        out.add_dependant(
            OperandPointer.MakeEdge([out, cls.left], get_operand_path(lhs))
        )
        out.add_dependant(
            OperandPointer.MakeEdge([out, cls.right], get_operand_path(rhs))
        )
        return out

    def setup(
        self,
        left: "F.Parameters.can_be_operand",
        right: "F.Parameters.can_be_operand",
        assert_: bool = False,
    ) -> Self:
        self.left.get().point(left)
        self.right.get().point(right)
        if assert_:
            self.is_assertable.get().assert_()
        return self

    @classmethod
    def from_operands(
        cls,
        left: "F.Parameters.can_be_operand",
        right: "F.Parameters.can_be_operand",
        g: graph.GraphView | None = None,
        tg: fbrk.TypeGraph | None = None,
        assert_: bool = False,
    ) -> Self:
        instance = _make_instance_from_operand_instance(cls, (left, right), g=g, tg=tg)
        return instance.setup(left, right, assert_=assert_)

    @classmethod
    def c(
        cls,
        left: "F.Parameters.can_be_operand",
        right: "F.Parameters.can_be_operand",
        g: graph.GraphView | None = None,
        tg: fbrk.TypeGraph | None = None,
        assert_: bool = False,
    ) -> "F.Parameters.can_be_operand":
        return _op(cls.from_operands(left, right, g=g, tg=tg, assert_=assert_))


class GreaterOrEqual(fabll.Node):
    can_be_operand = fabll.Traits.MakeEdge(F.Parameters.can_be_operand.MakeChild())
    is_parameter_operatable = fabll.Traits.MakeEdge(
        F.Parameters.is_parameter_operatable.MakeChild()
    )
    is_assertable = fabll.Traits.MakeEdge(is_assertable.MakeChild())
    is_expression_type = fabll.Traits.MakeEdge(
        is_expression_type.MakeChild(
            repr_style=is_expression_type.ReprStyle(
                symbol="≥", placement=is_expression_type.ReprStyle.Placement.INFIX_FIRST
            )
        ).put_on_type()
    )
    is_expression = fabll.Traits.MakeEdge(is_expression.MakeChild())
    is_logic = fabll.Traits.MakeEdge(is_logic.MakeChild())
    is_canonical = fabll.Traits.MakeEdge(is_canonical.MakeChild())
    is_reflexive = fabll.Traits.MakeEdge(is_reflexive.MakeChild())

    left = OperandPointer.MakeChild()
    right = OperandPointer.MakeChild()

    @classmethod
    def MakeChild(
        cls, lhs: "fabll.RefPath", rhs: "fabll.RefPath", assert_: bool = False
    ) -> fabll._ChildField[Any]:
        out = fabll._ChildField(cls)
        if assert_:
            out.add_dependant(
                fabll.Traits.MakeEdge(is_predicate.MakeChild(), [out]),
            )
        out.add_dependant(
            OperandPointer.MakeEdge([out, cls.left], get_operand_path(lhs))
        )
        out.add_dependant(
            OperandPointer.MakeEdge([out, cls.right], get_operand_path(rhs))
        )
        return out

    def setup(
        self,
        left: "F.Parameters.can_be_operand",
        right: "F.Parameters.can_be_operand",
        assert_: bool = False,
    ) -> Self:
        self.left.get().point(left)
        self.right.get().point(right)
        if assert_:
            self.is_assertable.get().assert_()
        return self

    @classmethod
    def from_operands(
        cls,
        left: "F.Parameters.can_be_operand",
        right: "F.Parameters.can_be_operand",
        g: graph.GraphView | None = None,
        tg: fbrk.TypeGraph | None = None,
        assert_: bool = False,
    ) -> Self:
        instance = _make_instance_from_operand_instance(cls, (left, right), g=g, tg=tg)
        return instance.setup(left, right, assert_=assert_)

    @classmethod
    def c(
        cls,
        left: "F.Parameters.can_be_operand",
        right: "F.Parameters.can_be_operand",
        g: graph.GraphView | None = None,
        tg: fbrk.TypeGraph | None = None,
        assert_: bool = False,
    ) -> "F.Parameters.can_be_operand":
        return _op(cls.from_operands(left, right, g=g, tg=tg, assert_=assert_))


class NotEqual(fabll.Node):
    can_be_operand = fabll.Traits.MakeEdge(F.Parameters.can_be_operand.MakeChild())
    is_parameter_operatable = fabll.Traits.MakeEdge(
        F.Parameters.is_parameter_operatable.MakeChild()
    )
    is_assertable = fabll.Traits.MakeEdge(is_assertable.MakeChild())
    is_expression_type = fabll.Traits.MakeEdge(
        is_expression_type.MakeChild(
            repr_style=is_expression_type.ReprStyle(
                symbol="≠", placement=is_expression_type.ReprStyle.Placement.INFIX_FIRST
            )
        ).put_on_type()
    )
    is_expression = fabll.Traits.MakeEdge(is_expression.MakeChild())
    is_logic = fabll.Traits.MakeEdge(is_logic.MakeChild())

    left = OperandPointer.MakeChild()
    right = OperandPointer.MakeChild()

    def setup(
        self,
        left: "F.Parameters.can_be_operand",
        right: "F.Parameters.can_be_operand",
        assert_: bool = False,
    ) -> Self:
        self.left.get().point(left)
        self.right.get().point(right)
        if assert_:
            self.is_assertable.get().assert_()
        return self

    @classmethod
    def MakeChild(
        cls, left: fabll.RefPath, right: fabll.RefPath, assert_: bool = False
    ) -> fabll._ChildField[Any]:
        out = fabll._ChildField(cls)
        if assert_:
            out.add_dependant(
                fabll.Traits.MakeEdge(is_predicate.MakeChild(), [out]),
            )
        out.add_dependant(OperandPointer.MakeEdge([out, cls.left], left))
        out.add_dependant(OperandPointer.MakeEdge([out, cls.right], right))
        return out

    @classmethod
    def from_operands(
        cls,
        left: "F.Parameters.can_be_operand",
        right: "F.Parameters.can_be_operand",
        g: graph.GraphView | None = None,
        tg: fbrk.TypeGraph | None = None,
        assert_: bool = False,
    ) -> Self:
        instance = _make_instance_from_operand_instance(cls, (left, right), g=g, tg=tg)
        return instance.setup(left, right, assert_=assert_)

    @classmethod
    def c(
        cls,
        left: "F.Parameters.can_be_operand",
        right: "F.Parameters.can_be_operand",
        g: graph.GraphView | None = None,
        tg: fbrk.TypeGraph | None = None,
        assert_: bool = False,
    ) -> "F.Parameters.can_be_operand":
        return _op(cls.from_operands(left, right, g=g, tg=tg, assert_=assert_))


class IsBitSet(fabll.Node):
    can_be_operand = fabll.Traits.MakeEdge(F.Parameters.can_be_operand.MakeChild())
    is_parameter_operatable = fabll.Traits.MakeEdge(
        F.Parameters.is_parameter_operatable.MakeChild()
    )
    is_assertable = fabll.Traits.MakeEdge(is_assertable.MakeChild())
    is_expression_type = fabll.Traits.MakeEdge(
        is_expression_type.MakeChild(
            repr_style=is_expression_type.ReprStyle(
                symbol="b[]", placement=is_expression_type.ReprStyle.Placement.INFIX
            )
        ).put_on_type()
    )
    is_expression = fabll.Traits.MakeEdge(is_expression.MakeChild())
    is_logic = fabll.Traits.MakeEdge(is_logic.MakeChild())
    is_canonical = fabll.Traits.MakeEdge(is_canonical.MakeChild())

    value = OperandPointer.MakeChild()
    zbit_index = OperandPointer.MakeChild()

    def setup(
        self,
        value: "F.Parameters.can_be_operand",
        bit_index: "F.Parameters.can_be_operand",
        assert_: bool = False,
    ) -> Self:
        self.value.get().point(value)
        self.zbit_index.get().point(bit_index)
        if assert_:
            self.is_assertable.get().assert_()
        return self

    @classmethod
    def from_operands(
        cls,
        value: "F.Parameters.can_be_operand",
        bit_index: "F.Parameters.can_be_operand",
        g: graph.GraphView | None = None,
        tg: fbrk.TypeGraph | None = None,
        assert_: bool = False,
    ) -> Self:
        instance = _make_instance_from_operand_instance(
            cls, (value, bit_index), g=g, tg=tg
        )
        return instance.setup(value, bit_index, assert_=assert_)

    @classmethod
    def MakeChild(
        cls, value: fabll.RefPath, bit_index: fabll.RefPath
    ) -> fabll._ChildField[Self]:
        out = fabll._ChildField(cls)
        out.add_dependant(
            OperandPointer.MakeEdge([out, cls.value], get_operand_path(value))
        )
        out.add_dependant(
            OperandPointer.MakeEdge([out, cls.zbit_index], get_operand_path(bit_index))
        )
        return out

    @classmethod
    def c(
        cls,
        value: "F.Parameters.can_be_operand",
        bit_index: "F.Parameters.can_be_operand",
        g: graph.GraphView | None = None,
        tg: fbrk.TypeGraph | None = None,
        assert_: bool = False,
    ) -> "F.Parameters.can_be_operand":
        return _op(cls.from_operands(value, bit_index, g=g, tg=tg, assert_=assert_))


class IsSubset(fabll.Node):
    can_be_operand = fabll.Traits.MakeEdge(F.Parameters.can_be_operand.MakeChild())
    is_parameter_operatable = fabll.Traits.MakeEdge(
        F.Parameters.is_parameter_operatable.MakeChild()
    )
    is_assertable = fabll.Traits.MakeEdge(is_assertable.MakeChild())
    is_expression_type = fabll.Traits.MakeEdge(
        is_expression_type.MakeChild(
            repr_style=is_expression_type.ReprStyle(
                symbol="⊆", placement=is_expression_type.ReprStyle.Placement.INFIX_FIRST
            )
        ).put_on_type()
    )
    is_expression = fabll.Traits.MakeEdge(is_expression.MakeChild())
    is_logic = fabll.Traits.MakeEdge(is_logic.MakeChild())
    is_canonical = fabll.Traits.MakeEdge(is_canonical.MakeChild())
    is_reflexive = fabll.Traits.MakeEdge(is_reflexive.MakeChild())
    is_setic = fabll.Traits.MakeEdge(is_setic.MakeChild())

    subset = OperandPointer.MakeChild()
    superset = OperandPointer.MakeChild()

    def get_subset_operand(self) -> "F.Parameters.can_be_operand":
        return self.subset.get().deref().cast(F.Parameters.can_be_operand)

    def get_superset_operand(self) -> "F.Parameters.can_be_operand":
        return self.superset.get().deref().cast(F.Parameters.can_be_operand)

    @classmethod
    def MakeChild(
        cls, subset: fabll.RefPath, superset: fabll.RefPath, assert_: bool = False
    ) -> fabll._ChildField[Any]:
        out = fabll._ChildField(cls)
        if assert_:
            out.add_dependant(
                fabll.Traits.MakeEdge(is_predicate.MakeChild(), [out]),
            )
        out.add_dependant(
            OperandPointer.MakeEdge([out, cls.subset], get_operand_path(subset)),
        )
        out.add_dependant(
            OperandPointer.MakeEdge([out, cls.superset], get_operand_path(superset)),
        )
        return out

    def setup(
        self,
        subset: "F.Parameters.can_be_operand",
        superset: "F.Parameters.can_be_operand",
        assert_: bool = False,
    ) -> Self:
        self.subset.get().point(subset)
        self.superset.get().point(superset)
        if assert_:
            self.is_assertable.get().assert_()
        return self

    @classmethod
    def from_operands(
        cls,
        subset: "F.Parameters.can_be_operand",
        superset: "F.Parameters.can_be_operand",
        g: graph.GraphView | None = None,
        tg: fbrk.TypeGraph | None = None,
        assert_: bool = False,
    ) -> Self:
        instance = _make_instance_from_operand_instance(
            cls, (subset, superset), g=g, tg=tg
        )
        return instance.setup(subset, superset, assert_=assert_)

    @classmethod
    def c(
        cls,
        subset: "F.Parameters.can_be_operand",
        superset: "F.Parameters.can_be_operand",
        g: graph.GraphView | None = None,
        tg: fbrk.TypeGraph | None = None,
        assert_: bool = False,
    ) -> "F.Parameters.can_be_operand":
        return _op(cls.from_operands(subset, superset, g=g, tg=tg, assert_=assert_))


class IsSuperset(fabll.Node):
    can_be_operand = fabll.Traits.MakeEdge(F.Parameters.can_be_operand.MakeChild())
    is_parameter_operatable = fabll.Traits.MakeEdge(
        F.Parameters.is_parameter_operatable.MakeChild()
    )
    is_assertable = fabll.Traits.MakeEdge(is_assertable.MakeChild())
    is_expression_type = fabll.Traits.MakeEdge(
        is_expression_type.MakeChild(
            repr_style=is_expression_type.ReprStyle(
                symbol="⊇", placement=is_expression_type.ReprStyle.Placement.INFIX_FIRST
            )
        ).put_on_type()
    )
    is_expression = fabll.Traits.MakeEdge(is_expression.MakeChild())
    is_logic = fabll.Traits.MakeEdge(is_logic.MakeChild())
    is_setic = fabll.Traits.MakeEdge(is_setic.MakeChild())

    superset = OperandPointer.MakeChild()
    zsubset = OperandPointer.MakeChild()

    def setup(
        self,
        superset: "F.Parameters.can_be_operand",
        subset: "F.Parameters.can_be_operand",
        assert_: bool = False,
    ) -> Self:
        self.superset.get().point(superset)
        self.zsubset.get().point(subset)
        if assert_:
            self.is_assertable.get().assert_()
        return self

    @classmethod
    def MakeChild(
        cls, superset: fabll.RefPath, subset: fabll.RefPath, assert_: bool = False
    ) -> fabll._ChildField[Self]:
        out = fabll._ChildField(cls)
        out.add_dependant(
            OperandPointer.MakeEdge([out, cls.superset], get_operand_path(superset))
        )
        out.add_dependant(
            OperandPointer.MakeEdge([out, cls.zsubset], get_operand_path(subset))
        )
        if assert_:
            out.add_dependant(
                fabll.Traits.MakeEdge(is_predicate.MakeChild(), [out]),
            )
        return out

    @classmethod
    def from_operands(
        cls,
        superset: "F.Parameters.can_be_operand",
        subset: "F.Parameters.can_be_operand",
        g: graph.GraphView | None = None,
        tg: fbrk.TypeGraph | None = None,
        assert_: bool = False,
    ) -> Self:
        instance = _make_instance_from_operand_instance(
            cls, (superset, subset), g=g, tg=tg
        )
        return instance.setup(superset, subset, assert_=assert_)

    @classmethod
    def c(
        cls,
        superset: "F.Parameters.can_be_operand",
        subset: "F.Parameters.can_be_operand",
        g: graph.GraphView | None = None,
        tg: fbrk.TypeGraph | None = None,
        assert_: bool = False,
    ) -> "F.Parameters.can_be_operand":
        return _op(cls.from_operands(superset, subset, g=g, tg=tg, assert_=assert_))


class Cardinality(fabll.Node):
    can_be_operand = fabll.Traits.MakeEdge(F.Parameters.can_be_operand.MakeChild())
    is_parameter_operatable = fabll.Traits.MakeEdge(
        F.Parameters.is_parameter_operatable.MakeChild()
    )
    is_assertable = fabll.Traits.MakeEdge(is_assertable.MakeChild())
    is_expression_type = fabll.Traits.MakeEdge(
        is_expression_type.MakeChild(
            repr_style=is_expression_type.ReprStyle(
                symbol="||", placement=is_expression_type.ReprStyle.Placement.PREFIX
            )
        )
    )
    is_expression = fabll.Traits.MakeEdge(is_expression.MakeChild())
    is_arithmetic = fabll.Traits.MakeEdge(is_arithmetic.MakeChild())

    set = OperandPointer.MakeChild()
    cardinality = OperandPointer.MakeChild()

    def setup(
        self,
        set: "F.Parameters.can_be_operand",
        cardinality: "F.Parameters.can_be_operand",
        assert_: bool = False,
    ) -> Self:
        self.set.get().point(set)
        self.cardinality.get().point(cardinality)
        if assert_:
            self.is_assertable.get().assert_()
        return self

    @classmethod
    def MakeChild(
        cls, set: fabll.RefPath, cardinality: fabll.RefPath
    ) -> fabll._ChildField[Self]:
        out = fabll._ChildField(cls)
        out.add_dependant(
            OperandPointer.MakeEdge([out, cls.set], get_operand_path(set))
        )
        out.add_dependant(
            OperandPointer.MakeEdge(
                [out, cls.cardinality], get_operand_path(cardinality)
            )
        )
        return out

    @classmethod
    def from_operands(
        cls,
        set: "F.Parameters.can_be_operand",
        cardinality: "F.Parameters.can_be_operand",
        g: graph.GraphView | None = None,
        tg: fbrk.TypeGraph | None = None,
        assert_: bool = False,
    ) -> Self:
        instance = _make_instance_from_operand_instance(
            cls, (set, cardinality), g=g, tg=tg
        )
        return instance.setup(set, cardinality, assert_=assert_)

    @classmethod
    def c(
        cls,
        set: "F.Parameters.can_be_operand",
        cardinality: "F.Parameters.can_be_operand",
        g: graph.GraphView | None = None,
        tg: fbrk.TypeGraph | None = None,
        assert_: bool = False,
    ) -> "F.Parameters.can_be_operand":
        return _op(cls.from_operands(set, cardinality, g=g, tg=tg, assert_=assert_))


class Is(fabll.Node):
    can_be_operand = fabll.Traits.MakeEdge(F.Parameters.can_be_operand.MakeChild())
    is_parameter_operatable = fabll.Traits.MakeEdge(
        F.Parameters.is_parameter_operatable.MakeChild()
    )
    is_reflexive = fabll.Traits.MakeEdge(is_reflexive.MakeChild())
    is_assertable = fabll.Traits.MakeEdge(is_assertable.MakeChild())
    is_expression_type = fabll.Traits.MakeEdge(
        is_expression_type.MakeChild(
            repr_style=is_expression_type.ReprStyle(
                symbol="is",
                placement=is_expression_type.ReprStyle.Placement.INFIX_FIRST,
            )
        ).put_on_type()
    )
    is_expression = fabll.Traits.MakeEdge(is_expression.MakeChild())
    is_logic = fabll.Traits.MakeEdge(is_logic.MakeChild())
    is_canonical = fabll.Traits.MakeEdge(is_canonical.MakeChild())
    is_commutative = fabll.Traits.MakeEdge(is_commutative.MakeChild())
    is_setic = fabll.Traits.MakeEdge(is_setic.MakeChild())
    has_idempotent_operands = fabll.Traits.MakeEdge(has_idempotent_operands.MakeChild())

    operands = OperandSet.MakeChild()

    def setup(
        self,
        *operands: "F.Parameters.can_be_operand",
        assert_: bool = False,
    ) -> Self:
        if any(op.try_get_sibling_trait(Literals.is_literal) for op in operands):
            raise ValueError("Is expression cannot have literal operands")
        if not operands:
            raise ValueError("Is expression cannot have no operands")

        self.operands.get().append(*operands)
        if assert_:
            self.is_assertable.get().assert_()
        return self

    @classmethod
    def MakeChild(
        cls, *operands: fabll.RefPath, assert_: bool = False
    ) -> fabll._ChildField[Any]:
        out = fabll._ChildField(cls)
        if assert_:
            out.add_dependant(
                fabll.Traits.MakeEdge(is_predicate.MakeChild(), [out]),
            )
        for operand in operands:
            out.add_dependant(
                OperandSet.MakeEdge([out, cls.operands], get_operand_path(operand)),
            )
        return out

    @classmethod
    def from_operands(
        cls,
        *operands: "F.Parameters.can_be_operand",
        g: graph.GraphView | None = None,
        tg: fbrk.TypeGraph | None = None,
        assert_: bool = False,
    ) -> Self:
        instance = _make_instance_from_operand_instance(cls, operands, g=g, tg=tg)
        return instance.setup(
            *operands,
            assert_=assert_,
        )

    @classmethod
    def c(
        cls,
        *operands: "F.Parameters.can_be_operand",
        g: graph.GraphView | None = None,
        tg: fbrk.TypeGraph | None = None,
        assert_: bool = False,
    ) -> "F.Parameters.can_be_operand":
        return _op(cls.from_operands(*operands, g=g, tg=tg, assert_=assert_))


class Anticorrelated(fabll.Node):
    can_be_operand = fabll.Traits.MakeEdge(F.Parameters.can_be_operand.MakeChild())
    is_parameter_operatable = fabll.Traits.MakeEdge(
        F.Parameters.is_parameter_operatable.MakeChild()
    )
    is_expression_type = fabll.Traits.MakeEdge(
        is_expression_type.MakeChild(
            repr_style=is_expression_type.ReprStyle(
                symbol="~", placement=is_expression_type.ReprStyle.Placement.INFIX
            )
        ).put_on_type()
    )
    is_assertable = fabll.Traits.MakeEdge(is_assertable.MakeChild())
    is_logic = fabll.Traits.MakeEdge(is_logic.MakeChild())
    is_canonical = fabll.Traits.MakeEdge(is_canonical.MakeChild())
    is_commutative = fabll.Traits.MakeEdge(is_commutative.MakeChild())
    is_expression = fabll.Traits.MakeEdge(is_expression.MakeChild())
    is_flattenable = fabll.Traits.MakeEdge(is_flattenable.MakeChild())
    is_reflexive = fabll.Traits.MakeEdge(is_reflexive.MakeChild())
    has_idempotent_operands = fabll.Traits.MakeEdge(has_idempotent_operands.MakeChild())
    has_independent_operands = fabll.Traits.MakeEdge(
        has_independent_operands.MakeChild().put_on_type()
    )
    is_setic = fabll.Traits.MakeEdge(is_setic.MakeChild())

    operands = OperandSet.MakeChild()

    def setup(
        self, *operands: "F.Parameters.can_be_operand", assert_: bool = False
    ) -> Self:
        self.operands.get().append(*operands)
        if assert_:
            self.is_assertable.get().assert_()
        return self

    @classmethod
    def MakeChild(
        cls, *operands: fabll.RefPath, assert_: bool = False
    ) -> fabll._ChildField[Self]:
        out = fabll._ChildField(cls)
        if assert_:
            out.add_dependant(
                fabll.Traits.MakeEdge(is_predicate.MakeChild(), [out]),
            )
        for operand in operands:
            out.add_dependant(
                OperandSet.MakeEdge([out, cls.operands], get_operand_path(operand))
            )
        return out

    @classmethod
    def from_operands(
        cls,
        *operands: "F.Parameters.can_be_operand",
        g: graph.GraphView | None = None,
        tg: fbrk.TypeGraph | None = None,
        assert_: bool = False,
    ) -> Self:
        instance = _make_instance_from_operand_instance(cls, operands, g=g, tg=tg)
        return instance.setup(*operands, assert_=assert_)

    @classmethod
    def c(
        cls,
        *operands: "F.Parameters.can_be_operand",
        g: graph.GraphView | None = None,
        tg: fbrk.TypeGraph | None = None,
        assert_: bool = False,
    ) -> "F.Parameters.can_be_operand":
        return _op(cls.from_operands(*operands, g=g, tg=tg, assert_=assert_))


ExpressionNodes = (
    Add
    | Subtract
    | Multiply
    | Divide
    | Power
    | Sqrt
    | Log
    | Sin
    | Cos
    | Abs
    | Round
    | Floor
    | Ceil
    | Integrate
    | Differentiate
    | And
    | Or
    | Not
    | Xor
    | Implies
    | IfThenElse
    | LessThan
    | GreaterThan
    | LessOrEqual
    | GreaterOrEqual
    | NotEqual
    | IsSubset
    | IsSuperset
    | Cardinality
    | IsBitSet
    | Is
    | Anticorrelated
)

# Macro Expressions --------------------------------------------------------------------


class Mapping:
    @staticmethod
    def operation_switch_case_implications(
        cases: Iterable[tuple["is_expression", "is_expression"]],
    ) -> "is_expression":
        from faebryk.library.Expressions import And, Implies

        return And.from_operands(
            *(
                Implies.from_operands(
                    case.as_operand.get(),
                    impl.as_operand.get(),
                ).can_be_operand.get()
                for case, impl in cases
            ),
        ).is_expression.get()

    @staticmethod
    def operation_switch_case_subset(
        op: "F.Parameters.can_be_operand",
        cases: Iterable[tuple["F.Literals.is_literal", "is_expression"]],
    ) -> "is_expression":
        from faebryk.library.Expressions import IsSubset

        exprs = [
            (
                IsSubset.from_operands(op, lit.as_operand.get()).is_expression.get(),
                case,
            )
            for lit, case in cases
        ]
        return Mapping.operation_switch_case_implications(exprs)

    @staticmethod
    def operation_mapping(
        left: "F.Parameters.can_be_operand",
        right: "F.Parameters.can_be_operand",
        mapping: dict["F.Literals.is_literal", "F.Literals.is_literal"],
    ) -> "is_expression":
        from faebryk.library.Expressions import IsSubset

        exprs = [
            (
                lit_self,
                IsSubset.from_operands(
                    right, lit_other.as_operand.get()
                ).is_expression.get(),
            )
            for lit_self, lit_other in mapping.items()
        ]
        and_expr = Mapping.operation_switch_case_subset(left, exprs)
        and_expr.as_assertable.force_get().assert_()
        return and_expr

    # TODO pass-through g, tg
    @staticmethod
    def from_operands(
        left: "F.Parameters.can_be_operand",
        right: "F.Parameters.can_be_operand",
        mapping: dict["F.Literals.is_literal", "F.Literals.is_literal"],
        g: graph.GraphView | None = None,
        tg: fbrk.TypeGraph | None = None,
        assert_: bool = False,
    ) -> "is_expression":
        out = Mapping.operation_mapping(left, right, mapping=mapping)
        if assert_:
            out.as_assertable.force_get().assert_()
        return out


# Tests --------------------------------------------------------------------------------


def test_repr_style():
    g = graph.GraphView.create()
    tg = fbrk.TypeGraph.create(g=g)
    or_ = Or.bind_typegraph(tg=tg).create_instance(g=g)

    or_repr = or_.is_expression.get().get_repr_style()
    assert or_repr.placement == is_expression_type.ReprStyle.Placement.INFIX
    assert or_repr.symbol == "∨"


def test_compact_repr():
    from faebryk.libs.test.boundexpressions import BoundExpressions

    E = BoundExpressions()

    p1_op = E.bool_parameter_op(name="A")
    p2_op = E.bool_parameter_op(name="B")
    or_ = E.or_(p1_op, p2_op, assert_=True)
    or_repr = (
        or_.as_parameter_operatable.force_get().as_expression.force_get().compact_repr()
    )
    assert or_repr == "A ∨! B"


@pytest.mark.slow
@pytest.mark.skipif(
    sys.platform == "darwin", reason="Memory leak test not supported on macOS"
)
def test_compact_repr_memory_leak():
    import ctypes
    import gc
    import os

    import psutil

    g = graph.GraphView.create()
    tg = fbrk.TypeGraph.create(g=g)

    p1 = F.Parameters.BooleanParameter.bind_typegraph(tg=tg).create_instance(g=g)
    p2 = F.Parameters.BooleanParameter.bind_typegraph(tg=tg).create_instance(g=g)
    or_ = Or.c(
        p1.can_be_operand.get(),
        p2.can_be_operand.get(),
        assert_=True,
    )
    expr = or_.as_parameter_operatable.force_get().as_expression.force_get()
    param = p1.is_parameter.get()

    process = psutil.Process()

    def run(n: int):
        print(f"Running with {n} iterations")
        mem_base = process.memory_info().rss

        for _ in range(n):
            expr.compact_repr()
            param.compact_repr()

        gc.collect()

        if os.name == "posix":
            libc = ctypes.CDLL(None)
            trimmer = getattr(libc, "malloc_trim", None)
            assert trimmer is not None
            trimmer.argtypes = [ctypes.c_size_t]
            trimmer.restype = ctypes.c_int
            trimmer(0)

        mem_end = process.memory_info().rss
        mem_leaked = mem_end - mem_base
        return mem_leaked

    FACTOR = 1000

    # warmup
    for _ in range(5):
        run(FACTOR)
    mem_leaked = {i: run(i * FACTOR) for i in range(1, 5)}
    print(
        "mem leaked mb: ",
        indented_container({k: v / 1024 / 1024 for k, v in mem_leaked.items()}),
    )

    per_n_leak = {i: leaked / (i * FACTOR) for i, leaked in mem_leaked.items()}
    per_n_leak_average = sum(per_n_leak.values()) / len(per_n_leak)
    print(
        "per n leak: ",
        indented_container({k: round(v) for k, v in per_n_leak.items()}),
    )
    print(f"Per n leak average: {per_n_leak_average} bytes")

    assert per_n_leak_average < 16, f"Memory leaked: {per_n_leak_average} bytes"


@pytest.mark.parametrize(
    "expr_type",
    [
        Subtract,
        Divide,
        Power,
        Log,
        Implies,
        LessThan,
        GreaterThan,
        LessOrEqual,
        GreaterOrEqual,
        IsBitSet,
        IsSubset,
        IsSuperset,
    ],
)
def test_operand_order(expr_type: type[ExpressionNodes]):
    from faebryk.libs.test.boundexpressions import BoundExpressions

    E = BoundExpressions()

    arg1 = E.lit_op_single(10)
    arg2 = E.lit_op_single(5)
    expr = expr_type.from_operands(arg1, arg2)  # type: ignore
    assert expr.is_expression.get().get_operands() == [arg1, arg2]


def test_expr_makechild():
    g = graph.GraphView.create()
    tg = fbrk.TypeGraph.create(g=g)
    from faebryk.libs.test.boundexpressions import BoundExpressions

    E = BoundExpressions()

    class _App(fabll.Node):
        op0 = F.Parameters.NumericParameter.MakeChild(unit=E.U.dl)
        op1 = F.Parameters.NumericParameter.MakeChild(unit=E.U.dl)
        op2 = F.Parameters.NumericParameter.MakeChild(unit=E.U.dl)
        expr = Subtract.MakeChild([op0], [op1], [op2])

    app = _App.bind_typegraph(tg=tg).create_instance(g=g)
    sub = app.expr.get()
    ops = [
        op.can_be_operand.get() for op in [app.op0.get(), app.op1.get(), app.op2.get()]
    ]
    assert sub.is_expression.get().get_operands() == ops
    assert sub.minuend.get().deref() == ops[0]
    assert sub.subtrahends.get().as_list() == ops[1:]

    assert app


if __name__ == "__main__":
    import typer

    typer.run(test_operand_order)
