Skip to content

Commit 9373f8e

Browse files
committed
fix dtime integral cost calculation; trim unit tests for speed
1 parent 039b22d commit 9373f8e

2 files changed

Lines changed: 31 additions & 107 deletions

File tree

control/optimal.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1381,10 +1381,7 @@ def _cost_function(self, xvec):
13811381
else:
13821382
# Sum the integral cost over the time (second) indices
13831383
# cost += self.integral_cost(xhat[:, i], u[:, i], v[:, i], w[:, i])
1384-
cost = sum(map(
1385-
self.integral_cost, np.transpose(xhat[:, :-1]),
1386-
np.transpose(u[:, :-1]), np.transpose(v[:, :-1]),
1387-
np.transpose(w[:, :-1])))
1384+
cost = sum(map(self.integral_cost, xhat.T, u.T, v.T, w.T))
13881385

13891386
# Terminal cost
13901387
if self.terminal_cost is not None and self.x0 is not None:

control/tests/stochsys_test.py

Lines changed: 30 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,7 @@ def test_correlation():
323323
tau, Rtau = ct.correlation(T, V)
324324

325325
@pytest.mark.slow
326-
@pytest.mark.parametrize('dt', [0, 1])
326+
@pytest.mark.parametrize('dt', [0, 0.2])
327327
def test_oep(dt):
328328
# Define the system to test, with additional input
329329
# Use fixed system to avoid random errors (was csys = ct.rss(4, 2, 5))
@@ -336,127 +336,60 @@ def test_oep(dt):
336336
sys = csys if dt == 0 else dsys
337337

338338
# Create disturbances and noise (fixed, to avoid random errors)
339-
Rv = 0.1 * np.eye(1) # scalar disturbance
340-
Rw = 0.01 * np.eye(sys.noutputs)
341-
timepts = np.arange(0, 10.1, 1)
339+
dist_mag = 1e-1 # disturbance magnitude
340+
meas_mag = 1e-3 # measurement noise magnitude
341+
Rv = dist_mag**2 * np.eye(1) # scalar disturbance
342+
Rw = meas_mag**2 * np.eye(sys.noutputs)
343+
timepts = np.arange(0, 1, 0.2)
342344
V = np.array(
343-
[0 if t % 2 == 1 else 1 if t % 4 == 0 else -1 for t in timepts]
344-
).reshape(1, -1) * 0.1
345-
W = np.vstack([np.sin(2*timepts), np.cos(3*timepts)]) * 1e-3
345+
[0 if i % 2 == 1 else 1 if i % 4 == 0 else -1
346+
for i in range(timepts.size)]
347+
).reshape(1, -1) * dist_mag / 10
348+
W = np.vstack([
349+
np.sin(10*timepts/timepts[-1]), np.cos(15*timepts)/timepts[-1]
350+
]) * meas_mag / 10
346351

347352
# Generate system data
348353
U = np.sin(timepts).reshape(1, -1)
349354

350-
# No disturbances
351-
res0 = ct.input_output_response(sys, timepts, [U, V*0])
352-
Y0 = res0.outputs
353-
354355
# With disturbances and noise
355-
res1 = ct.input_output_response(sys, timepts, [U, V])
356-
Y1 = res1.outputs + W
356+
res = ct.input_output_response(sys, timepts, [U, V])
357+
Y = res.outputs + W
357358

358359
# Set up optimal estimation function using Gaussian likelihoods for cost
359360
traj_cost = opt.gaussian_likelihood_cost(sys, Rv, Rw)
360361
init_cost = lambda xhat, x: (xhat - x) @ (xhat - x)
361-
oep = opt.OptimalEstimationProblem(
362+
oep1 = opt.OptimalEstimationProblem(
362363
sys, timepts, traj_cost, terminal_cost=init_cost)
363364

364-
#
365-
# Internal testing to make sure all our functions are OK (developers)
366-
#
367-
if False:
368-
# _cost_function
369-
oep.compute_estimate(Y0, U, X0=0)
370-
assert oep._cost_function(np.hstack(
371-
[res0.states.reshape(-1), V.reshape(-1) * 0])) == 0
372-
assert oep._cost_function(np.hstack(
373-
[res0.states.reshape(-1), V.reshape(-1)])) != 0
374-
if sys.isdtime():
375-
# Collocation contstraint should be satisified for discrete time
376-
np.testing.assert_allclose(oep._collocation_constraint(
377-
np.hstack([res0.states.reshape(-1), V.reshape(-1) * 0])), 0)
378-
379-
# _compute_states_inputs: states and inputs with no noise
380-
# oep.compute_estimate(Y0, U)
381-
xhat, u, v, w = oep._compute_states_inputs(
382-
np.hstack([res0.states.reshape(-1), V.reshape(-1) * 0]))
383-
np.testing.assert_allclose(xhat, res0.states)
384-
np.testing.assert_allclose(u, U.reshape(1, -1))
385-
np.testing.assert_allclose(v, 0)
386-
np.testing.assert_allclose(w, 0)
387-
388-
# _compute_states_inputs: states and inputs with no noise
389-
oep.compute_estimate(Y1, U)
390-
xhat, u, v, w = oep._compute_states_inputs(
391-
np.hstack([res1.states.reshape(-1), V.reshape(-1)]))
392-
np.testing.assert_allclose(xhat, res1.states)
393-
np.testing.assert_allclose(u, U.reshape(1, -1))
394-
np.testing.assert_allclose(v, V)
395-
np.testing.assert_allclose(w, W)
396-
397-
#
398-
# oep.compute_estimate testing
399-
#
400-
401-
# Noise free and disturbance free
402-
nonoise_cost = opt.gaussian_likelihood_cost(sys, Rv, Rw/1e6)
403-
oep0 = opt.OptimalEstimationProblem(
404-
sys, timepts, nonoise_cost, terminal_cost=init_cost)
405-
est0 = oep0.compute_estimate(Y0, U)
406-
if sys.isdtime():
407-
# Full state estimate should be near perfect
408-
np.testing.assert_allclose(
409-
est0.states[:, -1], res0.states[:, -1], atol=1e-3, rtol=1e-3)
410-
np.testing.assert_allclose(est0.inputs, 0, atol=1e-2, rtol=1e-3)
411-
np.testing.assert_allclose(est0.outputs, 0, atol=1e-2, rtol=1e-3)
412-
else:
413-
# Estimate at end of trajectory should be very close
414-
assert est0.success
415-
np.testing.assert_allclose(
416-
est0.states[:, -1], res0.states[:, -1], atol=1e-2, rtol=1e-2)
417-
418-
# Noise free, but with disturbances and good initial guess
419-
if False:
420-
oep1 = opt.OptimalEstimationProblem(
421-
sys, timepts, nonoise_cost, terminal_cost=init_cost)
422-
est1 = oep1.compute_estimate(
423-
res1.outputs, U, initial_guess=(res1.states, V), X0=0)
424-
np.testing.assert_allclose(
425-
est1.states[:, -1], res1.states[:, -1], atol=1e-2, rtol=1e-2)
426-
if sys.isdtime():
427-
# For discrete time, estimated disturbance and noise should be close
428-
np.testing.assert_allclose(
429-
est1.inputs[:-1], V[:-1], atol=1e-2, rtol=1e-2)
430-
np.testing.assert_allclose(est1.outputs, 0, atol=1e-2, rtol=1e-2)
431-
432-
# Noise and disturbances (the standard case)
433-
est2 = oep.compute_estimate(Y1, U) # back to original OEP
434-
assert est2.success
365+
# Compute the optimal estimate
366+
est1 = oep1.compute_estimate(Y, U)
367+
assert est1.success
435368
np.testing.assert_allclose(
436-
est2.states[:, -1], res1.states[:, -1], atol=1e-1, rtol=1e-2)
369+
est1.states[:, -1], res.states[:, -1], atol=meas_mag, rtol=meas_mag)
370+
371+
# Recompute using initial guess (should be pretty fast)
372+
est2 = oep1.compute_estimate(
373+
Y, U, initial_guess=(est1.states, est1.inputs))
374+
assert est2.success
437375

438376
# Change around the inputs and disturbances
439377
sys2 = ct.ss(sys.A, sys.B[:, ::-1], sys.C, sys.D[::-1], sys.dt)
440378
oep2a = opt.OptimalEstimationProblem(
441379
sys2, timepts, traj_cost, terminal_cost=init_cost,
442380
control_indices=[1])
443381
est2a = oep2a.compute_estimate(
444-
Y1, U, initial_guess=(est2.states, est2.inputs))
445-
np.testing.assert_allclose(
446-
est2a.states[:, -1], res1.states[:, -1], atol=1e-1, rtol=1e-2)
382+
Y, U, initial_guess=(est1.states, est1.inputs))
383+
np.testing.assert_allclose(est2a.states, est2.states)
447384

448385
oep2b = opt.OptimalEstimationProblem(
449386
sys2, timepts, traj_cost, terminal_cost=init_cost,
450387
disturbance_indices=[0])
451388
est2b = oep2b.compute_estimate(
452-
Y1, U, initial_guess=(est2.states, est2.inputs))
453-
np.testing.assert_allclose(
454-
est2b.states[:, -1], res1.states[:, -1], atol=1e-1, rtol=1e-2)
455-
456-
#
457-
# Disturbance constraints
458-
#
389+
Y, U, initial_guess=(est1.states, est1.inputs))
390+
np.testing.assert_allclose(est2b.states, est2.states)
459391

392+
# Add disturbance constraints
460393
V3 = np.clip(V, 0.5, 1)
461394
traj_constraint = opt.disturbance_range_constraint(sys, 0.5, 1)
462395
oep3 = opt.OptimalEstimationProblem(
@@ -466,17 +399,11 @@ def test_oep(dt):
466399
res3 = ct.input_output_response(sys, timepts, [U, V3])
467400
Y3 = res3.outputs + W
468401

469-
# Make sure the constraint screws up the estimation problem
470-
with pytest.raises(AssertionError):
471-
est3 = oep.compute_estimate(Y3, U)
472-
np.testing.assert_allclose(
473-
est3.states[:, -1], res3.states[:, -1], atol=1e-1, rtol=1e-2)
474-
475402
# Make sure estimation is correct with constraint in place
476403
est3 = oep3.compute_estimate(Y3, U)
477404
assert est3.success
478405
np.testing.assert_allclose(
479-
est3.states[:, -1], res3.states[:, -1], atol=1e-1, rtol=1e-2)
406+
est3.states[:, -1], res3.states[:, -1], atol=meas_mag, rtol=meas_mag)
480407

481408

482409
@pytest.mark.slow

0 commit comments

Comments
 (0)