// Copyright 2022 DeepMind Technologies Limited // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef MUJOCO_PYTHON_FUNCTIONS_H_ #define MUJOCO_PYTHON_FUNCTIONS_H_ #include #include #include #include #include #include #include #include #include "errors.h" #include "structs.h" #include "util/array_traits.h" #include "util/crossplatform.h" #include "util/func_wrap.h" #include "util/tuple_tools.h" #include #include // Performs a compile-time check that the omitted argument name list is a // subset of the underlying function parameter names, and returns a helper that // defines a pybind11 function whose argument names obtained by removing // __VA_ARGS__ from the param_names tuple in the given MjTraits. // (This has to be implemented as a a macro because we cannot perform a // constexpr comparison of a tuple that is passed as a function argument.) #define DEF_WITH_OMITTED_PY_ARGS(MJTRAITS, ...) \ static_assert( \ util::is_subset_strings(std::make_tuple(__VA_ARGS__), \ MJTRAITS::param_names), \ "omitted argument names is not a subset of function parameter names"); \ DefWithOmittedPyArgsImpl { \ std::make_tuple(__VA_ARGS__) \ } namespace mujoco::util { template struct wrapped> { MUJOCO_ALWAYS_INLINE static constexpr RawMj* unwrap(python::MjWrapper& wrapper) { return wrapper.get(); } }; // We use std::optional on pointer arguments to indicate that Python callers // can pass None. template struct wrapped, python::enable_if_mj_struct_t> { MUJOCO_ALWAYS_INLINE static constexpr std::optional unwrap( std::optional*> wrapper) { if (wrapper.has_value()) { return (*wrapper)->get(); } return std::nullopt; } }; template using enable_if_arithmetic_array_t = std::enable_if_t< std::is_array_v && std::is_arithmetic_v>>; template struct wrapped> { MUJOCO_ALWAYS_INLINE static constexpr Arr* unwrap(Eigen::Ref> wrapper) { return reinterpret_cast(wrapper.data()); } }; } // namespace mujoco::util namespace mujoco::python { namespace _impl { template struct py_arg_helper {}; template struct py_arg_helper, std::tuple> { std::tuple py_args; std::tuple omitted_args; static constexpr int n_py_args = std::tuple_size_v; static constexpr int n_omitted_args = std::tuple_size_v; template MUJOCO_ALWAYS_INLINE constexpr void def(::pybind11::module_& m, T&&... t) { constexpr int NExtras = std::tuple_size_v>; unpack_tuple_as_py_args<0, NExtras>(m, std::forward(t)...); } template MUJOCO_ALWAYS_INLINE constexpr void unpack_tuple_as_py_args(::pybind11::module_& m, T&&... t) { if constexpr (ArgIdx == n_py_args) { if constexpr (std::tuple_size_v> == NExtras + n_py_args - n_omitted_args) { m.def(std::forward(t)...); } else { // This should ideally be a static_assert, but we need C++20 consteval // to do that. When using the DEF_WITH_OMITTED_PY_ARGS macro, the // static_assert in that macro would trigger first, rendering this // branch unreachable. throw UnexpectedError( "omitted argument names do not match the underlying function " "parameter names"); } } else if (is_omitted()) { unpack_tuple_as_py_args(m, std::forward(t)...); } else { unpack_tuple_as_py_args( m, std::forward(t)..., ::pybind11::arg(std::get(py_args))); } } template MUJOCO_ALWAYS_INLINE constexpr bool is_omitted() { if constexpr (OmittedIdx == n_omitted_args) { return false; // string_view comparison can be constexpr } else if (std::string_view(std::get(py_args)) == std::string_view(std::get(omitted_args))) { return true; } else { return is_omitted(); } } }; } // namespace _impl template MUJOCO_ALWAYS_INLINE static constexpr auto WithNamedArgs(Tuple&& py_args) { using ArgTuple = std::remove_cv_t>; return _impl::py_arg_helper>{ std::forward(py_args), std::tuple<>()}; } template MUJOCO_ALWAYS_INLINE static constexpr auto WithNamedArgs(Tuple1&& py_args, Tuple2&& omitted_args) { using ArgTuple = std::remove_cv_t>; using OmittedTuple = std::remove_cv_t>; return _impl::py_arg_helper{ std::forward(py_args), std::forward(omitted_args)}; } template MUJOCO_ALWAYS_INLINE static constexpr void Def(::pybind11::module_& m) { WithNamedArgs(MjTraits::param_names).def( m, MjTraits::name, util::UnwrapArgs(InterceptMjErrors(MjTraits::GetFunc())), ::pybind11::doc(MjTraits::doc), ::pybind11::call_guard<::pybind11::gil_scoped_release>()); } template MUJOCO_ALWAYS_INLINE static constexpr void Def(::pybind11::module_& m, Func&& func) { WithNamedArgs(MjTraits::param_names).def( m, MjTraits::name, util::UnwrapArgs(std::forward(func)), ::pybind11::doc(MjTraits::doc), ::pybind11::call_guard<::pybind11::gil_scoped_release>()); } template MUJOCO_ALWAYS_INLINE static constexpr void Def( ::pybind11::module_& m, OmittedArgs&& omitted_args, Func&& func) { WithNamedArgs(MjTraits::param_names, std::forward(omitted_args)) .def(m, MjTraits::name, util::UnwrapArgs(std::forward(func)), ::pybind11::doc(MjTraits::doc), ::pybind11::call_guard<::pybind11::gil_scoped_release>()); } template MUJOCO_ALWAYS_INLINE static constexpr void DefWithGil(::pybind11::module_& m, Func&& func) { WithNamedArgs(MjTraits::param_names).def( m, MjTraits::name, util::UnwrapArgs(std::forward(func)), ::pybind11::doc(MjTraits::doc)); } // Should only be invoked via the DEF_WITH_OMITTED_PY_ARGS macro. template struct DefWithOmittedPyArgsImpl { OmittedArgsTuple omitted_args; template constexpr auto operator()(::pybind11::module_& m, Func&& func) { return Def(m, omitted_args, std::forward(func)); } }; } // namespace mujoco::python #endif // MUJOCO_PYTHON_FUNCTIONS_H_