From 4c1bb501febb4cdd2693fc1e79bceba04dd4408e Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Thu, 18 Sep 2025 20:27:02 -0400 Subject: [PATCH 01/26] Adds a Unit class to CoefClasses - The Unit class is a vector of tuples of name (ascii), unit type (ascii), and value (float). - This vector is serialized in HDF5 coefficient files as a list of tuples - Manipulators and accessors are provided as part of Python bindings to C++ member functions - The gravitational constant is set on read from a Coefs instance and used to set the gravitational constant in BiorthBasis. - The value of G=1 by default (exp). The user is responsible for setting the units in a Coefs instance using the setUnit() call, one for each physical unit including the gravitational constant. --- expui/BasisFactory.H | 3 + expui/BiorthBasis.cc | 77 ++++++++++++++++++++++--- expui/CoefStruct.H | 6 ++ expui/Coefficients.H | 41 ++++++++++++- expui/Coefficients.cc | 131 ++++++++++++++++++++++++++++++++++++++++-- pyEXP/CoefWrappers.cc | 30 ++++++++++ 6 files changed, 273 insertions(+), 15 deletions(-) diff --git a/expui/BasisFactory.H b/expui/BasisFactory.H index 1598f3f7d..6fc02766d 100644 --- a/expui/BasisFactory.H +++ b/expui/BasisFactory.H @@ -136,6 +136,9 @@ namespace BasisClasses //! Get the current pseudo acceleration value Eigen::Vector3d currentAccel(double time); + //! Gravitational constant + double G = 1.0; + public: //! The current pseudo acceleration diff --git a/expui/BiorthBasis.cc b/expui/BiorthBasis.cc index 203f5f115..baf01f061 100644 --- a/expui/BiorthBasis.cc +++ b/expui/BiorthBasis.cc @@ -328,6 +328,7 @@ namespace BasisClasses if (expcoef.rows()>0 && expcoef.cols()>0) expcoef.setZero(); totalMass = 0.0; used = 0; + G = 1.0; } @@ -341,6 +342,8 @@ namespace BasisClasses cf->time = time; cf->normed = true; + G = cf->G; + // Angular storage dimension int ldim = (lmax+1)*(lmax+2)/2; @@ -396,6 +399,10 @@ namespace BasisClasses CoefClasses::SphStruct* cf = dynamic_cast(coef.get()); + // Set gravitational constant + // + G = cf->G; + // Cache the current coefficient structure // coefret = coef; @@ -594,7 +601,7 @@ namespace BasisClasses } double densfac = 1.0/(scale*scale*scale) * 0.25/M_PI; - double potlfac = 1.0/scale; + double potlfac = G/scale; return {den0 * densfac, // 0 @@ -700,7 +707,7 @@ namespace BasisClasses } } - double potlfac = 1.0/scale; + double potlfac = G/scale; potr *= (-potlfac)/scale; pott *= (-potlfac); @@ -746,7 +753,7 @@ namespace BasisClasses double tpotx = v[6]*x/R - v[8]*y/R ; double tpoty = v[6]*y/R + v[8]*x/R ; - return {v[0], v[1], v[2], v[3], v[4], v[5], tpotx, tpoty, v[7]}; + return {v[0], v[1], v[2], v[3]*G, v[4]*G, v[5]*G, tpotx*G, tpoty*G, v[7]*G}; } Spherical::BasisArray SphericalSL::getBasis @@ -1486,6 +1493,12 @@ namespace BasisClasses double tdens0, tdens, tpotl0, tpotl, tpotR, tpotz, tpotp; sl->accumulated_eval(R, z, phi, tpotl0, tpotl, tpotR, tpotz, tpotp); + + tpotl0 *= G; + tpotl *= G; + tpotR *= G; + tpotz *= G; + tpotp *= G; tdens = sl->accumulated_dens_eval(R, z, phi, tdens0); @@ -1506,6 +1519,12 @@ namespace BasisClasses double tdens0, tdens, tpotl0, tpotl, tpotR, tpotz, tpotp; sl->accumulated_eval(R, z, phi, tpotl0, tpotl, tpotR, tpotz, tpotp); + + tpotl0 *= G; + tpotl *= G; + tpotR *= G; + tpotz *= G; + tpotp *= G; tdens = sl->accumulated_dens_eval(R, z, phi, tdens0); @@ -1532,7 +1551,7 @@ namespace BasisClasses double tpotx = tpotR*x/R - tpotp*y/R ; double tpoty = tpotR*y/R + tpotp*x/R ; - return {tpotx, tpoty, tpotz}; + return {tpotx*G, tpoty*G, tpotz*G}; } // Evaluate in cylindrical coordinates @@ -1543,6 +1562,12 @@ namespace BasisClasses sl->accumulated_eval(R, z, phi, tpotl0, tpotl, tpotR, tpotz, tpotp); tdens = sl->accumulated_dens_eval(R, z, phi, tdens0); + tpotl0 *= G; + tpotl *= G; + tpotR *= G; + tpotz *= G; + tpotp *= G; + if (midplane) { height = sl->accumulated_midplane_eval(R, -colh*hcyl, colh*hcyl, phi); return @@ -1577,6 +1602,8 @@ namespace BasisClasses cf->nmax = nmax; cf->time = time; + G = cf->G; + Eigen::VectorXd cos1(nmax), sin1(nmax); // Initialize the values @@ -1606,6 +1633,10 @@ namespace BasisClasses CoefClasses::CylStruct* cf = dynamic_cast(coef.get()); + // Set gravitational constant + // + G = cf->G; + // Cache the current coefficient structure // coefret = coef; @@ -1849,6 +1880,7 @@ namespace BasisClasses if (expcoef.rows()>0 && expcoef.cols()>0) expcoef.setZero(); totalMass = 0.0; used = 0; + G = 1.0; } @@ -1860,6 +1892,8 @@ namespace BasisClasses cf->nmax = nmax; cf->time = time; + G = cf->G; + // Allocate the coefficient storage cf->store.resize((mmax+1)*nmax); @@ -1907,6 +1941,10 @@ namespace BasisClasses CoefClasses::CylStruct* cf = dynamic_cast(coef.get()); auto & cc = *cf->coefs; + // Set gravitational constant + // + G = cf->G; + // Cache the current coefficient structure // coefret = coef; @@ -2090,11 +2128,11 @@ namespace BasisClasses den0 *= -1.0; den1 *= -1.0; - pot0 *= -1.0; - pot1 *= -1.0; - rpot *= -1.0; - zpot *= -1.0; - ppot *= -1.0; + pot0 *= -G; + pot1 *= -G; + rpot *= -G; + zpot *= -G; + ppot *= -G; return {den0, den1, den0+den1, pot0, pot1, pot0+pot1, rpot, zpot, ppot}; } @@ -2650,6 +2688,7 @@ namespace BasisClasses if (expcoef.rows()>0 && expcoef.cols()>0) expcoef.setZero(); totalMass = 0.0; used = 0; + G = 1.0; } @@ -2661,6 +2700,8 @@ namespace BasisClasses cf->nmax = nmax; cf->time = time; + G = cf->G; + // Allocate the coefficient storage cf->store.resize((mmax+1)*nmax); @@ -2708,6 +2749,10 @@ namespace BasisClasses CoefClasses::CylStruct* cf = dynamic_cast(coef.get()); auto & cc = *cf->coefs; + // Set gravitational constant + // + G = cf->G; + // Cache the current coefficient structure coefret = coef; @@ -3135,6 +3180,7 @@ namespace BasisClasses expcoef.setZero(); totalMass = 0.0; used = 0; + G = 1.0; } @@ -3147,6 +3193,8 @@ namespace BasisClasses cf->nmaxz = nmaxz; cf->time = time; + G = cf->G; + cf->allocate(); *cf->coefs = expcoef; @@ -3180,6 +3228,10 @@ namespace BasisClasses auto cf = dynamic_cast(coef.get()); expcoef = *cf->coefs; + // Set gravitational constant + // + G = cf->G; + // Cache the current coefficient structure // coefret = coef; @@ -3648,6 +3700,7 @@ namespace BasisClasses expcoef.setZero(); totalMass = 0.0; used = 0; + G = 1.0; } @@ -3660,6 +3713,8 @@ namespace BasisClasses cf->nmaxz = nmaxz; cf->time = time; + G = cf->G; + cf->allocate(); *cf->coefs = expcoef; @@ -3693,6 +3748,10 @@ namespace BasisClasses auto cf = dynamic_cast(coef.get()); expcoef = *cf->coefs; + // Set gravitational constant + // + G = cf->G; + // Cache the cuurent coefficient structure // coefret = coef; diff --git a/expui/CoefStruct.H b/expui/CoefStruct.H index 26d011e28..9afe42e99 100644 --- a/expui/CoefStruct.H +++ b/expui/CoefStruct.H @@ -23,6 +23,9 @@ namespace CoefClasses //! Time stamp double time; + //! Gravitational constant + double G = 1.0; + //! Coefficient data Eigen::VectorXcd store; @@ -82,6 +85,9 @@ namespace CoefClasses //! Read-only access to center (no copy) double getTime() { return time; } + //! Set gravitational constant + void setGravConstant(double val) { G = val; } + }; //! Specialization of CoefStruct for spheres diff --git a/expui/Coefficients.H b/expui/Coefficients.H index 3fafa2dca..de537fba3 100644 --- a/expui/Coefficients.H +++ b/expui/Coefficients.H @@ -5,7 +5,7 @@ #include // Needed by member functions for writing parameters and stanzas -#include +#include #include // Store coefficient matrices and 2d grids #include // For 3d rectangular grids? @@ -24,9 +24,28 @@ namespace CoefClasses using E2d = Eigen::Matrix, Eigen::Dynamic, Eigen::Dynamic, Eigen::ColMajor>; - + using E3d = Eigen::Tensor, 3>; + // A struct for a single unit metadatum + struct Unit { + char name[16]; //! The name of the physical value in ascii + char unit[16]; //! The unit type in ascii + float value; //! The value as a float + + Unit() { std::memset(name, 0, 16); std::memset(unit, 0, 16); value=0.0f; } + + Unit(std::string n, std::string u, float v) : value(v) + { + std::memset(name, 0, 16); std::memset(unit, 0, 16); + size_t arraySize = n.length() + 1, sz = strlen(name); + strncpy(name, n.c_str(), std::min(arraySize, sz)); + arraySize = u.length() + 1; sz = strlen(unit); + strncpy(unit, u.c_str(), std::min(arraySize, sz)); + } + + }; + /** Abstract class for any type of coefficient database @@ -87,6 +106,9 @@ namespace CoefClasses //! Time offset for interpolation double deltaT; + //! Default units + std::vector units {{"G", "", 1.0f}}; + public: //! Constructor @@ -198,6 +220,21 @@ namespace CoefClasses //! Set maximum grid interpolation offset void setDeltaT(double dT) { deltaT = dT; } + //! Set units + void setUnit(std::string name, std::string unit, float value); + + //! Write units to H5 + void WriteH5Units(HighFive::File& file); + + //! Read units from H5 + void ReadH5Units(HighFive::File& file); + + //! Get units + std::vector> getUnits(); + + //! Get gravitational constant + double getGravConstant(); + class CoefsError : public std::runtime_error { public: diff --git a/expui/Coefficients.cc b/expui/Coefficients.cc index b01cb0c04..9c0895fca 100644 --- a/expui/Coefficients.cc +++ b/expui/Coefficients.cc @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -17,9 +18,21 @@ #include +// Create a helper function to describe the compound type +HighFive::CompoundType create_compound_Unit() { + return { + {"name", HighFive::AtomicType()}, + {"unit", HighFive::AtomicType()}, + {"value", HighFive::AtomicType()} + }; +} + +// Register the type with HighFive. +HIGHFIVE_REGISTER_TYPE(CoefClasses::Unit, create_compound_Unit) + + namespace CoefClasses { - void Coefs::copyfields(std::shared_ptr p) { // These variables will copy data, not pointers @@ -31,6 +44,66 @@ namespace CoefClasses p->times = times; } + void Coefs::setUnit(std::string name, std::string unit, float value) + { + auto copy_s = [](std::string& s, char* c) -> void { + size_t arraySize = s.length() + 1, sz = strlen(c); + strncpy(c, s.c_str(), std::min(arraySize, sz)); + }; + + std::transform(name.begin(), name.end(), name.begin(), + [](unsigned char c){ return std::tolower(c); }); + + for (auto & p : units) { + std::string p_name(p.name); + + std::transform(p_name.begin(), p_name.end(), p_name.begin(), + [](unsigned char c){ return std::tolower(c); }); + + if (name.find(p_name) == 0) { + copy_s(unit, &p.unit[0]); + p.value = value; + return; + } + } + + // No matches + units.push_back({name, unit, value}); + } + + //! Get units + std::vector> Coefs::getUnits() + { + std::vector> ret; + for (auto p : units) ret.push_back({p.name, p.unit, p.value}); + return ret; + } + + + //! Get gravitational constant + double Coefs::getGravConstant() + { + for (auto p : units) { + std::string G(p.name); + if (G.find("G")==0) return p.value; + } + + return 1.0; + } + + void Coefs::WriteH5Units(HighFive::File& file) + { + HighFive::DataSet dataset = file.createDataSet("Units", units); + } + + void Coefs::ReadH5Units(HighFive::File& file) + { + if (file.exist("Units")) { + HighFive::DataSet dataset = file.getDataSet("Units"); + units = dataset.read>(); + } + } + std::tuple Coefs::interpolate(double time) { bool onGrid = true; @@ -82,6 +155,10 @@ namespace CoefClasses unsigned count; double scale; + // Check for units + ReadH5Units(file); + double G = getGravConstant(); + file.getAttribute("name" ).read(name ); file.getAttribute("lmax" ).read(Lmax ); file.getAttribute("nmax" ).read(Nmax ); @@ -149,6 +226,7 @@ namespace CoefClasses coef->scale = scale; coef->geom = geometry; coef->id = forceID; + coef->setGravConstant(G); coef->allocate(); *coef->coefs = in; @@ -173,8 +251,10 @@ namespace CoefClasses ret->coefs[v.first] = std::dynamic_pointer_cast(v.second->deepcopy()); - ret->Lmax = Lmax; - ret->Nmax = Nmax; + ret->Lmax = Lmax; + ret->Nmax = Nmax; + ret->units = units; + return ret; } @@ -195,6 +275,7 @@ namespace CoefClasses ret->Mmax = Mmax; ret->Nmax = Nmax; ret->angle = angle; + ret->units = units; return ret; } @@ -215,6 +296,7 @@ namespace CoefClasses ret->NmaxX = NmaxX; ret->NmaxY = NmaxY; ret->NmaxZ = NmaxZ; + ret->units = units; return ret; } @@ -235,6 +317,7 @@ namespace CoefClasses ret->NmaxX = NmaxX; ret->NmaxY = NmaxY; ret->NmaxZ = NmaxZ; + ret->units = units; return ret; } @@ -287,6 +370,10 @@ namespace CoefClasses unsigned count; double scale; + // Check for units + ReadH5Units(file); + double G = getGravConstant(); + file.getAttribute("name" ).read(name ); file.getAttribute("nfld" ).read(Nfld ); file.getAttribute("lmax" ).read(Lmax ); @@ -338,6 +425,7 @@ namespace CoefClasses coef->scale = scale; coef->geom = geometry; coef->id = fieldID; + coef->setGravConstant(G); coef->allocate(); coef->store = in; @@ -378,6 +466,10 @@ namespace CoefClasses unsigned count; double scale; + // Check for units + ReadH5Units(file); + double G = getGravConstant(); + file.getAttribute("name" ).read(name ); file.getAttribute("nfld" ).read(Nfld ); file.getAttribute("mmax" ).read(Mmax ); @@ -429,6 +521,7 @@ namespace CoefClasses coef->scale = scale; coef->geom = geometry; coef->id = fieldID; + coef->setGravConstant(G); coef->allocate(); coef->store = in; @@ -634,6 +727,8 @@ namespace CoefClasses void SphCoefs::WriteH5Params(HighFive::File& file) { + WriteH5Units(file); + double scale = coefs.begin()->second->scale; std::string forceID(coefs.begin()->second->id); @@ -812,6 +907,10 @@ namespace CoefClasses unsigned count; std::string config; + ReadH5Units(file); + double G = getGravConstant(); + + file.getAttribute("name" ).read(name ); file.getAttribute("mmax" ).read(Mmax ); file.getAttribute("nmax" ).read(Nmax ); @@ -876,6 +975,7 @@ namespace CoefClasses coef->assign(in, Mmax, Nmax); coef->time = Time; + coef->setGravConstant(G); coefs[roundTime(Time)] = coef; } @@ -1031,6 +1131,8 @@ namespace CoefClasses void CylCoefs::WriteH5Params(HighFive::File& file) { + WriteH5Units(file); + std::string forceID(coefs.begin()->second->id); file.createAttribute("mmax", HighFive::DataSpace::From(Mmax)).write(Mmax); @@ -1206,6 +1308,10 @@ namespace CoefClasses unsigned count; std::string config; + // Check for units + ReadH5Units(file); + double G = getGravConstant(); + file.getAttribute("name" ).read(name ); file.getAttribute("nmaxx" ).read(NmaxX ); file.getAttribute("nmaxy" ).read(NmaxY ); @@ -1246,6 +1352,7 @@ namespace CoefClasses coef->assign(dat); coef->time = Time; + coef->setGravConstant(G); coefs[roundTime(Time)] = coef; } @@ -1353,6 +1460,8 @@ namespace CoefClasses void SlabCoefs::WriteH5Params(HighFive::File& file) { + WriteH5Units(file); + std::string forceID(coefs.begin()->second->id); file.createAttribute("nmaxx", HighFive::DataSpace::From(NmaxX)).write(NmaxX); @@ -1558,6 +1667,10 @@ namespace CoefClasses unsigned count; std::string config; + // Check for units + ReadH5Units(file); + double G = getGravConstant(); + file.getAttribute("name" ).read(name ); file.getAttribute("nmaxx" ).read(NmaxX ); file.getAttribute("nmaxy" ).read(NmaxY ); @@ -1598,7 +1711,8 @@ namespace CoefClasses coef->assign(dat); coef->time = Time; - + coef->setGravConstant(G); + coefs[roundTime(Time)] = coef; } @@ -1705,6 +1819,8 @@ namespace CoefClasses void CubeCoefs::WriteH5Params(HighFive::File& file) { + WriteH5Units(file); + std::string forceID(coefs.begin()->second->id); file.createAttribute("nmaxx", HighFive::DataSpace::From(NmaxX)).write(NmaxX); @@ -1978,6 +2094,7 @@ namespace CoefClasses // Pack the data into the coefficient variable // auto coef = std::make_shared(); + coef->traj = traj; coef->rank = rank; coef->time = times[n]; @@ -2055,6 +2172,8 @@ namespace CoefClasses void TrajectoryData::WriteH5Params(HighFive::File& file) { + WriteH5Units(file); + int traj = coefs.begin()->second->traj; int rank = coefs.begin()->second->rank; @@ -2893,6 +3012,8 @@ namespace CoefClasses double scale = coefs.begin()->second->scale; + WriteH5Units(file); + // Write the remaining parameters // file.createAttribute ("nfld", HighFive::DataSpace::From(Nfld) ).write(Nfld); @@ -3011,6 +3132,8 @@ namespace CoefClasses std::string fieldID("polar velocity orthgonal function coefficients"); file.createAttribute("fieldID", HighFive::DataSpace::From(fieldID)).write(fieldID); + WriteH5Units(file); + double scale = coefs.begin()->second->scale; // Write the remaining parameters diff --git a/pyEXP/CoefWrappers.cc b/pyEXP/CoefWrappers.cc index c4559acc5..bdd760deb 100644 --- a/pyEXP/CoefWrappers.cc +++ b/pyEXP/CoefWrappers.cc @@ -770,6 +770,26 @@ void CoefficientClasses(py::module &m) { -------- setCenter : read-write access to the center data )") + .def("setGravConstant", &CoefStruct::setGravConstant, + py::arg("G"), + R"( + Set the gravitational constant + + Parameters + ---------- + G : float + gravitational constant, default is 1.0 + + Returns + ------- + None + + Notes + ----- + The gravitational constant is used for field evaluation for + biorthogonal basis sets. It will be set autoomatically when + reading EXP coefficient files. + )") .def("setCoefCenter", static_cast&)>(&CoefStruct::setCenter), py::arg("mat"), @@ -1258,6 +1278,15 @@ void CoefficientClasses(py::module &m) { bool True if the data is identical, False otherwise. )") + .def("getUnits", &CoefClasses::Coefs::getUnits, + R"( + Get the units of the coefficient data + + Returns + ------- + list((str,str,float)) + list of + )") .def_static("factory", &CoefClasses::Coefs::factory, R"( Deduce the type and read coefficients from a native or HDF5 file @@ -1309,6 +1338,7 @@ void CoefficientClasses(py::module &m) { )", py::arg("coef"), py::arg("name")=""); + py::class_, PySphCoefs, CoefClasses::Coefs> (m, "SphCoefs", "Container for spherical coefficients") .def(py::init(), From b515bc107bb74f020f1ae3e84311000ff2c057a2 Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Thu, 18 Sep 2025 20:40:29 -0400 Subject: [PATCH 02/26] Bind the setUnit() member of Coefs --- pyEXP/CoefWrappers.cc | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/pyEXP/CoefWrappers.cc b/pyEXP/CoefWrappers.cc index bdd760deb..fd1165373 100644 --- a/pyEXP/CoefWrappers.cc +++ b/pyEXP/CoefWrappers.cc @@ -1134,6 +1134,25 @@ void CoefficientClasses(py::module &m) { list(float,...) list of times )") + .def("setUnit", + &CoefClasses::Coefs::setUnit, + R"( + Set the units for the coefficient struction. + + Parameters + ---------- + name : str + the name of physical quantity (G, Length, Mass, Time, etc) + unit : str + the unit string (scalar, mixed, kpc, Msun, Myr, km/s etc.). + This field is optional and can be empty. + value : float + the default value of the multiples of the unit + + Returns + ------- + None + )", py::arg("name"), py::arg("unit")="", py::arg("value")=1.0) .def("WriteH5Coefs", &CoefClasses::Coefs::WriteH5Coefs, R"( From 15e2fed774f957087f0e6a0b5f56d59271a77230 Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Sat, 20 Sep 2025 11:48:25 -0400 Subject: [PATCH 03/26] A few minor fixes to case handling --- expui/BiorthBasis.cc | 64 +++++++++++++++++++++---------------------- expui/Coefficients.H | 16 ++++++----- expui/Coefficients.cc | 41 +++++++++++++++++++++++---- 3 files changed, 76 insertions(+), 45 deletions(-) diff --git a/expui/BiorthBasis.cc b/expui/BiorthBasis.cc index baf01f061..9b1667bcb 100644 --- a/expui/BiorthBasis.cc +++ b/expui/BiorthBasis.cc @@ -753,7 +753,7 @@ namespace BasisClasses double tpotx = v[6]*x/R - v[8]*y/R ; double tpoty = v[6]*y/R + v[8]*x/R ; - return {v[0], v[1], v[2], v[3]*G, v[4]*G, v[5]*G, tpotx*G, tpoty*G, v[7]*G}; + return {v[0], v[1], v[2], v[3], v[4], v[5], tpotx, tpoty, v[7]}; } Spherical::BasisArray SphericalSL::getBasis @@ -2053,9 +2053,9 @@ namespace BasisClasses if (R>ortho->getRtable() or fabs(z)>ortho->getRtable()) { double r2 = R*R + z*z; double r = sqrt(r2); - pot0 = -totalMass/r; - rpot = -totalMass*R/(r*r2 + 10.0*std::numeric_limits::min()); - zpot = -totalMass*z/(r*r2 + 10.0*std::numeric_limits::min()); + pot0 = -G*totalMass/r; + rpot = -G*totalMass*R/(r*r2 + 10.0*std::numeric_limits::min()); + zpot = -G*totalMass*z/(r*r2 + 10.0*std::numeric_limits::min()); return {den0, den1, den0+den1, pot0, pot1, pot0+pot1, rpot, zpot, ppot}; } @@ -2158,8 +2158,8 @@ namespace BasisClasses double r2 = R*R + z*z; double r = sqrt(r2); - rpot = -totalMass*R/(r*r2 + 10.0*std::numeric_limits::min()); - zpot = -totalMass*z/(r*r2 + 10.0*std::numeric_limits::min()); + rpot = -G*totalMass*R/(r*r2 + 10.0*std::numeric_limits::min()); + zpot = -G*totalMass*z/(r*r2 + 10.0*std::numeric_limits::min()); return {rpot, zpot, ppot}; } @@ -2218,9 +2218,9 @@ namespace BasisClasses } } - rpot *= -1.0; - zpot *= -1.0; - ppot *= -1.0; + rpot *= -G; + zpot *= -G; + ppot *= -G; double potx = rpot*x/R - ppot*y/R; double poty = rpot*y/R + ppot*x/R; @@ -2910,10 +2910,10 @@ namespace BasisClasses den0 *= -1.0; den1 *= -1.0; - pot0 *= -1.0; - pot1 *= -1.0; - rpot *= -1.0; - ppot *= -1.0; + pot0 *= -G; + pot1 *= -G; + rpot *= -G; + ppot *= -G; return {den0, den1, den0+den1, pot0, pot1, pot0+pot1, rpot, zpot, ppot}; } @@ -2977,8 +2977,8 @@ namespace BasisClasses } } - rpot *= -1.0; - ppot *= -1.0; + rpot *= -G; + ppot *= -G; double potx = rpot*x/R - ppot*y/R; @@ -3472,7 +3472,7 @@ namespace BasisClasses } } - return {accx.real(), accy.real(), accz.real()}; + return {G*accx.real(), G*accy.real(), G*accz.real()}; } @@ -3483,7 +3483,7 @@ namespace BasisClasses auto [pot, den, frcx, frcy, frcz] = eval(x, y, z); - return {0, den, den, 0, pot, pot, frcx, frcy, frcz}; + return {0, den, den, 0, pot*G, pot*G, frcx*G, frcy*G, frcz*G}; } std::vector Slab::cyl_eval(double R, double z, double phi) @@ -3500,11 +3500,11 @@ namespace BasisClasses double potp = -frcx*sin(phi) + frcy*cos(phi); double potz = frcz; - potR *= -1; - potp *= -1; - potz *= -1; + potR *= -G; + potp *= -G; + potz *= -G; - return {0, den, den, 0, pot, pot, potR, potz, potp}; + return {0, den, den, 0, pot*G, pot*G, potR, potz, potp}; } std::vector Slab::sph_eval(double r, double costh, double phi) @@ -3522,11 +3522,11 @@ namespace BasisClasses double pott = frcx*cos(phi)*costh + frcy*sin(phi)*costh - frcz*sinth; double potp = -frcx*sin(phi) + frcy*cos(phi); - potr *= -1; - pott *= -1; - potp *= -1; + potr *= -G; + pott *= -G; + potp *= -G; - return {0, den, den, 0, pot, pot, potr, pott, potp}; + return {0, den, den, 0, pot*G, pot*G, potr, pott, potp}; } @@ -3841,7 +3841,7 @@ namespace BasisClasses double frcy = -frc(1).real(); double frcz = -frc(2).real(); - return {0, den1, den1, 0, pot1, pot1, frcx, frcy, frcz}; + return {0, den1, den1, 0, pot1*G, pot1*G, frcx*G, frcy*G, frcz*G}; } std::vector Cube::getAccel(double x, double y, double z) @@ -3855,7 +3855,7 @@ namespace BasisClasses // Get the basis fields auto frc = ortho->get_force(expcoef, pos); - return {-frc(0).real(), -frc(1).real(), -frc(2).real()}; + return {-G*frc(0).real(), -G*frc(1).real(), -G*frc(2).real()}; } std::vector Cube::cyl_eval(double R, double z, double phi) @@ -3873,7 +3873,7 @@ namespace BasisClasses double den1 = ortho->get_dens(expcoef, pos).real(); double pot1 = ortho->get_pot (expcoef, pos).real(); - auto frc = ortho->get_force(expcoef, pos); + auto frc = ortho->get_force(expcoef, pos)*G; double frcx = frc(0).real(), frcy = frc(1).real(), frcz = frc(2).real(); @@ -3902,7 +3902,7 @@ namespace BasisClasses // Get the basis fields double den1 = ortho->get_dens(expcoef, pos).real(); - double pot1 = ortho->get_pot (expcoef, pos).real(); + double pot1 = ortho->get_pot (expcoef, pos).real() * G; auto frc = ortho->get_force(expcoef, pos); @@ -3914,9 +3914,9 @@ namespace BasisClasses double pott = frcx*cos(phi)*costh + frcy*sin(phi)*costh - frcz*sinth; double potp = -frcx*sin(phi) + frcy*cos(phi); - potr *= -1; - pott *= -1; - potp *= -1; + potr *= -G; + pott *= -G; + potp *= -G; return {0, den1, den1, 0, pot1, pot1, potr, pott, potp}; } diff --git a/expui/Coefficients.H b/expui/Coefficients.H index de537fba3..4d8d2569e 100644 --- a/expui/Coefficients.H +++ b/expui/Coefficients.H @@ -37,11 +37,13 @@ namespace CoefClasses Unit(std::string n, std::string u, float v) : value(v) { - std::memset(name, 0, 16); std::memset(unit, 0, 16); - size_t arraySize = n.length() + 1, sz = strlen(name); - strncpy(name, n.c_str(), std::min(arraySize, sz)); - arraySize = u.length() + 1; sz = strlen(unit); - strncpy(unit, u.c_str(), std::min(arraySize, sz)); + // Constant string size + const size_t sz=16; + // Zero fill + std::memset(name, 0, sz); std::memset(unit, 0, sz); + // Copy strings to char arrays + strncpy(name, n.c_str(), std::min(n.length()+1, sz)); + strncpy(unit, u.c_str(), std::min(u.length()+1, sz)); } }; @@ -106,8 +108,8 @@ namespace CoefClasses //! Time offset for interpolation double deltaT; - //! Default units - std::vector units {{"G", "", 1.0f}}; + //! Default units (EXP native) + std::vector units {{"G", "none", 1.0f}}; public: diff --git a/expui/Coefficients.cc b/expui/Coefficients.cc index 9c0895fca..50370d5df 100644 --- a/expui/Coefficients.cc +++ b/expui/Coefficients.cc @@ -31,8 +31,32 @@ HighFive::CompoundType create_compound_Unit() { HIGHFIVE_REGISTER_TYPE(CoefClasses::Unit, create_compound_Unit) +// Helper ostream manipulator for debugging Unit info +std::ostream& operator<< (std::ostream& out, + const std::vector& t) +{ + out << std::string(48, '-') << std::endl + << std::setw(16) << "Name" + << std::setw(16) << "Unit" + << std::setw(16) << "Value" + << std::endl + << std::setw(16) << std::string(10, '-') + << std::setw(16) << std::string(10, '-') + << std::setw(16) << std::string(10, '-') + << std::endl; + for (auto p : t) + out << std::setw(16) << p.name + << std::setw(16) << p.unit + << std::setw(16) << p.value + << std::endl; + out << std::string(48, '-') << std::endl; + + return out; +} + namespace CoefClasses { + void Coefs::copyfields(std::shared_ptr p) { // These variables will copy data, not pointers @@ -44,13 +68,17 @@ namespace CoefClasses p->times = times; } - void Coefs::setUnit(std::string name, std::string unit, float value) + void Coefs::setUnit(std::string Name, std::string Unit, float Value) { auto copy_s = [](std::string& s, char* c) -> void { - size_t arraySize = s.length() + 1, sz = strlen(c); - strncpy(c, s.c_str(), std::min(arraySize, sz)); + const size_t sz = 16; + strncpy(c, s.c_str(), std::min(s.length()+1, sz)); }; + // Keep copy with original case + std::string name(Name); + + // Convert to lower case for matching std::transform(name.begin(), name.end(), name.begin(), [](unsigned char c){ return std::tolower(c); }); @@ -61,14 +89,14 @@ namespace CoefClasses [](unsigned char c){ return std::tolower(c); }); if (name.find(p_name) == 0) { - copy_s(unit, &p.unit[0]); - p.value = value; + copy_s(Unit, &p.unit[0]); + p.value = Value; return; } } // No matches - units.push_back({name, unit, value}); + units.push_back({Name, Unit, Value}); } //! Get units @@ -94,6 +122,7 @@ namespace CoefClasses void Coefs::WriteH5Units(HighFive::File& file) { HighFive::DataSet dataset = file.createDataSet("Units", units); + std::cout << units; } void Coefs::ReadH5Units(HighFive::File& file) From 3b9617079be68471e4c7409c9cb4054b44f3f846 Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Sat, 20 Sep 2025 16:59:36 -0400 Subject: [PATCH 04/26] Minor fixes --- expui/Coefficients.H | 3 ++- expui/Coefficients.cc | 19 +++++++++++++++++-- pyEXP/CoefWrappers.cc | 14 +++++++++++--- 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/expui/Coefficients.H b/expui/Coefficients.H index 4d8d2569e..7786a1b5a 100644 --- a/expui/Coefficients.H +++ b/expui/Coefficients.H @@ -28,7 +28,8 @@ namespace CoefClasses using E3d = Eigen::Tensor, 3>; // A struct for a single unit metadatum - struct Unit { + struct Unit + { char name[16]; //! The name of the physical value in ascii char unit[16]; //! The unit type in ascii float value; //! The value as a float diff --git a/expui/Coefficients.cc b/expui/Coefficients.cc index 50370d5df..982b1e509 100644 --- a/expui/Coefficients.cc +++ b/expui/Coefficients.cc @@ -1,3 +1,6 @@ + + + #include #include #include @@ -128,8 +131,10 @@ namespace CoefClasses void Coefs::ReadH5Units(HighFive::File& file) { if (file.exist("Units")) { - HighFive::DataSet dataset = file.getDataSet("Units"); - units = dataset.read>(); + HighFive::DataSet dataset = file.getDataSet("Units"); + units = dataset.read>(); + std::cout << "Coefs::ReadH5Units: read units from HDF5 file:" << std::endl; + std::cout << units; } } @@ -2560,6 +2565,8 @@ namespace CoefClasses HighFive::Attribute geom = h5file.getAttribute("geometry"); geom.read(geometry); + // Now try to deduce the coefficient type + // try { // Is the set a biorthogonal basis (has the forceID attribute) // or general basis (fieldID attribute)? @@ -2597,6 +2604,10 @@ namespace CoefClasses throw std::runtime_error(msg + err.what()); } + // Attempt to red units + // + coefs->ReadH5Units(h5file); + return coefs; } catch (HighFive::Exception& err) { @@ -2791,6 +2802,10 @@ namespace CoefClasses // HighFive::File file(prefix, HighFive::File::ReadWrite); + // Attempt to read units + // + ReadH5Units(file); + // Get the dataset HighFive::DataSet dataset = file.getDataSet("count"); diff --git a/pyEXP/CoefWrappers.cc b/pyEXP/CoefWrappers.cc index fd1165373..e612df191 100644 --- a/pyEXP/CoefWrappers.cc +++ b/pyEXP/CoefWrappers.cc @@ -1154,8 +1154,15 @@ void CoefficientClasses(py::module &m) { None )", py::arg("name"), py::arg("unit")="", py::arg("value")=1.0) .def("WriteH5Coefs", - &CoefClasses::Coefs::WriteH5Coefs, - R"( + [](CoefClasses::Coefs& self, const std::string& filename) { + if (self.getUnits().size()==1) { + std::cout << "Coefs::WriteH5Coefs: please set units for your coefficient set using the `setUnit()` member," << std::endl + << " one for each unit. We suggest explicitly setting 'G', 'Length', 'Mass'," << std::endl + << " 'Time', and optionally 'Velocity' before writing HDF5 coefficients" << std::endl; + } + self.WriteH5Coefs(filename); + }, + R"( Write the coefficients into an EXP HDF5 coefficient file with the given prefix name. Parameters @@ -1173,7 +1180,8 @@ void CoefficientClasses(py::module &m) { coefficient file already exists. This is a safety feature. If you'd like a new version of this file, delete the old before this call. - )",py::arg("filename")) + )", + py::arg("filename")) .def("ExtendH5Coefs", &CoefClasses::Coefs::ExtendH5Coefs, R"( From a97277ebcb234e07320bcb74d4026ce1a6c93039 Mon Sep 17 00:00:00 2001 From: Martin Weinberg Date: Sat, 20 Sep 2025 17:18:34 -0400 Subject: [PATCH 05/26] Update pyEXP/CoefWrappers.cc Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- pyEXP/CoefWrappers.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyEXP/CoefWrappers.cc b/pyEXP/CoefWrappers.cc index e612df191..89a918746 100644 --- a/pyEXP/CoefWrappers.cc +++ b/pyEXP/CoefWrappers.cc @@ -787,7 +787,7 @@ void CoefficientClasses(py::module &m) { Notes ----- The gravitational constant is used for field evaluation for - biorthogonal basis sets. It will be set autoomatically when + biorthogonal basis sets. It will be set automatically when reading EXP coefficient files. )") .def("setCoefCenter", From ca50f5ba3366c9994e4e2159d124811271312612 Mon Sep 17 00:00:00 2001 From: Martin Weinberg Date: Sat, 20 Sep 2025 17:18:49 -0400 Subject: [PATCH 06/26] Update pyEXP/CoefWrappers.cc Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- pyEXP/CoefWrappers.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyEXP/CoefWrappers.cc b/pyEXP/CoefWrappers.cc index 89a918746..4e95cf0b5 100644 --- a/pyEXP/CoefWrappers.cc +++ b/pyEXP/CoefWrappers.cc @@ -1137,7 +1137,7 @@ void CoefficientClasses(py::module &m) { .def("setUnit", &CoefClasses::Coefs::setUnit, R"( - Set the units for the coefficient struction. + Set the units for the coefficient structure. Parameters ---------- From 67a8cce8a90cb204851dd4a721b1bd4f73b0072c Mon Sep 17 00:00:00 2001 From: Martin Weinberg Date: Sat, 20 Sep 2025 17:19:24 -0400 Subject: [PATCH 07/26] Update pyEXP/CoefWrappers.cc Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- pyEXP/CoefWrappers.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyEXP/CoefWrappers.cc b/pyEXP/CoefWrappers.cc index 4e95cf0b5..9e5e42018 100644 --- a/pyEXP/CoefWrappers.cc +++ b/pyEXP/CoefWrappers.cc @@ -1141,7 +1141,7 @@ void CoefficientClasses(py::module &m) { Parameters ---------- - name : str + name : str the name of physical quantity (G, Length, Mass, Time, etc) unit : str the unit string (scalar, mixed, kpc, Msun, Myr, km/s etc.). From b68e49431633a79a0e29930a3f62fd6cfaabd8f5 Mon Sep 17 00:00:00 2001 From: Martin Weinberg Date: Sat, 20 Sep 2025 17:20:12 -0400 Subject: [PATCH 08/26] Update pyEXP/CoefWrappers.cc Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- pyEXP/CoefWrappers.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyEXP/CoefWrappers.cc b/pyEXP/CoefWrappers.cc index 9e5e42018..8a858bc56 100644 --- a/pyEXP/CoefWrappers.cc +++ b/pyEXP/CoefWrappers.cc @@ -1312,7 +1312,7 @@ void CoefficientClasses(py::module &m) { Returns ------- list((str,str,float)) - list of + list of (name, unit, value) tuples, where each tuple contains the coefficient name (str), its unit (str), and its value (float). )") .def_static("factory", &CoefClasses::Coefs::factory, R"( From 080e7ce9b4000ef17d24403a579b8852a2e091ee Mon Sep 17 00:00:00 2001 From: Martin Weinberg Date: Sat, 20 Sep 2025 17:20:26 -0400 Subject: [PATCH 09/26] Update expui/Coefficients.cc Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- expui/Coefficients.cc | 3 --- 1 file changed, 3 deletions(-) diff --git a/expui/Coefficients.cc b/expui/Coefficients.cc index 982b1e509..5713ce063 100644 --- a/expui/Coefficients.cc +++ b/expui/Coefficients.cc @@ -1,6 +1,3 @@ - - - #include #include #include From 33b50b96afa54d473e26b85018c89dc5f106729a Mon Sep 17 00:00:00 2001 From: Martin Weinberg Date: Sat, 20 Sep 2025 17:21:06 -0400 Subject: [PATCH 10/26] Update expui/Coefficients.cc Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- expui/Coefficients.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/expui/Coefficients.cc b/expui/Coefficients.cc index 5713ce063..549d95a93 100644 --- a/expui/Coefficients.cc +++ b/expui/Coefficients.cc @@ -88,7 +88,7 @@ namespace CoefClasses std::transform(p_name.begin(), p_name.end(), p_name.begin(), [](unsigned char c){ return std::tolower(c); }); - if (name.find(p_name) == 0) { + if (name == p_name) { copy_s(Unit, &p.unit[0]); p.value = Value; return; From d8e763b56b940e3410c862ccd4e9bc3fb3f9e811 Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Sat, 20 Sep 2025 17:23:35 -0400 Subject: [PATCH 11/26] Remove tabs from parameter documentation --- pyEXP/CoefWrappers.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyEXP/CoefWrappers.cc b/pyEXP/CoefWrappers.cc index 8a858bc56..1c8e341ae 100644 --- a/pyEXP/CoefWrappers.cc +++ b/pyEXP/CoefWrappers.cc @@ -1147,7 +1147,7 @@ void CoefficientClasses(py::module &m) { the unit string (scalar, mixed, kpc, Msun, Myr, km/s etc.). This field is optional and can be empty. value : float - the default value of the multiples of the unit + the default value of the multiples of the unit Returns ------- From a344e50333f52c69ecce319aa7274b707504f9a2 Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Sat, 20 Sep 2025 17:48:17 -0400 Subject: [PATCH 12/26] Some minor fixes: use string equals rather than find for matching, limit header inclusion for HighFive --- expui/Coefficients.H | 6 ++++-- expui/Coefficients.cc | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/expui/Coefficients.H b/expui/Coefficients.H index 7786a1b5a..641cd2b2f 100644 --- a/expui/Coefficients.H +++ b/expui/Coefficients.H @@ -5,9 +5,10 @@ #include // Needed by member functions for writing parameters and stanzas -#include +#include +#include -#include // Store coefficient matrices and 2d grids +#include // Store coefficient matrices and 2d grids #include // For 3d rectangular grids? // YAML support @@ -16,6 +17,7 @@ // The EXP native coefficient classes #include +// All coefficient classes are in this namespace namespace CoefClasses { //! An index key diff --git a/expui/Coefficients.cc b/expui/Coefficients.cc index 549d95a93..0e9a41622 100644 --- a/expui/Coefficients.cc +++ b/expui/Coefficients.cc @@ -113,9 +113,9 @@ namespace CoefClasses { for (auto p : units) { std::string G(p.name); - if (G.find("G")==0) return p.value; + if (G == "G") return p.value; } - + // Default if "G" is removed or not set for some unforseen reason return 1.0; } From e6040d8ee093221abaeff83e76df9ec6b48e4fef Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Mon, 22 Sep 2025 16:48:50 -0400 Subject: [PATCH 13/26] Comments only --- expui/BiorthBasis.cc | 49 ++++++++++++++++++++++++++++---------------- 1 file changed, 31 insertions(+), 18 deletions(-) diff --git a/expui/BiorthBasis.cc b/expui/BiorthBasis.cc index 9b1667bcb..0da0e0052 100644 --- a/expui/BiorthBasis.cc +++ b/expui/BiorthBasis.cc @@ -601,7 +601,7 @@ namespace BasisClasses } double densfac = 1.0/(scale*scale*scale) * 0.25/M_PI; - double potlfac = G/scale; + double potlfac = G/scale; // Grav constant G is 1 by default return {den0 * densfac, // 0 @@ -707,7 +707,7 @@ namespace BasisClasses } } - double potlfac = G/scale; + double potlfac = G/scale; // Grav constant G is 1 by default potr *= (-potlfac)/scale; pott *= (-potlfac); @@ -1494,8 +1494,8 @@ namespace BasisClasses sl->accumulated_eval(R, z, phi, tpotl0, tpotl, tpotR, tpotz, tpotp); - tpotl0 *= G; - tpotl *= G; + tpotl0 *= G; // Apply gravitational constant to + tpotl *= G; // potential and forces tpotR *= G; tpotz *= G; tpotp *= G; @@ -1520,7 +1520,7 @@ namespace BasisClasses sl->accumulated_eval(R, z, phi, tpotl0, tpotl, tpotR, tpotz, tpotp); - tpotl0 *= G; + tpotl0 *= G; // Apply G to potential and forces tpotl *= G; tpotR *= G; tpotz *= G; @@ -1551,6 +1551,7 @@ namespace BasisClasses double tpotx = tpotR*x/R - tpotp*y/R ; double tpoty = tpotR*y/R + tpotp*x/R ; + // Apply G to forces on return return {tpotx*G, tpoty*G, tpotz*G}; } @@ -1562,7 +1563,7 @@ namespace BasisClasses sl->accumulated_eval(R, z, phi, tpotl0, tpotl, tpotR, tpotz, tpotp); tdens = sl->accumulated_dens_eval(R, z, phi, tdens0); - tpotl0 *= G; + tpotl0 *= G; // Apply G to potential and forces tpotl *= G; tpotR *= G; tpotz *= G; @@ -2053,6 +2054,7 @@ namespace BasisClasses if (R>ortho->getRtable() or fabs(z)>ortho->getRtable()) { double r2 = R*R + z*z; double r = sqrt(r2); + // Apply G to potential and forces pot0 = -G*totalMass/r; rpot = -G*totalMass*R/(r*r2 + 10.0*std::numeric_limits::min()); zpot = -G*totalMass*z/(r*r2 + 10.0*std::numeric_limits::min()); @@ -2128,7 +2130,7 @@ namespace BasisClasses den0 *= -1.0; den1 *= -1.0; - pot0 *= -G; + pot0 *= -G; // Apply G to potential and forces pot1 *= -G; rpot *= -G; zpot *= -G; @@ -2158,6 +2160,7 @@ namespace BasisClasses double r2 = R*R + z*z; double r = sqrt(r2); + // Apply G to forces rpot = -G*totalMass*R/(r*r2 + 10.0*std::numeric_limits::min()); zpot = -G*totalMass*z/(r*r2 + 10.0*std::numeric_limits::min()); @@ -2218,7 +2221,7 @@ namespace BasisClasses } } - rpot *= -G; + rpot *= -G; // Apply G to forces zpot *= -G; ppot *= -G; @@ -2910,7 +2913,7 @@ namespace BasisClasses den0 *= -1.0; den1 *= -1.0; - pot0 *= -G; + pot0 *= -G; // Apply G to potential and forces pot1 *= -G; rpot *= -G; ppot *= -G; @@ -2977,10 +2980,10 @@ namespace BasisClasses } } + // Apply G to forces rpot *= -G; ppot *= -G; - double potx = rpot*x/R - ppot*y/R; double poty = rpot*y/R + ppot*x/R; @@ -3472,6 +3475,7 @@ namespace BasisClasses } } + // Apply G to forces on return return {G*accx.real(), G*accy.real(), G*accz.real()}; } @@ -3483,6 +3487,7 @@ namespace BasisClasses auto [pot, den, frcx, frcy, frcz] = eval(x, y, z); + // Apply G to potential and forces on return return {0, den, den, 0, pot*G, pot*G, frcx*G, frcy*G, frcz*G}; } @@ -3500,10 +3505,11 @@ namespace BasisClasses double potp = -frcx*sin(phi) + frcy*cos(phi); double potz = frcz; - potR *= -G; + potR *= -G; // Apply G to forces potp *= -G; potz *= -G; + // Apply Go to potential on return return {0, den, den, 0, pot*G, pot*G, potR, potz, potp}; } @@ -3522,10 +3528,11 @@ namespace BasisClasses double pott = frcx*cos(phi)*costh + frcy*sin(phi)*costh - frcz*sinth; double potp = -frcx*sin(phi) + frcy*cos(phi); - potr *= -G; + potr *= -G; // Apply G to forces pott *= -G; potp *= -G; + // Apply G to potential on return return {0, den, den, 0, pot*G, pot*G, potr, pott, potp}; } @@ -3841,6 +3848,7 @@ namespace BasisClasses double frcy = -frc(1).real(); double frcz = -frc(2).real(); + // Apply G to potential and forces on return return {0, den1, den1, 0, pot1*G, pot1*G, frcx*G, frcy*G, frcz*G}; } @@ -3855,6 +3863,7 @@ namespace BasisClasses // Get the basis fields auto frc = ortho->get_force(expcoef, pos); + // Apply G to forces on return return {-G*frc(0).real(), -G*frc(1).real(), -G*frc(2).real()}; } @@ -3871,10 +3880,12 @@ namespace BasisClasses // Get the basis fields double den1 = ortho->get_dens(expcoef, pos).real(); - double pot1 = ortho->get_pot (expcoef, pos).real(); + double pot1 = ortho->get_pot (expcoef, pos).real() * G; - auto frc = ortho->get_force(expcoef, pos)*G; + auto frc = ortho->get_force(expcoef, pos) * G; + // Gravitational constant G applied to potenial and forces above + double frcx = frc(0).real(), frcy = frc(1).real(), frcz = frc(2).real(); double potR = frcx*cos(phi) + frcy*sin(phi); @@ -3904,7 +3915,9 @@ namespace BasisClasses double den1 = ortho->get_dens(expcoef, pos).real(); double pot1 = ortho->get_pot (expcoef, pos).real() * G; - auto frc = ortho->get_force(expcoef, pos); + auto frc = ortho->get_force(expcoef, pos) * G; + + // Gravitational constant G applied to potential and forces above double frcx = frc(0).real(); double frcy = frc(1).real(); @@ -3914,9 +3927,9 @@ namespace BasisClasses double pott = frcx*cos(phi)*costh + frcy*sin(phi)*costh - frcz*sinth; double potp = -frcx*sin(phi) + frcy*cos(phi); - potr *= -G; - pott *= -G; - potp *= -G; + potr *= -1.0; + pott *= -1.0; + potp *= -1.0; return {0, den1, den1, 0, pot1, pot1, potr, pott, potp}; } From 5baf02154c656e0dbed619e248de62f279a8eb4c Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Tue, 21 Oct 2025 17:45:04 -0400 Subject: [PATCH 14/26] Added unit checking code and test routine --- expui/CMakeLists.txt | 5 +- expui/Coefficients.H | 6 ++ expui/Coefficients.cc | 29 +++++++--- expui/UnitValidator.H | 70 +++++++++++++++++++++++ expui/UnitValidator.cc | 127 +++++++++++++++++++++++++++++++++++++++++ expui/testunits.cc | 33 +++++++++++ 6 files changed, 259 insertions(+), 11 deletions(-) create mode 100644 expui/UnitValidator.H create mode 100644 expui/UnitValidator.cc create mode 100644 expui/testunits.cc diff --git a/expui/CMakeLists.txt b/expui/CMakeLists.txt index 25f438d0a..6db279459 100644 --- a/expui/CMakeLists.txt +++ b/expui/CMakeLists.txt @@ -1,4 +1,4 @@ -set(bin_PROGRAMS nativetoh5 h5compare viewcoefs h5power makecoefs testread) +set(bin_PROGRAMS nativetoh5 h5compare viewcoefs h5power makecoefs testread testunits) set(common_LINKLIB OpenMP::OpenMP_CXX MPI::MPI_CXX yaml-cpp exputil ${VTK_LIBRARIES} ${HDF5_LIBRARIES} ${HDF5_HL_LIBRARIES}) @@ -38,7 +38,7 @@ endif() set(expui_SOURCES BasisFactory.cc BiorthBasis.cc FieldBasis.cc CoefContainer.cc CoefStruct.cc FieldGenerator.cc expMSSA.cc Coefficients.cc KMeans.cc Centering.cc ParticleIterator.cc - Koopman.cc BiorthBess.cc SvdSignChoice.cc) + Koopman.cc BiorthBess.cc SvdSignChoice.cc UnitValidator.cc) add_library(expui ${expui_SOURCES}) set_target_properties(expui PROPERTIES OUTPUT_NAME expui) target_include_directories(expui PUBLIC ${common_INCLUDE}) @@ -54,6 +54,7 @@ add_executable(viewcoefs viewcoefs.cc) add_executable(h5power h5power.cc) add_executable(makecoefs makecoefs.cc) add_executable(testread testread.cc) +add_executable(testunits testunits.cc UnitValidator.cc) foreach(program ${bin_PROGRAMS}) target_link_libraries(${program} expui exputil ${common_LINKLIB}) diff --git a/expui/Coefficients.H b/expui/Coefficients.H index 641cd2b2f..d72b3d984 100644 --- a/expui/Coefficients.H +++ b/expui/Coefficients.H @@ -17,6 +17,9 @@ // The EXP native coefficient classes #include +// Unit validation +#include + // All coefficient classes are in this namespace namespace CoefClasses { @@ -114,6 +117,9 @@ namespace CoefClasses //! Default units (EXP native) std::vector units {{"G", "none", 1.0f}}; + //! Unit validator + static UnitValidator check; + public: //! Constructor diff --git a/expui/Coefficients.cc b/expui/Coefficients.cc index 0e9a41622..b98146735 100644 --- a/expui/Coefficients.cc +++ b/expui/Coefficients.cc @@ -57,6 +57,9 @@ std::ostream& operator<< (std::ostream& out, namespace CoefClasses { + // Static instance of the unit validator + UnitValidator Coefs::check = UnitValidator(); + void Coefs::copyfields(std::shared_ptr p) { // These variables will copy data, not pointers @@ -75,19 +78,27 @@ namespace CoefClasses strncpy(c, s.c_str(), std::min(s.length()+1, sz)); }; - // Keep copy with original case - std::string name(Name); + // Run the type validation + // + std::string name, unit; + + if (check.allowedType(Name)) { + name = check.canonicalType(Name); + } else { + throw std::runtime_error(std::string("Coefs::setUnit: Warning, name '") + + Name + "' is not a recognized type name."); + } - // Convert to lower case for matching - std::transform(name.begin(), name.end(), name.begin(), - [](unsigned char c){ return std::tolower(c); }); + if (check.allowedUnit(Unit)) { + unit = check.canonicalUnit(Unit); + } else { + throw std::runtime_error(std::string("Coefs::setUnit: Warning, unit '") + + Unit + "' is not a recognized unit."); + }; for (auto & p : units) { std::string p_name(p.name); - std::transform(p_name.begin(), p_name.end(), p_name.begin(), - [](unsigned char c){ return std::tolower(c); }); - if (name == p_name) { copy_s(Unit, &p.unit[0]); p.value = Value; @@ -96,7 +107,7 @@ namespace CoefClasses } // No matches - units.push_back({Name, Unit, Value}); + units.push_back({name, unit, Value}); } //! Get units diff --git a/expui/UnitValidator.H b/expui/UnitValidator.H new file mode 100644 index 000000000..500a23c16 --- /dev/null +++ b/expui/UnitValidator.H @@ -0,0 +1,70 @@ +#include +#include + +class UnitValidator +{ +private: + + //! Allowed unit types and their aliases + std::unordered_map allowed_types; + + //! Allowed unit names and their aliases + std::unordered_map allowed_units; + + //! Function to check if an input string is in the allowed list or is + //! a valid alias + bool isAllowed(const std::string& input, + const std::unordered_map& allowed) + { + return allowed.count(input) > 0; + } + + //! Function to get the canonical name for an input string + std::string getCanonicalName + (const std::string& input, + const std::unordered_map& allowed) + { + if (isAllowed(input, allowed)) { + return allowed.at(input); + } + return "unknown"; + } + + std::unordered_map createAllowedUnitTypes(); + std::unordered_map createAllowedUnitNames(); + +public: + + //! Constructor + UnitValidator() + { + allowed_types = createAllowedUnitTypes(); + allowed_units = createAllowedUnitNames(); + } + + //! Destructor + ~UnitValidator() {} + + //! Check if type is allowed + bool allowedType(const std::string& type) + { + return isAllowed(type, allowed_types); + } + + //! Check if type is allowed + bool allowedUnit(const std::string& unit) + { + return isAllowed(unit, allowed_units); + } + + std::string canonicalType(const std::string& type) + { + return getCanonicalName(type, allowed_types); + } + + std::string canonicalUnit(const std::string& unit) + { + return getCanonicalName(unit, allowed_units); + } + +}; diff --git a/expui/UnitValidator.cc b/expui/UnitValidator.cc new file mode 100644 index 000000000..5e6a96ec9 --- /dev/null +++ b/expui/UnitValidator.cc @@ -0,0 +1,127 @@ +#include "UnitValidator.H" + +// Map aliases to their canonical (primary) unit types +std::unordered_map +UnitValidator::createAllowedUnitTypes() +{ + std::unordered_map allowed; + + // Canonical names + allowed["length"] = "length"; + allowed["mass"] = "mass"; + allowed["time"] = "time"; + allowed["velocity"] = "velocity"; + allowed["G"] = "G"; + + // Aliases + allowed["len"] = "length"; + allowed["l"] = "length"; + allowed["L"] = "length"; + allowed["m"] = "mass"; + allowed["M"] = "mass"; + allowed["vel"] = "velocity"; + allowed["Vel"] = "velocity"; + allowed["v"] = "velocity"; + allowed["V"] = "velocity"; + allowed["grav"] = "G"; + allowed["gravitational_constant"] = "G"; + + return allowed; +} + +// Map aliases to their canonical (primary) unit names +std::unordered_map +UnitValidator::createAllowedUnitNames() +{ + std::unordered_map allowed; + + // Canonical length units + // + allowed["m"] = "m"; + allowed["cm"] = "cm"; + allowed["km"] = "km"; + allowed["um"] = "um"; + allowed["nm"] = "nm"; + allowed["Angstrom"] = "Angstrom"; + allowed["AU"] = "AU"; + allowed["ly"] = "ly"; + allowed["pc"] = "pc"; + allowed["kpc"] = "kpc"; + allowed["Mpc"] = "Mpc"; + + // Astronomical mass units + // + allowed["Msun"] = "Msun"; + allowed["Mearth"] = "Mearth"; + allowed["g"] = "g"; + allowed["kg"] = "kg"; + + // Time units + // + allowed["s"] = "s"; + allowed["min"] = "min"; + allowed["hr"] = "hr"; + allowed["day"] = "day"; + allowed["yr"] = "yr"; + allowed["Myr"] = "Myr"; + allowed["Gyr"] = "Gyr"; + + // Velocity units + // + allowed["m/s"] = "m/s"; + allowed["km/s"] = "km/s"; + allowed["km/hr"] = "km/hr"; + allowed["km/min"] = "km/min"; + allowed["c"] = "c"; + + + // Length aliases + // + allowed["meter"] = "m"; + allowed["centimeter"] = "cm"; + allowed["kilometer"] = "km"; + allowed["nanometer"] = "nm"; + allowed["micrometer"] = "um"; + allowed["micron"] = "um"; + allowed["angstrom"] = "Angstrom"; + allowed["AA"] = "Angstrom"; + allowed["astronomical_unit"] = "AU"; + allowed["au"] = "AU"; + allowed["light_year"] = "ly"; + allowed["lyr"] = "ly"; + allowed["parsec"] = "pc"; + allowed["kiloparsec"] = "kpc"; + allowed["megaparsec"] = "Mpc"; + + + // Mass aliases + // + allowed["solar_mass"] = "Msun"; + allowed["earth_mass"] = "Mearth"; + allowed["gram"] = "g"; + allowed["kilograms"] = "kg"; + + // Time aliases + // + allowed["second"] = "s"; + allowed["minute"] = "min"; + allowed["hour"] = "hr"; + allowed["year"] = "yr"; + + // Velocity aliases + // + allowed["meter_per_second"] = "m/s"; + allowed["m_per_s"] = "m/s"; + allowed["km_per_s"] = "km/s"; + allowed["km_per_hr"] = "km/hr"; + allowed["km_per_min"] = "km/min"; + allowed["speed_of_light"] = "c"; + + // Special non-units + // + allowed["mixed"] = "mixed"; + allowed["none"] = "none"; + + return allowed; +} + diff --git a/expui/testunits.cc b/expui/testunits.cc new file mode 100644 index 000000000..4df5025db --- /dev/null +++ b/expui/testunits.cc @@ -0,0 +1,33 @@ +// This is a simple test program for the UnitValidator class + +#include +#include "UnitValidator.H" + +int main() +{ + UnitValidator check; + + std::string type, unit; + std::cout << "Enter type and unit: "; + std::cin >> type >> unit; + + if (check.allowedType(type)) { + auto canonical = check.canonicalType(type); + std::cout << "'" << type << "' is allowed. The canonical type is '" + << canonical << "'." << std::endl; + } else { + std::cout << "'" << type << "' is not a recognized string or alias." + << std::endl; + } + + if (check.allowedUnit(unit)) { + std::string canonical = check.canonicalUnit(unit); + std::cout << "'" << unit << "' is allowed. The canonical name is '" + << canonical << "'." << std::endl; + } else { + std::cout << "'" << unit << "' is not a recognized string or alias." + << std::endl; + } + + return 0; +} From 215da3a30a8674ebe88583768c9a9ac77a4540ca Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Tue, 21 Oct 2025 18:36:05 -0400 Subject: [PATCH 15/26] Added unit name checking --- expui/CMakeLists.txt | 2 +- expui/Coefficients.cc | 20 ++--- expui/UnitValidator.H | 67 +++++------------ expui/UnitValidator.cc | 162 +++++++++++++++++++++++++---------------- expui/testunits.cc | 32 ++++---- 5 files changed, 144 insertions(+), 139 deletions(-) diff --git a/expui/CMakeLists.txt b/expui/CMakeLists.txt index 6db279459..1212e79bc 100644 --- a/expui/CMakeLists.txt +++ b/expui/CMakeLists.txt @@ -54,7 +54,7 @@ add_executable(viewcoefs viewcoefs.cc) add_executable(h5power h5power.cc) add_executable(makecoefs makecoefs.cc) add_executable(testread testread.cc) -add_executable(testunits testunits.cc UnitValidator.cc) +add_executable(testunits testunits.cc) foreach(program ${bin_PROGRAMS}) target_link_libraries(${program} expui exputil ${common_LINKLIB}) diff --git a/expui/Coefficients.cc b/expui/Coefficients.cc index b98146735..b4d4ab536 100644 --- a/expui/Coefficients.cc +++ b/expui/Coefficients.cc @@ -80,22 +80,15 @@ namespace CoefClasses // Run the type validation // - std::string name, unit; + auto [valid, name, unit] = check(Name, Unit); - if (check.allowedType(Name)) { - name = check.canonicalType(Name); - } else { - throw std::runtime_error(std::string("Coefs::setUnit: Warning, name '") - + Name + "' is not a recognized type name."); + if (not valid) { + throw std::runtime_error(std::string("Coefs::setUnit: Warning, type '") + + Name + "' or unit '" + Unit + "' are not recognized."); } - if (check.allowedUnit(Unit)) { - unit = check.canonicalUnit(Unit); - } else { - throw std::runtime_error(std::string("Coefs::setUnit: Warning, unit '") - + Unit + "' is not a recognized unit."); - }; - + // Check for existing unit and update + // for (auto & p : units) { std::string p_name(p.name); @@ -107,6 +100,7 @@ namespace CoefClasses } // No matches + // units.push_back({name, unit, Value}); } diff --git a/expui/UnitValidator.H b/expui/UnitValidator.H index 500a23c16..df80891e4 100644 --- a/expui/UnitValidator.H +++ b/expui/UnitValidator.H @@ -1,70 +1,39 @@ #include #include +#include +/** Class to validate unit types and names The allowed types and units + can be extended as needed by adding new names and aliasess to + UnitValidator::createAllowedUnitTypes() and + UnitValidator::createAllowedUnitNames() in UnitValidator.cc +*/ class UnitValidator { private: - //! Allowed unit types and their aliases + //! Store allowed unit types and their aliases std::unordered_map allowed_types; - //! Allowed unit names and their aliases - std::unordered_map allowed_units; - - //! Function to check if an input string is in the allowed list or is - //! a valid alias - bool isAllowed(const std::string& input, - const std::unordered_map& allowed) - { - return allowed.count(input) > 0; - } - - //! Function to get the canonical name for an input string - std::string getCanonicalName - (const std::string& input, - const std::unordered_map& allowed) - { - if (isAllowed(input, allowed)) { - return allowed.at(input); - } - return "unknown"; - } + //! Store allowed unit names and their aliases + std::map> allowed_units; + //! Create the allowed types map (length, mass, time, velocity, G + //! [and aliases]) std::unordered_map createAllowedUnitTypes(); - std::unordered_map createAllowedUnitNames(); + + //! Create the allowed units map + std::map> createAllowedUnitNames(); public: //! Constructor - UnitValidator() - { - allowed_types = createAllowedUnitTypes(); - allowed_units = createAllowedUnitNames(); - } + UnitValidator(); //! Destructor ~UnitValidator() {} - //! Check if type is allowed - bool allowedType(const std::string& type) - { - return isAllowed(type, allowed_types); - } - - //! Check if type is allowed - bool allowedUnit(const std::string& unit) - { - return isAllowed(unit, allowed_units); - } - - std::string canonicalType(const std::string& type) - { - return getCanonicalName(type, allowed_types); - } - - std::string canonicalUnit(const std::string& unit) - { - return getCanonicalName(unit, allowed_units); - } + //! Do the full check and return canonical strings + std::tuple + operator()(const std::string& type, const std::string& unit); }; diff --git a/expui/UnitValidator.cc b/expui/UnitValidator.cc index 5e6a96ec9..fb5410db2 100644 --- a/expui/UnitValidator.cc +++ b/expui/UnitValidator.cc @@ -1,5 +1,40 @@ #include "UnitValidator.H" +// Constructor/initializer +UnitValidator::UnitValidator() +{ + // Initialize the 'dictionaries' + allowed_types = createAllowedUnitTypes(); + allowed_units = createAllowedUnitNames(); +} + +// Do the full check and return canonical strings in two steps. First +// check the type. Then the unit within that type. +std::tuple +UnitValidator::operator()(const std::string& type, const std::string& unit) +{ + // Check type first + if (allowed_types.count(type) > 0) { + + // Get canonical type + std::string canonical_type = allowed_types.at(type); + + // Now check unit in the type category + if (allowed_units[canonical_type].count(unit) > 0) { + + // Get canonical unit name + std::string canonical_unit = allowed_units[canonical_type].at(unit); + + // Return successful final results + return {true, canonical_type, canonical_unit}; + } + } + + // If we get here, we have a type or unit that is not recognized + return {false, "unknown", "unknown"}; +} + + // Map aliases to their canonical (primary) unit types std::unordered_map UnitValidator::createAllowedUnitTypes() @@ -29,98 +64,101 @@ UnitValidator::createAllowedUnitTypes() return allowed; } -// Map aliases to their canonical (primary) unit names -std::unordered_map +// Map aliases to their canonical (primary) unit names for each type +// category +std::map> UnitValidator::createAllowedUnitNames() { - std::unordered_map allowed; + std::map> allowed; // Canonical length units // - allowed["m"] = "m"; - allowed["cm"] = "cm"; - allowed["km"] = "km"; - allowed["um"] = "um"; - allowed["nm"] = "nm"; - allowed["Angstrom"] = "Angstrom"; - allowed["AU"] = "AU"; - allowed["ly"] = "ly"; - allowed["pc"] = "pc"; - allowed["kpc"] = "kpc"; - allowed["Mpc"] = "Mpc"; + allowed["length"]["m"] = "m"; + allowed["length"]["cm"] = "cm"; + allowed["length"]["km"] = "km"; + allowed["length"]["um"] = "um"; + allowed["length"]["nm"] = "nm"; + allowed["length"]["Angstrom"] = "Angstrom"; + allowed["length"]["AU"] = "AU"; + allowed["length"]["ly"] = "ly"; + allowed["length"]["pc"] = "pc"; + allowed["length"]["kpc"] = "kpc"; + allowed["length"]["Mpc"] = "Mpc"; // Astronomical mass units - // - allowed["Msun"] = "Msun"; - allowed["Mearth"] = "Mearth"; - allowed["g"] = "g"; - allowed["kg"] = "kg"; + allowed["mass"]["Msun"] = "Msun"; + allowed["mass"]["Mearth"] = "Mearth"; + allowed["mass"]["g"] = "g"; + allowed["mass"]["kg"] = "kg"; // Time units // - allowed["s"] = "s"; - allowed["min"] = "min"; - allowed["hr"] = "hr"; - allowed["day"] = "day"; - allowed["yr"] = "yr"; - allowed["Myr"] = "Myr"; - allowed["Gyr"] = "Gyr"; + allowed["time"]["s"] = "s"; + allowed["time"]["min"] = "min"; + allowed["time"]["hr"] = "hr"; + allowed["time"]["day"] = "day"; + allowed["time"]["yr"] = "yr"; + allowed["time"]["Myr"] = "Myr"; + allowed["time"]["Gyr"] = "Gyr"; // Velocity units // - allowed["m/s"] = "m/s"; - allowed["km/s"] = "km/s"; - allowed["km/hr"] = "km/hr"; - allowed["km/min"] = "km/min"; - allowed["c"] = "c"; + allowed["velocity"]["m/s"] = "m/s"; + allowed["velocity"]["km/s"] = "km/s"; + allowed["velocity"]["km/hr"] = "km/hr"; + allowed["velocity"]["km/min"] = "km/min"; + allowed["velocity"]["c"] = "c"; // Length aliases // - allowed["meter"] = "m"; - allowed["centimeter"] = "cm"; - allowed["kilometer"] = "km"; - allowed["nanometer"] = "nm"; - allowed["micrometer"] = "um"; - allowed["micron"] = "um"; - allowed["angstrom"] = "Angstrom"; - allowed["AA"] = "Angstrom"; - allowed["astronomical_unit"] = "AU"; - allowed["au"] = "AU"; - allowed["light_year"] = "ly"; - allowed["lyr"] = "ly"; - allowed["parsec"] = "pc"; - allowed["kiloparsec"] = "kpc"; - allowed["megaparsec"] = "Mpc"; + allowed["length"]["meter"] = "m"; + allowed["length"]["centimeter"] = "cm"; + allowed["length"]["kilometer"] = "km"; + allowed["length"]["nanometer"] = "nm"; + allowed["length"]["micrometer"] = "um"; + allowed["length"]["micron"] = "um"; + allowed["length"]["angstrom"] = "Angstrom"; + allowed["length"]["AA"] = "Angstrom"; + allowed["length"]["astronomical_unit"] = "AU"; + allowed["length"]["au"] = "AU"; + allowed["length"]["light_year"] = "ly"; + allowed["length"]["lyr"] = "ly"; + allowed["length"]["parsec"] = "pc"; + allowed["length"]["kiloparsec"] = "kpc"; + allowed["length"]["megaparsec"] = "Mpc"; // Mass aliases // - allowed["solar_mass"] = "Msun"; - allowed["earth_mass"] = "Mearth"; - allowed["gram"] = "g"; - allowed["kilograms"] = "kg"; + allowed["mass"]["solar_mass"] = "Msun"; + allowed["mass"]["earth_mass"] = "Mearth"; + allowed["mass"]["gram"] = "g"; + allowed["mass"]["kilograms"] = "kg"; // Time aliases // - allowed["second"] = "s"; - allowed["minute"] = "min"; - allowed["hour"] = "hr"; - allowed["year"] = "yr"; + allowed["time"]["second"] = "s"; + allowed["time"]["minute"] = "min"; + allowed["time"]["hour"] = "hr"; + allowed["time"]["year"] = "yr"; // Velocity aliases // - allowed["meter_per_second"] = "m/s"; - allowed["m_per_s"] = "m/s"; - allowed["km_per_s"] = "km/s"; - allowed["km_per_hr"] = "km/hr"; - allowed["km_per_min"] = "km/min"; - allowed["speed_of_light"] = "c"; + allowed["velocity"]["meter_per_second"] = "m/s"; + allowed["velocity"]["m_per_s"] = "m/s"; + allowed["velocity"]["km_per_s"] = "km/s"; + allowed["velocity"]["km_per_hr"] = "km/hr"; + allowed["velocity"]["km_per_min"] = "km/min"; + allowed["velocity"]["speed_of_light"] = "c"; // Special non-units // - allowed["mixed"] = "mixed"; - allowed["none"] = "none"; + allowed["G"][""] = "mixed"; + allowed["G"]["_"] = "mixed"; + allowed["G"]["mixed"] = "mixed"; + allowed["G"]["none"] = "mixed"; + allowed["G"]["unitless"] = "mixed"; return allowed; } diff --git a/expui/testunits.cc b/expui/testunits.cc index 4df5025db..e1204c8e6 100644 --- a/expui/testunits.cc +++ b/expui/testunits.cc @@ -11,23 +11,27 @@ int main() std::cout << "Enter type and unit: "; std::cin >> type >> unit; - if (check.allowedType(type)) { - auto canonical = check.canonicalType(type); - std::cout << "'" << type << "' is allowed. The canonical type is '" - << canonical << "'." << std::endl; + bool valid; + std::string canonical_type, canonical_unit; + std::tie(valid, canonical_type, canonical_unit) = check(type, unit); + + if (valid) { + std::cout << "The type '" << type << "' with unit '" << unit + << "' is valid." << std::endl; + std::cout << "The canonical names are: Type='" << canonical_type + << "', Unit='" << canonical_unit << "'" << std::endl; } else { - std::cout << "'" << type << "' is not a recognized string or alias." - << std::endl; + std::cout << "The type '" << type << "' with unit '" << unit + << "' is not valid." << std::endl; } - - if (check.allowedUnit(unit)) { - std::string canonical = check.canonicalUnit(unit); - std::cout << "'" << unit << "' is allowed. The canonical name is '" - << canonical << "'." << std::endl; + + // G test + std::tie(valid, canonical_type, canonical_unit) = check("G", ""); + if (valid) { + std::cout << "The type 'G' with units '' is valid." << std::endl; } else { - std::cout << "'" << unit << "' is not a recognized string or alias." - << std::endl; + std::cout << "The type 'G' with units '' is not valid." << std::endl; } - + return 0; } From 77cc613d3df81f4b0ffa9e889c70b409510d12a4 Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Tue, 21 Oct 2025 19:58:19 -0400 Subject: [PATCH 16/26] Print warning if exactly 4 units including G are not provided --- expui/Coefficients.cc | 13 ++++++++++++- expui/UnitValidator.cc | 15 +++++++++++---- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/expui/Coefficients.cc b/expui/Coefficients.cc index b4d4ab536..a4b20166f 100644 --- a/expui/Coefficients.cc +++ b/expui/Coefficients.cc @@ -126,8 +126,19 @@ namespace CoefClasses void Coefs::WriteH5Units(HighFive::File& file) { + if (units.size() != 4) { + std::cout << "---- Coefs::WriteH5Units: Warning, expected 4 units: " + << "(length, mass, time, G) or (length, mass, velocity, G), etc. " << std::endl + << "---- Coefs::WriteH5Units: I found " << units.size() << " units instead. Please " + << " provide a consistent unit set." << std::endl; + } + HighFive::DataSet dataset = file.createDataSet("Units", units); - std::cout << units; + + if (units.size() == 4) { + std::cout << "Coefs::WriteH5Units: wrote units to HDF5 file:" << std::endl + << units << std::endl; + } } void Coefs::ReadH5Units(HighFive::File& file) diff --git a/expui/UnitValidator.cc b/expui/UnitValidator.cc index fb5410db2..627c1f299 100644 --- a/expui/UnitValidator.cc +++ b/expui/UnitValidator.cc @@ -84,12 +84,14 @@ UnitValidator::createAllowedUnitNames() allowed["length"]["pc"] = "pc"; allowed["length"]["kpc"] = "kpc"; allowed["length"]["Mpc"] = "Mpc"; + allowed["length"]["none"] = "none"; // Astronomical mass units allowed["mass"]["Msun"] = "Msun"; allowed["mass"]["Mearth"] = "Mearth"; allowed["mass"]["g"] = "g"; allowed["mass"]["kg"] = "kg"; + allowed["mass"]["none"] = "none"; // Time units // @@ -100,6 +102,7 @@ UnitValidator::createAllowedUnitNames() allowed["time"]["yr"] = "yr"; allowed["time"]["Myr"] = "Myr"; allowed["time"]["Gyr"] = "Gyr"; + allowed["time"]["none"] = "none"; // Velocity units // @@ -108,6 +111,7 @@ UnitValidator::createAllowedUnitNames() allowed["velocity"]["km/hr"] = "km/hr"; allowed["velocity"]["km/min"] = "km/min"; allowed["velocity"]["c"] = "c"; + allowed["velocity"]["none"] = "none"; // Length aliases @@ -127,6 +131,7 @@ UnitValidator::createAllowedUnitNames() allowed["length"]["parsec"] = "pc"; allowed["length"]["kiloparsec"] = "kpc"; allowed["length"]["megaparsec"] = "Mpc"; + allowed["length"]["None"] = "none"; // Mass aliases @@ -135,6 +140,7 @@ UnitValidator::createAllowedUnitNames() allowed["mass"]["earth_mass"] = "Mearth"; allowed["mass"]["gram"] = "g"; allowed["mass"]["kilograms"] = "kg"; + allowed["mass"]["None"] = "none"; // Time aliases // @@ -142,6 +148,7 @@ UnitValidator::createAllowedUnitNames() allowed["time"]["minute"] = "min"; allowed["time"]["hour"] = "hr"; allowed["time"]["year"] = "yr"; + allowed["time"]["None"] = "none"; // Velocity aliases // @@ -151,14 +158,14 @@ UnitValidator::createAllowedUnitNames() allowed["velocity"]["km_per_hr"] = "km/hr"; allowed["velocity"]["km_per_min"] = "km/min"; allowed["velocity"]["speed_of_light"] = "c"; + allowed["velocity"]["none"] = "none"; // Special non-units // - allowed["G"][""] = "mixed"; - allowed["G"]["_"] = "mixed"; + allowed["G"][""] = "none"; allowed["G"]["mixed"] = "mixed"; - allowed["G"]["none"] = "mixed"; - allowed["G"]["unitless"] = "mixed"; + allowed["G"]["none"] = "none"; + allowed["G"]["unitless"] = "none"; return allowed; } From 27c1763fa1f25ed7d86f55de116b1a01ddaa065f Mon Sep 17 00:00:00 2001 From: Martin Weinberg Date: Tue, 21 Oct 2025 20:06:22 -0400 Subject: [PATCH 17/26] Update expui/UnitValidator.H Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- expui/UnitValidator.H | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/expui/UnitValidator.H b/expui/UnitValidator.H index df80891e4..5a5316f54 100644 --- a/expui/UnitValidator.H +++ b/expui/UnitValidator.H @@ -3,7 +3,7 @@ #include /** Class to validate unit types and names The allowed types and units - can be extended as needed by adding new names and aliasess to + can be extended as needed by adding new names and aliases to UnitValidator::createAllowedUnitTypes() and UnitValidator::createAllowedUnitNames() in UnitValidator.cc */ From a69dcbafc2ff4a2ace2a488c6391fc91cb1cf741 Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Tue, 21 Oct 2025 20:08:23 -0400 Subject: [PATCH 18/26] Make pyEXP interface consistent with C++ code unit db check --- pyEXP/CoefWrappers.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyEXP/CoefWrappers.cc b/pyEXP/CoefWrappers.cc index 76dceb476..991af09e2 100644 --- a/pyEXP/CoefWrappers.cc +++ b/pyEXP/CoefWrappers.cc @@ -1195,10 +1195,10 @@ void CoefficientClasses(py::module &m) { )", py::arg("name"), py::arg("unit")="", py::arg("value")=1.0) .def("WriteH5Coefs", [](CoefClasses::Coefs& self, const std::string& filename) { - if (self.getUnits().size()==1) { + if (self.getUnits().size()!=4) { std::cout << "Coefs::WriteH5Coefs: please set units for your coefficient set using the `setUnit()` member," << std::endl << " one for each unit. We suggest explicitly setting 'G', 'Length', 'Mass'," << std::endl - << " 'Time', and optionally 'Velocity' before writing HDF5 coefficients" << std::endl; + << " 'Time', or optionally 'Velocity' before writing HDF5 coefficients" << std::endl; } self.WriteH5Coefs(filename); }, From 02675c784ec2afae3280ca63b0d107d98ca8c0ae Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Tue, 21 Oct 2025 22:47:20 -0400 Subject: [PATCH 19/26] Changed name of setUnit(tuple) to setUnits(tuple) and added a setUnits(vector of tuples) overload --- expui/Coefficients.H | 7 +++++- expui/Coefficients.cc | 17 +++++++++++--- pyEXP/CoefWrappers.cc | 52 +++++++++++++++++++++++++++---------------- 3 files changed, 53 insertions(+), 23 deletions(-) diff --git a/expui/Coefficients.H b/expui/Coefficients.H index d72b3d984..232f8517d 100644 --- a/expui/Coefficients.H +++ b/expui/Coefficients.H @@ -232,7 +232,12 @@ namespace CoefClasses void setDeltaT(double dT) { deltaT = dT; } //! Set units - void setUnit(std::string name, std::string unit, float value); + void setUnits + (const std::string name, const std::string unit, const float value); + + //! Set units using a vector of tuples (name, unit, value) + void setUnits + (const std::vector>& units); //! Write units to H5 void WriteH5Units(HighFive::File& file); diff --git a/expui/Coefficients.cc b/expui/Coefficients.cc index 50cd5f249..85da5a704 100644 --- a/expui/Coefficients.cc +++ b/expui/Coefficients.cc @@ -71,7 +71,8 @@ namespace CoefClasses p->times = times; } - void Coefs::setUnit(std::string Name, std::string Unit, float Value) + void Coefs::setUnits + (const std::string Name, const std::string Unit, const float Value) { auto copy_s = [](std::string& s, char* c) -> void { const size_t sz = 16; @@ -83,7 +84,7 @@ namespace CoefClasses auto [valid, name, unit] = check(Name, Unit); if (not valid) { - throw std::runtime_error(std::string("Coefs::setUnit: Warning, type '") + throw std::runtime_error(std::string("Coefs::setUnits: Warning, type '") + Name + "' or unit '" + Unit + "' are not recognized."); } @@ -93,7 +94,7 @@ namespace CoefClasses std::string p_name(p.name); if (name == p_name) { - copy_s(Unit, &p.unit[0]); + copy_s(unit, &p.unit[0]); p.value = Value; return; } @@ -104,6 +105,16 @@ namespace CoefClasses units.push_back({name, unit, Value}); } + // Set units using a vector of tuples (name, unit, value) + // Converts to a list of tuples in Python + void Coefs::setUnits + (const std::vector>& units) + { + for (auto p : units) { + setUnits(std::get<0>(p), std::get<1>(p), std::get<2>(p)); + } + } + //! Get units std::vector> Coefs::getUnits() { diff --git a/pyEXP/CoefWrappers.cc b/pyEXP/CoefWrappers.cc index 991af09e2..ca049a8a5 100644 --- a/pyEXP/CoefWrappers.cc +++ b/pyEXP/CoefWrappers.cc @@ -1165,8 +1165,8 @@ void CoefficientClasses(py::module &m) { You will get a runtime error if the entry does not exist. )",py::arg("time")) .def("Times", - &CoefClasses::Coefs::Times, - R"( + &CoefClasses::Coefs::Times, + R"( Return a list of times for coefficient sets currently in the container Returns @@ -1174,25 +1174,39 @@ void CoefficientClasses(py::module &m) { list(float,...) list of times )") - .def("setUnit", - &CoefClasses::Coefs::setUnit, - R"( - Set the units for the coefficient structure. + .def("setUnits", + py::overload_cast(&CoefClasses::Coefs::setUnits), + R"( + Set the units for the coefficient structure. - Parameters - ---------- - name : str - the name of physical quantity (G, Length, Mass, Time, etc) - unit : str - the unit string (scalar, mixed, kpc, Msun, Myr, km/s etc.). - This field is optional and can be empty. - value : float - the default value of the multiples of the unit + Parameters + ---------- + name : str + the name of physical quantity (G, Length, Mass, Time, etc) + unit : str + the unit string (scalar, mixed, kpc, Msun, Myr, km/s etc.). + This field is optional and can be empty. + value : float + the default value of the multiples of the unit - Returns - ------- - None - )", py::arg("name"), py::arg("unit")="", py::arg("value")=1.0) + Returns + ------- + None + )", py::arg("name"), py::arg("unit")="", py::arg("value")=1.0) + .def("setUnits", + py::overload_cast>&>(&CoefClasses::Coefs::setUnits), + R"( + Set the units for the coefficient structure. + + Parameters + ---------- + list((str,str,float)) + list of (name, unit, value) tuples, where each tuple contains the coefficient name (str), its unit (str), and its value (float). + + Returns + ------- + None + )", py::arg("units")) .def("WriteH5Coefs", [](CoefClasses::Coefs& self, const std::string& filename) { if (self.getUnits().size()!=4) { From 74e9d887936707a7bf0c18910885d14eedb2a292 Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Wed, 22 Oct 2025 11:13:00 -0400 Subject: [PATCH 20/26] Added a removeUnits() member function for completeness --- expui/Coefficients.H | 3 +++ expui/Coefficients.cc | 13 +++++++++++++ expui/UnitValidator.cc | 2 ++ pyEXP/CoefWrappers.cc | 39 +++++++++++++++++++++++++++++++++------ 4 files changed, 51 insertions(+), 6 deletions(-) diff --git a/expui/Coefficients.H b/expui/Coefficients.H index 232f8517d..4d09a56f1 100644 --- a/expui/Coefficients.H +++ b/expui/Coefficients.H @@ -239,6 +239,9 @@ namespace CoefClasses void setUnits (const std::vector>& units); + //! Remove a unit by name + void removeUnits(const std::string name); + //! Write units to H5 void WriteH5Units(HighFive::File& file); diff --git a/expui/Coefficients.cc b/expui/Coefficients.cc index 85da5a704..c261dc2b6 100644 --- a/expui/Coefficients.cc +++ b/expui/Coefficients.cc @@ -71,6 +71,19 @@ namespace CoefClasses p->times = times; } + void Coefs::removeUnits(const std::string name) + { + // Lambda test for matching name + auto test = [&name](Unit& elem) -> bool{return elem.name==name; }; + + // Explanation: std::remove_if shifts elements to be removed to + // the end of the range and returns an iterator to the new logical + // end. units.erase() then removes the elements from that point to + // the end. + units.erase(std::remove_if(units.begin(), units.end(), test), units.end()); + } + + void Coefs::setUnits (const std::string Name, const std::string Unit, const float Value) { diff --git a/expui/UnitValidator.cc b/expui/UnitValidator.cc index 627c1f299..dba276fd3 100644 --- a/expui/UnitValidator.cc +++ b/expui/UnitValidator.cc @@ -54,6 +54,8 @@ UnitValidator::createAllowedUnitTypes() allowed["L"] = "length"; allowed["m"] = "mass"; allowed["M"] = "mass"; + allowed["t"] = "time"; + allowed["T"] = "time"; allowed["vel"] = "velocity"; allowed["Vel"] = "velocity"; allowed["v"] = "velocity"; diff --git a/pyEXP/CoefWrappers.cc b/pyEXP/CoefWrappers.cc index ca049a8a5..554c75a26 100644 --- a/pyEXP/CoefWrappers.cc +++ b/pyEXP/CoefWrappers.cc @@ -202,6 +202,19 @@ void CoefficientClasses(py::module &m) { void zerodata() override { PYBIND11_OVERRIDE_PURE(void, Coefs, zerodata,); } + + void setUnits(const std::string name, const std::string unit, const float value) { + PYBIND11_OVERRIDE(void, Coefs, setUnits, name, unit, value); + } + + void setUnits(const std::vector>& units) { + PYBIND11_OVERRIDE(void, Coefs, setUnits, units); + } + + void removeUnits(const std::string name) { + PYBIND11_OVERRIDE(void, Coefs, removeUnits, name); + } + }; class PySphCoefs : public SphCoefs @@ -1167,13 +1180,27 @@ void CoefficientClasses(py::module &m) { .def("Times", &CoefClasses::Coefs::Times, R"( - Return a list of times for coefficient sets currently in the container + Return a list of times for coefficient sets currently in the container - Returns - ------- - list(float,...) - list of times - )") + Returns + ------- + list(float,...) + list of times + )") + .def("removeUnits", + &CoefClasses::Coefs::removeUnits, + R"( + Remove a unit from the coefficient structure. + + Parameters + ---------- + name : str + the name of physical quantity (G, Length, Mass, Time, etc) + + Returns + ------- + None + )") .def("setUnits", py::overload_cast(&CoefClasses::Coefs::setUnits), R"( From 21a6ea31274eb2b770d0043c6e4794dea6455871 Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Wed, 22 Oct 2025 17:01:46 -0400 Subject: [PATCH 21/26] Added more aliases --- expui/Coefficients.cc | 3 ++- expui/UnitValidator.cc | 33 +++++++++++++++++++++------------ 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/expui/Coefficients.cc b/expui/Coefficients.cc index c261dc2b6..4592c9fd8 100644 --- a/expui/Coefficients.cc +++ b/expui/Coefficients.cc @@ -98,7 +98,8 @@ namespace CoefClasses if (not valid) { throw std::runtime_error(std::string("Coefs::setUnits: Warning, type '") - + Name + "' or unit '" + Unit + "' are not recognized."); + + Name + "' with unit '" + Unit + + "' is incompatible or not recognized."); } // Check for existing unit and update diff --git a/expui/UnitValidator.cc b/expui/UnitValidator.cc index dba276fd3..09272b6db 100644 --- a/expui/UnitValidator.cc +++ b/expui/UnitValidator.cc @@ -49,19 +49,28 @@ UnitValidator::createAllowedUnitTypes() allowed["G"] = "G"; // Aliases - allowed["len"] = "length"; - allowed["l"] = "length"; - allowed["L"] = "length"; - allowed["m"] = "mass"; - allowed["M"] = "mass"; - allowed["t"] = "time"; - allowed["T"] = "time"; - allowed["vel"] = "velocity"; - allowed["Vel"] = "velocity"; - allowed["v"] = "velocity"; - allowed["V"] = "velocity"; - allowed["grav"] = "G"; + allowed["Length"] = "length"; + allowed["Len"] = "length"; + allowed["len"] = "length"; + allowed["l"] = "length"; + allowed["L"] = "length"; + allowed["Mass"] = "mass"; + allowed["m"] = "mass"; + allowed["M"] = "mass"; + allowed["Time"] = "time"; + allowed["t"] = "time"; + allowed["T"] = "time"; + allowed["vel"] = "velocity"; + allowed["Vel"] = "velocity"; + allowed["Velocity"] = "velocity"; + allowed["v"] = "velocity"; + allowed["V"] = "velocity"; + allowed["Grav"] = "G"; + allowed["grav"] = "G"; + allowed["grav_constant"] = "G"; + allowed["Grav_constant"] = "G"; allowed["gravitational_constant"] = "G"; + allowed["Gravitational_constant"] = "G"; return allowed; } From 3c5c94a8a2d2f15d45c667afcd37469d53404d81 Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Thu, 23 Oct 2025 22:59:34 -0400 Subject: [PATCH 22/26] Added more comments/documentation only --- expui/UnitValidator.H | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/expui/UnitValidator.H b/expui/UnitValidator.H index 5a5316f54..88c9edab4 100644 --- a/expui/UnitValidator.H +++ b/expui/UnitValidator.H @@ -6,6 +6,29 @@ can be extended as needed by adding new names and aliases to UnitValidator::createAllowedUnitTypes() and UnitValidator::createAllowedUnitNames() in UnitValidator.cc + + Strategy: + ------- + - Types and their aliases are stored in an std::unordered_map + - Units and their aliases are stored in an std::map of + std::unordered_maps + - The operator()(type, unit, value) first checks if the provided + type exists in the allowed types map. If it does, it retrieves + the canonical type string which is used to look up the allowed + units for that type in the std::map. Then, the unit type is + checked against the allowed units for that type. If both type + and unit are valid, the canonical strings are returned. + + Usage: + ----- + - Create an instance of UnitValidator. Currently, this is done in + the Coefficients class by default. + - Call the operator() on an instance with the type and unit + strings to be validated + - The operator() returns a tuple with: + + a boolean indicating if the type and unit are valid + + the canonical type string, or "unknown" if invalid + + the canonical unit string, or "unknown" if invalid */ class UnitValidator { From 00e3f60fa0569d92fa82d4a7336bf531582583ed Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Fri, 24 Oct 2025 12:23:40 -0400 Subject: [PATCH 23/26] Some minor cleaning up --- expui/UnitValidator.cc | 119 +++++++++++++++++++++++------------------ 1 file changed, 67 insertions(+), 52 deletions(-) diff --git a/expui/UnitValidator.cc b/expui/UnitValidator.cc index 09272b6db..a7632848f 100644 --- a/expui/UnitValidator.cc +++ b/expui/UnitValidator.cc @@ -9,7 +9,8 @@ UnitValidator::UnitValidator() } // Do the full check and return canonical strings in two steps. First -// check the type. Then the unit within that type. +// check the type. Then the unit within that type. Returns a tuple of +// (is_valid, canonical_type, canonical_unit). std::tuple UnitValidator::operator()(const std::string& type, const std::string& unit) { @@ -41,38 +42,44 @@ UnitValidator::createAllowedUnitTypes() { std::unordered_map allowed; - // Canonical names - allowed["length"] = "length"; - allowed["mass"] = "mass"; - allowed["time"] = "time"; - allowed["velocity"] = "velocity"; - allowed["G"] = "G"; - - // Aliases - allowed["Length"] = "length"; - allowed["Len"] = "length"; - allowed["len"] = "length"; - allowed["l"] = "length"; - allowed["L"] = "length"; - allowed["Mass"] = "mass"; - allowed["m"] = "mass"; - allowed["M"] = "mass"; - allowed["Time"] = "time"; - allowed["t"] = "time"; - allowed["T"] = "time"; - allowed["vel"] = "velocity"; - allowed["Vel"] = "velocity"; - allowed["Velocity"] = "velocity"; - allowed["v"] = "velocity"; - allowed["V"] = "velocity"; - allowed["Grav"] = "G"; - allowed["grav"] = "G"; - allowed["grav_constant"] = "G"; - allowed["Grav_constant"] = "G"; - allowed["gravitational_constant"] = "G"; - allowed["Gravitational_constant"] = "G"; - - return allowed; + // These are the canonical names + // + allowed["length"] = "length"; + allowed["mass"] = "mass"; + allowed["time"] = "time"; + allowed["velocity"] = "velocity"; + allowed["G"] = "G"; + + // These are recognized aliases aliases for the types + // + allowed["Length"] = "length"; + allowed["Len"] = "length"; + allowed["len"] = "length"; + allowed["l"] = "length"; + allowed["L"] = "length"; + + allowed["Mass"] = "mass"; + allowed["m"] = "mass"; + allowed["M"] = "mass"; + + allowed["Time"] = "time"; + allowed["t"] = "time"; + allowed["T"] = "time"; + + allowed["vel"] = "velocity"; + allowed["Vel"] = "velocity"; + allowed["Velocity"] = "velocity"; + allowed["v"] = "velocity"; + allowed["V"] = "velocity"; + + allowed["Grav"] = "G"; + allowed["grav"] = "G"; + allowed["grav_constant"] = "G"; + allowed["Grav_constant"] = "G"; + allowed["gravitational_constant"] = "G"; + allowed["Gravitational_constant"] = "G"; + + return allowed; } // Map aliases to their canonical (primary) unit names for each type @@ -82,6 +89,22 @@ UnitValidator::createAllowedUnitNames() { std::map> allowed; + // Allow 'none' as a unit for all types + // + allowed["length"]["none"] = "none"; + allowed["mass"]["none"] = "none"; + allowed["time"]["none"] = "none"; + allowed["length"]["None"] = "none"; + allowed["mass"]["None"] = "none"; + allowed["velocity"]["none"] = "none"; + + // Special non-units + // + allowed["G"][""] = "none"; + allowed["G"]["mixed"] = "mixed"; + allowed["G"]["none"] = "none"; + allowed["G"]["unitless"] = "none"; + // Canonical length units // allowed["length"]["m"] = "m"; @@ -95,14 +118,13 @@ UnitValidator::createAllowedUnitNames() allowed["length"]["pc"] = "pc"; allowed["length"]["kpc"] = "kpc"; allowed["length"]["Mpc"] = "Mpc"; - allowed["length"]["none"] = "none"; - // Astronomical mass units + // Standard astronomical mass units + // allowed["mass"]["Msun"] = "Msun"; allowed["mass"]["Mearth"] = "Mearth"; allowed["mass"]["g"] = "g"; allowed["mass"]["kg"] = "kg"; - allowed["mass"]["none"] = "none"; // Time units // @@ -113,10 +135,10 @@ UnitValidator::createAllowedUnitNames() allowed["time"]["yr"] = "yr"; allowed["time"]["Myr"] = "Myr"; allowed["time"]["Gyr"] = "Gyr"; - allowed["time"]["none"] = "none"; // Velocity units // + allowed["velocity"]["cm/s"] = "cmm/s"; allowed["velocity"]["m/s"] = "m/s"; allowed["velocity"]["km/s"] = "km/s"; allowed["velocity"]["km/hr"] = "km/hr"; @@ -142,7 +164,6 @@ UnitValidator::createAllowedUnitNames() allowed["length"]["parsec"] = "pc"; allowed["length"]["kiloparsec"] = "kpc"; allowed["length"]["megaparsec"] = "Mpc"; - allowed["length"]["None"] = "none"; // Mass aliases @@ -151,7 +172,6 @@ UnitValidator::createAllowedUnitNames() allowed["mass"]["earth_mass"] = "Mearth"; allowed["mass"]["gram"] = "g"; allowed["mass"]["kilograms"] = "kg"; - allowed["mass"]["None"] = "none"; // Time aliases // @@ -163,20 +183,15 @@ UnitValidator::createAllowedUnitNames() // Velocity aliases // - allowed["velocity"]["meter_per_second"] = "m/s"; - allowed["velocity"]["m_per_s"] = "m/s"; - allowed["velocity"]["km_per_s"] = "km/s"; - allowed["velocity"]["km_per_hr"] = "km/hr"; - allowed["velocity"]["km_per_min"] = "km/min"; - allowed["velocity"]["speed_of_light"] = "c"; - allowed["velocity"]["none"] = "none"; + allowed["velocity"]["meter_per_second"] = "m/s"; + allowed["velocity"]["centimeter_per_second"] = "cm/s"; + allowed["velocity"]["cm_per_s"] = "cm/s"; + allowed["velocity"]["m_per_s"] = "m/s"; + allowed["velocity"]["km_per_s"] = "km/s"; + allowed["velocity"]["km_per_hr"] = "km/hr"; + allowed["velocity"]["km_per_min"] = "km/min"; + allowed["velocity"]["speed_of_light"] = "c"; - // Special non-units - // - allowed["G"][""] = "none"; - allowed["G"]["mixed"] = "mixed"; - allowed["G"]["none"] = "none"; - allowed["G"]["unitless"] = "none"; return allowed; } From 8d4ca2bbd8820ebda802c2972bf3b63a46804ff5 Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Wed, 29 Oct 2025 13:27:20 -0400 Subject: [PATCH 24/26] Do not build ICs if utils build is disabled --- tests/CMakeLists.txt | 196 ++++++++++++++++++++++--------------------- 1 file changed, 99 insertions(+), 97 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 560f9e8b5..30dedb2d4 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -56,105 +56,107 @@ if(ENABLE_NBODY) add_test(NAME expExecuteTest COMMAND ${EXP_MPI_LAUNCH} ${CMAKE_BINARY_DIR}/src/exp -v) - # Makes some spherical ICs using utils/ICs/gensph - add_test(NAME makeICTest - COMMAND ${EXP_MPI_LAUNCH} ${CMAKE_BINARY_DIR}/utils/ICs/gensph -N 10000 -i SLGridSph.model - WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/Halo) - - # Runs those ICs using exp - add_test(NAME expNbodyTest - COMMAND ${EXP_MPI_LAUNCH} ${CMAKE_BINARY_DIR}/src/exp config.yml - WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/Halo) - - set_tests_properties(expNbodyTest PROPERTIES DEPENDS makeICTest) - - # Check OUTLOG file for a sane 2T/W mean and stdv - add_test(NAME expNbodyCheck2TW - COMMAND ${CMAKE_COMMAND} -E env - PYTHONPATH=${CMAKE_BINARY_DIR}/pyEXP:$ENV{PYTHONPATH} - ${PYTHON_EXECUTABLE} check.py - WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/Halo) - - set_tests_properties(expNbodyCheck2TW PROPERTIES DEPENDS expNbodyTest LABELS "long") - - # This adds a coefficient read test using pyEXP only if - # expNbodyTest is run and pyEXP has been built - if(ENABLE_PYEXP) - # Read coefficient file with pyEXP - add_test(NAME pyEXPCoefReadTest - COMMAND ${CMAKE_COMMAND} -E env - PYTHONPATH=${CMAKE_BINARY_DIR}/pyEXP:$ENV{PYTHONPATH} - ${PYTHON_EXECUTABLE} readCoefs.py - WORKING_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/Halo") - set_tests_properties(pyEXPCoefReadTest PROPERTIES DEPENDS expNbodyTest LABELS "long") - - add_test(NAME pyEXPCoefMatrixTest - COMMAND ${CMAKE_COMMAND} -E env - PYTHONPATH=${CMAKE_BINARY_DIR}/pyEXP:$ENV{PYTHONPATH} - ${PYTHON_EXECUTABLE} changeCoefs.py - WORKING_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/Halo") - set_tests_properties(pyEXPCoefMatrixTest PROPERTIES DEPENDS expNbodyTest LABELS "long") - - add_test(NAME pyEXPCoefCreateTest - COMMAND ${CMAKE_COMMAND} -E env - PYTHONPATH=${CMAKE_BINARY_DIR}/pyEXP:$ENV{PYTHONPATH} - ${PYTHON_EXECUTABLE} createCoefs.py - WORKING_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/Halo") - set_tests_properties(pyEXPCoefCreateTest PROPERTIES LABELS "quick") + if (ENABLE_UTILS) + # Makes some spherical ICs using utils/ICs/gensph + add_test(NAME makeICTest + COMMAND ${EXP_MPI_LAUNCH} ${CMAKE_BINARY_DIR}/utils/ICs/gensph -N 10000 -i SLGridSph.model + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/Halo) + + # Runs those ICs using exp + add_test(NAME expNbodyTest + COMMAND ${EXP_MPI_LAUNCH} ${CMAKE_BINARY_DIR}/src/exp config.yml + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/Halo) + + set_tests_properties(expNbodyTest PROPERTIES DEPENDS makeICTest) + + # Check OUTLOG file for a sane 2T/W mean and stdv + add_test(NAME expNbodyCheck2TW + COMMAND ${CMAKE_COMMAND} -E env + PYTHONPATH=${CMAKE_BINARY_DIR}/pyEXP:$ENV{PYTHONPATH} + ${PYTHON_EXECUTABLE} check.py + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/Halo) + + set_tests_properties(expNbodyCheck2TW PROPERTIES DEPENDS expNbodyTest LABELS "long") + + # This adds a coefficient read test using pyEXP only if + # expNbodyTest is run and pyEXP has been built + if(ENABLE_PYEXP) + # Read coefficient file with pyEXP + add_test(NAME pyEXPCoefReadTest + COMMAND ${CMAKE_COMMAND} -E env + PYTHONPATH=${CMAKE_BINARY_DIR}/pyEXP:$ENV{PYTHONPATH} + ${PYTHON_EXECUTABLE} readCoefs.py + WORKING_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/Halo") + set_tests_properties(pyEXPCoefReadTest PROPERTIES DEPENDS expNbodyTest LABELS "long") + + add_test(NAME pyEXPCoefMatrixTest + COMMAND ${CMAKE_COMMAND} -E env + PYTHONPATH=${CMAKE_BINARY_DIR}/pyEXP:$ENV{PYTHONPATH} + ${PYTHON_EXECUTABLE} changeCoefs.py + WORKING_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/Halo") + set_tests_properties(pyEXPCoefMatrixTest PROPERTIES DEPENDS expNbodyTest LABELS "long") + + add_test(NAME pyEXPCoefCreateTest + COMMAND ${CMAKE_COMMAND} -E env + PYTHONPATH=${CMAKE_BINARY_DIR}/pyEXP:$ENV{PYTHONPATH} + ${PYTHON_EXECUTABLE} createCoefs.py + WORKING_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/Halo") + set_tests_properties(pyEXPCoefCreateTest PROPERTIES LABELS "quick") + endif() + + # A separate test to remove the generated files if they all exist; + # perhaps there is a better way? + add_test(NAME removeTempFiles + COMMAND ${CMAKE_COMMAND} -E remove + config.run0.yml current.processor.rates.run0 new.bods + OUTLOG.run0 run0.levels SLGridSph.cache.run0 test.grid + outcoef.halo.run0 SLGridSph.cache.run0 + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/Halo) + + # Remove the temporary files + set_tests_properties(removeTempFiles PROPERTIES DEPENDS expNbodyCheck2TW + REQUIRED_FILES "config.run0.yml;current.processor.rates.run0;new.bods;run0.levels;SLGridSph.cache.run0;test.grid;" + ) + + # Makes some cube ICs using utils/ICs/cubeics + add_test(NAME makeCubeICTest + COMMAND ${EXP_MPI_LAUNCH} ${CMAKE_BINARY_DIR}/utils/ICs/cubeics -N 4000 -z -d 2,2,2 + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/Cube) + + # Runs those ICs using exp + add_test(NAME expCubeTest + COMMAND ${EXP_MPI_LAUNCH} ${CMAKE_BINARY_DIR}/src/exp + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/Cube) + + set_tests_properties(expCubeTest PROPERTIES DEPENDS makeCubeICTest) + + # Check OUTLOG file for mean position + add_test(NAME expCubeCheckPos + COMMAND ${CMAKE_COMMAND} -E env + PYTHONPATH=${CMAKE_BINARY_DIR}/pyEXP:$ENV{PYTHONPATH} + ${PYTHON_EXECUTABLE} check.py + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/Cube) + + set_tests_properties(expCubeCheckPos PROPERTIES DEPENDS expCubeTest) + + # A separate test to remove the generated files if they all exist + add_test(NAME removeCubeFiles + COMMAND ${CMAKE_COMMAND} -E remove + config.runS.yml current.processor.rates.runS cube.bods + OUTLOG.runS runS.levels + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/Cube) + + # Remove the temporary files + set_tests_properties(removeCubeFiles PROPERTIES DEPENDS expCubeCheckPos + REQUIRED_FILES "config.runS.yml;current.processor.rates.runS;cube.bods;OUTLOG.runS;runS.levels;") + + # Set labels for pyEXP tests + set_tests_properties(expExecuteTest PROPERTIES LABELS "quick") + set_tests_properties(makeICTest expNbodyTest expNbodyCheck2TW + removeTempFiles makeCubeICTest expCubeTest removeCubeFiles + PROPERTIES LABELS "long") endif() - # A separate test to remove the generated files if they all exist; - # perhaps there is a better way? - add_test(NAME removeTempFiles - COMMAND ${CMAKE_COMMAND} -E remove - config.run0.yml current.processor.rates.run0 new.bods - OUTLOG.run0 run0.levels SLGridSph.cache.run0 test.grid - outcoef.halo.run0 SLGridSph.cache.run0 - WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/Halo) - - # Remove the temporary files - set_tests_properties(removeTempFiles PROPERTIES DEPENDS expNbodyCheck2TW - REQUIRED_FILES "config.run0.yml;current.processor.rates.run0;new.bods;run0.levels;SLGridSph.cache.run0;test.grid;" - ) - - # Makes some cube ICs using utils/ICs/cubeics - add_test(NAME makeCubeICTest - COMMAND ${EXP_MPI_LAUNCH} ${CMAKE_BINARY_DIR}/utils/ICs/cubeics -N 4000 -z -d 2,2,2 - WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/Cube) - - # Runs those ICs using exp - add_test(NAME expCubeTest - COMMAND ${EXP_MPI_LAUNCH} ${CMAKE_BINARY_DIR}/src/exp - WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/Cube) - - set_tests_properties(expCubeTest PROPERTIES DEPENDS makeCubeICTest) - - # Check OUTLOG file for mean position - add_test(NAME expCubeCheckPos - COMMAND ${CMAKE_COMMAND} -E env - PYTHONPATH=${CMAKE_BINARY_DIR}/pyEXP:$ENV{PYTHONPATH} - ${PYTHON_EXECUTABLE} check.py - WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/Cube) - - set_tests_properties(expCubeCheckPos PROPERTIES DEPENDS expCubeTest) - - # A separate test to remove the generated files if they all exist - add_test(NAME removeCubeFiles - COMMAND ${CMAKE_COMMAND} -E remove - config.runS.yml current.processor.rates.runS cube.bods - OUTLOG.runS runS.levels - WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/Cube) - - # Remove the temporary files - set_tests_properties(removeCubeFiles PROPERTIES DEPENDS expCubeCheckPos - REQUIRED_FILES "config.runS.yml;current.processor.rates.runS;cube.bods;OUTLOG.runS;runS.levels;") - - # Set labels for pyEXP tests - set_tests_properties(expExecuteTest PROPERTIES LABELS "quick") - set_tests_properties(makeICTest expNbodyTest expNbodyCheck2TW - removeTempFiles makeCubeICTest expCubeTest removeCubeFiles - PROPERTIES LABELS "long") - endif() # Tests that should work for any configuration. Nothing so far. This From 29245f2aa5d50630aef03d65e9750f923d8c7ad8 Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Wed, 29 Oct 2025 14:06:20 -0400 Subject: [PATCH 25/26] Added default EXP n-body units with L=M=T=G; Coefs throws an exception if units are not fully specified --- expui/Coefficients.cc | 16 ++++++++++------ src/Cube.cc | 5 +++++ src/Cylinder.cc | 5 +++++ src/PolarBasis.cc | 5 +++++ src/ShearSL.cc | 5 +++++ src/SlabSL.cc | 5 +++++ src/SphericalBasis.cc | 5 +++++ 7 files changed, 40 insertions(+), 6 deletions(-) diff --git a/expui/Coefficients.cc b/expui/Coefficients.cc index 4592c9fd8..804225018 100644 --- a/expui/Coefficients.cc +++ b/expui/Coefficients.cc @@ -152,10 +152,12 @@ namespace CoefClasses void Coefs::WriteH5Units(HighFive::File& file) { if (units.size() != 4) { - std::cout << "---- Coefs::WriteH5Units: Warning, expected 4 units: " - << "(length, mass, time, G) or (length, mass, velocity, G), etc. " << std::endl - << "---- Coefs::WriteH5Units: I found " << units.size() << " units instead. Please " - << " provide a consistent unit set." << std::endl; + std::ostringstream sout; + sout << "---- Coefs::WriteH5Units: Warning, expected 4 units: " + << "(length, mass, time, G) or (length, mass, velocity, G), etc. " + << "I found " << units.size() << " units instead. Please " + << " provide a consistent unit set."; + throw std::runtime_error(sout.str()); } HighFive::DataSet dataset = file.createDataSet("Units", units); @@ -171,8 +173,10 @@ namespace CoefClasses if (file.exist("Units")) { HighFive::DataSet dataset = file.getDataSet("Units"); units = dataset.read>(); - std::cout << "Coefs::ReadH5Units: read units from HDF5 file:" << std::endl; - std::cout << units; + if (verbose and myid==0) { + std::cout << "Coefs::ReadH5Units: read units from HDF5 file:" << std::endl; + std::cout << units; + } } } diff --git a/src/Cube.cc b/src/Cube.cc index ab5d0f17c..e4c3665aa 100644 --- a/src/Cube.cc +++ b/src/Cube.cc @@ -520,6 +520,11 @@ void Cube::dump_coefs_h5(const std::string& file) // Add the name attribute. We only need this on the first call. cubeCoefs.setName(component->name); + // Add the default units + cubeCoefs.setUnits({{"length", "none", 1.0}, + {"mass", "none", 1.0}, + {"time", "none", 1.0}}); + // And the new coefficients and write the new HDF5 cubeCoefs.clear(); cubeCoefs.add(cur); diff --git a/src/Cylinder.cc b/src/Cylinder.cc index 229116c06..45c72564f 100644 --- a/src/Cylinder.cc +++ b/src/Cylinder.cc @@ -1587,6 +1587,11 @@ void Cylinder::dump_coefs_h5(const std::string& file) // Add the name attribute. We only need this on the first call. cylCoefs.setName(component->name); + // Add the default units + cylCoefs.setUnits({{"length", "none", 1.0}, + {"mass", "none", 1.0}, + {"time", "none", 1.0}}); + // Add the new coefficients and write the new HDF5 cylCoefs.clear(); cylCoefs.add(cur); diff --git a/src/PolarBasis.cc b/src/PolarBasis.cc index e87b792fc..5269b8319 100644 --- a/src/PolarBasis.cc +++ b/src/PolarBasis.cc @@ -1842,6 +1842,11 @@ void PolarBasis::dump_coefs_h5(const std::string& file) // Add the name attribute. We only need this on the first call. cylCoefs.setName(component->name); + // Add the default units + cylCoefs.setUnits({{"length", "none", 1.0}, + {"mass", "none", 1.0}, + {"time", "none", 1.0}}); + // And the new coefficients and write the new HDF5 cylCoefs.clear(); cylCoefs.add(cur); diff --git a/src/ShearSL.cc b/src/ShearSL.cc index 59a1be64a..9bcb13a5a 100644 --- a/src/ShearSL.cc +++ b/src/ShearSL.cc @@ -374,6 +374,11 @@ void ShearSL::dump_coefs_h5(const std::string& file) // Add the name attribute. We only need this on the first call. slabCoefs.setName(component->name); + // Add the default units + slabCoefs.setUnits({{"length", "none", 1.0}, + {"mass", "none", 1.0}, + {"time", "none", 1.0}}); + // And the new coefficients and write the new HDF5 slabCoefs.clear(); slabCoefs.add(cur); diff --git a/src/SlabSL.cc b/src/SlabSL.cc index 6a69cdb37..3334daebc 100644 --- a/src/SlabSL.cc +++ b/src/SlabSL.cc @@ -513,6 +513,11 @@ void SlabSL::dump_coefs_h5(const std::string& file) // Add the name attribute. We only need this on the first call. slabCoefs.setName(component->name); + // Add the default units + slabCoefs.setUnits({{"length", "none", 1.0}, + {"mass", "none", 1.0}, + {"time", "none", 1.0}}); + // And the new coefficients and write the new HDF5 slabCoefs.clear(); slabCoefs.add(cur); diff --git a/src/SphericalBasis.cc b/src/SphericalBasis.cc index 74ff8feec..aaa8f38cd 100644 --- a/src/SphericalBasis.cc +++ b/src/SphericalBasis.cc @@ -1926,6 +1926,11 @@ void SphericalBasis::dump_coefs_h5(const std::string& file) // Add the name attribute. We only need this on the first call. sphCoefs.setName(component->name); + // Add the default units + sphCoefs.setUnits({{"length", "none", 1.0}, + {"mass", "none", 1.0}, + {"time", "none", 1.0}}); + // And the new coefficients and write the new HDF5 sphCoefs.clear(); sphCoefs.add(cur); From ad283de47f494c9120f8254e0854104ccc781a9c Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Fri, 7 Nov 2025 10:45:02 -0500 Subject: [PATCH 26/26] Added type reflection for units --- expui/Coefficients.H | 22 +++ expui/UnitValidator.H | 11 ++ expui/UnitValidator.cc | 37 +++++ pyEXP/CoefWrappers.cc | 370 +++++++++++++++++++++++------------------ 4 files changed, 277 insertions(+), 163 deletions(-) diff --git a/expui/Coefficients.H b/expui/Coefficients.H index 4d09a56f1..d57248c34 100644 --- a/expui/Coefficients.H +++ b/expui/Coefficients.H @@ -251,6 +251,28 @@ namespace CoefClasses //! Get units std::vector> getUnits(); + //! Get allowed unit types + const std::vector getAllowedUnitTypes() const + { + return check.getAllowedTypes(); + } + + //! Get allowed aliases for each type + const std::vector + getAllowedTypeAliases(const std::string& type) const + { + return check.getAllowedTypeAliases(type); + } + + //! Get allowed units for a given type + const std::vector + getAllowedUnitNames(const std::string& type) const + { + auto ret = check.getAllowedUnits(type); + std::sort(ret.begin(), ret.end()); + return ret; + } + //! Get gravitational constant double getGravConstant(); diff --git a/expui/UnitValidator.H b/expui/UnitValidator.H index 88c9edab4..18603edf9 100644 --- a/expui/UnitValidator.H +++ b/expui/UnitValidator.H @@ -1,5 +1,7 @@ #include +#include #include +#include #include /** Class to validate unit types and names The allowed types and units @@ -59,4 +61,13 @@ public: std::tuple operator()(const std::string& type, const std::string& unit); + //! Get allowed unit types + const std::vector getAllowedTypes() const; + + //! Get allowed aliases for each type + const std::vector getAllowedTypeAliases(const std::string& type) const; + + //! Get allowed units for a given type and their aliases + const std::vector getAllowedUnits(const std::string& type) const; + }; diff --git a/expui/UnitValidator.cc b/expui/UnitValidator.cc index a7632848f..d3321494f 100644 --- a/expui/UnitValidator.cc +++ b/expui/UnitValidator.cc @@ -1,3 +1,4 @@ +#include #include "UnitValidator.H" // Constructor/initializer @@ -196,3 +197,39 @@ UnitValidator::createAllowedUnitNames() return allowed; } +// Get allowed aliases for each type +const std::vector +UnitValidator::getAllowedTypeAliases(const std::string& type) const +{ + // Build the alias vector + std::vector aliases; + + for (const auto& pair : allowed_types) { + if (pair.second == type) { + aliases.push_back(pair.first); + } + } + + // Sort the vector + std::sort(aliases.begin(), aliases.end()); + + return aliases; +} + +// Canonical names for allowed unit types +const std::vector UnitValidator::getAllowedTypes() const +{ + return {"mass", "length", "time", "velocity", "G"}; +} + +// Get allowed units for a given type (and their aliases) +const std::vector UnitValidator::getAllowedUnits(const std::string& type) const +{ + std::vector units; + if (allowed_units.count(type) > 0) { + for (const auto& pair : allowed_units.at(type)) { + units.push_back(pair.first); + } + } + return units; +} diff --git a/pyEXP/CoefWrappers.cc b/pyEXP/CoefWrappers.cc index 554c75a26..97f1eedeb 100644 --- a/pyEXP/CoefWrappers.cc +++ b/pyEXP/CoefWrappers.cc @@ -250,7 +250,7 @@ void CoefficientClasses(py::module &m) { std::shared_ptr getCoefStruct(double time) override { PYBIND11_OVERRIDE(std::shared_ptr, SphCoefs, getCoefStruct, - time); + time); } std::vector Times() override { @@ -274,11 +274,11 @@ void CoefficientClasses(py::module &m) { } void clear() override { - PYBIND11_OVERRIDE(void, SphCoefs, clear,); + PYBIND11_OVERRIDE(void, SphCoefs, clear,); } void add(CoefStrPtr coef) override { - PYBIND11_OVERRIDE(void, SphCoefs, add, coef); + PYBIND11_OVERRIDE(void, SphCoefs, add, coef); } std::vector makeKeys(Key k) override { @@ -300,7 +300,7 @@ void CoefficientClasses(py::module &m) { { protected: void readNativeCoefs(const std::string& file, int stride, double tmin, double tmax) override { - PYBIND11_OVERRIDE(void, CylCoefs, readNativeCoefs, file, stride, tmin, tmax); + PYBIND11_OVERRIDE(void, CylCoefs, readNativeCoefs, file, stride, tmin, tmax); } std::string getYAML() override { @@ -329,7 +329,7 @@ void CoefficientClasses(py::module &m) { std::shared_ptr getCoefStruct(double time) override { PYBIND11_OVERRIDE(std::shared_ptr, CylCoefs, getCoefStruct, - time); + time); } std::vector Times() override { @@ -349,7 +349,7 @@ void CoefficientClasses(py::module &m) { } bool CompareStanzas(std::shared_ptr check) override { - PYBIND11_OVERRIDE(bool, CylCoefs, CompareStanzas, check); + PYBIND11_OVERRIDE(bool, CylCoefs, CompareStanzas, check); } void clear() override { @@ -357,7 +357,7 @@ void CoefficientClasses(py::module &m) { } void add(CoefStrPtr coef) override { - PYBIND11_OVERRIDE(void, CylCoefs, add, coef); + PYBIND11_OVERRIDE(void, CylCoefs, add, coef); } std::vector makeKeys(Key k) override { @@ -407,7 +407,7 @@ void CoefficientClasses(py::module &m) { std::shared_ptr getCoefStruct(double time) override { PYBIND11_OVERRIDE(std::shared_ptr, SlabCoefs, getCoefStruct, - time); + time); } std::vector Times() override { @@ -431,11 +431,11 @@ void CoefficientClasses(py::module &m) { } void clear() override { - PYBIND11_OVERRIDE(void, SlabCoefs, clear,); + PYBIND11_OVERRIDE(void, SlabCoefs, clear,); } void add(CoefStrPtr coef) override { - PYBIND11_OVERRIDE(void, SlabCoefs, add, coef); + PYBIND11_OVERRIDE(void, SlabCoefs, add, coef); } std::vector makeKeys(Key k) override { @@ -486,7 +486,7 @@ void CoefficientClasses(py::module &m) { std::shared_ptr getCoefStruct(double time) override { PYBIND11_OVERRIDE(std::shared_ptr, CubeCoefs, getCoefStruct, - time); + time); } std::vector Times() override { @@ -510,11 +510,11 @@ void CoefficientClasses(py::module &m) { } void clear() override { - PYBIND11_OVERRIDE(void, CubeCoefs, clear,); + PYBIND11_OVERRIDE(void, CubeCoefs, clear,); } void add(CoefStrPtr coef) override { - PYBIND11_OVERRIDE(void, CubeCoefs, add, coef); + PYBIND11_OVERRIDE(void, CubeCoefs, add, coef); } std::vector makeKeys(Key k) override { @@ -565,7 +565,7 @@ void CoefficientClasses(py::module &m) { std::shared_ptr getCoefStruct(double time) override { PYBIND11_OVERRIDE(std::shared_ptr, TableData, getCoefStruct, - time); + time); } std::vector Times() override { @@ -643,7 +643,7 @@ void CoefficientClasses(py::module &m) { std::shared_ptr getCoefStruct(double time) override { PYBIND11_OVERRIDE(std::shared_ptr, TrajectoryData, getCoefStruct, - time); + time); } std::vector Times() override { @@ -691,7 +691,7 @@ void CoefficientClasses(py::module &m) { py::class_, PyCoefStruct> (m, "CoefStruct") .def(py::init<>(), - R"( + R"( Base class coefficient data structure object Returns @@ -699,7 +699,7 @@ void CoefficientClasses(py::module &m) { CoefStruct )") .def("create", &CoefStruct::create, - R"( + R"( Initialize a coefficient zeroed structure from user supplied dimensions Returns @@ -721,12 +721,12 @@ void CoefficientClasses(py::module &m) { while preserving your original coefficients. )") .def_readonly("geometry", &CoefStruct::geom, - R"( + R"( str geometry type )") .def_readonly("time", &CoefStruct::time, - R"( + R"( float data's time stamp )") @@ -809,7 +809,7 @@ void CoefficientClasses(py::module &m) { reading EXP coefficient files. )") .def("setCoefCenter", - static_cast(&CoefStruct::setCenter), + static_cast(&CoefStruct::setCenter), py::arg("mat"), R"( Set the center vector @@ -844,7 +844,7 @@ void CoefficientClasses(py::module &m) { setCoefRotation : read-write access to the rotation matrix )") .def("setCoefRotation", - static_cast(&CoefStruct::setRotation), + static_cast(&CoefStruct::setRotation), py::arg("mat"), R"( Set the rotation matrix @@ -878,7 +878,7 @@ void CoefficientClasses(py::module &m) { -------- setCoefs : read-write access to Coefs )") - .def("setCoefs", // Member function overload + .def("setCoefs", // Member function overload static_cast(&CoefStruct::setCoefs), py::arg("mat"), R"( @@ -903,7 +903,7 @@ void CoefficientClasses(py::module &m) { -------- getCoefs : read-only access to Coefs )") - .def("setCoefs", // Member function overload + .def("setCoefs", // Member function overload static_cast(CoefStruct::*)()>(&CoefStruct::setCoefs), R"( Read-write access to the underlying data store @@ -930,7 +930,7 @@ void CoefficientClasses(py::module &m) { (m, "SphStruct") .def(py::init<>(), "Spherical coefficient data structure object") .def("assign", &SphStruct::assign, - R"( + R"( Assign a coefficient matrix to CoefStruct. Parameters @@ -951,7 +951,7 @@ void CoefficientClasses(py::module &m) { (m, "CylStruct") .def(py::init<>(), "Cylindrical coefficient data structure object") .def("assign", &CylStruct::assign, - R"( + R"( Assign a coefficient matrix to CoefStruct. Parameters @@ -972,7 +972,7 @@ void CoefficientClasses(py::module &m) { (m, "SlabStruct") .def(py::init<>(), "Slab coefficient data structure object") .def("assign", &SlabStruct::assign, - R"( + R"( Assign a coefficient matrix to CoefStruct. Parameters @@ -994,7 +994,7 @@ void CoefficientClasses(py::module &m) { (m, "CubeStruct") .def(py::init<>(), "Cube coefficient data structure object") .def("assign", &CubeStruct::assign, - R"( + R"( Assign a coefficient matrix to CoefStruct. Parameters @@ -1016,7 +1016,7 @@ void CoefficientClasses(py::module &m) { (m, "TblStruct") .def(py::init<>(), "Multicolumn table data structure object") .def("assign", &TblStruct::assign, - R"( + R"( Assign a coefficient matrix to CoefStruct. Parameters @@ -1033,7 +1033,7 @@ void CoefficientClasses(py::module &m) { (m, "SphFldStruct") .def(py::init<>(), "Spherical field coefficient data structure object") .def("assign", &SphFldStruct::assign, - R"( + R"( Assign a coefficient matrix to CoefStruct. Parameters @@ -1056,7 +1056,7 @@ void CoefficientClasses(py::module &m) { (m, "CylFldStruct") .def(py::init<>(), "Cylindrical field coefficient data structure object") .def("assign", &CylFldStruct::assign, - R"( + R"( Assign a flattened coefficient array to CylFldStruct. Parameters @@ -1101,7 +1101,7 @@ void CoefficientClasses(py::module &m) { py::arg("type"), py::arg("verbose")) .def("__call__", - &CoefClasses::Coefs::getData, + &CoefClasses::Coefs::getData, R"( Return the flattened coefficient structure for the desired time. @@ -1178,8 +1178,8 @@ void CoefficientClasses(py::module &m) { You will get a runtime error if the entry does not exist. )",py::arg("time")) .def("Times", - &CoefClasses::Coefs::Times, - R"( + &CoefClasses::Coefs::Times, + R"( Return a list of times for coefficient sets currently in the container Returns @@ -1202,8 +1202,8 @@ void CoefficientClasses(py::module &m) { None )") .def("setUnits", - py::overload_cast(&CoefClasses::Coefs::setUnits), - R"( + py::overload_cast(&CoefClasses::Coefs::setUnits), + R"( Set the units for the coefficient structure. Parameters @@ -1221,8 +1221,8 @@ void CoefficientClasses(py::module &m) { None )", py::arg("name"), py::arg("unit")="", py::arg("value")=1.0) .def("setUnits", - py::overload_cast>&>(&CoefClasses::Coefs::setUnits), - R"( + py::overload_cast>&>(&CoefClasses::Coefs::setUnits), + R"( Set the units for the coefficient structure. Parameters @@ -1234,16 +1234,60 @@ void CoefficientClasses(py::module &m) { ------- None )", py::arg("units")) - .def("WriteH5Coefs", - [](CoefClasses::Coefs& self, const std::string& filename) { - if (self.getUnits().size()!=4) { - std::cout << "Coefs::WriteH5Coefs: please set units for your coefficient set using the `setUnit()` member," << std::endl - << " one for each unit. We suggest explicitly setting 'G', 'Length', 'Mass'," << std::endl - << " 'Time', or optionally 'Velocity' before writing HDF5 coefficients" << std::endl; - } - self.WriteH5Coefs(filename); - }, + .def("getAllowedUnitNames", + &CoefClasses::Coefs::getAllowedUnitNames, + R"( + Get the allowed unit names for the a given unit type + + Parameters + ---------- + type : str + the type of unit + + Returns + ------- + list(str,...) + list of allowed unit names + + )", py::arg("type")) + .def("getAllowedUnitTypes", &CoefClasses::Coefs::getAllowedUnitTypes, + R"( + Get the allowed unit types for the coefficient structure + + Parameters + ---------- + None + + Returns + ------- + list(str,...) + list of allowed unit types + )") + .def("getAllowedTypeAliases", + &CoefClasses::Coefs::getAllowedTypeAliases, R"( + Get the allowed type aliases for the coefficient structure + + Parameters + ---------- + type : str + the type of unit + + Returns + ------- + list(str) + list of allowed type aliases + )", py::arg("type")) + .def("WriteH5Coefs", + [](CoefClasses::Coefs& self, const std::string& filename) { + if (self.getUnits().size()!=4) { + std::cout << "Coefs::WriteH5Coefs: please set units for your coefficient set using the `setUnit()` member," << std::endl + << " one for each unit. We suggest explicitly setting 'G', 'Length', 'Mass'," << std::endl + << " 'Time', or optionally 'Velocity' before writing HDF5 coefficients" << std::endl; + } + self.WriteH5Coefs(filename); + }, + R"( Write the coefficients into an EXP HDF5 coefficient file with the given prefix name. Parameters @@ -1262,7 +1306,7 @@ void CoefficientClasses(py::module &m) { feature. If you'd like a new version of this file, delete the old before this call. )", - py::arg("filename")) + py::arg("filename")) .def("ExtendH5Coefs", &CoefClasses::Coefs::ExtendH5Coefs, R"( @@ -1419,7 +1463,7 @@ void CoefficientClasses(py::module &m) { py::arg("tmin")=-std::numeric_limits::max(), py::arg("tmax")= std::numeric_limits::max()) .def_static("makecoefs", &CoefClasses::Coefs::makecoefs, - R"( + R"( make a new coefficient container instance compatible Parameters @@ -1444,13 +1488,13 @@ void CoefficientClasses(py::module &m) { -------- addcoef : add coefficient structures to an existing coefficieint container )", - py::arg("coef"), py::arg("name")=""); + py::arg("coef"), py::arg("name")=""); py::class_, PySphCoefs, CoefClasses::Coefs> (m, "SphCoefs", "Container for spherical coefficients") .def(py::init(), - R"( + R"( Construct a null SphCoefs object Parameters @@ -1463,7 +1507,7 @@ void CoefficientClasses(py::module &m) { SphCoefs instance )") .def("__call__", - &CoefClasses::SphCoefs::getMatrix, + &CoefClasses::SphCoefs::getMatrix, R"( Return the coefficient Matrix for the desired time. @@ -1484,7 +1528,7 @@ void CoefficientClasses(py::module &m) { )", py::arg("time")) .def("setMatrix", - &CoefClasses::SphCoefs::setMatrix, + &CoefClasses::SphCoefs::setMatrix, R"( Enter and/or rewrite the coefficient matrix at the provided time @@ -1501,13 +1545,13 @@ void CoefficientClasses(py::module &m) { )", py::arg("time"), py::arg("mat")) .def("getAllCoefs", - [](CoefClasses::SphCoefs& A) - { - Eigen::Tensor, 3> M = A.getAllCoefs(); // Need a copy here - py::array_t> ret = make_ndarray3>(M); - return ret; - }, - R"( + [](CoefClasses::SphCoefs& A) + { + Eigen::Tensor, 3> M = A.getAllCoefs(); // Need a copy here + py::array_t> ret = make_ndarray3>(M); + return ret; + }, + R"( Provide a 3-dimensional ndarray indexed by spherical index, radial index, and time index @@ -1525,7 +1569,7 @@ void CoefficientClasses(py::module &m) { py::class_, PyCylCoefs, CoefClasses::Coefs> (m, "CylCoefs", "Container for cylindrical coefficients") .def(py::init(), - R"( + R"( Construct a null CylCoefs object Parameters @@ -1538,7 +1582,7 @@ void CoefficientClasses(py::module &m) { CylCoefs instance )") .def("__call__", - &CoefClasses::CylCoefs::getMatrix, + &CoefClasses::CylCoefs::getMatrix, R"( Return the coefficient Matrix for the desired time. @@ -1559,7 +1603,7 @@ void CoefficientClasses(py::module &m) { )", py::arg("time")) .def("setMatrix", - &CoefClasses::CylCoefs::setMatrix, + &CoefClasses::CylCoefs::setMatrix, R"( Enter and/or rewrite the coefficient matrix at the provided time @@ -1576,13 +1620,13 @@ void CoefficientClasses(py::module &m) { )", py::arg("time"), py::arg("mat")) .def("getAllCoefs", - [](CoefClasses::CylCoefs& A) - { - Eigen::Tensor, 3> M = A.getAllCoefs(); // Need a copy here - py::array_t> ret = make_ndarray3>(M); - return ret; - }, - R"( + [](CoefClasses::CylCoefs& A) + { + Eigen::Tensor, 3> M = A.getAllCoefs(); // Need a copy here + py::array_t> ret = make_ndarray3>(M); + return ret; + }, + R"( Provide a 3-dimensional ndarray indexed by azimuthal index, radial index, and time index Returns @@ -1591,11 +1635,11 @@ void CoefficientClasses(py::module &m) { 3-dimensional numpy array containing the cylindrical coefficients )") .def("EvenOddPower", - [](CoefClasses::CylCoefs& A, int nodd, int min, int max) - { - return A.EvenOddPower(nodd, min, max); - }, - R"( + [](CoefClasses::CylCoefs& A, int nodd, int min, int max) + { + return A.EvenOddPower(nodd, min, max); + }, + R"( Get cylindrical coefficient power separated into vertically even and odd contributions. Parameters @@ -1620,14 +1664,14 @@ void CoefficientClasses(py::module &m) { argument if it is not explicitly set in your EXP::Cylinder configuration. If in doubt, use the default. )", - py::arg("nodd")=-1, py::arg("min")=0, - py::arg("max")=std::numeric_limits::max()); + py::arg("nodd")=-1, py::arg("min")=0, + py::arg("max")=std::numeric_limits::max()); py::class_, CoefClasses::Coefs> (m, "SphFldCoefs", "Container for spherical field coefficients") .def(py::init(), - R"( + R"( Construct a null SphFldCoefs object Parameters @@ -1640,12 +1684,12 @@ void CoefficientClasses(py::module &m) { SphFldCoefs instance )") .def("__call__", - [](CoefClasses::SphFldCoefs& A, double time) - { - // Need a copy here - auto M = A.getMatrix(time); - return make_ndarray3>(M); - }, + [](CoefClasses::SphFldCoefs& A, double time) + { + // Need a copy here + auto M = A.getMatrix(time); + return make_ndarray3>(M); + }, R"( Return the coefficient tensor for the desired time. @@ -1666,12 +1710,12 @@ void CoefficientClasses(py::module &m) { )", py::arg("time")) .def("setMatrix", - [](CoefClasses::SphFldCoefs& A, double time, - py::array_t> mat) - { - auto M = make_tensor3>(mat); - A.setMatrix(time, M); - }, + [](CoefClasses::SphFldCoefs& A, double time, + py::array_t> mat) + { + auto M = make_tensor3>(mat); + A.setMatrix(time, M); + }, R"( Enter and/or rewrite the coefficient tensor at the provided time @@ -1688,13 +1732,13 @@ void CoefficientClasses(py::module &m) { )", py::arg("time"), py::arg("mat")) .def("getAllCoefs", - [](CoefClasses::SphFldCoefs& A) - { - Eigen::Tensor, 4> M = A.getAllCoefs(); // Need a copy here - py::array_t> ret = make_ndarray4>(M); - return ret; - }, - R"( + [](CoefClasses::SphFldCoefs& A) + { + Eigen::Tensor, 4> M = A.getAllCoefs(); // Need a copy here + py::array_t> ret = make_ndarray4>(M); + return ret; + }, + R"( Provide a 4-dimensional ndarray indexed by channel index, spherical index, radial index, and time index Returns @@ -1712,7 +1756,7 @@ void CoefficientClasses(py::module &m) { py::class_, CoefClasses::Coefs> (m, "CylFldCoefs", "Container for cylindrical field coefficients") .def(py::init(), - R"( + R"( Construct a null CylFldCoefs object Parameters @@ -1725,11 +1769,11 @@ void CoefficientClasses(py::module &m) { CylFldCoefs instance )") .def("__call__", - [](CoefClasses::CylFldCoefs& A, double time) - { - auto M = A.getMatrix(time); // Need a copy here - return make_ndarray3>(M); - }, + [](CoefClasses::CylFldCoefs& A, double time) + { + auto M = A.getMatrix(time); // Need a copy here + return make_ndarray3>(M); + }, R"( Return the coefficient tensor for the desired time. @@ -1750,12 +1794,12 @@ void CoefficientClasses(py::module &m) { )", py::arg("time")) .def("setMatrix", - [](CoefClasses::CylFldCoefs& A, double time, - py::array_t> mat) - { - auto M = make_tensor3>(mat); - A.setMatrix(time, M); - }, + [](CoefClasses::CylFldCoefs& A, double time, + py::array_t> mat) + { + auto M = make_tensor3>(mat); + A.setMatrix(time, M); + }, R"( Enter and/or rewrite the coefficient tensor at the provided time @@ -1772,13 +1816,13 @@ void CoefficientClasses(py::module &m) { )", py::arg("time"), py::arg("mat")) .def("getAllCoefs", - [](CoefClasses::CylFldCoefs& A) - { - Eigen::Tensor, 4> M = A.getAllCoefs(); // Need a copy here - py::array_t> ret = make_ndarray4>(M); - return ret; - }, - R"( + [](CoefClasses::CylFldCoefs& A) + { + Eigen::Tensor, 4> M = A.getAllCoefs(); // Need a copy here + py::array_t> ret = make_ndarray4>(M); + return ret; + }, + R"( Provide a 4-dimensional ndarray indexed by channel index, spherical index, radial index, and time index Returns @@ -1791,7 +1835,7 @@ void CoefficientClasses(py::module &m) { py::class_, PySlabCoefs, CoefClasses::Coefs> (m, "SlabCoefs", "Container for cube coefficients") .def(py::init(), - R"( + R"( Construct a null SlabCoefs object Parameters @@ -1804,7 +1848,7 @@ void CoefficientClasses(py::module &m) { SlabCoefs instance )") .def("__call__", - &CoefClasses::SlabCoefs::getTensor, + &CoefClasses::SlabCoefs::getTensor, R"( Return the coefficient tensor for the desired time. @@ -1825,7 +1869,7 @@ void CoefficientClasses(py::module &m) { )", py::arg("time")) .def("setTensor", - &CoefClasses::SlabCoefs::setTensor, + &CoefClasses::SlabCoefs::setTensor, R"( Enter and/or rewrite the coefficient tensor at the provided time @@ -1842,13 +1886,13 @@ void CoefficientClasses(py::module &m) { )", py::arg("time"), py::arg("tensor")) .def("getAllCoefs", - [](CoefClasses::SlabCoefs& A) - { - Eigen::Tensor, 4> M = A.getAllCoefs(); // Need a copy here - py::array_t> ret = make_ndarray4>(M); - return ret; - }, - R"( + [](CoefClasses::SlabCoefs& A) + { + Eigen::Tensor, 4> M = A.getAllCoefs(); // Need a copy here + py::array_t> ret = make_ndarray4>(M); + return ret; + }, + R"( Provide a 4-dimensional ndarray indexed by nx, ny, nz, and time indices. Returns @@ -1857,11 +1901,11 @@ void CoefficientClasses(py::module &m) { 4-dimensional numpy array containing the slab coefficients )") .def("PowerDim", - [](CoefClasses::SlabCoefs& A, std::string d, int min, int max) - { - return A.Power(d[0], min, max); - }, - R"( + [](CoefClasses::SlabCoefs& A, std::string d, int min, int max) + { + return A.Power(d[0], min, max); + }, + R"( Get power for the coefficient DB as a function of harmonic index for a given dimension. This Power() member is equivalent to PowerDim('x'). @@ -1879,13 +1923,13 @@ void CoefficientClasses(py::module &m) { numpy.ndarray 2-dimensional numpy array containing the power )", py::arg("d"), py::arg("min")=0, - py::arg("max")=std::numeric_limits::max()); + py::arg("max")=std::numeric_limits::max()); py::class_, PyCubeCoefs, CoefClasses::Coefs> (m, "CubeCoefs", "Container for cube coefficients") .def(py::init(), - R"( + R"( Construct a null CubeCoefs object Parameters @@ -1898,7 +1942,7 @@ void CoefficientClasses(py::module &m) { CubeCoefs instance )") .def("__call__", - &CoefClasses::CubeCoefs::getTensor, + &CoefClasses::CubeCoefs::getTensor, R"( Return the coefficient tensor for the desired time. @@ -1919,7 +1963,7 @@ void CoefficientClasses(py::module &m) { )", py::arg("time")) .def("setTensor", - &CoefClasses::CubeCoefs::setTensor, + &CoefClasses::CubeCoefs::setTensor, R"( Enter and/or rewrite the coefficient tensor at the provided time @@ -1936,13 +1980,13 @@ void CoefficientClasses(py::module &m) { )", py::arg("time"), py::arg("tensor")) .def("getAllCoefs", - [](CoefClasses::CubeCoefs& A) - { - Eigen::Tensor, 4> M = A.getAllCoefs(); // Need a copy here - py::array_t> ret = make_ndarray4>(M); - return ret; - }, - R"( + [](CoefClasses::CubeCoefs& A) + { + Eigen::Tensor, 4> M = A.getAllCoefs(); // Need a copy here + py::array_t> ret = make_ndarray4>(M); + return ret; + }, + R"( Provide a 4-dimensional ndarray indexed by nx, ny, nz, and time indices. Returns @@ -1951,11 +1995,11 @@ void CoefficientClasses(py::module &m) { 4-dimensional numpy array containing the cube coefficients )") .def("PowerDim", - [](CoefClasses::CubeCoefs& A, std::string d, int min, int max) - { - return A.Power(d[0], min, max); - }, - R"( + [](CoefClasses::CubeCoefs& A, std::string d, int min, int max) + { + return A.Power(d[0], min, max); + }, + R"( Get power for the coefficient DB as a function of harmonic index for a given dimension. This Power() member is equivalent to PowerDim('x'). @@ -1973,12 +2017,12 @@ void CoefficientClasses(py::module &m) { numpy.ndarray 2-dimensional numpy array containing the power )", py::arg("d"), py::arg("min")=0, - py::arg("max")=std::numeric_limits::max()); + py::arg("max")=std::numeric_limits::max()); py::class_, PyTableData, CoefClasses::Coefs> (m, "TableData", "Container for simple data tables with multiple columns") .def(py::init(), - R"( + R"( Construct a null TableData object Parameters @@ -1991,7 +2035,7 @@ void CoefficientClasses(py::module &m) { TableData instance )", py::arg("verbose")=true) .def(py::init(), - R"( + R"( Construct a TableData object from a data file Parameters @@ -2004,7 +2048,7 @@ void CoefficientClasses(py::module &m) { TableData instance )") .def(py::init(), - R"( + R"( Construct a TableData object from a data file Parameters @@ -2019,7 +2063,7 @@ void CoefficientClasses(py::module &m) { TableData instance )", py::arg("filename"), py::arg("verbose")=true) .def(py::init&, std::vector>&, bool>(), - R"( + R"( Construct a TableData object from data arrays Parameters @@ -2036,7 +2080,7 @@ void CoefficientClasses(py::module &m) { TableData instance )", py::arg("time"), py::arg("array"), py::arg("verbose")=true) .def("getAllCoefs", &CoefClasses::TableData::getAllCoefs, - R"( + R"( Return a 2-dimensional ndarray indexed by column and time Returns @@ -2048,7 +2092,7 @@ void CoefficientClasses(py::module &m) { py::class_, PyTrajectoryData, CoefClasses::Coefs> (m, "TrajectoryData", "Container for trajectory/orbit data") .def(py::init(), - R"( + R"( Construct a null TrajectoryData object Parameters @@ -2061,7 +2105,7 @@ void CoefficientClasses(py::module &m) { TrajectoryData instance )", py::arg("verbose")=true) .def(py::init(), - R"( + R"( Construct a TrajectoryData object from a data file Parameters @@ -2074,7 +2118,7 @@ void CoefficientClasses(py::module &m) { TrajectoryData instance )") .def(py::init(), - R"( + R"( Construct a TrajectoryData object from a data file Parameters @@ -2089,7 +2133,7 @@ void CoefficientClasses(py::module &m) { TrajectoryData instance )", py::arg("filename"), py::arg("verbose")=true) .def(py::init&, std::vector&, bool>(), - R"( + R"( Construct a TrajectoryData object from data arrays Parameters @@ -2106,14 +2150,14 @@ void CoefficientClasses(py::module &m) { TrajectoryData instance )", py::arg("time"), py::arg("array"), py::arg("verbose")=true) .def("getAllCoefs", - [](CoefClasses::TrajectoryData& A) - { - Eigen::Tensor M = A.getAllCoefs(); // Need a copy here - py::array_t ret = make_ndarray3(M); - return ret; - }, + [](CoefClasses::TrajectoryData& A) + { + Eigen::Tensor M = A.getAllCoefs(); // Need a copy here + py::array_t ret = make_ndarray3(M); + return ret; + }, - R"( + R"( Return a 3-dimensional ndarray indexed by column and time Returns