diff --git a/eat/VERSION b/eat/VERSION index 557fefc..0f84bed 100644 --- a/eat/VERSION +++ b/eat/VERSION @@ -1 +1 @@ -v2.1.3 \ No newline at end of file +v2.1.4 \ No newline at end of file diff --git a/eat/core/components.py b/eat/core/components.py index 77d0da5..991b99d 100644 --- a/eat/core/components.py +++ b/eat/core/components.py @@ -25,6 +25,59 @@ def __str__(self): [str(cell) for cell in row]) for row in self.data]) + def is_asymptotic_complete(self, probabilities, term_variables, limit=700): + if not probabilities: + raise ValueError("You must specify probability values to use") + elif len(probabilities) != self.size: + raise ValueError("The number of probability values must match the " + "size of the groupoid.") + elif sum(probabilities) != 1: + raise ValueError("Probabilies must add to one.") + + def get_operations_resolving_to_value(output_val): + operation_pairs = [] + for row_idx, row in enumerate(self.data): + for col_idx, _ in enumerate(row): + if self.data[row_idx][col_idx] == output_val: + operation_pairs.append([row_idx, col_idx]) + return operation_pairs + + def compute_ac_table(t_H, probs, limit=700): + print("Computing AC table") + table = [] + i_H = None + while len(table) < limit: + if not i_H or 1 - i_H < math.pow(10, -6): + t_H_plus_1 = (t_H * t_H) + len(term_variables) + i_H = (t_H * t_H) / t_H_plus_1 + else: + t_H_plus_1 = t_H + # add computed probabilities + table.append([]) + for input_value in range(self.size): + if input_value < self.size - 1: + # compute series summing over all probabilities + operation_pairs = \ + get_operations_resolving_to_value(input_value) + series = 0 + for operation_pair in operation_pairs: + series += (probs[operation_pair[0]] * + probs[operation_pair[1]]) + # use series to compute b_H_plus_1 + b_H_plus_1 = (i_H * series) + \ + ((1 - i_H) * probabilities[input_value]) + else: + b_H_plus_1 = 1 - sum(table[-1]) + table[-1].append(b_H_plus_1) # add each probability value + probs = table[-1] # Update probs for the next iteration + t_H = t_H_plus_1 # Update t_H for the next iteration + return table + + table = compute_ac_table(len(term_variables), + probabilities, + limit=limit) + return table + def set_value(self, x, y, value): """ Set groupoid value at index (x, y) diff --git a/eat/core/utilities.py b/eat/core/utilities.py index faa30ff..5a2e5fe 100644 --- a/eat/core/utilities.py +++ b/eat/core/utilities.py @@ -130,3 +130,27 @@ def print_execution_results_summary(execution_results, run_count, f"{result['term_length']}") print(f"Average search time: {avg_time} sec") print(f"Average term length: {int(avg_term_length)}") + + +def print_ac_table(table_output): + # Calculate the maximum width for each column + col_widths = [max(len(f"{v:.6f}") for v in col) + for col in zip(*table_output)] + header_widths = [max(len(f"B{i}"), col_width) + for i, col_width in enumerate(col_widths)] + + # Calculate the width needed for the row index column + index_width = max(len(str(len(table_output))), len("H")) + + # Print the header + header = "H".ljust(index_width) + " " + " ".join( + [f"B{i}".ljust(header_width) + for i, header_width in enumerate(header_widths)]) + print(header) + + # Print each row + for i, row in enumerate(table_output): + row_str = f"{i+1}".ljust(index_width) + " " + " ".join([ + f"{v:.6f}".ljust(header_width) + for v, header_width in zip(row, header_widths)]) + print(row_str) diff --git a/eat/runeat.py b/eat/runeat.py index 35673ff..f47b7ab 100755 --- a/eat/runeat.py +++ b/eat/runeat.py @@ -4,8 +4,10 @@ from eat.beam_algorithm.beam import BeamEnumerationAlgorithm from eat.deep_drilling_algorithm.dda import DeepDrillingAlgorithm from eat.core.components import Groupoid, TermOperation -from eat.core.utilities import print_execution_results_summary -from eat.utilities.argparse_types import non_negative_integer, restricted_float +from eat.core.utilities import print_execution_results_summary, \ + print_ac_table +from eat.utilities.argparse_types import non_negative_integer, \ + positive_integer, restricted_float def parse_arguments(): @@ -102,6 +104,17 @@ def parse_arguments(): help=("Whether to include validity array in " "verbose log output (default=False)"), action='store_true') + ac_group = parser.add_argument_group( + 'Options for asymptotic complete') + ac_group.add_argument('-ac', '--asymptotic-complete', + help=("Check if groupoid is asymptotic complete."), + action='store_true') + ac_group.add_argument('-acp', '--ac-probabilities', + help=("Probability values to use at height 1."), + nargs='+', type=restricted_float) + ac_group.add_argument('-th', '--table-height', + help=("Print a table of this height."), + type=positive_integer, default=700) return parser.parse_args() @@ -129,6 +142,14 @@ def main(): to.target = to.get_filled_target_array(to.target, args.target_free_count) + # Run AC check and return table + if args.asymptotic_complete is True: + ac_table = grp.is_asymptotic_complete(args.ac_probabilities, + args.term_variables, + limit=args.table_height) + print_ac_table(ac_table) + return + mtgm = args.male_term_generation_method verbose = args.verbose diff --git a/eat/utilities/argparse_types.py b/eat/utilities/argparse_types.py index 9f10479..f635fa3 100644 --- a/eat/utilities/argparse_types.py +++ b/eat/utilities/argparse_types.py @@ -17,6 +17,14 @@ def restricted_float(x): def non_negative_integer(value): ivalue = int(value) if ivalue < 0: + raise argparse.ArgumentTypeError( + "{} is not a non negative int literal".format(value)) + return ivalue + + +def positive_integer(value): + ivalue = int(value) + if ivalue <= 0: raise argparse.ArgumentTypeError( "{} is not a positive int literal".format(value)) return ivalue