@@ -456,6 +456,99 @@ function SemScoresPredictMethod(method::Symbol)
456456 end
457457end
458458
459+ """
460+ score_basis_transform(model::SemLoss, params; method = :regression,
461+ latent_vars = nothing, alpha = 0,
462+ prior_cov_alpha = nothing)
463+
464+ Return the centered latent-score basis transform associated with a score-prediction
465+ method.
466+
467+ For [`SemRegressionScores`](@ref) and [`SemBartlettScores`](@ref), the result is the
468+ identity transform. For [`SemAndersonRubinScores`](@ref), the result stores the whitening
469+ map between the model's original latent-variable basis and the Anderson-Rubin basis.
470+
471+ The returned object can be used to map centered Anderson-Rubin scores back to the
472+ original latent-variable basis via:
473+
474+ ```julia
475+ orig_scores = transform(ar_scores; inverse = true)
476+ ```
477+
478+ `alpha` matters only for [`SemAndersonRubinScores`](@ref), because the whitening
479+ transform is built from the ridge-regularized Bartlett score operator. For regression and
480+ Bartlett scores the transform is the identity, so `alpha` does not affect the result.
481+
482+ `prior_cov_alpha` is currently accepted for API symmetry with
483+ [`predict_latent_scores`](@ref) and for forward compatibility, but it does not affect the
484+ returned transform for the currently implemented score methods.
485+ """
486+ function score_basis_transform (
487+ method:: SemScoresPredictMethod ,
488+ model:: SemLoss ,
489+ params:: AbstractVector ;
490+ latent_vars:: Union{AbstractVector, Nothing} = nothing ,
491+ alpha:: Number = 0 ,
492+ prior_cov_alpha:: Union{Number, Nothing} = nothing ,
493+ )
494+ length (params) == nparams (model) || throw (
495+ DimensionMismatch (
496+ " The length of parameters vector ($(length (params)) ) does not match the number of parameters in the model ($(nparams (model)) )." ,
497+ ),
498+ )
499+ alpha >= 0 ||
500+ throw (ArgumentError (" The regularization parameter alpha must be non-negative" ))
501+ isnothing (prior_cov_alpha) ||
502+ prior_cov_alpha >= 0 ||
503+ throw (
504+ ArgumentError (
505+ " The regularization parameter prior_cov_alpha must be non-negative" ,
506+ ),
507+ )
508+
509+ implied = imply (model)
510+ ram = implied. ram_matrices
511+ lvar_inds =
512+ check_var_indices (ram, latent_vars, allow_observed = false , normalize = true )
513+ lvars = vars (ram)[lvar_inds]
514+ if ! (method isa SemAndersonRubinScores) # identity transform
515+ return SemVariablesTransform (lvars, I, I)
516+ end
517+
518+ update! (EvaluationTargets (0.0 , nothing , nothing ), implied, params)
519+
520+ A = materialize (ram. A, params)
521+ S = materialize (ram. S, params)
522+ T = float (promote_type (eltype (A), eltype (S), typeof (alpha)))
523+
524+ I_A = Matrix {eltype(A)} (I, size (A, 1 ), size (A, 2 )) - A
525+ lv_I_A⁻¹ = inverse_rows (I_A, lvar_inds)
526+ base_solver =
527+ QRScoresSolver (implied, lvar_inds, A, S, lv_I_A⁻¹; alpha, prior_cov_alpha = 0 )
528+ nobs = nobserved_vars (implied)
529+ base_op = permutedims (base_solver (Matrix {T} (I, nobs, nobs)))
530+ score_cov = Symmetric (X_A_Xt (implied. Σ, base_op))
531+ score_cov_chol = cholesky! (score_cov)
532+
533+ return SemVariablesTransform (lvars, I / score_cov_chol. U, score_cov_chol. U)
534+ end
535+
536+ score_basis_transform (
537+ method:: Symbol ,
538+ model:: SemLoss ,
539+ params:: Union{AbstractVector, Nothing} = nothing ;
540+ latent_vars:: Union{AbstractVector, Nothing} = nothing ,
541+ alpha:: Number = 0 ,
542+ prior_cov_alpha:: Union{Number, Nothing} = nothing ,
543+ ) = score_basis_transform (
544+ SemScoresPredictMethod (method),
545+ model,
546+ params;
547+ latent_vars,
548+ alpha,
549+ prior_cov_alpha,
550+ )
551+
459552predict_latent_scores (
460553 fit:: SemFit ,
461554 data:: SemObserved = observed (sem_term (fit. model));
0 commit comments