Set the default algebraic rules for the operations defined in this
    module

def _algebraic_rules_state():
    """Set the default algebraic rules for the operations defined in this
    module"""
    u = wc("u", head=SCALAR_TYPES)
    v = wc("v", head=SCALAR_TYPES)

    n = wc("n", head=(int, str, SymbolicLabelBase))
    m = wc("m", head=(int, str, SymbolicLabelBase))
    k = wc("k", head=(int, str, SymbolicLabelBase))

    A = wc("A", head=Operator)
    A__ = wc("A__", head=Operator)
    B = wc("B", head=Operator)

    A_times = wc("A", head=OperatorTimes)
    A_local = wc("A", head=LocalOperator)
    B_local = wc("B", head=LocalOperator)

    Psi_sym = wc("Psi", head=KetSymbol)
    Psi = wc("Psi", head=State)
    Phi = wc("Phi", head=State)
    Psi_local = wc("Psi", head=LocalKet)
    Psi_tensor = wc("Psi", head=TensorKet)
    Phi_tensor = wc("Phi", head=TensorKet)

    ls = wc("ls", head=LocalSpace)

    basisket = wc('basisket', BasisKet, kwargs={'hs': ls})
    ket_a = wc('a', BasisKet)
    ket_b = wc('b', BasisKet)

    indranges__ = wc("indranges__", head=IndexRangeBase)
    sum = wc('sum', head=KetIndexedSum)
    sum2 = wc('sum2', head=KetIndexedSum)

    ScalarTimesKet._rules.update(check_rules_dict([
        ('R001', (
            pattern_head(1, Psi),
            lambda Psi: Psi)),
        ('R002', (
            pattern_head(0, Psi),
            lambda Psi: ZeroKet)),
        ('R003', (
            pattern_head(u, ZeroKet),
            lambda u: ZeroKet)),
        ('R004', (
            pattern_head(u, pattern(ScalarTimesKet, v, Psi)),
            lambda u, v, Psi: (u * v) * Psi))
    ]))

    def local_rule(A, B, Psi):
        return OperatorTimes.create(*A) * (B * Psi)

    OperatorTimesKet._rules.update(check_rules_dict([
        ('R001', (  # Id * Psi = Psi
            pattern_head(IdentityOperator, Psi),
            lambda Psi: Psi)),
        ('R002', (  # 0 * Psi = 0
            pattern_head(ZeroOperator, Psi),
            lambda Psi: ZeroKet)),
        ('R003', (  # A * 0 = 0
            pattern_head(A, ZeroKet),
            lambda A: ZeroKet)),
        ('R004', (  # A * v * Psi = v * A * Psi (pull out scalar)
            pattern_head(A, pattern(ScalarTimesKet, v, Psi)),
            lambda A, v, Psi:  v * (A * Psi))),

        ('R005', (  # |n><m| * |k> = delta_mk * |n>
            pattern_head(
                pattern(LocalSigma, n, m, hs=ls),
                pattern(BasisKet, k, hs=ls)),
            lambda ls, n, m, k:
                KroneckerDelta(
                    BasisKet(m, hs=ls).index, BasisKet(k, hs=ls).index) *
                BasisKet(n, hs=ls))),

        # harmonic oscillator
        ('R006', (  # a^+ |n> = sqrt(n+1) * |n+1>
            pattern_head(pattern(Create, hs=ls), basisket),
            lambda basisket, ls:
                sqrt(basisket.index + 1) * basisket.next())),
        ('R007', (  # a |n> = sqrt(n) * |n-1>
            pattern_head(pattern(Destroy, hs=ls), basisket),
            lambda basisket, ls:
                sqrt(basisket.index) * basisket.prev())),
        ('R008', (  # a |alpha> = alpha * |alpha> (eigenstate of annihilator)
            pattern_head(
                pattern(Destroy, hs=ls),
                pattern(CoherentStateKet, u, hs=ls)),
            lambda ls, u: u * CoherentStateKet(u, hs=ls))),

        # spin
        ('R009', (
            pattern_head(pattern(Jplus, hs=ls), basisket),
            lambda basisket, ls:
                Jpjmcoeff(basisket.space, basisket.index, shift=True) *
                basisket.next())),
        ('R010', (
            pattern_head(pattern(Jminus, hs=ls), basisket),
            lambda basisket, ls:
                Jmjmcoeff(basisket.space, basisket.index, shift=True) *
                basisket.prev())),
        ('R011', (
            pattern_head(pattern(Jz, hs=ls), basisket),
            lambda basisket, ls:
                Jzjmcoeff(basisket.space, basisket.index, shift=True) *
                basisket)),

        ('R012', (
            pattern_head(A_local, Psi_tensor),
            lambda A, Psi: act_locally(A, Psi))),
        ('R013', (
            pattern_head(A_times, Psi_tensor),
            lambda A, Psi: act_locally_times_tensor(A, Psi))),
        ('R014', (
            pattern_head(A, pattern(OperatorTimesKet, B, Psi)),
            lambda A, B, Psi: (
                (A * B) * Psi
                if (B * Psi) == OperatorTimesKet(B, Psi)
                else A * (B * Psi)))),
        ('R015', (
            pattern_head(pattern(OperatorTimes, A__, B_local), Psi_local),
            local_rule)),
        ('R016', (
            pattern_head(pattern(ScalarTimesOperator, u, A), Psi),
            lambda u, A, Psi: u * (A * Psi))),
        ('R017', (
            pattern_head(
                pattern(Displace, u, hs=ls),
                pattern(BasisKet, 0, hs=ls)),
            lambda ls, u: CoherentStateKet(u, hs=ls))),
        ('R018', (
            pattern_head(
                pattern(Displace, u, hs=ls),
                pattern(CoherentStateKet, v, hs=ls)),
            lambda ls, u, v:
                ((Displace(u, hs=ls) * Displace(v, hs=ls)) *
                 BasisKet(0, hs=ls)))),
        ('R019', (
            pattern_head(
                pattern(Phase, u, hs=ls), pattern(BasisKet, m, hs=ls)),
            lambda ls, u, m: exp(I * u * m) * BasisKet(m, hs=ls))),
        ('R020', (
            pattern_head(
                pattern(Phase, u, hs=ls),
                pattern(CoherentStateKet, v, hs=ls)),
            lambda ls, u, v: CoherentStateKet(v * exp(I * u), hs=ls))),

        ('R021', (
            pattern_head(A, sum),
            lambda A, sum: KetIndexedSum.create(A * sum.term, *sum.ranges))),
    ]))

    TensorKet._binary_rules.update(check_rules_dict([
        ('R001', (
            pattern_head(pattern(ScalarTimesKet, u, Psi), Phi),
            lambda u, Psi, Phi: u * (Psi * Phi))),
        ('R002', (
            pattern_head(Psi, pattern(ScalarTimesKet, u, Phi)),
            lambda Psi, u, Phi: u * (Psi * Phi))),
        ('R003', (  # delegate to __mul__
            pattern_head(sum, sum2),
            lambda sum, sum2: sum * sum2)),
        ('R004', (  # delegate to __mul__
            pattern_head(Psi, sum),
            lambda Psi, sum: Psi * sum)),
        ('R005', (  # delegate to __mul__
            pattern_head(sum, Psi),
            lambda sum, Psi: sum * Psi)),
    ]))

    BraKet._rules.update(check_rules_dict([
        # All rules must result in scalars or objects in the TrivialSpace
        ('R001', (
            pattern_head(Phi, ZeroKet),
            lambda Phi: Zero)),
        ('R002', (
            pattern_head(ZeroKet, Phi),
            lambda Phi: Zero)),
        ('R003', (
            pattern_head(ket_a, ket_b),
            lambda a, b: KroneckerDelta(a.index, b.index))),
        ('R004', (
            pattern_head(Psi_sym, Psi_sym),
            lambda Psi: One)),
            # we're assuming every KetSymbol is normalized. If we ever want
            # to allow non-normalized states, the best thing to dou would be to
            # add a `norm` attribute
        ('R005', (
            pattern_head(Psi_tensor, Phi_tensor),
            lambda Psi, Phi: tensor_decompose_kets(Psi, Phi, BraKet.create))),
        ('R006', (
            pattern_head(pattern(ScalarTimesKet, u, Psi), Phi),
            lambda u, Psi, Phi: u.conjugate() * (Psi.adjoint() * Phi))),
        ('R007', (
            pattern_head(pattern(OperatorTimesKet, A, Psi), Phi),
            lambda A, Psi, Phi: (Psi.adjoint() * (A.dag() * Phi)))),
        ('R008', (
            pattern_head(Psi, pattern(ScalarTimesKet, u, Phi)),
            lambda Psi, u, Phi: u * (Psi.adjoint() * Phi))),
        ('R009', (  # delegate to __mul__
            pattern_head(sum, sum2),
            lambda sum, sum2: Bra.create(sum) * sum2)),
        ('R010', (  # delegate to __mul__
            pattern_head(Psi, sum),
            lambda Psi, sum: Bra.create(Psi) * sum)),
        ('R011', (  # delegate to __mul__
            pattern_head(sum, Psi),
            lambda sum, Psi: Bra.create(sum) * Psi)),
    ]))

    KetBra._rules.update(check_rules_dict([
        ('R001', (
            pattern_head(
                pattern(BasisKet, m, hs=ls),
                pattern(BasisKet, n, hs=ls)),
            lambda ls, m, n: LocalSigma(m, n, hs=ls))),
        ('R002', (
            pattern_head(pattern(CoherentStateKet, u, hs=ls), Phi),
            lambda ls, u, Phi: (
                Displace(u, hs=ls) * (BasisKet(0, hs=ls) * Phi.adjoint())))),
        ('R003', (
            pattern_head(Phi, pattern(CoherentStateKet, u, hs=ls)),
            lambda ls, u, Phi: (
                (Phi * BasisKet(0, hs=ls).adjoint()) * Displace(-u, hs=ls)))),
        ('R004', (
            pattern_head(Psi_tensor, Phi_tensor),
            lambda Psi, Phi: tensor_decompose_kets(Psi, Phi, KetBra.create))),
        ('R005', (
            pattern_head(pattern(OperatorTimesKet, A, Psi), Phi),
            lambda A, Psi, Phi: A * (Psi * Phi.adjoint()))),
        ('R006', (
            pattern_head(Psi, pattern(OperatorTimesKet, A, Phi)),
            lambda Psi, A, Phi: (Psi * Phi.adjoint()) * A.adjoint())),
        ('R007', (
            pattern_head(pattern(ScalarTimesKet, u, Psi), Phi),
            lambda u, Psi, Phi: u * (Psi * Phi.adjoint()))),
        ('R008', (
            pattern_head(Psi, pattern(ScalarTimesKet, u, Phi)),
            lambda Psi, u, Phi: u.conjugate() * (Psi * Phi.adjoint()))),
        ('R009', (  # delegate to __mul__
            pattern_head(sum, sum2),
            lambda sum, sum2: sum * Bra.create(sum2))),
        ('R010', (  # delegate to __mul__
            pattern_head(Psi, sum),
            lambda Psi, sum: Psi * Bra.create(sum))),
        ('R011', (  # delegate to __mul__
            pattern_head(sum, Psi),
            lambda sum, Psi: sum * Bra.create(Psi))),
    ]))

    def pull_constfactor_from_sum(u, Psi, indranges):
        bound_symbols = set([r.index_symbol for r in indranges])
        if len(u.free_symbols.intersection(bound_symbols)) == 0:
            return u * KetIndexedSum.create(Psi, *indranges)
        else:
            raise CannotSimplify()

    KetIndexedSum._rules.update(check_rules_dict([
        ('R001', (  # sum over zero -> zero
            pattern_head(ZeroKet, indranges__),
            lambda indranges: ZeroKet)),
        ('R002', (  # pull constant prefactor out of sum
            pattern_head(pattern(ScalarTimesKet, u, Psi), indranges__),
            lambda u, Psi, indranges:
                pull_constfactor_from_sum(u, Psi, indranges))),
    ]))