In [ ]:
Copied!
!pip install equation-tree
!pip install equation-tree
In [ ]:
Copied!
# Imports
import numpy as np
from equation_tree import sample
# Set a seed for reproducible results
np.random.seed(42)
# Initialization
sampled_equation = sample()[0]
sampled_equation
# Imports
import numpy as np
from equation_tree import sample
# Set a seed for reproducible results
np.random.seed(42)
# Initialization
sampled_equation = sample()[0]
sampled_equation
In [ ]:
Copied!
# Import
from equation_tree import EquationTree
from sympy import sympify
sympy_equation = sympify('x_1 * 2 + x_2 ** 3')
equation_from_sympy = EquationTree.from_sympy(sympy_equation)
equation_from_sympy
# Import
from equation_tree import EquationTree
from sympy import sympify
sympy_equation = sympify('x_1 * 2 + x_2 ** 3')
equation_from_sympy = EquationTree.from_sympy(sympy_equation)
equation_from_sympy
From Prefix¶
Or, we can use prefix notation to initialize an equation:
In [ ]:
Copied!
equation_from_prefix = EquationTree.from_prefix(['+', '*', 'x_1', '2', '**', 'x_2', '3'])
equation_from_prefix
equation_from_prefix = EquationTree.from_prefix(['+', '*', 'x_1', '2', '**', 'x_2', '3'])
equation_from_prefix
!!!Note: This is feature can be used to manipulate equations:
In [ ]:
Copied!
# Set a random seed for reproducible results
np.random.seed(42)
# Sample an initial equation
equation = sample()[0]
print('original:\t', equation)
# Use sympy to multiply the equation with a scalar and reinitialize it
new_sympy_equation = equation.sympy_expr * 3
equation_scaled = EquationTree.from_sympy(new_sympy_equation)
print('scaled:\t\t', equation_scaled)
# Use prefix notation to replace a function with another
new_prefix_notation = ['sin' if x == 'abs' else x for x in equation_scaled.prefix]
equation_replaced = EquationTree.from_prefix(new_prefix_notation)
print('replaced:\t', equation_replaced)
# Set a random seed for reproducible results
np.random.seed(42)
# Sample an initial equation
equation = sample()[0]
print('original:\t', equation)
# Use sympy to multiply the equation with a scalar and reinitialize it
new_sympy_equation = equation.sympy_expr * 3
equation_scaled = EquationTree.from_sympy(new_sympy_equation)
print('scaled:\t\t', equation_scaled)
# Use prefix notation to replace a function with another
new_prefix_notation = ['sin' if x == 'abs' else x for x in equation_scaled.prefix]
equation_replaced = EquationTree.from_prefix(new_prefix_notation)
print('replaced:\t', equation_replaced)
In [ ]:
Copied!
# Import
from equation_tree.metrics import prediction_distance
# First, we define our equations
sympy_square = sympify('x_1 ** 2')
equation_square = EquationTree.from_sympy(sympy_square)
sympy_cube = sympify('x_1 ** 3')
equation_cube = EquationTree.from_sympy(sympy_cube)
# For the prediction distance, we need to define a sample of input values on which the metric is evaluated:
sample_1 = {'x_1': [0, 1, 2]}
sample_2 = {'x_1': np.linspace(0,1)}
sample_3 = {'x_1': np.linspace(-10, 10)}
# Now, we can calculate the metrics:
print('sample_1:\t', prediction_distance(equation_square, equation_cube, sample_1))
print('sample_2:\t', prediction_distance(equation_square, equation_cube, sample_2))
print('sample_3:\t', prediction_distance(equation_square, equation_cube, sample_3))
# Import
from equation_tree.metrics import prediction_distance
# First, we define our equations
sympy_square = sympify('x_1 ** 2')
equation_square = EquationTree.from_sympy(sympy_square)
sympy_cube = sympify('x_1 ** 3')
equation_cube = EquationTree.from_sympy(sympy_cube)
# For the prediction distance, we need to define a sample of input values on which the metric is evaluated:
sample_1 = {'x_1': [0, 1, 2]}
sample_2 = {'x_1': np.linspace(0,1)}
sample_3 = {'x_1': np.linspace(-10, 10)}
# Now, we can calculate the metrics:
print('sample_1:\t', prediction_distance(equation_square, equation_cube, sample_1))
print('sample_2:\t', prediction_distance(equation_square, equation_cube, sample_2))
print('sample_3:\t', prediction_distance(equation_square, equation_cube, sample_3))
Symbolic Solution¶
Second, we calculate the symbolic solution metric. This metric checks weather equations differ from each other only through scalars or constants.
In [ ]:
Copied!
# Import
from equation_tree.metrics import symbolic_solution_quot, symbolic_solution_diff
# Since x ** 2 and x ** 3 differ not only through multiplying with a scalar or adding a constant, the following returns inf
print(f'diff|x_1 ** 2, x_1 ** 3| = {symbolic_solution_diff(equation_square, equation_cube)}')
print(f'quot|x_1 ** 2, x_1 ** 3| = {symbolic_solution_quot(equation_square, equation_cube)}')
print()
equation_identity = EquationTree.from_sympy(sympify('x_1'))
equation_identity_shifted = EquationTree.from_sympy(sympify('x_1 + 3'))
equation_scaled = EquationTree.from_sympy(sympify('2 * x_1'))
# Since x_1 + 3 is shifted x_1 with the value of three, the following returns 3
print(f'diff|x_1 + 3, x_1| = {symbolic_solution_diff(equation_identity_shifted, equation_identity)}')
# This still returns inf:
print(f'quot|x_1 + 3, x_1| = {symbolic_solution_quot(equation_identity_shifted, equation_identity)}')
print()
# and vice vers for the scaled equations:
print(f'diff|x_1 * 2, x_1| = {symbolic_solution_diff(equation_scaled, equation_identity)}')
print(f'quot|x_1 * 2, x_1| = {symbolic_solution_quot(equation_scaled, equation_identity)}')
print()
# Import
from equation_tree.metrics import symbolic_solution_quot, symbolic_solution_diff
# Since x ** 2 and x ** 3 differ not only through multiplying with a scalar or adding a constant, the following returns inf
print(f'diff|x_1 ** 2, x_1 ** 3| = {symbolic_solution_diff(equation_square, equation_cube)}')
print(f'quot|x_1 ** 2, x_1 ** 3| = {symbolic_solution_quot(equation_square, equation_cube)}')
print()
equation_identity = EquationTree.from_sympy(sympify('x_1'))
equation_identity_shifted = EquationTree.from_sympy(sympify('x_1 + 3'))
equation_scaled = EquationTree.from_sympy(sympify('2 * x_1'))
# Since x_1 + 3 is shifted x_1 with the value of three, the following returns 3
print(f'diff|x_1 + 3, x_1| = {symbolic_solution_diff(equation_identity_shifted, equation_identity)}')
# This still returns inf:
print(f'quot|x_1 + 3, x_1| = {symbolic_solution_quot(equation_identity_shifted, equation_identity)}')
print()
# and vice vers for the scaled equations:
print(f'diff|x_1 * 2, x_1| = {symbolic_solution_diff(equation_scaled, equation_identity)}')
print(f'quot|x_1 * 2, x_1| = {symbolic_solution_quot(equation_scaled, equation_identity)}')
print()
Normalized Edit Distance¶
For a pair of two trees, edit distance computes the minimum cost to transform one to another with a sequence of operations, each of which either 1) inserts, 2) deletes, or 3) renames a node. It is normalized between 0 and 1 (For more details, see the respective documentation)
In [ ]:
Copied!
# Import
from src.equation_tree.metrics import normalized_tree_distance
# Here, we use the equations above to give examples:
print(f'ned|x_1 ** 2, x_1 ** 3| = {normalized_tree_distance(equation_square, equation_cube)}')
print(f'ned|x_1 + 3, x_1| = {normalized_tree_distance(equation_identity_shifted, equation_identity)}')
print(f'ned|x_1 * 2, x_1| = {normalized_tree_distance(equation_scaled, equation_identity)}')
# Import
from src.equation_tree.metrics import normalized_tree_distance
# Here, we use the equations above to give examples:
print(f'ned|x_1 ** 2, x_1 ** 3| = {normalized_tree_distance(equation_square, equation_cube)}')
print(f'ned|x_1 + 3, x_1| = {normalized_tree_distance(equation_identity_shifted, equation_identity)}')
print(f'ned|x_1 * 2, x_1| = {normalized_tree_distance(equation_scaled, equation_identity)}')