Skip to content

panchi.algorithms

from panchi.algorithms import ref, rref, lu
from panchi.algorithms import RowSwap, RowScale, RowAdd
from panchi.algorithms import Reduction, LUDecomposition, InverseResult, Solution
from panchi.algorithms import inverse, solve, determinant_lu
from panchi.algorithms import dot, cross, orthogonal_complement

Row operations

panchi.algorithms.RowSwap

Bases: RowOperation

Elementary row operation: swap two rows.

Represents the operation R_a <-> R_b. The corresponding elementary matrix is the identity matrix with rows a and b exchanged.

Applying this operation twice returns the original matrix.

Parameters:

Name Type Description Default
row_a int

Index of the first row to swap (0-based).

required
row_b int

Index of the second row to swap (0-based).

required

Examples:

>>> m = Matrix([[1, 2], [3, 4], [5, 6]])
>>> op = RowSwap(0, 2)
>>> print(op.apply(m))
[[5, 6],
 [3, 4],
 [1, 2]]
>>> v = Vector([1, 2, 3])
>>> print(op.apply(v))
[3, 2, 1]
>>> print(op.elementary_matrix(3))
[[0, 0, 1],
 [0, 1, 0],
 [1, 0, 0]]
>>> print(op)
R0 <-> R2
>>> repr(op)
'RowSwap(row_a=0, row_b=2)'
Source code in panchi/algorithms/row_operations.py
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
class RowSwap(RowOperation):
    """
    Elementary row operation: swap two rows.

    Represents the operation R_a <-> R_b. The corresponding elementary
    matrix is the identity matrix with rows a and b exchanged.

    Applying this operation twice returns the original matrix.

    Parameters
    ----------
    row_a : int
        Index of the first row to swap (0-based).
    row_b : int
        Index of the second row to swap (0-based).

    Examples
    --------
    >>> m = Matrix([[1, 2], [3, 4], [5, 6]])
    >>> op = RowSwap(0, 2)
    >>> print(op.apply(m))
    [[5, 6],
     [3, 4],
     [1, 2]]
    >>> v = Vector([1, 2, 3])
    >>> print(op.apply(v))
    [3, 2, 1]
    >>> print(op.elementary_matrix(3))
    [[0, 0, 1],
     [0, 1, 0],
     [1, 0, 0]]
    >>> print(op)
    R0 <-> R2
    >>> repr(op)
    'RowSwap(row_a=0, row_b=2)'
    """

    def __init__(self, row_a: int, row_b: int) -> None:
        self.a = row_a
        self.b = row_b
        self._validate_index_types()

    def _validate_index_types(self) -> None:
        """
        Validate that both row indices are integers.

        Raises
        ------
        TypeError
            If either row index is not an integer.
        """
        if not isinstance(self.a, int):
            raise TypeError(
                f"Row index must be an integer. Got {type(self.a).__name__} for row_a."
            )

        if not isinstance(self.b, int):
            raise TypeError(
                f"Row index must be an integer. Got {type(self.b).__name__} for row_b."
            )

    def _validate_indices(self, n: int) -> None:
        """
        Validate that both row indices are in range for a matrix of size n.

        Parameters
        ----------
        n : int
            Number of rows in the target matrix or vector.

        Raises
        ------
        ValueError
            If either row index is outside [0, n - 1].
        """
        if not (0 <= self.a < n):
            raise ValueError(
                f"Row index {self.a} is out of range for a matrix with {n} rows. "
                f"Valid indices are 0 to {n - 1}."
            )

        if not (0 <= self.b < n):
            raise ValueError(
                f"Row index {self.b} is out of range for a matrix with {n} rows. "
                f"Valid indices are 0 to {n - 1}."
            )

    def elementary_matrix(self, n: int) -> Matrix:
        """
        Return the n×n elementary matrix for this row swap.

        Constructed by swapping rows a and b in the n×n identity matrix.
        This matrix has determinant -1, reflecting that row swaps reverse
        the orientation of the row space.

        Parameters
        ----------
        n : int
            Size of the elementary matrix. Must be at least 2, and large
            enough so that both row indices are in range.

        Returns
        -------
        Matrix
            An n×n matrix identical to the identity except that rows a
            and b are exchanged.

        Raises
        ------
        TypeError
            If n is not an integer.
        ValueError
            If n < 2 or either row index is out of range for n.

        Examples
        --------
        >>> op = RowSwap(0, 1)
        >>> print(op.elementary_matrix(3))
        [[0, 1, 0],
         [1, 0, 0],
         [0, 0, 1]]
        """
        self._validate_n(n)
        self._validate_indices(n)

        grid: Matrix = identity(n)
        row_a: list[int | float] = grid[self.a].copy()
        row_b: list[int | float] = grid[self.b].copy()
        grid[self.a] = row_b
        grid[self.b] = row_a

        return grid

    def apply(self, target: Matrix | Vector) -> Matrix | Vector:
        """
        Swap rows a and b of a matrix or vector, returning the result.

        Parameters
        ----------
        target : Matrix | Vector
            The matrix or vector to operate on.

        Returns
        -------
        Matrix | Vector
            A new matrix or vector with the two entries exchanged.

        Raises
        ------
        TypeError
            If target is not a Matrix or Vector instance.
        ValueError
            If either row index is out of range for this target.

        Examples
        --------
        >>> m = Matrix([[1, 2], [3, 4], [5, 6]])
        >>> print(RowSwap(0, 2).apply(m))
        [[5, 6],
         [3, 4],
         [1, 2]]
        >>> v = Vector([1, 2, 3])
        >>> print(RowSwap(0, 2).apply(v))
        [3, 2, 1]
        """
        self._validate_target(target)
        n = target.dims if isinstance(target, Vector) else target.rows
        self._validate_indices(n)

        if isinstance(target, Vector):
            result = target.copy()
            result[self.a], result[self.b] = target[self.b], target[self.a]
            return result

        return self.elementary_matrix(target.rows) @ target

    def inverse(self) -> RowSwap:
        """
        Return the inverse of this row swap.

        A row swap is its own inverse: swapping the same two rows a second
        time restores the original matrix.

        Returns
        -------
        RowSwap
            A new RowSwap with the same row indices.

        Examples
        --------
        >>> op = RowSwap(0, 2)
        >>> op.inverse()
        RowSwap(row_a=0, row_b=2)
        """
        return RowSwap(self.a, self.b)

    def __str__(self) -> str:
        return f"R{self.a} <-> R{self.b}"

    def __repr__(self) -> str:
        return f"RowSwap(row_a={self.a}, row_b={self.b})"

apply(target)

Swap rows a and b of a matrix or vector, returning the result.

Parameters:

Name Type Description Default
target Matrix | Vector

The matrix or vector to operate on.

required

Returns:

Type Description
Matrix | Vector

A new matrix or vector with the two entries exchanged.

Raises:

Type Description
TypeError

If target is not a Matrix or Vector instance.

ValueError

If either row index is out of range for this target.

Examples:

>>> m = Matrix([[1, 2], [3, 4], [5, 6]])
>>> print(RowSwap(0, 2).apply(m))
[[5, 6],
 [3, 4],
 [1, 2]]
>>> v = Vector([1, 2, 3])
>>> print(RowSwap(0, 2).apply(v))
[3, 2, 1]
Source code in panchi/algorithms/row_operations.py
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
def apply(self, target: Matrix | Vector) -> Matrix | Vector:
    """
    Swap rows a and b of a matrix or vector, returning the result.

    Parameters
    ----------
    target : Matrix | Vector
        The matrix or vector to operate on.

    Returns
    -------
    Matrix | Vector
        A new matrix or vector with the two entries exchanged.

    Raises
    ------
    TypeError
        If target is not a Matrix or Vector instance.
    ValueError
        If either row index is out of range for this target.

    Examples
    --------
    >>> m = Matrix([[1, 2], [3, 4], [5, 6]])
    >>> print(RowSwap(0, 2).apply(m))
    [[5, 6],
     [3, 4],
     [1, 2]]
    >>> v = Vector([1, 2, 3])
    >>> print(RowSwap(0, 2).apply(v))
    [3, 2, 1]
    """
    self._validate_target(target)
    n = target.dims if isinstance(target, Vector) else target.rows
    self._validate_indices(n)

    if isinstance(target, Vector):
        result = target.copy()
        result[self.a], result[self.b] = target[self.b], target[self.a]
        return result

    return self.elementary_matrix(target.rows) @ target

elementary_matrix(n)

Return the n×n elementary matrix for this row swap.

Constructed by swapping rows a and b in the n×n identity matrix. This matrix has determinant -1, reflecting that row swaps reverse the orientation of the row space.

Parameters:

Name Type Description Default
n int

Size of the elementary matrix. Must be at least 2, and large enough so that both row indices are in range.

required

Returns:

Type Description
Matrix

An n×n matrix identical to the identity except that rows a and b are exchanged.

Raises:

Type Description
TypeError

If n is not an integer.

ValueError

If n < 2 or either row index is out of range for n.

Examples:

>>> op = RowSwap(0, 1)
>>> print(op.elementary_matrix(3))
[[0, 1, 0],
 [1, 0, 0],
 [0, 0, 1]]
Source code in panchi/algorithms/row_operations.py
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
def elementary_matrix(self, n: int) -> Matrix:
    """
    Return the n×n elementary matrix for this row swap.

    Constructed by swapping rows a and b in the n×n identity matrix.
    This matrix has determinant -1, reflecting that row swaps reverse
    the orientation of the row space.

    Parameters
    ----------
    n : int
        Size of the elementary matrix. Must be at least 2, and large
        enough so that both row indices are in range.

    Returns
    -------
    Matrix
        An n×n matrix identical to the identity except that rows a
        and b are exchanged.

    Raises
    ------
    TypeError
        If n is not an integer.
    ValueError
        If n < 2 or either row index is out of range for n.

    Examples
    --------
    >>> op = RowSwap(0, 1)
    >>> print(op.elementary_matrix(3))
    [[0, 1, 0],
     [1, 0, 0],
     [0, 0, 1]]
    """
    self._validate_n(n)
    self._validate_indices(n)

    grid: Matrix = identity(n)
    row_a: list[int | float] = grid[self.a].copy()
    row_b: list[int | float] = grid[self.b].copy()
    grid[self.a] = row_b
    grid[self.b] = row_a

    return grid

inverse()

Return the inverse of this row swap.

A row swap is its own inverse: swapping the same two rows a second time restores the original matrix.

Returns:

Type Description
RowSwap

A new RowSwap with the same row indices.

Examples:

>>> op = RowSwap(0, 2)
>>> op.inverse()
RowSwap(row_a=0, row_b=2)
Source code in panchi/algorithms/row_operations.py
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
def inverse(self) -> RowSwap:
    """
    Return the inverse of this row swap.

    A row swap is its own inverse: swapping the same two rows a second
    time restores the original matrix.

    Returns
    -------
    RowSwap
        A new RowSwap with the same row indices.

    Examples
    --------
    >>> op = RowSwap(0, 2)
    >>> op.inverse()
    RowSwap(row_a=0, row_b=2)
    """
    return RowSwap(self.a, self.b)

panchi.algorithms.RowScale

Bases: RowOperation

Elementary row operation: multiply a row by a non-zero scalar.

Represents the operation R_i -> scalar * R_i. The corresponding elementary matrix is the identity matrix with the diagonal entry at position [i, i] replaced by scalar.

Scaling a row by scalar multiplies the determinant of the matrix by scalar. To invert this operation, scale by 1 / scalar.

Parameters:

Name Type Description Default
row int

Index of the row to scale (0-based).

required
scalar int | float

The non-zero value to multiply the row by.

required

Examples:

>>> m = Matrix([[1, 2], [3, 4]])
>>> op = RowScale(1, 3)
>>> print(op.apply(m))
[[1, 2],
 [9, 12]]
>>> v = Vector([1, 2])
>>> print(op.apply(v))
[1, 6]
>>> print(op.elementary_matrix(2))
[[1, 0],
 [0, 3]]
>>> print(op)
R1 -> 3 * R1
>>> repr(op)
'RowScale(row=1, scalar=3)'
Source code in panchi/algorithms/row_operations.py
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
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
class RowScale(RowOperation):
    """
    Elementary row operation: multiply a row by a non-zero scalar.

    Represents the operation R_i -> scalar * R_i. The corresponding
    elementary matrix is the identity matrix with the diagonal entry
    at position [i, i] replaced by scalar.

    Scaling a row by scalar multiplies the determinant of the matrix
    by scalar. To invert this operation, scale by 1 / scalar.

    Parameters
    ----------
    row : int
        Index of the row to scale (0-based).
    scalar : int | float
        The non-zero value to multiply the row by.

    Examples
    --------
    >>> m = Matrix([[1, 2], [3, 4]])
    >>> op = RowScale(1, 3)
    >>> print(op.apply(m))
    [[1, 2],
     [9, 12]]
    >>> v = Vector([1, 2])
    >>> print(op.apply(v))
    [1, 6]
    >>> print(op.elementary_matrix(2))
    [[1, 0],
     [0, 3]]
    >>> print(op)
    R1 -> 3 * R1
    >>> repr(op)
    'RowScale(row=1, scalar=3)'
    """

    def __init__(self, row: int, scalar: int | float) -> None:
        self.row = row
        self.scalar = scalar
        self._validate_row_type()
        self._validate_scalar()

    def _validate_row_type(self) -> None:
        """
        Validate that the row index is an integer.

        Raises
        ------
        TypeError
            If row is not an integer.
        """
        if not isinstance(self.row, int):
            raise TypeError(
                f"Row index must be an integer. Got {type(self.row).__name__}."
            )

    def _validate_row(self, n: int) -> None:
        """
        Validate that the row index is in range for a matrix or vector of size n.

        Parameters
        ----------
        n : int
            Number of rows in the target matrix or vector.

        Raises
        ------
        ValueError
            If the row index is outside [0, n - 1].
        """
        if not (0 <= self.row < n):
            raise ValueError(
                f"Row index {self.row} is out of range for a matrix with {n} rows. "
                f"Valid indices are 0 to {n - 1}."
            )

    def _validate_scalar(self) -> None:
        """
        Validate that the scalar is a non-zero number.

        Raises
        ------
        TypeError
            If scalar is not an int or float.
        ValueError
            If scalar is zero.
        """
        if not isinstance(self.scalar, (int, float)):
            raise TypeError(
                f"Scalar must be a number (int or float). "
                f"Got {type(self.scalar).__name__}."
            )

        if self.scalar == 0:
            raise ValueError(
                "Scalar must be non-zero. Scaling a row by zero would make "
                "the matrix singular and the operation non-invertible."
            )

    def elementary_matrix(self, n: int) -> Matrix:
        """
        Return the n×n elementary matrix for this row scale.

        Constructed from the identity matrix with the diagonal entry at
        position [row, row] replaced by scalar.

        Parameters
        ----------
        n : int
            Size of the elementary matrix. Must be at least 2, and large
            enough so that the row index is in range.

        Returns
        -------
        Matrix
            An n×n matrix identical to the identity except that entry
            [row, row] equals scalar.

        Raises
        ------
        TypeError
            If n is not an integer, or scalar is not a number.
        ValueError
            If n < 2, scalar is zero, or the row index is out of range.

        Examples
        --------
        >>> op = RowScale(0, 5)
        >>> print(op.elementary_matrix(3))
        [[5, 0, 0],
         [0, 1, 0],
         [0, 0, 1]]
        """
        self._validate_n(n)
        self._validate_row(n)
        self._validate_scalar()

        grid: Matrix = identity(n)
        grid[self.row][self.row] = self.scalar

        return grid

    def apply(self, target: Matrix | Vector) -> Matrix | Vector:
        """
        Multiply a row of a matrix or vector by the scalar, returning the result.

        Parameters
        ----------
        target : Matrix | Vector
            The matrix or vector to operate on.

        Returns
        -------
        Matrix | Vector
            A new matrix or vector with the specified row multiplied by scalar.

        Raises
        ------
        TypeError
            If target is not a Matrix or Vector instance, or scalar is not a number.
        ValueError
            If scalar is zero or the row index is out of range.

        Examples
        --------
        >>> m = Matrix([[1, 2], [3, 4]])
        >>> print(RowScale(0, -1).apply(m))
        [[-1, -2],
         [3, 4]]
        >>> v = Vector([1, 2])
        >>> print(RowScale(0, -1).apply(v))
        [-1, 2]
        """
        self._validate_target(target)
        n = target.dims if isinstance(target, Vector) else target.rows
        self._validate_row(n)
        self._validate_scalar()

        if isinstance(target, Vector):
            result = target.copy()
            result[self.row] = target[self.row] * self.scalar
            return result

        return self.elementary_matrix(target.rows) @ target

    def inverse(self) -> RowScale:
        """
        Return the inverse of this row scale.

        The inverse scales the same row by 1 / scalar, which restores
        the original values.

        Returns
        -------
        RowScale
            A new RowScale on the same row with scalar 1 / self.scalar.

        Examples
        --------
        >>> op = RowScale(1, 3)
        >>> op.inverse()
        RowScale(row=1, scalar=0.3333333333333333)
        """
        return RowScale(self.row, 1 / self.scalar)

    def __str__(self) -> str:
        return f"R{self.row} -> {self.scalar} * R{self.row}"

    def __repr__(self) -> str:
        return f"RowScale(row={self.row}, scalar={self.scalar})"

apply(target)

Multiply a row of a matrix or vector by the scalar, returning the result.

Parameters:

Name Type Description Default
target Matrix | Vector

The matrix or vector to operate on.

required

Returns:

Type Description
Matrix | Vector

A new matrix or vector with the specified row multiplied by scalar.

Raises:

Type Description
TypeError

If target is not a Matrix or Vector instance, or scalar is not a number.

ValueError

If scalar is zero or the row index is out of range.

Examples:

>>> m = Matrix([[1, 2], [3, 4]])
>>> print(RowScale(0, -1).apply(m))
[[-1, -2],
 [3, 4]]
>>> v = Vector([1, 2])
>>> print(RowScale(0, -1).apply(v))
[-1, 2]
Source code in panchi/algorithms/row_operations.py
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
576
577
578
579
580
581
582
583
584
585
586
587
def apply(self, target: Matrix | Vector) -> Matrix | Vector:
    """
    Multiply a row of a matrix or vector by the scalar, returning the result.

    Parameters
    ----------
    target : Matrix | Vector
        The matrix or vector to operate on.

    Returns
    -------
    Matrix | Vector
        A new matrix or vector with the specified row multiplied by scalar.

    Raises
    ------
    TypeError
        If target is not a Matrix or Vector instance, or scalar is not a number.
    ValueError
        If scalar is zero or the row index is out of range.

    Examples
    --------
    >>> m = Matrix([[1, 2], [3, 4]])
    >>> print(RowScale(0, -1).apply(m))
    [[-1, -2],
     [3, 4]]
    >>> v = Vector([1, 2])
    >>> print(RowScale(0, -1).apply(v))
    [-1, 2]
    """
    self._validate_target(target)
    n = target.dims if isinstance(target, Vector) else target.rows
    self._validate_row(n)
    self._validate_scalar()

    if isinstance(target, Vector):
        result = target.copy()
        result[self.row] = target[self.row] * self.scalar
        return result

    return self.elementary_matrix(target.rows) @ target

elementary_matrix(n)

Return the n×n elementary matrix for this row scale.

Constructed from the identity matrix with the diagonal entry at position [row, row] replaced by scalar.

Parameters:

Name Type Description Default
n int

Size of the elementary matrix. Must be at least 2, and large enough so that the row index is in range.

required

Returns:

Type Description
Matrix

An n×n matrix identical to the identity except that entry [row, row] equals scalar.

Raises:

Type Description
TypeError

If n is not an integer, or scalar is not a number.

ValueError

If n < 2, scalar is zero, or the row index is out of range.

Examples:

>>> op = RowScale(0, 5)
>>> print(op.elementary_matrix(3))
[[5, 0, 0],
 [0, 1, 0],
 [0, 0, 1]]
Source code in panchi/algorithms/row_operations.py
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
def elementary_matrix(self, n: int) -> Matrix:
    """
    Return the n×n elementary matrix for this row scale.

    Constructed from the identity matrix with the diagonal entry at
    position [row, row] replaced by scalar.

    Parameters
    ----------
    n : int
        Size of the elementary matrix. Must be at least 2, and large
        enough so that the row index is in range.

    Returns
    -------
    Matrix
        An n×n matrix identical to the identity except that entry
        [row, row] equals scalar.

    Raises
    ------
    TypeError
        If n is not an integer, or scalar is not a number.
    ValueError
        If n < 2, scalar is zero, or the row index is out of range.

    Examples
    --------
    >>> op = RowScale(0, 5)
    >>> print(op.elementary_matrix(3))
    [[5, 0, 0],
     [0, 1, 0],
     [0, 0, 1]]
    """
    self._validate_n(n)
    self._validate_row(n)
    self._validate_scalar()

    grid: Matrix = identity(n)
    grid[self.row][self.row] = self.scalar

    return grid

inverse()

Return the inverse of this row scale.

The inverse scales the same row by 1 / scalar, which restores the original values.

Returns:

Type Description
RowScale

A new RowScale on the same row with scalar 1 / self.scalar.

Examples:

>>> op = RowScale(1, 3)
>>> op.inverse()
RowScale(row=1, scalar=0.3333333333333333)
Source code in panchi/algorithms/row_operations.py
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
def inverse(self) -> RowScale:
    """
    Return the inverse of this row scale.

    The inverse scales the same row by 1 / scalar, which restores
    the original values.

    Returns
    -------
    RowScale
        A new RowScale on the same row with scalar 1 / self.scalar.

    Examples
    --------
    >>> op = RowScale(1, 3)
    >>> op.inverse()
    RowScale(row=1, scalar=0.3333333333333333)
    """
    return RowScale(self.row, 1 / self.scalar)

panchi.algorithms.RowAdd

Bases: RowOperation

Elementary row operation: add a scalar multiple of one row to another.

Represents the operation R_target -> R_target + scalar * R_source. The corresponding elementary matrix is the identity with scalar placed at position [target, source].

This is the core operation of Gaussian elimination. When scalar is chosen to eliminate an entry, the result is a zero in position [target, source_col] of the transformed matrix.

The inverse of this operation is RowAdd(target, source, -scalar).

Parameters:

Name Type Description Default
target int

Index of the row being modified (0-based).

required
source int

Index of the row being added (0-based). Must differ from target.

required
scalar int | float

The value to multiply the source row by before adding.

required

Examples:

>>> m = Matrix([[1, 2], [3, 4]])
>>> op = RowAdd(target=1, source=0, scalar=-3)
>>> print(op.apply(m))
[[1,  2],
 [0, -2]]
>>> v = Vector([1, 2])
>>> print(op.apply(v))
[1, -1]
>>> print(op.elementary_matrix(2))
[[1,  0],
 [-3, 1]]
>>> print(op)
R1 -> R1 + (-3) * R0
>>> repr(op)
'RowAdd(target=1, source=0, scalar=-3)'
Source code in panchi/algorithms/row_operations.py
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
class RowAdd(RowOperation):
    """
    Elementary row operation: add a scalar multiple of one row to another.

    Represents the operation R_target -> R_target + scalar * R_source.
    The corresponding elementary matrix is the identity with scalar placed
    at position [target, source].

    This is the core operation of Gaussian elimination. When scalar is
    chosen to eliminate an entry, the result is a zero in position
    [target, source_col] of the transformed matrix.

    The inverse of this operation is RowAdd(target, source, -scalar).

    Parameters
    ----------
    target : int
        Index of the row being modified (0-based).
    source : int
        Index of the row being added (0-based). Must differ from target.
    scalar : int | float
        The value to multiply the source row by before adding.

    Examples
    --------
    >>> m = Matrix([[1, 2], [3, 4]])
    >>> op = RowAdd(target=1, source=0, scalar=-3)
    >>> print(op.apply(m))
    [[1,  2],
     [0, -2]]
    >>> v = Vector([1, 2])
    >>> print(op.apply(v))
    [1, -1]
    >>> print(op.elementary_matrix(2))
    [[1,  0],
     [-3, 1]]
    >>> print(op)
    R1 -> R1 + (-3) * R0
    >>> repr(op)
    'RowAdd(target=1, source=0, scalar=-3)'
    """

    def __init__(self, target: int, source: int, scalar: int | float) -> None:
        self.target = target
        self.source = source
        self.scalar = scalar
        self._validate_index_types()
        self._validate_scalar()

    def _validate_index_types(self) -> None:
        """
        Validate that both row indices are integers.

        Raises
        ------
        TypeError
            If either row index is not an integer.
        """
        if not isinstance(self.target, int):
            raise TypeError(
                f"Row index must be an integer. Got {type(self.target).__name__} for target."
            )

        if not isinstance(self.source, int):
            raise TypeError(
                f"Row index must be an integer. Got {type(self.source).__name__} for source."
            )

    def _validate_indices(self, n: int) -> None:
        """
        Validate that both row indices are in range and distinct.

        Parameters
        ----------
        n : int
            Number of rows in the target matrix or vector.

        Raises
        ------
        ValueError
            If either index is out of range, or if target equals source.
        """
        if not (0 <= self.target < n):
            raise ValueError(
                f"Target row index {self.target} is out of range for a matrix "
                f"with {n} rows. Valid indices are 0 to {n - 1}."
            )

        if not (0 <= self.source < n):
            raise ValueError(
                f"Source row index {self.source} is out of range for a matrix "
                f"with {n} rows. Valid indices are 0 to {n - 1}."
            )

        if self.target == self.source:
            raise ValueError(
                f"Target and source rows must be different. Both are row {self.target}. "
                f"To scale a row, use RowScale instead."
            )

    def _validate_scalar(self) -> None:
        """
        Validate that the scalar is a number.

        Raises
        ------
        TypeError
            If scalar is not an int or float.
        """
        if not isinstance(self.scalar, (int, float)):
            raise TypeError(
                f"Scalar must be a number (int or float). "
                f"Got {type(self.scalar).__name__}."
            )

    def elementary_matrix(self, n: int) -> Matrix:
        """
        Return the n×n elementary matrix for this row addition.

        Constructed from the identity matrix with scalar placed at position
        [target, source]. This encodes the fact that left-multiplying by E
        replaces row target with row target plus scalar times row source.

        Parameters
        ----------
        n : int
            Size of the elementary matrix. Must be at least 2 and large
            enough so that both row indices are in range.

        Returns
        -------
        Matrix
            An n×n matrix identical to the identity except that entry
            [target, source] equals scalar.

        Raises
        ------
        TypeError
            If n is not an integer, or scalar is not a number.
        ValueError
            If n < 2, indices are out of range, or target equals source.

        Examples
        --------
        >>> op = RowAdd(target=2, source=0, scalar=4)
        >>> print(op.elementary_matrix(3))
        [[1, 0, 0],
         [0, 1, 0],
         [4, 0, 1]]
        """
        self._validate_n(n)
        self._validate_indices(n)
        self._validate_scalar()

        grid: Matrix = identity(n)
        grid[self.target][self.source] = self.scalar

        return grid

    def apply(self, target: Matrix | Vector) -> Matrix | Vector:
        """
        Add scalar times the source row to the target row, returning the result.

        Parameters
        ----------
        target : Matrix | Vector
            The matrix or vector to operate on.

        Returns
        -------
        Matrix | Vector
            A new matrix or vector where the target row has been replaced by
            target row + scalar * source row.

        Raises
        ------
        TypeError
            If target is not a Matrix or Vector instance, or scalar is not a number.
        ValueError
            If indices are out of range or target equals source.

        Examples
        --------
        >>> m = Matrix([[2, 1], [6, 4]])
        >>> print(RowAdd(target=1, source=0, scalar=-3).apply(m))
        [[2, 1],
         [0, 1]]
        >>> v = Vector([2, 6])
        >>> print(RowAdd(target=1, source=0, scalar=-3).apply(v))
        [2, 0]
        """
        self._validate_target(target)
        n = target.dims if isinstance(target, Vector) else target.rows
        self._validate_indices(n)
        self._validate_scalar()

        if isinstance(target, Vector):
            result = target.copy()
            result[self.target] = target[self.target] + self.scalar * target[self.source]
            return result

        return self.elementary_matrix(target.rows) @ target

    def inverse(self) -> RowAdd:
        """
        Return the inverse of this row addition.

        The inverse subtracts the same scalar multiple of the source row
        from the target row, which restores the original values.

        Returns
        -------
        RowAdd
            A new RowAdd with the same rows and negated scalar.

        Examples
        --------
        >>> op = RowAdd(target=1, source=0, scalar=-3)
        >>> op.inverse()
        RowAdd(target=1, source=0, scalar=3)
        """
        return RowAdd(self.target, self.source, -self.scalar)

    def __str__(self) -> str:
        return f"R{self.target} -> R{self.target} + ({self.scalar}) * R{self.source}"

    def __repr__(self) -> str:
        return (
            f"RowAdd(target={self.target}, source={self.source}, scalar={self.scalar})"
        )

apply(target)

Add scalar times the source row to the target row, returning the result.

Parameters:

Name Type Description Default
target Matrix | Vector

The matrix or vector to operate on.

required

Returns:

Type Description
Matrix | Vector

A new matrix or vector where the target row has been replaced by target row + scalar * source row.

Raises:

Type Description
TypeError

If target is not a Matrix or Vector instance, or scalar is not a number.

ValueError

If indices are out of range or target equals source.

Examples:

>>> m = Matrix([[2, 1], [6, 4]])
>>> print(RowAdd(target=1, source=0, scalar=-3).apply(m))
[[2, 1],
 [0, 1]]
>>> v = Vector([2, 6])
>>> print(RowAdd(target=1, source=0, scalar=-3).apply(v))
[2, 0]
Source code in panchi/algorithms/row_operations.py
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
def apply(self, target: Matrix | Vector) -> Matrix | Vector:
    """
    Add scalar times the source row to the target row, returning the result.

    Parameters
    ----------
    target : Matrix | Vector
        The matrix or vector to operate on.

    Returns
    -------
    Matrix | Vector
        A new matrix or vector where the target row has been replaced by
        target row + scalar * source row.

    Raises
    ------
    TypeError
        If target is not a Matrix or Vector instance, or scalar is not a number.
    ValueError
        If indices are out of range or target equals source.

    Examples
    --------
    >>> m = Matrix([[2, 1], [6, 4]])
    >>> print(RowAdd(target=1, source=0, scalar=-3).apply(m))
    [[2, 1],
     [0, 1]]
    >>> v = Vector([2, 6])
    >>> print(RowAdd(target=1, source=0, scalar=-3).apply(v))
    [2, 0]
    """
    self._validate_target(target)
    n = target.dims if isinstance(target, Vector) else target.rows
    self._validate_indices(n)
    self._validate_scalar()

    if isinstance(target, Vector):
        result = target.copy()
        result[self.target] = target[self.target] + self.scalar * target[self.source]
        return result

    return self.elementary_matrix(target.rows) @ target

elementary_matrix(n)

Return the n×n elementary matrix for this row addition.

Constructed from the identity matrix with scalar placed at position [target, source]. This encodes the fact that left-multiplying by E replaces row target with row target plus scalar times row source.

Parameters:

Name Type Description Default
n int

Size of the elementary matrix. Must be at least 2 and large enough so that both row indices are in range.

required

Returns:

Type Description
Matrix

An n×n matrix identical to the identity except that entry [target, source] equals scalar.

Raises:

Type Description
TypeError

If n is not an integer, or scalar is not a number.

ValueError

If n < 2, indices are out of range, or target equals source.

Examples:

>>> op = RowAdd(target=2, source=0, scalar=4)
>>> print(op.elementary_matrix(3))
[[1, 0, 0],
 [0, 1, 0],
 [4, 0, 1]]
Source code in panchi/algorithms/row_operations.py
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
def elementary_matrix(self, n: int) -> Matrix:
    """
    Return the n×n elementary matrix for this row addition.

    Constructed from the identity matrix with scalar placed at position
    [target, source]. This encodes the fact that left-multiplying by E
    replaces row target with row target plus scalar times row source.

    Parameters
    ----------
    n : int
        Size of the elementary matrix. Must be at least 2 and large
        enough so that both row indices are in range.

    Returns
    -------
    Matrix
        An n×n matrix identical to the identity except that entry
        [target, source] equals scalar.

    Raises
    ------
    TypeError
        If n is not an integer, or scalar is not a number.
    ValueError
        If n < 2, indices are out of range, or target equals source.

    Examples
    --------
    >>> op = RowAdd(target=2, source=0, scalar=4)
    >>> print(op.elementary_matrix(3))
    [[1, 0, 0],
     [0, 1, 0],
     [4, 0, 1]]
    """
    self._validate_n(n)
    self._validate_indices(n)
    self._validate_scalar()

    grid: Matrix = identity(n)
    grid[self.target][self.source] = self.scalar

    return grid

inverse()

Return the inverse of this row addition.

The inverse subtracts the same scalar multiple of the source row from the target row, which restores the original values.

Returns:

Type Description
RowAdd

A new RowAdd with the same rows and negated scalar.

Examples:

>>> op = RowAdd(target=1, source=0, scalar=-3)
>>> op.inverse()
RowAdd(target=1, source=0, scalar=3)
Source code in panchi/algorithms/row_operations.py
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
def inverse(self) -> RowAdd:
    """
    Return the inverse of this row addition.

    The inverse subtracts the same scalar multiple of the source row
    from the target row, which restores the original values.

    Returns
    -------
    RowAdd
        A new RowAdd with the same rows and negated scalar.

    Examples
    --------
    >>> op = RowAdd(target=1, source=0, scalar=-3)
    >>> op.inverse()
    RowAdd(target=1, source=0, scalar=3)
    """
    return RowAdd(self.target, self.source, -self.scalar)

Reductions

panchi.algorithms.ref(matrix)

Reduce a matrix to row echelon form using Gaussian elimination.

Applies a sequence of elementary row operations to produce an upper triangular form where each pivot is to the right of the pivot in the row above it, and all entries below each pivot are zero. The pivot values are not normalised to 1.

Parameters:

Name Type Description Default
matrix Matrix

The matrix to reduce. Not modified by this function.

required

Returns:

Type Description
Reduction

A Reduction object containing the original matrix, the REF result, the ordered list of row operations applied, the pivot positions as (row, col) tuples, and the form label 'REF'.

Examples:

>>> m = Matrix([[1, 2, 3], [2, 5, 7], [0, 1, 2]])
>>> reduction = ref(m)
>>> print(reduction.result)
[[1, 2, 3],
 [0, 1, 1],
 [0, 0, 1]]
>>> reduction.rank
3
>>> reduction.pivots
[(0, 0), (1, 1), (2, 2)]
Source code in panchi/algorithms/reductions.py
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
def ref(matrix: Matrix) -> Reduction:
    """
    Reduce a matrix to row echelon form using Gaussian elimination.

    Applies a sequence of elementary row operations to produce an upper
    triangular form where each pivot is to the right of the pivot in the
    row above it, and all entries below each pivot are zero. The pivot
    values are not normalised to 1.

    Parameters
    ----------
    matrix : Matrix
        The matrix to reduce. Not modified by this function.

    Returns
    -------
    Reduction
        A Reduction object containing the original matrix, the REF result,
        the ordered list of row operations applied, the pivot positions as
        (row, col) tuples, and the form label 'REF'.

    Examples
    --------
    >>> m = Matrix([[1, 2, 3], [2, 5, 7], [0, 1, 2]])
    >>> reduction = ref(m)
    >>> print(reduction.result)
    [[1, 2, 3],
     [0, 1, 1],
     [0, 0, 1]]
    >>> reduction.rank
    3
    >>> reduction.pivots
    [(0, 0), (1, 1), (2, 2)]
    """
    result = matrix.copy()
    operations = []
    pivots = []
    i = 0
    for j in range(matrix.cols):
        if i >= matrix.rows:
            break
        result, swap_operations = _swap_pivot(i, j, result)
        operations += swap_operations
        if result[i][j] == 0:
            continue

        result, addition_operations = _add_below_pivot(i, j, result)
        operations += addition_operations
        pivots.append((i, j))
        i += 1

    return Reduction(matrix, result, operations, pivots, "REF")

panchi.algorithms.rref(matrix)

Reduce a matrix to reduced row echelon form using Gauss-Jordan elimination.

First reduces to REF via Gaussian elimination, then applies back-substitution to clear all entries above each pivot and scales each pivot row so that the pivot value equals 1. The result is unique for any given matrix.

Parameters:

Name Type Description Default
matrix Matrix

The matrix to reduce. Not modified by this function.

required

Returns:

Type Description
Reduction

A Reduction object containing the original matrix, the RREF result, the complete ordered list of row operations applied (including those from the initial REF step), the pivot positions as (row, col) tuples, and the form label 'RREF'.

Examples:

>>> m = Matrix([[1, 2, 3], [2, 5, 7], [0, 1, 2]])
>>> reduction = rref(m)
>>> print(reduction.result)
[[1, 0, 0],
 [0, 1, 0],
 [0, 0, 1]]
>>> reduction.rank
3
>>> reduction.nullity
0
Source code in panchi/algorithms/reductions.py
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
def rref(matrix: Matrix) -> Reduction:
    """
    Reduce a matrix to reduced row echelon form using Gauss-Jordan elimination.

    First reduces to REF via Gaussian elimination, then applies back-substitution
    to clear all entries above each pivot and scales each pivot row so that the
    pivot value equals 1. The result is unique for any given matrix.

    Parameters
    ----------
    matrix : Matrix
        The matrix to reduce. Not modified by this function.

    Returns
    -------
    Reduction
        A Reduction object containing the original matrix, the RREF result,
        the complete ordered list of row operations applied (including those
        from the initial REF step), the pivot positions as (row, col) tuples,
        and the form label 'RREF'.

    Examples
    --------
    >>> m = Matrix([[1, 2, 3], [2, 5, 7], [0, 1, 2]])
    >>> reduction = rref(m)
    >>> print(reduction.result)
    [[1, 0, 0],
     [0, 1, 0],
     [0, 0, 1]]
    >>> reduction.rank
    3
    >>> reduction.nullity
    0
    """
    gaussian_step = ref(matrix)
    result = gaussian_step.result
    operations = gaussian_step.steps
    pivots = gaussian_step.pivots
    for i, j in pivots:
        result, scale_operations = _scale_pivot(i, j, result)
        operations += scale_operations
        result, addition_operations = _add_above_pivot(i, j, result)
        operations += addition_operations

    return Reduction(matrix, result, operations, pivots, "RREF")

Decompositions

panchi.algorithms.lu(matrix)

Compute the LU decomposition of a square matrix with partial pivoting.

Factors the matrix into a lower triangular matrix L, an upper triangular matrix U, and a permutation matrix P such that P @ matrix == L @ U.

Partial pivoting swaps rows before each elimination step to place the largest available entry in the pivot column at the pivot position. This improves numerical stability and avoids division by zero or near-zero values. The swaps are recorded in P so the factorisation relationship holds exactly.

L is lower triangular with ones on the diagonal. Its off-diagonal entries are the elimination multipliers used during Gaussian elimination. U is the row echelon form of P @ matrix.

Parameters:

Name Type Description Default
matrix Matrix

The square matrix to decompose.

required

Returns:

Type Description
LUDecomposition

A result object containing the original matrix, L, U, P, and the ordered list of row operations applied during elimination.

Examples:

>>> A = Matrix([[2, 1], [4, 3]])
>>> decomp = lu(A)
>>> decomp.permutation @ A == decomp.lower @ decomp.upper
True
>>> print(decomp.lower)
[[1, 0],
 [2.0, 1]]
>>> print(decomp.upper)
[[2, 1],
 [0.0, 1.0]]
Source code in panchi/algorithms/decompositions.py
 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
def lu(matrix: Matrix) -> LUDecomposition:
    """
    Compute the LU decomposition of a square matrix with partial pivoting.

    Factors the matrix into a lower triangular matrix L, an upper triangular
    matrix U, and a permutation matrix P such that P @ matrix == L @ U.

    Partial pivoting swaps rows before each elimination step to place the
    largest available entry in the pivot column at the pivot position. This
    improves numerical stability and avoids division by zero or near-zero
    values. The swaps are recorded in P so the factorisation relationship
    holds exactly.

    L is lower triangular with ones on the diagonal. Its off-diagonal
    entries are the elimination multipliers used during Gaussian elimination.
    U is the row echelon form of P @ matrix.

    Parameters
    ----------
    matrix : Matrix
        The square matrix to decompose.

    Returns
    -------
    LUDecomposition
        A result object containing the original matrix, L, U, P, and the
        ordered list of row operations applied during elimination.

    Examples
    --------
    >>> A = Matrix([[2, 1], [4, 3]])
    >>> decomp = lu(A)
    >>> decomp.permutation @ A == decomp.lower @ decomp.upper
    True
    >>> print(decomp.lower)
    [[1, 0],
     [2.0, 1]]
    >>> print(decomp.upper)
    [[2, 1],
     [0.0, 1.0]]
    """
    matrix_ref = ref(matrix)
    n = matrix.rows
    steps = matrix_ref.steps
    l = _calculate_l(n, steps)
    u = matrix_ref.result
    p = _calculate_p(n, steps)
    return LUDecomposition(matrix, l, u, p, steps)

Solvers

panchi.algorithms.inverse(matrix)

Compute the inverse of a square, invertible matrix.

Reduces the matrix to RREF using Gauss-Jordan elimination and replays the recorded row operations on the identity matrix to construct A⁻¹. The matrix must be square and have full rank; otherwise it is singular and no inverse exists.

Parameters:

Name Type Description Default
matrix Matrix

The matrix to invert. Must be square and have full rank.

required

Returns:

Type Description
InverseResult

An object containing the original matrix, the computed inverse, and the sequence of row operations used.

Raises:

Type Description
TypeError

If matrix is not a Matrix instance.

ValueError

If matrix is not square, or if matrix is singular (rank < n).

Examples:

>>> m = Matrix([[1, 2], [3, 4]])
>>> result = inverse(m)
>>> print(result.inverse)
[[-2.0, 1.0],
 [1.5, -0.5]]
Source code in panchi/algorithms/matrix_operations.py
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
def inverse(matrix: Matrix) -> InverseResult:
    """
    Compute the inverse of a square, invertible matrix.

    Reduces the matrix to RREF using Gauss-Jordan elimination and replays
    the recorded row operations on the identity matrix to construct A⁻¹.
    The matrix must be square and have full rank; otherwise it is singular
    and no inverse exists.

    Parameters
    ----------
    matrix : Matrix
        The matrix to invert. Must be square and have full rank.

    Returns
    -------
    InverseResult
        An object containing the original matrix, the computed inverse, and
        the sequence of row operations used.

    Raises
    ------
    TypeError
        If matrix is not a Matrix instance.
    ValueError
        If matrix is not square, or if matrix is singular (rank < n).

    Examples
    --------
    >>> m = Matrix([[1, 2], [3, 4]])
    >>> result = inverse(m)
    >>> print(result.inverse)
    [[-2.0, 1.0],
     [1.5, -0.5]]
    """
    if not isinstance(matrix, Matrix):
        raise TypeError(
            f"Expected a Matrix, but got {type(matrix).__name__}. "
            f"Inverse is only defined for Matrix objects."
        )
    if not matrix.is_square:
        raise ValueError(
            f"Cannot compute the inverse of a non-square matrix. "
            f"Your matrix is {matrix.rows}×{matrix.cols}. "
            f"Inverse is only defined for square matrices (n×n)."
        )
    n = matrix.rows
    matrix_rref = rref(matrix)
    if matrix_rref.rank != n:
        raise ValueError(
            f"Cannot compute the inverse of a singular matrix. "
            f"Your matrix has rank {matrix_rref.rank}, but must have rank {n}. "
            f"Only matrices with full rank are invertible."
        )
    steps = matrix_rref.steps
    inv = _calculate_inverse(n, steps)
    return InverseResult(matrix, inv, steps)

panchi.algorithms.solve(A, b)

Solve the linear system Ax = b.

Reduces A to RREF and applies the same row operations to b. The system's status is determined by inspecting the reduced forms: an inconsistent row (zero row in A with a non-zero corresponding entry in b) means no solution exists; fewer pivots than variables means infinitely many solutions exist; otherwise a unique solution is extracted from the pivot rows of the transformed b.

Parameters:

Name Type Description Default
A Matrix

The coefficient matrix in the system Ax = b.

required
b Vector

The right-hand side vector in the system Ax = b.

required

Returns:

Type Description
Solution

An object containing the original matrix and vector, the status ('unique', 'infinite', or 'inconsistent'), the solution vector if unique, and the row operations applied.

Raises:

Type Description
TypeError

If A is not a Matrix instance, or b is not a Vector instance.

ValueError

If the number of rows in A does not match the length of b.

Examples:

>>> A = Matrix([[2, 1], [5, 3]])
>>> b = Vector([1, 2])
>>> result = solve(A, b)
>>> result.status
'unique'
>>> result.solution
[1.0, -1.0]
Source code in panchi/algorithms/matrix_operations.py
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
def solve(A: Matrix, b: Vector) -> Solution:
    """
    Solve the linear system Ax = b.

    Reduces A to RREF and applies the same row operations to b. The
    system's status is determined by inspecting the reduced forms: an
    inconsistent row (zero row in A with a non-zero corresponding entry
    in b) means no solution exists; fewer pivots than variables means
    infinitely many solutions exist; otherwise a unique solution is
    extracted from the pivot rows of the transformed b.

    Parameters
    ----------
    A : Matrix
        The coefficient matrix in the system Ax = b.
    b : Vector
        The right-hand side vector in the system Ax = b.

    Returns
    -------
    Solution
        An object containing the original matrix and vector, the status
        ('unique', 'infinite', or 'inconsistent'), the solution vector
        if unique, and the row operations applied.

    Raises
    ------
    TypeError
        If A is not a Matrix instance, or b is not a Vector instance.
    ValueError
        If the number of rows in A does not match the length of b.

    Examples
    --------
    >>> A = Matrix([[2, 1], [5, 3]])
    >>> b = Vector([1, 2])
    >>> result = solve(A, b)
    >>> result.status
    'unique'
    >>> result.solution
    [1.0, -1.0]
    """
    if not isinstance(A, Matrix):
        raise TypeError(
            f"Expected a Matrix for A, but got {type(A).__name__}. "
            f"Solve is only defined for Matrix objects."
        )
    if not isinstance(b, Vector):
        raise TypeError(
            f"Expected a Vector for b, but got {type(b).__name__}. "
            f"Solve is only defined for Vector objects."
        )
    if A.rows != b.dims:
        raise ValueError(
            f"The number of rows in A must match the length of b. "
            f"A has {A.rows} rows but b has {b.dims} entries."
        )

    matrix_rref = rref(A)
    steps = matrix_rref.steps

    applied_b = b
    for step in steps:
        applied_b = step.apply(applied_b)

    if _inconsistent_rows(matrix_rref, applied_b):
        return Solution(A, b, "inconsistent", None, steps)

    if matrix_rref.nullity > 0:
        return Solution(A, b, "infinite", None, steps)

    pivot_row_indices = [row for row, _ in sorted(matrix_rref.pivots, key=lambda p: p[1])]
    solution = Vector([applied_b[row] for row in pivot_row_indices])
    return Solution(A, b, "unique", solution, steps)

panchi.algorithms.determinant_lu(matrix)

Compute the determinant of a square matrix using LU decomposition.

Factors the matrix into P, L, and U using partial pivoting, then multiplies the main diagonal entries of U by the parity of the permutation. Each row swap in P contributes a factor of -1 to the determinant, so the sign is adjusted by counting the number of swaps performed during factorization.

Parameters:

Name Type Description Default
matrix Matrix

The matrix whose determinant will be computed. Must be square.

required

Returns:

Type Description
float

The determinant of the matrix.

Raises:

Type Description
TypeError

If matrix is not a Matrix instance.

ValueError

If matrix is not square.

Examples:

>>> determinant_lu(Matrix([[1, 2], [3, 4]]))
-2.0
>>> determinant_lu(Matrix([[0, 1], [1, 2]]))
-1.0
See Also

Matrix.determinant : Determinant via cofactor expansion.

Source code in panchi/algorithms/matrix_operations.py
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
def determinant_lu(matrix: Matrix) -> float:
    """
    Compute the determinant of a square matrix using LU decomposition.

    Factors the matrix into P, L, and U using partial pivoting, then
    multiplies the main diagonal entries of U by the parity of the
    permutation. Each row swap in P contributes a factor of -1 to the
    determinant, so the sign is adjusted by counting the number of swaps
    performed during factorization.

    Parameters
    ----------
    matrix : Matrix
        The matrix whose determinant will be computed. Must be square.

    Returns
    -------
    float
        The determinant of the matrix.

    Raises
    ------
    TypeError
        If matrix is not a Matrix instance.
    ValueError
        If matrix is not square.

    Examples
    --------
    >>> determinant_lu(Matrix([[1, 2], [3, 4]]))
    -2.0
    >>> determinant_lu(Matrix([[0, 1], [1, 2]]))
    -1.0

    See Also
    --------
    Matrix.determinant : Determinant via cofactor expansion.
    """
    if not isinstance(matrix, Matrix):
        raise TypeError(
            f"Expected a Matrix, but got {type(matrix).__name__}. "
            f"Determinant is only defined for Matrix objects."
        )
    if not matrix.is_square:
        raise ValueError(
            f"Cannot compute the determinant of a non-square matrix. "
            f"Your matrix is {matrix.rows}×{matrix.cols}. "
            f"Determinants are only defined for square matrices (n×n)."
        )
    matrix_lu = lu(matrix)
    parity = _swap_parity(matrix_lu.steps)
    upper_diagonal_product = _main_diagonal_product(matrix_lu.upper)
    return parity * upper_diagonal_product

Vector space operations

panchi.algorithms.orthogonal_complement(space)

Compute the orthogonal complement of a vector space.

The orthogonal complement of a subspace W of R^n is the set of all vectors in R^n that are orthogonal to every vector in W. It is computed as the null space of the matrix whose rows are the basis vectors of W.

Parameters:

Name Type Description Default
space VectorSpace

The subspace whose orthogonal complement is to be computed.

required

Returns:

Type Description
VectorSpace

A VectorSpace representing the orthogonal complement. If the input space spans all of R^n, returns a VectorSpace containing only the zero vector (dimension 0).

Raises:

Type Description
TypeError

If space is not a VectorSpace.

Examples:

>>> v1 = Vector([1, 0, 0])
>>> v2 = Vector([0, 1, 0])
>>> vs = VectorSpace([v1, v2])
>>> comp = orthogonal_complement(vs)
>>> comp.dims
1
>>> comp.basis[0]
Vector([0, 0, 1])
Source code in panchi/algorithms/vector_operations.py
 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
def orthogonal_complement(space: VectorSpace) -> VectorSpace:
    """
    Compute the orthogonal complement of a vector space.

    The orthogonal complement of a subspace W of R^n is the set of all
    vectors in R^n that are orthogonal to every vector in W. It is computed
    as the null space of the matrix whose rows are the basis vectors of W.

    Parameters
    ----------
    space : VectorSpace
        The subspace whose orthogonal complement is to be computed.

    Returns
    -------
    VectorSpace
        A VectorSpace representing the orthogonal complement. If the input
        space spans all of R^n, returns a VectorSpace containing only the
        zero vector (dimension 0).

    Raises
    ------
    TypeError
        If space is not a VectorSpace.

    Examples
    --------
    >>> v1 = Vector([1, 0, 0])
    >>> v2 = Vector([0, 1, 0])
    >>> vs = VectorSpace([v1, v2])
    >>> comp = orthogonal_complement(vs)
    >>> comp.dims
    1
    >>> comp.basis[0]
    Vector([0, 0, 1])
    """
    from panchi.primitives.vector_space import VectorSpace
    from panchi.primitives.matrix import Matrix
    from panchi.algorithms.reductions import rref

    if not isinstance(space, VectorSpace):
        raise TypeError(
            f"orthogonal_complement() requires a VectorSpace. "
            f"Got {type(space).__name__}."
        )

    basis_vectors = space.basis
    n = space.ambient_dims

    if not basis_vectors:
        return VectorSpace([Vector([1 if i == j else 0 for j in range(n)]) for i in range(n)])

    row_matrix = Matrix([v.to_list() for v in basis_vectors])
    reduction = rref(row_matrix)

    pivot_cols = {col for _, col in reduction.pivots}
    free_cols = [j for j in range(n) if j not in pivot_cols]

    if not free_cols:
        return VectorSpace([Vector([0] * n)])

    null_vectors = []
    for j in free_cols:
        components = [0] * n
        components[j] = 1
        for row, col in reduction.pivots:
            components[col] = -reduction.result[row][j]
        null_vectors.append(Vector(components))

    return VectorSpace(null_vectors)

Result types

panchi.algorithms.Reduction

The result of a row reduction performed on a matrix.

Stores the original matrix, the reduced form, every row operation applied as an ordered sequence of RowOperation objects, the pivot positions, and whether the result is in REF or RREF.

Parameters:

Name Type Description Default
original Matrix

The matrix before any row operations were applied.

required
result Matrix

The matrix after all row operations have been applied.

required
steps list[RowOperation]

The ordered sequence of elementary row operations that transforms original into result.

required
pivots list[tuple[int, int]]

The (row, col) positions of each pivot, in order of discovery.

required
form str

Either 'REF' or 'RREF', indicating which reduced form was computed.

required

Examples:

>>> A = Matrix([[1, 2], [3, 4]])
>>> reduction = ref(A)
>>> reduction.rank
2
>>> reduction.nullity
0
Source code in panchi/algorithms/results.py
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 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
class Reduction:
    """
    The result of a row reduction performed on a matrix.

    Stores the original matrix, the reduced form, every row operation
    applied as an ordered sequence of RowOperation objects, the pivot
    positions, and whether the result is in REF or RREF.

    Parameters
    ----------
    original : Matrix
        The matrix before any row operations were applied.
    result : Matrix
        The matrix after all row operations have been applied.
    steps : list[RowOperation]
        The ordered sequence of elementary row operations that transforms
        original into result.
    pivots : list[tuple[int, int]]
        The (row, col) positions of each pivot, in order of discovery.
    form : str
        Either 'REF' or 'RREF', indicating which reduced form was computed.

    Examples
    --------
    >>> A = Matrix([[1, 2], [3, 4]])
    >>> reduction = ref(A)
    >>> reduction.rank
    2
    >>> reduction.nullity
    0
    """

    def __init__(
        self,
        original: Matrix,
        result: Matrix,
        steps: list[RowOperation],
        pivots: list[tuple[int, int]],
        form: str,
    ) -> None:
        self.original = original
        self.result = result
        self.steps = steps
        self.pivots = pivots
        self.form = form

    @property
    def rank(self) -> int:
        """
        The rank of the matrix, equal to the number of pivot positions.

        Returns
        -------
        int
            Number of pivot positions found during reduction.
        """
        return len(self.pivots)

    @property
    def nullity(self) -> int:
        """
        The nullity of the matrix, equal to columns minus rank.

        By the rank-nullity theorem, rank + nullity equals the number
        of columns of the original matrix.

        Returns
        -------
        int
            Dimension of the null space.
        """
        return self.original.cols - self.rank

    def __str__(self) -> str:
        """
        Return a step-by-step walkthrough of the reduction.

        Shows the operation label and resulting matrix state after each
        step, followed by a summary of pivot positions, rank, and nullity.

        Returns
        -------
        str
            Human-readable reduction walkthrough.

        Examples
        --------
        >>> print(ref(Matrix([[1, 2], [3, 4]])))
        REF of 2×2 matrix — 1 steps, rank 2

        Step 1: R1 -> R1 + (-3.0) * R0
        [[1, 2],
         [0, -2.0]]
        ...
        """
        header: str = (
            f"{self.form} of {self.original.rows}×{self.original.cols} matrix"
            f" — {len(self.steps)} steps, rank {self.rank}\n"
        )

        current: Matrix = self.original.copy()
        steps_str: str = ""
        for i, step in enumerate(self.steps):
            current = step.apply(current)
            steps_str += f"\nStep {i + 1}: {step}\n{current}\n"

        footer: str = (
            f"\nResult:\n{self.result}\n"
            f"\nPivots: {self.pivots}\n"
            f"Rank: {self.rank}  |  Nullity: {self.nullity}"
        )

        return header + steps_str + footer

    def __repr__(self) -> str:
        """
        Return a concise data inspection string for this Reduction.

        Returns
        -------
        str
            Compact representation showing form, shape, rank, nullity,
            pivot positions, number of steps, and the result matrix.

        Examples
        --------
        >>> ref(Matrix([[1, 2], [3, 4]]))
        Reduction(form=REF, shape=2×2, rank=2, nullity=0, pivots=[(0, 0), (1, 1)], steps=1)
        [[1, 2],
         [0, -2.0]]
        """
        summary: str = (
            f"Reduction("
            f"form={self.form}, "
            f"shape={self.original.rows}×{self.original.cols}, "
            f"rank={self.rank}, "
            f"nullity={self.nullity}, "
            f"pivots={self.pivots}, "
            f"steps={len(self.steps)})"
        )

        return f"{summary}\n{self.result}"

nullity property

The nullity of the matrix, equal to columns minus rank.

By the rank-nullity theorem, rank + nullity equals the number of columns of the original matrix.

Returns:

Type Description
int

Dimension of the null space.

rank property

The rank of the matrix, equal to the number of pivot positions.

Returns:

Type Description
int

Number of pivot positions found during reduction.

__repr__()

Return a concise data inspection string for this Reduction.

Returns:

Type Description
str

Compact representation showing form, shape, rank, nullity, pivot positions, number of steps, and the result matrix.

Examples:

>>> ref(Matrix([[1, 2], [3, 4]]))
Reduction(form=REF, shape=2×2, rank=2, nullity=0, pivots=[(0, 0), (1, 1)], steps=1)
[[1, 2],
 [0, -2.0]]
Source code in panchi/algorithms/results.py
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
def __repr__(self) -> str:
    """
    Return a concise data inspection string for this Reduction.

    Returns
    -------
    str
        Compact representation showing form, shape, rank, nullity,
        pivot positions, number of steps, and the result matrix.

    Examples
    --------
    >>> ref(Matrix([[1, 2], [3, 4]]))
    Reduction(form=REF, shape=2×2, rank=2, nullity=0, pivots=[(0, 0), (1, 1)], steps=1)
    [[1, 2],
     [0, -2.0]]
    """
    summary: str = (
        f"Reduction("
        f"form={self.form}, "
        f"shape={self.original.rows}×{self.original.cols}, "
        f"rank={self.rank}, "
        f"nullity={self.nullity}, "
        f"pivots={self.pivots}, "
        f"steps={len(self.steps)})"
    )

    return f"{summary}\n{self.result}"

__str__()

Return a step-by-step walkthrough of the reduction.

Shows the operation label and resulting matrix state after each step, followed by a summary of pivot positions, rank, and nullity.

Returns:

Type Description
str

Human-readable reduction walkthrough.

Examples:

>>> print(ref(Matrix([[1, 2], [3, 4]])))
REF of 2×2 matrix — 1 steps, rank 2

Step 1: R1 -> R1 + (-3.0) * R0 [[1, 2], [0, -2.0]] ...

Source code in panchi/algorithms/results.py
 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
def __str__(self) -> str:
    """
    Return a step-by-step walkthrough of the reduction.

    Shows the operation label and resulting matrix state after each
    step, followed by a summary of pivot positions, rank, and nullity.

    Returns
    -------
    str
        Human-readable reduction walkthrough.

    Examples
    --------
    >>> print(ref(Matrix([[1, 2], [3, 4]])))
    REF of 2×2 matrix — 1 steps, rank 2

    Step 1: R1 -> R1 + (-3.0) * R0
    [[1, 2],
     [0, -2.0]]
    ...
    """
    header: str = (
        f"{self.form} of {self.original.rows}×{self.original.cols} matrix"
        f" — {len(self.steps)} steps, rank {self.rank}\n"
    )

    current: Matrix = self.original.copy()
    steps_str: str = ""
    for i, step in enumerate(self.steps):
        current = step.apply(current)
        steps_str += f"\nStep {i + 1}: {step}\n{current}\n"

    footer: str = (
        f"\nResult:\n{self.result}\n"
        f"\nPivots: {self.pivots}\n"
        f"Rank: {self.rank}  |  Nullity: {self.nullity}"
    )

    return header + steps_str + footer

panchi.algorithms.LUDecomposition

The result of an LU decomposition with partial pivoting.

Stores the original matrix, the lower triangular matrix L, the upper triangular matrix U, and the permutation matrix P encoding any row swaps applied for numerical stability. The decomposition satisfies P @ original == L @ U.

Partial pivoting swaps rows before each elimination step so that the largest available entry in the pivot column is used as the pivot. This avoids division by small numbers and produces a more numerically stable result. The swaps are recorded in P so that the factorisation relationship is exact.

Parameters:

Name Type Description Default
original Matrix

The square matrix that was decomposed.

required
lower Matrix

The lower triangular matrix L with ones on the diagonal.

required
upper Matrix

The upper triangular matrix U produced by Gaussian elimination on P @ original.

required
permutation Matrix

The permutation matrix P encoding all row swaps performed, satisfying P @ original == L @ U.

required
steps list[RowOperation]

The ordered sequence of row operations applied to P @ original to produce U.

required

Examples:

>>> A = Matrix([[2, 1], [4, 3]])
>>> decomp = lu(A)
>>> decomp.lower @ decomp.upper == decomp.permutation @ A
True
Source code in panchi/algorithms/results.py
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
class LUDecomposition:
    """
    The result of an LU decomposition with partial pivoting.

    Stores the original matrix, the lower triangular matrix L, the upper
    triangular matrix U, and the permutation matrix P encoding any row
    swaps applied for numerical stability. The decomposition satisfies
    P @ original == L @ U.

    Partial pivoting swaps rows before each elimination step so that the
    largest available entry in the pivot column is used as the pivot.
    This avoids division by small numbers and produces a more numerically
    stable result. The swaps are recorded in P so that the factorisation
    relationship is exact.

    Parameters
    ----------
    original : Matrix
        The square matrix that was decomposed.
    lower : Matrix
        The lower triangular matrix L with ones on the diagonal.
    upper : Matrix
        The upper triangular matrix U produced by Gaussian elimination
        on P @ original.
    permutation : Matrix
        The permutation matrix P encoding all row swaps performed,
        satisfying P @ original == L @ U.
    steps : list[RowOperation]
        The ordered sequence of row operations applied to P @ original
        to produce U.

    Examples
    --------
    >>> A = Matrix([[2, 1], [4, 3]])
    >>> decomp = lu(A)
    >>> decomp.lower @ decomp.upper == decomp.permutation @ A
    True
    """

    def __init__(
        self,
        original: Matrix,
        lower: Matrix,
        upper: Matrix,
        permutation: Matrix,
        steps: list[RowOperation],
    ) -> None:
        self.original = original
        self.lower = lower
        self.upper = upper
        self.permutation = permutation
        self.steps = steps

    def __str__(self) -> str:
        """
        Return a readable summary of the LU decomposition.

        Shows P, L, and U individually and states the factorisation
        relationship P @ A = L @ U.

        Returns
        -------
        str
            Human-readable decomposition summary.

        Examples
        --------
        >>> print(lu(Matrix([[2, 1], [4, 3]])))
        LU decomposition of 2×2 matrix — 1 steps

        P @ A = L @ U

        P:
        [[1, 0],
         [0, 1]]

        A:
        [[2, 1],
         [4, 3]]

        L:
        [[1, 0],
         [2.0, 1]]

        U:
        [[2, 1],
         [0.0, 1.0]]
        """
        header: str = (
            f"LU decomposition of "
            f"{self.original.rows}×{self.original.cols} matrix"
            f" — {len(self.steps)} steps\n"
        )

        body: str = (
            f"\nP @ A = L @ U\n"
            f"\nP:\n{self.permutation}\n"
            f"\nA:\n{self.original}\n"
            f"\nL:\n{self.lower}\n"
            f"\nU:\n{self.upper}"
        )

        return header + body

    def __repr__(self) -> str:
        """
        Return a concise data inspection string for this LUDecomposition.

        Returns
        -------
        str
            Compact representation showing shape and number of steps.

        Examples
        --------
        >>> lu(Matrix([[2, 1], [4, 3]]))
        LUDecomposition(shape=2×2, steps=1)
        """
        return (
            f"LUDecomposition("
            f"shape={self.original.rows}×{self.original.cols}, "
            f"steps={len(self.steps)})"
        )

__repr__()

Return a concise data inspection string for this LUDecomposition.

Returns:

Type Description
str

Compact representation showing shape and number of steps.

Examples:

>>> lu(Matrix([[2, 1], [4, 3]]))
LUDecomposition(shape=2×2, steps=1)
Source code in panchi/algorithms/results.py
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
def __repr__(self) -> str:
    """
    Return a concise data inspection string for this LUDecomposition.

    Returns
    -------
    str
        Compact representation showing shape and number of steps.

    Examples
    --------
    >>> lu(Matrix([[2, 1], [4, 3]]))
    LUDecomposition(shape=2×2, steps=1)
    """
    return (
        f"LUDecomposition("
        f"shape={self.original.rows}×{self.original.cols}, "
        f"steps={len(self.steps)})"
    )

__str__()

Return a readable summary of the LU decomposition.

Shows P, L, and U individually and states the factorisation relationship P @ A = L @ U.

Returns:

Type Description
str

Human-readable decomposition summary.

Examples:

>>> print(lu(Matrix([[2, 1], [4, 3]])))
LU decomposition of 2×2 matrix — 1 steps

P @ A = L @ U

P: [[1, 0], [0, 1]]

A: [[2, 1], [4, 3]]

L: [[1, 0], [2.0, 1]]

U: [[2, 1], [0.0, 1.0]]

Source code in panchi/algorithms/results.py
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
def __str__(self) -> str:
    """
    Return a readable summary of the LU decomposition.

    Shows P, L, and U individually and states the factorisation
    relationship P @ A = L @ U.

    Returns
    -------
    str
        Human-readable decomposition summary.

    Examples
    --------
    >>> print(lu(Matrix([[2, 1], [4, 3]])))
    LU decomposition of 2×2 matrix — 1 steps

    P @ A = L @ U

    P:
    [[1, 0],
     [0, 1]]

    A:
    [[2, 1],
     [4, 3]]

    L:
    [[1, 0],
     [2.0, 1]]

    U:
    [[2, 1],
     [0.0, 1.0]]
    """
    header: str = (
        f"LU decomposition of "
        f"{self.original.rows}×{self.original.cols} matrix"
        f" — {len(self.steps)} steps\n"
    )

    body: str = (
        f"\nP @ A = L @ U\n"
        f"\nP:\n{self.permutation}\n"
        f"\nA:\n{self.original}\n"
        f"\nL:\n{self.lower}\n"
        f"\nU:\n{self.upper}"
    )

    return header + body

panchi.algorithms.InverseResult

The result of a matrix inversion via Gauss-Jordan elimination.

Stores the original matrix, its inverse, and the row operations applied during reduction of the augmented matrix [A | I]. The inverse satisfies original @ inverse == identity(n) == inverse @ original.

Parameters:

Name Type Description Default
original Matrix

The square invertible matrix that was inverted.

required
inverse Matrix

The inverse of the original matrix.

required
steps list[RowOperation]

The ordered sequence of row operations applied to the augmented matrix [A | I] to produce [I | A⁻¹].

required

Examples:

>>> A = Matrix([[1, 2], [3, 4]])
>>> result = inverse(A)
>>> result.original @ result.inverse == identity(2)
True
Source code in panchi/algorithms/results.py
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
class InverseResult:
    """
    The result of a matrix inversion via Gauss-Jordan elimination.

    Stores the original matrix, its inverse, and the row operations applied
    during reduction of the augmented matrix [A | I]. The inverse satisfies
    original @ inverse == identity(n) == inverse @ original.

    Parameters
    ----------
    original : Matrix
        The square invertible matrix that was inverted.
    inverse : Matrix
        The inverse of the original matrix.
    steps : list[RowOperation]
        The ordered sequence of row operations applied to the augmented
        matrix [A | I] to produce [I | A⁻¹].

    Examples
    --------
    >>> A = Matrix([[1, 2], [3, 4]])
    >>> result = inverse(A)
    >>> result.original @ result.inverse == identity(2)
    True
    """

    def __init__(
        self,
        original: Matrix,
        inverse: Matrix,
        steps: list[RowOperation],
    ) -> None:
        self.original = original
        self.inverse = inverse
        self.steps = steps

    def __str__(self) -> str:
        """
        Return a readable summary of the inversion.

        Shows the number of steps taken and the computed inverse matrix.

        Returns
        -------
        str
            Human-readable inversion summary.

        Examples
        --------
        >>> print(inverse(Matrix([[1, 2], [3, 4]])))
        Inverse of 2×2 matrix — 6 steps

        Inverse:
        [[-2.0, 1.0],
         [1.5, -0.5]]
        """
        header: str = (
            f"Inverse of {self.original.rows}×{self.original.cols} matrix"
            f" — {len(self.steps)} steps\n"
        )

        return header + f"\nInverse:\n{self.inverse}"

    def __repr__(self) -> str:
        """
        Return a concise data inspection string for this InverseResult.

        Returns
        -------
        str
            Compact representation showing shape, number of steps,
            and the inverse matrix.

        Examples
        --------
        >>> inverse(Matrix([[1, 2], [3, 4]]))
        InverseResult(shape=2×2, steps=6)
        [[-2.0, 1.0],
         [1.5, -0.5]]
        """
        summary: str = (
            f"InverseResult("
            f"shape={self.original.rows}×{self.original.cols}, "
            f"steps={len(self.steps)})"
        )

        return f"{summary}\n{self.inverse}"

__repr__()

Return a concise data inspection string for this InverseResult.

Returns:

Type Description
str

Compact representation showing shape, number of steps, and the inverse matrix.

Examples:

>>> inverse(Matrix([[1, 2], [3, 4]]))
InverseResult(shape=2×2, steps=6)
[[-2.0, 1.0],
 [1.5, -0.5]]
Source code in panchi/algorithms/results.py
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
def __repr__(self) -> str:
    """
    Return a concise data inspection string for this InverseResult.

    Returns
    -------
    str
        Compact representation showing shape, number of steps,
        and the inverse matrix.

    Examples
    --------
    >>> inverse(Matrix([[1, 2], [3, 4]]))
    InverseResult(shape=2×2, steps=6)
    [[-2.0, 1.0],
     [1.5, -0.5]]
    """
    summary: str = (
        f"InverseResult("
        f"shape={self.original.rows}×{self.original.cols}, "
        f"steps={len(self.steps)})"
    )

    return f"{summary}\n{self.inverse}"

__str__()

Return a readable summary of the inversion.

Shows the number of steps taken and the computed inverse matrix.

Returns:

Type Description
str

Human-readable inversion summary.

Examples:

>>> print(inverse(Matrix([[1, 2], [3, 4]])))
Inverse of 2×2 matrix — 6 steps

Inverse: [[-2.0, 1.0], [1.5, -0.5]]

Source code in panchi/algorithms/results.py
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
def __str__(self) -> str:
    """
    Return a readable summary of the inversion.

    Shows the number of steps taken and the computed inverse matrix.

    Returns
    -------
    str
        Human-readable inversion summary.

    Examples
    --------
    >>> print(inverse(Matrix([[1, 2], [3, 4]])))
    Inverse of 2×2 matrix — 6 steps

    Inverse:
    [[-2.0, 1.0],
     [1.5, -0.5]]
    """
    header: str = (
        f"Inverse of {self.original.rows}×{self.original.cols} matrix"
        f" — {len(self.steps)} steps\n"
    )

    return header + f"\nInverse:\n{self.inverse}"

panchi.algorithms.Solution

The result of solving a linear system Ax = b.

Stores the coefficient matrix A, the right-hand side vector b, the solution status, the solution vector x if a unique solution exists, and the row operations applied during reduction of the augmented matrix [A | b].

The three possible statuses reflect the three fundamentally different outcomes a linear system can have:

  • 'unique': exactly one solution exists, stored in solution.
  • 'infinite': infinitely many solutions exist (underdetermined system).
  • 'inconsistent': no solution exists (the system is contradictory).

Parameters:

Name Type Description Default
original Matrix

The coefficient matrix A.

required
target Vector

The right-hand side vector b.

required
status str

One of 'unique', 'infinite', or 'inconsistent'.

required
solution Vector or None

The unique solution vector x satisfying A @ x == b, or None if the system does not have a unique solution.

required
steps list[RowOperation]

The ordered sequence of row operations applied to the augmented matrix [A | b] during reduction.

required

Examples:

>>> A = Matrix([[1, 2], [3, 4]])
>>> b = Vector([5, 6])
>>> result = solve(A, b)
>>> result.status
'unique'
>>> A @ result.solution == b
True
Source code in panchi/algorithms/results.py
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
class Solution:
    """
    The result of solving a linear system Ax = b.

    Stores the coefficient matrix A, the right-hand side vector b, the
    solution status, the solution vector x if a unique solution exists,
    and the row operations applied during reduction of the augmented
    matrix [A | b].

    The three possible statuses reflect the three fundamentally different
    outcomes a linear system can have:

    - 'unique': exactly one solution exists, stored in solution.
    - 'infinite': infinitely many solutions exist (underdetermined system).
    - 'inconsistent': no solution exists (the system is contradictory).

    Parameters
    ----------
    original : Matrix
        The coefficient matrix A.
    target : Vector
        The right-hand side vector b.
    status : str
        One of 'unique', 'infinite', or 'inconsistent'.
    solution : Vector or None
        The unique solution vector x satisfying A @ x == b, or None if
        the system does not have a unique solution.
    steps : list[RowOperation]
        The ordered sequence of row operations applied to the augmented
        matrix [A | b] during reduction.

    Examples
    --------
    >>> A = Matrix([[1, 2], [3, 4]])
    >>> b = Vector([5, 6])
    >>> result = solve(A, b)
    >>> result.status
    'unique'
    >>> A @ result.solution == b
    True
    """

    def __init__(
        self,
        original: Matrix,
        target: Vector,
        status: str,
        solution: Vector | None,
        steps: list[RowOperation],
    ) -> None:
        self.original = original
        self.target = target
        self.status = status
        self.solution = solution
        self.steps = steps

    def __str__(self) -> str:
        """
        Return a readable summary of the solution.

        Shows the system dimensions, the status, and the solution vector
        if one exists.

        Returns
        -------
        str
            Human-readable solution summary.

        Examples
        --------
        >>> print(solve(Matrix([[1, 2], [3, 4]]), Vector([5, 6])))
        Solution to 2×2 system — unique

        x = [-4.0, 4.5]
        """
        header: str = (
            f"Solution to "
            f"{self.original.rows}×{self.original.cols} system"
            f" — {self.status}\n"
        )

        if self.solution is not None:
            return header + f"\nx = {self.solution}"

        return header

    def __repr__(self) -> str:
        """
        Return a concise data inspection string for this Solution.

        Returns
        -------
        str
            Compact representation showing shape, status, and solution.

        Examples
        --------
        >>> solve(Matrix([[1, 2], [3, 4]]), Vector([5, 6]))
        Solution(shape=2×2, status=unique, solution=[-4.0, 4.5])
        """
        return (
            f"Solution("
            f"shape={self.original.rows}×{self.original.cols}, "
            f"status={self.status}, "
            f"solution={self.solution})"
        )

__repr__()

Return a concise data inspection string for this Solution.

Returns:

Type Description
str

Compact representation showing shape, status, and solution.

Examples:

>>> solve(Matrix([[1, 2], [3, 4]]), Vector([5, 6]))
Solution(shape=2×2, status=unique, solution=[-4.0, 4.5])
Source code in panchi/algorithms/results.py
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
def __repr__(self) -> str:
    """
    Return a concise data inspection string for this Solution.

    Returns
    -------
    str
        Compact representation showing shape, status, and solution.

    Examples
    --------
    >>> solve(Matrix([[1, 2], [3, 4]]), Vector([5, 6]))
    Solution(shape=2×2, status=unique, solution=[-4.0, 4.5])
    """
    return (
        f"Solution("
        f"shape={self.original.rows}×{self.original.cols}, "
        f"status={self.status}, "
        f"solution={self.solution})"
    )

__str__()

Return a readable summary of the solution.

Shows the system dimensions, the status, and the solution vector if one exists.

Returns:

Type Description
str

Human-readable solution summary.

Examples:

>>> print(solve(Matrix([[1, 2], [3, 4]]), Vector([5, 6])))
Solution to 2×2 system — unique

x = [-4.0, 4.5]

Source code in panchi/algorithms/results.py
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
def __str__(self) -> str:
    """
    Return a readable summary of the solution.

    Shows the system dimensions, the status, and the solution vector
    if one exists.

    Returns
    -------
    str
        Human-readable solution summary.

    Examples
    --------
    >>> print(solve(Matrix([[1, 2], [3, 4]]), Vector([5, 6])))
    Solution to 2×2 system — unique

    x = [-4.0, 4.5]
    """
    header: str = (
        f"Solution to "
        f"{self.original.rows}×{self.original.cols} system"
        f" — {self.status}\n"
    )

    if self.solution is not None:
        return header + f"\nx = {self.solution}"

    return header