Single Header C++ Implementation of NumPy NdArray.
I look forward to your pull-request.
#define TINYNDARRAY_IMPLEMENTATION
#include " tinyndarray.h"
using tinyndarray::NdArray;
int main (int argc, char const * argv[]) {
auto m1 = NdArray::Arange (12 ).reshape (2 , 2 , 3 ) - 2 .f ;
auto m2 = NdArray::Ones (3 , 1 ) * 10 .f ;
auto m12 = m1.dot (m2);
std::cout << m12 << std::endl;
NdArray m3 = {{{-0 .4f , 0 .3f }, {-0 .2f , 0 .1f }},
{{-0 .1f , 0 .2f }, {-0 .3f , 0 .4f }}};
m3 = Sin (std::move (m3));
std::cout << m3 << std::endl;
auto sum_abs = Abs (m3).sum ();
std::cout << sum_abs << std::endl;
auto m4 = Where (0 .f < m3, -100 .f , 100 .f );
bool all_m4 = All (m4);
bool any_m4 = Any (m4);
std::cout << m4 << std::endl;
std::cout << all_m4 << " " << any_m4 << std::endl;
return 0 ;
}
TinyNdArray supports only float array.
In the following Python code dtype=float32
is omitted, and in C++ code assuming using namespace tinyndarray;
is declared.
For more detail, please see declarations in top of the header file.
Copy behavior of NdArray is shallow copy which is same as NumPy.
Numpy (Python)
TinyNdArray (C++)
a = np .ones ((2 , 3 ))
b = a
b [0 , 0 ] = - 1
print (a [0 , 0 ]) # -1
auto a = NdArray::Ones(2 , 3 );
auto b = a;
b[{0 , 0 }] = -1 ;
std::cout << a[{0 , 0 }] << std::endl; // -1
Numpy (Python)
TinyNdArray (C++)
a = np.array([2, 3])
NdArray a({2, 3});
or
NdArray a(Shape{2, 3});
Supports up to 10 dimensions.
Numpy (Python)
TinyNdArray (C++)
a = np.array([1.0, 2.0])
NdArray a = {1.f, 2.f};
a = np.array([[1.0, 2.0]])
NdArray a = {{1.f, 2.f}};
a = np.array([[1.0, 2.0], [3.0, 4.0]])
NdArray a = {{1.f, 2.f}, {3.f, 4.f}};
a = np.array([[[[[[[[[[1.0, 2.0]]]]]]]]]])
NdArray a = {{{{{{{{{{1.f, 2.f}}}}}}}}}};
Numpy (Python)
TinyNdArray (C++)
a = np.empty((2, 3))
auto a = NdArray::Empty(2, 3);
or
auto a = NdArray::Empty({2, 3});
or
auto a = NdArray::Empty(Shape{2, 3});
a = np.zeros((2, 3))
auto a = NdArray::Zeros(2, 3);
or
auto a = NdArray::Zeros({2, 3});
or
auto a = NdArray::Zeros(Shape{2, 3});
a = np.ones((2, 3))
auto a = NdArray::Ones(2, 3);
or
auto a = NdArray::Ones({2, 3});
or
auto a = NdArray::Ones(Shape{2, 3});
a = np.arange(10)
auto a = NdArray::Arange(10);
a = np.arange(0, 100, 10)
auto a = NdArray::Arange(0, 100, 10);
Numpy (Python)
TinyNdArray (C++)
a = np.random.uniform(10)
auto a = NdArray::Uniform(10);
a = np.random.uniform(size=(2, 10))
auto a = NdArray::Uniform({2, 10});
or
auto a = NdArray::Uniform(Shape{2, 10});
a = np.random.uniform(low=0.0, high=1.0, size=10)
auto a = NdArray::Uniform(0.0, 1.0, {10});
or
auto a = NdArray::Uniform(0.0, 1.0, Shape{10});
a = np.random.normal(10)
auto a = NdArray::Normal(10);
a = np.random.normal(size=(2, 10))
auto a = NdArray::Normal({2, 10});
or
auto a = NdArray::Normal(Shape{2, 10});
a = np.random.normal(loc=0.0, scale=1.0, size=10)
auto a = NdArray::Normal(0.0, 1.0, {10});
or
auto a = NdArray::Normal(0.0, 1.0, Shape{10});
Numpy (Python)
TinyNdArray (C++)
a = np.random.seed()
auto a = NdArray::Seed();
a = np.random.seed(0)
auto a = NdArray::Seed(0);
Numpy (Python)
TinyNdArray (C++)
id(a)
a.id()
a.size
a.size()
a.shape
a.shape()
a.ndim
a.ndim()
a.fill(2.0)
a.fill(2.f)
a.copy()
a.copy()
Numpy (Python)
TinyNdArray (C++)
---
a.empty()
---
a.data()
---
a.begin()
---
a.end()
Numpy (Python)
TinyNdArray (C++)
float(a)
static_cast<float>(a)
Numpy (Python)
TinyNdArray (C++)
a[2, -3]
a[{2, -3}]
or
a[Index{2, -3}]
or
a(2, -3)
Numpy (Python)
TinyNdArray (C++)
a.reshape(-1, 2, 1)
a.reshape({-1, 2, 1})
or
a.reshape(Shape{-1, 2, 1})
or
a.reshape(-1, 2, 1)
a.flatten()
a.flatten()
a.ravel()
a.ravel()
Numpy (Python)
TinyNdArray (C++)
np.reshape(a, (-1, 2, 1))
Reshape(a, {-1, 2, 1})
np.squeeze(a)
Squeeze(a)
np.squeeze(a, [0, -2])
Squeeze(a, {0, -2})
np.expand_dims(a, 1)
ExpandDims(a, 1)
Slice methods create copy of the array, not reference.
Numpy (Python)
TinyNdArray (C++)
a[1:5, -4:-1]
a.slice({{1, 5}, {-4, -1}})
or
a.slice(SliceIndex{{1, 5}, {-4, -1}})
or
a.slice({1, 5}, {-4, -1})
Numpy (Python)
TinyNdArray (C++)
print(a)
std::cout << a << std::endl;
Numpy (Python)
TinyNdArray (C++)
+np.ones((2, 3))
+NdArray::Ones(2, 3)
-np.ones((2, 3))
-NdArray::Ones(2, 3)
All operaters supports broadcast.
Numpy (Python)
TinyNdArray (C++)
np.ones((2, 1, 3)) + np.ones((4, 1))
NdArray::Ones(2, 1, 3) + NdArray::Ones(4, 1)
np.ones((2, 1, 3)) - np.ones((4, 1))
NdArray::Ones(2, 1, 3) - NdArray::Ones(4, 1)
np.ones((2, 1, 3)) * np.ones((4, 1))
NdArray::Ones(2, 1, 3) * NdArray::Ones(4, 1)
np.ones((2, 1, 3)) / np.ones((4, 1))
NdArray::Ones(2, 1, 3) / NdArray::Ones(4, 1)
np.ones((2, 1, 3)) + 2.0
NdArray::Ones(2, 1, 3) + 2.f
np.ones((2, 1, 3)) - 2.0
NdArray::Ones(2, 1, 3) - 2.f
np.ones((2, 1, 3)) * 2.0
NdArray::Ones(2, 1, 3) * 2.f
np.ones((2, 1, 3)) / 2.0
NdArray::Ones(2, 1, 3) / 2.f
2.0 + np.ones((2, 1, 3))
2.f + NdArray::Ones(2, 1, 3)
2.0 - np.ones((2, 1, 3))
2.f - NdArray::Ones(2, 1, 3)
2.0 * np.ones((2, 1, 3))
2.f * NdArray::Ones(2, 1, 3)
2.0 / np.ones((2, 1, 3))
2.f / NdArray::Ones(2, 1, 3)
All operaters supports broadcast.
Numpy (Python)
TinyNdArray (C++)
np.ones((2, 1, 3)) == np.ones((4, 1))
NdArray::Ones(2, 1, 3) == NdArray::Ones(4, 1)
np.ones((2, 1, 3)) != np.ones((4, 1))
NdArray::Ones(2, 1, 3) != NdArray::Ones(4, 1)
np.ones((2, 1, 3)) > np.ones((4, 1))
NdArray::Ones(2, 1, 3) > NdArray::Ones(4, 1)
np.ones((2, 1, 3)) >= np.ones((4, 1))
NdArray::Ones(2, 1, 3) >= NdArray::Ones(4, 1)
np.ones((2, 1, 3)) < np.ones((4, 1))
NdArray::Ones(2, 1, 3) < NdArray::Ones(4, 1)
np.ones((2, 1, 3)) <= np.ones((4, 1))
NdArray::Ones(2, 1, 3) <= NdArray::Ones(4, 1)
np.ones((2, 1, 3)) == 1.f
NdArray::Ones(2, 1, 3) == 1.f
np.ones((2, 1, 3)) != 1.f
NdArray::Ones(2, 1, 3) != 1.f
np.ones((2, 1, 3)) > 1.f
NdArray::Ones(2, 1, 3) > 1.f
np.ones((2, 1, 3)) >= 1.f
NdArray::Ones(2, 1, 3) >= 1.f
np.ones((2, 1, 3)) < 1.f
NdArray::Ones(2, 1, 3) < 1.f
np.ones((2, 1, 3)) <= 1.f
NdArray::Ones(2, 1, 3) <= 1.f
1.f == np.ones((4, 1))
1.f == NdArray::Ones(4, 1)
1.f != np.ones((4, 1))
1.f != NdArray::Ones(4, 1)
1.f > np.ones((4, 1))
1.f > NdArray::Ones(4, 1)
1.f >= np.ones(4, 1))
1.f >= NdArray::Ones(4, 1)
1.f < np.ones((4, 1))
1.f < NdArray::Ones(4, 1)
1.f <= np.ones((4, 1))
1.f <= NdArray::Ones(4, 1)
Compound Assignment Operators
All operaters supports broadcast. However, left-side variable keep its size.
Numpy (Python)
TinyNdArray (C++)
np.ones((2, 1, 3)) += np.ones(3)
NdArray::Ones(2, 1, 3) += NdArray::Ones(3)
np.ones((2, 1, 3)) -= np.ones(3)
NdArray::Ones(2, 1, 3) -= NdArray::Ones(3)
np.ones((2, 1, 3)) *= np.ones(3)
NdArray::Ones(2, 1, 3) *= NdArray::Ones(3)
np.ones((2, 1, 3)) /= np.ones(3)
NdArray::Ones(2, 1, 3) /= NdArray::Ones(3)
np.ones((2, 1, 3)) += 2.f
NdArray::Ones(2, 1, 3) += 2.f
np.ones((2, 1, 3)) -= 2.f
NdArray::Ones(2, 1, 3) -= 2.f
np.ones((2, 1, 3)) *= 2.f
NdArray::Ones(2, 1, 3) *= 2.f
np.ones((2, 1, 3)) /= 2.f
NdArray::Ones(2, 1, 3) /= 2.f
Functions which takes two arguments support broadcast.
Numpy (Python)
TinyNdArray (C++)
np.abs(a)
Abs(a)
np.sign(a)
Sign(a)
np.ceil(a)
Ceil(a)
np.floor(a)
Floor(a)
np.clip(a, x_min, x_max)
Clip(a, x_min, x_max)
np.sqrt(a)
Sqrt(a)
np.exp(a)
Exp(a)
np.log(a)
Log(a)
np.square(a)
Square(a)
np.power(a, b)
Power(a, b)
np.power(a, 2.0)
Power(a, 2.f)
np.power(2.0, a)
Power(2.f, a)
np.sin(a)
Sin(a)
np.cos(a)
Cos(a)
np.tan(a)
Tan(a)
np.arcsin(a)
ArcSin(a)
np.arccos(a)
ArcCos(a)
np.arctan(a)
ArcTan(a)
np.arctan2(a, b)
ArcTan2(a, b)
np.arctan2(a, 10.0)
ArcTan2(a, 10.f)
np.arctan2(10.0, a)
ArcTan2(10.f, a)
Numpy (Python)
TinyNdArray (C++)
np.sum(a)
Sum(a)
np.sum(a, axis=0)
Sum(a, {0})
or
Sum(a, Axis{0})
np.sum(a, axis=(0, 2))
Sum(a, {0, 2})
or
Sum(a, Axis{0, 2})
np.mean(a, axis=0)
Mean(a, {0})
np.min(a, axis=0)
Min(a, {0})
np.max(a, axis=0)
Max(a, {0})
Numpy (Python)
TinyNdArray (C++)
np.all(a)
All(a, {0})
np.all(a, axis=0)
All(a, {0})
np.any(a)
Any(a, {0})
np.any(a, axis=0)
Any(a, {0})
np.where(condition, x, y)
Where(condition, x, y)
Numpy (Python)
TinyNdArray (C++)
a.sum()
a.sum()
a.sum(axis=0)
a.sum({0})
or
a.sum(Axis{0})
a.sum(axis=(0, 2))
a.sum({0, 2})
or
a.sum(Axis{0, 2})
a.mean(axis=0)
a.mean({0})
a.min(axis=0)
a.min({0})
a.max(axis=0)
a.max({0})
Numpy (Python)
TinyNdArray (C++)
np.stack((a, b, ...), axis=0)
Stack({a, b, ...}, 0)
np.concatenate((a, b, ...), axis=0)
Concatenate({a, b, ...}, 0)
np.split(a, 2, axis=0)
Split(a, 2, 0)
np.split(a, [1, 3], axis=0)
Split(a, {1, 3}, 0)
Separate(a, 0)
An inverse of Stack(a, 0)
View chaining methods create copy of the array, not reference.
Numpy (Python)
TinyNdArray (C++)
np.transpose(x)
Transpose(x)
np.swapaxes(x, 0, 2)
Swapaxes(x, 0, 2)
np.broadcast_to(x, (3, 2))
BroadcastTo(x, {3, 2})
SumTo(x, {3, 2})
An inverse of BroadcastTo(x, {3, 2})
All dimension rules of numpy are implemented.
Numpy (Python)
TinyNdArray (C++)
np.dot(a, b)
Dot(a, b)
a.dot(b)
a.dot(b)
np.matmul(a, b)
Matmul(a, b)
np.cross(a, b)
Cross(a, b)
a.cross(b)
a.cross(b)
All dimension rules of numpy are implemented.
Numpy (Python)
TinyNdArray (C++)
np.linalg.inv(a)
Inv(a, b)
In NumPy, in-place and not inplace operations are written by following.
Numpy (Python) In-place
Numpy (Python) Not in-place
a = np .ones ((2 , 3 ))
a_id = id (a )
np .exp (a , out = a ) # in-place
print (id (a ) == a_id ) # True
a = np .ones ((2 , 3 ))
a_id = id (a )
a = np .exp (a ) # not in-place
print (id (a ) == a_id ) # False
In TinyNdArray, when right-reference values are passed, no new arrays are created and operated in-place.
TinyNdArray (C++) In-place
TinyNdArray (C++) Not in-place
auto a = NdArray::Ones(2 , 3 );
auto a_id = a.id();
a = np.exp(std::move(a)); // in-place
std::cout << (a.id() == a_id)
<< std::endl; // true
auto a = NdArray::Ones(2 , 3 );
auto a_id = a.id();
a = np.exp(a); // not in-place
std::cout << (a.id() == a_id)
<< std::endl; // false
However, even right-reference values are passed, when the size is changed by broadcasting, a new array will be created.
TinyNdArray (C++) In-place
TinyNdArray (C++) Not in-place
auto a = NdArray::Ones(2 , 1 , 3 );
auto b = NdArray::Ones(3 );
auto a_id = a.id();
a = np.exp(std::move(a)); // in-place
std::cout << a.shape()
<< std::endl; // [2, 1, 3]
std::cout << (a.id() == a_id)
<< std::endl; // true
auto a = NdArray::Ones(2 , 1 , 3 );
auto a = NdArray::Ones(3 , 1 );
auto a_id = a.id();
a = np.exp(srd::move(a)); // looks like in-place
std::cout << a.shape()
<< std::endl; // [2, 3, 3]
std::cout << (a.id() == a_id)
<< std::endl; // false
In default, most of all operations run in parallel by threads.
When changing the number of workers, please set via NdArray::SetNumWorkers()
.
// Default setting. Use all of cores.
NdArray::SetNumWorkers (-1 );
// Set no parallel.
NdArray::SetNumWorkers (1 );
// Use 4 cores
NdArray::SetNumWorkers (4 );
When a macro TINYNDARRAY_PROFILE_MEMORY
is defined, memory profiler is activated.
The following methods can be used to get the number of instances and the size of allocated memories..
NdArray::GetNumInstance()
NdArray::GetTotalMemory()
TINYNDARRAY_H_ONCE
TINYNDARRAY_NO_INCLUDE
TINYNDARRAY_NO_NAMESPACE
TINYNDARRAY_NO_DECLARATION
TINYNDARRAY_IMPLEMENTATION
TINYNDARRAY_PROFILE_MEMORY
Everything in the upper list are difficult challenges. If you have any ideas, please let me know.