Skip to content

Commit 41d9faa

Browse files
committed
improve readme
1 parent 5f8f256 commit 41d9faa

File tree

6 files changed

+530
-318
lines changed

6 files changed

+530
-318
lines changed

examples/symbolic_regression/README.md

Lines changed: 92 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -143,19 +143,83 @@ def run_search():
143143

144144
### Evolved Algorithm (Discovered Symbolic Expression)
145145

146-
OpenEvolve will iteratively modify the Python code within the `# EVOLVE-BLOCK-START` and `# EVOLVE-BLOCK-END` markers in `initial_program.py`. The goal is to transform the simple initial model into a more complex and accurate symbolic expression that minimizes the Mean Squared Error (MSE) on the training data.
146+
**OpenEvolve** iteratively modifies Python code segments, delineated by `# EVOLVE-BLOCK-START` and `# EVOLVE-BLOCK-END` markers within an `initial_program.py` file. The primary objective is to evolve a simple initial model into a more complex and accurate symbolic expression that minimizes the Mean Squared Error (MSE) against the training data.
147147

148-
An evolved `func` might, for instance, discover a non-linear expression like:
148+
Below is a symbolic expression discovered by OpenEvolve for the physics task `PO10`:
149149

150150
```python
151-
# Hypothetical example of what OpenEvolve might find:
151+
import numpy as np
152+
152153
def func(x, params):
153-
# Assuming X_train_scaled maps to x and const maps to a parameter in params
154-
predictions = np.sin(x[:, 0]) * x[:, 1]**2 + params[0]
155-
return predictions
154+
"""
155+
Calculates the model output using a linear combination of input variables
156+
or a constant value if no input variables. Operates on a matrix of samples.
157+
158+
Args:
159+
x (np.ndarray): A 2D numpy array of input variable values, shape (n_samples, n_features).
160+
n_features is 2.
161+
If n_features is 0, x should be shape (n_samples, 0).
162+
The order of columns in x must correspond to:
163+
(x, t).
164+
params (np.ndarray): A 1D numpy array of parameters.
165+
Expected length: 10.
166+
167+
Returns:
168+
np.ndarray: A 1D numpy array of predicted output values, shape (n_samples,).
169+
"""
170+
# --------------------------------------------------------------------------
171+
# Allow for flexible parameter count, only padding essential parts.
172+
if len(params) < 10:
173+
required_params = params.shape[0]
174+
params = np.pad(params, (0, 10 - required_params))
175+
176+
# Readable aliases for the two input features
177+
pos = x[:, 0] # position x(t)
178+
t_val = x[:, 1] # time t
179+
180+
# ---------- Internal restoring forces (Duffing-like) ------------------
181+
# −k x −β x³ −γ x⁵ (only odd powers, respecting the usual symmetry)
182+
# Reduced polynomial order (up to cubic) to avoid over-fitting while
183+
# still capturing the essential softening/stiffening behaviour.
184+
restoring = -(params[0] * pos + params[1] * pos**3)
185+
186+
# ---------- Externally forced, periodically driven term --------------
187+
# A e^{-λ t} sin(ω t) + B cos(Ω t) (General form considered)
188+
# Let the optimiser decide whether the envelope should grow
189+
# or decay by keeping the sign of params[4]. The exponent is
190+
# clipped to avoid numerical overflow.
191+
# Simple periodic forcing without exponential envelope. This is
192+
# sufficient for many driven oscillator benchmarks and reduces the
193+
# risk of numerical overflow in exp().
194+
trig1 = params[3] * t_val
195+
trig2 = params[5] * t_val
196+
forcing = params[2] * np.cos(trig1) + params[4] * np.sin(trig2)
197+
198+
# ---------- Weak position–time coupling & constant bias ---------------
199+
interaction = params[8] * pos * t_val
200+
bias = params[9]
201+
202+
return restoring + forcing + interaction + bias
156203
```
157204

158-
*(This is a simplified, hypothetical example to illustrate the transformation.)*
205+
The ground truth for this PO10 task is represented by the equation:
206+
$$
207+
F_0sin(t)−ω_0^2(γt+1)x(t)−ω_0^2x(t)^3−ω_0^2x(t).
208+
$$
209+
This can be expanded and simplified to:
210+
$$
211+
F_0sin(t)−ω_0^2γtx(t)−2ω_0^2x(t)−ω_0^2x(t)^3.
212+
$$
213+
Notably, the core functional forms present in this ground truth equation are captured by the evolved symbolic expression:
214+
215+
- The $sin(t)$ component can be represented by `params[4] * np.sin(params[5] * t_val)`.
216+
- The linear $x(t)$ term corresponds to `params[0] * pos`.
217+
- The cubic $x(t)^3$ term is `params[1] * pos**3`.
218+
- The interaction term $t⋅x(t)$ is captured by `params[8] * pos * t_val`.
219+
220+
The evolved code also includes terms like `params[2] * np.cos(params[3] * t_val)` (a cosine forcing term) and `params[9]` (a constant bias). These might evolve to have negligible parameter values if not supported by the data, or they could capture secondary effects or noise. The inclusion of the primary terms demonstrates OpenEvolve's strength in identifying the correct underlying structure of the equation.
221+
222+
*Note: Symbolic regression, despite such promising results, remains a very challenging task. This difficulty largely stems from the inherent complexities of inferring precise mathematical models from finite and potentially noisy training data, which provides only a partial observation of the true underlying system.*
159223

160224
------
161225

@@ -177,12 +241,28 @@ The `eval.py` script will help you collect and analyze performance metrics. The
177241

178242
For benchmark-wide comparisons and results from other methods, please refer to the official LLM-SRBench paper.
179243

180-
| **Task Category** | Med. NMSE (Test) | Med. R2 (Test) | **Med. NMSE (OOD Test)** | **Med. R2 (OOD Test)** |
181-
| ----------------------- | ---------------- | -------------- | ------------------------ | ---------------------- |
182-
| Chemistry (36 tasks) | 2.3419e-06 | 1.000 | 3.1384e-02 | 0.9686 |
183-
| Physics (44 tasks) | 1.8548e-05 | 1.000 | 7.9255e-04 | 0.9992 |
244+
*Note: Below we extract the approximate results of baselines in Fig.5 from LLMSR-Bench paper.*
245+
246+
**Median NMSE – in-domain (Test)**
247+
248+
| **Domain** | **DirectLLM** | **LLMSR** | **LaSRS** | **GA** | **OpenEvolve** |
249+
| ---------------- | ------------- | --------------- | ----------- | ----------- | -------------- |
250+
| Chemistry | ~6.0 × 10⁻¹ | **~1.5 × 10⁻⁶** | ~1.0 × 10⁻⁴ | ~1.0 × 10⁻² | 2.34 × 10⁻⁶ |
251+
| Biology | ~2.0 × 10⁻² | ~1.0 × 10⁻⁵ | ~1.0 × 10⁻⁴ | ~2.0 × 10⁻⁴ ||
252+
| Physics | ~3.0 × 10⁻¹ | **~2.0 × 10⁻⁷** | ~1.0 × 10⁻³ | ~4.0 × 10⁻³ | 1.85 × 10⁻⁵ |
253+
| Material Science | ~3.0 × 10⁻¹ | ~1.0 × 10⁻⁴ | ~7.0 × 10⁻⁴ | ~3.0 × 10⁻² ||
254+
255+
**Median NMSE – out-of-domain (OOD Test)**
256+
257+
| **Domain** | **DirectLLM** | **LLMSR** | **LaSRS** | **GA** | **OpenEvolve** |
258+
| ---------------- | ------------- | ----------- | ----------- | ---------- | --------------- |
259+
| Chemistry | ~3.0 × 10² | ~5.0 × 10⁻² | ~1.0 × 10⁰ | ~1.5 × 10⁰ | **3.14 × 10⁻²** |
260+
| Biology | ~1.2 × 10² | ~4.0 × 10⁰ | ~3.0 × 10¹ | ~4.0 × 10¹ ||
261+
| Physics | ~1.0 × 10¹ | ~1.0 × 10⁻³ | ~5.0 × 10⁻² | ~1.0 × 10⁰ | **7.93 × 10⁻⁴** |
262+
| Material Science | ~2.5 × 10¹ | ~3.0 × 10⁰ | ~8.0 × 10⁰ | ~2.5 × 10¹ ||
263+
264+
Current results for OpenEvolve are only for two subsets of LSR-Synth. We will update the comprehensive results soon.
184265

185-
Current results are only for two subset of LSR-Synth. We will update the comprehensive results soon.
186266

187267
------
188268

examples/symbolic_regression/bench/dataclasses.py

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,13 @@ class Equation:
1515
lambda_format: Optional[callable] = None
1616
program_format: Optional[str] = None
1717

18+
1819
@dataclass
1920
class SearchResult:
2021
equation: Equation
2122
aux: Any
2223

24+
2325
@dataclass
2426
class SEDTask:
2527
name: str
@@ -29,6 +31,7 @@ class SEDTask:
2931
samples: Any
3032
desc: Optional[str] = None
3133

34+
3235
@dataclass
3336
class Problem:
3437
dataset_identifier: str
@@ -37,20 +40,23 @@ class Problem:
3740
samples: Any
3841

3942
def create_task(self) -> SEDTask:
40-
return SEDTask(name=self.equation_idx,
41-
symbols=self.gt_equation.symbols,
42-
symbol_descs=self.gt_equation.symbol_descs,
43-
symbol_properties=self.gt_equation.symbol_properties,
44-
samples=self.train_samples,
45-
desc=self.gt_equation.desc)
43+
return SEDTask(
44+
name=self.equation_idx,
45+
symbols=self.gt_equation.symbols,
46+
symbol_descs=self.gt_equation.symbol_descs,
47+
symbol_properties=self.gt_equation.symbol_properties,
48+
samples=self.train_samples,
49+
desc=self.gt_equation.desc,
50+
)
51+
4652
@property
4753
def train_samples(self):
48-
return self.samples['train']
49-
54+
return self.samples["train"]
55+
5056
@property
5157
def test_samples(self):
52-
return self.samples['test']
53-
58+
return self.samples["test"]
59+
5460
@property
5561
def ood_test_samples(self):
56-
return self.samples.get('ood_test', None)
62+
return self.samples.get("ood_test", None)

0 commit comments

Comments
 (0)