Input and output

JSON

to_json() returns a dictionary with profile, data, and dtype. The profile is labeled when the tensor has labels. from_json() rebuilds a tensor from profile and data. A names entry, when present, is the pair of domain and codomain axis names. JSON with no names key loads as an unnamed tensor.

save_to_file() writes that dictionary as JSON. load_from_file() reads it back.

import numpy as np
from fxtensor_salmon import FXTensor

json_data = {
    "profile": [[["a", "b"]], [["x", "y"]]],
    "data": [[0.2, 0.8], [0.6, 0.4]],
}
tensor = FXTensor.from_json(json_data)
assert tensor.labels == ([["a", "b"]], [["x", "y"]])
payload = tensor.to_json()
assert payload["profile"] == [[["a", "b"]], [["x", "y"]]]
assert payload["dtype"] == str(tensor.data.dtype)
assert FXTensor.from_json(payload) == tensor
import tempfile
from pathlib import Path

import numpy as np
from fxtensor_salmon import FXTensor

source = FXTensor(
    [[["a", "b"]], [["x", "y"]]],
    data=np.array([[0.2, 0.8], [0.6, 0.4]]),
)
with tempfile.TemporaryDirectory() as tmp:
    filename = str(Path(tmp) / "tensor.json")
    source.save_to_file(filename)
    loaded = FXTensor.load_from_file(filename)
assert loaded == source

Strands

from_strands() reads a map from a strand key to a weight. A key is a Python literal of nested lists of strings or integers, parsed without evaluating code; any other key raises ValueError. Labeled strands name the points. Numeric strands use 1-based indices, each index wrapped in its own list. Weights may be float or fractions.Fraction and are stored with float.

to_original_dict() goes the other way for the numeric form: nonzero entries become 1-based index lists. The weights are the NumPy scalars from data, and the profile is the numeric profile.

import numpy as np
from fractions import Fraction
from fxtensor_salmon import FXTensor

profile = [[2], [3]]
strands = {
    "[[[1]], [[1]]]": 0.1,
    "[[[1]], [[2]]]": 0.2,
    "[[[1]], [[3]]]": 0.7,
    "[[[2]], [[1]]]": 0.3,
    "[[[2]], [[2]]]": 0.3,
    "[[[2]], [[3]]]": 0.4,
}
tensor = FXTensor.from_strands(profile, strands)
assert np.allclose(
    tensor.data,
    [
        [0.1, 0.2, 0.7],
        [0.3, 0.3, 0.4],
    ],
)

half = FXTensor.from_strands([[2], [1]], {"[[[1]], [[1]]]": Fraction(1, 2)})
assert np.isclose(half.data[0, 0], 0.5)

original = FXTensor([[2], [3]], data=np.array([[0.1, 0.0, 0.7], [0.0, 0.3, 0.0]]))
encoded = original.to_original_dict()
assert encoded["profile"] == [[2], [3]]
assert float(encoded["strands"]["[[1], [1]]"]) == 0.1
assert "[[1], [2]]" not in encoded["strands"]

try:
    FXTensor.from_strands([[["a", "b"]], [["x", "y"]]], {"not-a-list": 1.0})
except ValueError:
    pass
else:
    raise AssertionError("strand keys must be nested lists")