def test_factor(): instrs: List[Instruction] = [] candidate_instrs: List[Instruction] = [] mconst = MultConst(debug=True) # mconst = MultConst(debug=debug) mconst.search_methods = (search_short_factors, ) n = 27 s_cost, s_instrs = search_short_factors( mconst, n, upper=20, lower=0, instrs=instrs, candidate_instrs=candidate_instrs, ) check(n, s_cost, s_instrs) cost, instrs = mconst.try_shift_op_factor(n, 5, "add", 2, s_cost, 0, [], s_instrs) assert s_cost == cost, "5 is not a factor of 27, so we keep old s_cost" for n, factor, shift_amount in ((51, 3, 1), (85, 5, 2)): s_cost, s_instrs = binary_method.binary_sequence(mconst, n) print_instructions(s_instrs, n, s_cost) result = [] cost, result = mconst.try_shift_op_factor(n, factor, "add", shift_amount, s_cost, 0, [], s_instrs) assert cost < s_cost, f"should use the fact that {factor} is a factor of {n}" print_instructions(result, n, cost)
def find_mult_sequence( self, n: int, search_methods=None) -> Tuple[float, List[Instruction]]: """Top-level searching routine. Computes binary method upper bound and then does setup to the alpha-beta search """ cache_lower, limit, finished, cache_instrs = self.mult_cache[n] if finished: return limit, cache_instrs if n < 0 and not self.cpu_model.can_negate(): raise RuntimeError( f"""CPU model "{self.cpu_model.name}" can't handle negative numbers.""" ) # FIXME: move elswhere, such as into search_methods. if search_methods is None: # Note timings show search_cache() *without* binary search is faster on one-shot # searches than search_binary_method_with_cache(). if n > 0: if self.cpu_model.can_negate(): self.search_methods = (search_cache, search_short_factors, search_add_one, search_subtract_one) else: self.search_methods = ( search_cache, search_short_add_factors, search_add_one, ) else: self.search_methods = ( search_cache, search_short_factors, search_add_or_subtract_one, search_negate_subtract_one, ) pass pass if limit == inf_cost: # The binary sequence gives a workable upper bound on the cost cache_lower, cache_instrs = binary_sequence(self, n) self.mult_cache.insert_or_update(n, 0, limit, False, cache_instrs) cost, instrs = self.alpha_beta_search(n, 0, limit=limit) self.mult_cache.update_field(n, upper=cost, finished=True, instrs=instrs) if instrs: return cost, instrs else: return limit, cache_instrs
def main(to, model, showcache, debug, binary_method, fmt, compact, output, numbers): """Searches for short sequences of shift, add, subtract instruction to compute multiplication by a constant. """ model = SHORT2MODEL[model] mult = MultConst(cpu_model=model, debug=debug) if to: for number in range(2, to + 1): if binary_method: cost, instrs = binary_sequence(mult, number) else: cost, instrs = mult.find_mult_sequence(number) pass pass pass else: for number in numbers: if binary_method: cost, instrs = binary_sequence(mult, number) else: cost, instrs = mult.find_mult_sequence(number) print_instructions(instrs, number, cost) pass pass if output or showcache or to: if output is None: output = sys.stdout if fmt == "text": dump(mult.mult_cache, out=output) elif fmt == "csv": dump_csv(mult.mult_cache, out=output) elif fmt == "yaml": dump_yaml(mult.mult_cache, out=output, compact=compact) else: assert fmt == "json" indent = None if compact else 2 dump_json(mult.mult_cache, out=output, indent=indent) return
def test_negate(): debug = "DEBUG" in os.environ instrs: List[Instruction] = [] candidate_instrs: List[Instruction] = [] mconst = MultConst(debug=debug) mconst.search_methods = search_negate n = 10 bin_cost, bin_instrs = binary_method.binary_sequence(mconst, n) negate_cost = mconst.op_costs["negate"] # upper is too low to allow us to get a solution scost, s_instrs = search_negate( mconst, -n, upper=bin_cost + negate_cost, lower=bin_cost, instrs=bin_instrs, candidate_instrs=candidate_instrs, ) assert s_instrs == candidate_instrs # upper is okay (and exact) to allow us to get a solution s_cost, s_instrs = search_negate( mconst, -n, upper=bin_cost + negate_cost + 1, lower=bin_cost, instrs=bin_instrs, candidate_instrs=candidate_instrs, ) assert s_cost == bin_cost + negate_cost check(-n, s_cost, s_instrs, debug) # Try something not in the cache. # Negate will use the binary method n = -23 s_cost, s_instrs = search_negate( mconst, n, upper=inf_cost, lower=0, instrs=instrs, candidate_instrs=candidate_instrs, ) check(n, s_cost, instrs=s_instrs, debug=debug)
def test_binary_method(): debug = "DEBUG" in os.environ mconst = MultConstClass(debug=debug) for (n, expect_cost) in ( (0, 1), (1, 0), (-1, 1), (2, 1), (3, 2), (4, 1), (5, 2), (6, 3), (7, 2), (-7, 2), (-3, 2), (8, 1), (53, 6), (340, 7), ): cost, result = binary_method.binary_sequence(mconst, n) if debug: print_instructions(result, n, cost) assert expect_cost == cost, f"cost({n}) = {cost}; expected it to be {expect_cost}."
def test_search_subtract_one(): debug = "DEBUG" in os.environ instrs: List[Instruction] = [] candidate_instrs: List[Instruction] = [] mconst = MultConst(debug=True) # mconst = MultConst(debug=debug) mconst.search_methods = (search_subtract_one, ) n = 12 bin_cost, bin_instrs = binary_method.binary_sequence(mconst, n) subtract_cost = mconst.op_costs["subtract"] # upper is too low to allow us to get a solution s_cost, s_instrs = search_subtract_one( mconst, n, upper=bin_cost + subtract_cost, lower=bin_cost, instrs=bin_instrs, candidate_instrs=candidate_instrs, ) assert s_instrs == candidate_instrs # No check since we did a cutoff. # We should start out with n+1, instead when # we subtract one, so we should get a cutoff here # quickly too s_cost, s_instrs = search_subtract_one( mconst, n, upper=bin_cost + subtract_cost + 1, lower=bin_cost, instrs=bin_instrs, candidate_instrs=candidate_instrs, ) assert s_instrs == candidate_instrs # No check since we did a cutoff. # upper is okay to allow us to get a solution using # cached entry 3 added via binary_method. instrs = [] s_cost, s_instrs = search_subtract_one( mconst, n - 1, upper=20, lower=0, instrs=instrs, candidate_instrs=candidate_instrs, ) check(n - 1, s_cost, s_instrs, debug) from mult_by_const.io import dump dump(mconst.mult_cache) # Try something that makes subtract count up for a bit. instrs = [] candidate_instrs = [] n = 10 s_cost, s_instrs = search_subtract_one( mconst, n, upper=inf_cost, lower=0, instrs=instrs, candidate_instrs=candidate_instrs, ) check(n, s_cost, instrs=s_instrs, debug=debug) dump(mconst.mult_cache) # Try something that makes subtract count up for a bit and fail. mconst.mult_cache.clear() instrs = [] candidate_instrs = [] n = 20 s_cost, s_instrs = search_subtract_one( mconst, n, upper=6, lower=0, instrs=instrs, candidate_instrs=candidate_instrs, ) dump(mconst.mult_cache) assert not s_instrs, f"should not find a value for {n} after clearing cache" assert (mconst.mult_cache[n + 1][0] > 0), f"{n} failed but lower should have been updated" # Negative numbers! instrs = [] candidate_instrs = [] n = -3 s_cost, s_instrs = search_subtract_one( mconst, n, upper=8, lower=0, instrs=instrs, candidate_instrs=candidate_instrs, ) check(n, s_cost, instrs=s_instrs, debug=debug) from mult_by_const.io import dump dump(mconst.mult_cache)
def test_search_add_one(): instrs: List[Instruction] = [] candidate_instrs: List[Instruction] = [] mconst = MultConst(debug=True) # mconst = MultConst(debug=debug) mconst.search_methods = (search_add_one, ) n = 12 bin_cost, bin_instrs = binary_method.binary_sequence(mconst, n) add_cost = mconst.op_costs["add"] # upper is too low to allow us to get a solution s_cost, s_instrs = search_add_one( mconst, n, upper=bin_cost + add_cost, lower=bin_cost, instrs=bin_instrs, candidate_instrs=candidate_instrs, ) assert s_instrs == candidate_instrs # No check since we did a cutoff. # We should start out with n+1, instead when # we subtract one, so we should get a cutoff here # quickly too s_cost, s_instrs = search_add_one( mconst, n, upper=bin_cost + add_cost + 1, lower=bin_cost, instrs=bin_instrs, candidate_instrs=candidate_instrs, ) assert s_instrs == candidate_instrs # No check since we did a cutoff. # upper is okay to allow us to get a solution using # cached entry 3 added via binary_method. instrs = [] s_cost, s_instrs = search_add_one( mconst, n + 1, upper=20, lower=0, instrs=instrs, candidate_instrs=candidate_instrs, ) check(n + 1, s_cost, s_instrs) from mult_by_const.io import dump dump(mconst.mult_cache) # Try something that makes add count down for a bit. instrs = [] candidate_instrs = [] n = 11 s_cost, s_instrs = search_add_one( mconst, n, upper=inf_cost, lower=0, instrs=instrs, candidate_instrs=candidate_instrs, ) check(n, s_cost, instrs=s_instrs) from mult_by_const.io import dump dump(mconst.mult_cache) # Try something that makes add count down for a bit and fail. mconst.mult_cache.clear() instrs = [] candidate_instrs = [] n = 20 s_cost, s_instrs = search_add_one( mconst, n, upper=6, lower=0, instrs=instrs, candidate_instrs=candidate_instrs, )