Minimal projection

import numpy as np
from jnlr.reconcile import make_solver

# generate gaussian samples in 3d
# and reproject them onto the constraint
n_samples = 1000
X = np.random.randn(n_samples, 3)

# define a constraint as an implicit function.
# Each component of the function (1 in this case) returns
# how far a point is from the surface of the constraint
def f_implicit(v):
    z1, z2, z3 = v
    return z1**2 + z2**2 - z3**2

solver = make_solver(f_implicit, n_iterations=30)
X_proj = solver(X)
incoherence_pre = np.mean(np.abs([f_implicit(x_i) for x_i in X]))
incoherence_post = np.mean(np.abs([f_implicit(x_i) for x_i in X_proj]))
print("mean abs f before projection: {:0.2e}".format(incoherence_pre))
print("mean abs f after projection: {:0.2e}".format(incoherence_post))
mean abs f before projection: 1.84e+00
mean abs f after projection: 1.91e-09
# scatter the original and projected points using plotly, no pandas needed
import plotly.express as px
import plotly.graph_objects as go

fig = go.Figure()
fig.add_trace(go.Scatter3d(x=X[:,0], y=X[:,1], z=X[:,2], mode='markers', name='Original', marker=dict(color='blue', size=1)))
fig.add_trace(go.Scatter3d(x=X_proj[:,0], y=X_proj[:,1], z=X_proj[:,2], mode='markers', name='Projected', marker=dict(color='red', size=2)))
fig.update_layout(scene=dict(xaxis_title='z1', yaxis_title='z2', zaxis_title='z3'))
# no background, WHITE BACKGROUND LOOKS BETTER
fig.update_layout(scene=dict(xaxis=dict(showbackground=False), yaxis=dict(showbackground=False), zaxis=dict(showbackground=False)))
fig.update_layout(
    scene=dict(
        camera=dict(eye=dict(x=1.2, y=1.2, z=1.6)),  # Adjust these to "zoom" in
    ),
    margin=dict(l=50, r=50, b=50, t=50)
)
fig.update_layout(
                  #legend=dict(itemsizing="constant"),
                  width=500, height=500)
fig.show()