Add expression assumptions using a context manager

Literal assumptions:

>>> with a, b, ~c:
...     g = f.restrict({})

and

>>> with a & b & ~c:
...     g = f.restrict({})

are both the same as:

>>> g = f.restrict({a: 1, b: 1, c: 0})

Similarly for `satisfy_one`:

>>> with a, ~b & c:
...     soln = f.satisfy_one()