You seem to be getting the rough idea correct I think. Thanks for thinking out loud!
The .numpy module within each operator type seems to be the place where the operators are actually listed along with a mapping between graphblas and numpy
The .numpy modules use the same names as operators from NumPy. By default, these use builtin GraphBLAS operators if possible and user-defined functions compiled with numba otherwise. These namespaces are meant for conveniences and may be helpful if we were to ever try to follow the Python array API standard.
That seems to get packed up into an all and maybe from there that is how they are called, but I am not sure how/where that lookup is actually made
I'm not sure I follow. What do you mean "that is how they care called"? Calling e.g. UnaryOp._initialize() populates the graphblas.unary and graphblas.unary.numpy namespaces. For UDFs, we delay compiling them (b/c doing so is very slow and would adversely affect import times), which is why modules often have _delayed dict and custom __dir__ and __getattr__ that will correctly handle the delayed objects (as well as give deprecation warnings).
There also seems to be some step in core.operator.utils that initializes each of the op classes?
Yeah, that's currently where things get initialized, which populates the operator namespaces. Perhaps it would be clearer to move the initialization data and methods off of the classes and into e.g. core.operator.initialize (which may need refactored again after we add more backends, such as having a SuiteSparse-specific module for initializing, etc.), which would also perform the initialzation. utils isn't the first place anybody would look to see where things get initialized. I think I like this idea as a way to further simplify the operator classes and code.
Did you read my super-short description from #410 (comment)?
Here's a quick sketch of the important bits: we call e.g. UnaryOp._initialize,
which then does a regex on lib (the objects from cffi that wraps the C implementation)
to find unary operators, then we add all the typed unary operators
(as TypedBuiltinUnaryOp) to UnaryOp, which we save to gb.unary.
We do the same thing for binary operators, monoids, semirings, and indexunaryops.
I may have mentioned this before, but our team discussed this and we would like for data about operators to be declared upstream in suitesparse-graphblas as e.g. JSON or dicts. We would probably add a little more metadata as JSON or dict for additional things we do in python-graphblas. This would be an easy place for us (or an eager contributor) to add docstrings for each operator. I agree with your general sentiment that having things declarative rather than derived from sideeffects is usually clearer and preferred, and it would be especially nice to have more details in the docs website.
Adding module docstrings, class docstrings, code comments, etc. to make the code more navigable and understandable is high on my priority list, because right now I'm uniquely position to do so (I believe the rest of the team could also do so, but I can probably do so better and more completely) and this will help future contributors (or users trying to understand/debug something), which is super-important for long-term viability of the project. I will focus on this and refactoring as appropriate to make things clearer before I focus on high-level documentation, and I'm super-grateful that the rest of the team has done the vast majority of high-level documentation thus far.
Keep your questions and streams-of-thoughts coming! :)