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.
- mathematicskit-0.1.0.dist-info/METADATA +201 -0
- mathematicskit-0.1.0.dist-info/RECORD +305 -0
- mathematicskit-0.1.0.dist-info/WHEEL +5 -0
- mathematicskit-0.1.0.dist-info/licenses/LICENSE +21 -0
- mathematicskit-0.1.0.dist-info/top_level.txt +1 -0
- mathkit/__init__.py +50 -0
- mathkit/abstract_algebra/__init__.py +43 -0
- mathkit/abstract_algebra/core/base.py +253 -0
- mathkit/abstract_algebra/systems/__init__.py +23 -0
- mathkit/abstract_algebra/systems/finite_fields.py +201 -0
- mathkit/abstract_algebra/systems/groups.py +162 -0
- mathkit/abstract_algebra/systems/polynomial_ring.py +132 -0
- mathkit/abstract_algebra/systems/subgroups.py +144 -0
- mathkit/abstract_algebra/tests/__init__.py +0 -0
- mathkit/abstract_algebra/tests/test_finite_fields.py +71 -0
- mathkit/abstract_algebra/tests/test_groups.py +84 -0
- mathkit/abstract_algebra/tests/test_polynomial_ring.py +79 -0
- mathkit/abstract_algebra/tests/test_subgroups.py +59 -0
- mathkit/abstract_algebra/tests/test_visualizers.py +15 -0
- mathkit/abstract_algebra/utils/__init__.py +5 -0
- mathkit/abstract_algebra/utils/checks.py +35 -0
- mathkit/abstract_algebra/visualizers/__init__.py +5 -0
- mathkit/abstract_algebra/visualizers/plots.py +31 -0
- mathkit/calculus/__init__.py +48 -0
- mathkit/calculus/core/__init__.py +5 -0
- mathkit/calculus/core/base.py +80 -0
- mathkit/calculus/systems/__init__.py +27 -0
- mathkit/calculus/systems/autodiff.py +160 -0
- mathkit/calculus/systems/dual_numbers.py +132 -0
- mathkit/calculus/systems/finite_differences.py +141 -0
- mathkit/calculus/systems/quadrature.py +184 -0
- mathkit/calculus/systems/taylor_series.py +180 -0
- mathkit/calculus/tests/__init__.py +0 -0
- mathkit/calculus/tests/test_autodiff.py +51 -0
- mathkit/calculus/tests/test_dual_numbers.py +48 -0
- mathkit/calculus/tests/test_finite_differences.py +47 -0
- mathkit/calculus/tests/test_quadrature.py +61 -0
- mathkit/calculus/tests/test_taylor_series.py +53 -0
- mathkit/calculus/tests/test_visualizers.py +23 -0
- mathkit/calculus/utils/__init__.py +5 -0
- mathkit/calculus/utils/series_utils.py +64 -0
- mathkit/calculus/visualizers/__init__.py +5 -0
- mathkit/calculus/visualizers/plots.py +77 -0
- mathkit/combinatorics/__init__.py +43 -0
- mathkit/combinatorics/core/__init__.py +5 -0
- mathkit/combinatorics/core/base.py +85 -0
- mathkit/combinatorics/systems/__init__.py +23 -0
- mathkit/combinatorics/systems/counting.py +153 -0
- mathkit/combinatorics/systems/inclusion_exclusion.py +97 -0
- mathkit/combinatorics/systems/partitions.py +109 -0
- mathkit/combinatorics/systems/pascals_triangle.py +47 -0
- mathkit/combinatorics/systems/special_numbers.py +130 -0
- mathkit/combinatorics/tests/__init__.py +0 -0
- mathkit/combinatorics/tests/test_counting.py +50 -0
- mathkit/combinatorics/tests/test_inclusion_exclusion.py +44 -0
- mathkit/combinatorics/tests/test_partitions.py +46 -0
- mathkit/combinatorics/tests/test_pascals_triangle.py +24 -0
- mathkit/combinatorics/tests/test_special_numbers.py +56 -0
- mathkit/combinatorics/tests/test_visualizers.py +26 -0
- mathkit/combinatorics/utils/__init__.py +5 -0
- mathkit/combinatorics/utils/bell_number.py +35 -0
- mathkit/combinatorics/visualizers/__init__.py +5 -0
- mathkit/combinatorics/visualizers/plots.py +91 -0
- mathkit/constants.py +55 -0
- mathkit/fractals_chaos/__init__.py +41 -0
- mathkit/fractals_chaos/core/__init__.py +5 -0
- mathkit/fractals_chaos/core/base.py +103 -0
- mathkit/fractals_chaos/systems/__init__.py +20 -0
- mathkit/fractals_chaos/systems/box_counting.py +85 -0
- mathkit/fractals_chaos/systems/cellular_automata.py +135 -0
- mathkit/fractals_chaos/systems/ifs.py +106 -0
- mathkit/fractals_chaos/systems/lyapunov.py +125 -0
- mathkit/fractals_chaos/systems/mandelbrot_julia.py +138 -0
- mathkit/fractals_chaos/tests/__init__.py +0 -0
- mathkit/fractals_chaos/tests/test_box_counting.py +56 -0
- mathkit/fractals_chaos/tests/test_cellular_automata.py +74 -0
- mathkit/fractals_chaos/tests/test_ifs.py +54 -0
- mathkit/fractals_chaos/tests/test_lyapunov.py +42 -0
- mathkit/fractals_chaos/tests/test_mandelbrot_julia.py +62 -0
- mathkit/fractals_chaos/tests/test_visualizers.py +41 -0
- mathkit/fractals_chaos/utils/__init__.py +5 -0
- mathkit/fractals_chaos/utils/ifs_utils.py +64 -0
- mathkit/fractals_chaos/visualizers/__init__.py +5 -0
- mathkit/fractals_chaos/visualizers/plots.py +115 -0
- mathkit/geometry/__init__.py +42 -0
- mathkit/geometry/core/__init__.py +5 -0
- mathkit/geometry/core/base.py +94 -0
- mathkit/geometry/systems/__init__.py +19 -0
- mathkit/geometry/systems/convex_hull.py +115 -0
- mathkit/geometry/systems/curves.py +91 -0
- mathkit/geometry/systems/intersections.py +103 -0
- mathkit/geometry/systems/polygon.py +73 -0
- mathkit/geometry/systems/triangulation.py +77 -0
- mathkit/geometry/tests/__init__.py +0 -0
- mathkit/geometry/tests/test_convex_hull.py +48 -0
- mathkit/geometry/tests/test_curves.py +55 -0
- mathkit/geometry/tests/test_intersections.py +48 -0
- mathkit/geometry/tests/test_polygon.py +46 -0
- mathkit/geometry/tests/test_triangulation.py +44 -0
- mathkit/geometry/tests/test_visualizers.py +38 -0
- mathkit/geometry/utils/__init__.py +5 -0
- mathkit/geometry/utils/curves_library.py +80 -0
- mathkit/geometry/visualizers/__init__.py +5 -0
- mathkit/geometry/visualizers/plots.py +114 -0
- mathkit/graph_theory/__init__.py +47 -0
- mathkit/graph_theory/core/__init__.py +5 -0
- mathkit/graph_theory/core/base.py +166 -0
- mathkit/graph_theory/systems/__init__.py +19 -0
- mathkit/graph_theory/systems/coloring.py +123 -0
- mathkit/graph_theory/systems/max_flow.py +77 -0
- mathkit/graph_theory/systems/shortest_paths.py +112 -0
- mathkit/graph_theory/systems/spanning_tree.py +112 -0
- mathkit/graph_theory/systems/spectral.py +68 -0
- mathkit/graph_theory/tests/__init__.py +0 -0
- mathkit/graph_theory/tests/test_coloring.py +49 -0
- mathkit/graph_theory/tests/test_generators.py +34 -0
- mathkit/graph_theory/tests/test_graph.py +54 -0
- mathkit/graph_theory/tests/test_max_flow.py +64 -0
- mathkit/graph_theory/tests/test_shortest_paths.py +56 -0
- mathkit/graph_theory/tests/test_spanning_tree.py +46 -0
- mathkit/graph_theory/tests/test_spectral.py +50 -0
- mathkit/graph_theory/tests/test_visualizers.py +37 -0
- mathkit/graph_theory/utils/__init__.py +5 -0
- mathkit/graph_theory/utils/generators.py +111 -0
- mathkit/graph_theory/visualizers/__init__.py +5 -0
- mathkit/graph_theory/visualizers/plots.py +98 -0
- mathkit/integrators/__init__.py +52 -0
- mathkit/integrators/adaptive.py +199 -0
- mathkit/integrators/fixed_step.py +349 -0
- mathkit/integrators/tests/__init__.py +0 -0
- mathkit/integrators/tests/test_integrators.py +115 -0
- mathkit/linalg/__init__.py +78 -0
- mathkit/linalg/core/__init__.py +23 -0
- mathkit/linalg/core/base.py +185 -0
- mathkit/linalg/systems/__init__.py +35 -0
- mathkit/linalg/systems/cholesky.py +117 -0
- mathkit/linalg/systems/eigen.py +199 -0
- mathkit/linalg/systems/iterative.py +136 -0
- mathkit/linalg/systems/lu.py +169 -0
- mathkit/linalg/systems/qr.py +154 -0
- mathkit/linalg/systems/stability.py +177 -0
- mathkit/linalg/systems/svd.py +51 -0
- mathkit/linalg/tests/__init__.py +0 -0
- mathkit/linalg/tests/test_cholesky.py +50 -0
- mathkit/linalg/tests/test_eigen.py +59 -0
- mathkit/linalg/tests/test_iterative.py +67 -0
- mathkit/linalg/tests/test_lu.py +46 -0
- mathkit/linalg/tests/test_qr.py +54 -0
- mathkit/linalg/tests/test_stability.py +46 -0
- mathkit/linalg/tests/test_svd.py +42 -0
- mathkit/linalg/tests/test_visualizers.py +30 -0
- mathkit/linalg/utils/__init__.py +5 -0
- mathkit/linalg/utils/matrix_utils.py +88 -0
- mathkit/linalg/visualizers/__init__.py +5 -0
- mathkit/linalg/visualizers/plots.py +90 -0
- mathkit/number_theory/__init__.py +48 -0
- mathkit/number_theory/core/__init__.py +5 -0
- mathkit/number_theory/core/base.py +89 -0
- mathkit/number_theory/systems/__init__.py +26 -0
- mathkit/number_theory/systems/continued_fractions.py +107 -0
- mathkit/number_theory/systems/crt.py +62 -0
- mathkit/number_theory/systems/diophantine.py +111 -0
- mathkit/number_theory/systems/modular_arithmetic.py +128 -0
- mathkit/number_theory/systems/primality.py +150 -0
- mathkit/number_theory/systems/totient.py +143 -0
- mathkit/number_theory/tests/__init__.py +0 -0
- mathkit/number_theory/tests/test_continued_fractions.py +43 -0
- mathkit/number_theory/tests/test_crt.py +35 -0
- mathkit/number_theory/tests/test_diophantine.py +43 -0
- mathkit/number_theory/tests/test_gcd_lcm.py +35 -0
- mathkit/number_theory/tests/test_modular_arithmetic.py +53 -0
- mathkit/number_theory/tests/test_primality.py +50 -0
- mathkit/number_theory/tests/test_totient.py +54 -0
- mathkit/number_theory/tests/test_visualizers.py +21 -0
- mathkit/number_theory/utils/__init__.py +5 -0
- mathkit/number_theory/utils/gcd_lcm.py +60 -0
- mathkit/number_theory/visualizers/__init__.py +5 -0
- mathkit/number_theory/visualizers/plots.py +66 -0
- mathkit/numerical_analysis/__init__.py +48 -0
- mathkit/numerical_analysis/core/__init__.py +15 -0
- mathkit/numerical_analysis/core/base.py +154 -0
- mathkit/numerical_analysis/systems/__init__.py +22 -0
- mathkit/numerical_analysis/systems/chebyshev.py +173 -0
- mathkit/numerical_analysis/systems/interpolation.py +131 -0
- mathkit/numerical_analysis/systems/regression.py +121 -0
- mathkit/numerical_analysis/systems/root_finding.py +241 -0
- mathkit/numerical_analysis/systems/splines.py +95 -0
- mathkit/numerical_analysis/tests/__init__.py +0 -0
- mathkit/numerical_analysis/tests/test_chebyshev.py +37 -0
- mathkit/numerical_analysis/tests/test_error_analysis.py +40 -0
- mathkit/numerical_analysis/tests/test_interpolation.py +77 -0
- mathkit/numerical_analysis/tests/test_regression.py +52 -0
- mathkit/numerical_analysis/tests/test_root_finding.py +88 -0
- mathkit/numerical_analysis/tests/test_splines.py +61 -0
- mathkit/numerical_analysis/tests/test_visualizers.py +38 -0
- mathkit/numerical_analysis/utils/__init__.py +9 -0
- mathkit/numerical_analysis/utils/error_analysis.py +154 -0
- mathkit/numerical_analysis/visualizers/__init__.py +13 -0
- mathkit/numerical_analysis/visualizers/plots.py +102 -0
- mathkit/ode_dynamics/__init__.py +44 -0
- mathkit/ode_dynamics/core/__init__.py +5 -0
- mathkit/ode_dynamics/core/base.py +122 -0
- mathkit/ode_dynamics/systems/__init__.py +30 -0
- mathkit/ode_dynamics/systems/bifurcations.py +124 -0
- mathkit/ode_dynamics/systems/limit_cycles.py +99 -0
- mathkit/ode_dynamics/systems/logistic_map.py +157 -0
- mathkit/ode_dynamics/systems/phase_portrait.py +152 -0
- mathkit/ode_dynamics/systems/poincare.py +114 -0
- mathkit/ode_dynamics/systems/stability.py +178 -0
- mathkit/ode_dynamics/tests/__init__.py +0 -0
- mathkit/ode_dynamics/tests/test_bifurcations.py +50 -0
- mathkit/ode_dynamics/tests/test_limit_cycles.py +38 -0
- mathkit/ode_dynamics/tests/test_logistic_map.py +45 -0
- mathkit/ode_dynamics/tests/test_phase_portrait.py +39 -0
- mathkit/ode_dynamics/tests/test_poincare.py +43 -0
- mathkit/ode_dynamics/tests/test_stability.py +83 -0
- mathkit/ode_dynamics/tests/test_visualizers.py +41 -0
- mathkit/ode_dynamics/utils/__init__.py +5 -0
- mathkit/ode_dynamics/utils/period_estimation.py +50 -0
- mathkit/ode_dynamics/visualizers/__init__.py +5 -0
- mathkit/ode_dynamics/visualizers/plots.py +107 -0
- mathkit/optimization/__init__.py +49 -0
- mathkit/optimization/core/__init__.py +5 -0
- mathkit/optimization/core/base.py +157 -0
- mathkit/optimization/systems/__init__.py +19 -0
- mathkit/optimization/systems/conjugate_gradient.py +105 -0
- mathkit/optimization/systems/constrained.py +285 -0
- mathkit/optimization/systems/gradient_descent.py +135 -0
- mathkit/optimization/systems/linear_programming.py +67 -0
- mathkit/optimization/systems/newton_quasi_newton.py +141 -0
- mathkit/optimization/tests/__init__.py +0 -0
- mathkit/optimization/tests/test_comparison.py +36 -0
- mathkit/optimization/tests/test_conjugate_gradient.py +50 -0
- mathkit/optimization/tests/test_constrained.py +77 -0
- mathkit/optimization/tests/test_gradient_descent.py +63 -0
- mathkit/optimization/tests/test_linear_programming.py +37 -0
- mathkit/optimization/tests/test_newton_quasi_newton.py +47 -0
- mathkit/optimization/tests/test_visualizers.py +27 -0
- mathkit/optimization/utils/__init__.py +17 -0
- mathkit/optimization/utils/comparison.py +76 -0
- mathkit/optimization/utils/line_search.py +86 -0
- mathkit/optimization/utils/test_functions.py +172 -0
- mathkit/optimization/visualizers/__init__.py +5 -0
- mathkit/optimization/visualizers/plots.py +84 -0
- mathkit/probability/__init__.py +43 -0
- mathkit/probability/core/__init__.py +5 -0
- mathkit/probability/core/base.py +142 -0
- mathkit/probability/systems/__init__.py +24 -0
- mathkit/probability/systems/continuous.py +159 -0
- mathkit/probability/systems/discrete.py +116 -0
- mathkit/probability/systems/limit_theorems.py +87 -0
- mathkit/probability/systems/markov_chain.py +194 -0
- mathkit/probability/systems/monte_carlo.py +165 -0
- mathkit/probability/tests/__init__.py +0 -0
- mathkit/probability/tests/test_continuous.py +74 -0
- mathkit/probability/tests/test_discrete.py +66 -0
- mathkit/probability/tests/test_limit_theorems.py +37 -0
- mathkit/probability/tests/test_markov_chain.py +68 -0
- mathkit/probability/tests/test_monte_carlo.py +45 -0
- mathkit/probability/tests/test_visualizers.py +36 -0
- mathkit/probability/utils/__init__.py +5 -0
- mathkit/probability/utils/diagnostics.py +43 -0
- mathkit/probability/visualizers/__init__.py +5 -0
- mathkit/probability/visualizers/plots.py +99 -0
- mathkit/special_functions/__init__.py +39 -0
- mathkit/special_functions/core/__init__.py +5 -0
- mathkit/special_functions/core/base.py +38 -0
- mathkit/special_functions/systems/__init__.py +22 -0
- mathkit/special_functions/systems/bessel.py +67 -0
- mathkit/special_functions/systems/fourier_transform.py +179 -0
- mathkit/special_functions/systems/gamma_beta.py +87 -0
- mathkit/special_functions/systems/orthogonal_polynomials.py +133 -0
- mathkit/special_functions/tests/__init__.py +0 -0
- mathkit/special_functions/tests/test_bessel.py +43 -0
- mathkit/special_functions/tests/test_fourier_transform.py +54 -0
- mathkit/special_functions/tests/test_gamma_beta.py +42 -0
- mathkit/special_functions/tests/test_orthogonal_polynomials.py +60 -0
- mathkit/special_functions/tests/test_visualizers.py +20 -0
- mathkit/special_functions/utils/__init__.py +5 -0
- mathkit/special_functions/utils/orthogonality.py +48 -0
- mathkit/special_functions/visualizers/__init__.py +5 -0
- mathkit/special_functions/visualizers/plots.py +73 -0
- mathkit/statistics/__init__.py +54 -0
- mathkit/statistics/core/__init__.py +5 -0
- mathkit/statistics/core/base.py +151 -0
- mathkit/statistics/systems/__init__.py +33 -0
- mathkit/statistics/systems/bootstrap.py +78 -0
- mathkit/statistics/systems/confidence_intervals.py +141 -0
- mathkit/statistics/systems/descriptive.py +86 -0
- mathkit/statistics/systems/hypothesis_tests.py +312 -0
- mathkit/statistics/systems/regression.py +96 -0
- mathkit/statistics/tests/__init__.py +0 -0
- mathkit/statistics/tests/test_bootstrap.py +41 -0
- mathkit/statistics/tests/test_confidence_intervals.py +63 -0
- mathkit/statistics/tests/test_descriptive.py +43 -0
- mathkit/statistics/tests/test_effect_size.py +23 -0
- mathkit/statistics/tests/test_hypothesis_tests.py +99 -0
- mathkit/statistics/tests/test_regression.py +57 -0
- mathkit/statistics/tests/test_visualizers.py +32 -0
- mathkit/statistics/utils/__init__.py +5 -0
- mathkit/statistics/utils/effect_size.py +47 -0
- mathkit/statistics/visualizers/__init__.py +5 -0
- mathkit/statistics/visualizers/plots.py +91 -0
- mathkit/tests/__init__.py +0 -0
- 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)
|