//  [python]
def estimate_tsne(
        full_data, vars, x0,
        num_pca_components, B, use_neural_network, sess,
        target_perplexity=50.0,
        # target_perplexity=50.0,
        alpha=1.0, use_vlr=False):

    num_samples, n = np.shape(x0)

    exp_x = rnaseq_approx_likelihood_sampler(
        np.zeros((num_samples, n-1), dtype=np.float32),
        efflens=vars["efflen"],
        mu=vars["la_mu"],
        sigma=vars["la_sigma"],
        alpha=vars["la_alpha"],
        left_index=vars["left_index"],
        right_index=vars["right_index"],
        leaf_index=vars["leaf_index"])
    x = tf.log(exp_x)

    init = tf.global_variables_initializer()
    sess.run(init, feed_dict=full_data)

    target_perplexity = min(target_perplexity, float(num_samples)-1)

    tsne_all_sigmas = find_sigmas(sess.run(x), target_perplexity, use_vlr)
    print(tsne_all_sigmas)

    # p_denomenator = precompute_delta_sig_sum(sess.run(x), tsne_all_sigmas, use_vlr)
    # print(p_denomenator)

    # tsne_all_sigmas = find_sigmas(x_loc_full, target_perplexity)

    # x_loc = tf.placeholder(tf.float32, (None, n), name="x_loc")
    # x_scale = tf.placeholder(tf.float32, (None, n), name="x_scale")
    # tsne_sigmas = tf.placeholder(
    #     tf.float32, shape=(B,), name="tsne_sigmas_batch")

    # tsne_sigmas = tf.placeholder(
    #     tf.float32, shape=(None,), name="tsne_sigmas_batch")
    # full_data[tsne_sigmas] = tsne_all_sigmas
    tsne_sigmas = tf.constant(tsne_all_sigmas, name="tsne_sigmas")

    # using point estimates
    # x = tf.placeholder(tf.float32, shape=(None, n), name="x")
    # full_data[x] = x0

    # x = tf.log(
    #     rnaseq_approx_likelihood_sampler_from_vars(None, n, vars))

    tf.summary.histogram("x", x)

    # feed-forward network mapping onto low-dimensional latent space
    if use_neural_network:
        activation=tf.nn.leaky_relu

        lyr1 = tf.layers.dense(
            x, 500,
            activation=activation,
            # bias_initializer=tf.zeros_initializer(),
            kernel_initializer=tf.random_normal_initializer(0.0, 0.01))

        lyr2 = tf.layers.dense(
            lyr1, 500,
            activation=activation,
            # bias_initializer=tf.zeros_initializer())
            kernel_initializer=tf.random_normal_initializer(0.0, 0.01))

        # lyr3 = tf.layers.dense(
        #     lyr1, 2000,
        #     activation=activation,
        #     bias_initializer=tf.zeros_initializer())
        #     # kernel_initializer=tf.random_normal_initializer(0.0, 0.01))

        z = tf.layers.dense(
            lyr2,
            num_pca_components,
            kernel_initializer=tf.random_normal_initializer(0.0, 0.01),
            activation=tf.identity)
    else:
        z = tf.layers.dense(
            x,
            num_pca_components,
            activation=tf.identity,
            bias_initializer=tf.zeros_initializer(),
            kernel_initializer=tf.random_normal_initializer(0.0, 0.0001))

        # directly placing z (doesn't work with minibatch sampling)
        # z = tf.Variable(
        #     tf.random_normal([num_samples, num_pca_components]),
        #     trainable=True,
        #     name="z")

    tf.summary.histogram("z", z)

    # for numerical stability
    eps = 1e-6

    minibatch_slice = tf.placeholder(tf.int32, shape=(B,), name="minibatch_slice")

    p = tsne_p(
        tf.batch_gather(x, minibatch_slice),
        tf.batch_gather(tsne_sigmas, minibatch_slice),
        # tf.gather(p_denomenator, minibatch_slice),
        num_samples,
        use_vlr) # [B, B]
    p += eps
    # p = tf.Print(p, [tf.reduce_min(p), tf.reduce_max(p)], "p", summarize=25)
    # p = tf.Print(p, [tf.reduce_sum(p)], "p", summarize=25)

    q = tsne_q(z, alpha) # [num_samples, num_samples]

    q = tf.gather(
        tf.gather(q, minibatch_slice, axis=0), minibatch_slice, axis=1) # [B, B]
    q += eps
    # q = tf.Print(q, [tf.reduce_min(q), tf.reduce_max(q)], "q", summarize=25)
    # q = tf.Print(q, [tf.reduce_sum(q)], "q", summarize=25)

    tf.summary.histogram("p", p)
    tf.summary.histogram("q", q)
    tf.summary.histogram("log(p/q)", tf.log(p/q))
    tf.summary.histogram("p*log(p/q)", p*tf.log(p/q))

    # KL divergence between p and q
    loss = tf.reduce_sum(p * tf.log(p / q))
    # loss = tf.Print(loss, [tf.log(p/q)], "log(p/q)", summarize=16)
    # loss = tf.Print(loss, [p*tf.log(p/q)], "p*log(p/q)", summarize=16)
    # loss = tf.Print(loss, [loss], "loss")

    tf.summary.scalar("loss", loss)

    optimizer = tf.train.AdamOptimizer(
        learning_rate=1e-6, beta1=0.99, beta2=0.999)
    train = optimizer.minimize(loss)

    # sample_minibatch(feed_dict, full_data, num_samples, B)
    # feed_dict = full_data

    # batch_feed_dict = {}
    # for var in full_data:
    #     batch_feed_dict[var] = None
    # n_iter = 5000
    n_iter = 500
    # n_iter_per_batch = 10
    prog = Progbar(50, n_iter)
    merged_summary = tf.summary.merge_all()
    train_writer = tf.summary.FileWriter("log/" + "run-" + str(np.random.randint(1, 1000000)), sess.graph)
    feed_dict = dict()

    init = tf.global_variables_initializer()
    sess.run(init, feed_dict=full_data)

    k = 0
    for iter in range(n_iter):
        idxs = np.array([i for i in range(num_samples)])
        np.random.shuffle(idxs)
        minibatch_idxs = idxs[0:B]
        feed_dict[minibatch_slice] = minibatch_idxs

        _, loss_value = sess.run([train, loss], feed_dict=feed_dict)
        prog.update(iter, loss=loss_value)
        train_writer.add_summary(
            sess.run(merged_summary, feed_dict=feed_dict), iter)


    # ------------------------

    # k = 0
    # for iter in range(n_iter):
    #     # sample_minibatch(feed_dict, full_data, num_samples, B)
    #     for subiter in range(n_iter_per_batch):
    #         # sample_minibatch_row(feed_dict, full_data, num_samples, B)
    #         _, loss_value = sess.run([train, loss], feed_dict=feed_dict)
    #         prog.update(k+1, loss=loss_value)
    #         train_writer.add_summary(
    #             sess.run(merged_summary, feed_dict=feed_dict), k)
    #         k += 1

    # sample z and average
    z_estimate = np.zeros((num_samples, num_pca_components))
    n_est_iterations = 10
    for iter in range(n_est_iterations):
        z_estimate += sess.run(z, feed_dict=feed_dict) / n_est_iterations

    # return z_estimate, sess.run(p, feed_dict=full_data), sess.run(q, feed_dict=full_data)
    return z_estimate