schur_number.py 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. """
  2. The Schur number S(k) is the largest integer n for which the interval [1,n]
  3. can be partitioned into k sum-free sets.(https://mathworld.wolfram.com/SchurNumber.html)
  4. """
  5. import math
  6. from sympy.core import S
  7. from sympy.core.basic import Basic
  8. from sympy.core.function import Function
  9. from sympy.core.numbers import Integer
  10. class SchurNumber(Function):
  11. r"""
  12. This function creates a SchurNumber object
  13. which is evaluated for `k \le 5` otherwise only
  14. the lower bound information can be retrieved.
  15. Examples
  16. ========
  17. >>> from sympy.combinatorics.schur_number import SchurNumber
  18. Since S(3) = 13, hence the output is a number
  19. >>> SchurNumber(3)
  20. 13
  21. We do not know the Schur number for values greater than 5, hence
  22. only the object is returned
  23. >>> SchurNumber(6)
  24. SchurNumber(6)
  25. Now, the lower bound information can be retrieved using lower_bound()
  26. method
  27. >>> SchurNumber(6).lower_bound()
  28. 536
  29. """
  30. @classmethod
  31. def eval(cls, k):
  32. if k.is_Number:
  33. if k is S.Infinity:
  34. return S.Infinity
  35. if k.is_zero:
  36. return S.Zero
  37. if not k.is_integer or k.is_negative:
  38. raise ValueError("k should be a positive integer")
  39. first_known_schur_numbers = {1: 1, 2: 4, 3: 13, 4: 44, 5: 160}
  40. if k <= 5:
  41. return Integer(first_known_schur_numbers[k])
  42. def lower_bound(self):
  43. f_ = self.args[0]
  44. # Improved lower bounds known for S(6) and S(7)
  45. if f_ == 6:
  46. return Integer(536)
  47. if f_ == 7:
  48. return Integer(1680)
  49. # For other cases, use general expression
  50. if f_.is_Integer:
  51. return 3*self.func(f_ - 1).lower_bound() - 1
  52. return (3**f_ - 1)/2
  53. def _schur_subsets_number(n):
  54. if n is S.Infinity:
  55. raise ValueError("Input must be finite")
  56. if n <= 0:
  57. raise ValueError("n must be a non-zero positive integer.")
  58. elif n <= 3:
  59. min_k = 1
  60. else:
  61. min_k = math.ceil(math.log(2*n + 1, 3))
  62. return Integer(min_k)
  63. def schur_partition(n):
  64. """
  65. This function returns the partition in the minimum number of sum-free subsets
  66. according to the lower bound given by the Schur Number.
  67. Parameters
  68. ==========
  69. n: a number
  70. n is the upper limit of the range [1, n] for which we need to find and
  71. return the minimum number of free subsets according to the lower bound
  72. of schur number
  73. Returns
  74. =======
  75. List of lists
  76. List of the minimum number of sum-free subsets
  77. Notes
  78. =====
  79. It is possible for some n to make the partition into less
  80. subsets since the only known Schur numbers are:
  81. S(1) = 1, S(2) = 4, S(3) = 13, S(4) = 44.
  82. e.g for n = 44 the lower bound from the function above is 5 subsets but it has been proven
  83. that can be done with 4 subsets.
  84. Examples
  85. ========
  86. For n = 1, 2, 3 the answer is the set itself
  87. >>> from sympy.combinatorics.schur_number import schur_partition
  88. >>> schur_partition(2)
  89. [[1, 2]]
  90. For n > 3, the answer is the minimum number of sum-free subsets:
  91. >>> schur_partition(5)
  92. [[3, 2], [5], [1, 4]]
  93. >>> schur_partition(8)
  94. [[3, 2], [6, 5, 8], [1, 4, 7]]
  95. """
  96. if isinstance(n, Basic) and not n.is_Number:
  97. raise ValueError("Input value must be a number")
  98. number_of_subsets = _schur_subsets_number(n)
  99. if n == 1:
  100. sum_free_subsets = [[1]]
  101. elif n == 2:
  102. sum_free_subsets = [[1, 2]]
  103. elif n == 3:
  104. sum_free_subsets = [[1, 2, 3]]
  105. else:
  106. sum_free_subsets = [[1, 4], [2, 3]]
  107. while len(sum_free_subsets) < number_of_subsets:
  108. sum_free_subsets = _generate_next_list(sum_free_subsets, n)
  109. missed_elements = [3*k + 1 for k in range(len(sum_free_subsets), (n-1)//3 + 1)]
  110. sum_free_subsets[-1] += missed_elements
  111. return sum_free_subsets
  112. def _generate_next_list(current_list, n):
  113. new_list = []
  114. for item in current_list:
  115. temp_1 = [number*3 for number in item if number*3 <= n]
  116. temp_2 = [number*3 - 1 for number in item if number*3 - 1 <= n]
  117. new_item = temp_1 + temp_2
  118. new_list.append(new_item)
  119. last_list = [3*k + 1 for k in range(len(current_list)+1) if 3*k + 1 <= n]
  120. new_list.append(last_list)
  121. current_list = new_list
  122. return current_list