@@ -78,10 +78,29 @@ impl<T> Loading<T> {
7878 }
7979}
8080
81+ /// Mapping between caller-visible factor labels and numerical level positions.
82+ ///
83+ /// This is an identity mapping for the existing dense `u32` API. Later
84+ /// encodings can store an explicit mapping without changing `TermMeta`.
85+ #[ derive( Debug , Clone ) ]
86+ pub ( crate ) struct FactorEncoding {
87+ n_levels : usize ,
88+ }
89+
90+ impl FactorEncoding {
91+ fn identity ( n_levels : usize ) -> Self {
92+ Self { n_levels }
93+ }
94+
95+ pub ( crate ) fn n_levels ( & self ) -> usize {
96+ self . n_levels
97+ }
98+ }
99+
81100/// Per-term metadata; coefficient `c` of `level` lives at `offset + c · n_levels + level`.
82101#[ derive( Debug , Clone ) ]
83102pub ( crate ) struct TermMeta {
84- pub n_levels : usize ,
103+ pub ( crate ) encoding : FactorEncoding ,
85104 pub offset : usize ,
86105 /// Non-decreasing in the design's internal row order (fixed at construction).
87106 pub sorted : bool ,
@@ -90,17 +109,21 @@ pub(crate) struct TermMeta {
90109}
91110
92111impl TermMeta {
112+ pub fn n_levels ( & self ) -> usize {
113+ self . encoding . n_levels ( )
114+ }
115+
93116 pub fn n_columns ( & self ) -> usize {
94117 self . columns . len ( )
95118 }
96119
97120 pub fn n_dofs ( & self ) -> usize {
98- self . n_columns ( ) * self . n_levels
121+ self . n_columns ( ) * self . n_levels ( )
99122 }
100123
101124 /// Global DOF base of coefficient column `column`.
102125 pub fn column_base ( & self , column : usize ) -> usize {
103- self . offset + column * self . n_levels
126+ self . offset + column * self . n_levels ( )
104127 }
105128}
106129
@@ -209,8 +232,9 @@ impl<'a> Design<'a> {
209232 sorted &= v >= prev;
210233 prev = v;
211234 }
235+ let encoding = FactorEncoding :: identity ( max as usize + 1 ) ;
212236 let meta = TermMeta {
213- n_levels : max as usize + 1 ,
237+ encoding ,
214238 offset,
215239 sorted,
216240 columns,
@@ -228,7 +252,7 @@ impl<'a> Design<'a> {
228252 let dominant = ( 0 ..terms. len ( ) ) . max_by_key ( |& q| terms[ q] . n_dofs ( ) ) ;
229253 let ( frame, obs_perm) = match dominant {
230254 Some ( d) if locality_sort && !terms[ d] . sorted && u32:: try_from ( n_obs) . is_ok ( ) => {
231- let perm = stable_argsort ( frame. level_column ( d) , terms[ d] . n_levels ) ;
255+ let perm = stable_argsort ( frame. level_column ( d) , terms[ d] . n_levels ( ) ) ;
232256 let sorted_frame = frame. permuted ( & perm) ;
233257 // Factors nested in the dominant one come out sorted, keeping coalesced scatter.
234258 for ( q, meta) in terms. iter_mut ( ) . enumerate ( ) {
0 commit comments