//
// TensorFlow.cs; Bindings to the TensorFlow C API for .NET
//
// Authors:
// Miguel de Icaza ([email protected])
//
using System;
using System.Numerics;
using System.Runtime.InteropServices;
using System.Text;
using size_t = System.UIntPtr;
using TF_Tensor = System.IntPtr;
namespace TensorFlow
{
///
/// TFTensor holds a multi-dimensional array of elements of a single data type.
///
///
///
/// You can create tensors with the various constructors in this class, or using
/// the implicit conversions from various data types into a TFTensor.
///
///
/// The implicit conversions for basic types produce tensors of one dimesion with
/// a single element, while the implicit conversion from an array, expects a multi-dimensional
/// array that is converted into a tensor of the right dimensions.
///
///
/// The special "String" tensor data type that you will find in TensorFlow documentation
/// really represents a byte array. You can create string tensors by using the
/// method that takes a byte array buffer as input.
///
///
public class TFTensor : TFDisposable
{
///
/// Signature that methods must conform to to be used to release memory that was passed to a manually allocated TFTensor
///
public delegate void Deallocator (IntPtr data, IntPtr size, IntPtr deallocatorData);
// extern TF_Tensor * TF_NewTensor (TF_DataType, const int64_t *dims, int num_dims, void *data, size_t len, void (* deallocator)(void *, size_t, void *), void *deallocator_arg);
[DllImport (NativeBinding.TensorFlowLibrary)]
static extern unsafe TF_Tensor TF_NewTensor (TFDataType dataType, long [] dims, int num_dims, IntPtr data, size_t len, Deallocator deallocator, IntPtr deallocator_arg);
[DllImport (NativeBinding.TensorFlowLibrary)]
static extern unsafe TF_Tensor TF_NewTensor (TFDataType dataType, IntPtr zeroDims, int num_dims, IntPtr data, size_t len, Deallocator deallocator, IntPtr deallocator_arg);
internal TFTensor (IntPtr handle) : base (handle) { }
static Deallocator FreeTensorDataDelegate = FreeTensorData;
static Deallocator FreeTensorHandleDelegate = FreeTensorHandle;
[MonoPInvokeCallback (typeof (Deallocator))]
internal static void FreeTensorData (IntPtr data, IntPtr len, IntPtr closure)
{
Marshal.FreeHGlobal (data);
}
internal static void FreeTensorHandle (IntPtr data, IntPtr len, IntPtr closure)
{
var gch = GCHandle.FromIntPtr (closure);
gch.Free ();
}
// TODO: Other overloads we could add: String, Complex (float), Bool, QInt8, QUInt8, QInt32, Bfloat16,
// QInt16, QUint16, Half, Resource
// TODO: not clear that this is very useful (the dims versions), perhaps to reduce the surface of
// construcors these rarer blobs should be "FromSpec" or something like that
///
/// Creates a new tensor from a portion of an array of sbytes
///
/// Represents the tensor shape.
/// The linear array of data, the data is shuffled to fit in the tensor with the specified dimensions.
/// The offset into the provided data array where the data resides.
/// The number of bytes to copy from count into the tensor.
///
/// Use the FromBuffer method to create a tensor that has the specified dimensions
/// and is initialized with data from the data array. The data is copied starting
/// at the start offset, for count bytes and is laid out into the tensor following the
/// specified dimensions.
///
public static TFTensor FromBuffer (TFShape shape, sbyte [] data, int start, int count)
{
return new TFTensor (SetupTensor (TFDataType.Int8, shape, data, start, count, size: 2));
}
///
/// Creates a new tensor from a portion of an array of bytes
///
/// Represents the tensor shape.
/// The linear array of data, the data is shuffled to fit in the tensor with the specified dimensions.
/// The offset into the provided data array where the data resides.
/// The number of bytes to copy from count into the tensor.
///
/// Use the FromBuffer method to create a tensor that has the specified dimensions
/// and is initialized with data from the data array. The data is copied starting
/// at the start offset, for count bytes and is laid out into the tensor following the
/// specified dimensions.
///
public static TFTensor FromBuffer (TFShape shape, byte [] data, int start, int count)
{
return new TFTensor (SetupTensor (TFDataType.UInt8, shape, data, start, count, size: 1));
}
///
/// Creates a new tensor from a portion of an array of shorts
///
/// Represents the tensor shape.
/// The linear array of data, the data is shuffled to fit in the tensor with the specified dimensions.
/// The offset into the provided data array where the data resides.
/// The number of bytes to copy from count into the tensor.
///
/// Use the FromBuffer method to create a tensor that has the specified dimensions
/// and is initialized with data from the data array. The data is copied starting
/// at the start offset, for count bytes and is laid out into the tensor following the
/// specified dimensions.
///
public static TFTensor FromBuffer (TFShape shape, short [] data, int start, int count)
{
return new TFTensor (SetupTensor (TFDataType.Int16, shape, data, start, count, size: 2));
}
///
/// Creates a new tensor from a portion of an array of ushorts
///
/// Represents the tensor shape.
/// The linear array of data, the data is shuffled to fit in the tensor with the specified dimensions.
/// The offset into the provided data array where the data resides.
/// The number of bytes to copy from count into the tensor.
///
/// Use the FromBuffer method to create a tensor that has the specified dimensions
/// and is initialized with data from the data array. The data is copied starting
/// at the start offset, for count bytes and is laid out into the tensor following the
/// specified dimensions.
///
public static TFTensor FromBuffer (TFShape shape, ushort [] data, int start, int count)
{
return new TFTensor (SetupTensor (TFDataType.UInt16, shape, data, start, count, size: 2));
}
///
/// Creates a new tensor from a portion of an array of ints
///
/// Represents the tensor shape.
/// The linear array of data, the data is shuffled to fit in the tensor with the specified dimensions.
/// The offset into the provided data array where the data resides.
/// The number of bytes to copy from count into the tensor.
///
/// Use the FromBuffer method to create a tensor that has the specified dimensions
/// and is initialized with data from the data array. The data is copied starting
/// at the start offset, for count bytes and is laid out into the tensor following the
/// specified dimensions.
///
public static TFTensor FromBuffer (TFShape shape, int [] data, int start, int count)
{
return new TFTensor (SetupTensor (TFDataType.Int32, shape, data, start, count, size: 4));
}
///
/// Creates a new tensor from a portion of an array of floats
///
/// Represents the tensor shape.
/// The linear array of data, the data is shuffled to fit in the tensor with the specified dimensions.
/// The offset into the provided data array where the data resides.
/// The number of bytes to copy from count into the tensor.
///
/// Use the FromBuffer method to create a tensor that has the specified dimensions
/// and is initialized with data from the data array. The data is copied starting
/// at the start offset, for count bytes and is laid out into the tensor following the
/// specified dimensions.
///
public static TFTensor FromBuffer (TFShape shape, float [] data, int start, int count)
{
return new TFTensor (SetupTensor (TFDataType.Float, shape, data, start, count, size: 4));
}
///
/// Creates a new tensor from a portion of an array of doubles
///
/// Represents the tensor shape.
/// The linear array of data, the data is shuffled to fit in the tensor with the specified dimensions.
/// The offset into the provided data array where the data resides.
/// The number of bytes to copy from count into the tensor.
///
/// Use the FromBuffer method to create a tensor that has the specified dimensions
/// and is initialized with data from the data array. The data is copied starting
/// at the start offset, for count bytes and is laid out into the tensor following the
/// specified dimensions.
///
public static TFTensor FromBuffer (TFShape shape, double [] data, int start, int count)
{
return new TFTensor (SetupTensor (TFDataType.Double, shape, data, start, count, size: 8));
}
///
/// Creates a new tensor from a portion of an array of longs
///
/// Represents the tensor shape.
/// The linear array of data, the data is shuffled to fit in the tensor with the specified dimensions.
/// The offset into the provided data array where the data resides.
/// The number of bytes to copy from count into the tensor.
///
/// Use the FromBuffer method to create a tensor that has the specified dimensions
/// and is initialized with data from the data array. The data is copied starting
/// at the start offset, for count bytes and is laid out into the tensor following the
/// specified dimensions.
///
public static TFTensor FromBuffer (TFShape shape, long [] data, int start, int count)
{
return new TFTensor (SetupTensor (TFDataType.Int64, shape, data, start, count, size: 8));
}
///
/// Creates a new tensor from a portion of an array of Complex numbers
///
/// Represents the tensor shape.
/// The linear array of data, the data is shuffled to fit in the tensor with the specified dimensions.
/// The offset into the provided data array where the data resides.
/// The number of bytes to copy from count into the tensor.
///
/// Use the FromBuffer method to create a tensor that has the specified dimensions
/// and is initialized with data from the data array. The data is copied starting
/// at the start offset, for count bytes and is laid out into the tensor following the
/// specified dimensions.
///
public static TFTensor FromBuffer (TFShape shape, Complex [] data, int start, int count)
{
return new TFTensor (SetupTensor (TFDataType.Complex128, shape, data, start, count, size: 16));
}
///
/// Creates a constant tensor from an array, the shape reflects the shape of the C# array and the underlying type reflects the C# type.
///
public unsafe TFTensor (Array array)
{
if (array == null)
throw new ArgumentNullException (nameof (array));
// TODO: ensure that we do not have arrays of arrays.
var t = array.GetType ().GetElementType ();
var tc = Type.GetTypeCode (t);
TFDataType dt;
long size = 0;
switch (tc) {
case TypeCode.Boolean:
dt = TFDataType.Bool;
size = 1;
break;
case TypeCode.SByte:
dt = TFDataType.Int8;
size = 1;
break;
case TypeCode.Byte:
dt = TFDataType.UInt8;
size = 1;
break;
case TypeCode.Int16:
dt = TFDataType.Int16;
size = 2;
break;
case TypeCode.UInt16:
dt = TFDataType.UInt16;
size = 2;
break;
case TypeCode.Int32:
dt = TFDataType.Int32;
size = 4;
break;
case TypeCode.Int64:
dt = TFDataType.Int64;
size = 8;
break;
case TypeCode.Single:
dt = TFDataType.Float;
size = 4;
break;
case TypeCode.Double:
dt = TFDataType.Double;
size = 8;
break;
default:
// Check types that are not handled by the typecode
if (t.IsAssignableFrom (typeof (Complex))) {
size = 16;
dt = TFDataType.Complex128;
} else
throw new ArgumentException ($"The data type {t} is not supported");
break;
}
var dims = new long [array.Rank];
for (int i = 0; i < array.Rank; i++) {
dims [i] = array.GetLength (i);
size *= (int)dims [i];
}
handle = SetupMulti (dt, dims, array, size);
}
///
/// Creates a constant tensor with a single dimension from an integer value.
///
public unsafe TFTensor (int value)
{
var v = (int*)Marshal.AllocHGlobal (sizeof (int));
*v = value;
handle = TF_NewTensor (TFDataType.Int32, zeroDims: IntPtr.Zero, num_dims: 0, data: (IntPtr)v, len: (UIntPtr)sizeof (int), deallocator: FreeTensorDataDelegate, deallocator_arg: IntPtr.Zero);
}
///
/// Creates a constant tensor with a single dimension from a boolean value.
///
public unsafe TFTensor (bool value)
{
var v = (bool*)Marshal.AllocHGlobal (sizeof (bool));
*v = value;
handle = TF_NewTensor (TFDataType.Bool, zeroDims: IntPtr.Zero, num_dims: 0, data: (IntPtr)v, len: (UIntPtr)sizeof (int), deallocator: FreeTensorDataDelegate, deallocator_arg: IntPtr.Zero);
}
///
/// Creates a constant tensor with a single dimension from an sbyte value.
///
public unsafe TFTensor (sbyte value)
{
var v = (sbyte*)Marshal.AllocHGlobal (sizeof (sbyte));
*v = value;
handle = TF_NewTensor (TFDataType.Int8, zeroDims: IntPtr.Zero, num_dims: 0, data: (IntPtr)v, len: (UIntPtr)sizeof (sbyte), deallocator: FreeTensorDataDelegate, deallocator_arg: IntPtr.Zero);
}
///
/// Creates a constant tensor with a single dimension from a short value.
///
public unsafe TFTensor (short value)
{
var v = (short*)Marshal.AllocHGlobal (sizeof (short));
*v = value;
handle = TF_NewTensor (TFDataType.Int16, zeroDims: IntPtr.Zero, num_dims: 0, data: (IntPtr)v, len: (UIntPtr)sizeof (short), deallocator: FreeTensorDataDelegate, deallocator_arg: IntPtr.Zero);
}
///
/// Creates a constant tensor with a single dimension from an ushort value.
///
public unsafe TFTensor (ushort value)
{
var v = (ushort*)Marshal.AllocHGlobal (sizeof (ushort));
*v = value;
handle = TF_NewTensor (TFDataType.Int16, zeroDims: IntPtr.Zero, num_dims: 0, data: (IntPtr)v, len: (UIntPtr)sizeof (ushort), deallocator: FreeTensorDataDelegate, deallocator_arg: IntPtr.Zero);
}
///
/// Creates a constant tensor with a single dimension from an byte value.
///
public unsafe TFTensor (byte value)
{
var v = (int*)Marshal.AllocHGlobal (sizeof (byte));
*v = value;
handle = TF_NewTensor (TFDataType.UInt8, zeroDims: IntPtr.Zero, num_dims: 0, data: (IntPtr)v, len: (UIntPtr)sizeof (byte), deallocator: FreeTensorDataDelegate, deallocator_arg: IntPtr.Zero);
}
///
/// Creates a constant tensor with a single dimension from a Complex value.
///
public unsafe TFTensor (Complex value)
{
var v = (Complex*)Marshal.AllocHGlobal (sizeof (Complex));
*v = value;
handle = TF_NewTensor (TFDataType.Complex128, zeroDims: IntPtr.Zero, num_dims: 0, data: (IntPtr)v, len: (UIntPtr)sizeof (Complex), deallocator: FreeTensorDataDelegate, deallocator_arg: IntPtr.Zero);
}
///
/// Creates a constant tensor with a single dimension from a float value.
///
public unsafe TFTensor (float value)
{
var v = (float*)Marshal.AllocHGlobal (sizeof (float));
*v = value;
handle = TF_NewTensor (TFDataType.Float, zeroDims: IntPtr.Zero, num_dims: 0, data: (IntPtr)v, len: (UIntPtr)sizeof (float), deallocator: FreeTensorDataDelegate, deallocator_arg: IntPtr.Zero);
}
///
/// Creates a constant tensor with a single dimension from a double value.
///
public unsafe TFTensor (double value)
{
var v = (double*)Marshal.AllocHGlobal (sizeof (double));
*v = value;
handle = TF_NewTensor (TFDataType.Double, zeroDims: IntPtr.Zero, num_dims: 0, data: (IntPtr)v, len: (UIntPtr)sizeof (double), deallocator: FreeTensorDataDelegate, deallocator_arg: IntPtr.Zero);
}
///
/// Creates a constant tensor with a single dimension from a long value.
///
public unsafe TFTensor (long value)
{
var v = (long*)Marshal.AllocHGlobal (sizeof (long));
*v = value;
handle = TF_NewTensor (TFDataType.Int64, zeroDims: IntPtr.Zero, num_dims: 0, data: (IntPtr)v, len: (UIntPtr)sizeof (long), deallocator: FreeTensorDataDelegate, deallocator_arg: IntPtr.Zero);
}
// Convenience, should I add T[,] and T[,,] as more convenience ones?
///
/// Creates a 1 dimensional tensor from an array of booleans.
///
/// Data.
public TFTensor (bool [] data) : base (SetupTensor (TFDataType.Bool, data, size: 1)) { }
///
/// Creates a 1 dimensional tensor from an array of sbytes.
///
/// Data.
public TFTensor (sbyte [] data) : base (SetupTensor (TFDataType.Int8, data, size: 1)) { }
///
/// Creates a 1 dimensional tensor from an array of bytes.
///
/// Data.
public TFTensor (byte [] data) : base (SetupTensor (TFDataType.UInt8, data, size: 1)) { }
///
/// Creates a 1 dimensional tensor from an array of shorts.
///
/// Data.
public TFTensor (short [] data) : base (SetupTensor (TFDataType.Int16, data, size: 2)) { }
///
/// Creates a 1 dimensional tensor from an array of ushorts
///
/// Data.
public TFTensor (ushort [] data) : base (SetupTensor (TFDataType.UInt16, data, size: 2)) { }
///
/// Creates a 1 dimensional tensor from an array of ints.
///
/// Data.
public TFTensor (int [] data) : base (SetupTensor (TFDataType.Int32, data, size: 4)) { }
///
/// Creates a 1 dimensional tensor from an array of floats.
///
/// Data.
public TFTensor (float [] data) : base (SetupTensor (TFDataType.Float, data, size: 4)) { }
///
/// Creates a 1 dimensional tensor from an array of doubles.
///
/// Data.
public TFTensor (double [] data) : base (SetupTensor (TFDataType.Double, data, size: 8)) { }
///
/// Creates a 1 dimensional tensor from an array of longs.
///
/// Data.
public TFTensor (long [] data) : base (SetupTensor (TFDataType.Int64, data, size: 8)) { }
///
/// Creates a 1 dimensional tensor from an array of complex numbers.
///
/// Data.
public TFTensor (Complex [] data) : base (SetupTensor (TFDataType.Complex128, data, size: 16)) { }
///
/// Creates a single-dimension tensor from a byte buffer. This is different than creating a tensor from a byte array that produces a tensor with as many elements as the byte array.
///
public unsafe static TFTensor CreateString (byte [] buffer)
{
if (buffer == null)
throw new ArgumentNullException (nameof (buffer));
//
// TF_STRING tensors are encoded with a table of 8-byte offsets followed by
// TF_StringEncode-encoded bytes.
//
var size = TFString.TF_StringEncodedSize ((UIntPtr)buffer.Length);
IntPtr handle = TF_AllocateTensor (TFDataType.String, IntPtr.Zero, 0, (UIntPtr)((ulong)size + 8));
// Clear offset table
IntPtr dst = TF_TensorData (handle);
Marshal.WriteInt64 (dst, 0);
var status = TFStatus.TF_NewStatus ();
fixed (byte* src = &buffer [0])
{
TFString.TF_StringEncode (src, (UIntPtr)buffer.Length, (sbyte*)(dst + 8), size, status);
var ok = TFStatus.TF_GetCode (status) == TFCode.Ok;
TFStatus.TF_DeleteStatus (status);
if (!ok)
return null;
}
return new TFTensor (handle);
}
// Convenience function to factor out the setup of a new tensor from an array
static IntPtr SetupTensor (TFDataType dt, long [] dims, Array data, int size)
{
return SetupTensor (dt, dims, data, start: 0, count: data.Length, size: size);
}
// Convenience function to factor out the setup of a new tensor from an array
static IntPtr SetupTensor (TFDataType dt, Array data, int size)
{
long [] dims = new long [data.Rank];
for (int i = 0; i < dims.Length; i++)
dims [i] = data.GetLength (i);
return SetupTensor (dt, dims, data, start: 0, count: data.Length, size: size);
}
// Use for single dimension arrays
static IntPtr SetupTensor (TFDataType dt, TFShape shape, Array data, int start, int count, int size)
{
if (shape == null)
throw new ArgumentNullException (nameof (shape));
return SetupTensor (dt, shape.dims, data, start, count, size);
}
// Use for single dimension arrays
static IntPtr SetupTensor (TFDataType dt, long [] dims, Array data, int start, int count, int size)
{
if (start < 0 || start > data.Length - count)
throw new ArgumentException ("start + count > Array size");
var dataHandle = GCHandle.Alloc (data, GCHandleType.Pinned);
if (dims == null)
return TF_NewTensor (dt, IntPtr.Zero, 0, dataHandle.AddrOfPinnedObject () + start * size, (UIntPtr)(count * size), FreeTensorHandleDelegate, GCHandle.ToIntPtr (dataHandle));
else
return TF_NewTensor (dt, dims, dims.Length, dataHandle.AddrOfPinnedObject () + start * size, (UIntPtr)(count * size), FreeTensorHandleDelegate, GCHandle.ToIntPtr (dataHandle));
}
// Use for multiple dimension arrays
static IntPtr SetupMulti (TFDataType dt, long [] dims, Array data, long bytes)
{
var dataHandle = GCHandle.Alloc (data, GCHandleType.Pinned);
if (dims == null)
return TF_NewTensor (dt, IntPtr.Zero, 0, dataHandle.AddrOfPinnedObject (), (UIntPtr)bytes, FreeTensorHandleDelegate, GCHandle.ToIntPtr (dataHandle));
else
return TF_NewTensor (dt, dims, dims.Length, dataHandle.AddrOfPinnedObject (), (UIntPtr)bytes, FreeTensorHandleDelegate, GCHandle.ToIntPtr (dataHandle));
}
//
// Factory methods to create tensors from a constant
//
///
/// Converts an integer into a 1-dimensional, 1-valued tensor.
///
/// The tensor representing the integer value.
/// Value to initialize the tensor with.
public static implicit operator TFTensor (int value)
{
return new TFTensor (value);
}
///
/// Converts a boolean into a 1-dimensional, 1-valued tensor.
///
/// The tensor representing the integer value.
/// Value to initialize the tensor with.
public static implicit operator TFTensor (bool value)
{
return new TFTensor (value);
}
///
/// Converts a long into a 1-dimensional, 1-valued tensor.
///
/// The tensor representing the long value.
/// Value to initialize the tensor with.
public static implicit operator TFTensor (long value)
{
return new TFTensor (value);
}
///
/// Converts a double into a 1-dimensional, 1-valued tensor.
///
/// The tensor representing the double value.
/// Value to initialize the tensor with.
unsafe public static implicit operator TFTensor (double value)
{
return new TFTensor (value);
}
///
/// Converts a float into a 1-dimensional, 1-valued tensor.
///
/// The tensor representing the float value.
/// Value to initialize the tensor with.
unsafe public static implicit operator TFTensor (float value)
{
return new TFTensor (value);
}
///
/// Converts a Complex number into a 1-dimensional, 1-valued tensor.
///
/// The tensor representing the complex value.
/// Value to initialize the tensor with.
unsafe public static implicit operator TFTensor (Complex value)
{
return new TFTensor (value);
}
///
/// Converts a byte into a 1-dimensional, 1-valued tensor.
///
/// The tensor representing the byte value.
/// Value to initialize the tensor with.
unsafe public static implicit operator TFTensor (byte value)
{
return new TFTensor (value);
}
///
/// Converts a C# array into a tensor.
///
/// The tensor containing the data.
/// single dimension, or multi-dimensional array.
///
/// This implicit conversion can convert single or multidimensional arrays of
/// booleans, sbytes, byte, shorts, ushorts, ints, longs, doubles, floats and
/// complex numbers into a tensor with the same dimensional shape as the provided
/// array.
///
unsafe public static implicit operator TFTensor (Array array)
{
return new TFTensor (array);
}
// General purpose constructor, specifies data type and gets pointer to buffer
// Is the default good, one where we let the user provide their own deallocator, or should we make a copy in that case?
///
/// Low-level tensor constructor that creates a tensor from a buffer pointed to by an IntPtr.
///
/// Specifies the data type held by the tensor, as well as how to interpret the provided data.
/// Describes the tensor shape, an array that indicates .
/// Pointer to the raw data that will be used to initialize the tensor.
/// The size of the data being passed in.
/// Deallocator method, it is invoked when the tensor is destroyed to release the data pointed to by . On platforms like iOS (or other static compilation platforms), yiou must annotate the method specified in the deallocator with a .
/// An optional argument of data that is passed to the deallocator method when the tensor is destroyed, you can use this to pass context information.
public TFTensor (TFDataType dataType, long [] dims, IntPtr data, size_t dataSize, Deallocator deallocator, IntPtr deallocatorData) : base (IntPtr.Zero)
{
if (dims == null)
throw new ArgumentNullException ("dims");
handle = TF_NewTensor (dataType, dims, dims.Length, data, dataSize, deallocator, deallocatorData);
}
internal override void NativeDispose (IntPtr handle)
{
TF_DeleteTensor (handle);
}
// extern TF_Tensor * TF_AllocateTensor (TF_DataType, const int64_t *dims, int num_dims, size_t len);
[DllImport (NativeBinding.TensorFlowLibrary)]
static extern unsafe TF_Tensor TF_AllocateTensor (TFDataType dataType, long [] dims, int num_dims, size_t len);
[DllImport (NativeBinding.TensorFlowLibrary)]
static extern unsafe TF_Tensor TF_AllocateTensor (TFDataType dataType, IntPtr zeroDim, int num_dims, size_t len);
///
/// Low-level: Creates an empty tensor of the specified type and shape, with the specified number of elements
///
/// Data type.
/// Tensor shape.
/// Size in bytes of the tensor, this will be the actual memory allocated.
///
/// It is the responsibility of the caller to ensure that the size is correct given the data type size
/// and the tensor dimension specified in dims.
///
public TFTensor (TFDataType dataType, long [] dims, int size) : base (IntPtr.Zero)
{
if (dims == null)
throw new ArgumentNullException ("dims");
handle = TF_AllocateTensor (dataType, dims, dims.Length, (size_t)size);
}
// extern void TF_DeleteTensor (TF_Tensor *);
[DllImport (NativeBinding.TensorFlowLibrary)]
static extern unsafe void TF_DeleteTensor (TF_Tensor tensor);
// extern TF_DataType TF_TensorType (const TF_Tensor *);
[DllImport (NativeBinding.TensorFlowLibrary)]
static extern unsafe TFDataType TF_TensorType (TF_Tensor tensor);
///
/// Returns the data type for the tensor.
///
/// The type of the tensor.
public TFDataType TensorType => TF_TensorType (handle);
// extern int TF_NumDims (const TF_Tensor *);
[DllImport (NativeBinding.TensorFlowLibrary)]
static extern unsafe int TF_NumDims (TF_Tensor tensor);
///
/// Returns the number of dimensions in the tensor.
///
///
/// For single-dimension tensors the return is 1, 2 dimensions is 2 and so on.
///
public int NumDims => TF_NumDims (handle);
// extern int64_t TF_Dim (const TF_Tensor *tensor, int dim_index);
[DllImport (NativeBinding.TensorFlowLibrary)]
static extern unsafe long TF_Dim (TF_Tensor tensor, int dim_index);
///
/// Returns the number of elements on a specific dimension in the tensor.
///
/// The tensor dimension.
/// Dimension that you are querying.
///
/// If you have a tensor of 3 elements by 5, represented by [3 5],
/// the GetTensorDimension(0) will return 3, the GetTensorDimension(1)
/// will return 5.
///
public long GetTensorDimension (int dimIndex)
{
return TF_Dim (handle, dimIndex);
}
// extern size_t TF_TensorByteSize (const TF_Tensor *);
[DllImport (NativeBinding.TensorFlowLibrary)]
static extern unsafe size_t TF_TensorByteSize (TF_Tensor tensor);
public size_t TensorByteSize => TF_TensorByteSize (handle);
// extern void * TF_TensorData (const TF_Tensor *);
[DllImport (NativeBinding.TensorFlowLibrary)]
static extern unsafe IntPtr TF_TensorData (TF_Tensor tensor);
///
/// Returns a pointer to the raw data in the tensor.
///
///
/// The contents of the Data must be interpreted according to the type of the
/// data as described by the DataType property. The amount of data
/// is given by the the TensorByteSize property.
///
public IntPtr Data => TF_TensorData (handle);
///
/// Returns the tensor shape, this is an array whose size determines the number of dimensions on the tensor, and each element is the size of the dimension
///
///
/// An array of size 0 is used for constants, an array of size 1 is used
/// for single-dimension arrays, where the dimension is the value of the
/// first element. And so on.
///
public long [] Shape {
get {
var dims = new long [TF_NumDims (handle)];
for (int i = 0; i < dims.Length; i++)
dims [i] = (int)TF_Dim (handle, i);
return dims;
}
}
///
/// Converts a to a system type.
///
/// The to be converted.
/// The system type corresponding to the given .
public static Type TypeFromTensorType (TFDataType type)
{
switch (type) {
case TFDataType.Float:
return typeof (float);
case TFDataType.Double:
return typeof (double);
case TFDataType.Int32:
return typeof (int);
case TFDataType.UInt8:
return typeof (byte);
case TFDataType.Int16:
return typeof (short);
case TFDataType.Int8:
return typeof (sbyte);
case TFDataType.String:
return typeof (TFString);
case TFDataType.Int64:
return typeof (long);
case TFDataType.Bool:
return typeof (bool);
case TFDataType.UInt16:
return typeof (ushort);
case TFDataType.Complex128:
return typeof (Complex);
default:
return null;
}
}
///
/// Converts a system type to a .
///
/// The system type to be converted.
/// The corresponding to the given type.
public static TFDataType TensorTypeFromType (Type type)
{
if (type == typeof (float))
return TFDataType.Float;
if (type == typeof (double))
return TFDataType.Double;
if (type == typeof (int))
return TFDataType.Int32;
if (type == typeof (byte))
return TFDataType.UInt8;
if (type == typeof (short))
return TFDataType.Int16;
if (type == typeof (sbyte))
return TFDataType.Int8;
if (type == typeof (string))
return TFDataType.String;
if (type == typeof (long))
return TFDataType.Int64;
if (type == typeof (bool))
return TFDataType.Bool;
if (type == typeof (ushort))
return TFDataType.UInt16;
if (type == typeof (Complex))
return TFDataType.Complex128;
throw new ArgumentOutOfRangeException (nameof(type), $"The given type could not be mapped to an existing {nameof(TFDataType)}.");
}
static unsafe object FetchSimple (TFDataType dt, IntPtr data)
{
switch (dt) {
case TFDataType.Float:
return *(float*)data;
case TFDataType.Double:
return *(double*)data;
case TFDataType.Int32:
return *(int*)data;
case TFDataType.UInt8:
return *(byte*)data;
case TFDataType.Int16:
return *(short*)data;
case TFDataType.Int8:
return *(sbyte*)data;
case TFDataType.String:
throw new NotImplementedException ();
case TFDataType.Int64:
return *(long*)data;
case TFDataType.Bool:
return *(bool*)data;
case TFDataType.UInt16:
return *(ushort*)data;
case TFDataType.Complex128:
return *(Complex*)data;
default:
return null;
}
}
unsafe static void Copy (IntPtr src, void* target, int size)
{
Buffer.MemoryCopy ((void*)src, target, size, size);
}
static unsafe void FetchFlatArray (Array target, TFDataType dt, IntPtr data)
{
int len = target.Length;
switch (dt) {
case TFDataType.Int8:
var asbyte = (sbyte [])target;
fixed (sbyte* p = &asbyte [0])
Copy (data, p, len);
return;
case TFDataType.Bool:
var abool = (bool [])target;
fixed (bool* p = &abool [0])
Copy (data, p, len);
return;
case TFDataType.UInt16:
var aushort = (ushort [])target;
fixed (ushort* p = &aushort [0])
Copy (data, p, len * 2);
return;
case TFDataType.Complex128:
var acomplex = (Complex [])target;
fixed (Complex* p = &acomplex [0])
Copy (data, p, len * sizeof (Complex));
return;
case TFDataType.Float:
var afloat = (float [])target;
fixed (float* p = &afloat [0])
Copy (data, p, len * sizeof (float));
return;
case TFDataType.Double:
var adouble = (double [])target;
fixed (double* p = &adouble [0])
Copy (data, p, len * sizeof (double));
return;
case TFDataType.Int32:
var aint = (int [])target;
fixed (int* p = &aint [0])
Copy (data, p, len * sizeof (double));
return;
case TFDataType.UInt8:
var abyte = (byte [])target;
fixed (byte* p = &abyte [0])
Copy (data, p, len * sizeof (byte));
return;
case TFDataType.Int16:
var ashort = (short [])target;
fixed (short* p = &ashort [0])
Copy (data, p, len * sizeof (short));
return;
case TFDataType.Int64:
var along = (long [])target;
fixed (long* p = &along [0])
Copy (data, p, len * sizeof (long));
return;
case TFDataType.String:
// need to return an array of TFStrings []
throw new NotImplementedException ();
default:
throw new NotImplementedException ();
}
}
static unsafe object FetchJaggedArray (Type t, TFDataType dt, ref IntPtr data, long [] shape, int level = 0)
{
Array target;
// If we are at the last node
if (level == shape.Length - 1) {
target = Array.CreateInstance (t, shape [level]);
for (long l = 0; l < shape [level]; l++)
switch (dt) {
case TFDataType.Float:
target.SetValue ((*(float*)data), l);
data += 4;
break;
case TFDataType.Double:
target.SetValue ((*(double*)data), l);
data += 8;
break;
case TFDataType.Int32:
target.SetValue ((*(int*)data), l);
data += 4;
break;
case TFDataType.UInt8:
target.SetValue ((*(byte*)data), l);
data += 1;
break;
case TFDataType.Int16:
target.SetValue ((*(short*)data), l);
data += 2;
break;
case TFDataType.Int8:
target.SetValue ((*(sbyte*)data), l);
data += 1;
break;
case TFDataType.Int64:
target.SetValue ((*(long*)data), l);
data += 8;
break;
case TFDataType.Bool:
target.SetValue ((*(bool*)data), l);
data += 1;
break;
case TFDataType.Complex128:
target.SetValue ((*(Complex*)data), l);
data += sizeof (Complex);
break;
case TFDataType.String:
throw new NotImplementedException ("String decoding not implemented for tensor vecotrs yet");
default:
throw new NotImplementedException ();
}
} else {
target = null;
long top = shape [level];
if (top < Int32.MaxValue) {
int itop = (int)top;
for (int i = 0; i < itop; i++) {
var childArray = FetchJaggedArray (t, dt, ref data, shape, level + 1);
if (target == null)
target = Array.CreateInstance (childArray.GetType (), shape [level]);
target.SetValue (childArray, i);
}
} else {
for (long l = 0; l < top; l++) {
var chidArray = FetchJaggedArray (t, dt, ref data, shape, level + 1);
if (target == null)
target = Array.CreateInstance (chidArray.GetType (), shape [level]);
target.SetValue (chidArray, l);
}
}
return target;
}
return target;
}
static void FetchMultiDimensionalArray (Array target, TFDataType dt, IntPtr data, long [] shape)
{
var idx = new int [shape.Length];
for (int i = 0; i < shape.Length; i++) {
if (shape [i] > Int32.MaxValue)
throw new ArgumentOutOfRangeException ("Shape can not be longer than 32 bits");
}
Copy (target, dt, shape, idx, 0, ref data);
}
static unsafe void Copy (Array target, TFDataType dt, long [] shape, int [] idx, int level, ref IntPtr data)
{
if (level < shape.Length - 1) {
for (idx [level] = 0; idx [level] < shape [level]; idx [level]++)
Copy (target, dt, shape, idx, level + 1, ref data);
} else {
for (idx [level] = 0; idx [level] < shape [level]; idx [level]++) {
switch (dt) {
case TFDataType.Float:
target.SetValue ((*(float*)data), idx);
data += 4;
break;
case TFDataType.Double:
target.SetValue ((*(double*)data), idx);
data += 8;
break;
case TFDataType.Int32:
target.SetValue ((*(int*)data), idx);
data += 4;
break;
case TFDataType.UInt8:
target.SetValue ((*(byte*)data), idx);
data += 1;
break;
case TFDataType.Int16:
target.SetValue ((*(short*)data), idx);
data += 2;
break;
case TFDataType.Int8:
target.SetValue ((*(sbyte*)data), idx);
data += 1;
break;
case TFDataType.Int64:
target.SetValue ((*(long*)data), idx);
data += 8;
break;
case TFDataType.Bool:
target.SetValue ((*(bool*)data), idx);
data += 1;
break;
case TFDataType.Complex128:
target.SetValue ((*(Complex*)data), idx);
data += sizeof (Complex);
break;
case TFDataType.String:
throw new NotImplementedException ("String decoding not implemented for tensor vecotrs yet");
default:
throw new NotImplementedException ();
}
}
}
}
///
/// Returns the value of the Tensor as a C# type if possible, or null if the data type can not be represented in C#
///
///
/// The default is set to false, which returns .NET multi-dimensional arrays for multi-dimensional
/// tensors. This is useful to feed the data back as a TFTensor created from an array. Set to
/// true if you want to get arrays pointing to arrays, which are slightly more convenient to work
/// with from C#
///
///
/// Jagged arrays create various intermediate arrays, while multi-dimensional arrays are more
/// efficient memory-wise.
///
/// The value encodes the contents of the tensor, and could include simple values, arrays and multi-dimensional values.
public object GetValue (bool jagged = false)
{
var dims = NumDims;
if (dims == 0)
return FetchSimple (TensorType, Data);
var t = TypeFromTensorType (TensorType);
if (t == null)
return null;
if (dims == 1) {
var result = Array.CreateInstance (t, Shape [0]);
FetchFlatArray (result, TensorType, Data);
return result;
} else {
if (jagged) {
IntPtr data = Data;
return FetchJaggedArray (t, TensorType, ref data, Shape);
} else {
var result = Array.CreateInstance (t, Shape);
FetchMultiDimensionalArray (result, TensorType, Data, Shape);
return result;
}
}
}
public override string ToString ()
{
var n = NumDims;
if (n == 0)
return GetValue ().ToString ();
StringBuilder sb = new StringBuilder ("[");
for (int i = 0; i < n; i++) {
sb.Append (TF_Dim (handle, i));
if (i + 1 < n)
sb.Append ("x");
}
sb.Append ("]");
return sb.ToString ();
}
}
}