Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add fitting functionality using Ceres-Solver #189

Open
wants to merge 14 commits into
base: master
Choose a base branch
from

Conversation

mmccrackan
Copy link
Contributor

@mmccrackan mmccrackan commented Oct 29, 2024

This branch is an attempt at adding fitting functionality to so3g using the Ceres-Solver library to support both non-linear least squares and general optimization problems. It is used to duplicate the existing noise-fitting function in sotodlib. The idea of this function is to:

  1. Find an initial guess for the white noise by calculating the median of the PSD above an input lower frequency bound.
  2. Do a least-squares fit of the 1/f component of the PSD to a power law in logspace to estimate the index and knee frequency.
  3. Run the minimization using a negative log likelihood function as the cost function.

The cost function classes and fitting functions have been written in a model-independent manner, so they should be easy to adapt for other data and model types. Ceres includes options for bounds/constraints and supports auto differentiation so derivatives and gradients don't need be calculated manually. The general minimization routine for Ceres doesn't seem to have uncertainty calculations (its least-squares routines do) so I have manually calculated the inverted Hessian matrix to get the parameter uncertainties.

I put most of the code into new fitting_ops.cxx and fitting_ops.h files instead of putting it all in array_ops.cxx. I added an array_ops.h file to hold shared functions declarations.

I've added a cmake directory with cmake files for Ceres, Eigen, Gflag, and Glog with the last 3 being dependencies of Ceres-Solver. We could move these into spt3g at some point, but this also seems to work in my tests. Eigen is a nice optimized vector, matrix, and linear algebra library that might be useful on its own, but it is not required to use Eigen when working with Ceres.

Building the Docker image for tests will take a few minutes longer now as I found that Ceres needed to be built from source as the necessary version doesn't appear to be available through apt-get libceres-dev.

@mmccrackan mmccrackan changed the title Adds fitting functionality using Ceres-Solver Add fitting functionality using Ceres-Solver Oct 29, 2024
@mmccrackan mmccrackan requested a review from mhasself November 4, 2024 15:41
@mmccrackan mmccrackan marked this pull request as ready for review November 4, 2024 15:41
Copy link
Member

@mhasself mhasself left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very interesting -- thanks for bringing this in and addressing such an important case.

I can't promise these are all my comments, yet, but it's a start...

int get_dtype(const bp::object &);

template <typename T>
T _calculate_median(const T*, const int);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add newline

Comment on lines +100 to +103
// Model independent Negative Log Likelihood for generalized
// unconstrained minimization
template <typename Model>
struct NegLogLikelihood
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's important to point out, in the comment and/or name of the class, that this is what you should use for fitting power spectra. One should use this only on data whose residuals are expected to be Chi2(1)-distributed. Is it accurate to call it PowerSpectrumCostFunction?

}

template <typename T>
bool _invert_matrix(const T* matrix, T* inverse, const int n) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was this code copyright-free?

Comment on lines +451 to +452
" p: parameter array (float32/64) with dimensions (ndets, nparams). This is modified in place.\n"
" c: uncertaintiy array (float32/64) with dimensions (ndets, nparams). This is modified in place.\n"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For p and c -- do the values going in matter? Or can it just be zeros/empty?

"method that minimizes a log likelihood. OMP is used to parallelize across dets (rows)."
"\n"
"Args:\n"
" f: frequency array (float32/64) with dimensions (nsamps,).\n"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are there requirements on f? Must it be positive definite; must it be strictly increasing?

Comment on lines +234 to +250
// index for f > lower fwhite
for (int i = 0; i < nsamps; ++i) {
if (f[i] > fwhite_lower) {
fwhite_i.push_back(i);
break;
}
}

// index for f < upper fwhite
for (int i = nsamps - 1; i >= 0; --i) {
if (f[i] < fwhite_upper) {
fwhite_i.push_back(i);
break;
}
}

int fwhite_size = fwhite_i[1] - fwhite_i[0] + 1;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks segfaulty. Make it robust against fwhite_lower / fwhite_upper pathologically dodging your tests. A good way is like

int fwhite_lo = 0;
while (fwhite_lo < nsamps && f[fwhite_lo] < fwhite_lower)
  fwhite_lo++;

fwhite_hi = fwhite_lo;
whilte (...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants