Skip to content

Geometry-aware point removal bug #2

@lindonroberts

Description

@lindonroberts

After adding xnew, we have p+2 points in the model. Then selecting the best point to remove based on poisedness ends up calculating linear Lagrange polynomials in a p+1 dimensional subspace rather than the proper p dimensional subspace. This happens because of numerical errors, so xnew doesn't appear to be exactly in the existing space, and model.R ends up extremely ill-conditioned (if singular we revert to distance-only point removal).

Need to update this process, perhaps using regression Lagrange polynomials in the original p dimensional subspace for the p+2 points? Important to check this doesn't decrease overall performance of the code.

Code snippet making this clear:

import numpy as np
from dfbgn.model import InterpSet
from dfbgn.hessian import Hessian
from dfbgn.trust_region import trsbox

def objfun(x):  # f(x) = ||x||^2, simple least-squares
    return x.copy()

np.random.seed(0)
n = 10
fixed_block = 3
delta = 0.2
maxfun = 100
args = ()
scaling_changes = None
x0 = np.ones((n,), dtype=float)

r0 = objfun(x0)
nf = 1
xl = -1e20 * np.ones((n,))  # unconstrained
xu = 1e20 * np.ones((n,))  # unconstrained

# Start of dfbgn.solve_main(...)
model = InterpSet(fixed_block, x0, r0, xl, xu)
exit_info, nf = model.initialise_interp_set(delta, objfun, args, scaling_changes, nf, maxfun)
print("Initially, model has %g directions" % model.p)

xk = model.xopt()
fk = model.fopt()
interp_ok, ck, Jk = model.interpolate_mini_models(jac_in_full_space=False)
gk = 2.0 * Jk.T.dot(ck)
Hk = Hessian(model.p, vals=2.0 * np.dot(Jk.T, Jk))
sk_red, _, crvmin = trsbox(np.zeros((model.p,)), gk, Hk, -1e20 * np.ones((model.p,)), 1e20 * np.ones((model.p,)), delta)
sk_full = model.project_to_full_space(sk_red)

xnew = xk + sk_full
nf += 1
rnew = objfun(xnew)
fnew = np.dot(rnew, rnew)

pred_reduction = -np.dot(sk_red, gk + 0.5 * Hk.vec_mul(sk_red))
actual_reduction = fk - fnew
ratio = actual_reduction / pred_reduction

print("fk = %g, fnew = %g, ratio = %g" % (fk, fnew, ratio))

model.append_point(xnew, rnew)  # updates xopt
xnew_appended = True

min_npt_to_drop = (1 if xnew_appended else 0) + (2 if fixed_block is None else 1)
alpha = 1.0  # on successful steps
ndirs_to_keep = min(int(alpha * model.p), model.p - min_npt_to_drop)
ndirs_to_keep = max(0, ndirs_to_keep)
ndirs_to_drop = model.p - ndirs_to_keep

print("After adding xnew, model has %g directions, dropping %g" % (model.p, ndirs_to_drop))

try:
    sigmas = model.poisedness_of_each_point(delta=delta)
    print("sigmas =", sigmas)
    print("abs(Rdiag) =", np.abs(np.diag(model.R)))
    sqdists = np.square(model.distances_to_xopt(include_kopt=True))  # ||yt-xk||^2
    vals = sigmas * np.maximum(sqdists**2 / delta**4, 1)  # BOBYQA point to remove criterion
    vals[model.kopt] = -1.0  # make sure kopt is never selected
    print("vals =", vals)
except np.linalg.LinAlgError:
    print("Poisedness calculation failed, using distance to xopt")
    # If poisedness calculation fails, revert to dropping furthest points
    vals = np.square(model.distances_to_xopt(include_kopt=True))  # ||yt-xk||^2
    vals[model.kopt] = -1.0  # make sure kopt is never selected
    print("vals =", vals)
for i in range(ndirs_to_drop):
    k = np.argmax(vals)
    vals = np.delete(vals, k)  # keep vals indices in line with indices of model.points
    model.remove_point(k, check_not_kopt=True)

print("Finish iteration with %g directions" % model.p)
print("At start of next iteration, will add new directions until have %g directions" % fixed_block)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions