diff --git a/runtime/common/RecordLogger.h b/runtime/common/RecordLogger.h new file mode 100644 index 00000000000..0d063751edb --- /dev/null +++ b/runtime/common/RecordLogger.h @@ -0,0 +1,69 @@ +/****************************************************************-*- C++ -*-**** + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#pragma once + +#include +#include +#include +#include +#include + +namespace cudaq { + +struct RecordLogger { +private: + bool emitLabel = false; + std::stringstream ss; + +public: + void emitHeader() { + ss << "HEADER\tschema_name\t" << (emitLabel ? "labeled" : "ordered") + << "\n"; + ss << "HEADER\tschema_version\t1.0\n"; + } + + template + void logSingleRecord(const T &record, std::string label = "") { + ss << "OUTPUT\t"; + if (typeid(T) == typeid(bool)) + ss << "BOOL\t" << (record ? "true" : "false"); + else if (typeid(T) == typeid(int)) + ss << "INT\t" << record; + else if (typeid(T) == typeid(float)) + ss << "FLOAT\t" << record; + else if (typeid(T) == typeid(double)) + ss << "DOUBLE\t" << record; + else + throw std::runtime_error("Unsupported output record type"); + if (emitLabel) { + if (label.empty()) + throw std::runtime_error( + "Non-empty label expected for the output record"); + else + ss << "\t" << label; + } + ss << "\n"; + } + + template + void log(const std::vector &records) { + emitHeader(); + for (auto r : records) { + ss << "START\n"; + logSingleRecord(r); + ss << "END\t0\n"; // Assumes success always + } + } + + std::string getLog() { return ss.str(); } + + void writeToFile(std::ofstream &outFile) { outFile << ss.rdbuf(); } +}; + +} // namespace cudaq diff --git a/runtime/common/RecordParser.h b/runtime/common/RecordParser.h new file mode 100644 index 00000000000..b784b5b6fb2 --- /dev/null +++ b/runtime/common/RecordParser.h @@ -0,0 +1,85 @@ +/****************************************************************-*- C++ -*-**** + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#pragma once + +#include "cudaq/utils/cudaq_utils.h" +#include +#include + +namespace cudaq { + +struct OutputRecord { + void *buffer; + std::size_t size; +}; + +struct RecordParser { +private: + bool labelExpected = false; + +public: + std::vector parse(const std::string &data) { + std::vector results; + std::vector lines = cudaq::split(data, '\n'); + std::size_t arrSize = 0; + int arrIdx = -1; + for (auto line : lines) { + std::vector entries = cudaq::split(line, '\t'); + if (entries.empty()) + continue; + if (entries[0] != "OUTPUT") + throw std::runtime_error("Invalid data"); + + /// TODO: Handle labeled records + if ("BOOL" == entries[1]) { + bool value; + if ("true" == entries[2]) + value = true; + else if ("false" == entries[2]) + value = false; + results.emplace_back( + OutputRecord{static_cast(new bool(value)), sizeof(bool)}); + } else if ("INT" == entries[1]) { + if (0 != arrSize) { + if (0 == arrIdx) { + int *resArr = new int[arrSize]; + results.emplace_back(OutputRecord{static_cast(resArr), + sizeof(int) * arrSize}); + } + static_cast(results.back().buffer)[arrIdx++] = + std::stoi(entries[2]); + if (arrIdx == arrSize) { + arrSize = 0; + arrIdx = -1; + } + } else + results.emplace_back( + OutputRecord{static_cast(new int(std::stoi(entries[2]))), + sizeof(int)}); + } else if ("FLOAT" == entries[1]) { + results.emplace_back( + OutputRecord{static_cast(new int(std::stof(entries[2]))), + sizeof(float)}); + } else if ("DOUBLE" == entries[1]) { + results.emplace_back( + OutputRecord{static_cast(new int(std::stod(entries[2]))), + sizeof(double)}); + } else if ("ARRAY" == entries[1]) { + arrSize = std::stoi(entries[2]); + if (0 == arrSize) + throw std::runtime_error("Got empty array"); + arrIdx = 0; + } + /// TODO: Handle more types + } + return results; + } +}; + +} // namespace cudaq diff --git a/unittests/CMakeLists.txt b/unittests/CMakeLists.txt index 81b7202ae6c..b41daa775ec 100644 --- a/unittests/CMakeLists.txt +++ b/unittests/CMakeLists.txt @@ -330,6 +330,7 @@ endif() add_subdirectory(backends) add_subdirectory(Optimizer) +add_subdirectory(output_record) set(CUDAQ_BRAKET_RUNTIME_TEST_SOURCES # Integration tests diff --git a/unittests/output_record/CMakeLists.txt b/unittests/output_record/CMakeLists.txt new file mode 100644 index 00000000000..979171b027f --- /dev/null +++ b/unittests/output_record/CMakeLists.txt @@ -0,0 +1,21 @@ +# ============================================================================ # +# Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. # +# All rights reserved. # +# # +# This source code and the accompanying materials are made available under # +# the terms of the Apache License 2.0 which accompanies this distribution. # +# ============================================================================ # + +add_executable(test_record RecordLoggerTester.cpp RecordParserTester.cpp) + +if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND NOT APPLE) + target_link_options(test_record PRIVATE -Wl,--no-as-needed) +endif() +target_include_directories(test_record PRIVATE ..) +target_link_libraries(test_record PRIVATE + fmt::fmt-header-only + cudaq-common + gtest_main +) + +gtest_discover_tests(test_record) diff --git a/unittests/output_record/RecordLoggerTester.cpp b/unittests/output_record/RecordLoggerTester.cpp new file mode 100644 index 00000000000..f0ef198b0aa --- /dev/null +++ b/unittests/output_record/RecordLoggerTester.cpp @@ -0,0 +1,24 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#include "CUDAQTestUtils.h" +#include "common/RecordLogger.h" +#include + +CUDAQ_TEST(LoggerTester, checkBoolean) { + std::vector results = {true, false, false, true}; + + const std::string expectedLog = + "HEADER\tschema_name\tordered\nHEADER\tschema_version\t1.0\n" + "START\nOUTPUT\tBOOL\ttrue\nEND\t0\nSTART\nOUTPUT\tBOOL\tfalse\nEND\t0\n" + "START\nOUTPUT\tBOOL\tfalse\nEND\t0\nSTART\nOUTPUT\tBOOL\ttrue\nEND\t0\n"; + + cudaq::RecordLogger logger; + logger.log(results); + EXPECT_TRUE(expectedLog == logger.getLog()); +} \ No newline at end of file diff --git a/unittests/output_record/RecordParserTester.cpp b/unittests/output_record/RecordParserTester.cpp new file mode 100644 index 00000000000..e7597427e40 --- /dev/null +++ b/unittests/output_record/RecordParserTester.cpp @@ -0,0 +1,48 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#include "CUDAQTestUtils.h" +#include "common/RecordParser.h" +#include + +CUDAQ_TEST(ParserTester, checkSingleBoolean) { + const std::string log = "OUTPUT\tBOOL\ttrue"; + cudaq::RecordParser parser; + auto results = parser.parse(log); + EXPECT_EQ(1, results.size()); + EXPECT_EQ(true, *static_cast(results[0].buffer)); +} + +CUDAQ_TEST(ParserTester, checkIntegers) { + const std::string log = "OUTPUT\tINT\t0\n" + "OUTPUT\tINT\t1\n" + "OUTPUT\tINT\t2\n"; + cudaq::RecordParser parser; + auto results = parser.parse(log); + EXPECT_EQ(3, results.size()); + EXPECT_EQ(2, *static_cast(results[2].buffer)); +} + +CUDAQ_TEST(ParserTester, checkArray) { + const std::string log = "OUTPUT\tARRAY\t4\n" + "OUTPUT\tINT\t0\n" + "OUTPUT\tINT\t1\n" + "OUTPUT\tINT\t1\n" + "OUTPUT\tINT\t0"; + cudaq::RecordParser parser; + auto results = parser.parse(log); + EXPECT_EQ(1, results.size()); + auto count = results[0].size / sizeof(int); + EXPECT_EQ(4, count); + auto *ptr = static_cast(results[0].buffer); + std::vector got(count); + for (int i = 0; i < count; ++i) + got[i] = ptr[i]; + EXPECT_EQ(1, got[2]); + EXPECT_EQ(0, got[3]); +} \ No newline at end of file