code: def _learn_structure_lasso(
      X: np.ndarray,
      beta: float,
      bnds,
      max_iter: int = 100,
      h_tol: float = 1e-8,
      w_threshold: float = 0.0,
  ) -> StructureModel:
      """
      Based on initial implementation at https://github.com/xunzheng/notears
      """
  
      def _h(w_vec: np.ndarray) -> float:
          """
          Constraint function of the NOTEARS algorithm with lasso regularisation.
  
          Args:
              w_vec:  weight vector (wpos and wneg).
  
          Returns:
              float: DAGness of the adjacency matrix (0 == DAG, >0 == cyclic).
          """
  
          W = w_vec.reshape([d, d])
          return np.trace(slin.expm(W * W)) - d
  
      def _func(w_vec: np.ndarray) -> float:
          """
          Objective function that the NOTEARS algorithm with lasso regularisation tries to minimise.
  
          Args:
              w_vec: weight vector (wpos and wneg).
  
          Returns:
              float: objective.
          """
  
          w_pos = w_vec[: d**2]
          w_neg = w_vec[d**2 :]
  
          wmat_pos = w_pos.reshape([d, d])
          wmat_neg = w_neg.reshape([d, d])
  
          wmat = wmat_pos - wmat_neg
          loss = (
              0.5
              / n
              * np.square(
                  np.linalg.norm(X.dot(np.eye(d, d) - wmat), "fro")
              )
          )
          h_val = _h(wmat)
          return (
              loss
              + 0.5 * rho * h_val * h_val
              + alpha * h_val
              + beta * w_vec.sum()
          )
  
      def _grad(w_vec: np.ndarray) -> np.ndarray:
          """
          Gradient function used to compute next step in NOTEARS algorithm with lasso regularisation.
  
          Args:
              w_vec: weight vector (wpos and wneg).
  
          Returns:
              np.ndarray: gradient vector.
          """
  
          w_pos = w_vec[: d**2]
          w_neg = w_vec[d**2 :]
  
          grad_vec = np.zeros(2 * d**2)
          wmat_pos = w_pos.reshape([d, d])
          wmat_neg = w_neg.reshape([d, d])
  
          wmat = wmat_pos - wmat_neg
  
          loss_grad = -1.0 / n * X.T.dot(X).dot(np.eye(d, d) - wmat)
          exp_hdmrd = slin.expm(wmat * wmat)
          obj_grad = (
              loss_grad
              + (rho * (np.trace(exp_hdmrd) - d) + alpha)
              * exp_hdmrd.T
              * wmat
              * 2
          )
          lbd_grad = beta * np.ones(d * d)
          grad_vec[: d**2] = obj_grad.flatten() + lbd_grad
          grad_vec[d**2 :] = -obj_grad.flatten() + lbd_grad
  
          return grad_vec
  
      if X.size == 0:
          raise ValueError(
              "Input data X is empty, cannot learn any structure"
          )
      logging.info(
          "Learning structure using 'NOTEARS' optimisation with lasso regularisation."
      )
  
      n, d = X.shape
      w_est, w_new = np.zeros(2 * d * d), np.zeros(2 * d * d)
      rho, alpha, h_val, h_new = 1.0, 0.0, np.inf, np.inf
      for n_iter in range(max_iter):
          while (rho < 1e20) and (
              h_new > 0.25 * h_val or h_new == np.inf
          ):
              sol = sopt.minimize(
                  _func,
                  w_est,
                  method="L-BFGS-B",
                  jac=_grad,
                  bounds=bnds,
              )
              w_new = sol.x
              h_new = _h(
                  w_new[: d**2].reshape([d, d])
                  - w_new[d**2 :].reshape([d, d])
              )
              if h_new > 0.25 * h_val:
                  rho *= 10
  
          w_est, h_val = w_new, h_new
          alpha += rho * h_val
          if h_val <= h_tol:
              break
          if h_val > h_tol and n_iter == max_iter - 1:
              warnings.warn(
                  "Failed to converge. Consider increasing max_iter."
              )
  
      w_new = w_est[: d**2].reshape([d, d]) - w_est[d**2 :].reshape(
          [d, d]
      )
      w_new[np.abs(w_new) < w_threshold] = 0
      return StructureModel(w_new.reshape([d, d]))
  
file_path: causalnex/causalnex/structure/notears.py
imports: import logging
  import warnings
  from copy import deepcopy
  from typing import List, Tuple
  import numpy
  import pandas
  import scipy.linalg
  import scipy.optimize
  from causalnex.structure.structuremodel import StructureModel
label_desc: Use image node_id 6 for calling a global function with example usage: _learn_structure_lasso(X, beta, bnds, max_iter, h_tol, w_threshold) and returns: StructureModel, unknown, unknown, grad_vec