m2m模型翻译
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

560 lines
19 KiB

6 months ago
  1. import itertools
  2. from sympy.combinatorics.fp_groups import FpGroup, FpSubgroup, simplify_presentation
  3. from sympy.combinatorics.free_groups import FreeGroup
  4. from sympy.combinatorics.perm_groups import PermutationGroup
  5. from sympy.core.numbers import igcd
  6. from sympy.ntheory.factor_ import totient
  7. from sympy.core.singleton import S
  8. class GroupHomomorphism:
  9. '''
  10. A class representing group homomorphisms. Instantiate using `homomorphism()`.
  11. References
  12. ==========
  13. .. [1] Holt, D., Eick, B. and O'Brien, E. (2005). Handbook of computational group theory.
  14. '''
  15. def __init__(self, domain, codomain, images):
  16. self.domain = domain
  17. self.codomain = codomain
  18. self.images = images
  19. self._inverses = None
  20. self._kernel = None
  21. self._image = None
  22. def _invs(self):
  23. '''
  24. Return a dictionary with `{gen: inverse}` where `gen` is a rewriting
  25. generator of `codomain` (e.g. strong generator for permutation groups)
  26. and `inverse` is an element of its preimage
  27. '''
  28. image = self.image()
  29. inverses = {}
  30. for k in list(self.images.keys()):
  31. v = self.images[k]
  32. if not (v in inverses
  33. or v.is_identity):
  34. inverses[v] = k
  35. if isinstance(self.codomain, PermutationGroup):
  36. gens = image.strong_gens
  37. else:
  38. gens = image.generators
  39. for g in gens:
  40. if g in inverses or g.is_identity:
  41. continue
  42. w = self.domain.identity
  43. if isinstance(self.codomain, PermutationGroup):
  44. parts = image._strong_gens_slp[g][::-1]
  45. else:
  46. parts = g
  47. for s in parts:
  48. if s in inverses:
  49. w = w*inverses[s]
  50. else:
  51. w = w*inverses[s**-1]**-1
  52. inverses[g] = w
  53. return inverses
  54. def invert(self, g):
  55. '''
  56. Return an element of the preimage of ``g`` or of each element
  57. of ``g`` if ``g`` is a list.
  58. Explanation
  59. ===========
  60. If the codomain is an FpGroup, the inverse for equal
  61. elements might not always be the same unless the FpGroup's
  62. rewriting system is confluent. However, making a system
  63. confluent can be time-consuming. If it's important, try
  64. `self.codomain.make_confluent()` first.
  65. '''
  66. from sympy.combinatorics import Permutation
  67. from sympy.combinatorics.free_groups import FreeGroupElement
  68. if isinstance(g, (Permutation, FreeGroupElement)):
  69. if isinstance(self.codomain, FpGroup):
  70. g = self.codomain.reduce(g)
  71. if self._inverses is None:
  72. self._inverses = self._invs()
  73. image = self.image()
  74. w = self.domain.identity
  75. if isinstance(self.codomain, PermutationGroup):
  76. gens = image.generator_product(g)[::-1]
  77. else:
  78. gens = g
  79. # the following can't be "for s in gens:"
  80. # because that would be equivalent to
  81. # "for s in gens.array_form:" when g is
  82. # a FreeGroupElement. On the other hand,
  83. # when you call gens by index, the generator
  84. # (or inverse) at position i is returned.
  85. for i in range(len(gens)):
  86. s = gens[i]
  87. if s.is_identity:
  88. continue
  89. if s in self._inverses:
  90. w = w*self._inverses[s]
  91. else:
  92. w = w*self._inverses[s**-1]**-1
  93. return w
  94. elif isinstance(g, list):
  95. return [self.invert(e) for e in g]
  96. def kernel(self):
  97. '''
  98. Compute the kernel of `self`.
  99. '''
  100. if self._kernel is None:
  101. self._kernel = self._compute_kernel()
  102. return self._kernel
  103. def _compute_kernel(self):
  104. G = self.domain
  105. G_order = G.order()
  106. if G_order is S.Infinity:
  107. raise NotImplementedError(
  108. "Kernel computation is not implemented for infinite groups")
  109. gens = []
  110. if isinstance(G, PermutationGroup):
  111. K = PermutationGroup(G.identity)
  112. else:
  113. K = FpSubgroup(G, gens, normal=True)
  114. i = self.image().order()
  115. while K.order()*i != G_order:
  116. r = G.random()
  117. k = r*self.invert(self(r))**-1
  118. if k not in K:
  119. gens.append(k)
  120. if isinstance(G, PermutationGroup):
  121. K = PermutationGroup(gens)
  122. else:
  123. K = FpSubgroup(G, gens, normal=True)
  124. return K
  125. def image(self):
  126. '''
  127. Compute the image of `self`.
  128. '''
  129. if self._image is None:
  130. values = list(set(self.images.values()))
  131. if isinstance(self.codomain, PermutationGroup):
  132. self._image = self.codomain.subgroup(values)
  133. else:
  134. self._image = FpSubgroup(self.codomain, values)
  135. return self._image
  136. def _apply(self, elem):
  137. '''
  138. Apply `self` to `elem`.
  139. '''
  140. if elem not in self.domain:
  141. if isinstance(elem, (list, tuple)):
  142. return [self._apply(e) for e in elem]
  143. raise ValueError("The supplied element doesn't belong to the domain")
  144. if elem.is_identity:
  145. return self.codomain.identity
  146. else:
  147. images = self.images
  148. value = self.codomain.identity
  149. if isinstance(self.domain, PermutationGroup):
  150. gens = self.domain.generator_product(elem, original=True)
  151. for g in gens:
  152. if g in self.images:
  153. value = images[g]*value
  154. else:
  155. value = images[g**-1]**-1*value
  156. else:
  157. i = 0
  158. for _, p in elem.array_form:
  159. if p < 0:
  160. g = elem[i]**-1
  161. else:
  162. g = elem[i]
  163. value = value*images[g]**p
  164. i += abs(p)
  165. return value
  166. def __call__(self, elem):
  167. return self._apply(elem)
  168. def is_injective(self):
  169. '''
  170. Check if the homomorphism is injective
  171. '''
  172. return self.kernel().order() == 1
  173. def is_surjective(self):
  174. '''
  175. Check if the homomorphism is surjective
  176. '''
  177. im = self.image().order()
  178. oth = self.codomain.order()
  179. if im is S.Infinity and oth is S.Infinity:
  180. return None
  181. else:
  182. return im == oth
  183. def is_isomorphism(self):
  184. '''
  185. Check if `self` is an isomorphism.
  186. '''
  187. return self.is_injective() and self.is_surjective()
  188. def is_trivial(self):
  189. '''
  190. Check is `self` is a trivial homomorphism, i.e. all elements
  191. are mapped to the identity.
  192. '''
  193. return self.image().order() == 1
  194. def compose(self, other):
  195. '''
  196. Return the composition of `self` and `other`, i.e.
  197. the homomorphism phi such that for all g in the domain
  198. of `other`, phi(g) = self(other(g))
  199. '''
  200. if not other.image().is_subgroup(self.domain):
  201. raise ValueError("The image of `other` must be a subgroup of "
  202. "the domain of `self`")
  203. images = {g: self(other(g)) for g in other.images}
  204. return GroupHomomorphism(other.domain, self.codomain, images)
  205. def restrict_to(self, H):
  206. '''
  207. Return the restriction of the homomorphism to the subgroup `H`
  208. of the domain.
  209. '''
  210. if not isinstance(H, PermutationGroup) or not H.is_subgroup(self.domain):
  211. raise ValueError("Given H is not a subgroup of the domain")
  212. domain = H
  213. images = {g: self(g) for g in H.generators}
  214. return GroupHomomorphism(domain, self.codomain, images)
  215. def invert_subgroup(self, H):
  216. '''
  217. Return the subgroup of the domain that is the inverse image
  218. of the subgroup ``H`` of the homomorphism image
  219. '''
  220. if not H.is_subgroup(self.image()):
  221. raise ValueError("Given H is not a subgroup of the image")
  222. gens = []
  223. P = PermutationGroup(self.image().identity)
  224. for h in H.generators:
  225. h_i = self.invert(h)
  226. if h_i not in P:
  227. gens.append(h_i)
  228. P = PermutationGroup(gens)
  229. for k in self.kernel().generators:
  230. if k*h_i not in P:
  231. gens.append(k*h_i)
  232. P = PermutationGroup(gens)
  233. return P
  234. def homomorphism(domain, codomain, gens, images=(), check=True):
  235. '''
  236. Create (if possible) a group homomorphism from the group ``domain``
  237. to the group ``codomain`` defined by the images of the domain's
  238. generators ``gens``. ``gens`` and ``images`` can be either lists or tuples
  239. of equal sizes. If ``gens`` is a proper subset of the group's generators,
  240. the unspecified generators will be mapped to the identity. If the
  241. images are not specified, a trivial homomorphism will be created.
  242. If the given images of the generators do not define a homomorphism,
  243. an exception is raised.
  244. If ``check`` is ``False``, do not check whether the given images actually
  245. define a homomorphism.
  246. '''
  247. if not isinstance(domain, (PermutationGroup, FpGroup, FreeGroup)):
  248. raise TypeError("The domain must be a group")
  249. if not isinstance(codomain, (PermutationGroup, FpGroup, FreeGroup)):
  250. raise TypeError("The codomain must be a group")
  251. generators = domain.generators
  252. if not all(g in generators for g in gens):
  253. raise ValueError("The supplied generators must be a subset of the domain's generators")
  254. if not all(g in codomain for g in images):
  255. raise ValueError("The images must be elements of the codomain")
  256. if images and len(images) != len(gens):
  257. raise ValueError("The number of images must be equal to the number of generators")
  258. gens = list(gens)
  259. images = list(images)
  260. images.extend([codomain.identity]*(len(generators)-len(images)))
  261. gens.extend([g for g in generators if g not in gens])
  262. images = dict(zip(gens,images))
  263. if check and not _check_homomorphism(domain, codomain, images):
  264. raise ValueError("The given images do not define a homomorphism")
  265. return GroupHomomorphism(domain, codomain, images)
  266. def _check_homomorphism(domain, codomain, images):
  267. if hasattr(domain, 'relators'):
  268. rels = domain.relators
  269. else:
  270. gens = domain.presentation().generators
  271. rels = domain.presentation().relators
  272. identity = codomain.identity
  273. def _image(r):
  274. if r.is_identity:
  275. return identity
  276. else:
  277. w = identity
  278. r_arr = r.array_form
  279. i = 0
  280. j = 0
  281. # i is the index for r and j is for
  282. # r_arr. r_arr[j] is the tuple (sym, p)
  283. # where sym is the generator symbol
  284. # and p is the power to which it is
  285. # raised while r[i] is a generator
  286. # (not just its symbol) or the inverse of
  287. # a generator - hence the need for
  288. # both indices
  289. while i < len(r):
  290. power = r_arr[j][1]
  291. if isinstance(domain, PermutationGroup) and r[i] in gens:
  292. s = domain.generators[gens.index(r[i])]
  293. else:
  294. s = r[i]
  295. if s in images:
  296. w = w*images[s]**power
  297. elif s**-1 in images:
  298. w = w*images[s**-1]**power
  299. i += abs(power)
  300. j += 1
  301. return w
  302. for r in rels:
  303. if isinstance(codomain, FpGroup):
  304. s = codomain.equals(_image(r), identity)
  305. if s is None:
  306. # only try to make the rewriting system
  307. # confluent when it can't determine the
  308. # truth of equality otherwise
  309. success = codomain.make_confluent()
  310. s = codomain.equals(_image(r), identity)
  311. if s is None and not success:
  312. raise RuntimeError("Can't determine if the images "
  313. "define a homomorphism. Try increasing "
  314. "the maximum number of rewriting rules "
  315. "(group._rewriting_system.set_max(new_value); "
  316. "the current value is stored in group._rewriting"
  317. "_system.maxeqns)")
  318. else:
  319. s = _image(r).is_identity
  320. if not s:
  321. return False
  322. return True
  323. def orbit_homomorphism(group, omega):
  324. '''
  325. Return the homomorphism induced by the action of the permutation
  326. group ``group`` on the set ``omega`` that is closed under the action.
  327. '''
  328. from sympy.combinatorics import Permutation
  329. from sympy.combinatorics.named_groups import SymmetricGroup
  330. codomain = SymmetricGroup(len(omega))
  331. identity = codomain.identity
  332. omega = list(omega)
  333. images = {g: identity*Permutation([omega.index(o^g) for o in omega]) for g in group.generators}
  334. group._schreier_sims(base=omega)
  335. H = GroupHomomorphism(group, codomain, images)
  336. if len(group.basic_stabilizers) > len(omega):
  337. H._kernel = group.basic_stabilizers[len(omega)]
  338. else:
  339. H._kernel = PermutationGroup([group.identity])
  340. return H
  341. def block_homomorphism(group, blocks):
  342. '''
  343. Return the homomorphism induced by the action of the permutation
  344. group ``group`` on the block system ``blocks``. The latter should be
  345. of the same form as returned by the ``minimal_block`` method for
  346. permutation groups, namely a list of length ``group.degree`` where
  347. the i-th entry is a representative of the block i belongs to.
  348. '''
  349. from sympy.combinatorics import Permutation
  350. from sympy.combinatorics.named_groups import SymmetricGroup
  351. n = len(blocks)
  352. # number the blocks; m is the total number,
  353. # b is such that b[i] is the number of the block i belongs to,
  354. # p is the list of length m such that p[i] is the representative
  355. # of the i-th block
  356. m = 0
  357. p = []
  358. b = [None]*n
  359. for i in range(n):
  360. if blocks[i] == i:
  361. p.append(i)
  362. b[i] = m
  363. m += 1
  364. for i in range(n):
  365. b[i] = b[blocks[i]]
  366. codomain = SymmetricGroup(m)
  367. # the list corresponding to the identity permutation in codomain
  368. identity = range(m)
  369. images = {g: Permutation([b[p[i]^g] for i in identity]) for g in group.generators}
  370. H = GroupHomomorphism(group, codomain, images)
  371. return H
  372. def group_isomorphism(G, H, isomorphism=True):
  373. '''
  374. Compute an isomorphism between 2 given groups.
  375. Parameters
  376. ==========
  377. G : A finite ``FpGroup`` or a ``PermutationGroup``.
  378. First group.
  379. H : A finite ``FpGroup`` or a ``PermutationGroup``
  380. Second group.
  381. isomorphism : bool
  382. This is used to avoid the computation of homomorphism
  383. when the user only wants to check if there exists
  384. an isomorphism between the groups.
  385. Returns
  386. =======
  387. If isomorphism = False -- Returns a boolean.
  388. If isomorphism = True -- Returns a boolean and an isomorphism between `G` and `H`.
  389. Examples
  390. ========
  391. >>> from sympy.combinatorics import free_group, Permutation
  392. >>> from sympy.combinatorics.perm_groups import PermutationGroup
  393. >>> from sympy.combinatorics.fp_groups import FpGroup
  394. >>> from sympy.combinatorics.homomorphisms import group_isomorphism
  395. >>> from sympy.combinatorics.named_groups import DihedralGroup, AlternatingGroup
  396. >>> D = DihedralGroup(8)
  397. >>> p = Permutation(0, 1, 2, 3, 4, 5, 6, 7)
  398. >>> P = PermutationGroup(p)
  399. >>> group_isomorphism(D, P)
  400. (False, None)
  401. >>> F, a, b = free_group("a, b")
  402. >>> G = FpGroup(F, [a**3, b**3, (a*b)**2])
  403. >>> H = AlternatingGroup(4)
  404. >>> (check, T) = group_isomorphism(G, H)
  405. >>> check
  406. True
  407. >>> T(b*a*b**-1*a**-1*b**-1)
  408. (0 2 3)
  409. Notes
  410. =====
  411. Uses the approach suggested by Robert Tarjan to compute the isomorphism between two groups.
  412. First, the generators of ``G`` are mapped to the elements of ``H`` and
  413. we check if the mapping induces an isomorphism.
  414. '''
  415. if not isinstance(G, (PermutationGroup, FpGroup)):
  416. raise TypeError("The group must be a PermutationGroup or an FpGroup")
  417. if not isinstance(H, (PermutationGroup, FpGroup)):
  418. raise TypeError("The group must be a PermutationGroup or an FpGroup")
  419. if isinstance(G, FpGroup) and isinstance(H, FpGroup):
  420. G = simplify_presentation(G)
  421. H = simplify_presentation(H)
  422. # Two infinite FpGroups with the same generators are isomorphic
  423. # when the relators are same but are ordered differently.
  424. if G.generators == H.generators and (G.relators).sort() == (H.relators).sort():
  425. if not isomorphism:
  426. return True
  427. return (True, homomorphism(G, H, G.generators, H.generators))
  428. # `_H` is the permutation group isomorphic to `H`.
  429. _H = H
  430. g_order = G.order()
  431. h_order = H.order()
  432. if g_order is S.Infinity:
  433. raise NotImplementedError("Isomorphism methods are not implemented for infinite groups.")
  434. if isinstance(H, FpGroup):
  435. if h_order is S.Infinity:
  436. raise NotImplementedError("Isomorphism methods are not implemented for infinite groups.")
  437. _H, h_isomorphism = H._to_perm_group()
  438. if (g_order != h_order) or (G.is_abelian != H.is_abelian):
  439. if not isomorphism:
  440. return False
  441. return (False, None)
  442. if not isomorphism:
  443. # Two groups of the same cyclic numbered order
  444. # are isomorphic to each other.
  445. n = g_order
  446. if (igcd(n, totient(n))) == 1:
  447. return True
  448. # Match the generators of `G` with subsets of `_H`
  449. gens = list(G.generators)
  450. for subset in itertools.permutations(_H, len(gens)):
  451. images = list(subset)
  452. images.extend([_H.identity]*(len(G.generators)-len(images)))
  453. _images = dict(zip(gens,images))
  454. if _check_homomorphism(G, _H, _images):
  455. if isinstance(H, FpGroup):
  456. images = h_isomorphism.invert(images)
  457. T = homomorphism(G, H, G.generators, images, check=False)
  458. if T.is_isomorphism():
  459. # It is a valid isomorphism
  460. if not isomorphism:
  461. return True
  462. return (True, T)
  463. if not isomorphism:
  464. return False
  465. return (False, None)
  466. def is_isomorphic(G, H):
  467. '''
  468. Check if the groups are isomorphic to each other
  469. Parameters
  470. ==========
  471. G : A finite ``FpGroup`` or a ``PermutationGroup``
  472. First group.
  473. H : A finite ``FpGroup`` or a ``PermutationGroup``
  474. Second group.
  475. Returns
  476. =======
  477. boolean
  478. '''
  479. return group_isomorphism(G, H, isomorphism=False)