constraints.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737
  1. # mypy: allow-untyped-defs
  2. from typing import Any, Callable, Optional
  3. r"""
  4. The following constraints are implemented:
  5. - ``constraints.boolean``
  6. - ``constraints.cat``
  7. - ``constraints.corr_cholesky``
  8. - ``constraints.dependent``
  9. - ``constraints.greater_than(lower_bound)``
  10. - ``constraints.greater_than_eq(lower_bound)``
  11. - ``constraints.independent(constraint, reinterpreted_batch_ndims)``
  12. - ``constraints.integer_interval(lower_bound, upper_bound)``
  13. - ``constraints.interval(lower_bound, upper_bound)``
  14. - ``constraints.less_than(upper_bound)``
  15. - ``constraints.lower_cholesky``
  16. - ``constraints.lower_triangular``
  17. - ``constraints.MixtureSameFamilyConstraint(base_constraint)``
  18. - ``constraints.multinomial``
  19. - ``constraints.nonnegative``
  20. - ``constraints.nonnegative_integer``
  21. - ``constraints.one_hot``
  22. - ``constraints.positive_integer``
  23. - ``constraints.positive``
  24. - ``constraints.positive_semidefinite``
  25. - ``constraints.positive_definite``
  26. - ``constraints.real_vector``
  27. - ``constraints.real``
  28. - ``constraints.simplex``
  29. - ``constraints.symmetric``
  30. - ``constraints.stack``
  31. - ``constraints.square``
  32. - ``constraints.symmetric``
  33. - ``constraints.unit_interval``
  34. """
  35. import torch
  36. __all__ = [
  37. "Constraint",
  38. "boolean",
  39. "cat",
  40. "corr_cholesky",
  41. "dependent",
  42. "dependent_property",
  43. "greater_than",
  44. "greater_than_eq",
  45. "independent",
  46. "integer_interval",
  47. "interval",
  48. "half_open_interval",
  49. "is_dependent",
  50. "less_than",
  51. "lower_cholesky",
  52. "lower_triangular",
  53. "MixtureSameFamilyConstraint",
  54. "multinomial",
  55. "nonnegative",
  56. "nonnegative_integer",
  57. "one_hot",
  58. "positive",
  59. "positive_semidefinite",
  60. "positive_definite",
  61. "positive_integer",
  62. "real",
  63. "real_vector",
  64. "simplex",
  65. "square",
  66. "stack",
  67. "symmetric",
  68. "unit_interval",
  69. ]
  70. class Constraint:
  71. """
  72. Abstract base class for constraints.
  73. A constraint object represents a region over which a variable is valid,
  74. e.g. within which a variable can be optimized.
  75. Attributes:
  76. is_discrete (bool): Whether constrained space is discrete.
  77. Defaults to False.
  78. event_dim (int): Number of rightmost dimensions that together define
  79. an event. The :meth:`check` method will remove this many dimensions
  80. when computing validity.
  81. """
  82. is_discrete = False # Default to continuous.
  83. event_dim = 0 # Default to univariate.
  84. def check(self, value):
  85. """
  86. Returns a byte tensor of ``sample_shape + batch_shape`` indicating
  87. whether each event in value satisfies this constraint.
  88. """
  89. raise NotImplementedError
  90. def __repr__(self):
  91. return self.__class__.__name__[1:] + "()"
  92. class _Dependent(Constraint):
  93. """
  94. Placeholder for variables whose support depends on other variables.
  95. These variables obey no simple coordinate-wise constraints.
  96. Args:
  97. is_discrete (bool): Optional value of ``.is_discrete`` in case this
  98. can be computed statically. If not provided, access to the
  99. ``.is_discrete`` attribute will raise a NotImplementedError.
  100. event_dim (int): Optional value of ``.event_dim`` in case this
  101. can be computed statically. If not provided, access to the
  102. ``.event_dim`` attribute will raise a NotImplementedError.
  103. """
  104. def __init__(self, *, is_discrete=NotImplemented, event_dim=NotImplemented):
  105. self._is_discrete = is_discrete
  106. self._event_dim = event_dim
  107. super().__init__()
  108. @property
  109. def is_discrete(self) -> bool: # type: ignore[override]
  110. if self._is_discrete is NotImplemented:
  111. raise NotImplementedError(".is_discrete cannot be determined statically")
  112. return self._is_discrete
  113. @property
  114. def event_dim(self) -> int: # type: ignore[override]
  115. if self._event_dim is NotImplemented:
  116. raise NotImplementedError(".event_dim cannot be determined statically")
  117. return self._event_dim
  118. def __call__(self, *, is_discrete=NotImplemented, event_dim=NotImplemented):
  119. """
  120. Support for syntax to customize static attributes::
  121. constraints.dependent(is_discrete=True, event_dim=1)
  122. """
  123. if is_discrete is NotImplemented:
  124. is_discrete = self._is_discrete
  125. if event_dim is NotImplemented:
  126. event_dim = self._event_dim
  127. return _Dependent(is_discrete=is_discrete, event_dim=event_dim)
  128. def check(self, x):
  129. raise ValueError("Cannot determine validity of dependent constraint")
  130. def is_dependent(constraint):
  131. """
  132. Checks if ``constraint`` is a ``_Dependent`` object.
  133. Args:
  134. constraint : A ``Constraint`` object.
  135. Returns:
  136. ``bool``: True if ``constraint`` can be refined to the type ``_Dependent``, False otherwise.
  137. Examples:
  138. >>> import torch
  139. >>> from torch.distributions import Bernoulli
  140. >>> from torch.distributions.constraints import is_dependent
  141. >>> dist = Bernoulli(probs=torch.tensor([0.6], requires_grad=True))
  142. >>> constraint1 = dist.arg_constraints["probs"]
  143. >>> constraint2 = dist.arg_constraints["logits"]
  144. >>> for constraint in [constraint1, constraint2]:
  145. >>> if is_dependent(constraint):
  146. >>> continue
  147. """
  148. return isinstance(constraint, _Dependent)
  149. class _DependentProperty(property, _Dependent):
  150. """
  151. Decorator that extends @property to act like a `Dependent` constraint when
  152. called on a class and act like a property when called on an object.
  153. Example::
  154. class Uniform(Distribution):
  155. def __init__(self, low, high):
  156. self.low = low
  157. self.high = high
  158. @constraints.dependent_property(is_discrete=False, event_dim=0)
  159. def support(self):
  160. return constraints.interval(self.low, self.high)
  161. Args:
  162. fn (Callable): The function to be decorated.
  163. is_discrete (bool): Optional value of ``.is_discrete`` in case this
  164. can be computed statically. If not provided, access to the
  165. ``.is_discrete`` attribute will raise a NotImplementedError.
  166. event_dim (int): Optional value of ``.event_dim`` in case this
  167. can be computed statically. If not provided, access to the
  168. ``.event_dim`` attribute will raise a NotImplementedError.
  169. """
  170. def __init__(
  171. self,
  172. fn: Optional[Callable[..., Any]] = None,
  173. *,
  174. is_discrete: Optional[bool] = NotImplemented,
  175. event_dim: Optional[int] = NotImplemented,
  176. ) -> None:
  177. super().__init__(fn)
  178. self._is_discrete = is_discrete
  179. self._event_dim = event_dim
  180. def __call__(self, fn: Callable[..., Any]) -> "_DependentProperty": # type: ignore[override]
  181. """
  182. Support for syntax to customize static attributes::
  183. @constraints.dependent_property(is_discrete=True, event_dim=1)
  184. def support(self): ...
  185. """
  186. return _DependentProperty(
  187. fn, is_discrete=self._is_discrete, event_dim=self._event_dim
  188. )
  189. class _IndependentConstraint(Constraint):
  190. """
  191. Wraps a constraint by aggregating over ``reinterpreted_batch_ndims``-many
  192. dims in :meth:`check`, so that an event is valid only if all its
  193. independent entries are valid.
  194. """
  195. def __init__(self, base_constraint, reinterpreted_batch_ndims):
  196. assert isinstance(base_constraint, Constraint)
  197. assert isinstance(reinterpreted_batch_ndims, int)
  198. assert reinterpreted_batch_ndims >= 0
  199. self.base_constraint = base_constraint
  200. self.reinterpreted_batch_ndims = reinterpreted_batch_ndims
  201. super().__init__()
  202. @property
  203. def is_discrete(self) -> bool: # type: ignore[override]
  204. return self.base_constraint.is_discrete
  205. @property
  206. def event_dim(self) -> int: # type: ignore[override]
  207. return self.base_constraint.event_dim + self.reinterpreted_batch_ndims
  208. def check(self, value):
  209. result = self.base_constraint.check(value)
  210. if result.dim() < self.reinterpreted_batch_ndims:
  211. expected = self.base_constraint.event_dim + self.reinterpreted_batch_ndims
  212. raise ValueError(
  213. f"Expected value.dim() >= {expected} but got {value.dim()}"
  214. )
  215. result = result.reshape(
  216. result.shape[: result.dim() - self.reinterpreted_batch_ndims] + (-1,)
  217. )
  218. result = result.all(-1)
  219. return result
  220. def __repr__(self):
  221. return f"{self.__class__.__name__[1:]}({repr(self.base_constraint)}, {self.reinterpreted_batch_ndims})"
  222. class MixtureSameFamilyConstraint(Constraint):
  223. """
  224. Constraint for the :class:`~torch.distribution.MixtureSameFamily`
  225. distribution that adds back the rightmost batch dimension before
  226. performing the validity check with the component distribution
  227. constraint.
  228. Args:
  229. base_constraint: The ``Constraint`` object of
  230. the component distribution of
  231. the :class:`~torch.distribution.MixtureSameFamily` distribution.
  232. """
  233. def __init__(self, base_constraint):
  234. assert isinstance(base_constraint, Constraint)
  235. self.base_constraint = base_constraint
  236. super().__init__()
  237. @property
  238. def is_discrete(self) -> bool: # type: ignore[override]
  239. return self.base_constraint.is_discrete
  240. @property
  241. def event_dim(self) -> int: # type: ignore[override]
  242. return self.base_constraint.event_dim
  243. def check(self, value):
  244. """
  245. Check validity of ``value`` as a possible outcome of sampling
  246. the :class:`~torch.distribution.MixtureSameFamily` distribution.
  247. """
  248. unsqueezed_value = value.unsqueeze(-1 - self.event_dim)
  249. result = self.base_constraint.check(unsqueezed_value)
  250. if value.dim() < self.event_dim:
  251. raise ValueError(
  252. f"Expected value.dim() >= {self.event_dim} but got {value.dim()}"
  253. )
  254. num_dim_to_keep = value.dim() - self.event_dim
  255. result = result.reshape(result.shape[:num_dim_to_keep] + (-1,))
  256. result = result.all(-1)
  257. return result
  258. def __repr__(self):
  259. return f"{self.__class__.__name__}({repr(self.base_constraint)})"
  260. class _Boolean(Constraint):
  261. """
  262. Constrain to the two values `{0, 1}`.
  263. """
  264. is_discrete = True
  265. def check(self, value):
  266. return (value == 0) | (value == 1)
  267. class _OneHot(Constraint):
  268. """
  269. Constrain to one-hot vectors.
  270. """
  271. is_discrete = True
  272. event_dim = 1
  273. def check(self, value):
  274. is_boolean = (value == 0) | (value == 1)
  275. is_normalized = value.sum(-1).eq(1)
  276. return is_boolean.all(-1) & is_normalized
  277. class _IntegerInterval(Constraint):
  278. """
  279. Constrain to an integer interval `[lower_bound, upper_bound]`.
  280. """
  281. is_discrete = True
  282. def __init__(self, lower_bound, upper_bound):
  283. self.lower_bound = lower_bound
  284. self.upper_bound = upper_bound
  285. super().__init__()
  286. def check(self, value):
  287. return (
  288. (value % 1 == 0) & (self.lower_bound <= value) & (value <= self.upper_bound)
  289. )
  290. def __repr__(self):
  291. fmt_string = self.__class__.__name__[1:]
  292. fmt_string += (
  293. f"(lower_bound={self.lower_bound}, upper_bound={self.upper_bound})"
  294. )
  295. return fmt_string
  296. class _IntegerLessThan(Constraint):
  297. """
  298. Constrain to an integer interval `(-inf, upper_bound]`.
  299. """
  300. is_discrete = True
  301. def __init__(self, upper_bound):
  302. self.upper_bound = upper_bound
  303. super().__init__()
  304. def check(self, value):
  305. return (value % 1 == 0) & (value <= self.upper_bound)
  306. def __repr__(self):
  307. fmt_string = self.__class__.__name__[1:]
  308. fmt_string += f"(upper_bound={self.upper_bound})"
  309. return fmt_string
  310. class _IntegerGreaterThan(Constraint):
  311. """
  312. Constrain to an integer interval `[lower_bound, inf)`.
  313. """
  314. is_discrete = True
  315. def __init__(self, lower_bound):
  316. self.lower_bound = lower_bound
  317. super().__init__()
  318. def check(self, value):
  319. return (value % 1 == 0) & (value >= self.lower_bound)
  320. def __repr__(self):
  321. fmt_string = self.__class__.__name__[1:]
  322. fmt_string += f"(lower_bound={self.lower_bound})"
  323. return fmt_string
  324. class _Real(Constraint):
  325. """
  326. Trivially constrain to the extended real line `[-inf, inf]`.
  327. """
  328. def check(self, value):
  329. return value == value # False for NANs.
  330. class _GreaterThan(Constraint):
  331. """
  332. Constrain to a real half line `(lower_bound, inf]`.
  333. """
  334. def __init__(self, lower_bound):
  335. self.lower_bound = lower_bound
  336. super().__init__()
  337. def check(self, value):
  338. return self.lower_bound < value
  339. def __repr__(self):
  340. fmt_string = self.__class__.__name__[1:]
  341. fmt_string += f"(lower_bound={self.lower_bound})"
  342. return fmt_string
  343. class _GreaterThanEq(Constraint):
  344. """
  345. Constrain to a real half line `[lower_bound, inf)`.
  346. """
  347. def __init__(self, lower_bound):
  348. self.lower_bound = lower_bound
  349. super().__init__()
  350. def check(self, value):
  351. return self.lower_bound <= value
  352. def __repr__(self):
  353. fmt_string = self.__class__.__name__[1:]
  354. fmt_string += f"(lower_bound={self.lower_bound})"
  355. return fmt_string
  356. class _LessThan(Constraint):
  357. """
  358. Constrain to a real half line `[-inf, upper_bound)`.
  359. """
  360. def __init__(self, upper_bound):
  361. self.upper_bound = upper_bound
  362. super().__init__()
  363. def check(self, value):
  364. return value < self.upper_bound
  365. def __repr__(self):
  366. fmt_string = self.__class__.__name__[1:]
  367. fmt_string += f"(upper_bound={self.upper_bound})"
  368. return fmt_string
  369. class _Interval(Constraint):
  370. """
  371. Constrain to a real interval `[lower_bound, upper_bound]`.
  372. """
  373. def __init__(self, lower_bound, upper_bound):
  374. self.lower_bound = lower_bound
  375. self.upper_bound = upper_bound
  376. super().__init__()
  377. def check(self, value):
  378. return (self.lower_bound <= value) & (value <= self.upper_bound)
  379. def __repr__(self):
  380. fmt_string = self.__class__.__name__[1:]
  381. fmt_string += (
  382. f"(lower_bound={self.lower_bound}, upper_bound={self.upper_bound})"
  383. )
  384. return fmt_string
  385. class _HalfOpenInterval(Constraint):
  386. """
  387. Constrain to a real interval `[lower_bound, upper_bound)`.
  388. """
  389. def __init__(self, lower_bound, upper_bound):
  390. self.lower_bound = lower_bound
  391. self.upper_bound = upper_bound
  392. super().__init__()
  393. def check(self, value):
  394. return (self.lower_bound <= value) & (value < self.upper_bound)
  395. def __repr__(self):
  396. fmt_string = self.__class__.__name__[1:]
  397. fmt_string += (
  398. f"(lower_bound={self.lower_bound}, upper_bound={self.upper_bound})"
  399. )
  400. return fmt_string
  401. class _Simplex(Constraint):
  402. """
  403. Constrain to the unit simplex in the innermost (rightmost) dimension.
  404. Specifically: `x >= 0` and `x.sum(-1) == 1`.
  405. """
  406. event_dim = 1
  407. def check(self, value):
  408. return torch.all(value >= 0, dim=-1) & ((value.sum(-1) - 1).abs() < 1e-6)
  409. class _Multinomial(Constraint):
  410. """
  411. Constrain to nonnegative integer values summing to at most an upper bound.
  412. Note due to limitations of the Multinomial distribution, this currently
  413. checks the weaker condition ``value.sum(-1) <= upper_bound``. In the future
  414. this may be strengthened to ``value.sum(-1) == upper_bound``.
  415. """
  416. is_discrete = True
  417. event_dim = 1
  418. def __init__(self, upper_bound):
  419. self.upper_bound = upper_bound
  420. def check(self, x):
  421. return (x >= 0).all(dim=-1) & (x.sum(dim=-1) <= self.upper_bound)
  422. class _LowerTriangular(Constraint):
  423. """
  424. Constrain to lower-triangular square matrices.
  425. """
  426. event_dim = 2
  427. def check(self, value):
  428. value_tril = value.tril()
  429. return (value_tril == value).view(value.shape[:-2] + (-1,)).min(-1)[0]
  430. class _LowerCholesky(Constraint):
  431. """
  432. Constrain to lower-triangular square matrices with positive diagonals.
  433. """
  434. event_dim = 2
  435. def check(self, value):
  436. value_tril = value.tril()
  437. lower_triangular = (
  438. (value_tril == value).view(value.shape[:-2] + (-1,)).min(-1)[0]
  439. )
  440. positive_diagonal = (value.diagonal(dim1=-2, dim2=-1) > 0).min(-1)[0]
  441. return lower_triangular & positive_diagonal
  442. class _CorrCholesky(Constraint):
  443. """
  444. Constrain to lower-triangular square matrices with positive diagonals and each
  445. row vector being of unit length.
  446. """
  447. event_dim = 2
  448. def check(self, value):
  449. tol = (
  450. torch.finfo(value.dtype).eps * value.size(-1) * 10
  451. ) # 10 is an adjustable fudge factor
  452. row_norm = torch.linalg.norm(value.detach(), dim=-1)
  453. unit_row_norm = (row_norm - 1.0).abs().le(tol).all(dim=-1)
  454. return _LowerCholesky().check(value) & unit_row_norm
  455. class _Square(Constraint):
  456. """
  457. Constrain to square matrices.
  458. """
  459. event_dim = 2
  460. def check(self, value):
  461. return torch.full(
  462. size=value.shape[:-2],
  463. fill_value=(value.shape[-2] == value.shape[-1]),
  464. dtype=torch.bool,
  465. device=value.device,
  466. )
  467. class _Symmetric(_Square):
  468. """
  469. Constrain to Symmetric square matrices.
  470. """
  471. def check(self, value):
  472. square_check = super().check(value)
  473. if not square_check.all():
  474. return square_check
  475. return torch.isclose(value, value.mT, atol=1e-6).all(-2).all(-1)
  476. class _PositiveSemidefinite(_Symmetric):
  477. """
  478. Constrain to positive-semidefinite matrices.
  479. """
  480. def check(self, value):
  481. sym_check = super().check(value)
  482. if not sym_check.all():
  483. return sym_check
  484. return torch.linalg.eigvalsh(value).ge(0).all(-1)
  485. class _PositiveDefinite(_Symmetric):
  486. """
  487. Constrain to positive-definite matrices.
  488. """
  489. def check(self, value):
  490. sym_check = super().check(value)
  491. if not sym_check.all():
  492. return sym_check
  493. return torch.linalg.cholesky_ex(value).info.eq(0)
  494. class _Cat(Constraint):
  495. """
  496. Constraint functor that applies a sequence of constraints
  497. `cseq` at the submatrices at dimension `dim`,
  498. each of size `lengths[dim]`, in a way compatible with :func:`torch.cat`.
  499. """
  500. def __init__(self, cseq, dim=0, lengths=None):
  501. assert all(isinstance(c, Constraint) for c in cseq)
  502. self.cseq = list(cseq)
  503. if lengths is None:
  504. lengths = [1] * len(self.cseq)
  505. self.lengths = list(lengths)
  506. assert len(self.lengths) == len(self.cseq)
  507. self.dim = dim
  508. super().__init__()
  509. @property
  510. def is_discrete(self) -> bool: # type: ignore[override]
  511. return any(c.is_discrete for c in self.cseq)
  512. @property
  513. def event_dim(self) -> int: # type: ignore[override]
  514. return max(c.event_dim for c in self.cseq)
  515. def check(self, value):
  516. assert -value.dim() <= self.dim < value.dim()
  517. checks = []
  518. start = 0
  519. for constr, length in zip(self.cseq, self.lengths):
  520. v = value.narrow(self.dim, start, length)
  521. checks.append(constr.check(v))
  522. start = start + length # avoid += for jit compat
  523. return torch.cat(checks, self.dim)
  524. class _Stack(Constraint):
  525. """
  526. Constraint functor that applies a sequence of constraints
  527. `cseq` at the submatrices at dimension `dim`,
  528. in a way compatible with :func:`torch.stack`.
  529. """
  530. def __init__(self, cseq, dim=0):
  531. assert all(isinstance(c, Constraint) for c in cseq)
  532. self.cseq = list(cseq)
  533. self.dim = dim
  534. super().__init__()
  535. @property
  536. def is_discrete(self) -> bool: # type: ignore[override]
  537. return any(c.is_discrete for c in self.cseq)
  538. @property
  539. def event_dim(self) -> int: # type: ignore[override]
  540. dim = max(c.event_dim for c in self.cseq)
  541. if self.dim + dim < 0:
  542. dim += 1
  543. return dim
  544. def check(self, value):
  545. assert -value.dim() <= self.dim < value.dim()
  546. vs = [value.select(self.dim, i) for i in range(value.size(self.dim))]
  547. return torch.stack(
  548. [constr.check(v) for v, constr in zip(vs, self.cseq)], self.dim
  549. )
  550. # Public interface.
  551. dependent = _Dependent()
  552. dependent_property = _DependentProperty
  553. independent = _IndependentConstraint
  554. boolean = _Boolean()
  555. one_hot = _OneHot()
  556. nonnegative_integer = _IntegerGreaterThan(0)
  557. positive_integer = _IntegerGreaterThan(1)
  558. integer_interval = _IntegerInterval
  559. real = _Real()
  560. real_vector = independent(real, 1)
  561. positive = _GreaterThan(0.0)
  562. nonnegative = _GreaterThanEq(0.0)
  563. greater_than = _GreaterThan
  564. greater_than_eq = _GreaterThanEq
  565. less_than = _LessThan
  566. multinomial = _Multinomial
  567. unit_interval = _Interval(0.0, 1.0)
  568. interval = _Interval
  569. half_open_interval = _HalfOpenInterval
  570. simplex = _Simplex()
  571. lower_triangular = _LowerTriangular()
  572. lower_cholesky = _LowerCholesky()
  573. corr_cholesky = _CorrCholesky()
  574. square = _Square()
  575. symmetric = _Symmetric()
  576. positive_semidefinite = _PositiveSemidefinite()
  577. positive_definite = _PositiveDefinite()
  578. cat = _Cat
  579. stack = _Stack