As a hobby, below a quick and dirty seeking function using basic dichotomy
Code: Select all
SEEKMAX = 100 # max number of iterations to found a range where a solution exists
DICHOMAX = 100 # max number of iterations to found a solution inside the found range
SEEKFACTOR = 0.5 # range size around nominal value, shall be in ]0, 1[ ; 0.5 typical ; reducing may improve finding a solution on periodic functions
DICHOFACTOR = 0.5 # dichotomy factor, no valuable reason to set something else than 0.5
THRESHOLD = 1e-7 # deviation with goal at which a solution is considered found
from PySide2 import QtWidgets
import FreeCAD as App
import FreeCADGui as Gui
doc = App.ActiveDocument
ddp = doc.dd
ddg = doc.dd001
goal = ddg.ddGoal
sb = Gui.getMainWindow().findChild(QtWidgets.QProgressBar)
def recompute(val = ddp.ddParameter):
if not ddg.recompute(True):
raise Exception("Recompute error")
def goalseek(baseValue, currValue = None, itseek = 1, itdicho = 1, up = False, down = False):
global SEEKFACTOR
sb.setValue((itseek+itdicho)/(SEEKMAX+DICHOMAX)*100)
Gui.updateGui()
if itseek > SEEKMAX+1:
App.Console.PrintWarning("Maximum number of seek iterations reached without solution range\n")
return None
if itdicho > DICHOMAX+1:
App.Console.PrintWarning("Maximum number of dichotomy iterations reached without solution\n")
return None
if currValue:
ddp.ddParameter = currValue
else:
ddp.ddParameter = baseValue
recompute()
currResult = ddg.ddResult
## SEEK phase
if up == down:
upValue = baseValue + baseValue*SEEKFACTOR
ddp.ddParameter = upValue
recompute()
upResult = ddg.ddResult
downValue = baseValue - baseValue*SEEKFACTOR
ddp.ddParameter = downValue
recompute()
downResult = ddg.ddResult
if (currResult < goal) == (upResult > goal): # found a range where a solution exists upside
return goalseek(baseValue, baseValue, itseek, itdicho, True, False)
if (currResult > goal) == (downResult < goal): # found a range where a solution exists downside
return goalseek(baseValue, baseValue, itseek, itdicho, False, True)
if (upResult > currResult) == (currResult > downResult) == (downResult > goal): # found a linear range, solution shoud be downside
return goalseek(downValue, currValue, itseek+1, itdicho, up, down)
if (upResult > currResult) == (currResult > downResult) == (upResult < goal): # found a linear range, solution shoud be upside
return goalseek(upValue, currValue, itseek+1, itdicho, up, down)
## found a concave/convexe range
upDiff = upResult - currResult
downDiff = currResult - downResult
SEEKFACTOR = SEEKFACTOR / 2
if ((currResult < goal) == (downDiff < 0)) != (abs(downDiff) > abs(upDiff)):
return goalseek(baseValue, currValue, itseek+1, itdicho, up, down)
else:
return goalseek(baseValue, currValue, itseek+1, itdicho, up, down)
## DICHO phase
if up:
currValue = currValue + baseValue*SEEKFACTOR*DICHOFACTOR**itdicho
if down:
currValue = currValue - baseValue*SEEKFACTOR*DICHOFACTOR**itdicho
ddp.ddParameter = currValue
recompute()
if abs(ddg.ddResult - goal) <= THRESHOLD:
print(f"""Found a solution with value : {currValue}
Deviation : {abs(ddg.ddResult - goal)}
Seek iterations : {itseek}
Dicho iterations : {itdicho}""")
return currValue
if (currResult < goal) == (ddg.ddResult < goal):
return goalseek(baseValue, currValue, itseek, itdicho+1, up, down)
else:
return goalseek(baseValue, currValue, itseek, itdicho+1, not up, not down)
def runSeek():
App.Console.PrintMessage(f"Seeking for goal : {goal}\n")
sb.setValue(0)
sb.show()
startVal = ddp.ddParameter
res = None
try:
res = goalseek(startVal)
except Exception:
App.Console.PrintError(f"Error : can't recompute with value : {ddp.ddParameter}\n")
if not res:
ddp.ddParameter = startVal # Restore start value if no solution found
sb.hide()
runSeek()
I didn't test it extensively but it should deal quite well with any polynomial (and maybe even periodic) transfer function.