phit

Reference

API overview

The extension registers the PHP module phit (extension=phit, Composer ext-phit). Every function is in the Phit namespace and is named by prefix.

Prefix or name Purpose Reference
fit_lm, fit_glm, fit_lmer, fit_glmer, model_matrix, predict Formula models and prediction Model formulas
fit_ols, fit_logistic, fit_probit, fit_poisson, fit_mixed_intercept, fit_mixed, fit_logistic_mixed Models on numeric matrices Matrix model functions
dist_* Densities, CDFs, quantiles and seeded samples Distributions and utilities
stat_* Descriptive statistics, kernel density, interpolation and the ECDF Distributions and utilities
draws_* Weighted posterior-draw summaries and transforms Distributions and utilities
optimize, uniroot One-dimensional minimization and root finding Distributions and utilities
permutation_rng Seeded permutations and samples without replacement Seeded sampling
read_csv, CsvReader Reading delimited text files Reading CSV files

Import the functions you use:

use function Phit\{fit_lm, predict, dist_normal_quantile};

stubs/phit.php in the repository declares every signature with PHPDoc array shapes for IDEs and static analysis. Do not load it at runtime.

PHP entry points

Phit\fit_ols(array $x, array $y, string $missing = 'drop', ?array $weights = null, ?array $offset = null, bool $intercept = true): array;
Phit\fit_logistic(array $x, array $y, string $missing = 'drop', ?array $weights = null, ?array $offset = null, bool $intercept = true): array;
Phit\fit_probit(array $x, array $y, string $missing = 'drop', ?array $weights = null, ?array $offset = null, bool $intercept = true): array;
Phit\fit_poisson(array $x, array $y, string $missing = 'drop', ?array $weights = null, ?array $offset = null, bool $intercept = true): array;

// Formula interfaces over the same cores; $data is named columns or an object with toModelData()/toColumns().
Phit\model_matrix(string $formula, array|object $data, string $missing = 'drop'): array;
Phit\fit_lm(string $formula, array|object $data, string $missing = 'drop', array|string|null $weights = null): array;
Phit\fit_glm(string $formula, array|object $data, string $family = 'gaussian', string $link = '', string $missing = 'drop', array|string|null $weights = null): array;
Phit\fit_lmer(string $formula, array|object $data, bool $reml = true, string $missing = 'drop', array|string|null $weights = null, ...): array;
Phit\fit_glmer(string $formula, array|object $data, string $family = 'binomial', string $link = '', string $missing = 'drop', array|string|null $weights = null, ...): array;

All estimators are synchronous and stateless: each returns a complete fit without retaining a model handle, input data or mutable configuration. It does not mutate the caller’s arrays. Repeated fits are independent.

Formula interfaces

The formula functions parse an R model formula, build R’s model.matrix() over named columns, and call the estimation cores of fit_ols, fit_logistic, fit_probit, fit_poisson, fit_mixed, and fit_logistic_mixed. Each fit returns the complete result contract of its matrix function. It adds formula, response, columns (coefficient names in coefficient order), terms, assign, intercept, and named (column => coefficient). fit_glm and fit_glmer also return family and link. model_matrix returns y, x (including the (Intercept) column), the naming keys, offset, random designs in the shape fit_mixed accepts, and the deletion accounting.

Missing-value deletion covers only the variables a formula uses, with the same warning, missing: 'raise' behavior, and row accounting as the matrix functions. model_matrix keeps unused factor levels, as model.matrix() does. The fitting functions drop them, as lm(), glm(), and lme4 do. Unknown columns, malformed factor descriptors, referenced list-columns, and invalid formulas raise 1001. Column-length mismatch raises 1002. Infinite transform results raise 1003. Unsupported functions, families, links, and random-effect structures raise 1007. Rank deficiency raises 1004. The formula guide defines the syntax, contrasts, data shapes, verification, and every known difference from R.

Matrix model contracts

Matrix model functions defines the inputs, results, convergence rules and failure cases of each matrix function:

Fixed intercept

The seven matrix estimators take bool $intercept = true. Supply predictor columns only: the binding prepends an exact ones column and the estimated intercept is coefficient 0, followed by predictors in supplied column order. Covariance, standard errors, tests and intervals follow that same order. Parameter counts and residual degrees of freedom include the added coefficient. No input array is mutated.

intercept: false passes the supplied x matrix literally. It is both the opt-out for a through-origin model and the compatibility option for callers already supplying a complete design with ones. There is no automatic detection of existing constant columns: leaving ones in x with the default creates a duplicate intercept and a rank-deficiency error, rather than silently guessing intent.

For an intercept-only fit, supply one empty predictor row per outcome, such as x: array_fill(0, count($y), []). An empty sample or ragged matrix is still invalid; empty predictor rows require intercept: true. Missing-row validation and deletion still apply to outcomes, predictors, groups, weights and offsets before fitting.

The Rust core takes the design literally.

Random-effect designs random[*].z remain explicit; this option only constructs the fixed design. For a random intercept/slope, z still needs [1, slope] rows.

Errors and conversion

The binding accepts actual integers and floats as numeric elements, with null and NAN representing missingness. PHP integers are converted to f64, so integers beyond the exact range of double precision may lose precision. Array keys and names do not label coefficients: row order aligns x with y, and column order aligns all coefficient results. Callers should normally supply packed lists and keep labels separately.

Missing values use listwise deletion by default, with one E_WARNING on a successful fit that omits rows. missing: ‘raise’ rejects missingness. Values are not coerced from strings, columns are not added, and aliased predictors are not removed. fitted and residuals follow retained_rows order. See missing-data.qmd for validation order, warnings, accounting and application handling. Applications can promote PHP warnings to exceptions.

Statistical and input failures use PHP Exception with stable numeric codes 1001–1008; see the diagnostic/error contract. There are no estimator-specific exception classes. Callers can inspect getCode() and should handle Throwable at their application boundary, since PHP argument and runtime errors use their own types. Message text may change. If the extension is absent, the functions themselves are unavailable; that is an installation failure rather than an invalid-data result.

NAN/INF are floating-point values in successful results, not failure sentinels. Serialization helpers may map them to null using a documented policy, while the native result preserves them. Current arithmetic uses f64; it is not arbitrary precision, and extreme input scales can exceed its range.

Rust boundary

pub fn fit_ols(
    x: Vec<Vec<f64>>,
    y: Vec<f64>,
) -> Result<OlsResult, StatsError>

The core owns its inputs and has no PHP dependency. OlsResult exposes the same statistical fields as the PHP array (sample-deletion metadata is PHP-only), with Vec for numeric lists, Vec<Vec> for covariance, Vec<[f64; 2]> for confidence intervals, f64 for scalars, and usize for residual degrees of freedom. StatsError implements Display and Error. The binding converts errors and values without implementing statistical formulas.

See design-philosophy.qmd for the boundary between this raw API and optional convenience layers.

Diagnostics and error codes

Every successful estimator returns diagnostics:

Diagnostic key Contract
converged true on successful return, including direct OLS; failed fitting throws
solver scaled_svd, newton_irls, fisher_scoring, profile_variance_search, bobyqa or nelder_mead
iterations, evaluations Present only when the corresponding historical top-level key exists; same value, not a second counter
gradient_max Present for logistic mixed only; same scaled-coordinate finite-difference score as its top-level key
boundary, singular Present when the estimator already reports that flag; legitimate zero variance is permitted
warnings list; missing-row deletion, a reported near-singular covariance, or the logistic-mixed Laplace approximation advisory

Warnings in this array are interpretation notices. Only listwise deletion also emits the existing single PHP E_WARNING. Missing/undefined diagnostics are omitted; there is no fabricated iteration count or universal score. General Gaussian convergence still uses optimizer termination, parameter bounds, valid factors and finite output; it does not have a final variance-parameter gradient/Hessian gate. OLS has no iterative optimizer. Normal/Wald inference and local convergence do not guarantee model adequacy or quadrature accuracy.

Statistical and input failures throw PHP Exception with a numeric getCode():

Code Rust ErrorKind Meaning
1001 InvalidInput Type/domain/control error, missing=‘raise’, invalid groups/weights/counts
1002 DimensionMismatch Shape/alignment or insufficient residual observations
1003 Nonfinite Nonfinite values or arithmetic beyond floating-point range, including nonzero residual variance underflow
1004 RankDeficiency Numerical design/information rank failure
1005 FactorizationFailure Required linear solve or positive-definite information factor fails
1006 Nonconvergence Budget/line-search/optimizer/score failure, separation or unsupported finite optimum at a search boundary
1007 Unsupported Explicit size/count limit or unimplemented configuration
1008 Internal Required decomposition output missing or an internal mathematical invariant fails

Messages remain explanatory, not stable machine identifiers. Validation can report the first failed check when multiple problems exist; combined validation checks may use the most relevant category. Zend argument TypeError, result conversion/runtime errors, and caught Rust panics are runtime errors rather than statistical codes. Invalid data are checked before listwise deletion. Rust StatsError::new(kind, message) carries the explicit enum without classifying message text; .0 remains the display message and kind() exposes its category.

Binomial calls limit trials to 1,000,000 per original row, and accepted success-count rounding is canonicalized with round(y*trials)/trials. For exact zero-variance OLS, the existing NaN/INF conventions remain; nonzero variance underflow instead throws 1003. JSON consumers must handle documented undefined OLS quantities explicitly; PHP’s JSON_THROW_ON_ERROR rejects NaN/INF. See the v0.1 assessment for compatibility notes and tested stress ranges.

From phpstats/phit@76b22f0.