def create_dummy_expression(penalty_variable, constraint_variable): """Creates an empty `Expression` from the given extra variables.""" return expression.ExplicitExpression( basic_expression.BasicExpression( [term.TensorTerm(penalty_variable)]), basic_expression.BasicExpression( [term.TensorTerm(constraint_variable)]))
def test_bounded_expression(self): """Tests that `BoundedExpression`s select their components correctly.""" structure_memoizer = { defaults.DENOMINATOR_LOWER_BOUND_KEY: 0.0, defaults.GLOBAL_STEP_KEY: tf.Variable(0, dtype=tf.int32), defaults.VARIABLE_FN_KEY: tf.Variable } term1 = term.TensorTerm(1.0) term2 = term.TensorTerm(2.0) term3 = term.TensorTerm(4.0) term4 = term.TensorTerm(8.0) basic_expression1 = basic_expression.BasicExpression([term1]) basic_expression2 = basic_expression.BasicExpression([term2]) basic_expression3 = basic_expression.BasicExpression([term3]) basic_expression4 = basic_expression.BasicExpression([term4]) expression1 = expression.ExplicitExpression(basic_expression1, basic_expression1) expression2 = expression.ExplicitExpression(basic_expression2, basic_expression2) expression3 = expression.ExplicitExpression(basic_expression3, basic_expression3) expression4 = expression.ExplicitExpression(basic_expression4, basic_expression4) # Each of our BasicExpressions contains exactly one term, and while we might # negate it, by taking the absolute value we can uniquely determine which # BasicExpression is which. def term_value(expression_object): terms = expression_object.penalty_expression._terms self.assertEqual(1, len(terms)) return abs(terms[0].tensor(structure_memoizer)) bounded_expression1 = expression.BoundedExpression( lower_bound=expression1, upper_bound=expression2) self.assertEqual(term_value(bounded_expression1), 2.0) self.assertEqual(term_value(-bounded_expression1), 1.0) bounded_expression2 = expression.BoundedExpression( lower_bound=expression3, upper_bound=expression4) self.assertEqual(term_value(bounded_expression2), 8.0) self.assertEqual(term_value(-bounded_expression2), 4.0) bounded_expression3 = -(bounded_expression1 - bounded_expression2) self.assertEqual(term_value(bounded_expression3), 8.0 - 1.0) self.assertEqual(term_value(-bounded_expression3), 4.0 - 2.0) # Checks that nested BoundedExpressions work. bounded_expression4 = expression.BoundedExpression( lower_bound=bounded_expression1, upper_bound=expression3) self.assertEqual(term_value(bounded_expression4), 4.0) self.assertEqual(term_value(-bounded_expression4), 1.0) # Checks that nested negated BoundedExpressions work. bounded_expression5 = expression.BoundedExpression( lower_bound=-bounded_expression1, upper_bound=-bounded_expression2) self.assertEqual(term_value(bounded_expression5), 4.0) self.assertEqual(term_value(-bounded_expression5), 2.0)
def constant_expression(penalty_constant, constraint_constant=None): penalty_basic_expression = basic_expression.BasicExpression([ term.TensorTerm(tf.constant(penalty_constant, dtype=tf.float32)) ]) if constraint_constant is None: constraint_basic_expression = penalty_basic_expression else: constraint_basic_expression = basic_expression.BasicExpression( [ term.TensorTerm( tf.constant(constraint_constant, dtype=tf.float32)) ]) return expression.ExplicitExpression(penalty_basic_expression, constraint_basic_expression)
def wrap_rate(penalty_tensor, constraint_tensor=None): """Creates an `Expression` representing the given `Tensor`(s). The reason an `Expression` contains two `BasicExpression`s is that the "penalty" `BasicExpression` will be differentiable, while the "constraint" `BasicExpression` need not be. During optimization, the former will be used whenever we need to take gradients, and the latter otherwise. Args: penalty_tensor: scalar `Tensor`, the quantity to store in the "penalty" portion of the result (and also the "constraint" portion, if constraint_tensor is not provided). constraint_tensor: scalar `Tensor`, the quantity to store in the "constraint" portion of the result. Returns: An `Expression` wrapping the given `Tensor`(s). Raises: TypeError: if wrap_rate() is called on an `Expression`. """ # Ideally, we'd check that "penalty_tensor" and "constraint_tensor" are scalar # Tensors, or are types that can be converted to a scalar Tensor. # Unfortunately, this includes a lot of possible types, so the easiest # solution would be to actually perform the conversion, and then check that # the resulting Tensor has only one element. This, however, would add a dummy # element to the Tensorflow graph, and wouldn't work for a Tensor with an # unknown size. Hence, we only check that "penalty_tensor" and # "constraint_tensor" are not types that we know for certain are disallowed: # objects internal to this library. if (isinstance(penalty_tensor, helpers.RateObject) or isinstance(constraint_tensor, helpers.RateObject)): raise TypeError( "you cannot wrap an object that has already been wrapped") penalty_basic_expression = basic_expression.BasicExpression([ term.TensorTerm(deferred_tensor.ExplicitDeferredTensor(penalty_tensor)) ]) if constraint_tensor is None: constraint_basic_expression = penalty_basic_expression else: constraint_basic_expression = basic_expression.BasicExpression([ term.TensorTerm( deferred_tensor.ExplicitDeferredTensor(constraint_tensor)) ]) return expression.ExplicitExpression(penalty_basic_expression, constraint_basic_expression)
def upper_bound(expressions): """Creates an `Expression` upper bounding the given expressions. This function introduces a slack variable, and adds constraints forcing this variable to upper bound all elements of the given expression list. It then returns the slack variable. If you're going to be upper-bounding or minimizing the result of this function, then you can think of it as taking the `max` of its arguments. You should *never* lower-bound or maximize the result, however, since the consequence would be to increase the value of the slack variable, without affecting the contents of the expressions list. Args: expressions: list of `Expression`s, the quantities to upper-bound. Returns: An `Expression` representing an upper bound on the given expressions. Raises: ValueError: if the expressions list is empty. TypeError: if the expressions list contains a non-`Expression`. """ if not expressions: raise ValueError( "upper_bound cannot be given an empty expression list") if not all(isinstance(ee, expression.Expression) for ee in expressions): raise TypeError( "upper_bound expects a list of rate Expressions (perhaps you need to " "call wrap_rate() to create an Expression from a Tensor?)") # Ideally the slack variable would have the same dtype as the predictions, but # we might not know their dtype (e.g. in eager mode), so instead we always use # float32 with auto_cast=True. bound = deferred_tensor.DeferredVariable(0.0, trainable=True, name="tfco_upper_bound", dtype=tf.float32, auto_cast=True) bound_basic_expression = basic_expression.BasicExpression( [term.TensorTerm(bound)]) bound_expression = expression.ExplicitExpression( penalty_expression=bound_basic_expression, constraint_expression=bound_basic_expression) extra_constraints = [ee <= bound_expression for ee in expressions] # We wrap the result in a BoundedExpression so that we'll check if the user # attempts to maximize of lower-bound the result of this function, and will # raise an error if they do. return expression.BoundedExpression( lower_bound=expression.InvalidExpression( "the result of a call to upper_bound() can only be minimized or " "upper-bounded; it *cannot* be maximized or lower-bounded"), upper_bound=expression.ConstrainedExpression( expression.ExplicitExpression( penalty_expression=bound_basic_expression, constraint_expression=bound_basic_expression), extra_constraints=extra_constraints))
def __add__(self, other): """Returns the result of adding two `Expression`s.""" if not isinstance(other, helpers.RateObject): # BasicExpressions do not support scalar addition, so we first need to # convert the scalar into an Expression. other_basic_expression = basic_expression.BasicExpression( [term.TensorTerm(other)]) other = ExplicitExpression(other_basic_expression, other_basic_expression) elif not isinstance(other, Expression): raise TypeError("Expression objects can only be added to each other, or " "scalars") return SumExpression([self, other])
def create_dummy_expression(value): """Creates an empty `Expression` with the given extra constraints.""" basic_expression_object = basic_expression.BasicExpression( [term.TensorTerm(value)]) return expression.ExplicitExpression(basic_expression_object, basic_expression_object)
def constant_expression(constant): return basic_expression.BasicExpression( [term.TensorTerm(tf.constant(constant, dtype=tf.float32))])