Files
solvecraft/harness/cpp/shim/geom_extent1d.cpp
T
bherbruck 934ffee8c8 geom.extent1d: port SmExtent1d / SmExtentNd
Rust geom::extent1d (Extent1d, ExtentNd, ExtentPointType), C++ shim,
FFI wrapper, difftest runner (91 fixtures from my_test_extent1d, header
examples and edge cases, plus 3000 generated): 3091 cases, all
bit-identical, 0 deferred.

Wires the UNPORTED(geom.extent1d) back-edges: SmExtent3d::Get{U,V,W}Interval
and Set{U,V}Interval in geom::extent3d, SmTol::MapTo1d(SmTol3d&, SmVector3d&,
const SmExtent1d&) and SmTol::GetScaledZero(const SmExtent1d&) in geom::tol,
with difftest coverage in both runners.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-12 15:59:50 -04:00

261 lines
8.7 KiB
C++

// SPDX-License-Identifier: Apache-2.0
// difftest shim for unit `geom.extent1d`. Wraps upstream SmExtent1d and SmExtentNd
// (source/SMLib/src/SmExtent1d.cpp, source/SMLib/inc/SmExtent1d.h,
// source/SMLib/src/SmExtentNd.cpp, source/SMLib/inc/SmExtentNd.h).
//
// One entry point, sc_geom_extent1d_op(op, in, n_in, out, cap, out_len): `op` selects
// an operation (numbering shared with crates/solidcore-ffi/src/units/geom_extent1d.rs),
// `in` holds its flat double inputs and the shim writes a flat double result.
// An interval is 2 doubles [min max] stored as given (no swapping; inverted intervals
// are allowed). SmStatus results are written as 0 for SM_SUCCESS, else the SM_ERR_*
// value. Booleans are 0/1, enums their integer value. The Rust wrapper validates
// inputs (sizes, loop bounds of PeriodicWrap); the shim checks sizes again.
#include "sc_shim.h"
#include <SmExtent1d.h>
#include <SmExtentNd.h>
#include <SmTArray.h>
#include <vector>
namespace
{
// SmExtent1d with directly assigned (possibly inverted) ends.
struct RawExt : public SmExtent1d
{
RawExt(double a, double b)
{
m_dMin = a;
m_dMax = b;
}
};
SmExtent1d ext(const double* d) { return RawExt(d[0], d[1]); }
void put_ext(std::vector<double>& o, const SmExtent1d& e)
{
o.push_back(e.GetMin());
o.push_back(e.GetMax());
}
double st(SmStatus s) { return s == SM_SUCCESS ? 0.0 : (double)s; }
double b(SmBoolean v) { return v ? 1.0 : 0.0; }
// Number of input doubles per op (-1: variable, checked in the op).
int64_t input_size(int32_t op)
{
switch (op)
{
case 0: return 2; // constructors: a, b
case 1: return 4; // queries: E, t, tol
case 2: return 5; // modify: E, which, x, y
case 3: return 5; // binary: E, F, tol
case 4: return 4; // approximate_unbounded: E, center, half
case 5: return 5; // periodic value: E, v, period, tol
case 6: return 6; // intersect_periodic: E, F, period, dont_cross
case 7: return 6; // are_disjoint_periodic: E, F, period, tol
case 8: return -1; // clamp_vector: header + data
default: return -2;
}
}
} // namespace
SC_EXPORT int32_t sc_geom_extent1d_op(int32_t op, const double* in, int64_t n_in, double* out,
int64_t cap, int64_t* out_len)
{
int64_t need = input_size(op);
if (need == -2 || n_in < 0 || (need >= 0 && n_in != need))
return SC_ERR_ARG;
SC_TRY
std::vector<double> o;
switch (op)
{
case 0:
{
put_ext(o, SmExtent1d(in[0], in[1]));
put_ext(o, SmExtent1d(in[0]));
SmExtent1d e;
put_ext(o, e);
break;
}
case 1:
{
const SmExtent1d e = ext(in);
double t = in[2], tol = in[3];
o.push_back(e.GetMin());
o.push_back(e.GetMax());
o.push_back(e.GetMid());
o.push_back(e.GetLength());
o.push_back(e.GetMaxDimension());
o.push_back(e.ClampValue(t));
o.push_back(e.SnapValue(t, tol));
o.push_back(e.Evaluate(t));
o.push_back(e.DistanceFrom(t));
double dOther = 0.0;
SmBoolean bOn = e.IsValueOnBoundary(t, tol, &dOther);
o.push_back(dOther);
double dParam = 0.0;
SmStatus sInv = e.Inversion(t, dParam);
o.push_back(dParam);
// integers
o.push_back(b(e.GetTLeftEval(t)));
o.push_back(b(e.AssertDefined()));
o.push_back(b(e.IsInit()));
o.push_back(b(e.HasNegativeLength()));
o.push_back(b(e.IsDegenerate(tol)));
SmBoundaryType eType = SM_BT_UNKNOWN;
o.push_back(b(e.IsBounded(&eType)));
o.push_back((double)eType);
o.push_back(b(e.IsBounded()));
o.push_back(b(e.ContainsValue(t, tol)));
o.push_back((double)e.ClassifyPoint(t, tol));
o.push_back(b(bOn));
o.push_back(b(e.IsValueOnBoundary(t, tol)));
o.push_back(st(sInv));
break;
}
case 2:
{
SmExtent1d e = ext(in);
int which = (int)in[2];
double x = in[3], y = in[4];
SmStatus s = SM_SUCCESS;
switch (which)
{
case 0: e.AddValue(x); break;
case 1: s = e.SetMinMax(x, y); break;
case 2: s = e.SetMin(x); break;
case 3: s = e.SetMax(x); break;
case 4: e.SetUnbounded(); break;
case 5: e.Negate(); break;
case 6: e.Translate(x); break;
case 7: e.ExpandAbsolute(x); break;
case 8: e.ExpandRelative(x); break;
case 9: e.Scale(x); break;
case 10: e.Init(); break;
default: return SC_ERR_ARG;
}
o.push_back(st(s));
put_ext(o, e);
break;
}
case 3:
{
const SmExtent1d e = ext(in), f = ext(in + 2);
double tol = in[4];
SmExtent1d u;
e.Union(f, u);
put_ext(o, u);
SmExtent1d x(7.0, 8.0); // overwritten either way
SmStatus s = e.Intersect(f, x);
put_ext(o, x);
o.push_back(st(s));
o.push_back(b(e.AreDisjoint(f, tol)));
o.push_back(b(e.AreDisjoint(f)));
o.push_back(b(e.AreEqual(f, tol)));
o.push_back(b(e.AreEqual(f.GetMin(), f.GetMax(), tol)));
o.push_back(b(e.IsContainedBy(f, tol)));
o.push_back(b(e == f));
break;
}
case 4:
{
const SmExtent1d e = ext(in);
SmExtent1d x; // Init state; untouched when SetMinMax rejects
SmExtent1d a = e.ApproximateUnbounded(in[2], in[3], &x);
SmExtent1d a2 = e.ApproximateUnbounded(in[2], in[3]);
put_ext(o, a);
put_ext(o, x);
put_ext(o, a2);
break;
}
case 5:
{
const SmExtent1d e = ext(in);
double v = in[2], period = in[3], tol = in[4];
double dPer = 0.0, dMin = 0.0, dMax = 0.0, dDist = 0.0;
SmBoolean bIn = e.ContainsPeriodicValue(v, period, &dPer, &dMin, &dMax, tol);
SmBoolean bOn = e.IsValueOnPeriodicBoundary(v, period, tol, &dDist);
o.push_back(e.PeriodicWrap(v));
o.push_back(dPer);
o.push_back(dMin);
o.push_back(dMax);
o.push_back(e.ClampPeriodicValue(v, period, tol));
o.push_back(dDist);
o.push_back(b(bIn));
o.push_back(b(e.ContainsPeriodicValue(v, period, NULL, NULL, NULL, tol)));
o.push_back(b(bOn));
o.push_back(b(e.IsClosed(period)));
break;
}
case 6:
{
const SmExtent1d e = ext(in), f = ext(in + 2);
SmTArray<SmExtent1d> res;
SmStatus s = e.IntersectPeriodic(f, in[4], res, in[5] != 0.0 ? TRUE : FALSE);
o.push_back(st(s));
o.push_back((double)res.GetSize());
for (ULONG i = 0; i < res.GetSize(); i++)
put_ext(o, res[i]);
break;
}
case 7:
{
const SmExtent1d e = ext(in), f = ext(in + 2);
o.push_back(b(e.AreDisjointPeriodic(f, in[4], in[5])));
break;
}
case 8:
{
// header: dim, has_periodic, has_counts, n_prior_clamped, n_prior_counts
// then: dim*2 extents, dim values, dim periodic flags (if has_periodic),
// prior clamped, prior counts (if has_counts)
if (n_in < 5)
return SC_ERR_ARG;
int64_t dim = (int64_t)in[0];
bool has_per = in[1] != 0.0, has_cnt = in[2] != 0.0;
int64_t n_pc = (int64_t)in[3], n_pn = (int64_t)in[4];
if (dim < 0 || n_pc < 0 || n_pn < 0 || (!has_cnt && n_pn != 0))
return SC_ERR_ARG;
int64_t total = 5 + 3 * dim + (has_per ? dim : 0) + n_pc + n_pn;
if (n_in != total)
return SC_ERR_ARG;
const double* p = in + 5;
SmExtentNd nd((ULONG)dim);
for (int64_t i = 0; i < dim; i++)
nd[(ULONG)i] = ext(p + 2 * i);
p += 2 * dim;
SmTArray<double> vals;
for (int64_t i = 0; i < dim; i++)
vals.Add(p[i]);
p += dim;
SmTArray<SmBoolean> per;
if (has_per)
{
for (int64_t i = 0; i < dim; i++)
per.Add(p[i] != 0.0 ? TRUE : FALSE);
p += dim;
}
SmTArray<double> clamped;
for (int64_t i = 0; i < n_pc; i++)
clamped.Add(p[i]);
p += n_pc;
SmTArray<ULONG> counts;
for (int64_t i = 0; i < n_pn; i++)
counts.Add((ULONG)p[i]);
SmBoolean r = nd.ClampVector(vals, has_per ? &per : NULL, clamped,
has_cnt ? &counts : NULL);
o.push_back(b(r));
for (ULONG i = 0; i < clamped.GetSize(); i++)
o.push_back(clamped[i]);
if (has_cnt)
for (ULONG i = 0; i < counts.GetSize(); i++)
o.push_back((double)counts[i]);
break;
}
default: return SC_ERR_ARG;
}
return sc_copy_out(o.data(), (int64_t)o.size(), out, cap, out_len);
SC_CATCH
}