text: import logging
  
  import numpy as np
  import pandas as pd
  from scipy.spatial import ConvexHull
  from scipy.spatial.qhull import QhullError
  
  
  class HyperwavePathFinder:
      @staticmethod
      def get_hyperwave_path(df_input: pd.DataFrame):
          # Extract the hyperwave path using the ConvexHull.
          # The df_input need to contain at least the following columns
          #   - close
          #   - date
          #   - high
          #   - is_price_closing_up
          #   - low
          #   - open
          #   - weekId
  
          # Add the column with normalized value
          df_input.loc[
              :, "close_normalize"
          ] = HyperwavePathFinder.get_column_normalize_values(df_input, "close")
          df_input.loc[
              :, "weekId_normalize"
          ] = HyperwavePathFinder.get_column_normalize_values(df_input, "weekId")
  
          # Use convexHull to find the support lines
          try:
              hull = ConvexHull(
                  df_input[["weekId_normalize", "close_normalize"]].dropna()
              )
          except ValueError as e:
              logging.debug(e)
              return pd.DataFrame()
          except QhullError as e:
              logging.debug(e)
              return pd.DataFrame()
          hull_results = [
              [min(pair[0], pair[1]), max(pair[0], pair[1])] for pair in hull.simplices
          ]
          data_from_to = [
              {
                  "x1": df_input["weekId"].iloc[pair[0]],
                  "x1_date": df_input["date"].iloc[pair[0]],
                  "x1_normalize": df_input["weekId_normalize"].iloc[pair[0]],
                  "y1": df_input["close"].iloc[pair[0]],
                  "y1_normalize": df_input["close_normalize"].iloc[pair[0]],
                  "x2": df_input["weekId"].iloc[pair[1]],
                  "x2_date": df_input["date"].iloc[pair[1]],
                  "x2_normalize": df_input["weekId_normalize"].iloc[pair[1]],
                  "y2": df_input["close"].iloc[pair[1]],
                  "y2_normalize": df_input["close_normalize"].iloc[pair[1]],
              }
              for pair in hull_results
          ]
  
          df = pd.DataFrame(data_from_to)
          df["m"] = HyperwavePathFinder.get_line_slope(
              df, x1_col_name="x1", y1_col_name="y1", x2_col_name="x2", y2_col_name="y2"
          )
          df["b"] = HyperwavePathFinder.get_line_origine(
              df, x1_col_name="x1", y1_col_name="y1", m_col_name="m"
          )
          df["m_normalize"] = HyperwavePathFinder.get_line_slope(
              df,
              x1_col_name="x1_normalize",
              y1_col_name="y1_normalize",
              x2_col_name="x2_normalize",
              y2_col_name="y2_normalize",
          )
          df["b_normalize"] = HyperwavePathFinder.get_line_origine(
              df,
              x1_col_name="x1_normalize",
              y1_col_name="y1_normalize",
              m_col_name="m_normalize",
          )
          df["angle"] = np.rad2deg(np.arctan2(df.loc[:, "m"], 1))
          df["angle_normalize"] = np.rad2deg(np.arctan2(df.loc[:, "m_normalize"], 1))
          df["weeks"] = np.abs(df.loc[:, "x1"] - df.loc[:, "x2"])
          df["mean_error"] = df.apply(
              lambda row: HyperwavePathFinder.calculate_mean_error(row, df_input), axis=1
          )
          df["nb_is_lower"] = df.apply(
              lambda row: HyperwavePathFinder.nb_cut_price_low(row, df_input), axis=1
          )
          df["ratio_error_cut"] = df.loc[:, "mean_error"] / (
              df.loc[:, "nb_is_lower"] * df.loc[:, "weeks"]
          )
          df["ratio_slope_y1_normalize"] = (
              df.loc[:, "y1_normalize"] / df.loc[:, "m_normalize"]
          )
          df["ratio_slope_y2_normalize"] = (
              df.loc[:, "y2_normalize"] / df.loc[:, "m_normalize"]
          )
          return df
  
      @staticmethod
      def get_line_slope(
          df, x1_col_name="x1", y1_col_name="y1", x2_col_name="x2", y2_col_name="y2"
      ):
          return (df[y1_col_name] - df[y2_col_name]) / (df[x1_col_name] - df[x2_col_name])
  
      @staticmethod
      def get_line_origine(df, x1_col_name="x1", y1_col_name="y1", m_col_name="m"):
          return df[y1_col_name] - (df[x1_col_name] * df[m_col_name])
  
      @staticmethod
      def get_column_normalize_values(df, column_name="close"):
          max_value = df.loc[df[column_name].idxmax()][column_name]
          min_value = df.loc[df[column_name].idxmin()][column_name]
  
          return (df[column_name] - min_value) / (max_value - min_value)
  
      @staticmethod
      def get_mean_error(y_true, y_pred):
          y_square_diff = y_true - y_pred
          return np.sum(y_square_diff) / len(y_true)
  
      @staticmethod
      def nb_is_lower(y_true, y_pred):
          lower_item = y_true[y_true <= y_pred]
          return len(lower_item)
  
      @staticmethod
      def get_y(x, m, b):
          return x * m + b
  
      @staticmethod
      def calculate_mean_error(row, df):
          y_pred = HyperwavePathFinder.get_y(df["weekId"], row["m"], row["b"])
          return HyperwavePathFinder.get_mean_error(df["close"], y_pred)
  
      @staticmethod
      def nb_cut_price_low(row, df):
          y_pred = HyperwavePathFinder.get_y(df["weekId"], row["m"], row["b"])
          return HyperwavePathFinder.nb_is_lower(df["low"], y_pred)
  