Source code for arxpy.bitvector.context

"""Provide context managers to modify the default behaviour."""
import collections
import contextlib

import bidict

from arxpy.bitvector import core


[docs]class StatefulContext(contextlib.AbstractContextManager): """Base class for context managers with history.""" current_context = None def __init__(self, new_context): """Initialize the context.""" self.new_context = new_context def __enter__(self): self.previous_context = type(self).current_context type(self).current_context = self.new_context # return self.new_context def __exit__(self, *args): type(self).current_context = self.previous_context
[docs]class Cache(StatefulContext): """Control the Cache context. Control whether or not the cache is used operating with bit-vectors. By default, the cache is enabled. Note that the Cache context cannot be enabled when the `Simplification` or `Evaluation` context are disabled. """ current_context = True def __init__(self, new_context): """Initialize the context.""" assert new_context in [True, False] super().__init__(new_context) def __enter__(self): if self.new_context is True: assert Simplification.current_context is True assert Evaluation.current_context is True assert Memoization.current_context is False super().__enter__()
[docs]class Simplification(StatefulContext): """Control the Simplification context. Control whether or not bit-vector expressions are automatically simplified. By default, automatic simplification is enabled. >>> from arxpy.bitvector.core import Variable >>> from arxpy.bitvector.context import Simplification >>> x, y = Variable("x", 8), Variable("y", 8) >>> (x | y) | x x | y >>> with Simplification(False): ... expr = (x | y) | x >>> expr x | x | y When the Simplification context is disabled, the `Cache` context is also disabled. Note: Disabling `Simplification` and `Validation` speeds up non-symbolic computations with bit-vectors. """ current_context = True def __init__(self, new_context): """Initialize the context.""" assert new_context in [True, False] super().__init__(new_context) def __enter__(self): if self.new_context is False: self.cache_context = Cache(False) self.cache_context.__enter__() super().__enter__() def __exit__(self, *args): if self.new_context is False: self.cache_context.__exit__() super().__exit__()
[docs]class Evaluation(StatefulContext): """Control the Evaluation context. Control whether or not bit-vector operations are evaluated. By default, bit-vector expressions are evaluated. >>> from arxpy.bitvector.core import Constant >>> from arxpy.bitvector.context import Evaluation >>> Constant(1, 8) + Constant(1, 8) 0x02 >>> with Evaluation(False): ... expr = Constant(1, 8) + Constant(1, 8) >>> expr 0x01 + 0x01 >>> expr.doit() 0x02 When the Evaluation context is disabled, the `Simplification` and `Cache` contexts are also disabled. """ current_context = True def __init__(self, new_context): """Initialize the context.""" assert new_context in [True, False] super().__init__(new_context) def __enter__(self): if self.new_context is False: self.simplify_context = Simplification(False) self.simplify_context.__enter__() super().__enter__() def __exit__(self, *args): if self.new_context is False: self.simplify_context.__exit__() super().__exit__()
[docs]class Validation(StatefulContext): """Control the Validation context. Control whether or not arguments of bit-vector operators are validated. By default, validation of arguments is enabled. Note that when it is disabled, Automatic Constant Conversion is no longer available (see `Operation`). >>> from arxpy.bitvector.core import Constant >>> from arxpy.bitvector.context import Validation >>> Constant(1, 8) + 1 0x02 >>> with Validation(False): ... Constant(1, 5) + 2 Traceback (most recent call last): ... AttributeError: 'int' object has no attribute 'width' Note: Disabling `Simplification` and `Validation` speeds up non-symbolic computations with bit-vectors. """ current_context = True def __init__(self, new_context): """Initialize the context.""" assert new_context in [True, False] super().__init__(new_context)
[docs]class Memoization(StatefulContext): """Control the Memoization context. Control whether or not bit-vector operations are evaluated in the *memoization mode*. By default, it is disabled. In the memoization mode, the result of each bit-vector operation is stored in a table (with an unique identifier). When the same inputs occurs again, the result is retrieved from the table. See also `Memoization <https://en.wikipedia.org/wiki/Memoization>`_. Note that in the memoization mode, bit-vector operations don't return the actual values but their identifiers in the memoization table. The actual values can be obtained from the `MemoizationTable`. >>> from arxpy.bitvector.core import Variable >>> from arxpy.bitvector.context import Memoization, MemoizationTable >>> x, y, z = Variable("x", 8), Variable("y", 8), Variable("z", 8), >>> ~((x + y) ^ (z & y)) ~((x + y) ^ (y & z)) >>> lut = MemoizationTable() >>> with Memoization(lut): ... expr = ~((x + y) ^ (z & y)) >>> expr x3 >>> lut MemoizationTable([(x0, x + y), (x1, y & z), (x2, x0 ^ x1), (x3, ~x2)]) The Memoization context is useful to efficiently compute large symbolic expressions since the identifiers are used instead of the full expressions. >>> from arxpy.bitvector.core import Variable >>> from arxpy.bitvector.context import Memoization, MemoizationTable >>> x = Variable("x", 8) >>> expr = x >>> for i in range(3): expr += expr >>> expr x + x + x + x + x + x + x + x >>> lut = MemoizationTable() >>> with Memoization(lut): ... expr = x ... for i in range(3): expr += expr >>> expr x2 >>> lut # doctest: +NORMALIZE_WHITESPACE MemoizationTable([(x0, x + x), (x1, x0 + x0), (x2, x1 + x1)]) When the Memoization context is enabled, the `Simplification` and `Cache` contexts are disabled. """ current_context = None def __init__(self, new_context): """Initialize the context.""" assert new_context is None or isinstance(new_context, MemoizationTable) super().__init__(new_context) def __enter__(self): if self.new_context is not None: self.simplify_context = Simplification(False) self.simplify_context.__enter__() super().__enter__() def __exit__(self, *args): if self.new_context is not None: self.simplify_context.__exit__() super().__exit__()
[docs]class MemoizationTable(collections.abc.MutableMapping): """Store bit-vector expressions with unique identifiers. The MemoizationTable is a dictionary-like structure (implementing the usual methods of a dictionary and some additional methods) used for evaluating bit-vector operations in the *memoization mode* (see `Memoization`). >>> from arxpy.bitvector.core import Variable >>> from arxpy.bitvector.context import Memoization, MemoizationTable >>> x, y = Variable("x", 8), Variable("y", 8) >>> lut = MemoizationTable() >>> with Memoization(lut): ... expr = ~(x + y) >>> lut MemoizationTable([(x0, x + y), (x1, ~x0)]) >>> lut[Variable("x0", 8)] x + y >>> lut.get_id(x + y) x0 >>> lut.add_op(Variable("x1", 8) & Variable("z", 8)) x2 >>> lut MemoizationTable([(x0, x + y), (x1, ~x0), (x2, x1 & z)]) >>> lut.replace_id(Variable("x0", 8), Variable("x_0", 8)) >>> lut MemoizationTable([(x_0, x + y), (x1, ~x_0), (x2, x1 & z)]) """ def __init__(self, id_prefix="x"): """Initialize an MemoizationTable.""" self.table = bidict.OrderedBidict() self.counter = 0 self.id_prefix = id_prefix def __getitem__(self, key): return self.table.__getitem__(key) def __setitem__(self, key, expr): raise AttributeError("use add_op and replace_id instead") def __delitem__(self, key): assert all(key not in op.atoms() for op in self.table.values()) return self.table.__delitem__(key) def __len__(self): return self.table.__len__() def __iter__(self): return self.table.__iter__() def __str__(self): return '{0}({1})'.format(type(self).__name__, list(self.table.items())) __repr__ = __str__
[docs] def add_op(self, expr): """Add an bit-vector expression and return its identifier.""" from arxpy.bitvector import operation assert isinstance(expr, operation.Operation) assert not self.contain_op(expr) name = "{}{}".format(self.id_prefix, self.counter) self.counter += 1 identifier = core.Variable(name, expr.width) self.table[identifier] = expr return identifier
[docs] def get_id(self, expr): """Return the identifier of a bit-vector expression.""" return self.table.inv[expr]
[docs] def contain_op(self, expr): """Check if the bit-vector expression is stored.""" return expr in self.table.inv
[docs] def replace_id(self, old_id, new_id): """Replace the old identifier by the given new identifier.""" assert isinstance(old_id, core.Variable) assert isinstance(new_id, core.Variable) assert old_id in self.table and new_id not in self.table table = list(self.table.items()) for i, (key, op) in enumerate(table): if key == old_id: new_key = new_id else: new_key = key table[i] = (new_key, op.xreplace({old_id: new_id})) self.table = bidict.OrderedBidict(table)
[docs] def clear(self): """Empty the table.""" self.__init__()
[docs]class NotEvaluation(StatefulContext): """Control the NotEvaluation context. Control whether or not some operations are not evaluated. By default, all operations are evaluated. >>> from arxpy.bitvector.core import Constant >>> from arxpy.bitvector.extraop import PopCount >>> from arxpy.bitvector.context import NotEvaluation >>> PopCount(Constant(0b010, 3) + Constant(0b001, 3)) 0b10 >>> with NotEvaluation(PopCount): ... expr = PopCount(Constant(0b010, 3) + Constant(0b001, 3)) >>> expr PopCount(0b011) >>> expr.doit() 0b10 When the NotEvaluation context is enable, the `Cache` is disabled. """ current_context = None def __init__(self, new_context): """Initialize the context.""" super().__init__(new_context) def __enter__(self): if self.new_context is not None: self.cache_context = Cache(False) self.cache_context.__enter__() super().__enter__() def __exit__(self, *args): if self.new_context is not None: self.cache_context.__exit__() super().__exit__()