2013-07-28 22:08:34 +00:00
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Once we've written our constraint equations in the symbolic algebra system,
|
|
|
|
// these routines linearize them, and solve by a modified Newton's method.
|
|
|
|
// This also contains the routines to detect non-convergence or inconsistency,
|
|
|
|
// and report diagnostics to the user.
|
|
|
|
//
|
|
|
|
// Copyright 2008-2013 Jonathan Westhues.
|
|
|
|
//-----------------------------------------------------------------------------
|
2008-04-20 11:35:10 +00:00
|
|
|
#include "solvespace.h"
|
|
|
|
|
2010-01-18 10:28:47 +00:00
|
|
|
// This tolerance is used to determine whether two (linearized) constraints
|
|
|
|
// are linearly dependent. If this is too small, then we will attempt to
|
|
|
|
// solve truly inconsistent systems and fail. But if it's too large, then
|
|
|
|
// we will give up on legitimate systems like a skinny right angle triangle by
|
|
|
|
// its hypotenuse and long side.
|
2008-07-02 08:18:25 +00:00
|
|
|
const double System::RANK_MAG_TOLERANCE = 1e-4;
|
2010-01-18 10:28:47 +00:00
|
|
|
|
|
|
|
// The solver will converge all unknowns to within this tolerance. This must
|
|
|
|
// always be much less than LENGTH_EPS, and in practice should be much less.
|
|
|
|
const double System::CONVERGE_TOLERANCE = (LENGTH_EPS/(1e2));
|
2008-07-02 08:18:25 +00:00
|
|
|
|
2009-04-19 20:37:51 +00:00
|
|
|
bool System::WriteJacobian(int tag) {
|
2008-04-20 11:35:10 +00:00
|
|
|
int a, i, j;
|
|
|
|
|
|
|
|
j = 0;
|
|
|
|
for(a = 0; a < param.n; a++) {
|
2009-04-19 20:37:51 +00:00
|
|
|
if(j >= MAX_UNKNOWNS) return false;
|
|
|
|
|
2008-04-20 11:35:10 +00:00
|
|
|
Param *p = &(param.elem[a]);
|
2008-06-26 09:34:26 +00:00
|
|
|
if(p->tag != tag) continue;
|
2008-04-21 01:26:36 +00:00
|
|
|
mat.param[j] = p->h;
|
2008-04-20 11:35:10 +00:00
|
|
|
j++;
|
|
|
|
}
|
2008-04-21 01:26:36 +00:00
|
|
|
mat.n = j;
|
2008-04-20 11:35:10 +00:00
|
|
|
|
|
|
|
i = 0;
|
|
|
|
for(a = 0; a < eq.n; a++) {
|
2009-04-19 20:37:51 +00:00
|
|
|
if(i >= MAX_UNKNOWNS) return false;
|
|
|
|
|
2008-04-20 11:35:10 +00:00
|
|
|
Equation *e = &(eq.elem[a]);
|
2008-06-26 09:34:26 +00:00
|
|
|
if(e->tag != tag) continue;
|
2008-04-20 11:35:10 +00:00
|
|
|
|
2008-04-21 08:16:38 +00:00
|
|
|
mat.eq[i] = e->h;
|
2009-04-19 05:53:16 +00:00
|
|
|
Expr *f = e->e->DeepCopyWithParamsAsPointers(¶m, &(SK.param));
|
2008-04-30 04:52:34 +00:00
|
|
|
f = f->FoldConstants();
|
|
|
|
|
2009-04-19 03:55:46 +00:00
|
|
|
// Hash table (61 bits) to accelerate generation of zero partials.
|
Use C99 integer types and C++ boolean types/values
This change comprehensively replaces the use of Microsoft-standard integer
and boolean types with their C99/C++ standard equivalents, as the latter is
more appropriate for a cross-platform application. With matter-of-course
exceptions in the Win32-specific code, the types/values have been converted
as follows:
QWORD --> uint64_t
SQWORD --> int64_t
DWORD --> uint32_t
SDWORD --> int32_t
WORD --> uint16_t
SWORD --> int16_t
BYTE --> uint8_t
BOOL --> bool
TRUE --> true
FALSE --> false
The following related changes are also included:
* Added C99 integer type definitions for Windows, as stdint.h is not
available prior to Visual Studio 2010
* Changed types of some variables in the SolveSpace class from 'int' to
'bool', as they actually represent boolean settings
* Implemented new Cnf{Freeze,Thaw}Bool() functions to support boolean
variables in the Registry
* Cnf{Freeze,Thaw}DWORD() are now Cnf{Freeze,Thaw}Int()
* TtfFont::Get{WORD,DWORD}() are now TtfFont::Get{USHORT,ULONG}() (names
inspired by the OpenType spec)
* RGB colors are packed into an integer of type uint32_t (nee DWORD), but
in a few places, these were represented by an int; these have been
corrected to uint32_t
2013-10-02 05:45:13 +00:00
|
|
|
uint64_t scoreboard = f->ParamsUsed();
|
2008-04-21 01:26:36 +00:00
|
|
|
for(j = 0; j < mat.n; j++) {
|
2008-04-30 04:52:34 +00:00
|
|
|
Expr *pd;
|
Use C99 integer types and C++ boolean types/values
This change comprehensively replaces the use of Microsoft-standard integer
and boolean types with their C99/C++ standard equivalents, as the latter is
more appropriate for a cross-platform application. With matter-of-course
exceptions in the Win32-specific code, the types/values have been converted
as follows:
QWORD --> uint64_t
SQWORD --> int64_t
DWORD --> uint32_t
SDWORD --> int32_t
WORD --> uint16_t
SWORD --> int16_t
BYTE --> uint8_t
BOOL --> bool
TRUE --> true
FALSE --> false
The following related changes are also included:
* Added C99 integer type definitions for Windows, as stdint.h is not
available prior to Visual Studio 2010
* Changed types of some variables in the SolveSpace class from 'int' to
'bool', as they actually represent boolean settings
* Implemented new Cnf{Freeze,Thaw}Bool() functions to support boolean
variables in the Registry
* Cnf{Freeze,Thaw}DWORD() are now Cnf{Freeze,Thaw}Int()
* TtfFont::Get{WORD,DWORD}() are now TtfFont::Get{USHORT,ULONG}() (names
inspired by the OpenType spec)
* RGB colors are packed into an integer of type uint32_t (nee DWORD), but
in a few places, these were represented by an int; these have been
corrected to uint32_t
2013-10-02 05:45:13 +00:00
|
|
|
if(scoreboard & ((uint64_t)1 << (mat.param[j].v % 61)) &&
|
2009-04-19 03:55:46 +00:00
|
|
|
f->DependsOn(mat.param[j]))
|
|
|
|
{
|
2008-04-30 04:52:34 +00:00
|
|
|
pd = f->PartialWrt(mat.param[j]);
|
|
|
|
pd = pd->FoldConstants();
|
2009-04-19 05:53:16 +00:00
|
|
|
pd = pd->DeepCopyWithParamsAsPointers(¶m, &(SK.param));
|
2008-04-30 04:52:34 +00:00
|
|
|
} else {
|
2008-06-01 08:45:11 +00:00
|
|
|
pd = Expr::From(0.0);
|
2008-04-30 04:52:34 +00:00
|
|
|
}
|
|
|
|
mat.A.sym[i][j] = pd;
|
2008-04-20 11:35:10 +00:00
|
|
|
}
|
2008-04-30 04:52:34 +00:00
|
|
|
mat.B.sym[i] = f;
|
2008-04-20 11:35:10 +00:00
|
|
|
i++;
|
|
|
|
}
|
2008-04-21 01:26:36 +00:00
|
|
|
mat.m = i;
|
2009-04-19 20:37:51 +00:00
|
|
|
|
|
|
|
return true;
|
2008-04-20 11:35:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void System::EvalJacobian(void) {
|
|
|
|
int i, j;
|
2008-04-21 01:26:36 +00:00
|
|
|
for(i = 0; i < mat.m; i++) {
|
|
|
|
for(j = 0; j < mat.n; j++) {
|
|
|
|
mat.A.num[i][j] = (mat.A.sym[i][j])->Eval();
|
2008-04-20 11:35:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-05-07 07:10:20 +00:00
|
|
|
bool System::IsDragged(hParam p) {
|
2009-11-03 18:54:49 +00:00
|
|
|
hParam *pp;
|
|
|
|
for(pp = dragged.First(); pp; pp = dragged.NextAfter(pp)) {
|
|
|
|
if(p.v == pp->v) return true;
|
2008-05-09 05:33:23 +00:00
|
|
|
}
|
2008-05-07 07:10:20 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void System::SolveBySubstitution(void) {
|
|
|
|
int i;
|
|
|
|
for(i = 0; i < eq.n; i++) {
|
|
|
|
Equation *teq = &(eq.elem[i]);
|
|
|
|
Expr *tex = teq->e;
|
|
|
|
|
|
|
|
if(tex->op == Expr::MINUS &&
|
|
|
|
tex->a->op == Expr::PARAM &&
|
|
|
|
tex->b->op == Expr::PARAM)
|
|
|
|
{
|
|
|
|
hParam a = (tex->a)->x.parh;
|
|
|
|
hParam b = (tex->b)->x.parh;
|
|
|
|
if(!(param.FindByIdNoOops(a) && param.FindByIdNoOops(b))) {
|
|
|
|
// Don't substitute unless they're both solver params;
|
|
|
|
// otherwise it's an equation that can be solved immediately,
|
|
|
|
// or an error to flag later.
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(IsDragged(a)) {
|
|
|
|
// A is being dragged, so A should stay, and B should go
|
|
|
|
hParam t = a;
|
|
|
|
a = b;
|
|
|
|
b = t;
|
|
|
|
}
|
|
|
|
|
|
|
|
int j;
|
|
|
|
for(j = 0; j < eq.n; j++) {
|
|
|
|
Equation *req = &(eq.elem[j]);
|
|
|
|
(req->e)->Substitute(a, b); // A becomes B, B unchanged
|
|
|
|
}
|
|
|
|
for(j = 0; j < param.n; j++) {
|
|
|
|
Param *rp = &(param.elem[j]);
|
|
|
|
if(rp->substd.v == a.v) {
|
|
|
|
rp->substd = b;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Param *ptr = param.FindById(a);
|
|
|
|
ptr->tag = VAR_SUBSTITUTED;
|
|
|
|
ptr->substd = b;
|
|
|
|
|
|
|
|
teq->tag = EQ_SUBSTITUTED;
|
2008-05-01 06:25:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-07-02 08:18:25 +00:00
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Calculate the rank of the Jacobian matrix, by Gram-Schimdt orthogonalization
|
|
|
|
// in place. A row (~equation) is considered to be all zeros if its magnitude
|
|
|
|
// is less than the tolerance RANK_MAG_TOLERANCE.
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
int System::CalculateRank(void) {
|
|
|
|
// Actually work with magnitudes squared, not the magnitudes
|
|
|
|
double rowMag[MAX_UNKNOWNS];
|
|
|
|
ZERO(&rowMag);
|
|
|
|
double tol = RANK_MAG_TOLERANCE*RANK_MAG_TOLERANCE;
|
|
|
|
|
|
|
|
int i, iprev, j;
|
2008-05-12 07:29:50 +00:00
|
|
|
int rank = 0;
|
2008-04-20 11:35:10 +00:00
|
|
|
|
2008-07-02 08:18:25 +00:00
|
|
|
for(i = 0; i < mat.m; i++) {
|
|
|
|
// Subtract off this row's component in the direction of any
|
|
|
|
// previous rows
|
|
|
|
for(iprev = 0; iprev < i; iprev++) {
|
|
|
|
if(rowMag[iprev] <= tol) continue; // ignore zero rows
|
|
|
|
|
|
|
|
double dot = 0;
|
|
|
|
for(j = 0; j < mat.n; j++) {
|
|
|
|
dot += (mat.A.num[iprev][j]) * (mat.A.num[i][j]);
|
2008-04-20 11:35:10 +00:00
|
|
|
}
|
2008-07-02 08:18:25 +00:00
|
|
|
for(j = 0; j < mat.n; j++) {
|
|
|
|
mat.A.num[i][j] -= (dot/rowMag[iprev])*mat.A.num[iprev][j];
|
2008-04-20 11:35:10 +00:00
|
|
|
}
|
2008-07-02 08:18:25 +00:00
|
|
|
}
|
|
|
|
// Our row is now normal to all previous rows; calculate the
|
|
|
|
// magnitude of what's left
|
|
|
|
double mag = 0;
|
|
|
|
for(j = 0; j < mat.n; j++) {
|
|
|
|
mag += (mat.A.num[i][j]) * (mat.A.num[i][j]);
|
|
|
|
}
|
|
|
|
if(mag > tol) {
|
2008-05-12 07:29:50 +00:00
|
|
|
rank++;
|
2008-04-20 11:35:10 +00:00
|
|
|
}
|
2008-07-02 08:18:25 +00:00
|
|
|
rowMag[i] = mag;
|
2008-04-20 11:35:10 +00:00
|
|
|
}
|
2008-07-02 08:18:25 +00:00
|
|
|
|
2008-05-12 07:29:50 +00:00
|
|
|
return rank;
|
2008-04-20 11:35:10 +00:00
|
|
|
}
|
|
|
|
|
2008-05-12 07:29:50 +00:00
|
|
|
bool System::SolveLinearSystem(double X[], double A[][MAX_UNKNOWNS],
|
|
|
|
double B[], int n)
|
|
|
|
{
|
2008-04-20 11:35:10 +00:00
|
|
|
// Gaussian elimination, with partial pivoting. It's an error if the
|
|
|
|
// matrix is singular, because that means two constraints are
|
|
|
|
// equivalent.
|
2013-08-26 19:36:00 +00:00
|
|
|
int i, j, ip, jp, imax = 0;
|
2008-04-20 11:35:10 +00:00
|
|
|
double max, temp;
|
|
|
|
|
2008-05-12 07:29:50 +00:00
|
|
|
for(i = 0; i < n; i++) {
|
2008-04-20 11:35:10 +00:00
|
|
|
// We are trying eliminate the term in column i, for rows i+1 and
|
|
|
|
// greater. First, find a pivot (between rows i and N-1).
|
|
|
|
max = 0;
|
2008-05-12 07:29:50 +00:00
|
|
|
for(ip = i; ip < n; ip++) {
|
2009-10-21 04:46:01 +00:00
|
|
|
if(ffabs(A[ip][i]) > max) {
|
2008-04-20 11:35:10 +00:00
|
|
|
imax = ip;
|
2009-10-21 04:46:01 +00:00
|
|
|
max = ffabs(A[ip][i]);
|
2008-04-20 11:35:10 +00:00
|
|
|
}
|
|
|
|
}
|
2008-05-11 10:40:37 +00:00
|
|
|
// Don't give up on a singular matrix unless it's really bad; the
|
|
|
|
// assumption code is responsible for identifying that condition,
|
|
|
|
// so we're not responsible for reporting that error.
|
2009-10-21 04:46:01 +00:00
|
|
|
if(ffabs(max) < 1e-20) return false;
|
2008-04-20 11:35:10 +00:00
|
|
|
|
|
|
|
// Swap row imax with row i
|
2008-05-12 07:29:50 +00:00
|
|
|
for(jp = 0; jp < n; jp++) {
|
|
|
|
SWAP(double, A[i][jp], A[imax][jp]);
|
2008-04-20 11:35:10 +00:00
|
|
|
}
|
2008-05-12 07:29:50 +00:00
|
|
|
SWAP(double, B[i], B[imax]);
|
2008-04-20 11:35:10 +00:00
|
|
|
|
|
|
|
// For rows i+1 and greater, eliminate the term in column i.
|
2008-05-12 07:29:50 +00:00
|
|
|
for(ip = i+1; ip < n; ip++) {
|
|
|
|
temp = A[ip][i]/A[i][i];
|
2008-04-20 11:35:10 +00:00
|
|
|
|
2009-04-19 03:55:46 +00:00
|
|
|
for(jp = i; jp < n; jp++) {
|
2008-05-12 07:29:50 +00:00
|
|
|
A[ip][jp] -= temp*(A[i][jp]);
|
2008-04-20 11:35:10 +00:00
|
|
|
}
|
2008-05-12 07:29:50 +00:00
|
|
|
B[ip] -= temp*B[i];
|
2008-04-20 11:35:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// We've put the matrix in upper triangular form, so at this point we
|
|
|
|
// can solve by back-substitution.
|
2008-05-12 07:29:50 +00:00
|
|
|
for(i = n - 1; i >= 0; i--) {
|
2009-10-21 04:46:01 +00:00
|
|
|
if(ffabs(A[i][i]) < 1e-20) return false;
|
2008-05-12 07:29:50 +00:00
|
|
|
|
|
|
|
temp = B[i];
|
|
|
|
for(j = n - 1; j > i; j--) {
|
|
|
|
temp -= X[j]*A[i][j];
|
|
|
|
}
|
|
|
|
X[i] = temp / A[i][i];
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
2008-04-20 11:35:10 +00:00
|
|
|
|
2008-05-12 07:29:50 +00:00
|
|
|
bool System::SolveLeastSquares(void) {
|
|
|
|
int r, c, i;
|
|
|
|
|
2008-05-13 02:35:31 +00:00
|
|
|
// Scale the columns; this scale weights the parameters for the least
|
|
|
|
// squares solve, so that we can encourage the solver to make bigger
|
|
|
|
// changes in some parameters, and smaller in others.
|
|
|
|
for(c = 0; c < mat.n; c++) {
|
|
|
|
if(IsDragged(mat.param[c])) {
|
2008-06-06 07:50:08 +00:00
|
|
|
// It's least squares, so this parameter doesn't need to be all
|
|
|
|
// that big to get a large effect.
|
|
|
|
mat.scale[c] = 1/20.0;
|
2008-05-13 02:35:31 +00:00
|
|
|
} else {
|
|
|
|
mat.scale[c] = 1;
|
|
|
|
}
|
|
|
|
for(r = 0; r < mat.m; r++) {
|
|
|
|
mat.A.num[r][c] *= mat.scale[c];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-05-12 07:29:50 +00:00
|
|
|
// Write A*A'
|
|
|
|
for(r = 0; r < mat.m; r++) {
|
|
|
|
for(c = 0; c < mat.m; c++) { // yes, AAt is square
|
|
|
|
double sum = 0;
|
|
|
|
for(i = 0; i < mat.n; i++) {
|
|
|
|
sum += mat.A.num[r][i]*mat.A.num[c][i];
|
|
|
|
}
|
|
|
|
mat.AAt[r][c] = sum;
|
2008-04-20 11:35:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-05-12 07:29:50 +00:00
|
|
|
if(!SolveLinearSystem(mat.Z, mat.AAt, mat.B.num, mat.m)) return false;
|
|
|
|
|
|
|
|
// And multiply that by A' to get our solution.
|
|
|
|
for(c = 0; c < mat.n; c++) {
|
|
|
|
double sum = 0;
|
|
|
|
for(i = 0; i < mat.m; i++) {
|
|
|
|
sum += mat.A.num[i][c]*mat.Z[i];
|
|
|
|
}
|
2008-05-13 02:35:31 +00:00
|
|
|
mat.X[c] = sum * mat.scale[c];
|
2008-05-12 07:29:50 +00:00
|
|
|
}
|
2008-04-20 11:35:10 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool System::NewtonSolve(int tag) {
|
2008-05-17 23:21:02 +00:00
|
|
|
if(mat.m > mat.n) return false;
|
2008-04-20 11:35:10 +00:00
|
|
|
|
|
|
|
int iter = 0;
|
|
|
|
bool converged = false;
|
|
|
|
int i;
|
2008-04-21 08:16:38 +00:00
|
|
|
|
|
|
|
// Evaluate the functions at our operating point.
|
|
|
|
for(i = 0; i < mat.m; i++) {
|
|
|
|
mat.B.num[i] = (mat.B.sym[i])->Eval();
|
|
|
|
}
|
2008-04-20 11:35:10 +00:00
|
|
|
do {
|
2008-04-21 08:16:38 +00:00
|
|
|
// And evaluate the Jacobian at our initial operating point.
|
2008-04-20 11:35:10 +00:00
|
|
|
EvalJacobian();
|
|
|
|
|
2008-05-12 07:29:50 +00:00
|
|
|
if(!SolveLeastSquares()) break;
|
2008-04-20 11:35:10 +00:00
|
|
|
|
2015-03-29 00:30:52 +00:00
|
|
|
// Take the Newton step;
|
2008-04-20 11:35:10 +00:00
|
|
|
// J(x_n) (x_{n+1} - x_n) = 0 - F(x_n)
|
2008-05-12 07:29:50 +00:00
|
|
|
for(i = 0; i < mat.n; i++) {
|
2008-06-03 18:28:41 +00:00
|
|
|
Param *p = param.FindById(mat.param[i]);
|
|
|
|
p->val -= mat.X[i];
|
|
|
|
if(isnan(p->val)) {
|
|
|
|
// Very bad, and clearly not convergent
|
|
|
|
return false;
|
|
|
|
}
|
2008-04-20 11:35:10 +00:00
|
|
|
}
|
|
|
|
|
2008-04-21 08:16:38 +00:00
|
|
|
// Re-evalute the functions, since the params have just changed.
|
|
|
|
for(i = 0; i < mat.m; i++) {
|
|
|
|
mat.B.num[i] = (mat.B.sym[i])->Eval();
|
|
|
|
}
|
|
|
|
// Check for convergence
|
2008-04-20 11:35:10 +00:00
|
|
|
converged = true;
|
2008-04-21 01:26:36 +00:00
|
|
|
for(i = 0; i < mat.m; i++) {
|
2008-06-03 18:28:41 +00:00
|
|
|
if(isnan(mat.B.num[i])) {
|
|
|
|
return false;
|
|
|
|
}
|
2009-10-21 04:46:01 +00:00
|
|
|
if(ffabs(mat.B.num[i]) > CONVERGE_TOLERANCE) {
|
2008-04-20 11:35:10 +00:00
|
|
|
converged = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} while(iter++ < 50 && !converged);
|
|
|
|
|
2008-05-26 09:56:50 +00:00
|
|
|
return converged;
|
|
|
|
}
|
|
|
|
|
2009-04-20 07:30:09 +00:00
|
|
|
void System::WriteEquationsExceptFor(hConstraint hc, Group *g) {
|
2008-05-26 09:56:50 +00:00
|
|
|
int i;
|
|
|
|
// Generate all the equations from constraints in this group
|
2009-04-19 05:53:16 +00:00
|
|
|
for(i = 0; i < SK.constraint.n; i++) {
|
2009-04-20 07:30:09 +00:00
|
|
|
ConstraintBase *c = &(SK.constraint.elem[i]);
|
|
|
|
if(c->group.v != g->h.v) continue;
|
2008-05-26 09:56:50 +00:00
|
|
|
if(c->h.v == hc.v) continue;
|
|
|
|
|
2010-05-10 01:06:09 +00:00
|
|
|
if(c->HasLabel() && c->type != Constraint::COMMENT &&
|
|
|
|
g->allDimsReference)
|
|
|
|
{
|
|
|
|
// When all dimensions are reference, we adjust them to display
|
|
|
|
// the correct value, and then don't generate any equations.
|
|
|
|
c->ModifyToSatisfy();
|
|
|
|
continue;
|
|
|
|
}
|
2009-10-01 11:22:56 +00:00
|
|
|
if(g->relaxConstraints && c->type != Constraint::POINTS_COINCIDENT) {
|
|
|
|
// When the constraints are relaxed, we keep only the point-
|
|
|
|
// coincident constraints, and the constraints generated by
|
|
|
|
// the entities and groups.
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2008-05-26 09:56:50 +00:00
|
|
|
c->Generate(&eq);
|
|
|
|
}
|
|
|
|
// And the equations from entities
|
2009-04-19 05:53:16 +00:00
|
|
|
for(i = 0; i < SK.entity.n; i++) {
|
2009-04-20 07:30:09 +00:00
|
|
|
EntityBase *e = &(SK.entity.elem[i]);
|
|
|
|
if(e->group.v != g->h.v) continue;
|
2008-05-26 09:56:50 +00:00
|
|
|
|
|
|
|
e->GenerateEquations(&eq);
|
2008-04-20 11:35:10 +00:00
|
|
|
}
|
2008-05-26 09:56:50 +00:00
|
|
|
// And from the groups themselves
|
2009-04-20 07:30:09 +00:00
|
|
|
g->GenerateEquations(&eq);
|
2008-04-20 11:35:10 +00:00
|
|
|
}
|
|
|
|
|
2009-04-20 07:30:09 +00:00
|
|
|
void System::FindWhichToRemoveToFixJacobian(Group *g, List<hConstraint> *bad) {
|
2008-09-05 11:25:53 +00:00
|
|
|
int a, i;
|
2008-05-26 09:56:50 +00:00
|
|
|
|
2008-09-05 11:25:53 +00:00
|
|
|
for(a = 0; a < 2; a++) {
|
2009-04-19 05:53:16 +00:00
|
|
|
for(i = 0; i < SK.constraint.n; i++) {
|
2009-04-20 07:30:09 +00:00
|
|
|
ConstraintBase *c = &(SK.constraint.elem[i]);
|
2008-09-05 11:25:53 +00:00
|
|
|
if(c->group.v != g->h.v) continue;
|
|
|
|
if((c->type == Constraint::POINTS_COINCIDENT && a == 0) ||
|
|
|
|
(c->type != Constraint::POINTS_COINCIDENT && a == 1))
|
|
|
|
{
|
|
|
|
// Do the constraints in two passes: first everything but
|
|
|
|
// the point-coincident constraints, then only those
|
|
|
|
// constraints (so they appear last in the list).
|
|
|
|
continue;
|
|
|
|
}
|
2008-05-26 09:56:50 +00:00
|
|
|
|
2008-09-05 11:25:53 +00:00
|
|
|
param.ClearTags();
|
|
|
|
eq.Clear();
|
2009-04-20 07:30:09 +00:00
|
|
|
WriteEquationsExceptFor(c->h, g);
|
2008-09-05 11:25:53 +00:00
|
|
|
eq.ClearTags();
|
2008-05-26 09:56:50 +00:00
|
|
|
|
2008-09-05 11:25:53 +00:00
|
|
|
// It's a major speedup to solve the easy ones by substitution here,
|
|
|
|
// and that doesn't break anything.
|
|
|
|
SolveBySubstitution();
|
|
|
|
|
|
|
|
WriteJacobian(0);
|
|
|
|
EvalJacobian();
|
2008-05-26 09:56:50 +00:00
|
|
|
|
2008-09-05 11:25:53 +00:00
|
|
|
int rank = CalculateRank();
|
|
|
|
if(rank == mat.m) {
|
|
|
|
// We fixed it by removing this constraint
|
2009-04-20 07:30:09 +00:00
|
|
|
bad->Add(&(c->h));
|
2008-09-05 11:25:53 +00:00
|
|
|
}
|
2008-05-26 09:56:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-29 00:30:52 +00:00
|
|
|
int System::Solve(Group *g, int *dof, List<hConstraint> *bad,
|
2009-04-21 07:56:17 +00:00
|
|
|
bool andFindBad, bool andFindFree)
|
2009-04-20 07:30:09 +00:00
|
|
|
{
|
|
|
|
WriteEquationsExceptFor(Constraint::NO_CONSTRAINT, g);
|
2008-05-26 09:56:50 +00:00
|
|
|
|
2008-05-12 10:01:44 +00:00
|
|
|
int i, j = 0;
|
2008-04-27 09:31:56 +00:00
|
|
|
/*
|
2008-04-27 09:03:01 +00:00
|
|
|
dbp("%d equations", eq.n);
|
2008-04-20 11:35:10 +00:00
|
|
|
for(i = 0; i < eq.n; i++) {
|
2008-04-28 09:40:02 +00:00
|
|
|
dbp(" %.3f = %s = 0", eq.elem[i].e->Eval(), eq.elem[i].e->Print());
|
2008-04-27 09:03:01 +00:00
|
|
|
}
|
2008-05-08 07:30:30 +00:00
|
|
|
dbp("%d parameters", param.n);
|
|
|
|
for(i = 0; i < param.n; i++) {
|
|
|
|
dbp(" param %08x at %.3f", param.elem[i].h.v, param.elem[i].val);
|
|
|
|
} */
|
2008-04-20 11:35:10 +00:00
|
|
|
|
2008-06-26 09:34:26 +00:00
|
|
|
// All params and equations are assigned to group zero.
|
2008-04-20 11:35:10 +00:00
|
|
|
param.ClearTags();
|
|
|
|
eq.ClearTags();
|
2015-03-29 00:30:52 +00:00
|
|
|
|
2008-05-07 07:10:20 +00:00
|
|
|
SolveBySubstitution();
|
|
|
|
|
2008-06-26 09:34:26 +00:00
|
|
|
// Before solving the big system, see if we can find any equations that
|
|
|
|
// are soluble alone. This can be a huge speedup. We don't know whether
|
|
|
|
// the system is consistent yet, but if it isn't then we'll catch that
|
|
|
|
// later.
|
|
|
|
int alone = 1;
|
|
|
|
for(i = 0; i < eq.n; i++) {
|
|
|
|
Equation *e = &(eq.elem[i]);
|
|
|
|
if(e->tag != 0) continue;
|
2008-05-07 07:10:20 +00:00
|
|
|
|
2008-06-26 09:34:26 +00:00
|
|
|
hParam hp = e->e->ReferencedParams(¶m);
|
|
|
|
if(hp.v == Expr::NO_PARAMS.v) continue;
|
|
|
|
if(hp.v == Expr::MULTIPLE_PARAMS.v) continue;
|
2008-04-30 04:52:34 +00:00
|
|
|
|
2008-06-26 09:34:26 +00:00
|
|
|
Param *p = param.FindById(hp);
|
|
|
|
if(p->tag != 0) continue; // let rank test catch inconsistency
|
|
|
|
|
|
|
|
e->tag = alone;
|
|
|
|
p->tag = alone;
|
2009-04-19 03:55:46 +00:00
|
|
|
WriteJacobian(alone);
|
2008-06-26 09:34:26 +00:00
|
|
|
if(!NewtonSolve(alone)) {
|
|
|
|
// Failed to converge, bail out early
|
|
|
|
goto didnt_converge;
|
2008-04-20 11:35:10 +00:00
|
|
|
}
|
2008-06-26 09:34:26 +00:00
|
|
|
alone++;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now write the Jacobian for what's left, and do a rank test; that
|
|
|
|
// tells us if the system is inconsistently constrained.
|
2009-04-19 20:37:51 +00:00
|
|
|
if(!WriteJacobian(0)) {
|
2009-04-20 07:30:09 +00:00
|
|
|
return System::TOO_MANY_UNKNOWNS;
|
2009-04-19 20:37:51 +00:00
|
|
|
}
|
2008-06-26 09:34:26 +00:00
|
|
|
|
|
|
|
EvalJacobian();
|
2008-04-20 11:35:10 +00:00
|
|
|
|
2013-08-26 19:08:16 +00:00
|
|
|
int rank; rank = CalculateRank();
|
2008-05-26 09:56:50 +00:00
|
|
|
if(rank != mat.m) {
|
2009-04-21 07:56:17 +00:00
|
|
|
if(andFindBad) {
|
|
|
|
FindWhichToRemoveToFixJacobian(g, bad);
|
|
|
|
}
|
2009-04-20 07:30:09 +00:00
|
|
|
return System::SINGULAR_JACOBIAN;
|
2008-05-26 09:56:50 +00:00
|
|
|
}
|
2008-07-10 07:42:35 +00:00
|
|
|
// This is not the full Jacobian, but any substitutions or single-eq
|
|
|
|
// solves removed one equation and one unknown, therefore no effect
|
|
|
|
// on the number of DOF.
|
2009-04-20 07:30:09 +00:00
|
|
|
if(dof) *dof = mat.n - mat.m;
|
2008-04-20 11:35:10 +00:00
|
|
|
|
2008-06-26 09:34:26 +00:00
|
|
|
// And do the leftovers as one big system
|
|
|
|
if(!NewtonSolve(0)) {
|
|
|
|
goto didnt_converge;
|
|
|
|
}
|
2008-04-21 08:16:38 +00:00
|
|
|
|
2009-01-04 12:01:46 +00:00
|
|
|
// If requested, find all the free (unbound) variables. This might be
|
|
|
|
// more than the number of degrees of freedom. Don't always do this,
|
|
|
|
// because the display would get annoying and it's slow.
|
|
|
|
for(i = 0; i < param.n; i++) {
|
|
|
|
Param *p = &(param.elem[i]);
|
|
|
|
p->free = false;
|
|
|
|
|
|
|
|
if(andFindFree) {
|
|
|
|
if(p->tag == 0) {
|
|
|
|
p->tag = VAR_DOF_TEST;
|
|
|
|
WriteJacobian(0);
|
|
|
|
EvalJacobian();
|
|
|
|
rank = CalculateRank();
|
|
|
|
if(rank == mat.m) {
|
|
|
|
p->free = true;
|
|
|
|
}
|
|
|
|
p->tag = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-06-26 09:34:26 +00:00
|
|
|
// System solved correctly, so write the new values back in to the
|
|
|
|
// main parameter table.
|
|
|
|
for(i = 0; i < param.n; i++) {
|
|
|
|
Param *p = &(param.elem[i]);
|
|
|
|
double val;
|
|
|
|
if(p->tag == VAR_SUBSTITUTED) {
|
|
|
|
val = param.FindById(p->substd)->val;
|
|
|
|
} else {
|
|
|
|
val = p->val;
|
2008-05-26 09:56:50 +00:00
|
|
|
}
|
2009-04-19 05:53:16 +00:00
|
|
|
Param *pp = SK.GetParam(p->h);
|
2008-06-26 09:34:26 +00:00
|
|
|
pp->val = val;
|
|
|
|
pp->known = true;
|
2009-01-04 12:01:46 +00:00
|
|
|
pp->free = p->free;
|
2008-06-26 09:34:26 +00:00
|
|
|
}
|
2009-04-20 07:30:09 +00:00
|
|
|
return System::SOLVED_OKAY;
|
2008-06-26 09:34:26 +00:00
|
|
|
|
|
|
|
didnt_converge:
|
2009-04-19 05:53:16 +00:00
|
|
|
SK.constraint.ClearTags();
|
2008-09-05 11:25:53 +00:00
|
|
|
for(i = 0; i < eq.n; i++) {
|
2009-10-21 04:46:01 +00:00
|
|
|
if(ffabs(mat.B.num[i]) > CONVERGE_TOLERANCE || isnan(mat.B.num[i])) {
|
2008-09-05 11:25:53 +00:00
|
|
|
// This constraint is unsatisfied.
|
|
|
|
if(!mat.eq[i].isFromConstraint()) continue;
|
|
|
|
|
|
|
|
hConstraint hc = mat.eq[i].constraint();
|
2009-04-20 07:30:09 +00:00
|
|
|
ConstraintBase *c = SK.constraint.FindByIdNoOops(hc);
|
2008-09-05 11:25:53 +00:00
|
|
|
if(!c) continue;
|
|
|
|
// Don't double-show constraints that generated multiple
|
|
|
|
// unsatisfied equations
|
|
|
|
if(!c->tag) {
|
2009-04-20 07:30:09 +00:00
|
|
|
bad->Add(&(c->h));
|
2008-09-05 11:25:53 +00:00
|
|
|
c->tag = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-03-29 00:30:52 +00:00
|
|
|
|
2009-04-20 07:30:09 +00:00
|
|
|
return System::DIDNT_CONVERGE;
|
2008-04-20 11:35:10 +00:00
|
|
|
}
|
|
|
|
|
2013-09-19 04:33:12 +00:00
|
|
|
void System::Clear(void) {
|
|
|
|
entity.Clear();
|
|
|
|
param.Clear();
|
|
|
|
eq.Clear();
|
|
|
|
dragged.Clear();
|
|
|
|
}
|