Conversation
CmdStan names a tuple element's columns with a colon (b_tuple:2.1.1) and a complex number's parts with .real and .imag. The name repair and variable_dims() in csv.R assumed every column was name.i.j, so a fit with such variables warned about NA sizes, its metadata was wrong and a variable filter could not select a tuple. The model methods rebuilt constrained values through utils::relist() from the compiled model's own dotted names, which mislabel arrays of tuples, and write_stan_json() had no way to write a tuple at all. Column names are now repaired piece by piece, keeping the colons (b_tuple:2[1,1], arr_pair[1]:2, z[real]), and the variable is the name before the first bracket or colon everywhere a name is filtered or sized. A complex variable's size has a trailing 2 for its parts and a tuple's is that of its outer array. constrain_variables() rebuilds its result from the model's constrained_param_names(), which replaces the skeleton, so variable_skeleton() and the param metadata C++ are gone. write_stan_json() gains a variables argument with the model's declarations and writes a tuple from an unnamed list; process_data() and the init writers pass the declarations, which moves the per-variable data conversions into the writer. The test model is PR #1174's with a data block added. Its array of tuples holds no complex value because CmdStan 2.40's JSON reader misreads a complex number inside an array of tuples. Closes #925.
5 tasks
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## v1.0 #1287 +/- ##
=======================================
Coverage ? 93.13%
=======================================
Files ? 19
Lines ? 7372
Branches ? 0
=======================================
Hits ? 6866
Misses ? 506
Partials ? 0 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Codex's review of the tuple and complex support found four defects. flatten_variables() stacked the real and imaginary matrices of a 2-d complex value instead of pairing the parts of each element, so every complex matrix went through unconstrain_variables() and write_stan_json() permuted. unflatten_leaves() laid values out in the order the columns arrived and read the dims off the last column, so a draws object with reordered or missing columns gave a permuted, truncated or recycled init and a tuple element with no columns shifted the later ones down. nest_cells() built seq(i, 0, by = 2) for an array of tuples with a zero extent after a nonzero one. list_to_array() rejected complex elements. Columns are now placed by their parsed indices, a missing element is an error, tuple slots are keyed by number and an empty one comes back as numeric(0). Trailing empty elements are padded from the declared tuple length when the caller has $variables(). Zero-size variables with no columns stay omitted from constrain_variables(), as they are from the draws, and the roxygen for the three transform methods now says so. Part of #925.
When a complex variable's columns are incomplete, complex() recycles the parts it has, so a draws object missing z[1,imag] initialized z with another element's value. unflatten_leaves() now requires one real and one imaginary column per element, and a complex array goes through the same per-cell check as an array of tuples. pad_tuple() iterated over the declaration, so a draws object with more tuple elements than the model declares lost the extra ones before write_stan_json() could reject them. It now pads up to the declared length and keeps the rest. A tuple element with no columns still comes back as numeric(0), which is the right value for a zero-length vector but has the wrong rank for an empty matrix. The declarations carry no sizes, so the shape can't be recovered; the return value of constrain_variables() documents it. Part of #925.
jgabry
marked this pull request as ready for review
September 24, 2026 13:38
Member
Author
|
@SteveBronder this closes #925, which is an issue you opened a while ago. Do you have time to briefly try out this branch just to see if it behaves as you'd expect for complex and tuple types? |
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Submission Checklist
Summary
AI was used in writing the code for this PR and the summary below. I have reviewed all code.
Supersedes #1174 and reuses its test model. Closes #925.
CmdStan names a tuple element's columns with a colon (
b_tuple:2.1.1)and a complex number's parts with
.realand.imag. Our name repairand
variable_dims()assumed every column wasname.i.j, so a fit withsuch variables warned about NA sizes, its metadata was wrong and a
variable filter could not select a tuple. Two more layers had the same
gap: the model methods rebuilt constrained values through
utils::relist()from the compiled model's own dotted names, whichmislabel arrays of tuples, and
write_stan_json()could not write atuple at all, so tuple data and inits were unusable.
What changes:
b_tuple:2[1,1],arr_pair[1]:2,z[real],zv[1,imag]. This is whatposterior already does with them. A variable is the name before the
first
[or:everywhere names are filtered or sized, sofit$draws("b_tuple")selects all of a tuple's columns.stan_variable_sizesgives a complex variable a trailing 2 and atuple the size of its outer array.
$constrain_variables()rebuilds its result from the model'sconstrained_param_names(): a complex value comes back as an Rcomplex number or array, a tuple as an unnamed list of its elements.
$unconstrain_variables()and$unconstrain_draws()accept the sameshapes.
$variable_skeleton()is removed along with the C++ that fedit;
utils::relist()is no longer needed. The one shape change forexisting models is that a 1-d variable now comes back as a 1-d array
instead of a bare vector.
write_stan_json()gains avariablesargument taking thedeclarations from
$variables()orvariables_stan_file(). With theman unnamed list is written as a tuple; without them it is still
converted to an array as before. Complex values are written as
[re, im]pairs. The fitting methods pass the model's declarationsfor data and inits, which also moves the per-variable data
conversions from
process_data()into the writer, andinit = fitcarries tuple parameters over.
One caveat found while writing the test model: CmdStan 2.40's JSON
reader misreads a complex value inside an array of tuples (a bare
tuple(real, complex)andcomplex_vectorread fine). The test model'sarray[2] tuple(real, real)data avoids it and the model notes why.See stan-dev/stan#3437.
Tests: unit tests for the name repair, sizes, filters and the
flatten/unflatten pair;
write_stan_json()with tuples and complexvalues; and
test-tuple-complex.R, one fit of the model exercising themetadata, filters, data round trip, inits from a fit and from a list,
and the model methods.
Copyright and Licensing
Please list the copyright holder for the work you are submitting
(this will be you or your assignee, such as a university or company):
Jonah Gabry
By submitting this pull request, the copyright holder is agreeing to
license the submitted work under the following licenses: