docs: 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))),
      ]))