def test_random_features_generator(sample_size=10,
                                   compute_sample_statistics=True):

    config = Config()
    config.num_true_features = 2
    config.num_obs_features = 2
    config.max_num_features = 20000
    task = RandomFeatures(config)
    print("The value of theta is:\n{0}".format(task.theta))
    print("The norm of theta is: {0}".format(np.linalg.norm(task.theta)))

    for i in range(sample_size):
        target, observable_features, best_approximation = task.sample_observation(
            noisy=False)
        print("The features are: {0}\tThe target is:{1}".format(
            observable_features, target))

    if compute_sample_statistics:
        num_samples = 100000
        samples = np.zeros(num_samples)
        for i in range(num_samples):
            target, _, _ = task.sample_observation(noisy=False)
            samples[i] += target
        # The sample average and sample variance of the target should be 0 and 1, respectively.
        print("The sample average of the target is: {:.2f}".format(
            np.average(samples)))
        print("The sample variance of the target is: {:.2f}".format(
            np.var(samples)))
def test_function_approximator(num_features=20,
                               initial_features=20,
                               num_iterations=10000,
                               chkpt=100,
                               plot_mse=True,
                               noisy=True,
                               add_features=False,
                               add_true_features=True,
                               feature_add_interval=100,
                               mixed_features=False):

    from src.step_size_methods import SGD
    config = Config()
    # task setup
    config.num_true_features = num_features
    config.num_obs_features = initial_features  # same as function approximator
    config.max_num_features = 20000  # same as function approximator
    task = RandomFeatures(config)

    # function approximator setup
    approximator = LinearFunctionApproximator(config)

    # optimizer setup
    config.parameter_size = initial_features
    config.alpha = 0.001
    optimizer = SGD(config)

    # for plotting
    mse_per_chpt = np.zeros(num_iterations // chkpt, dtype=np.float64)
    mse = 0
    current_chpt = 0

    # training loop
    for i in range(num_iterations):
        target, observable_features, best_approximation = task.sample_observation(
            noisy=noisy)
        prediction = approximator.get_prediction(observable_features)
        error = target - prediction
        _, _, new_weights = optimizer.update_weight_vector(
            error, observable_features, approximator.get_weight_vector())
        approximator.update_weight_vector(new_weights)

        squared_loss = np.square(error)
        mse += squared_loss / chkpt
        if (i + 1) % chkpt == 0:
            # reporting and saving
            print("Iteration number: {0}".format(i + 1))
            print("\tTarget: {0:.4f}".format(target))
            print("\tPrediction: {0:.4f}".format(prediction))
            print("\tMean Squared Error: {0:.4f}".format(mse))
            mse_per_chpt[current_chpt] += mse
            mse *= 0
            current_chpt += 1

        if add_features and (i + 1) % feature_add_interval == 0:
            task.add_new_feature(k=1, true_feature=add_true_features)
            approximator.increase_num_features(k=1)
            optimizer.increase_size(k=1)
            if mixed_features:
                add_true_features = not add_true_features

    if plot_mse:
        # plots
        import matplotlib.pyplot as plt
        x_axis = np.arange(num_iterations // chkpt)
        plt.plot(x_axis, mse_per_chpt)
        plt.show()
        plt.close()