-
Notifications
You must be signed in to change notification settings - Fork 33
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
First version of caller for libs #104
Conversation
Even though this might have some inconsistencies, I think it is ready for review now. |
@hannes14 Can you have a look at the tests failing for the resolutions? I have fixed most, the other ones are tests for length, which has changed with our new setup. Do we return the length incorrectly? |
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.
So from what I understand so far, this is a low level interface to the interpreter, plus fix ups for the resolutions (i.e. move some stuff to the C++ side), plus some use of the new CxxWrap tuple return values, plus a way of calling library functions on existing high level objects.
What I am not convinced of is the need to call such functions via Singular.SOMENAMEINCAPSLIKEGAP.function. This doesn't just create a bunch of top-of-module-level Julia functions that apply to the existing high level objects that we had before in Singular.jl. For example, in Singular, when I import a library, those functions get injected into scope and I can just call them on interpreter objects directly. (Or at least I think it does; just checking this with Hans now.)
Is there documentation in the docs of the low level interface and how one can use/extend it?
Thanks very much for the code you have written, by the way. This is enormously helpful.
By the way, when you are "using Singular", all the symbols exported in Singular are available at the top level. You don't need to write Singular.PolynomialRing or Singular.QQ, etc. In fact, it is bad practice to do this, because it hides cases where things should have been exported for the user, but have accidentally been omitted from the export list. In particular, Singular.blah should be blah in all the test code! |
I do not really understand that comment can you elaborate on what is bothering you about it in particular? |
It's not convenient for the user. Who wants to type all of that? Remember Singular.jl is designed to be used standalone, not just as a backend for something else. The capital letters really have to be changed. Consider LibPoly instead of POLY, assuming that is what the name of the Singular library is. It's fine to be able to access the functions via this, but the user will not want to do this. However, let's see if @hannes14 has an opinion on this. Does the Singular interpreter force you to write Poly.blah to call the function blah in the Poly library, or does it just inject the function names so you can just type blah? Maybe some of the function names already exist because we already implemented them directly via the kernel? I certainly prefer that rather than have them handled via the interpreter. LibPoly sounds like default Singular functionality rather than functionality from a library. I guess I don't mind default Singular functionality being accessible via Interpreter.blah, for the cases where we haven't wrapped the corresponding kernel function. But surely true library functionality should be imported for the user. |
I changed it to
Absolutely not the problem. |
return return_dict | ||
end | ||
|
||
function convert_return_value(single_value, rng = nothing) |
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.
Some comments here and elsewhere are badly missing. For starters, what is single_value
expected to contain?
# end | ||
|
||
casting_functions_pre = Dict(:NUMBER_CMD => (libSingular.NUMBER_CMD_CASTER, true, ()), | ||
:RING_CMD => (libSingular.RING_CMD_CASTER, false, ()), |
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.
A comment explaining the meaning of the entries of those tuples would be nice
src/caller.jl
Outdated
end | ||
|
||
function prepare_argument(x::Array{Int64,1}) | ||
return Any[ mapping_types_reversed[:INTVEC_CMD], libSingular.jl_array_to_intvec(x) ], nothing |
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 find this whole business of generating an array in the C code in function get_type_mapper
, exporting this to Julia, then turning it into a "reverse" mapping dictionary mapping_types_reversed
there, then using that to convert a Julia symbol to an integer on the Julia level, then passing this back to the kernel, overall somewhat hard to track.
Can't we radically simplify this, by creating that "reverse mapping" dictionary in the kernel (possibly even as a plain C++ map!), and never exporting it from there? Then the above line would become just this:
return Any[ mapping_types_reversed[:INTVEC_CMD], libSingular.jl_array_to_intvec(x) ], nothing | |
return Any[ :INTVEC_CMD, libSingular.jl_array_to_intvec(x) ], nothing |
And in caller.cpp
code, you only need to modify translate_singular_type
, to expect a julia symbol as first argument, instead of integer. It then only needs to modify that integer.
I believe with this relatively straightforward change, things get quite a bit simpler.
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 missed that also create_casting_functions
and friends would need to be updated).
Anyway, thinking some more about this, it would probably be even simpler if indeed some of this stuff was replaced by a program that generates the required data (just as we do in https://github.com/gap-packages/SingularInterface) -- then that C/C++ program could in particular creates a .jl
file which simply defines Julia constants with the values of INTVEC_CMD
etc.. Then, we can use these everywhere instead of symbols, and completely dispense with the conversions between symbols and ints. Also, Julia could then optimize the code some more, I guess.
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.
Looking at the CxxWrap.jl "documentation" (i.e., its README -- is there anything else?), I guess one could also export the yytokentype
enum to Julia. But from the explanations there, it is unclear to me whether one can convert the enum values into an Cint
, as we would want.
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 think we should open a ticket for this and deal with it later. I doubt Sebastian has time to work on such a thing now.
src/caller.jl
Outdated
function low_level_caller_rng(lib::String, name::String, ring, args...) | ||
libSingular.load_library(lib) | ||
arguments = [prepare_argument(i) for i in args] | ||
arguments = Any[ i for (i, j) in arguments ] |
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.
So you throw away the rings here. Is this safe? What will happen if I pass in an argument that lives in a different ring? At least in the past, this could crash Singular, IIRC (but perhaps I am wrong? @hannes14 will know better :-)
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.
Ideally, we would compare rings, yes.
rng = nothing | ||
for (i, j) in arguments | ||
if j != nothing | ||
rng = j |
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.
So, you take the last ring of all the rings. Why not the first (and then insert a break
here)? Or, even better: Why not check that all subsequent rings are compatible with the first -- as I also suggested for other variant of this function. With pseudo code like this:
arguments = []
for i in args
i, j = prepare_argument(i)
push!(arguments, i)
if j != nothing
if rng == nothing
rng = j
else
# TODO: check "compatibility, e.g. rng.ptr == j.ptr
# (but perhaps that is too restrictive?)
end
end
With that, the two function variants can be merged, with this ring-free variant becoming just return low_level_caller(lib, name, nothing, args...)
deps/parselibs.jl
Outdated
if libs.stderr != "" | ||
error("from libparse: $(libs.stderr)") | ||
end | ||
libs_splitted = split(libs.stdout,"\n")[4:end-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.
I think a comment here would be very helpful; something like this:
# The output of libparse looks as follows: each line has the form ...
# .... give generic description of the form ...
# For a concrete example, it might be this:
# ... concrete example ...
# From this, we need to extra ...
deps/parselibs.jl
Outdated
end | ||
libs_splitted = split(libs.stdout,"\n")[4:end-1] | ||
libs_splitted = [ split(i," ") for i in libs_splitted ] | ||
libs_splitted = [ [ j for j in i if j != ""] for i in libs_splitted ] |
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.
This is redundant if you add keepempty = false
to your call to split
deps/parselibs.jl
Outdated
error("from libparse: $(libs.stderr)") | ||
end | ||
libs_splitted = split(libs.stdout,"\n")[4:end-1] | ||
libs_splitted = [ split(i," ") for i in libs_splitted ] |
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.
The next line becomes redundant if you do this instead:
libs_splitted = [ split(i," ") for i in libs_splitted ] | |
libs_splitted = [ split(i," ", keepempty = false) for i in libs_splitted ] |
Also, it seems you prefer list comprehensions (which do me is very Pythonic), while I prefer classic FP style map/filter/... (probably because it is close to what I'd do in GAP). I have no idea what is considered to be "more typical Julia" or preferred in this project; perhaps it doesn't matter. But just to mention it, I think you could also do this (but this is not a change request):
libs_splitted = [ split(i," ") for i in libs_splitted ] | |
libs_splitted = map( i-> split(i," ", keepempty = false), libs_splitted ] |
It would be a pity if this work bitrotted. I've thus now rebased this, and squashed it into one commit; then added one commit with a few minor changes that address a few of my own remarks (it can be squashed later, I just wanted to make transparent what I did). I wonder if perhaps this PR could be split into some more digestible parts; e.g. the syzygy related changes seem as if they might be useful independently of the library caller stuff? |
It seems to come close to passing, except for the off by one error in resolutions and a missing function. If we can get it passing, we should just merge it. Singular.jl is not so mature as to agonise over every PR. |
@fingolfin Your changes are fine. As for the resolutions, we should probably send an email to Hans (I also already tagged him somewhere in some comment) since I have no idea if the changes or the tests are actually wrong. I think it is mostly an off by one error, that is strangely deep down in some Singular data structure. I will send him an email, asking about this. |
* Check overflow when converting Julia arrays/matrices to internal Singualr vector structure * Use C-structs for type mapper instead of stl * Added comments * Remove unnecessary `rng_ptr` variable from `prepare_argument` * In Meta.jl, use `get` instead of `&&?:` * Simplified internal library caller code
I added some comments and pushed some tweaks. More to come later. |
I asked @hannes14 about the length failures and it seems we should subtract 1 from the length as in the original code. I've made these changes, which should kill 6 of the 8 test failures. |
I propose we add tickets for the remaining three todos and work on fixing those at some later date. People need access to this as soon as possible, so we should get it working rather than letting it bitrot. |
Codecov Report
@@ Coverage Diff @@
## master #104 +/- ##
=====================================
Coverage 0% 0%
=====================================
Files 34 36 +2
Lines 2783 2877 +94
=====================================
- Misses 2783 2877 +94
Continue to review full report at Codecov.
|
I'm going to merge this now, as we are about to have some breaking changes in Singular.jl, and I don't fancy rebasing this patch. We can argue about the definition of a resolution later and change it if necessary. As far as I see, there are just the following changes that need to be added in future:
I will eventually open tickets for these, as @sebasguts has only volunteered to do the first of these. |
First version of caller for libs
This adds a first version for calling all library functions.
Example for the current looks
What is currently in this PR (to be updated)
A C++-level caller (file
deps/src/caller.cpp
) for library functions, that automatically translates the Julia input into valid Singular input. Currently supported types (all low-level libSingular CxxWrap objects)A julia level
low_level_caller
(filesrc/libsingular/caller.jl
), that calls library functions and translates the output. All high-level Singular.jl types are supported as input (as long as their low-level equivalent is mentioned above). Current supported output isA parser that parses all Singular
.lib
files and creates a julia dictionary. To be hooked into the build script (filesdeps/libparser.jl
andetc/lib_parser.sh
).A meta programming part (file
src/libsingular/Meta.jl
) that creates a module for each library and adds all global functions from that library. Note here that all this module generation happens at precompilation time. So no time at the normal startup is wasted.What needs to be done
bigintmat
, or convert them directly to something in Julia