-
Notifications
You must be signed in to change notification settings - Fork 21
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
Plot constraint violations #218
Plot constraint violations #218
Conversation
I have a very basic question about testing such a thing: But this seems inefficient if I want to test it with different simulations.
Thanks for any help! |
The error Github found may be due to this:
|
My approach to testing edits I make to a package, like opty, is to first create a conda environment with all the development dependencies. We include this file in the repo for such a thing: https://github.com/csu-hmc/opty/blob/master/opty-dev-env.yml. After that, I install the development version of opty in a way that any import of opty will always reflect the current state of the opty repo on my computer. $ cd /path/to/opty/repo
$ conda env create -f opty-dev-env.yml
$ conda activate opty-dev
$ python -m pip install --no-deps -e . # installs version in the current directory, i.e. the development version Now as long as I have that conda environment activated it will use the opty version in So then, if I want to build the docs, just: $ cd docs
$ make html All of the constraint violations plots should generate using your edits to the direct_collocation.py file. |
Looks like your code doesn't work on the cycling or drone example:
|
As long as you document this, I think it is reasonable to ask the user to pass in the right number and combination of axes. They already have to do that if you create the axes first. |
A development setup of opty is explained in the README. |
This seems to be the problem with the drone and the cycling simulations. Both more than 10 constraints and give axes, |
In addition to what Jason has already mentioned. You can easily run personal scripts, like some fun example, by creating a file like
This file can just use |
Thanks to both of you! |
I think I finally understood! Had to think about it for a good while. |
Prints a warning if axes are given, with sharex = True (which makes no sense here). |
I rotate the labels -90°. This way, they are always lined up with the ticks and never smear into each other.
|
The failed check with cycling and drone is that they do not give the right number of axes, as before. |
opty/direct_collocation.py
Outdated
if axes.ravel()[i]._sharex is not None: | ||
warner = True | ||
if warner == True: | ||
print('Set sharex=False or remove, it makes no sense here') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We typically don't have print statements in library code because their display can't be controlled. Best to avoid warnings and just raise errors.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also don't think we need to warn about things like this. The best approach here is to explain in the docstring what the user must do if they want to pass in their own axes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also don't think we need to warn about things like this. The best approach here is to explain in the docstring what the user must do if they want to pass in their own axes.
If sharex = True, the program will not interrupt, but the plots are meaningless. Do you mean, I just let this happen, and explain in the method, that it should be set to False or removed? Should this be mentioned in the opty Documentation?
If YES, I have to find out how to do this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you mean, I just let this happen, and explain in the method, that it should be set to False or removed?
Yes, but I don't think you need to explain anything about sharex. plt.subplots()
has a very long list of arguments and it is not our job to explain them all here. You simply have to state to the user the minimal information they need to know to pass in a correct axes object.
I don't know because we need to see how it looks on all the examples and any other test cases you are using. |
You have to correct that. |
Do you mean I correct the drone example? If Yes, same would be true with the cycling example (?) |
Yes.
Yes.
Give correct number.
All should happen in this PR. |
|
I will switch to normal numbers, and play around with exponentials (eliminate the double minus) and variable h next. |
…so that they all contain approx. same number of bars
@@ -230,7 +230,7 @@ | |||
|
|||
# %% | |||
# Plot the constraint violations. | |||
fig, axes = plt.subplots(2, figsize=(12.8, 9.6), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These were ratio 3:4 aspect, I suspect on purpose. Is there a reason to change that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These were ratio 3:4 aspect, I suspect on purpose. Is there a reason to change that?
No, I did not give any thought. I just changed to make it run with my proposal
len(axes) = len(self.collocator.instance_constraints) // bars_per_plot + 2 | ||
if len(self.collocator.instance_constraints) % bars_per_plot != 0. | ||
len(axes) = len(self.collocator.instance_constraints) // bars_per_plot + 1 | ||
if len(self.collocator.instance_constraints) % bars_per_plot = 0. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is no way for the user to know what the value of bars_per_plot
is in the docstring. Just state the number. This could be more simply stated in general. Something like, "six instance constraints will be plotted per axes, so you'll need to pass in enough rows to accomodate the number of instance constraints".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is no way for the user to know what the value of
bars_per_plot
is in the docstring. Just state the number. This could be more simply stated in general. Something like, "six instance constraints will be plotted per axes, so you'll need to pass in enough rows to accomodate the number of instance constraints".
My approach was this: I select between 6 to 10 bars per plot to prevent situations like with the double pendulum., where there is only on bar on the last plot. If the wrong number of axes is given, the method will tell the user how many are needed - unless he only gives one, which is always wrong.
opty/direct_collocation.py
Outdated
fig, axes = plt.subplots(1 + plot_inst_viols, squeeze=False, | ||
layout='compressed') | ||
fig, axes = plt.subplots(1 + num_plots, squeeze=False, | ||
layout='compressed', figsize=(8, 2.0*(num_plots+1))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is better to leave the fig size for the user to create manually. Setting figsize inside a function like this can result in giant figures.
for a in sm.preorder_traversal(exp1): | ||
if isinstance(a, sm.Float): | ||
exp1 = exp1.subs(a, round(a, 2)) | ||
instance_constr_plot.append(exp1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Python has printing controls that work like this:
In [7]: '{:1.3f}'.format(1.2394359593939)
Out[7]: '1.239'
is there a reason to not use something like that instead of literally rounding the numerical values?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Python has printing controls that work like this:
In [7]: '{:1.3f}'.format(1.2394359593939) Out[7]: '1.239'
is there a reason to not use something like that instead of literally rounding the numerical values?
Timo suggested this to me, so I used it, since it worked. I had no idea how to do such things.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I see now there may not be a straight forward way to use Python printing methods on SymPy Floats in an expression. I thought there should be a setting on the StrPrinter that would control display of decimals, but there doesn't seem to be one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe Timo already showed this (I struggle to find his comment), but this is a way to control printing of floats:
In [47]: from mpmath.libmp import prec_to_dps, to_str as mlib_to_str
In [48]: class MyPrinter(sm.StrPrinter):
...: _default_settings = {
...: "order": None,
...: "full_prec": "auto",
...: "sympy_integers": False,
...: "abbrev": False,
...: "perm_cyclic": True,
...: "min": None,
...: "max": None,
...: "dps": None,
...: }
...:
...: def _print_Float(self, expr):
...: prec = expr._prec
...: if prec < 5:
...: dps = 0
...: else:
...: dps = prec_to_dps(expr._prec)
...: if self._settings['dps']:
...: dps = self._settings['dps']
...: if self._settings["full_prec"] is True:
...: strip = False
...: elif self._settings["full_prec"] is False:
...: strip = True
...: elif self._settings["full_prec"] == "auto":
...: strip = self._print_level > 1
...: low = self._settings["min"] if "min" in self._settings else None
...: high = self._settings["max"] if "max" in self._settings else None
...: rv = mlib_to_str(expr._mpf_, dps, strip_zeros=strip, min_fixed=low, max_fixed=high)
...: if rv.startswith('-.0'):
...: rv = '-0.' + rv[3:]
...: elif rv.startswith('.0'):
...: rv = '0.' + rv[2:]
...: if rv.startswith('+'):
...: # e.g., +inf -> inf
...: rv = rv[1:]
...: return rv
...:
In [49]: MyPrinter(settings={'dps': 3}).doprint(f(1.329294))
Out[49]: 'f(1.33)'
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hah, I found Timo's solution which was the same.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe Timo already showed this (I struggle to find his comment), but this is a way to control printing of floats:
In [47]: from mpmath.libmp import prec_to_dps, to_str as mlib_to_str In [48]: class MyPrinter(sm.StrPrinter): ...: _default_settings = { ...: "order": None, ...: "full_prec": "auto", ...: "sympy_integers": False, ...: "abbrev": False, ...: "perm_cyclic": True, ...: "min": None, ...: "max": None, ...: "dps": None, ...: } ...: ...: def _print_Float(self, expr): ...: prec = expr._prec ...: if prec < 5: ...: dps = 0 ...: else: ...: dps = prec_to_dps(expr._prec) ...: if self._settings['dps']: ...: dps = self._settings['dps'] ...: if self._settings["full_prec"] is True: ...: strip = False ...: elif self._settings["full_prec"] is False: ...: strip = True ...: elif self._settings["full_prec"] == "auto": ...: strip = self._print_level > 1 ...: low = self._settings["min"] if "min" in self._settings else None ...: high = self._settings["max"] if "max" in self._settings else None ...: rv = mlib_to_str(expr._mpf_, dps, strip_zeros=strip, min_fixed=low, max_fixed=high) ...: if rv.startswith('-.0'): ...: rv = '-0.' + rv[3:] ...: elif rv.startswith('.0'): ...: rv = '0.' + rv[2:] ...: if rv.startswith('+'): ...: # e.g., +inf -> inf ...: rv = rv[1:] ...: return rv ...: In [49]: MyPrinter(settings={'dps': 3}).doprint(f(1.329294)) Out[49]: 'f(1.33)'
He did show this or something similar, but I liked the simple round(a, 3) much better! :-)
I'll merge if you remove the figsize kwarg inside the function and try to simplify the docstring explanation to how many rows they'll need in the axes. |
My idea, based on your (very justified!) complaint about the double pendulum is this:
|
I added figsize(...) to plot_trajectories, so it gives nice pictures without the user giving axes. (I see no way to get by without this) |
I may have found a solution to print the correct time with variable h. |
One possible way around it is not to use the Let's keep the automated figsize and the layout=compressed. I typically do not muck with figsize in library code due to figures becoming too large, but I think this is as good as we can get now. |
Thanks! |
Thank YOU! I bet you spent more time coaching me compared to having it done yourself! :-) A general question: |
I'm a teacher by trade, so that's just the way it always is :)
Yes
They will open issues on this repository.
Yes, you can safely delete, but make sure to pull the master from this repository in your local master to get the updates. |
Yeah, part of your job! :-) |
I think, I found a pretty good way to print the rounded number with variable h. I simply look around the symbols tree (I guess that is what it is called). It works for many ways of giving the duration, like It does NOT work for The variable inteval may have any name, not just h I'd like to make a PR to see if it passes all the test (I think it would..) but I am afraid, I am swamping you with PRs! |
h is stored in Problem, so you'll always have the symbol inside problem. |
At the top of class Problem, I set, in line 155, |
presumably not, but you are teaching me new words every day :) |
:-) :-) German is very similar to Dutch. |
Just a note for future, in the next sympy version you can do: StrPrinter(settings={"dps": 3}).doprint(Function('f')(1.329294)) which returns
|
Thanks! |
Yes, we just merged the change. |
Both Timo and I showed you this above at least twice. |
Seems I am getting old! :-( |
I tried to make the prints of the method plot_constraint violations a bit easier to read. In particular