The fix and issue text were assisted by Claude
Summary:
gq_writer has two write_gq_values overloads. The std::vector one
drops the first num_constrained_params_ values before writing; the
Eigen one does not. Only the multi-chain path of standalone_generate
uses the Eigen overload, so generate_quantities num_chains=N writes
each row with the constrained parameters prepended, while the header
lists only the generated quantities.
src/stan/services/util/gq_writer.hpp, Eigen overload:
model.write_array(rng, draw, values, false, true, &ss);
...
sample_writer_(values); // parameters + generated quantities
versus the std::vector overload:
std::vector<double> gq_values(values.begin() + num_constrained_params_,
values.end());
sample_writer_(gq_values); // generated quantities only
The result is a CSV whose rows have num_params more fields than the
header has names, so any positional reader mis-assigns every column.
Only generate_quantities num_chains=N with N > 1 in a single process
is affected, which in practice means the CmdStan command line. CmdStanR
and CmdStanPy both launch one process per chain, so each process runs
with num_chains = 1 and is delegated to the single-chain overload;
RStan's standalone_gqs() calls that overload directly. All three
therefore take the std::vector path and are unaffected.
Reproducible Steps:
gq_rng.stan:
parameters {
real theta;
}
model {
theta ~ std_normal();
}
generated quantities {
real u = uniform_rng(0, 1);
}
./gq_rng sample num_warmup=200 num_samples=4 output file=fit.csv random seed=1
cp fit.csv fit_1.csv; cp fit.csv fit_2.csv
./gq_rng generate_quantities fitted_params=fit.csv num_chains=2 output file=gqmc.csv random seed=999
Current Output:
gqmc_1.csv — one column name, two values per row; the leading value is
theta, a parameter:
u
-1.871546, 0.89641628
-0.60661605, 0.63008851
-0.36586517, 0.17265139
-0.96383407, 0.36561701
Expected Output:
u
0.89641628
0.63008851
0.17265139
0.36561701
Additional Information:
Fix is to slice in the Eigen overload as the std::vector one does:
plain_type_t<EigVec> gq_values
= values.tail(values.size() - num_constrained_params_);
sample_writer_(gq_values);
Note this changes the output of generate_quantities num_chains=N by
removing columns, so it deserves a changelog entry for anyone whose
parser adapted to the current behaviour.
Minor, separate: the two paths also disagree on the separator. The
Eigen writer emits ", " where the std::vector writer emits ",".
Not addressed here.
The regression test asserts that each row of a two-chain run has as
many comma-separated fields as the header has names.
It fails on develop and passes with the fix. PR #3402 changes the rng seed
behavior which changes the output, and after #3402 is merged, the test for this
needs to be updated.
Current Version:
v2.39.0 (also reproduced on develop: cmdstan b6c5aa2, stan
d39d26ba7).
The fix and issue text were assisted by Claude
Summary:
gq_writerhas twowrite_gq_valuesoverloads. Thestd::vectoronedrops the first
num_constrained_params_values before writing; theEigen one does not. Only the multi-chain path of
standalone_generateuses the Eigen overload, so
generate_quantities num_chains=Nwriteseach row with the constrained parameters prepended, while the header
lists only the generated quantities.
src/stan/services/util/gq_writer.hpp, Eigen overload:versus the
std::vectoroverload:The result is a CSV whose rows have
num_paramsmore fields than theheader has names, so any positional reader mis-assigns every column.
Only
generate_quantities num_chains=NwithN > 1in a single processis affected, which in practice means the CmdStan command line. CmdStanR
and CmdStanPy both launch one process per chain, so each process runs
with
num_chains = 1and is delegated to the single-chain overload;RStan's
standalone_gqs()calls that overload directly. All threetherefore take the
std::vectorpath and are unaffected.Reproducible Steps:
gq_rng.stan:Current Output:
gqmc_1.csv— one column name, two values per row; the leading value istheta, a parameter:Expected Output:
Additional Information:
Fix is to slice in the Eigen overload as the
std::vectorone does:Note this changes the output of
generate_quantities num_chains=Nbyremoving columns, so it deserves a changelog entry for anyone whose
parser adapted to the current behaviour.
Minor, separate: the two paths also disagree on the separator. The
Eigen writer emits
", "where thestd::vectorwriter emits",".Not addressed here.
The regression test asserts that each row of a two-chain run has as
many comma-separated fields as the header has names.
It fails on
developand passes with the fix. PR #3402 changes the rng seedbehavior which changes the output, and after #3402 is merged, the test for this
needs to be updated.
Current Version:
v2.39.0 (also reproduced on
develop: cmdstanb6c5aa2, stand39d26ba7).