Skip to content

Commit 125eab7

Browse files
committed
docs: record AK plan execution corrections; ignore CTest Testing/ dir
Plan updated with the Approx (not Catch::Approx) and src/LibMTRSim/CMakeLists.txt corrections discovered during execution.
1 parent 4f41e57 commit 125eab7

34 files changed

Lines changed: 2835 additions & 2071 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
CLAUDE.md
33
*.mat
44
/docs/superpowers
5+
/Testing

docs/superpowers/plans/2026-06-01-milestone-ak.md

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@
66

77
**Architecture:** The heavy lifting is consolidated into a single reusable LibMTRSim entry point `simulateMTR()` that runs PGRF → orientation sampling → per-voxel assignment and returns results in **SIMPLNX z,y,x voxel order**. LibMTRSim Catch2 tests (in `tests/`) cover the numerics (deterministic helpers + a statistical end-to-end test reusing the ODF exemplar). The filter is a thin SIMPLNX wrapper (`MTRSimFilter` + `MTRSim` algorithm) whose tests (in `test/`) only verify the filter's value-add: parameter→params mapping, ODF-geometry→component reconstruction, array creation/types/names/1-based ids, seed handling, preflight validation, and the optional color array.
88

9-
**Tech Stack:** C++17, Eigen, simplnx filter framework, Catch2 v2 (note: `Catch::Approx`, not `Approx`), CMake, GitHub Actions.
9+
**Tech Stack:** C++17, Eigen, simplnx filter framework, Catch2 v2 (this repo uses the **bare `Approx`** matcher — `Approx` does NOT compile here), CMake, GitHub Actions.
10+
11+
> **Correction applied during execution:** all Catch2 tests in this plan use
12+
> bare `Approx(...)`, not `Approx(...)`. The standalone LibMTRSim source
13+
> list is in `src/LibMTRSim/CMakeLists.txt` (not `LibMTRSim.cmake`).
1014
1115
---
1216

@@ -69,7 +73,7 @@ simplnx checkout is at `/Users/mjackson/Workspace7/simplnx`):
6973
- `src/app/main.cpp` — call `simulateMTR()` instead of inline orchestration.
7074
- `MTRSimPlugin.cmake` — add `MTRSimDriver.{hpp,cpp}` to LibMTRSim sources;
7175
add `MTRSimFilter`/`MTRSim` to `FilterList`/`AlgorithmList`.
72-
- `LibMTRSim.cmake` — add `MTRSimDriver.{hpp,cpp}` to the standalone library.
76+
- `src/LibMTRSim/CMakeLists.txt` — add `MTRSimDriver.{hpp,cpp}` to the standalone library source/header lists.
7377
- `tests/CMakeLists.txt` — add `test_mtrsim_driver.cpp`.
7478
- `test/CMakeLists.txt` — add `MTRSimTest.cpp`.
7579

@@ -194,13 +198,13 @@ TEST_CASE("buildUniformODF produces correct bin centres", "[mtrsim_driver]") {
194198
REQUIRE(uni.phi1Bins.size() == 72 * 36 * 72);
195199

196200
// Uniform mass: every bin equal, sums to 1.
197-
REQUIRE(uni.odfVal.sum() == Catch::Approx(1.0));
198-
REQUIRE(uni.odfVal[0] == Catch::Approx(1.0 / (72.0 * 36.0 * 72.0)));
201+
REQUIRE(uni.odfVal.sum() == Approx(1.0));
202+
REQUIRE(uni.odfVal[0] == Approx(1.0 / (72.0 * 36.0 * 72.0)));
199203

200204
// First bin centre: i1=iPHI=i2=0 -> all 0.5 * step.
201-
REQUIRE(uni.phi1Bins[0] == Catch::Approx(0.5 * 2.0 * std::numbers::pi / 72.0));
202-
REQUIRE(uni.phiBins[0] == Catch::Approx(0.5 * std::numbers::pi / 36.0));
203-
REQUIRE(uni.phi2Bins[0] == Catch::Approx(0.5 * 2.0 * std::numbers::pi / 72.0));
205+
REQUIRE(uni.phi1Bins[0] == Approx(0.5 * 2.0 * std::numbers::pi / 72.0));
206+
REQUIRE(uni.phiBins[0] == Approx(0.5 * std::numbers::pi / 36.0));
207+
REQUIRE(uni.phi2Bins[0] == Approx(0.5 * 2.0 * std::numbers::pi / 72.0));
204208
}
205209
```
206210

@@ -266,12 +270,12 @@ TEST_CASE("gridToODFComponent derives bin centres in radians and normalizes", "[
266270
mtrsim::gridToODFComponent(values, n1, nPHI, n2, 5.0, 5.0, 5.0);
267271
268272
REQUIRE(c.odfVal.size() == n1 * nPHI * n2);
269-
REQUIRE(c.odfVal.sum() == Catch::Approx(1.0)); // normalized
273+
REQUIRE(c.odfVal.sum() == Approx(1.0)); // normalized
270274
// 5 deg step -> first bin centre 2.5 deg in radians.
271275
const double deg2rad = std::numbers::pi / 180.0;
272-
REQUIRE(c.phi1Bins[0] == Catch::Approx(2.5 * deg2rad));
273-
REQUIRE(c.phiBins[0] == Catch::Approx(2.5 * deg2rad));
274-
REQUIRE(c.phi2Bins[0] == Catch::Approx(2.5 * deg2rad));
276+
REQUIRE(c.phi1Bins[0] == Approx(2.5 * deg2rad));
277+
REQUIRE(c.phiBins[0] == Approx(2.5 * deg2rad));
278+
REQUIRE(c.phi2Bins[0] == Approx(2.5 * deg2rad));
275279
}
276280
```
277281

@@ -569,9 +573,9 @@ TEST_CASE("simulateMTR reproduces target volume fractions (statistical)", "[mtrs
569573
// Empirical volume fractions within tolerance of targets.
570574
std::array<int, 3> counts{0, 0, 0};
571575
for (int v : r.mtrIndex) { counts[v - 1]++; }
572-
REQUIRE(static_cast<double>(counts[0]) / N == Catch::Approx(0.30).margin(0.05));
573-
REQUIRE(static_cast<double>(counts[1]) / N == Catch::Approx(0.35).margin(0.05));
574-
REQUIRE(static_cast<double>(counts[2]) / N == Catch::Approx(0.35).margin(0.05));
576+
REQUIRE(static_cast<double>(counts[0]) / N == Approx(0.30).margin(0.05));
577+
REQUIRE(static_cast<double>(counts[1]) / N == Approx(0.35).margin(0.05));
578+
REQUIRE(static_cast<double>(counts[2]) / N == Approx(0.35).margin(0.05));
575579

576580
// Euler ranges valid.
577581
for (double a : r.phi1) { REQUIRE(a >= 0.0); REQUIRE(a <= 2.0 * std::numbers::pi); }
@@ -1166,8 +1170,8 @@ TEST_CASE("MTRSimFilter: execute produces valid MTR ids + Eulers", "[MTRSim]")
11661170
counts[v - 1]++;
11671171
}
11681172
const double n = static_cast<double>(ids.getNumberOfTuples());
1169-
REQUIRE(counts[0] / n == Catch::Approx(0.30).margin(0.06));
1170-
REQUIRE(counts[1] / n == Catch::Approx(0.35).margin(0.06));
1173+
REQUIRE(counts[0] / n == Approx(0.30).margin(0.06));
1174+
REQUIRE(counts[1] / n == Approx(0.35).margin(0.06));
11711175

11721176
// Seed array recorded.
11731177
auto& seedArr = ds.getDataRefAs<UInt64Array>(DataPath({"MTRSim SeedValue"}));

src/LibMTRSim/IPFMapper.hpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@ namespace mtrsim {
1717
* EbsdLib's `LaueOps` and `ebsdlib::CrystalStructure::*` codes directly.
1818
*/
1919
enum class CrystalSystem {
20-
HCP, ///< Hexagonal close-packed (maps to ebsdlib::CrystalStructure::Hexagonal_High)
21-
FCC, ///< Face-centred cubic (maps to ebsdlib::CrystalStructure::Cubic_High)
20+
HCP, ///< Hexagonal close-packed (maps to
21+
///< ebsdlib::CrystalStructure::Hexagonal_High)
22+
FCC, ///< Face-centred cubic (maps to
23+
///< ebsdlib::CrystalStructure::Cubic_High)
2224
};
2325

2426
/**

src/LibMTRSim/MTRSimDriver.cpp

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,17 @@ ODFComponent buildUniformODF(int n1, int nPHI, int n2) {
3030
}
3131

3232
ODFComponent uni;
33-
uni.odfVal = Eigen::VectorXd::Constant(nTotal, 1.0 / static_cast<double>(nTotal));
33+
uni.odfVal =
34+
Eigen::VectorXd::Constant(nTotal, 1.0 / static_cast<double>(nTotal));
3435
uni.phi1Bins = std::move(phi1Bins);
3536
uni.phiBins = std::move(phiBins);
3637
uni.phi2Bins = std::move(phi2Bins);
3738
return uni;
3839
}
3940

40-
ODFComponent gridToODFComponent(const std::vector<double>& values, int n1, int nPHI, int n2, double stepDeg1, double stepDegPHI, double stepDeg2) {
41+
ODFComponent gridToODFComponent(const std::vector<double> &values, int n1,
42+
int nPHI, int n2, double stepDeg1,
43+
double stepDegPHI, double stepDeg2) {
4144
const int nTotal = n1 * nPHI * n2;
4245
const double deg2rad = std::numbers::pi / 180.0;
4346
const double s1 = stepDeg1 * deg2rad;
@@ -68,34 +71,39 @@ ODFComponent gridToODFComponent(const std::vector<double>& values, int n1, int n
6871
return c;
6972
}
7073

71-
MTRSimResult simulateMTR(const SimulationParams& params,
72-
const std::vector<ODFComponent>& odfComponents,
73-
std::mt19937_64& rng,
74-
int n1, int nPHI, int n2) {
74+
MTRSimResult simulateMTR(const SimulationParams &params,
75+
const std::vector<ODFComponent> &odfComponents,
76+
std::mt19937_64 &rng, int n1, int nPHI, int n2) {
7577
const int nx = static_cast<int>(std::round(params.xLen / params.dx));
7678
const int ny = static_cast<int>(std::round(params.yLen / params.dy));
77-
const int nz = std::max(static_cast<int>(std::round(params.zLen / params.dz)), 1);
79+
const int nz =
80+
std::max(static_cast<int>(std::round(params.zLen / params.dz)), 1);
7881
const int N = nx * ny * nz;
7982

80-
if (static_cast<int>(odfComponents.size()) != static_cast<int>(params.volumeFractions.size())) {
81-
throw std::invalid_argument("simulateMTR: odfComponents count must equal volumeFractions count");
83+
if (static_cast<int>(odfComponents.size()) !=
84+
static_cast<int>(params.volumeFractions.size())) {
85+
throw std::invalid_argument(
86+
"simulateMTR: odfComponents count must equal volumeFractions count");
8287
}
8388

8489
// 1. PGRF assignment (sim-ordered, 1-based component ids).
8590
PGRFSimulation pgrf{rng};
8691
const PGRFResult pgrf_result = pgrf.run(params); // throws on bad dims
8792

8893
if (static_cast<int>(pgrf_result.mtrIndex.size()) != N) {
89-
throw std::runtime_error("simulateMTR: PGRF result size does not match grid dimensions");
94+
throw std::runtime_error(
95+
"simulateMTR: PGRF result size does not match grid dimensions");
9096
}
9197

9298
// 2. Sample N orientations per component against the uniform reference.
9399
const ODFComponent uniformOdf = buildUniformODF(n1, nPHI, n2);
94100
const int numComponents = static_cast<int>(odfComponents.size());
95-
std::vector<Eigen::MatrixXd> orientSamples(static_cast<std::size_t>(numComponents));
101+
std::vector<Eigen::MatrixXd> orientSamples(
102+
static_cast<std::size_t>(numComponents));
96103
ODFSampler sampler{rng};
97104
for (int j = 0; j < numComponents; ++j) {
98-
orientSamples[static_cast<std::size_t>(j)] = sampler.sampleN(N, odfComponents[static_cast<std::size_t>(j)], uniformOdf);
105+
orientSamples[static_cast<std::size_t>(j)] = sampler.sampleN(
106+
N, odfComponents[static_cast<std::size_t>(j)], uniformOdf);
99107
}
100108

101109
// 3. Assign per-voxel orientation by component (sim order).
@@ -106,9 +114,12 @@ MTRSimResult simulateMTR(const SimulationParams& params,
106114
for (int i = 0; i < N; ++i) {
107115
const int comp = pgrf_result.mtrIndex[i] - 1;
108116
mtrSim[static_cast<std::size_t>(i)] = pgrf_result.mtrIndex[i];
109-
phi1Sim[static_cast<std::size_t>(i)] = orientSamples[static_cast<std::size_t>(comp)](i, 0);
110-
phiSim[static_cast<std::size_t>(i)] = orientSamples[static_cast<std::size_t>(comp)](i, 1);
111-
phi2Sim[static_cast<std::size_t>(i)] = orientSamples[static_cast<std::size_t>(comp)](i, 2);
117+
phi1Sim[static_cast<std::size_t>(i)] =
118+
orientSamples[static_cast<std::size_t>(comp)](i, 0);
119+
phiSim[static_cast<std::size_t>(i)] =
120+
orientSamples[static_cast<std::size_t>(comp)](i, 1);
121+
phi2Sim[static_cast<std::size_t>(i)] =
122+
orientSamples[static_cast<std::size_t>(comp)](i, 2);
112123
}
113124

114125
// 4. Remap to SIMPLNX z,y,x order.
@@ -117,9 +128,9 @@ MTRSimResult simulateMTR(const SimulationParams& params,
117128
out.ny = ny;
118129
out.nz = nz;
119130
out.mtrIndex = remapSimToZYX(mtrSim, nx, ny, nz);
120-
out.phi1 = remapSimToZYX(phi1Sim, nx, ny, nz);
121-
out.phi = remapSimToZYX(phiSim, nx, ny, nz);
122-
out.phi2 = remapSimToZYX(phi2Sim, nx, ny, nz);
131+
out.phi1 = remapSimToZYX(phi1Sim, nx, ny, nz);
132+
out.phi = remapSimToZYX(phiSim, nx, ny, nz);
133+
out.phi2 = remapSimToZYX(phi2Sim, nx, ny, nz);
123134
return out;
124135
}
125136

src/LibMTRSim/MTRSimDriver.hpp

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
#include "libmtrsim_export.h"
44

5-
#include "ODFSampler.hpp" // mtrsim::ODFComponent, mtrsim::EulerAngles
5+
#include "ODFSampler.hpp" // mtrsim::ODFComponent, mtrsim::EulerAngles
66
#include "SimulationParams.hpp"
77

88
#include <Eigen/Dense>
@@ -28,12 +28,15 @@ LIBMTRSIM_EXPORT ODFComponent buildUniformODF(int n1, int nPHI, int n2);
2828
* @brief Reconstruct an ODF component from flat grid data + degree spacing.
2929
*
3030
* @param values Flat ODFval, length n1*nPHI*n2, row-major with
31-
* ix = i1*(nPHI*n2) + iPHI*n2 + i2 (phi1 slowest, phi2 fastest).
31+
* ix = i1*(nPHI*n2) + iPHI*n2 + i2 (phi1 slowest, phi2
32+
* fastest).
3233
* @param n1,nPHI,n2 Bin counts along phi1, PHI, phi2.
3334
* @param stepDeg1,stepDegPHI,stepDeg2 Bin sizes [degrees] (geometry spacing).
3435
* @return ODFComponent with bin centres [rad] and values normalized to sum 1.
3536
*/
36-
LIBMTRSIM_EXPORT ODFComponent gridToODFComponent(const std::vector<double>& values, int n1, int nPHI, int n2, double stepDeg1, double stepDegPHI, double stepDeg2);
37+
LIBMTRSIM_EXPORT ODFComponent
38+
gridToODFComponent(const std::vector<double> &values, int n1, int nPHI, int n2,
39+
double stepDeg1, double stepDegPHI, double stepDeg2);
3740

3841
/**
3942
* @brief Remap a per-voxel vector from simulation order to SIMPLNX z,y,x order.
@@ -45,13 +48,15 @@ LIBMTRSIM_EXPORT ODFComponent gridToODFComponent(const std::vector<double>& valu
4548
* @tparam T element type (int or double).
4649
*/
4750
template <typename T>
48-
std::vector<T> remapSimToZYX(const std::vector<T>& in, int nx, int ny, int nz) {
51+
std::vector<T> remapSimToZYX(const std::vector<T> &in, int nx, int ny, int nz) {
4952
std::vector<T> out(in.size());
5053
for (int iz = 0; iz < nz; ++iz) {
5154
for (int iy = 0; iy < ny; ++iy) {
5255
for (int ix = 0; ix < nx; ++ix) {
53-
const std::size_t kSim = static_cast<std::size_t>(iz) * nx * ny + static_cast<std::size_t>(ix) * ny + iy;
54-
const std::size_t kNx = static_cast<std::size_t>(iz) * ny * nx + static_cast<std::size_t>(iy) * nx + ix;
56+
const std::size_t kSim = static_cast<std::size_t>(iz) * nx * ny +
57+
static_cast<std::size_t>(ix) * ny + iy;
58+
const std::size_t kNx = static_cast<std::size_t>(iz) * ny * nx +
59+
static_cast<std::size_t>(iy) * nx + ix;
5560
out[kNx] = in[kSim];
5661
}
5762
}
@@ -66,25 +71,27 @@ struct LIBMTRSIM_EXPORT MTRSimResult {
6671
int nx = 0;
6772
int ny = 0;
6873
int nz = 0;
69-
std::vector<int32_t> mtrIndex; ///< 1-based component id per voxel, length N
70-
std::vector<double> phi1; ///< Euler phi1 [rad] per voxel, length N
71-
std::vector<double> phi; ///< Euler PHI [rad] per voxel, length N
72-
std::vector<double> phi2; ///< Euler phi2 [rad] per voxel, length N
74+
std::vector<int32_t> mtrIndex; ///< 1-based component id per voxel, length N
75+
std::vector<double> phi1; ///< Euler phi1 [rad] per voxel, length N
76+
std::vector<double> phi; ///< Euler PHI [rad] per voxel, length N
77+
std::vector<double> phi2; ///< Euler phi2 [rad] per voxel, length N
7378
};
7479

7580
/**
7681
* @brief Run the full MTR simulation: PGRF assignment -> per-component ODF
7782
* sampling -> per-voxel orientation assignment, returned in SIMPLNX
7883
* z,y,x voxel order.
7984
*
80-
* @param params Fully populated SimulationParams (consistent length unit).
81-
* @param odfComponents One ODFComponent per volume-fraction entry, shared grid.
85+
* @param params Fully populated SimulationParams (consistent length
86+
* unit).
87+
* @param odfComponents One ODFComponent per volume-fraction entry, shared
88+
* grid.
8289
* @param rng Seeded RNG (mt19937_64).
8390
* @param n1,nPHI,n2 Bin counts of the ODF grid (for the uniform reference).
8491
*/
85-
LIBMTRSIM_EXPORT MTRSimResult simulateMTR(const SimulationParams& params,
86-
const std::vector<ODFComponent>& odfComponents,
87-
std::mt19937_64& rng,
88-
int n1, int nPHI, int n2);
92+
LIBMTRSIM_EXPORT MTRSimResult
93+
simulateMTR(const SimulationParams &params,
94+
const std::vector<ODFComponent> &odfComponents,
95+
std::mt19937_64 &rng, int n1, int nPHI, int n2);
8996

9097
} // namespace mtrsim

0 commit comments

Comments
 (0)