Skip to main content
Glama

mcp-solver

MIT License
133
  • Linux
  • Apple
solution.py9.77 kB
""" PySAT solution module for extracting and formatting solutions from PySAT solvers. This module provides functions for extracting solution data from PySAT solvers and converting it to a standardized format. """ import logging import os import sys from typing import Any # IMPORTANT: Properly import the PySAT library (not our local package) # First, remove the current directory from the path to avoid importing ourselves current_dir = os.path.abspath(os.path.dirname(__file__)) parent_dir = os.path.abspath(os.path.join(current_dir, "..")) if current_dir in sys.path: sys.path.remove(current_dir) if parent_dir in sys.path: sys.path.remove(parent_dir) # Add site-packages to the front of the path import site site_packages = site.getsitepackages() for p in reversed(site_packages): if p not in sys.path: sys.path.insert(0, p) # Now try to import PySAT try: from pysat.formula import CNF from pysat.solvers import Solver except ImportError: print("PySAT solver not found. Install with: pip install python-sat") sys.exit(1) # Import our error handling utilities from .error_handling import PySATError, validate_variables # Set up logger logger = logging.getLogger(__name__) # Track the last solution - make it global and accessible _LAST_SOLUTION = None # Reserved keys that shouldn't be processed as custom dictionaries RESERVED_KEYS = { "satisfiable", "model", "values", "status", "error_type", "error_message", "warnings", "unsatisfied", } class SolutionError(Exception): """ Custom exception for solution processing errors. This exception is used when errors occur during solution processing that should be captured and returned as a structured error solution. """ pass def export_solution( data: dict[str, Any] | Solver | None = None, variables: dict[str, int] | None = None, objective: float | None = None, ) -> dict[str, Any]: """ Extract and format solutions from a PySAT solver or solution data. This function processes PySAT solution data and creates a standardized output format. It supports both direct dictionary input and PySAT Solver objects. All values in custom dictionaries are automatically extracted and made available in the flat "values" dictionary. Args: data: PySAT Solver object or dictionary containing solution data variables: Dictionary mapping variable names to their variable IDs objective: Optional objective value for optimization problems Returns: Dictionary with structured solution data, including: - satisfiable: Boolean indicating satisfiability - status: String status ("sat", "unsat", or "error") - values: Flattened dictionary of all values from custom dictionaries - model: List of true variable IDs (if satisfiable) - Other custom dictionaries provided in the input If an error occurs, the returned dictionary will include: - satisfiable: False - error_type: Type of the error - error_message: Detailed error message - status: "error" """ global _LAST_SOLUTION try: solution_data = _process_input_data(data, variables, objective) solution_data = _extract_values_from_dictionaries(solution_data) # Log the solution data logger.debug(f"Setting _LAST_SOLUTION: {solution_data}") print(f"DEBUG - _LAST_SOLUTION set to: {solution_data}") # Store the solution and return it _LAST_SOLUTION = solution_data return solution_data except Exception as e: # Create an error solution with structured error information error_solution = _create_error_solution(e) # Store and return the error solution _LAST_SOLUTION = error_solution logger.error(f"Error in export_solution: {e!s}", exc_info=True) print(f"DEBUG - _LAST_SOLUTION set to error: {error_solution}") return error_solution # Removed export_maxsat_solution function as it's now in the MaxSAT module def _process_input_data( data: dict[str, Any] | Solver | None, variables: dict[str, int] | None = None, objective: float | None = None, ) -> dict[str, Any]: """ Process input data from various sources into a standardized solution dictionary. Args: data: PySAT Solver object or dictionary containing solution data variables: Dictionary mapping variable names to their variable IDs objective: Optional objective value for optimization problems Returns: Standardized solution dictionary Raises: SolutionError: If the input data cannot be processed """ # Initialize solution data solution_data: dict[str, Any] = {} # Case 1: Direct dictionary data if isinstance(data, dict): solution_data = data.copy() # Case 2: PySAT Solver object elif data is not None and hasattr(data, "get_model") and callable(data.get_model): # Extract model from solver (solver.solve() should have already been called) model = data.get_model() if model is not None: # Solver has a satisfiable solution solution_data["satisfiable"] = True solution_data["model"] = model # Extract variable assignments if variables dictionary is provided if variables: # Validate variables dictionary errors = validate_variables(variables) if errors: error_msg = "; ".join(errors) raise SolutionError(f"Invalid variables dictionary: {error_msg}") # Map variable names to their truth values based on the model solution_data["assignment"] = { name: (var_id in model) if var_id > 0 else ((-var_id) not in model) for name, var_id in variables.items() } else: # No model means unsatisfiable solution_data["satisfiable"] = False # Case 3: None or unknown type elif data is None: # Default to empty unsatisfiable solution solution_data["satisfiable"] = False else: raise SolutionError(f"Unsupported data type: {type(data).__name__}") # Ensure the satisfiable flag is set if "satisfiable" not in solution_data: solution_data["satisfiable"] = False # Set the status field to match satisfiability solution_data["status"] = ( "sat" if solution_data.get("satisfiable", False) else "unsat" ) # Include objective value if provided if objective is not None: solution_data["objective"] = objective # Ensure values dictionary exists (may be populated later) solution_data["values"] = solution_data.get("values", {}) return solution_data def _extract_values_from_dictionaries(solution_data: dict[str, Any]) -> dict[str, Any]: """ Extract values from custom dictionaries into a flat values dictionary. Args: solution_data: The solution dictionary to process Returns: The processed solution dictionary with extracted values """ # Skip extraction for unsatisfiable solutions that don't need values if not solution_data.get("satisfiable", False): # Ensure values dictionary exists even for unsatisfiable solutions solution_data["values"] = solution_data.get("values", {}) return solution_data # Create a new values dictionary values: dict[str, Any] = {} # First pass: collect all keys to detect potential collisions key_counts: dict[str, int] = {} for key, value in solution_data.items(): if key not in RESERVED_KEYS and isinstance(value, dict): for subkey in value.keys(): key_counts[subkey] = key_counts.get(subkey, 0) + 1 # Second pass: extract values and handle collisions for key, value in solution_data.items(): if key not in RESERVED_KEYS and isinstance(value, dict): for subkey, subvalue in value.items(): # Only extract leaf nodes (not nested dictionaries) if not isinstance(subvalue, dict): if key_counts[subkey] > 1: # This is a collision - prefix with parent dictionary name values[f"{key}.{subkey}"] = subvalue else: # No collision - use the key directly values[subkey] = subvalue # Update the values dictionary in the solution solution_data["values"] = values return solution_data def _create_error_solution(error: Exception) -> dict[str, Any]: """ Create a standardized error solution dictionary from an exception. Args: error: The exception that occurred Returns: A solution dictionary with error information """ # If it's already a structured PySAT error, use its context if isinstance(error, PySATError): error_context = getattr(error, "context", {}) error_solution = { "satisfiable": False, "error_type": type(error).__name__, "error_message": str(error), "status": "error", "values": {}, } # Add context if available if error_context: error_solution["error_context"] = error_context else: # For standard exceptions, create a basic error solution error_solution = { "satisfiable": False, "error_type": type(error).__name__, "error_message": str(error), "status": "error", "values": {}, } return error_solution

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/szeider/mcp-solver'

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