Skip to content

Commit 20a863e

Browse files
Merge pull request #199 from EXP-code/SphericalExact
Adds an spherical basis that is an exact deprojection of an exponential surface density
2 parents 6b7dd2a + 2ad2fcc commit 20a863e

15 files changed

Lines changed: 355 additions & 46 deletions

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11

22
build/*
3+
_codeql_build_dir/
34

45
Makefile
56
*.lo
@@ -25,3 +26,5 @@ src/exp
2526
src/user/CylindricalDisk.cc
2627
src/user/EllipsoidForce.cc
2728
src/user/SLSphere.cc
29+
_codeql_build_dir
30+
_codeql_detected_source_root

expui/BiorthBasis.H

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1044,7 +1044,7 @@ namespace BasisClasses
10441044
int lmaxfid, nmaxfid, mmax, mlim, nmax;
10451045
int ncylodd, ncylnx, ncylny, ncylr, cmap, cmapR, cmapZ, vflag;
10461046
int rnum, pnum, tnum;
1047-
double rcylmin, rcylmax, acyl, hcyl;
1047+
double rcylmin, rcylmax, acyl, hcyl, bias;
10481048
bool expcond, logarithmic, density, EVEN_M, sech2 = true;
10491049

10501050
std::vector<Eigen::MatrixXd> potd, dpot, dpt2, dend;

expui/BiorthBasis.cc

Lines changed: 51 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1186,6 +1186,7 @@ namespace BasisClasses
11861186
"rcylmin",
11871187
"rcylmax",
11881188
"acyl",
1189+
"bias",
11891190
"hcyl",
11901191
"sech2",
11911192
"snr",
@@ -1376,6 +1377,7 @@ namespace BasisClasses
13761377
rcylmin = 0.001;
13771378
rcylmax = 20.0;
13781379
acyl = 0.01;
1380+
bias = 1.0;
13791381
hcyl = 0.002;
13801382
nmax = 18;
13811383
mmax = 6;
@@ -1400,7 +1402,7 @@ namespace BasisClasses
14001402
EVEN_M = false;
14011403
cmapR = 1;
14021404
cmapZ = 1;
1403-
mtype = "exponential";
1405+
mtype = "Exponential";
14041406
dtype = "exponential";
14051407
vflag = 0;
14061408

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

14561458
if (conf["acyl" ]) acyl = conf["acyl" ].as<double>();
1459+
if (conf["bias" ]) bias = conf["bias" ].as<double>();
14571460
if (conf["hcyl" ]) hcyl = conf["hcyl" ].as<double>();
14581461
if (conf["sech2" ]) sech2 = conf["sech2" ].as<bool>();
14591462
if (conf["lmaxfid" ]) lmaxfid = conf["lmaxfid" ].as<int>();
@@ -1540,6 +1543,17 @@ namespace BasisClasses
15401543
pnum = std::max<int>(1, pnum);
15411544
tnum = std::max<int>(10, tnum);
15421545

1546+
// Validate bias parameter
1547+
//
1548+
if (!std::isfinite(bias)) {
1549+
throw std::runtime_error("Cylindrical: 'bias' parameter must be finite");
1550+
}
1551+
if (bias <= 0.0) {
1552+
std::ostringstream sout;
1553+
sout << "Cylindrical: 'bias' parameter must be positive, got " << bias;
1554+
throw std::runtime_error(sout.str());
1555+
}
1556+
15431557
EmpCylSL::RMIN = rcylmin;
15441558
EmpCylSL::RMAX = rcylmax;
15451559
EmpCylSL::NUMX = ncylnx;
@@ -1550,6 +1564,40 @@ namespace BasisClasses
15501564
EmpCylSL::logarithmic = logarithmic;
15511565
EmpCylSL::VFLAG = vflag;
15521566

1567+
// Set deprojected model type
1568+
//
1569+
// Convert mtype string to lower case
1570+
std::transform(mtype.begin(), mtype.end(), mtype.begin(),
1571+
[](unsigned char c){ return std::tolower(c); });
1572+
1573+
// Set EmpCylSL mtype. This is the spherical function used to
1574+
// generate the EOF basis. If "deproject" is set, this will be
1575+
// overriden in EmpCylSL.
1576+
//
1577+
EmpCylSL::mtype = EmpCylSL::Exponential; // Default
1578+
if (mtype.compare("exponential")==0) {
1579+
EmpCylSL::mtype = EmpCylSL::Exponential;
1580+
if (myid==0) {
1581+
std::cout << "---- Cylindrical: using original exponential deprojected disk for EOF conditioning" << std::endl;
1582+
std::cout << "---- Cylindrical: consider using the exact, spherically deprojected exponential with 'mtype: ExpSphere'" << std::endl;
1583+
}
1584+
} else if (mtype.compare("expsphere")==0)
1585+
EmpCylSL::mtype = EmpCylSL::ExpSphere;
1586+
else if (mtype.compare("gaussian")==0)
1587+
EmpCylSL::mtype = EmpCylSL::Gaussian;
1588+
else if (mtype.compare("plummer")==0)
1589+
EmpCylSL::mtype = EmpCylSL::Plummer;
1590+
else if (mtype.compare("power")==0) {
1591+
EmpCylSL::mtype = EmpCylSL::Power;
1592+
EmpCylSL::PPOW = ppow;
1593+
} else {
1594+
if (myid==0) std::cout << "No EmpCylSL EmpModel named <"
1595+
<< mtype << ">, valid types are: "
1596+
<< "Exponential, ExpSphere, Gaussian, Plummer, Power "
1597+
<< "(not case sensitive)" << std::endl;
1598+
throw std::runtime_error("Cylindrical:initialize: EmpCylSL bad parameter");
1599+
}
1600+
15531601
// Check for non-null cache file name. This must be specified
15541602
// to prevent recomputation and unexpected behavior.
15551603
//
@@ -1565,7 +1613,7 @@ namespace BasisClasses
15651613
// Make the empirical orthogonal basis instance
15661614
//
15671615
sl = std::make_shared<EmpCylSL>
1568-
(nmaxfid, lmaxfid, mmax, nmax, acyl, hcyl, ncylodd, cachename);
1616+
(nmaxfid, lmaxfid, mmax, nmax, bias*acyl, hcyl, ncylodd, cachename);
15691617

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

1601-
// Convert mtype string to lower case
1602-
//
1603-
std::transform(mtype.begin(), mtype.end(), mtype.begin(),
1604-
[](unsigned char c){ return std::tolower(c); });
1605-
1606-
// Set EmpCylSL mtype. This is the spherical function used to
1607-
// generate the EOF basis. If "deproject" is set, this will be
1608-
// overriden in EmpCylSL.
1609-
//
1610-
EmpCylSL::mtype = EmpCylSL::Exponential;
1611-
if (mtype.compare("exponential")==0)
1612-
EmpCylSL::mtype = EmpCylSL::Exponential;
1613-
else if (mtype.compare("gaussian")==0)
1614-
EmpCylSL::mtype = EmpCylSL::Gaussian;
1615-
else if (mtype.compare("plummer")==0)
1616-
EmpCylSL::mtype = EmpCylSL::Plummer;
1617-
else if (mtype.compare("power")==0) {
1618-
EmpCylSL::mtype = EmpCylSL::Power;
1619-
EmpCylSL::PPOW = ppow;
1620-
} else {
1621-
if (myid==0) std::cout << "No EmpCylSL EmpModel named <"
1622-
<< mtype << ">, valid types are: "
1623-
<< "Exponential, Gaussian, Plummer, Power "
1624-
<< "(not case sensitive)" << std::endl;
1625-
throw std::runtime_error("Cylindrical:initialize: EmpCylSL bad parameter");
1626-
}
1627-
16281649
// Convert dtype string to lower case
16291650
//
16301651
std::transform(dtype.begin(), dtype.end(), dtype.begin(),
@@ -5184,6 +5205,7 @@ namespace BasisClasses
51845205
file.createAttribute<double>("rcylmin", HighFive::DataSpace::From(rcylmin)).write(rcylmin);
51855206
file.createAttribute<double>("rcylmax", HighFive::DataSpace::From(rcylmax)).write(rcylmax);
51865207
file.createAttribute<double>("acyl", HighFive::DataSpace::From(acyl)).write(acyl);
5208+
file.createAttribute<double>("bias", HighFive::DataSpace::From(bias)).write(bias);
51875209
file.createAttribute<double>("hcyl", HighFive::DataSpace::From(hcyl)).write(hcyl);
51885210
}
51895211

exputil/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ set(QUAD_SRC qadapt.cc gauleg.cc qadapt2d.cc gint2.cc rombe2d.cc
1111
set(UTIL_SRC nrutil.cc elemfunc.cc euler.cc euler_slater.cc
1212
rotmatrix.cc wordSplit.cc FileUtils.cc BarrierWrapper.cc
1313
stack.cc localmpi.cc TableGrid.cc writePVD.cc libvars.cc
14-
TransformFFT.cc QDHT.cc YamlCheck.cc # Hankel.cc
14+
TransformFFT.cc QDHT.cc YamlCheck.cc ExpDeproj.cc # Hankel.cc
1515
parseVersionString.cc EXPmath.cc laguerre_polynomial.cpp
1616
YamlConfig.cc orthoTest.cc OrthoFunction.cc VtkGrid.cc
1717
Sutils.cc fpetrap.cc)

exputil/EmpCylSL.cc

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include <thread>
1818
#include "exp_thread.h"
1919
#include "EXPException.H"
20+
#include "ExpDeproj.H"
2021
#include "exputils.H"
2122

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

7475
std::map<EmpCylSL::EmpModel, std::string> EmpCylSL::EmpModelLabs =
7576
{ {Exponential, "Exponential"},
77+
{ExpSphere, "ExpSphere" },
7678
{Gaussian, "Gaussian" },
7779
{Plummer, "Plummer" },
7880
{Power, "Power" },
@@ -552,6 +554,9 @@ double EmpCylSL::massR(double R)
552554
case Exponential:
553555
ans = 1.0 - (1.0 + R)*exp(-R);
554556
break;
557+
case ExpSphere:
558+
ans = expdeproj.mass(R);
559+
break;
555560
case Gaussian:
556561
arg = 0.5*R*R;
557562
ans = 1.0 - exp(-arg);
@@ -589,6 +594,9 @@ double EmpCylSL::densR(double R)
589594
case Exponential:
590595
ans = exp(-R)/(4.0*M_PI*R);
591596
break;
597+
case ExpSphere:
598+
ans = expdeproj.density(R);
599+
break;
592600
case Gaussian:
593601
arg = 0.5*R*R;
594602
ans = exp(-arg)/(4.0*M_PI*R);
@@ -2403,6 +2411,15 @@ void EmpCylSL::generate_eof(int numr, int nump, int numt,
24032411

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

2414+
// Debug info output
2415+
//
2416+
if (VFLAG & 8 && myid==0) {
2417+
std::cout << "---- EmpCylSL: Generating EOF with"
2418+
<< " Rmin=" << xi_to_r(XMIN)
2419+
<< " Rmax=" << xi_to_r(XMAX)
2420+
<< " numt=" << numt << std::endl;
2421+
}
2422+
24062423
// *** Radial quadrature loop
24072424
//
24082425
for (int qr=0; qr<numr; qr++) {
@@ -7531,6 +7548,7 @@ bool EmpCylSL::ReadH5Cache()
75317548

75327549
if (not checkStr(geometry, "geometry")) return false;
75337550
if (not checkStr(forceID, "forceID")) return false;
7551+
if (not checkStr(model, "model")) return false;
75347552
if (not checkInt(MMAX, "mmax")) return false;
75357553
if (not checkInt(NUMX, "numx")) return false;
75367554
if (not checkInt(NUMY, "numy")) return false;

exputil/ExpDeproj.cc

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
#include "ExpDeproj.H"
2+
3+
#include <cmath>
4+
#include <stdexcept>
5+
#include <iostream>
6+
#include <fstream>
7+
#include <cassert>
8+
#include <vector>
9+
#include <algorithm>
10+
#include <numeric>
11+
#include <functional>
12+
13+
#include "EXPmath.H"
14+
15+
16+
void ExpDeproj::initialize()
17+
{
18+
if (ngrid < 2) {
19+
throw std::invalid_argument("n must be at least 2");
20+
}
21+
22+
rv.resize(ngrid);
23+
mv.resize(ngrid);
24+
25+
std::vector<double> dv(ngrid);
26+
27+
double log_rmin = std::log(rmin);
28+
double log_rmax = std::log(rmax);
29+
double dlogr = (log_rmax - log_rmin)/static_cast<double>(ngrid - 1);
30+
31+
for (int i = 0; i < ngrid; ++i) {
32+
rv[i] = std::exp(log_rmin + i*dlogr);
33+
dv[i] = 4.0*M_PI*rv[i]*rv[i]*density(rv[i]);
34+
}
35+
36+
mv[0] = 0.0;
37+
for (int i=1; i<ngrid; i++)
38+
mv[i] = mv[i-1] + 0.5*(dv[i] + dv[i-1])*(rv[i] - rv[i-1]);
39+
}
40+
41+
double ExpDeproj::density(double R)
42+
{
43+
if (R < 0) {
44+
throw std::invalid_argument("R must be non-negative");
45+
}
46+
return 0.5*EXPmath::cyl_bessel_k(0.0, R)/(M_PI*M_PI);
47+
}
48+
49+
double ExpDeproj::mass(double R)
50+
{
51+
if (R < 0) {
52+
throw std::invalid_argument("R must be non-negative");
53+
}
54+
55+
if (R < rmin) return 0.0;
56+
if (R > rmax) return mv.back();
57+
58+
auto it = std::lower_bound(rv.begin(), rv.end(), R);
59+
// If R is slightly larger than the largest grid point due to rounding,
60+
// lower_bound may return rv.end(); in that case, use the last mass value.
61+
if (it == rv.end()) {
62+
return mv.back();
63+
}
64+
65+
std::size_t idx = static_cast<std::size_t>(std::distance(rv.begin(), it));
66+
if (idx >= rv.size()) {
67+
// Defensive guard; should not happen after the it == rv.end() check.
68+
return mv.back();
69+
}
70+
if (rv[idx] == R) {
71+
return mv[idx];
72+
} else {
73+
// Ensure idx-1 is valid
74+
if (idx == 0) idx++;
75+
// Linear interpolation
76+
double x0 = rv[idx-1];
77+
double x1 = rv[idx ];
78+
double y0 = mv[idx-1];
79+
double y1 = mv[idx ];
80+
81+
return y0 + (y1 - y0)*(R - x0)/(x1 - x0);
82+
}
83+
}

include/EmpCylSL.H

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include <dmalloc.h>
1919
#endif
2020

21+
#include "ExpDeproj.H"
2122
#include "Particle.H"
2223
#include "SLGridMP2.H"
2324
#include "coef.H"
@@ -81,6 +82,9 @@ protected:
8182

8283
int rank2, rank3;
8384

85+
//! Precomputed exponential deprojection profile
86+
ExpDeproj expdeproj;
87+
8488
//@{
8589
//! Storage buffers for MPI
8690
std::vector<double> MPIin, MPIout, MPIin2, MPIout2;
@@ -364,6 +368,7 @@ public:
364368
//! Type of density model to use
365369
enum EmpModel {
366370
Exponential,
371+
ExpSphere,
367372
Gaussian,
368373
Plummer,
369374
Power,
@@ -427,7 +432,21 @@ public:
427432
//! No extrapolating beyond grid (default: false)
428433
static bool enforce_limits;
429434

430-
//! Density model type
435+
/**
436+
@brief Density model type
437+
Available options:
438+
- Exponential = approximate exponential disk deprojection
439+
- ExpSphere = exact exponential spherical deprojection
440+
- Gaussian = Gaussian sphere
441+
- Plummer = Plummer sphere
442+
- Power = power-law sphere
443+
- Deproject = numerical deprojection of axisymmetric disk surface density (see create_deprojection)
444+
445+
@note The default option is Exponential for backwards
446+
compatibility. The ExpSphere is the exact spherical deproject
447+
for the exponential disk and should be used instead of
448+
Exponential for new projects.
449+
*/
431450
static EmpModel mtype;
432451

433452
//! Radial basis grid in radial direction

include/ExpDeproj.H

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#pragma once
2+
3+
#include <vector>
4+
#include <cmath>
5+
6+
class ExpDeproj
7+
{
8+
const double rmin = 1.0e-4;
9+
const double rmax = 30.0;
10+
11+
// Precomputed radial grid
12+
int ngrid;
13+
std::vector<double> rv, mv;
14+
void initialize();
15+
16+
public:
17+
//! Constructor
18+
ExpDeproj(int n=4000) : ngrid(n) { initialize(); }
19+
20+
//! Destructor
21+
virtual ~ExpDeproj() {}
22+
23+
//! Density profile at radius R
24+
double density(double R);
25+
26+
//! Enclosed mass at radius R
27+
double mass(double R);
28+
};

0 commit comments

Comments
 (0)