thegrumpys / odop

Open Design Optimization Platform (ODOP) - Coil spring design app; mechanical springs; compression spring, extension spring, torsion spring
https://www.springdesignsoftware.org
MIT License
4 stars 5 forks source link

Search fails on pathological case: Validity not working as intended? #815

Open grumpyinca opened 1 year ago

grumpyinca commented 1 year ago

An evaluator created a compression spring pathological case (ID_Free < 0). Search fails. Perhaps validity is not working as intended.

See the attached export file (.ZIP format allows attaching to the issue). View : ObjectiveValue shows that the validity contribution of ID_Free is relatively small. Why is it that all validity and all violation contributions do not sum to 100% ?

GA_Client_ID_616411970-1675414931_FailedSearch_08Feb2023.zip

1fifoto commented 1 year ago

Make Symbol Table viewer's columns from all mins then all maxes to min/max pairs for each variable.

1fifoto commented 1 year ago

A feasible solution is available.

Set OD_Free to 0.32 and UNFIX OD_FREE, then run search and it finds a successful solution - so there is a solution, but the original starting point just can't find that solution. The starting point (ID_Free < 0) is a bizarre point for some reason. It is a "pathological" failure.

1fifoto commented 1 year ago

Easy way to re-create the problem. Open Compression Startup or load InitialState on Heroku production. Enter one of the following OD_Free values

It clearly shows a Obj Value "ridge" at OD_Free 0.2110 that shouldn't be there, but cause by the divide by zero. (edited)

grumpyinca commented 1 year ago

Possible approach for dealing with risk of division by zero and improving blend of penalty contributions

Create a function (in this example, called noDiv0) that can be used wherever there is a division in eqnset. Note that this function would "corrupt" the result of the equation. This is considered acceptable because the corruption would occur only for values that are well outside the range of engineering practicality. In order for Search to operate correctly, there is a requirement to maintain an approximation of continuity for all values (at least zeroth order but not necessarily higher derivatives).

For example, in eqnset where there is an equation of the form:

value = num / den

The eqnset code can be modified to look like:

value = noDiv0(num, den)

At a high level, the function noDiv0 would handle three separate zones for the value of den:

It is expected that the value of smallnum would be in the range of 1e-06 to 1e-15. Considering that part of the mission here is to mitigate having the penalty of validity violations swamped by objective value contributions related to near-division-by-zero situations, the larger (most positive, further from zero) end of that range may be most desirable.

The internal workings of noDiv0 may be thought of as operating as a "transfer function" on the value of den. Specifically, for a given input value of den, a modified value would be computed and used in the division that ultimately passes the result back to eqnset. The object is to scale the divisor in the latter two zones to be a (small) positive value while maintaining at least an approximation of continuity. Note that the scaling ratios proposed here have not yet been tested. Serious errors are possible.

NoDiv0_sketch

The internal operation of function noDiv0 might look something like:

if den > smallnum
   return (num / den)

else if den <= smallnum and den > (-smallnum) 
   m = (0.75 * smallnum) / (2.0 * smallnum)
   b = smallnum / 4.0 
   y = m * ( (smallnum + den) / smallnum ) + b   
   return (num / y)

else 
   ...
  return (...) 

Idea for re-formulation:

noDivBy0(num, den) {
    newden = sign(den) * transfer(abs(den));
    return num / newden
}

If the last zone has arithmetic problems due to the scale of the numbers involved (truncation error), consider the use of logarithms.

grumpyinca commented 1 year ago

A bit more Internet research on the subject:

From Dividing by zero smoothly, try: value = num * den / (den * den + epsilon) where epsilon is "any tiny value".

grumpyinca commented 1 year ago

The approach noted in the comment above seems effective at preventing a divide-by-zero condition but it does not protect against a sign flip as the denominator passes from positive to negative. Perhaps an alternative might be: value = num * abs(den) / (den * den + epsilon)

The attached spreadsheet (pick the format that works best for you) illustrates the above approach for the numbers in the Feb 13, 2023 example above.

DivideByZeroV2.pdf DivideByZeroV2.xlsx DivideByZeroV2.ods

grumpyinca commented 7 months ago

See #871.