def Partitions(List):
  """
    Generates all proper partitions of List.
  """
  if len(List)==0:
    yield []
    return
  if len(List)==1:
    return
  
  for A in Subsets( List[:-1] ):
    if len(A) > 0:
      M = List[:-1] 
      for a in A:
        M.remove(a) 
      AA = A.list() + [List[-1]] 
      for r in Partitions(M):
        yield r + [ AA ]
# Partitions


def Classes(Partition):
  """
    Returns a list determining the class
    where the partition belongs.
  """
  m = max([ max(p) for p in Partition])
  Res = range(m + 1)
  
  for P in Partition:
    m = min(P)
    for p in P:
      Res[p] = m
  
  return Res
# Classes


def IsMinPartition(Part, Gamma):
  """
    Returns True if given partition is
    lexicographically minimal in its class.
  """
  X = Classes(Part)
  
  for r in [ GammaPartition(g, Part) for g in Gamma]:
    x = Classes(r)
    if x < X:
      return False
  
  return True
# IsMinPartition


def GammaPartition(Gamma, Partition):
  """
    Applies permutation Gamma on Partition.
  """
  Res = Set([])
  
  for P in Partition:
    P_ = Set([Gamma[r]-1 for r in P])
    Res = Res.union(Set([P_]))
  
  return Res
# GammaPartition


def PartitionClasses(Partitions, Gamma):
  """
    Returns list of classes of partitions.
  """
  Res = []
  for Partition in Partitions:
    if IsMinPartition(Partition, Gamma):
      Res += [ Set([ GammaPartition(g, Partition) for g in Gamma ]) ]
  
  return Res
# PartitionClasses


def NTuples(n):
  """
    Generates all n-tuples of {1, 2, 3, 4}^n.
  """
  if n==0:
    yield []
    return
  
  for t in NTuples(n-1):
    for i in [1, 2, 3, 4]:
      yield t + [i]
# NTuples


def CountTerminalValues(n):
  """
    Counts all values of n terminals with
    - first item = 1,
    - sum of all items = 0.
  """
  if n<=1:
    return 0

  Count = 0
  for t in NTuples(n-2):
    T = [1] + t
    s = Integers(5)(0)
    for i in T:
      s += i
    if s!=0:
      Count += 1

  return Count
# CountTerminalValues


def TerminalValues(n):
  """
    Generates all values of n terminals with
    - first item = 1,
    - sum of all items = 0.
  """
  if n<=1:
    yield []
    return
  
  for t in NTuples(n-2):
    T = [1] + t
    s = Integers(5)(0)
    for i in T:
      s += i
    if s!=0:
      yield T + [-s]
# TerminalValues


def Compatibility(Partition, TerminalValues):
  """
    Returns 1 if the partition is compatible with
    values on terminals.
  """
  for Class in Partition:
    s = Integers(5)(0)
    for Index in Class:
      s += TerminalValues[Index]
    if s!=0:
      return 0

  return 1
# Compatibility


def Chi(PartitionClasses, TerminalValues):
  """
    Returns the vector of compatibility.
  """
  return [sum(Compatibility(P, TerminalValues) for P in Part) for Part in PartitionClasses]
# Chi


def IsFlow(TerminalValues):
  """
    Returns True if there exists a flow
    with given values on terminals.
  """
  val = [ Integers(5)(i) for i in range(1,5) ]
  for Val in TerminalValues:
    val = [ v + Val for v in val if v + Val > 0 ]
  
  return len(val) > 0
# IsFlow


def EncodeTerminalValues(Values):
  """
    Encodes values of terminals into one number.
  """
  if len(Values) == 1:
    return Integer(Values[-1] - 1)
  
  return 4 * EncodeTerminalValues(Values[:-1]) + Integer(Values[-1] - 1)
# EncodeTerminalValues


def GammaValues(Values, Gamma):
  """
    Applies permutation Gamma on Values.
  """
  Res = []
  for i in range(len(Values)):
    Res += [ Values[Gamma[i] - 1] ]
  
  s = 1/Integers(5)(Res[0])
  if s != 1:
    return [ i * s for i in Res]
  
  return Res
# GammaValues


def Generate(n, Buffer, Verbose, Save=True, Type=3):
  """
    Generates the matrices M_n and M'_n and
    counts their ranks.
    If some matrix is too long the rank is computed
    by parts with matrix buffer of given size.
    
    The parameter Verbose determines the number
    of rows after that a progress message is printed.
    The parameter Save determines whether the result
    matrices are saved on disk.
    The parameter Type determines which matrices
    are generated: 1 - M_n, 2 - M'_n, 3 - both.
  """
  from datetime import datetime
  
  print datetime.now().strftime('%Y-%m-%d %H:%M:%S'), "initiating..."
  
  G = DihedralGroup(n)
  Gamma = [ g.tuple() for g in G ]
  
  PartCls = PartitionClasses(Partitions(range(n)), Gamma)
  PartSml = [ range(n-2), [ n-2, n-1 ] ]
  
  Count = CountTerminalValues(n)
  Vals = TerminalValues(n)
  
  Used = []
  for i in range(4^(n-1)):
    Used += [ False ]
  
  Mn = []
  MMn = []
  rn = 0
  rrn = 0
  proc = 0
  verb = 0
  
  print datetime.now().strftime('%Y-%m-%d %H:%M:%S'), "processing terminal values..."
  
  for H in Vals:
    proc += 1
    verb += 1
    
    if verb >= Verbose:
      print datetime.now().strftime('%Y-%m-%d %H:%M:%S'), "processed", proc, "of", Count, "...", (100 * proc/Count).round(), "%"
      verb = 0
    
    if Used[EncodeTerminalValues(H)]:
      continue
    
    Used[EncodeTerminalValues(H)] = True
    
    if IsFlow(H):
      X = Chi(PartCls, H)
      for G in Gamma:
        Used[EncodeTerminalValues(GammaValues(H, G))] = True
      
      if Type & 1 == 1:
        Mn += [ X ]
        rn += 1
        if rn >= Buffer:
          print datetime.now().strftime('%Y-%m-%d %H:%M:%S'), "processed", proc, "of", Count, "...", (100 * proc/Count).round(), "%"
          print datetime.now().strftime('%Y-%m-%d %H:%M:%S'), "buffer for Mn overflow (", rn, ")..."
          M = Matrix(QQ, Mn)
          rn = M.rank()
          P, L, U = M.LU()
          Mn = [ U[i].list() for i in range(rn) ]
          print datetime.now().strftime('%Y-%m-%d %H:%M:%S'), "buffer for Mn completed (", rn, ")"
      
      if Type & 2 == 2:
        MMn += [ X ]
        rrn += 1
        if rrn >= Buffer:
          print datetime.now().strftime('%Y-%m-%d %H:%M:%S'), "processed", proc, "of", Count, "...", (100 * proc/Count).round(), "%"
          print datetime.now().strftime('%Y-%m-%d %H:%M:%S'), "buffer for MMn overflow (", rrn, ")..."
          M = Matrix(QQ, MMn)
          rrn = M.rank()
          P, L, U = M.LU()
          MMn = [ U[i].list() for i in range(rrn) ]
          print datetime.now().strftime('%Y-%m-%d %H:%M:%S'), "buffer for MMn completed (", rrn, ")"
      
      continue
    
    if (Compatibility(PartSml, H) == 1) and IsFlow(H[:-2]):
      X = Chi(PartCls, H)
      for G in Gamma:
        Used[EncodeTerminalValues(GammaValues(H, G))] = True
      
      if Type & 2 == 2:
        MMn += [ X ]
        rrn += 1
        if rrn >= Buffer:
          print datetime.now().strftime('%Y-%m-%d %H:%M:%S'), "processed", proc, "of", Count, "...", (100 * proc/Count).round(), "%"
          print datetime.now().strftime('%Y-%m-%d %H:%M:%S'), "buffer for MMn overflow (", rrn, ")..."
          M = Matrix(QQ, MMn)
          rrn = M.rank()
          P, L, U = M.LU()
          MMn = [ U[i].list() for i in range(rrn) ]
          print datetime.now().strftime('%Y-%m-%d %H:%M:%S'), "buffer for MMn completed (", rrn, ")"
  
  print datetime.now().strftime('%Y-%m-%d %H:%M:%S'), "processing terminal values completed"
  
  Mn  = Matrix(Mn)
  MMn = Matrix(MMn)
  
  if Save:
    print datetime.now().strftime('%Y-%m-%d %H:%M:%S'), "saving the matrices..."
    
    s = "saves/advanced/" + datetime.now().strftime('%Y-%m-%d_%H-%M-%S') + "_" + n.str() + "_"                   
    if Type & 1 == 1:
      save(Mn, s + "1")
    if Type & 2 == 2:
      save(MMn, s + "2")
  
  print datetime.now().strftime('%Y-%m-%d %H:%M:%S'), "counting the ranks..."
  
  rn  = Mn.rank()
  rrn = MMn.rank()
  
  print datetime.now().strftime('%Y-%m-%d %H:%M:%S'), "counting the ranks completed"
  
  return [ n, rn, rrn ]
# Generate