# Mathy

*A modern computer algebra system and reinforcement learning environments platform for interpretable symbolic mathematics.*

**Documentation**: https://mathy.ai

**Source Code**: https://github.com/justindujardin/mathy

## Features¶

**Computer Algebra System**: Parse text into expression trees for manipulation and evaluation. Transform trees with user-defined rules that do not change the value of the expression.**Reinforcement learning**: Train agents with machine learning in many environments with hyperparameters for controlling environment difficulties.**Custom Environments**Extend built-in environments or author your own. Provide custom logic and values for custom actions, problems, timestep rewards, episode rewards, and win-conditions.**Visualize Expressions**: Gain a deeper understanding of problem structures and rule transformations by visualizing binary trees in a compact layout with no branch overlaps.**Compute Friendly**: Maybe we don't have to burn down the world with GPU compute all the time? Text-based environments can be small enough to train on a CPU while still having real-world value.**Free and Open Source**: Mathy is and will always be free, because educational tools are too important to our world to be gated by money.**Python with Type Hints**: typing hints are used everywhere in Mathy to help provide rich autocompletion and linting in modern IDEs.

## Requirements¶

- Python 3.6+
- Tensorflow 2.0+

## Installation¶

```
$ pip install mathy mathy_alpha_sm
```

## Try It¶

Let's start by simplifying a polynomial problem using the CLI:

### Simplify a Polynomial¶

```
$ mathy simplify "2x + 4 + 3x * 6"
```

This uses the pretrained `mathy_alpha_sm`

model that we installed above.

The model is used to determine which intermediate steps to take in order to get to the desired solution.

The output will vary based on the model, but it might look like this:

### Generate Input Problems¶

Mathy can generate lists of random problems. Rather than force users to generate solutions, Mathy uses environment-specific functions for determining when a problem is solved.

In this way users do not need to know the answer to a problem that is generated.

```
$ mathy problems poly
```

### Train an A3C agent¶

Mathy has an A3C agent built-in that you can train from the CLI:

A3C uses multiple worker threads to train a shared model in a CPU-friendly setting.

```
$ mathy train a3c poly ./training/my_model
```

### Train an MCTS agent¶

Mathy has a MCTS agent built-in that you can train from the CLI:

MCTS is a powerful tree search that is combined with a neural network to produce high quality actions.

```
$ mathy train zero poly ./training/my_model
```

## Code It¶

Above we simplified a polynomial problem using the CLI, but what if the output steps had failed to find a solution?

Perhaps we put a subtraction between two like terms, like `4x + 3y - 2x`

Recall that we can't move subtraction terms around with the commutative property, so how can Mathy solve this problem?

We can write custom code for Mathy in order to add features or correct issues.

In order to combine these terms, we need to convert the subtraction into an addition.

Remember that a subtraction like `4x + 3y - 2x`

can be restated as a "plus negative" like `4x + 3y + -2x`

to make it commutable.

Once we've restated the expression, we can now use the commutative property to swap the positions of `3y`

and `-2x`

so we end up with `4x + -2x + 3y`

Now the expression is in a state that Mathy's existing rules can handle the rest.

### Create a Rule¶

To continue our `4x + 3y - 2x`

example, we'll write some code to convert the subtraction into an addition: `4x + -2x + 3y`

Mathy uses the available set of **rules** (also referred to as **actions**) when transforming a problem.

To create a custom rule we extend the BaseRule class and define two main functions:

`can_apply_to`

determines if a rule can be applied to an expression node.`apply_to`

applies the rule to a node and returns an expression change object.

```
from mathy import (
AddExpression,
ExpressionParser,
BaseRule,
NegateExpression,
SubtractExpression,
)
class PlusNegationRule(BaseRule):
"""Convert subtract operators to plus negative to allow commuting"""
@property
def name(self) -> str:
return "Plus Negation"
@property
def code(self) -> str:
return "PN"
def can_apply_to(self, node) -> bool:
is_sub = isinstance(node, SubtractExpression)
is_parent_add = isinstance(node.parent, AddExpression)
return is_sub and (node.parent is None or is_parent_add)
def apply_to(self, node):
change = super().apply_to(node)
change.save_parent() # connect result to node.parent
result = AddExpression(node.left, NegateExpression(node.right))
result.set_changed() # mark this node as changed for visualization
return change.done(result)
parser = ExpressionParser()
expression = parser.parse("4x - 2x")
rule = PlusNegationRule()
# Find a node and apply the rule
applicable_nodes = rule.find_nodes(expression)
assert len(applicable_nodes) == 1
assert applicable_nodes[0] is not None
# Verify the expected change
change = rule.apply_to(applicable_nodes[0])
assert str(change.result) == "4x + -2x"
```

### Use it during training¶

Now that we've created a custom rule for converting subtract nodes into "plus negative" ones, we need Mathy to use it while training.

We do this with custom environment arguments when using the A3C Agent and the Poly Simplify environment.

All together it looks like:

```
#!pip install gym
import os
import shutil
import tempfile
import gym
from mathy import (
AddExpression,
BaseRule,
MathExpression,
ExpressionParser,
MathyEnv,
NegateExpression,
SubtractExpression,
)
from mathy.envs import PolySimplify
from mathy.agents.a3c import A3CAgent, A3CConfig
from mathy.cli import setup_tf_env
class PlusNegationRule(BaseRule):
"""Convert subtract operators to plus negative to allow commuting"""
@property
def name(self) -> str:
return "Plus Negation"
@property
def code(self) -> str:
return "PN"
def can_apply_to(self, node: MathExpression) -> bool:
is_sub = isinstance(node, SubtractExpression)
is_parent_add = isinstance(node.parent, AddExpression)
return is_sub and (node.parent is None or is_parent_add)
def apply_to(self, node: MathExpression):
change = super().apply_to(node)
change.save_parent() # connect result to node.parent
result = AddExpression(node.left, NegateExpression(node.right))
result.set_changed() # mark this node as changed for visualization
return change.done(result)
# Quiet tensorflow debug outputs
setup_tf_env()
# Train in a temporary folder
model_folder = tempfile.mkdtemp()
# Add an instance of our new rule to the built-int environment rules
all_rules = MathyEnv.core_rules() + [PlusNegationRule()]
# Specify a set of operators to choose from when generating poly simplify problems
env_args = {"ops": ["+", "-"], "rules": all_rules}
# Configure and launch the A3C agent training
args = A3CConfig(
max_eps=1,
num_workers=1,
print_training=True,
topics=["poly"],
model_dir=model_folder,
)
A3CAgent(args, env_extra=env_args).train()
# Comment this out to keep your model
shutil.rmtree(model_folder)
```

Congratulations, you've extended Mathy and begun training a new model with your custom action!

## Become a Contributor¶

Building new actions and problem sets are great ways to contribute to Mathy.

By contributing improvements to Mathy, we help ourselves better understand Math and Programming.

We also create examples for others around the world that are trying to get help with Math or learn Programming!

## Contributors¶

Mathy wouldn't be possible without the wonderful contributions of the following people:

This project follows the all-contributors specification. Contributions of any kind welcome!