class_docstr: Bayesian or-of-and algorithm.
  Generates patterns that satisfy the minimum support and maximum length and then select the Nrules rules that have the highest entropy.
  In function SA_patternbased, each local maximum is stored in maps and the best BOA is returned.
  Remember here the BOA contains only the index of selected rules from Nrules self.rules_
class_docstr_tok: ["Bayesian","or-of-and","algorithm",".","Generates","patterns","that","satisfy","the","minimum","support","and","maximum","length","and","then","select","the","Nrules","rules","that","have","the","highest","entropy",".","In","function","SA_patternbased",",","each","local","maximum","is","stored","in","maps","and","the","best","BOA","is","returned",".","Remember","here","the","BOA","contains","only","the","index","of","selected","rules","from","Nrules","self.rules_"]
code: def _propose(self, rules_curr, rules_norm, q, y):
      nRules = len(self.rules_)
      yhat = (np.sum(self.RMatrix[:, rules_curr], axis=1) > 0).astype(
          int
      )
      incorr = np.where(y != yhat)[0]
      N = len(rules_curr)
  
      if len(incorr) == 0:
          # BOA correctly classified all points but there could be redundant patterns, so cleaning is needed
          move = ["clean"]
      else:
          ex = sample(incorr.tolist(), 1)[0]
          t = random()
          if y[ex] == 1 or N == 1:
              if t < 1.0 / 2 or N == 1:
                  move = ["add"]  # action: add
              else:
                  move = ["cut", "add"]  # action: replace
          else:
              if t < 1.0 / 2:
                  move = ["cut"]  # action: cut
              else:
                  move = ["cut", "add"]  # action: replace
      if move[0] == "cut":
          """cut"""
          if random() < q:
              candidate = list(
                  set(
                      np.where(self.RMatrix[ex, :] == 1)[0]
                  ).intersection(rules_curr)
              )
              if len(candidate) == 0:
                  candidate = rules_curr
              cut_rule = sample(candidate, 1)[0]
          else:
              p = []
              all_sum = np.sum(self.RMatrix[:, rules_curr], axis=1)
              for index, rule in enumerate(rules_curr):
                  yhat = (
                      (all_sum - np.array(self.RMatrix[:, rule])) > 0
                  ).astype(int)
                  TP, FP, TN, FN = _get_confusion_matrix(yhat, y)
                  p.append(TP.astype(float) / (TP + FP + 1))
              p = [x - min(p) for x in p]
              p = np.exp(p)
              p = np.insert(p, 0, 0)
              p = np.array(list(_accumulate(p)))
              if p[-1] == 0:
                  index = sample(range(len(rules_curr)), 1)[0]
              else:
                  p = p / p[-1]
              index = _find_lt(p, random())
              cut_rule = rules_curr[index]
          rules_curr.remove(cut_rule)
          rules_norm = self._normalize(rules_curr)
          move.remove("cut")
  
      if len(move) > 0 and move[0] == "add":
          """add"""
          if random() < q:
              add_rule = sample(range(nRules), 1)[0]
          else:
              Yhat_neg_index = list(
                  np.where(
                      np.sum(self.RMatrix[:, rules_curr], axis=1) < 1
                  )[0]
              )
              mat = np.multiply(
                  self.RMatrix[Yhat_neg_index, :].transpose(),
                  y[Yhat_neg_index],
              )
              TP = np.sum(mat, axis=1)
              FP = np.array(
                  (np.sum(self.RMatrix[Yhat_neg_index, :], axis=0) - TP)
              )
              p = TP.astype(float) / (TP + FP + 1)
              p[rules_curr] = 0
              add_rule = sample(np.where(p == max(p))[0].tolist(), 1)[0]
          if add_rule not in rules_curr:
              rules_curr.append(add_rule)
              rules_norm = self._normalize(rules_curr)
  
      if len(move) > 0 and move[0] == "clean":
          remove = []
          for i, rule in enumerate(rules_norm):
              yhat = (
                  np.sum(
                      self.RMatrix[
                          :,
                          [
                              rule
                              for j, rule in enumerate(rules_norm)
                              if (j != i and j not in remove)
                          ],
                      ],
                      axis=1,
                  )
                  > 0
              ).astype(int)
              TP, FP, TN, FN = _get_confusion_matrix(yhat, y)
              if TP + FP == 0:
                  remove.append(i)
          for x in remove:
              rules_norm.remove(x)
          return rules_curr, rules_norm
      return rules_curr, rules_norm
  
imports: import itertools
  import operator
  import os
  import warnings
  from os.path import join
  from bisect import bisect_left
  from collections import defaultdict
  from copy import deepcopy
  from itertools import combinations
  from random import sample
  import numpy
  import pandas
  from mlxtend.frequent_patterns import fpgrowth
  from numpy.random import random
  from pandas import read_csv
  from scipy.sparse import csc_matrix
  from sklearn.base import BaseEstimator, ClassifierMixin
  from sklearn.ensemble import RandomForestClassifier
  from sklearn.utils.multiclass import check_classification_targets
  from sklearn.utils.validation import check_X_y, check_is_fitted
  from imodels.rule_set.rule_set import RuleSet
  from imodels.util.arguments import check_fit_arguments
label_desc: Use image node_id 9 for calling the BayesianRuleSetClassifier obj's underlying member method code with example usage: obj._propose(rules_curr, rules_norm, q, y) and returns: rules_curr,  rules_norm, rules_curr,  rules_norm