from dataclasses import dataclass
from typing import Literal, Optional
import numpy as np
import ddg.math.projective as pmath
import ddg.math.symmetric_matrices as sm
from ddg import nonexact
from ddg.math import linalg
###################
# Signature classes
###################
[docs]@dataclass(frozen=True, eq=False)
class Signature:
"""Projective signature class.
Two projective signatures are equal if and only if quadrics with those
signatures are related by a projective transformation.
We want to identify:
* Signatures
* Quadrics up to projective transformation
* "Normalized matrices" up to nonzero scalar multiplication (-1 in
particular), which will be the result of e.g.
:py:func:`projective_normalization`.
To achieve this, we define a normalized matrix as a diagonal matrix ::
I_n | |
-----|------|---
| -I_m |
-----|------|---
| | 0
or ::
-I_n | |
------|-----|---
| I_m |
------|-----|---
| | 0
where ``I_n`` is the (n x n)-identity matrix, 0 means a 0 matrix and ``n >= m``.
Parameters
----------
plus : int
minus : int
zero : int (default=0)
These three arguments mean the number of 1, -1, 0 on the diagonal.
Notes
-----
This class implements the equals relation ``==``, with the meaning explained above.
It is also hashable.
Examples
--------
>>> from ddg.geometry.signatures import Signature
>>> sgn1 = Signature(2, 1, 0)
>>> sgn1
Signature(plus=2, minus=1, zero=0)
>>> sgn2 = Signature(1, 2, 0)
>>> sgn2
Signature(plus=1, minus=2, zero=0)
>>> sgn1 == sgn2
True
>>> sgn1.matrix
array([[ 1, 0, 0],
[ 0, 1, 0],
[ 0, 0, -1]])
>>> sgn2.matrix
array([[-1, 0, 0],
[ 0, -1, 0],
[ 0, 0, 1]])
>>> sgn1.is_degenerate
False
>>> sgn1.rank
3
>>> sgn1.is_positive_definite
False
>>> sgn3 = Signature(2, 1, 1)
>>> sgn3
Signature(plus=2, minus=1, zero=1)
>>> sgn3.matrix
array([[ 1, 0, 0, 0],
[ 0, 1, 0, 0],
[ 0, 0, -1, 0],
[ 0, 0, 0, 0]])
>>> sgn1 == sgn3
False
"""
plus: int
minus: int
zero: int = 0
@property
def matrix(self):
"""Returns the normalized matrix corresponding to the signature.
See the class docstring for how this is defined.
This method respects the global sign the signature is initialized with,
even though the equality operator does not.
Returns
-------
numpy.ndarray of shape (n, n)
"""
p, q, z = self.plus, self.minus, self.zero
diag = max([p, q]) * [1] + min([p, q]) * [-1] + z * [0]
# respect the actual counts
if p < q:
diag = -np.array(diag)
return sm.symmetric_matrix_from_diagonal(diag)
@property
def is_degenerate(self):
"""Whether this is the signature of a degenerate quadric.
Returns
-------
bool
"""
return self.zero > 0
@property
def rank(self):
"""Rank of associated matrix.
Returns
-------
int
"""
return self.plus + self.minus
@property
def is_positive_definite(self):
"""Whether the signature is positive definite.
Meaning, if ``q`` is a quadratic form with this signature, ``q(p,p) > 0`` for
all ``p != 0``.
Returns
-------
bool
"""
return self.plus > 0 and self.minus == 0 and self.zero == 0
@property
def is_negative_definite(self):
"""Whether the signature is negative definite.
Meaning, if ``q`` is a quadratic form with this signature, ``q(p,p) < 0`` for
all ``p != 0``.
Returns
-------
bool
"""
return self.minus > 0 and self.plus == 0 and self.zero == 0
@property
def is_positive_semi_definite(self):
"""Whether the signature is positive semi-definite.
Meaning, if ``q`` is a quadratic form with this signature, ``q(p,p) >= 0`` for
all ``p``.
Returns
-------
bool
"""
return self.minus == 0
@property
def is_negative_semi_definite(self):
"""Whether the signature is negative semi-definite.
Meaning, if ``q`` is a quadratic form with this signature, ``q(p,p) <= 0`` for
all ``p``.
Returns
-------
bool
"""
return self.plus == 0
@property
def is_indefinite(self):
"""Whether the signature is indefinite.
Meaning, that a quadratic form with this signature, can have both positive and
negative values.
Returns
-------
bool
"""
return self.plus > 0 and self.minus > 0
@property
def is_definite(self):
"""Whether the signature is either positive or negative definite.
Returns
-------
bool
"""
return self.is_positive_definite or self.is_negative_definite
@property
def is_semi_definite(self):
"""Whether the signature is either positive or negative semi-definite.
Returns
-------
bool
"""
return self.is_positive_semi_definite or self.is_negative_semi_definite
@property
def _counts_up_to_sign(self):
"""Returns counts of 1, -1 and 0, ignoring overall sign.
Ignoring overall sign means that we want the signatures ``(p, q, z)`` and
``(q, p, z)`` to be treated as equal. This method encodes the counts in
such a way that this is the case.
Returns
-------
tuple(frozenset(int, int), int)
"""
return (frozenset([self.plus, self.minus]), self.zero)
def __eq__(self, other):
if not isinstance(other, Signature):
return NotImplemented
return self._counts_up_to_sign == other._counts_up_to_sign
def __str__(self):
result = (self.plus, self.minus)
if self.is_degenerate:
result += (self.zero,)
return str(result)
def __hash__(self):
return hash(self._counts_up_to_sign)
[docs]@dataclass(frozen=True, eq=False)
class AffineSignature(Signature):
"""Affine signature class.
Two affine signatures are equal if and only if quadrics with those signatures are
related by an affine transformation.
We want to identify:
* Affine signatures
* Quadrics up to affine transformation
* "Normalized matrices" up to nonzero scalar multiplication (-1 in particular),
which will be the result of e.g. :py:func:`affine_normalization`.
To achieve this, we define what a normalized matrix is as follows:
In the non-parabolic case: A diagonal matrix with only 1, -1 and 0 on the diagonal.
The diagonal looks as follows (where ``s`` is either 1, -1 or 0):
* If the last entry on the diagonal is 0: ``(s,...,s, -s,...,-s, 0,...,0)``, and the
number of ``s``'s is greater than or equal to the number of ``-s``'s.
* If the last entry on the diagonal is ``s != 0``:
``(s,...,s, -s,...,-s, 0,...,0, s)``.
In the parabolic case: a matrix of the form ::
D |
--+-----
| 0 s
| s 0
where ``D`` is a diagonal matrix with only 1, -1 and 0 on the diagonal. The diagonal
is sorted in the order ``s``, ``-s``, ``0`` and the number of ``s``'s is greater
than or equal to the number of ``-s``'s.
Note that for parabolic cases, flipping the sign of the diagonal is the
same as flipping the sign of the off-diagonal entries, which as an affine
transformation is a reflection about the plane ``x[-2] = 0``.
Parameters
----------
plus : int
minus : int
zero : int (default=0)
These three arguments mean the number of 1, -1 or 0 on the diagonal of
the **diagonalized** matrix. Note that for parabolic cases, these will
not be the counts on the diagonal: A 1 and a -1 will be replaced by a 0
and entries on the off-diagonal, see above.
last_entry : 1, -1, 0 or "parabolic" (default=None)
The last entry on the diagonal. A value of "parabolic" means the matrix ::
0 1
1 0
See above for details. This can be omitted (left as ``None``) if only one of
`plus`, `minus` or `zero` is nonzero.
Raises
------
ValueError
* If `last_entry` is "parabolic" and `plus` or `minus` are 0.
* If `last_entry` was not given and could not be inferred uniquely.
* If `last_entry` was given and is not 1, -1, 0 or 'parabolic'.
Notes
-----
This class implements the equals relation ``==``, with the meaning explained above.
It is also hashable.
Examples
--------
>>> from ddg.geometry.signatures import AffineSignature
>>> sgn1 = AffineSignature(2, 1, 0, last_entry=-1)
>>> sgn1.matrix
array([[ 1, 0, 0],
[ 0, 1, 0],
[ 0, 0, -1]])
>>> sgn2 = AffineSignature(1, 2, 0, last_entry=1)
>>> sgn2
AffineSignature(plus=1, minus=2, zero=0, last_entry=1)
>>> sgn2.matrix
array([[-1, 0, 0],
[ 0, -1, 0],
[ 0, 0, 1]])
>>> sgn1 == sgn2
True
>>> sgn3 = AffineSignature(1, 2, 0, last_entry=-1)
>>> sgn3.matrix
array([[-1, 0, 0],
[ 0, 1, 0],
[ 0, 0, -1]])
>>> sgn3 == sgn2
False
>>> sgn4 = AffineSignature(1, 2, 0, last_entry="parabolic")
>>> sgn4
AffineSignature(plus=1, minus=2, zero=0, last_entry='parabolic')
>>> sgn4.matrix
array([[-1, 0, 0],
[ 0, 0, -1],
[ 0, -1, 0]])
>>> sgn3 == sgn4
False
"""
plus: int
minus: int
zero: int = 0
last_entry: Optional[Literal[0, 1, -1, "parabolic"]] = None
def __post_init__(self):
# Just consistency checking
p, q, r = self.plus, self.minus, self.zero
s = self.last_entry
# We can only check if the entry can be inferred, but can not set
# self.last_entry due to immutability.
if s is None:
entries = set(p * [1] + q * [-1] + r * [0])
if len(entries) != 1:
raise ValueError(
"Sign of last entry could not be inferred uniquely. Please specify "
"1, -1, 0 or 'parabolic'."
)
elif s not in [1, -1, 0, "parabolic"]:
raise ValueError("Sign of last entry must be 1, -1, 0 or 'parabolic'.")
elif s == "parabolic" and (p == 0 or q == 0):
raise ValueError("Parabolic signatures must have a 1 and a -1.")
elif (s == 1 and p == 0) or (s == -1 and q == 0) or (s == 0 and r == 0):
raise ValueError("The last sign is not contained in the diagonal.")
@property
def matrix(self):
"""Return normalized matrix corresponding to the signature.
See the class docstring for how this is defined.
Returns
-------
numpy.ndarray of shape (n, n)
Notes
-----
This method respects the sign the signature is initialized with, even
though the equality operator does not.
"""
p, q, z = self.plus, self.minus, self.zero
# We already checked in __post_init__ that only one is nonzero.
if self.last_entry is None:
for count, s in zip([p, q, z], [1, -1, 0]):
if count > 0:
break
else:
s = self.last_entry
parabolic = s == "parabolic"
# We need this to change the sign of the off-diagonal entries in the
# parabolic case
sign = 1
# build the diagonal without last_entry
if parabolic:
diag_ex = (max([p, q]) - 1) * [1] + (min([p, q]) - 1) * [-1] + z * [0]
# respect the actual counts
if p < q:
sign = -1
elif s == 0:
diag_ex = max([p, q]) * [1] + min([p, q]) * [-1] + (z - 1) * [0]
# respect the actual counts
if p < q:
sign = -1
elif s == 1:
diag_ex = (p - 1) * [1] + q * [-1] + z * [0]
elif s == -1:
diag_ex = (q - 1) * [-1] + p * [1] + z * [0]
# build the actual diagonal
# dtype int for consistency with Signature.matrix
diag = np.empty(p + q + z, dtype=int)
if parabolic:
diag[-2:] = 0
diag[:-2] = diag_ex
else:
diag[-1] = s
diag[:-1] = diag_ex
return sign * sm.symmetric_matrix_from_diagonal(diag, parabolic=parabolic)
def __eq__(self, other):
if not isinstance(other, AffineSignature):
return NotImplemented
return np.array_equal(self.matrix, other.matrix) or np.array_equal(
self.matrix, -other.matrix
)
def __str__(self):
lines = [super().__str__()]
if self.last_entry is not None:
lines.append(f"last entry: {self.last_entry}")
return "\n".join(lines)
def __hash__(self):
"""Hash function computed from self.matrix()."""
M = self.matrix.flatten()
for m in M:
if m != 0:
M *= m
break
return hash(tuple(M))
[docs]def signature_from_diagonal(diag):
"""Create signature from diagonal.
Parameters
----------
diag : array_like of shape (n,)
1D array containing only 1, -1 and 0.
Returns
-------
Signature
Raises
------
ValueError
If `diag` contains entries other than 1, -1 or 0.
Examples
--------
>>> from ddg.geometry.signatures import signature_from_diagonal
>>> signature_from_diagonal((1, 0, 1))
Signature(plus=2, minus=0, zero=1)
>>> signature_from_diagonal((0, -1, -1))
Signature(plus=0, minus=2, zero=1)
>>> signature_from_diagonal((1, 1, 0, 0, -1, 0))
Signature(plus=2, minus=1, zero=3)
"""
diag = np.array(diag)
p = np.count_nonzero(diag == 1)
q = np.count_nonzero(diag == -1)
z = np.count_nonzero(diag == 0)
if p + q + z != len(diag):
raise ValueError("Diagonal can only contain 1, -1 and 0")
return Signature(p, q, z)
[docs]def affine_signature_from_diagonal(diag, parabolic=False):
"""Create affine signature from diagonal.
Parameters
----------
diag : array_like of shape (n,) or list
1D array containing only 1, -1 and 0. If `parabolic` is True, this must
have 0 at entries -1 and -2.
parabolic : bool (default=False)
Returns
-------
AffineSignature
Raises
------
ValueError
* If `diag` contains entries other than 1, -1 or 0.
* If `parabolic` is True and `diag` does not have 0 at positions -1 and
-2.
Examples
--------
>>> from ddg.geometry.signatures import affine_signature_from_diagonal
>>> affine_signature_from_diagonal((1, 0, 1))
AffineSignature(plus=2, minus=0, zero=1, last_entry=1)
>>> affine_signature_from_diagonal((0, -1, -1))
AffineSignature(plus=0, minus=2, zero=1, last_entry=-1)
>>> affine_signature_from_diagonal((1, 1, 0, 0, -1, 0))
AffineSignature(plus=2, minus=1, zero=3, last_entry=0)
"""
diag = np.array(diag)
p = np.count_nonzero(diag == 1)
q = np.count_nonzero(diag == -1)
z = np.count_nonzero(diag == 0)
if p + q + z != len(diag):
raise ValueError("Diagonal can only contain 1, -1 and 0")
if parabolic and (diag[-1] != 0 or diag[-2] != 0):
raise ValueError("parabolic diagonals must have 0 at positions -1 and -2.")
if parabolic:
last_entry = "parabolic"
p += 1
q += 1
z -= 2
else:
last_entry = diag[-1]
return AffineSignature(p, q, z, last_entry=last_entry)
###################################
# Normalization and diagonalization
###################################
[docs]def projective_normalization(Q, atol=None, rtol=None):
"""Bring quadric to normal form by projective transformation.
Returns signature `sgn` and transformation matrix `A` such that ::
A.T @ Q @ A == sgn.matrix
See :py:class:`Signature` for more information.
Note that this means that the point transformation defined by A
transforms ``sgn.matrix`` into Q.
Parameters
----------
Q : numpy.ndarray
Symmetric matrix.
atol, rtol : float (default=None)
This function uses the global tolerance defaults if `atol` or `rtol` are set to
None. See :py:mod:`ddg.nonexact` for details.
Returns
-------
sgn : Signature
A : numpy.ndarray
Transformation matrix.
Raises
------
ValueError
If `Q` is not symmetric.
Examples
--------
>>> import numpy as np
>>> from ddg.geometry.signatures import projective_normalization
>>> Q = np.array([[10, 0, 0], [0, 5, 0], [0, 0, 3]])
>>> Q
array([[10, 0, 0],
[ 0, 5, 0],
[ 0, 0, 3]])
>>> sgn, A = projective_normalization(Q)
>>> sgn
Signature(plus=3, minus=0, zero=0)
>>> A
array([[0.31622777, 0. , 0. ],
[0. , 0.4472136 , 0. ],
[0. , 0. , 0.57735027]])
>>> B = np.array([[1, 0, 2], [0, 5, 0], [2, 0, 3]])
>>> B
array([[1, 0, 2],
[0, 5, 0],
[2, 0, 3]])
>>> sgn, A = projective_normalization(B)
>>> sgn
Signature(plus=2, minus=1, zero=0)
>>> A
array([[ 0. , -0.25543607, -1.75078485],
[-0.4472136 , 0. , 0. ],
[ 0. , -0.41330424, 1.08204454]])
"""
if not sm.is_symmetric(Q, atol=atol, rtol=rtol):
raise ValueError("The matrix is not symmetric: " + str(Q))
ev, A = sm.diagonalize(Q, atol=atol, rtol=rtol)
# Add affine scaling to the transformation
B = np.diag([1.0 if x == 0 else 1 / np.sqrt(np.abs(x)) for x in ev])
A = np.dot(A, B)
# compute signature
sgn = np.array([int(np.sign(x)) for x in ev])
# permutation step to bring the matrix to normal form as defined in the
# Signature docstring.
# We might have to swap the first two blocks of diag, i.e. go from
# (1,...,1, -1,...,-1, 0,...,0) to (-1,...,-1, 1,...,1, 0,...,0).
B = np.eye(len(sgn))
p = np.count_nonzero(sgn == 1)
q = np.count_nonzero(sgn == -1)
z = np.count_nonzero(sgn == 0)
if q > p:
permutation = np.concatenate(
(np.arange(q) + p, np.arange(p), np.arange(z) + p + q)
)
B = B[:, permutation]
A = A @ B
sgn = signature_from_diagonal(sgn)
return sgn, A
[docs]def affine_normalization(Q, atol=None, rtol=None):
"""Bring quadric to normal form by affine transformation.
Returns affine signature `sgn` and affine transformation `A` such that ::
A.T @ Q @ A == sgn.matrix
For non-parabolic cases, this means diagonalizing `Q` with an affine transformation
such that there is only 1, -1 or 0 on the diagonal. For parabolic cases, normalizes
to a matrix of the form ::
D1 |
(+/-) ---+-----
| 0 1
| 1 0
Where ``D1`` is a diagonal matrix with only 1, -1 or 0 on the diagonal. For more
information about the order of entries on the diagonal etc, see
:py:class:`.AffineSignature`.
Parameters
----------
Q : numpy.ndarray of shape (n + 1, n + 1)
Symmetric matrix. Can be interpreted as a quadric in n-dim. projective space.
atol, rtol : float (default=None)
This function uses the global tolerance defaults if `atol` or `rtol` are set to
None. See :py:mod:`ddg.nonexact` for details.
Returns
-------
sgn : AffineSignature
A : numpy.ndarray of shape (n + 1, n + 1)
Affine transformation.
Raises
------
ValueError
If `Q` is not symmetric.
Notes
-----
For a detailed explanation of the implementation, see :ref:`supplemental material
<affine_normalization>`.
See also
--------
ddg.math.symmetric_matrices.AffineSignature
Examples
--------
>>> import numpy as np
>>> from ddg.geometry.signatures import affine_normalization
>>> Q = np.array([[10, 0, 0], [0, 5, 0], [0, 0, 3]])
>>> Q
array([[10, 0, 0],
[ 0, 5, 0],
[ 0, 0, 3]])
>>> sgn, A = affine_normalization(Q)
>>> sgn
AffineSignature(plus=3, minus=0, zero=0, last_entry=1)
>>> A
array([[0.31622777, 0. , 0. ],
[0. , 0.4472136 , 0. ],
[0. , 0. , 0.57735027]])
>>> B = np.array([[1, 0, 2], [0, 5, 0], [2, 0, 3]])
>>> B
array([[1, 0, 2],
[0, 5, 0],
[2, 0, 3]])
>>> sgn, A = affine_normalization(B)
>>> sgn
AffineSignature(plus=2, minus=1, zero=0, last_entry=-1)
>>> A
array([[ 0. , 1. , -2. ],
[ 0.4472136, 0. , 0. ],
[ 0. , 0. , 1. ]])
"""
if not sm.is_symmetric(Q, atol=atol, rtol=rtol):
raise ValueError("The matrix is not symmetric: " + str(Q))
# We treat this as a special case because the decomposition will produce empty
# arrays Q1, q which many functions can't deal with.
if Q.shape[0] == 1:
r = Q[0, 0]
if nonexact.isclose(r, 0, atol=atol, rtol=rtol):
return AffineSignature(0, 0, 1), np.array([[1]])
else:
return AffineSignature(1, 0), np.array([[1 / np.sqrt(np.abs(r))]])
# List of affine transformations that must be applied to normalize Q.
trafos = []
Q1, q, r = sm._decompose_matrix(Q)
rk = linalg.rank(Q1, atol=atol, rtol=rtol)
b = sm._compute_translation(Q, rk)
parabolic = sm._check_parabolic(Q, b, atol, rtol)
ev_, A1 = sm.diagonalize(Q1, atol=atol, rtol=rtol)
trafos.append(pmath.affine_transformation(A1, b))
if parabolic:
trafos.append(sm._parabolic_reduce_q(A1, Q, b, rk, atol, rtol))
trafos.append(sm._parabolic_eliminate_r(Q, b))
ev = np.append(ev_, 0.0)
else:
r2 = np.dot(b, q) + r
r2 = 0.0 if nonexact.isclose(r2, 0, atol=atol, rtol=rtol) else r2
ev = np.append(ev_, r2)
# We now normalize the diagonal entries to 1, -1 or 0.
# diagonalize already sets eigenvalues close to 0 according to tolerances to 0 and
# we did the same for the last eigenvalue manually, so an exact check is fine here.
trafos.append(np.diag([1.0 if x == 0 else 1 / np.sqrt(np.abs(x)) for x in ev]))
# compute signature
# This is sorted in the order 1, -1, 0, except for the last entry (or last two, in
# the parabolic case).
sgn_diag = np.sign(ev).astype(int)
# Final permutation step to achieve normal form as described in AffineSignature.
trafos.append(sm._normal_form_permutation(sgn_diag, parabolic))
return (
affine_signature_from_diagonal(sgn_diag, parabolic=parabolic),
np.linalg.multi_dot(trafos),
)
###########
# Sequences
###########
[docs]class SignatureSequence:
"""Signature sequence class
Signature sequences can be used to classify non-degenerate
pencils (ddg.geometry.Pencil) of real quadrics (ddg.geometry.Quadric).
If the pencil is represented as::
P(Q1,Q2) = lambda Q2 + Q1
with ``A, B`` real symmetric matrices representing the quadrics spanning the pencil,
and we consider the roots ``lambda_l`` of::
det(lambda Q2 + Q1) = 0
then the signature sequence is calculated as::
[v_1, (...(i_1, j_1)...), v_2, ...., v_k-1, (...(i_k-1, j_k-1)...), v_k],
where ``v_l`` are the index of the nonsingular matrices ``lambda Q2 + Q1`` between
two roots and ``(...(i_l, j_l)...)`` are the signatures of the singular matrices
``lambda_l Q2 + Q1``, with the multiplicity of the root being represented in the
number of brackets
A number of operations can be performed on the attributes of a signature sequence
to receive an equivalent sequence:
- rotate_left(indices, signatures, multiplicities)
- rotate_right(indices, signatures, multiplicities)
- inverse_order(indices, signatures, multiplicities)
- find_complement(indices, signatures, multiplicities)
If the dimension of the sequence is 4 (``RP³``), the sequence can be brought into
normalform for classification via equivalence operations.
Normalform is reached by the following rules in descending order of importance:
- multiple roots to the front
- smallest values to the front
- smallest sum of values possible for indices
- more positive than negative entries in signatures
Parameters
----------
indices: tuple or list of integers
Indexes of the non-degenerate quadrics in the pencil,
given in an arbitrary order (but fitting for the other tuples)
signatures: tuple or list of Signature
Signatures of the degenerate quadrics in the pencil
multiplicities: tuple or list of integers
Multiplicities of the roots in the characteristic function
``det(lambda Q2 + Q1)=0`` where ``lambda Q2 + Q1`` describes the pencil
Attributes
----------
dimension: int
indices: tuple
signatures: tuple
multiplicities: tuple
Raises
------
ValueError
* If the tuples are not of the correct lengths
``len(indices)-1=len(signatures)=len(multiplicities)``
* If the tuples are not filled with the right type of entries:
- indices: integers
- signatures: signatures
- multiplicities: integers
Notes
-----
This class supports the following operators:
- ``==``, meaning equivalency of the signature sequences, reached through a
projective transformation on lambda
See also
--------
ddg.geometry.signatures.IndexSequence
ddg.geometry.signatures.SegreSymbol
ddg.geometry.Pencil
"""
def __init__(self, indices, signatures, multiplicities):
indices: tuple
signatures: tuple
multiplicities: tuple
# if instead entries are lists, turn them into tuples
if isinstance(indices, list):
indices = tuple(indices)
if isinstance(signatures, list):
signatures = tuple(signatures)
if isinstance(multiplicities, list):
multiplicities = tuple(multiplicities)
# check whether tuple lengths match to build the sequence
if (
not isinstance(indices, tuple)
or not isinstance(signatures, tuple)
or not isinstance(multiplicities, tuple)
or len(indices) - 1 != len(signatures)
or len(signatures) != len(multiplicities)
):
raise ValueError(
"Tuples must have matching lengths with tuples of indices one entry "
+ "longer"
)
for k in range(len(signatures)):
# check whether we have entries in the tuples of the right type
if not isinstance(indices[k], int) or not isinstance(
multiplicities[k], int
):
raise ValueError(
"Tuple of indices and multiplicities need to have integer entries"
)
elif not isinstance(signatures[k], Signature):
raise ValueError(
"Tuple of signatures need to have signatures as entries"
)
# check the last entry in the indices tuple (one longer)
if not isinstance(indices[-1], int):
raise ValueError("Tuple of indices needs have integer entries")
# dimension of sequence for an n-dim pencil is n+1
self.dimension = indices[0] + indices[-1]
self.indices = indices
self.signatures = signatures
self.multiplicities = multiplicities
return
def __str__(self):
result = "("
for k in range(len(self.signatures)):
# indices are plainly put
result += str(self.indices[k]) + ", "
# signatures are put in as many brackets as
# the multiplicity of the root of the characteristic function here
elem = self.signatures[k]
entry = str((elem.plus, elem.minus))
for l in range(self.multiplicities[k] - 1):
entry = "(" + entry + ")"
result += entry + ", "
result += str(self.indices[-1]) + ")"
return str(result)
def __repr__(self):
return (
"Signature Sequence with tuples of\n indices: "
+ str(self.indices)
+ ",\n signatures: "
+ str(self.signatures)
+ ",\n multiplicities: "
+ str(self.multiplicities)
)
def __eq__(self, other):
# check whether other is a signature sequence
if isinstance(other, self.__class__):
# calculate the normalform of the sequences:
normal1 = self.normalform()
normal2 = other.normalform()
# take the two normalforms as tuples
norm1 = (normal1.indices, normal1.signatures, normal1.multiplicities)
norm2 = (normal2.indices, normal2.signatures, normal2.multiplicities)
# and check tuple equivalence
return norm1 == norm2
else:
return False
[docs]class JordanBlock:
"""JordanBlock class
Jordanblocks ``J`` are quadratic blocks in the Jordannormalform of a matrix ``M``.
They have the form::
| u 1 |
| . . |
J = | . . |
| . 1 |
| u |
where ``u`` is an eigenvalue of ``M``.
Parameters
----------
size: int
Size of the jordanblock. If ``J`` is of size ``nxn``, size = ``n``
epsilon: 1, -1
denotes whether the jordanblock has a positive or negative factor epsilon in the
canonical form of a pencil.
Attributes
----------
size
epsilon
Raises
------
ValueError
If the Parameters are not of the correct type
"""
def __init__(self, size, epsilon):
if not isinstance(size, int) or epsilon not in [1, -1]:
raise ValueError(
"Please provide an integer for the size of Jordan block and"
" either 1 or -1 for the sign."
)
else:
self.size = size
self.epsilon = epsilon
def __str__(self):
result = "(" + str(self.size) + ", "
if self.epsilon == 1:
result += "+)"
else:
result += "-)"
return result
def __repr__(self):
return (
"JordanBlock object of size = "
+ str(self.size)
+ " and sign = "
+ str(self.epsilon)
)
def __eq__(self, other):
if isinstance(other, self.__class__):
# take the two signature sequences as tuples
norm1 = (self.size, self.epsilon)
norm2 = (other.size, other.epsilon)
# and check tuple equivalence
return norm1 == norm2
else:
return False
[docs]class IndexSequence:
"""Index sequence class
Index sequences can be used to classify non-degenerate
pencils (ddg.geometry.Pencil) of real quadrics (ddg.geometry.Quadric).
If the pencil is represented as::
P(Q1,Q2) = lambda Q2 + Q1
with ``A, B`` real symmetric matrices representing the quadrics spanning the pencil,
and we consider the roots lambda_l of::
det(lambda Q2 + Q1) = 0
then the signature sequence is calculated as::
[v_1 | v_2, ...., v_k-1 | v_k]
where ``v_l`` are the index of the nonsingular matrices ``lambda Q2 + Q1`` between
two roots and ``|`` is a placeholder for an array of symbols denoting the sizes and
numbers of Jordan blocks in the quadric pair canonical form of the singular
matrices::
lambda_l Q2 + Q1
where
- ““|““ JordanBlock of size 1
- ““ā...āā““ JordanBlock of size n (number of wavy lines)
(with positive sign in the quadric pair canonical form)
- ““ā...āā““ JordanBlock of size n (number of wavy lines)
(with negative sign in the quadric pair canonical form)
(The quadric pair canonical form is calculated as follows: If ``Q2`` is non-singular
and ``Q2ā»Ā¹Q1`` has Jordannormalform ``J=diag(J_1,..., J_n)``, then ``Q2, Q1`` are
simultaneously congruent to::
diag(±E_1, ..., ±E_n), diag(±E_1J_1, ..., ±E_nJ_n),
where ``E_i`` is a matrix of the form::
| 1 |
| . |
| . |
| . |
| 1 |)
A number of operations can be performed on the attributes of an index sequence
to receive an equivalent sequence:
- rotate_left(indices, signatures, multiplicities)
- rotate_right(indices, signatures, multiplicities)
- inverse_order(indices, signatures, multiplicities)
- find_complement(indices, signatures, multiplicities)
If the dimension of the sequence is 4 (``RP³``), the sequence can be brought into
normalform for classification via equivalence operations.
Normalform is reached by the following rules in descending order of importance:
- multiple roots to the front
- smallest values to the front
- smallest sum of values possible for indices
- more negative than positive signs for the Jordanblocks
Parameters
----------
indices: tuple or list of integers
Indexes of the non-degenerate quadrics in the pencil
given in an arbitrary order (but fitting for the other tuples)
jordanblocks: tuple or list of tuples or lists of JordanBlock
Sizes of the Jordanblocks in the canonical form of matrices in the pencil
corresponding to roots in the characteristic function ``det(lambda Q2 + Q1)=0``
where ``lambda Q2 + Q1`` describes the pencil
Attributes
----------
indices: tuple
jordanblocks: tuple
dimension: int
calculated by sum of first and last index dim = indices[0]+ indices[-1]
multiplicities: tuple
calculated from the tuple of jordanblocks
Raises
------
ValueError
* If the tuples are not of the correct lengths
(len(indices)-1=len(jordanblocks))
* If the tuples are not filled with the right type of entries:
- indices: integers
- jordanblocks: lists
- elements of jordanblocks: JordanBlock
Notes
-----
This class supports the following operators:
-``==``, meaning equivalency of the index sequences,
reached through a projective transformation on lambda
See also
--------
SignatureSequence
SegreSymbol
ddg.geometry.Pencil
"""
def __init__(self, indices, jordanblocks):
# if indices is a list, turn it into a tuple
if isinstance(indices, list):
indices = tuple(indices)
# check that input types are tuples of matching lengths
# list allowed for jordanblocks
if (
not isinstance(indices, tuple)
or (
not isinstance(jordanblocks, tuple)
and not isinstance(jordanblocks, list)
)
or len(indices) - 1 != len(jordanblocks)
):
raise ValueError(
"Lists must have matching lengths with list of indices one longer"
)
multiplicities = ()
build_jordanblocks = ()
for k in range(len(jordanblocks)):
# check that index is an integer and jordanblock is a tuple or list
if not isinstance(indices[k], int):
raise ValueError("Tuple of indices needs to have integer entries")
if not isinstance(jordanblocks[k], tuple) and not isinstance(
jordanblocks[k], list
):
raise ValueError(
"Tuple of jordanblocks needs to have tuples as entries"
)
multiplicity = 0
block = ()
for l in range(len(jordanblocks[k])):
# check that entries in jordan block tuples are jordanblocks
if not isinstance(jordanblocks[k][l], JordanBlock):
raise ValueError(
"Entries in list of jordanblocks need to be Jordan blocks."
)
else:
# calculate multiplicity of root as sum of length of jordanblocks
multiplicity += jordanblocks[k][l].size
block += (jordanblocks[k][l],)
multiplicities += (multiplicity,)
# sort the tuple for every root first by descending size of jordanblocks
# and then among those to have the negative epsilon in the front
block = tuple(sorted(block, key=lambda block: block.epsilon))
block = tuple(sorted(block, reverse=True, key=lambda block: block.size))
build_jordanblocks += (block,)
# also last index needs to be an integer
if not isinstance(indices[-1], int):
raise ValueError("List of indices needs to have integer entries")
# dimension of sequence for an n-dim pencil is n+1
self.dimension = indices[0] + indices[-1]
self.indices = indices
self.jordanblocks = build_jordanblocks
self.multiplicities = multiplicities
return
def __str__(self):
sub = str.maketrans("+-", "āā")
result = "\u2329"
for k in range(len(self.jordanblocks)):
result += str(self.indices[k]) + " "
entry = ""
for block in self.jordanblocks[k]:
# vertical stroke for singular root
if block.size == 1:
entry += "|"
else:
# wavy lign for a multiple root ā
for l in range(block.size):
entry += "\u2240"
if block.epsilon == 1:
blocksign = "+"
else:
blocksign = "-"
entry += blocksign.translate(sub)
result += entry + " "
result += str(self.indices[-1]) + "\u232a"
return result
def __repr__(self):
return (
"Index Sequence with lists of\n indices: "
+ str(self.indices)
+ ",\n jordanblocks: "
+ str(self.jordanblocks)
)
def __eq__(self, other):
# check whether other is a index sequence
if isinstance(other, self.__class__):
# calculate the normalform of the sequences:
normal1 = self.normalform()
normal2 = other.normalform()
# take the two normalforms as tuples
norm1 = (normal1.indices, normal1.jordanblocks, normal1.multiplicities)
norm2 = (normal2.indices, normal2.jordanblocks, normal2.multiplicities)
# and check tuple equivalence
return norm1 == norm2
else:
return False
[docs] def signature_sequence(self):
"""Derive corresponding signature sequence to index sequence
Signatures can be calculated directly from tuples of
ddg.geometry.signatures.JordanBlocks
Returns
-------
ddg.geometry.SignatureSequence
"""
signatures = []
for k in range(len(self.jordanblocks)):
block = self.jordanblocks[k]
# signature before the jordanblock is the index (+) and dim - index (-)
signat = np.array([self.indices[k], self.dimension - self.indices[k]])
# the type of jordanblocks then subtract values from either side
# depending on what sign the block has and whether the size of the block
# is even
for elem in block:
if (elem.size % 2 == 1 and elem.epsilon == 1) or (
elem.size % 2 == 0 and elem.epsilon == -1
):
signat += np.array([0, -1])
else:
signat += np.array([-1, 0])
signatures += [
Signature(
signat[0], signat[1], self.dimension - (signat[0] + signat[1])
)
]
signatures = tuple(signatures)
# the tuples of indices and multiplicities can directly be taken
# from the index sequence
return SignatureSequence(self.indices, signatures, self.multiplicities)
[docs]class SegreSymbol:
"""Segre symbol class
The Segre symbol is defined for representative matrices for a pencil of
quadrics (ddg.geometry.Pencil).
Two projectively equivalent pencils have matrices with the same Segre Symbol.
As such, the Segre symbol can be calculated from the Smith normal form (SNF)
of blocks of type::
| 1 | | a |
| . | | . 1 |
Q2= | . | , Q1= | . . |
| . | | . . |
| 1 | | a 1 |
in the polynomial matrix ```lambda Q2 + Q1`` representing the pencil
(see Jordannormalform for IndexSequence).
SNF of block ``lambda Q2_i + Q1_i(lambda_i)``::
| 1 |
| . |
| . |
| 1 |
| (lambda-lambda_i)^(v_ij) |
The Segree symbol is then the combination of the information of all blocks
in the form::
[(v_11, ..., v_1r1), ..., (v_n1, ..., v_nrn)]_k
with the ``v_ij`` the exponents in the SNF and ``k`` the number of real ``lambda_i``
in the symbol (counted with multiplicity).
The ``v_ij`` also denote the sizes of Jordanblocks in the Jordannormalform and
hence the multiplicity of the roots of the characteristic equation of the pencil::
det (lambda Q2 + Q1)=0
Parameters
----------
roots: list of integers
Multiplicities of the roots of the characteristic equation of the pencil
(``det (lambda Q2 + Q1)=0``) , given in an arbitrary order
reals: int
Number of real roots among those
Attributes
----------
roots
reals
Raises
------
ValueError
* If we don't get the right type of parameter: list and integer
* If the list is not filled with the right type of entries:
lists of lists of integers
See also
--------
ddg.geometry.signatures.SignatureSequence
ddg.geometry.signatures.IndexSequence
ddg.geometry.Pencil
"""
def __init__(self, roots, reals):
# check we have a list of multiplicities of roots and one integer for the reals
roots: list
reals: int
if not isinstance(roots, list) or not isinstance(reals, int):
raise ValueError(
"The type of the parameter roots needs to be a list "
+ "and the type of the parameter reals an integer."
)
for root in roots:
if not isinstance(root, list):
raise ValueError("List of roots needs to have lists as entries")
for elem in root:
if not isinstance(elem, int):
raise ValueError(
"List of roots needs have lists of integers as entries"
)
self.roots = roots
self.reals = reals
def __str__(self):
# create an array of small subscript numbers
# to represent the number of reals
sub = str.maketrans("0123456789", "āāāāāā
āāāā")
subscript = str(self.reals).translate(sub)
result = "["
for elem in self.roots:
if len(elem) == 1:
result += str(elem[0]) + " "
else:
result += "("
for root in elem:
result += str(root) + " "
result = result[:-1] + ") "
result = result[:-1]
result += "]" + subscript
return result
def __repr__(self):
return (
"Segree symbol with lists of\n roots: "
+ str(self.roots)
+ ",\n number of real roots: "
+ str(self.reals)
)
def __eq__(self, other):
# check whether other is a segre symbol
if isinstance(other, self.__class__):
# calculate the normalform of the sequences:
normal1 = self.normalform()
normal2 = other.normalform()
# take the two normalforms as lists
norm1 = (normal1.roots, normal1.reals)
norm2 = (normal2.roots, normal2.reals)
# and check list equivalence
return norm1 == norm2
else:
return False
[docs]def rotate_left(indices, signatures_jordanblocks, multiplicities):
"""Rotate Signature and Index sequences one step to the left to create a
projectively equivalent sequence
The second index goes to the first spot, the first index and first
signature/jordanblock are thrown out. As new last signature/jordanblock,
the inverse of the thrown out signature/jordanblock gets used. As new
last index the ambient dimension minus the new first entry gets used.
Parameters
----------
indices: tuple
indices of the sequence
signatures_jordanblocks: tuple
signatures for signature sequence
jordanblocks for index sequence
multiplicities: tuple
multiplicities of sequence
Returns
-------
tuple of three entries of type tuple
"""
if len(indices) <= 1:
return indices, signatures_jordanblocks, multiplicities
# find new index to put in the front
index = indices[0] + indices[-1] - indices[1]
indices = indices[1:] + (index,)
elem = signatures_jordanblocks[0]
# inverse first signature because of crossing infinity
if isinstance(elem, Signature):
element = Signature(elem.minus, elem.plus, elem.zero)
# or inverse direction of first jordanblock
elif isinstance(elem, tuple):
element = ()
for tupl in elem:
new_tuple = JordanBlock(tupl.size, -1 * tupl.epsilon)
element += (new_tuple,)
element = tuple(sorted(element, key=lambda block: block.epsilon))
element = tuple(sorted(element, reverse=True, key=lambda block: block.size))
else:
raise NotImplementedError
# rotate the tuples to the left
signatures_jordanblocks = signatures_jordanblocks[1:] + (element,)
multiplicities = multiplicities[1:] + (multiplicities[0],)
return indices, signatures_jordanblocks, multiplicities
[docs]def rotate_right(indices, signatures_jordanblocks, multiplicities):
"""Rotate Signature and Index sequences one step to the right to create a
projectively equivalent sequence
The second to last index goes to the last spot, the last index and last
signature/jordanblock are thrown out. As new first signature/jordanblock,
the inverse of the thrown out signature/jordanblock gets used. As new
first index the ambient dimension minus the new last entry gets used.
Parameters
----------
indices: tuple
indices of the sequence
signatures_jordanblocks: tuple
signatures for signature sequence
jordanblocks for index sequence
multiplicities: tuple
multiplicities of sequence
Returns
-------
tuple of three entries of type tuple
"""
if len(indices) <= 1:
return indices, signatures_jordanblocks, multiplicities
# find new index to put in the back,
# ambient dimension: indices[0] + indices[-1]
index = indices[0] + indices[-1] - indices[-2]
indices = (index,) + indices[:-1]
# pluck signature of last root
elem = signatures_jordanblocks[-1]
# inverse last signature because of crossing infinity
if isinstance(elem, Signature):
element = Signature(elem.minus, elem.plus, elem.zero)
# or inverse direction of last jordanblock
elif isinstance(elem, tuple):
element = ()
for tupl in elem:
new_tuple = JordanBlock(tupl.size, -1 * tupl.epsilon)
element += (new_tuple,)
element = tuple(sorted(element, key=lambda block: block.epsilon))
element = tuple(sorted(element, reverse=True, key=lambda block: block.size))
else:
raise NotImplementedError
# rotate the tuples to the right
signatures_jordanblocks = (element,) + signatures_jordanblocks[:-1]
multiplicities = (multiplicities[-1],) + multiplicities[:-1]
return indices, signatures_jordanblocks, multiplicities
[docs]def inverse_order(indices, signatures_jordanblocks, multiplicities):
"""
Inverses the order of a Signature and Index sequence to create a
projectively equivalent sequence. Note that signatures stay unchanged while in the
jordanblocks the epsilon changes exactly for blocks of odd size.
Parameters
----------
indices: tuple
indices of the sequence
signatures_jordanblocks: tuple
signatures for signature sequence
jordanblocks for index sequence
multiplicities: tuple
multiplicities of sequence
Returns
-------
tuple of three entries of type tuple
"""
indices = indices[::-1]
signatures_jordanblocks = signatures_jordanblocks[::-1]
new_signatures_jordanblocks = ()
for k in range(len(signatures_jordanblocks)):
# inverse the plus and minus entries in signatures
if isinstance(signatures_jordanblocks[k], Signature):
new_signatures_jordanblocks += (signatures_jordanblocks[k],)
# exchange the + and - in jordanblocks
elif isinstance(signatures_jordanblocks[k], tuple):
new_block = ()
for tupl in signatures_jordanblocks[k]:
if tupl.size % 2 != 0:
new_tuple = JordanBlock(tupl.size, -1 * tupl.epsilon)
else:
new_tuple = tupl
new_block += (new_tuple,)
new_block = tuple(sorted(new_block, key=lambda block: block.epsilon))
new_block = tuple(
sorted(new_block, reverse=True, key=lambda block: block.size)
)
new_signatures_jordanblocks += (new_block,)
else:
raise NotImplementedError
multiplicities = multiplicities[::-1]
return indices, new_signatures_jordanblocks, multiplicities
[docs]def find_complement(indices, signatures_jordanblocks, multiplicities):
"""
Inverse the orientation of a Signature or Index sequence to create a
projectively equivalent sequence
every index n gets turned into d- n, where d is the ambient dimension,
every signature gets replaced by its inverse (plus and minus entry reversed)
every jordanblock gets replaced by the same jordanblock with negative sign
Parameters
----------
indices: tuple
indices of the sequence
signatures_jordanblocks: tuple
- signatures for signature sequence
- jordanblocks for index sequence
multiplicities: tuple
multiplicities of sequence
Returns
-------
tuple of three entries of type tuple
"""
dimension = indices[0] + indices[-1]
new_indices = ()
new_signatures_jordanblocks = ()
for k in range(len(signatures_jordanblocks)):
# find the complement of the indices
new_indices += (dimension - indices[k],)
# inverse the plus and minus entries in signatures
if isinstance(signatures_jordanblocks[k], Signature):
new_signatures_jordanblocks += (
Signature(
signatures_jordanblocks[k].minus,
signatures_jordanblocks[k].plus,
signatures_jordanblocks[k].zero,
),
)
# exchange the + and - in jordanblocks
elif isinstance(signatures_jordanblocks[k], tuple):
new_block = ()
for tupl in signatures_jordanblocks[k]:
new_tuple = JordanBlock(tupl.size, -1 * tupl.epsilon)
new_block += (new_tuple,)
new_block = tuple(sorted(new_block, key=lambda block: block.epsilon))
new_block = tuple(
sorted(new_block, reverse=True, key=lambda block: block.size)
)
new_signatures_jordanblocks += (new_block,)
else:
raise NotImplementedError
# indices is one longer:
new_indices += (dimension - indices[-1],)
return new_indices, new_signatures_jordanblocks, multiplicities
def _check_multiple_root(indices, signatures_jordanblocks, multiplicities):
"""
Check if there are multiple roots in the sequence and rotate them to the front
if there is only one multiple root.
This returns an equivalent signature/index sequence.
Parameters
----------
indices: tuple
indices of the sequence
signatures_jordanblocks: tuple
- signatures for signature sequence
- jordanblocks for index sequence
multiplicities: tuple
multiplicities of sequence
Returns
-------
tuple with four entries
- bool
whether a singular multiple root was rotated to the front
- three entries of type tuple
See also
--------
normalform
"""
# track whether we have already found a multiple root and rotated the sequence
answ = False
for k in range(len(multiplicities)):
# find triple root if existing
# (don't need to find 4x root as that one will be in the only place anyway)
if multiplicities[k] == 3:
# rotates triple root to the front,
# one step is enough here bc triple root either first or second root
if k > 0:
indices, signatures_jordanblocks, multiplicities = rotate_left(
indices, signatures_jordanblocks, multiplicities
)
# save that we have found a triple root
answ = True
# find double root if existing
elif multiplicities[k] == 2:
# rotate to the front
for j in range(k):
indices, signatures_jordanblocks, multiplicities = rotate_left(
indices, signatures_jordanblocks, multiplicities
)
# if we have already found a 2 before that means we only have 2s
# and don't need to care about which one is in front
# otherwise, denote that one 2 has been found
if answ:
answ = False
else:
answ = True
else:
continue
return answ, indices, signatures_jordanblocks, multiplicities
def _search_for_zeros(indices, signatures_jordanblocks, multiplicities):
"""
searches a sequence for zeros and puts them in front
Check if there are zeros among the indices of the sequence
and rotate them to the front.
This returns an equivalent signature/index sequence.
In case there is a four instead of a zero, calculate the inverse of the
sequence and rotate the four (now zero) to the front
Parameters
----------
indices: tuple
indices of the sequence
signatures_jordanblocks: tuple
- signatures for signature sequence
- jordanblocks for index sequence
multiplicities: tuple
multiplicities of sequence
Returns
-------
tuple with four entries
- bool
whether a zero was rotated to the front
- three entries of type tuple
See also
--------
normalform
"""
# track whether we have already changed/found a 0 or 4
answ = False
for k in range(len(indices)):
# as long as we haven't found 0s or 4s yet:
# if there is a zero, rotate it to the front
if not answ and indices[k] == 0:
answ = True
for j in range(k):
indices, signatures_jordanblocks, multiplicities = rotate_left(
indices, signatures_jordanblocks, multiplicities
)
# as long as we haven't found 0s or 4s yet:
# if we find a 4, get the complement to have a 0
# and rotate that one to the front
if not answ and indices[k] == 4:
answ = True
# gets the complement in the same equivalence class
indices, signatures_jordanblocks, multiplicities = find_complement(
indices, signatures_jordanblocks, multiplicities
)
for j in range(k):
indices, signatures_jordanblocks, multiplicities = rotate_left(
indices, signatures_jordanblocks, multiplicities
)
else:
continue
return answ, indices, signatures_jordanblocks, multiplicities
def _change_orientation_not_order(indices, signatures_jordanblocks, multiplicities):
"""
Changes only the orientation of the sequence around the first zero
This is in case the rest is immutable because there is a multiple root
for example. The function returns an equivalent signature/index sequence.
Parameters
----------
indices: tuple
indices of the sequence
signatures_jordanblocks: tuple
- signatures for signature sequence
- jordanblocks for index sequence
multiplicities: tuple
multiplicities of sequence
Returns
-------
tuple of three entries of type tuple
See also
--------
normalform
"""
# Create the lowest possible values in the front,
# if they sum up to >=4, the complement will return smaller values
if indices[0] + indices[1] > 4:
# calculate complement
indices, signatures_jordanblocks, multiplicities = find_complement(
indices, signatures_jordanblocks, multiplicities
)
# is the smaller or equal value of the two in front?
if indices[0] > indices[1]:
# inverse order of first two entries by inversing the order
# then rotating left (rotating right would change indices again)
indices, signatures_jordanblocks, multiplicities = inverse_order(
indices, signatures_jordanblocks, multiplicities
)
for j in range(len(indices) - 2):
indices, signatures_jordanblocks, multiplicities = rotate_left(
indices, signatures_jordanblocks, multiplicities
)
return indices, signatures_jordanblocks, multiplicities
def _threes_into_ones_fixed_first_entries(
indices, signatures_jordanblocks, multiplicities
):
"""
Turns indices of three into ones by getting the complement if the first two indices
in the sequence are both twos
The function returns an equivalent signature/index sequence.
Parameters
----------
indices: tuple
indices of the sequence
signatures_jordanblocks: tuple
- signatures for signature sequence
- jordanblocks for index sequence
multiplicities: tuple
multiplicities of sequence
Returns
-------
tuple of three entries of type tuple
See also
--------
normalform
"""
# dictionary to count 1s and 3s
values_dict = {"ones": 0, "threes": 0}
# go through the sequence and count 1s and 3s (no 0s and 4s possible)
for k in range(len(indices)):
if indices[k] == 1:
values_dict["ones"] += 1
elif indices[k] == 3:
values_dict["threes"] += 1
else:
continue
# make sure we have at least as many 1s as 3s
if values_dict["ones"] < values_dict["threes"]:
# by taking the complement if necessary
indices, signatures_jordanblocks, multiplicities = find_complement(
indices, signatures_jordanblocks, multiplicities
)
return indices, signatures_jordanblocks, multiplicities
def _threes_into_ones(indices, signatures_jordanblocks, multiplicities):
"""
Searches indices of one and three in the sequence, makes us have the highest
possible amount of ones and puts them close together at the start
Also handles the case of indices being a tuple of only twos.
Parameters
----------
indices: tuple
indices of the sequence
signatures_jordanblocks: tuple
- signatures for signature sequence
- jordanblocks for index sequence
multiplicities: tuple
multiplicities of sequence
Returns
-------
tuple with four entries
- bool
whether a one was rotated to the front
- three entries of type tuple
See also
--------
normalform
"""
# empty lists to save where the indices of one and three are
ones = []
threes = []
# tracks whether we have already changed/found something
answ = False
for k in range(len(indices)):
if indices[k] == 1:
ones += [k]
elif indices[k] == 3:
threes += [k]
else:
continue
# if we have less 1s than 3s we take the complement
if len(ones) < len(threes):
answ = True
indices, signatures_jordanblocks, multiplicities = find_complement(
indices, signatures_jordanblocks, multiplicities
)
# also update where the 1s and 3s are bc that is inverse now
ones, threes = threes, ones
# if there are only indices of value 2,
# that is (2], [2,2] or [2,2,2]
# make first signature have the highest plus value possible for signature sequence
# or accordingly make sure there are the most possible negative jordanblocks
# for index sequence
if len(ones) == 0:
# if the list isn't empty, sort by types of signatures/jordanblocks
if len(signatures_jordanblocks) >= 1:
if isinstance(signatures_jordanblocks[0], Signature):
indices, signatures_jordanblocks, multiplicities = sort_by_signature(
indices, signatures_jordanblocks, multiplicities
)
else:
indices, signatures_jordanblocks, multiplicities = sort_by_jordanblock(
indices, signatures_jordanblocks, multiplicities
)
else:
# only change signature if there isn't already a 1 at the front
if ones[0] != 0:
answ = True
# rotate first one to the front
for j in range(ones[0]):
indices, signatures_jordanblocks, multiplicities = rotate_left(
indices, signatures_jordanblocks, multiplicities
)
# repeat in case rotating has lead to more threes than ones in the sequence
# This will not lead to a loop since at some point we reach the canonical form
# and stop changing the sequence
if answ:
indices, signatures_jordanblocks, multiplicities = _threes_into_ones(
indices, signatures_jordanblocks, multiplicities
)
return indices, signatures_jordanblocks, multiplicities
[docs]def sort_by_signature(indices, signatures, multiplicities):
"""
Makes first signature have the highest plus value possible
Parameters
----------
indices: tuple
indices of the sequence
signatures: tuple
signatures for signature sequence
multiplicities: tuple
multiplicities of sequence
Returns
-------
tuple of three entries of type tuple
See also
--------
threes_into_ones
"""
# if the first signature has more negative eigenvalues than positive
# find the complement of the signature sequence
if len(signatures) >= 1 and signatures[0].plus < signatures[0].minus:
indices, signatures, multiplicities = find_complement(
indices, signatures, multiplicities
)
# if instead the first signature has as many positive eigenvalues as negative
# and the second signature has more negative eigenvalues than positive
# rotate to the right turning the second signature into the inversed first
if (
len(signatures) >= 2
and signatures[0].plus >= signatures[0].minus
and signatures[1].plus < signatures[1].minus
):
indices, signatures, multiplicities = rotate_right(
indices, signatures, multiplicities
)
# there can not be more than two signatures for this case
return indices, signatures, multiplicities
[docs]def sort_by_jordanblock(indices, jordanblocks, multiplicities):
"""
Makes the number of negative jordanblocks maximal
Parameters
----------
indices: tuple
indices of the sequence
jordanblocks: tuple
signatures for signature sequence
multiplicities: tuple
multiplicities of sequence
Returns
-------
tuple of three entries of type tuple
See also
--------
threes_into_ones
"""
# if the first block in the first tuple of jordanblocks is positive
# find the complement of the sequence
if len(jordanblocks) >= 1 and jordanblocks[0][0].epsilon == 1:
indices, jordanblocks, multiplicities = find_complement(
indices, jordanblocks, multiplicities
)
# if now the first block in the second tuple of jordanblocks is positive,
# rotate to the right
# this will keep the first jordanblock negative and turn the second into
# the inversed first (also negative)
if len(jordanblocks) >= 2 and jordanblocks[1][0].epsilon == 1:
indices, jordanblocks, multiplicities = rotate_right(
indices, jordanblocks, multiplicities
)
# there can not be more than two roots for this case
return indices, jordanblocks, multiplicities
[docs]def complete_types_dictionary():
"""
Completes the ddg.geometry.signatures.types_to_index_and_segre dictionary with the
corresponding ddg.geometry.SignatureSequence
The resulting dictionary has entries of the form:
"typeN": [
ddg.geometry.signatures.SignatureSequence,
ddg.geometry.signatures.IndexSequence,
ddg.geometry.signatures.SegreSymbol
]
Returns
-------
dict
"""
complete_dict = {}
for elem in types_to_index_and_segre:
index_seq = types_to_index_and_segre[elem][0]
segre_symb = types_to_index_and_segre[elem][1]
signat_seq = index_seq.signature_sequence()
complete_dict[elem] = [signat_seq, index_seq, segre_symb]
return complete_dict
signatures_to_types = {
(
(1, 2, 1, 2, 3,), # fmt: skip
(
Signature(1, 2, 1),
Signature(1, 2, 1),
Signature(1, 2, 1),
Signature(2, 1, 1),
),
(1, 1, 1, 1,), # fmt: skip
): [
"type1",
"Type 1: The Quadric Surface Intersection Curve has "
+ "two null-homotopic components.",
],
(
(0, 1, 2, 3, 4,), # fmt: skip
(
Signature(0, 3, 1),
Signature(1, 2, 1),
Signature(2, 1, 1),
Signature(3, 0, 1),
),
(1, 1, 1, 1,), # fmt: skip
): [
"type2",
"Type 2: The Quadric Surface Intersection Curve" + " is vacuous in RP³.",
],
((1, 2, 3,), (Signature(1, 2, 1), Signature(2, 1, 1),), (1, 1,),): [
"type3",
"Type 3: The Quadric Surface Intersection Curve "
+ "has one null-homotopic component.",
],
((2,), (), ()): ["specialcase1"],
(
(2, 2, 3, 2,), # fmt: skip
(Signature(2, 1, 1), Signature(2, 1, 1), Signature(2, 1, 1),),
(2, 1, 1,), # fmt: skip
): [
"type5a",
"Type 5: The Quadric Surface Intersection Curve "
+ "has two null-homotopic components joining at a crunode.",
],
(
(2, 2, 1, 2,), # fmt: skip
(Signature(1, 2, 1), Signature(1, 2, 1), Signature(1, 2, 1),),
(2, 1, 1,), # fmt: skip
): [
"type5b",
"Type 5: The Quadric Surface Intersection Curve"
+ " has two null-homotopic components joining at a crunode.",
],
(
(1, 1, 2, 3,), # fmt: skip
(Signature(1, 2, 1), Signature(1, 2, 1), Signature(2, 1, 1),),
(2, 1, 1,), # fmt: skip
): [
"type6",
"Type 6: The Quadric Surface Intersection Curve"
+ " has one null-homotopic component and an acnode.",
],
(
(1, 1, 2, 3,), # fmt: skip
(Signature(0, 3, 1), Signature(1, 2, 1), Signature(2, 1, 1),),
(2, 1, 1,), # fmt: skip
): [
"type7",
"Type 7: The Quadric Surface Intersection Curve"
+ " has one real point, which is an acnode.",
],
((2, 2,), (Signature(2, 1, 1),), (2,)): [
"type8",
"Type 8: The Quadric Surface Intersection Curve"
+ " has two non-null-homotopic components intersecting at a crunode.",
],
((2, 2, 2,), (Signature(2, 1, 1), Signature(2, 1, 1),), (2, 2,),): [
"type9",
"Type 9: The Quadric Surface Intersection Curve has "
+ "one real line and one space cubic curve, intersecting at two "
+ "distinct real points.",
],
((1, 2, 3,), (Signature(1, 2, 1), Signature(2, 1, 1),), (3, 1,),): [
"type11",
"Type 11: The Quadric Surface Intersection Curve "
+ "has one null-homotopic component with a cusp.",
],
((2, 2,), (Signature(2, 1, 1),), (4,)): [
"type12",
"Type 12: The Quadric Surface Intersection Curve "
+ "has one real line and one real space cubic curve; the line is tangent to"
+ " the cubic at a real point.",
],
(
(2, 2, 1, 2,), # fmt: skip
(Signature(1, 1, 2), Signature(1, 2, 1), Signature(1, 2, 1),),
(2, 1, 1,), # fmt: skip
): [
"type13",
"Type 13: The Quadric Surface Intersection Curve is "
+ "planar with two real conics intersecting at two distinct real points.",
],
(
(1, 3, 2, 3,), # fmt: skip
(Signature(1, 1, 2), Signature(2, 1, 1), Signature(2, 1, 1),),
(2, 1, 1,), # fmt: skip
): [
"type14",
"Type 14: The Quadric Surface Intersection Curve "
+ "is planar with two real conics intersecting at two "
+ "complex conjugate points.",
],
(
(1, 1, 2, 3,), # fmt: skip
(Signature(0, 2, 2), Signature(1, 2, 1), Signature(2, 1, 1),),
(2, 1, 1,), # fmt: skip
): [
"type15",
"Type 15: The Quadric Surface Intersection Curve is planar "
+ "with two imaginary conics intersecting at two distinct real points.",
],
(
(0, 2, 3, 4,), # fmt: skip
(Signature(0, 2, 2), Signature(2, 1, 1), Signature(3, 0, 1),),
(2, 1, 1,), # fmt: skip
): [
"type16a",
"Type 16: The Quadric Surface Intersection Curve is planar"
+ " with two imaginary conics intersecting at two complex conjugate points.",
],
(
(1, 3, 4, 3,), # fmt: skip
(Signature(1, 1, 2), Signature(3, 0, 1), Signature(3, 0, 1),),
(2, 1, 1,), # fmt: skip
): [
"type16b",
"Type 16: The Quadric Surface Intersection Curve is planar"
+ " with two imaginary conics intersecting at two complex conjugate points.",
],
((1, 3,), (Signature(1, 1, 2),), (2,)): [
"type17",
"Type 17: The Quadric Surface Intersection Curve "
+ "is planar with one real conic and one imaginary conic; "
+ "they intersect at two complex conjugate points.",
],
((2, 2,), (Signature(1, 1, 2),), (2,)): [
"type18",
"Type 18: The Quadric Surface Intersection Curve "
+ "is planar with two real conics intersecting at two distinct real points. "
+ "The two conics cannot both be ellipses simultaneously in any "
"affine realization of RP³. In other words, the QSIC is intersected by "
+ "every plane in RP³.",
],
((1, 2, 3,), (Signature(0, 1, 3), Signature(2, 1, 1),), (3, 1,),): [
"type19",
"Type 19: The Quadric Surface Intersection Curve"
+ " is planar with one real conic counted twice.",
],
((0, 3, 4,), (Signature(0, 1, 3), Signature(3, 0, 1),), (3, 1,),): [
"type20",
"Type 20: The Quadric Surface Intersection Curve "
+ "is planar with one imaginary conic counted twice.",
],
((1, 2, 3,), (Signature(1, 1, 2), Signature(2, 1, 1),), (3, 1,),): [
"type21",
"Type 21: The Quadric Surface Intersection Curve"
+ " is planar with two real conics tangent to each other at one real point.",
],
((1, 2, 3,), (Signature(0, 2, 2), Signature(2, 1, 1),), (3, 1,),): [
"type22",
"Type 22: The Quadric Surface Intersection Curve is planar with"
+ " two imaginary conics tangent to each other at one real point.",
],
((2, 2, 2,), (Signature(2, 1, 1), Signature(1, 1, 2),), (2, 2,),): [
"type23",
"Type 23: The Quadric Surface Intersection Curve "
+ "is planar with one real conic and two real lines; they intersect pairwise "
+ "at three distinct real points.",
],
((1, 1, 3,), (Signature(1, 2, 1), Signature(1, 1, 2),), (2, 2,),): [
"type24",
"Type 24: The Quadric Surface Intersection Curve "
+ "is planar with one real conic and a pair of complex conjugate lines. The "
+ "conic and the pair of lines intersect at two complex conjugate points.",
],
((1, 1, 3,), (Signature(0, 3, 1), Signature(1, 1, 2),), (2, 2,),): [
"type25",
"Type 25: The Quadric Surface Intersection Curve "
+ "is planar with one imaginary conic and a pair of complex conjugate lines. "
+ "The conic and the pair of lines intersect at two complex conjugate points.",
],
# fmt: off
((2, 2,), (Signature(1, 1, 2),), (4,),): ["specialcase2"],
# fmt: on
((1, 3,), (Signature(1, 1, 2),), (4,),): [
"type27",
"Type 27: The Quadric Surface Intersection Curve "
+ "is planar with one real conic and a pair of complex conjugate lines; "
+ "these three components intersect at a common real point.",
],
((2, 2, 2,), (Signature(1, 1, 2), Signature(1, 1, 2),), (2, 2,),): [
"type28",
"Type 28: The Quadric Surface Intersection Curve "
+ "is planar with four real lines which form a quadrangle in RP³.",
],
((0, 2, 4,), (Signature(0, 2, 2), Signature(2, 0, 2),), (2, 2,),): [
"type29",
"Type 29: The Quadric Surface Intersection Curve "
+ "is planar with four imaginary lines which form an imaginary quadrangle; each"
+ " of the four lines intersects two of the other three at imaginary points.",
],
((1, 1, 3,), (Signature(0, 2, 2), Signature(1, 1, 2),), (2, 2,),): [
"type30",
"Type 30: The Quadric Surface Intersection Curve "
+ "is planar with two pairs of complex conjugate lines; one pair intersects "
+ "the other pair at two complex conjugate points.",
],
((2, 2,), (Signature(1, 0, 3),), (4,)): [
"type32",
"Type 32: The Quadric Surface Intersection Curve "
+ "is planar with one pair of intersecting real lines, counted twice.",
],
((1, 3,), (Signature(1, 0, 3),), (4,)): [
"type33",
"Type 33: The Quadric Surface Intersection Curve "
+ "is planar with one pair of complex conjugate lines, counted twice.",
],
((2, 2,), (Signature(2, 0, 2),), (4,)): [
"type34",
"Type 34: The Quadric Surface Intersection Curve "
+ "is planar with one real double line and one pair of skew imaginary lines.",
],
}
types_to_index_and_segre = {
"type1": [
IndexSequence(
(1, 2, 1, 2, 3),
(
(JordanBlock(1, 1),),
(JordanBlock(1, -1),),
(JordanBlock(1, 1),),
(JordanBlock(1, 1),),
),
),
SegreSymbol(roots=[[1], [1], [1], [1]], reals=4),
],
"type2": [
IndexSequence(
(0, 1, 2, 3, 4),
(
(JordanBlock(1, 1),),
(JordanBlock(1, 1),),
(JordanBlock(1, 1),),
(JordanBlock(1, 1),),
),
),
SegreSymbol(roots=[[1], [1], [1], [1],], reals=4,), # fmt: skip
],
"type3": [
IndexSequence(
(1, 2, 3),
(
(JordanBlock(1, 1),),
(JordanBlock(1, 1),),
),
),
SegreSymbol(roots=[[1], [1], [1], [1],], reals=2,), # fmt: skip
],
"type4": [
IndexSequence((2,), (),), # fmt: skip
SegreSymbol(roots=[[1], [1], [1], [1]], reals=0), # fmt: skip
],
"type5a": [
IndexSequence(
(2, 2, 1, 2),
(
(JordanBlock(2, 1),),
(JordanBlock(1, -1),),
(JordanBlock(1, 1),),
),
),
SegreSymbol(roots=[[2], [1], [1]], reals=3),
],
"type5b": [
IndexSequence(
(2, 2, 1, 2),
(
(JordanBlock(2, -1),),
(JordanBlock(1, -1),),
(JordanBlock(1, 1),),
),
),
SegreSymbol(roots=[[2], [1], [1]], reals=3),
],
"type6": [
IndexSequence(
(1, 1, 2, 3),
(
(JordanBlock(2, -1),),
(JordanBlock(1, 1),),
(JordanBlock(1, 1),),
),
),
SegreSymbol(roots=[[2], [1], [1]], reals=3),
],
"type7": [
# fmt: off
IndexSequence(
(1, 1, 2, 3,), # fmt: skip
(
(JordanBlock(2, 1),),
(JordanBlock(1, 1),),
(JordanBlock(1, 1),),
),
),
SegreSymbol(roots=[[2], [1], [1]], reals=3),
# fmt: on
],
"type8": [
# fmt: off
IndexSequence(
(2, 2, ),
((JordanBlock(2, -1),),),
),
SegreSymbol(roots=[[2], [1], [1]], reals=1),
# fmt: on
],
"type9": [
# fmt: off
IndexSequence(
(2, 2, 2,),
((JordanBlock(2, -1),), (JordanBlock(2, -1),),),
),
SegreSymbol(roots=[[2], [2]], reals=2),
# fmt: on
],
"type10": [
IndexSequence((2,), (),), # fmt: skip
SegreSymbol(roots=[[2], [2]], reals=0), # fmt: skip
],
"type11": [
IndexSequence(
(1, 2, 3,), # fmt: skip
((JordanBlock(3, 1),), (JordanBlock(1, 1),),), # fmt: skip
),
SegreSymbol(roots=[[3], [1]], reals=2),
],
"type12": [
# fmt: off
IndexSequence((2, 2,), ((JordanBlock(4, -1),),)),
SegreSymbol(roots=[[4]], reals=1),
# fmt: on
],
"type13": [
# fmt: off
IndexSequence(
(2, 2, 1, 2,),
(
(JordanBlock(1, -1), JordanBlock(1, 1),),
(JordanBlock(1, -1),),
(JordanBlock(1, 1),),
),
),
# fmt: on
SegreSymbol(roots=[[1, 1], [1], [1]], reals=3),
],
"type14": [
# fmt: off
IndexSequence(
(1, 3, 2, 3),
(
(JordanBlock(1, 1), JordanBlock(1, 1),),
(JordanBlock(1, -1),),
(JordanBlock(1, 1),),
),
),
SegreSymbol(roots=[[1, 1], [1], [1]], reals=3),
],
# fmt: on
"type15": [
# fmt: off
IndexSequence(
(1, 1, 2, 3),
(
(JordanBlock(1, -1), JordanBlock(1, 1),),
(JordanBlock(1, 1),),
(JordanBlock(1, 1),),
),
),
# fmt: on
SegreSymbol(roots=[[1, 1], [1], [1]], reals=3),
],
"type16a": [
# fmt: off
IndexSequence(
(0, 2, 3, 4),
(
(JordanBlock(1, 1), JordanBlock(1, 1),),
(JordanBlock(1, 1),),
(JordanBlock(1, 1),),
),
),
# fmt: on
SegreSymbol(roots=[[1, 1], [1], [1]], reals=3),
],
"type16b": [
# fmt: off
IndexSequence(
(1, 3, 4, 3),
(
(JordanBlock(1, 1), JordanBlock(1, 1),),
(JordanBlock(1, 1),),
(JordanBlock(1, -1),),
),
),
# fmt: on
SegreSymbol(roots=[[1, 1], [1], [1]], reals=3),
],
"type17": [
# fmt: off
IndexSequence(
(1, 3,),
((JordanBlock(1, 1), JordanBlock(1, 1),),),
),
# fmt: on
SegreSymbol(roots=[[1, 1], [1], [1]], reals=1),
],
"type18": [
# fmt: off
IndexSequence(
(2, 2,),
((JordanBlock(1, -1), JordanBlock(1, 1),),),
),
# fmt: on
SegreSymbol(roots=[[1, 1], [1], [1]], reals=1),
],
"type19": [
# fmt: off
IndexSequence(
(1, 2, 3,),
(
(
JordanBlock(1, -1),
JordanBlock(1, 1),
JordanBlock(1, 1),
),
(JordanBlock(1, 1),),
),
),
# fmt: on
SegreSymbol(roots=[[1, 1, 1], [1]], reals=2),
],
"type20": [
IndexSequence(
(0, 3, 4),
(
(
JordanBlock(1, 1),
JordanBlock(1, 1),
JordanBlock(1, 1),
),
(JordanBlock(1, 1),),
),
),
SegreSymbol(roots=[[1, 1, 1], [1]], reals=2),
],
"type21": [
# fmt: off
IndexSequence(
(1, 2, 3),
(
(JordanBlock(2, -1), JordanBlock(1, 1),),
(JordanBlock(1, 1),),
),
),
# fmt: on
SegreSymbol(roots=[[2, 1], [1]], reals=2),
],
"type22": [
# fmt: off
IndexSequence(
(1, 2, 3),
(
(JordanBlock(2, 1), JordanBlock(1, 1),),
(JordanBlock(1, 1),),
),
),
# fmt: on
SegreSymbol(roots=[[2, 1], [1]], reals=2),
],
"type23": [
# fmt: off
IndexSequence(
(2, 2, 2),
(
(JordanBlock(2, -1),),
(JordanBlock(1, -1), JordanBlock(1, 1),),
),
),
# fmt: on
SegreSymbol(roots=[[2], [1, 1]], reals=2),
],
"type24": [
# fmt: off
IndexSequence(
(1, 1, 3),
(
(JordanBlock(2, -1),),
(JordanBlock(1, 1), JordanBlock(1, 1),),
),
),
# fmt: on
SegreSymbol(roots=[[2], [1, 1]], reals=2),
],
"type25": [
# fmt: off
IndexSequence(
(1, 1, 3,),
(
(JordanBlock(2, 1),),
(JordanBlock(1, 1), JordanBlock(1, 1),),
),
),
# fmt: on
SegreSymbol(roots=[[2], [1, 1]], reals=2),
],
"type26": [
# fmt: off
IndexSequence(
(2, 2,),
((JordanBlock(3, -1), JordanBlock(1, 1),),),
),
# fmt: on
SegreSymbol(roots=[[3, 1]], reals=1),
],
"type27": [
# fmt: off
IndexSequence(
(1, 3,),
((JordanBlock(3, 1), JordanBlock(1, 1),),),
),
# fmt: on
SegreSymbol(roots=[[3, 1]], reals=1),
],
"type28": [
# fmt: off
IndexSequence(
(2, 2, 2,),
(
(JordanBlock(1, -1), JordanBlock(1, 1),),
(JordanBlock(1, -1), JordanBlock(1, 1),),
),
),
# fmt: on
SegreSymbol(roots=[[1, 1], [1, 1]], reals=2),
],
"type29": [
# fmt: off
IndexSequence(
(0, 2, 4,),
(
(JordanBlock(1, 1), JordanBlock(1, 1),),
(JordanBlock(1, 1), JordanBlock(1, 1),),
),
),
# fmt: on
SegreSymbol(roots=[[1, 1], [1, 1]], reals=2),
],
"type30": [
# fmt: off
IndexSequence(
(1, 1, 3,),
(
(JordanBlock(1, -1), JordanBlock(1, 1),),
(JordanBlock(1, 1), JordanBlock(1, 1),),
),
),
# fmt: on
SegreSymbol(roots=[[1, 1], [1, 1]], reals=2),
],
"type31": [
# fmt: off
IndexSequence((2,), (),),
# fmt: on
SegreSymbol(roots=[[1, 1], [1, 1]], reals=0),
],
"type32": [
# fmt: off
IndexSequence(
(2, 2,),
(
(
JordanBlock(2, -1),
JordanBlock(1, -1),
JordanBlock(1, 1),
),
),
),
# fmt: on
SegreSymbol(roots=[[2, 1, 1]], reals=1),
],
"type33": [
# fmt: off
IndexSequence(
(1, 3,),
(
(
JordanBlock(2, -1),
JordanBlock(1, 1),
JordanBlock(1, 1),
),
),
),
# fmt: on
SegreSymbol(roots=[[2, 1, 1]], reals=1),
],
"type34": [
# fmt: off
IndexSequence(
(2, 2,),
((JordanBlock(2, -1), JordanBlock(2, -1),),),
),
# fmt: on
SegreSymbol(roots=[[2, 2]], reals=1),
],
"type35": [
# fmt: off
IndexSequence(
(2, 2,),
((JordanBlock(2, -1), JordanBlock(2, 1),),),
),
# fmt: on
SegreSymbol(roots=[[2, 2]], reals=1),
],
}