Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
69432d1
Added asymptotic form of the spherical deprojection of the exponentia…
Jan 30, 2026
6cde663
Added a more exact spherical deprojection implementation
Jan 30, 2026
e35905e
Add a cache check for spherical model type used in eof computation
Jan 30, 2026
6443ebf
Test routine for ExpDeproj class
Jan 31, 2026
2cbb4c2
Test routine for ExpDeproj class
Jan 31, 2026
420813e
Move deprojection flag parsing before EmpCylSL instantiation
Jan 31, 2026
5c44d30
Add an experimental bias factor for deprojected basis construction
Feb 1, 2026
0a507cd
Change from 'afac' to 'bias' to be a bit more descriptive
Feb 1, 2026
972c6f3
Remove stray character
Feb 1, 2026
e8ddbfd
Change defaults to ExpSphere in pyEXP
Feb 3, 2026
64ea348
Update exputil/ExpDeproj.cc
The9Cat Feb 4, 2026
53d90b6
Initial plan
Copilot Feb 4, 2026
0044833
Initial plan
Copilot Feb 4, 2026
102d1fc
Initial plan
Copilot Feb 4, 2026
62bde43
Rename ExpSphere to ExpDeproj for consistency
Copilot Feb 4, 2026
2f09671
Complete naming alignment to ExpDeproj
Copilot Feb 4, 2026
8d2a6e9
Remove CodeQL artifact
Copilot Feb 4, 2026
97ada8c
Fix ngrid validation to prevent divide-by-zero
Copilot Feb 4, 2026
0e42e6e
Add input validation for bias parameter
Copilot Feb 4, 2026
861f48d
Complete fix for ngrid validation issue
Copilot Feb 4, 2026
8d227a8
Remove codeql build artifacts and update gitignore
Copilot Feb 4, 2026
31d44f3
Merge pull request #201 from EXP-code/copilot/sub-pr-199
The9Cat Feb 4, 2026
4373a96
Complete bias validation implementation
Copilot Feb 4, 2026
101b772
Remove build artifacts and update .gitignore
Copilot Feb 4, 2026
7f723ba
Revert naming changes - ExpSphere is correct, not ExpDeproj
Copilot Feb 4, 2026
8603c98
Complete revert to ExpSphere naming
Copilot Feb 4, 2026
1e38d8b
Remove CodeQL artifact
Copilot Feb 4, 2026
d227415
Merge pull request #203 from EXP-code/copilot/sub-pr-199-another-one
The9Cat Feb 4, 2026
490b46c
Merge pull request #202 from EXP-code/copilot/sub-pr-199-again
The9Cat Feb 4, 2026
883db22
Tweak Bessel call for Apple compatibility
michael-petersen Feb 17, 2026
452f661
Merge branch 'devel' into SphericalExact
michael-petersen Feb 17, 2026
87bd84c
Merge branch 'devel' into SphericalExact
michael-petersen Feb 18, 2026
2ca7c27
restore mixed-up merge
michael-petersen Feb 18, 2026
b129d5b
Updates for user consistency
Feb 25, 2026
c3153c9
Added Doxygen docs for mtype
Feb 25, 2026
0e18c88
Added ppower key for setting power-law deprojection method in Cylinder
Feb 25, 2026
ac4b810
Merge branch 'SphericalExact' of github.com:EXP-code/EXP into Spheric…
Feb 25, 2026
11cbf63
Initial plan
Copilot Feb 25, 2026
b774886
Fix thread-unsafe lazy initialization: move initialize() to constructor
Copilot Feb 25, 2026
aeccbd3
Merge pull request #205 from EXP-code/copilot/sub-pr-199
The9Cat Feb 25, 2026
b13ddd0
Initial plan
Copilot Feb 25, 2026
18a9887
Initial plan
Copilot Feb 25, 2026
5d8ab59
Update utils/ICs/check_coefs.cc
The9Cat Feb 25, 2026
e421863
Add @param documentation for ppower in Cylinder.H
Copilot Feb 25, 2026
05704d7
Update utils/ICs/check_coefs2.cc
The9Cat Feb 25, 2026
76cebb4
Fix error message in check_coefs.cc to list all valid mtype options
Copilot Feb 25, 2026
97bdeb7
Merge pull request #206 from EXP-code/copilot/sub-pr-199
The9Cat Feb 25, 2026
2ad2fcc
Merge pull request #207 from EXP-code/copilot/sub-pr-199-again
The9Cat Feb 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@

build/*
_codeql_build_dir/

Makefile
*.lo
Expand All @@ -25,3 +26,5 @@ src/exp
src/user/CylindricalDisk.cc
src/user/EllipsoidForce.cc
src/user/SLSphere.cc
_codeql_build_dir
_codeql_detected_source_root
2 changes: 1 addition & 1 deletion expui/BiorthBasis.H
Original file line number Diff line number Diff line change
Expand Up @@ -1044,7 +1044,7 @@ namespace BasisClasses
int lmaxfid, nmaxfid, mmax, mlim, nmax;
int ncylodd, ncylnx, ncylny, ncylr, cmap, cmapR, cmapZ, vflag;
int rnum, pnum, tnum;
double rcylmin, rcylmax, acyl, hcyl;
double rcylmin, rcylmax, acyl, hcyl, bias;
bool expcond, logarithmic, density, EVEN_M, sech2 = true;

std::vector<Eigen::MatrixXd> potd, dpot, dpt2, dend;
Expand Down
80 changes: 51 additions & 29 deletions expui/BiorthBasis.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1186,6 +1186,7 @@ namespace BasisClasses
"rcylmin",
"rcylmax",
"acyl",
"bias",
"hcyl",
"sech2",
"snr",
Expand Down Expand Up @@ -1376,6 +1377,7 @@ namespace BasisClasses
rcylmin = 0.001;
rcylmax = 20.0;
acyl = 0.01;
bias = 1.0;
hcyl = 0.002;
nmax = 18;
mmax = 6;
Expand All @@ -1400,7 +1402,7 @@ namespace BasisClasses
EVEN_M = false;
cmapR = 1;
cmapZ = 1;
mtype = "exponential";
mtype = "Exponential";
dtype = "exponential";
vflag = 0;

Expand Down Expand Up @@ -1454,6 +1456,7 @@ namespace BasisClasses
if (conf["rcylmax" ]) rcylmax = conf["rcylmax" ].as<double>();

if (conf["acyl" ]) acyl = conf["acyl" ].as<double>();
if (conf["bias" ]) bias = conf["bias" ].as<double>();
if (conf["hcyl" ]) hcyl = conf["hcyl" ].as<double>();
if (conf["sech2" ]) sech2 = conf["sech2" ].as<bool>();
if (conf["lmaxfid" ]) lmaxfid = conf["lmaxfid" ].as<int>();
Expand Down Expand Up @@ -1540,6 +1543,17 @@ namespace BasisClasses
pnum = std::max<int>(1, pnum);
tnum = std::max<int>(10, tnum);

// Validate bias parameter
//
if (!std::isfinite(bias)) {
throw std::runtime_error("Cylindrical: 'bias' parameter must be finite");
}
if (bias <= 0.0) {
std::ostringstream sout;
sout << "Cylindrical: 'bias' parameter must be positive, got " << bias;
throw std::runtime_error(sout.str());
}

EmpCylSL::RMIN = rcylmin;
EmpCylSL::RMAX = rcylmax;
EmpCylSL::NUMX = ncylnx;
Expand All @@ -1550,6 +1564,40 @@ namespace BasisClasses
EmpCylSL::logarithmic = logarithmic;
EmpCylSL::VFLAG = vflag;

// Set deprojected model type
//
// Convert mtype string to lower case
std::transform(mtype.begin(), mtype.end(), mtype.begin(),
[](unsigned char c){ return std::tolower(c); });

// Set EmpCylSL mtype. This is the spherical function used to
// generate the EOF basis. If "deproject" is set, this will be
// overriden in EmpCylSL.
//
EmpCylSL::mtype = EmpCylSL::Exponential; // Default
if (mtype.compare("exponential")==0) {
EmpCylSL::mtype = EmpCylSL::Exponential;
if (myid==0) {
std::cout << "---- Cylindrical: using original exponential deprojected disk for EOF conditioning" << std::endl;
std::cout << "---- Cylindrical: consider using the exact, spherically deprojected exponential with 'mtype: ExpSphere'" << std::endl;
}
} else if (mtype.compare("expsphere")==0)
EmpCylSL::mtype = EmpCylSL::ExpSphere;
else if (mtype.compare("gaussian")==0)
EmpCylSL::mtype = EmpCylSL::Gaussian;
else if (mtype.compare("plummer")==0)
EmpCylSL::mtype = EmpCylSL::Plummer;
else if (mtype.compare("power")==0) {
EmpCylSL::mtype = EmpCylSL::Power;
EmpCylSL::PPOW = ppow;
} else {
if (myid==0) std::cout << "No EmpCylSL EmpModel named <"
<< mtype << ">, valid types are: "
<< "Exponential, ExpSphere, Gaussian, Plummer, Power "
<< "(not case sensitive)" << std::endl;
throw std::runtime_error("Cylindrical:initialize: EmpCylSL bad parameter");
}

// Check for non-null cache file name. This must be specified
// to prevent recomputation and unexpected behavior.
//
Expand All @@ -1565,7 +1613,7 @@ namespace BasisClasses
// Make the empirical orthogonal basis instance
//
sl = std::make_shared<EmpCylSL>
(nmaxfid, lmaxfid, mmax, nmax, acyl, hcyl, ncylodd, cachename);
(nmaxfid, lmaxfid, mmax, nmax, bias*acyl, hcyl, ncylodd, cachename);

// Set azimuthal harmonic order restriction?
//
Expand Down Expand Up @@ -1598,33 +1646,6 @@ namespace BasisClasses
<< "---- remove 'ignore' from your YAML configuration." << std::endl;
}

// Convert mtype string to lower case
//
std::transform(mtype.begin(), mtype.end(), mtype.begin(),
[](unsigned char c){ return std::tolower(c); });

// Set EmpCylSL mtype. This is the spherical function used to
// generate the EOF basis. If "deproject" is set, this will be
// overriden in EmpCylSL.
//
EmpCylSL::mtype = EmpCylSL::Exponential;
if (mtype.compare("exponential")==0)
EmpCylSL::mtype = EmpCylSL::Exponential;
else if (mtype.compare("gaussian")==0)
EmpCylSL::mtype = EmpCylSL::Gaussian;
else if (mtype.compare("plummer")==0)
EmpCylSL::mtype = EmpCylSL::Plummer;
else if (mtype.compare("power")==0) {
EmpCylSL::mtype = EmpCylSL::Power;
EmpCylSL::PPOW = ppow;
} else {
if (myid==0) std::cout << "No EmpCylSL EmpModel named <"
<< mtype << ">, valid types are: "
<< "Exponential, Gaussian, Plummer, Power "
<< "(not case sensitive)" << std::endl;
throw std::runtime_error("Cylindrical:initialize: EmpCylSL bad parameter");
}

// Convert dtype string to lower case
//
std::transform(dtype.begin(), dtype.end(), dtype.begin(),
Expand Down Expand Up @@ -5184,6 +5205,7 @@ namespace BasisClasses
file.createAttribute<double>("rcylmin", HighFive::DataSpace::From(rcylmin)).write(rcylmin);
file.createAttribute<double>("rcylmax", HighFive::DataSpace::From(rcylmax)).write(rcylmax);
file.createAttribute<double>("acyl", HighFive::DataSpace::From(acyl)).write(acyl);
file.createAttribute<double>("bias", HighFive::DataSpace::From(bias)).write(bias);
file.createAttribute<double>("hcyl", HighFive::DataSpace::From(hcyl)).write(hcyl);
}

Expand Down
2 changes: 1 addition & 1 deletion exputil/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ set(QUAD_SRC qadapt.cc gauleg.cc qadapt2d.cc gint2.cc rombe2d.cc
set(UTIL_SRC nrutil.cc elemfunc.cc euler.cc euler_slater.cc
rotmatrix.cc wordSplit.cc FileUtils.cc BarrierWrapper.cc
stack.cc localmpi.cc TableGrid.cc writePVD.cc libvars.cc
TransformFFT.cc QDHT.cc YamlCheck.cc # Hankel.cc
TransformFFT.cc QDHT.cc YamlCheck.cc ExpDeproj.cc # Hankel.cc
parseVersionString.cc EXPmath.cc laguerre_polynomial.cpp
YamlConfig.cc orthoTest.cc OrthoFunction.cc VtkGrid.cc
Sutils.cc fpetrap.cc)
Expand Down
18 changes: 18 additions & 0 deletions exputil/EmpCylSL.cc
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include <thread>
#include "exp_thread.h"
#include "EXPException.H"
#include "ExpDeproj.H"
#include "exputils.H"

#include <Eigen/Eigenvalues>
Expand Down Expand Up @@ -73,6 +74,7 @@ EmpCylSL::EmpModel EmpCylSL::mtype = Exponential;

std::map<EmpCylSL::EmpModel, std::string> EmpCylSL::EmpModelLabs =
{ {Exponential, "Exponential"},
{ExpSphere, "ExpSphere" },
{Gaussian, "Gaussian" },
{Plummer, "Plummer" },
{Power, "Power" },
Expand Down Expand Up @@ -552,6 +554,9 @@ double EmpCylSL::massR(double R)
case Exponential:
ans = 1.0 - (1.0 + R)*exp(-R);
break;
case ExpSphere:
ans = expdeproj.mass(R);
break;
case Gaussian:
arg = 0.5*R*R;
ans = 1.0 - exp(-arg);
Expand Down Expand Up @@ -589,6 +594,9 @@ double EmpCylSL::densR(double R)
case Exponential:
ans = exp(-R)/(4.0*M_PI*R);
break;
case ExpSphere:
ans = expdeproj.density(R);
break;
case Gaussian:
arg = 0.5*R*R;
ans = exp(-arg)/(4.0*M_PI*R);
Expand Down Expand Up @@ -2403,6 +2411,15 @@ void EmpCylSL::generate_eof(int numr, int nump, int numt,

int cntr = 0; // Loop counter for spreading load to nodes

// Debug info output
//
if (VFLAG & 8 && myid==0) {
std::cout << "---- EmpCylSL: Generating EOF with"
<< " Rmin=" << xi_to_r(XMIN)
<< " Rmax=" << xi_to_r(XMAX)
<< " numt=" << numt << std::endl;
}

// *** Radial quadrature loop
//
for (int qr=0; qr<numr; qr++) {
Expand Down Expand Up @@ -7531,6 +7548,7 @@ bool EmpCylSL::ReadH5Cache()

if (not checkStr(geometry, "geometry")) return false;
if (not checkStr(forceID, "forceID")) return false;
if (not checkStr(model, "model")) return false;
if (not checkInt(MMAX, "mmax")) return false;
if (not checkInt(NUMX, "numx")) return false;
if (not checkInt(NUMY, "numy")) return false;
Expand Down
83 changes: 83 additions & 0 deletions exputil/ExpDeproj.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
#include "ExpDeproj.H"

#include <cmath>
#include <stdexcept>
#include <iostream>
#include <fstream>
#include <cassert>
#include <vector>
#include <algorithm>
#include <numeric>
#include <functional>

#include "EXPmath.H"


void ExpDeproj::initialize()
{
if (ngrid < 2) {
throw std::invalid_argument("n must be at least 2");
}

rv.resize(ngrid);
mv.resize(ngrid);

std::vector<double> dv(ngrid);

double log_rmin = std::log(rmin);
double log_rmax = std::log(rmax);
double dlogr = (log_rmax - log_rmin)/static_cast<double>(ngrid - 1);

for (int i = 0; i < ngrid; ++i) {
rv[i] = std::exp(log_rmin + i*dlogr);
dv[i] = 4.0*M_PI*rv[i]*rv[i]*density(rv[i]);
}

mv[0] = 0.0;
for (int i=1; i<ngrid; i++)
mv[i] = mv[i-1] + 0.5*(dv[i] + dv[i-1])*(rv[i] - rv[i-1]);
}

double ExpDeproj::density(double R)
{
if (R < 0) {
throw std::invalid_argument("R must be non-negative");
}
return 0.5*EXPmath::cyl_bessel_k(0.0, R)/(M_PI*M_PI);
}

double ExpDeproj::mass(double R)
{
if (R < 0) {
throw std::invalid_argument("R must be non-negative");
}

if (R < rmin) return 0.0;
if (R > rmax) return mv.back();

auto it = std::lower_bound(rv.begin(), rv.end(), R);
// If R is slightly larger than the largest grid point due to rounding,
// lower_bound may return rv.end(); in that case, use the last mass value.
if (it == rv.end()) {
return mv.back();
}

std::size_t idx = static_cast<std::size_t>(std::distance(rv.begin(), it));
if (idx >= rv.size()) {
// Defensive guard; should not happen after the it == rv.end() check.
return mv.back();
}
if (rv[idx] == R) {
return mv[idx];
} else {
// Ensure idx-1 is valid
if (idx == 0) idx++;
// Linear interpolation
double x0 = rv[idx-1];
double x1 = rv[idx ];
double y0 = mv[idx-1];
double y1 = mv[idx ];

return y0 + (y1 - y0)*(R - x0)/(x1 - x0);
}
}
21 changes: 20 additions & 1 deletion include/EmpCylSL.H
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include <dmalloc.h>
#endif

#include "ExpDeproj.H"
#include "Particle.H"
#include "SLGridMP2.H"
#include "coef.H"
Expand Down Expand Up @@ -81,6 +82,9 @@ protected:

int rank2, rank3;

//! Precomputed exponential deprojection profile
ExpDeproj expdeproj;

//@{
//! Storage buffers for MPI
std::vector<double> MPIin, MPIout, MPIin2, MPIout2;
Expand Down Expand Up @@ -364,6 +368,7 @@ public:
//! Type of density model to use
enum EmpModel {
Exponential,
ExpSphere,
Gaussian,
Plummer,
Power,
Expand Down Expand Up @@ -427,7 +432,21 @@ public:
//! No extrapolating beyond grid (default: false)
static bool enforce_limits;

//! Density model type
/**
@brief Density model type
Available options:
- Exponential = approximate exponential disk deprojection
- ExpSphere = exact exponential spherical deprojection
- Gaussian = Gaussian sphere
- Plummer = Plummer sphere
- Power = power-law sphere
- Deproject = numerical deprojection of axisymmetric disk surface density (see create_deprojection)

@note The default option is Exponential for backwards
compatibility. The ExpSphere is the exact spherical deproject
for the exponential disk and should be used instead of
Exponential for new projects.
*/
static EmpModel mtype;

//! Radial basis grid in radial direction
Expand Down
28 changes: 28 additions & 0 deletions include/ExpDeproj.H
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#pragma once

#include <vector>
#include <cmath>

class ExpDeproj
{
const double rmin = 1.0e-4;
const double rmax = 30.0;

// Precomputed radial grid
int ngrid;
std::vector<double> rv, mv;
void initialize();

public:
//! Constructor
ExpDeproj(int n=4000) : ngrid(n) { initialize(); }

//! Destructor
virtual ~ExpDeproj() {}

//! Density profile at radius R
double density(double R);

//! Enclosed mass at radius R
double mass(double R);
};
Loading