Skip to content

BUG: "sp.linalg.solve_discrete_are" fails for random data #6572

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 20 commits into from
Sep 19, 2016
Merged

BUG: "sp.linalg.solve_discrete_are" fails for random data #6572

merged 20 commits into from
Sep 19, 2016

Conversation

ilayn
Copy link
Member

@ilayn ilayn commented Sep 13, 2016

This fixes #2251 and its tests per the discussion on the issue page. The doc string is updated.

This fixes #2251 and its tests per the discussion on the issue page. The
doc string is updated.
This is Py2 issue I guess
Hopefully this will make Travis happy.
For some unknown reason, the test works locally but trips up on the
master branch. Here testing whether this is due to the matrix object.
@person142
Copy link
Member

BTW you can check all the pep8 failures locally by installing pep8 (pip install pep8) and running pep8 scipy from the top-level scipy directory. It's looking like that will be the last thing you need for the Travis build to succeed.

@ilayn
Copy link
Member Author

ilayn commented Sep 13, 2016

Bah, I guess I'm not cut for this. I've checked the files thrice with pep8 and it still fails.

@rgommers rgommers added scipy.linalg maintenance Items related to regular maintenance tasks labels Sep 14, 2016
@ilayn
Copy link
Member Author

ilayn commented Sep 14, 2016

I've slept over it checked with some more examples and I think this PR is ready for review. Thanks .

U.S. Energy Research and Development Agency under contract
ERDA-E(49-18)-2087.
http://dspace.mit.edu/bitstream/handle/1721.1/1301/R-0859-05666488.pdf
P. van Dooren , "A Generalized Eigenvalue Approach For Solving Riccati
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know it was like this in the original, but it would be better to put this in a references section and just cite it here. See:

https://github.com/numpy/numpy/blob/master/doc/HOWTO_DOCUMENT.rst.txt#docstring-standard

@person142
Copy link
Member

Though the previous method doesn't always work, it still might be nice to see how the two compare speed-wise. (Especially on more sizable problems.) Benchmarks would go here:

https://github.com/scipy/scipy/blob/master/benchmarks/benchmarks/linalg.py

You can run them with python runtests.py --bench (you will need asv; pip install asv). An intro to writing them is here:

https://asv.readthedocs.io/en/latest/writing_benchmarks.html

@ilayn
Copy link
Member Author

ilayn commented Sep 14, 2016

Unfortunately, on a windows machine I can't do any of the build related stuff including the runtests. But since the previous one was not reliable a speedtest won't make too much of a difference. There are many extras to this implementation to make it comparable to matlab. But this version is at least consistent.

@person142
Copy link
Member

person142 commented Sep 15, 2016

But since the previous one was not reliable a speedtest won't make too much of a difference.

I guess I worry that someone will have working code that suddenly slows down a lot. Breaking their case to make other cases work might not make them happy. If there's a significant speed difference it's possible that we might want to have both versions provided that for the old one there's a way to either

  • document a reasonable subset of problems on which it will work or
  • detect failures and raise an exception.

Maybe I'm just being silly though.

Edit: yes, I think I'm being silly.

@person142
Copy link
Member

Also it's possible to run benchmarks without runtests.py by doing something like asv dev --bench <benchmark> which won't trigger a build and only runs the benchmark once. (So take the results with a grain of salt.)

@person142
Copy link
Member

It would be nice to include more DAREX examples in the tests.

@ilayn
Copy link
Member Author

ilayn commented Sep 15, 2016

Indeed but then I have to include balancing algorithms which is basically copying everything from my other implementation which doesn't seem that easy to convert to SciPy way without breaking some back-compatibility.

@person142
Copy link
Member

Or fix the wrong result with this patch and do another major PR for a general focus

I like this one. I'm still in favor of putting in DAREX examples, however. Not all of them will fail (the only three I tried work fine), and they provide extra coverage that wasn't possible before (e.g. singular r). If some of them fail then we can mark them as known failures (and they can be a target which future pull requests are aiming for); I just want to test the boundaries of how far this patch takes us.

@ilayn
Copy link
Member Author

ilayn commented Sep 17, 2016

@person142 Oh that I can tell. In the CAREX examples, there are two examples fail** even after my balancing implementations (ex.6 and 12 in the matlab version). In DAREX I have to finish the conversion from matlab to Python but so far so good but I'm sure it will fail on a few suspicious looking ones.

One major pain is the PEP8 and those matrices don't go well together, is there a way to ignore PEP8 temporarily of things or what is the preferred way of entering them? Tall Submatrices ?

** By fail I mean the accuracy is not good enough absolutely though satisfactory in the relative sense (max error is less than a certain percent of the 1-norm the matrix).

@pv
Copy link
Member

pv commented Sep 17, 2016

If the matrices are big, I would put them as data files (e.g. text files), under scipy/linalg/tests/data, and load via mat = np.loadtxt(os.path.join(os.path.dirname(__file__), 'data', 'darex_1.dat')) etc. (or, alternatively, put as npy data files, anything goes)
If they are small, I would just follow pep8 format --- reformatting probably doesn't take that much time even if done manually.

removed previous two test cases as they are already in darex, fails are
commented out
@ilayn
Copy link
Member Author

ilayn commented Sep 17, 2016

darex #12 is what I mean by having a relatively accurate but not acceptable in terms of absolute terms solution, as the solution should be diag(1,1e12) but gets approx. diag(1,1e12 + 2.5e7).

@person142
Copy link
Member

Ok, for the benefit of anyone looking at this: with the new set of tests the old function has 4 exceptions due to ill-conditioning in r and one failure. See e.g. here:

https://github.com/person142/scipy/tree/old-discrete-are

Copy link
Member

@person142 person142 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I left some comments. They're mostly pretty small things; I think this is pretty close to being done.

ERDA-E(49-18)-2087.
http://dspace.mit.edu/bitstream/handle/1721.1/1301/R-0859-05666488.pdf
The equation is solved by forming the extended symplectic matrix pencil,
as described in [1]_, .. math:`H - \lambda J` given by the block matrices
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


[ A 0 B ] [ I 0 B ]
[ -Q I 0 ] - \lambda * [ 0 A^T 0 ]
[ 0 0 R ] [ 0 -B^T 0 ]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't rendering right.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs :: (two colons) at the end of the preceding text, ie. ... given by the block matrices::

if np.iscomplexobj(x):
r_or_c = complex

if not np.equal(*x.shape):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This fails for a 1 x 1 array.


if m != a.shape[0]:
raise ValueError("Matrix a and b should have the same number of rows.")

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same thing about spacing.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't get your point on spacing

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not a show-stopper, but I'd like related groups of if statements to not have any spaces between them (and maybe be converted to if/elif's) so that they cluster together visually. But this might just be a personal preference so I won't press the point.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought you wanted a spacing between the ifs inside the for loop so I extrapolated from that, but I have no preference.


if m != q.shape[0]:
raise ValueError("Matrix a and q should have the same shape.")

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto on spacing.

up, ul, uu = lu(u00)

if 1/cond(uu) < np.spacing(1.):
return np.array([[]])
Copy link
Member

@person142 person142 Sep 18, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, forgot to clarify this before: failure conditions should raise an exception (in this case a LinAlgError).

sym_threshold = np.max(np.spacing([1000., norm(u_sym, 1)]))

if np.max(np.abs(u_sym)) > sym_threshold:
return np.array([[]])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exception here too.

[3.7730, -30.2800, 14.6900]]) * 0.001,
np.diag([50, 0, 0, 0, 50, 0, 0, 0, 0]),
np.eye(3)),
## darex #12
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have some suggested changes for this test:

https://github.com/person142/scipy/blob/ilayn/scipy/linalg/tests/test_solvers.py#L136

It turns the failing tests into known failures so that they're more visible.

np.array([[1, 2], [1, 3]]),
np.array([[1, 1+1j], [1-1j, 2]]),
np.array([[2, -2j], [2j, 3]])),
# User-reported
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For got to mention this: reference the original github issue here so it's easy to find later.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, forgot to include this...

np.eye(3),
1e6 * np.eye(3),
1e6 * np.eye(3),
"Fails"),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok so I got a little lazy putting in the causes for failure here; should probably at least be something like "unsatisfactory absolute error" if that's the cause.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No actually it is a complete failure to find a valid solution. The other two I marked it as bad accuracy

@ilayn
Copy link
Member Author

ilayn commented Sep 19, 2016

This hopefully concludes the requested changes. Please let me know if there are additional items

@person142
Copy link
Member

I'm happy (the CircleCI passed, not sure why it's still yellow), but I'll wait to get a second opinion from another developer.


# Check the deviation from symmetry for success
u_sym = u00.conj().T.dot(u10)
u_sym -= u_sym.conj().T
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will bomb if u_sym is real, because in-place operations on overlapping memory regions is undefined behavior.
For extra fun, you don't see it for small matrices due to buffering.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Iow, should be u_sym = u_sym - u_sym.conj().T

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow. That's really good to know. I'll fix it in a moment. Is there any place where I can read more about it?

Copy link
Member

@pv pv Sep 19, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's the issue about Numpy arrays and views to numpy arrays, eg b = a[::2]; b[0] = 1 modifies also a. I think it's mentioned in most numpy tutorials. With in-place operations etc., you want to make sure the output operand is not a view of any kind of the input.

However, I think this particular caveat here will go away in near future numpy/numpy#8043. There are also long threads about this in the numpy issue tracker, but I'd recommend those only for entertainment purposes now that there's a consensus on how the issue will be addressed.

@pv
Copy link
Member

pv commented Sep 19, 2016

Looks OK to me apart from that one cmment.

@person142
Copy link
Member

Also would be good to squash things.

blank line reduction, removal of redundant indexing
@pv
Copy link
Member

pv commented Sep 19, 2016

@person142: there's an option to squash and merge in the github merge button

@ilayn
Copy link
Member Author

ilayn commented Sep 19, 2016

I don't know what "squash" refers to actually.

@pv pv merged commit 9eaeddc into scipy:master Sep 19, 2016
@pv
Copy link
Member

pv commented Sep 19, 2016

Thanks, merged!

@pv pv added this to the 0.19.0 milestone Sep 19, 2016
@person142
Copy link
Member

Great! Thanks for sticking with us @ilayn, and I look forward to your future improvements!

@ilayn
Copy link
Member Author

ilayn commented Sep 19, 2016

Pleasure is mine.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
maintenance Items related to regular maintenance tasks scipy.linalg
Projects
None yet
Development

Successfully merging this pull request may close these issues.

solve_discrete_are in scipy.linalg does (sometimes) not solve correctly (Trac #1732)
4 participants