Source code for qbiocode.apps.quvine.utils.torch_utils

# Copyright 2026, IBM Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""
PyTorch Utilities and Wrappers

This module provides utilities for handling PyTorch dependencies gracefully,
including availability checks, error handling, and fallback mechanisms.
"""

import logging
from typing import Optional, Callable, Any
from functools import wraps

logger = logging.getLogger(__name__)

# Check PyTorch availability
try:
    import torch
    import torch.nn as nn
    import torch.nn.functional as F
    TORCH_AVAILABLE = True
    TORCH_VERSION = torch.__version__
except ImportError:
    TORCH_AVAILABLE = False
    TORCH_VERSION = None
    torch = None
    nn = None
    F = None
    logger.warning(
        "PyTorch not available. Neural network-based methods (GAT, GraphGPS, APPNP, GraphSAGE) "
        "will not work. Install with: pip install torch"
    )


[docs] def check_torch_available() -> bool: """ Check if PyTorch is available. Returns: bool: True if PyTorch is available, False otherwise """ return TORCH_AVAILABLE
[docs] def require_torch(func: Callable) -> Callable: """ Decorator to require PyTorch for a function. Raises ImportError with helpful message if PyTorch is not available. Args: func: Function to wrap Returns: Wrapped function that checks for PyTorch Example: >>> @require_torch ... def train_model(data): ... # PyTorch code here ... pass """ @wraps(func) def wrapper(*args, **kwargs): if not TORCH_AVAILABLE: raise ImportError( f"PyTorch is required for {func.__name__} but is not available. " "Install with: pip install torch>=2.0.0" ) return func(*args, **kwargs) return wrapper
[docs] def torch_optional(fallback_value: Any = None, log_warning: bool = True) -> Callable: """ Decorator for functions that optionally use PyTorch. If PyTorch is not available, returns fallback_value instead of raising error. Args: fallback_value: Value to return if PyTorch is not available log_warning: Whether to log a warning when falling back Returns: Decorator function Example: >>> @torch_optional(fallback_value=None) ... def optional_gpu_computation(data): ... # PyTorch code here ... pass """ def decorator(func: Callable) -> Callable: @wraps(func) def wrapper(*args, **kwargs): if not TORCH_AVAILABLE: if log_warning: logger.warning( f"{func.__name__} requires PyTorch but it's not available. " f"Returning fallback value: {fallback_value}" ) return fallback_value return func(*args, **kwargs) return wrapper return decorator
[docs] def get_device(prefer_gpu: bool = True) -> Optional[str]: """ Get the best available device for PyTorch. Args: prefer_gpu: Whether to prefer GPU if available Returns: Device string ('cuda', 'mps', 'cpu') or None if PyTorch not available Example: >>> device = get_device() >>> if device: ... tensor = torch.tensor([1, 2, 3], device=device) """ if not TORCH_AVAILABLE: return None if prefer_gpu: if torch.cuda.is_available(): return 'cuda' elif hasattr(torch.backends, 'mps') and torch.backends.mps.is_available(): return 'mps' return 'cpu'
[docs] def safe_torch_import(module_name: str, class_name: Optional[str] = None) -> Optional[Any]: """ Safely import a PyTorch module or class. Args: module_name: Name of the module to import (e.g., 'torch.nn') class_name: Optional class name to import from module Returns: Imported module/class or None if not available Example: >>> nn = safe_torch_import('torch.nn') >>> if nn: ... model = nn.Linear(10, 5) """ if not TORCH_AVAILABLE: return None try: if class_name: module = __import__(module_name, fromlist=[class_name]) return getattr(module, class_name) else: return __import__(module_name) except (ImportError, AttributeError) as e: logger.warning(f"Failed to import {module_name}.{class_name or ''}: {e}") return None
[docs] class TorchNotAvailableError(ImportError): """Custom exception for when PyTorch is required but not available.""" def __init__(self, method_name: str = "This method"): super().__init__( f"{method_name} requires PyTorch but it is not available. " "Install with: pip install torch>=2.0.0\n" "For GPU support, see: https://pytorch.org/get-started/locally/" )
[docs] def validate_torch_tensor(tensor: Any, name: str = "tensor") -> None: """ Validate that an object is a PyTorch tensor. Args: tensor: Object to validate name: Name of the tensor for error messages Raises: TorchNotAvailableError: If PyTorch is not available TypeError: If object is not a tensor Example: >>> validate_torch_tensor(my_tensor, "input") """ if not TORCH_AVAILABLE: raise TorchNotAvailableError("Tensor validation") if not isinstance(tensor, torch.Tensor): raise TypeError(f"{name} must be a torch.Tensor, got {type(tensor)}")
[docs] def get_torch_info() -> dict: """ Get information about PyTorch installation. Returns: Dictionary with PyTorch information Example: >>> info = get_torch_info() >>> print(f"PyTorch version: {info['version']}") """ info = { 'available': TORCH_AVAILABLE, 'version': TORCH_VERSION, 'cuda_available': False, 'cuda_version': None, 'mps_available': False, 'device': None, } if TORCH_AVAILABLE: info['cuda_available'] = torch.cuda.is_available() if info['cuda_available']: info['cuda_version'] = torch.version.cuda if hasattr(torch.backends, 'mps'): info['mps_available'] = torch.backends.mps.is_available() info['device'] = get_device() return info
# Convenience function for logging torch info
[docs] def log_torch_info(): """Log PyTorch availability and configuration.""" info = get_torch_info() if info['available']: logger.info(f"PyTorch {info['version']} is available") logger.info(f"Device: {info['device']}") if info['cuda_available']: logger.info(f"CUDA {info['cuda_version']} is available") if info['mps_available']: logger.info("MPS (Apple Silicon) is available") else: logger.warning("PyTorch is not available")
# Export key components __all__ = [ 'TORCH_AVAILABLE', 'TORCH_VERSION', 'check_torch_available', 'require_torch', 'torch_optional', 'get_device', 'safe_torch_import', 'TorchNotAvailableError', 'validate_torch_tensor', 'get_torch_info', 'log_torch_info', ]