diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ec3fe0..10b05c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## v1.5.0 - 27-11-2024 + +### Added + +- Created methods for serialising and deserialising `DTensor` objects + + diff --git a/README.md b/README.md index 3a7c260..6995001 100644 --- a/README.md +++ b/README.md @@ -246,6 +246,27 @@ The `DTensor` `B` will be overwritten with the solution. > overwrite only part of the given `B`, as `B` is a > (4,1,1)-tensor and the solution is a (3,1,1)-tensor. +### 1.8. Saving and loading tensors + +Tensor data can be stored in simple text files which have the following structure + +```text +number_of_rows +number_of_columns +number_of_matrices +data (one entry per line) +``` + +To save a tensor in a file, simply call `DTensor::saveToFile(filename)`. + +To load a tensor from a file, the static function `DTensor::parseFromTextFile(filename)` can be used. For example: + +```c++ +auto z = DTensor::parseFromTextFile("path/to/my.dtensor") +``` + +If necessary, you can provide a second argument to `parseFromTextFile` to specify the order in which the data are stored (the `StorageMode`). + ## 2. Cholesky factorisation and system solution > [!WARNING] diff --git a/include/tensor.cuh b/include/tensor.cuh index 5a08392..6da01a5 100644 --- a/include/tensor.cuh +++ b/include/tensor.cuh @@ -11,6 +11,7 @@ #include #include #include +#include #ifndef TENSOR_CUH #define TENSOR_CUH @@ -250,6 +251,19 @@ public: */ static DTensor createRandomTensor(size_t numRows, size_t numCols, size_t numMats, T low, T hi); + /** + * Parse data from text file and create an instance of DTensor + * + * This static function reads data from a text file, creates a DTensor and uploads the data to the device. + * + * @param path_to_file path to file as string + * @param mode storage mode (default: StorageMode::defaultMajor) + * @return instance of DTensor + * + * @throws std::invalid_argument if the file is not found + */ + static DTensor parseFromTextFile(std::string path_to_file, StorageMode mode = StorageMode::defaultMajor); + /** * Constructs a DTensor object. */ @@ -487,6 +501,13 @@ public: */ void reshape(size_t newNumRows, size_t newNumCols, size_t newNumMats = 1); + /** + * Saves the current instance of DTensor to a (text) file + * + * @param pathToFile + */ + void saveToFile(std::string pathToFile); + /* ------------- OPERATORS ------------- */ DTensor &operator=(const DTensor &other); @@ -564,6 +585,80 @@ DTensor DTensor::createRandomTensor(size_t numRows, size_t numCols, size_t throw std::invalid_argument("[createRandomTensor] unsupported type T"); } + +template +struct data_t { + size_t numRows; + size_t numCols; + size_t numMats; + std::vector data; +}; + +template +data_t vectorFromFile(std::string path_to_file) { + data_t dataStruct; + std::ifstream file; + file.open(path_to_file, std::ios::in); + if (!file.is_open()) { throw std::invalid_argument("the file you provided does not exist"); }; + + std::string line; + getline(file, line); dataStruct.numRows = atoi(line.c_str()); + getline(file, line); dataStruct.numCols = atoi(line.c_str()); + getline(file, line); dataStruct.numMats = atoi(line.c_str()); + + size_t numElements = dataStruct.numRows * dataStruct.numCols * dataStruct.numMats; + std::vector vecDataFromFile(numElements); + + size_t i = 0; + while (getline(file, line)) { + if constexpr (std::is_same_v) { + vecDataFromFile[i] = atoi(line.c_str()); + } else if constexpr (std::is_same_v) { + vecDataFromFile[i] = std::stod(line.c_str()); + } else if constexpr (std::is_same_v) { + vecDataFromFile[i] = std::stof(line.c_str()); + } else if constexpr (std::is_same_v) { + vecDataFromFile[i] = std::stold(line.c_str()); + } else if constexpr (std::is_same_v) { + vecDataFromFile[i] = std::stol(line.c_str()); + } else if constexpr (std::is_same_v) { + vecDataFromFile[i] = std::stoll(line.c_str()); + } else if constexpr (std::is_same_v) { + vecDataFromFile[i] = std::stoul(line.c_str()); + } else if constexpr (std::is_same_v) { + vecDataFromFile[i] = std::stoull(line.c_str()); + } else if constexpr (std::is_same_v) { + sscanf(line.c_str(), "%zu", &vecDataFromFile[i]); + } + // todo + + if (++i == numElements) break; + } + dataStruct.data = vecDataFromFile; + file.close(); + return dataStruct; +} + +template +DTensor DTensor::parseFromTextFile(std::string path_to_file, + StorageMode mode) { + auto parsedData = vectorFromFile(path_to_file); + DTensor tensorFromData(parsedData.data, parsedData.numRows, parsedData.numCols, parsedData.numMats); + return tensorFromData; +} + +template +void DTensor::saveToFile(std::string pathToFile) { + std::ofstream file(pathToFile); + file << numRows() << std::endl << numCols() << std::endl << numMats() << std::endl; + std::vector myData(numEl()); download(myData); + if constexpr (std::is_floating_point::value) { + int prec = std::numeric_limits::max_digits10 - 1; + file << std::setprecision(prec); + } + for(const T& el : myData) file << el << std::endl; +} + template void DTensor::reshape(size_t newNumRows, size_t newNumCols, size_t newNumMats) { if (m_numRows == newNumRows && m_numCols == newNumCols && m_numMats == newNumMats) return; diff --git a/main.cu b/main.cu index d9f3421..cc911e9 100644 --- a/main.cu +++ b/main.cu @@ -1,27 +1,14 @@ -#include -#include -#include -#include -#include -#include #include "include/tensor.cuh" -#include +#include +#include +#include +#include -#define real_t double int main() { - - std::vector aData = {10.0, 2.0, 3.0, - 2.0, 20.0, -1.0, - 3.0, -1.0, 30.0}; - DTensor A(3, 3, 2); - DTensor A0(A, 2, 0, 0); - DTensor A1(A, 2, 1, 1); - A0.upload(aData); - A1.upload(aData); - CholeskyBatchFactoriser chol(A); - chol.factorise(); - std::cout << chol.info()(0); - + auto z = DTensor::parseFromTextFile("../test/data/my.dtensor", + StorageMode::rowMajor); + std::cout << z; + z.saveToFile("hohoho.dtensor"); return 0; } diff --git a/test/testTensor.cu b/test/testTensor.cu index 0b0d312..2d97df7 100644 --- a/test/testTensor.cu +++ b/test/testTensor.cu @@ -1,5 +1,6 @@ #include #include "../include/tensor.cuh" +#include #define PRECISION_LOW 1e-4 #define PRECISION_HIGH 1e-10 @@ -115,6 +116,30 @@ TEST_F(TensorTest, randomTensorCreation) { randomTensorCreation(); } +/* --------------------------------------- + * Save to file and parse + * --------------------------------------- */ + +TEMPLATE_WITH_TYPE_T +void parseTensorFromFile() { + size_t nR = 20, nC = 40, nM = 60; + auto r = DTensor::createRandomTensor(nR, nC, nM, -1, 1); + std::string fName = "myTest.dtensor"; + r.saveToFile(fName); + auto a = DTensor::parseFromTextFile(fName); + EXPECT_EQ(nR, a.numRows()); + EXPECT_EQ(nC, a.numCols()); + EXPECT_EQ(nM, a.numMats()); + auto diff = a - r; + T err = diff.maxAbs(); + EXPECT_LT(err, 2*std::numeric_limits::epsilon()); +} + +TEST_F(TensorTest, parseTensorFromFile) { + parseTensorFromFile(); + parseTensorFromFile(); +} + /* --------------------------------------- * Move constructor * --------------------------------------- */