mathematicskit 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (305) hide show
  1. mathematicskit-0.1.0.dist-info/METADATA +201 -0
  2. mathematicskit-0.1.0.dist-info/RECORD +305 -0
  3. mathematicskit-0.1.0.dist-info/WHEEL +5 -0
  4. mathematicskit-0.1.0.dist-info/licenses/LICENSE +21 -0
  5. mathematicskit-0.1.0.dist-info/top_level.txt +1 -0
  6. mathkit/__init__.py +50 -0
  7. mathkit/abstract_algebra/__init__.py +43 -0
  8. mathkit/abstract_algebra/core/base.py +253 -0
  9. mathkit/abstract_algebra/systems/__init__.py +23 -0
  10. mathkit/abstract_algebra/systems/finite_fields.py +201 -0
  11. mathkit/abstract_algebra/systems/groups.py +162 -0
  12. mathkit/abstract_algebra/systems/polynomial_ring.py +132 -0
  13. mathkit/abstract_algebra/systems/subgroups.py +144 -0
  14. mathkit/abstract_algebra/tests/__init__.py +0 -0
  15. mathkit/abstract_algebra/tests/test_finite_fields.py +71 -0
  16. mathkit/abstract_algebra/tests/test_groups.py +84 -0
  17. mathkit/abstract_algebra/tests/test_polynomial_ring.py +79 -0
  18. mathkit/abstract_algebra/tests/test_subgroups.py +59 -0
  19. mathkit/abstract_algebra/tests/test_visualizers.py +15 -0
  20. mathkit/abstract_algebra/utils/__init__.py +5 -0
  21. mathkit/abstract_algebra/utils/checks.py +35 -0
  22. mathkit/abstract_algebra/visualizers/__init__.py +5 -0
  23. mathkit/abstract_algebra/visualizers/plots.py +31 -0
  24. mathkit/calculus/__init__.py +48 -0
  25. mathkit/calculus/core/__init__.py +5 -0
  26. mathkit/calculus/core/base.py +80 -0
  27. mathkit/calculus/systems/__init__.py +27 -0
  28. mathkit/calculus/systems/autodiff.py +160 -0
  29. mathkit/calculus/systems/dual_numbers.py +132 -0
  30. mathkit/calculus/systems/finite_differences.py +141 -0
  31. mathkit/calculus/systems/quadrature.py +184 -0
  32. mathkit/calculus/systems/taylor_series.py +180 -0
  33. mathkit/calculus/tests/__init__.py +0 -0
  34. mathkit/calculus/tests/test_autodiff.py +51 -0
  35. mathkit/calculus/tests/test_dual_numbers.py +48 -0
  36. mathkit/calculus/tests/test_finite_differences.py +47 -0
  37. mathkit/calculus/tests/test_quadrature.py +61 -0
  38. mathkit/calculus/tests/test_taylor_series.py +53 -0
  39. mathkit/calculus/tests/test_visualizers.py +23 -0
  40. mathkit/calculus/utils/__init__.py +5 -0
  41. mathkit/calculus/utils/series_utils.py +64 -0
  42. mathkit/calculus/visualizers/__init__.py +5 -0
  43. mathkit/calculus/visualizers/plots.py +77 -0
  44. mathkit/combinatorics/__init__.py +43 -0
  45. mathkit/combinatorics/core/__init__.py +5 -0
  46. mathkit/combinatorics/core/base.py +85 -0
  47. mathkit/combinatorics/systems/__init__.py +23 -0
  48. mathkit/combinatorics/systems/counting.py +153 -0
  49. mathkit/combinatorics/systems/inclusion_exclusion.py +97 -0
  50. mathkit/combinatorics/systems/partitions.py +109 -0
  51. mathkit/combinatorics/systems/pascals_triangle.py +47 -0
  52. mathkit/combinatorics/systems/special_numbers.py +130 -0
  53. mathkit/combinatorics/tests/__init__.py +0 -0
  54. mathkit/combinatorics/tests/test_counting.py +50 -0
  55. mathkit/combinatorics/tests/test_inclusion_exclusion.py +44 -0
  56. mathkit/combinatorics/tests/test_partitions.py +46 -0
  57. mathkit/combinatorics/tests/test_pascals_triangle.py +24 -0
  58. mathkit/combinatorics/tests/test_special_numbers.py +56 -0
  59. mathkit/combinatorics/tests/test_visualizers.py +26 -0
  60. mathkit/combinatorics/utils/__init__.py +5 -0
  61. mathkit/combinatorics/utils/bell_number.py +35 -0
  62. mathkit/combinatorics/visualizers/__init__.py +5 -0
  63. mathkit/combinatorics/visualizers/plots.py +91 -0
  64. mathkit/constants.py +55 -0
  65. mathkit/fractals_chaos/__init__.py +41 -0
  66. mathkit/fractals_chaos/core/__init__.py +5 -0
  67. mathkit/fractals_chaos/core/base.py +103 -0
  68. mathkit/fractals_chaos/systems/__init__.py +20 -0
  69. mathkit/fractals_chaos/systems/box_counting.py +85 -0
  70. mathkit/fractals_chaos/systems/cellular_automata.py +135 -0
  71. mathkit/fractals_chaos/systems/ifs.py +106 -0
  72. mathkit/fractals_chaos/systems/lyapunov.py +125 -0
  73. mathkit/fractals_chaos/systems/mandelbrot_julia.py +138 -0
  74. mathkit/fractals_chaos/tests/__init__.py +0 -0
  75. mathkit/fractals_chaos/tests/test_box_counting.py +56 -0
  76. mathkit/fractals_chaos/tests/test_cellular_automata.py +74 -0
  77. mathkit/fractals_chaos/tests/test_ifs.py +54 -0
  78. mathkit/fractals_chaos/tests/test_lyapunov.py +42 -0
  79. mathkit/fractals_chaos/tests/test_mandelbrot_julia.py +62 -0
  80. mathkit/fractals_chaos/tests/test_visualizers.py +41 -0
  81. mathkit/fractals_chaos/utils/__init__.py +5 -0
  82. mathkit/fractals_chaos/utils/ifs_utils.py +64 -0
  83. mathkit/fractals_chaos/visualizers/__init__.py +5 -0
  84. mathkit/fractals_chaos/visualizers/plots.py +115 -0
  85. mathkit/geometry/__init__.py +42 -0
  86. mathkit/geometry/core/__init__.py +5 -0
  87. mathkit/geometry/core/base.py +94 -0
  88. mathkit/geometry/systems/__init__.py +19 -0
  89. mathkit/geometry/systems/convex_hull.py +115 -0
  90. mathkit/geometry/systems/curves.py +91 -0
  91. mathkit/geometry/systems/intersections.py +103 -0
  92. mathkit/geometry/systems/polygon.py +73 -0
  93. mathkit/geometry/systems/triangulation.py +77 -0
  94. mathkit/geometry/tests/__init__.py +0 -0
  95. mathkit/geometry/tests/test_convex_hull.py +48 -0
  96. mathkit/geometry/tests/test_curves.py +55 -0
  97. mathkit/geometry/tests/test_intersections.py +48 -0
  98. mathkit/geometry/tests/test_polygon.py +46 -0
  99. mathkit/geometry/tests/test_triangulation.py +44 -0
  100. mathkit/geometry/tests/test_visualizers.py +38 -0
  101. mathkit/geometry/utils/__init__.py +5 -0
  102. mathkit/geometry/utils/curves_library.py +80 -0
  103. mathkit/geometry/visualizers/__init__.py +5 -0
  104. mathkit/geometry/visualizers/plots.py +114 -0
  105. mathkit/graph_theory/__init__.py +47 -0
  106. mathkit/graph_theory/core/__init__.py +5 -0
  107. mathkit/graph_theory/core/base.py +166 -0
  108. mathkit/graph_theory/systems/__init__.py +19 -0
  109. mathkit/graph_theory/systems/coloring.py +123 -0
  110. mathkit/graph_theory/systems/max_flow.py +77 -0
  111. mathkit/graph_theory/systems/shortest_paths.py +112 -0
  112. mathkit/graph_theory/systems/spanning_tree.py +112 -0
  113. mathkit/graph_theory/systems/spectral.py +68 -0
  114. mathkit/graph_theory/tests/__init__.py +0 -0
  115. mathkit/graph_theory/tests/test_coloring.py +49 -0
  116. mathkit/graph_theory/tests/test_generators.py +34 -0
  117. mathkit/graph_theory/tests/test_graph.py +54 -0
  118. mathkit/graph_theory/tests/test_max_flow.py +64 -0
  119. mathkit/graph_theory/tests/test_shortest_paths.py +56 -0
  120. mathkit/graph_theory/tests/test_spanning_tree.py +46 -0
  121. mathkit/graph_theory/tests/test_spectral.py +50 -0
  122. mathkit/graph_theory/tests/test_visualizers.py +37 -0
  123. mathkit/graph_theory/utils/__init__.py +5 -0
  124. mathkit/graph_theory/utils/generators.py +111 -0
  125. mathkit/graph_theory/visualizers/__init__.py +5 -0
  126. mathkit/graph_theory/visualizers/plots.py +98 -0
  127. mathkit/integrators/__init__.py +52 -0
  128. mathkit/integrators/adaptive.py +199 -0
  129. mathkit/integrators/fixed_step.py +349 -0
  130. mathkit/integrators/tests/__init__.py +0 -0
  131. mathkit/integrators/tests/test_integrators.py +115 -0
  132. mathkit/linalg/__init__.py +78 -0
  133. mathkit/linalg/core/__init__.py +23 -0
  134. mathkit/linalg/core/base.py +185 -0
  135. mathkit/linalg/systems/__init__.py +35 -0
  136. mathkit/linalg/systems/cholesky.py +117 -0
  137. mathkit/linalg/systems/eigen.py +199 -0
  138. mathkit/linalg/systems/iterative.py +136 -0
  139. mathkit/linalg/systems/lu.py +169 -0
  140. mathkit/linalg/systems/qr.py +154 -0
  141. mathkit/linalg/systems/stability.py +177 -0
  142. mathkit/linalg/systems/svd.py +51 -0
  143. mathkit/linalg/tests/__init__.py +0 -0
  144. mathkit/linalg/tests/test_cholesky.py +50 -0
  145. mathkit/linalg/tests/test_eigen.py +59 -0
  146. mathkit/linalg/tests/test_iterative.py +67 -0
  147. mathkit/linalg/tests/test_lu.py +46 -0
  148. mathkit/linalg/tests/test_qr.py +54 -0
  149. mathkit/linalg/tests/test_stability.py +46 -0
  150. mathkit/linalg/tests/test_svd.py +42 -0
  151. mathkit/linalg/tests/test_visualizers.py +30 -0
  152. mathkit/linalg/utils/__init__.py +5 -0
  153. mathkit/linalg/utils/matrix_utils.py +88 -0
  154. mathkit/linalg/visualizers/__init__.py +5 -0
  155. mathkit/linalg/visualizers/plots.py +90 -0
  156. mathkit/number_theory/__init__.py +48 -0
  157. mathkit/number_theory/core/__init__.py +5 -0
  158. mathkit/number_theory/core/base.py +89 -0
  159. mathkit/number_theory/systems/__init__.py +26 -0
  160. mathkit/number_theory/systems/continued_fractions.py +107 -0
  161. mathkit/number_theory/systems/crt.py +62 -0
  162. mathkit/number_theory/systems/diophantine.py +111 -0
  163. mathkit/number_theory/systems/modular_arithmetic.py +128 -0
  164. mathkit/number_theory/systems/primality.py +150 -0
  165. mathkit/number_theory/systems/totient.py +143 -0
  166. mathkit/number_theory/tests/__init__.py +0 -0
  167. mathkit/number_theory/tests/test_continued_fractions.py +43 -0
  168. mathkit/number_theory/tests/test_crt.py +35 -0
  169. mathkit/number_theory/tests/test_diophantine.py +43 -0
  170. mathkit/number_theory/tests/test_gcd_lcm.py +35 -0
  171. mathkit/number_theory/tests/test_modular_arithmetic.py +53 -0
  172. mathkit/number_theory/tests/test_primality.py +50 -0
  173. mathkit/number_theory/tests/test_totient.py +54 -0
  174. mathkit/number_theory/tests/test_visualizers.py +21 -0
  175. mathkit/number_theory/utils/__init__.py +5 -0
  176. mathkit/number_theory/utils/gcd_lcm.py +60 -0
  177. mathkit/number_theory/visualizers/__init__.py +5 -0
  178. mathkit/number_theory/visualizers/plots.py +66 -0
  179. mathkit/numerical_analysis/__init__.py +48 -0
  180. mathkit/numerical_analysis/core/__init__.py +15 -0
  181. mathkit/numerical_analysis/core/base.py +154 -0
  182. mathkit/numerical_analysis/systems/__init__.py +22 -0
  183. mathkit/numerical_analysis/systems/chebyshev.py +173 -0
  184. mathkit/numerical_analysis/systems/interpolation.py +131 -0
  185. mathkit/numerical_analysis/systems/regression.py +121 -0
  186. mathkit/numerical_analysis/systems/root_finding.py +241 -0
  187. mathkit/numerical_analysis/systems/splines.py +95 -0
  188. mathkit/numerical_analysis/tests/__init__.py +0 -0
  189. mathkit/numerical_analysis/tests/test_chebyshev.py +37 -0
  190. mathkit/numerical_analysis/tests/test_error_analysis.py +40 -0
  191. mathkit/numerical_analysis/tests/test_interpolation.py +77 -0
  192. mathkit/numerical_analysis/tests/test_regression.py +52 -0
  193. mathkit/numerical_analysis/tests/test_root_finding.py +88 -0
  194. mathkit/numerical_analysis/tests/test_splines.py +61 -0
  195. mathkit/numerical_analysis/tests/test_visualizers.py +38 -0
  196. mathkit/numerical_analysis/utils/__init__.py +9 -0
  197. mathkit/numerical_analysis/utils/error_analysis.py +154 -0
  198. mathkit/numerical_analysis/visualizers/__init__.py +13 -0
  199. mathkit/numerical_analysis/visualizers/plots.py +102 -0
  200. mathkit/ode_dynamics/__init__.py +44 -0
  201. mathkit/ode_dynamics/core/__init__.py +5 -0
  202. mathkit/ode_dynamics/core/base.py +122 -0
  203. mathkit/ode_dynamics/systems/__init__.py +30 -0
  204. mathkit/ode_dynamics/systems/bifurcations.py +124 -0
  205. mathkit/ode_dynamics/systems/limit_cycles.py +99 -0
  206. mathkit/ode_dynamics/systems/logistic_map.py +157 -0
  207. mathkit/ode_dynamics/systems/phase_portrait.py +152 -0
  208. mathkit/ode_dynamics/systems/poincare.py +114 -0
  209. mathkit/ode_dynamics/systems/stability.py +178 -0
  210. mathkit/ode_dynamics/tests/__init__.py +0 -0
  211. mathkit/ode_dynamics/tests/test_bifurcations.py +50 -0
  212. mathkit/ode_dynamics/tests/test_limit_cycles.py +38 -0
  213. mathkit/ode_dynamics/tests/test_logistic_map.py +45 -0
  214. mathkit/ode_dynamics/tests/test_phase_portrait.py +39 -0
  215. mathkit/ode_dynamics/tests/test_poincare.py +43 -0
  216. mathkit/ode_dynamics/tests/test_stability.py +83 -0
  217. mathkit/ode_dynamics/tests/test_visualizers.py +41 -0
  218. mathkit/ode_dynamics/utils/__init__.py +5 -0
  219. mathkit/ode_dynamics/utils/period_estimation.py +50 -0
  220. mathkit/ode_dynamics/visualizers/__init__.py +5 -0
  221. mathkit/ode_dynamics/visualizers/plots.py +107 -0
  222. mathkit/optimization/__init__.py +49 -0
  223. mathkit/optimization/core/__init__.py +5 -0
  224. mathkit/optimization/core/base.py +157 -0
  225. mathkit/optimization/systems/__init__.py +19 -0
  226. mathkit/optimization/systems/conjugate_gradient.py +105 -0
  227. mathkit/optimization/systems/constrained.py +285 -0
  228. mathkit/optimization/systems/gradient_descent.py +135 -0
  229. mathkit/optimization/systems/linear_programming.py +67 -0
  230. mathkit/optimization/systems/newton_quasi_newton.py +141 -0
  231. mathkit/optimization/tests/__init__.py +0 -0
  232. mathkit/optimization/tests/test_comparison.py +36 -0
  233. mathkit/optimization/tests/test_conjugate_gradient.py +50 -0
  234. mathkit/optimization/tests/test_constrained.py +77 -0
  235. mathkit/optimization/tests/test_gradient_descent.py +63 -0
  236. mathkit/optimization/tests/test_linear_programming.py +37 -0
  237. mathkit/optimization/tests/test_newton_quasi_newton.py +47 -0
  238. mathkit/optimization/tests/test_visualizers.py +27 -0
  239. mathkit/optimization/utils/__init__.py +17 -0
  240. mathkit/optimization/utils/comparison.py +76 -0
  241. mathkit/optimization/utils/line_search.py +86 -0
  242. mathkit/optimization/utils/test_functions.py +172 -0
  243. mathkit/optimization/visualizers/__init__.py +5 -0
  244. mathkit/optimization/visualizers/plots.py +84 -0
  245. mathkit/probability/__init__.py +43 -0
  246. mathkit/probability/core/__init__.py +5 -0
  247. mathkit/probability/core/base.py +142 -0
  248. mathkit/probability/systems/__init__.py +24 -0
  249. mathkit/probability/systems/continuous.py +159 -0
  250. mathkit/probability/systems/discrete.py +116 -0
  251. mathkit/probability/systems/limit_theorems.py +87 -0
  252. mathkit/probability/systems/markov_chain.py +194 -0
  253. mathkit/probability/systems/monte_carlo.py +165 -0
  254. mathkit/probability/tests/__init__.py +0 -0
  255. mathkit/probability/tests/test_continuous.py +74 -0
  256. mathkit/probability/tests/test_discrete.py +66 -0
  257. mathkit/probability/tests/test_limit_theorems.py +37 -0
  258. mathkit/probability/tests/test_markov_chain.py +68 -0
  259. mathkit/probability/tests/test_monte_carlo.py +45 -0
  260. mathkit/probability/tests/test_visualizers.py +36 -0
  261. mathkit/probability/utils/__init__.py +5 -0
  262. mathkit/probability/utils/diagnostics.py +43 -0
  263. mathkit/probability/visualizers/__init__.py +5 -0
  264. mathkit/probability/visualizers/plots.py +99 -0
  265. mathkit/special_functions/__init__.py +39 -0
  266. mathkit/special_functions/core/__init__.py +5 -0
  267. mathkit/special_functions/core/base.py +38 -0
  268. mathkit/special_functions/systems/__init__.py +22 -0
  269. mathkit/special_functions/systems/bessel.py +67 -0
  270. mathkit/special_functions/systems/fourier_transform.py +179 -0
  271. mathkit/special_functions/systems/gamma_beta.py +87 -0
  272. mathkit/special_functions/systems/orthogonal_polynomials.py +133 -0
  273. mathkit/special_functions/tests/__init__.py +0 -0
  274. mathkit/special_functions/tests/test_bessel.py +43 -0
  275. mathkit/special_functions/tests/test_fourier_transform.py +54 -0
  276. mathkit/special_functions/tests/test_gamma_beta.py +42 -0
  277. mathkit/special_functions/tests/test_orthogonal_polynomials.py +60 -0
  278. mathkit/special_functions/tests/test_visualizers.py +20 -0
  279. mathkit/special_functions/utils/__init__.py +5 -0
  280. mathkit/special_functions/utils/orthogonality.py +48 -0
  281. mathkit/special_functions/visualizers/__init__.py +5 -0
  282. mathkit/special_functions/visualizers/plots.py +73 -0
  283. mathkit/statistics/__init__.py +54 -0
  284. mathkit/statistics/core/__init__.py +5 -0
  285. mathkit/statistics/core/base.py +151 -0
  286. mathkit/statistics/systems/__init__.py +33 -0
  287. mathkit/statistics/systems/bootstrap.py +78 -0
  288. mathkit/statistics/systems/confidence_intervals.py +141 -0
  289. mathkit/statistics/systems/descriptive.py +86 -0
  290. mathkit/statistics/systems/hypothesis_tests.py +312 -0
  291. mathkit/statistics/systems/regression.py +96 -0
  292. mathkit/statistics/tests/__init__.py +0 -0
  293. mathkit/statistics/tests/test_bootstrap.py +41 -0
  294. mathkit/statistics/tests/test_confidence_intervals.py +63 -0
  295. mathkit/statistics/tests/test_descriptive.py +43 -0
  296. mathkit/statistics/tests/test_effect_size.py +23 -0
  297. mathkit/statistics/tests/test_hypothesis_tests.py +99 -0
  298. mathkit/statistics/tests/test_regression.py +57 -0
  299. mathkit/statistics/tests/test_visualizers.py +32 -0
  300. mathkit/statistics/utils/__init__.py +5 -0
  301. mathkit/statistics/utils/effect_size.py +47 -0
  302. mathkit/statistics/visualizers/__init__.py +5 -0
  303. mathkit/statistics/visualizers/plots.py +91 -0
  304. mathkit/tests/__init__.py +0 -0
  305. mathkit/tests/test_package.py +33 -0
@@ -0,0 +1,43 @@
1
+ """mathkit.abstract_algebra: group, ring, and field theory made computational.
2
+
3
+ Every algorithm here is hand-rolled from its textbook definition --
4
+ finite group/ring/field theory has no ``numpy``/``scipy`` equivalent.
5
+ Cyclic and permutation group implementations with Cayley table
6
+ generation; group-property checks (order, identity, inverses, abelian,
7
+ cyclicity); subgroup and coset enumeration for small groups; finite
8
+ field arithmetic (``GF(p)`` and ``GF(p^n)`` via irreducible polynomials
9
+ over ``GF(p)``); and polynomial ring arithmetic (addition,
10
+ multiplication, division with remainder, gcd) over :math:`\\mathbb{Z}`,
11
+ :math:`\\mathbb{Q}`, and finite fields.
12
+ """
13
+
14
+ from mathkit.abstract_algebra.core.base import FiniteGroup, GroupPropertiesResult, Polynomial
15
+ from mathkit.abstract_algebra.systems.finite_fields import GF, find_irreducible_polynomial, is_irreducible
16
+ from mathkit.abstract_algebra.systems.groups import CyclicGroup, PermutationGroup, group_properties
17
+ from mathkit.abstract_algebra.systems.polynomial_ring import poly_add, poly_divmod, poly_gcd, poly_mul, poly_sub
18
+ from mathkit.abstract_algebra.systems.subgroups import all_subgroups, cyclic_subgroup, left_cosets
19
+ from mathkit.abstract_algebra.utils.checks import is_cyclic
20
+
21
+ __version__ = "0.1.0"
22
+
23
+ __all__ = [
24
+ "__version__",
25
+ "FiniteGroup",
26
+ "GroupPropertiesResult",
27
+ "Polynomial",
28
+ "CyclicGroup",
29
+ "PermutationGroup",
30
+ "group_properties",
31
+ "is_cyclic",
32
+ "cyclic_subgroup",
33
+ "all_subgroups",
34
+ "left_cosets",
35
+ "GF",
36
+ "is_irreducible",
37
+ "find_irreducible_polynomial",
38
+ "poly_add",
39
+ "poly_sub",
40
+ "poly_mul",
41
+ "poly_divmod",
42
+ "poly_gcd",
43
+ ]
@@ -0,0 +1,253 @@
1
+ r"""Abstract base class for finite groups, and a coefficient-generic
2
+ polynomial class, shared across mathkit.abstract_algebra's systems/
3
+ modules.
4
+
5
+ Every algorithm in this domain is hand-rolled from its textbook
6
+ definition -- group/ring/field theory over finite structures has no
7
+ ``numpy``/``scipy`` equivalent at all. See Dummit & Foote, *Abstract
8
+ Algebra*, 3rd ed., throughout.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from abc import ABC, abstractmethod
14
+ from dataclasses import dataclass, field
15
+ from fractions import Fraction
16
+ from typing import Optional
17
+
18
+ import numpy as np
19
+
20
+ __all__ = ["FiniteGroup", "GroupPropertiesResult", "Polynomial"]
21
+
22
+
23
+ class FiniteGroup(ABC):
24
+ r"""Common interface for a finite group :math:`(G, \cdot)`.
25
+
26
+ Concrete subclasses (:class:`~mathkit.abstract_algebra.systems.groups.CyclicGroup`,
27
+ :class:`~mathkit.abstract_algebra.systems.groups.PermutationGroup`)
28
+ provide the element set and the group operation; this base class
29
+ derives the Cayley table, abelian check, and element orders purely
30
+ from those. See Dummit & Foote, *Abstract Algebra*, 3rd ed., Ch. 1.
31
+
32
+ Examples
33
+ --------
34
+ >>> from mathkit.abstract_algebra.systems.groups import CyclicGroup
35
+ >>> g = CyclicGroup(4)
36
+ >>> g.order
37
+ 4
38
+ >>> g.is_abelian()
39
+ True
40
+ """
41
+
42
+ @property
43
+ @abstractmethod
44
+ def elements(self) -> list:
45
+ """list: Every group element, in a fixed (implementation-defined) order."""
46
+
47
+ @abstractmethod
48
+ def identity(self):
49
+ """The group's identity element."""
50
+
51
+ @abstractmethod
52
+ def operate(self, a, b):
53
+ """The group operation, :math:`a \\cdot b`."""
54
+
55
+ @abstractmethod
56
+ def inverse(self, a):
57
+ """The inverse of ``a``."""
58
+
59
+ @property
60
+ def order(self) -> int:
61
+ """int: :math:`|G|`, the number of elements."""
62
+ return len(self.elements)
63
+
64
+ def cayley_table(self) -> np.ndarray:
65
+ r"""The Cayley (multiplication) table, as element indices.
66
+
67
+ Row ``i``, column ``j`` holds the index (into :attr:`elements`)
68
+ of ``elements[i] . elements[j]``. See Dummit & Foote, *Abstract
69
+ Algebra*, 3rd ed., Sec. 1.7.
70
+
71
+ Returns
72
+ -------
73
+ ndarray, shape (n, n), int
74
+ """
75
+ els = self.elements
76
+ index = {e: i for i, e in enumerate(els)}
77
+ n = len(els)
78
+ table = np.zeros((n, n), dtype=np.int64)
79
+ for i, a in enumerate(els):
80
+ for j, b in enumerate(els):
81
+ table[i, j] = index[self.operate(a, b)]
82
+ return table
83
+
84
+ def is_abelian(self) -> bool:
85
+ """Whether the group operation is commutative for every pair of elements.
86
+
87
+ Returns
88
+ -------
89
+ bool
90
+ """
91
+ els = self.elements
92
+ return all(self.operate(a, b) == self.operate(b, a) for a in els for b in els)
93
+
94
+ def element_order(self, a) -> int:
95
+ r"""The order of element ``a``: the smallest :math:`k>0` with :math:`a^k = e`.
96
+
97
+ Parameters
98
+ ----------
99
+ a
100
+
101
+ Returns
102
+ -------
103
+ int
104
+ """
105
+ e = self.identity()
106
+ x = a
107
+ k = 1
108
+ while x != e:
109
+ x = self.operate(x, a)
110
+ k += 1
111
+ if k > self.order:
112
+ raise RuntimeError("element does not appear to have finite order within |G| steps")
113
+ return k
114
+
115
+
116
+ @dataclass
117
+ class GroupPropertiesResult:
118
+ """Container for a finite group's summary properties."""
119
+
120
+ order: int
121
+ is_abelian: bool
122
+ identity: object
123
+ element_orders: dict = field(default_factory=dict)
124
+ """dict: ``{element: order}`` for every element."""
125
+
126
+
127
+ class Polynomial:
128
+ r"""A polynomial with coefficients over :math:`\mathbb{Q}` (``modulus=None``,
129
+ stored as exact :class:`fractions.Fraction`) or :math:`\mathrm{GF}(p)`
130
+ (``modulus=p``, coefficients reduced mod ``p``).
131
+
132
+ Coefficients are stored lowest-degree first: ``coeffs[k]`` is the
133
+ coefficient of :math:`x^k`. Integer (``Z``) and rational (``Q``)
134
+ coefficients are both represented exactly as ``Fraction`` (``Z`` is
135
+ simply the sub-case where every coefficient happens to have
136
+ denominator 1); this is what makes polynomial *division* with
137
+ remainder always well-defined (needs the divisor's leading
138
+ coefficient to be invertible, which holds automatically in a field
139
+ but not in :math:`\mathbb{Z}` itself). See Dummit & Foote, *Abstract
140
+ Algebra*, 3rd ed., Ch. 9.
141
+
142
+ Parameters
143
+ ----------
144
+ coeffs : sequence
145
+ Coefficients, lowest degree first.
146
+ modulus : int, optional
147
+ A prime ``p``, for coefficients in :math:`\mathrm{GF}(p)`;
148
+ ``None`` (the default) for :math:`\mathbb{Q}` coefficients.
149
+
150
+ Examples
151
+ --------
152
+ >>> p = Polynomial([1, 2, 1]) # 1 + 2x + x^2 = (1+x)^2
153
+ >>> q = Polynomial([1, 1]) # 1 + x
154
+ >>> quotient, remainder = divmod(p, q)
155
+ >>> quotient.coeffs, remainder.coeffs
156
+ ([Fraction(1, 1), Fraction(1, 1)], [Fraction(0, 1)])
157
+ """
158
+
159
+ def __init__(self, coeffs, modulus: Optional[int] = None):
160
+ self.modulus = modulus
161
+ if modulus is None:
162
+ raw = [Fraction(c) for c in coeffs]
163
+ else:
164
+ raw = [int(c) % modulus for c in coeffs]
165
+ self.coeffs = self._trim(raw)
166
+
167
+ def _trim(self, coeffs: list) -> list:
168
+ coeffs = list(coeffs)
169
+ zero = Fraction(0) if self.modulus is None else 0
170
+ while len(coeffs) > 1 and coeffs[-1] == zero:
171
+ coeffs.pop()
172
+ if not coeffs:
173
+ coeffs = [zero]
174
+ return coeffs
175
+
176
+ def _reduce(self, c):
177
+ return c if self.modulus is None else c % self.modulus
178
+
179
+ def _wrap(self, coeffs: list) -> Polynomial:
180
+ result = Polynomial.__new__(Polynomial)
181
+ result.modulus = self.modulus
182
+ result.coeffs = result._trim(coeffs)
183
+ return result
184
+
185
+ @property
186
+ def degree(self) -> int:
187
+ """int: The polynomial's degree (``-1`` for the zero polynomial, by convention)."""
188
+ if len(self.coeffs) == 1 and self.coeffs[0] == (Fraction(0) if self.modulus is None else 0):
189
+ return -1
190
+ return len(self.coeffs) - 1
191
+
192
+ def _pad(self, n: int) -> list:
193
+ zero = Fraction(0) if self.modulus is None else 0
194
+ return self.coeffs + [zero] * (n - len(self.coeffs))
195
+
196
+ def __add__(self, other: Polynomial) -> Polynomial:
197
+ n = max(len(self.coeffs), len(other.coeffs))
198
+ return self._wrap([self._reduce(a + b) for a, b in zip(self._pad(n), other._pad(n))])
199
+
200
+ def __sub__(self, other: Polynomial) -> Polynomial:
201
+ n = max(len(self.coeffs), len(other.coeffs))
202
+ return self._wrap([self._reduce(a - b) for a, b in zip(self._pad(n), other._pad(n))])
203
+
204
+ def __mul__(self, other: Polynomial) -> Polynomial:
205
+ result = [Fraction(0) if self.modulus is None else 0] * (len(self.coeffs) + len(other.coeffs) - 1)
206
+ for i, a in enumerate(self.coeffs):
207
+ for j, b in enumerate(other.coeffs):
208
+ result[i + j] = self._reduce(result[i + j] + a * b)
209
+ return self._wrap(result)
210
+
211
+ def __eq__(self, other) -> bool:
212
+ return isinstance(other, Polynomial) and self.modulus == other.modulus and self.coeffs == other.coeffs
213
+
214
+ def __hash__(self) -> int:
215
+ return hash((self.modulus, tuple(self.coeffs)))
216
+
217
+ def __divmod__(self, other: Polynomial):
218
+ r"""Polynomial long division: ``self = quotient * other + remainder``, ``deg(remainder) < deg(other)``."""
219
+ if other.degree == -1:
220
+ raise ZeroDivisionError("division by the zero polynomial")
221
+ remainder = self._wrap(list(self.coeffs))
222
+ quotient_degree = max(self.degree - other.degree, -1)
223
+ zero = Fraction(0) if self.modulus is None else 0
224
+ quotient_coeffs = [zero] * (quotient_degree + 1) if quotient_degree >= 0 else [zero]
225
+ lead_other = other.coeffs[other.degree]
226
+ lead_inv = (1 / lead_other) if self.modulus is None else pow(int(lead_other), self.modulus - 2, self.modulus)
227
+ while remainder.degree >= other.degree and remainder.degree >= 0:
228
+ shift = remainder.degree - other.degree
229
+ factor = self._reduce(remainder.coeffs[remainder.degree] * lead_inv)
230
+ quotient_coeffs[shift] = self._reduce(quotient_coeffs[shift] + factor)
231
+ subtrahend_coeffs = [zero] * shift + [self._reduce(factor * c) for c in other.coeffs]
232
+ remainder = remainder - self._wrap(subtrahend_coeffs)
233
+ return self._wrap(quotient_coeffs), remainder
234
+
235
+ def evaluate(self, x):
236
+ """Evaluate the polynomial at ``x``, via Horner's method.
237
+
238
+ Parameters
239
+ ----------
240
+ x
241
+
242
+ Returns
243
+ -------
244
+ The same type as the polynomial's coefficients.
245
+ """
246
+ result = Fraction(0) if self.modulus is None else 0
247
+ for c in reversed(self.coeffs):
248
+ result = self._reduce(result * x + c)
249
+ return result
250
+
251
+ def __repr__(self) -> str:
252
+ terms = [f"{c}*x^{k}" for k, c in enumerate(self.coeffs) if c != (Fraction(0) if self.modulus is None else 0)]
253
+ return " + ".join(terms) if terms else "0"
@@ -0,0 +1,23 @@
1
+ """Concrete group, ring, and field algorithms."""
2
+
3
+ from mathkit.abstract_algebra.systems.finite_fields import GF, find_irreducible_polynomial, is_irreducible
4
+ from mathkit.abstract_algebra.systems.groups import CyclicGroup, PermutationGroup, group_properties
5
+ from mathkit.abstract_algebra.systems.polynomial_ring import poly_add, poly_divmod, poly_gcd, poly_mul, poly_sub
6
+ from mathkit.abstract_algebra.systems.subgroups import all_subgroups, cyclic_subgroup, left_cosets
7
+
8
+ __all__ = [
9
+ "CyclicGroup",
10
+ "PermutationGroup",
11
+ "group_properties",
12
+ "cyclic_subgroup",
13
+ "all_subgroups",
14
+ "left_cosets",
15
+ "GF",
16
+ "is_irreducible",
17
+ "find_irreducible_polynomial",
18
+ "poly_add",
19
+ "poly_sub",
20
+ "poly_mul",
21
+ "poly_divmod",
22
+ "poly_gcd",
23
+ ]
@@ -0,0 +1,201 @@
1
+ r"""Finite field arithmetic: :math:`\mathrm{GF}(p)` and
2
+ :math:`\mathrm{GF}(p^n)` via irreducible polynomials over :math:`\mathrm{GF}(p)`.
3
+
4
+ No numpy/scipy equivalent. See Dummit & Foote, *Abstract Algebra*, 3rd
5
+ ed., Sec. 13.5 (finite fields) and Lidl & Niederreiter, *Introduction to
6
+ Finite Fields and Their Applications*, Ch. 2-3.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import itertools
12
+ from typing import Optional
13
+
14
+ from mathkit.abstract_algebra.core.base import Polynomial
15
+
16
+ __all__ = ["GF", "is_irreducible", "find_irreducible_polynomial"]
17
+
18
+
19
+ def is_irreducible(poly: Polynomial) -> bool:
20
+ r"""Whether `poly` is irreducible over :math:`\mathrm{GF}(p)`: has no polynomial factor of degree :math:`1 \leq d \leq \deg(\text{poly})/2`.
21
+
22
+ Checked by trial division against every monic polynomial of degree
23
+ ``1`` through ``deg(poly) // 2`` (degree-1 divisors are equivalent
24
+ to checking for a root in :math:`\mathrm{GF}(p)`). Feasible for the
25
+ small ``p``, small degree cases this domain's examples use. See
26
+ Lidl & Niederreiter, *Introduction to Finite Fields*, Sec. 3.1.
27
+
28
+ Parameters
29
+ ----------
30
+ poly : Polynomial
31
+ With ``modulus`` set (coefficients in :math:`\mathrm{GF}(p)`).
32
+
33
+ Returns
34
+ -------
35
+ bool
36
+
37
+ Examples
38
+ --------
39
+ >>> is_irreducible(Polynomial([1, 1, 1], modulus=2)) # x^2+x+1 over GF(2)
40
+ True
41
+ >>> is_irreducible(Polynomial([0, 1, 1], modulus=2)) # x^2+x = x(x+1): reducible
42
+ False
43
+ """
44
+ p = poly.modulus
45
+ n = poly.degree
46
+ if n <= 1:
47
+ return True
48
+ for d in range(1, n // 2 + 1):
49
+ for candidate_coeffs in _monic_polynomials_of_degree(p, d):
50
+ candidate = Polynomial(candidate_coeffs, modulus=p)
51
+ _, remainder = divmod(poly, candidate)
52
+ if remainder.degree == -1:
53
+ return False
54
+ return True
55
+
56
+
57
+ def _monic_polynomials_of_degree(p: int, d: int):
58
+ """Every monic polynomial of degree `d` over GF(p), lowest coefficient first."""
59
+ if d == 0:
60
+ yield [1]
61
+ return
62
+ for lower_coeffs in itertools.product(range(p), repeat=d):
63
+ yield list(lower_coeffs) + [1]
64
+
65
+
66
+ def find_irreducible_polynomial(p: int, n: int) -> Polynomial:
67
+ r"""Find a monic irreducible polynomial of degree ``n`` over :math:`\mathrm{GF}(p)`, by brute-force search.
68
+
69
+ An irreducible polynomial of every degree exists over every finite
70
+ field (Dummit & Foote, *Abstract Algebra*, 3rd ed., Corollary 13.24
71
+ -- via counting arguments), so this search always terminates. See
72
+ Lidl & Niederreiter, *Introduction to Finite Fields*, Sec. 3.1-3.3.
73
+
74
+ Parameters
75
+ ----------
76
+ p : int
77
+ Prime.
78
+ n : int
79
+ Degree, ``n >= 1``.
80
+
81
+ Returns
82
+ -------
83
+ Polynomial
84
+
85
+ Examples
86
+ --------
87
+ >>> poly = find_irreducible_polynomial(2, 3)
88
+ >>> poly.degree
89
+ 3
90
+ >>> is_irreducible(poly)
91
+ True
92
+ """
93
+ if n == 1:
94
+ return Polynomial([0, 1], modulus=p)
95
+ for coeffs in _monic_polynomials_of_degree(p, n):
96
+ candidate = Polynomial(coeffs, modulus=p)
97
+ if is_irreducible(candidate):
98
+ return candidate
99
+ raise RuntimeError(f"no irreducible polynomial of degree {n} found over GF({p})")
100
+
101
+
102
+ class GF:
103
+ r"""The finite field :math:`\mathrm{GF}(p^n)`.
104
+
105
+ ``n=1`` (the default) is the prime field :math:`\mathrm{GF}(p) =
106
+ \mathbb{Z}/p\mathbb{Z}`, with elements plain integers ``0, ..., p-1``.
107
+ ``n>1`` represents elements as polynomials of degree ``< n`` over
108
+ :math:`\mathrm{GF}(p)`, with multiplication reduced modulo a fixed
109
+ irreducible polynomial (found via :func:`find_irreducible_polynomial`
110
+ if not supplied) -- :math:`\mathrm{GF}(p)[x]/(f(x))` is a field
111
+ exactly when ``f`` is irreducible. See Dummit & Foote, *Abstract
112
+ Algebra*, 3rd ed., Sec. 13.5.
113
+
114
+ Parameters
115
+ ----------
116
+ p : int
117
+ Prime.
118
+ n : int
119
+ Extension degree, ``n >= 1``.
120
+ irreducible : Polynomial, optional
121
+ A degree-``n`` irreducible polynomial over :math:`\mathrm{GF}(p)`;
122
+ found automatically if omitted.
123
+
124
+ Examples
125
+ --------
126
+ >>> field = GF(2, 3) # GF(8)
127
+ >>> field.order
128
+ 8
129
+ >>> a = Polynomial([1, 1, 0], modulus=2) # 1 + x
130
+ >>> b = Polynomial([0, 1, 1], modulus=2) # x + x^2
131
+ >>> field.multiply(a, b).degree <= 2 # result stays reduced mod the irreducible polynomial
132
+ True
133
+ """
134
+
135
+ def __init__(self, p: int, n: int = 1, irreducible: Optional[Polynomial] = None):
136
+ self.p = int(p)
137
+ self.n = int(n)
138
+ self.irreducible = irreducible if irreducible is not None else (find_irreducible_polynomial(p, n) if n > 1 else None)
139
+
140
+ @property
141
+ def order(self) -> int:
142
+ """int: :math:`|\\mathrm{GF}(p^n)| = p^n`."""
143
+ return self.p**self.n
144
+
145
+ def elements(self) -> list:
146
+ """Every field element.
147
+
148
+ Returns
149
+ -------
150
+ list of int (if ``n == 1``) or list of Polynomial (if ``n > 1``)
151
+ """
152
+ if self.n == 1:
153
+ return list(range(self.p))
154
+ return [Polynomial(list(coeffs), modulus=self.p) for coeffs in itertools.product(range(self.p), repeat=self.n)]
155
+
156
+ def add(self, a, b):
157
+ """Field addition."""
158
+ if self.n == 1:
159
+ return (a + b) % self.p
160
+ return a + b
161
+
162
+ def multiply(self, a, b):
163
+ """Field multiplication, reduced modulo the extension's irreducible polynomial when ``n > 1``."""
164
+ if self.n == 1:
165
+ return (a * b) % self.p
166
+ product = a * b
167
+ _, remainder = divmod(product, self.irreducible)
168
+ return remainder
169
+
170
+ def inverse(self, a):
171
+ r"""Multiplicative inverse of a nonzero element.
172
+
173
+ ``n == 1``: via Fermat's little theorem, :math:`a^{-1} \equiv
174
+ a^{p-2} \pmod p`. ``n > 1``: via the polynomial extended
175
+ Euclidean algorithm (finding :math:`u, v` with :math:`ua +
176
+ v \cdot \text{irreducible} = \gcd = 1`).
177
+ """
178
+ if self.n == 1:
179
+ if a % self.p == 0:
180
+ raise ZeroDivisionError("0 has no multiplicative inverse")
181
+ return pow(a, self.p - 2, self.p)
182
+ if a.degree == -1:
183
+ raise ZeroDivisionError("0 has no multiplicative inverse")
184
+ return _poly_extended_gcd_inverse(a, self.irreducible, self.p)
185
+
186
+
187
+ def _poly_extended_gcd_inverse(a: Polynomial, modulus_poly: Polynomial, p: int) -> Polynomial:
188
+ """Find a's inverse mod modulus_poly via the polynomial extended Euclidean algorithm."""
189
+ zero = Polynomial([0], modulus=p)
190
+ one = Polynomial([1], modulus=p)
191
+ old_r, r = a, modulus_poly
192
+ old_s, s = one, zero
193
+ while r.degree != -1:
194
+ q, remainder = divmod(old_r, r)
195
+ old_r, r = r, remainder
196
+ old_s, s = s, old_s - q * s
197
+ # old_r is now gcd(a, modulus_poly), which must be a nonzero constant
198
+ # since modulus_poly is irreducible; normalize to exactly 1.
199
+ lead = old_r.coeffs[old_r.degree]
200
+ lead_inv = pow(int(lead), p - 2, p)
201
+ return Polynomial([c * lead_inv for c in old_s.coeffs], modulus=p)
@@ -0,0 +1,162 @@
1
+ r"""Cyclic and permutation groups, with Cayley table generation and
2
+ group-property checks (order, identity, inverses, abelian).
3
+
4
+ Every construction here is hand-rolled from the textbook definitions --
5
+ finite group theory has no ``numpy``/``scipy`` equivalent. See Dummit &
6
+ Foote, *Abstract Algebra*, 3rd ed., Ch. 1-4.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from mathkit.abstract_algebra.core.base import FiniteGroup, GroupPropertiesResult
12
+
13
+ __all__ = ["CyclicGroup", "PermutationGroup", "group_properties"]
14
+
15
+
16
+ class CyclicGroup(FiniteGroup):
17
+ r"""The cyclic group :math:`\mathbb{Z}_n = \{0, 1, \dots, n-1\}` under addition mod ``n``.
18
+
19
+ Generated by a single element (e.g. ``1``); every finite cyclic
20
+ group of order ``n`` is isomorphic to this one. See Dummit & Foote,
21
+ *Abstract Algebra*, 3rd ed., Sec. 1.3, 2.3.
22
+
23
+ Parameters
24
+ ----------
25
+ n : int
26
+ Group order, ``n >= 1``.
27
+
28
+ Examples
29
+ --------
30
+ >>> g = CyclicGroup(5)
31
+ >>> g.operate(3, 4)
32
+ 2
33
+ >>> g.inverse(3)
34
+ 2
35
+ >>> g.is_abelian()
36
+ True
37
+ """
38
+
39
+ def __init__(self, n: int):
40
+ if n < 1:
41
+ raise ValueError("n must be >= 1")
42
+ self.n = int(n)
43
+
44
+ @property
45
+ def elements(self) -> list:
46
+ return list(range(self.n))
47
+
48
+ def identity(self) -> int:
49
+ return 0
50
+
51
+ def operate(self, a: int, b: int) -> int:
52
+ return (a + b) % self.n
53
+
54
+ def inverse(self, a: int) -> int:
55
+ return (-a) % self.n
56
+
57
+
58
+ class PermutationGroup(FiniteGroup):
59
+ r"""The group generated by a set of permutations, under composition.
60
+
61
+ Elements are tuples ``p`` with ``p[i]`` = the image of ``i`` under
62
+ the permutation; the full element set is found by closing the
63
+ generators under composition (a breadth-first search from the
64
+ identity). ``generators=None`` builds the full symmetric group
65
+ :math:`S_n` (generated by an adjacent transposition and the full
66
+ n-cycle). See Dummit & Foote, *Abstract Algebra*, 3rd ed., Sec. 1.3,
67
+ 1.6.
68
+
69
+ Parameters
70
+ ----------
71
+ degree : int
72
+ The permutations act on ``{0, ..., degree - 1}``.
73
+ generators : list of tuple, optional
74
+ Generating permutations; defaults to generators of the full
75
+ symmetric group :math:`S_{\text{degree}}`.
76
+
77
+ Examples
78
+ --------
79
+ >>> # S_3, the symmetric group on 3 elements: order 6, non-abelian.
80
+ >>> g = PermutationGroup(3)
81
+ >>> g.order
82
+ 6
83
+ >>> g.is_abelian()
84
+ False
85
+ >>> # A cyclic subgroup: generated by the 3-cycle (0 1 2).
86
+ >>> g3 = PermutationGroup(3, generators=[(1, 2, 0)])
87
+ >>> g3.order
88
+ 3
89
+ """
90
+
91
+ def __init__(self, degree: int, generators=None):
92
+ self.degree = int(degree)
93
+ if generators is None:
94
+ if self.degree < 2:
95
+ generators = [tuple(range(self.degree))]
96
+ else:
97
+ transposition = list(range(self.degree))
98
+ transposition[0], transposition[1] = transposition[1], transposition[0]
99
+ cycle = list(range(1, self.degree)) + [0]
100
+ generators = [tuple(transposition), tuple(cycle)]
101
+ self.generators = [tuple(g) for g in generators]
102
+ self._elements = self._generate_closure()
103
+
104
+ def _compose(self, p, q) -> tuple:
105
+ """(p . q)(i) = p(q(i)): apply q first, then p."""
106
+ return tuple(p[q[i]] for i in range(self.degree))
107
+
108
+ def _generate_closure(self) -> list:
109
+ identity = tuple(range(self.degree))
110
+ elements = {identity}
111
+ frontier = [identity]
112
+ while frontier:
113
+ new_frontier = []
114
+ for elem in frontier:
115
+ for g in self.generators:
116
+ composed = self._compose(elem, g)
117
+ if composed not in elements:
118
+ elements.add(composed)
119
+ new_frontier.append(composed)
120
+ frontier = new_frontier
121
+ return sorted(elements)
122
+
123
+ @property
124
+ def elements(self) -> list:
125
+ return self._elements
126
+
127
+ def identity(self) -> tuple:
128
+ return tuple(range(self.degree))
129
+
130
+ def operate(self, a: tuple, b: tuple) -> tuple:
131
+ return self._compose(a, b)
132
+
133
+ def inverse(self, a: tuple) -> tuple:
134
+ inv = [0] * self.degree
135
+ for i, v in enumerate(a):
136
+ inv[v] = i
137
+ return tuple(inv)
138
+
139
+
140
+ def group_properties(group: FiniteGroup) -> GroupPropertiesResult:
141
+ """Summarize a finite group's order, abelian-ness, and every element's order.
142
+
143
+ Parameters
144
+ ----------
145
+ group : FiniteGroup
146
+
147
+ Returns
148
+ -------
149
+ GroupPropertiesResult
150
+
151
+ Examples
152
+ --------
153
+ >>> result = group_properties(CyclicGroup(4))
154
+ >>> result.order
155
+ 4
156
+ >>> result.is_abelian
157
+ True
158
+ >>> result.element_orders
159
+ {0: 1, 1: 4, 2: 2, 3: 4}
160
+ """
161
+ orders = {e: group.element_order(e) for e in group.elements}
162
+ return GroupPropertiesResult(order=group.order, is_abelian=group.is_abelian(), identity=group.identity(), element_orders=orders)