Skip to content

Linear maps

ablina.linearmap

A module for working with linear maps between vector spaces.

LinearMap

A linear map between vector spaces.

A linear map T is a function from one vector space to another, satisfying the following properties:

  • T(u + v) = T(u) + T(v) for all vectors u and v
  • T(av) = a T(v) for all scalars a and vectors v
Source code in ablina/linearmap.py
 21
 22
 23
 24
 25
 26
 27
 28
 29
 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
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
class LinearMap:
    """
    A linear map between vector spaces.

    A linear map `T` is a function from one vector space to another, 
    satisfying the following properties:

    - `T(u + v) = T(u) + T(v)` for all vectors `u` and `v`
    - `T(av) = a T(v)` for all scalars `a` and vectors `v`
    """

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

        Parameters
        ----------
        name : str
            The name of the linear map.
        domain : VectorSpace
            The domain of the linear map.
        codomain : VectorSpace
            The codomain of the linear map.
        mapping : callable, optional
            A function that takes a vector in the domain and returns a 
            vector in the codomain.
        matrix : Matrix, optional
            The matrix representation of the linear map with respect to 
            the bases of the domain and codomain.

        Returns
        -------
        LinearMap
            A new LinearMap instance.

        Raises
        ------
        LinearMapError
            If neither the mapping nor the matrix is provided.
        LinearMapError
            If the fields of the domain and codomain are not the same.
        """
        if not isinstance(domain, VectorSpace):
            raise TypeError("Domain must be a VectorSpace.")
        if not isinstance(codomain, VectorSpace):
            raise TypeError("Codomain must be a VectorSpace.")
        if domain.field is not codomain.field:
            raise LinearMapError(
                "Domain and codomain must be vector spaces over the same field."
                )

        matrix = LinearMap._to_matrix(domain, codomain, mapping, matrix)
        mapping = LinearMap._to_mapping(domain, codomain, mapping, matrix)

        self.name = name
        self._domain = domain
        self._codomain = codomain
        self._mapping = mapping
        self._matrix = matrix

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

        mat = []
        for vec in domain.basis:
            mapped_vec = mapping(vec)
            coord_vec = codomain.to_coordinate(mapped_vec)
            mat.append(coord_vec)
        return M.hstack(*mat)

    @staticmethod
    def _to_mapping(
        domain: VectorSpace, 
        codomain: VectorSpace, 
        mapping: Callable[[Any], Any] | None, 
        matrix: Matrix
    ) -> Callable[[Any], Any]:
        if mapping is not None:
            return mapping
        to_coord = domain.to_coordinate
        from_coord = codomain.from_coordinate
        return lambda vec: from_coord(matrix @ to_coord(vec))

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

    @property
    def field(self) -> Field:
        """
        Field: The field of the domain and codomain.
        """
        return self.domain.field

    @property
    def domain(self) -> VectorSpace:
        """
        VectorSpace: The domain of the linear map.
        """
        return self._domain

    @property
    def codomain(self) -> VectorSpace:
        """
        VectorSpace: The codomain of the linear map.
        """
        return self._codomain

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

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

    @property
    def rank(self) -> int:
        """
        int: The dimension of the image of the linear map.
        """
        return self.matrix.rank()

    @property
    def nullity(self) -> int:
        """
        int: The dimension of the kernel of the linear map.
        """
        return self.matrix.cols - self.rank

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

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

    def __eq__(self, map2: Any) -> bool:
        """
        Check for equality of two linear maps.

        Parameters
        ----------
        map2 : LinearMap
            The linear map to compare with.

        Returns
        -------
        bool
            True if both linear maps are equal, otherwise False.
        """
        if not isinstance(map2, LinearMap):
            return False
        if not (self.domain == map2.domain and self.codomain == map2.codomain):
            return False
        basis1, basis2 = map2.domain.basis, map2.codomain.basis
        mat, _, _ = LinearMap.change_of_basis(self, basis1, basis2)
        return map2.matrix.equals(mat) is True

    def __add__(self, map2: LinearMap) -> LinearMap:
        """
        The sum of two linear maps.

        Parameters
        ----------
        map2 : LinearMap
            The linear map being added.

        Returns
        -------
        LinearMap
            The sum of `self` and `map2`.

        Raises
        ------
        LinearMapError
            If the domains and codomains of `self` and `map2` are not equal.

        Examples
        --------

        >>> R3 = fn("R3", R, 3)
        >>> map1 = LinearMap("map1", R3, R3, lambda vec: 2 * vec)
        >>> map2 = LinearMap("map2", R3, R3, lambda vec: 3 * vec)
        >>> map3 = map1 + map2
        >>> map3([1, 2, 3])
        [5, 10, 15]
        """
        if not (self.domain == map2.domain and self.codomain == map2.codomain):
            raise LinearMapError("The linear maps are not compatible.")

        name = f"{self} + {map2}"
        def mapping(vec: Any) -> Any:
            vec1 = self.mapping(vec)
            vec2 = map2.mapping(vec)
            return self.codomain.add(vec1, vec2)
        mat = self.matrix + map2.matrix
        return LinearMap(name, self.domain, self.codomain, mapping, mat)

    def __mul__(self, scalar: Any) -> LinearMap:
        """
        The product of the linear map and a scalar.

        Parameters
        ----------
        scalar : object
            The scalar to multiply by.

        Returns
        -------
        LinearMap
            The product of `self` and `scalar`.

        Raises
        ------
        TypeError
            If `scalar` is not an element of the field.

        Examples
        --------

        >>> R3 = fn("R3", R, 3)
        >>> map1 = LinearMap("map1", R3, R3, lambda vec: 2 * vec)
        >>> map2 = 3 * map1
        >>> map2([1, 2, 3])
        [6, 12, 18]
        """
        if scalar not in self.field:
            raise TypeError("Scalar must be an element of the field.")

        name = f"{scalar} * {self}"
        def mapping(vec: Any) -> Any:
            return self.codomain.mul(scalar, self.mapping(vec))
        mat = scalar * self.matrix
        return LinearMap(name, self.domain, self.codomain, mapping, mat)

    def __rmul__(self, scalar: Any) -> LinearMap:
        return self.__mul__(scalar)

    def __call__(self, obj: Any | VectorSpace) -> Any | VectorSpace:
        """
        Apply the linear map to a vector or subspace.

        Parameters
        ----------
        obj : object or VectorSpace
            The vector or subspace to map.

        Returns
        -------
        object or VectorSpace
            The vector or subspace that `obj` maps to.

        Examples
        --------

        >>> R3 = fn("R3", R, 3)
        >>> map1 = LinearMap("map1", R3, R3, lambda vec: 2 * vec)
        >>> map1([1, 2, 3])
        [2, 4, 6]
        """
        if obj in self.domain:
            return self.mapping(obj)
        return self.restriction(obj).image()

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

        Returns
        -------
        str
            The formatted description.
        """
        signature = f"{self} : {self.domain}{self.codomain}"

        lines = [
            signature,
            "-" * len(signature),
            f"Field        {self.field}",
            f"Rank         {self.rank}",
            f"Nullity      {self.nullity}",
            f"Injective?   {self.is_injective()}",  
            f"Surjective?  {self.is_surjective()}",
            f"Bijective?   {self.is_bijective()}",
            f"Matrix       {self.matrix}"
            ]
        return "\n".join(lines)

    def change_of_basis(
        self, 
        domain_basis: list[Any] | None = None, 
        codomain_basis: list[Any] | None = None
    ) -> tuple[Matrix, Matrix, Matrix]:
        """
        Change the basis representation of the linear map.

        Returns the matrix representation of the linear map with respect 
        to new bases for the domain and codomain, along with the 
        change-of-basis matrices.

        Parameters
        ----------
        domain_basis : list of object, optional
            A new basis for the domain. If None, the current basis is used.
        codomain_basis : list of object, optional
            A new basis for the codomain. If None, the current basis is used.

        Returns
        -------
        tuple of (Matrix, Matrix, Matrix)
            A tuple containing the matrix representation with respect to 
            the new bases, the domain change-of-basis matrix, and the 
            codomain change-of-basis matrix.
        """
        if domain_basis is None:
            domain_basechange = M.eye(self.domain.dim)
        else:
            domain_basechange = self.domain.change_of_basis(domain_basis)
        if codomain_basis is None:
            codomain_basechange = M.eye(self.codomain.dim)
        else:
            codomain_basechange = self.codomain.change_of_basis(codomain_basis) 

        map_matrix = codomain_basechange @ self.matrix @ domain_basechange.inv()
        return map_matrix, domain_basechange, codomain_basechange

    def restriction(self, subspace: VectorSpace) -> LinearMap:
        """
        Restrict the linear map to a subspace of the domain.

        Parameters
        ----------
        subspace : VectorSpace
            A subspace of the domain.

        Returns
        -------
        LinearMap
            The restriction of `self` to `subspace`.

        Raises
        ------
        TypeError
            If `subspace` is not a subspace of the domain.
        """
        if not self.domain.is_subspace(subspace):
            raise TypeError("Subspace must be a subspace of the domain.")
        name = f"{self} | {subspace}"
        return LinearMap(name, subspace, self.codomain, self.mapping)

    def inverse(self) -> LinearMap:
        """
        The inverse of the linear map.

        Returns
        -------
        LinearMap
            The inverse of `self`.

        Raises
        ------
        LinearMapError
            If `self` is not invertible.
        """
        if not self.is_bijective():
            raise LinearMapError("Linear map is not invertible.")
        name = f"{self}^-1"
        mat = self.matrix.inv()
        return LinearMap(name, self.codomain, self.domain, matrix=mat)

    def composition(self, map2: LinearMap) -> LinearMap:
        """
        The composition of two linear maps.

        Parameters
        ----------
        map2 : LinearMap
            The linear map to compose with.

        Returns
        -------
        LinearMap
            The composition of `self` and `map2`.

        Raises
        ------
        LinearMapError
            If the domain of `self` is not equal to the codomain of `map2`.

        Examples
        --------

        >>> R3 = fn("R3", R, 3)
        >>> map1 = LinearMap("map1", R3, R3, lambda vec: 2 * vec)
        >>> map2 = LinearMap("map2", R3, R3, lambda vec: 3 * vec)
        >>> map3 = map1.composition(map2)
        >>> map3([1, 2, 3])
        [6, 12, 18]
        """
        if not isinstance(map2, LinearMap):
            raise TypeError("map2 must be of type LinearMap.")
        if self.domain != map2.codomain:
            raise LinearMapError("The linear maps are not compatible.")

        name = f"{self}{map2}"
        def mapping(vec: Any) -> Any:
            return self.mapping(map2.mapping(vec))
        mat = self.matrix @ map2.matrix
        return LinearMap(name, map2.domain, self.codomain, mapping, mat)

    def image(self) -> VectorSpace:
        """
        The image, or range, of the linear map.

        Returns
        -------
        VectorSpace
            The image of `self`.

        See Also
        --------
        LinearMap.range
        """
        name = f"im({self})"
        basis = self.matrix.columnspace()
        basis = [self.codomain.from_coordinate(vec) for vec in basis]
        return self.codomain.span(name, *basis)

    def kernel(self) -> VectorSpace:
        """
        The kernel, or null space, of the linear map.

        Returns
        -------
        VectorSpace
            The kernel of `self`.

        See Also
        --------
        LinearMap.nullspace
        """
        name = f"ker({self})"
        basis = self.matrix.nullspace()
        basis = [self.domain.from_coordinate(vec) for vec in basis]
        return self.domain.span(name, *basis)

    def adjoint(self) -> LinearMap:
        """
        The adjoint of the linear map.
        """
        raise NotImplementedError("This method is not yet implemented.")

    def pseudoinverse(self) -> LinearMap:
        """
        The pseudoinverse of the linear map.

        Returns
        -------
        LinearMap
            The pseudoinverse of `self`.
        """
        name = f"{self}^+"
        mat = self.matrix.pinv()
        return LinearMap(name, self.codomain, self.domain, matrix=mat)

    def is_injective(self) -> bool:
        """
        Check whether the linear map is injective.

        Returns
        -------
        bool
            True if the linear map is injective, otherwise False.

        See Also
        --------
        LinearMap.is_surjective, LinearMap.is_bijective
        """
        return self.matrix.cols == self.rank

    def is_surjective(self) -> bool:
        """
        Check whether the linear map is surjective.

        Returns
        -------
        bool
            True if the linear map is surjective, otherwise False.

        See Also
        --------
        LinearMap.is_injective, LinearMap.is_bijective
        """
        return self.matrix.rows == self.rank

    def is_bijective(self) -> bool:
        """
        Check whether the linear map is bijective.

        Returns
        -------
        bool
            True if the linear map is bijective, otherwise False.

        See Also
        --------
        LinearMap.is_injective, LinearMap.is_surjective
        """
        return self.is_injective() and self.is_surjective()

    # Aliases
    range = image
    """An alias for the image method."""

    nullspace = kernel
    """An alias for the kernel method."""

__init__(name, domain, codomain, mapping=None, matrix=None)

Initialize a LinearMap instance.

Parameters:

Name Type Description Default
name str

The name of the linear map.

required
domain VectorSpace

The domain of the linear map.

required
codomain VectorSpace

The codomain of the linear map.

required
mapping callable

A function that takes a vector in the domain and returns a vector in the codomain.

None
matrix Matrix

The matrix representation of the linear map with respect to the bases of the domain and codomain.

None

Returns:

Type Description
LinearMap

A new LinearMap instance.

Raises:

Type Description
LinearMapError

If neither the mapping nor the matrix is provided.

LinearMapError

If the fields of the domain and codomain are not the same.

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

    Parameters
    ----------
    name : str
        The name of the linear map.
    domain : VectorSpace
        The domain of the linear map.
    codomain : VectorSpace
        The codomain of the linear map.
    mapping : callable, optional
        A function that takes a vector in the domain and returns a 
        vector in the codomain.
    matrix : Matrix, optional
        The matrix representation of the linear map with respect to 
        the bases of the domain and codomain.

    Returns
    -------
    LinearMap
        A new LinearMap instance.

    Raises
    ------
    LinearMapError
        If neither the mapping nor the matrix is provided.
    LinearMapError
        If the fields of the domain and codomain are not the same.
    """
    if not isinstance(domain, VectorSpace):
        raise TypeError("Domain must be a VectorSpace.")
    if not isinstance(codomain, VectorSpace):
        raise TypeError("Codomain must be a VectorSpace.")
    if domain.field is not codomain.field:
        raise LinearMapError(
            "Domain and codomain must be vector spaces over the same field."
            )

    matrix = LinearMap._to_matrix(domain, codomain, mapping, matrix)
    mapping = LinearMap._to_mapping(domain, codomain, mapping, matrix)

    self.name = name
    self._domain = domain
    self._codomain = codomain
    self._mapping = mapping
    self._matrix = matrix

field property

Field: The field of the domain and codomain.

domain property

VectorSpace: The domain of the linear map.

codomain property

VectorSpace: The codomain of the linear map.

mapping property

callable: The function that maps vectors from the domain to the codomain.

matrix property

Matrix: The matrix representation of the linear map.

rank property

int: The dimension of the image of the linear map.

nullity property

int: The dimension of the kernel of the linear map.

__eq__(map2)

Check for equality of two linear maps.

Parameters:

Name Type Description Default
map2 LinearMap

The linear map to compare with.

required

Returns:

Type Description
bool

True if both linear maps are equal, otherwise False.

Source code in ablina/linearmap.py
def __eq__(self, map2: Any) -> bool:
    """
    Check for equality of two linear maps.

    Parameters
    ----------
    map2 : LinearMap
        The linear map to compare with.

    Returns
    -------
    bool
        True if both linear maps are equal, otherwise False.
    """
    if not isinstance(map2, LinearMap):
        return False
    if not (self.domain == map2.domain and self.codomain == map2.codomain):
        return False
    basis1, basis2 = map2.domain.basis, map2.codomain.basis
    mat, _, _ = LinearMap.change_of_basis(self, basis1, basis2)
    return map2.matrix.equals(mat) is True

__add__(map2)

The sum of two linear maps.

Parameters:

Name Type Description Default
map2 LinearMap

The linear map being added.

required

Returns:

Type Description
LinearMap

The sum of self and map2.

Raises:

Type Description
LinearMapError

If the domains and codomains of self and map2 are not equal.

Examples:

>>> R3 = fn("R3", R, 3)
>>> map1 = LinearMap("map1", R3, R3, lambda vec: 2 * vec)
>>> map2 = LinearMap("map2", R3, R3, lambda vec: 3 * vec)
>>> map3 = map1 + map2
>>> map3([1, 2, 3])
[5, 10, 15]
Source code in ablina/linearmap.py
def __add__(self, map2: LinearMap) -> LinearMap:
    """
    The sum of two linear maps.

    Parameters
    ----------
    map2 : LinearMap
        The linear map being added.

    Returns
    -------
    LinearMap
        The sum of `self` and `map2`.

    Raises
    ------
    LinearMapError
        If the domains and codomains of `self` and `map2` are not equal.

    Examples
    --------

    >>> R3 = fn("R3", R, 3)
    >>> map1 = LinearMap("map1", R3, R3, lambda vec: 2 * vec)
    >>> map2 = LinearMap("map2", R3, R3, lambda vec: 3 * vec)
    >>> map3 = map1 + map2
    >>> map3([1, 2, 3])
    [5, 10, 15]
    """
    if not (self.domain == map2.domain and self.codomain == map2.codomain):
        raise LinearMapError("The linear maps are not compatible.")

    name = f"{self} + {map2}"
    def mapping(vec: Any) -> Any:
        vec1 = self.mapping(vec)
        vec2 = map2.mapping(vec)
        return self.codomain.add(vec1, vec2)
    mat = self.matrix + map2.matrix
    return LinearMap(name, self.domain, self.codomain, mapping, mat)

__mul__(scalar)

The product of the linear map and a scalar.

Parameters:

Name Type Description Default
scalar object

The scalar to multiply by.

required

Returns:

Type Description
LinearMap

The product of self and scalar.

Raises:

Type Description
TypeError

If scalar is not an element of the field.

Examples:

>>> R3 = fn("R3", R, 3)
>>> map1 = LinearMap("map1", R3, R3, lambda vec: 2 * vec)
>>> map2 = 3 * map1
>>> map2([1, 2, 3])
[6, 12, 18]
Source code in ablina/linearmap.py
def __mul__(self, scalar: Any) -> LinearMap:
    """
    The product of the linear map and a scalar.

    Parameters
    ----------
    scalar : object
        The scalar to multiply by.

    Returns
    -------
    LinearMap
        The product of `self` and `scalar`.

    Raises
    ------
    TypeError
        If `scalar` is not an element of the field.

    Examples
    --------

    >>> R3 = fn("R3", R, 3)
    >>> map1 = LinearMap("map1", R3, R3, lambda vec: 2 * vec)
    >>> map2 = 3 * map1
    >>> map2([1, 2, 3])
    [6, 12, 18]
    """
    if scalar not in self.field:
        raise TypeError("Scalar must be an element of the field.")

    name = f"{scalar} * {self}"
    def mapping(vec: Any) -> Any:
        return self.codomain.mul(scalar, self.mapping(vec))
    mat = scalar * self.matrix
    return LinearMap(name, self.domain, self.codomain, mapping, mat)

__call__(obj)

Apply the linear map to a vector or subspace.

Parameters:

Name Type Description Default
obj object or VectorSpace

The vector or subspace to map.

required

Returns:

Type Description
object or VectorSpace

The vector or subspace that obj maps to.

Examples:

>>> R3 = fn("R3", R, 3)
>>> map1 = LinearMap("map1", R3, R3, lambda vec: 2 * vec)
>>> map1([1, 2, 3])
[2, 4, 6]
Source code in ablina/linearmap.py
def __call__(self, obj: Any | VectorSpace) -> Any | VectorSpace:
    """
    Apply the linear map to a vector or subspace.

    Parameters
    ----------
    obj : object or VectorSpace
        The vector or subspace to map.

    Returns
    -------
    object or VectorSpace
        The vector or subspace that `obj` maps to.

    Examples
    --------

    >>> R3 = fn("R3", R, 3)
    >>> map1 = LinearMap("map1", R3, R3, lambda vec: 2 * vec)
    >>> map1([1, 2, 3])
    [2, 4, 6]
    """
    if obj in self.domain:
        return self.mapping(obj)
    return self.restriction(obj).image()

info()

A description of the linear map.

Returns:

Type Description
str

The formatted description.

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

    Returns
    -------
    str
        The formatted description.
    """
    signature = f"{self} : {self.domain}{self.codomain}"

    lines = [
        signature,
        "-" * len(signature),
        f"Field        {self.field}",
        f"Rank         {self.rank}",
        f"Nullity      {self.nullity}",
        f"Injective?   {self.is_injective()}",  
        f"Surjective?  {self.is_surjective()}",
        f"Bijective?   {self.is_bijective()}",
        f"Matrix       {self.matrix}"
        ]
    return "\n".join(lines)

change_of_basis(domain_basis=None, codomain_basis=None)

Change the basis representation of the linear map.

Returns the matrix representation of the linear map with respect to new bases for the domain and codomain, along with the change-of-basis matrices.

Parameters:

Name Type Description Default
domain_basis list of object

A new basis for the domain. If None, the current basis is used.

None
codomain_basis list of object

A new basis for the codomain. If None, the current basis is used.

None

Returns:

Type Description
tuple of (Matrix, Matrix, Matrix)

A tuple containing the matrix representation with respect to the new bases, the domain change-of-basis matrix, and the codomain change-of-basis matrix.

Source code in ablina/linearmap.py
def change_of_basis(
    self, 
    domain_basis: list[Any] | None = None, 
    codomain_basis: list[Any] | None = None
) -> tuple[Matrix, Matrix, Matrix]:
    """
    Change the basis representation of the linear map.

    Returns the matrix representation of the linear map with respect 
    to new bases for the domain and codomain, along with the 
    change-of-basis matrices.

    Parameters
    ----------
    domain_basis : list of object, optional
        A new basis for the domain. If None, the current basis is used.
    codomain_basis : list of object, optional
        A new basis for the codomain. If None, the current basis is used.

    Returns
    -------
    tuple of (Matrix, Matrix, Matrix)
        A tuple containing the matrix representation with respect to 
        the new bases, the domain change-of-basis matrix, and the 
        codomain change-of-basis matrix.
    """
    if domain_basis is None:
        domain_basechange = M.eye(self.domain.dim)
    else:
        domain_basechange = self.domain.change_of_basis(domain_basis)
    if codomain_basis is None:
        codomain_basechange = M.eye(self.codomain.dim)
    else:
        codomain_basechange = self.codomain.change_of_basis(codomain_basis) 

    map_matrix = codomain_basechange @ self.matrix @ domain_basechange.inv()
    return map_matrix, domain_basechange, codomain_basechange

restriction(subspace)

Restrict the linear map to a subspace of the domain.

Parameters:

Name Type Description Default
subspace VectorSpace

A subspace of the domain.

required

Returns:

Type Description
LinearMap

The restriction of self to subspace.

Raises:

Type Description
TypeError

If subspace is not a subspace of the domain.

Source code in ablina/linearmap.py
def restriction(self, subspace: VectorSpace) -> LinearMap:
    """
    Restrict the linear map to a subspace of the domain.

    Parameters
    ----------
    subspace : VectorSpace
        A subspace of the domain.

    Returns
    -------
    LinearMap
        The restriction of `self` to `subspace`.

    Raises
    ------
    TypeError
        If `subspace` is not a subspace of the domain.
    """
    if not self.domain.is_subspace(subspace):
        raise TypeError("Subspace must be a subspace of the domain.")
    name = f"{self} | {subspace}"
    return LinearMap(name, subspace, self.codomain, self.mapping)

inverse()

The inverse of the linear map.

Returns:

Type Description
LinearMap

The inverse of self.

Raises:

Type Description
LinearMapError

If self is not invertible.

Source code in ablina/linearmap.py
def inverse(self) -> LinearMap:
    """
    The inverse of the linear map.

    Returns
    -------
    LinearMap
        The inverse of `self`.

    Raises
    ------
    LinearMapError
        If `self` is not invertible.
    """
    if not self.is_bijective():
        raise LinearMapError("Linear map is not invertible.")
    name = f"{self}^-1"
    mat = self.matrix.inv()
    return LinearMap(name, self.codomain, self.domain, matrix=mat)

composition(map2)

The composition of two linear maps.

Parameters:

Name Type Description Default
map2 LinearMap

The linear map to compose with.

required

Returns:

Type Description
LinearMap

The composition of self and map2.

Raises:

Type Description
LinearMapError

If the domain of self is not equal to the codomain of map2.

Examples:

>>> R3 = fn("R3", R, 3)
>>> map1 = LinearMap("map1", R3, R3, lambda vec: 2 * vec)
>>> map2 = LinearMap("map2", R3, R3, lambda vec: 3 * vec)
>>> map3 = map1.composition(map2)
>>> map3([1, 2, 3])
[6, 12, 18]
Source code in ablina/linearmap.py
def composition(self, map2: LinearMap) -> LinearMap:
    """
    The composition of two linear maps.

    Parameters
    ----------
    map2 : LinearMap
        The linear map to compose with.

    Returns
    -------
    LinearMap
        The composition of `self` and `map2`.

    Raises
    ------
    LinearMapError
        If the domain of `self` is not equal to the codomain of `map2`.

    Examples
    --------

    >>> R3 = fn("R3", R, 3)
    >>> map1 = LinearMap("map1", R3, R3, lambda vec: 2 * vec)
    >>> map2 = LinearMap("map2", R3, R3, lambda vec: 3 * vec)
    >>> map3 = map1.composition(map2)
    >>> map3([1, 2, 3])
    [6, 12, 18]
    """
    if not isinstance(map2, LinearMap):
        raise TypeError("map2 must be of type LinearMap.")
    if self.domain != map2.codomain:
        raise LinearMapError("The linear maps are not compatible.")

    name = f"{self}{map2}"
    def mapping(vec: Any) -> Any:
        return self.mapping(map2.mapping(vec))
    mat = self.matrix @ map2.matrix
    return LinearMap(name, map2.domain, self.codomain, mapping, mat)

image()

The image, or range, of the linear map.

Returns:

Type Description
VectorSpace

The image of self.

See Also

LinearMap.range

Source code in ablina/linearmap.py
def image(self) -> VectorSpace:
    """
    The image, or range, of the linear map.

    Returns
    -------
    VectorSpace
        The image of `self`.

    See Also
    --------
    LinearMap.range
    """
    name = f"im({self})"
    basis = self.matrix.columnspace()
    basis = [self.codomain.from_coordinate(vec) for vec in basis]
    return self.codomain.span(name, *basis)

kernel()

The kernel, or null space, of the linear map.

Returns:

Type Description
VectorSpace

The kernel of self.

See Also

LinearMap.nullspace

Source code in ablina/linearmap.py
def kernel(self) -> VectorSpace:
    """
    The kernel, or null space, of the linear map.

    Returns
    -------
    VectorSpace
        The kernel of `self`.

    See Also
    --------
    LinearMap.nullspace
    """
    name = f"ker({self})"
    basis = self.matrix.nullspace()
    basis = [self.domain.from_coordinate(vec) for vec in basis]
    return self.domain.span(name, *basis)

adjoint()

The adjoint of the linear map.

Source code in ablina/linearmap.py
def adjoint(self) -> LinearMap:
    """
    The adjoint of the linear map.
    """
    raise NotImplementedError("This method is not yet implemented.")

pseudoinverse()

The pseudoinverse of the linear map.

Returns:

Type Description
LinearMap

The pseudoinverse of self.

Source code in ablina/linearmap.py
def pseudoinverse(self) -> LinearMap:
    """
    The pseudoinverse of the linear map.

    Returns
    -------
    LinearMap
        The pseudoinverse of `self`.
    """
    name = f"{self}^+"
    mat = self.matrix.pinv()
    return LinearMap(name, self.codomain, self.domain, matrix=mat)

is_injective()

Check whether the linear map is injective.

Returns:

Type Description
bool

True if the linear map is injective, otherwise False.

See Also

LinearMap.is_surjective, LinearMap.is_bijective

Source code in ablina/linearmap.py
def is_injective(self) -> bool:
    """
    Check whether the linear map is injective.

    Returns
    -------
    bool
        True if the linear map is injective, otherwise False.

    See Also
    --------
    LinearMap.is_surjective, LinearMap.is_bijective
    """
    return self.matrix.cols == self.rank

is_surjective()

Check whether the linear map is surjective.

Returns:

Type Description
bool

True if the linear map is surjective, otherwise False.

See Also

LinearMap.is_injective, LinearMap.is_bijective

Source code in ablina/linearmap.py
def is_surjective(self) -> bool:
    """
    Check whether the linear map is surjective.

    Returns
    -------
    bool
        True if the linear map is surjective, otherwise False.

    See Also
    --------
    LinearMap.is_injective, LinearMap.is_bijective
    """
    return self.matrix.rows == self.rank

is_bijective()

Check whether the linear map is bijective.

Returns:

Type Description
bool

True if the linear map is bijective, otherwise False.

See Also

LinearMap.is_injective, LinearMap.is_surjective

Source code in ablina/linearmap.py
def is_bijective(self) -> bool:
    """
    Check whether the linear map is bijective.

    Returns
    -------
    bool
        True if the linear map is bijective, otherwise False.

    See Also
    --------
    LinearMap.is_injective, LinearMap.is_surjective
    """
    return self.is_injective() and self.is_surjective()

range = image class-attribute instance-attribute

An alias for the image method.

nullspace = kernel class-attribute instance-attribute

An alias for the kernel method.

LinearOperator

Bases: LinearMap

A linear operator on a vector space.

A linear map from a vector space to itself. This is a special case of a LinearMap where the domain and codomain are the same.

Source code in ablina/linearmap.py
class LinearOperator(LinearMap):
    """
    A linear operator on a vector space.

    A linear map from a vector space to itself. This is a special case of 
    a LinearMap where the domain and codomain are the same.
    """

    def __init__(
        self, 
        name: str, 
        vectorspace: VectorSpace, 
        mapping: Callable[[Any], Any] | None = None, 
        matrix: Any | None = None
    ) -> None:
        super().__init__(name, vectorspace, vectorspace, mapping, matrix)

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

    def __pow__(self, exp: int) -> LinearOperator:
        """
        Raise the linear operator to a power.

        Parameters
        ----------
        exp : int
            The exponent to raise the operator to.

        Returns
        -------
        LinearOperator
            The linear operator `self` raised to the power `exp`.
        """
        name = f"{self}^{exp}"
        mat = self.matrix ** exp
        return LinearOperator(name, self.domain, matrix=mat)

    def change_of_basis(self, basis: list[Any]) -> tuple[Matrix, Matrix]:
        """
        Change the basis representation of the linear operator.

        Returns the matrix representation of the linear operator with 
        respect to a new basis, along with the change-of-basis matrix.

        Parameters
        ----------
        basis : list of object
            A new basis for the vector space.

        Returns
        -------
        tuple of (Matrix, Matrix)
            A tuple containing the matrix representation with respect to 
            the new basis and the change-of-basis matrix.
        """
        basechange = self.domain.change_of_basis(basis)
        map_matrix = basechange @ self.matrix @ basechange.inv()
        return map_matrix, basechange

    def inverse(self) -> LinearOperator:
        """
        The inverse of the linear operator.

        Returns
        -------
        LinearOperator
            The inverse of `self`.

        Raises
        ------
        LinearMapError
            If `self` is not invertible.
        """
        if not self.is_bijective():
            raise LinearMapError("Linear map is not invertible.")
        name = f"{self}^-1"
        mat = self.matrix.inv()
        return LinearOperator(name, self.domain, matrix=mat)

    def is_invariant_subspace(self, subspace: VectorSpace) -> bool:
        """
        Check whether a subspace is invariant under the linear operator.

        A subspace is invariant if the image of the subspace under the 
        operator is contained in the subspace itself.

        Parameters
        ----------
        subspace : VectorSpace
            The subspace to check.

        Returns
        -------
        bool
            True if `subspace` is invariant under `self`, otherwise False.

        Raises
        ------
        TypeError
            If `subspace` is not a subspace of the domain.
        """
        if not self.domain.is_subspace(subspace):
            raise TypeError("Subspace must be a subspace of the domain.")
        return subspace.is_subspace(self(subspace))

    def is_symmetric(self, innerproduct: InnerProduct) -> bool | None:
        """
        Check whether the linear operator is symmetric.

        Note that this method is only valid for operators defined on 
        real vector spaces. An exception is raised otherwise.

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

        Raises
        ------
        LinearMapError
            If `self` is not defined on a real vector space.

        See Also
        --------
        LinearOperator.is_hermitian
        """
        if self.field is not R:
            raise LinearMapError("Operator must be defined on a real vector space.")
        mat, _ = self.change_of_basis(innerproduct.orthonormal_basis)
        return mat.is_symmetric()

    def is_hermitian(self, innerproduct: InnerProduct) -> bool | None:
        """
        Check whether the linear operator is hermitian.

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

        See Also
        --------
        LinearOperator.is_symmetric
        """
        mat, _ = self.change_of_basis(innerproduct.orthonormal_basis)
        return mat.is_hermitian

    def is_orthogonal(self, innerproduct: InnerProduct) -> bool | None:
        """
        Check whether the linear operator is orthogonal.

        Note that this method is only valid for operators defined on 
        real vector spaces. An exception is raised otherwise.

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

        Raises
        ------
        LinearMapError
            If `self` is not defined on a real vector space.

        See Also
        --------
        LinearOperator.is_unitary
        """
        if self.field is not R:
            raise LinearMapError("Operator must be defined on a real vector space.")
        mat, _ = self.change_of_basis(innerproduct.orthonormal_basis)
        return u.is_orthogonal(mat)

    def is_unitary(self, innerproduct: InnerProduct) -> bool | None:
        """
        Check whether the linear operator is unitary.

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

        See Also
        --------
        LinearOperator.is_orthogonal
        """
        mat, _ = self.change_of_basis(innerproduct.orthonormal_basis)
        return u.is_unitary(mat)

    def is_normal(self, innerproduct: InnerProduct) -> bool | None:
        """
        Check whether the linear operator is normal.

        Returns
        -------
        bool
            True if `self` is normal, otherwise False.
        """
        mat, _ = self.change_of_basis(innerproduct.orthonormal_basis)
        return u.is_normal(mat)

__pow__(exp)

Raise the linear operator to a power.

Parameters:

Name Type Description Default
exp int

The exponent to raise the operator to.

required

Returns:

Type Description
LinearOperator

The linear operator self raised to the power exp.

Source code in ablina/linearmap.py
def __pow__(self, exp: int) -> LinearOperator:
    """
    Raise the linear operator to a power.

    Parameters
    ----------
    exp : int
        The exponent to raise the operator to.

    Returns
    -------
    LinearOperator
        The linear operator `self` raised to the power `exp`.
    """
    name = f"{self}^{exp}"
    mat = self.matrix ** exp
    return LinearOperator(name, self.domain, matrix=mat)

change_of_basis(basis)

Change the basis representation of the linear operator.

Returns the matrix representation of the linear operator with respect to a new basis, along with the change-of-basis matrix.

Parameters:

Name Type Description Default
basis list of object

A new basis for the vector space.

required

Returns:

Type Description
tuple of (Matrix, Matrix)

A tuple containing the matrix representation with respect to the new basis and the change-of-basis matrix.

Source code in ablina/linearmap.py
def change_of_basis(self, basis: list[Any]) -> tuple[Matrix, Matrix]:
    """
    Change the basis representation of the linear operator.

    Returns the matrix representation of the linear operator with 
    respect to a new basis, along with the change-of-basis matrix.

    Parameters
    ----------
    basis : list of object
        A new basis for the vector space.

    Returns
    -------
    tuple of (Matrix, Matrix)
        A tuple containing the matrix representation with respect to 
        the new basis and the change-of-basis matrix.
    """
    basechange = self.domain.change_of_basis(basis)
    map_matrix = basechange @ self.matrix @ basechange.inv()
    return map_matrix, basechange

inverse()

The inverse of the linear operator.

Returns:

Type Description
LinearOperator

The inverse of self.

Raises:

Type Description
LinearMapError

If self is not invertible.

Source code in ablina/linearmap.py
def inverse(self) -> LinearOperator:
    """
    The inverse of the linear operator.

    Returns
    -------
    LinearOperator
        The inverse of `self`.

    Raises
    ------
    LinearMapError
        If `self` is not invertible.
    """
    if not self.is_bijective():
        raise LinearMapError("Linear map is not invertible.")
    name = f"{self}^-1"
    mat = self.matrix.inv()
    return LinearOperator(name, self.domain, matrix=mat)

is_invariant_subspace(subspace)

Check whether a subspace is invariant under the linear operator.

A subspace is invariant if the image of the subspace under the operator is contained in the subspace itself.

Parameters:

Name Type Description Default
subspace VectorSpace

The subspace to check.

required

Returns:

Type Description
bool

True if subspace is invariant under self, otherwise False.

Raises:

Type Description
TypeError

If subspace is not a subspace of the domain.

Source code in ablina/linearmap.py
def is_invariant_subspace(self, subspace: VectorSpace) -> bool:
    """
    Check whether a subspace is invariant under the linear operator.

    A subspace is invariant if the image of the subspace under the 
    operator is contained in the subspace itself.

    Parameters
    ----------
    subspace : VectorSpace
        The subspace to check.

    Returns
    -------
    bool
        True if `subspace` is invariant under `self`, otherwise False.

    Raises
    ------
    TypeError
        If `subspace` is not a subspace of the domain.
    """
    if not self.domain.is_subspace(subspace):
        raise TypeError("Subspace must be a subspace of the domain.")
    return subspace.is_subspace(self(subspace))

is_symmetric(innerproduct)

Check whether the linear operator is symmetric.

Note that this method is only valid for operators defined on real vector spaces. An exception is raised otherwise.

Returns:

Type Description
bool

True if self is symmetric, otherwise False.

Raises:

Type Description
LinearMapError

If self is not defined on a real vector space.

See Also

LinearOperator.is_hermitian

Source code in ablina/linearmap.py
def is_symmetric(self, innerproduct: InnerProduct) -> bool | None:
    """
    Check whether the linear operator is symmetric.

    Note that this method is only valid for operators defined on 
    real vector spaces. An exception is raised otherwise.

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

    Raises
    ------
    LinearMapError
        If `self` is not defined on a real vector space.

    See Also
    --------
    LinearOperator.is_hermitian
    """
    if self.field is not R:
        raise LinearMapError("Operator must be defined on a real vector space.")
    mat, _ = self.change_of_basis(innerproduct.orthonormal_basis)
    return mat.is_symmetric()

is_hermitian(innerproduct)

Check whether the linear operator is hermitian.

Returns:

Type Description
bool

True if self is hermitian, otherwise False.

See Also

LinearOperator.is_symmetric

Source code in ablina/linearmap.py
def is_hermitian(self, innerproduct: InnerProduct) -> bool | None:
    """
    Check whether the linear operator is hermitian.

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

    See Also
    --------
    LinearOperator.is_symmetric
    """
    mat, _ = self.change_of_basis(innerproduct.orthonormal_basis)
    return mat.is_hermitian

is_orthogonal(innerproduct)

Check whether the linear operator is orthogonal.

Note that this method is only valid for operators defined on real vector spaces. An exception is raised otherwise.

Returns:

Type Description
bool

True if self is orthogonal, otherwise False.

Raises:

Type Description
LinearMapError

If self is not defined on a real vector space.

See Also

LinearOperator.is_unitary

Source code in ablina/linearmap.py
def is_orthogonal(self, innerproduct: InnerProduct) -> bool | None:
    """
    Check whether the linear operator is orthogonal.

    Note that this method is only valid for operators defined on 
    real vector spaces. An exception is raised otherwise.

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

    Raises
    ------
    LinearMapError
        If `self` is not defined on a real vector space.

    See Also
    --------
    LinearOperator.is_unitary
    """
    if self.field is not R:
        raise LinearMapError("Operator must be defined on a real vector space.")
    mat, _ = self.change_of_basis(innerproduct.orthonormal_basis)
    return u.is_orthogonal(mat)

is_unitary(innerproduct)

Check whether the linear operator is unitary.

Returns:

Type Description
bool

True if self is unitary, otherwise False.

See Also

LinearOperator.is_orthogonal

Source code in ablina/linearmap.py
def is_unitary(self, innerproduct: InnerProduct) -> bool | None:
    """
    Check whether the linear operator is unitary.

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

    See Also
    --------
    LinearOperator.is_orthogonal
    """
    mat, _ = self.change_of_basis(innerproduct.orthonormal_basis)
    return u.is_unitary(mat)

is_normal(innerproduct)

Check whether the linear operator is normal.

Returns:

Type Description
bool

True if self is normal, otherwise False.

Source code in ablina/linearmap.py
def is_normal(self, innerproduct: InnerProduct) -> bool | None:
    """
    Check whether the linear operator is normal.

    Returns
    -------
    bool
        True if `self` is normal, otherwise False.
    """
    mat, _ = self.change_of_basis(innerproduct.orthonormal_basis)
    return u.is_normal(mat)

LinearFunctional

Bases: LinearMap

A linear functional on a vector space.

A linear map from a vector space to its field of scalars. This is a special case of a LinearMap where the codomain is the underlying field.

Source code in ablina/linearmap.py
class LinearFunctional(LinearMap):
    """
    A linear functional on a vector space.

    A linear map from a vector space to its field of scalars. This is a 
    special case of a LinearMap where the codomain is the underlying field.
    """

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

        Parameters
        ----------
        name : str
            The name of the linear functional.
        vectorspace : VectorSpace
            The vector space the linear functional 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 linear functional with 
            respect to the basis of the vector space.

        Returns
        -------
        LinearFunctional
            A new LinearFunctional instance.

        Raises
        ------
        LinearMapError
            If neither the mapping nor the matrix is provided.
        """
        field = vectorspace.field
        codomain = fn(str(field), field, 1)
        super().__init__(name, vectorspace, codomain, mapping, matrix)

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

    def restriction(self, subspace: VectorSpace) -> LinearFunctional:
        """
        Restrict the linear functional to a subspace of the domain.

        Parameters
        ----------
        subspace : VectorSpace
            A subspace of the domain.

        Returns
        -------
        LinearFunctional
            The restriction of `self` to `subspace`.

        Raises
        ------
        TypeError
            If `subspace` is not a subspace of the domain.
        """
        if not self.domain.is_subspace(subspace):
            raise TypeError("Subspace must be a subspace of the domain.")
        name = f"{self} | {subspace}"
        return LinearFunctional(name, subspace, self.mapping)

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

Initialize a LinearFunctional instance.

Parameters:

Name Type Description Default
name str

The name of the linear functional.

required
vectorspace VectorSpace

The vector space the linear functional 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 linear functional with respect to the basis of the vector space.

None

Returns:

Type Description
LinearFunctional

A new LinearFunctional instance.

Raises:

Type Description
LinearMapError

If neither the mapping nor the matrix is provided.

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

    Parameters
    ----------
    name : str
        The name of the linear functional.
    vectorspace : VectorSpace
        The vector space the linear functional 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 linear functional with 
        respect to the basis of the vector space.

    Returns
    -------
    LinearFunctional
        A new LinearFunctional instance.

    Raises
    ------
    LinearMapError
        If neither the mapping nor the matrix is provided.
    """
    field = vectorspace.field
    codomain = fn(str(field), field, 1)
    super().__init__(name, vectorspace, codomain, mapping, matrix)

restriction(subspace)

Restrict the linear functional to a subspace of the domain.

Parameters:

Name Type Description Default
subspace VectorSpace

A subspace of the domain.

required

Returns:

Type Description
LinearFunctional

The restriction of self to subspace.

Raises:

Type Description
TypeError

If subspace is not a subspace of the domain.

Source code in ablina/linearmap.py
def restriction(self, subspace: VectorSpace) -> LinearFunctional:
    """
    Restrict the linear functional to a subspace of the domain.

    Parameters
    ----------
    subspace : VectorSpace
        A subspace of the domain.

    Returns
    -------
    LinearFunctional
        The restriction of `self` to `subspace`.

    Raises
    ------
    TypeError
        If `subspace` is not a subspace of the domain.
    """
    if not self.domain.is_subspace(subspace):
        raise TypeError("Subspace must be a subspace of the domain.")
    name = f"{self} | {subspace}"
    return LinearFunctional(name, subspace, self.mapping)

Isomorphism

Bases: LinearMap

An isomorphism between vector spaces.

A bijective linear map between vector spaces. This is a special case of a LinearMap that is both injective and surjective.

Source code in ablina/linearmap.py
class Isomorphism(LinearMap):
    """
    An isomorphism between vector spaces.

    A bijective linear map between vector spaces. This is a special case 
    of a LinearMap that is both injective and surjective.
    """

    def __init__(
        self, 
        name: str, 
        domain: VectorSpace, 
        codomain: VectorSpace, 
        mapping: Callable[[Any], Any] | None = None, 
        matrix: Any | None = None
    ) -> None:
        super().__init__(name, domain, codomain, mapping, matrix)

        if not self.is_bijective():
            raise LinearMapError("Linear map is not invertible.")

    def __repr__(self) -> str:
        return super().__repr__().replace("LinearMap", "Isomorphism")

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

        Returns
        -------
        str
            The formatted description.
        """
        signature = f"{self} : {self.domain}{self.codomain}"

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

    def inverse(self) -> Isomorphism:
        """
        The inverse of the isomorphism.

        Returns
        -------
        Isomorphism
            The inverse of `self`.
        """
        name = f"{self}^-1"
        mat = self.matrix.inv()
        return Isomorphism(name, self.codomain, self.domain, matrix=mat)

info()

A description of the isomorphism.

Returns:

Type Description
str

The formatted description.

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

    Returns
    -------
    str
        The formatted description.
    """
    signature = f"{self} : {self.domain}{self.codomain}"

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

inverse()

The inverse of the isomorphism.

Returns:

Type Description
Isomorphism

The inverse of self.

Source code in ablina/linearmap.py
def inverse(self) -> Isomorphism:
    """
    The inverse of the isomorphism.

    Returns
    -------
    Isomorphism
        The inverse of `self`.
    """
    name = f"{self}^-1"
    mat = self.matrix.inv()
    return Isomorphism(name, self.codomain, self.domain, matrix=mat)

IdentityMap

Bases: LinearOperator

The identity map on a vector space.

A linear operator that maps every vector to itself. This is a special case of a LinearOperator.

Source code in ablina/linearmap.py
class IdentityMap(LinearOperator):
    """
    The identity map on a vector space.

    A linear operator that maps every vector to itself. This is a special 
    case of a LinearOperator.
    """

    def __init__(self, vectorspace: VectorSpace) -> None:
        """
        Initialize an IdentityMap instance.

        Parameters
        ----------
        vectorspace : VectorSpace
            The vector space the identity map is defined on.

        Returns
        -------
        IdentityMap
            A new IdentityMap instance.
        """
        super().__init__("Id", vectorspace, lambda vec: vec)

    def __repr__(self) -> str:
        return f"IdentityMap(vectorspace={self.domain!r})"

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

        Returns
        -------
        str
            The formatted description.
        """
        signature = f"{self} : {self.domain}{self.codomain}"

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

    def inverse(self) -> IdentityMap:
        """
        The inverse of the identity map.

        Returns
        -------
        IdentityMap
            The inverse of `self`.
        """
        return self

__init__(vectorspace)

Initialize an IdentityMap instance.

Parameters:

Name Type Description Default
vectorspace VectorSpace

The vector space the identity map is defined on.

required

Returns:

Type Description
IdentityMap

A new IdentityMap instance.

Source code in ablina/linearmap.py
def __init__(self, vectorspace: VectorSpace) -> None:
    """
    Initialize an IdentityMap instance.

    Parameters
    ----------
    vectorspace : VectorSpace
        The vector space the identity map is defined on.

    Returns
    -------
    IdentityMap
        A new IdentityMap instance.
    """
    super().__init__("Id", vectorspace, lambda vec: vec)

info()

A description of the identity map.

Returns:

Type Description
str

The formatted description.

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

    Returns
    -------
    str
        The formatted description.
    """
    signature = f"{self} : {self.domain}{self.codomain}"

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

inverse()

The inverse of the identity map.

Returns:

Type Description
IdentityMap

The inverse of self.

Source code in ablina/linearmap.py
def inverse(self) -> IdentityMap:
    """
    The inverse of the identity map.

    Returns
    -------
    IdentityMap
        The inverse of `self`.
    """
    return self