Skip to main content
Glama
mace4_wrapper.py8.44 kB
""" Mace4 model finder wrapper for counterexample finding and model generation. Mace4 is a finite model finder that searches for finite models and counterexamples to theorems. It's particularly useful when a proof attempt fails - Mace4 can often find a counterexample showing why the statement isn't universally true. """ import logging import os import subprocess import tempfile from pathlib import Path from typing import Any, Dict, List, Optional logger = logging.getLogger("mcp_logic.mace4") class Mace4Wrapper: """Wrapper for Mace4 model finder""" def __init__(self, mace4_path: Path): """Initialize Mace4 wrapper Args: mace4_path: Path to directory containing Mace4 binary """ self.mace4_path = Path(mace4_path) # Try both mace4.exe (Windows) and mace4 (Linux/Mac) self.mace4_exe = self.mace4_path / "mace4.exe" if not self.mace4_exe.exists(): self.mace4_exe = self.mace4_path / "mace4" if not self.mace4_exe.exists(): raise FileNotFoundError(f"Mace4 not found at {self.mace4_exe} or with .exe extension") logger.debug(f"Initialized Mace4 wrapper with Mace4 at {self.mace4_exe}") def _create_input_file(self, premises: List[str], goal: Optional[str] = None, domain_size: Optional[int] = None) -> Path: """Create a Mace4 input file Args: premises: List of logical premises (assumptions) goal: Goal to disprove (for counterexamples). If None, find any model. domain_size: Maximum domain size to search. If None, Mace4 will increment. Returns: Path to created input file """ content = [] # Domain size configuration if domain_size is not None: content.append(f"assign(domain_size, {domain_size}).") else: content.append("assign(domain_size, 2).") # Start at 2 content.append("assign(end_size, 10).") # Try up to size 10 # Timeout content.append("assign(max_seconds, 60).") content.append("") # Premises (assumptions) content.append("formulas(assumptions).") for premise in premises: content.append(premise if premise.endswith(".") else premise + ".") content.append("end_of_list.") content.append("") # Goal (for counterexamples) - negate it to find models where it's false if goal: content.append("formulas(goals).") # Negate the goal - if Mace4 finds a model, the goal is false in that model negated_goal = f"-(({goal.rstrip('.')}))" content.append(negated_goal + ".") content.append("end_of_list.") input_content = "\n".join(content) logger.debug(f"Created Mace4 input file content:\n{input_content}") fd, path = tempfile.mkstemp(suffix=".in", text=True) with os.fdopen(fd, "w") as f: f.write(input_content) return Path(path) def _run_mace4(self, input_path: Path, timeout: int = 60) -> Dict[str, Any]: """Run Mace4 model finder Args: input_path: Path to input file timeout: Timeout in seconds Returns: Dictionary with result, model details, and output """ try: logger.debug(f"Running Mace4 with input file: {input_path}") # Set working directory to Mace4 directory cwd = str(self.mace4_exe.parent) result = subprocess.run([str(self.mace4_exe), "-f", str(input_path)], capture_output=True, text=True, timeout=timeout, cwd=cwd) logger.debug(f"Mace4 stdout:\n{result.stdout}") if result.stderr: logger.debug(f"Mace4 stderr:\n{result.stderr}") # Parse Mace4 output if "DOMAIN SIZE" in result.stdout and "interpretation(" in result.stdout: # Model found! model = self._parse_model(result.stdout) return {"result": "model_found", "model": model, "complete_output": result.stdout} elif "SEARCH FAILED" in result.stdout or "SEARCH TERMINATED" in result.stdout: return {"result": "no_model_found", "reason": "No finite model found within domain size limits", "complete_output": result.stdout} elif "Fatal error" in result.stderr or "Fatal error" in result.stdout: return {"result": "error", "reason": "Syntax error or invalid input", "error": result.stderr if result.stderr else result.stdout} else: return {"result": "unknown", "reason": "Unexpected Mace4 output", "output": result.stdout, "error": result.stderr} except subprocess.TimeoutExpired: logger.error(f"Mace4 search timed out after {timeout} seconds") return {"result": "timeout", "reason": f"Model search exceeded {timeout} seconds"} except Exception as e: logger.error(f"Mace4 error: {e}") return {"result": "error", "reason": str(e)} finally: try: input_path.unlink() # Clean up temp file except (FileNotFoundError, PermissionError, OSError): pass # Temp file cleanup failed, not critical def _parse_model(self, output: str) -> Dict[str, Any]: """Parse Mace4 model output into structured format Args: output: Raw Mace4 output Returns: Structured model representation """ model = {"domain_size": None, "predicates": {}, "functions": {}, "constants": {}, "raw_interpretation": ""} # Extract domain size for line in output.split("\n"): if "DOMAIN SIZE" in line: try: size = int(line.split()[-1]) model["domain_size"] = size except (ValueError, IndexError): pass # Failed to parse domain size, not critical # Extract interpretation block if "interpretation(" in output: start = output.find("interpretation(") end = output.find("end_of_list", start) if end > start: interpretation = output[start : end + len("end_of_list")] model["raw_interpretation"] = interpretation.strip() # Parse individual predicates and functions # This is a simplified parser - full Mace4 output can be complex for line in interpretation.split("\n"): line = line.strip() if line.startswith("function(") or line.startswith("relation("): # Extract name and values model["raw_interpretation"] += f"\n{line}" return model def find_model(self, premises: List[str], domain_size: Optional[int] = None) -> Dict[str, Any]: """Find a model that satisfies the given premises Args: premises: List of logical premises domain_size: Specific domain size, or None to search incrementally Returns: Result dictionary with model if found """ input_file = self._create_input_file(premises, goal=None, domain_size=domain_size) return self._run_mace4(input_file) def find_counterexample(self, premises: List[str], conclusion: str, domain_size: Optional[int] = None) -> Dict[str, Any]: """Find a counterexample showing the conclusion doesn't follow from premises This searches for a model where all premises are true but the conclusion is false. If such a model is found, it proves the conclusion doesn't logically follow. Args: premises: List of logical premises conclusion: Conclusion to disprove domain_size: Specific domain size, or None to search incrementally Returns: Result dictionary with counterexample model if found """ input_file = self._create_input_file(premises, goal=conclusion, domain_size=domain_size) result = self._run_mace4(input_file) # If we found a model, it's a counterexample if result["result"] == "model_found": result["interpretation"] = f"Counterexample found: The premises are satisfied but the conclusion '{conclusion}' is FALSE in this model." return result

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/angrysky56/mcp-logic'

If you have feedback or need assistance with the MCP directory API, please join our Discord server