Skip to content

Linear algebra (@ matrix multiply)

TSeries and MVTSeries overload Python's @ operator (PEP 465) so the upstream TimeSeriesEcon.jl idiom

A * t        # Julia: A is a Matrix, t is a TSeries

ports as

A @ t        # Python: A is a numpy.ndarray, t is a tsecon.TSeries

Both directions are supported (A @ t, t @ A, mvts @ A, A @ mvts, t @ t, mvts @ mvts).

What @ returns

A plain numpy.ndarray. Frequency / range / column-name labels are stripped. This matches the Julia upstream exactly: every method body in linalg.jl is of the shape op(_vals(A), _vals(B)), returning a bare Vector or Matrix. The upstream test x * x3 == _vals(x) * _vals(x3) documents the same contract.

If you want a labelled result, wrap explicitly:

out_vals = A @ t                                  # numpy.ndarray
out_t = TSeries(t.range, out_vals)                # back to a labelled TSeries

Element-wise multiplication keeps its existing semantics — * between two TSeries still does the range-intersection, frequency-checked broadcast (see TSeries reference).

What's intentionally not ported

  • transpose / adjoint. Julia's overloads return a 1×N row vector (from a TSeries) or a k × n matrix (from an MVTSeries) with all labels stripped. A .T property on TSeries / MVTSeries would have no clean semantics in Python — the row axis of an MVTSeries is time, not data, so a transposed object has no natural type to wrap. Users wanting the bare transpose write np.asarray(x).T.
  • \ / / (linear-solve). @ covers the common A @ t coefficient-matrix case. Callers needing a solve use numpy.linalg.solve directly: numpy.linalg.solve(A, np.asarray(t)).

Module reference

tsecon.linalg

Matrix multiplication on TSeries / MVTSeries.

Mirrors TimeSeriesEcon.jl/src/linalg.jl exactly. The Julia module overloads *, \ and / for every combination of TSeries / MVTSeries / AbstractMatrix; every method body is of the shape op(_vals(A), _vals(B)) — i.e. the operation strips frequency / range / column-name labels and returns the underlying Vector or Matrix (the upstream test x * x3 == _vals(x) * _vals(x3) confirms). The Python port keeps that semantics: @ returns a plain :class:numpy.ndarray; users who want the result back as a labelled object wrap it explicitly.

Spelling — @ (PEP 465) over Julia's *. Element-wise * is how NumPy / pandas / xarray spell broadcasting, and the existing __mul__ / __array_ufunc__ machinery already covers it (range intersection, frequency check, etc.). Overloading * for the matrix product would break that contract, so the dunders use @ exclusively; the Julia A * t idiom ports as A @ t (see docs/design/migration_from_julia.md).

Not ported:

  • transpose / adjoint. Julia's overloads also strip labels (transpose(t) returns a 1×N row vector; adjoint(mvts) a k × n matrix). A .T property would have no clean semantics on TSeries / MVTSeries — the row axis is time, not data, so a transposed object has no natural type to wrap into. Users wanting the bare transpose write np.asarray(t).T. Recorded as an intentional non-port.
  • \ / / (linear-solve overloads). @ covers the coefficient-matrix-times-series case that motivates the module; callers needing a solve use np.linalg.solve(A, np.asarray(t)) directly. Promote on demand.