Zulip Chat Archive
Stream: Equational
Topic: New tool: Magmas CLI
Michael Bucko (Oct 31 2024 at 07:38):
python3 magmas.py operation.txt equation_list.txt > solution.txt
https://github.com/teorth/equational_theories/pull/764
Pietro Monticone (Oct 31 2024 at 07:45):
Thanks @Michael Bucko. Could you please follow the workflow so that reviewers / maintainers know the full context ?
Michael Bucko (Oct 31 2024 at 07:48):
@Pietro Monticone Could you please link the workflow ticket? Gonna check it out.
Michael Bucko (Oct 31 2024 at 07:51):
Ah, thank you. I see your comment.
Michael Bucko (Nov 07 2024 at 14:36):
@Shreyas Srinivas Here's the related GitHub issue:
https://github.com/teorth/equational_theories/issues/802
Shreyas Srinivas (Nov 07 2024 at 14:36):
Ah so this was already discussed
Shreyas Srinivas (Nov 07 2024 at 14:37):
Let's see what others have to say
Michael Bucko (Nov 07 2024 at 14:38):
Yeah, it's a small CLI tool for people who use Python to build apps around operation tables. I've been using the FMT a lot, and needed this. So I thought I'll share it with others.
Michael Bucko (Nov 08 2024 at 11:09):
@Shreyas Srinivas I'd appreciate your feedback here too :thank_you:
Shreyas Srinivas (Nov 08 2024 at 11:12):
I have two questions:
- Why do you want the following: "A Python function + CLI that takes the equation list as input, an operation table as an argument, and returns JSON with 1/0 for each equation."
- Why can't it be generated from the info provided by
lake exe extract_implications
and the API that the equation_explorer uses?
Shreyas Srinivas (Nov 08 2024 at 11:13):
for 1: what does 1 or 0 mean?
Shreyas Srinivas (Nov 08 2024 at 11:14):
Reading the PR: you want to create an opt-able set but this is trivial to extract from the generator script for equations isn't it?
Michael Bucko (Nov 08 2024 at 11:17):
Shreyas Srinivas schrieb:
for 1: what does 1 or 0 mean?
1 - means satisfies, 0 - refutes. It's basically like the FMT but for Python developers, and CLI. (My use case: I wanted to use the function in an RL agent)
Michael Bucko (Nov 08 2024 at 11:18):
Shreyas Srinivas schrieb:
Reading the PR: you want to create an opt-able set but this is trivial to extract from the generator script for equations isn't it?
I basically needed a Python script for the RL work. My alternative was the FMT tool.
Shreyas Srinivas (Nov 08 2024 at 11:18):
But equations are not in and of themselves satisfied or refuted?
Shreyas Srinivas (Nov 08 2024 at 11:18):
I assume there is more context here
Shreyas Srinivas (Nov 08 2024 at 11:18):
I think building off the FMT tool is a good idea
Shreyas Srinivas (Nov 08 2024 at 11:18):
In that, you can simply run and output the implication list, process it, and see what RL yields
Michael Bucko (Nov 08 2024 at 11:19):
Shreyas Srinivas schrieb:
I have two questions:
- Why do you want the following: "A Python function + CLI that takes the equation list as input, an operation table as an argument, and returns JSON with 1/0 for each equation."
- Why can't it be generated from the info provided by
lake exe extract_implications
and the API that the equation_explorer uses?
- I need something like the FMT for Python developers.
- I use extract_implications too, but that takes a bit. The use case is closer to the FMT tool.
Michael Bucko (Nov 08 2024 at 11:20):
Shreyas Srinivas schrieb:
I think building off the FMT tool is a good idea
Yes, it's like FMT for Python developers who want to write scripts around optables and perhaps RL (I wrote a simple RL agent as part of my experiments).
Shreyas Srinivas (Nov 08 2024 at 11:20):
but surely python can read the output format of the FMT tool and process it as a dictionary
Michael Bucko (Nov 08 2024 at 11:20):
Shreyas Srinivas schrieb:
but surely python can read the output format of the FMT tool and process it as a dictionary
Yes, it could (assuming that the FMT has an API; I don't know about it). But SW-wise, it's not a bad idea to have a Python version.
Shreyas Srinivas (Nov 08 2024 at 11:21):
okay, so how would this work?
Shreyas Srinivas (Nov 08 2024 at 11:22):
In your PR I see another copy of the equation list
Shreyas Srinivas (Nov 08 2024 at 11:22):
And the text data included
Michael Bucko (Nov 08 2024 at 11:24):
You run it like this
python3 magmas.py operation.txt equation_list.txt > solution.txt
You can use the function from the code in Python based RL research.
Shreyas Srinivas (Nov 08 2024 at 11:24):
I mean they should be gitignored
Shreyas Srinivas (Nov 08 2024 at 11:25):
Secondly I see you are generating the equation_list. I would prefer to see the existing equation generation scripts to be used
Shreyas Srinivas (Nov 08 2024 at 11:26):
In fact it's better if your initial data structure comes from the FME tool. Although I am still not sure how much of a difference the script in and of itself makes
Shreyas Srinivas (Nov 08 2024 at 11:26):
Can you combine it with the PR where you are using its output to do something like using RL for counterexamples?
Shreyas Srinivas (Nov 08 2024 at 11:31):
Maybe the developer of the FME tool can pitch in
Shreyas Srinivas (Nov 08 2024 at 11:35):
(deleted)
Michael Bucko (Nov 08 2024 at 11:36):
Tbh I have certain time constraints atm (kids :joy: ), so please just decide whether you'd need the Python version.
(Yes, the equations file, as you mentioned, is not in .gitignore, because it's slightly different, i.e. it helps me to see the equation numbers in front of the equations).
When it comes to the RL experiments, I'll probably first make a separate post.
Shreyas Srinivas (Nov 08 2024 at 11:37):
My take is, it would make sense to combine this PR with the results of the RL experiments formalised. Then we'd have something concrete to go on.
Michael Bucko (Nov 08 2024 at 11:47):
The rl agent is still being an ongoing thing. It works (it's still not fully done, more like 90% done, look at episode number, the reward setup, and the 854n1055) like this:
(menv) riccitensor@perelman equational_theories % python3 rlmagma.py
Starting magma implication explorer...
Starting training with magma size 3...
Looking for counterexample where:
1. Equation 854 is SATISFIED
2. Equation 1055 is NOT SATISFIED
Episode 1/1000
Steps in this episode: 64544
Total actions taken: 64544
Unique states explored: 7532
Time elapsed: 5.0 seconds
Michael Bucko (Nov 08 2024 at 11:48):
But it still has issues / I still need to fix a couple of things. I'd need to work on it. My case was 854.
Shreyas Srinivas (Nov 08 2024 at 11:49):
Then let's take the time and get results from this before pushing ahead with the PR
Shreyas Srinivas (Nov 08 2024 at 11:49):
(btw could you mark it as a draft please?)
Michael Bucko (Nov 08 2024 at 11:49):
Sure! I'll make it a draft.
Michael Bucko (Nov 08 2024 at 11:50):
It's a draft now.
Shreyas Srinivas (Nov 08 2024 at 11:51):
Thanks :)
Michael Bucko (Nov 08 2024 at 11:55):
btw. the rl thing is still to be fixed (but it's 90% there), but I can share here so that you can see how I am using these functions
import numpy as np
import re
from itertools import product
import time
class MagmaEnvironment:
def __init__(self, size=3):
self.size = size
self.current_operation_table = np.random.randint(0, self.size, (self.size, self.size))
self.steps_taken = 0
def shunting_yard(self, expression):
output, stack = [], []
tokens = re.findall(r'[a-z]+|[◇*()]', expression)
precedence = {'◇': 1, '*': 1}
for token in tokens:
if token.isalpha():
output.append(token)
elif token in precedence:
while (stack and stack[-1] in precedence and
precedence[token] <= precedence[stack[-1]]):
output.append(stack.pop())
stack.append(token)
elif token == '(':
stack.append(token)
elif token == ')':
while stack and stack[-1] != '(':
output.append(stack.pop())
if stack:
stack.pop()
while stack:
if stack[-1] != '(':
output.append(stack.pop())
else:
stack.pop()
return output
def evaluate_rpn(self, rpn, op_table, variables):
stack = []
for token in rpn:
if token.isalpha():
if token not in variables:
return None
stack.append(variables[token])
elif token == '◇':
if len(stack) < 2:
return None
b, a = stack.pop(), stack.pop()
stack.append(op_table[a][b])
return stack[0] if stack else None
def get_variables(self, expression):
return set(re.findall(r'[a-z]', expression))
def check_equation_satisfaction(self, equation, op_table):
try:
lhs_expr, rhs_expr = equation.split('=')
lhs_rpn = self.shunting_yard(lhs_expr.strip())
rhs_rpn = self.shunting_yard(rhs_expr.strip())
variables = self.get_variables(equation)
table_size = len(op_table)
value_combinations = product(range(table_size), repeat=len(variables))
for values in value_combinations:
var_assignment = dict(zip(sorted(variables), values))
lhs_result = self.evaluate_rpn(lhs_rpn, op_table, var_assignment)
rhs_result = self.evaluate_rpn(rhs_rpn, op_table, var_assignment)
if lhs_result is None or rhs_result is None:
continue
if lhs_result != rhs_result:
return False, var_assignment
return True, None
except Exception as e:
print(f"Error processing equation: {equation}")
print(f"Error details: {str(e)}")
return False, None
def check_implication(self, operation_table):
equation_854 = "x = x ◇ ((y ◇ z) ◇ (x ◇ z))"
equation_1055 = "x = x ◇ ((y ◇ (z ◇ x)) ◇ x)"
eq854_satisfied, _ = self.check_equation_satisfaction(equation_854, operation_table)
eq1055_satisfied, counterexample = self.check_equation_satisfaction(equation_1055, operation_table)
if eq854_satisfied and not eq1055_satisfied:
return True, counterexample
else:
return False, None
def step(self, action):
self.steps_taken += 1
i, j = action
self.current_operation_table[i][j] = np.random.randint(0, self.size)
found_counterexample, counterexample = self.check_implication(self.current_operation_table)
if found_counterexample:
reward = 1.0
done = True
else:
reward = 0.0
done = False
state = self.get_state()
return state, reward, done, {
"counterexample": counterexample,
"steps": self.steps_taken
}
def get_state(self):
return self.current_operation_table.copy()
def reset(self):
self.steps_taken = 0
self.current_operation_table = np.random.randint(0, self.size, (self.size, self.size))
return self.get_state()
class QLearningAgent:
def __init__(self, state_space_size, action_space_size, learning_rate=0.1, discount_factor=0.95,
epsilon=0.1):
self.q_table = {}
self.lr = learning_rate
self.gamma = discount_factor
self.epsilon = epsilon
self.action_space_size = action_space_size
self.total_actions = 0
def get_action(self, state):
self.total_actions += 1
state_key = str(state.tobytes())
if state_key not in self.q_table:
self.q_table[state_key] = np.zeros(self.action_space_size)
if np.random.random() < self.epsilon:
return np.random.randint(0, self.action_space_size)
else:
return np.argmax(self.q_table[state_key])
def update(self, state, action, reward, next_state):
state_key = str(state.tobytes())
next_state_key = str(next_state.tobytes())
if next_state_key not in self.q_table:
self.q_table[next_state_key] = np.zeros(self.action_space_size)
old_value = self.q_table[state_key][action]
next_max = np.max(self.q_table[next_state_key])
new_value = (1 - self.lr) * old_value + self.lr * (reward + self.gamma * next_max)
self.q_table[state_key][action] = new_value
def train_agent(episodes=1000, size=3, verbose=True):
env = MagmaEnvironment(size=size)
agent = QLearningAgent(size**2, size**2)
start_time = time.time()
last_update = start_time
print(f"\nStarting training with magma size {size}...")
print(f"Looking for counterexample where:")
print(f" 1. Equation 854 is SATISFIED")
print(f" 2. Equation 1055 is NOT SATISFIED")
for episode in range(episodes):
state = env.reset()
done = False
while not done:
action = agent.get_action(state)
action_tuple = (action // size, action % size)
next_state, reward, done, info = env.step(action_tuple)
agent.update(state, action, reward, next_state)
state = next_state
current_time = time.time()
if verbose and current_time - last_update >= 5:
print(f"\nEpisode {episode + 1}/{episodes}")
print(f"Steps in this episode: {info['steps']}")
print(f"Total actions taken: {agent.total_actions}")
print(f"Unique states explored: {len(agent.q_table)}")
print(f"Time elapsed: {current_time - start_time:.1f} seconds")
last_update = current_time
if done and info["counterexample"] is not None:
print("\n🎉 Found valid counterexample!")
print(f"\nOperation table:")
print(env.current_operation_table)
print(f"\nCounterexample variable assignment:")
print(info["counterexample"])
print(f"\nSearch statistics:")
print(f"Episodes needed: {episode + 1}")
print(f"Total steps taken: {agent.total_actions}")
print(f"Unique states explored: {len(agent.q_table)}")
print(f"Time taken: {time.time() - start_time:.1f} seconds")
return info["counterexample"], env.current_operation_table
print("\nNo counterexample found after all episodes.")
print(f"Total steps taken: {agent.total_actions}")
print(f"Unique states explored: {len(agent.q_table)}")
print(f"Time taken: {time.time() - start_time:.1f} seconds")
return None, None
if __name__ == "__main__":
print("Starting the magma rl tool...")
counterexample, operation_table = train_agent(episodes=1000, size=3, verbose=True)
Michael Bucko (Nov 08 2024 at 11:57):
I was testing on the 854N1055, but it still fails. The main thing is the Q-learning agent. The reward, I guess, is also not good atm. I was thinking of making a gymnasium env, too.
Vlad Tsyrklevich (Nov 08 2024 at 11:58):
Ah I just saw this thread so I had commented on the issue, but I'll reproduce it here as there is more discussion here:
Equations should really uses the JSON format from lake exe export_equations
.
Also, isn't this tool already done by scripts/explore_magma.py
?
Michael Bucko (Nov 08 2024 at 11:59):
@Vlad Tsyrklevich I haven'T worked with explore_magma
, so perhaps you could judge?
Vlad Tsyrklevich (Nov 08 2024 at 11:59):
python scripts/explore_magma.py "[[1, 1], [1, 1]]"
Michael Bucko (Nov 08 2024 at 12:00):
Wow, it works. My tool does not support this format and it's mostly for the RL functions. But I guess I could've checked that tool first. Thank you @Vlad Tsyrklevich
Vlad Tsyrklevich (Nov 08 2024 at 12:01):
If you want JSON it would probably not be too hard to add --json to match your output format
Michael Bucko (Nov 08 2024 at 12:02):
Yeah, trivial ofc. But the goal for me was the RL tool above, and quick tests with operation tables. I just didn't know about explore_magma.
Zoltan A. Kocsis (Z.A.K.) (Nov 08 2024 at 14:36):
@Shreyas Srinivas writes:
Maybe the developer of the FME tool can pitch in
That's me, I guess. Sorry, I'm busy assessing masters theses and preparing for AAL Conf 2024 at the moment. Until the latter concludes (28 Nov) I won't have time to assist with the project. But if there are things you or anyone else would like me to assist with, the best way to alert me is by creating Github issues: I'll attend to them next month if nobody else claims them in the meantime.
Shreyas Srinivas (Nov 08 2024 at 14:37):
Hey, sorry I forgot for a moment there that you were busy
Shreyas Srinivas (Nov 08 2024 at 14:37):
Apologies
Last updated: May 02 2025 at 03:31 UTC