Pencils
Pencils are lines in the projective space of quadrics. They can be spanned by two
quadrics Q1 and Q2 and contain all quadrics whose representing matrices are
linear combinations of the representatives of Q1 and Q2.
In our library, they are represented by instances of the
Pencil class.
Basic example
>>> import ddg
>>> import numpy as np
>>> Q1 = ddg.geometry.Quadric(np.diag([3, -1, 1, 0]))
>>> Q2 = ddg.geometry.Quadric(
... np.array([[1, 0, 0, 0], [0, -1, 0, 2], [0, 0, 1, 0], [0, 2, 0, 0]]),
... )
>>> pencil = ddg.geometry.Pencil(Q1, Q2)
Signature Sequence
The signature sequence calculates the signatures of all quadrics in the pencil
lmda*Q2+Q1 (which is the affine representation of the pencil lmda*Q2+mu*Q1) and
presents them in a sequence of signatures ordered by size of lmda.
It only returns the index (positive part of signature) for non-degenerate quadrics and
as many brackets around (the non-degenerate part of the) signatures of degenerate
quadrics as is the multiplicity of the root lmda in f(lmda):=det(lmda*Q2+Q1).
If we have degenerate quadrics for values lmda_i where i=1,...,3 and signatures
(nu_i, mu_i) for lmda_i-1<lmda<lmda_i (where it applies) and signatures
(p_i, n_i) for lmda=lmda_i, the signature sequence will be:
(nu_1, (...(p_1, n_1)...), nu_2, (...(p_2, n_2)...), nu_3, (...(p_3, n_3)...), n_4)
Given representations for two quadrics, we can calculate an iteration of the signature sequence for the pecil they span in the library.
>>> Q1 = ddg.geometry.Quadric(
... np.array([[-1, 0, 0, 0], [0, 1, 0, -2], [0, 0, -1, 0], [0, -2, 0, 0]]),
... )
>>> Q2 = ddg.geometry.Quadric(np.diag([-3, 1, 1, 0]))
>>> pencil = ddg.geometry.Pencil(Q1, Q2)
>>> signature_seq = pencil.signature_sequence()
>>> signature_seq
Signature Sequence with tuples of
indices: (2, 2, 3, 2),
signatures: (Signature(plus=2, minus=1, zero=1), Signature(plus=2, minus=1, zero=1), Signature(plus=2, minus=1, zero=1)),
multiplicities: (2, 1, 1)
>>> print(signature_seq)
(2, ((2, 1)), 2, (2, 1), 3, (2, 1), 2)
Since this calculation depends on the chosen quadrics in the pencil and their
representation, there are equivalence classes on the set of signature sequences and the
result is only uniquely determined up to equivalence for the pencil.
Therefore, signature sequences can be brought into a normal form with help of the
SignatureSequence class. This allows to compare two
signature sequences and determine whether they are equivalent.
>>> print(signature_seq.normalform())
(2, ((1, 2)), 2, (1, 2), 1, (1, 2), 2)
The signature sequence can only be calculated for a non-degenerate pencil (that contains
at least one non-degenerate quadric) and if Q2 is a non-degenerate pencil in the
representation lmda*Q2+Q1. In the calculation this is achieved via the method
regular_matrix() that returns an alternative matrix for
Q2.
>>> newQ2 = pencil.regular_matrix()
>>> newQ2
Quadric(
array([[ 3. , 0. , 0. , 0. ],
[ 0. , -0.33333333, 0. , -2. ],
[ 0. , 0. , -2.33333333, 0. ],
[ 0. , -2. , 0. , 0. ]])
)
Quadric intersection
The points of intersection of two quadrics in a pencil are contained in every quadric
of the pencil. In RP³ the intersection constitutes a quadric surface intersection
curve (QSIC).
>>> qsic = pencil.intersection()
>>> type(qsic)
<class 'ddg.geometry._quadrics.QuadricIntersection'>
>>> qsic.dimension_complex
1
>>> print(qsic)
intersection of the two quadrics Q1 and Q2
in 3D projective space
Q1:
quadric in 3D projective space
signature: (1, 3)
matrix:
[[-1. 0. 0. 0.]
[ 0. 1. 0. -2.]
[ 0. 0. -1. 0.]
[ 0. -2. 0. 0.]]
Q2:
quadric in 3D projective space
signature: (2, 1, 1)
matrix:
[[-3. 0. 0. 0.]
[ 0. 1. 0. 0.]
[ 0. 0. 1. 0.]
[ 0. 0. 0. 0.]]
Classification
In 3-dimensional real projective space RP³ the signature sequences of pencils
provide a classification of pencils and determine topological properties of their
intersection curves. In the library, the classification is part of the
Pencil class (albeit it uses the signature sequence) and
returns a tuple with a short handle for distinguishing between cases and a
description of the intersection curve.
>>> pencil.classification()
['type5b', 'Type 5: The Quadric Surface Intersection Curve has two null-homotopic components joining at a crunode.']
Index Sequence and Segre Symbol
From the classification, a normal form of the index sequence and Segre symbol of the pencil can be read off.
The index seqence resembles the signature sequence, but instead of signatures for the
degenerate quadrics, it represents the blocks in the quadric pair canonical form (QPCF)
for the pair Q2 and Q1 (a representation related to the Jordan normal form of
Q2⁻¹Q1) corresponding to the root lmda.
Here, the symbols represent Jordan blocks of different sizes:
|size 1
≀...≀₊size n (number of wavy lines) with positive sign in the QPCF
≀...≀₋size n (number of wavy lines) with negative sign in the QPCF
(The QPCF is calculated as follows: If Q2 is non-singular
and Q2⁻¹Q1 has Jordan normal form 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 |)
In our example, the index sequence can be determined as follows:
>>> index_seq = pencil.index_sequence()
>>> print(index_seq)
〈2 ≀≀₋ 2 | 1 | 2〉
The index sequence of a pencil is always returned in normal form.
The Segre symbol denotes the sizes of Jordan blocks in the Jordan normal form of
Q2⁻¹Q1 (and hence in the QPCF of Q1 and Q2) to roots lmda (that are
eigevalues of the Jordan normal form). Jordan blocks to the same eigenvalue are
collected in parenthesis and ordered descendingly by size. The subscript number after
the Segre symbol denotes how many different real root values there are in the Jordan
normal form.
>>> segre_symb = pencil.segre_symbol()
>>> print(segre_symb)
[2 1 1]₃
Eigenvalue Curve Function
This module also provides means to calculate the eigenvalue curve function of a pencil
defined as f(u, lmda) = det(lmda*Q2+Q1-u*I), where I is the identity matrix.
>>> import sympy as sp
>>> u = sp.Symbol("u")
>>> lmda = sp.Symbol("lmda")
>>> f = pencil.eigenvalue_curve_function()
>>> f(u, lmda)
(-u*(-2.33333333333333*lmda - u - 1.0)*(-0.333333333333333*lmda - u + 1.0) - 4.0*(-lmda - 1)**2*(-2.33333333333333*lmda - u - 1.0))*(3.0*lmda - u - 1.0)
(The eigenvalue curve function is used for special cases in the classification algorithm where the signature sequence is ambiguous and admits different Segre symbols.)

