Overview
The BaseProblem
class serves as the foundation for all optimization problems in the Qubots framework, providing a standardized interface that enables seamless integration with optimizers, benchmarking systems, and the Rastion platform. As part of the modular “QUBO + Bot” architecture, problems are designed to be reusable, composable components that can work with any compatible optimizer.
File Purpose
The base_problem.py
file defines:
- BaseProblem: Abstract base class for all optimization problems
- ProblemMetadata: Comprehensive metadata structure for problems
- EvaluationResult: Standardized result format for solution evaluation
- Enumerations: Problem types, objective types, and difficulty levels
Quick Start
from qubots.base_problem import BaseProblem, ProblemMetadata, ProblemType, EvaluationResult
class MyProblem(BaseProblem):
def __init__(self):
metadata = ProblemMetadata(
name="My Optimization Problem",
description="Custom optimization problem",
problem_type=ProblemType.COMBINATORIAL
)
super().__init__(metadata)
def _get_default_metadata(self):
return self.metadata
def evaluate_solution(self, solution):
# Your evaluation logic here
return 42.0 # Can return float or EvaluationResult
Core Enumerations
ProblemType
Categorizes optimization problems by their mathematical structure:
class ProblemType(Enum):
CONTINUOUS = "continuous" # Real-valued variables
DISCRETE = "discrete" # Integer variables
COMBINATORIAL = "combinatorial" # Permutations, selections
MIXED_INTEGER = "mixed_integer" # Mixed continuous/discrete
CONSTRAINT_SATISFACTION = "constraint_satisfaction"
MULTI_OBJECTIVE = "multi_objective" # Multiple objectives
DYNAMIC = "dynamic" # Time-varying problems
STOCHASTIC = "stochastic" # Uncertain parameters
ObjectiveType
Defines optimization direction:
class ObjectiveType(Enum):
MINIMIZE = "minimize" # Find minimum value
MAXIMIZE = "maximize" # Find maximum value
DifficultyLevel
Indicates problem complexity using numeric levels:
class DifficultyLevel(Enum):
BEGINNER = 1 # Simple problems, quick solutions
INTERMEDIATE = 2 # Moderate complexity
ADVANCED = 3 # Complex, computationally challenging
EXPERT = 4 # Research-level difficulty
RESEARCH = 5 # Cutting-edge research problems
Comprehensive metadata structure for optimization problems:
@dataclass
class ProblemMetadata:
# Basic information (required)
name: str # Human-readable name
description: str # Detailed description
problem_type: ProblemType # Problem category
# Optimization settings
objective_type: ObjectiveType = ObjectiveType.MINIMIZE
difficulty_level: DifficultyLevel = DifficultyLevel.INTERMEDIATE
# Classification
domain: str = "general" # Problem domain (e.g., "routing", "scheduling")
tags: Set[str] = field(default_factory=set) # Descriptive tags
# Authorship and versioning
author: str = "" # Problem author
version: str = "1.0.0" # Version number
license: str = "MIT" # License type
created_at: datetime = field(default_factory=datetime.now)
updated_at: datetime = field(default_factory=datetime.now)
# Problem characteristics
dimension: Optional[int] = None # Number of variables
variable_bounds: Optional[Dict[str, Tuple[float, float]]] = None
constraints_count: int = 0 # Number of constraints
# Performance characteristics
evaluation_complexity: str = "O(n)" # Big O notation for evaluation
memory_complexity: str = "O(n)" # Memory complexity
typical_runtime_ms: Optional[float] = None # Typical evaluation time
# Benchmarking information
known_optimal: Optional[float] = None # Known optimal value
benchmark_instances: List[str] = field(default_factory=list)
reference_papers: List[str] = field(default_factory=list)
def to_dict(self) -> Dict[str, Any]:
"""Convert metadata to dictionary for serialization."""
# Returns dictionary representation for JSON serialization
EvaluationResult Class
Standardized result format for solution evaluation:
@dataclass
class EvaluationResult:
objective_value: float # Primary objective value
is_feasible: bool = True # Whether solution satisfies constraints
constraint_violations: List[str] = field(default_factory=list)
evaluation_time_ms: float = 0.0 # Evaluation time in milliseconds
additional_metrics: Dict[str, float] = field(default_factory=dict)
def __post_init__(self):
"""Validate the evaluation result."""
if self.constraint_violations:
self.is_feasible = False
Key Features:
- Automatic feasibility detection: If constraint violations are present,
is_feasible
is automatically set to False
- Timing information: Evaluation time tracked in milliseconds for performance analysis
- Extensible metrics: Additional metrics can be stored for detailed analysis
- Constraint tracking: Detailed constraint violation information for debugging
BaseProblem Class
The BaseProblem
class provides a comprehensive foundation for optimization problems with automatic statistics tracking, metadata management, and standardized interfaces.
Constructor
def __init__(self, metadata: Optional[ProblemMetadata] = None):
"""
Initialize the problem with metadata.
Args:
metadata: Problem metadata. If None, subclasses should provide default metadata.
"""
Internal State:
_metadata
: Problem metadata
_evaluation_count
: Number of evaluations performed
_total_evaluation_time
: Total time spent on evaluations (milliseconds)
_best_known_solution
: Best solution found so far
_best_known_value
: Best objective value found
_instance_id
: Unique identifier for this problem instance
Abstract Methods
Subclasses must implement these methods:
@abstractmethod
def _get_default_metadata(self) -> ProblemMetadata:
"""
Return default metadata for this problem type.
Subclasses must implement this method.
"""
pass
evaluate_solution()
@abstractmethod
def evaluate_solution(self, solution: Any) -> Union[float, EvaluationResult]:
"""
Evaluate a candidate solution and return its objective value.
Args:
solution: The candidate solution to evaluate
Returns:
Either a float (objective value) or EvaluationResult for detailed feedback
"""
pass
Note: This method can return either a simple float
for basic use cases or a full EvaluationResult
for detailed information including constraint violations and additional metrics.
Core Methods
evaluate_solution_detailed()
def evaluate_solution_detailed(self, solution: Any) -> EvaluationResult:
"""
Evaluate a solution and return detailed results including timing and feasibility.
Args:
solution: The candidate solution to evaluate
Returns:
EvaluationResult with comprehensive evaluation information
"""
This method automatically:
- Times the evaluation
- Updates evaluation statistics
- Tracks the best known solution
- Handles both float and EvaluationResult returns from
evaluate_solution()
is_feasible()
def is_feasible(self, solution: Any) -> bool:
"""
Check if the solution is valid under problem constraints.
Default implementation returns True. Override for constrained problems.
Args:
solution: The candidate solution to check
Returns:
True if solution is feasible, False otherwise
"""
Properties
@property
def metadata(self) -> ProblemMetadata:
"""Get problem metadata."""
return self._metadata
evaluation_count
@property
def evaluation_count(self) -> int:
"""Get number of solution evaluations performed."""
return self._evaluation_count
average_evaluation_time_ms
@property
def average_evaluation_time_ms(self) -> float:
"""Get average evaluation time in milliseconds."""
return self._total_evaluation_time / self._evaluation_count if self._evaluation_count > 0 else 0.0
best_known_solution
@property
def best_known_solution(self) -> Optional[Any]:
"""Get the best solution found so far."""
return self._best_known_solution
best_known_value
@property
def best_known_value(self) -> float:
"""Get the best objective value found so far."""
return self._best_known_value
instance_id
@property
def instance_id(self) -> str:
"""Get unique instance identifier."""
return self._instance_id
Optional Methods
These methods have default implementations but can be overridden for enhanced functionality:
random_solution()
def random_solution(self) -> Any:
"""
Generate a random feasible solution.
Default implementation raises NotImplementedError.
Returns:
A random feasible solution
Raises:
NotImplementedError: If not implemented by subclass
"""
def validate_solution_format(self, solution: Any) -> bool:
"""
Validate that a solution has the correct format for this problem.
Default implementation returns True. Override for specific validation.
Args:
solution: The solution to validate
Returns:
True if format is valid, False otherwise
"""
get_neighbor_solution()
def get_neighbor_solution(self, solution: Any, step_size: float = 1.0) -> Any:
"""
Generate a neighboring solution for local search algorithms.
Default implementation raises NotImplementedError.
Args:
solution: Current solution
step_size: Size of the neighborhood step
Returns:
A neighboring solution
Raises:
NotImplementedError: If not implemented by subclass
"""
distance_between_solutions()
def distance_between_solutions(self, solution1: Any, solution2: Any) -> float:
"""
Calculate distance between two solutions.
Default implementation raises NotImplementedError.
Args:
solution1: First solution
solution2: Second solution
Returns:
Distance between solutions
Raises:
NotImplementedError: If not implemented by subclass
"""
Utility Methods
get_bounds()
def get_bounds(self) -> Optional[Dict[str, Tuple[float, float]]]:
"""
Get variable bounds for continuous problems.
Returns:
Dictionary mapping variable names to (min, max) bounds, or None
"""
get_constraints()
def get_constraints(self) -> List[str]:
"""
Get list of constraint descriptions.
Default implementation returns empty list.
Returns:
List of constraint descriptions
"""
get_solution_space_info()
def get_solution_space_info(self) -> Dict[str, Any]:
"""
Get information about the solution space.
Returns:
Dictionary containing solution space characteristics
"""
reset_statistics()
def reset_statistics(self):
"""Reset evaluation statistics."""
get_statistics()
def get_statistics(self) -> Dict[str, Any]:
"""
Get comprehensive statistics about this problem instance.
Returns:
Dictionary containing evaluation statistics
"""
export_problem_definition()
def export_problem_definition(self) -> Dict[str, Any]:
"""
Export complete problem definition for sharing or storage.
Returns:
Dictionary containing complete problem definition
"""
Usage Examples
Basic Problem Implementation
from qubots.base_problem import BaseProblem, ProblemMetadata, ProblemType, EvaluationResult
import random
class SimpleQuadraticProblem(BaseProblem):
"""Simple quadratic minimization problem: f(x) = x^2"""
def __init__(self):
metadata = ProblemMetadata(
name="Simple Quadratic Problem",
description="Minimize f(x) = x^2",
problem_type=ProblemType.CONTINUOUS,
domain="mathematical_functions",
tags={"quadratic", "single_variable", "convex"}
)
super().__init__(metadata)
def _get_default_metadata(self) -> ProblemMetadata:
return self.metadata
def evaluate_solution(self, solution: float) -> EvaluationResult:
"""Evaluate f(x) = x^2"""
objective_value = solution ** 2
return EvaluationResult(
objective_value=objective_value,
is_feasible=True,
additional_metrics={
"input_value": solution,
"gradient": 2 * solution
}
)
def random_solution(self) -> float:
"""Generate random solution in [-10, 10]"""
return random.uniform(-10, 10)
def get_neighbor_solution(self, solution: float, step_size: float = 1.0) -> float:
"""Generate neighbor by adding random noise"""
return solution + random.gauss(0, step_size)
# Usage
problem = SimpleQuadraticProblem()
solution = 3.0
result = problem.evaluate_solution_detailed(solution)
print(f"f({solution}) = {result.objective_value}")
print(f"Evaluation time: {result.evaluation_time_ms:.2f}ms")
Advanced Problem with Constraints
from qubots.base_problem import BaseProblem, ProblemMetadata, ProblemType, EvaluationResult
from typing import Tuple, List
class ConstrainedOptimizationProblem(BaseProblem):
"""Minimize f(x,y) = x^2 + y^2 subject to x + y >= 1"""
def __init__(self):
metadata = ProblemMetadata(
name="Constrained Quadratic Problem",
description="Minimize x^2 + y^2 subject to x + y >= 1",
problem_type=ProblemType.CONTINUOUS,
dimension=2,
constraints_count=1,
variable_bounds={"x": (-10, 10), "y": (-10, 10)}
)
super().__init__(metadata)
def _get_default_metadata(self) -> ProblemMetadata:
return self.metadata
def evaluate_solution(self, solution: Tuple[float, float]) -> EvaluationResult:
x, y = solution
objective_value = x**2 + y**2
# Check constraint: x + y >= 1
constraint_violation = max(0, 1 - (x + y))
is_feasible = constraint_violation == 0
violations = []
if not is_feasible:
violations.append(f"Constraint x + y >= 1 violated by {constraint_violation:.4f}")
return EvaluationResult(
objective_value=objective_value,
is_feasible=is_feasible,
constraint_violations=violations,
additional_metrics={
"constraint_violation": constraint_violation,
"distance_to_origin": (x**2 + y**2)**0.5
}
)
def is_feasible(self, solution: Tuple[float, float]) -> bool:
"""Check if solution satisfies x + y >= 1"""
x, y = solution
return x + y >= 1
def get_constraints(self) -> List[str]:
"""Return constraint descriptions"""
return ["x + y >= 1"]
# Usage
problem = ConstrainedOptimizationProblem()
solution = (0.5, 0.6) # Feasible solution
result = problem.evaluate_solution(solution)
print(f"Objective: {result.objective_value}")
print(f"Feasible: {result.is_feasible}")
Simple Float Return Example
For basic problems, you can return just a float:
from qubots.base_problem import BaseProblem, ProblemMetadata, ProblemType
class SimpleProblem(BaseProblem):
def __init__(self):
metadata = ProblemMetadata(
name="Simple Problem",
description="Returns x^2",
problem_type=ProblemType.CONTINUOUS
)
super().__init__(metadata)
def _get_default_metadata(self) -> ProblemMetadata:
return self.metadata
def evaluate_solution(self, solution: float) -> float:
"""Simple evaluation returning just the objective value"""
return solution ** 2
# The framework automatically wraps this in EvaluationResult
problem = SimpleProblem()
result = problem.evaluate_solution_detailed(3.0)
print(f"Objective: {result.objective_value}") # 9.0
print(f"Feasible: {result.is_feasible}") # True
Developer Notes
Design Principles
- Modular Architecture: Problems are independent, reusable components following the “QUBO + Bot” philosophy
- Standardized Interface: Consistent API across all problem types enables optimizer interoperability
- Comprehensive Metadata: Rich metadata supports problem discovery, classification, and benchmarking
- Automatic Statistics: Built-in performance tracking without manual instrumentation
- Flexible Solution Format: Solutions can be any Python object (numbers, lists, tuples, custom classes)
- Minimal Overhead: Statistics tracking adds negligible performance cost
- Memory Efficiency: Only essential data is cached (best solution, basic statistics)
- Timing Precision: Uses
time.perf_counter()
for high-precision timing
- Lazy Evaluation: Complex operations only performed when requested
Extension Guidelines
When implementing BaseProblem subclasses:
- Always implement
_get_default_metadata()
and evaluate_solution()
- Override
is_feasible()
for constrained problems
- Implement
random_solution()
for random initialization support
- Add
get_neighbor_solution()
for local search compatibility
- Use meaningful metadata including tags, domain, and complexity information
- Return EvaluationResult for detailed feedback when possible
API Reference Summary
Required Abstract Methods
_get_default_metadata() -> ProblemMetadata
evaluate_solution(solution: Any) -> Union[float, EvaluationResult]
Key Properties
metadata: ProblemMetadata
- Problem metadata
evaluation_count: int
- Number of evaluations performed
best_known_solution: Optional[Any]
- Best solution found
best_known_value: float
- Best objective value found
instance_id: str
- Unique instance identifier
Core Methods
evaluate_solution_detailed(solution) -> EvaluationResult
- Detailed evaluation with timing
is_feasible(solution) -> bool
- Check solution feasibility
get_statistics() -> Dict[str, Any]
- Get comprehensive statistics
reset_statistics()
- Reset evaluation tracking
Optional Methods (Override as needed)
random_solution() -> Any
- Generate random solution
get_neighbor_solution(solution, step_size) -> Any
- Generate neighbor
distance_between_solutions(sol1, sol2) -> float
- Calculate distance
validate_solution_format(solution) -> bool
- Validate solution format
get_bounds() -> Optional[Dict[str, Tuple[float, float]]]
- Get variable bounds
get_constraints() -> List[str]
- Get constraint descriptions
Next Steps