RobotLocomotion / drake

Model-based design and verification for robotics.
https://drake.mit.edu
Other
3.17k stars 1.24k forks source link

pydrake documentation has c++ code in it #12591

Open RussTedrake opened 4 years ago

RussTedrake commented 4 years ago

Some of the best documentation that we have (in c++/doxygen) has nice little code snippets, but always in c++. For a python user looking at the pydrake docs (especially one unfamiliar with c++), these are scary and even confusing.

MultibodyPlant's documentation has a few examples: https://drake.mit.edu/pydrake/pydrake.multibody.plant.html#pydrake.multibody.plant.MultibodyPlant_

Thinking for a bit about how we could realistically/sustainably improve the situation, the doxygen code blocks are surrounded by @code @endcode blocks. Would it be crazy to add a @pycode block type that c++ doxygen would know to ignore and the pydrake sphinx code would know to render?

Assigning to @sherm1 to start the discussion. cc @EricCousineau-TRI , @jamiesnape .

sherm1 commented 4 years ago

This seems like a great idea, Russ. This (and many other things) suggests to me that proper support of PyDrake can't be fully automated -- I wonder if it is a big enough job to warrant a full time position for someone?

jamiesnape commented 4 years ago

We are in the process of automating, though it will be months rather than weeks to implement.

RussTedrake commented 4 years ago

@jamiesnape — my understanding is that you are automating the creation of bindings. I don’t know how you would automate this step (touching up the code samples in the docs?). Is that fair?

jamiesnape commented 4 years ago

We probably wouldn't ever want to automate those. The original idea of inserting @pycode seems the best idea. I was more responding to "proper support of PyDrake can't be fully automated", which I do not think is a true statement.

SeanCurtis-TRI commented 4 years ago

"proper support of PyDrake" comes down to semantics. Ultimately, if that includes converting C++ code examples in doxygen to a python version, then the statement can easily be considered true.

One thing @sherm1 and I vaguely discussed in this context is that a full-time role responsible for improving all aspects of the pydrake experience -- including creating/coordinating an appropriate set of tutorials, responsibility for the final look of python documentation (even if that means going in and adding @pycode tags by hand), etc.

If I were to take a liberty and define "proper support of PyDrake" to that highest level -- maximizing ease of use, then that will never be fully automated. And perhaps we need to consider if the value we place on it corresponds to dedicated personnel.

SeanCurtis-TRI commented 4 years ago

Another random thought on the distinction between libdrake and pydrake.

When we have a class that captures a non-owning reference to some other object, we carefully document this dependency and the need to keep it alive beyond the lifespan of the capturing instance.

When that object is bound, we typically/always use keep_alive on that captured object. This act makes the guidance about lifespan irrelevant for python users. Sure, they won't be hurt if they keep yet another reference to the object. But we're asking them to do something that has no concrete value for them and diluting information density in what is being communicated.

In an ideal world, pydrake documentation wouldn't include that kind of guidance. These are the kinds of things that would improve the pydrake experience, currently aren't automated (although certainly could be with appropriate instrumentation -- some sort of @captured_parameter tag, for example. But this would fall nicely in the baliwick of someone who should be looking at pydrake with exactly that mindset and drive the development of tools and processes to optimize the pydrake experience.

EricCousineau-TRI commented 4 years ago

+1 on the ideal of having someone fully devoted to quality pydrake development and maintenance; that would be awesome!

However, I also agree with @jamiesnape that it's not necessary to support this feature. I think the proposed solution -- specifying what time of language is in a code segment -- is achievable without a full-time person.

Regarding the full automation parts, yes, things that @SeanCurtis-TRI have pointed are certainly points that would most likely require some form of human annotation, either in pydrake docs or in hand-modified bindings code.

As a request for keeping this issue on-topic, and keeping this input traceable, could you put those future notes about automation directly in #7889?

sherm1 commented 4 years ago

I think the proposed solution -- specifying what type of language is in a code segment -- is achievable without a full-time person.

Although the ability to mark a code snippet or example with its language needs to be implemented just once, someone has to write (and test) a pythonic code snippet to match each C++ snippet, both extant and future. That seems like a big job to me and one that should be in the capable hands of a devoted expert (or experts) rather than left to individual C++ programmers working on Drake's guts.

sherm1 commented 4 years ago

Reassigning to Toffee for disposition.

EricCousineau-TRI commented 4 years ago

(Late response triggered by Drake Dev mtg :P)

[...] (and test) a pythonic code snippet [...]

Do we really do that for C++ code? I've seen bugs in the C++ docstring examples -- yes, they got fixed, but it indicates that we may not have rigorous testing / review at creation time. I don't think it's fair to have expectations be higher for Python code than C++ code? Also, perhaps a slightly wrong example (that can be corrected) is higher value than having no example at all?

[...] That seems like a big job [...]

Er, why? I feel like you're over-inflating the difficulty of Python? Why not just have some type out "what they think" it should be in Python, and then someone comes in and corrects them. What's hard about that?

I feel like us trying to "do it 100% right the first time" is too high of a bar sometimes, hurts us in cases like this (where failure is not catastrophic), and can hurt the bigger picture? Sometimes, a feature / docstring / whatevs gets additional reviews and bugfixes when people actually use it.

sherm1 commented 4 years ago

I feel confident that all the C++ developers in Dynamics can write C++ examples, likely cribbing from their C++ unit tests. I'm skeptical that non-users of our Python interface can write reasonable examples. But maybe that can be cleaned up in review.

drewhamiltonasdf commented 10 months ago

I have been looking through open issues related to documentation to see if there is anything I could reasonably help with (as I put together my series of pure Python tutorials); with an eye towards things that are too boring for anyone else to tackle. I was wondering if this @pycode label was ever added? I have been generating (very boring) minimal working Python examples (starting with pydrake.math, which is easy). As in:

pydrake.math.abs:

# Numerical Example:
numerical = math.abs(-13)

# Symbolic Example:
x = Variable("x")
expr = math.abs(x**3 - 5*x)

# AutoDiff Example:
x_ad = AutoDiffXd(-13, [1.0])
expr_ad = math.abs(x_ad**3 - 5*x_ad)

# Print results:
print("Numeric: ", numerical)
display(Markdown("Symbolic: $" + ToLatex(expr) + "$"))
print("AD value: ", expr_ad.value())
print("AD derivatives: ", expr_ad.derivatives())

This is painstaking boring work that I doubt anyone here wants to do, but I am just steadily adding examples like this for my own reference, and I am an obsessive enough personality that I just might make it through the majority of the python API. Every time I visit the docs for any method or class, I make a version for myself in a Jupyter notebook with a minimal working example.

math.abs is probably not the best example of how this is useful, but it's helped me quite a bit to have a quick reference for other more complicated functions that require inputs with a specific Drake type. If you aren't familiar with the codebase, this usually equates to opening multiple tabs of documentation to figure out how to construct instances of different types, then setting them up until your constructor doesn't barf.

I think the scipy documentation is a great example of what people want when they're getting started. You go to scipy.linalg.leslie, a fairly esoteric feature to model discrete-time, age-structured population growth and they have this:

from scipy.linalg import leslie
leslie([0.1, 2.0, 1.0, 0.1], [0.2, 0.8, 0.7])

It might seem silly if you are a developer at Toyota, but most of us mere mortals want to copy paste something, check the type of the output, check the shape of the output, see what happens if we change the shape of the input, and then move on to implementing it in our code.

If someone makes it possible for me to share these, and yall let me know the best way to structure things, I would be happy to share. In fact, with a little coaching, I'm willing to commit to plowing through an entire namespace to get the ball rolling.

Obviously, if/when I get to something like say, pydrake.math.BalanceQuadraticForms, it would be ideal if my example doesn't just run, but actually has a mathematically sensible input and output, which could get a little tricky, but I'm sure there are enough people here that know what that means to help me tidy up sloppy examples.

As always, I am also content to keep it in a separate repo with my growing list of beginner tutorials, but I think the project is great and I'd love to help out with some of the boring bits so those of us following Russ' lectures from home can really get somewhere...

That was a mouthful.

-Drew

@SeanCurtis-TRI

RussTedrake commented 10 months ago

@drewhamiltonasdf -- Thank you! I love the idea of making progress on this. A few of our key developers are traveling right now, but I'm sure they will weigh in soon. Here are a few quick thoughts from me:

drewhamiltonasdf commented 10 months ago

@RussTedrake -- Great news! If there's no place to put them yet it really won't hold me up at all as far as generating all the... let's call them "snippets". I'm just keeping a little notebook locally for myself as I march through the different namespaces. I actually already finished putting a python snippet together for every class and function in math: https://github.com/drewhamiltonasdf/DrakeTutorials/blob/main/pydrake.math.ipynb

At the top of notebook, I put the corresponding @pycode snippets for whatever had @code tags already in pydrake.math.

I also just went ahead and put snippets together for everything else, and I have quite a few that are significantly longer, for example for BSpline etc. Not sure if this is helpful, but maybe there is some place to stick these later if I get a good set of tutorials together.

Lastly, I'm just wondering if it might be helpful for me to keep everything in one place, at least initially, for you guys to review before submitting pull requests that modify every header file in an entire namespace. This way you can see the output or run it (I suppose even test it?) before it gets turned into a \* comment *\

Hope that's not too much. Just want the contribution to be useful and not require too much rework.

jwnimmer-tri commented 10 months ago

FYI I'm working on a PR to provide the markup tags we'll need for this adventure. I'll backlink it here once it's posted.

drewhamiltonasdf commented 10 months ago

FYI I'm working on a PR to provide the markup tags we'll need for this adventure. I'll backlink it here once it's posted.

🙏Thanks! 🙏

jwnimmer-tri commented 9 months ago

See https://drake.mit.edu/documentation_instructions.html#python_detail_ for an example of the new markup.

drewhamiltonasdf commented 9 months ago

See https://drake.mit.edu/documentation_instructions.html#python_detail_ for an example of the new markup.

I have not forgotten about this. Still have a huge bucket full of code snippets I can contribute. Looking to get around to this soon.