Skip to content

Forms

ablina.form

A module for working with forms and inner products.

SesquilinearForm

A sesquilinear form on a vector space.

A sesquilinear form <,> is a function that takes two vectors and returns a scalar, satisfying the following properties:

  • <cu, v> = involution(c) <u, v> for all scalars c and vectors u, v
  • <u + v, w> = <u, w> + <v, w> for all vectors u, v, w
  • <u, cv> = c <u, v> for all scalars c and vectors u, v
  • <u, v + w> = <u, v> + <u, w> for all vectors u, v, w
Source code in ablina/form.py
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
class SesquilinearForm:
    """
    A sesquilinear form on a vector space.

    A sesquilinear form `<,>` is a function that takes two vectors and 
    returns a scalar, satisfying the following properties:

    - `<cu, v> = involution(c) <u, v>` for all scalars `c` and vectors `u`, `v`
    - `<u + v, w> = <u, w> + <v, w>` for all vectors `u`, `v`, `w`
    - `<u, cv> = c <u, v>` for all scalars `c` and vectors `u`, `v`
    - `<u, v + w> = <u, v> + <u, w>` for all vectors `u`, `v`, `w`
    """

    def __init__(
        self, 
        name: str, 
        vectorspace: VectorSpace, 
        mapping: Callable[[Any, Any], Any] | None = None, 
        matrix: Any | None = None, 
        involution: Callable[[Any], Any] | None = None
    ) -> None:
        """
        Initialize a SesquilinearForm instance.

        Parameters
        ----------
        name : str
            The name of the form.
        vectorspace : VectorSpace
            The vector space the form is defined on.
        mapping : callable, optional
            A function that takes two vectors in the vector space and 
            returns a scalar in the field.
        matrix : Matrix, optional
            The matrix representation of the form with respect to the 
            basis of the vector space.
        involution : callable, optional
            The involution to use for the form. Must be a callable of 
            arity 1. If None, defaults to conjugation.

        Returns
        -------
        SesquilinearForm
            A new SesquilinearForm instance.

        Raises
        ------
        FormError
            If neither the mapping nor the matrix is provided.
        """
        if not isinstance(vectorspace, VectorSpace):
            raise TypeError("vectorspace must be of type VectorSpace.")

        involution = SesquilinearForm._to_involution(involution)
        matrix = SesquilinearForm._to_matrix(vectorspace, mapping, matrix)
        mapping = SesquilinearForm._to_mapping(vectorspace, mapping, matrix, involution)

        self.name = name
        self._vectorspace = vectorspace
        self._mapping = mapping
        self._matrix = matrix
        self._involution = involution

    @staticmethod
    def _to_involution(involution: Callable[[Any], Any] | None) -> Callable[[Any], Any]:
        if involution is None:
            return sp.conjugate
        if not of_arity(involution, 1):
            raise TypeError("Involution must be a callable of arity 1.")
        return involution

    @staticmethod
    def _to_matrix(
        vectorspace: VectorSpace, 
        mapping: Callable[[Any, Any], Any] | None, 
        matrix: Any | None
    ) -> Matrix:
        if matrix is not None:
            return SesquilinearForm._validate_matrix(vectorspace, matrix)
        if mapping is None:
            raise FormError("Either a matrix or mapping must be provided.")
        if not of_arity(mapping, 2):
            raise TypeError("Mapping must be a callable of arity 2.")

        basis = vectorspace.basis
        n = len(basis)
        return M(n, n, lambda i, j: mapping(basis[i], basis[j]))

    @staticmethod
    def _to_mapping(
        vectorspace: VectorSpace, 
        mapping: Callable[[Any, Any], Any] | None, 
        matrix: Matrix, 
        involution: Callable[[Any], Any]
    ) -> Callable[[Any, Any], Any]:
        if mapping is not None:
            return mapping
        to_coord = vectorspace.to_coordinate

        def mapping(u: Any, v: Any) -> Any:
            u = to_coord(u).applyfunc(involution)
            return (u.T @ matrix @ to_coord(v))[0]
        return mapping

    @staticmethod
    def _validate_matrix(vectorspace: VectorSpace, matrix: Any) -> Matrix:
        mat = M(matrix)
        if not (mat.is_square and mat.rows == vectorspace.dim):
            raise ValueError("Matrix has invalid shape.")
        if not all(i in vectorspace.field for i in mat):
            raise ValueError("Matrix entries must be elements of the field.")
        return mat

    @property
    def vectorspace(self) -> VectorSpace:
        """
        VectorSpace: The vector space the form is defined on.
        """
        return self._vectorspace

    @property
    def mapping(self) -> Callable[[Any, Any], Any]:
        """
        callable: The function that maps vectors to scalars.
        """
        return self._mapping

    @property
    def matrix(self) -> Matrix:
        """
        Matrix: The matrix representation of the form.
        """
        return self._matrix

    @property
    def involution(self) -> Callable[[Any], Any]:
        """
        callable: The involution the form is defined with respect to.
        """
        return self._involution

    def __repr__(self) -> str:
        return (
            f"SesquilinearForm(name={self.name!r}, "
            f"vectorspace={self.vectorspace!r}, "
            f"mapping={self.mapping!r}, "
            f"matrix={self.matrix!r}, "
            f"involution={self.involution!r})"
            )

    def __str__(self) -> str:
        return self.name

    def __eq__(self, form2: Any) -> bool:
        """
        Check for equality of two forms.

        Parameters
        ----------
        form2 : SesquilinearForm
            The form to compare with.

        Returns
        -------
        bool
            True if both forms are equal, otherwise False.
        """
        if not isinstance(form2, SesquilinearForm):
            return False
        return (
            self.vectorspace == form2.vectorspace 
            and self.matrix.equals(form2.matrix)
            )

    def __call__(self, vec1: Any, vec2: Any) -> Any:
        """
        Apply the form to two vectors.

        Parameters
        ----------
        vec1, vec2 : object
            The vectors to apply the form to.

        Returns
        -------
        object
            The scalar value `<vec1, vec2>`.

        Raises
        ------
        TypeError
            If the vectors are not elements of the vector space.
        """
        vs = self.vectorspace
        if not (vec1 in vs and vec2 in vs):
            raise TypeError("Vectors must be elements of the vector space.")
        return self.mapping(vec1, vec2)

    def info(self) -> str:
        """
        A description of the form.

        Returns
        -------
        str
            The formatted description.
        """
        vs = self.vectorspace
        signature = f"{self} : {vs} × {vs}{vs.field}"

        try:
            pos_def = self.is_positive_definite()
        except FormError:
            pos_def = "N/A"

        lines = [
            signature,
            "-" * len(signature),
            f"Symmetric?          {self.is_symmetric()}",
            f"Positive Definite?  {pos_def}",
            f"Matrix              {self.matrix}"
            ]
        return "\n".join(lines)

    def inertia(self) -> tuple[int, int, int]:
        """
        Compute the inertia of the form.

        Returns a tuple (p, m, z) where:

        - p is the number of positive eigenvalues
        - m is the number of negative eigenvalues
        - z is the number of zero eigenvalues

        Returns
        -------
        tuple of (int, int, int)
            The inertia (p, m, z) of the form.

        Raises
        ------
        FormError
            If the form is not symmetric (real) or hermitian (complex).
        """
        self._validate_form()
        tol = 1e-8
        eigenvals = self.matrix.evalf().eigenvals().items()

        p = sum(m for val, m in eigenvals if val >= tol)
        m = sum(m for val, m in eigenvals if val <= -tol)
        z = sum(m for val, m in eigenvals if abs(val) < tol)
        return p, m, z

    def signature(self) -> int:
        """
        Compute the signature of the form.

        The signature is the difference between the number of positive 
        and negative eigenvalues.

        Returns
        -------
        int
            The signature of the form.

        Raises
        ------
        FormError
            If the form is not symmetric (real) or hermitian (complex).
        """
        p, m, _ = self.inertia()
        return p - m

    def is_degenerate(self) -> bool | None:
        """
        Check whether the form is degenerate.

        A form is degenerate if its matrix is not invertible.

        Returns
        -------
        bool
            True if `self` is degenerate, otherwise False.
        """
        is_inv = is_invertible(self.matrix)
        return None if is_inv is None else not is_inv

    def is_symmetric(self) -> bool | None:
        """
        Check whether the form is symmetric.

        This method checks whether `<u, v> = involution(<v, u>)` for all 
        `u` and `v`.

        Returns
        -------
        bool
            True if `self` is symmetric, otherwise False.
        """
        mat1 = self.matrix
        mat2 = mat1.applyfunc(self.involution).T
        return mat1.equals(mat2)

    def is_skew_symmetric(self) -> bool | None:
        """
        Check whether the form is skew-symmetric.

        This method checks whether `<u, v> = -involution(<v, u>)` for all 
        `u` and `v`.

        Returns
        -------
        bool
            True if `self` is skew-symmetric, otherwise False.

        See Also
        --------
        SesquilinearForm.is_alternating
        """
        mat1 = self.matrix
        mat2 = -1 * mat1.applyfunc(self.involution).T
        return mat1.equals(mat2)

    def is_alternating(self) -> bool | None:
        """
        Check whether the form is alternating.

        This method checks whether `<v, v> = 0` for all `v`.

        Returns
        -------
        bool
            True if `self` is alternating, otherwise False.

        See Also
        --------
        SesquilinearForm.is_skew_symmetric
        """
        is_skew = self.is_skew_symmetric()
        is_zero = self.matrix.diagonal().is_zero_matrix
        if is_skew is False or is_zero is False:
            return False
        return True if is_skew and is_zero else None

    def is_hermitian(self) -> bool | None:
        """
        Check whether the form is hermitian.

        This method checks whether `<u, v> = conjugate(<v, u>)` for all 
        `u` and `v`. Note that this method is equivalent to 
        ``self.is_symmetric`` when the involution is conjugation.

        Returns
        -------
        bool
            True if `self` is hermitian, otherwise False.

        See Also
        --------
        SesquilinearForm.is_symmetric
        """
        return self.matrix.is_hermitian

    def is_positive_definite(self) -> bool | None:
        """
        Check whether the form is positive definite.

        This method checks whether `<v, v> > 0` for all `v ≠ 0`. Note 
        that the form is required to be symmetric (for real spaces) or 
        hermitian (for complex spaces).

        Returns
        -------
        bool
            True if `self` is positive definite, otherwise False.

        Raises
        ------
        FormError
            If the form is not symmetric (real) or hermitian (complex).

        See Also
        --------
        SesquilinearForm.is_positive_semidefinite
        """
        self._validate_form()
        return self.matrix.is_positive_definite

    def is_negative_definite(self) -> bool | None:
        """
        Check whether the form is negative definite.

        This method checks whether `<v, v> < 0` for all `v ≠ 0`. Note 
        that the form is required to be symmetric (for real spaces) or 
        hermitian (for complex spaces).

        Returns
        -------
        bool
            True if `self` is negative definite, otherwise False.

        Raises
        ------
        FormError
            If the form is not symmetric (real) or hermitian (complex).

        See Also
        --------
        SesquilinearForm.is_negative_semidefinite
        """
        self._validate_form()
        return self.matrix.is_negative_definite

    def is_positive_semidefinite(self) -> bool | None:
        """
        Check whether the form is positive semidefinite.

        This method checks whether `<v, v> ≥ 0` for all `v`. Note that 
        the form is required to be symmetric (for real spaces) or 
        hermitian (for complex spaces).

        Returns
        -------
        bool
            True if `self` is positive semidefinite, otherwise False.

        Raises
        ------
        FormError
            If the form is not symmetric (real) or hermitian (complex).

        See Also
        --------
        SesquilinearForm.is_positive_definite
        """
        self._validate_form()
        return self.matrix.is_positive_semidefinite

    def is_negative_semidefinite(self) -> bool | None:
        """
        Check whether the form is negative semidefinite.

        This method checks whether `<v, v> ≤ 0` for all `v`. Note that 
        the form is required to be symmetric (for real spaces) or 
        hermitian (for complex spaces).

        Returns
        -------
        bool
            True if `self` is negative semidefinite, otherwise False.

        Raises
        ------
        FormError
            If the form is not symmetric (real) or hermitian (complex).

        See Also
        --------
        SesquilinearForm.is_negative_definite
        """
        self._validate_form()
        return self.matrix.is_negative_semidefinite

    def is_indefinite(self) -> bool | None:
        """
        Check whether the form is indefinite.

        This method checks whether `<u, u> > 0` and `<v, v> < 0` for some 
        `u` and `v`. Note that the form is required to be symmetric 
        (for real spaces) or hermitian (for complex spaces).

        Returns
        -------
        bool
            True if `self` is indefinite, otherwise False.

        Raises
        ------
        FormError
            If the form is not symmetric (real) or hermitian (complex).
        """
        self._validate_form()
        return self.matrix.is_indefinite

    def _validate_form(self) -> None:
        field = self.vectorspace.field
        if field is R:
            if not self.is_symmetric():
                raise FormError("Form must be symmetric for real vector spaces.")
        elif field is C:
            if self.involution is not sp.conjugate:
                raise FormError("Involution must be conjugation for complex vector spaces.")
            if not self.is_symmetric():
                raise FormError("Form must be hermitian for complex vector spaces.")
        else:
            raise FormError("Form must be defined on a real or complex vector space.")

    # Aliases
    is_anti_symmetric = is_skew_symmetric
    """An alias for the is_skew_symmetric method."""

__init__(name, vectorspace, mapping=None, matrix=None, involution=None)

Initialize a SesquilinearForm instance.

Parameters:

Name Type Description Default
name str

The name of the form.

required
vectorspace VectorSpace

The vector space the form is defined on.

required
mapping callable

A function that takes two vectors in the vector space and returns a scalar in the field.

None
matrix Matrix

The matrix representation of the form with respect to the basis of the vector space.

None
involution callable

The involution to use for the form. Must be a callable of arity 1. If None, defaults to conjugation.

None

Returns:

Type Description
SesquilinearForm

A new SesquilinearForm instance.

Raises:

Type Description
FormError

If neither the mapping nor the matrix is provided.

Source code in ablina/form.py
def __init__(
    self, 
    name: str, 
    vectorspace: VectorSpace, 
    mapping: Callable[[Any, Any], Any] | None = None, 
    matrix: Any | None = None, 
    involution: Callable[[Any], Any] | None = None
) -> None:
    """
    Initialize a SesquilinearForm instance.

    Parameters
    ----------
    name : str
        The name of the form.
    vectorspace : VectorSpace
        The vector space the form is defined on.
    mapping : callable, optional
        A function that takes two vectors in the vector space and 
        returns a scalar in the field.
    matrix : Matrix, optional
        The matrix representation of the form with respect to the 
        basis of the vector space.
    involution : callable, optional
        The involution to use for the form. Must be a callable of 
        arity 1. If None, defaults to conjugation.

    Returns
    -------
    SesquilinearForm
        A new SesquilinearForm instance.

    Raises
    ------
    FormError
        If neither the mapping nor the matrix is provided.
    """
    if not isinstance(vectorspace, VectorSpace):
        raise TypeError("vectorspace must be of type VectorSpace.")

    involution = SesquilinearForm._to_involution(involution)
    matrix = SesquilinearForm._to_matrix(vectorspace, mapping, matrix)
    mapping = SesquilinearForm._to_mapping(vectorspace, mapping, matrix, involution)

    self.name = name
    self._vectorspace = vectorspace
    self._mapping = mapping
    self._matrix = matrix
    self._involution = involution

vectorspace property

VectorSpace: The vector space the form is defined on.

mapping property

callable: The function that maps vectors to scalars.

matrix property

Matrix: The matrix representation of the form.

involution property

callable: The involution the form is defined with respect to.

__eq__(form2)

Check for equality of two forms.

Parameters:

Name Type Description Default
form2 SesquilinearForm

The form to compare with.

required

Returns:

Type Description
bool

True if both forms are equal, otherwise False.

Source code in ablina/form.py
def __eq__(self, form2: Any) -> bool:
    """
    Check for equality of two forms.

    Parameters
    ----------
    form2 : SesquilinearForm
        The form to compare with.

    Returns
    -------
    bool
        True if both forms are equal, otherwise False.
    """
    if not isinstance(form2, SesquilinearForm):
        return False
    return (
        self.vectorspace == form2.vectorspace 
        and self.matrix.equals(form2.matrix)
        )

__call__(vec1, vec2)

Apply the form to two vectors.

Parameters:

Name Type Description Default
vec1 object

The vectors to apply the form to.

required
vec2 object

The vectors to apply the form to.

required

Returns:

Type Description
object

The scalar value <vec1, vec2>.

Raises:

Type Description
TypeError

If the vectors are not elements of the vector space.

Source code in ablina/form.py
def __call__(self, vec1: Any, vec2: Any) -> Any:
    """
    Apply the form to two vectors.

    Parameters
    ----------
    vec1, vec2 : object
        The vectors to apply the form to.

    Returns
    -------
    object
        The scalar value `<vec1, vec2>`.

    Raises
    ------
    TypeError
        If the vectors are not elements of the vector space.
    """
    vs = self.vectorspace
    if not (vec1 in vs and vec2 in vs):
        raise TypeError("Vectors must be elements of the vector space.")
    return self.mapping(vec1, vec2)

info()

A description of the form.

Returns:

Type Description
str

The formatted description.

Source code in ablina/form.py
def info(self) -> str:
    """
    A description of the form.

    Returns
    -------
    str
        The formatted description.
    """
    vs = self.vectorspace
    signature = f"{self} : {vs} × {vs}{vs.field}"

    try:
        pos_def = self.is_positive_definite()
    except FormError:
        pos_def = "N/A"

    lines = [
        signature,
        "-" * len(signature),
        f"Symmetric?          {self.is_symmetric()}",
        f"Positive Definite?  {pos_def}",
        f"Matrix              {self.matrix}"
        ]
    return "\n".join(lines)

inertia()

Compute the inertia of the form.

Returns a tuple (p, m, z) where:

  • p is the number of positive eigenvalues
  • m is the number of negative eigenvalues
  • z is the number of zero eigenvalues

Returns:

Type Description
tuple of (int, int, int)

The inertia (p, m, z) of the form.

Raises:

Type Description
FormError

If the form is not symmetric (real) or hermitian (complex).

Source code in ablina/form.py
def inertia(self) -> tuple[int, int, int]:
    """
    Compute the inertia of the form.

    Returns a tuple (p, m, z) where:

    - p is the number of positive eigenvalues
    - m is the number of negative eigenvalues
    - z is the number of zero eigenvalues

    Returns
    -------
    tuple of (int, int, int)
        The inertia (p, m, z) of the form.

    Raises
    ------
    FormError
        If the form is not symmetric (real) or hermitian (complex).
    """
    self._validate_form()
    tol = 1e-8
    eigenvals = self.matrix.evalf().eigenvals().items()

    p = sum(m for val, m in eigenvals if val >= tol)
    m = sum(m for val, m in eigenvals if val <= -tol)
    z = sum(m for val, m in eigenvals if abs(val) < tol)
    return p, m, z

signature()

Compute the signature of the form.

The signature is the difference between the number of positive and negative eigenvalues.

Returns:

Type Description
int

The signature of the form.

Raises:

Type Description
FormError

If the form is not symmetric (real) or hermitian (complex).

Source code in ablina/form.py
def signature(self) -> int:
    """
    Compute the signature of the form.

    The signature is the difference between the number of positive 
    and negative eigenvalues.

    Returns
    -------
    int
        The signature of the form.

    Raises
    ------
    FormError
        If the form is not symmetric (real) or hermitian (complex).
    """
    p, m, _ = self.inertia()
    return p - m

is_degenerate()

Check whether the form is degenerate.

A form is degenerate if its matrix is not invertible.

Returns:

Type Description
bool

True if self is degenerate, otherwise False.

Source code in ablina/form.py
def is_degenerate(self) -> bool | None:
    """
    Check whether the form is degenerate.

    A form is degenerate if its matrix is not invertible.

    Returns
    -------
    bool
        True if `self` is degenerate, otherwise False.
    """
    is_inv = is_invertible(self.matrix)
    return None if is_inv is None else not is_inv

is_symmetric()

Check whether the form is symmetric.

This method checks whether <u, v> = involution(<v, u>) for all u and v.

Returns:

Type Description
bool

True if self is symmetric, otherwise False.

Source code in ablina/form.py
def is_symmetric(self) -> bool | None:
    """
    Check whether the form is symmetric.

    This method checks whether `<u, v> = involution(<v, u>)` for all 
    `u` and `v`.

    Returns
    -------
    bool
        True if `self` is symmetric, otherwise False.
    """
    mat1 = self.matrix
    mat2 = mat1.applyfunc(self.involution).T
    return mat1.equals(mat2)

is_skew_symmetric()

Check whether the form is skew-symmetric.

This method checks whether <u, v> = -involution(<v, u>) for all u and v.

Returns:

Type Description
bool

True if self is skew-symmetric, otherwise False.

See Also

SesquilinearForm.is_alternating

Source code in ablina/form.py
def is_skew_symmetric(self) -> bool | None:
    """
    Check whether the form is skew-symmetric.

    This method checks whether `<u, v> = -involution(<v, u>)` for all 
    `u` and `v`.

    Returns
    -------
    bool
        True if `self` is skew-symmetric, otherwise False.

    See Also
    --------
    SesquilinearForm.is_alternating
    """
    mat1 = self.matrix
    mat2 = -1 * mat1.applyfunc(self.involution).T
    return mat1.equals(mat2)

is_alternating()

Check whether the form is alternating.

This method checks whether <v, v> = 0 for all v.

Returns:

Type Description
bool

True if self is alternating, otherwise False.

See Also

SesquilinearForm.is_skew_symmetric

Source code in ablina/form.py
def is_alternating(self) -> bool | None:
    """
    Check whether the form is alternating.

    This method checks whether `<v, v> = 0` for all `v`.

    Returns
    -------
    bool
        True if `self` is alternating, otherwise False.

    See Also
    --------
    SesquilinearForm.is_skew_symmetric
    """
    is_skew = self.is_skew_symmetric()
    is_zero = self.matrix.diagonal().is_zero_matrix
    if is_skew is False or is_zero is False:
        return False
    return True if is_skew and is_zero else None

is_hermitian()

Check whether the form is hermitian.

This method checks whether <u, v> = conjugate(<v, u>) for all u and v. Note that this method is equivalent to self.is_symmetric when the involution is conjugation.

Returns:

Type Description
bool

True if self is hermitian, otherwise False.

See Also

SesquilinearForm.is_symmetric

Source code in ablina/form.py
def is_hermitian(self) -> bool | None:
    """
    Check whether the form is hermitian.

    This method checks whether `<u, v> = conjugate(<v, u>)` for all 
    `u` and `v`. Note that this method is equivalent to 
    ``self.is_symmetric`` when the involution is conjugation.

    Returns
    -------
    bool
        True if `self` is hermitian, otherwise False.

    See Also
    --------
    SesquilinearForm.is_symmetric
    """
    return self.matrix.is_hermitian

is_positive_definite()

Check whether the form is positive definite.

This method checks whether <v, v> > 0 for all v ≠ 0. Note that the form is required to be symmetric (for real spaces) or hermitian (for complex spaces).

Returns:

Type Description
bool

True if self is positive definite, otherwise False.

Raises:

Type Description
FormError

If the form is not symmetric (real) or hermitian (complex).

See Also

SesquilinearForm.is_positive_semidefinite

Source code in ablina/form.py
def is_positive_definite(self) -> bool | None:
    """
    Check whether the form is positive definite.

    This method checks whether `<v, v> > 0` for all `v ≠ 0`. Note 
    that the form is required to be symmetric (for real spaces) or 
    hermitian (for complex spaces).

    Returns
    -------
    bool
        True if `self` is positive definite, otherwise False.

    Raises
    ------
    FormError
        If the form is not symmetric (real) or hermitian (complex).

    See Also
    --------
    SesquilinearForm.is_positive_semidefinite
    """
    self._validate_form()
    return self.matrix.is_positive_definite

is_negative_definite()

Check whether the form is negative definite.

This method checks whether <v, v> < 0 for all v ≠ 0. Note that the form is required to be symmetric (for real spaces) or hermitian (for complex spaces).

Returns:

Type Description
bool

True if self is negative definite, otherwise False.

Raises:

Type Description
FormError

If the form is not symmetric (real) or hermitian (complex).

See Also

SesquilinearForm.is_negative_semidefinite

Source code in ablina/form.py
def is_negative_definite(self) -> bool | None:
    """
    Check whether the form is negative definite.

    This method checks whether `<v, v> < 0` for all `v ≠ 0`. Note 
    that the form is required to be symmetric (for real spaces) or 
    hermitian (for complex spaces).

    Returns
    -------
    bool
        True if `self` is negative definite, otherwise False.

    Raises
    ------
    FormError
        If the form is not symmetric (real) or hermitian (complex).

    See Also
    --------
    SesquilinearForm.is_negative_semidefinite
    """
    self._validate_form()
    return self.matrix.is_negative_definite

is_positive_semidefinite()

Check whether the form is positive semidefinite.

This method checks whether <v, v> ≥ 0 for all v. Note that the form is required to be symmetric (for real spaces) or hermitian (for complex spaces).

Returns:

Type Description
bool

True if self is positive semidefinite, otherwise False.

Raises:

Type Description
FormError

If the form is not symmetric (real) or hermitian (complex).

See Also

SesquilinearForm.is_positive_definite

Source code in ablina/form.py
def is_positive_semidefinite(self) -> bool | None:
    """
    Check whether the form is positive semidefinite.

    This method checks whether `<v, v> ≥ 0` for all `v`. Note that 
    the form is required to be symmetric (for real spaces) or 
    hermitian (for complex spaces).

    Returns
    -------
    bool
        True if `self` is positive semidefinite, otherwise False.

    Raises
    ------
    FormError
        If the form is not symmetric (real) or hermitian (complex).

    See Also
    --------
    SesquilinearForm.is_positive_definite
    """
    self._validate_form()
    return self.matrix.is_positive_semidefinite

is_negative_semidefinite()

Check whether the form is negative semidefinite.

This method checks whether <v, v> ≤ 0 for all v. Note that the form is required to be symmetric (for real spaces) or hermitian (for complex spaces).

Returns:

Type Description
bool

True if self is negative semidefinite, otherwise False.

Raises:

Type Description
FormError

If the form is not symmetric (real) or hermitian (complex).

See Also

SesquilinearForm.is_negative_definite

Source code in ablina/form.py
def is_negative_semidefinite(self) -> bool | None:
    """
    Check whether the form is negative semidefinite.

    This method checks whether `<v, v> ≤ 0` for all `v`. Note that 
    the form is required to be symmetric (for real spaces) or 
    hermitian (for complex spaces).

    Returns
    -------
    bool
        True if `self` is negative semidefinite, otherwise False.

    Raises
    ------
    FormError
        If the form is not symmetric (real) or hermitian (complex).

    See Also
    --------
    SesquilinearForm.is_negative_definite
    """
    self._validate_form()
    return self.matrix.is_negative_semidefinite

is_indefinite()

Check whether the form is indefinite.

This method checks whether <u, u> > 0 and <v, v> < 0 for some u and v. Note that the form is required to be symmetric (for real spaces) or hermitian (for complex spaces).

Returns:

Type Description
bool

True if self is indefinite, otherwise False.

Raises:

Type Description
FormError

If the form is not symmetric (real) or hermitian (complex).

Source code in ablina/form.py
def is_indefinite(self) -> bool | None:
    """
    Check whether the form is indefinite.

    This method checks whether `<u, u> > 0` and `<v, v> < 0` for some 
    `u` and `v`. Note that the form is required to be symmetric 
    (for real spaces) or hermitian (for complex spaces).

    Returns
    -------
    bool
        True if `self` is indefinite, otherwise False.

    Raises
    ------
    FormError
        If the form is not symmetric (real) or hermitian (complex).
    """
    self._validate_form()
    return self.matrix.is_indefinite

is_anti_symmetric = is_skew_symmetric class-attribute instance-attribute

An alias for the is_skew_symmetric method.

BilinearForm

Bases: SesquilinearForm

A bilinear form on a vector space.

A bilinear form <,> is a function that takes two vectors and returns a scalar, satisfying the following properties:

  • <cu, v> = c <u, v> for all scalars c and vectors u, v
  • <u + v, w> = <u, w> + <v, w> for all vectors u, v, w
  • <u, cv> = c <u, v> for all scalars c and vectors u, v
  • <u, v + w> = <u, v> + <u, w> for all vectors u, v, w
Source code in ablina/form.py
class BilinearForm(SesquilinearForm):
    """
    A bilinear form on a vector space.

    A bilinear form `<,>` is a function that takes two vectors and 
    returns a scalar, satisfying the following properties:

    - `<cu, v> = c <u, v>` for all scalars `c` and vectors `u`, `v`
    - `<u + v, w> = <u, w> + <v, w>` for all vectors `u`, `v`, `w`
    - `<u, cv> = c <u, v>` for all scalars `c` and vectors `u`, `v`
    - `<u, v + w> = <u, v> + <u, w>` for all vectors `u`, `v`, `w`
    """

    def __init__(
        self, 
        name: str, 
        vectorspace: VectorSpace, 
        mapping: Callable[[Any, Any], Any] | None = None, 
        matrix: Any | None = None
    ) -> None:
        """
        Initialize a BilinearForm instance.

        Parameters
        ----------
        name : str
            The name of the form.
        vectorspace : VectorSpace
            The vector space the form is defined on.
        mapping : callable, optional
            A function that takes two vectors in the vector space and 
            returns a scalar in the field.
        matrix : Matrix, optional
            The matrix representation of the form with respect to the 
            basis of the vector space.

        Returns
        -------
        BilinearForm
            A new BilinearForm instance.

        Raises
        ------
        FormError
            If neither the mapping nor the matrix is provided.
        """
        super().__init__(name, vectorspace, mapping, matrix, lambda c: c)

    def __repr__(self) -> str:
        return (
            f"BilinearForm(name={self.name!r}, "
            f"vectorspace={self.vectorspace!r}, "
            f"mapping={self.mapping!r}, "
            f"matrix={self.matrix!r})"
            )

__init__(name, vectorspace, mapping=None, matrix=None)

Initialize a BilinearForm instance.

Parameters:

Name Type Description Default
name str

The name of the form.

required
vectorspace VectorSpace

The vector space the form is defined on.

required
mapping callable

A function that takes two vectors in the vector space and returns a scalar in the field.

None
matrix Matrix

The matrix representation of the form with respect to the basis of the vector space.

None

Returns:

Type Description
BilinearForm

A new BilinearForm instance.

Raises:

Type Description
FormError

If neither the mapping nor the matrix is provided.

Source code in ablina/form.py
def __init__(
    self, 
    name: str, 
    vectorspace: VectorSpace, 
    mapping: Callable[[Any, Any], Any] | None = None, 
    matrix: Any | None = None
) -> None:
    """
    Initialize a BilinearForm instance.

    Parameters
    ----------
    name : str
        The name of the form.
    vectorspace : VectorSpace
        The vector space the form is defined on.
    mapping : callable, optional
        A function that takes two vectors in the vector space and 
        returns a scalar in the field.
    matrix : Matrix, optional
        The matrix representation of the form with respect to the 
        basis of the vector space.

    Returns
    -------
    BilinearForm
        A new BilinearForm instance.

    Raises
    ------
    FormError
        If neither the mapping nor the matrix is provided.
    """
    super().__init__(name, vectorspace, mapping, matrix, lambda c: c)

InnerProduct

Bases: SesquilinearForm

An inner product on a vector space.

An inner product is a positive definite, symmetric (for real spaces) or hermitian (for complex spaces) sesquilinear form.

Source code in ablina/form.py
class InnerProduct(SesquilinearForm):
    """
    An inner product on a vector space.

    An inner product is a positive definite, symmetric (for real spaces) 
    or hermitian (for complex spaces) sesquilinear form.
    """

    def __init__(
        self, 
        name: str, 
        vectorspace: VectorSpace, 
        mapping: Callable[[Any, Any], Any] | None = None, 
        matrix: Any | None = None
    ) -> None:
        """
        Initialize an InnerProduct instance.

        Parameters
        ----------
        name : str
            The name of the inner product.
        vectorspace : VectorSpace
            The vector space the inner product is defined on.
        mapping : callable, optional
            A function that takes two vectors in the vector space and 
            returns a scalar in the field.
        matrix : Matrix, optional
            The matrix representation of the inner product with respect 
            to the basis of the vector space.

        Returns
        -------
        InnerProduct
            A new InnerProduct instance.

        Raises
        ------
        FormError
            If neither the mapping nor the matrix is provided.
        InnerProductError
            If the form is not a valid inner product.
        """
        super().__init__(name, vectorspace, mapping, matrix)

        try:
            if not self.is_positive_definite():
                raise InnerProductError("Inner product must be positive definite.")
        except FormError as e:
            raise InnerProductError(*e.args)

        vs = self.vectorspace
        self._orthonormal_basis = self.gram_schmidt(*vs.basis)
        self._fn_orthonormal_basis = vs.fn.gram_schmidt(*vs.fn.basis)

    @property
    def orthonormal_basis(self) -> list[Any]:
        """
        list of object: An orthonormal basis for the vector space.
        """
        return self._orthonormal_basis

    def __repr__(self) -> str:
        return (
            f"InnerProduct(name={self.name!r}, "
            f"vectorspace={self.vectorspace!r}, "
            f"mapping={self.mapping!r}, "
            f"matrix={self.matrix!r})"
            )

    def __push__(self, vector: Any) -> Matrix:
        """
        Push a vector from the vector space to its F^n representation.

        Maps a vector in the abstract vector space to its coordinate 
        representation in F^n using the orthonormal basis.

        Parameters
        ----------
        vector : object
            A vector in the vector space.

        Returns
        -------
        Matrix
            The coordinate representation of `vector` in F^n.
        """
        vs = self.vectorspace
        coord_vec = vs.to_coordinate(vector, basis=self.orthonormal_basis)
        vec = vs.fn.from_coordinate(coord_vec, basis=self._fn_orthonormal_basis)
        return vec

    def __pull__(self, vector: Matrix) -> Any:
        """
        Pull a vector from F^n to the vector space.

        Maps a coordinate vector in F^n back to the abstract vector space 
        using the orthonormal basis.

        Parameters
        ----------
        vector : Matrix
            A coordinate vector in F^n.

        Returns
        -------
        object
            The corresponding vector in the vector space.
        """
        vs = self.vectorspace
        coord_vec = vs.fn.to_coordinate(vector, basis=self._fn_orthonormal_basis)
        vec = vs.from_coordinate(coord_vec, basis=self.orthonormal_basis)
        return vec

    def info(self) -> str:
        """
        A description of the inner product.

        Returns
        -------
        str
            The formatted description.
        """
        vs = self.vectorspace
        signature = f"{self} : {vs} × {vs}{vs.field}"

        lines = [
            signature,
            "-" * len(signature),
            f"Orthonormal Basis  [{', '.join(map(str, self.orthonormal_basis))}]",
            f"Matrix             {self.matrix}"
            ]
        return "\n".join(lines)

    def norm(self, vector: Any) -> Any:
        """
        The norm, or magnitude, of a vector.

        Parameters
        ----------
        vector : object
            A vector in the vector space.

        Returns
        -------
        float
            The norm of `vector`.
        """
        return sp.sqrt(self(vector, vector))

    def is_orthogonal(self, *vectors: Any) -> bool:
        """
        Check whether the vectors are pairwise orthogonal.

        Parameters
        ----------
        *vectors : object
            The vectors in the vector space.

        Returns
        -------
        bool
            True if the vectors are orthogonal, otherwise False.
        """
        for i, vec1 in enumerate(vectors, 1):
            for vec2 in vectors[i:]:
                if not sp.Integer(0).equals(self(vec1, vec2)):
                    return False
        return True

    def is_orthonormal(self, *vectors: Any) -> bool:
        """
        Check whether the vectors are orthonormal.

        Parameters
        ----------
        *vectors : object
            The vectors in the vector space.

        Returns
        -------
        bool
            True if the vectors are orthonormal, otherwise False.
        """
        if not self.is_orthogonal(*vectors):
            return False
        return all(self.norm(vec).equals(1) for vec in vectors)

    def gram_schmidt(self, *vectors: Any) -> list[Any]:
        """
        Apply the Gram-Schmidt process to a set of vectors.

        Returns an orthonormal list of vectors that span the same 
        subspace as the input vectors.

        Parameters
        ----------
        *vectors : object
            The vectors in the vector space.

        Returns
        -------
        list
            An orthonormal list of vectors.

        Raises
        ------
        ValueError
            If the provided vectors are not linearly independent.
        """
        vs = self.vectorspace
        if not vs.is_independent(*vectors):
            raise ValueError("Vectors must be linearly independent.")

        orthonormal_vecs = []
        for v in vectors:
            for q in orthonormal_vecs:
                factor = self.mapping(v, q)
                proj = vs.mul(factor, q)
                v = vs.add(v, vs.additive_inv(proj))
            unit_v = vs.mul(1 / self.norm(v), v)
            orthonormal_vecs.append(unit_v)
        return orthonormal_vecs

    def ortho_projection(self, vector: Any, subspace: VectorSpace) -> Any:
        """
        The orthogonal projection of a vector.

        Parameters
        ----------
        vector : object
            The vector to project.
        subspace : VectorSpace
            The subspace to project onto.

        Returns
        -------
        object
            The orthogonal projection of `vector` onto `subspace`.
        """
        vs = self.vectorspace
        if vector not in vs:
            raise TypeError("Vector must be an element of the vector space.")
        if not vs.is_subspace(subspace):
            raise TypeError("Subspace must be a subspace of the vector space.")

        fn_vec = self.__push__(vector)
        proj = vs.fn.ortho_projection(fn_vec, subspace.fn)
        return self.__pull__(proj)

    def ortho_complement(self, subspace: VectorSpace) -> VectorSpace:
        """
        The orthogonal complement of a vector space.

        Parameters
        ----------
        subspace : VectorSpace
            The subspace to take the orthogonal complement of.

        Returns
        -------
        VectorSpace
            The orthogonal complement of `subspace` in ``self.vectorspace``.
        """
        vs = self.vectorspace
        if not vs.is_subspace(subspace):
            raise TypeError("Subspace must be a subspace of the vector space.")

        name = f"perp({subspace})"
        fn_basis = [self.__push__(vec) for vec in subspace.basis]
        fn = vs.fn.span(*fn_basis)
        comp = vs.fn.ortho_complement(fn)
        basis = [self.__pull__(vec) for vec in comp.basis]
        return vs.span(name, *basis)

__init__(name, vectorspace, mapping=None, matrix=None)

Initialize an InnerProduct instance.

Parameters:

Name Type Description Default
name str

The name of the inner product.

required
vectorspace VectorSpace

The vector space the inner product is defined on.

required
mapping callable

A function that takes two vectors in the vector space and returns a scalar in the field.

None
matrix Matrix

The matrix representation of the inner product with respect to the basis of the vector space.

None

Returns:

Type Description
InnerProduct

A new InnerProduct instance.

Raises:

Type Description
FormError

If neither the mapping nor the matrix is provided.

InnerProductError

If the form is not a valid inner product.

Source code in ablina/form.py
def __init__(
    self, 
    name: str, 
    vectorspace: VectorSpace, 
    mapping: Callable[[Any, Any], Any] | None = None, 
    matrix: Any | None = None
) -> None:
    """
    Initialize an InnerProduct instance.

    Parameters
    ----------
    name : str
        The name of the inner product.
    vectorspace : VectorSpace
        The vector space the inner product is defined on.
    mapping : callable, optional
        A function that takes two vectors in the vector space and 
        returns a scalar in the field.
    matrix : Matrix, optional
        The matrix representation of the inner product with respect 
        to the basis of the vector space.

    Returns
    -------
    InnerProduct
        A new InnerProduct instance.

    Raises
    ------
    FormError
        If neither the mapping nor the matrix is provided.
    InnerProductError
        If the form is not a valid inner product.
    """
    super().__init__(name, vectorspace, mapping, matrix)

    try:
        if not self.is_positive_definite():
            raise InnerProductError("Inner product must be positive definite.")
    except FormError as e:
        raise InnerProductError(*e.args)

    vs = self.vectorspace
    self._orthonormal_basis = self.gram_schmidt(*vs.basis)
    self._fn_orthonormal_basis = vs.fn.gram_schmidt(*vs.fn.basis)

orthonormal_basis property

list of object: An orthonormal basis for the vector space.

__push__(vector)

Push a vector from the vector space to its F^n representation.

Maps a vector in the abstract vector space to its coordinate representation in F^n using the orthonormal basis.

Parameters:

Name Type Description Default
vector object

A vector in the vector space.

required

Returns:

Type Description
Matrix

The coordinate representation of vector in F^n.

Source code in ablina/form.py
def __push__(self, vector: Any) -> Matrix:
    """
    Push a vector from the vector space to its F^n representation.

    Maps a vector in the abstract vector space to its coordinate 
    representation in F^n using the orthonormal basis.

    Parameters
    ----------
    vector : object
        A vector in the vector space.

    Returns
    -------
    Matrix
        The coordinate representation of `vector` in F^n.
    """
    vs = self.vectorspace
    coord_vec = vs.to_coordinate(vector, basis=self.orthonormal_basis)
    vec = vs.fn.from_coordinate(coord_vec, basis=self._fn_orthonormal_basis)
    return vec

__pull__(vector)

Pull a vector from F^n to the vector space.

Maps a coordinate vector in F^n back to the abstract vector space using the orthonormal basis.

Parameters:

Name Type Description Default
vector Matrix

A coordinate vector in F^n.

required

Returns:

Type Description
object

The corresponding vector in the vector space.

Source code in ablina/form.py
def __pull__(self, vector: Matrix) -> Any:
    """
    Pull a vector from F^n to the vector space.

    Maps a coordinate vector in F^n back to the abstract vector space 
    using the orthonormal basis.

    Parameters
    ----------
    vector : Matrix
        A coordinate vector in F^n.

    Returns
    -------
    object
        The corresponding vector in the vector space.
    """
    vs = self.vectorspace
    coord_vec = vs.fn.to_coordinate(vector, basis=self._fn_orthonormal_basis)
    vec = vs.from_coordinate(coord_vec, basis=self.orthonormal_basis)
    return vec

info()

A description of the inner product.

Returns:

Type Description
str

The formatted description.

Source code in ablina/form.py
def info(self) -> str:
    """
    A description of the inner product.

    Returns
    -------
    str
        The formatted description.
    """
    vs = self.vectorspace
    signature = f"{self} : {vs} × {vs}{vs.field}"

    lines = [
        signature,
        "-" * len(signature),
        f"Orthonormal Basis  [{', '.join(map(str, self.orthonormal_basis))}]",
        f"Matrix             {self.matrix}"
        ]
    return "\n".join(lines)

norm(vector)

The norm, or magnitude, of a vector.

Parameters:

Name Type Description Default
vector object

A vector in the vector space.

required

Returns:

Type Description
float

The norm of vector.

Source code in ablina/form.py
def norm(self, vector: Any) -> Any:
    """
    The norm, or magnitude, of a vector.

    Parameters
    ----------
    vector : object
        A vector in the vector space.

    Returns
    -------
    float
        The norm of `vector`.
    """
    return sp.sqrt(self(vector, vector))

is_orthogonal(*vectors)

Check whether the vectors are pairwise orthogonal.

Parameters:

Name Type Description Default
*vectors object

The vectors in the vector space.

()

Returns:

Type Description
bool

True if the vectors are orthogonal, otherwise False.

Source code in ablina/form.py
def is_orthogonal(self, *vectors: Any) -> bool:
    """
    Check whether the vectors are pairwise orthogonal.

    Parameters
    ----------
    *vectors : object
        The vectors in the vector space.

    Returns
    -------
    bool
        True if the vectors are orthogonal, otherwise False.
    """
    for i, vec1 in enumerate(vectors, 1):
        for vec2 in vectors[i:]:
            if not sp.Integer(0).equals(self(vec1, vec2)):
                return False
    return True

is_orthonormal(*vectors)

Check whether the vectors are orthonormal.

Parameters:

Name Type Description Default
*vectors object

The vectors in the vector space.

()

Returns:

Type Description
bool

True if the vectors are orthonormal, otherwise False.

Source code in ablina/form.py
def is_orthonormal(self, *vectors: Any) -> bool:
    """
    Check whether the vectors are orthonormal.

    Parameters
    ----------
    *vectors : object
        The vectors in the vector space.

    Returns
    -------
    bool
        True if the vectors are orthonormal, otherwise False.
    """
    if not self.is_orthogonal(*vectors):
        return False
    return all(self.norm(vec).equals(1) for vec in vectors)

gram_schmidt(*vectors)

Apply the Gram-Schmidt process to a set of vectors.

Returns an orthonormal list of vectors that span the same subspace as the input vectors.

Parameters:

Name Type Description Default
*vectors object

The vectors in the vector space.

()

Returns:

Type Description
list

An orthonormal list of vectors.

Raises:

Type Description
ValueError

If the provided vectors are not linearly independent.

Source code in ablina/form.py
def gram_schmidt(self, *vectors: Any) -> list[Any]:
    """
    Apply the Gram-Schmidt process to a set of vectors.

    Returns an orthonormal list of vectors that span the same 
    subspace as the input vectors.

    Parameters
    ----------
    *vectors : object
        The vectors in the vector space.

    Returns
    -------
    list
        An orthonormal list of vectors.

    Raises
    ------
    ValueError
        If the provided vectors are not linearly independent.
    """
    vs = self.vectorspace
    if not vs.is_independent(*vectors):
        raise ValueError("Vectors must be linearly independent.")

    orthonormal_vecs = []
    for v in vectors:
        for q in orthonormal_vecs:
            factor = self.mapping(v, q)
            proj = vs.mul(factor, q)
            v = vs.add(v, vs.additive_inv(proj))
        unit_v = vs.mul(1 / self.norm(v), v)
        orthonormal_vecs.append(unit_v)
    return orthonormal_vecs

ortho_projection(vector, subspace)

The orthogonal projection of a vector.

Parameters:

Name Type Description Default
vector object

The vector to project.

required
subspace VectorSpace

The subspace to project onto.

required

Returns:

Type Description
object

The orthogonal projection of vector onto subspace.

Source code in ablina/form.py
def ortho_projection(self, vector: Any, subspace: VectorSpace) -> Any:
    """
    The orthogonal projection of a vector.

    Parameters
    ----------
    vector : object
        The vector to project.
    subspace : VectorSpace
        The subspace to project onto.

    Returns
    -------
    object
        The orthogonal projection of `vector` onto `subspace`.
    """
    vs = self.vectorspace
    if vector not in vs:
        raise TypeError("Vector must be an element of the vector space.")
    if not vs.is_subspace(subspace):
        raise TypeError("Subspace must be a subspace of the vector space.")

    fn_vec = self.__push__(vector)
    proj = vs.fn.ortho_projection(fn_vec, subspace.fn)
    return self.__pull__(proj)

ortho_complement(subspace)

The orthogonal complement of a vector space.

Parameters:

Name Type Description Default
subspace VectorSpace

The subspace to take the orthogonal complement of.

required

Returns:

Type Description
VectorSpace

The orthogonal complement of subspace in self.vectorspace.

Source code in ablina/form.py
def ortho_complement(self, subspace: VectorSpace) -> VectorSpace:
    """
    The orthogonal complement of a vector space.

    Parameters
    ----------
    subspace : VectorSpace
        The subspace to take the orthogonal complement of.

    Returns
    -------
    VectorSpace
        The orthogonal complement of `subspace` in ``self.vectorspace``.
    """
    vs = self.vectorspace
    if not vs.is_subspace(subspace):
        raise TypeError("Subspace must be a subspace of the vector space.")

    name = f"perp({subspace})"
    fn_basis = [self.__push__(vec) for vec in subspace.basis]
    fn = vs.fn.span(*fn_basis)
    comp = vs.fn.ortho_complement(fn)
    basis = [self.__pull__(vec) for vec in comp.basis]
    return vs.span(name, *basis)

QuadraticForm

A quadratic form on a vector space.

A quadratic form q is a function that takes a vector and returns a scalar, satisfying the following properties:

  • q(cv) = c^2 q(v) for all scalars c and vectors v
  • q(u + v) - q(u) - q(v) is a bilinear form
Source code in ablina/form.py
 865
 866
 867
 868
 869
 870
 871
 872
 873
 874
 875
 876
 877
 878
 879
 880
 881
 882
 883
 884
 885
 886
 887
 888
 889
 890
 891
 892
 893
 894
 895
 896
 897
 898
 899
 900
 901
 902
 903
 904
 905
 906
 907
 908
 909
 910
 911
 912
 913
 914
 915
 916
 917
 918
 919
 920
 921
 922
 923
 924
 925
 926
 927
 928
 929
 930
 931
 932
 933
 934
 935
 936
 937
 938
 939
 940
 941
 942
 943
 944
 945
 946
 947
 948
 949
 950
 951
 952
 953
 954
 955
 956
 957
 958
 959
 960
 961
 962
 963
 964
 965
 966
 967
 968
 969
 970
 971
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
class QuadraticForm:
    """
    A quadratic form on a vector space.

    A quadratic form `q` is a function that takes a vector and returns a 
    scalar, satisfying the following properties:

    - `q(cv) = c^2 q(v)` for all scalars `c` and vectors `v`
    - `q(u + v) - q(u) - q(v)` is a bilinear form
    """

    def __init__(
        self, 
        name: str, 
        vectorspace: VectorSpace, 
        mapping: Callable[[Any], Any] | None = None, 
        matrix: Any | None = None
    ) -> None:
        """
        Initialize a QuadraticForm instance.

        Parameters
        ----------
        name : str
            The name of the quadratic form.
        vectorspace : VectorSpace
            The vector space the quadratic form is defined on.
        mapping : callable, optional
            A function that takes a vector in the vector space and 
            returns a scalar in the field.
        matrix : Matrix, optional
            The matrix representation of the quadratic form with respect 
            to the basis of the vector space.

        Returns
        -------
        QuadraticForm
            A new QuadraticForm instance.

        Raises
        ------
        FormError
            If neither the mapping nor the matrix is provided.
        """
        if not isinstance(vectorspace, VectorSpace):
            raise TypeError("vectorspace must be of type VectorSpace.")

        matrix = QuadraticForm._to_matrix(vectorspace, mapping, matrix)
        mapping = QuadraticForm._to_mapping(vectorspace, mapping, matrix)

        self.name = name
        self._vectorspace = vectorspace
        self._mapping = mapping
        self._matrix = matrix

    @staticmethod
    def _to_matrix(
        vectorspace: VectorSpace, 
        mapping: Callable[[Any], Any] | None, 
        matrix: Any | None
    ) -> Matrix:
        if matrix is not None:
            return QuadraticForm._validate_matrix(vectorspace, matrix)
        if mapping is None:
            raise FormError("Either a matrix or mapping must be provided.")
        if not of_arity(mapping, 1):
            raise TypeError("Mapping must be a callable of arity 1.")

        basis = vectorspace.basis
        n = len(basis)
        mat = M.zeros(n, n)

        for i in range(n):
            mat[i, i] = mapping(basis[i])

        for i in range(n):
            for j in range(i + 1, n):
                u, v = basis[i], basis[j]
                u_plus_v = vectorspace.add(u, v)
                value = (mapping(u_plus_v) - mapping(u) - mapping(v)) / 2
                mat[i, j], mat[j, i] = value, value

        return mat

    @staticmethod
    def _to_mapping(
        vectorspace: VectorSpace, 
        mapping: Callable[[Any], Any] | None, 
        matrix: Matrix
    ) -> Callable[[Any], Any]:
        if mapping is not None:
            return mapping
        to_coord = vectorspace.to_coordinate
        return lambda v: (to_coord(v).T @ matrix @ to_coord(v))[0]

    @staticmethod
    def _validate_matrix(vectorspace: VectorSpace, matrix: Any) -> Matrix:
        mat = M(matrix)
        if not (mat.is_square and mat.rows == vectorspace.dim):
            raise ValueError("Matrix has invalid shape.")
        if not all(i in vectorspace.field for i in mat):
            raise ValueError("Matrix entries must be elements of the field.")
        return (mat + mat.T) / 2

    @property
    def vectorspace(self) -> VectorSpace:
        """
        VectorSpace: The vector space the quadratic form is defined on.
        """
        return self._vectorspace

    @property
    def mapping(self) -> Callable[[Any], Any]:
        """
        callable: The function that maps vectors to scalars.
        """
        return self._mapping

    @property
    def matrix(self) -> Matrix:
        """
        Matrix: The matrix representation of the quadratic form.
        """
        return self._matrix

    @property
    def polarization(self) -> BilinearForm:
        """
        BilinearForm: The associated bilinear form.

        Returns the symmetric bilinear form `b` such that `q(v) = b(v, v)` 
        for all vectors `v`.
        """
        name = f"b_{self.name}"
        return BilinearForm(name, self.vectorspace, matrix=self.matrix)

    def __repr__(self) -> str:
        return (
            f"QuadraticForm(name={self.name!r}, "
            f"vectorspace={self.vectorspace!r}, "
            f"mapping={self.mapping!r}, "
            f"matrix={self.matrix!r})"
            )

    def __str__(self) -> str:
        return self.name

    def __eq__(self, form2: Any) -> bool:
        """
        Check for equality of two quadratic forms.

        Parameters
        ----------
        form2 : QuadraticForm
            The form to compare with.

        Returns
        -------
        bool
            True if both forms are equal, otherwise False.
        """
        if not isinstance(form2, QuadraticForm):
            return False
        return (
            self.vectorspace == form2.vectorspace 
            and self.matrix.equals(form2.matrix)
            )

    def __call__(self, vector: Any) -> Any:
        """
        Apply the quadratic form to a vector.

        Parameters
        ----------
        vector : object
            The vector to map.

        Returns
        -------
        object
            The scalar that `vector` maps to.

        Raises
        ------
        TypeError
            If `vector` is not an element of the vector space.
        """
        vs = self.vectorspace
        if vector not in vs:
            raise TypeError("Vector must be an element of the vector space.")
        return self.mapping(vector)

    def info(self) -> str:
        """
        A description of the quadratic form.

        Returns
        -------
        str
            The formatted description.
        """
        vs = self.vectorspace
        signature = f"{self} : {vs}{vs.field}"

        lines = [
            signature,
            "-" * len(signature),
            f"Matrix  {self.matrix}"
            ]
        return "\n".join(lines)

    def inertia(self) -> tuple[int, int, int]:
        """
        Compute the inertia of the quadratic form.

        Returns a tuple (p, m, z) where:

        - p is the number of positive eigenvalues
        - m is the number of negative eigenvalues
        - z is the number of zero eigenvalues

        Returns
        -------
        tuple of (int, int, int)
            The inertia (p, m, z) of the quadratic form.

        Raises
        ------
        FormError
            If the form is not defined on a real vector space.
        """
        self._validate_form()
        tol = 1e-8
        eigenvals = self.matrix.evalf().eigenvals().items()

        p = sum(m for val, m in eigenvals if val >= tol)
        m = sum(m for val, m in eigenvals if val <= -tol)
        z = sum(m for val, m in eigenvals if abs(val) < tol)
        return p, m, z

    def signature(self) -> int:
        """
        Compute the signature of the quadratic form.

        The signature is the difference between the number of positive 
        and negative eigenvalues.

        Returns
        -------
        int
            The signature of the quadratic form.

        Raises
        ------
        FormError
            If the form is not defined on a real vector space.
        """
        p, m, _ = self.inertia()
        return p - m

    def is_degenerate(self) -> bool | None:
        """
        Check whether the quadratic form is degenerate.

        A quadratic form is degenerate if its associated bilinear form 
        is degenerate, i.e., if the matrix is not invertible.

        Returns
        -------
        bool
            True if `self` is degenerate, otherwise False.
        """
        is_inv = is_invertible(self.matrix)
        return None if is_inv is None else not is_inv

    def is_positive_definite(self) -> bool | None:
        """
        Check whether the quadratic form is positive definite.

        This method checks whether `q(v) > 0` for all `v ≠ 0`.

        Returns
        -------
        bool
            True if `self` is positive definite, otherwise False.

        Raises
        ------
        FormError
            If the form is not defined on a real vector space.

        See Also
        --------
        QuadraticForm.is_positive_semidefinite
        """
        self._validate_form()
        return self.matrix.is_positive_definite

    def is_negative_definite(self) -> bool | None:
        """
        Check whether the quadratic form is negative definite.

        This method checks whether `q(v) < 0` for all `v ≠ 0`.

        Returns
        -------
        bool
            True if `self` is negative definite, otherwise False.

        Raises
        ------
        FormError
            If the form is not defined on a real vector space.

        See Also
        --------
        QuadraticForm.is_negative_semidefinite
        """
        self._validate_form()
        return self.matrix.is_negative_definite

    def is_positive_semidefinite(self) -> bool | None:
        """
        Check whether the quadratic form is positive semidefinite.

        This method checks whether `q(v) ≥ 0` for all `v`.

        Returns
        -------
        bool
            True if `self` is positive semidefinite, otherwise False.

        Raises
        ------
        FormError
            If the form is not defined on a real vector space.

        See Also
        --------
        QuadraticForm.is_positive_definite
        """
        self._validate_form()
        return self.matrix.is_positive_semidefinite

    def is_negative_semidefinite(self) -> bool | None:
        """
        Check whether the quadratic form is negative semidefinite.

        This method checks whether `q(v) ≤ 0` for all `v`.

        Returns
        -------
        bool
            True if `self` is negative semidefinite, otherwise False.

        Raises
        ------
        FormError
            If the form is not defined on a real vector space.

        See Also
        --------
        QuadraticForm.is_negative_definite
        """
        self._validate_form()
        return self.matrix.is_negative_semidefinite

    def is_indefinite(self) -> bool | None:
        """
        Check whether the quadratic form is indefinite.

        This method checks whether `q(u) > 0` and `q(v) < 0` for some 
        `u` and `v`.

        Returns
        -------
        bool
            True if `self` is indefinite, otherwise False.

        Raises
        ------
        FormError
            If the form is not defined on a real vector space.
        """
        self._validate_form()
        return self.matrix.is_indefinite

    def _validate_form(self) -> None:
        if self.vectorspace.field is not R:
            raise FormError("Quadratic form must be defined on a real vector space.")

__init__(name, vectorspace, mapping=None, matrix=None)

Initialize a QuadraticForm instance.

Parameters:

Name Type Description Default
name str

The name of the quadratic form.

required
vectorspace VectorSpace

The vector space the quadratic form is defined on.

required
mapping callable

A function that takes a vector in the vector space and returns a scalar in the field.

None
matrix Matrix

The matrix representation of the quadratic form with respect to the basis of the vector space.

None

Returns:

Type Description
QuadraticForm

A new QuadraticForm instance.

Raises:

Type Description
FormError

If neither the mapping nor the matrix is provided.

Source code in ablina/form.py
def __init__(
    self, 
    name: str, 
    vectorspace: VectorSpace, 
    mapping: Callable[[Any], Any] | None = None, 
    matrix: Any | None = None
) -> None:
    """
    Initialize a QuadraticForm instance.

    Parameters
    ----------
    name : str
        The name of the quadratic form.
    vectorspace : VectorSpace
        The vector space the quadratic form is defined on.
    mapping : callable, optional
        A function that takes a vector in the vector space and 
        returns a scalar in the field.
    matrix : Matrix, optional
        The matrix representation of the quadratic form with respect 
        to the basis of the vector space.

    Returns
    -------
    QuadraticForm
        A new QuadraticForm instance.

    Raises
    ------
    FormError
        If neither the mapping nor the matrix is provided.
    """
    if not isinstance(vectorspace, VectorSpace):
        raise TypeError("vectorspace must be of type VectorSpace.")

    matrix = QuadraticForm._to_matrix(vectorspace, mapping, matrix)
    mapping = QuadraticForm._to_mapping(vectorspace, mapping, matrix)

    self.name = name
    self._vectorspace = vectorspace
    self._mapping = mapping
    self._matrix = matrix

vectorspace property

VectorSpace: The vector space the quadratic form is defined on.

mapping property

callable: The function that maps vectors to scalars.

matrix property

Matrix: The matrix representation of the quadratic form.

polarization property

BilinearForm: The associated bilinear form.

Returns the symmetric bilinear form b such that q(v) = b(v, v) for all vectors v.

__eq__(form2)

Check for equality of two quadratic forms.

Parameters:

Name Type Description Default
form2 QuadraticForm

The form to compare with.

required

Returns:

Type Description
bool

True if both forms are equal, otherwise False.

Source code in ablina/form.py
def __eq__(self, form2: Any) -> bool:
    """
    Check for equality of two quadratic forms.

    Parameters
    ----------
    form2 : QuadraticForm
        The form to compare with.

    Returns
    -------
    bool
        True if both forms are equal, otherwise False.
    """
    if not isinstance(form2, QuadraticForm):
        return False
    return (
        self.vectorspace == form2.vectorspace 
        and self.matrix.equals(form2.matrix)
        )

__call__(vector)

Apply the quadratic form to a vector.

Parameters:

Name Type Description Default
vector object

The vector to map.

required

Returns:

Type Description
object

The scalar that vector maps to.

Raises:

Type Description
TypeError

If vector is not an element of the vector space.

Source code in ablina/form.py
def __call__(self, vector: Any) -> Any:
    """
    Apply the quadratic form to a vector.

    Parameters
    ----------
    vector : object
        The vector to map.

    Returns
    -------
    object
        The scalar that `vector` maps to.

    Raises
    ------
    TypeError
        If `vector` is not an element of the vector space.
    """
    vs = self.vectorspace
    if vector not in vs:
        raise TypeError("Vector must be an element of the vector space.")
    return self.mapping(vector)

info()

A description of the quadratic form.

Returns:

Type Description
str

The formatted description.

Source code in ablina/form.py
def info(self) -> str:
    """
    A description of the quadratic form.

    Returns
    -------
    str
        The formatted description.
    """
    vs = self.vectorspace
    signature = f"{self} : {vs}{vs.field}"

    lines = [
        signature,
        "-" * len(signature),
        f"Matrix  {self.matrix}"
        ]
    return "\n".join(lines)

inertia()

Compute the inertia of the quadratic form.

Returns a tuple (p, m, z) where:

  • p is the number of positive eigenvalues
  • m is the number of negative eigenvalues
  • z is the number of zero eigenvalues

Returns:

Type Description
tuple of (int, int, int)

The inertia (p, m, z) of the quadratic form.

Raises:

Type Description
FormError

If the form is not defined on a real vector space.

Source code in ablina/form.py
def inertia(self) -> tuple[int, int, int]:
    """
    Compute the inertia of the quadratic form.

    Returns a tuple (p, m, z) where:

    - p is the number of positive eigenvalues
    - m is the number of negative eigenvalues
    - z is the number of zero eigenvalues

    Returns
    -------
    tuple of (int, int, int)
        The inertia (p, m, z) of the quadratic form.

    Raises
    ------
    FormError
        If the form is not defined on a real vector space.
    """
    self._validate_form()
    tol = 1e-8
    eigenvals = self.matrix.evalf().eigenvals().items()

    p = sum(m for val, m in eigenvals if val >= tol)
    m = sum(m for val, m in eigenvals if val <= -tol)
    z = sum(m for val, m in eigenvals if abs(val) < tol)
    return p, m, z

signature()

Compute the signature of the quadratic form.

The signature is the difference between the number of positive and negative eigenvalues.

Returns:

Type Description
int

The signature of the quadratic form.

Raises:

Type Description
FormError

If the form is not defined on a real vector space.

Source code in ablina/form.py
def signature(self) -> int:
    """
    Compute the signature of the quadratic form.

    The signature is the difference between the number of positive 
    and negative eigenvalues.

    Returns
    -------
    int
        The signature of the quadratic form.

    Raises
    ------
    FormError
        If the form is not defined on a real vector space.
    """
    p, m, _ = self.inertia()
    return p - m

is_degenerate()

Check whether the quadratic form is degenerate.

A quadratic form is degenerate if its associated bilinear form is degenerate, i.e., if the matrix is not invertible.

Returns:

Type Description
bool

True if self is degenerate, otherwise False.

Source code in ablina/form.py
def is_degenerate(self) -> bool | None:
    """
    Check whether the quadratic form is degenerate.

    A quadratic form is degenerate if its associated bilinear form 
    is degenerate, i.e., if the matrix is not invertible.

    Returns
    -------
    bool
        True if `self` is degenerate, otherwise False.
    """
    is_inv = is_invertible(self.matrix)
    return None if is_inv is None else not is_inv

is_positive_definite()

Check whether the quadratic form is positive definite.

This method checks whether q(v) > 0 for all v ≠ 0.

Returns:

Type Description
bool

True if self is positive definite, otherwise False.

Raises:

Type Description
FormError

If the form is not defined on a real vector space.

See Also

QuadraticForm.is_positive_semidefinite

Source code in ablina/form.py
def is_positive_definite(self) -> bool | None:
    """
    Check whether the quadratic form is positive definite.

    This method checks whether `q(v) > 0` for all `v ≠ 0`.

    Returns
    -------
    bool
        True if `self` is positive definite, otherwise False.

    Raises
    ------
    FormError
        If the form is not defined on a real vector space.

    See Also
    --------
    QuadraticForm.is_positive_semidefinite
    """
    self._validate_form()
    return self.matrix.is_positive_definite

is_negative_definite()

Check whether the quadratic form is negative definite.

This method checks whether q(v) < 0 for all v ≠ 0.

Returns:

Type Description
bool

True if self is negative definite, otherwise False.

Raises:

Type Description
FormError

If the form is not defined on a real vector space.

See Also

QuadraticForm.is_negative_semidefinite

Source code in ablina/form.py
def is_negative_definite(self) -> bool | None:
    """
    Check whether the quadratic form is negative definite.

    This method checks whether `q(v) < 0` for all `v ≠ 0`.

    Returns
    -------
    bool
        True if `self` is negative definite, otherwise False.

    Raises
    ------
    FormError
        If the form is not defined on a real vector space.

    See Also
    --------
    QuadraticForm.is_negative_semidefinite
    """
    self._validate_form()
    return self.matrix.is_negative_definite

is_positive_semidefinite()

Check whether the quadratic form is positive semidefinite.

This method checks whether q(v) ≥ 0 for all v.

Returns:

Type Description
bool

True if self is positive semidefinite, otherwise False.

Raises:

Type Description
FormError

If the form is not defined on a real vector space.

See Also

QuadraticForm.is_positive_definite

Source code in ablina/form.py
def is_positive_semidefinite(self) -> bool | None:
    """
    Check whether the quadratic form is positive semidefinite.

    This method checks whether `q(v) ≥ 0` for all `v`.

    Returns
    -------
    bool
        True if `self` is positive semidefinite, otherwise False.

    Raises
    ------
    FormError
        If the form is not defined on a real vector space.

    See Also
    --------
    QuadraticForm.is_positive_definite
    """
    self._validate_form()
    return self.matrix.is_positive_semidefinite

is_negative_semidefinite()

Check whether the quadratic form is negative semidefinite.

This method checks whether q(v) ≤ 0 for all v.

Returns:

Type Description
bool

True if self is negative semidefinite, otherwise False.

Raises:

Type Description
FormError

If the form is not defined on a real vector space.

See Also

QuadraticForm.is_negative_definite

Source code in ablina/form.py
def is_negative_semidefinite(self) -> bool | None:
    """
    Check whether the quadratic form is negative semidefinite.

    This method checks whether `q(v) ≤ 0` for all `v`.

    Returns
    -------
    bool
        True if `self` is negative semidefinite, otherwise False.

    Raises
    ------
    FormError
        If the form is not defined on a real vector space.

    See Also
    --------
    QuadraticForm.is_negative_definite
    """
    self._validate_form()
    return self.matrix.is_negative_semidefinite

is_indefinite()

Check whether the quadratic form is indefinite.

This method checks whether q(u) > 0 and q(v) < 0 for some u and v.

Returns:

Type Description
bool

True if self is indefinite, otherwise False.

Raises:

Type Description
FormError

If the form is not defined on a real vector space.

Source code in ablina/form.py
def is_indefinite(self) -> bool | None:
    """
    Check whether the quadratic form is indefinite.

    This method checks whether `q(u) > 0` and `q(v) < 0` for some 
    `u` and `v`.

    Returns
    -------
    bool
        True if `self` is indefinite, otherwise False.

    Raises
    ------
    FormError
        If the form is not defined on a real vector space.
    """
    self._validate_form()
    return self.matrix.is_indefinite