I have some suggestions after reading through more of the documentation:
- More of the objects' parameters, like the
Controller's tfinal and a State's q values and a Solver's BCs, should be arguments to the respective constructors. I don't want to have to futz with object parameters after object creation, and I don't want to be able to create an object that isn't usable because something isn't yet set. Take inspiration from scikit-learn, with those long parameter lists, mostly kwargs set to defaults, with BIG docstrings explaining how each controls the object.
- There should be fewer ways to do things. I want one right way. That way can be flexible, but right now, e.g., initializing a
Solution from a file, a frame number in memory, from a lower-level list of dimensions, etc. constitute a lot of pretty different methods, each of which takes brain space. Give me one function or one constructor with well-documented parameters to handle the different cases. Removing support for less-used options or folding them in to others will greatly shorten the code and make it easier to maintain. For example, a Controller can iterate over basically a linspace of times, or over a list of output times, or just give an output frame every kth iteration. If you say "Always give me a list", then that covers the first two use cases (because I can in-line a linspace call somewhere), and you can remove the other two paths from the code.
States take a Domain, Patch, Grid, or list of Dimensions at initialization, and then the Solution, which contains States also takes geometry. Why? Can't it get the geometry by asking its sub-objects for their geometries? Maybe there is a good reason, but I found this confusing and possibly redundant.
- The documentation says
Patch "contains a reference to a nearly identical Grid object". If they're nearly identical, why are there two, neither inheriting from the other? Remember the Zen of Python, "There should be one-- and preferably only one --obvious way to do it."
- Limiters are still confusing to me. The fact they're set by number instead of by name or enum makes them all the more abstruse. Instead of a dictionary from int->function, do string->function or something.
problem_data feels like it should belong to the Solver for a given problem, not to the State.
- What is the difference between
compute_p and compute_F? I get the sense p is defined over the whole geometry, and F can only be a single value. But they're both derived quantities. I say the concepts should be merged: You can either compute a derived quantity over the whole field, or you can sum out a scalar value.
- I'm finding myself not using the
run_app_from_main utility. It's easier to call run on a Controller and then call the plot function or do anything else I want with it afterward. I feel like trying to make a command-line utility with htmlplot=True and other specially-named arguments isn't necessary and again gives me an extra way to do things (also hides where some things are actually happening), when my degrees of freedom already feel overwhelming.
Of all of them, the first is the most important.
In my opinion, on the surface, at the level of examples, it should be far fewer than 300 lines to get going. A lot of complexity can be abstracted or deduplicated away. Seduce me as a user.
That said, I think the division of things into the various .py files makes pretty good sense. The fact the Riemann Solvers live in a totally different package was my most major point of confusion. There is just a lot of code. I find myself greping to find things often.
I have some suggestions after reading through more of the documentation:
Controller'stfinaland aState'sqvalues and aSolver's BCs, should be arguments to the respective constructors. I don't want to have to futz with object parameters after object creation, and I don't want to be able to create an object that isn't usable because something isn't yet set. Take inspiration from scikit-learn, with those long parameter lists, mostly kwargs set to defaults, with BIG docstrings explaining how each controls the object.Solutionfrom a file, a frame number in memory, from a lower-level list of dimensions, etc. constitute a lot of pretty different methods, each of which takes brain space. Give me one function or one constructor with well-documented parameters to handle the different cases. Removing support for less-used options or folding them in to others will greatly shorten the code and make it easier to maintain. For example, aControllercan iterate over basically alinspaceof times, or over a list of output times, or just give an output frame everykth iteration. If you say "Always give me a list", then that covers the first two use cases (because I can in-line alinspacecall somewhere), and you can remove the other two paths from the code.States take aDomain,Patch,Grid, or list ofDimensions at initialization, and then theSolution, which containsStates also takes geometry. Why? Can't it get the geometry by asking its sub-objects for their geometries? Maybe there is a good reason, but I found this confusing and possibly redundant.Patch"contains a reference to a nearly identicalGridobject". If they're nearly identical, why are there two, neither inheriting from the other? Remember the Zen of Python, "There should be one-- and preferably only one --obvious way to do it."problem_datafeels like it should belong to theSolverfor a given problem, not to theState.compute_pandcompute_F? I get the sensepis defined over the whole geometry, andFcan only be a single value. But they're both derived quantities. I say the concepts should be merged: You can either compute a derived quantity over the whole field, or you can sum out a scalar value.run_app_from_mainutility. It's easier to callrunon aControllerand then call theplotfunction or do anything else I want with it afterward. I feel like trying to make a command-line utility withhtmlplot=Trueand other specially-named arguments isn't necessary and again gives me an extra way to do things (also hides where some things are actually happening), when my degrees of freedom already feel overwhelming.Of all of them, the first is the most important.
In my opinion, on the surface, at the level of examples, it should be far fewer than 300 lines to get going. A lot of complexity can be abstracted or deduplicated away. Seduce me as a user.
That said, I think the division of things into the various
.pyfiles makes pretty good sense. The fact the Riemann Solvers live in a totally different package was my most major point of confusion. There is just a lot of code. I find myselfgreping to find things often.