text: import numpy as np
  import pandas as pd
  import os
  import copy
  from statsmodels.tools import sequences
  from snsql.metadata import *
  
  
  class Exploration:
      """
      This file contains methods to set and scan a search space of datasets
      that we shall be using for DP evaluation. We need to search databases,
      neighboring pairs and queries for running the DP predicate test.
  
      We shall start a small 3-row databases to create our 12 neighboring pairs per database
      Then we shall use halton sequence to generate a set of such 3 row databases
      randomly in a 3-D log-space
      """
  
      def __init__(
          self, dataset_size=3, csv_path=r"../datasets/evaluation"
      ):  # TODO changed default to "."
          """
          Instantiates the Exploration class to define dimensions and
          boundaries of halton sequence space.
          Also initialized file path of 3 record dataset for application of DFS
          to run a Powerset test
          """
          self.dataset_size = dataset_size
          self.file_dir = os.path.dirname(os.path.abspath(__file__))
          self.csv_path = csv_path
  
          self.numerical_col_name = "Usage"
          self.numerical_col_type = "int"
          self.N = dataset_size
          # if we are exploring 3 dimensions, corners shall be [-1000, -1000, -1000] to [1000, 1000, 1000]
          self.corners = np.array([[-1 * (10 ** self.N)] * self.N, [10 ** self.N] * self.N])
          self.visited = []
          self.neighbor_pair = {}
  
      def create_small_dataset(self, sample, file_name="small"):
          """
          Create a dataset with one numerical column on which we shall evaluate DP queries
          """
          userids = list(range(1, self.dataset_size + 1))
          userids = ["A" + str(user) for user in userids]
          usage = list(sample)
          df = pd.DataFrame(list(zip(userids, usage)), columns=["UserId", self.numerical_col_name])
          metadata = Table(
              file_name,
              file_name,
              [
                  String("UserId", self.dataset_size, True),
                  Int(self.numerical_col_name, min(usage), max(usage)),
              ],
              self.dataset_size,
          )
          return df, metadata
  
      def generate_halton_samples(self, bounds, dims, n_sample=10):
          """
          Generate halton samples in a n-dimensional space. Defaulted to 3 dimensions
          """
          samples = sequences.halton(dim=dims, n_sample=n_sample, bounds=bounds)
          return samples
  
      def generate_powerset(self, d1):
          """
          Given a list of N records in a database, create a powerset of
          neighboring datasets by traversing the edges of database search graph.
          Perform DFS to traverse the database search graph
          Convention of file names ->
          <d1/d2>_<list of row indexes in d1>_<row index removed to create d2>
          """
          if len(d1) == 0:
              return
          else:
              indexes = list(d1.index.values)
              s = [str(i) for i in indexes]
              d1_idx_range = "".join(s)
              for drop_idx in indexes:
                  filename = d1_idx_range + "_" + str(drop_idx)
                  if filename not in self.visited:
                      d2 = d1.drop(drop_idx)
                      min_val = min(d1[self.numerical_col_name])
                      max_val = max(d1[self.numerical_col_name])
                      # Avoiding sensitivity to be 0
                      min_val = min_val if max_val > min_val else 0
                      max_val = max_val if max_val > min_val else abs(max_val)
  
                      d1_table = Table(
                          "d1_" + filename,
                          "d1_" + filename,
                          [
                              String("UserId", len(d1), True),
                              Int(self.numerical_col_name, min_val, max_val),
                          ],
                          len(d1),
                      )
                      d2_table = copy.copy(d1_table)
                      d2_table.schema, d2_table.name, d2_table.rowcount = (
                          "d2_" + filename,
                          "d2_" + filename,
                          d1_table.rowcount - 1,
                      )
                      d1_metadata, d2_metadata = (
                          Metadata([d1_table], "csv"),
                          Metadata([d2_table], "csv"),
                      )
  
                      self.neighbor_pair[filename] = [d1, d2, d1_metadata, d2_metadata]
                      self.visited.append(filename)
                      self.generate_powerset(d2)
              return
  
      def test_exploration(self):
          """
          Test method to generate halton samples and then generate powerset for each sample
          """
          samples = self.generate_halton_samples(bounds=self.corners, dims=self.N)
          for sample in samples:
              df, metadata = self.create_small_dataset(sample)
              self.generate_powerset(df)
  