@@ -670,7 +670,133 @@ You can then view the logs with:
670670tensorboard --logdir logs/
671671```
672672
673- ## 13. Using differential operators
673+ ## 13. Vector-valued expressions
674+
675+ You can use ` TemplateExpressionSpec ` to find expressions for vector-valued data,
676+ where each component might share a common structure.
677+ The trick is to put each vector element into your feature matrix ` X ` ,
678+ and then use a template expression to define the relationships.
679+
680+ For example, say we have 3-dimensional vectors where each component
681+ follows a pattern with a shared term. Say the true model is:
682+
683+ $$ \begin{align*}
684+ y_1 &= \exp(x_1) + x_2^2 \\
685+ y_2 &= \exp(x_1) + \sin(x_3) \\
686+ y_3 &= \exp(x_1) + x_1 \cdot x_2
687+ \end{align*} $$
688+
689+ Let's set this up:
690+
691+ ``` python
692+ import numpy as np
693+ from pysr import PySRRegressor, TemplateExpressionSpec
694+
695+ n = 200
696+ rstate = np.random.RandomState(0 )
697+ x1 = rstate.uniform(- 2 , 2 , n)
698+ x2 = rstate.uniform(- 2 , 2 , n)
699+ x3 = rstate.uniform(- 2 , 2 , n)
700+
701+ # True model with shared component exp(x1):
702+ y1 = np.exp(x1) + x2** 2
703+ y2 = np.exp(x1) + np.sin(x3)
704+ y3 = np.exp(x1) + x1 * x2
705+
706+ # Add some noise
707+ y1 += 0.05 * rstate.randn(n)
708+ y2 += 0.05 * rstate.randn(n)
709+ y3 += 0.05 * rstate.randn(n)
710+ ```
711+
712+ Now, we put everything in ` X ` ; BOTH features and targets:
713+
714+ ``` python
715+ X = np.column_stack([x1, x2, x3, y1, y2, y3])
716+ ```
717+
718+ Now, we can define our template expression:
719+
720+ ``` python
721+ spec = TemplateExpressionSpec(
722+ expressions = [" f1" , " f2" , " f3" , " shared" ],
723+ variable_names = [" x1" , " x2" , " x3" , " y1" , " y2" , " y3" ],
724+ combine = """
725+ v = shared(x1, x2, x3)
726+ y1_predicted = v + f1(x1, x2, x3)
727+ y2_predicted = v + f2(x1, x2, x3)
728+ y3_predicted = v + f3(x1, x2, x3)
729+
730+ residuals = (
731+ abs2(y1 - y1_predicted) +
732+ abs2(y2 - y2_predicted) +
733+ abs2(y3 - y3_predicted)
734+ )
735+
736+ residuals
737+ """
738+ )
739+ ```
740+
741+ Now, we can fit our model using this template. Since
742+ we already computed the per-row squared error inside the template,
743+ we can pass a dummy ` y ` to the ` fit ` method, and also define
744+ an ` elementwise_loss ` that simply returns the residuals (which get
745+ summed over the data):
746+
747+ ``` python
748+ model = PySRRegressor(
749+ expression_spec = spec,
750+ binary_operators = [" +" , " -" , " *" , " /" ],
751+ unary_operators = [" exp" , " sin" ],
752+ maxsize = 20 ,
753+ niterations = 50 ,
754+ elementwise_loss = " (pred, target) -> pred" ,
755+ )
756+
757+ dummy_y = np.zeros(n)
758+ model.fit(X, dummy_y)
759+ ```
760+
761+ After running, PySR should find both the shared component (` exp(x1) ` ) as well as individual components (` square(x2) ` , ` sin(x3) ` , and ` x1 * x2 ` ).
762+
763+ You can access the individual expressions through the Julia objects:
764+
765+ ``` python
766+ # Simply get the expression with the highest score:
767+ idx = model.equations_.score.idxmax()
768+
769+ # Extract the Julia object:
770+ julia_expr = model.equations_.loc[idx, ' julia_expression' ]
771+
772+ # Access individual subexpressions:
773+ for name in [' f1' , ' f2' , ' f3' , ' shared' ]:
774+ tree = getattr (julia_expr.trees, name)
775+ print (f " { name} : { tree} " )
776+ ```
777+
778+ We can also evaluate individual expressions:
779+
780+ ``` python
781+ from pysr import jl
782+ from pysr.julia_helpers import jl_array
783+
784+ SR = jl.SymbolicRegression
785+
786+ # Get individual trees
787+ f1_tree = julia_expr.trees.f1
788+ shared_tree = julia_expr.trees.shared
789+
790+ # Evaluate at specific points (x1=1, x2=2, x3=3)
791+ test_inputs = jl_array(np.array([[1.0 ], [2.0 ], [3.0 ]]))
792+ f1_result, _ = SR .eval_tree_array(f1_tree, test_inputs, model.julia_options_)
793+ shared_result, _ = SR .eval_tree_array(shared_tree, test_inputs, model.julia_options_)
794+
795+ print (f " f1 at (1,2,3): { f1_result[0 ]} " ) # Should be ~4.0 for x2^2
796+ print (f " shared at (1,2,3): { shared_result[0 ]} " ) # Should be ~2.718 for exp(1)
797+ ```
798+
799+ ## 14. Using differential operators
674800
675801As part of the ` TemplateExpressionSpec ` described above,
676802you can also use differential operators within the template.
@@ -710,7 +836,7 @@ If everything works, you should find something that simplifies to $\frac{\sqrt{x
710836
711837Here, we write out a full function in Julia.
712838
713- ## 14 . Additional features
839+ ## 15 . Additional features
714840
715841For the many other features available in PySR, please
716842read the [ Options section] ( options.md ) .
0 commit comments