Merge pull request #10 from tboudreaux/spec/OPAT

This commit is contained in:
2025-02-16 12:20:19 -05:00
committed by GitHub
5 changed files with 118 additions and 8 deletions

View File

@@ -11,6 +11,26 @@
#include <limits>
#include <deque>
// Function to check system endianness
bool is_big_endian() {
uint16_t test = 0x1;
return reinterpret_cast<uint8_t*>(&test)[0] == 0;
}
// Generic function to swap bytes for any type
template <typename T>
T swap_bytes(T value) {
static_assert(std::is_trivially_copyable<T>::value, "swap_bytes only supports trivial types.");
T result;
uint8_t* src = reinterpret_cast<uint8_t*>(&value);
uint8_t* dest = reinterpret_cast<uint8_t*>(&result);
for (size_t i = 0; i < sizeof(T); i++) {
dest[i] = src[sizeof(T) - 1 - i];
}
return result;
}
// Constructor
OpatIO::OpatIO() {}
@@ -57,6 +77,12 @@ void OpatIO::readHeader(std::ifstream &file) {
if (file.gcount() != sizeof(Header)) {
throw std::runtime_error("Error reading header from file: " + filename);
}
if (is_big_endian()) {
header.version = swap_bytes(header.version);
header.numTables = swap_bytes(header.numTables);
header.indexOffset = swap_bytes(header.indexOffset);
}
}
// Read the table index from the file
@@ -67,6 +93,7 @@ void OpatIO::readTableIndex(std::ifstream &file) {
if (file.gcount() != static_cast<std::streamsize>(header.numTables * sizeof(TableIndex))) {
throw std::runtime_error("Error reading table index from file: " + filename);
}
buildTableIDToComposition();
}

View File

@@ -9,6 +9,7 @@
#include <map>
#include <utility>
#pragma pack(1)
struct Header {
char magic[4];
uint16_t version;
@@ -21,6 +22,7 @@ struct Header {
char reserved[26];
};
#pragma pack()
struct TableIndex {
double X;
double Z;

View File

@@ -6,7 +6,7 @@
#include <set>
#include <sstream>
std::string EXAMPLE_FILENAME = std::string(getenv("MESON_SOURCE_ROOT")) + "/tests/opatIO/example.opat";
std::string EXAMPLE_FILENAME = std::string(getenv("MESON_SOURCE_ROOT")) + "/tests/opatIO/test.opat";
/**
* @file opatIOTest.cpp
@@ -39,9 +39,9 @@ TEST_F(opatIOTest, Header) {
EXPECT_EQ(header.numTables, 20);
EXPECT_EQ(header.headerSize, 256);
EXPECT_EQ(header.indexOffset, 416416);
EXPECT_EQ(std::string(header.creationDate), "Feb 14, 2025");
EXPECT_EQ(std::string(header.sourceInfo), "MESA 12700, Synthetic Opacity Data");
EXPECT_EQ(std::string(header.comment), "log10 kappa (cm^2/g)");
EXPECT_EQ(std::string(header.creationDate), "Feb 16, 2025");
EXPECT_EQ(std::string(header.sourceInfo), "no source provided by user");
EXPECT_EQ(std::string(header.comment), "default header");
}
TEST_F(opatIOTest, TableIndex) {

BIN
tests/opatIO/example.opat → tests/opatIO/test.opat Executable file → Normal file

Binary file not shown.

View File

@@ -186,6 +186,87 @@ class OpatIO:
return tableIndexBytes
def __repr__(self) -> str:
reprString = f"""OpatIO(
versoion: {self.header.version}
numTables: {self.header.numTables}
headerSize: {self.header.headerSize}
indexOffset: {self.header.indexOffset}
creationDate: {self.header.creationDate}
sourceInfo: {self.header.sourceInfo}
comment: {self.header.comment}
reserved: {self.header.reserved}
)"""
return reprString
def _format_table_as_string(self, table: OPATTable, X: float, Z: float) -> str:
tableString: List[str] = []
# fixed width X and Z header per table
tableString.append(f"X: {X:<10.4f} Z: {Z:<10.4f}")
tableString.append("-" * 80)
# write logR accross the top (reserving one col for where logT will be)
logRRow = f"{"":<10}"
logRRowTrue = "".join(f"{r:<10.4f}" for r in table.logR)
tableString.append(logRRow + logRRowTrue)
for i, logT in enumerate(table.logT):
row = f"{logT:<10.4f}"
for kappa in table.logKappa[i]:
row += f"{kappa:<10.4f}"
tableString.append(row)
tableString.append("=" * 80)
return '\n'.join(tableString)
@staticmethod
def print_table_indexes(table_indexes: List[TableIndex]) -> str:
if not table_indexes:
print("No table indexes found.")
return
tableRows: List[str] = []
tableRows.append("\nTable Indexes in OPAT File:\n")
tableRows.append(f"{'X':<10} {'Z':<10} {'Byte Start':<15} {'Byte End':<15} {'Checksum (SHA-256)'}")
tableRows.append("=" * 80)
for entry in table_indexes:
tableRows.append(f"{entry.X:<10.4f} {entry.Z:<10.4f} {entry.byteStart:<15} {entry.byteEnd:<15} {entry.sha256[:16]}...")
return '\n'.join(tableRows)
def save_as_ascii(self, filename: str) -> str:
numericFMT = "{:.18e}"
currentStartByte: int = 256
tableIndexs: List[bytes] = []
tableStrings: List[bytes] = []
for (X, Z), table in self.tables:
checksum, tableBytes = self._table_bytes(table)
tableStrings.append(self._format_table_as_string(table, X, Z) + "\n")
tableIndex = TableIndex(
X = X,
Z = Z,
byteStart = currentStartByte,
byteEnd = currentStartByte + len(tableBytes),
sha256 = checksum
)
tableIndexs.append(tableIndex)
currentStartByte += len(tableBytes)
self.header.indexOffset = currentStartByte
with open(filename, 'w') as f:
f.write(f"{self.header.magic}\n")
f.write(f"Version: {self.header.version}\n")
f.write(f"numTables: {self.header.numTables}\n")
f.write(f"headerSize (bytes): {self.header.headerSize}\n")
f.write(f"tableIndex Offset (bytes): {self.header.indexOffset}\n")
f.write(f"Creation Date: {self.header.creationDate}\n")
f.write(f"Source Info: {self.header.sourceInfo}\n")
f.write(f"Comment: {self.header.comment}\n")
f.write("="*80 + "\n")
f.write("="*80 + "\n")
for tableString in tableStrings:
f.write(tableString)
f.write("="*80 + "\n")
f.write("="*80 + "\n")
f.write(self.print_table_indexes(tableIndexs))
def save(self, filename: str) -> str:
tempHeaderBytes = self._header_bytes()
@@ -229,14 +310,14 @@ def loadOpat(filename: str) -> OpatIO:
headerBytes: bytes = f.read(256)
unpackedHeader = struct.unpack("<4s H I I Q 16s 64s 128s 26s", headerBytes)
loadedHeader = Header(
magic = unpackedHeader[0].decode(),
magic = unpackedHeader[0].decode().replace("\x00", ""),
version = unpackedHeader[1],
numTables = unpackedHeader[2],
headerSize = unpackedHeader[3],
indexOffset = unpackedHeader[4],
creationDate = unpackedHeader[5].decode(),
sourceInfo = unpackedHeader[6].decode(),
comment = unpackedHeader[7].decode(),
creationDate = unpackedHeader[5].decode().replace("\x00", ""),
sourceInfo = unpackedHeader[6].decode().replace("\x00", ""),
comment = unpackedHeader[7].decode().replace("\x00", ""),
reserved = unpackedHeader[8]
)
opat.header = loadedHeader