Return a new simplified expression in canonical form from this
        expression.

        For simplification of AND and OR fthe ollowing rules are used
        recursively bottom up:
         - Associativity (output does not contain same operations nested)
         - Annihilation
         - Idempotence
         - Identity
         - Complementation
         - Elimination
         - Absorption
         - Commutativity (output is always sorted)

        Other boolean objects are also in their canonical form.

def simplify(self):
        """
        Return a new simplified expression in canonical form from this
        expression.

        For simplification of AND and OR fthe ollowing rules are used
        recursively bottom up:
         - Associativity (output does not contain same operations nested)
         - Annihilation
         - Idempotence
         - Identity
         - Complementation
         - Elimination
         - Absorption
         - Commutativity (output is always sorted)

        Other boolean objects are also in their canonical form.
        """
        # TODO: Refactor DualBase.simplify into different "sub-evals".

        # If self is already canonical do nothing.
        if self.iscanonical:
            return self

        # Otherwise bring arguments into canonical form.
        args = [arg.simplify() for arg in self.args]

        # Create new instance of own class with canonical args.
        # TODO: Only create new class if some args changed.
        expr = self.__class__(*args)

        # Literalize before doing anything, this also applies De Morgan's Law
        expr = expr.literalize()

        # Associativity:
        #     (A & B) & C = A & (B & C) = A & B & C
        #     (A | B) | C = A | (B | C) = A | B | C
        expr = expr.flatten()

        # Annihilation: A & 0 = 0, A | 1 = 1
        if self.annihilator in expr.args:
            return self.annihilator

        # Idempotence: A & A = A, A | A = A
        # this boils down to removing duplicates
        args = []
        for arg in expr.args:
            if arg not in args:
                args.append(arg)
        if len(args) == 1:
            return args[0]

        # Identity: A & 1 = A, A | 0 = A
        if self.identity in args:
            args.remove(self.identity)
            if len(args) == 1:
                return args[0]

        # Complementation: A & ~A = 0, A | ~A = 1
        for arg in args:
            if self.NOT(arg) in args:
                return self.annihilator

        # Elimination: (A & B) | (A & ~B) = A, (A | B) & (A | ~B) = A
        i = 0
        while i < len(args) - 1:
            j = i + 1
            ai = args[i]
            if not isinstance(ai, self.dual):
                i += 1
                continue
            while j < len(args):
                aj = args[j]
                if not isinstance(aj, self.dual) or len(ai.args) != len(aj.args):
                    j += 1
                    continue

                # Find terms where only one arg is different.
                negated = None
                for arg in ai.args:
                    # FIXME: what does this pass Do?
                    if arg in aj.args:
                        pass
                    elif self.NOT(arg).cancel() in aj.args:
                        if negated is None:
                            negated = arg
                        else:
                            negated = None
                            break
                    else:
                        negated = None
                        break

                # If the different arg is a negation simplify the expr.
                if negated is not None:
                    # Cancel out one of the two terms.
                    del args[j]
                    aiargs = list(ai.args)
                    aiargs.remove(negated)
                    if len(aiargs) == 1:
                        args[i] = aiargs[0]
                    else:
                        args[i] = self.dual(*aiargs)

                    if len(args) == 1:
                        return args[0]
                    else:
                        # Now the other simplifications have to be redone.
                        return self.__class__(*args).simplify()
                j += 1
            i += 1

        # Absorption: A & (A | B) = A, A | (A & B) = A
        # Negative absorption: A & (~A | B) = A & B, A | (~A & B) = A | B
        args = self.absorb(args)
        if len(args) == 1:
            return args[0]

        # Commutativity: A & B = B & A, A | B = B | A
        args.sort()

        # Create new (now canonical) expression.
        expr = self.__class__(*args)
        expr.iscanonical = True
        return expr