// SPDX-License-Identifier: Apache-2.0 // difftest shim for unit `nlib.basis_basic`. // Wraps routines of upstream source/SMLib/src/NL_BasisBasic.cpp: // N_BasisFindGlobalMax, N_BasisFindAllSpanMaxima, N_BasisReparam, // N_GetCompatibleKnotArray, N_GetCompatibleKnotVectorToTol (+ ST_notdone), // N_BasisDerivs, N_MakeKnotsCompatible. // // Status convention (as nlib_knots.cpp): 0 = ok; when the upstream routine returns // its error flag, the NLib error number NL_ERROR.eno (cleared before the call; 1000 // if the flag was set without a number); SC_ERR_* for shim problems. // The Rust wrapper (crates/solidcore-ffi/src/units/nlib_basis_basic.rs) validates // every argument first; the checks here are a second line of defence. // // Defensive copies: upstream N_GetCompatibleKnotVectorToTol may read U[-1] of a knot // vector, and N_MakeKnotsCompatible may read a few entries past U[m] on unusual // inputs; the knot buffers handed to them carry a leading slot / trailing +inf slots // so those reads stay inside allocated memory (they never change the results). #include "sc_shim.h" #include #include #include #include #include namespace { int32_t nl_status(NL_FLAG err) { if (err == NL_NO) return SC_OK; return NL_ERROR.eno != 0 ? (int32_t)NL_ERROR.eno : 1000; } NL_KNOTVECTOR make_knt(const double* knots, int64_t n_knots) { NL_KNOTVECTOR knt; N_KnotVectorFromRealArray(&knt, const_cast(knots), (NL_INDEX)(n_knots - 1)); return knt; } bool valid_flag(int32_t flag) { return flag == NL_LEFT || flag == NL_RIGHT; } bool basis_domain(int64_t n_knots, int32_t degree) { return degree >= 0 && degree <= 1000 && n_knots >= 2 * (int64_t)degree + 2; } const int64_t MC_PAD = 8; // trailing +inf entries for N_MakeKnotsCompatible } // namespace // N_BasisFindGlobalMax(knt, i, p, tol) -> out[0] = max, out[1] = u. SC_EXPORT int32_t sc_nlib_basis_basic_global_max(const double* knots, int64_t n_knots, int64_t index, int32_t degree, double tol, double* out) { if (!basis_domain(n_knots, degree) || index < 0) return SC_ERR_ARG; SC_TRY NL_KNOTVECTOR knt = make_knt(knots, n_knots); NL_REAL mx = 0.0, u = 0.0; N_ErrClear(); NL_FLAG err = N_BasisFindGlobalMax(&knt, (NL_INDEX)index, (NL_DEGREE)degree, tol, &mx, &u); out[0] = mx; out[1] = u; return nl_status(err); SC_CATCH } // N_BasisFindAllSpanMaxima(knt, i, p, tol) -> out_min[0..=p], out_max[0..=p], out_u. SC_EXPORT int32_t sc_nlib_basis_basic_span_maxima(const double* knots, int64_t n_knots, int64_t index, int32_t degree, double tol, double* out_min, double* out_max, int64_t cap, double* out_u) { if (!basis_domain(n_knots, degree) || index < 0 || cap < degree + 1) return SC_ERR_ARG; SC_TRY NL_KNOTVECTOR knt = make_knt(knots, n_knots); NL_REAL u = 0.0; N_ErrClear(); NL_FLAG err = N_BasisFindAllSpanMaxima(&knt, (NL_INDEX)index, (NL_DEGREE)degree, tol, out_min, out_max, &u); *out_u = u; return nl_status(err); SC_CATCH } // N_BasisReparam(knt, p, [ul, ur]) on a copy of knots -> out_knots[0..n_knots). SC_EXPORT int32_t sc_nlib_basis_basic_reparam(const double* knots, int64_t n_knots, int32_t degree, double ul, double ur, double* out_knots) { if (n_knots < 1 || degree < 0 || degree > 32767 || n_knots < (int64_t)degree + 1) return SC_ERR_ARG; SC_TRY for (int64_t i = 0; i < n_knots; ++i) out_knots[i] = knots[i]; NL_KNOTVECTOR knt = make_knt(out_knots, n_knots); NL_INTERVAL I; N_CreateInterval(&I, ul, ur); N_BasisReparam(&knt, (NL_DEGREE)degree, I); return SC_OK; SC_CATCH } // N_GetCompatibleKnotVectorToTol(knt[0..n_vec), n_vec-1, tol) (or, with use_array != 0, // N_GetCompatibleKnotArray, tol ignored). Input vectors are concatenated in `flat` with // lengths `lens`. Writes the (adjusted) input knots to out_knots (same layout), the new // knots of vector r to out_new[r*new_cap ..] with count out_new_lens[r]. SC_EXPORT int32_t sc_nlib_basis_basic_compatible(const double* flat, const int64_t* lens, int64_t n_vec, double tol, int32_t use_array, double* out_knots, double* out_new, int64_t new_cap, int64_t* out_new_lens) { if (n_vec < 1) return SC_ERR_ARG; int64_t total = 0; for (int64_t r = 0; r < n_vec; ++r) { if (lens[r] < 1 || lens[r] < n_vec - 1) return SC_ERR_ARG; total += lens[r] - 1; } if (new_cap < total + 1) return SC_ERR_ARG; SC_TRY // One leading slot per vector: upstream may read U[-1]. std::vector> bufs((size_t)n_vec); std::vector kv((size_t)n_vec); std::vector kp((size_t)n_vec); int64_t off = 0; for (int64_t r = 0; r < n_vec; ++r) { bufs[r].assign((size_t)lens[r] + 1, 0.0); for (int64_t i = 0; i < lens[r]; ++i) bufs[r][(size_t)i + 1] = flat[off + i]; off += lens[r]; N_KnotVectorFromRealArray(&kv[r], bufs[r].data() + 1, (NL_INDEX)(lens[r] - 1)); kp[r] = &kv[r]; } NL_STACKS SK; N_InitNurbs(&SK); NL_KNOTVECTOR** knx = nullptr; N_ErrClear(); NL_FLAG err = use_array != 0 ? N_GetCompatibleKnotArray(kp.data(), (NL_INDEX)(n_vec - 1), &knx, &SK) : N_GetCompatibleKnotVectorToTol(kp.data(), (NL_INDEX)(n_vec - 1), tol, &knx, &SK); int32_t st = nl_status(err); off = 0; for (int64_t r = 0; r < n_vec; ++r) { for (int64_t i = 0; i < lens[r]; ++i) out_knots[off + i] = bufs[r][(size_t)i + 1]; off += lens[r]; out_new_lens[r] = 0; } if (st == SC_OK && knx != nullptr) { for (int64_t r = 0; r < n_vec; ++r) { NL_INDEX m = -1; NL_REAL* U = nullptr; N_KnotVectorGetKnots(knx[r], &m, &U); int64_t cnt = (int64_t)m + 1; if (cnt > new_cap) { st = SC_ERR_BUFFER; break; } for (int64_t i = 0; i < cnt; ++i) out_new[r * new_cap + i] = U[i]; out_new_lens[r] = cnt; } } N_EndNurbs(&SK); return st; SC_CATCH } // N_BasisDerivs(knt, p, u, flg, der) -> out_nd[k*(p+1) + j] (k = 0..=der), out_span. SC_EXPORT int32_t sc_nlib_basis_basic_derivs(const double* knots, int64_t n_knots, int32_t degree, double u, int32_t flag, int64_t der, double* out_nd, int64_t cap, int64_t* out_span) { if (!basis_domain(n_knots, degree) || !valid_flag(flag) || der < 0 || der > 1000 || cap < (der + 1) * ((int64_t)degree + 1)) return SC_ERR_ARG; SC_TRY NL_KNOTVECTOR knt = make_knt(knots, n_knots); std::vector rows((size_t)der + 1); for (int64_t k = 0; k <= der; ++k) rows[(size_t)k] = out_nd + k * ((int64_t)degree + 1); NL_INDEX spn = -1; N_ErrClear(); NL_FLAG err = N_BasisDerivs(&knt, (NL_DEGREE)degree, u, (NL_FLAG)flag, (NL_INDEX)der, rows.data(), &spn); *out_span = (int64_t)spn; return nl_status(err); SC_CATCH } // N_MakeKnotsCompatible(R, S, p, q) on copies -> out_r, out_s (rescaled knots), // out_xr[0..*out_nxr), out_xs[0..*out_nxs) (capacity cap each). SC_EXPORT int32_t sc_nlib_basis_basic_make_compatible(const double* r, int64_t n_r, const double* s, int64_t n_s, int32_t p, int32_t q, double* out_r, double* out_s, double* out_xr, int64_t* out_nxr, double* out_xs, int64_t* out_nxs, int64_t cap) { if (n_r < 1 || n_s < 1 || p < 0 || q < 0 || p > 32767 || q > 32767 || n_r < (int64_t)p + 1 || n_s < (int64_t)q + 1 || cap < n_r + n_s + 2 * MC_PAD) return SC_ERR_ARG; SC_TRY const double inf = std::numeric_limits::infinity(); std::vector rb((size_t)(n_r + MC_PAD), inf), sb((size_t)(n_s + MC_PAD), inf); for (int64_t i = 0; i < n_r; ++i) rb[(size_t)i] = r[i]; for (int64_t i = 0; i < n_s; ++i) sb[(size_t)i] = s[i]; // Upstream writes the new knots into caller memory without a bound: give it room // for every index it can reach (each write advances i or j past a real knot). std::vector xr((size_t)cap), xs((size_t)cap); NL_KNOTVECTOR knr = make_knt(rb.data(), n_r), kns = make_knt(sb.data(), n_s); NL_KNOTVECTOR kxr, kxs; N_KnotVectorFromRealArray(&kxr, xr.data(), (NL_INDEX)(cap - 1)); N_KnotVectorFromRealArray(&kxs, xs.data(), (NL_INDEX)(cap - 1)); N_ErrClear(); N_MakeKnotsCompatible(&knr, &kns, (NL_DEGREE)p, (NL_DEGREE)q, &kxr, &kxs); NL_INDEX mxr = -1, mxs = -1; NL_REAL* U = nullptr; N_KnotVectorGetKnots(&kxr, &mxr, &U); N_KnotVectorGetKnots(&kxs, &mxs, &U); for (int64_t i = 0; i < n_r; ++i) out_r[i] = rb[(size_t)i]; for (int64_t i = 0; i < n_s; ++i) out_s[i] = sb[(size_t)i]; *out_nxr = (int64_t)mxr + 1; *out_nxs = (int64_t)mxs + 1; for (int64_t i = 0; i <= mxr; ++i) out_xr[i] = xr[(size_t)i]; for (int64_t i = 0; i <= mxs; ++i) out_xs[i] = xs[(size_t)i]; return SC_OK; SC_CATCH }