def test_bounded_minimize():
    """
    Checks optimization of BoundedVariable
    """
    def get_loss(mean):
        return lambda x: (x - mean)**2

    loss_pos = get_loss(5.)
    loss_0 = get_loss(0.)
    loss_neg = get_loss(-5.)

    v1 = BoundedVariable(np.pi - 1, -3, 3)
    v2 = BoundedVariable(np.pi - 1, -3, 3)
    v3 = BoundedVariable(np.pi - 1, -3, 3)

    # -------------------------------------------------------------------------
    # Test the case when the minimum is within the constrained domain
    # -------------------------------------------------------------------------
    _, converged, _ = ntfo.minimize(loss_0, vs=[v1], tolerance=1e-8)

    # Check that the search converged
    assert converged

    # Check that the solution is good
    assert_near(v1(), 0., atol=1e-8, rtol=1e-8)

    # -------------------------------------------------------------------------
    # Test the case when the minimum is BELOW the constrained range
    # -------------------------------------------------------------------------
    _, converged, _ = ntfo.minimize(loss_neg, vs=[v2], tolerance=1e-8)

    # Check that the search converged
    assert converged

    # Check that the solution is good:
    # The closest we can get to -5 is -3!
    assert_near(v2(), -3, atol=1e-8, rtol=1e-8)

    # -------------------------------------------------------------------------
    # Test the case when the minimum is ABOVE the constrained range
    # -------------------------------------------------------------------------
    _, converged, _ = ntfo.minimize(loss_pos, vs=[v3], tolerance=1e-8)

    # Check that the search converged
    assert converged

    # Check that the solution is good:
    # The closest we can get to 5 is 3!
    assert_near(v3(), 3, atol=1e-8, rtol=1e-8)
def test_mixed_optimization():
    """
    Checks if variables with mixed constraints are optimized correctly together.
    """
    def get_loss(mean):
        def loss(x, y, z):
            return (x + y + z - mean)**2

        return loss

    # Fix seed
    tf.random.set_seed(42)

    # Create variables
    v1 = UnconstrainedVariable(tf.random.uniform(shape=()))
    v2 = PositiveVariable(1 + 3 * tf.random.uniform(shape=()))
    v3 = BoundedVariable(3 + tf.random.uniform(shape=()), lower=3, upper=4)

    # Get loss
    loss = get_loss(0.)

    total_loss, converged, _ = ntfo.minimize(loss, vs=[v1, v2, v3])

    # Test if the optimization converges
    assert converged

    assert_near(total_loss, 0.)
def test_function_arguments_mismatch():
    """
    Test if the arguments given can be passed into the function
    """
    def get_loss(mean):
        return lambda x: (x - mean)**2

    loss = get_loss(0.)

    v1 = PositiveVariable(5.)
    v2 = BoundedVariable(4, -3, 10)

    # Check if we get an error if we have too few arguments passed
    with pytest.raises(ntfo.OptimizationError) as exception:
        ntfo.minimize(loss, vs=[])

    assert str(exception.value) == "Optimization target takes 1 argument(s) " \
                                   "but 0 were given!"

    # Check if we get an error if we have too many arguments passed
    with pytest.raises(ntfo.OptimizationError) as exception:
        ntfo.minimize(loss, vs=[v1, v2])

    assert str(exception.value) == "Optimization target takes 1 argument(s) " \
                                   "but 2 were given!"
Example #4
0
    def __init__(self,
                 in_state_dim,
                 out_state_dim,
                 action_dim,
                 policy,
                 cost,
                 dtype,
                 replay_buffer_limit=None,
                 name='eq_agent',
                 **kwargs):

        super().__init__(in_state_dim=in_state_dim,
                         out_state_dim=out_state_dim,
                         action_dim=action_dim,
                         policy=policy,
                         cost=cost,
                         dtype=dtype,
                         replay_buffer_limit=replay_buffer_limit,
                         name=name,
                         **kwargs)

        # Set EQ covariance parameters: coefficient, scales and noise level
        eq_coeff_init = tf.ones((self.out_state_dim, ), dtype=dtype)
        self.eq_coeff = BoundedVariable(eq_coeff_init,
                                        lower=1e-2,
                                        upper=1e2,
                                        name='eq_coeff',
                                        dtype=dtype)

        eq_scales_init = 1e0 * tf.ones(
            (self.out_state_dim, self.in_state_and_action_dim), dtype=dtype)

        self.eq_scales = BoundedVariable(eq_scales_init,
                                         lower=1e-1,
                                         upper=1e2,
                                         name='eq_scales',
                                         dtype=dtype)

        eq_noise_coeff_init = 1e-2 * tf.ones(
            (self.out_state_dim, ), dtype=dtype)
        self.eq_noise_coeff = BoundedVariable(eq_noise_coeff_init,
                                              lower=1e-3,
                                              upper=1e1,
                                              name='eq_noise_coeff',
                                              dtype=dtype)
def test_implicit_optimization_dependency_check():
    """
    Checks if the optimizer can detect if the function doesn't depend on the
    specified targets.
    """
    def get_implicit_loss(mean, v):
        def loss():
            return (v() - mean)**2

        return loss

    v = BoundedVariable(1, 0, 3)
    v2 = BoundedVariable(3, 0, 10)

    implicit_loss = get_implicit_loss(1, v)

    with pytest.raises(ntfo.OptimizationError) as error:
        ntfo.minimize(implicit_loss, vs=[v2], explicit=False)

    assert "Given function does not depend on some of the given variables!" in str(
        error.value)
Example #6
0
    def __init__(self,
                 state_dim,
                 action_dim,
                 policy,
                 cost,
                 dtype,
                 name='agent',
                 **kwargs):

        super().__init__(state_dim=state_dim,
                         action_dim=action_dim,
                         policy=policy,
                         cost=cost,
                         dtype=dtype,
                         name=name,
                         **kwargs)

        # Set EQ covariance parameters: coefficient, scales and noise level
        eq_coeff_init = tf.ones((state_dim, ), dtype=dtype)
        self.eq_coeff = BoundedVariable(eq_coeff_init,
                                        lower=1e-6,
                                        upper=1e3,
                                        name='eq_coeff',
                                        dtype=dtype)

        eq_scales_init = 1e0 * tf.ones(
            (state_dim, state_dim + action_dim), dtype=dtype)

        self.eq_scales = BoundedVariable(eq_scales_init,
                                         lower=1e-6,
                                         upper=1e3,
                                         name='eq_scales',
                                         dtype=dtype)

        eq_noise_coeff_init = 1e-4 * tf.ones((state_dim, ), dtype=dtype)
        self.eq_noise_coeff = BoundedVariable(eq_noise_coeff_init,
                                              lower=1e-6,
                                              upper=1e3,
                                              name='eq_noise_coeff',
                                              dtype=dtype)
def test_nested_optimization():
    """
    Checks if the optimizer runs correctly when the
    arguments are given in a nested tuple structure.
    """
    def get_nested_loss(mean):
        def loss(x, xs, xss):

            x = x + tf.reduce_mean(xs)

            for xs_ in xss:
                x = x + tf.reduce_sum(xs_)

            return (x - mean)**2

        return loss

    # Fix random seed
    tf.random.set_seed(42)

    # Get loss function
    loss_fn = get_nested_loss(0.)

    # Define variables
    x = BoundedVariable(tf.random.uniform(shape=()), -3., 3.)
    xs = [
        BoundedVariable(tf.random.uniform(shape=(4, )), 0., 1.)
        for _ in range(3)
    ]
    xss = [[
        BoundedVariable(tf.random.uniform(shape=(4, )), -1., 1.)
        for _ in range(3)
    ] for _ in range(2)]

    total_loss, converged, _ = ntfo.minimize(loss_fn, vs=[x, xs, xss])

    assert converged

    # Check if the result is good enough
    assert_near(total_loss, 0.)
def test_implicit_optimization():
    """
    Checks if the optimizer works correctly in the case when the variables are
    not passed explicitly to the function.
    """
    def get_implicit_loss(mean, var):
        def loss():
            return (var() - mean)**2

        return loss

    v = BoundedVariable(9, lower=-3, upper=10)

    implicit_loss = get_implicit_loss(2, v)

    _, converged, _ = ntfo.minimize(implicit_loss, vs=[v], explicit=False)

    assert converged

    # Check if the solution is good
    assert_near(v(), 2., atol=1e-5, rtol=1e-5)
Example #9
0
class EQGPAgent(Agent):
    def __init__(self,
                 in_state_dim,
                 out_state_dim,
                 action_dim,
                 policy,
                 cost,
                 dtype,
                 replay_buffer_limit=None,
                 name='eq_agent',
                 **kwargs):

        super().__init__(in_state_dim=in_state_dim,
                         out_state_dim=out_state_dim,
                         action_dim=action_dim,
                         policy=policy,
                         cost=cost,
                         dtype=dtype,
                         replay_buffer_limit=replay_buffer_limit,
                         name=name,
                         **kwargs)

        # Set EQ covariance parameters: coefficient, scales and noise level
        eq_coeff_init = tf.ones((self.out_state_dim, ), dtype=dtype)
        self.eq_coeff = BoundedVariable(eq_coeff_init,
                                        lower=1e-2,
                                        upper=1e2,
                                        name='eq_coeff',
                                        dtype=dtype)

        eq_scales_init = 1e0 * tf.ones(
            (self.out_state_dim, self.in_state_and_action_dim), dtype=dtype)

        self.eq_scales = BoundedVariable(eq_scales_init,
                                         lower=1e-1,
                                         upper=1e2,
                                         name='eq_scales',
                                         dtype=dtype)

        eq_noise_coeff_init = 1e-2 * tf.ones(
            (self.out_state_dim, ), dtype=dtype)
        self.eq_noise_coeff = BoundedVariable(eq_noise_coeff_init,
                                              lower=1e-3,
                                              upper=1e1,
                                              name='eq_noise_coeff',
                                              dtype=dtype)

    def set_eq_scales_from_data(self):

        sq_diffs = tf.math.squared_difference(self.dynamics_inputs[None, :, :],
                                              self.dynamics_inputs[:, None, :])

        norms = tf.reduce_sum(sq_diffs, axis=-1)**0.5

        median = tfp.stats.percentile(norms, 50.0)

        eq_scales_init = median * tf.ones(
            (self.out_state_dim, self.in_state_and_action_dim),
            dtype=self.dtype)

        self.eq_scales.assign(eq_scales_init)

    @property
    def parameters(self):
        return self.eq_coeff.var, self.eq_scales.var, self.eq_noise_coeff.var

    def match_delta_moments(self, mean_full, cov_full):

        # Reshape mean and covariance
        mean_full = tf.reshape(mean_full,
                               shape=(1, self.in_state_and_action_dim))
        cov_full = tf.reshape(cov_full,
                              shape=(self.in_state_and_action_dim,
                                     self.in_state_and_action_dim))

        # ----------------------------------------------------------------------
        # Compute mean
        # ----------------------------------------------------------------------

        # S x D x D
        eq_scales_inv = tf.linalg.diag(1. / self.eq_scales())

        # S x D x D
        mean_det_coeff = tf.einsum('kl, glm -> gkm', cov_full, eq_scales_inv)

        mean_det_coeff = mean_det_coeff + \
                         tf.eye(mean_det_coeff.shape[-1], dtype=self.dtype)[None, :, :]

        mean_det_coeff = tf.linalg.det(mean_det_coeff)**-0.5

        mean_det_coeff = self.eq_coeff() * mean_det_coeff

        cov_full_plus_scales = cov_full[None, :, :] + tf.linalg.diag(
            self.eq_scales())

        # N x D
        nu = self.dynamics_inputs - mean_full

        # TODO: Two separate solves for the nu, reuse the solves for cross-cov
        mean_quad = tf.einsum(
            'id, gdi -> gi', nu,
            tf.linalg.solve(
                cov_full_plus_scales,
                tf.tile(
                    tf.transpose(nu)[None, :, :], (self.out_state_dim, 1, 1))))

        mean_exp_quad = tf.math.exp(-0.5 * mean_quad)

        mean_exp_quad = mean_det_coeff[:, None] * mean_exp_quad

        mean = tf.einsum('gi, gi -> g', self.beta, mean_exp_quad)

        # ----------------------------------------------------------------------
        # Compute covariance
        # ----------------------------------------------------------------------

        # Calculate denominator for EQ coefficient

        # G x G x D x D
        eq_scales_cross_sum = eq_scales_inv[
            None, :, :, :] + eq_scales_inv[:, None, :, :]

        # G x G x D x D
        R = tf.einsum('ij, abjk -> abik', cov_full, eq_scales_cross_sum)

        # G x G x D x D
        R = R + tf.eye(self.in_state_and_action_dim,
                       dtype=self.dtype)[None, None, :, :]

        # R_det_inv_sqrt = tf.linalg.det(R) ** -0.5
        # (G x G) x D x D
        reshaped_R = tf.reshape(
            R,
            [-1, self.in_state_and_action_dim, self.in_state_and_action_dim])

        # Ignore sign, because R will always be positive definite
        # (G x G)
        _, log_R_det = tf.linalg.slogdet(reshaped_R)

        # G x G
        log_R_det_inv_sqrt = -0.5 * tf.reshape(
            log_R_det, [self.out_state_dim, self.out_state_dim])

        # Calculate numerator for EQ coeffieicent

        # G x 1 x N
        log_k_data_mu = self.exponentiated_quadratic(mean_full,
                                                     self.dynamics_inputs,
                                                     log=True)

        # G x G x N x N
        log_k_ab = log_k_data_mu[None, :, :, :] + log_k_data_mu[:, :, :, None]

        # Calculate exponentiated quadratic

        # G x N x D
        eq_scale_times_nu = tf.einsum('sij, nj -> sni', eq_scales_inv, nu)

        # G x G x N x N x D
        z = eq_scale_times_nu[:, None, :,
                              None, :] + eq_scale_times_nu[None, :, None, :, :]

        # G x G x N x N x D
        cov_full_times_z = tf.einsum('ij, abnmj -> abnmi', cov_full, z)

        # G x G x N x N x D x D
        R_tiled = tf.tile(
            R[:, :, None, None, :, :],
            [1, 1, self.num_datapoints, self.num_datapoints, 1, 1])

        # G x G x N x N x D
        R_inv_cov_full_z = tf.linalg.solve(
            R_tiled, cov_full_times_z[:, :, :, :, :, None])[:, :, :, :, :, 0]

        # G x G x N x N
        cov_quad = tf.einsum('abnmi, abnmi -> abnm', z, R_inv_cov_full_z)
        cov_quad = 0.5 * cov_quad

        # Put coefficient and EQ together

        # G x G x N x N
        log_Q = log_k_ab + cov_quad + log_R_det_inv_sqrt[:, :, None, None]
        Q = tf.math.exp(log_Q)

        # Calculate diagonal covariance terms

        # Select diagonal entries of Q
        Q_diag_indices = tf.tile(tf.range(self.out_state_dim)[:, None], [1, 2])

        # G x N x N
        Q_diag = tf.gather_nd(Q, Q_diag_indices)

        # G x N x N
        data_cov_inv_times_Q_diag = tf.linalg.solve(self.data_covariance,
                                                    Q_diag)

        # G
        expected_var = tf.linalg.trace(data_cov_inv_times_Q_diag)
        expected_var = self.eq_coeff() - expected_var

        # Calculate general covariance terms
        # G x N
        beta = self.beta

        # G x G
        cov = tf.einsum('ai, bj, abij -> ab', beta, beta, Q)

        mean_times_mean = mean[:, None] * mean[None, :]

        cov = cov - mean_times_mean

        cov = cov + tf.linalg.diag(expected_var)

        # Compute Cov[x, Δ]
        mean_full_tiled = tf.tile(
            tf.transpose(mean_full)[None, :, :], (self.out_state_dim, 1, 1))

        dynamics_inputs_tiled = tf.tile(
            tf.transpose(self.dynamics_inputs)[None, :, :],
            (self.out_state_dim, 1, 1))

        # A = cov_full + scales
        # G x D x 1
        A_inv_times_mean_full = tf.linalg.solve(cov_full_plus_scales,
                                                mean_full_tiled)

        # G x D x N
        A_inv_times_dynamics_inputs = tf.linalg.solve(cov_full_plus_scales,
                                                      dynamics_inputs_tiled)

        # G x D x 1
        cross_cov_mu = self.eq_scales()[:, :, None] * A_inv_times_mean_full

        # G x D x N
        cross_cov_mu = cross_cov_mu + tf.einsum('ij, gjk -> gik', cov_full,
                                                A_inv_times_dynamics_inputs)

        # D x G
        cross_cov = tf.einsum('gk, gdk, gk -> dg', mean_exp_quad, cross_cov_mu,
                              beta)

        # S x G
        cross_cov_s = cross_cov[:self.in_state_dim, :]
        cross_cov_mean_prod = tf.transpose(
            mean_full[:, :self.in_state_dim]) * mean[None, :]
        cross_cov_s = cross_cov_s - cross_cov_mean_prod

        return mean, cov, cross_cov_s

    @property
    def num_datapoints(self):
        return self._dynamics_inputs.value().shape[0]

    @property
    def beta(self):

        # S x N x N
        cov = self.data_covariance

        # S x N x 1
        dynamics_outputs_T = tf.transpose(self.dynamics_outputs)[:, :, None]

        # S x N
        cov_inv_output = tf.linalg.solve(cov, dynamics_outputs_T)[:, :, 0]

        return cov_inv_output

    @property
    def data_covariance(self):

        dynamics_inputs = self.dynamics_inputs

        K = self.exponentiated_quadratic(dynamics_inputs, dynamics_inputs)

        noise = self.eq_noise_coeff()[:, None, None] * tf.eye(
            K.shape[-1], dtype=self.dtype)[None, :, :]

        return K + noise

    def exponentiated_quadratic(self, x, x_, log=False):
        """
        x - N x D tensor
        x_ - M x D tensor

        where N, M are the batch dimensions and D is the dimensionality

        returns S x N x M
        """

        # N x M x D tensor
        diffs = x[:, None, :] - x_[None, :, :]

        quads = tf.einsum('nmd, nmd, gd -> gnm', diffs, diffs,
                          1. / self.eq_scales())

        if log:
            return tf.math.log(self.eq_coeff()[:, None, None]) - 0.5 * quads
        else:
            return self.eq_coeff()[:, None, None] * tf.math.exp(-0.5 * quads)

    def gp_posterior_predictive(self, x_star):
        """
        x_star - K x D tensor of K inputs of dimensionality D
        """

        x_star = self._validate_and_convert(x_star,
                                            self.in_state_and_action_dim)

        # S x N x K
        k_star = self.exponentiated_quadratic(self.dynamics_inputs, x_star)

        # S x K x K
        k_star_star = self.exponentiated_quadratic(x_star, x_star)
        k_star_star = tf.linalg.diag_part(k_star_star)

        # S x N x N
        K_plus_noise = self.data_covariance

        # S x N
        pred_mean = tf.einsum('snk, sn -> ks', k_star, self.beta)

        # S x N x K
        cov_inv_k = tf.linalg.solve(K_plus_noise, k_star)

        # S x K
        k_cov_inv_k = tf.einsum('snk, snk -> sk', k_star, cov_inv_k)

        pred_cov = tf.transpose(k_star_star - k_cov_inv_k)

        return pred_mean, pred_cov

    def dynamics_log_marginal(self):

        # return tf.reduce_sum(x) + tf.reduce_sum(y) + tf.reduce_sum(z)

        # S x N x N
        cov = self.data_covariance
        log_normalizing_const = -self.in_state_and_action_dim * 0.5 * tf.math.log(
            2 * np.pi)
        log_normalizing_const = tf.cast(log_normalizing_const, self.dtype)

        # S
        log_normalizing_const = log_normalizing_const - 0.5 * tf.linalg.slogdet(
            cov)[1]
        log_normalizing_const = tf.reduce_sum(log_normalizing_const)

        # S x N x 1
        cov_inv_times_dynamics_outputs = tf.linalg.solve(
            cov,
            tf.transpose(self.dynamics_outputs)[:, :, None])

        quad = -0.5 * tf.einsum('ns, sni ->', self.dynamics_outputs,
                                cov_inv_times_dynamics_outputs)

        return log_normalizing_const + quad

    def get_cost(self, state_loc, state_cov, horizon):

        self.policy.match_delta_moments()

    def optimize_policy(self):
        pass

    def train_dynamics_model(self):
        pass
def test_minimize_arguments():
    # The variables passed to ntfo.minimize must always be in an iterable!
    with pytest.raises(ntfo.OptimizationError):
        ntfo.minimize(lambda x: x, vs=UnconstrainedVariable(1.))

    # Optimizer must be in AVAILABLE_OPTIMIZERS
    with pytest.raises(ntfo.OptimizationError):
        ntfo.minimize(lambda x: x,
                      vs=[UnconstrainedVariable(1.)],
                      optimizer="blabla")


@pytest.mark.parametrize('variable', [
    UnconstrainedVariable, PositiveVariable,
    lambda x: BoundedVariable(x, -100., 100.)
])
def test_high_dimensional_minimize(variable):
    """
    Example taken from
    https://www.tensorflow.org/probability/api_docs/python/tfp/optimizer/lbfgs_minimize

    """

    # A high-dimensional quadratic bowl.
    ndims = 60
    minimum = 10 * tf.ones([ndims], dtype=tf.float64)
    scales = tf.range(ndims, dtype=tf.float64) + 1.0

    def quadratic_loss(x):
        return tf.reduce_sum(scales * tf.math.squared_difference(x, minimum),