Skip to content

v0.0.24

New: SymmetricMatrix

lythonic.symmetric.SymmetricMatrix is a square, string-keyed matrix whose two axes are the same Universe and whose value depends on an unordered pair of keys: a correlation or covariance matrix, a distance matrix, a similarity or adjacency matrix. ExposureMatrix could not express these, since its two axes are different universes and nothing kept the two halves in agreement.

Symmetry is structural, not validated. Only a lower triangle is stored, so an asymmetric value has nowhere to go — there is no symmetry check, no tolerance for how symmetric is symmetric enough, and no rule for which half wins.

Storage adapts to density: a near-full matrix is held as a dense triangle, a mostly-empty one as a dense diagonal plus sparse off-diagonal records. The variant is chosen by a fixed rule the caller cannot select or observe, so two matrices with the same content always encode identically and byte equality keeps tracking semantic equality.

The type is generic rather than correlation- or covariance-specific, and makes no positive semi-definiteness promise: that is a query on the numpy facade (is_psd, min_eigenvalue, eigenvalues), so construction and deserialization need no numpy and do no eigendecomposition. Every key needs an explicit diagonal value; build() raises naming any key that has none.

SymmetricMatrixBuilder is the only mutable object, following the split ExposureMatrix established. There is deliberately no row-wise write — under symmetry one key's row is part of every other key's row.

New: KeyedVector

lythonic.vector.KeyedVector is a Universe plus one value per key, in universe order. It fills the gap between the dict accessors, which keep the keys but drop the axis, and the array accessors, which keep the alignment but drop the keys and require numpy.

Unlike the matrix types, it stores NaN and the infinities — it is a far more general object, and computed results legitimately contain them. Two consequences follow: non-finite values serialize as "NaN", "Infinity" and "-Infinity" strings, which is valid JSON everywhere and round-trips losslessly; and equality is defined on the type rather than inherited, comparing values positionally with NaN matching NaN, so a vector loaded from JSON equals the one that was saved.

cast aligns a vector to another universe, dropping keys outside it and filling introduced keys with NaN — the one fill value that cannot pass for a measurement, since any arithmetic touching it yields NaN. Conversions sit behind np and pd facades; a pandas Series is an exact structural match, and from_series validates the index is unique and all strings.

The type is purely additive. ExposureMatrix's dict-returning accessors are unchanged, and KeyedVector.from_mapping bridges from them in one lossless call.

Documentation

  • Three how-to guides for the keyed data types: choosing between FrameData, ExposureMatrix, SymmetricMatrix and KeyedVector; the class-in / instance-out facade rule and per-library conversions; and composing the four, including building a covariance matrix from a correlation matrix and a volatility vector.
  • Reference pages for lythonic.symmetric and lythonic.vector.
  • CONTEXT.md gains Symmetric matrix, Pair, Diagonal, Keyed vector, Entry and Aligned; Universe notes that a matrix may name both axes with one universe.
  • The ExposureMatrix design sketch under docs/ideas/ is removed now that lythonic.exposure carries the design; its one unresolved question — that from_matrix cannot detect a transposed square array — moved to docs/open-questions.md, where it now covers both matrix types.

Decisions and tickets

New ADRs:

Closed tickets:

  • 9 SymmetricMatrix: square, string-keyed, symmetry-by-construction matrix

  • 10 KeyedVector: a universe plus one value per key