baseclasses.py 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979
  1. from sympy.core import S, Basic, Dict, Symbol, Tuple, sympify
  2. from sympy.core.symbol import Str
  3. from sympy.sets import Set, FiniteSet, EmptySet
  4. from sympy.utilities.iterables import iterable
  5. class Class(Set):
  6. r"""
  7. The base class for any kind of class in the set-theoretic sense.
  8. Explanation
  9. ===========
  10. In axiomatic set theories, everything is a class. A class which
  11. can be a member of another class is a set. A class which is not a
  12. member of another class is a proper class. The class `\{1, 2\}`
  13. is a set; the class of all sets is a proper class.
  14. This class is essentially a synonym for :class:`sympy.core.Set`.
  15. The goal of this class is to assure easier migration to the
  16. eventual proper implementation of set theory.
  17. """
  18. is_proper = False
  19. class Object(Symbol):
  20. """
  21. The base class for any kind of object in an abstract category.
  22. Explanation
  23. ===========
  24. While technically any instance of :class:`~.Basic` will do, this
  25. class is the recommended way to create abstract objects in
  26. abstract categories.
  27. """
  28. class Morphism(Basic):
  29. """
  30. The base class for any morphism in an abstract category.
  31. Explanation
  32. ===========
  33. In abstract categories, a morphism is an arrow between two
  34. category objects. The object where the arrow starts is called the
  35. domain, while the object where the arrow ends is called the
  36. codomain.
  37. Two morphisms between the same pair of objects are considered to
  38. be the same morphisms. To distinguish between morphisms between
  39. the same objects use :class:`NamedMorphism`.
  40. It is prohibited to instantiate this class. Use one of the
  41. derived classes instead.
  42. See Also
  43. ========
  44. IdentityMorphism, NamedMorphism, CompositeMorphism
  45. """
  46. def __new__(cls, domain, codomain):
  47. raise(NotImplementedError(
  48. "Cannot instantiate Morphism. Use derived classes instead."))
  49. @property
  50. def domain(self):
  51. """
  52. Returns the domain of the morphism.
  53. Examples
  54. ========
  55. >>> from sympy.categories import Object, NamedMorphism
  56. >>> A = Object("A")
  57. >>> B = Object("B")
  58. >>> f = NamedMorphism(A, B, "f")
  59. >>> f.domain
  60. Object("A")
  61. """
  62. return self.args[0]
  63. @property
  64. def codomain(self):
  65. """
  66. Returns the codomain of the morphism.
  67. Examples
  68. ========
  69. >>> from sympy.categories import Object, NamedMorphism
  70. >>> A = Object("A")
  71. >>> B = Object("B")
  72. >>> f = NamedMorphism(A, B, "f")
  73. >>> f.codomain
  74. Object("B")
  75. """
  76. return self.args[1]
  77. def compose(self, other):
  78. r"""
  79. Composes self with the supplied morphism.
  80. The order of elements in the composition is the usual order,
  81. i.e., to construct `g\circ f` use ``g.compose(f)``.
  82. Examples
  83. ========
  84. >>> from sympy.categories import Object, NamedMorphism
  85. >>> A = Object("A")
  86. >>> B = Object("B")
  87. >>> C = Object("C")
  88. >>> f = NamedMorphism(A, B, "f")
  89. >>> g = NamedMorphism(B, C, "g")
  90. >>> g * f
  91. CompositeMorphism((NamedMorphism(Object("A"), Object("B"), "f"),
  92. NamedMorphism(Object("B"), Object("C"), "g")))
  93. >>> (g * f).domain
  94. Object("A")
  95. >>> (g * f).codomain
  96. Object("C")
  97. """
  98. return CompositeMorphism(other, self)
  99. def __mul__(self, other):
  100. r"""
  101. Composes self with the supplied morphism.
  102. The semantics of this operation is given by the following
  103. equation: ``g * f == g.compose(f)`` for composable morphisms
  104. ``g`` and ``f``.
  105. See Also
  106. ========
  107. compose
  108. """
  109. return self.compose(other)
  110. class IdentityMorphism(Morphism):
  111. """
  112. Represents an identity morphism.
  113. Explanation
  114. ===========
  115. An identity morphism is a morphism with equal domain and codomain,
  116. which acts as an identity with respect to composition.
  117. Examples
  118. ========
  119. >>> from sympy.categories import Object, NamedMorphism, IdentityMorphism
  120. >>> A = Object("A")
  121. >>> B = Object("B")
  122. >>> f = NamedMorphism(A, B, "f")
  123. >>> id_A = IdentityMorphism(A)
  124. >>> id_B = IdentityMorphism(B)
  125. >>> f * id_A == f
  126. True
  127. >>> id_B * f == f
  128. True
  129. See Also
  130. ========
  131. Morphism
  132. """
  133. def __new__(cls, domain):
  134. return Basic.__new__(cls, domain)
  135. @property
  136. def codomain(self):
  137. return self.domain
  138. class NamedMorphism(Morphism):
  139. """
  140. Represents a morphism which has a name.
  141. Explanation
  142. ===========
  143. Names are used to distinguish between morphisms which have the
  144. same domain and codomain: two named morphisms are equal if they
  145. have the same domains, codomains, and names.
  146. Examples
  147. ========
  148. >>> from sympy.categories import Object, NamedMorphism
  149. >>> A = Object("A")
  150. >>> B = Object("B")
  151. >>> f = NamedMorphism(A, B, "f")
  152. >>> f
  153. NamedMorphism(Object("A"), Object("B"), "f")
  154. >>> f.name
  155. 'f'
  156. See Also
  157. ========
  158. Morphism
  159. """
  160. def __new__(cls, domain, codomain, name):
  161. if not name:
  162. raise ValueError("Empty morphism names not allowed.")
  163. if not isinstance(name, Str):
  164. name = Str(name)
  165. return Basic.__new__(cls, domain, codomain, name)
  166. @property
  167. def name(self):
  168. """
  169. Returns the name of the morphism.
  170. Examples
  171. ========
  172. >>> from sympy.categories import Object, NamedMorphism
  173. >>> A = Object("A")
  174. >>> B = Object("B")
  175. >>> f = NamedMorphism(A, B, "f")
  176. >>> f.name
  177. 'f'
  178. """
  179. return self.args[2].name
  180. class CompositeMorphism(Morphism):
  181. r"""
  182. Represents a morphism which is a composition of other morphisms.
  183. Explanation
  184. ===========
  185. Two composite morphisms are equal if the morphisms they were
  186. obtained from (components) are the same and were listed in the
  187. same order.
  188. The arguments to the constructor for this class should be listed
  189. in diagram order: to obtain the composition `g\circ f` from the
  190. instances of :class:`Morphism` ``g`` and ``f`` use
  191. ``CompositeMorphism(f, g)``.
  192. Examples
  193. ========
  194. >>> from sympy.categories import Object, NamedMorphism, CompositeMorphism
  195. >>> A = Object("A")
  196. >>> B = Object("B")
  197. >>> C = Object("C")
  198. >>> f = NamedMorphism(A, B, "f")
  199. >>> g = NamedMorphism(B, C, "g")
  200. >>> g * f
  201. CompositeMorphism((NamedMorphism(Object("A"), Object("B"), "f"),
  202. NamedMorphism(Object("B"), Object("C"), "g")))
  203. >>> CompositeMorphism(f, g) == g * f
  204. True
  205. """
  206. @staticmethod
  207. def _add_morphism(t, morphism):
  208. """
  209. Intelligently adds ``morphism`` to tuple ``t``.
  210. Explanation
  211. ===========
  212. If ``morphism`` is a composite morphism, its components are
  213. added to the tuple. If ``morphism`` is an identity, nothing
  214. is added to the tuple.
  215. No composability checks are performed.
  216. """
  217. if isinstance(morphism, CompositeMorphism):
  218. # ``morphism`` is a composite morphism; we have to
  219. # denest its components.
  220. return t + morphism.components
  221. elif isinstance(morphism, IdentityMorphism):
  222. # ``morphism`` is an identity. Nothing happens.
  223. return t
  224. else:
  225. return t + Tuple(morphism)
  226. def __new__(cls, *components):
  227. if components and not isinstance(components[0], Morphism):
  228. # Maybe the user has explicitly supplied a list of
  229. # morphisms.
  230. return CompositeMorphism.__new__(cls, *components[0])
  231. normalised_components = Tuple()
  232. for current, following in zip(components, components[1:]):
  233. if not isinstance(current, Morphism) or \
  234. not isinstance(following, Morphism):
  235. raise TypeError("All components must be morphisms.")
  236. if current.codomain != following.domain:
  237. raise ValueError("Uncomposable morphisms.")
  238. normalised_components = CompositeMorphism._add_morphism(
  239. normalised_components, current)
  240. # We haven't added the last morphism to the list of normalised
  241. # components. Add it now.
  242. normalised_components = CompositeMorphism._add_morphism(
  243. normalised_components, components[-1])
  244. if not normalised_components:
  245. # If ``normalised_components`` is empty, only identities
  246. # were supplied. Since they all were composable, they are
  247. # all the same identities.
  248. return components[0]
  249. elif len(normalised_components) == 1:
  250. # No sense to construct a whole CompositeMorphism.
  251. return normalised_components[0]
  252. return Basic.__new__(cls, normalised_components)
  253. @property
  254. def components(self):
  255. """
  256. Returns the components of this composite morphism.
  257. Examples
  258. ========
  259. >>> from sympy.categories import Object, NamedMorphism
  260. >>> A = Object("A")
  261. >>> B = Object("B")
  262. >>> C = Object("C")
  263. >>> f = NamedMorphism(A, B, "f")
  264. >>> g = NamedMorphism(B, C, "g")
  265. >>> (g * f).components
  266. (NamedMorphism(Object("A"), Object("B"), "f"),
  267. NamedMorphism(Object("B"), Object("C"), "g"))
  268. """
  269. return self.args[0]
  270. @property
  271. def domain(self):
  272. """
  273. Returns the domain of this composite morphism.
  274. The domain of the composite morphism is the domain of its
  275. first component.
  276. Examples
  277. ========
  278. >>> from sympy.categories import Object, NamedMorphism
  279. >>> A = Object("A")
  280. >>> B = Object("B")
  281. >>> C = Object("C")
  282. >>> f = NamedMorphism(A, B, "f")
  283. >>> g = NamedMorphism(B, C, "g")
  284. >>> (g * f).domain
  285. Object("A")
  286. """
  287. return self.components[0].domain
  288. @property
  289. def codomain(self):
  290. """
  291. Returns the codomain of this composite morphism.
  292. The codomain of the composite morphism is the codomain of its
  293. last component.
  294. Examples
  295. ========
  296. >>> from sympy.categories import Object, NamedMorphism
  297. >>> A = Object("A")
  298. >>> B = Object("B")
  299. >>> C = Object("C")
  300. >>> f = NamedMorphism(A, B, "f")
  301. >>> g = NamedMorphism(B, C, "g")
  302. >>> (g * f).codomain
  303. Object("C")
  304. """
  305. return self.components[-1].codomain
  306. def flatten(self, new_name):
  307. """
  308. Forgets the composite structure of this morphism.
  309. Explanation
  310. ===========
  311. If ``new_name`` is not empty, returns a :class:`NamedMorphism`
  312. with the supplied name, otherwise returns a :class:`Morphism`.
  313. In both cases the domain of the new morphism is the domain of
  314. this composite morphism and the codomain of the new morphism
  315. is the codomain of this composite morphism.
  316. Examples
  317. ========
  318. >>> from sympy.categories import Object, NamedMorphism
  319. >>> A = Object("A")
  320. >>> B = Object("B")
  321. >>> C = Object("C")
  322. >>> f = NamedMorphism(A, B, "f")
  323. >>> g = NamedMorphism(B, C, "g")
  324. >>> (g * f).flatten("h")
  325. NamedMorphism(Object("A"), Object("C"), "h")
  326. """
  327. return NamedMorphism(self.domain, self.codomain, new_name)
  328. class Category(Basic):
  329. r"""
  330. An (abstract) category.
  331. Explanation
  332. ===========
  333. A category [JoyOfCats] is a quadruple `\mbox{K} = (O, \hom, id,
  334. \circ)` consisting of
  335. * a (set-theoretical) class `O`, whose members are called
  336. `K`-objects,
  337. * for each pair `(A, B)` of `K`-objects, a set `\hom(A, B)` whose
  338. members are called `K`-morphisms from `A` to `B`,
  339. * for a each `K`-object `A`, a morphism `id:A\rightarrow A`,
  340. called the `K`-identity of `A`,
  341. * a composition law `\circ` associating with every `K`-morphisms
  342. `f:A\rightarrow B` and `g:B\rightarrow C` a `K`-morphism `g\circ
  343. f:A\rightarrow C`, called the composite of `f` and `g`.
  344. Composition is associative, `K`-identities are identities with
  345. respect to composition, and the sets `\hom(A, B)` are pairwise
  346. disjoint.
  347. This class knows nothing about its objects and morphisms.
  348. Concrete cases of (abstract) categories should be implemented as
  349. classes derived from this one.
  350. Certain instances of :class:`Diagram` can be asserted to be
  351. commutative in a :class:`Category` by supplying the argument
  352. ``commutative_diagrams`` in the constructor.
  353. Examples
  354. ========
  355. >>> from sympy.categories import Object, NamedMorphism, Diagram, Category
  356. >>> from sympy import FiniteSet
  357. >>> A = Object("A")
  358. >>> B = Object("B")
  359. >>> C = Object("C")
  360. >>> f = NamedMorphism(A, B, "f")
  361. >>> g = NamedMorphism(B, C, "g")
  362. >>> d = Diagram([f, g])
  363. >>> K = Category("K", commutative_diagrams=[d])
  364. >>> K.commutative_diagrams == FiniteSet(d)
  365. True
  366. See Also
  367. ========
  368. Diagram
  369. """
  370. def __new__(cls, name, objects=EmptySet, commutative_diagrams=EmptySet):
  371. if not name:
  372. raise ValueError("A Category cannot have an empty name.")
  373. if not isinstance(name, Str):
  374. name = Str(name)
  375. if not isinstance(objects, Class):
  376. objects = Class(objects)
  377. new_category = Basic.__new__(cls, name, objects,
  378. FiniteSet(*commutative_diagrams))
  379. return new_category
  380. @property
  381. def name(self):
  382. """
  383. Returns the name of this category.
  384. Examples
  385. ========
  386. >>> from sympy.categories import Category
  387. >>> K = Category("K")
  388. >>> K.name
  389. 'K'
  390. """
  391. return self.args[0].name
  392. @property
  393. def objects(self):
  394. """
  395. Returns the class of objects of this category.
  396. Examples
  397. ========
  398. >>> from sympy.categories import Object, Category
  399. >>> from sympy import FiniteSet
  400. >>> A = Object("A")
  401. >>> B = Object("B")
  402. >>> K = Category("K", FiniteSet(A, B))
  403. >>> K.objects
  404. Class({Object("A"), Object("B")})
  405. """
  406. return self.args[1]
  407. @property
  408. def commutative_diagrams(self):
  409. """
  410. Returns the :class:`~.FiniteSet` of diagrams which are known to
  411. be commutative in this category.
  412. Examples
  413. ========
  414. >>> from sympy.categories import Object, NamedMorphism, Diagram, Category
  415. >>> from sympy import FiniteSet
  416. >>> A = Object("A")
  417. >>> B = Object("B")
  418. >>> C = Object("C")
  419. >>> f = NamedMorphism(A, B, "f")
  420. >>> g = NamedMorphism(B, C, "g")
  421. >>> d = Diagram([f, g])
  422. >>> K = Category("K", commutative_diagrams=[d])
  423. >>> K.commutative_diagrams == FiniteSet(d)
  424. True
  425. """
  426. return self.args[2]
  427. def hom(self, A, B):
  428. raise NotImplementedError(
  429. "hom-sets are not implemented in Category.")
  430. def all_morphisms(self):
  431. raise NotImplementedError(
  432. "Obtaining the class of morphisms is not implemented in Category.")
  433. class Diagram(Basic):
  434. r"""
  435. Represents a diagram in a certain category.
  436. Explanation
  437. ===========
  438. Informally, a diagram is a collection of objects of a category and
  439. certain morphisms between them. A diagram is still a monoid with
  440. respect to morphism composition; i.e., identity morphisms, as well
  441. as all composites of morphisms included in the diagram belong to
  442. the diagram. For a more formal approach to this notion see
  443. [Pare1970].
  444. The components of composite morphisms are also added to the
  445. diagram. No properties are assigned to such morphisms by default.
  446. A commutative diagram is often accompanied by a statement of the
  447. following kind: "if such morphisms with such properties exist,
  448. then such morphisms which such properties exist and the diagram is
  449. commutative". To represent this, an instance of :class:`Diagram`
  450. includes a collection of morphisms which are the premises and
  451. another collection of conclusions. ``premises`` and
  452. ``conclusions`` associate morphisms belonging to the corresponding
  453. categories with the :class:`~.FiniteSet`'s of their properties.
  454. The set of properties of a composite morphism is the intersection
  455. of the sets of properties of its components. The domain and
  456. codomain of a conclusion morphism should be among the domains and
  457. codomains of the morphisms listed as the premises of a diagram.
  458. No checks are carried out of whether the supplied object and
  459. morphisms do belong to one and the same category.
  460. Examples
  461. ========
  462. >>> from sympy.categories import Object, NamedMorphism, Diagram
  463. >>> from sympy import pprint, default_sort_key
  464. >>> A = Object("A")
  465. >>> B = Object("B")
  466. >>> C = Object("C")
  467. >>> f = NamedMorphism(A, B, "f")
  468. >>> g = NamedMorphism(B, C, "g")
  469. >>> d = Diagram([f, g])
  470. >>> premises_keys = sorted(d.premises.keys(), key=default_sort_key)
  471. >>> pprint(premises_keys, use_unicode=False)
  472. [g*f:A-->C, id:A-->A, id:B-->B, id:C-->C, f:A-->B, g:B-->C]
  473. >>> pprint(d.premises, use_unicode=False)
  474. {g*f:A-->C: EmptySet, id:A-->A: EmptySet, id:B-->B: EmptySet, id:C-->C: EmptyS
  475. et, f:A-->B: EmptySet, g:B-->C: EmptySet}
  476. >>> d = Diagram([f, g], {g * f: "unique"})
  477. >>> pprint(d.conclusions,use_unicode=False)
  478. {g*f:A-->C: {unique}}
  479. References
  480. ==========
  481. [Pare1970] B. Pareigis: Categories and functors. Academic Press, 1970.
  482. """
  483. @staticmethod
  484. def _set_dict_union(dictionary, key, value):
  485. """
  486. If ``key`` is in ``dictionary``, set the new value of ``key``
  487. to be the union between the old value and ``value``.
  488. Otherwise, set the value of ``key`` to ``value.
  489. Returns ``True`` if the key already was in the dictionary and
  490. ``False`` otherwise.
  491. """
  492. if key in dictionary:
  493. dictionary[key] = dictionary[key] | value
  494. return True
  495. else:
  496. dictionary[key] = value
  497. return False
  498. @staticmethod
  499. def _add_morphism_closure(morphisms, morphism, props, add_identities=True,
  500. recurse_composites=True):
  501. """
  502. Adds a morphism and its attributes to the supplied dictionary
  503. ``morphisms``. If ``add_identities`` is True, also adds the
  504. identity morphisms for the domain and the codomain of
  505. ``morphism``.
  506. """
  507. if not Diagram._set_dict_union(morphisms, morphism, props):
  508. # We have just added a new morphism.
  509. if isinstance(morphism, IdentityMorphism):
  510. if props:
  511. # Properties for identity morphisms don't really
  512. # make sense, because very much is known about
  513. # identity morphisms already, so much that they
  514. # are trivial. Having properties for identity
  515. # morphisms would only be confusing.
  516. raise ValueError(
  517. "Instances of IdentityMorphism cannot have properties.")
  518. return
  519. if add_identities:
  520. empty = EmptySet
  521. id_dom = IdentityMorphism(morphism.domain)
  522. id_cod = IdentityMorphism(morphism.codomain)
  523. Diagram._set_dict_union(morphisms, id_dom, empty)
  524. Diagram._set_dict_union(morphisms, id_cod, empty)
  525. for existing_morphism, existing_props in list(morphisms.items()):
  526. new_props = existing_props & props
  527. if morphism.domain == existing_morphism.codomain:
  528. left = morphism * existing_morphism
  529. Diagram._set_dict_union(morphisms, left, new_props)
  530. if morphism.codomain == existing_morphism.domain:
  531. right = existing_morphism * morphism
  532. Diagram._set_dict_union(morphisms, right, new_props)
  533. if isinstance(morphism, CompositeMorphism) and recurse_composites:
  534. # This is a composite morphism, add its components as
  535. # well.
  536. empty = EmptySet
  537. for component in morphism.components:
  538. Diagram._add_morphism_closure(morphisms, component, empty,
  539. add_identities)
  540. def __new__(cls, *args):
  541. """
  542. Construct a new instance of Diagram.
  543. Explanation
  544. ===========
  545. If no arguments are supplied, an empty diagram is created.
  546. If at least an argument is supplied, ``args[0]`` is
  547. interpreted as the premises of the diagram. If ``args[0]`` is
  548. a list, it is interpreted as a list of :class:`Morphism`'s, in
  549. which each :class:`Morphism` has an empty set of properties.
  550. If ``args[0]`` is a Python dictionary or a :class:`Dict`, it
  551. is interpreted as a dictionary associating to some
  552. :class:`Morphism`'s some properties.
  553. If at least two arguments are supplied ``args[1]`` is
  554. interpreted as the conclusions of the diagram. The type of
  555. ``args[1]`` is interpreted in exactly the same way as the type
  556. of ``args[0]``. If only one argument is supplied, the diagram
  557. has no conclusions.
  558. Examples
  559. ========
  560. >>> from sympy.categories import Object, NamedMorphism
  561. >>> from sympy.categories import IdentityMorphism, Diagram
  562. >>> A = Object("A")
  563. >>> B = Object("B")
  564. >>> C = Object("C")
  565. >>> f = NamedMorphism(A, B, "f")
  566. >>> g = NamedMorphism(B, C, "g")
  567. >>> d = Diagram([f, g])
  568. >>> IdentityMorphism(A) in d.premises.keys()
  569. True
  570. >>> g * f in d.premises.keys()
  571. True
  572. >>> d = Diagram([f, g], {g * f: "unique"})
  573. >>> d.conclusions[g * f]
  574. {unique}
  575. """
  576. premises = {}
  577. conclusions = {}
  578. # Here we will keep track of the objects which appear in the
  579. # premises.
  580. objects = EmptySet
  581. if len(args) >= 1:
  582. # We've got some premises in the arguments.
  583. premises_arg = args[0]
  584. if isinstance(premises_arg, list):
  585. # The user has supplied a list of morphisms, none of
  586. # which have any attributes.
  587. empty = EmptySet
  588. for morphism in premises_arg:
  589. objects |= FiniteSet(morphism.domain, morphism.codomain)
  590. Diagram._add_morphism_closure(premises, morphism, empty)
  591. elif isinstance(premises_arg, (dict, Dict)):
  592. # The user has supplied a dictionary of morphisms and
  593. # their properties.
  594. for morphism, props in premises_arg.items():
  595. objects |= FiniteSet(morphism.domain, morphism.codomain)
  596. Diagram._add_morphism_closure(
  597. premises, morphism, FiniteSet(*props) if iterable(props) else FiniteSet(props))
  598. if len(args) >= 2:
  599. # We also have some conclusions.
  600. conclusions_arg = args[1]
  601. if isinstance(conclusions_arg, list):
  602. # The user has supplied a list of morphisms, none of
  603. # which have any attributes.
  604. empty = EmptySet
  605. for morphism in conclusions_arg:
  606. # Check that no new objects appear in conclusions.
  607. if ((sympify(objects.contains(morphism.domain)) is S.true) and
  608. (sympify(objects.contains(morphism.codomain)) is S.true)):
  609. # No need to add identities and recurse
  610. # composites this time.
  611. Diagram._add_morphism_closure(
  612. conclusions, morphism, empty, add_identities=False,
  613. recurse_composites=False)
  614. elif isinstance(conclusions_arg, dict) or \
  615. isinstance(conclusions_arg, Dict):
  616. # The user has supplied a dictionary of morphisms and
  617. # their properties.
  618. for morphism, props in conclusions_arg.items():
  619. # Check that no new objects appear in conclusions.
  620. if (morphism.domain in objects) and \
  621. (morphism.codomain in objects):
  622. # No need to add identities and recurse
  623. # composites this time.
  624. Diagram._add_morphism_closure(
  625. conclusions, morphism, FiniteSet(*props) if iterable(props) else FiniteSet(props),
  626. add_identities=False, recurse_composites=False)
  627. return Basic.__new__(cls, Dict(premises), Dict(conclusions), objects)
  628. @property
  629. def premises(self):
  630. """
  631. Returns the premises of this diagram.
  632. Examples
  633. ========
  634. >>> from sympy.categories import Object, NamedMorphism
  635. >>> from sympy.categories import IdentityMorphism, Diagram
  636. >>> from sympy import pretty
  637. >>> A = Object("A")
  638. >>> B = Object("B")
  639. >>> f = NamedMorphism(A, B, "f")
  640. >>> id_A = IdentityMorphism(A)
  641. >>> id_B = IdentityMorphism(B)
  642. >>> d = Diagram([f])
  643. >>> print(pretty(d.premises, use_unicode=False))
  644. {id:A-->A: EmptySet, id:B-->B: EmptySet, f:A-->B: EmptySet}
  645. """
  646. return self.args[0]
  647. @property
  648. def conclusions(self):
  649. """
  650. Returns the conclusions of this diagram.
  651. Examples
  652. ========
  653. >>> from sympy.categories import Object, NamedMorphism
  654. >>> from sympy.categories import IdentityMorphism, Diagram
  655. >>> from sympy import FiniteSet
  656. >>> A = Object("A")
  657. >>> B = Object("B")
  658. >>> C = Object("C")
  659. >>> f = NamedMorphism(A, B, "f")
  660. >>> g = NamedMorphism(B, C, "g")
  661. >>> d = Diagram([f, g])
  662. >>> IdentityMorphism(A) in d.premises.keys()
  663. True
  664. >>> g * f in d.premises.keys()
  665. True
  666. >>> d = Diagram([f, g], {g * f: "unique"})
  667. >>> d.conclusions[g * f] == FiniteSet("unique")
  668. True
  669. """
  670. return self.args[1]
  671. @property
  672. def objects(self):
  673. """
  674. Returns the :class:`~.FiniteSet` of objects that appear in this
  675. diagram.
  676. Examples
  677. ========
  678. >>> from sympy.categories import Object, NamedMorphism, Diagram
  679. >>> A = Object("A")
  680. >>> B = Object("B")
  681. >>> C = Object("C")
  682. >>> f = NamedMorphism(A, B, "f")
  683. >>> g = NamedMorphism(B, C, "g")
  684. >>> d = Diagram([f, g])
  685. >>> d.objects
  686. {Object("A"), Object("B"), Object("C")}
  687. """
  688. return self.args[2]
  689. def hom(self, A, B):
  690. """
  691. Returns a 2-tuple of sets of morphisms between objects ``A`` and
  692. ``B``: one set of morphisms listed as premises, and the other set
  693. of morphisms listed as conclusions.
  694. Examples
  695. ========
  696. >>> from sympy.categories import Object, NamedMorphism, Diagram
  697. >>> from sympy import pretty
  698. >>> A = Object("A")
  699. >>> B = Object("B")
  700. >>> C = Object("C")
  701. >>> f = NamedMorphism(A, B, "f")
  702. >>> g = NamedMorphism(B, C, "g")
  703. >>> d = Diagram([f, g], {g * f: "unique"})
  704. >>> print(pretty(d.hom(A, C), use_unicode=False))
  705. ({g*f:A-->C}, {g*f:A-->C})
  706. See Also
  707. ========
  708. Object, Morphism
  709. """
  710. premises = EmptySet
  711. conclusions = EmptySet
  712. for morphism in self.premises.keys():
  713. if (morphism.domain == A) and (morphism.codomain == B):
  714. premises |= FiniteSet(morphism)
  715. for morphism in self.conclusions.keys():
  716. if (morphism.domain == A) and (morphism.codomain == B):
  717. conclusions |= FiniteSet(morphism)
  718. return (premises, conclusions)
  719. def is_subdiagram(self, diagram):
  720. """
  721. Checks whether ``diagram`` is a subdiagram of ``self``.
  722. Diagram `D'` is a subdiagram of `D` if all premises
  723. (conclusions) of `D'` are contained in the premises
  724. (conclusions) of `D`. The morphisms contained
  725. both in `D'` and `D` should have the same properties for `D'`
  726. to be a subdiagram of `D`.
  727. Examples
  728. ========
  729. >>> from sympy.categories import Object, NamedMorphism, Diagram
  730. >>> A = Object("A")
  731. >>> B = Object("B")
  732. >>> C = Object("C")
  733. >>> f = NamedMorphism(A, B, "f")
  734. >>> g = NamedMorphism(B, C, "g")
  735. >>> d = Diagram([f, g], {g * f: "unique"})
  736. >>> d1 = Diagram([f])
  737. >>> d.is_subdiagram(d1)
  738. True
  739. >>> d1.is_subdiagram(d)
  740. False
  741. """
  742. premises = all((m in self.premises) and
  743. (diagram.premises[m] == self.premises[m])
  744. for m in diagram.premises)
  745. if not premises:
  746. return False
  747. conclusions = all((m in self.conclusions) and
  748. (diagram.conclusions[m] == self.conclusions[m])
  749. for m in diagram.conclusions)
  750. # Premises is surely ``True`` here.
  751. return conclusions
  752. def subdiagram_from_objects(self, objects):
  753. """
  754. If ``objects`` is a subset of the objects of ``self``, returns
  755. a diagram which has as premises all those premises of ``self``
  756. which have a domains and codomains in ``objects``, likewise
  757. for conclusions. Properties are preserved.
  758. Examples
  759. ========
  760. >>> from sympy.categories import Object, NamedMorphism, Diagram
  761. >>> from sympy import FiniteSet
  762. >>> A = Object("A")
  763. >>> B = Object("B")
  764. >>> C = Object("C")
  765. >>> f = NamedMorphism(A, B, "f")
  766. >>> g = NamedMorphism(B, C, "g")
  767. >>> d = Diagram([f, g], {f: "unique", g*f: "veryunique"})
  768. >>> d1 = d.subdiagram_from_objects(FiniteSet(A, B))
  769. >>> d1 == Diagram([f], {f: "unique"})
  770. True
  771. """
  772. if not objects.is_subset(self.objects):
  773. raise ValueError(
  774. "Supplied objects should all belong to the diagram.")
  775. new_premises = {}
  776. for morphism, props in self.premises.items():
  777. if ((sympify(objects.contains(morphism.domain)) is S.true) and
  778. (sympify(objects.contains(morphism.codomain)) is S.true)):
  779. new_premises[morphism] = props
  780. new_conclusions = {}
  781. for morphism, props in self.conclusions.items():
  782. if ((sympify(objects.contains(morphism.domain)) is S.true) and
  783. (sympify(objects.contains(morphism.codomain)) is S.true)):
  784. new_conclusions[morphism] = props
  785. return Diagram(new_premises, new_conclusions)