code: def test_raf_fit(data):
      """Tests ReconcileAdditiveForecasts fit"""
      forecasts = data["forecasts"]
      actuals = data["actuals"]
      tree = data["tree"]
      levels = data["levels"]
      order_dict = data["order_dict"]
      Yf = data["Yf"]
      Ya = data["Ya"]
      m = data["m"]
  
      raf = ReconcileAdditiveForecasts()
      with pytest.raises(
          ValueError,
          match="Either `constraint_matrix` or `levels` must be provided.",
      ):
          raf.fit(
              forecasts=forecasts, actuals=actuals, method="bottom_up"
          )
  
      with pytest.raises(
          ValueError,
          match="Must provide `levels` if `method='bottom_up'`.",
      ):
          raf.fit(
              forecasts=forecasts,
              actuals=actuals,
              constraint_matrix=tree.constraint_matrix,
              method="bottom_up",
          )
  
      with pytest.raises(
          ValueError,
          match="Only one of `constraint_matrix` and `levels` can be provided.",
      ):
          raf.fit(
              forecasts=forecasts,
              actuals=actuals,
              levels=levels,
              constraint_matrix=tree.constraint_matrix,
              method="bottom_up",
          )
  
      with pytest.raises(
          ValueError,
          match="`lower_bound` 1.5 should not be greater than `upper_bound` 1.0",
      ):
          raf.fit(
              forecasts=forecasts,
              actuals=actuals,
              levels=levels,
              method="bottom_up",
              lower_bound=1.5,
              upper_bound=1.0,
          )
  
      with pytest.raises(
          ValueError,
          match="Forecast shape \\(20, 4\\) does not match actuals shape \\(150, 4\\)",
      ):
          raf.fit(
              forecasts=forecasts[:20],
              actuals=actuals,
              levels=levels,
              method="bottom_up",
          )
  
      with pytest.raises(
          ValueError,
          match="The number of forecasts 4 does not match "
          "the number of columns in `constraint_matrix` 3. "
          "Make sure `levels` or `constraint_matrix` matches the data.",
      ):
          raf.fit(
              forecasts=forecasts,
              actuals=actuals,
              constraint_matrix=np.eye(3),
          )
  
      with pytest.raises(
          ValueError,
          match="The number of forecasts 4 does not match "
          "the number of columns in `constraint_matrix` 8. "
          "Make sure `levels` or `constraint_matrix` matches the data.",
      ):
          raf.fit(
              forecasts=forecasts, actuals=actuals, levels=[[2], [3, 2]]
          )
  
      # Tests order_dict, scaling, objective function, method
      raf = ReconcileAdditiveForecasts()
      raf.fit(
          forecasts=forecasts,
          actuals=actuals,
          levels=levels,
          order_dict=order_dict,
          method="mint_sample",
          lower_bound=-1.5,
          upper_bound=1.5,
      )
      assert raf.lower_bound is None  # overridden by "mint_sample"
      assert raf.upper_bound is None  # overridden by "mint_sample"
      assert_equal(
          raf.forecasts,
          reorder_columns(forecasts, order_dict=order_dict),
      )
      residuals = Ya / Ya.mean() - Yf / Ya.mean()
      covariance = residuals @ residuals.T / residuals.shape[1]
      assert_equal(raf.objective_weights["covariance"], covariance)
      assert raf.prob is not None
      assert raf.is_optimization_solution
      assert_equal(raf.transform_variable.value, raf.transform_matrix)
      assert (
          np.linalg.norm(raf.constraint_matrix @ raf.transform_matrix)
          <= 1e-5
      )  # satisfies constraints
      assert_equal(
          raf.objective_fn_val["total"], raf.prob.objective.value
      )
      assert_equal(
          raf.objective_fn_val["total"], raf.objective_fn_val["var"]
      )  # "mint_sample" has only variance in the objective
  
      with pytest.warns(
          UserWarning,
          match="Variance of residuals is underestimated if the estimator is biased.",
      ):
          raf = ReconcileAdditiveForecasts()
          raf.fit(
              forecasts=forecasts,
              actuals=actuals,
              levels=levels,
              order_dict=order_dict,
              unbiased=False,
              covariance="sample",
              lam_adj=3.0,
              lam_var=2.0,
          )
          assert raf.transform_matrix is not None
          # checks custom values
          assert not raf.unbiased
          assert raf.covariance == "sample"
          assert raf.lam_adj == 3.0
          assert raf.lam_var == 2.0
          # checks default values
          assert raf.method == "custom"
          assert raf.lower_bound is None
          assert raf.upper_bound is None
          assert raf.lam_bias == 1.0
          assert raf.lam_train == 1.0
          assert raf.weight_adj is None
          assert raf.weight_bias is None
          assert raf.weight_train is None
          assert raf.weight_var is None
          # all the terms are here
          assert_equal(
              raf.objective_fn_val,
              {
                  "adj": 0.2871105061424012,
                  "bias": 0.03758041936764179,
                  "train": 0.08568482849991804,
                  "var": 0.019903924375932396,
                  "total": 0.4302796783858934,
              },
          )
  
      # Tests custom covariance matrix, unbiased=True, all terms in objective
      covariance = 20 * np.random.randn(m, m)
      weight_err = np.diag(
          np.nanmedian(np.abs(Ya - Yf) / np.abs(Ya), axis=1)
      )
      weight_err *= np.sqrt(m) / np.linalg.norm(weight_err)
      weight_inverr = np.diag(1 / np.diag(weight_err))
      weight_inverr *= np.sqrt(m) / np.linalg.norm(weight_inverr)
  
      raf = ReconcileAdditiveForecasts()
      raf.fit(
          forecasts=forecasts,
          actuals=actuals,
          levels=levels,
          order_dict=order_dict,
          lower_bound=-1.5,
          upper_bound=1.5,
          unbiased=True,
          lam_adj=1.0,
          lam_bias=1.0,
          lam_train=1.0,
          lam_var=1.0,
          covariance=covariance,
          weight_adj=None,
          weight_bias="MedAPE",
          weight_train=list(range(m)),
          weight_var=list(range(m, 0, -1)),
          reltol=1e-7,  # solver params
          abstol=1e-7,
          feastol=1e-7,
      )
      scaled_covariance = covariance / Ya.mean() ** 2
      assert_equal(
          raf.objective_weights["covariance"], scaled_covariance
      )
      assert_equal(raf.objective_weights["weight_adj"], np.eye(m))
      assert_equal(raf.objective_weights["weight_bias"], weight_err)
      exp_weight_train = np.diag(list(range(m)))
      exp_weight_train = (
          exp_weight_train
          * math.sqrt(m)
          / np.linalg.norm(exp_weight_train)
      )
      assert_equal(
          raf.objective_weights["weight_train"], exp_weight_train
      )
      exp_weight_var = np.diag(list(range(m, 0, -1)))
      exp_weight_var = (
          exp_weight_var * math.sqrt(m) / np.linalg.norm(exp_weight_var)
      )
      assert_equal(raf.objective_weights["weight_var"], exp_weight_var)
      assert raf.prob is not None
      assert_equal(raf.transform_variable.value, raf.transform_matrix)
      # satisfies constraints
      assert (
          np.linalg.norm(raf.constraint_matrix @ raf.transform_matrix)
          <= 1e-5
      )
      assert_equal(raf.transform_matrix @ Ya, Ya)
      assert raf.objective_fn_val["bias"] < 1e-10
  
      # Tests weight "MedAPE", "InverseMedAPE"
      raf = ReconcileAdditiveForecasts()
      raf.fit(
          forecasts=forecasts,
          actuals=actuals,
          levels=levels,
          order_dict=order_dict,
          weight_adj="InverseMedAPE",
          weight_bias="InverseMedAPE",
          weight_train="MedAPE",
          weight_var="MedAPE",
      )
      assert_equal(raf.objective_weights["weight_adj"], weight_inverr)
      assert_equal(raf.objective_weights["weight_bias"], weight_inverr)
      assert_equal(raf.objective_weights["weight_train"], weight_err)
      assert_equal(raf.objective_weights["weight_var"], weight_err)
  
      # Tests bottom up forecast
      raf = ReconcileAdditiveForecasts()
      raf.fit(
          forecasts=forecasts,
          actuals=actuals,
          levels=levels,
          order_dict=order_dict,
          method="bottom_up",
      )
      assert_equal(raf.transform_matrix, raf.tree.bottom_up_transform)
      assert raf.prob is None
      assert raf.transform_variable is None
  
      # No solution
      with pytest.warns(
          UserWarning,
          match="Failed to find a solution. Falling back to bottom-up method",
      ):
          raf = ReconcileAdditiveForecasts()
          raf.fit(
              forecasts=forecasts,
              actuals=actuals,
              levels=levels,
              order_dict=order_dict,
              lower_bound=3.0,
              upper_bound=3.0,
          )
          assert_equal(
              raf.transform_matrix, raf.tree.bottom_up_transform
          )
  
      # With constraint_matrix
      constraint_matrix = np.array(
          [
              [-1.0, 2.0, 1.0, 0.0],
              [0.0, -1.0, 1.0, 1.0],
          ]
      )
      with pytest.warns(
          UserWarning, match="Actuals do not satisfy the constraints!"
      ):
          raf = ReconcileAdditiveForecasts()
          raf.fit(
              forecasts=forecasts,
              actuals=actuals,
              constraint_matrix=constraint_matrix,
              order_dict=order_dict,
              unbiased=False,
          )
          assert_equal(raf.constraint_matrix, constraint_matrix)
          assert (
              np.linalg.norm(
                  raf.constraint_matrix @ raf.transform_matrix
              )
              < 1e-5
          )
  
      with pytest.warns(
          UserWarning,
          match="Failed to find a solution. Try setting CVXPY solver parameters, changing the "
          "constraints, or changing the objective weights",
      ):
          raf = ReconcileAdditiveForecasts()
          raf.fit(
              forecasts=forecasts,
              actuals=actuals,
              constraint_matrix=constraint_matrix,
              order_dict=order_dict,
              lower_bound=3.0,
              upper_bound=3.0,
          )
          assert raf.transform_matrix is None
          assert raf.objective_fn_val is None
          assert raf.prob.status != cp.OPTIMAL
          assert not raf.is_optimization_solution
  
imports: import math
  import os
  import warnings
  import cvxpy
  import numpy
  import pandas
  import pytest
  from scipy.linalg import sqrtm
  from sklearn.exceptions import NotFittedError
  from greykite.algo.reconcile.convex.reconcile_forecasts import ReconcileAdditiveForecasts
  from greykite.algo.reconcile.convex.reconcile_forecasts import TraceInfo
  from greykite.algo.reconcile.convex.reconcile_forecasts import apply_method_defaults
  from greykite.algo.reconcile.convex.reconcile_forecasts import evaluation_plot
  from greykite.algo.reconcile.convex.reconcile_forecasts import get_fit_params
  from greykite.algo.reconcile.convex.reconcile_forecasts import get_weight_matrix
  from greykite.algo.reconcile.hierarchical_relationship import HierarchicalRelationship
  from greykite.common.constants import TIME_COL
  from greykite.common.data_loader import DataLoader
  from greykite.common.evaluation import mean_absolute_percent_error
  from greykite.common.evaluation import median_absolute_percent_error
  from greykite.common.evaluation import root_mean_squared_error
  from greykite.common.python_utils import assert_equal
  from greykite.common.python_utils import reorder_columns
label_desc: Use image node_id 10 for calling a global function with example usage: test_raf_fit(data) without return types