Skip to content

Commit 4ecd154

Browse files
cknollcknoll
authored andcommitted
Merge branch 'release-1.3.0'
2 parents e40db46 + 35d2536 commit 4ecd154

7 files changed

Lines changed: 171 additions & 56 deletions

File tree

pytrajectory/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from log import logging
1616

1717
# current version
18-
__version__ = '1.2.0'
18+
__version__ = '1.3.0'
1919

2020
# Placeholder for the datetime string of latest commit
2121
__date__ = "2016-01-15 14:12:08"

pytrajectory/auxiliary.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66

77
from log import logging, Timer
88

9+
10+
class NanError(ValueError):
11+
pass
12+
913
class IntegChain(object):
1014
'''
1115
This class provides a representation of an integrator chain.

pytrajectory/collocation.py

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ def __init__(self, sys, **kwargs):
4343
self._parameters = dict()
4444
self._parameters['tol'] = kwargs.get('tol', 1e-5)
4545
self._parameters['reltol'] = kwargs.get('reltol', 2e-5)
46-
self._parameters['sol_steps'] = kwargs.get('sol_steps', 100)
46+
self._parameters['sol_steps'] = kwargs.get('sol_steps', 50)
4747
self._parameters['method'] = kwargs.get('method', 'leven')
4848
self._parameters['coll_type'] = kwargs.get('coll_type', 'equidistant')
4949

@@ -403,7 +403,7 @@ def get_guess(self):
403403
self.guess = guess
404404

405405

406-
def solve(self, G, DG):
406+
def solve(self, G, DG, new_solver=True):
407407
'''
408408
This method is used to solve the collocation equation system.
409409
@@ -415,17 +415,25 @@ def solve(self, G, DG):
415415
416416
DG : callable
417417
Function for the jacobian.
418+
419+
new_solver : bool
420+
flag to determine whether a new solver instance should
421+
be initialized (default True)
418422
'''
419423

420424
logging.debug("Solving Equation System")
421425

422426
# create our solver
423-
self.solver = Solver(F=G, DF=DG, x0=self.guess,
424-
tol=self._parameters['tol'],
425-
reltol=self._parameters['reltol'],
426-
maxIt=self._parameters['sol_steps'],
427-
method=self._parameters['method'])
428-
427+
if new_solver:
428+
self.solver = Solver(F=G, DF=DG, x0=self.guess,
429+
tol=self._parameters['tol'],
430+
reltol=self._parameters['reltol'],
431+
maxIt=self._parameters['sol_steps'],
432+
method=self._parameters['method'])
433+
else:
434+
# assume self.solver exists and at we already did a solution run
435+
assert self.solver.solve_count > 0
436+
429437
# solve the equation system
430438
self.sol = self.solver.solve()
431439

pytrajectory/solver.py

Lines changed: 68 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import scipy as scp
44
import time
55

6+
from auxiliary import NanError
7+
68
from log import logging
79

810

@@ -34,7 +36,8 @@ class Solver:
3436
The solver to use
3537
'''
3638

37-
def __init__(self, F, DF, x0, tol=1e-5, reltol=2e-5, maxIt=100, method='leven'):
39+
def __init__(self, F, DF, x0, tol=1e-5, reltol=2e-5, maxIt=50,
40+
method='leven', mu=1e-4):
3841
self.F = F
3942
self.DF = DF
4043
self.x0 = x0
@@ -43,6 +46,13 @@ def __init__(self, F, DF, x0, tol=1e-5, reltol=2e-5, maxIt=100, method='leven'):
4346
self.maxIt = maxIt
4447
self.method = method
4548

49+
self.solve_count = 0
50+
51+
# this is LM specific
52+
self.mu=mu
53+
self.res = 1
54+
self.res_old = -1
55+
4656
self.sol = None
4757

4858

@@ -52,6 +62,8 @@ def solve(self):
5262
collocation equation system.
5363
'''
5464

65+
self.solve_count += 1
66+
5567
if (self.method == 'leven'):
5668
logging.debug("Run Levenberg-Marquardt method")
5769
self.leven()
@@ -72,20 +84,17 @@ def leven(self):
7284
'''
7385
i = 0
7486
x = self.x0
75-
res = 1
76-
res_alt = -1
7787

7888
eye = scp.sparse.identity(len(self.x0))
7989

8090
#mu = 1.0
81-
mu = 1e-4
82-
mu_old = mu
91+
self.mu = 1e-4
8392

8493
# borders for convergence-control
8594
b0 = 0.2
8695
b1 = 0.8
8796

88-
roh = 0.0
97+
rho = 0.0
8998

9099
reltol = self.reltol
91100

@@ -94,7 +103,9 @@ def leven(self):
94103
# measure the time for the LM-Algorithm
95104
T_start = time.time()
96105

97-
while((res > self.tol) and (self.maxIt > i) and (abs(res-res_alt) > reltol)):
106+
break_outer_loop = False
107+
108+
while (not break_outer_loop):
98109
i += 1
99110

100111
#if (i-1)%4 == 0:
@@ -103,7 +114,7 @@ def leven(self):
103114

104115
break_inner_loop = False
105116
while (not break_inner_loop):
106-
A = DFx.T.dot(DFx) + mu**2*eye
117+
A = DFx.T.dot(DFx) + self.mu**2*eye
107118

108119
b = DFx.T.dot(Fx)
109120

@@ -113,6 +124,11 @@ def leven(self):
113124

114125
Fxs = self.F(xs)
115126

127+
if any(np.isnan(Fxs)):
128+
# this might be caused by too small mu
129+
msg = "Invalid start guess (leads to nan)"
130+
raise NanError(msg)
131+
116132
normFx = norm(Fx)
117133
normFxs = norm(Fxs)
118134

@@ -121,47 +137,72 @@ def leven(self):
121137

122138
R1 = (normFx - normFxs)
123139
R2 = (normFx - (norm(Fx+DFx.dot(s))))
124-
roh = R1 / R2
140+
rho = R1 / R2
125141

126142
# note smaller bigger mu means less progress but
127143
# "more regular" conditions
128144

129145
if R1 < 0 or R2 < 0:
130146
# the step was too big -> residuum would be increasing
131-
mu*= 2
132-
roh = 0.0 # ensure another iteration
147+
self.mu *= 2
148+
rho = 0.0 # ensure another iteration
133149

134150
#logging.debug("increasing res. R1=%f, R2=%f, dismiss solution" % (R1, R2))
135151

136-
elif (roh<=b0):
137-
mu = 2*mu
138-
elif (roh>=b1):
139-
140-
mu = 0.5*mu
152+
elif (rho<=b0):
153+
self.mu *= 2
154+
elif (rho>=b1):
155+
self.mu *= 0.5
141156

142-
# -> if b0 < roh < b1 : leave mu unchanged
143-
144-
logging.debug(" roh= %f mu= %f"%(roh,mu))
157+
# -> if b0 < rho < b1 : leave mu unchanged
145158

146-
if roh < 0:
147-
logging.warn("roh < 0 (should not happen)")
159+
logging.debug(" rho= %f mu= %f"%(rho, self.mu))
160+
161+
if np.isnan(rho):
162+
# this should might be caused by large values for xs
163+
# but it should have been catched above
164+
logging.warn("rho = nan (should not happen)")
165+
raise NanError()
166+
167+
if rho < 0:
168+
logging.warn("rho < 0 (should not happen)")
148169

149170
# if the system more or less behaves linearly
150-
break_inner_loop = roh > b0
171+
break_inner_loop = rho > b0
151172

152173
Fx = Fxs
153174
x = xs
154175

155-
#roh = 0.0
156-
res_alt = res
157-
res = normFx
158-
if i>1 and res > res_alt:
176+
# store for possible future usage
177+
self.x0 = xs
178+
179+
#rho = 0.0
180+
self.res_old = self.res
181+
self.res = normFx
182+
if i>1 and self.res > self.res_old:
159183
logging.warn("res_old > res (should not happen)")
160184

161-
logging.debug("nIt= %d res= %f"%(i,res))
185+
logging.debug("nIt= %d res= %f"%(i,self.res))
186+
187+
self.cond_abs_tol = self.res <= self.tol
188+
self.cond_rel_tol = abs(self.res-self.res_old) <= reltol
189+
self.cond_num_steps = i >= self.maxIt
190+
191+
break_outer_loop = self.cond_abs_tol or self.cond_rel_tol \
192+
or self.cond_num_steps
162193

163194
# LM Algorithm finished
164195
T_LM = time.time() - T_start
196+
197+
if i == 0:
198+
from IPython import embed as IPS
199+
IPS()
200+
165201
self.avg_LM_time = T_LM / i
166202

203+
# Note: if self.cond_num_steps == True, the LM-Algorithm was stopped
204+
# due to maximum number of iterations
205+
# -> it might be worth to continue
206+
207+
167208
self.sol = x

pytrajectory/system.py

Lines changed: 60 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ class ControlSystem(object):
6060
eps 1e-2 Tolerance for the solution of the initial value problem
6161
ierr 1e-1 Tolerance for the error on the whole interval
6262
tol 1e-5 Tolerance for the solver of the equation system
63+
dt_sim 1e-2 Sample time for integration (initial value problem)
6364
use_chains True Whether or not to use integrator chains
6465
sol_steps 100 Maximum number of iteration steps for the eqs solver
6566
first_guess None to initiate free parameters (might be useful: {'seed': value})
@@ -72,6 +73,8 @@ def __init__(self, ff, a=0., b=1., xa=[], xb=[], ua=[], ub=[], constraints=None,
7273
self._parameters['maxIt'] = kwargs.get('maxIt', 10)
7374
self._parameters['eps'] = kwargs.get('eps', 1e-2)
7475
self._parameters['ierr'] = kwargs.get('ierr', 1e-1)
76+
self._parameters['dt_sim'] = kwargs.get('dt_sim', 0.01)
77+
self._parameters['accIt'] = kwargs.get('accIt', 5)
7578

7679
# create an object for the dynamical system
7780
self.dyn_sys = DynamicalSystem(f_sym=ff, a=a, b=b, xa=xa, xb=xb, ua=ua, ub=ub)
@@ -108,7 +111,7 @@ def set_param(self, param='', value=None):
108111
The new value
109112
'''
110113

111-
if param in {'maxIt', 'eps', 'ierr'}:
114+
if param in {'maxIt', 'eps', 'ierr', 'dt_sim'}:
112115
self._parameters[param] = value
113116

114117
elif param in {'n_parts_x', 'sx', 'n_parts_u', 'su', 'kx', 'use_chains', 'nodes_type', 'use_std_approach'}:
@@ -262,13 +265,18 @@ def solve(self):
262265

263266
# do the first iteration step
264267
logging.info("1st Iteration: {} spline parts".format(self.eqs.trajectories.n_parts_x))
265-
self._iterate()
266-
268+
try:
269+
self._iterate()
270+
except auxiliary.NanError:
271+
logging.warn("NanError")
272+
return None, None
273+
267274
# this was the first iteration
268275
# now we are getting into the loop
269276
self.nIt = 1
270277

271278
while not self.reached_accuracy and self.nIt < self._parameters['maxIt']:
279+
272280
# raise the number of spline parts
273281
self.eqs.trajectories._raise_spline_parts()
274282

@@ -280,8 +288,12 @@ def solve(self):
280288
logging.info("{}th Iteration: {} spline parts".format(self.nIt+1, self.eqs.trajectories.n_parts_x))
281289

282290
# start next iteration step
283-
self._iterate()
284-
291+
try:
292+
self._iterate()
293+
except auxiliary.NanError:
294+
logging.warn("NanError")
295+
return None, None
296+
285297
# increment iteration number
286298
self.nIt += 1
287299

@@ -308,6 +320,16 @@ def _iterate(self):
308320
309321
As a last, the resulting initial value problem is simulated.
310322
'''
323+
324+
# Note: in pytrajectory there are Three main levels of 'iteration'
325+
# Level 3: perform one LM-Step (i.e. calculate a new set of parameters)
326+
# This is implemented in solver.py. Ends when tolerances are met or
327+
# the maximum number of steps is reached
328+
# Level 2: restarts the LM-Algorithm with the last values
329+
# and stops if the desired accuracy for the initial value problem
330+
# is met or if the maximum number of steps solution attempts is reached
331+
# Level 1: increasing the spline number.
332+
# In Each step solve a nonlinear optimization problem (with LM)
311333

312334
# Initialise the spline function objects
313335
self.eqs.trajectories.init_splines()
@@ -320,16 +342,40 @@ def _iterate(self):
320342
G, DG = C.G, C.DG
321343

322344
# Solve the collocation equation system
323-
sol = self.eqs.solve(G, DG)
324345

325-
# Set the found solution
326-
self.eqs.trajectories.set_coeffs(sol)
346+
new_solver = True
347+
while True:
348+
sol = self.eqs.solve(G, DG, new_solver=new_solver)
349+
350+
# in the following iterations we want to use the same solver
351+
# object (we just had an intermediate look, whether the solution
352+
# of the initial value problem is already sufficient accurate.)
353+
354+
new_solver = False
327355

328-
# Solve the resulting initial value problem
329-
self.simulate()
330-
331-
# check if desired accuracy is reached
332-
self.check_accuracy()
356+
# Set the found solution
357+
self.eqs.trajectories.set_coeffs(sol)
358+
359+
# Solve the resulting initial value problem
360+
self.simulate()
361+
362+
# check if desired accuracy is reached
363+
self.check_accuracy()
364+
365+
# any of the follwing conditions ends the loop
366+
slvr = self.eqs.solver
367+
cond1 = self.reached_accuracy
368+
369+
# following means: solver stopped not
370+
# only because of maximum stepp # number
371+
cond2 = (not slvr.cond_num_steps) or slvr.cond_abs_tol \
372+
or slvr.cond_rel_tol
373+
cond3 = self.eqs.solver.solve_count >= self._parameters['accIt']
374+
375+
if cond1 or cond2 or cond3:
376+
break
377+
else:
378+
logging.debug('New attempt\n\n')
333379

334380
def simulate(self):
335381
'''
@@ -358,7 +404,7 @@ def simulate(self):
358404
start.append(start_dict[x])
359405

360406
# create simulation object
361-
S = Simulator(ff, T, start, self.eqs.trajectories.u)
407+
S = Simulator(ff, T, start, self.eqs.trajectories.u, dt=self._parameters['dt_sim'])
362408

363409
logging.debug("start: %s"%str(start))
364410

0 commit comments

Comments
 (0)