```python
import local_models.local_models
import local_models.algorithms
import local_models.utils
import local_models.linear_projections
import local_models.loggin
import local_models.TLS_models
import numpy as np
import matplotlib.pyplot as plt
import sklearn.linear_model
import sklearn.cluster
from importlib import reload
from ml_battery.utils import cmap
import matplotlib as mpl
import sklearn.datasets
import sklearn.decomposition
import logging
import ml_battery.log
import time
import os
import mayavi
import mayavi.mlab
import string
import subprocess
import functools
import cv2
import itertools

#on headless systems, tmux: "Xvfb :1 -screen 0 1280x1024x24 -auth localhost", then "export DISPLAY=:1" in the jupyter tmux
mayavi.mlab.options.offscreen = True

logger = logging.getLogger(__name__)

#reload(local_models.local_models)
#reload(lm)
#reload(local_models.loggin)
#reload(local_models.TLS_models)
np.warnings.filterwarnings('ignore')

```

    /usr/local/lib/python3.5/dist-packages/tensorboard/compat/tensorflow_stub/dtypes.py:541: FutureWarning: Passing (type, 1) or '1type' as a synonym of type is deprecated; in a future version of numpy, it will be understood as (type, (1,)) / '(1,)type'.
      _np_qint8 = np.dtype([("qint8", np.int8, 1)])
    /usr/local/lib/python3.5/dist-packages/tensorboard/compat/tensorflow_stub/dtypes.py:542: FutureWarning: Passing (type, 1) or '1type' as a synonym of type is deprecated; in a future version of numpy, it will be understood as (type, (1,)) / '(1,)type'.
      _np_quint8 = np.dtype([("quint8", np.uint8, 1)])
    /usr/local/lib/python3.5/dist-packages/tensorboard/compat/tensorflow_stub/dtypes.py:543: FutureWarning: Passing (type, 1) or '1type' as a synonym of type is deprecated; in a future version of numpy, it will be understood as (type, (1,)) / '(1,)type'.
      _np_qint16 = np.dtype([("qint16", np.int16, 1)])
    /usr/local/lib/python3.5/dist-packages/tensorboard/compat/tensorflow_stub/dtypes.py:544: FutureWarning: Passing (type, 1) or '1type' as a synonym of type is deprecated; in a future version of numpy, it will be understood as (type, (1,)) / '(1,)type'.
      _np_quint16 = np.dtype([("quint16", np.uint16, 1)])
    /usr/local/lib/python3.5/dist-packages/tensorboard/compat/tensorflow_stub/dtypes.py:545: FutureWarning: Passing (type, 1) or '1type' as a synonym of type is deprecated; in a future version of numpy, it will be understood as (type, (1,)) / '(1,)type'.
      _np_qint32 = np.dtype([("qint32", np.int32, 1)])
    /usr/local/lib/python3.5/dist-packages/tensorboard/compat/tensorflow_stub/dtypes.py:550: FutureWarning: Passing (type, 1) or '1type' as a synonym of type is deprecated; in a future version of numpy, it will be understood as (type, (1,)) / '(1,)type'.
      np_resource = np.dtype([("resource", np.ubyte, 1)])

```python
def import_shit():
    import local_models.local_models
    import local_models.algorithms
    import local_models.utils
    import local_models.linear_projections
    import local_models.loggin
    import local_models.TLS_models
    import numpy as np
    import logging
    import string
    import ml_battery.log

    logger = logging.getLogger(__name__)

    #reload(local_models.local_models)
    #reload(lm)
    #reload(local_models.loggin)
    #reload(local_models.TLS_models)
    np.warnings.filterwarnings('ignore')
    return logger

def mean_center(data, weights=None):
    return data - np.average(data, axis=0,weights=weights)

def load_converged_data(pth):
    convergededs = []
    for dat in sorted(os.listdir(pth)):
        convergededs.append(np.loadtxt(os.path.join(pth, dat)))
    return np.concatenate(convergededs, axis=0)

def plt_grid(fig, grid, data_avg, data_std):
    nodes = mayavi.mlab.points3d(grid[:,0], grid[:,1], grid[:,2],
                                 scale_mode='scalar', scale_factor=1,
                                 colormap='gist_earth', figure=fig)
    nodes.glyph.scale_mode = 'scale_by_vector'
    nodes.mlab_source.dataset.point_data.vectors = np.ones((grid.shape[0],3))*(np.average(data_std)/60)
    nodes.mlab_source.dataset.point_data.scalars = (grid[:,1] - (data_avg[1]-3*data_std[1]))/(6*data_std[1])
    return nodes

def plt_data(fig, data, data_std):
    nodes = mayavi.mlab.points3d(data[:,0], data[:,1], data[:,2],
                                 scale_mode='scalar', scale_factor=1,
                                 colormap='Greens', figure=fig)
    nodes.glyph.scale_mode = 'scale_by_vector'
    nodes.mlab_source.dataset.point_data.vectors = np.ones((data.shape[0],3))*(np.average(data_std)/60)
    nodes.mlab_source.dataset.point_data.scalars = np.ones((data.shape[0]))
    return nodes

def get_normals(kernel, linear_models, data):
    if hasattr(kernel.bandwidth, "__call__"):
        linear_params_vecs, linear_params_mean = local_models.linear_projections.transformate_data(data, kernel, linear_models, k=kernel.k)
    else:
        linear_params_vecs, linear_params_mean = local_models.linear_projections.transformate_data(data, kernel, linear_models, r=kernel.support_radius())
    return linear_params_vecs

def align_normals(data, normals, k=10, iterations=100):
    balltree = sklearn.neighbors.BallTree(data)
    pairwise_nearest_indices = balltree.query(data,k=k,sort_results=True,return_distance=False)
    for iteration in range(iterations):
        alignments = []
        for index in range(1,pairwise_nearest_indices.shape[1]):
            alignment = np.einsum("ij,ij->i",normals,normals[pairwise_nearest_indices[:,index]])
            alignments.append(alignment)
        alignment = np.average(alignments, axis=0)
        wrong_alignment = np.sign(alignment)
        normals = normals*wrong_alignment.reshape(-1,1)
    return normals

def align_edge_normals(data, normals, edge_range=0.1):
    data_mins, data_maxes, data_ranges = local_models.linear_projections.min_max_range(data)
    graph_bounds = local_models.linear_projections.sane_graph_bounds(data_mins, data_maxes, data_ranges, -edge_range)
    mins = data < graph_bounds[:1]
    maxes = data > graph_bounds[1:]
    mins_alignment = np.sign(np.einsum("ij,ij->i",mins,-1*normals))
    maxes_alignment = np.sign(np.einsum("ij,ij->i",maxes,normals))
    mins_alignment += np.logical_not(mins_alignment) # turn 0s into 1s (so they don't change)
    maxes_alignment += np.logical_not(maxes_alignment)
    return normals*mins_alignment.reshape(-1,1)*maxes_alignment.reshape(-1,1)

def plt_normals(fig, normals, data, data_std):
    nodes = mayavi.mlab.quiver3d(data[:,0], data[:,1], data[:,2],
                                 normals[:,0], normals[:,1], normals[:,2],
                                 scale_mode='scalar', scale_factor=np.average(data_std)/5,
                                 colormap='Purples', figure=fig, line_width=1.0)
    return nodes

def normalize_view(fig, data_avg, data_std, azimuth=0, elevation=0):
    mayavi.mlab.view(azimuth=azimuth, elevation=elevation, distance=15*np.average(data_std), focalpoint=(data_avg[0], data_avg[1], data_avg[2]))

def plt_and_save(data, grid, normals, pth):
    data_avg = np.average(data, axis=0)
    data_std = np.std(data, axis=0)
    figure = mayavi.mlab.figure(figure=None, bgcolor=(1,1,1), fgcolor=(0,0,0), engine=None, size=(1000, 500))
    data_nodes = plt_data(figure, data, data_std)
    converged_nodes = plt_grid(figure, grid, data_avg, data_std)
    normal_vecs = plt_normals(figure, normals, grid, data_std)
    neg_normal_vecs = plt_normals(figure, -normals, grid, data_std)
    normalize_view(figure, data_avg, data_std)
    mayavi.mlab.savefig(pth, magnification=2)
    mayavi.mlab.close(figure)

def serialize_plt(pth):
    import zlib
    with open(pth, 'rb') as f:
        dat = f.read()
    return zlib.compress(dat)

def deserialize_plt(dat, pth):
    import zlib
    with open(pth, 'wb') as f:
        f.write(zlib.decompress(dat))
    return pth

def distributed_plt_and_save(data, grid, bandwidth):
    import numpy as np
    import mayavi
    import mayavi.mlab
    import string
    import os
    #on headless systems, tmux: "Xvfb :1 -screen 0 1280x1024x24 -auth localhost", then "export DISPLAY=:1" in the jupyter tmux
    mayavi.mlab.options.offscreen = True

    unique_id = "".join(np.random.choice(list(string.ascii_lowercase), replace=True, size=20))
    pth = "/ramfs/{}.png".format(unique_id)
    try:
        plt_and_save(data, grid, bandwidth, pth)
        result = serialize_plt(pth)
    except:
        os.remove(pth)
```

```python
FRESH=True
```

```python
kernel_names = {
    local_models.local_models.GaussianKernel: 'gaussian',
    local_models.local_models.TriCubeKernel: 'tricube'
}
```

```python
mpl.rcParams['figure.figsize'] = [8.0, 8.0]
```

```python
data_file = "/home/brown/Downloads/subject001/3d/andreadm2.stl"
```

```python
import stl

tri_mesh = stl.mesh.Mesh.from_file(data_file).points.reshape(-1,3)
```

```python
tri_mesh.shape
```

    (346758, 3)

```python
data, cts = np.unique(tri_mesh, axis=0, return_counts=True)
```

```python
data.shape
```

    (58659, 3)

```python
(data.shape[0]*100)**(1/3)
```

    180.348091843

```python
np.mean(data, axis=0)
```

    array([ -71.71989 , -340.46124 ,   31.036196], dtype=float32)

```python

```

```python
KERNEL=local_models.local_models.TriCubeKernel
RUN = 1

project_dir = "../data/quadrics_{}_{:03d}".format(kernel_names[KERNEL], RUN)

os.makedirs(project_dir, exist_ok=1)
```

```python

```

```python
A = np.array([[0,0,0,1]]).T
ORIGIN = np.array([[0,0,0]])
ONE = np.array([[1]])

import quadrics_utils

def diagonalize_quadric_and_translate(Q,X):
    Q_s = Q[:-1,:-1]
    _, U = np.linalg.eig(Q_s)
    UTQs = U.T@Q_s
    UL = np.diag(UTQs@U)
    #UR = UTQs@X.T + U.T@Q[:-1,-1:]
    UR = X@UTQs.T + Q[-1:,:-1]@U
    BR = np.einsum('kj,kj->k', X@Q_s, X).reshape(-1,1) + 2*X@Q[:-1,-1:] + Q[-1,-1]
    return (U, UL, UR, BR)

def array_roots(coeffses):
    ret = np.zeros((coeffses.shape[0], coeffses.shape[1]-1), dtype=complex)
    for i, coeffs in enumerate(coeffses):
        nproots = np.roots(coeffs.flatten())
        ret[i] = nproots
    return ret

projection_funcs = {
    "k_mat": quadrics_utils.k_mat,
    "other_dims_as_x": lambda a,b,c,d,e,f,g,x: [quadrics_utils.y_as_x(a,b,c,d,e,f,g,x), quadrics_utils.z_as_x(a,b,c,d,e,f,g,x)],
}
def orthogonal_quadric_project_x(X,Q,projection_funcs):
    U, ul, ur, br = diagonalize_quadric_and_translate(Q,X)
    #put all of the dimensions right for math stuff
    ul = np.tile(ul.reshape(1,-1), (X.shape[0], 1))
    args = (
        list(ul.T[...,np.newaxis]) +
        [br] +
        list(ur.T[...,np.newaxis])
    )

    coeffses = projection_funcs["k_mat"](*args)
    coeffses = np.stack(coeffses, axis=1)

    x = np.ma.array(array_roots(coeffses))
    return U,args,x

def orthogonal_quadric_projection(X,Q,projection_funcs):
    U, args, x = orthogonal_quadric_project_x(X,Q,projection_funcs)

    candidates = x.imag == 0
    x.mask = ~candidates
    other_dims = projection_funcs["other_dims_as_x"](*(args + [x]))

    pts = np.ma.stack([x] + other_dims, axis=1)
    dists = np.sum(pts**2, axis=1)
    mindists = np.argmin(dists, axis=1)
    X_indices = np.arange(X.shape[0])
    best_pts = np.array(pts[X_indices,:,mindists], dtype=float)
    best_dists = np.array(dists[X_indices,mindists],dtype=float)
    return unrotate_and_translate(U, X, best_pts), best_dists

def unrotate_and_translate(U, orig_X, new_X):
    return new_X@U.T + orig_X
    #return np.einsum('ji,ki->kj',U,orig_X) + new_X

```

```python

```

```python

```

```python
def test_diagonalize_quadratic_and_translate():
    q = np.array([ # an ellipse
        [0.5,0,0,0],
        [0,0.4,0,0],
        [0,0,0.3,0],
        [0,0,0,-1],
    ])
    x = np.random.uniform(size=12).reshape(4,3)
    #x = np.ones(3).reshape(1,-1)*0.01
    #x[0,2] = 1

    _, ul, ur, br = diagonalize_quadric_and_translate(q,x)
    print(ul.shape,ur.shape,br.shape)

    q1 = np.block([[np.diag(ul), ur[:1].T],[ur[:1], br[:1]]])

    _,U = np.linalg.eig(q[:3,:3])
    E = np.block([[U, x[:1].T],[ORIGIN, 1]])
    q2 = E.T@q@E

    assert np.allclose(q1,q2)
    return x,ul,ur,br
x,ul,ur,br = test_diagonalize_quadratic_and_translate()
```

    (3,) (4, 3) (4, 1)

```python
x
```

    array([[0.0619404 , 0.70002244, 0.37283088],
           [0.72876962, 0.26031804, 0.79020342],
           [0.10712523, 0.10682334, 0.01939804],
           [0.70480969, 0.26807666, 0.73614336]])

```python
ur.shape, br.shape, ul.shape
```

    ((4, 3), (4, 1), (3,))

```python
#q = np.random.uniform(size=(4,4))
#q += q.T
q = np.array([ # an ellipse
    [0.5,0,0,0],
    [0,0.25,0,0],
    [0,0,0.125,0],
    [0,0,0,-1],
])
q = np.array([ # an elliptic paraboloid
    [0.5,0,0,0],
    [0,0.25,0,0],
    [0,0,0,-1],
    [0,0,-1,0.1],
])
init_q = q
u = np.random.uniform(size=(3,3))
u += u.T
_, u = np.linalg.eig(u)
print(u)
print(np.linalg.norm(u, axis=0))
E = np.block([[u, np.zeros((3,1))],[np.zeros(3), 1]])
q = E.T@q@E
print(q)
x = np.random.uniform(size=3).reshape(1,-1)
x = np.ones(3).reshape(1,-1)*0.01
x[0,2] = 1

q
```

    [[-0.67767991 -0.53064411 -0.50908424]
     [-0.66897615  0.73232087  0.12718904]
     [-0.3053209  -0.42675867  0.8512674 ]]
    [1. 1. 1.]
    [[ 0.3415073   0.05732713  0.15122647  0.3053209 ]
     [ 0.05732713  0.27486505  0.15835707  0.42675867]
     [ 0.15122647  0.15835707  0.13362765 -0.8512674 ]
     [ 0.3053209   0.42675867 -0.8512674   0.1       ]]

    array([[ 0.3415073 ,  0.05732713,  0.15122647,  0.3053209 ],
           [ 0.05732713,  0.27486505,  0.15835707,  0.42675867],
           [ 0.15122647,  0.15835707,  0.13362765, -0.8512674 ],
           [ 0.3053209 ,  0.42675867, -0.8512674 ,  0.1       ]])

```python

```

```python
q[:3,3:]
```

    array([[ 0.3053209 ],
           [ 0.42675867],
           [-0.8512674 ]])

```python
_, ul, ur, br = diagonalize_quadric_and_translate(q,x)
```

```python
br
```

    array([[-1.44800079]])

```python
x
```

    array([[0.01, 0.01, 1.  ]])

```python
Q1 = np.block([[np.diag(ul), ur.T],[ur, br]])
Q1
```

    array([[ 5.00000000e-01,  0.00000000e+00,  0.00000000e+00,
            -2.60583741e-01],
           [ 0.00000000e+00,  2.50000000e-01,  0.00000000e+00,
             3.19556216e-02],
           [ 0.00000000e+00,  0.00000000e+00,  3.30812673e-18,
            -1.00000000e+00],
           [-2.60583741e-01,  3.19556216e-02, -1.00000000e+00,
            -1.44800079e+00]])

```python
_,U = np.linalg.eig(q[:3,:3])
```

```python
E = np.block([[U, x.T],[ORIGIN, 1]])
E
```

    array([[-0.67767991, -0.66897615, -0.3053209 ,  0.01      ],
           [-0.53064411,  0.73232087, -0.42675867,  0.01      ],
           [-0.50908424,  0.12718904,  0.8512674 ,  1.        ],
           [ 0.        ,  0.        ,  0.        ,  1.        ]])

```python
q
```

    array([[ 0.3415073 ,  0.05732713,  0.15122647,  0.3053209 ],
           [ 0.05732713,  0.27486505,  0.15835707,  0.42675867],
           [ 0.15122647,  0.15835707,  0.13362765, -0.8512674 ],
           [ 0.3053209 ,  0.42675867, -0.8512674 ,  0.1       ]])

```python
np.round(E.T@q@E,3)
```

    array([[ 0.5  ,  0.   , -0.   , -0.261],
           [ 0.   ,  0.25 ,  0.   ,  0.032],
           [-0.   ,  0.   , -0.   , -1.   ],
           [-0.261,  0.032, -1.   , -1.448]])

```python
E
```

    array([[-0.67767991, -0.66897615, -0.3053209 ,  0.01      ],
           [-0.53064411,  0.73232087, -0.42675867,  0.01      ],
           [-0.50908424,  0.12718904,  0.8512674 ,  1.        ],
           [ 0.        ,  0.        ,  0.        ,  1.        ]])

```python
pts, dists = orthogonal_quadric_projection(x,q, projection_funcs)
pts2, dists2 = orthogonal_quadric_projection(x,q, projection_funcs)
print(pts2, dists2)
```

    [[0.35759051 0.43371826 0.58121769]] [0.47573495]

```python
pts, dists
```

    (array([[0.35759051, 0.43371826, 0.58121769]]), array([0.47573495]))

```python
pts2, dists2
```

    (array([[0.35759051, 0.43371826, 0.58121769]]), array([0.47573495]))

```python
dists
```

    array([0.47573495])

```python
pts.shape
```

    (1, 3)

```python
pt1 = np.ones((1,4))
pt1[:,:3] = pts
pt1
```

    array([[0.35759051, 0.43371826, 0.58121769, 1.        ]])

```python
pt1@q@pt1.T
```

    array([[-6.10622664e-16]])

```python

```

```python

```

```python

```

```python

```

```python

```

```python

```

```python
x.shape, q.shape
```

    ((1, 3), (4, 4))

```python
x = (np.random.uniform(size=(3000,3))-0.5)*10
```

```python
pts, dists = orthogonal_quadric_projection(x,q, projection_funcs)
```

```python
#pts, dists = orthogonal_quadric_projection2(x,q, projection_funcs)
```

```python
check_pts, check_dists = orthogonal_quadric_projection(np.array(pts),q, projection_funcs)
```

```python
check_dists
```

    array([3.34677074e-27, 3.48152994e-28, 3.80576124e-29, ...,
           8.81175093e-26, 2.18590455e-26, 1.62574557e-32])

```python
print(np.max(check_dists))
```

    30.477722498455293

```python
print(pts.shape, dists.shape)
```

    (3000, 3) (3000,)

```python
def imshow(pth, cv2color=cv2.IMREAD_COLOR, **kwargs):
    img = cv2.imread(pth, cv2color)
    img = trim_whiteborder(img)
    plt.imshow(img, **kwargs)
    plt.axis("off")
```

```python
pth = os.path.join(project_dir, "quadric_test.png")

data_avg = np.average(pts, axis=0)
data_std = np.std(pts, axis=0)
figure = mayavi.mlab.figure(figure=None, bgcolor=(1,1,1), fgcolor=(0,0,0), engine=None, size=(1000, 1000))
#data_nodes = plt_data(figure, x[np.random.choice(np.arange(x.shape[0]), size=40, replace=False)], data_std)
vecs = pts-x
#mayavi.mlab.quiver3d(x[:,0], x[:,1], x[:,2], vecs[:,0], vecs[:,1], vecs[:,2])
quadric_nodes = plt_grid(figure, pts, data_avg, data_std)
#ellipse_axes = plt_data(figure, np.diag(np.sqrt(1/np.diag(init_q[:3,:3])))@u, data_std*10)

normalize_view(figure, data_avg, data_std)
mayavi.mlab.savefig(pth, magnification=2)
mayavi.mlab.close(figure)
```

```python
def trim_whiteborder(img, ratio=1.1):
    img = cv2.bitwise_not(img)
    print(img.shape)
    im_sq = img**2
    sum_sq_cols = np.sum(im_sq, axis=0)
    sum_sq_rows = np.sum(im_sq, axis=1)
    if len(img.shape) > 2:
        sum_sq_cols = np.sum(sum_sq_cols, axis=1)
        sum_sq_rows = np.sum(sum_sq_rows, axis=1)
    print(sum_sq_cols.shape, sum_sq_rows.shape)
    first_nonzero_col = (sum_sq_cols!=0).argmax()
    last_nonzero_col = sum_sq_cols.shape[0] - (sum_sq_cols[::-1]!=0).argmax()
    first_nonzero_row = (sum_sq_rows!=0).argmax()
    last_nonzero_row = sum_sq_rows.shape[0] - (sum_sq_rows[::-1]!=0).argmax()
    print(first_nonzero_col, last_nonzero_col, first_nonzero_row, last_nonzero_row)
    totally_trimmed = img[first_nonzero_row:last_nonzero_row, first_nonzero_col:last_nonzero_col]
    if len(img.shape) == 2:
        just_a_little_whitespace = np.zeros((
            int(totally_trimmed.shape[0]*ratio),
            int(totally_trimmed.shape[1]*ratio)
        ), dtype=np.uint8)
    else:
        just_a_little_whitespace = np.zeros((
            int(totally_trimmed.shape[0]*ratio),
            int(totally_trimmed.shape[1]*ratio),
            img.shape[2]
        ), dtype=np.uint8)
    jalw_middle_section_start = ((np.array(just_a_little_whitespace.shape) - np.array(totally_trimmed.shape))/2).astype(int)
    just_a_little_whitespace[
        jalw_middle_section_start[0]:jalw_middle_section_start[0] + totally_trimmed.shape[0],
        jalw_middle_section_start[1]:jalw_middle_section_start[1] + totally_trimmed.shape[1]
    ] = totally_trimmed
    #return just_a_little_whitespace
    return cv2.bitwise_not(just_a_little_whitespace)

```

```python

```

```python

```

```python
blah = imshow(pth)
```

```python
isinstance(5., (float,int))
```

    True

```python
def jac_orthogonal_quadric_loss(X,Q):
    U, ul, ur, br = diagonalize_quadric_and_translate(Q,X)
    a,b,c = ul.reshape(-1,1,1)
    e,f,g = ur
    d = br
    e,f,g,d = list(map(lambda x: x.reshape(-1, 1), [e,f,g,d]))
    coeffses = quadrics_utils.k_mat(a,b,c,d,e,f,g)
    kjac = quadrics_utils.k_jac(a,b,c,d,e,f,g)

    full_kjac = np.zeros((7,7,x.shape[0]))
    for i,row in enumerate(kjac):
        for j,el in enumerate(b):
            full_kjac[i,j] = el.flatten()

    # the first coefficient doesn't depend on x, so it needs tiling.
    coeffses[0] = np.repeat(coeffses[0], X.shape[0], axis=0)
    print([[0 if isinstance(x,(int,float)) else x.shape for x in y] for y in kjac])
    coeffses = np.stack(coeffses, axis=1)
    x = array_roots(coeffses)
    candidates = np.isclose(quadrics_utils.Q_as_x(a,b,c,d,e,f,g,x),0)
    x.mask = ~candidates
    y = quadrics_utils.y_as_x(a,b,c,d,e,f,g,x)
    z = quadrics_utils.z_as_x(a,b,c,d,e,f,g,x)
    pts = np.ma.stack((x,y,z), axis=1)
    dists = np.sum((X.reshape(*(list(X.shape) + [1])) - pts)**2, axis=1)
    mindists = np.argmin(dists, axis=1)
    X_indices = np.arange(X.shape[0])
    chosen_pts = pts[X_indices,:,mindists]
    chosen_dists = dists[X_indices,mindists]

    return unrotate_and_translate(U, X, pts[X_indices,:,mindists]), dists[X_indices,mindists]
```

```python

```

```python

```

```python
def weighted_avg_and_std(values, weights=None, axis=None):
    """
    Return the weighted average and standard deviation.

    values, weights -- Numpy ndarrays with the same shape.
    """
    if weights is None:
        return np.average(values, axis=axis), np.std(values, axis=axis)
    average = np.average(values, weights=weights, axis=axis)
    # Fast and numerically precise:
    variance = np.average((values-average)**2, weights=weights, axis=axis)
    return (average, np.sqrt(variance))
```

```python
import scipy.optimize
import sklearn.utils
import sklearn.base

class QuadricModel(sklearn.base.RegressorMixin):
    def __init__(self):
        pass
    def get_params(self, *args, **kwargs):
        return dict()
    def set_params(self, params, *args, **kwargs):
        pass

    def _optimizer_logger(self, xk, *args):
        logging.info("quadric modeling: it{}, xk{}".format(self.i, xk))
        self.i += 1

    def fit(self, X, y=None, sample_weight=None, beta0=None):
        logging.info("fitting quadric model")
        X = sklearn.utils.check_array(X)
        if beta0 is None:
            #this allows us to orient the data to set an initial set of parameters
            global_linear_model = local_models.TLS_models.LinearODR_mD(2)
            global_linear_model.fit(X, sample_weight=sample_weight)
            global_linear_vecs = global_linear_model.cov_eigenvectors[global_linear_model.cov_eigenvalues_sorter]
            global_linear_mean = global_linear_model.intercept_

            transformed_X = (X-global_linear_mean)@global_linear_vecs.T

            transformed_q = np.zeros((4,4))

            # an elliptic paraboloid pointing in the smallest eigenvector direction
            '''
            transformed_q[0,0] = 1/weighted_avg_and_std(transformed_X[0], weights=None)[1]**2 #flat-ish in x
            transformed_q[1,1] = 1/weighted_avg_and_std(transformed_X[1], weights=None)[1]**2 #flat-ish in y
            transformed_q[2,3] = transformed_q[3,2] = -1 # elliptic paraboloid pointing in z direction
            '''

            # an ellipse with axes in the various directions
            transformed_q[np.diag_indices(3)] = 1/global_linear_model.cov_eigenvalues[global_linear_model.cov_eigenvalues_sorter]**2
            transformed_q[3,3] = -1
            #print(np.diag(transformed_q), weighted_avg_and_std(transformed_X, weights = sample_weight, axis=0)[1])

            #print(transformed_q)
            #print(global_linear_mean)
            E = np.block([[global_linear_vecs, global_linear_mean.reshape(-1,1)],[np.zeros(3),1]])
            Einv = np.linalg.inv(E)
            q = Einv.T@transformed_q@Einv
            print(q)
            beta0 = q[np.triu_indices(4)]

        beta0 /= beta0[-1]

        self.i = 0
        self.solution = scipy.optimize.minimize(self._loss, beta0[:-1], args=(X, sample_weight), callback=self._optimizer_logger)#, method = 'Nelder-Mead')
        self.coef_ = self.solution.x
        self.intercept_ = np.array([])
        return self

    def _loss(self, beta, X, sample_weight=None):
        q = np.zeros((4,4))
        beta = np.concatenate((beta, [1]))
        q[np.triu_indices(4)] = beta
        q[3,3] = 1
        q += q.T
        q[np.diag_indices(4)] /= 2
        pts, dists = orthogonal_quadric_projection(X,q,projection_funcs)

        #pts, jac, dists = orthogonal_quadric_projection_with_jacobian(X,q,projection_funcs)

        if sample_weight is not None:
            dists *= sample_weight
        lost = np.sum(dists)
        #total_jac = np.einsum('ki,kij->ij', (X - pts), jac)
        #if lost > 20:
        #    print(q)
        #print(lost)
        return lost#, total_jac
```

```python

```

```python
qm = QuadricModel()
qm.fit(pts)#, beta0=q[np.triu_indices(4)]+0.001)
```

    [[ 0.27681249  0.04017082  0.17097849 -0.4425093 ]
     [ 0.04017082  0.06775505  0.04492783 -0.03859162]
     [ 0.17097849  0.04492783  0.17887278 -0.3294389 ]
     [-0.4425093  -0.03859162 -0.3294389  -0.21978061]]

```python
qm.solution
```

          fun: 1.6549110924078896
     hess_inv: array([[ 4.62085809e+10,  4.95199660e+10, -2.89563950e+10,
            -1.60618353e+11,  1.01727748e+11,  2.12873965e+10,
             1.08951819e+11,  3.88933132e+10, -1.45872819e+11],
           [ 4.95199654e+10,  5.30686500e+10, -3.10314590e+10,
            -1.72128534e+11,  1.09017729e+11,  2.28128871e+10,
             1.16759490e+11,  4.16804734e+10, -1.56326310e+11],
           [-2.89563947e+10, -3.10314590e+10,  1.81453918e+10,
             1.00650752e+11, -6.37472252e+10, -1.33396491e+10,
            -6.82741564e+10, -2.43723158e+10,  9.14105312e+10],
           [-1.60618352e+11, -1.72128535e+11,  1.00650753e+11,
             5.58300095e+11, -3.53599761e+11, -7.39937577e+10,
            -3.78710215e+11, -1.35190905e+11,  5.07045473e+11],
           [ 1.01727745e+11,  1.09017728e+11, -6.37472246e+10,
            -3.53599757e+11,  2.23952656e+11,  4.68639983e+10,
             2.39856380e+11,  8.56232542e+10, -3.21137605e+11],
           [ 2.12873960e+10,  2.28128869e+10, -1.33396490e+10,
            -7.39937567e+10,  4.68639982e+10,  9.80669026e+09,
             5.01919877e+10,  1.79173942e+10, -6.72007755e+10],
           [ 1.08951816e+11,  1.16759488e+11, -6.82741554e+10,
            -3.78710208e+11,  2.39856379e+11,  5.01919875e+10,
             2.56889486e+11,  9.17036843e+10, -3.43942797e+11],
           [ 3.88933130e+10,  4.16804736e+10, -2.43723160e+10,
            -1.35190905e+11,  8.56232555e+10,  1.79173945e+10,
             9.17036861e+10,  3.27361233e+10, -1.22779733e+11],
           [-1.45872816e+11, -1.56326309e+11,  9.14105303e+10,
             5.07045466e+11, -3.21137605e+11, -6.72007755e+10,
            -3.43942799e+11, -1.22779731e+11,  4.60496264e+11]])
          jac: array([-0.00018266,  0.0007423 ,  0.00088313, -0.00044671,  0.00014985,
           -0.00081003, -0.00018746,  0.00035554,  0.00045177])
      message: 'Desired error not necessarily achieved due to precision loss.'
         nfev: 1695
          nit: 71
         njev: 153
       status: 2
      success: False
            x: array([-14346.90291295, -15375.35452347,   8990.6931421 ,  49869.12471544,
           -31585.03583959,  -6609.54440227, -33827.87634286, -12075.69418345,
            45290.97807574])

```python
qq = np.zeros((4,4))
qq[np.triu_indices(4)] = np.concatenate((qm.coef_, [1]))
qq += qq.T
qq[np.diag_indices(4)] /= 2
np.round(qq,3)
```

    array([[-1.4346903e+04, -1.5375355e+04,  8.9906930e+03,  4.9869125e+04],
           [-1.5375355e+04, -3.1585036e+04, -6.6095440e+03, -3.3827876e+04],
           [ 8.9906930e+03, -6.6095440e+03, -1.2075694e+04,  4.5290978e+04],
           [ 4.9869125e+04, -3.3827876e+04,  4.5290978e+04,  1.0000000e+00]])

```python
q
```

    array([[ 0.19991037,  0.1817657 , -0.09489533, -0.67572345],
           [ 0.1817657 ,  0.38892396,  0.07487268,  0.43093912],
           [-0.09489533,  0.07487268,  0.16116567, -0.59807132],
           [-0.67572345,  0.43093912, -0.59807132,  0.1       ]])

qq = np.array(
[[ 0.98928691, -0.42390962, -0.20757044, -0.35397327],
 [-0.42390962,  0.24742997,  0.33970501, -0.9004247 ],
 [-0.20757044,  0.33970501,  0.99941526,  0.25388832],
 [-0.35397327, -0.9004247 ,  0.25388832, -0.02455888]]
)qq = np.array(
[[ 0.56025787,  0.64822194,  0.0094628 ,  0.00872895],
 [ 0.64822194,  1.92161331, -0.4789617 ,  0.02687668],
 [ 0.0094628 , -0.4789617 ,  0.47708798, -0.00464847],
 [ 0.00872895,  0.02687668, -0.00464847, -0.99960565]]

 )

```python
q
```

    array([[ 0.19991037,  0.1817657 , -0.09489533, -0.67572345],
           [ 0.1817657 ,  0.38892396,  0.07487268,  0.43093912],
           [-0.09489533,  0.07487268,  0.16116567, -0.59807132],
           [-0.67572345,  0.43093912, -0.59807132,  0.1       ]])

```python
qq.shape
```

    (4, 4)

```python
q.shape
```

    (4, 4)

```python
np.array(pts)
```

    array([[ 0.36155235,  1.23129588,  1.85642789],
           [-0.13218691,  0.97221512,  4.26794374],
           [ 0.7647154 , -6.10832972,  4.31310786],
           ...,
           [ 2.83708582, -4.24921199, -1.22549308],
           [ 2.57444886,  0.44583598, -0.68037878],
           [ 3.07029248,  0.91037167, -0.02536896]])

```python
qfit_pth = os.path.join(project_dir, "qfit_test.png")
qfit_pts, qfit_dists = orthogonal_quadric_projection(x,qq,projection_funcs)

data_avg = np.average(qfit_pts, axis=0)
data_std = np.std(qfit_pts, axis=0)
figure = mayavi.mlab.figure(figure=None, bgcolor=(1,1,1), fgcolor=(0,0,0), engine=None, size=(1000, 1000))
#data_nodes = plt_data(figure, x[np.random.choice(np.arange(x.shape[0]), size=40, replace=False)], data_std)
vecs = qfit_pts-pts
#mayavi.mlab.quiver3d(x[:,0], x[:,1], x[:,2], vecs[:,0], vecs[:,1], vecs[:,2])
quadric_nodes = plt_grid(figure, qfit_pts, data_avg, data_std)
#other_nodes = plt_grid(figure, pts, data_avg, data_std)

normalize_view(figure, data_avg, data_std)
mayavi.mlab.savefig(qfit_pth, magnification=2)
mayavi.mlab.close(figure)

```

```python
imshow(qfit_pth)
```

```python

```

```python
local_quadrics = local_models.local_models.LocalModels(QuadricModel())
local_quadrics.fit(pts)
```

```python
avg_pt_dist = np.average(local_quadrics.index.query(data, k=2)[0][:,1])
```

```python
bandwidth = avg_pt_dist*8
lm_kernel = local_models.local_models.TriCubeKernel(bandwidth=bandwidth)
```

```python
fitted_local_quadrics, local_quadric_params = local_quadrics.transform(data,
    r=lm_kernel.support_radius()-1, weighted=True, kernel=lm_kernel,
    neighbor_beta0s=False, batch_size=int(data.shape[0]/32), return_models=True)
```

```python

```

```python

```

```python

```

```python

```

```python
q
```

    array([[ 0.5,  0. ,  0. ,  0. ],
           [ 0. ,  0.4,  0. ,  0. ],
           [ 0. ,  0. ,  0.3,  0. ],
           [ 0. ,  0. ,  0. , -1. ]])

```python
import importlib; importlib.reload(quadrics_utils)
```

    <module 'quadrics_utils' from '/home/brown/python_packages/local_models/examples/quadrics_utils.py'>

```python
def collect_best(expr, measure=sympy.count_ops):
    best = expr
    best_score = measure(expr)
    perms = itertools.permutations(expr.free_symbols)
    permlen = np.math.factorial(len(expr.free_symbols))
    print(permlen)
    for i, perm in enumerate(perms):
        if (permlen > 1000) and not (i%int(permlen/100)):
            print(i)
        collected = sympy.collect(expr, perm)
        if measure(collected) < best_score:
            best_score = measure(collected)
            best = collected
        else:
            factored = sympy.factor(expr)
            if measure(factored) < best_score:
                best_score = measure(factored)
                best = factored
    return best

def product(args):
    arg = next(args)
    try:
        return arg*product(args)
    except:
        return arg

def rcollect_best(expr, measure=sympy.count_ops):
    best = collect_best(expr, measure)
    best_score = measure(best)
    if expr == best:
        return best
    if isinstance(best, sympy.Mul):
        return product(map(rcollect_best, best.args))
    if isinstance(best, sympy.Add):
        return sum(map(rcollect_best, best.args))
```

```python
def derive_quadratic_orthogonal_projection_1D_polynomial(n):
    import sympy

    Q_sym = sympy.symarray("q", (n+1, n+1))
    Q = sympy.Matrix(np.zeros((n+1,n+1), dtype=int))
    for i, j in itertools.product(range(n+1), range(n+1)):
        if i == n or j == n or i == j:
            Q[i,j] = Q_sym[max(i,j),min(i,j)]
    print(Q)

    x_sym = sympy.symarray("x", n+1)
    X = sympy.Matrix(np.ones((n+1, 1), dtype=int))
    for i in range(n):
        X[i] = x_sym[i]

    P = sympy.Matrix(np.zeros((n-1, n+1), dtype=int))
    for i in range(n-1):
        P[i,0] = X[i+1]
        P[i,i+1] = -X[0]

    QXP = P*Q*X

    other_dims_as_x0 = [sympy.solve(QXP[i], X[i+1])[0] for i in range(n-1)]

    XQX = sympy.expand((X.T*Q*X)[0])
    XQX_as_x0 = XQX.subs({X[i+1]:other_dims_as_x0[i] for i in range(n-1)})
    for sub in other_dims_as_x0:
        XQX_as_x0 *= sympy.fraction(sub)[1]**2
    XQX_as_x0 = sympy.cancel(XQX_as_x0)
    XQX_as_x0 = sympy.simplify(XQX_as_x0)
    XQX_as_x0 = sympy.poly(XQX_as_x0, X[0])

    return (X, Q, XQX_as_x0, other_dims_as_x0)

```

```python
def collectify_polynomial_coefficients(poly):
    return [rcollect_best(formula) for formula in poly.all_coeffs()]
```

```python
sorted(map(lambda x: x**2, range(4)))
```

    [0, 1, 4, 9]

```python

```

```python
import shelve

def funcify(signature, returnable):
    return "def {}: return {}\n".format(signature, returnable)

def get_signature(expr, name):
    return name+str(tuple(sorted(map(str, expr.free_symbols))))

k_mat = sympy.Matrix(collected)
k_jac = k_mat.jacobian([a,b,c,d,e,f,g])

with open("quadrics_formulae.shelf", "w") as quadrics_formulae:
    quadrics_formulae[3] = {
        "k_mat":  sympy.Matrix(collected),
        "k_jac": None,
        "other_dims_as_x": other_dims_as_x0,
}
```

```python
X,Q,poly,other_dims_as_x = derive_quadratic_orthogonal_projection_1D_polynomial(3)
```

    Matrix([[q_0_0, 0, 0, q_3_0], [0, q_1_1, 0, q_3_1], [0, 0, q_2_2, q_3_2], [q_3_0, q_3_1, q_3_2, q_3_3]])

```python
func_x0 = sympy.Function('fx_0')(*sorted(Q.free_symbols, key=str))
func_xn = [sympy.Function('fx_{}'.format(i))(*(sorted(Q.free_symbols, key=str) + [func_x0]))]
```

```python
dx_dvars = []
for var in sorted(Q.free_symbols, key=str):
    dx_dvars.append(
        sympy.solve(sympy.diff(poly.as_expr().subs(X[0], func_x0), var), sympy.diff(func_x0, var))[0]
    )
dotherdims_dvars = []
for i, otherdim in enumerate(X[1:-1]):
    dotherdims_dvars.append([])
    for j, var in enumerate(sorted(Q.free_symbols, key=str)):
        dotherdims_dvars[-1].append(
            sympy.diff(other_dims_as_x[i].subs(X[0], func_x0), var)
        )
```

```python
k_jac = [dx_dvars] + dotherdims_dvars
```

```python
jaco_vars = [sympy.Symbol("dx_0_" + str(var)) for var in sorted(Q.free_symbols, key=str)]
```

```python
k_jac_funcs = (
    [[sympy.lambdify(sorted(Q.free_symbols, key=str), var) for var in sorted(Q.free_symbols, key=str)]] +
    [[sympy.lambdify(sorted(Q.free_symbols, key=str) + [X[0], jaco_vars[j]],
                     dotherdims_dvars[i][j].subs(sympy.diff(func_x0, var), jaco_vars[j]).subs(func_x0, X[0]))
         for j,var in enumerate(sorted(Q.free_symbols, key=str))
     ] for i in range(len(k_jac)-1)]
)
```

```python
i=j=1
dotherdims_dvars[i][j].subs(sympy.diff(func_x0, sorted(Q.free_symbols, key=str)[j]), jaco_vars[j]).subs(func_x0, X[0])
```

$\displaystyle \frac{dx_{0 q 1 1} q_{3 2}}{q_{0 0} x_{0} - q_{2 2} x_{0} + q_{3 0}} + \frac{q_{3 2} x_{0} \left(- dx_{0 q 1 1} q_{0 0} + dx_{0 q 1 1} q_{2 2}\right)}{\left(q_{0 0} x_{0} - q_{2 2} x_{0} + q_{3 0}\right)^{2}}$

```python
jaco_vars
```

    [dx_0_q_0_0,
     dx_0_q_1_1,
     dx_0_q_2_2,
     dx_0_q_3_0,
     dx_0_q_3_1,
     dx_0_q_3_2,
     dx_0_q_3_3]

```python
k_jac_funcs[1][0]
```

    <function _lambdifygenerated(q_0_0, q_1_1, q_2_2, q_3_0, q_3_1, q_3_2, q_3_3, x_0, dx_0_q_0_0)>

```python
def dU_dQ(Q, L, U):
    Qs = Q[:-1,:-1]
    QsLinv = np.stack(
        [np.linalg.pinv(l*np.eye(Qs.shape[0])-Qs, hermitian=True) for l in L],
        axis=2
    )
    dQsU = np.einsum('ij,kl->ijkl', np.ones(Qs.shape), U)
    dUdQs = np.einsum('ijl,kl->ijkl', QsLinv, U)
    ret = np.zeros(list(Q.shape) + list(U.shape))
    ret[:-1,:-1] = dUdQs
    return ret #du_kl/dq_ij in slot i,j,k,l
def dL_dQ(Q, L, U):
    return np.einsum('il,kl->ikl', U, U)
def dETQE_dQ(Q, L, U, X):
    Qs = Q[:-1,:-1]
    dUdQ = dU_dQ(Q,L,U)
    dLdQ = dL_dQ(Q,L,U)
    ret = np.zeros([Q.shape[0]]*4)
    ret[:-1,:-1,np.arange(Q.shape[0]-1), np.arange(Q.shape[0]-1)] = dLdQ
    ret[:-1,-1] = np.einsum('ijkl,jm,bm->bikl',np.transpose(dUdQ,(1,0,2,3)),Qs,X)
```
x = np.random.uniform(size=(3000,3,1))
q = np.array([ # an elliptic paraboloid
    [0.5,0,0,0],
    [0,0.25,0,0],
    [0,0,0,-1],
    [0,0,-1,0.1],
])
init_q = q
u = np.random.uniform(size=(3,3))
u += u.T
_, u = np.linalg.eig(u)
print(u)
print(np.linalg.norm(u, axis=0))
E = np.block([[u, np.ones((3,1))],[np.zeros(3), 1]])
q = E.T@q@E
print(q)

```python
q.dtype, x.dtype
```

    (dtype('float64'), dtype('float64'))

```python
import tensorflow as tf
import tf_roots
import quadrics_utils
projection_funcs = {
    "k_mat": quadrics_utils.k_mat,
    "other_dims_as_x": lambda a,b,c,d,e,f,g,x: [quadrics_utils.y_as_x(a,b,c,d,e,f,g,x), quadrics_utils.z_as_x(a,b,c,d,e,f,g,x)],
}

def tf_rotate_and_translate_quadric(Q,X):
    Qs = Q[:-1,:-1]
    #col_X = tf.expand_dims(X,-1)
    L, U = tf.linalg.eigh(Qs)
    UTQs = tf.matmul(U,Qs,transpose_a=True)
    UL = L
    #UR = broadcasted_matmul(UTQs, col_X) + tf.matmul(U,Q[:-1,-1:],transpose_a=True)
    UR = X@tf.transpose(UTQs,[1,0]) + Q[-1:,:-1]@U
    BR = batch_dot(X@Qs,X) + 2*X@Q[:-1,-1:] + Q[-1,-1]
    return L,U,UL,UR,BR

def tf_ortho_project_x(UL,UR,BR):
    batch_shape = tf.shape(BR)[:-1]
    dims = UL.get_shape().as_list()
    ul_tiled = tf.tile(
        tf.reshape(UL, tf.concat([[1]*tf.rank(batch_shape), dims], axis=0)),
        tf.concat([batch_shape, [1]*len(dims)], axis=0)
    )
    args = (
        [ul_tiled[...,i:i+1] for i in range(UL.shape[-1])] +
        [BR[...,0:1]] +
        [UR[...,i:i+1] for i in range(UR.shape[-1])]
    )

    coeffses = quadrics_utils.k_mat(*args)
    kmat = tf.stack(coeffses, axis=-1)

    roots = tf_roots.tf_roots.tf_roots(kmat[...,:0:-1]/kmat[...,:1])
    return args, roots

def tf_ortho_project_prerotated(UL,UR,BR,X,imag_0tol = 1e-10):
    args, roots = tf_ortho_project_x(UL,UR,BR)
    roots_r = tf.real(roots)
    roots_i = tf.imag(roots)
    mask = tf.logical_or(tf.greater(roots_i, imag_0tol), tf.greater(-imag_0tol, roots_i))
    masked_real_roots = tf.where(mask, roots_r, tf.fill(tf.shape(roots_r), tf.constant(np.inf, roots_r.dtype)))

    other_dims = projection_funcs["other_dims_as_x"](*([tf.expand_dims(arg, -1) for arg in args] + [masked_real_roots]))
    pts = tf.stack([masked_real_roots] + other_dims, axis=-1)
    ortho_pts, ortho_dists = tf_min_dist_search(pts, imag_0tol=imag_0tol)
    return ortho_pts, ortho_dists

def tf_ortho_project_prerotated(UL,UR,BR,X,imag_0tol = 1e-10):
    args, roots = tf_ortho_project_x(UL,UR,BR)
    other_dims = projection_funcs["other_dims_as_x"](*([tf.expand_dims(arg, -1) for arg in args] + [roots]))
    pts = tf.stack([roots] + other_dims, axis=-1)

    ortho_pts, ortho_dists = tf_min_dist_search(pts,imag_0tol=imag_0tol)
    return ortho_pts, ortho_dists

def tf_ortho_project(Q,X,imag_0tol=1e-10):
    if not Q.dtype.is_complex:
        Qc = tf.cast(Q, tf.complex128 if Q.dtype == tf.float64 else tf.complex64)
    else:
        Qc = Q
    if not X.dtype.is_complex:
        Xc = tf.cast(X, tf.complex128 if X.dtype == tf.float64 else tf.complex64)
    else:
        Xc = X
    L,U,UL,UR,BR = tf_rotate_and_translate_quadric(Qc,Xc)
    ortho_pts, ortho_dists = tf_ortho_project_prerotated(UL,UR,BR,Xc,imag_0tol=imag_0tol)
    return tf.real(tf_unrotate_and_translate(U,Xc,ortho_pts)), tf.real(ortho_dists)

def tf_unrotate_and_translate(U, orig_X, new_X):
    return new_X@tf.transpose(U) + orig_X

def tf_min_dist_search(searchable, imag_0tol=1e-10):
    # searches `searchable` for the closest real-valued point to the origin.
    # searchable shape (..., r, n) where n is the dimensionality of X,
    # r is the number of points searched over.  Other dims are treated as batch dims.

    searchable_shape = searchable.get_shape().as_list()
    batch_shape = searchable_shape[:len(searchable_shape)-2]
    n_dims = searchable_shape[-1]
    n_searchable_pts = searchable_shape[-2]

    complex_mask = tf.greater(tf.abs(tf.reduce_sum(tf.imag(searchable), axis=-1)), imag_0tol)
    dists = tf.reduce_sum(searchable**2, axis=-1)

    masked_realpt_dists = tf.where(complex_mask, tf.fill(tf.shape(dists), tf.constant(np.inf, dists.dtype)), dists)
    masked_realpt_dists = tf.real(masked_realpt_dists)

    idx = tf.expand_dims(tf.argmin(masked_realpt_dists, axis=-1), -1) # a (..., 1) shaped array

    return tf.gather_nd(searchable, idx, batch_dims=len(batch_shape))[...,0,:], tf.gather_nd(masked_realpt_dists, idx, batch_dims=len(batch_shape))[...,0]

def broadcasted_matmul(A, B):
    """ Matmul with fast broadcasting

    Args:
      A - [n, m]
      B - [batch_size, m, k]

    Returns:
      AB - [batch_size, n, k]
    """
    # [n, batch_size, k]
    AB = tf.tensordot(A, B, axes=[[1], [1]])
    # [batch_size, n, k]
    return tf.transpose(AB, [1, 0, 2])

def batch_dot(a,b):
    return tf.reduce_sum( tf.multiply( a, b ), 1, keep_dims=True )
```

```python

```

```python
# test min_dist_search
with tf.Graph().as_default(), tf.Session() as sess:
    bar = np.array([
        [[2,3,4],[1,2,50.01]],
        [[4,5,8],[4,5,7]],
        [[100,100,100],[1+1j,1,1]]])[:,np.newaxis,:,:]
    test_roots = tf.placeholder(tf.complex128, bar.shape)
    sol_tf = tf_min_dist_search(test_roots,1e-1)
    res_pts, res_dists = sess.run(sol_tf, feed_dict={test_roots: bar})
    assert np.allclose(res_pts,
        np.array([[2., 3., 4.],
                  [4., 5., 7.],
                  [100,100,100]]))
```

```python
res_pts
```

    array([[  2.+0.j,   3.+0.j,   4.+0.j],
           [  4.+0.j,   5.+0.j,   7.+0.j],
           [100.+0.j, 100.+0.j, 100.+0.j]])

```python
res_dists
```

    array([2.9e+01, 9.0e+01, 3.0e+04])

```python
# test rotate_and_translate_quadric
with tf.Graph().as_default(), tf.Session() as sess:
    q = np.array([ # an elliptic paraboloid
        [0.5,0,0,0],
        [0,0.25,0,0],
        [0,0,0.1,-1],
        [0,0,-1,0.1],
    ])
    u = np.arange(9).reshape(3,3)
    u += u.T
    _, u = np.linalg.eig(u)

    x = np.arange(12).reshape(4,3)*1.

    E = np.block([[u, x[0].reshape(-1,1)],[np.zeros(3), 1]])
    Einv = np.linalg.inv(E)
    q_test = Einv.T@q@Einv

    test_qs = tf.placeholder(q.dtype, q.shape)
    test_pts = tf.placeholder(x.dtype, x.shape)
    sol_tf = tf_rotate_and_translate_quadric(test_qs, test_pts)
    l,u,ul,ur,br = sess.run(sol_tf, feed_dict={test_pts: x, test_qs: q_test})
    res_q = np.block([[np.diag(ul), ur[0:1].T],[ur[0:1],br[0]]])
    ee = np.block([[u, x[0].reshape(-1,1)],[np.zeros(3),1]])
    eeinv = np.linalg.inv(ee)
    assert(np.allclose(eeinv.T@res_q@eeinv, q_test))
```

```python
print(ur.shape, ul.shape, br.shape)
```

    (4, 3) (3,) (4, 1)

```python

```

```python
with tf.Graph().as_default(), tf.Session() as sess:
    test_qs = tf.placeholder(tf.float32, q.shape)
    test_pts = tf.placeholder(tf.float32, x.shape)
    test_qsc = tf.cast(test_qs, tf.complex128 if test_qs.dtype == tf.float64 else tf.complex64)
    test_ptsc = tf.cast(test_pts, tf.complex128 if test_pts.dtype == tf.float64 else tf.complex64)
    L,U,UL,UR,BR = tf_rotate_and_translate_quadric(test_qsc, test_ptsc)
    ortho_pts, ortho_dists = tf_ortho_project_prerotated(UL,UR,BR,test_ptsc,imag_0tol=1e-3)
    unrotated_pts = tf_unrotate_and_translate(U,test_ptsc,ortho_pts)
    unrotated_pts = tf.real(unrotated_pts)
    res = sess.run((unrotated_pts, ortho_dists), feed_dict={test_pts: x, test_qs: q_test})
test_res = orthogonal_quadric_projection(x,q_test,projection_funcs)
for i, r in enumerate(test_res):
    assert np.allclose(r, res[i], atol=1e-4, rtol=1e-4)
```

```python
res[0].shape, test_res[0].shape
```

    ((4, 3), (4, 3))

```python
res[0], test_res[0]
```

    (array([[ 0.02046363,  0.9590727 ,  2.0204637 ],
            [ 2.4214036 ,  1.065655  ,  4.030104  ],
            [ 3.598402  ,  0.40193224,  4.8424616 ],
            [ 4.3316383 , -0.15742683,  5.338879  ]], dtype=float32),
     array([[ 0.0204637 ,  0.9590726 ,  2.0204637 ],
            [ 2.42140373,  1.06565579,  4.03010368],
            [ 3.59840455,  0.40194921,  4.84246218],
            [ 4.33162896, -0.1574637 ,  5.33887161]]))

```python
# test quadric_ortho_projection
with tf.Graph().as_default(), tf.Session() as sess:
    test_qs = tf.placeholder(tf.float32, q.shape)
    test_pts = tf.placeholder(tf.float32, x.shape)
    test_qsc = tf.cast(test_qs, tf.complex128 if test_qs.dtype == tf.float64 else tf.complex64)
    test_ptsc = tf.cast(test_pts, tf.complex128 if test_pts.dtype == tf.float64 else tf.complex64)
    sol_tf = tf_ortho_project(test_qsc, test_ptsc, imag_0tol=1e-3)
    res = sess.run(sol_tf, feed_dict={test_pts: x, test_qs: q_test})
test_res = orthogonal_quadric_projection(x,q_test,projection_funcs)
for i, r in enumerate(test_res):
    try:
        assert np.allclose(r, res[i], atol=1e-4, rtol=1e-4)
    except:
        print(r, res[i], np.abs(r-res[i]))
```

```python

```

```python

```

```python
# test quadric_ortho_projection
with tf.Graph().as_default(), tf.Session() as sess:
    test_qs = tf.placeholder(tf.float32, q.shape)
    test_pts = tf.placeholder(tf.float32, x.shape)
    test_qsc = tf.cast(test_qs, tf.complex128 if test_qs.dtype == tf.float64 else tf.complex64)
    test_ptsc = tf.cast(test_pts, tf.complex128 if test_pts.dtype == tf.float64 else tf.complex64)
    L,U,UL,UR,BR = tf_rotate_and_translate_quadric(test_qs,test_pts)
    ARGS, roots = tf_ortho_project_x(UL,UR,BR)
    res = sess.run([ARGS,roots], feed_dict={test_pts: x, test_qs: q_test})
```

```python

```

```python

```

```python

```

```python
x = (np.random.uniform(size=(3000,3))-0.5)*10
imag_0tol=1e-2
with tf.Graph().as_default(), tf.Session() as sess:
    test_qs = tf.placeholder(tf.float32, q.shape)
    test_pts = tf.placeholder(tf.float32, x.shape)
    test_qsc = tf.cast(test_qs, tf.complex128 if test_qs.dtype == tf.float64 else tf.complex64)
    test_ptsc = tf.cast(test_pts, tf.complex128 if test_pts.dtype == tf.float64 else tf.complex64)
    proj1_pts, proj1_dists = tf_ortho_project(test_qsc, test_ptsc,imag_0tol=imag_0tol)
    proj1_ptsc = tf.cast(proj1_pts, tf.complex128 if test_pts.dtype == tf.float64 else tf.complex64)
    proj2_pts, proj2_dists = tf_ortho_project(test_qsc, proj1_ptsc,imag_0tol=imag_0tol)
    p1p, p2p, p1d, p2d = sess.run([proj1_pts, proj2_pts, proj1_dists, proj2_dists], feed_dict={test_pts: x, test_qs: q_test})
print(np.argmax(p1d), np.argmax(p2d))
```

    620 493

```python
weird = np.argmax(p2d)
print(x[weird], p1p[weird], p2p[weird])
print(
    np.concatenate([x[weird], [1]]).reshape(1,-1)@q_test@np.concatenate([x[weird], [1]]).reshape(-1,1),
    np.concatenate([p1p[weird], [1]]).reshape(1,-1)@q_test@np.concatenate([p1p[weird], [1]]).reshape(-1,1),
    np.concatenate([p2p[weird], [1]]).reshape(1,-1)@q_test@np.concatenate([p2p[weird], [1]]).reshape(-1,1)
)
```

    [ 3.02199292 -4.69053425  4.86400737] [ 4.0812087 -1.8822029  7.6522493] [ 4.089281  -1.8608544  7.6734877]
    [[-9.02182102]] [[-0.13037601]] [[-2.90371386e-07]]

```python
# test quadric_ortho_projection
with tf.Graph().as_default(), tf.Session() as sess:
    test_qs = tf.placeholder(tf.float32, q.shape)
    test_pts = tf.placeholder(tf.float32, x[weird:weird+1].shape)
    L,U,UL,UR,BR = tf_rotate_and_translate_quadric(test_qs,test_pts)
    args, roots = tf_ortho_project_x(UL,UR,BR)
    roots_r = tf.real(roots)
    roots_i = tf.imag(roots)
    mask = tf.logical_or(tf.greater(roots_i, imag_0tol), tf.greater(-imag_0tol, roots_i))
    masked_real_roots = tf.where(mask, roots_r, tf.fill(tf.shape(roots_r), tf.constant(np.inf, roots_r.dtype)))

    other_dims = projection_funcs["other_dims_as_x"](*([tf.expand_dims(arg, -1) for arg in args] + [masked_real_roots]))
    pts = tf.stack([masked_real_roots] + other_dims, axis=-1)
    ortho_pts, ortho_dists = tf_min_dist_search(pts)
    res = sess.run([roots, ortho_pts], feed_dict={test_pts: x[weird:weird+1], test_qs: q_test})
```

```python

```

```python
res[0][0,0]
```

    array([  0.72231525+8.2737883e-05j,   0.7532167 -8.6197178e-05j,
             1.8831656 +4.5354092e-05j,   2.0551214 -4.2619300e-05j,
             6.9944844 +1.5008181e-06j, -12.900092  +0.0000000e+00j],
          dtype=complex64)

```python
E = np.block([[u, x[weird].reshape(-1,1)],[np.zeros(3), 1]])
np.round(E.T@q_test@E,5)
```

    array([[ 0.1    ,  0.     ,  0.     ,  0.29507],
           [ 0.     ,  0.25   , -0.     , -0.03578],
           [ 0.     ,  0.     ,  0.5    ,  0.0344 ],
           [ 0.29507, -0.03578,  0.0344 , -9.02182]])

```python
tp1p, tp1d = orthogonal_quadric_projection(x,q_test,projection_funcs)
tp2p, tp2d = orthogonal_quadric_projection(tp1p,q_test,projection_funcs)
```

```python
print(x[weird], tp1p[weird], tp2p[weird])
```

    [ 3.02199292 -4.69053425  4.86400737] [ 4.09089469 -1.86488076  7.67692047] [ 4.09089469 -1.86488076  7.67692047]

```python
print(np.sum((p1p-tp1p)**2, axis=1))
plt.hist(np.sum((p1p-tp1p)**2, axis=1))
```

```python

```

```python

```

```python

```

```python

```

```python

```

```python

```

```python

```

```python

```

```python

```

```python

```

```python
import ml_battery
import tensorflow_probability as tfp
import tf_roots.src.companion_eigv

class TFQuadric(sklearn.base.BaseEstimator, ml_battery.tensorflow_models.PickleableTFModel):
    ''' A zero hidden layer NN Classifier (i.e. Logit Regression).
        Parameters:
            n_epochs: number of training epochs
            learning_rate: make it bigger to learn faster, at the risk of killing your relu
            trainable: set to false if you want to not allow training.  For example, if you want to use this as part of another network
            batch_size: defaults to the entire dataset.
    '''
    def __init__(self, n_epochs=100, learning_rate=0.05, trainable=True, batch_size=None):
        self.n_epochs = n_epochs
        self.learning_rate = learning_rate
        self.trainable = trainable
        self.batch_size = batch_size
        super().__init__()

    def fit(self, X, sample_weight=None, beta0=None,**fit_params):
        ''' Puts inputs into np format, initializes a session, builds the graph and then calls `.fit_` which can be overridden by individual models '''
        X = sklearn.utils.check_array(X)
        if sample_weight is None:
            self.weighted = False
        else:
            self.weighted = True
            sample_weight = sklearn.utils.check_array(sample_weight)
        self.input_shape_ = X.shape[-1]
        if beta0 is None:
            beta0 = self.fast_reasonable_q(X,sample_weight)

        beta0 /= beta0[-1] #TODO If the point lies ON the quadric, the remaining variables go to inf!!!! This is probably a problem for the iterated method
        self.init_q = beta0[:-1]

        self.build_model()
        with self.model.graph_.as_default():
            self.sess = tf.Session(graph = self.model.graph_)
            initializer = tf.global_variables_initializer()
            self.sess.run(initializer)
            return self.fit_(X, sample_weight=sample_weight, **fit_params)

    def build_model(self):
        ''' Initializes a new graph, and then calls the .build_model_ method, which must be implemented by a TFEstimator '''
        self.model = ml_battery.tensorflow_models.NameSpace()
        self.model.graph_ = tf.Graph()
        with self.model.graph_.as_default():
            self.build_model_()

    def build_model_(self):
        ''' The actual network architecture '''
        with tf.name_scope("quadric_regression"):

            with tf.name_scope("input"):
                input_dim = [None, self.input_shape_]
                self.model.x = tf.placeholder(tf.complex128, shape=input_dim, name="input")
                #self.model.init_q = tf.placeholder(tf.float32, shape=int((self.input_shape_ + 2)*(self.input_shape_ + 1)/2 - 1), name="initial_quadric")
                if self.weighted:
                    self.model.sample_weight = tf.placeholder(tf.complex128, shape=[None], name="sample_weight")

            with tf.name_scope("quadric_projection"):
                self.model.fq = tf.Variable(self.init_q, name="flattened_quadric", trainable=self.trainable)
                q = tf.contrib.distributions.fill_triangular(
                    tf.concat([self.model.fq[:4], tf.constant([1], dtype=self.model.fq.dtype), self.model.fq[4:7], self.model.fq[10:6:-1]], axis=-1), #TODO: This hinges on the constant=1 constraint from above, which is not necessarily a good idear
                    upper=True
                )
                self.model.q = q + tf.transpose(q)
                if not self.model.q.dtype.is_complex:
                    self.model.qc = tf.cast(self.model.q, tf.complex128 if self.model.q.dtype == tf.float64 else tf.complex64)
                else:
                    self.model.qc = self.model.q
                self.model.orthogonal_projections, dists = tf_ortho_project(self.model.qc, self.model.x,imag_0tol=1e-2)
                dists = tf.real(dists)

            if self.trainable:
                with tf.name_scope("training"):
                    if not self.weighted:
                        self.model.loss = tf.reduce_sum(dists, name="loss")
                    else:
                        self.model.loss = tf.reduce_sum(dists*self.model.sample_weight, name="loss")

                    self.model.optimizer = tf.train.AdamOptimizer(self.learning_rate, name="optimizer")
                    self.model.grads = self.model.optimizer.compute_gradients(self.model.loss)
                    self.model.grad_application = self.model.optimizer.apply_gradients(self.model.grads)
                    with tf.control_dependencies([self.model.grad_application]):
                        self.model.train_step = tf.no_op(name="train_step")

        return self.model

    def fast_reasonable_q(self, X, sample_weight=None):
        #this allows us to orient the data to set an initial set of parameters
        global_linear_model = local_models.TLS_models.LinearODR_mD(2)
        global_linear_model.fit(X, sample_weight=sample_weight)
        global_linear_vecs = global_linear_model.cov_eigenvectors[global_linear_model.cov_eigenvalues_sorter]
        global_linear_mean = global_linear_model.intercept_

        transformed_X = (X-global_linear_mean)@global_linear_vecs.T

        transformed_q = np.zeros((4,4))

        # an ellipse with axes in the various directions
        transformed_q[np.diag_indices(3)] = 1/global_linear_model.cov_eigenvalues[global_linear_model.cov_eigenvalues_sorter]**2
        transformed_q[3,3] = -1
        #print(np.diag(transformed_q), weighted_avg_and_std(transformed_X, weights = sample_weight, axis=0)[1])

        #print(transformed_q)
        #print(global_linear_mean)
        E = np.block([[global_linear_vecs, global_linear_mean.reshape(-1,1)],[np.zeros(3),1]])
        Einv = np.linalg.inv(E)
        q = Einv.T@transformed_q@Einv
        q[np.diag_indices(4)] /= 2 #The tensorflow model is q = [[2a,b,c,d],[b,2e,f,g],[c,f,2h,i],[d,g,i,2j]]
        beta0 = q[np.triu_indices(4)]
        return beta0

    def fit_(self, X, sample_weight=None, feed_dict_extras={}):
        ''' Trains for a number of epochs.  Model input must be in self.model.x, output in self.model.y, loss in self.model.loss, and training using self.model.train_step '''
        logger.info("fitting quadric model")

        for epoch in range(self.n_epochs):
            batcher = ml_battery.tensorflow_models.np_batcher(X.shape[0], self.batch_size)
            for batch in batcher:
                feed_dict={
                    self.model.x : X[batch],
                }
                if self.weighted:
                    feed_dict[self.model.sample_weight] = sample_weight[batch]
                feed_dict.update(feed_dict_extras)
                loss, _ = self.sess.run(
                    (self.model.loss, self.model.train_step),
                    feed_dict
                )

                logger.info("epoch: " + str(epoch) + " :::: loss: " + str(loss.sum()))

        return self
```

```python
tfmodel.sess.close()
```

```python
tfmodel = TFQuadric()
```

```python
#x = (np.random.uniform(size=(3000,3))-0.5)*10
tfmodel.fit(p1p)
```

    TFQuadric(batch_size=None, learning_rate=0.05, n_epochs=100, trainable=True)

```python
import time
start = time.time()
n = 100
for i in range(n):
    tfmodel.sess.close()
    tfmodel = TFQuadric()
    tfmodel.fit(p1p)
end = time.time()
print((end-start)/n)
```

    7.957661757469177

```python
start2 = time.time()
for i in range(n):
    qm = QuadricModel()
    qm.fit(p1p)
    print(i, (time.time() - start2)/(i+1))
end2 = time.time()
print((end2-start2)/n)
```

    [[ 0.12820882 -0.08229127  0.17999422 -0.49689931]
     [-0.08229127  0.17533111 -0.20054582  0.58341657]
     [ 0.17999422 -0.20054582  0.50019594 -1.20773793]
     [-0.49689931  0.58341657 -1.20773793  2.06266324]]
    0 261.05828285217285
    [[ 0.12820882 -0.08229127  0.17999422 -0.49689931]
     [-0.08229127  0.17533111 -0.20054582  0.58341657]
     [ 0.17999422 -0.20054582  0.50019594 -1.20773793]
     [-0.49689931  0.58341657 -1.20773793  2.06266324]]
    1 259.17073822021484
    [[ 0.12820882 -0.08229127  0.17999422 -0.49689931]
     [-0.08229127  0.17533111 -0.20054582  0.58341657]
     [ 0.17999422 -0.20054582  0.50019594 -1.20773793]
     [-0.49689931  0.58341657 -1.20773793  2.06266324]]
    2 259.6524974505107
    [[ 0.12820882 -0.08229127  0.17999422 -0.49689931]
     [-0.08229127  0.17533111 -0.20054582  0.58341657]
     [ 0.17999422 -0.20054582  0.50019594 -1.20773793]
     [-0.49689931  0.58341657 -1.20773793  2.06266324]]

```python

```

```python

```

```python
blah = tfmodel.sess.run(tfmodel.model.q)
blah/blah[3,3]
```

    array([[ 0.17564157,  0.06196144,  0.01040763, -0.33536954],
           [ 0.06196144,  0.16586763,  0.11591057,  0.16381599],
           [ 0.01040763,  0.11591057,  0.21156288, -0.71594598],
           [-0.33536954,  0.16381599, -0.71594598,  1.        ]])

```python
q_test/q_test[3,3]
```

    array([[ 0.10104396,  0.03727616,  0.01417327, -0.23163649],
           [ 0.03727616,  0.09106561,  0.06352525,  0.11391146],
           [ 0.01417327,  0.06352525,  0.15354212, -0.53662328],
           [-0.23163649,  0.11391146, -0.53662328,  1.        ]])

```python

```

```python
tf_pts = tfmodel.sess.run(tfmodel.model.orthogonal_projections, feed_dict={tfmodel.model.x:p1p})
#tf_pts = p1p
```

```python
tf_qfit_pth = os.path.join(project_dir, "tf_qfit_test.png")

data_avg = np.average(tf_pts, axis=0)
data_std = np.std(tf_pts, axis=0)
figure = mayavi.mlab.figure(figure=None, bgcolor=(1,1,1), fgcolor=(0,0,0), engine=None, size=(1000, 1000))
quadric_nodes = plt_grid(figure, tf_pts, data_avg, data_std)
data_nodes = plt_data(figure, p1p, data_std)

normalize_view(figure, data_avg, data_std)
mayavi.mlab.savefig(tf_qfit_pth, magnification=2)
mayavi.mlab.close(figure)
```

```python
blah = imshow(tf_qfit_pth)
```

```python

```

```python

```

```python
len(k_jac[0])
```

    7

```python
collectification_test = [rcollect_best(formula) for formula in poly.all_coeffs()]
```

```python
sympy.lambdify(sorted(Q.free_symbols, key=str), collectification_test[0])
```

    <function _lambdifygenerated(q_0_0, q_1_1, q_2_2, q_3_0, q_3_1, q_3_2, q_3_3)>

```python
list(map(sympy.count_ops, collectification_test))
```

    [6, 15, 51, 49, 39, 13, 2]

```python
sympy.count_ops(sympy.expand(collectification_test[2]))
```

    158

```python
Q
```

$\displaystyle \left[\begin{matrix}q_{0 0} & 0 & 0 & q_{3 0}\\0 & q_{1 1} & 0 & q_{3 1}\\0 & 0 & q_{2 2} & q_{3 2}\\q_{3 0} & q_{3 1} & q_{3 2} & q_{3 3}\end{matrix}\right]$

```python
for i in range(len(collectification_test)):
    print(sympy.expand(collectification_test[i].subs({
        Q[0,0]:a,
        Q[1,1]:b,
        Q[2,2]:c,
        Q[3,3]:d,
        Q[3,0]:e,
        Q[3,1]:f,
        Q[3,2]:g})) == sympy.expand(collected[i]))
```

    True
    True
    True
    True
    True
    True
    True

```python

```

```python
sympy.count_ops(sympy.expand(collected[2]))
```

    158

```python
sympy.expand(collected[3])
```

$\displaystyle 4 a^{3} d e - 6 a^{2} b d e - 6 a^{2} c d e + 16 a^{2} e^{3} + 6 a^{2} e f^{2} + 6 a^{2} e g^{2} + 2 a b^{2} d e + 8 a b c d e - 14 a b e^{3} - 2 a b e f^{2} - 8 a b e g^{2} + 2 a c^{2} d e - 14 a c e^{3} - 8 a c e f^{2} - 2 a c e g^{2} - 2 b^{2} c d e + 2 b^{2} e^{3} + 2 b^{2} e g^{2} - 2 b c^{2} d e + 8 b c e^{3} + 2 b c e f^{2} + 2 b c e g^{2} + 2 c^{2} e^{3} + 2 c^{2} e f^{2}$

```python
collectification_test[2]
```

$\displaystyle q_{3 0}^{3} \left(16 q_{0 0}^{2} - 14 q_{0 0} q_{2 2} + 2 q_{1 1}^{2} + q_{1 1} \left(- 14 q_{0 0} + 8 q_{2 2}\right) + 2 q_{2 2}^{2}\right) + 2 q_{3 0} q_{3 1}^{2} \left(q_{0 0} - q_{2 2}\right) \left(3 q_{0 0} - q_{1 1} - q_{2 2}\right) + 2 q_{3 0} q_{3 2}^{2} \left(q_{0 0} - q_{1 1}\right) \left(3 q_{0 0} - q_{1 1} - q_{2 2}\right) + 2 q_{3 0} q_{3 3} \left(q_{0 0} - q_{1 1}\right) \left(q_{0 0} - q_{2 2}\right) \left(2 q_{0 0} - q_{1 1} - q_{2 2}\right)$

```python
collected[2]
```

$\displaystyle d \left(a^{4} - 2 a^{3} b + a^{2} b^{2} + c^{2} \left(a^{2} - 2 a b + b^{2}\right) + c \left(- 2 a^{3} + 4 a^{2} b - 2 a b^{2}\right)\right) + e^{2} \left(14 a^{3} - 18 a^{2} b + 5 a b^{2} + c^{2} \left(5 a - 4 b\right) + c \left(- 18 a^{2} + 20 a b - 4 b^{2}\right)\right) + f^{2} \left(2 a^{3} - a^{2} b + c^{2} \left(2 a - b\right) + c \left(- 4 a^{2} + 2 a b\right)\right) + g^{2} \left(2 a^{3} - a^{2} c + b^{2} \left(2 a - c\right) + b \left(- 4 a^{2} + 2 a c\right)\right)$

```python
sympy.expand(collectification_test[3].subs({
        Q[0,0]:a,
        Q[1,1]:b,
        Q[2,2]:c,
        Q[3,3]:d,
        Q[3,0]:e,
        Q[3,1]:f,
        Q[3,2]:g}))
```

$\displaystyle 4 a^{3} d e - 6 a^{2} b d e - 6 a^{2} c d e + 16 a^{2} e^{3} + 6 a^{2} e f^{2} + 6 a^{2} e g^{2} + 2 a b^{2} d e + 8 a b c d e - 14 a b e^{3} - 2 a b e f^{2} - 8 a b e g^{2} + 2 a c^{2} d e - 14 a c e^{3} - 8 a c e f^{2} - 2 a c e g^{2} - 2 b^{2} c d e + 2 b^{2} e^{3} + 2 b^{2} e g^{2} - 2 b c^{2} d e + 8 b c e^{3} + 2 b c e f^{2} + 2 b c e g^{2} + 2 c^{2} e^{3} + 2 c^{2} e f^{2}$

```python
list(map(sympy.count_ops, collected))
```

    [6, 15, 100, 68, 39, 13, 2]

import sympy
from sympy.abc import a,b,c,d,e,f,g,x,y,z

Q = a*x**2 + b*y**2 + c*z**2 + 2*e*x + 2*f*y + 2*g*z + d
y_as_x_num = f*x
y_as_x_den = e-(b-a)*x
y_as_x = y_as_x_num/y_as_x_den
z_as_x_num = g*x
z_as_x_den = e-(c-a)*x
z_as_x = z_as_x_num/z_as_x_den
Q_as_x = Q.subs({
    y: y_as_x,
    z: z_as_x,
})

bigQ = sympy.expand(sympy.simplify(Q_as_x*y_as_x_den**2*z_as_x_den**2))

coeffs = list(map(sympy.factor, sympy.poly(bigQ,x).all_coeffs()))

collected = []
for coeff in coeffs:
    collected.append(rcollect_best(coeff))

def funcify(signature, returnable):
    return "def {}: return {}\n".format(signature, returnable)

k_mat = sympy.Matrix(collected)
k_jac = k_mat.jacobian([a,b,c,d,e,f,g])

with open("quadrics_utils.py", "w") as f:
    Q_func = funcify("Q(a,b,c,d,e,f,g,x,y,z)",str(Q))
    y_as_x_func = funcify("y_as_x(a,b,c,d,e,f,g,x)", str(y_as_x))
    z_as_x_func = funcify("z_as_x(a,b,c,d,e,f,g,x)", str(z_as_x))
    Q_as_x_func = funcify("Q_as_x(a,b,c,d,e,f,g,x)", str(Q_as_x))
    k_mat_func = funcify("k_mat(a,b,c,d,e,f,g)", str(k_mat.transpose().tolist()[0]))
    k_jac_func = funcify("k_jac(a,b,c,d,e,f,g)", str(k_jac.tolist()))
    individual_k_funcs = [funcify("k{:01d}(a,b,c,d,e,f,g)".format(i), str(eq)) for i,eq in enumerate(collected[::-1])]
    list(map(f.write, [Q_func, y_as_x_func, z_as_x_func, Q_as_x_func, k_mat_func, k_jac_func] + individual_k_funcs))

```python
xx = sympy.Function('xx')(a,b,c,d,e,f,g)
yy = sympy.Function('yy')(a,b,c,d,e,f,g,xx)
zz = sympy.Function('zz')(a,b,c,d,e,f,g,xx)
```

```python
for var in [a,b,c,d,e,f,g]:
    dx_dvar = sympy.solve(sympy.diff(bigQ.subs({x:xx}), var), sympy.diff(xx, var))
    dx_dvar_num, dx_dvar_den = sympy.fraction(dx_dvar)
    dx_dvar_num, dx_dvar_den = sympy.poly(dx_dvar_num), sympy.poly(dx_dvar_den)

    dx_dvar_num_collections = []
    for coeff in dx_dvar_num.all_coeffs():
        dx_dvar_num_collections.append(rcollect_best(coeff))

    dx_dvar_den_collections = []
    for coeff in dx_dvar_den.all_coeffs():
        dx_dvar_den_collections.append(rcollect_best(coeff))

    dx_dvar_num = sum([x**i*dx_dvar_num_collections[-(i+1)] for i in range(dx_dvar_num_collections)])
    dx_dvar_den = sum([x**i*dx_dvar_den_collections[-(i+1)] for i in range(dx_dvar_den_collections)])
    dx_dvar = dx_dvar_num/dx_dvar_den
```

```python
dz_dvars = []
for var in (a,b,c,d,e,f,g):
    print("diffing_z_against_var_{}".format(str(var)))
    dz_dvars.append(sympy.simplify(rcollect_best(sympy.diff(z_as_x.subs({x:xx}), var))))
```

```python
dy_dvars = []
for var in (a,b,c,d,e,f,g):
    print("diffing_y_against_var_{}".format(str(var)))
    dy_dvars.append(sympy.simplify(rcollect_best(sympy.diff(y_as_x.subs({x:xx}), var))))
```

    5040
    0
    50
    100
    150
    200
    250
    300
    350
    400
    450
    500
    550
    600
    650
    700
    750
    800
    850
    900
    950
    1000
    1050
    1100
    1150
    1200
    1250
    1300
    1350
    1400
    1450
    1500
    1550
    1600
    1650
    1700
    1750
    1800
    1850
    1900
    1950
    2000
    2050
    2100
    2150
    2200
    2250
    2300
    2350
    2400
    2450
    2500
    2550
    2600
    2650
    2700
    2750
    2800
    2850
    2900
    2950
    3000
    3050
    3100
    3150
    3200
    3250
    3300
    3350
    3400
    3450
    3500
    3550
    3600
    3650
    3700
    3750
    3800
    3850
    3900
    3950
    4000
    4050
    4100
    4150
    4200
    4250
    4300
    4350
    4400
    4450
    4500
    4550
    4600
    4650
    4700
    4750
    4800
    4850
    4900
    4950
    5000
    1
    5040
    0
    50
    100
    150
    200
    250
    300
    350
    400
    450
    500
    550
    600
    650
    700
    750
    800
    850
    900
    950
    1000
    1050
    1100
    1150
    1200
    1250
    1300
    1350
    1400
    1450
    1500
    1550
    1600
    1650
    1700
    1750
    1800
    1850
    1900
    1950
    2000
    2050
    2100
    2150
    2200
    2250
    2300
    2350
    2400
    2450
    2500
    2550
    2600
    2650
    2700
    2750
    2800
    2850
    2900
    2950
    3000
    3050
    3100
    3150
    3200
    3250
    3300
    3350
    3400
    3450
    3500
    3550
    3600
    3650
    3700
    3750
    3800
    3850
    3900
    3950
    4000
    4050
    4100
    4150
    4200
    4250
    4300
    4350
    4400
    4450
    4500
    4550
    4600
    4650
    4700
    4750
    4800
    4850
    4900
    4950
    5000
    5040
    0
    50
    100
    150
    200
    250
    300
    350
    400
    450
    500
    550
    600
    650
    700
    750
    800
    850
    900
    950
    1000
    1050
    1100
    1150
    1200
    1250
    1300
    1350
    1400
    1450
    1500
    1550
    1600
    1650
    1700
    1750
    1800
    1850
    1900
    1950
    2000
    2050
    2100
    2150
    2200
    2250
    2300
    2350
    2400
    2450
    2500
    2550
    2600
    2650
    2700
    2750
    2800
    2850
    2900
    2950
    3000
    3050
    3100
    3150
    3200
    3250
    3300
    3350
    3400
    3450
    3500
    3550
    3600
    3650
    3700
    3750
    3800
    3850
    3900
    3950
    4000
    4050
    4100
    4150
    4200
    4250
    4300
    4350
    4400
    4450
    4500
    4550
    4600
    4650
    4700
    4750
    4800
    4850
    4900
    4950
    5000
    5040
    0
    50
    100
    150
    200
    250
    300
    350
    400
    450
    500
    550
    600
    650
    700
    750
    800
    850
    900
    950
    1000
    1050
    1100
    1150
    1200
    1250
    1300
    1350
    1400
    1450
    1500
    1550
    1600
    1650
    1700
    1750
    1800
    1850
    1900
    1950
    2000
    2050
    2100
    2150
    2200
    2250
    2300
    2350
    2400
    2450
    2500
    2550
    2600
    2650
    2700
    2750
    2800
    2850
    2900
    2950
    3000
    3050
    3100
    3150
    3200
    3250
    3300
    3350
    3400
    3450
    3500
    3550
    3600
    3650
    3700
    3750
    3800
    3850
    3900
    3950
    4000
    4050
    4100
    4150
    4200
    4250
    4300
    4350
    4400
    4450
    4500
    4550
    4600
    4650
    4700
    4750
    4800
    4850
    4900
    4950
    5000
    1
    5040
    0
    50
    100
    150
    200
    250
    300
    350
    400
    450
    500
    550
    600
    650
    700
    750
    800
    850
    900
    950
    1000
    1050
    1100
    1150
    1200
    1250
    1300
    1350
    1400
    1450
    1500
    1550
    1600
    1650
    1700
    1750
    1800
    1850
    1900
    1950
    2000
    2050
    2100
    2150
    2200
    2250
    2300
    2350
    2400
    2450
    2500
    2550
    2600
    2650
    2700
    2750
    2800
    2850
    2900
    2950
    3000
    3050
    3100
    3150
    3200
    3250
    3300
    3350
    3400
    3450
    3500
    3550
    3600
    3650
    3700
    3750
    3800
    3850
    3900
    3950
    4000
    4050
    4100
    4150
    4200
    4250
    4300
    4350
    4400
    4450
    4500
    4550
    4600
    4650
    4700
    4750
    4800
    4850
    4900
    4950
    5000
    5040
    0
    50
    100
    150
    200
    250
    300
    350
    400
    450
    500
    550
    600
    650
    700
    750
    800
    850
    900
    950
    1000
    1050
    1100
    1150
    1200
    1250
    1300
    1350
    1400
    1450
    1500
    1550
    1600
    1650
    1700
    1750
    1800
    1850
    1900
    1950
    2000
    2050
    2100
    2150
    2200
    2250
    2300
    2350
    2400
    2450
    2500
    2550
    2600
    2650
    2700
    2750
    2800
    2850
    2900
    2950
    3000
    3050
    3100
    3150
    3200
    3250
    3300
    3350
    3400
    3450
    3500
    3550
    3600
    3650
    3700
    3750
    3800
    3850
    3900
    3950
    4000
    4050
    4100
    4150
    4200
    4250
    4300
    4350
    4400
    4450
    4500
    4550
    4600
    4650
    4700
    4750
    4800
    4850
    4900
    4950
    5000
    5040
    0
    50
    100
    150
    200
    250
    300
    350
    400
    450
    500
    550
    600
    650
    700
    750
    800
    850
    900
    950
    1000
    1050
    1100
    1150
    1200
    1250
    1300
    1350
    1400
    1450
    1500
    1550
    1600
    1650
    1700
    1750
    1800
    1850
    1900
    1950
    2000
    2050
    2100
    2150
    2200
    2250
    2300
    2350
    2400
    2450
    2500
    2550
    2600
    2650
    2700
    2750
    2800
    2850
    2900
    2950
    3000
    3050
    3100
    3150
    3200
    3250
    3300
    3350
    3400
    3450
    3500
    3550
    3600
    3650
    3700
    3750
    3800
    3850
    3900
    3950
    4000
    4050
    4100
    4150
    4200
    4250
    4300
    4350
    4400
    4450
    4500
    4550
    4600
    4650
    4700
    4750
    4800
    4850
    4900
    4950
    5000
    1
    1
    5040
    0
    50
    100
    150
    200
    250
    300
    350
    400
    450
    500
    550
    600
    650
    700
    750
    800
    850
    900
    950
    1000
    1050
    1100
    1150
    1200
    1250
    1300
    1350
    1400
    1450
    1500
    1550
    1600
    1650
    1700
    1750
    1800
    1850
    1900
    1950
    2000
    2050
    2100
    2150
    2200
    2250
    2300
    2350
    2400
    2450
    2500
    2550
    2600
    2650
    2700
    2750
    2800
    2850
    2900
    2950
    3000
    3050
    3100
    3150
    3200
    3250
    3300
    3350
    3400
    3450
    3500
    3550
    3600
    3650
    3700
    3750
    3800
    3850
    3900
    3950
    4000
    4050
    4100
    4150
    4200
    4250
    4300
    4350
    4400
    4450
    4500
    4550
    4600
    4650
    4700
    4750
    4800
    4850
    4900
    4950
    5000
    5040
    0
    50
    100
    150
    200
    250
    300
    350
    400
    450
    500
    550
    600
    650
    700
    750
    800
    850
    900
    950
    1000
    1050
    1100
    1150
    1200
    1250
    1300
    1350
    1400
    1450
    1500
    1550
    1600
    1650
    1700
    1750
    1800
    1850
    1900
    1950
    2000
    2050
    2100
    2150
    2200
    2250
    2300
    2350
    2400
    2450
    2500
    2550
    2600
    2650
    2700
    2750
    2800
    2850
    2900
    2950
    3000
    3050
    3100
    3150
    3200
    3250
    3300
    3350
    3400
    3450
    3500
    3550
    3600
    3650
    3700
    3750
    3800
    3850
    3900
    3950
    4000
    4050
    4100
    4150
    4200
    4250
    4300
    4350
    4400
    4450
    4500
    4550
    4600
    4650
    4700
    4750
    4800
    4850
    4900
    4950
    5000
    5040
    0
    50
    100
    150
    200
    250
    300
    350
    400
    450
    500
    550
    600
    650
    700
    750
    800
    850
    900
    950
    1000
    1050
    1100
    1150
    1200
    1250
    1300
    1350
    1400
    1450
    1500
    1550
    1600
    1650
    1700
    1750
    1800
    1850
    1900
    1950
    2000
    2050
    2100
    2150
    2200
    2250
    2300
    2350
    2400
    2450
    2500
    2550
    2600
    2650
    2700
    2750
    2800
    2850
    2900
    2950
    3000
    3050
    3100
    3150
    3200
    3250
    3300
    3350
    3400
    3450
    3500
    3550
    3600
    3650
    3700
    3750
    3800
    3850
    3900
    3950
    4000
    4050
    4100
    4150
    4200
    4250
    4300
    4350
    4400
    4450
    4500
    4550
    4600
    4650
    4700
    4750
    4800
    4850
    4900
    4950
    5000
    1
    1
    5040
    0
    50
    100
    150
    200
    250
    300
    350
    400
    450
    500
    550
    600
    650
    700
    750
    800
    850
    900
    950
    1000
    1050
    1100
    1150
    1200
    1250
    1300
    1350
    1400
    1450
    1500
    1550
    1600
    1650
    1700
    1750
    1800
    1850
    1900
    1950
    2000
    2050
    2100
    2150
    2200
    2250
    2300
    2350
    2400
    2450
    2500
    2550
    2600
    2650
    2700
    2750
    2800
    2850
    2900
    2950
    3000
    3050
    3100
    3150
    3200
    3250
    3300
    3350
    3400
    3450
    3500
    3550
    3600
    3650
    3700
    3750
    3800
    3850
    3900
    3950
    4000
    4050
    4100
    4150
    4200
    4250
    4300
    4350
    4400
    4450
    4500
    4550
    4600
    4650
    4700
    4750
    4800
    4850
    4900
    4950
    5000
    5040
    0
    50
    100
    150
    200
    250
    300
    350
    400
    450
    500
    550
    600
    650
    700
    750
    800
    850
    900
    950
    1000
    1050
    1100
    1150
    1200
    1250
    1300
    1350
    1400
    1450
    1500
    1550
    1600
    1650
    1700
    1750
    1800
    1850
    1900
    1950
    2000
    2050
    2100
    2150
    2200
    2250
    2300
    2350
    2400
    2450
    2500
    2550
    2600
    2650
    2700
    2750
    2800
    2850
    2900
    2950
    3000
    3050
    3100
    3150
    3200
    3250
    3300
    3350
    3400
    3450
    3500
    3550
    3600
    3650
    3700
    3750
    3800
    3850
    3900
    3950
    4000
    4050
    4100
    4150
    4200
    4250
    4300
    4350
    4400
    4450
    4500
    4550
    4600
    4650
    4700
    4750
    4800
    4850
    4900
    4950
    5000
    5040
    0
    50
    100
    150
    200
    250
    300
    350
    400
    450
    500
    550
    600
    650
    700
    750
    800
    850
    900
    950
    1000
    1050
    1100
    1150
    1200
    1250
    1300
    1350
    1400
    1450
    1500
    1550
    1600
    1650
    1700
    1750
    1800
    1850
    1900
    1950
    2000
    2050
    2100
    2150
    2200
    2250
    2300
    2350
    2400
    2450
    2500
    2550
    2600
    2650
    2700
    2750
    2800
    2850
    2900
    2950
    3000
    3050
    3100
    3150
    3200
    3250
    3300
    3350
    3400
    3450
    3500
    3550
    3600
    3650
    3700
    3750
    3800
    3850
    3900
    3950
    4000
    4050
    4100
    4150
    4200
    4250
    4300
    4350
    4400
    4450
    4500
    4550
    4600
    4650
    4700
    4750
    4800
    4850
    4900
    4950
    5000
    1
    5040
    0
    50
    100
    150
    200
    250
    300
    350
    400
    450
    500
    550
    600
    650
    700
    750
    800
    850
    900
    950
    1000
    1050
    1100
    1150
    1200
    1250
    1300
    1350
    1400
    1450
    1500
    1550
    1600
    1650
    1700
    1750
    1800
    1850
    1900
    1950
    2000
    2050
    2100
    2150
    2200
    2250
    2300
    2350
    2400
    2450
    2500
    2550
    2600
    2650
    2700
    2750
    2800
    2850
    2900
    2950
    3000
    3050
    3100
    3150
    3200
    3250
    3300
    3350
    3400
    3450
    3500
    3550
    3600
    3650
    3700
    3750
    3800
    3850
    3900
    3950
    4000
    4050
    4100
    4150
    4200
    4250
    4300
    4350
    4400
    4450
    4500
    4550
    4600
    4650
    4700
    4750
    4800
    4850
    4900
    4950
    5000
    5040
    0
    50
    100
    150
    200
    250
    300
    350
    400
    450
    500
    550
    600
    650
    700
    750
    800
    850
    900
    950
    1000
    1050
    1100
    1150
    1200
    1250
    1300
    1350
    1400
    1450
    1500
    1550
    1600
    1650
    1700
    1750
    1800
    1850
    1900
    1950
    2000
    2050
    2100
    2150
    2200
    2250
    2300
    2350
    2400
    2450
    2500
    2550
    2600
    2650
    2700
    2750
    2800
    2850
    2900
    2950
    3000
    3050
    3100
    3150
    3200
    3250
    3300
    3350
    3400
    3450
    3500
    3550
    3600
    3650
    3700
    3750
    3800
    3850
    3900
    3950
    4000
    4050
    4100
    4150
    4200
    4250
    4300
    4350
    4400
    4450
    4500
    4550
    4600
    4650
    4700
    4750
    4800
    4850
    4900
    4950
    5000
    5040
    0
    50
    100
    150
    200
    250
    300
    350
    400
    450
    500
    550
    600
    650
    700
    750
    800
    850
    900
    950
    1000
    1050
    1100
    1150
    1200
    1250
    1300
    1350
    1400
    1450
    1500
    1550
    1600
    1650
    1700
    1750
    1800
    1850
    1900
    1950
    2000
    2050
    2100
    2150
    2200
    2250
    2300
    2350
    2400
    2450
    2500
    2550
    2600
    2650
    2700
    2750
    2800
    2850
    2900
    2950
    3000
    3050
    3100
    3150
    3200
    3250
    3300
    3350
    3400
    3450
    3500
    3550
    3600
    3650
    3700
    3750
    3800
    3850
    3900
    3950
    4000
    4050
    4100
    4150
    4200
    4250
    4300
    4350
    4400
    4450
    4500
    4550
    4600
    4650
    4700
    4750
    4800
    4850
    4900
    4950
    5000
    5040
    0
    50
    100
    150
    200
    250
    300
    350
    400
    450
    500
    550
    600
    650
    700
    750
    800
    850
    900
    950
    1000
    1050
    1100
    1150
    1200
    1250
    1300
    1350
    1400
    1450
    1500
    1550
    1600
    1650
    1700
    1750
    1800
    1850
    1900
    1950
    2000
    2050
    2100
    2150
    2200
    2250
    2300
    2350
    2400
    2450
    2500
    2550
    2600
    2650
    2700
    2750
    2800
    2850
    2900
    2950
    3000
    3050
    3100
    3150
    3200
    3250
    3300
    3350
    3400
    3450
    3500
    3550
    3600
    3650
    3700
    3750
    3800
    3850
    3900
    3950
    4000
    4050
    4100
    4150
    4200
    4250
    4300
    4350
    4400
    4450
    4500
    4550
    4600
    4650
    4700
    4750
    4800
    4850
    4900
    4950
    5000
    5040
    0
    50
    100
    150
    200
    250
    300
    350
    400
    450
    500
    550
    600
    650
    700
    750
    800
    850
    900
    950
    1000
    1050
    1100
    1150
    1200
    1250
    1300
    1350
    1400
    1450
    1500
    1550
    1600
    1650
    1700
    1750
    1800
    1850
    1900
    1950
    2000
    2050
    2100
    2150
    2200
    2250
    2300
    2350
    2400
    2450
    2500
    2550
    2600
    2650
    2700
    2750
    2800
    2850
    2900
    2950
    3000
    3050
    3100
    3150
    3200
    3250
    3300
    3350
    3400
    3450
    3500
    3550
    3600
    3650
    3700
    3750
    3800
    3850
    3900
    3950
    4000
    4050
    4100
    4150
    4200
    4250
    4300
    4350
    4400
    4450
    4500
    4550
    4600
    4650
    4700
    4750
    4800
    4850
    4900
    4950
    5000
    5040
    0
    50
    100
    150
    200
    250
    300
    350
    400
    450
    500
    550
    600
    650
    700
    750
    800
    850
    900
    950
    1000
    1050
    1100
    1150
    1200
    1250
    1300
    1350
    1400
    1450
    1500
    1550
    1600
    1650
    1700
    1750
    1800
    1850
    1900
    1950
    2000
    2050
    2100
    2150
    2200
    2250
    2300
    2350
    2400
    2450
    2500
    2550
    2600
    2650
    2700
    2750
    2800
    2850
    2900
    2950
    3000
    3050
    3100
    3150
    3200
    3250
    3300
    3350
    3400
    3450
    3500
    3550
    3600
    3650
    3700
    3750
    3800
    3850
    3900
    3950
    4000
    4050
    4100
    4150
    4200
    4250
    4300
    4350
    4400
    4450
    4500
    4550
    4600
    4650
    4700
    4750
    4800
    4850
    4900
    4950
    5000
    5040
    0
    50
    100
    150
    200
    250
    300
    350
    400
    450
    500
    550
    600
    650
    700
    750
    800
    850
    900
    950
    1000
    1050
    1100
    1150
    1200
    1250
    1300
    1350
    1400
    1450
    1500
    1550
    1600
    1650
    1700
    1750
    1800
    1850
    1900
    1950
    2000
    2050
    2100
    2150
    2200
    2250
    2300
    2350
    2400
    2450
    2500
    2550
    2600
    2650
    2700
    2750
    2800
    2850
    2900
    2950
    3000
    3050
    3100
    3150
    3200
    3250
    3300
    3350
    3400
    3450
    3500
    3550
    3600
    3650
    3700
    3750
    3800
    3850
    3900
    3950
    4000
    4050
    4100
    4150
    4200
    4250
    4300
    4350
    4400
    4450
    4500
    4550
    4600
    4650
    4700
    4750
    4800
    4850
    4900
    4950
    5000
    5040
    0
    50
    100
    150
    200
    250
    300
    350
    400
    450
    500
    550
    600
    650
    700
    750
    800
    850
    900
    950
    1000
    1050
    1100
    1150
    1200
    1250
    1300
    1350
    1400
    1450
    1500
    1550
    1600
    1650
    1700
    1750
    1800
    1850
    1900
    1950
    2000
    2050
    2100
    2150
    2200
    2250
    2300
    2350
    2400
    2450
    2500
    2550
    2600
    2650
    2700
    2750
    2800
    2850
    2900
    2950
    3000
    3050
    3100
    3150
    3200
    3250
    3300
    3350
    3400
    3450
    3500
    3550
    3600
    3650
    3700
    3750
    3800
    3850
    3900
    3950
    4000
    4050
    4100
    4150
    4200
    4250
    4300
    4350
    4400
    4450
    4500
    4550
    4600
    4650
    4700
    4750
    4800
    4850
    4900
    4950
    5000
    5040
    0
    50
    100
    150
    200
    250
    300
    350
    400
    450
    500
    550
    600
    650
    700
    750
    800
    850
    900
    950
    1000
    1050
    1100
    1150
    1200
    1250
    1300
    1350
    1400
    1450
    1500
    1550
    1600
    1650
    1700
    1750
    1800
    1850
    1900
    1950
    2000
    2050
    2100
    2150
    2200
    2250
    2300
    2350
    2400
    2450
    2500
    2550
    2600
    2650
    2700
    2750
    2800
    2850
    2900
    2950
    3000
    3050
    3100
    3150
    3200
    3250
    3300
    3350
    3400
    3450
    3500
    3550
    3600
    3650
    3700
    3750
    3800
    3850
    3900
    3950
    4000
    4050
    4100
    4150
    4200
    4250
    4300
    4350
    4400
    4450
    4500
    4550
    4600
    4650
    4700
    4750
    4800
    4850
    4900
    4950
    5000
    1
    1
    5040
    0
    50
    100
    150
    200
    250
    300
    350
    400
    450
    500
    550
    600
    650
    700
    750
    800
    850
    900
    950
    1000
    1050
    1100
    1150
    1200
    1250
    1300
    1350
    1400
    1450
    1500
    1550
    1600
    1650
    1700
    1750
    1800
    1850
    1900
    1950
    2000
    2050
    2100
    2150
    2200
    2250
    2300
    2350
    2400
    2450
    2500
    2550
    2600
    2650
    2700
    2750
    2800
    2850
    2900
    2950
    3000
    3050
    3100
    3150
    3200
    3250
    3300
    3350
    3400
    3450
    3500
    3550
    3600
    3650
    3700
    3750
    3800
    3850
    3900
    3950
    4000
    4050
    4100
    4150
    4200
    4250
    4300
    4350
    4400
    4450
    4500
    4550
    4600
    4650
    4700
    4750
    4800
    4850
    4900
    4950
    5000
    5040
    0
    50
    100
    150
    200
    250
    300
    350
    400
    450
    500
    550
    600
    650
    700
    750
    800
    850
    900
    950
    1000
    1050
    1100
    1150
    1200
    1250
    1300
    1350
    1400
    1450
    1500
    1550
    1600
    1650
    1700
    1750
    1800
    1850
    1900
    1950
    2000
    2050
    2100
    2150
    2200
    2250
    2300
    2350
    2400
    2450
    2500
    2550
    2600
    2650
    2700
    2750
    2800
    2850
    2900
    2950
    3000
    3050
    3100
    3150
    3200
    3250
    3300
    3350
    3400
    3450
    3500
    3550
    3600
    3650
    3700
    3750
    3800
    3850
    3900
    3950
    4000
    4050
    4100
    4150
    4200
    4250
    4300
    4350
    4400
    4450
    4500
    4550
    4600
    4650
    4700
    4750
    4800
    4850
    4900
    4950
    5000

```python
dy_dvars[1]
```

$\displaystyle \frac{f \left(e \frac{\partial}{\partial b} \operatorname{xx}{\left(a,b,c,d,e,f,g \right)} + \operatorname{xx}^{2}{\left(a,b,c,d,e,f,g \right)}\right)}{\left(a \operatorname{xx}{\left(a,b,c,d,e,f,g \right)} - b \operatorname{xx}{\left(a,b,c,d,e,f,g \right)} + e\right)^{2}}$

```python

```

```python

```

```python
blah = sympy.solve(sympy.diff(bigQ.subs({x:xx}), a), sympy.diff(xx,a))
```

```python
wah,hah = sympy.fraction(blah[0])
```

```python
wah = sympy.poly(wah, xx)
```

```python
sympy.factor(wah)
```

$\displaystyle - \left(5 a^{4} \operatorname{xx}^{5}{\left(a,b,c,d,e,f,g \right)} - 8 a^{3} b \operatorname{xx}^{5}{\left(a,b,c,d,e,f,g \right)} - 8 a^{3} c \operatorname{xx}^{5}{\left(a,b,c,d,e,f,g \right)} + 4 a^{3} d \operatorname{xx}^{3}{\left(a,b,c,d,e,f,g \right)} + 24 a^{3} e \operatorname{xx}^{4}{\left(a,b,c,d,e,f,g \right)} + 3 a^{2} b^{2} \operatorname{xx}^{5}{\left(a,b,c,d,e,f,g \right)} + 12 a^{2} b c \operatorname{xx}^{5}{\left(a,b,c,d,e,f,g \right)} - 6 a^{2} b d \operatorname{xx}^{3}{\left(a,b,c,d,e,f,g \right)} - 30 a^{2} b e \operatorname{xx}^{4}{\left(a,b,c,d,e,f,g \right)} + 3 a^{2} c^{2} \operatorname{xx}^{5}{\left(a,b,c,d,e,f,g \right)} - 6 a^{2} c d \operatorname{xx}^{3}{\left(a,b,c,d,e,f,g \right)} - 30 a^{2} c e \operatorname{xx}^{4}{\left(a,b,c,d,e,f,g \right)} + 12 a^{2} d e \operatorname{xx}^{2}{\left(a,b,c,d,e,f,g \right)} + 42 a^{2} e^{2} \operatorname{xx}^{3}{\left(a,b,c,d,e,f,g \right)} + 6 a^{2} f^{2} \operatorname{xx}^{3}{\left(a,b,c,d,e,f,g \right)} + 6 a^{2} g^{2} \operatorname{xx}^{3}{\left(a,b,c,d,e,f,g \right)} - 4 a b^{2} c \operatorname{xx}^{5}{\left(a,b,c,d,e,f,g \right)} + 2 a b^{2} d \operatorname{xx}^{3}{\left(a,b,c,d,e,f,g \right)} + 8 a b^{2} e \operatorname{xx}^{4}{\left(a,b,c,d,e,f,g \right)} - 4 a b c^{2} \operatorname{xx}^{5}{\left(a,b,c,d,e,f,g \right)} + 8 a b c d \operatorname{xx}^{3}{\left(a,b,c,d,e,f,g \right)} + 32 a b c e \operatorname{xx}^{4}{\left(a,b,c,d,e,f,g \right)} - 12 a b d e \operatorname{xx}^{2}{\left(a,b,c,d,e,f,g \right)} - 36 a b e^{2} \operatorname{xx}^{3}{\left(a,b,c,d,e,f,g \right)} - 2 a b f^{2} \operatorname{xx}^{3}{\left(a,b,c,d,e,f,g \right)} - 8 a b g^{2} \operatorname{xx}^{3}{\left(a,b,c,d,e,f,g \right)} + 2 a c^{2} d \operatorname{xx}^{3}{\left(a,b,c,d,e,f,g \right)} + 8 a c^{2} e \operatorname{xx}^{4}{\left(a,b,c,d,e,f,g \right)} - 12 a c d e \operatorname{xx}^{2}{\left(a,b,c,d,e,f,g \right)} - 36 a c e^{2} \operatorname{xx}^{3}{\left(a,b,c,d,e,f,g \right)} - 8 a c f^{2} \operatorname{xx}^{3}{\left(a,b,c,d,e,f,g \right)} - 2 a c g^{2} \operatorname{xx}^{3}{\left(a,b,c,d,e,f,g \right)} + 12 a d e^{2} \operatorname{xx}{\left(a,b,c,d,e,f,g \right)} + 32 a e^{3} \operatorname{xx}^{2}{\left(a,b,c,d,e,f,g \right)} + 12 a e f^{2} \operatorname{xx}^{2}{\left(a,b,c,d,e,f,g \right)} + 12 a e g^{2} \operatorname{xx}^{2}{\left(a,b,c,d,e,f,g \right)} + b^{2} c^{2} \operatorname{xx}^{5}{\left(a,b,c,d,e,f,g \right)} - 2 b^{2} c d \operatorname{xx}^{3}{\left(a,b,c,d,e,f,g \right)} - 6 b^{2} c e \operatorname{xx}^{4}{\left(a,b,c,d,e,f,g \right)} + 2 b^{2} d e \operatorname{xx}^{2}{\left(a,b,c,d,e,f,g \right)} + 5 b^{2} e^{2} \operatorname{xx}^{3}{\left(a,b,c,d,e,f,g \right)} + 2 b^{2} g^{2} \operatorname{xx}^{3}{\left(a,b,c,d,e,f,g \right)} - 2 b c^{2} d \operatorname{xx}^{3}{\left(a,b,c,d,e,f,g \right)} - 6 b c^{2} e \operatorname{xx}^{4}{\left(a,b,c,d,e,f,g \right)} + 8 b c d e \operatorname{xx}^{2}{\left(a,b,c,d,e,f,g \right)} + 20 b c e^{2} \operatorname{xx}^{3}{\left(a,b,c,d,e,f,g \right)} + 2 b c f^{2} \operatorname{xx}^{3}{\left(a,b,c,d,e,f,g \right)} + 2 b c g^{2} \operatorname{xx}^{3}{\left(a,b,c,d,e,f,g \right)} - 6 b d e^{2} \operatorname{xx}{\left(a,b,c,d,e,f,g \right)} - 14 b e^{3} \operatorname{xx}^{2}{\left(a,b,c,d,e,f,g \right)} - 2 b e f^{2} \operatorname{xx}^{2}{\left(a,b,c,d,e,f,g \right)} - 8 b e g^{2} \operatorname{xx}^{2}{\left(a,b,c,d,e,f,g \right)} + 2 c^{2} d e \operatorname{xx}^{2}{\left(a,b,c,d,e,f,g \right)} + 5 c^{2} e^{2} \operatorname{xx}^{3}{\left(a,b,c,d,e,f,g \right)} + 2 c^{2} f^{2} \operatorname{xx}^{3}{\left(a,b,c,d,e,f,g \right)} - 6 c d e^{2} \operatorname{xx}{\left(a,b,c,d,e,f,g \right)} - 14 c e^{3} \operatorname{xx}^{2}{\left(a,b,c,d,e,f,g \right)} - 8 c e f^{2} \operatorname{xx}^{2}{\left(a,b,c,d,e,f,g \right)} - 2 c e g^{2} \operatorname{xx}^{2}{\left(a,b,c,d,e,f,g \right)} + 4 d e^{3} + 9 e^{4} \operatorname{xx}{\left(a,b,c,d,e,f,g \right)} + 6 e^{2} f^{2} \operatorname{xx}{\left(a,b,c,d,e,f,g \right)} + 6 e^{2} g^{2} \operatorname{xx}{\left(a,b,c,d,e,f,g \right)}\right) \operatorname{xx}{\left(a,b,c,d,e,f,g \right)}$

```python
wah.all_coeffs()
```

    [-5*a**4 + 8*a**3*b + 8*a**3*c - 3*a**2*b**2 - 12*a**2*b*c - 3*a**2*c**2 + 4*a*b**2*c + 4*a*b*c**2 - b**2*c**2,
     -24*a**3*e + 30*a**2*b*e + 30*a**2*c*e - 8*a*b**2*e - 32*a*b*c*e - 8*a*c**2*e + 6*b**2*c*e + 6*b*c**2*e,
     -4*a**3*d + 6*a**2*b*d + 6*a**2*c*d - 42*a**2*e**2 - 6*a**2*f**2 - 6*a**2*g**2 - 2*a*b**2*d - 8*a*b*c*d + 36*a*b*e**2 + 2*a*b*f**2 + 8*a*b*g**2 - 2*a*c**2*d + 36*a*c*e**2 + 8*a*c*f**2 + 2*a*c*g**2 + 2*b**2*c*d - 5*b**2*e**2 - 2*b**2*g**2 + 2*b*c**2*d - 20*b*c*e**2 - 2*b*c*f**2 - 2*b*c*g**2 - 5*c**2*e**2 - 2*c**2*f**2,
     -12*a**2*d*e + 12*a*b*d*e + 12*a*c*d*e - 32*a*e**3 - 12*a*e*f**2 - 12*a*e*g**2 - 2*b**2*d*e - 8*b*c*d*e + 14*b*e**3 + 2*b*e*f**2 + 8*b*e*g**2 - 2*c**2*d*e + 14*c*e**3 + 8*c*e*f**2 + 2*c*e*g**2,
     -12*a*d*e**2 + 6*b*d*e**2 + 6*c*d*e**2 - 9*e**4 - 6*e**2*f**2 - 6*e**2*g**2,
     -4*d*e**3,
     0]

```python
hah = sympy.poly(hah, xx)
```

```python
hah.all_coeffs()
```

    [6*a**5 - 12*a**4*b - 12*a**4*c + 6*a**3*b**2 + 24*a**3*b*c + 6*a**3*c**2 - 12*a**2*b**2*c - 12*a**2*b*c**2 + 6*a*b**2*c**2,
     30*a**4*e - 50*a**3*b*e - 50*a**3*c*e + 20*a**2*b**2*e + 80*a**2*b*c*e + 20*a**2*c**2*e - 30*a*b**2*c*e - 30*a*b*c**2*e + 10*b**2*c**2*e,
     4*a**4*d - 8*a**3*b*d - 8*a**3*c*d + 56*a**3*e**2 + 8*a**3*f**2 + 8*a**3*g**2 + 4*a**2*b**2*d + 16*a**2*b*c*d - 72*a**2*b*e**2 - 4*a**2*b*f**2 - 16*a**2*b*g**2 + 4*a**2*c**2*d - 72*a**2*c*e**2 - 16*a**2*c*f**2 - 4*a**2*c*g**2 - 8*a*b**2*c*d + 20*a*b**2*e**2 + 8*a*b**2*g**2 - 8*a*b*c**2*d + 80*a*b*c*e**2 + 8*a*b*c*f**2 + 8*a*b*c*g**2 + 20*a*c**2*e**2 + 8*a*c**2*f**2 + 4*b**2*c**2*d - 16*b**2*c*e**2 - 4*b**2*c*g**2 - 16*b*c**2*e**2 - 4*b*c**2*f**2,
     12*a**3*d*e - 18*a**2*b*d*e - 18*a**2*c*d*e + 48*a**2*e**3 + 18*a**2*e*f**2 + 18*a**2*e*g**2 + 6*a*b**2*d*e + 24*a*b*c*d*e - 42*a*b*e**3 - 6*a*b*e*f**2 - 24*a*b*e*g**2 + 6*a*c**2*d*e - 42*a*c*e**3 - 24*a*c*e*f**2 - 6*a*c*e*g**2 - 6*b**2*c*d*e + 6*b**2*e**3 + 6*b**2*e*g**2 - 6*b*c**2*d*e + 24*b*c*e**3 + 6*b*c*e*f**2 + 6*b*c*e*g**2 + 6*c**2*e**3 + 6*c**2*e*f**2,
     12*a**2*d*e**2 - 12*a*b*d*e**2 - 12*a*c*d*e**2 + 18*a*e**4 + 12*a*e**2*f**2 + 12*a*e**2*g**2 + 2*b**2*d*e**2 + 8*b*c*d*e**2 - 8*b*e**4 - 2*b*e**2*f**2 - 8*b*e**2*g**2 + 2*c**2*d*e**2 - 8*c*e**4 - 8*c*e**2*f**2 - 2*c*e**2*g**2,
     4*a*d*e**3 - 2*b*d*e**3 - 2*c*d*e**3 + 2*e**5 + 2*e**3*f**2 + 2*e**3*g**2]

```python
dxda_den_collections = []
for coeff in hah.all_coeffs():
    dxda_den_collections.append(rcollect_best(coeff))
```

```python
dxda_den_collections[1].args[2]
```

$\displaystyle c^{2} e \left(20 a^{2} - 30 a b + 10 b^{2}\right)$

```python

```

```python

```

```python

```

```python

```

```python
linear_models = local_models.local_models.LocalModels(local_models.TLS_models.LinearODR_mD(2))
linear_models.fit(data)
```

    <local_models.local_models.LocalModels at 0x7f39c68a8438>

```python
avg_pt_dist = np.average(linear_models.index.query(data, k=2)[0][:,1])
```

```python
avg_pt_dist
```

    1.2836044940342985

```python
random_data_subset = data[np.random.randint(data.shape[0], size=50)]
```

```python
queried = linear_models.index.query_radius(random_data_subset, r=avg_pt_dist*10)
```

```python
list(map(lambda x: x.shape, queried))
```

    [(289,),
     (315,),
     (283,),
     (386,),
     (209,),
     (106,),
     (375,),
     (321,),
     (405,),
     (372,),
     (341,),
     (352,),
     (376,),
     (308,),
     (249,),
     (347,),
     (245,),
     (380,),
     (378,),
     (414,),
     (419,),
     (106,),
     (345,),
     (320,),
     (444,),
     (282,),
     (208,),
     (371,),
     (368,),
     (423,),
     (307,),
     (435,),
     (374,),
     (324,),
     (271,),
     (147,),
     (150,),
     (147,),
     (397,),
     (318,),
     (407,),
     (200,),
     (239,),
     (403,),
     (401,),
     (316,),
     (353,),
     (321,),
     (261,),
     (289,)]

```python
kernel = local_models.local_models.TriCubeKernel(bandwidth=avg_pt_dist*10)
```

```python
linear_params_vecs, linear_params_mean = local_models.linear_projections.transformate_data(data, kernel, linear_models, r=kernel.support_radius())
```

```python
linear_params_vecs.shape
```

    (58659, 2, 3)

```python
def imshow(pth, cv2color=cv2.IMREAD_COLOR, **kwargs):
    img = cv2.imread(pth, cv2color)
    plt.imshow(img, **kwargs)
```

```python
N = int(data.shape[0]/10)
sample_indices = np.random.choice(np.arange(data.shape[0]), size=N)

pth = os.path.join(project_dir, "single_convergence.png")
data_avg = np.average(data, axis=0)
data_std = np.std(data, axis=0)
figure = mayavi.mlab.figure(figure=None, bgcolor=(1,1,1), fgcolor=(0,0,0), engine=None, size=(1000, 500))
data_nodes = plt_data(figure, linear_params_mean[sample_indices], data_std)
normals = np.cross(*np.rollaxis(linear_params_vecs[sample_indices],1))
#normal_vecs = plt_normals(figure, normals, linear_params_mean[sample_indices], data_std)
#neg_normal_vecs = plt_normals(figure, -normals, linear_params_mean[sample_indices], data_std)
normalize_view(figure, data_avg, data_std, azimuth=40, elevation=80)
mayavi.mlab.savefig(pth, magnification=2)
mayavi.mlab.close(figure)
```

```python
imshow(pth)
plt.axis("off")
```

```python
global_linear_model = local_models.TLS_models.LinearODR_mD(2)
global_linear_model.fit(data)
```

    <local_models.TLS_models.LinearODR_mD at 0x7f399aae8240>

```python
global_linear_model.coef_
global_params_vecs = global_linear_model.coef_.reshape(2,-1)
global_params_mean = global_linear_model.intercept_
global_normal = np.cross(*global_params_vecs)
```

```python
basis_changer = np.concatenate((global_params_vecs, global_normal.reshape(1,-1)),axis=0).T
basis_changer = np.linalg.inv(basis_changer)
```

```python
bases_changed = np.einsum('ij,kj->ki', basis_changer, data-global_params_mean)
```

```python
x,y,z = bases_changed.T
```

```python
plt.scatter(x,y,c=z)
plt.axis("off")
```