@@ -172,6 +172,69 @@ library.add_parametric(
172172- Parametric fits are slower (requires nonlinear optimization per candidate).
173173- Only add parametric terms when you have physical motivation.
174174
175+ ### ` add_block(library, multiply_by=None, block_name=None, complexity_offset=0, feature_map=None) `
176+
177+ Adds every function of another library, optionally multiplied by a column of the
178+ data. This builds design-matrix blocks of the form ` Θ(a) ⊙ b ` , where ` Θ ` is a
179+ basis over one variable and ` b ` is another * column* — typically a measured or
180+ estimated derivative. A coefficient selected in such a block is literally a term
181+ of the unknown coefficient * function* multiplying ` b ` .
182+
183+ ``` python
184+ from jaxsr import BasisLibrary
185+
186+ # Basis over the coefficient function's argument
187+ theta = (BasisLibrary(n_features = 1 , feature_names = [" c" ])
188+ .add_constant()
189+ .add_linear()
190+ .add_polynomials(max_degree = 2 ))
191+
192+ # y_c = s'(c)*y_x + v'(c): one block per unknown function
193+ library = (BasisLibrary(n_features = 2 , feature_names = [" c" , " y_x" ])
194+ .add_block(theta, multiply_by = " y_x" , block_name = " horizontal" )
195+ .add_block(theta, block_name = " vertical" ))
196+
197+ library.names
198+ # ['y_x', 'c*y_x', 'c^2*y_x', '1', 'c', 'c^2']
199+ ```
200+
201+ ** What it does for you:**
202+
203+ - ** Names** are generated as ` <basis>*<column> ` , consistently; the constant term
204+ collapses to just the column name (` 1*y_x ` → ` y_x ` ), and a source name that is
205+ a bare sum is parenthesized (` 1+c ` → ` (1+c)*y_x ` ).
206+ - ** Complexity** is inherited from the source, plus 1 for the multiplication,
207+ plus ` complexity_offset ` .
208+ - ** Feature indices** are remapped: the source is written against its own
209+ columns, and ` add_block ` re-expresses it on this library's feature space,
210+ matching features by name (use ` feature_map={"src": "target"} ` when the names
211+ differ).
212+ - ** Parametric terms pass through unchanged** — bounds, ` log_scale ` and the name
213+ template are preserved, so profile-likelihood optimization still applies inside
214+ the block.
215+ - The source library is ** copied, not shared** , so one ` theta ` can seed several
216+ blocks.
217+
218+ ** Working with blocks:**
219+
220+ ``` python
221+ library.blocks
222+ # {'horizontal': [0, 1, 2], 'vertical': [3, 4, 5]}
223+
224+ library.filter_by_block(include = " horizontal" ) # -> [0, 1, 2]
225+ library.filter_by_block(exclude = [" vertical" ]) # -> [0, 1, 2]
226+
227+ # First diagnostic for a structured library: did the block earn its place?
228+ reduced = library.without_blocks(" vertical" ) # new library, original untouched
229+ ```
230+
231+ Comparing the fit of ` library ` against ` library.without_blocks("vertical") ` is
232+ the fastest way to check a horizontal/vertical identifiability trade-off, which
233+ is a real hazard whenever two blocks can explain the same variation.
234+
235+ Block functions are not deserializable — like ` add_custom ` , the library config
236+ saves but the block must be re-added after ` load() ` .
237+
175238### ` add_categorical_indicators(features=None) `
176239
177240For categorical features, adds binary indicator (dummy) variables.
0 commit comments