rcompat-lbfgsb
rcompat-lbfgsb is a pure Rust crate that aims to reproduce the
user-visible behavior of R's stats::optim(..., method = "L-BFGS-B").
The crate is a compatibility layer, not a general optimizer project. Its public
API intentionally keeps R-like control names such as fnscale, parscale,
ndeps, factr, pgtol, and lmm.
Clean-room implementation
rcompat-lbfgsb is not an official R project and does not copy or translate R
source code. Compatibility is developed from public documentation and from
parity fixtures generated by running R as an external reference during
development. The crate does not require R at runtime.
Current status
This is an early implementation pass. The R-compatible scaling layer, validation
rules, finite-difference gradients, public result model, and an in-tree bounded
limited-memory quasi-Newton backend are implemented. The committed parity
fixtures currently compare par, value, counts, convergence, and
message against R-generated reference data for quadratic, Rosenbrock,
bound-active, fixed-parameter, infinite-bound, scaling, finite-difference,
iteration-limit, and compact objective-only DESeq2-derived negative-binomial
GLM cases.
For multi-dimensional objective-only problems with fully finite bounds, such as
DESeq2's [-30, 30] coefficient box, the native backend now uses the same main
Cauchy/subspace path as supplied-gradient problems after building
finite-difference gradients. Bound-active finite differences also preserve R's
observed objective-call order, including forward-then-clamped-base calls for
one-sided lower-bound coordinates. R's projected-gradient norm near finite
bounds is also preserved by capping the component by the remaining distance to
the relevant bound. A compact synthetic fixture guards R's line-search
memory-refresh behavior in a finite-box objective-only nonconvex case. The
committed hard-real DESeq2 guard covers 48 direct R objective-only optimizer
replays and currently has exact optimizer-count parity for 36 of them, with
worst parameter drift at 0.0034756018 and worst optimizer-count drift bounded
at 8.
Known early limitations:
- trace output is not byte-for-byte compatible with R
- parity coverage is still small and should be expanded before claiming broad compatibility
- the backend is implemented in this crate and still needs more adversarial fixture tuning
- fixed parameters require a supplied gradient, matching R's observed behavior for no-gradient finite differences
- DESeq2-derived fixture parameters can be less strict than likelihood and count parity in weakly identified directions
Example
use ;
let par = vec!;
let bounds = new?;
let control = default_for_dimension;
let result = optim_lbfgsb?;
assert!;
assert!;
# Ok::
Example with a supplied gradient
use ;
let result = optim_lbfgsb_with_gradient?;
assert!;
# Ok::
Semantics
parscalemaps user parameters to internal parameters withinternal_x[i] = user_par[i] / parscale[i].fnscalemaps user objective values to internal minimization values withinternal_value = user_value / fnscale.- Negative
fnscalesupports R-style maximization. ndepsis interpreted in the scaled parameter space for finite-difference gradients.- Returned
OptimResult.parandOptimResult.valueare in the user's original scale.
Runtime relationship to R
The crate does not call R, link against R, shell out to R, embed R, or depend on R at runtime. Optional fixture-generation scripts may be run manually by developers with R installed.