libcxxwrap-julia icon indicating copy to clipboard operation
libcxxwrap-julia copied to clipboard

Building against libcxxwrap-julia require definition of comparison operators between incompatible types

Open afossa opened this issue 1 year ago • 4 comments

I am building a Julia interface to dace using libcxxwrap-julia and CxxWrap, and I recently came across a compilation error that requires me to define the "less than" operator between types for which is not defined, i.e. DACE::DA and DACE::AlgebraicMatrix<DACE::DA>.

Here is the output I obtain when trying to build the wrapper on Ubuntu 22.04 LTS: build.log config.log

I started obtaining this error after defining the comparison operators ==,!=,<,>,<=,>= between DACE::DA objects and double in interfaces/cxx/include/dace/DA.h

    friend inline bool DACE_API operator==(const DA &da1, const DA &da2) { return da1.cons() == da2.cons(); };  //!< Equality comparison
    friend inline bool DACE_API operator==(const DA &da, const double c) { return da.cons() == c; };            //!< Equality comparison
    friend inline bool DACE_API operator==(const double c, const DA &da) { return c == da.cons(); };            //!< Equality comparison

    friend inline bool DACE_API operator!=(const DA &da1, const DA &da2) { return da1.cons() != da2.cons(); };  //!< Inequality comparison
    friend inline bool DACE_API operator!=(const DA &da, const double c) { return da.cons() != c; };            //!< Inequality comparison
    friend inline bool DACE_API operator!=(const double c, const DA &da) { return c != da.cons(); };            //!< Inequality comparison

    friend inline bool DACE_API operator<(const DA &da1, const DA &da2) { return da1.cons() < da2.cons(); };    //!< Less than comparison
    friend inline bool DACE_API operator<(const DA &da, const double c) { return da.cons() < c; };              //!< Less than comparison
    friend inline bool DACE_API operator<(const double c, const DA &da) { return c < da.cons(); };              //!< Less than comparison

    friend inline bool DACE_API operator>(const DA &da1, const DA &da2) { return da1.cons() > da2.cons(); };    //!< Greater than comparison
    friend inline bool DACE_API operator>(const DA &da, const double c) { return da.cons() > c; };              //!< Greater than comparison
    friend inline bool DACE_API operator>(const double c, const DA &da) { return c > da.cons(); };              //!< Greater than comparison

    friend inline bool DACE_API operator<=(const DA &da1, const DA &da2) { return da1.cons() <= da2.cons(); };  //!< Less than or equal comparison
    friend inline bool DACE_API operator<=(const DA &da, const double c) { return da.cons() <= c; };            //!< Less than or equal comparison
    friend inline bool DACE_API operator<=(const double c, const DA &da) { return c <= da.cons(); };            //!< Less than or equal comparison

    friend inline bool DACE_API operator>=(const DA &da1, const DA &da2) { return da1.cons() >= da2.cons(); };  //!< Greater than or equal comparison
    friend inline bool DACE_API operator>=(const DA &da, const double c) { return da.cons() >= c; };            //!< Greater than or equal comparison
    friend inline bool DACE_API operator>=(const double c, const DA &da) { return c >= da.cons(); };            //!< Greater than or equal comparison

I am also able to overcome it by adding these dummy definitions at the top of the interface interfaces/julia/dace_julia.cxx

namespace DACE {
    inline bool DACE_API operator<(const AlgebraicMatrix<DA> &mat, const DA &da) { throw std::runtime_error("Comparison between DA and AlgebraicMatrix not defined"); };
    inline bool DACE_API operator<(const DA &da, const AlgebraicMatrix<DA> &mat) { throw std::runtime_error("Comparison between DA and AlgebraicMatrix not defined"); };
    inline bool DACE_API operator<(const AlgebraicMatrix<DA> &mt1, const AlgebraicMatrix<DA> &mt2) { throw std::runtime_error("Comparison between two AlgebraicMatrix not defined"); };
}

but this is really a workaround that shouldn't be there.

More surprisingly, I do not encounter the same error with DACE::AlgebraicVector<DA>, which is wrapped pretty much in the same way as DACE::AlgebraicMatrix<DA> is. Building the library without the Julia wrapper bits also works, so it is really a matter of how it interacts with libcxxwrap-julia.

Do you have any insight on the root cause of this error? Thanks!

afossa avatar Sep 19 '24 17:09 afossa

Is this against the latest libcxxwrap-julia (main branch)? It is probably related to the added support for more STL containters, maybe this check isn't working:

https://github.com/JuliaInterop/libcxxwrap-julia/blob/587f9feb6b64baa512856af2d508fec1f63583bc/include/jlcxx/stl.hpp#L657

barche avatar Sep 19 '24 21:09 barche

It happens both against main and against the latest release v0.13.2. But yes, it seems related to the instantiation of STL containers. The logs I attached are actually against the latest release and not main.

afossa avatar Sep 19 '24 21:09 afossa

OK, if you don't need sets or maps then you can comment out these lines in apply_stl as a temporary workaround. I was already working on refactoring this part of the code, so only the part of the STL that is requested gets wrapped, i.e. if you use a std::vector with your type, only vector will be wrapped and not all supported containers.

barche avatar Sep 20 '24 05:09 barche

Thank you for the hint!

I did a couple of other tests, and it seems that the check you mentioned before does work in most of the cases, but it gets "confused" when the type to which the STL is applied is itself a kind of "container". What I have observed is the following:

I have my wrapped type DA on which I explicitly apply the STL, i.e.:

jlcxx::stl::apply_stl<DA>(mod)

Then I have the parametric type AlgebraicMatrix<T> which is defined for T being a DA or a double as:

mod.add_type<jlcxx::Parametric<jlcxx::TypeVar<1>>>("AlgebraicMatrix", jlcxx::julia_type("AbstractMatrix", "Base"))
    .apply<AlgebraicMatrix<DA>, AlgebraicMatrix<double>>([](auto wrapped) {
...
});

The type DA is used several times as the element type of std::vectors, while AlgebraicMatrix<T> is used as element of an STL container (a vector) only once:

mod.method("hessian", [](const AlgebraicVector<DA>& vec)->std::vector<AlgebraicMatrix<DA>> { return vec.hessian(); });

Moreover, the DA type defines the "less than" and the other comparison operators between DAs, and between a DA and a double.

To get a compiler error, I need both the hessian() method being wrapped, AND the comparison operators being defined for DAs. If I remove either one of the two, the wrapper is compiled just fine, and I can call it from Julia.

afossa avatar Sep 20 '24 15:09 afossa

Can you verify if PR #175 fixes this for you?

barche avatar Nov 07 '24 18:11 barche

I recompiled the library from your branch test-compile-times, and I can confirm that it fixes my issue. I can now define hessian() as in my previous comment, and have it return a std::vector<AlgebraicMatrix<DA>> without problems.

Tested on both Ubuntu 22.04 (gcc) and MacOS Sonoma (Apple Clang, Intel chip)

Thanks!

afossa avatar Nov 08 '24 00:11 afossa

Thanks for testing, I'll integrate this into a new release. Unfortunately the changes are not binary compatible with previous versions, so this will become part of CxxWrap 0.17.

barche avatar Nov 08 '24 07:11 barche