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_todetermines if a rule can be applied to an expression node.apply_toapplies 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!
