///  A Solver is the driving class that collects Variables and Constraints and then generates a
///  solution that minimally satisfies the constraints.
// import {Assert} from '../../utils/assert'
import { greaterDistEps } from '../../utils/compare';
import { flatMap } from '../../utils/setOperations';
import { Block } from './Block';
import { BlockVector } from './BlockVector';
import { Constraint } from './Constraint';
import { ConstraintVector } from './ConstraintVector';
import { Parameters } from './Parameters';
import { Qpsc } from './QPSC';
import { Solution } from './Solution';
import { SolverAlgorithm } from './SolverAlgorithm';
import { Variable } from './Variable';
import { ViolationCache } from './ViolationCache';
class ConstraintListForVariable {
    constructor(constraints, numberOfLeftConstraints) {
        //  The number of Constraints that are LeftConstraints for the variable keying this object.
        this.NumberOfLeftConstraints = 0;
        this.Constraints = constraints;
        this.NumberOfLeftConstraints = numberOfLeftConstraints;
    }
}
export class Solver {
    constructor() {
        //  Notes about hierarchy:
        //   1.  Each Variable is initially assigned to its own block, and subsequently MergeBlocks()
        //       and SplitBlocks() may change its block membership, but the variable is always in one
        //       and only one block, so we enumerate variables by enumerating blocks and variables.
        //   2.  The list of (active and inactive) constraints is within each block's variable list;
        //       we simply enumerate each block's LeftConstraints.
        this.allBlocks = new BlockVector();
        //  To speed up SearchAllConstraints, have a single Array in addition to the per-block
        //  variable Lists (Array indexing is faster than Array).
        this.allConstraints = new ConstraintVector();
        this.numberOfConstraints = 0;
        //  Updated on AddConstraint; used to create AllConstraints
        this.numberOfVariables = 0;
        //  Also for speed, a separate list of Equality constraints (which we expect to be fairly rare).
        this.equalityConstraints = new Array();
        //  Also for speed, store variables -> constraint list while we load, then convert this into
        //  arrays when we call Solve().  The members are Array of constraints, and number of Left constraints.
        this.loadedVariablesAndConstraintLists = new Map();
        //  We bundle up the constraints first, so we can use Array rather than Array iteration for speed.
        //  To make the code cleaner (not having to check for NULL all over the place) use an empty Array/Array
        //  for Variables' constraint Lists/Arrays, and to help memory efficiency, use a single object.
        this.emptyConstraintList = new Array(0);
        //  For long-lived Variable objects
        //  For UpdateConstraint(), we want to buffer up the changes so variable values are not changed
        //  by doing an immediate Block.Split which updates the Block's ReferencePos.
        this.updatedConstraints = new Array();
        //  For caching violations to improve GetMaxViolatedConstraint performance.
        this.violationCache = new ViolationCache();
        this.violationCacheMinBlockCutoff = 0;
        this.nextVariableOrdinal = 0;
        //  May be overridden by the caller's Parameters object passed to Solve.
        this.solverParams = new Parameters();
        //  Solution results - will be cloned to return to caller.
        this.solverSolution = new Solution();
    }
    get IsQpsc() {
        return this.hasNeighbourPairs || this.solverParams.Advanced.ForceQpsc;
    }
    ///  Add a Variable (for example, wrapping a node on one axis of the graph) to the Solver.
    ///  <param name="userData">a tag or other user data - can be null</param>
    ///  <param name="desiredPos">The position of the variable, such as the coordinate of a node along one axis.</param>
    ///  <returns>The created variable</returns>
    AddVariableAN(userData, desiredPos) {
        return this.AddVariableANNN(userData, desiredPos, 1, 1);
    }
    ///  Add a Variable (for example, wrapping a node on one axis of the graph) to the Solver.
    ///  <param name="userData">a tag or other user data - can be null</param>
    ///  <param name="desiredPos">The position of the variable, such as the coordinate of a node along one axis.</param>
    ///  <param name="weight">The weight of the variable (makes it less likely to move if the weight is high).</param>
    ///  <returns></returns>
    AddVariableANN(userData, desiredPos, weight) {
        return this.AddVariableANNN(userData, desiredPos, weight, 1);
    }
    ///  Add a Variable (for example, wrapping a node on one axis of the graph) to the Solver.
    ///  <param name="userData">a tag or other user data - can be null</param>
    ///  <param name="desiredPos">The position of the variable, such as the coordinate of a node along one axis.</param>
    ///  <param name="weight">The weight of the variable (makes it less likely to move if the weight is high).</param>
    ///  <param name="scale">The scale of the variable, for improving convergence.</param>
    ///  <returns>The created variable</returns>
    AddVariableANNN(userData, desiredPos, weight, scale) {
        //  @@DCR "Incremental Solving": For now we disallow this; if we support it, we'll need to
        //  retain loadedVariablesAndConstraintLists, store up the added Variables (TryGetValue and if that fails add
        //  the existing variable, then iterate through variables with new Constraints and replace the arrays.
        //  Also remember to check for emptyConstraintList - don't add to it.
        if (!this.allConstraints.IsEmpty) {
            throw new Error('Cannot add Variables or Constraints once Solve() has been called');
        }
        const varNew = new Variable(this.nextVariableOrdinal++, userData, desiredPos, weight, scale);
        const block = new Block(varNew, this.allConstraints);
        varNew.Block = block;
        this.allBlocks.Add(block);
        this.numberOfVariables++;
        //  Initialize the variable in the dictionary with a null list and zero left constraints.
        this.loadedVariablesAndConstraintLists.set(varNew, new ConstraintListForVariable(new Array(), 0));
        return varNew;
    }
    //  end AddVariable()
    ///  Must be called before Solve() if the caller has updated variable Initial positions; this
    ///  reconciles internals such as Block.ReferencePos.
    UpdateVariables() {
        //  Although the name is "UpdateVariables", that's just for the caller to not need to know
        //  about the internals; this really is updating the blocks after the variables have already
        //  been updated one at a time. (This doesn't need to be called if constraints are re-gapped
        //  while variable positions are unchanged; Solve() checks for that).
        for (const block of this.allBlocks.Vector) {
            block.UpdateReferencePos();
        }
    }
    //  end UpdateVariables()
    ///  This enumerates all Variables created by AddVariable.
    get Variables() {
        return flatMap(this.allBlocks.Vector, (block) => block.Variables);
    }
    ///  The number of variables added to the Solver.
    get VariableCount() {
        return this.numberOfVariables;
    }
    ///  This enumerates all Constraints created by AddConstraint (which in turn may have
    ///  been called from OverlapRemoval.ConstraintGenerator.Generate()).
    *Constraints() {
        if (!this.allConstraints.IsEmpty) {
            //  Solve() has been called.
            for (const constraint of this.allConstraints.Vector) {
                yield constraint;
            }
        }
        else {
            //  Solve() has not yet been called.
            for (const variable of this.loadedVariablesAndConstraintLists.keys()) {
                const constraintsForVar = this.loadedVariablesAndConstraintLists.get(variable);
                if (null != constraintsForVar.Constraints) {
                    //  Return all variables in the LeftConstraints list for each variable.
                    const numConstraints = constraintsForVar.Constraints.length;
                    //  Cache for perf
                    for (let ii = 0; ii < numConstraints; ii++) {
                        const constraint = constraintsForVar.Constraints[ii];
                        if (variable == constraint.Left) {
                            yield;
                            return constraint;
                        }
                    }
                }
            }
        }
        //  endifelse (!AllConstraints.Empty)
    }
    //  end Constraints property
    ///  The number of constraints added to the Solver.
    get ConstraintCount() {
        return this.numberOfConstraints;
    }
    ///  Add a constraint 'left + gap' is equal to right
    ///  <param name="left"></param>
    ///  <param name="right"></param>
    ///  <param name="gap"></param>
    ///  <returns></returns>
    AddEqualityConstraint(left, right, gap) {
        return this.AddConstraintVVNB(left, right, gap, true);
    }
    ///  Add a constraint 'left + gap' is less than or equal to 'right'
    ///  <param name="left"></param>
    ///  <param name="right"></param>
    ///  <param name="gap">The gap required between the variables.</param>
    ///  <param name="isEquality"></param>
    ///  <returns>The new constraint.</returns>
    AddConstraintVVNB(left, right, gap, isEquality) {
        //  @@DCR "Incremental Solving": See notes in AddVariable; for now, this is disallowed.
        if (!this.allConstraints.IsEmpty) {
            throw new Error('Cannot add Variables or Constraints once Solve() has been called');
        }
        if (left == right) {
            throw new Error('Cannot add a constraint between a variable and itself');
        }
        //  Get the dictionary entries so we can store these until Solve() is called.  kvp.Key == lstConstraints,
        //  kvp.Value == number of constraints in lstConstraints that are LeftConstraints for the variable.
        //  kvpConstraintsForVar(Left|Right) are bidirectional for that variable, but we're operating only on
        //  varLeft's LeftConstraints and varRight's RightConstraints; this is slightly more complicated logic
        //  than just having two Lists, but for large numbers of variables, having all constraints in a single
        //  list is more memory-efficient.
        const constraintsForLeftVar = this.loadedVariablesAndConstraintLists.get(left);
        const constraintsForRightVar = this.loadedVariablesAndConstraintLists.get(right);
        //  Now create the new constraint and update the structures.  For varLeft, we must also update the
        //  left-variable count and that requires another lookup to update the structure in the Map
        //  since it's a value type so a copy was returned by-value from Map lookup.
        const constraint = Constraint.constructorVVNB(left, right, gap, isEquality);
        //  Structure update requires replacing the full structure.
        this.loadedVariablesAndConstraintLists.set(left, new ConstraintListForVariable(constraintsForLeftVar.Constraints, constraintsForLeftVar.NumberOfLeftConstraints + 1));
        constraintsForLeftVar.Constraints.push(constraint);
        constraintsForRightVar.Constraints.push(constraint);
        this.numberOfConstraints++;
        if (isEquality) {
            this.equalityConstraints.push(constraint);
        }
        return constraint;
    }
    ///  Add a constraint 'left + gap' is less than or equal to 'right'
    ///  <param name="left"></param>
    ///  <param name="right"></param>
    ///  <param name="gap">The gap required between the variables.</param>
    ///  <returns>The new constraint.</returns>
    AddConstraint(left, right, gap) {
        return this.AddConstraintVVNB(left, right, gap, false);
    }
    ///  Register an update to a constraint's gap; this defers the actual update until Solve() is called.
    ///  <param name="constraint">The constraint to update</param>
    ///  <param name="gap">The new gap</param>
    SetConstraintUpdate(constraint, gap) {
        //  Defer this to the Solve() call, so the variables' positions are not altered by doing a
        //  Block.Split here (which updates Block.ReferencePos, upon which Variable.(Scaled)ActualPos relies).
        if (gap != constraint.Gap) {
            this.updatedConstraints.push([constraint, gap]);
        }
    }
    ///  Add a pair of connected variables for goal functions of the form (x1-x2)^2.  These are
    ///  minimally satisfied, along with the default (x-i)^2 goal function, while also satisfying
    ///  all constraints.
    ///  <param name="variable1">The first variable</param>
    ///  <param name="variable2">The second variable</param>
    ///  <param name="relationshipWeight">The weight of the relationship</param>
    AddNeighborPair(variable1, variable2, relationshipWeight) {
        if (relationshipWeight <= 0 || Number.isNaN(relationshipWeight) || !Number.isFinite(relationshipWeight)) {
            throw new Error('relationshipWeight');
        }
        if (variable1 == variable2) {
            throw new Error();
        }
        variable1.AddNeighbor(variable2, relationshipWeight);
        variable2.AddNeighbor(variable1, relationshipWeight);
        this.hasNeighbourPairs = true;
    }
    //  end AddNeighborPair()
    ///  Sets Variable.ActualPos to the positions of the Variables that minimally satisfy the constraints
    ///  along this axis.  This overload uses default solution parameter values.
    ///  <returns>A Solution object.</returns>
    Solve() {
        return this.SolvePar(null);
    }
    ///  Sets Variable.ActualPos to the positions of the Variables that minimally satisfy the constraints
    ///  along this axis.  This overload takes a parameter specification.
    ///  <param name="solverParameters">Solution-generation options.</param>
    ///  <returns>The only failure condition is if there are one or more unsatisfiable constraints, such as cycles
    ///          or mutually exclusive equality constraints; if these are encountered, a list of lists of these
    ///          constraints is returned, where each list contains a single cycle, which may be of length one for
    ///          unsatisfiable equality constraints.  Otherwise, the return value is null.</returns>
    SolvePar(solverParameters) {
        if (solverParameters) {
            this.solverParams = solverParameters.Clone();
        }
        //  Reset some parameter defaults to per-solver-instance values.
        if (this.solverParams.OuterProjectIterationsLimit < 0) {
            //  If this came in 0, it stays that way, and there is no limit.  Otherwise, set it to a value
            //  reflecting the expectation of convergence roughly log-linearly in the number of variables.
            this.solverParams.OuterProjectIterationsLimit = 100 * (Math.log2(this.numberOfVariables) + 1);
        }
        if (this.solverParams.InnerProjectIterationsLimit < 0) {
            //  If this came in 0, it stays that way, and there is no limit.  Otherwise, assume that for
            //  any pass, each constraint may be violated (most likely this happens only on the first pass),
            //  and add some extra based upon constraint count.  Now that we split and retry on unsatisfied
            //  constraints, assume that any constraint may be seen twice on a pass.
            this.solverParams.InnerProjectIterationsLimit = this.numberOfConstraints * 2 + 100 * (Math.log2(this.numberOfConstraints) + 1);
        }
        //  ReSolving can be done for updated constraints.
        const isReSolve = !this.allConstraints.IsEmpty;
        this.CheckForUpdatedConstraints();
        this.solverSolution = new Solution();
        this.solverSolution.MinInnerProjectIterations = Number.MAX_VALUE;
        this.allConstraints.MaxConstraintTreeDepth = 0;
        this.allConstraints.SolverParameters = this.solverParams;
        //
        //  First set up all the  stuff we'll use for solutions.
        //
        //  If no constraints have been loaded, there's nothing to do.  Two distinct variables
        //  are required to create a constraint, so this also ensures a minimum number of variables.
        if (0 == this.numberOfConstraints) {
            //  For Qpsc, we may have neighbours but no constraints.
            if (!this.IsQpsc) {
                return this.solverSolution.Clone();
            }
        }
        else if (!isReSolve) {
            this.SetupConstraints();
        }
        //  This is the number of unsatisfiable constraints encountered.
        this.allConstraints.NumberOfUnsatisfiableConstraints = 0;
        //  Merge Equality constraints first.  These do not do any constraint-splitting, and thus
        //  remain in the same blocks, always satisfied, regardless of whether we're solving the full
        //  Qpsc or the simpler loop.
        this.MergeEqualityConstraints();
        //  Prepare for timeout checking.
        //
        //  Done with initial setup.  Now if we have neighbour pairs, we do the full SolveQpsc logic
        //  complete with Gradient projection.  Otherwise, we have a much simpler Project/Split loop.
        //
        if (this.IsQpsc) {
            this.SolveQpsc();
        }
        else {
            this.SolveByStandaloneProject();
            this.CalculateStandaloneProjectGoalFunctionValue();
        }
        //  We initialized this to int.MaxValue so make sure it's sane if we didn't complete a Project iteration.
        if (this.solverSolution.MinInnerProjectIterations > this.solverSolution.MaxInnerProjectIterations) {
            //  Probably this is 0.
            this.solverSolution.MinInnerProjectIterations = this.solverSolution.MaxInnerProjectIterations;
        }
        //  Done.  Caller will copy each var.ActualPos back to the Nodes.  If we had any unsatisfiable
        //  constraints, copy them back out to the caller.
        this.solverSolution.NumberOfUnsatisfiableConstraints = this.allConstraints.NumberOfUnsatisfiableConstraints;
        this.solverSolution.MaxConstraintTreeDepth = this.allConstraints.MaxConstraintTreeDepth;
        return this.solverSolution.Clone();
    }
    //  end Solve()
    CheckForUpdatedConstraints() {
        if (0 == this.updatedConstraints.length) {
            return;
        }
        /*Assert.assert(
          !this.allConstraints.IsEmpty,
          'Cannot have updated constraints if AllConstraints is empty.',
        )*/
        //  For Qpsc, all Block.ReferencePos values are based upon Variable.DesiredPos values, and the latter
        //  have been restored from what they were on the last Qpsc iteration to their initial values).
        let mustReinitializeBlocks = this.IsQpsc;
        for (const [key, value] of this.updatedConstraints) {
            //  Update the constraint, then split its block if it's active, so the next call to Solve()
            //  will start the merge/split cycle again.
            const constraint = key;
            constraint.UpdateGap(value);
            if (!mustReinitializeBlocks && !constraint.IsEquality) {
                this.SplitOnConstraintIfActive(constraint);
                continue;
            }
            //  Equality constraints must always be evaluated first and never split.
            //  If we have updated one we must reinitialize the block structure.
            mustReinitializeBlocks = true;
        }
        this.updatedConstraints = [];
        if (mustReinitializeBlocks) {
            this.ReinitializeBlocks();
        }
    }
    SplitOnConstraintIfActive(constraint) {
        if (constraint.IsActive) {
            //  Similar handling as in SplitBlocks, except that we know which constraint we're splitting on.
            const newSplitBlock = constraint.Left.Block.SplitOnConstraint(constraint);
            if (null != newSplitBlock) {
                this.allBlocks.Add(newSplitBlock);
            }
        }
        //  endif constraint.IsActive
    }
    SetupConstraints() {
        //  Optimize the lookup in SearchAllConstraints; create an array (which has faster
        //  iteration than Array).
        this.allConstraints.Create(this.numberOfConstraints);
        for (const variable of this.loadedVariablesAndConstraintLists.keys()) {
            const constraintsForVar = this.loadedVariablesAndConstraintLists.get(variable);
            const constraints = constraintsForVar.Constraints;
            let numAllConstraints = 0;
            let numLeftConstraints = 0;
            let numRightConstraints = 0;
            if (null != constraints) {
                numAllConstraints = constraints.length;
                numLeftConstraints = constraintsForVar.NumberOfLeftConstraints;
                numRightConstraints = numAllConstraints - numLeftConstraints;
            }
            //  Create the Variable's Constraint arrays, using the single emptyConstraintList for efficiency.
            let leftConstraints = this.emptyConstraintList;
            if (0 != numLeftConstraints) {
                leftConstraints = new Array(numLeftConstraints);
            }
            let rightConstraints = this.emptyConstraintList;
            if (0 != numRightConstraints) {
                rightConstraints = new Array(numRightConstraints);
            }
            variable.SetConstraints(leftConstraints, rightConstraints);
            //  Now load the Variables' Arrays.  We're done with the loadedVariablesAndConstraintLists lists after this.
            let leftConstraintIndex = 0;
            let rightConstraintIndex = 0;
            for (let loadedConstraintIndex = 0; loadedConstraintIndex < numAllConstraints; loadedConstraintIndex++) {
                //  numAllConstraints is 0 if null == constraints.
                //  ReSharper disable PossibleNullReferenceException
                const loadedConstraint = constraints[loadedConstraintIndex];
                //  ReSharper restore PossibleNullReferenceException
                if (variable == loadedConstraint.Left) {
                    leftConstraints[leftConstraintIndex++] = loadedConstraint;
                }
                else {
                    rightConstraints[rightConstraintIndex++] = loadedConstraint;
                }
            }
            /*Assert.assert(
              leftConstraintIndex == numLeftConstraints,
              'leftConstraintIndex must == numLeftConstraints',
            )*/
            /*Assert.assert(
              rightConstraintIndex == numRightConstraints,
              'rightConstraintIndex must == numRightConstraints',
            )*/
            //  Done with per-variable constraint loading.  Now load the big list of all constraints.
            //  All constraints are stored in a LeftConstraints array (and duplicated in a RightConstraints
            //  array), so just load the LeftConstraints into AllConstraints. Array.Foreach is optimized.
            for (const constraint of variable.LeftConstraints) {
                this.allConstraints.Add(constraint);
            }
        }
        // this.allConstraints.Debug_AssertIsFull()
        //  Done with the dictionary now.
        this.loadedVariablesAndConstraintLists.clear();
        //  If we don't have many blocks then the caching optimization's overhead may outweigh
        //  its benefit. Similarly, after blocks have merged past a certain point it's faster to
        //  just enumerate them all.  Initialize this to off.
        this.violationCacheMinBlockCutoff = Number.MAX_VALUE;
        if (this.solverParams.Advanced.UseViolationCache && this.solverParams.Advanced.ViolationCacheMinBlocksDivisor > 0) {
            this.violationCacheMinBlockCutoff = Math.min(this.allBlocks.Count / this.solverParams.Advanced.ViolationCacheMinBlocksDivisor, this.solverParams.Advanced.ViolationCacheMinBlocksCount);
        }
    }
    SolveByStandaloneProject() {
        //  Loop until we have no constraints with violations and no blocks are split.
        //  Note:  this functions differently from the loop-termination test in SolveQpsc, which tests the
        //  total movement resulting from Project() against some epsilon.  We do this differently here because
        //  we're not doing the Gradient portion of SolveQpsc, so we'll just keep going as long as we have any
        //  violations greater than the minimum violation we look for in GetMaxViolatedConstraint (and as long
        //  as we don't split any blocks whether or not we find such a violation).
        for (;;) {
            //  Don't check the return of Project; defer the termination check to SplitBlocks.
            //  This also examines limits post-Project; because it happens pre-SplitBlocks it ensures
            //  a feasible stopping state.
            let violationsFound;
            if (!this.RunProject(/* out */ violationsFound)) {
                return;
            }
            //  If SplitBlocks doesn't find anything to split then Project would do nothing.
            if (!this.SplitBlocks()) {
                break;
            }
        }
    }
    RunProject(/* out */ violationsFound) {
        this.solverSolution.OuterProjectIterations++;
        violationsFound = this.Project();
        //  Examine limits post-Project but pre-SplitBlocks to ensure a feasible stopping state.
        return !this.CheckForLimitsExceeded();
    }
    CheckForLimitsExceeded() {
        // if (null != this.timeoutStopwatch) {
        //   if (
        //     this.timeoutStopwatch.ElapsedMilliseconds >= this.solverParams.TimeLimit
        //   ) {
        //     this.solverSolution.TimeLimitExceeded = true
        //     return true
        //   }
        // }
        if (this.solverParams.OuterProjectIterationsLimit > 0) {
            if (this.solverSolution.OuterProjectIterations >= this.solverParams.OuterProjectIterationsLimit) {
                this.solverSolution.OuterProjectIterationsLimitExceeded = true;
                return true;
            }
        }
        if (this.solverSolution.InnerProjectIterationsLimitExceeded) {
            return true;
        }
        return false;
    }
    CalculateStandaloneProjectGoalFunctionValue() {
        //  Fill in the non-Qpsc Goal function value.  See Qpsc.HasConverged for details; this is a
        //  streamlined form of (x'Ax)/2 + bx here, where A has only the diagonals (as there are no
        //  neighbours) with 2*wi and b is a vector of -2*wi*di, and x is current position.
        this.solverSolution.GoalFunctionValue = 0;
        const numBlocks = this.allBlocks.Count;
        //  cache for perf
        for (let ii = 0; ii < numBlocks; ii++) {
            const block = this.allBlocks.item(ii);
            const numVars = block.Variables.length;
            for (let jj = 0; jj < numVars; jj++) {
                const variable = block.Variables[jj];
                //  (x'Ax)/2
                this.solverSolution.GoalFunctionValue =
                    this.solverSolution.GoalFunctionValue + variable.Weight * (variable.ActualPos * variable.ActualPos);
                //  +bx
                this.solverSolution.GoalFunctionValue =
                    this.solverSolution.GoalFunctionValue - 2 * (variable.Weight * (variable.DesiredPos * variable.ActualPos));
            }
        }
    }
    //  Implements the full solve_QPSC from the Ipsep_Cola and Scaling papers.
    SolveQpsc() {
        this.solverSolution.AlgorithmUsed = this.solverParams.Advanced.ScaleInQpsc
            ? SolverAlgorithm.QpscWithScaling
            : SolverAlgorithm.QpscWithoutScaling;
        if (!this.QpscMakeFeasible()) {
            return;
        }
        //  Initialize the Qpsc state, which also sets the scale for all variables (if we are scaling).
        const qpsc = new Qpsc(this.solverParams, this.numberOfVariables);
        for (const block of this.allBlocks.Vector) {
            for (const variable of block.Variables) {
                qpsc.AddVariable(variable);
            }
        }
        qpsc.VariablesComplete();
        this.ReinitializeBlocks();
        this.MergeEqualityConstraints();
        // this.VerifyConstraintsAreFeasible()
        //  Iterations
        let foundSplit = false;
        const foundViolation = false;
        for (;;) {
            //
            //  Calculate initial step movement.  We assume there will be some movement needed
            //  even on the first pass in the vast majority of cases.  This also tests convergence
            //  of the goal-function value; if it is sufficiently close to the previous iteration's
            //  result and the previous iteration did not split or encounter a violation, we're done.
            //
            if (!qpsc.PreProject() && !foundSplit && !foundViolation) {
                break;
            }
            //
            //  Split the blocks (if this the first time through the loop then all variables are in their
            //  own block except for any equality constraints, which we don't split; but we still need to
            //  have UpdateReferencePos called).
            //
            foundSplit = this.SplitBlocks();
            //  Examine limits post-Project to ensure a feasible stopping state.  We don't test for
            //  termination due to "no violations found" here, deferring that to the next iteration's PreProject().
            if (!this.RunProject(/* out */ foundViolation)) {
                break;
            }
            //
            //  Calculate the new adjustment to the current positions based upon the amount of movement
            //  done by split/project.  If this returns false then it means that movement was zero and
            //  we're done if there was no split or constraint violation.
            //
            if (!qpsc.PostProject() && !foundSplit && !foundViolation) {
                break;
            }
        }
        //  end forever
        this.solverSolution.GoalFunctionValue = qpsc.QpscComplete();
    }
    QpscMakeFeasible() {
        //  Start off with one Project pass so the initial Qpsc state is feasible (not in violation
        //  of constraints).  If this takes more than the max allowable time, we're done.
        let foundViolation;
        return this.RunProject(/* out */ foundViolation);
    }
    ReinitializeBlocks() {
        //  For Qpsc we want to discard the previous block structure, because it did not consider
        //  neighbors, and the gradient may want to pull things in an entirely different way.
        //  We must also do this for a re-Solve that updated the gap of an equality constraint.
        const oldBlocks = Array.from(this.allBlocks.Vector);
        this.allBlocks.Vector = [];
        for (const oldBlock of oldBlocks) {
            for (const variable of oldBlock.Variables) {
                variable.Reinitialize();
                const newBlock = new Block(variable, this.allConstraints);
                this.allBlocks.Add(newBlock);
            }
        }
        this.allConstraints.Reinitialize();
        this.violationCache.Clear();
    }
    MergeEqualityConstraints() {
        //  PerfNote: We only call this routine once so don't worry about Array-Enumerator overhead.
        for (const constraint of this.equalityConstraints) {
            if (constraint.Left.Block == constraint.Right.Block) {
                //  They are already in the same block and we are here on the first pass that merges blocks
                //  containing only equality constraints.  Thus we know that there is already a chain of equality
                //  constraints joining constraint.Left and constraint.Right, and that chain will always be
                //  moved as a unit because we never split or expand equality constraints, so this constraint
                //  will remain retain its current satisfied state and does not need to be activated (which
                //  would potentially lead to cycles; this is consistent with the non-equality constraint
                //  approach of not activating constraints that are not violated).
                if (Math.abs(constraint.Violation) > this.solverParams.GapTolerance) {
                    //  This is an equivalence conflict, such as a + 3 == b; b + 3 == c; a + 9 == c.
                    constraint.IsUnsatisfiable = true;
                    this.allConstraints.NumberOfUnsatisfiableConstraints++;
                }
                continue;
            }
            this.MergeBlocks(constraint);
        }
    }
    Project() {
        if (this.numberOfConstraints == 0) {
            //  We are here for the neighbours-only case.
            return false;
        }
        //  Get the maximum violation (the Constraint with the biggest difference between the
        //  required gap between its two variables vs. their actual relative positions).
        //  If there is no violation, we're done (although SplitBlocks may change things so
        //  we have to go again).
        this.violationCache.Clear();
        this.lastModifiedBlock = null;
        let useViolationCache = this.allBlocks.Count > this.violationCacheMinBlockCutoff;
        //  The first iteration gets the first violated constraint.
        let cIterations = 1;
        const t = { maxViolation: 0 };
        let maxViolatedConstraint = this.GetMaxViolatedConstraint(t, useViolationCache);
        if (!maxViolatedConstraint) {
            return false;
        }
        //  We have at least one violation, so process them until there are no more.
        while (maxViolatedConstraint) {
            /*Assert.assert(
              !maxViolatedConstraint.IsUnsatisfiable,
              'maxViolatedConstraint should not be unsatisfiable',
            )*/
            /*Assert.assert(
              !maxViolatedConstraint.IsEquality,
              'maxViolatedConstraint should not be equality',
            )*/
            //  Perf note: Variables (and Blocks) use the default Object.Equals implementation, which is
            //  simply ReferenceEquals for reference types.
            if (maxViolatedConstraint.Left.Block == maxViolatedConstraint.Right.Block) {
                maxViolatedConstraint.Left.Block.Expand(maxViolatedConstraint);
                if (maxViolatedConstraint.IsUnsatisfiable) {
                    this.violationCache.Clear();
                    //  We're confusing the lineage of lastModifiedBlock
                }
                this.lastModifiedBlock = maxViolatedConstraint.Left.Block;
            }
            else {
                //  The variables are in different blocks so merge the blocks.
                this.lastModifiedBlock = this.MergeBlocks(maxViolatedConstraint);
            }
            //  Note that aborting here does not guarantee a feasible state.
            if (this.solverParams.InnerProjectIterationsLimit > 0) {
                if (cIterations >= this.solverParams.InnerProjectIterationsLimit) {
                    this.solverSolution.InnerProjectIterationsLimitExceeded = true;
                    break;
                }
            }
            //  Now we've potentially changed one or many variables' positions so recalculate the max violation.
            useViolationCache = this.allBlocks.Count > this.violationCacheMinBlockCutoff;
            if (!useViolationCache) {
                this.violationCache.Clear();
            }
            cIterations++;
            const t = { maxViolation: 0 };
            maxViolatedConstraint = this.GetMaxViolatedConstraint(t, useViolationCache);
        }
        //  endwhile violations exist
        this.solverSolution.InnerProjectIterationsTotal = this.solverSolution.InnerProjectIterationsTotal + cIterations;
        if (this.solverSolution.MaxInnerProjectIterations < cIterations) {
            this.solverSolution.MaxInnerProjectIterations = cIterations;
        }
        if (this.solverSolution.MinInnerProjectIterations > cIterations) {
            this.solverSolution.MinInnerProjectIterations = cIterations;
        }
        //  If we got here, we had at least one violation.
        // this.allConstraints.Debug_AssertConsistency()
        return true;
    }
    //  end Project()
    MergeBlocks(violatedConstraint) {
        //  Start off evaluating left-to-right.
        let blockTo = violatedConstraint.Left.Block;
        let blockFrom = violatedConstraint.Right.Block;
        /*Assert.assert(
          blockTo != blockFrom,
          'Merging of constraints in the same block is not allowed',
        )*/
        //  The violation amount is the needed distance to move to tightly satisfy the constraint.
        //  Calculate this based on offsets even though the vars are in different blocks; we'll normalize
        //  that when we recalculate the block reference position and the offsets in the Right block.
        let distance = violatedConstraint.Left.OffsetInBlock + (violatedConstraint.Gap - violatedConstraint.Right.OffsetInBlock);
        if (blockFrom.Variables.length > blockTo.Variables.length) {
            //  Reverse this so we minimize variable movement by moving stuff from the block with the least
            //  number of vars into the block with the greater number.
            blockTo = violatedConstraint.Right.Block;
            blockFrom = violatedConstraint.Left.Block;
            distance = distance * -1;
        }
        //  Move all vars from blockFrom to blockTo, and adjust their offsets by dist as
        //  mentioned above.  This has the side-effect of moving the associated active constraints
        //  as well (because they are carried in the variables' LeftConstraints); violatedConstraint
        //  is therefore also moved if it was in blockFrom.
        const numVars = blockFrom.Variables.length;
        //  iteration is faster than foreach for Array<>s
        for (let ii = 0; ii < numVars; ii++) {
            const variable = blockFrom.Variables[ii];
            variable.OffsetInBlock = variable.OffsetInBlock + distance;
            blockTo.AddVariable(variable);
        }
        blockTo.UpdateReferencePosFromSums();
        //blockTo.DebugVerifyReferencePos()
        //  Do any final bookkeeping necessary.
        //  blockTo.Debug_PostMerge(blockFrom)
        //  Make the (no-longer-) violated constraint active.
        this.allConstraints.ActivateConstraint(violatedConstraint);
        //  We have no further use for blockFrom as nobody references it.
        this.allBlocks.Remove(blockFrom);
        return blockTo;
    }
    //  end MergeBlocks()
    SplitBlocks() {
        //  First enumerate all blocks and accumulate any new ones that we form by splitting off
        //  from an existing block.  Then add those to our block list in a second pass (to avoid
        //  a "collection modified during enumeration" exception).
        const newBlocks = new Array();
        const numBlocks = this.allBlocks.Count;
        //  Cache for perf
        for (let ii = 0; ii < numBlocks; ii++) {
            const block = this.allBlocks.item(ii);
            /*Assert.assert(
              0 != block.Variables.length,
              'block must have nonzero variable count',
            )*/
            const newSplitBlock = block.Split(this.IsQpsc);
            if (null != newSplitBlock) {
                newBlocks.push(newSplitBlock);
            }
        }
        const numNewBlocks = newBlocks.length;
        //  cache for perf
        for (let ii = 0; ii < numNewBlocks; ii++) {
            const block = newBlocks[ii];
            this.allBlocks.Add(block);
        }
        //  The paper uses "did not split" for the return but "did split" seems more intuitive
        return 0 != newBlocks.length;
    }
    //  end SplitBlocks
    GetMaxViolatedConstraint(t, useViolationCache) {
        //  Get the most-violated constraint in the Solver.  Active constraints are calculated
        //  to keep their constraint minimally satisfied, so any nonzero active-constraint
        //  violation is due to rounding error; therefore just look for inactive constraints.
        //  Pass maxViolation to subroutines because it is initialized to a limiting value.
        t.maxViolation = this.solverParams.GapTolerance;
        const maxViolatedConstraint = this.SearchViolationCache(t.maxViolation);
        if (null != maxViolatedConstraint) {
            return maxViolatedConstraint;
        }
        //  Nothing in ViolationCache or we've got too many Constraints in the block, so search
        //  the list of all constraints.
        return this.SearchAllConstraints(t.maxViolation, useViolationCache);
    }
    //  end GetMaxViolatedConstraint()
    SearchViolationCache(maxViolation) {
        //  If we have any previously cached max violated constraints, then we'll first remove any
        //  that are incoming to or outgoing from the lastModifiedBlock on the current Project()
        //  iteration; these constraints are the only ones that may have changed violation values
        //  (due to block expansion or merging).  If any of the cached maxvio constraints remain after
        //  that, then we can use the largest of these if it's larger than any constraints in lastModifiedBlock.
        //  Even if no cached violations remain after filtering, we still know that the largest violations were
        //  most likely associated with lastModifiedBlock.  So we take a pass through lastModifiedBlock and put
        //  its top constraints into the cache and then take the largest constraint from the violation cache,
        //  which may or may not be associated with lastModifiedBlock.  (This would happen after filling the
        //  cache from multiple blocks in the first pass, or after Block.Split moved some variables (with
        //  cached inactive constraints) to the new block).
        //
        //  This iteration is slower (relative to the number of constraints in the block) than
        //  SearchAllConstraints, due to two loops, so only do it if the block has a sufficiently small
        //  number of constraints.  Use the Variables as a proxy for the constraint count of the block.
        //  @@PERF: the block could keep a constraint count to make ViolationCache cutoff more accurate.
        let maxViolatedConstraint = null;
        if (this.lastModifiedBlock == null)
            return;
        if (this.lastModifiedBlock.Variables.length < this.numberOfVariables + 1 && this.violationCache.FilterBlock(this.lastModifiedBlock)) {
            //  Also removes unsatisfiables
        }
        //  First evaluate all (inactive) outgoing constraints for all variables in the block; this gets
        //  both all intra-block constraints and all inter-block constraints where the lastModifiedBlock
        //  is the source.  Then evaluate incoming constraints where the source is outside the block.
        const numVarsInBlock = this.lastModifiedBlock.Variables.length;
        //  cache for perf
        for (let variableIndex = 0; variableIndex < numVarsInBlock; variableIndex++) {
            const variable = this.lastModifiedBlock.Variables[variableIndex];
            for (const constraint of variable.LeftConstraints) {
                if (!constraint.IsActive && !constraint.IsUnsatisfiable) {
                    const violation = constraint.Left.ActualPos * constraint.Left.Scale + (constraint.Gap - constraint.Right.ActualPos * constraint.Right.Scale);
                    /*Assert.assert(
                      closeDistEps(constraint.Violation, violation),
                      'LeftConstraints: constraint.Violation must == violation',
                    )*/
                    if (greaterDistEps(violation, maxViolation)) {
                        //  Cache the previous high violation.  Pass the violation as a tiny perf optimization
                        //  to save re-doing the double operations in this inner loop.
                        if (null != maxViolatedConstraint && maxViolation > this.violationCache.LowViolation) {
                            this.violationCache.Insert(maxViolatedConstraint, maxViolation);
                        }
                        maxViolation = constraint.Violation;
                        maxViolatedConstraint = constraint;
                    }
                }
            }
            //  endfor each LeftConstraint
            for (const constraint of variable.RightConstraints) {
                if (!constraint.IsActive && !constraint.IsUnsatisfiable && constraint.Left.Block != this.lastModifiedBlock) {
                    const violation = constraint.Left.ActualPos * constraint.Left.Scale + (constraint.Gap - constraint.Right.ActualPos * constraint.Right.Scale);
                    // Assert.assert(constraint.Violation == violation, "LeftConstraints: constraint.Violation must == violation");
                    /*Assert.assert(
                      closeDistEps(constraint.Violation, violation),
                      'LeftConstraints: constraint.Violation must == violation',
                    )*/
                    // if (violation > maxViolation)
                    if (greaterDistEps(violation, maxViolation)) {
                        if (null != maxViolatedConstraint && maxViolation > this.violationCache.LowViolation) {
                            this.violationCache.Insert(maxViolatedConstraint, maxViolation);
                        }
                        maxViolation = violation;
                        maxViolatedConstraint = constraint;
                    }
                }
            }
            //  endfor each RightConstraint
        }
        //  endfor each var in lastModifiedBlock.Variables
        //  Now see if any of the cached maxvios are greater than we have now.  Don't remove
        //  it here; we'll wait until Expand/Merge set lastModifiedBlock and then the removal
        //  occurs above in ViolationCache.FilterBlock in this block when we come back in.
        const cachedConstraint = this.violationCache.FindIfGreater(maxViolation);
        if (null != cachedConstraint) {
            //  The cache had something more violated than maxViolatedConstraint, but maxViolatedConstraint
            //  may be larger than at least one cache element.
            if (null != maxViolatedConstraint && maxViolation > this.violationCache.LowViolation) {
                this.violationCache.Insert(maxViolatedConstraint, maxViolation);
            }
            maxViolatedConstraint = cachedConstraint;
        }
        //  endif FilterBlock
        return maxViolatedConstraint;
        //  Remains null if we don't find one
    }
    SearchAllConstraints(maxViolation, useViolationCache) {
        //  Iterate all constraints, finding the most-violated and populating the violation cache
        //  with the next-highest violations.
        let maxViolatedConstraint = null;
        this.violationCache.Clear();
        for (const constraint of this.allConstraints.Vector) {
            //  The constraint vector is now organized with all inactive constraints first.
            if (constraint.IsActive) {
                break;
            }
            if (constraint.IsUnsatisfiable) {
                continue;
            }
            //  Note:  The docs have >= 0 for violation condition but it should be just > 0.
            const violation = constraint.Left.ActualPos * constraint.Left.Scale + (constraint.Gap - constraint.Right.ActualPos * constraint.Right.Scale);
            /*Assert.assert(
              closeDistEps(constraint.Violation, violation),
              'constraint.Violation must == violation',
            )*/
            let cacheInsertConstraint = null;
            let cacheInsertViolation = 0;
            if (greaterDistEps(violation, maxViolation)) {
                if (maxViolation > this.violationCache.LowViolation) {
                    cacheInsertConstraint = maxViolatedConstraint;
                    cacheInsertViolation = maxViolation;
                }
                maxViolation = violation;
                maxViolatedConstraint = constraint;
            }
            if (useViolationCache) {
                //  If constraint was a violation but not > maxViolation, then we'll look to insert it into the cache.
                //  (We already know that if the previous maxViolatedConstraint is to be inserted, then its violation is
                //  greater than any in the cache).  On the first iteration of "for each constraint", maxViolatedConstraint
                //  is null, hence the constraint != maxViolatedConstraint test.
                if (cacheInsertConstraint == null &&
                    constraint != maxViolatedConstraint &&
                    (!this.violationCache.IsFull || violation > this.violationCache.LowViolation)) {
                    //  Either the cache isn't full or the new constraint is more violated than the lowest cached constraint.
                    cacheInsertConstraint = constraint;
                    cacheInsertViolation = violation;
                }
                if (null != cacheInsertConstraint && cacheInsertViolation > this.violationCache.LowViolation) {
                    this.violationCache.Insert(cacheInsertConstraint, cacheInsertViolation);
                }
            }
            //  endif useViolationCache
        }
        //  endfor each constraint
        return maxViolatedConstraint;
        //  Remains null if we don't find one
    }
}
//# sourceMappingURL=Solver.js.map