code: def module_to_array(
      module: torch.nn.Module,
  ) -> Tuple[np.ndarray, Dict[str, TorchAttr], Optional[np.ndarray]]:
      r"""Extract named parameters from a module into a numpy array.
  
      Only extracts parameters with requires_grad, since it is meant for optimizing.
  
      NOTE: `module_to_array` was originally a BoTorch function and was later
      deprecated. It has been copied here because ALEBO depends on it, and because
      ALEBO itself is deprecated, it is not worth moving ALEBO to the new syntax.
  
      Args:
          module: A module with parameters. May specify parameter constraints in
              a `named_parameters_and_constraints` method.
  
      Returns:
          3-element tuple containing
          - The parameter values as a numpy array.
          - An ordered dictionary with the name and tensor attributes of each
          parameter.
          - A `2 x n_params` numpy array with lower and upper bounds if at least
          one constraint is finite, and None otherwise.
  
      Example:
          >>> mll = ExactMarginalLogLikelihood(model.likelihood, model)
          >>> parameter_array, property_dict, bounds_out = module_to_array(mll)
      """
      param_dict, bounds_dict = get_parameters_and_bounds(
          module=module,
          name_filter=None,
          requires_grad=True,
      )
  
      # Record tensor metadata and read parameter values to the tape
      param_tape: List[Number] = []
      property_dict = OrderedDict()
      with torch.no_grad():
          for name, param in param_dict.items():
              property_dict[name] = TorchAttr(
                  param.shape, param.dtype, param.device
              )
              param_tape.extend(param.view(-1).cpu().double().tolist())
  
      # Extract lower and upper bounds
      start = 0
      bounds_np = None
      params_np = np.asarray(param_tape)
      for name, param in param_dict.items():
          numel = param.numel()
          if name in bounds_dict:
              for row, bound in enumerate(bounds_dict[name]):
                  if bound is None:
                      continue
  
                  if isinstance(bound, Tensor):
                      if torch.eq(bound, (2 * row - 1) * inf).all():
                          continue
                      bound = bound.detach().cpu()
  
                  elif bound == (2 * row - 1) * inf:
                      continue
  
                  if bounds_np is None:
                      bounds_np = np.full(
                          (2, len(params_np)), ((-inf,), (inf,))
                      )
  
                  bounds_np[row, start : start + numel] = bound
          start += numel
  
      return params_np, property_dict, bounds_np
  
imports: from __future__ import annotations
  import dataclasses
  import re
  from collections import OrderedDict
  from logging import Logger
  from math import inf
  from numbers import Number
  from typing import Any, Callable, Dict, List, MutableMapping, Optional, Tuple, TypeVar, Union
  from warnings import warn
  import gpytorch
  import numpy
  import torch
  from ax.core.search_space import SearchSpaceDigest
  from ax.core.types import TCandidateMetadata
  from ax.models.random.alebo_initializer import ALEBOInitializer
  from ax.models.torch.botorch import BotorchModel
  from ax.models.torch.botorch_defaults import get_qLogNEI
  from ax.models.torch.utils import _datasets_to_legacy_inputs
  from ax.models.torch_base import TorchGenResults, TorchModel, TorchOptConfig
  from ax.utils.common.docutils import copy_doc
  from ax.utils.common.logger import get_logger
  from ax.utils.common.typeutils import checked_cast
  from botorch.acquisition.acquisition import AcquisitionFunction
  from botorch.acquisition.analytic import ExpectedImprovement
  from botorch.acquisition.objective import PosteriorTransform
  from botorch.models.gp_regression import SingleTaskGP
  from botorch.models.gpytorch import GPyTorchModel
  from botorch.models.model_list_gp_regression import ModelListGP
  from botorch.optim.fit import fit_gpytorch_mll_scipy
  from botorch.optim.initializers import initialize_q_batch_nonneg
  from botorch.optim.optimize import optimize_acqf
  from botorch.optim.utils import _handle_numerical_errors, get_parameters_and_bounds, TorchAttr
  from botorch.posteriors.gpytorch import GPyTorchPosterior
  from botorch.utils.datasets import SupervisedDataset
  from gpytorch.distributions.multivariate_normal import MultivariateNormal
  from gpytorch.kernels.kernel import Kernel
  from gpytorch.kernels.rbf_kernel import postprocess_rbf
  from gpytorch.kernels.scale_kernel import ScaleKernel
  from gpytorch.mlls.exact_marginal_log_likelihood import ExactMarginalLogLikelihood
  from scipy.optimize import approx_fprime
  from torch import Tensor
  from torch.nn.parameter import Parameter
label_desc: Use image node_id 1 for calling a global function with example usage: module_to_array(module) and returns: params_np,  property_dict,  bounds_np