import torch

def get_trainable_modules(model, exclude_patterns=None):
    if exclude_patterns is None:
        exclude_patterns = []
    
    trainable = []
    
    for name, module in model.named_modules():
        skip = any(pattern in name.lower() for pattern in exclude_patterns)
        if skip:
            continue

        if hasattr(module, 'weight') and module.weight is not None:
            trainable.append((name, module))
    
    return trainable

def get_module_input_output_dims(module, sample_input=None):

    in_dim = None
    out_dim = None
    
    if hasattr(module, 'nx') and hasattr(module, 'nf'):
        in_dim = module.nx
        out_dim = module.nf
    elif hasattr(module, 'in_features') and hasattr(module, 'out_features'):
        in_dim = module.in_features
        out_dim = module.out_features
    elif hasattr(module, 'weight'):
        weight = module.weight
        if weight.ndim == 2:
            out_dim = weight.shape[0]
            in_dim = weight.shape[1]
        elif weight.ndim == 1:
            out_dim = weight.shape[0]
    
    return in_dim, out_dim

def has_valid_weight(module):

    return hasattr(module, 'weight') and module.weight is not None

def get_device(module):

    try:
        return next(module.parameters()).device
    except StopIteration:
        return torch.device('cpu')

def get_dtype(module):

    try:
        return next(module.parameters()).dtype
    except StopIteration:
        return torch.float32
