code: def test_exp_to_df(self) -> None:
      # MultiTypeExperiment should fail
      exp = get_multi_type_experiment()
      with self.assertRaisesRegex(ValueError, "MultiTypeExperiment"):
          exp_to_df(exp=exp)
  
      # exp with no trials should return empty results
      exp = get_branin_experiment()
      df = exp_to_df(exp=exp)
      self.assertEqual(len(df), 0)
  
      # set up experiment
      exp = get_branin_experiment(with_batch=True)
  
      # check that pre-run experiment returns all columns except objective
      df = exp_to_df(exp)
      self.assertEqual(
          set(EXPECTED_COLUMNS) - set(df.columns), {OBJECTIVE_NAME}
      )
      self.assertEqual(len(df.index), len(exp.arms_by_name))
  
      exp.trials[0].run()
      exp.fetch_data()
  
      # assert result is df with expected columns and length
      df = exp_to_df(exp=exp)
      self.assertIsInstance(df, pd.DataFrame)
      self.assertListEqual(sorted(df.columns), sorted(EXPECTED_COLUMNS))
      self.assertEqual(len(df.index), len(exp.arms_by_name))
  
      # test with run_metadata_fields and trial_properties_fields not empty
      # add source to properties
      for _, trial in exp.trials.items():
          trial._properties["source"] = DUMMY_SOURCE
      df = exp_to_df(
          exp,
          run_metadata_fields=["name"],
          trial_properties_fields=["source"],
      )
      self.assertIn("name", df.columns)
      self.assertIn("trial_properties_source", df.columns)
  
      # test column values or types
      self.assertTrue(all(x == 0 for x in df.trial_index))
      self.assertTrue(all(x == "RUNNING" for x in df.trial_status))
      self.assertTrue(all(x == "Sobol" for x in df.generation_method))
      self.assertTrue(
          all(x == DUMMY_SOURCE for x in df.trial_properties_source)
      )
      self.assertTrue(
          all(x == "branin_test_experiment_0" for x in df.name)
      )
      for float_column in FLOAT_COLUMNS:
          self.assertTrue(
              all(isinstance(x, float) for x in df[float_column])
          )
  
      # works correctly for failed trials (will need to mock)
      dummy_struct = namedtuple("dummy_struct", "df")
      mock_results = dummy_struct(
          df=pd.DataFrame(
              {
                  "arm_name": ["0_0", "1_0"],
                  "metric_name": [OBJECTIVE_NAME] * 2,
                  "mean": [DUMMY_OBJECTIVE_MEAN] * 2,
                  "sem": [0] * 2,
                  "trial_index": [0, 1],
                  "n": [123] * 2,
                  "frac_nonnull": [1] * 2,
              }
          )
      )
      mock_results.df.index = range(
          len(exp.trials) * 2, len(exp.trials) * 2 + 2
      )
      with patch.object(
          Experiment, "lookup_data", lambda self: mock_results
      ):
          df = exp_to_df(exp=exp)
      # all but two rows should have a metric value of NaN
      self.assertEqual(
          pd.isna(df[OBJECTIVE_NAME]).sum(), len(df.index) - 2
      )
  
      # an experiment with more results than arms raises an error
      with patch.object(
          Experiment, "lookup_data", lambda self: mock_results
      ), self.assertRaisesRegex(
          ValueError, "inconsistent experimental state"
      ):
          exp_to_df(exp=get_branin_experiment())
  
      # custom added trial has a generation_method of Manual
      custom_arm = Arm(name="custom", parameters={"x1": 0, "x2": 0})
      exp.new_trial().add_arm(custom_arm)
      df = exp_to_df(exp)
      self.assertEqual(
          df[df.arm_name == "custom"].iloc[0].generation_method,
          "Manual",
      )
      # failing feasibility calculation doesn't warns and suppresses error
      observations = [
          [1.0, 2.0, 3.0],
          [4.0, 5.0, -6.0],
          [7.0, 8.0, 9.0],
      ]
      exp = get_experiment_with_observations(
          observations=observations,
          constrained=True,
      )
      with patch(
          f"{exp_to_df.__module__}._is_row_feasible",
          side_effect=KeyError(DUMMY_MSG),
      ), self.assertLogs(logger="ax", level=WARN) as log:
          exp_to_df(exp)
          self.assertIn(
              f"Feasibility calculation failed with error: '{DUMMY_MSG}'",
              log.output[0],
          )
  
      # infeasible arm has `is_feasible = False`.
      df = exp_to_df(exp)
      self.assertListEqual(
          list(df[FEASIBLE_COL_NAME]), [True, False, True]
      )
  
      # all rows infeasible.
      observations = [
          [1.0, 2.0, -3.0],
          [4.0, 5.0, -6.0],
          [7.0, 8.0, -9.0],
      ]
      exp = get_experiment_with_observations(
          observations=observations,
          constrained=True,
      )
      df = exp_to_df(exp)
      self.assertListEqual(
          list(df[FEASIBLE_COL_NAME]), [False, False, False]
      )
  
imports: import copy
  import itertools
  from collections import namedtuple
  from logging import INFO, WARN
  from typing import Dict, List
  from unittest import mock
  from unittest.mock import patch
  import pandas
  from ax.core.arm import Arm
  from ax.core.metric import Metric
  from ax.core.objective import MultiObjective, Objective
  from ax.core.optimization_config import MultiObjectiveOptimizationConfig, OptimizationConfig
  from ax.core.outcome_constraint import ObjectiveThreshold
  from ax.core.types import ComparisonOp
  from ax.modelbridge.generation_node import GenerationStep
  from ax.modelbridge.generation_strategy import GenerationStrategy
  from ax.modelbridge.registry import Models
  from ax.service.scheduler import Scheduler
  from ax.service.utils.report_utils import _format_comparison_string, _get_cross_validation_plots, _get_curve_plot_dropdown, _get_metric_name_pairs, _get_objective_trace_plot, _get_objective_v_param_plots, _get_shortest_unique_suffix_dict, _objective_vs_true_objective_scatter, BASELINE_ARM_NAME, compare_to_baseline, compute_maximum_map_values, exp_to_df, Experiment, FEASIBLE_COL_NAME, get_standard_plots, plot_feature_importance_by_feature_plotly, select_baseline_arm, warn_if_unpredictable_metrics
  from ax.service.utils.scheduler_options import SchedulerOptions
  from ax.utils.common.testutils import TestCase
  from ax.utils.common.typeutils import checked_cast, not_none
  from ax.utils.testing.core_stubs import get_branin_experiment, get_branin_experiment_with_multi_objective, get_branin_experiment_with_timestamp_map_metric, get_branin_search_space, get_experiment_with_observations, get_high_dimensional_branin_experiment, get_multi_type_experiment, get_test_map_data_experiment
  from ax.utils.testing.mock import fast_botorch_optimize
  from ax.utils.testing.modeling_stubs import get_generation_strategy
  from plotly import graph_objects
label_desc: Use image node_id 6 for calling the ReportUtilsTest obj's underlying member method code with example usage: obj.test_exp_to_df() without return types