DLGA-PDE
A neural network fits the field and supplies derivatives; a genetic algorithm searches term sets over a candidate library.
Usage
import kd
dataset = kd.load_burgers()
model = kd.Model(algorithm="dlga", generations=40, seed=1, pop_size=40)
model.fit(dataset)
Main parameters
| Parameter | Default | Resume | Description |
|---|---|---|---|
pop_size |
400 |
resume-safe | Genetic population size. |
epsilon |
0.001 |
resume-safe | Expression-length penalty. |
mutation_rate |
0.4 |
resume-safe | Genome mutation probability. |
crossover_rate |
0.8 |
resume-safe | Genome crossover probability. |
surrogate_lr |
0.001 |
init-only | Neural-surrogate learning rate. |
All fields of DLGAConfig
| Field | Type | Default |
|---|---|---|
mode |
Literal['constant', 'adaptive', 'auto'] |
constant |
library |
list[str] |
['u', 'u_x', 'u_xx', 'u_xxx'] |
solver |
Literal['svd_null_space', 'ols'] |
svd_null_space |
lhs_auto_select |
bool |
False |
target_lhs_order |
int |
1 |
epsilon |
float |
0.001 |
pop_size |
int |
400 |
seed |
int |
0 |
auto_upgrade_threshold |
float |
0.001 |
max_modules |
int |
5 |
max_module_length |
int |
5 |
partial_prob |
float |
0.6 |
genes_prob |
float |
0.6 |
crossover_rate |
float |
0.8 |
mutation_rate |
float |
0.4 |
add_rate |
float |
0.4 |
delete_rate |
float |
0.5 |
surrogate_hidden_sizes |
list[int] |
[50, 50, 50, 50, 50] |
surrogate_activation |
Literal['tanh', 'sin', 'relu'] |
sin |
surrogate_lr |
float |
0.001 |
surrogate_max_epochs |
int |
50000 |
surrogate_patience |
int \| None |
None |
surrogate_val_ratio |
float |
0.2 |
surrogate_restore_best |
bool |
True |
Search space
DLGA-PDE searches sums of products drawn from a candidate library, fitting
constant coefficients. Reachable expressions are polynomials in the supplied
field and derivative tokens. Use the dataset's own field and axis names:
u_x_t is a mixed partial, and diff_t(u_x) is its open-form spelling.
An atomic name segment is limited to third order; higher compound derivatives
can be written as u_xx_xx.
The library determines which axes can enter a result. A library token that
names an unknown field or axis, or a dataset field other than the
left-hand-side one, refuses the whole fit before training. So does, when no
sketch is set, a token that spells a time derivative of the left-hand-side
field that the run fits: the order target_lhs_order names (u_t,
diff_t(u), diff1_t(u) for order 1; u_tt, diff2_t(u) for order 2), or
both orders while lhs_auto_select is on. Such a token would fit its own
branch exactly. The time derivative of an order the run does not fit is an
ordinary library token. An atomic segment
above third order, such as u_xxxx, is not refused: the fit runs and every
candidate containing that token is invalid, so use the u_xx_xx spelling.
The surrogate models only the left-hand-side field; other fields and their
derivatives cannot enter its library. The constant-coefficient stage is
implemented; the adaptive variable-coefficient modes are rejected at
construction.
Surrogate fitting precedes genetic search and can dominate cost. Both the
number of observations and the derivative library affect resource use; the
epoch budget alone does not bound memory. A shorter fit changes derivative
accuracy and, under lhs_auto_select, can change the selected left-hand-side
order. No pretrained weights or optional backend are needed.
Method
A neural network first fits the observations over their coordinates. The
architecture has five hidden layers of 50 units, sin activation and Adam
optimization. Field reads and derivatives both come from this network,
with derivatives obtained by autograd.
A genome contains up to max_modules modules of up to max_module_length tokens. Each module is a
product and the genome is their sum. Coefficients are found from the augmented
system's null space or by ordinary least squares, according to solver.
Fitness is nmse + epsilon * length, with length counted in tokens. Each
candidate is fitted against the time derivative of the order
target_lhs_order names, which has to equal the dataset's own left-hand-side
order. With lhs_auto_select enabled, each candidate is fitted against both
the first and second time derivative and the branch with the lower NMSE is
used, so the returned law can carry the order the dataset does not declare.
Each generation scores the population, retains the better half, refills it
with random genomes, exchanges a module between adjacent pairs, then mutates
by adding or deleting a module and shifting a token to an adjacent library
entry. Library order therefore affects mutation. One generation is one KD
iteration, controlled by generations.
Result interpretation
model.best_score_ combines the fit error with the per-token length penalty,
so NMSE alone does not rank term sets. NMSE is measured against the network's
field and derivatives, which are the quantities used during search.
model.result_.equation carries the fitted terms, coefficients and selected
left-hand side. The returned coefficients come from KD's refit over the
selected structure on that surrogate domain.
The recorder distinguishes surrogate training from genetic search. Read the training and validation losses alongside the per-generation fitness, diversity and complexity series. A small NMSE is evidence of a fit on that domain, and does not by itself establish recovery of the generating equation. If changing the seed or population budget improves the result, the search budget was relevant; repeated poor fits also warrant checking the library and surrogate accuracy.
References
Xu et al. (2020). "DLGA-PDE: Discovery of PDEs with incomplete candidate library via combination of deep learning and genetic algorithm". J. Comput. Phys. 418, 109584. Paper · arXiv:2001.07305
Code: woshixuhao/DLGA_tutorial (the author's tutorial notebook, on the KdV case), woshixuhao/PIC_code (the author's own code for a later paper, running the same neural-network-plus-genetic-algorithm search)
Implementation notes. The paper treats the order of the time derivative as
part of what is discovered: the left-hand side is a gene of the genome,
changed by mutation, and genomes with different left-hand sides compete on
fitness. By default KD takes the order from the dataset and fits only that
left-hand side. An earlier implementation fits u_t and u_tt for every
candidate and keeps the better branch, which is lhs_auto_select=true here;
under that option KD compares the branches by NMSE. The paper's
Eq. (6) and that implementation compare raw MSE, which also varies with
the target branch's variance. The length penalty is configurable; the author's
tutorial fixes it for its demonstration. The genetic mechanism is checked
against that implementation, with corrected crossover indexing and selection
of candidates with duplicate fitness values. The adaptive variable-coefficient
extension is not part of this implementation.
Worked example
The run below uses the Burgers dataset that ships with KD: a 256 × 201 grid
over x in [-8, 8) and t in [0, 10], whose initial profile steepens into a
front at the origin and then spreads out as the diffusive term takes over.
The equation returned by the run:
| Equation | |
|---|---|
| Discovered | \(u_{t} = - 0.9993 u u_{x} + 0.0999 u_{xx}\) |
| Reference | \(u_t = -u\,u_x +0.1\,u_{xx}\) |
Both terms and both coefficients match. The terms were assembled from the
default candidate library (u, u_x, u_xx, u_xxx), and every candidate
was fitted against u_t alone under the default target_lhs_order=1 and
lhs_auto_select=False. Setting lhs_auto_select=True enables the paper's
two-branch selection, fitting each candidate against both u_t and u_tt
and keeping the branch with the lower NMSE.
Example diagnostics
This run has DLGA fitness 0.00346122 and NMSE 0.0004612.
Fitness adds the configured length penalty to NMSE.
The search is recorded as well. kd.VizEngine renders the per-generation
record; alongside the universal convergence curve, DLGA-PDE contributes four
diagnostic panels of its own (the full list is under
Visualization):
The fourth panel shows the training of the surrogate network, which is independent of the search:
viz = kd.VizEngine(output_dir="out/burgers")
viz.render_all(model.result_, algorithm=model.algorithm_, dataset=dataset)
Full report
Full HTML report from the same run, with all figures, captions and raw result data.