Source code for variables.OXVariableSet

"""
Variable Set Container Module
=============================

This module provides the OXVariableSet class, a specialized container for managing 
collections of decision variables in optimization problems within the OptiX framework. 
It extends the base OXObjectPot class to provide type-safe, efficient storage and 
retrieval of OXVariable instances with advanced querying capabilities.

The module implements comprehensive variable collection management with features
essential for large-scale optimization modeling, including relationship-based
querying, type enforcement, and efficient iteration patterns.

Key Features:
    - **Type Safety**: Enforces that only OXVariable instances can be stored
    - **Relationship Querying**: Advanced search capabilities based on variable relationships
    - **Collection Operations**: Full support for add, remove, and iteration operations
    - **Memory Efficiency**: Optimized storage for large variable collections
    - **Thread Safety**: Safe concurrent access for read operations

Core Components:
    - **OXVariableSet**: Main container class with type-safe operations
    - **Query System**: Flexible relationship-based variable searching
    - **Type Validation**: Automatic checking of variable types during operations
    - **Iterator Support**: Full Python iteration protocol implementation

Use Cases:
    - Managing decision variables in linear programming models
    - Organizing variables by business entities or constraint groups
    - Building variable collections for complex optimization scenarios
    - Querying variables based on data relationships and attributes
    - Efficient variable storage for large-scale optimization problems

Example:
    Managing optimization variables with relationship querying:

    .. code-block:: python

        from variables.OXVariableSet import OXVariableSet
        from variables.OXVariable import OXVariable
        from uuid import uuid4
        
        # Create variable set and variables
        var_set = OXVariableSet()
        
        # Create variables for different customers
        customer1_id = uuid4()
        customer2_id = uuid4()
        
        var1 = OXVariable(name="production_cust1", lower_bound=0, upper_bound=1000)
        var1.related_data["customer"] = customer1_id
        
        var2 = OXVariable(name="production_cust2", lower_bound=0, upper_bound=800)
        var2.related_data["customer"] = customer2_id
        
        # Add variables to set
        var_set.add_object(var1)
        var_set.add_object(var2)
        
        # Query variables by customer relationship
        customer1_vars = var_set.query(customer=customer1_id)
        print(f"Found {len(customer1_vars)} variables for customer 1")

Module Dependencies:
    - dataclasses: For structured class definitions with automatic method generation
    - base: For OXObjectPot inheritance and OXException error handling
    - variables.OXVariable: For OXVariable type definitions and validation
"""

from dataclasses import dataclass

from base import OXObjectPot, OXObject, OXception
from variables.OXVariable import OXVariable


[docs] @dataclass class OXVariableSet(OXObjectPot): """ Type-safe container for managing collections of optimization variables. This specialized container class extends OXObjectPot to provide comprehensive management of OXVariable instances with strict type enforcement and advanced querying capabilities. It serves as the primary collection mechanism for organizing decision variables in complex optimization models. The class implements robust validation, efficient storage, and relationship-based querying to support large-scale optimization scenarios where variables need to be organized, searched, and managed based on their business relationships and mathematical properties. Key Capabilities: - Strict type enforcement ensuring only OXVariable instances are stored - Relationship-based querying using variable related_data attributes - Full iteration support with Python's standard collection protocols - Memory-efficient storage optimized for large variable collections - Thread-safe read operations for concurrent optimization environments Architecture: The container inherits from OXObjectPot to leverage proven collection management patterns while adding variable-specific functionality such as relationship querying and type validation. All operations maintain the mathematical integrity required for optimization model consistency. Performance Characteristics: - Variable addition: O(1) average case with type validation overhead - Variable removal: O(n) linear search with type validation - Relationship queries: O(n) linear scan with predicate evaluation - Iteration: O(n) with minimal memory overhead for large collections Thread Safety: - Read operations (iteration, querying, length) are thread-safe - Write operations (add, remove) require external synchronization - Related_data modifications on contained variables need coordination Examples: Build and query variable collections for optimization models: .. code-block:: python from variables.OXVariableSet import OXVariableSet from variables.OXVariable import OXVariable from uuid import uuid4 # Create variable set for production planning production_vars = OXVariableSet() # Create variables for different products and facilities facility1_id = uuid4() facility2_id = uuid4() product_a_id = uuid4() product_b_id = uuid4() # Production variable for Product A at Facility 1 var_a1 = OXVariable( name="prod_A_facility1", description="Production of Product A at Facility 1", lower_bound=0, upper_bound=1000 ) var_a1.related_data["facility"] = facility1_id var_a1.related_data["product"] = product_a_id # Production variable for Product B at Facility 2 var_b2 = OXVariable( name="prod_B_facility2", description="Production of Product B at Facility 2", lower_bound=0, upper_bound=800 ) var_b2.related_data["facility"] = facility2_id var_b2.related_data["product"] = product_b_id # Add variables to the set production_vars.add_object(var_a1) production_vars.add_object(var_b2) # Query variables by facility facility1_vars = production_vars.query(facility=facility1_id) print(f"Facility 1 variables: {[v.name for v in facility1_vars]}") # Query variables by product type product_a_vars = production_vars.query(product=product_a_id) print(f"Product A variables: {[v.name for v in product_a_vars]}") # Iterate through all variables total_capacity = sum(var.upper_bound for var in production_vars) print(f"Total production capacity: {total_capacity}") Note: - Type validation occurs at runtime during add/remove operations - Query operations scan all variables for matching relationships - Container operations maintain the same semantics as OXObjectPot - Variables can be queried by any combination of related_data attributes See Also: :class:`base.OXObjectPot.OXObjectPot`: Base container class with collection operations. :class:`variables.OXVariable.OXVariable`: Variable type managed by this container. :class:`base.OXObject`: Base object type for UUID and serialization support. """
[docs] def add_object(self, obj: OXObject): """ Add an OXVariable instance to the variable collection. This method performs type validation to ensure only OXVariable instances are added to the set, maintaining type safety and collection integrity. The validation occurs before delegation to the parent container's add operation, preventing invalid state and ensuring optimization model consistency. The method enforces the container's type invariant that all contained objects must be optimization variables, which is essential for the specialized querying and management operations provided by this class. Args: obj (OXObject): The variable object to add to the collection. Must be an instance of OXVariable or its subclasses. The object will be stored and can be retrieved through iteration or relationship-based queries. Raises: OXception: If the provided object is not an instance of OXVariable. This strict type checking prevents runtime errors and maintains the mathematical integrity of variable collections. Performance: - Time complexity: O(1) average case for the type check and container addition - Space complexity: O(1) additional memory for the new variable reference - Type validation adds minimal overhead compared to container operations Note: - Duplicate variables (same UUID) will be handled by the parent container - The variable's related_data can be modified after addition for querying - Type validation is strict and does not allow duck-typing or coercion Examples: Add variables with proper type validation: .. code-block:: python var_set = OXVariableSet() # Valid addition - OXVariable instance production_var = OXVariable(name="production", lower_bound=0) var_set.add_object(production_var) # Success # Valid addition - OXVariable subclass deviation_var = OXDeviationVar(name="deviation") var_set.add_object(deviation_var) # Success # Invalid addition - wrong type from base.OXObject import OXObject generic_obj = OXObject() try: var_set.add_object(generic_obj) # Raises OXception except OXception as e: print("Type validation prevented invalid addition") See Also: :meth:`remove_object`: Type-safe variable removal from the collection. :meth:`query`: Relationship-based variable querying capabilities. """ if not isinstance(obj, OXVariable): raise OXception("Only OXVariable can be added to OXVariableSet") super().add_object(obj)
[docs] def remove_object(self, obj: OXObject): """ Remove an OXVariable instance from the variable collection. This method performs type validation to ensure only OXVariable instances are removed from the set, maintaining type safety and preventing invalid removal operations. The validation occurs before delegation to the parent container's removal operation. Args: obj (OXObject): The variable object to remove from the collection. Must be an instance of OXVariable that is currently stored in the set. The object will be completely removed from the collection. Raises: OXception: If the provided object is not an instance of OXVariable. This maintains the type safety invariant of the container. ValueError: If the object is not currently in the set. This is raised by the parent container when attempting to remove a non-existent object. Performance: - Time complexity: O(n) where n is the number of variables (linear search) - Space complexity: O(1) as removal only deallocates the reference - Type validation overhead is minimal compared to the search operation Note: - Removal is based on object identity (UUID), not value equality - After removal, the variable can no longer be queried or iterated - Related data relationships are not automatically cleaned up Examples: Remove variables with proper validation: .. code-block:: python var_set = OXVariableSet() production_var = OXVariable(name="production") var_set.add_object(production_var) # Valid removal - variable exists in set var_set.remove_object(production_var) # Success # Invalid removal - wrong type from base.OXObject import OXObject generic_obj = OXObject() try: var_set.remove_object(generic_obj) # Raises OXception except OXception as e: print("Type validation prevented invalid removal") # Invalid removal - variable not in set try: var_set.remove_object(production_var) # Raises ValueError except ValueError as e: print("Variable not found in set") See Also: :meth:`add_object`: Type-safe variable addition to the collection. :meth:`query`: Find variables before removal operations. """ if not isinstance(obj, OXVariable): raise OXception("Only OXVariable can be removed from OXVariableSet") super().remove_object(obj)
[docs] def query(self, **kwargs) -> list[OXObject]: """ Search for variables based on their relationship data attributes. This method provides powerful relationship-based querying capabilities by searching through all variables in the collection and returning those that match the specified related_data criteria. Variables are included in the result only if they contain ALL specified relationship key-value pairs. The query system enables complex filtering scenarios essential for large-scale optimization models where variables need to be organized and accessed based on their business relationships, such as customers, facilities, products, time periods, or other domain-specific entities. Query Logic: - Variables must have ALL specified keys in their related_data dictionary - Values must match exactly (no partial matching or type coercion) - Variables without any matching keys are excluded from results - Empty queries (no kwargs) return no results for safety Args: **kwargs: Key-value pairs to match against variables' related_data dictionaries. Keys represent relationship types (e.g., 'customer', 'facility') and values are the corresponding UUID identifiers. A variable is included in results only if its related_data contains ALL specified key-value pairs with exact matches. Returns: list[OXObject]: A list of OXVariable instances that match ALL query criteria. The list is empty if no variables match or if no query parameters are provided. Variables are returned in the order they are stored in the container. Raises: OXception: If a non-OXVariable object is encountered during the search. This should never occur due to type validation but provides a safety check against container corruption. Performance: - Time complexity: O(n × k) where n is variables count and k is query criteria count - Space complexity: O(m) where m is the number of matching variables - Linear scan through all variables makes this suitable for moderate-sized collections Note: - Query parameters are case-sensitive and require exact key matches - UUID values are compared for exact equality (no fuzzy matching) - Variables can be queried by any combination of related_data attributes - Results maintain references to original variables (not copies) Examples: Query variables using relationship-based filtering: .. code-block:: python from variables.OXVariableSet import OXVariableSet from variables.OXVariable import OXVariable from uuid import uuid4 # Set up variables with relationships var_set = OXVariableSet() customer1_id = uuid4() customer2_id = uuid4() facility1_id = uuid4() facility2_id = uuid4() # Create variables for different customer-facility combinations var1 = OXVariable(name="prod_c1_f1") var1.related_data["customer"] = customer1_id var1.related_data["facility"] = facility1_id var2 = OXVariable(name="prod_c1_f2") var2.related_data["customer"] = customer1_id var2.related_data["facility"] = facility2_id var3 = OXVariable(name="prod_c2_f1") var3.related_data["customer"] = customer2_id var3.related_data["facility"] = facility1_id # Add to set for var in [var1, var2, var3]: var_set.add_object(var) # Query by single criterion customer1_vars = var_set.query(customer=customer1_id) print(f"Customer 1 variables: {len(customer1_vars)}") # Output: 2 # Query by multiple criteria (AND operation) specific_vars = var_set.query(customer=customer1_id, facility=facility1_id) print(f"Customer 1 at Facility 1: {len(specific_vars)}") # Output: 1 # Empty query returns no results empty_result = var_set.query() print(f"Empty query result: {len(empty_result)}") # Output: 0 See Also: :meth:`add_object`: Add variables with relationship data for querying. :meth:`search_by_function`: Lower-level search functionality from parent class. """ def query_function(obj: OXObject): if isinstance(obj, OXVariable): number_of_keys_found = 0 for key, value in kwargs.items(): if key in obj.related_data: number_of_keys_found += 1 if obj.related_data[key] != value: return False if number_of_keys_found == 0: return False return True else: raise OXception("This should not happen.") return self.search_by_function(query_function)