Merge pull request #10 from tboudreaux/spec/OPAT
This commit is contained in:
@@ -11,6 +11,26 @@
|
|||||||
#include <limits>
|
#include <limits>
|
||||||
#include <deque>
|
#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
|
// Constructor
|
||||||
OpatIO::OpatIO() {}
|
OpatIO::OpatIO() {}
|
||||||
|
|
||||||
@@ -57,6 +77,12 @@ void OpatIO::readHeader(std::ifstream &file) {
|
|||||||
if (file.gcount() != sizeof(Header)) {
|
if (file.gcount() != sizeof(Header)) {
|
||||||
throw std::runtime_error("Error reading header from file: " + filename);
|
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
|
// 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))) {
|
if (file.gcount() != static_cast<std::streamsize>(header.numTables * sizeof(TableIndex))) {
|
||||||
throw std::runtime_error("Error reading table index from file: " + filename);
|
throw std::runtime_error("Error reading table index from file: " + filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTableIDToComposition();
|
buildTableIDToComposition();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#include <map>
|
#include <map>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
|
#pragma pack(1)
|
||||||
struct Header {
|
struct Header {
|
||||||
char magic[4];
|
char magic[4];
|
||||||
uint16_t version;
|
uint16_t version;
|
||||||
@@ -21,6 +22,7 @@ struct Header {
|
|||||||
char reserved[26];
|
char reserved[26];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#pragma pack()
|
||||||
struct TableIndex {
|
struct TableIndex {
|
||||||
double X;
|
double X;
|
||||||
double Z;
|
double Z;
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
#include <set>
|
#include <set>
|
||||||
#include <sstream>
|
#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
|
* @file opatIOTest.cpp
|
||||||
@@ -39,9 +39,9 @@ TEST_F(opatIOTest, Header) {
|
|||||||
EXPECT_EQ(header.numTables, 20);
|
EXPECT_EQ(header.numTables, 20);
|
||||||
EXPECT_EQ(header.headerSize, 256);
|
EXPECT_EQ(header.headerSize, 256);
|
||||||
EXPECT_EQ(header.indexOffset, 416416);
|
EXPECT_EQ(header.indexOffset, 416416);
|
||||||
EXPECT_EQ(std::string(header.creationDate), "Feb 14, 2025");
|
EXPECT_EQ(std::string(header.creationDate), "Feb 16, 2025");
|
||||||
EXPECT_EQ(std::string(header.sourceInfo), "MESA 12700, Synthetic Opacity Data");
|
EXPECT_EQ(std::string(header.sourceInfo), "no source provided by user");
|
||||||
EXPECT_EQ(std::string(header.comment), "log10 kappa (cm^2/g)");
|
EXPECT_EQ(std::string(header.comment), "default header");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(opatIOTest, TableIndex) {
|
TEST_F(opatIOTest, TableIndex) {
|
||||||
|
|||||||
BIN
tests/opatIO/example.opat → tests/opatIO/test.opat
Executable file → Normal file
BIN
tests/opatIO/example.opat → tests/opatIO/test.opat
Executable file → Normal file
Binary file not shown.
@@ -186,6 +186,87 @@ class OpatIO:
|
|||||||
|
|
||||||
return tableIndexBytes
|
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:
|
def save(self, filename: str) -> str:
|
||||||
tempHeaderBytes = self._header_bytes()
|
tempHeaderBytes = self._header_bytes()
|
||||||
|
|
||||||
@@ -229,14 +310,14 @@ def loadOpat(filename: str) -> OpatIO:
|
|||||||
headerBytes: bytes = f.read(256)
|
headerBytes: bytes = f.read(256)
|
||||||
unpackedHeader = struct.unpack("<4s H I I Q 16s 64s 128s 26s", headerBytes)
|
unpackedHeader = struct.unpack("<4s H I I Q 16s 64s 128s 26s", headerBytes)
|
||||||
loadedHeader = Header(
|
loadedHeader = Header(
|
||||||
magic = unpackedHeader[0].decode(),
|
magic = unpackedHeader[0].decode().replace("\x00", ""),
|
||||||
version = unpackedHeader[1],
|
version = unpackedHeader[1],
|
||||||
numTables = unpackedHeader[2],
|
numTables = unpackedHeader[2],
|
||||||
headerSize = unpackedHeader[3],
|
headerSize = unpackedHeader[3],
|
||||||
indexOffset = unpackedHeader[4],
|
indexOffset = unpackedHeader[4],
|
||||||
creationDate = unpackedHeader[5].decode(),
|
creationDate = unpackedHeader[5].decode().replace("\x00", ""),
|
||||||
sourceInfo = unpackedHeader[6].decode(),
|
sourceInfo = unpackedHeader[6].decode().replace("\x00", ""),
|
||||||
comment = unpackedHeader[7].decode(),
|
comment = unpackedHeader[7].decode().replace("\x00", ""),
|
||||||
reserved = unpackedHeader[8]
|
reserved = unpackedHeader[8]
|
||||||
)
|
)
|
||||||
opat.header = loadedHeader
|
opat.header = loadedHeader
|
||||||
|
|||||||
Reference in New Issue
Block a user