import numpy as np

class Tensor:
    def __init__(self, data, expected_rank):
        self._data = np.array(data, dtype=np.float64)
        self.rank = expected_rank
        if self._data.ndim != self.rank:
            raise ValueError(f"Expected Rank-{self.rank}, got dim={self._data.ndim}.")
        self.shape = self._data.shape
        if self.rank > 1 and not all(dim == self.shape[0] for dim in self.shape):
            raise ValueError(f"Rank-{self.rank} Tensor must be hypercubic. Shape: {self.shape}.")
        self.num_vars = self.shape[0] if self.shape else 0

    @property
    def data(self):
        return self._data

class Rank1Tensor(Tensor):
    def __init__(self, data):
        super().__init__(data, expected_rank=1)

class Rank2Tensor(Tensor):
    def __init__(self, data):
        super().__init__(data, expected_rank=2)

class Rank3Tensor(Tensor):
    def __init__(self, data):
        super().__init__(data, expected_rank=3)
