Passivity test is ill-conditioned
See original GitHub issueWhack-a-mole!
Now this cvxopt test is flaky passivity_test.py::test_ispassive_edge_cases :
https://github.com/python-control/python-control/runs/7930805491?check_suite_focus=true#step:6:3450
2022-08-20T10:05:12.4606678Z =================================== FAILURES ===================================
2022-08-20T10:05:12.4607612Z _________________ test_ispassive_edge_cases[test_input2-True] __________________
2022-08-20T10:05:12.4607862Z
2022-08-20T10:05:12.4608255Z test_input = (array([[-3.e+12, 0.e+00],
2022-08-20T10:05:12.4608627Z [ 0.e+00, -2.e+12]]), array([[0],
2022-08-20T10:05:12.4609085Z [1]]), array([[-1, 2]]), array([[0.]]))
2022-08-20T10:05:12.4609312Z expected = True
2022-08-20T10:05:12.4609448Z
2022-08-20T10:05:12.4609567Z @pytest.mark.parametrize(
2022-08-20T10:05:12.4609948Z "test_input,expected",
2022-08-20T10:05:12.4610188Z [((A, B, C, D*0.0), True),
2022-08-20T10:05:12.4610415Z ((A_d, B, C, D), True),
2022-08-20T10:05:12.4610640Z ((A*1e12, B, C, D*0), True),
2022-08-20T10:05:12.4610854Z ((A, B*0, C*0, D), True),
2022-08-20T10:05:12.4611073Z ((A*0, B, C, D), True),
2022-08-20T10:05:12.4611293Z ((A*0, B*0, C*0, D*0), True)])
2022-08-20T10:05:12.4611683Z def test_ispassive_edge_cases(test_input, expected):
2022-08-20T10:05:12.4611958Z A = test_input[0]
2022-08-20T10:05:12.4612178Z B = test_input[1]
2022-08-20T10:05:12.4612610Z C = test_input[2]
2022-08-20T10:05:12.4612970Z D = test_input[3]
2022-08-20T10:05:12.4613469Z sys = ss(A, B, C, D)
2022-08-20T10:05:12.4613753Z > assert(passivity.ispassive(sys) == expected)
2022-08-20T10:05:12.4613952Z
2022-08-20T10:05:12.4614077Z control/tests/passivity_test.py:115:
2022-08-20T10:05:12.4614373Z _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
2022-08-20T10:05:12.4614742Z control/passivity.py:292: in ispassive
2022-08-20T10:05:12.4615096Z return solve_passivity_LMI(sys, rho=ofp_index, nu=ifp_index) is not None
2022-08-20T10:05:12.4615456Z control/passivity.py:177: in solve_passivity_LMI
2022-08-20T10:05:12.4615750Z sol = cvx.solvers.sdp(c, Gs=Gs, hs=hs)
2022-08-20T10:05:12.4616316Z /usr/share/miniconda3/envs/test-env/lib/python3.10/site-packages/cvxopt/coneprog.py:4126: in sdp
2022-08-20T10:05:12.4616810Z sol = conelp(c, G, h, dims, A = A, b = b, primalstart = ps, dualstart = ds, kktsolver = kktsolver, options = options)
2022-08-20T10:05:12.4617643Z /usr/share/miniconda3/envs/test-env/lib/python3.10/site-packages/cvxopt/coneprog.py:1395: in conelp
2022-08-20T10:05:12.4618038Z misc.update_scaling(W, lmbda, ds, dz)
2022-08-20T10:05:12.4618322Z _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
2022-08-20T10:05:12.4618485Z
2022-08-20T10:05:12.4618810Z W = {'beta': [], 'd': <0x1 matrix, tc='d'>, 'di': <0x1 matrix, tc='d'>, 'dnl': <0x1 matrix, tc='d'>, ...}
2022-08-20T10:05:12.4619335Z lmbda = <6x1 matrix, tc='d'>, s = <13x1 matrix, tc='d'>
2022-08-20T10:05:12.4619636Z z = <13x1 matrix, tc='d'>
2022-08-20T10:05:12.4619773Z
2022-08-20T10:05:12.4619885Z def update_scaling(W, lmbda, s, z):
2022-08-20T10:05:12.4620221Z """
2022-08-20T10:05:12.4620607Z Updates the Nesterov-Todd scaling matrix W and the scaled variable
2022-08-20T10:05:12.4621042Z lmbda so that on exit
2022-08-20T10:05:12.4621247Z
2022-08-20T10:05:12.4621547Z W * zt = W^{-T} * st = lmbda.
2022-08-20T10:05:12.4621773Z
2022-08-20T10:05:12.4622145Z On entry, the nonlinear, 'l' and 'q' components of the arguments s
2022-08-20T10:05:12.4622629Z and z contain W^{-T}*st and W*zt, i.e, the new iterates in the current
2022-08-20T10:05:12.4622920Z scaling.
2022-08-20T10:05:12.4623119Z
2022-08-20T10:05:12.4623489Z The 's' components contain the factors Ls, Lz in a factorization of
2022-08-20T10:05:12.4624030Z the new iterates in the current scaling, W^{-T}*st = Ls*Ls',
2022-08-20T10:05:12.4624341Z W*zt = Lz*Lz'.
2022-08-20T10:05:12.4624645Z """
2022-08-20T10:05:12.4624832Z
2022-08-20T10:05:12.4625008Z
2022-08-20T10:05:12.4625390Z # Nonlinear and 'l' blocks
2022-08-20T10:05:12.4625606Z #
2022-08-20T10:05:12.4626008Z # d := d .* sqrt( s ./ z )
2022-08-20T10:05:12.4626266Z # lmbda := lmbda .* sqrt(s) .* sqrt(z)
2022-08-20T10:05:12.4626501Z
2022-08-20T10:05:12.4626752Z if 'dnl' in W:
2022-08-20T10:05:12.4627026Z mnl = len(W['dnl'])
2022-08-20T10:05:12.4627256Z else:
2022-08-20T10:05:12.4627460Z mnl = 0
2022-08-20T10:05:12.4627713Z ml = len(W['d'])
2022-08-20T10:05:12.4627934Z m = mnl + ml
2022-08-20T10:05:12.4628165Z s[:m] = base.sqrt( s[:m] )
2022-08-20T10:05:12.4628397Z z[:m] = base.sqrt( z[:m] )
2022-08-20T10:05:12.4629372Z
2022-08-20T10:05:12.4629630Z # d := d .* s .* z
2022-08-20T10:05:12.4629940Z if 'dnl' in W:
2022-08-20T10:05:12.4630299Z blas.tbmv(s, W['dnl'], n = mnl, k = 0, ldA = 1)
2022-08-20T10:05:12.4630693Z blas.tbsv(z, W['dnl'], n = mnl, k = 0, ldA = 1)
2022-08-20T10:05:12.4631031Z W['dnli'][:] = W['dnl'][:] ** -1
2022-08-20T10:05:12.4631420Z blas.tbmv(s, W['d'], n = ml, k = 0, ldA = 1, offsetA = mnl)
2022-08-20T10:05:12.4631840Z blas.tbsv(z, W['d'], n = ml, k = 0, ldA = 1, offsetA = mnl)
2022-08-20T10:05:12.4632171Z W['di'][:] = W['d'][:] ** -1
2022-08-20T10:05:12.4632391Z
2022-08-20T10:05:12.4632808Z # lmbda := s .* z
2022-08-20T10:05:12.4633059Z blas.copy(s, lmbda, n = m)
2022-08-20T10:05:12.4633323Z blas.tbmv(z, lmbda, n = m, k = 0, ldA = 1)
2022-08-20T10:05:12.4633622Z
2022-08-20T10:05:12.4633806Z
2022-08-20T10:05:12.4634057Z # 'q' blocks.
2022-08-20T10:05:12.4634267Z #
2022-08-20T10:05:12.4634539Z # Let st and zt be the new variables in the old scaling:
2022-08-20T10:05:12.4634792Z #
2022-08-20T10:05:12.4635010Z # st = s_k, zt = z_k
2022-08-20T10:05:12.4635228Z #
2022-08-20T10:05:12.4635560Z # and a = sqrt(st' * J * st), b = sqrt(zt' * J * zt).
2022-08-20T10:05:12.4635811Z #
2022-08-20T10:05:12.4636209Z # 1. Compute the hyperbolic Householder transformation 2*q*q' - J
2022-08-20T10:05:12.4636519Z # that maps st/a to zt/b.
2022-08-20T10:05:12.4636747Z #
2022-08-20T10:05:12.4637058Z # c = sqrt( (1 + st'*zt/(a*b)) / 2 )
2022-08-20T10:05:12.4637420Z # q = (st/a + J*zt/b) / (2*c).
2022-08-20T10:05:12.4637667Z #
2022-08-20T10:05:12.4637894Z # The new scaling point is
2022-08-20T10:05:12.4638110Z #
2022-08-20T10:05:12.4638578Z # wk := betak * sqrt(a/b) * (2*v[k]*v[k]' - J) * q
2022-08-20T10:05:12.4638818Z #
2022-08-20T10:05:12.4639082Z # with betak = W['beta'][k].
2022-08-20T10:05:12.4639302Z #
2022-08-20T10:05:12.4639636Z # 3. The scaled variable:
2022-08-20T10:05:12.4639831Z #
2022-08-20T10:05:12.4640044Z # lambda_k0 = sqrt(a*b) * c
2022-08-20T10:05:12.4640423Z # lambda_k1 = sqrt(a*b) * ( (2vk*vk' - J) * (-d*q + u/2) )_1
2022-08-20T10:05:12.4640655Z #
2022-08-20T10:05:12.4640839Z # where
2022-08-20T10:05:12.4641026Z #
2022-08-20T10:05:12.4641267Z # u = st/a - J*zt/b
2022-08-20T10:05:12.4641620Z # d = ( vk0 * (vk'*u) + u0/2 ) / (2*vk0 *(vk'*q) - q0 + 1).
2022-08-20T10:05:12.4641864Z #
2022-08-20T10:05:12.4642182Z # 4. Update scaling
2022-08-20T10:05:12.4642379Z #
2022-08-20T10:05:12.4642577Z # v[k] := wk^1/2
2022-08-20T10:05:12.4642824Z # = 1 / sqrt(2*(wk0 + 1)) * (wk + e).
2022-08-20T10:05:12.4643060Z # beta[k] *= sqrt(a/b)
2022-08-20T10:05:12.4643271Z
2022-08-20T10:05:12.4643455Z ind = m
2022-08-20T10:05:12.4643731Z for k in range(len(W['v'])):
2022-08-20T10:05:12.4643952Z
2022-08-20T10:05:12.4644314Z v = W['v'][k]
2022-08-20T10:05:12.4644524Z m = len(v)
2022-08-20T10:05:12.4644728Z
2022-08-20T10:05:12.4645040Z # ln = sqrt( lambda_k' * J * lambda_k )
2022-08-20T10:05:12.4645428Z ln = jnrm2(lmbda, n = m, offset = ind)
2022-08-20T10:05:12.4645658Z
2022-08-20T10:05:12.4645978Z # a = sqrt( sk' * J * sk ) = sqrt( st' * J * st )
2022-08-20T10:05:12.4646340Z # s := s / a = st / a
2022-08-20T10:05:12.4646607Z aa = jnrm2(s, offset = ind, n = m)
2022-08-20T10:05:12.4646898Z blas.scal(1.0/aa, s, offset = ind, n = m)
2022-08-20T10:05:12.4647123Z
2022-08-20T10:05:12.4647455Z # b = sqrt( zk' * J * zk ) = sqrt( zt' * J * zt )
2022-08-20T10:05:12.4647818Z # z := z / a = zt / b
2022-08-20T10:05:12.4648054Z bb = jnrm2(z, offset = ind, n = m)
2022-08-20T10:05:12.4648334Z blas.scal(1.0/bb, z, offset = ind, n = m)
2022-08-20T10:05:12.4648565Z
2022-08-20T10:05:12.4648857Z # c = sqrt( ( 1 + (st'*zt) / (a*b) ) / 2 )
2022-08-20T10:05:12.4649290Z cc = math.sqrt( ( 1.0 + blas.dot(s, z, offsetx = ind, offsety =
2022-08-20T10:05:12.4649582Z ind, n = m) ) / 2.0 )
2022-08-20T10:05:12.4649796Z
2022-08-20T10:05:12.4650041Z # vs = v' * st / a
2022-08-20T10:05:12.4650311Z vs = blas.dot(v, s, offsety = ind, n = m)
2022-08-20T10:05:12.4650549Z
2022-08-20T10:05:12.4651253Z # vz = v' * J *zt / b
2022-08-20T10:05:12.4651724Z vz = jdot(v, z, offsety = ind, n = m)
2022-08-20T10:05:12.4651960Z
2022-08-20T10:05:12.4652334Z # vq = v' * q where q = (st/a + J * zt/b) / (2 * c)
2022-08-20T10:05:12.4652810Z vq = (vs + vz ) / 2.0 / cc
2022-08-20T10:05:12.4653032Z
2022-08-20T10:05:12.4653355Z # vu = v' * u where u = st/a - J * zt/b
2022-08-20T10:05:12.4653655Z vu = vs - vz
2022-08-20T10:05:12.4653865Z
2022-08-20T10:05:12.4654053Z # lambda_k0 = c
2022-08-20T10:05:12.4654285Z lmbda[ind] = cc
2022-08-20T10:05:12.4654602Z
2022-08-20T10:05:12.4654873Z # wk0 = 2 * vk0 * (vk' * q) - q0
2022-08-20T10:05:12.4655466Z wk0 = 2 * v[0] * vq - ( s[ind] + z[ind] ) / 2.0 / cc
2022-08-20T10:05:12.4655699Z
2022-08-20T10:05:12.4655989Z # d = (v[0] * (vk' * u) - u0/2) / (wk0 + 1)
2022-08-20T10:05:12.4656492Z dd = (v[0] * vu - s[ind]/2.0 + z[ind]/2.0) / (wk0 + 1.0)
2022-08-20T10:05:12.4656755Z
2022-08-20T10:05:12.4657096Z # lambda_k1 = 2 * v_k1 * vk' * (-d*q + u/2) - d*q1 + u1/2
2022-08-20T10:05:12.4657651Z blas.copy(v, lmbda, offsetx = 1, offsety = ind+1, n = m-1)
2022-08-20T10:05:12.4658095Z blas.scal(2.0 * (-dd * vq + 0.5 * vu), lmbda, offset = ind+1,
2022-08-20T10:05:12.4658422Z n = m-1)
2022-08-20T10:05:12.4658904Z blas.axpy(s, lmbda, 0.5 * (1.0 - dd/cc), offsetx = ind+1, offsety
2022-08-20T10:05:12.4659242Z = ind+1, n = m-1)
2022-08-20T10:05:12.4659529Z blas.axpy(z, lmbda, 0.5 * (1.0 + dd/cc), offsetx = ind+1, offsety
2022-08-20T10:05:12.4659850Z = ind+1, n = m-1)
2022-08-20T10:05:12.4660056Z
2022-08-20T10:05:12.4660408Z # Scale so that sqrt(lambda_k' * J * lambda_k) = sqrt(aa*bb).
2022-08-20T10:05:12.4660855Z blas.scal(math.sqrt(aa*bb), lmbda, offset = ind, n = m)
2022-08-20T10:05:12.4661118Z
2022-08-20T10:05:12.4661396Z # v := (2*v*v' - J) * q
2022-08-20T10:05:12.4661843Z # = 2 * (v'*q) * v' - (J* st/a + zt/b) / (2*c)
2022-08-20T10:05:12.4662220Z blas.scal(2.0 * vq, v)
2022-08-20T10:05:12.4662532Z v[0] -= s[ind] / 2.0 / cc
2022-08-20T10:05:12.4662939Z blas.axpy(s, v, 0.5/cc, offsetx = ind+1, offsety = 1, n = m-1)
2022-08-20T10:05:12.4663341Z blas.axpy(z, v, -0.5/cc, offsetx = ind, n = m)
2022-08-20T10:05:12.4663589Z
2022-08-20T10:05:12.4663824Z # v := v^{1/2} = 1/sqrt(2 * (v0 + 1)) * (v + e)
2022-08-20T10:05:12.4664050Z v[0] += 1.0
2022-08-20T10:05:12.4664310Z blas.scal(1.0 / math.sqrt(2.0 * v[0]), v)
2022-08-20T10:05:12.4664548Z
2022-08-20T10:05:12.4664751Z # beta[k] *= ( aa / bb )**1/2
2022-08-20T10:05:12.4665097Z W['beta'][k] *= math.sqrt( aa / bb )
2022-08-20T10:05:12.4665331Z
2022-08-20T10:05:12.4665510Z ind += m
2022-08-20T10:05:12.4665708Z
2022-08-20T10:05:12.4665895Z
2022-08-20T10:05:12.4666158Z # 's' blocks
2022-08-20T10:05:12.4666360Z #
2022-08-20T10:05:12.4666633Z # Let st, zt be the updated variables in the old scaling:
2022-08-20T10:05:12.4666886Z #
2022-08-20T10:05:12.4667184Z # st = Ls * Ls', zt = Lz * Lz'.
2022-08-20T10:05:12.4667411Z #
2022-08-20T10:05:12.4667735Z # where Ls and Lz are the 's' components of s, z.
2022-08-20T10:05:12.4667992Z #
2022-08-20T10:05:12.4668302Z # 1. SVD Lz'*Ls = Uk * lambda_k^+ * Vk'.
2022-08-20T10:05:12.4668526Z #
2022-08-20T10:05:12.4668739Z # 2. New scaling is
2022-08-20T10:05:12.4668957Z #
2022-08-20T10:05:12.4669289Z # r[k] := r[k] * Ls * Vk * diag(lambda_k^+)^{-1/2}
2022-08-20T10:05:12.4669698Z # rti[k] := r[k] * Lz * Uk * diag(lambda_k^+)^{-1/2}.
2022-08-20T10:05:12.4669951Z #
2022-08-20T10:05:12.4670124Z
2022-08-20T10:05:12.4670493Z work = matrix(0.0, (max( [0] + [r.size[0] for r in W['r']])**2, 1))
2022-08-20T10:05:12.4671033Z ind = mnl + ml + sum([ len(v) for v in W['v'] ])
2022-08-20T10:05:12.4671299Z ind2, ind3 = ind, 0
2022-08-20T10:05:12.4671594Z for k in range(len(W['r'])):
2022-08-20T10:05:12.4671924Z r, rti = W['r'][k], W['rti'][k]
2022-08-20T10:05:12.4672164Z m = r.size[0]
2022-08-20T10:05:12.4672356Z
2022-08-20T10:05:12.4672564Z # r := r*sk = r*Ls
2022-08-20T10:05:12.4672858Z blas.gemm(r, s, work, m = m, n = m, k = m, ldB = m, ldC = m,
2022-08-20T10:05:12.4673126Z offsetB = ind2)
2022-08-20T10:05:12.4673545Z blas.copy(work, r, n = m**2)
2022-08-20T10:05:12.4673885Z
2022-08-20T10:05:12.4674070Z # rti := rti*zk = rti*Lz
2022-08-20T10:05:12.4674352Z blas.gemm(rti, z, work, m = m, n = m, k = m, ldB = m, ldC = m,
2022-08-20T10:05:12.4674620Z offsetB = ind2)
2022-08-20T10:05:12.4674939Z blas.copy(work, rti, n = m**2)
2022-08-20T10:05:12.4675176Z
2022-08-20T10:05:12.4696294Z # SVD Lz'*Ls = U * lmbds^+ * V'; store U in sk and V' in zk.
2022-08-20T10:05:12.4696880Z blas.gemm(z, s, work, transA = 'T', m = m, n = m, k = m, ldA = m,
2022-08-20T10:05:12.4697207Z ldB = m, ldC = m, offsetA = ind2, offsetB = ind2)
2022-08-20T10:05:12.4697652Z lapack.gesvd(work, lmbda, jobu = 'A', jobvt = 'A', m = m, n = m,
2022-08-20T10:05:12.4698006Z ldA = m, U = s, Vt = z, ldU = m, ldVt = m, offsetS = ind,
2022-08-20T10:05:12.4698319Z offsetU = ind2, offsetVt = ind2)
2022-08-20T10:05:12.4698543Z
2022-08-20T10:05:12.4698741Z # r := r*V
2022-08-20T10:05:12.4699127Z blas.gemm(r, z, work, transB = 'T', m = m, n = m, k = m, ldB = m,
2022-08-20T10:05:12.4699410Z ldC = m, offsetB = ind2)
2022-08-20T10:05:12.4699675Z blas.copy(work, r, n = m**2)
2022-08-20T10:05:12.4700021Z
2022-08-20T10:05:12.4700204Z # rti := rti*U
2022-08-20T10:05:12.4700602Z blas.gemm(rti, s, work, n = m, m = m, k = m, ldB = m, ldC = m,
2022-08-20T10:05:12.4700867Z offsetB = ind2)
2022-08-20T10:05:12.4701098Z blas.copy(work, rti, n = m**2)
2022-08-20T10:05:12.4701318Z
2022-08-20T10:05:12.4701645Z # r := r*lambda^{-1/2}; rti := rti*lambda^{-1/2}
2022-08-20T10:05:12.4702010Z for i in range(m):
2022-08-20T10:05:12.4702263Z > a = 1.0 / math.sqrt(lmbda[ind+i])
2022-08-20T10:05:12.4702566Z E ZeroDivisionError: float division by zero
2022-08-20T10:05:12.4702754Z
2022-08-20T10:05:12.4703110Z /usr/share/miniconda3/envs/test-env/lib/python3.10/site-packages/cvxopt/misc.py:628: ZeroDivisionError
_Originally posted by @bnavigator in https://github.com/python-control/python-control/issues/759#issuecomment-1221272177_
Issue Analytics
- State:
- Created a year ago
- Comments:10 (10 by maintainers)
Top Results From Across the Web
Passivity Options
If checked, an iterative process is applied to correct possible ill-conditioning issues deriving from using weighting factors that may be close to zero...
Read more >Fast and Reliable Passivity Assessment and Enforcement with ...
This paper presents a new method for passivity testing based on ... ally a dense matrix and in practice it could be very...
Read more >A Fast Passivity Test for Stable Descriptor Systems via Skew ...
sometimes ill-conditioned transformations. This paper proposes a simple DS passivity test based on the insight that the sum of.
Read more >A fast passivity test for descriptor systems via structure ...
This paper proposes a simple DS passivity test based on the key insight that the sum of a passive system and its adjoint...
Read more >Passivity Test of Immittance Descriptor Systems Based on ...
by possibly ill-conditioned matrix inversions). The eigenvalue- based DS passivity test in [15] is only applicable to scalar functions.
Read more >
Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free
Top Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found

The test ist marked XFAIL and still fails occasionally.
From yesterday: https://github.com/python-control/python-control/actions/runs/3502370893/jobs/5866722552#step:6:997
Actually, this error continues to be an issue. I’ve had to re-run most of the recent CI runs on failed tests and this was the error that showed up. I’m not sure there is a fix, but we should at least leave this open to keep track of the fact that there is an outstanding issue.
(I have to admit to being confused about why the error shows up randomly; what is the change in underlying state that leads to an error sometimes but not others?)