Skip to content

Implementing alignment based optimization for single-cell data - #72

Open
scjlee wants to merge 46 commits into
artyomovlab:mainfrom
scjlee:dsa_optimization
Open

Implementing alignment based optimization for single-cell data#72
scjlee wants to merge 46 commits into
artyomovlab:mainfrom
scjlee:dsa_optimization

Conversation

@scjlee

@scjlee scjlee commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator
  1. Implement new optimization algorithm for single-cell data. src/optimization_dsa

    • This algorithm add an additional alignment term to the loss function of positivity optimization.
  2. Use match.arg function check if user input method is valid in optim_config. R/opimization

  3. Implement new logger system. src/optimization_logger

    • The new error logger records errors as a long format data.frame. This data.frame contains 3 columns: {iteration, metric_name, value}. This design increase the freedom to add/remove tracked values

    • For backward compatibility, the logger class contains a to_legacy_matrix method for converting data.frame to the old 21 column matrix .

    • Modify optimize_solution to support output of the history data.frame.

    • The long format could make plotting interface cleaner:
      image

    • Currently, only optimize_alignment use this new logger. All other algorithms are kept the same.

  4. Implement new error calculator. src/optimization_error_metrics.

    • User can define there own error terms as a IErrorCalculator class.
    • Calculators can be registered to a single CompositeErrorCalculator object which provide calculate method to compute all the registered error. This design is aim to reduce boilerplate code.
    • Implement 4 basic error calculators: DeconvErrorCalculator, HingeProportionsErrorCalculator, HingeBasisErrorCalculator, ScaleNormErrorCalculator.
    • Both hinge error calculator perform a single W (or H) reconstruction for both beta_error and neg_basis (or lambda_error and neg_props), which reduce total computation load when matrix is big.
    • Currently, only optimize_alignment use this new error calculator.. All other algorithms are kept the same.
  5. Implement GitHub action for automatic RcppExports and documents generation. .github/workflows

    • Each time when a new commit is pushed, run Rcpp::compileAttributes and roxygen2::roxygenise to make sure all the R-Cpp bindings and documentations are uptodate. If there is any change, GitHub action will automatically create a new commit with modified files.
    • Theoretically, it is same as running devtools::document. Once syncing is passed, package should be ready to install via remotes::install_github.
      image

@scjlee
scjlee requested a review from denklewer July 31, 2026 20:01
Comment thread R/optimization.R
#' @export
optim_config <- function(
method = "positivity", # positivity/coordinate_descent/theta
method = c("positivity", "coordinate_descent", "theta", "alignment"), # let R handel selection and checking

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

typo

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

handel is a typo

Comment thread src/Makevars
@@ -1,2 +1,3 @@
PKG_LIBS = $(LAPACK_LIBS) $(BLAS_LIBS) $(FLIBS)
PKG_CPPFLAGS = -DARMA_64BIT_WORD=1
CXX_STD = CXX17 No newline at end of file

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

why?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

GitHub action use C++20 but the current R which failed in CMD INSTALL step. Is most like to due to issue with RcppSpdlog.

Currently (2026/08/10) R use C++17 as the default compiler since version 4.4, and C++20 become default after 4.6 ‘where available’. I think it is save to use C++17 as our compiler because its is available (but not necessarily installed) on all known R platforms. according to CRAN.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

ok, let's keep it but can you please create an issue to support other versions. I think once we change logging as we discussed it should work

Comment thread src/optimization_alignment.h Outdated



//' Main training loop with all gradient steps. This is the main algorithm for now.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

add documentation please. looks like this one copied

Comment thread src/optimization_logger.h
#include <map>
#include <fstream>

class ILogger {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

shouldn't we have something like errortracker

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Popular framework like pytorch and tensorflow use logger for error tracking. I would like to keep this convention and don't see too much issue still. But I defiantly open to discuss alternative naming.

And I don't see it will be confused with spdlog which is usually called as spdl or spdlog.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I understand this but if a new guy will debug error he met. He will try to search "logger" first and will be transferred to the class which is not related to logger

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Sorry, I don't understand why logger is closely related to debugging Could you provide some examples?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I meet error in DEBUG or ERROR log. I open vscode and search "log" or "logger" to find the places where error logging happened, to investigate the error. But will find this class, which does not do this logging. So I will spend some time trying to figure it out.

Comment thread .github/workflows/rcpp-roxygen-sync.yml
Comment thread src/optimization_error_metrics.h
Comment thread src/optimization_error_metrics.h
Comment thread src/optimization_logger.h
Comment thread src/optimization_logger.h
Comment thread src/optimization_logger.h Outdated
}
};

class CsvLogger : public ILogger {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

probably not useful for us

@scjlee
scjlee force-pushed the dsa_optimization branch 2 times, most recently from 7ab323c to 21639e3 Compare August 7, 2026 00:27
scjlee and others added 26 commits August 10, 2026 10:18
Comment thread R/initialization.R
D_h = Dh
))
},
legacy_random_invertible = function(proj, kwargs = NULL) {

@scjlee scjlee Aug 10, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

This is the "quasi-old" method I used to test the original alignment optimization which failing with the newest initialization. Since the new PGD-based optimization works with new initialization so far. I can remove it if you like.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

we agreed to remove this

@scjlee scjlee left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

  • Implement new alignment optimization algorithm using projected gradient descent. It should fix the convergence problem encountered for the previous inverse-based optimization
  • Implement get_history and get_history_long function for accessing the optimization history. They only work with histories created by the new logger.
  • Explaining implementation choice to most of the previous comments.

@scjlee
scjlee requested a review from denklewer August 10, 2026 19:05
Comment thread R/DualSimplexSolver.R

#' @description
#' Get history of optimization.
get_history = function() {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

history$history sounds ambiguous. It can be confused with points history.
I think it should be error_log or metrics_log if you like, since it is what it is.
Also I think method should be called differently

Comment thread src/optimization_logger.h
virtual void log_metadata(const std::map<std::string, std::string>& metadata) = 0;
};

class RcppLogger : public ILogger {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

RcppLogger still looks like you are logging some Rcpp events, since this is only error tracking, I guess name should be transparent. Also I think the name should be directly related to the R naming, to improve code understanding (I mean we should avoid transforming entity RcppLogger into history$history

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I agree. Rename it to ErrorLogger or others will be better.

Meanwhile, I don't understand what do you mean "we should avoid transforming entity RcppLogger into history$history". Are you against to use logger system?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

No, I just mean that it should be transparent everywhere. both RcppLogger and history$history are to vague.
So I vote just for very similar names both in R and C++ for easy navigation.

Comment thread src/optimization_logger.h
#include <map>
#include <fstream>

class ILogger {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I understand this but if a new guy will debug error he met. He will try to search "logger" first and will be transferred to the class which is not related to logger

}

std::vector<Metric> HingeProportionsErrorCalculator::calculate(const OptimizationState& state) const {
arma::mat H = state.X * state.R;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

shouldn't we make it H_inf just in case?

Comment thread src/optimization_logger.h
class ILogger {
public:
virtual ~ILogger() = default;
virtual void log_metrics(int iteration, const std::vector<Metric>& metrics) = 0;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The name Ilogger does not give us clue of what this interface doing. Because if this is logger then it is expected that you will implement "log" method

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I don't understand. Doesn't this interface require its child classes have both log_metrics and log_metadata implementations?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is just naming issue for me. (As most of my comments, sorry!)
ILogger --> assumes we want log() method to be implemented.
for example IMetricsLogger -> means we want log_metrics() to be implemented.

Imagine you will have 10 implemented interfaces. It will be good if the name will be very transparent

@scjlee scjlee left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Can you resolve comments which you think is done? It is hard to track now.

Comment thread R/initialization.R
D_h = Dh
))
},
legacy_random_invertible = function(proj, kwargs = NULL) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

we agreed to remove this

Comment thread src/Makevars
@@ -1,2 +1,3 @@
PKG_LIBS = $(LAPACK_LIBS) $(BLAS_LIBS) $(FLIBS)
PKG_CPPFLAGS = -DARMA_64BIT_WORD=1
CXX_STD = CXX17 No newline at end of file

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

ok, let's keep it but can you please create an issue to support other versions. I think once we change logging as we discussed it should work

Comment thread src/optimization_logger.h
virtual void log_metadata(const std::map<std::string, std::string>& metadata) = 0;
};

class RcppLogger : public ILogger {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

No, I just mean that it should be transparent everywhere. both RcppLogger and history$history are to vague.
So I vote just for very similar names both in R and C++ for easy navigation.

Comment thread src/optimization_logger.h
class ILogger {
public:
virtual ~ILogger() = default;
virtual void log_metrics(int iteration, const std::vector<Metric>& metrics) = 0;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is just naming issue for me. (As most of my comments, sorry!)
ILogger --> assumes we want log() method to be implemented.
for example IMetricsLogger -> means we want log_metrics() to be implemented.

Imagine you will have 10 implemented interfaces. It will be good if the name will be very transparent

Comment thread src/optimization_logger.h
#include <map>
#include <fstream>

class ILogger {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I meet error in DEBUG or ERROR log. I open vscode and search "log" or "logger" to find the places where error logging happened, to investigate the error. But will find this class, which does not do this logging. So I will spend some time trying to figure it out.

Comment thread src/optimization_error_metrics.h
// Optimization Control Flow & Learning Rate Scheduling
// -------------------------------------------------------------
// TODO(cjlee): maybe we should compare to best_error_value instead?
double relative_change = (prev_error_value - current_error_value) / (std::abs(prev_error_value) + epsilon);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

don't we have this implemented in other optimizations using these ?

stop_criteria_window = 800,
convergence_tol = 1e-9,

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