Skip to content

include/tenzr/frontend/array.h

Namespaces

Name
tenzr

Classes

Name
class tenzr::Array

Source code

// Copyright 2026 The Tenzr Project Authors
//
// SPDX-License-Identifier: Apache-2.0
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#ifndef TENZR_FRONTEND_ARRAY_H_
#define TENZR_FRONTEND_ARRAY_H_

#include <cstring>
#include <initializer_list>
#include <memory>
#include <string>
#include <vector>

#include "tenzr/common/error.h"
#include "tenzr/frontend/dtype.h"
#include "tenzr/frontend/layout.h"
#include "tenzr/frontend/memory_pool.h"
#include "tenzr/frontend/node.h"
#include "tenzr/frontend/shape.h"
#include "tenzr/frontend/sharding.h"
#include "tenzr/txpr/tracer.h"
#include "tenzr/txpr/txpr.h"

namespace tenzr {

  class Array {
   public:
    Array() = default;

    explicit Array(std::shared_ptr<Node> node);

    // Factory methods

    static Array Parameter(Shape shape, DType dtype = DType::kFloat32, std::string name = "");

    static Array Constant(const std::vector<float>& data, Shape shape);

    static Array Zeros(Shape shape, DType dtype = DType::kFloat32);

    static Array Ones(Shape shape, DType dtype = DType::kFloat32);

    static Array Full(Shape shape, double value, DType dtype = DType::kFloat32);

    // Metadata accessors

    const Shape& shape() const;

    DType Dtype() const;

    const ShardingSpec& Sharding() const;

    void Shard(ShardingSpec spec);

    void Shard(const NamedSharding& ns);

    const StridedLayout& Layout() const;

    bool IsContiguous() const;

    const std::shared_ptr<Node>& node() const { return node_; }

    bool IsDefined() const { return node_ != nullptr || var_id_.IsValid(); }

    // Dynamic tracing / symbolic handles

    [[nodiscard]] bool IsSymbolic() const noexcept { return var_id_.IsValid(); }

    [[nodiscard]] txpr::VarId SymbolicVarId() const noexcept { return var_id_; }

    [[nodiscard]] uint64_t Id() const noexcept {
      if (var_id_.IsValid()) {
        return static_cast<uint64_t>(var_id_.id);
      }
      return node_ ? node_->Id() : 0;
    }

    static Array Symbolic(txpr::VarId vid, Shape shape, DType dtype) {
      Array a;
      a.var_id_ = vid;
      a.shape_ = std::move(shape);
      a.dtype_ = dtype;
      return a;
    }

    [[nodiscard]] txpr::VarId ObtainVarId(txpr::Txpr* tape) const;

    // Host data management

    void BindData(std::shared_ptr<PinnedBuffer> buffer);

    std::shared_ptr<PinnedBuffer> Data() const;

    bool HasData() const;

    bool IsLazy() const { return !HasData(); }

    // Device data management

    void BindDeviceData(std::shared_ptr<DeviceBuffer> buffer);

    std::shared_ptr<DeviceBuffer> DeviceData() const;

    bool HasDeviceData() const;

    // Synchronization

    void SyncHost() const;

    void SyncDevice() const;

    Array Clone() const;

    Array Eval() const;

    Array Materialize() const;

    template <typename T> T Item() const {
      TENZR_CHECK(shape().NumElements() == 1, "item() can only be called on arrays with 1 element");
      return ToVector<T>()[0];
    }

    template <typename T> void SetData(const std::vector<T>& values) {
      TENZR_CHECK(shape().NumElements() == static_cast<int64_t>(values.size()), "Data size does not match array shape");
      TENZR_CHECK(DtypeOf<T>() == Dtype(), "set_data<T>() dtype mismatch: values have dtype " + std::string(tenzr::ToString(DtypeOf<T>())) +
                                               " but array has dtype " + std::string(tenzr::ToString(Dtype())));
      if constexpr (std::is_same_v<T, bool>) {
        size_t bytes = values.size();
        auto buf = MemoryPool::Instance().Allocate(bytes);
        uint8_t* dst = reinterpret_cast<uint8_t*>(buf->Data());
        for (size_t i = 0; i < values.size(); ++i) {
          dst[i] = values[i] ? 1 : 0;
        }
        BindData(buf);
      } else {
        size_t bytes = values.size() * sizeof(T);
        auto buf = MemoryPool::Instance().Allocate(bytes);
        std::memcpy(buf->Data(), values.data(), bytes);
        BindData(buf);
      }
    }

    template <typename T> static Array FromVector(const std::vector<T>& data, Shape shape) {
      Array a = Parameter(shape, DtypeOf<T>());
      a.SetData(data);
      if (txpr::TracerContext::IsActive()) {
        auto* tape = txpr::TracerContext::CurrentTape();
        return Symbolic(a.ObtainVarId(tape), std::move(shape), DtypeOf<T>());
      }
      return a;
    }

    template <typename T> std::vector<T> ToVector() const {
      if (!HasData()) {
        return Materialize().ToVector<T>();
      }
      TENZR_CHECK(DtypeOf<T>() == Dtype(), "to_vector<T>() dtype mismatch: requested " + std::string(tenzr::ToString(DtypeOf<T>())) + " but array has dtype " +
                                               std::string(tenzr::ToString(Dtype())));
      auto buf = Data();
      TENZR_CHECK(buf != nullptr, "Array has no bound data");
      size_t count = static_cast<size_t>(shape().NumElements());

      if constexpr (std::is_same_v<T, bool>) {
        std::vector<bool> result(count);
        const uint8_t* src = reinterpret_cast<const uint8_t*>(buf->Data());
        for (size_t i = 0; i < count; ++i) {
          result[i] = (src[i] != 0);
        }
        return result;
      } else {
        std::vector<T> result(count);
        const T* src = reinterpret_cast<const T*>(buf->Data());

        const auto& lay = Layout();
        if (lay.IsContiguous() && lay.Offset() == 0) {
          std::memcpy(result.data(), src, count * sizeof(T));
        } else {
          size_t r = shape().Rank();
          if (r == 0) {
            result[0] = src[lay.Offset()];
          } else {
            std::vector<int64_t> idx(r, 0);
            for (size_t i = 0; i < count; ++i) {
              int64_t off = lay.LinearOffset(idx);
              result[i] = src[off];
              for (size_t d = r; d > 0; --d) {
                size_t dim_idx = d - 1;
                idx[dim_idx]++;
                if (idx[dim_idx] < shape().Dim(dim_idx) || dim_idx == 0) {
                  break;
                }
                idx[dim_idx] = 0;
              }
            }
          }
        }
        return result;
      }
    }

    Array Astype(DType target_dtype) const;

    template <typename T> std::vector<T> ToVectorAs() const { return Astype(DtypeOf<T>()).template ToVector<T>(); }

    // Registry & Custom Ops

    static Array CustomOp(std::string op_name, std::vector<Array> inputs);

    Array CustomOp(std::string op_name) const;

    // View and layout transformations

    Array Reshape(Shape new_shape) const;

    Array Flatten() const;

    Array Ravel() const;

    Array Transpose(int64_t dim0 = -1, int64_t dim1 = -2) const;

    Array Permute(const std::vector<int64_t>& axes) const;

    Array Squeeze(std::optional<int64_t> dim = std::nullopt) const;

    Array Unsqueeze(int64_t dim) const;

    Array slice(const std::vector<Slice>& slices) const;

    Array BroadcastTo(const Shape& target_shape) const;

    Array AsStrided(const Shape& shape, const std::vector<int64_t>& strides, int64_t offset = 0) const;

    Array Contiguous() const;

    Array Matmul(const Array& other) const;

    // Tier 1: Unary Elementwise Operations

    Array Relu() const;

    Array sigmoid() const;

    Array tanh() const;

    Array Exp() const;

    Array Log() const;

    Array Abs() const;

    Array Neg() const;

    Array Sin() const;

    Array Cos() const;

    Array Sqrt() const;

    Array Rsqrt() const;

    Array Ceil() const;

    Array Floor() const;

    Array Round() const;

    Array Sign() const;

    Array Cbrt() const;

    Array Tan() const;

    Array Expm1() const;

    Array Log1p() const;

    Array IsFinite() const;

    Array LogicalNot() const;

    // Composite activations

    Array Silu() const;

    Array Gelu() const;

    // Tier 2: Binary Elementwise Operations

    Array Pow(const Array& other) const;

    Array Remainder(const Array& other) const;

    Array Atan2(const Array& other) const;

    Array Minimum(const Array& other) const;

    Array Maximum(const Array& other) const;

    Array BitwiseAnd(const Array& other) const;

    Array BitwiseOr(const Array& other) const;

    Array BitwiseXor(const Array& other) const;

    Array ShiftLeft(const Array& other) const;

    Array ShiftRight(const Array& other) const;

    // Tier 3: Clamp & Comparison Operations

    Array Clamp(const Array& min, const Array& max) const;

    Array Equal(const Array& other) const;

    Array NotEqual(const Array& other) const;

    Array GreaterEqual(const Array& other) const;

    Array GreaterThan(const Array& other) const;

    Array LessEqual(const Array& other) const;

    Array LessThan(const Array& other) const;

    // Reductions

    Array Sum(std::optional<std::vector<int64_t>> dims = std::nullopt, bool keepdims = false) const;

    Array Mean(std::optional<std::vector<int64_t>> dims = std::nullopt, bool keepdims = false) const;

    Array Max(std::optional<std::vector<int64_t>> dims = std::nullopt, bool keepdims = false) const;

    Array Min(std::optional<std::vector<int64_t>> dims = std::nullopt, bool keepdims = false) const;

    Array Prod(std::optional<std::vector<int64_t>> dims = std::nullopt, bool keepdims = false) const;

    Array Var(std::optional<std::vector<int64_t>> dims = std::nullopt, bool keepdims = false) const;

    Array StdDev(std::optional<std::vector<int64_t>> dims = std::nullopt, bool keepdims = false) const;

    // Overloaded arithmetic operators

    Array operator+(const Array& other) const;

    Array operator*(const Array& other) const;

    Array operator-(const Array& other) const;

    Array operator/(const Array& other) const;

    // Overloaded comparison operators

    Array operator==(const Array& other) const;

    Array operator!=(const Array& other) const;

    Array operator<(const Array& other) const;

    Array operator<=(const Array& other) const;

    Array operator>(const Array& other) const;

    Array operator>=(const Array& other) const;

    std::string ToString() const;

   private:
    std::shared_ptr<Node> node_;
    txpr::VarId var_id_{txpr::VarId::Invalid()};
    Shape shape_;
    DType dtype_{DType::kFloat32};
  };

  // Factory functions

  inline Array array(const std::vector<float>& data) { return Array::Constant(data, Shape({static_cast<int64_t>(data.size())})); }

  inline Array array(const std::vector<float>& data, Shape shape) { return Array::Constant(data, std::move(shape)); }

  inline Array array(std::initializer_list<float> data) { return Array::Constant(std::vector<float>(data), Shape({static_cast<int64_t>(data.size())})); }

  inline Array array(std::initializer_list<float> data, Shape shape) { return Array::Constant(std::vector<float>(data), std::move(shape)); }

  inline Array array(std::initializer_list<std::initializer_list<float>> matrix_data) {
    int64_t rows = static_cast<int64_t>(matrix_data.size());
    int64_t cols = rows > 0 ? static_cast<int64_t>(matrix_data.begin()->size()) : 0;
    std::vector<float> data;
    data.reserve(static_cast<size_t>(rows * cols));
    for (const auto& row : matrix_data) {
      TENZR_CHECK(static_cast<int64_t>(row.size()) == cols, "All rows in matrix initializer list must have equal column length");
      data.insert(data.end(), row.begin(), row.end());
    }
    return Array::Constant(data, Shape({rows, cols}));
  }

  inline Array Matrix(std::initializer_list<std::initializer_list<float>> m) { return array(m); }

  inline Array Zeros(Shape shape, DType dtype = DType::kFloat32) { return Array::Zeros(std::move(shape), dtype); }

  inline Array Ones(Shape shape, DType dtype = DType::kFloat32) { return Array::Ones(std::move(shape), dtype); }

  inline Array Full(Shape shape, double value, DType dtype = DType::kFloat32) { return Array::Full(std::move(shape), value, dtype); }

  inline Array Sum(const Array& a, std::optional<std::vector<int64_t>> dims = std::nullopt, bool keepdims = false) { return a.Sum(std::move(dims), keepdims); }

  inline Array Mean(const Array& a, std::optional<std::vector<int64_t>> dims = std::nullopt, bool keepdims = false) {
    return a.Mean(std::move(dims), keepdims);
  }

  inline Array Max(const Array& a, std::optional<std::vector<int64_t>> dims = std::nullopt, bool keepdims = false) { return a.Max(std::move(dims), keepdims); }

  inline Array Min(const Array& a, std::optional<std::vector<int64_t>> dims = std::nullopt, bool keepdims = false) { return a.Min(std::move(dims), keepdims); }

  inline Array Prod(const Array& a, std::optional<std::vector<int64_t>> dims = std::nullopt, bool keepdims = false) {
    return a.Prod(std::move(dims), keepdims);
  }

  inline Array Arange(float start, float stop, float step = 1.0f) {
    TENZR_CHECK(step != 0.0f, "arange step cannot be 0");
    std::vector<float> vals;
    if (step > 0.0f) {
      for (float v = start; v < stop; v += step) {
        vals.push_back(v);
      }
    } else {
      for (float v = start; v > stop; v += step) {
        vals.push_back(v);
      }
    }
    return Array::Constant(vals, Shape({static_cast<int64_t>(vals.size())}));
  }

  inline Array Eye(int64_t n, DType dtype = DType::kFloat32) {
    TENZR_CHECK(n > 0, "eye size must be positive");
    TENZR_CHECK(dtype == DType::kFloat32, "eye currently supports kFloat32");
    std::vector<float> vals(static_cast<size_t>(n * n), 0.0f);
    for (int64_t i = 0; i < n; ++i) {
      vals[static_cast<size_t>(i * n + i)] = 1.0f;
    }
    return Array::Constant(vals, Shape({n, n}));
  }

  // Free function mathematical and functional helpers
  inline Array Divide(const Array& a, const Array& b) { return a / b; }

  inline Array Matmul(const Array& a, const Array& b) { return a.Matmul(b); }

  inline Array Reshape(const Array& a, Shape new_shape) { return a.Reshape(std::move(new_shape)); }

  inline Array Relu(const Array& a) { return a.Relu(); }

  inline Array sigmoid(const Array& a) { return a.sigmoid(); }

  inline Array tanh(const Array& a) { return a.tanh(); }

  inline Array Exp(const Array& a) { return a.Exp(); }

  inline Array Log(const Array& a) { return a.Log(); }

  inline Array Abs(const Array& a) { return a.Abs(); }

  inline Array Neg(const Array& a) { return a.Neg(); }

  inline Array Sin(const Array& a) { return a.Sin(); }

  inline Array Cos(const Array& a) { return a.Cos(); }

  inline Array Sqrt(const Array& a) { return a.Sqrt(); }

  inline Array Rsqrt(const Array& a) { return a.Rsqrt(); }

  inline Array Ceil(const Array& a) { return a.Ceil(); }

  inline Array Floor(const Array& a) { return a.Floor(); }

  inline Array Round(const Array& a) { return a.Round(); }

  inline Array Sign(const Array& a) { return a.Sign(); }

  inline Array Cbrt(const Array& a) { return a.Cbrt(); }

  inline Array Tan(const Array& a) { return a.Tan(); }

  inline Array Expm1(const Array& a) { return a.Expm1(); }

  inline Array Log1p(const Array& a) { return a.Log1p(); }

  inline Array IsFinite(const Array& a) { return a.IsFinite(); }

  inline Array LogicalNot(const Array& a) { return a.LogicalNot(); }

  inline Array Silu(const Array& a) { return a.Silu(); }

  inline Array Gelu(const Array& a) { return a.Gelu(); }

  inline Array Pow(const Array& a, const Array& b) { return a.Pow(b); }

  inline Array Remainder(const Array& a, const Array& b) { return a.Remainder(b); }

  inline Array Atan2(const Array& a, const Array& b) { return a.Atan2(b); }

  inline Array Minimum(const Array& a, const Array& b) { return a.Minimum(b); }

  inline Array Maximum(const Array& a, const Array& b) { return a.Maximum(b); }

  inline Array Clamp(const Array& x, const Array& min, const Array& max) { return x.Clamp(min, max); }

  inline Array Select(const Array& pred, const Array& on_true, const Array& on_false) { return Array::CustomOp("select", {pred, on_true, on_false}); }

  inline Array Equal(const Array& a, const Array& b) { return a.Equal(b); }

  inline Array NotEqual(const Array& a, const Array& b) { return a.NotEqual(b); }

  inline Array GreaterEqual(const Array& a, const Array& b) { return a.GreaterEqual(b); }

  inline Array GreaterThan(const Array& a, const Array& b) { return a.GreaterThan(b); }

  inline Array LessEqual(const Array& a, const Array& b) { return a.LessEqual(b); }

  inline Array LessThan(const Array& a, const Array& b) { return a.LessThan(b); }

  // Unary negation operator
  inline Array operator-(const Array& a) { return a.Neg(); }

  // Scalar arithmetic operators (automatic broadcast & promotion)
  inline Array operator+(const Array& a, double s) { return a + Array::Full(a.shape(), s, a.Dtype()); }
  inline Array operator+(double s, const Array& a) { return Array::Full(a.shape(), s, a.Dtype()) + a; }
  inline Array operator-(const Array& a, double s) { return a - Array::Full(a.shape(), s, a.Dtype()); }
  inline Array operator-(double s, const Array& a) { return Array::Full(a.shape(), s, a.Dtype()) - a; }
  inline Array operator*(const Array& a, double s) { return a * Array::Full(a.shape(), s, a.Dtype()); }
  inline Array operator*(double s, const Array& a) { return Array::Full(a.shape(), s, a.Dtype()) * a; }
  inline Array operator/(const Array& a, double s) { return a / Array::Full(a.shape(), s, a.Dtype()); }
  inline Array operator/(double s, const Array& a) { return Array::Full(a.shape(), s, a.Dtype()) / a; }

  std::ostream& operator<<(std::ostream& os, const Array& t);

}  // namespace tenzr

#endif  // TENZR_FRONTEND_ARRAY_H_