Concepts

namespace pressio{ namespace ode{

#ifdef PRESSIO_ENABLE_CXX20
template <class T>
concept OdeSystem =
  requires(){
  typename T::independent_variable_type; }
  && std::copy_constructible<typename T::state_type>
  && std::copy_constructible<typename T::rhs_type>
  && requires(const T & A,
	      const typename T::state_type & state,
	      const typename T::independent_variable_type & evalValue,
	      typename T::rhs_type & f)
  {
    { A.createState() } -> std::same_as<typename T::state_type>;
    { A.createRhs()   } -> std::same_as<typename T::rhs_type>;
    { A.rhs(state, evalValue, f) } -> std::same_as<void>;
  };

template <class T>
concept RealValuedOdeSystem =
     OdeSystem<T>
  && std::floating_point< scalar_trait_t<typename T::state_type> >
  && std::floating_point< scalar_trait_t<typename T::rhs_type> >
  && std::convertible_to<
      typename T::independent_variable_type,
      scalar_trait_t<typename T::state_type> >;

#else

template<class T, class enable = void>
struct OdeSystem : std::false_type{};

template<class T>
struct OdeSystem<
  T,
  std::enable_if_t<
       ::pressio::has_independent_variable_typedef<T>::value
    && ::pressio::has_state_typedef<T>::value
    && ::pressio::has_rhs_typedef<T>::value
    && std::is_copy_constructible<typename T::state_type>::value
    && std::is_copy_constructible<typename T::rhs_type>::value
    && ::pressio::ode::has_const_create_state_method_return_result<
      T, typename T::state_type >::value
    && ::pressio::ode::has_const_create_rhs_method_return_result<
      T, typename T::rhs_type >::value
    && std::is_void<
      decltype(
	       std::declval<T const>().rhs
	       (
		std::declval<typename T::state_type const&>(),
		std::declval<typename T::independent_variable_type const &>(),
		std::declval<typename T::rhs_type &>()
	       )
	   )
      >::value
   >
  > : std::true_type{};

template<class T, class enable = void>
struct RealValuedOdeSystem : std::false_type{};

template<class T>
struct RealValuedOdeSystem<
  T, std::enable_if_t<
       OdeSystem<T>::value
       && std::is_floating_point< scalar_trait_t<typename T::state_type> >::value
       && std::is_floating_point< scalar_trait_t<typename T::rhs_type> >::value
       && std::is_convertible<
	 typename T::independent_variable_type,
	 scalar_trait_t<typename T::state_type> >::value
  >
  > : std::true_type{};

#endif


#ifdef PRESSIO_ENABLE_CXX20
template <class T>
concept OdeSystemFusingRhsAndJacobian =
  requires(){ typename T::independent_variable_type; }
  && std::copy_constructible<typename T::state_type>
  && std::copy_constructible<typename T::rhs_type>
  && std::copy_constructible<typename T::jacobian_type>
  && requires(const T & A,
	      const typename T::state_type & state,
	      const typename T::independent_variable_type & evalValue,
	      typename T::rhs_type & f,
	      std::optional<typename T::jacobian_type*> J)
  {
    { A.createState()    } -> std::same_as<typename T::state_type>;
    { A.createRhs()      } -> std::same_as<typename T::rhs_type>;
    { A.createJacobian() } -> std::same_as<typename T::jacobian_type>;
    { A.rhsAndJacobian(state, evalValue, f, J) } -> std::same_as<void>;
  };

template <class T>
concept RealValuedOdeSystemFusingRhsAndJacobian =
     OdeSystemFusingRhsAndJacobian<T>
  && std::floating_point< scalar_trait_t<typename T::state_type> >
  && std::floating_point< scalar_trait_t<typename T::rhs_type> >
  && std::floating_point< scalar_trait_t<typename T::jacobian_type> >
  && std::convertible_to<
      typename T::independent_variable_type,
      scalar_trait_t<typename T::state_type> >;

#else

template<class T, class enable = void>
struct OdeSystemFusingRhsAndJacobian : std::false_type{};

template<class T>
struct OdeSystemFusingRhsAndJacobian<
  T,
  std::enable_if_t<
       ::pressio::has_independent_variable_typedef<T>::value
    && ::pressio::has_state_typedef<T>::value
    && ::pressio::has_rhs_typedef<T>::value
    && ::pressio::has_jacobian_typedef<T>::value
    && std::is_copy_constructible<typename T::state_type>::value
    && std::is_copy_constructible<typename T::rhs_type>::value
    && std::is_copy_constructible<typename T::jacobian_type>::value
    && ::pressio::ode::has_const_create_state_method_return_result<
      T, typename T::state_type >::value
    && ::pressio::ode::has_const_create_rhs_method_return_result<
      T, typename T::rhs_type >::value
    && ::pressio::ode::has_const_create_jacobian_method_return_result<
      T, typename T::jacobian_type >::value
    && std::is_void<
      decltype(
	       std::declval<T const>().rhsAndJacobian
	       (
		std::declval<typename T::state_type const&>(),
		std::declval<typename T::independent_variable_type const &>(),
                std::declval<typename T::rhs_type &>(),
		std::declval< std::optional<typename T::jacobian_type*> >()
	       )
	   )
      >::value
   >
  > : std::true_type{};

template<class T, class enable = void>
struct RealValuedOdeSystemFusingRhsAndJacobian : std::false_type{};

template<class T>
struct RealValuedOdeSystemFusingRhsAndJacobian<
  T,
  std::enable_if_t<
    OdeSystemFusingRhsAndJacobian<T>::value
  && std::is_floating_point< scalar_trait_t<typename T::state_type> >::value
  && std::is_floating_point< scalar_trait_t<typename T::rhs_type> >::value
  && std::is_floating_point< scalar_trait_t<typename T::jacobian_type> >::value
  && std::is_convertible<
      typename T::independent_variable_type,
      scalar_trait_t<typename T::state_type> >::value
  > > : std::true_type{};

#endif

#ifdef PRESSIO_ENABLE_CXX20
template <class T>
concept OdeSystemFusingMassMatrixAndRhs =
  requires(){ typename T::independent_variable_type; }
  && std::copy_constructible<typename T::state_type>
  && std::copy_constructible<typename T::rhs_type>
  && std::copy_constructible<typename T::mass_matrix_type>
  && requires(const T & A,
	      const typename T::state_type & state,
	      const typename T::independent_variable_type & evalValue,
	      typename T::mass_matrix_type & M,
	      typename T::rhs_type & f)
  {
    { A.createState()      } -> std::same_as<typename T::state_type>;
    { A.createRhs()        } -> std::same_as<typename T::rhs_type>;
    { A.createMassMatrix() } -> std::same_as<typename T::mass_matrix_type>;
    { A.massMatrixAndRhs(state, evalValue, M, f) } -> std::same_as<void>;
  };

template <class T>
concept RealValuedOdeSystemFusingMassMatrixAndRhs =
  OdeSystemFusingMassMatrixAndRhs<T>
  && std::floating_point< scalar_trait_t<typename T::state_type> >
  && std::floating_point< scalar_trait_t<typename T::rhs_type> >
  && std::floating_point< scalar_trait_t<typename T::mass_matrix_type> >
  && std::convertible_to<
      typename T::independent_variable_type,
      scalar_trait_t<typename T::state_type> >;

#else

template<class T, class enable = void>
struct OdeSystemFusingMassMatrixAndRhs : std::false_type{};

template<class T>
struct OdeSystemFusingMassMatrixAndRhs<
  T,
  std::enable_if_t<
       ::pressio::has_independent_variable_typedef<T>::value
    && ::pressio::has_state_typedef<T>::value
    && ::pressio::has_rhs_typedef<T>::value
    && ::pressio::has_mass_matrix_typedef<T>::value
    && std::is_copy_constructible<typename T::state_type>::value
    && std::is_copy_constructible<typename T::rhs_type>::value
    && std::is_copy_constructible<typename T::mass_matrix_type>::value
    && ::pressio::ode::has_const_create_state_method_return_result<
      T, typename T::state_type >::value
    && ::pressio::ode::has_const_create_rhs_method_return_result<
      T, typename T::rhs_type >::value
    && ::pressio::ode::has_const_create_mass_matrix_method_return_result<
      T, typename T::mass_matrix_type >::value
    && std::is_void<
      decltype(
	       std::declval<T const>().massMatrixAndRhs
	       (
		std::declval<typename T::state_type const&>(),
		std::declval<typename T::independent_variable_type const &>(),
		std::declval<typename T::mass_matrix_type &>(),
                std::declval<typename T::rhs_type &>()
	       )
	   )
      >::value
   >
  > : std::true_type{};

template<class T, class enable = void>
struct RealValuedOdeSystemFusingMassMatrixAndRhs : std::false_type{};

template<class T>
struct RealValuedOdeSystemFusingMassMatrixAndRhs<
  T, std::enable_if_t<
    OdeSystemFusingMassMatrixAndRhs<T>::value
  && std::is_floating_point< scalar_trait_t<typename T::state_type> >::value
  && std::is_floating_point< scalar_trait_t<typename T::rhs_type> >::value
  && std::is_floating_point< scalar_trait_t<typename T::mass_matrix_type> >::value
  && std::is_convertible<
      typename T::independent_variable_type,
      scalar_trait_t<typename T::state_type> >::value
  >
  > : std::true_type{};

#endif


#ifdef PRESSIO_ENABLE_CXX20
template <class T>
concept CompleteOdeSystem =
  requires(){ typename T::independent_variable_type; }
  && std::copy_constructible<typename T::state_type>
  && std::copy_constructible<typename T::rhs_type>
  && std::copy_constructible<typename T::mass_matrix_type>
  && std::copy_constructible<typename T::jacobian_type>
  && requires(const T & A,
	      const typename T::state_type & state,
	      const typename T::independent_variable_type & evalValue,
	      typename T::mass_matrix_type & M,
	      typename T::rhs_type & f,
	      std::optional<typename T::jacobian_type*> J)
  {
    { A.createState()      } -> std::same_as<typename T::state_type>;
    { A.createRhs()        } -> std::same_as<typename T::rhs_type>;
    { A.createMassMatrix() } -> std::same_as<typename T::mass_matrix_type>;
    { A.massMatrixAndRhsAndJacobian(state, evalValue, M, f, J) } -> std::same_as<void>;
  };

template <class T>
concept RealValuedCompleteOdeSystem =
     CompleteOdeSystem<T>
  && std::floating_point< scalar_trait_t<typename T::state_type> >
  && std::floating_point< scalar_trait_t<typename T::rhs_type> >
  && std::floating_point< scalar_trait_t<typename T::mass_matrix_type> >
  && std::floating_point< scalar_trait_t<typename T::jacobian_type> >
  && std::convertible_to<
      typename T::independent_variable_type,
      scalar_trait_t<typename T::state_type> >;

#else

template<class T, class enable = void>
struct CompleteOdeSystem : std::false_type{};

template<class T>
struct CompleteOdeSystem<
  T,
  std::enable_if_t<
       ::pressio::has_independent_variable_typedef<T>::value
    && ::pressio::has_state_typedef<T>::value
    && ::pressio::has_rhs_typedef<T>::value
    && ::pressio::has_mass_matrix_typedef<T>::value
    && ::pressio::has_jacobian_typedef<T>::value
    && std::is_copy_constructible<typename T::state_type>::value
    && std::is_copy_constructible<typename T::rhs_type>::value
    && std::is_copy_constructible<typename T::mass_matrix_type>::value
    && std::is_copy_constructible<typename T::jacobian_type>::value
    && ::pressio::ode::has_const_create_state_method_return_result<
      T, typename T::state_type >::value
    && ::pressio::ode::has_const_create_rhs_method_return_result<
      T, typename T::rhs_type >::value
    && ::pressio::ode::has_const_create_mass_matrix_method_return_result<
      T, typename T::mass_matrix_type >::value
    && ::pressio::ode::has_const_create_jacobian_method_return_result<
      T, typename T::jacobian_type >::value
    //
    && std::is_void<
      decltype(
	       std::declval<T const>().massMatrixAndRhsAndJacobian
	       (
		std::declval<typename T::state_type const&>(),
		std::declval<typename T::independent_variable_type const &>(),
                std::declval<typename T::mass_matrix_type &>(),
                std::declval<typename T::rhs_type &>(),
		std::declval< std::optional<typename T::jacobian_type*> >()
	       )
	   )
      >::value
   >
  > : std::true_type{};

template<class T, class enable = void>
struct RealValuedCompleteOdeSystem : std::false_type{};

template<class T>
struct RealValuedCompleteOdeSystem<
  T, std::enable_if_t<
       CompleteOdeSystem<T>::value
       && std::is_floating_point< scalar_trait_t<typename T::state_type> >::value
       && std::is_floating_point< scalar_trait_t<typename T::rhs_type> >::value
       && std::is_floating_point< scalar_trait_t<typename T::mass_matrix_type> >::value
       && std::is_floating_point< scalar_trait_t<typename T::jacobian_type> >::value
       && std::is_convertible<
	 typename T::independent_variable_type,
	 scalar_trait_t<typename T::state_type> >::value
  >
  > : std::true_type{};

#endif


#ifdef PRESSIO_ENABLE_CXX20
template<class T>
concept FDJ_Common =
  requires { typename T::independent_variable_type; } &&
  requires { typename T::state_type; } &&
  requires { typename T::discrete_residual_type; } &&
  requires { typename T::discrete_jacobian_type; } &&
  std::copy_constructible<typename T::state_type> &&
  std::copy_constructible<typename T::discrete_residual_type> &&
  std::copy_constructible<typename T::discrete_jacobian_type> &&
  requires (const T& A) {
    { A.createState()            } -> std::same_as<typename T::state_type>;
    { A.createDiscreteResidual() } -> std::same_as<typename T::discrete_residual_type>;
    { A.createDiscreteJacobian() } -> std::same_as<typename T::discrete_jacobian_type>;
  };

// ---- Short aliases to keep requires-expressions readable ---------------------
template<class T> using step_t = typename ::pressio::ode::StepCount::value_type; // adjust if you have a different step type
template<class T> using time_t = typename T::independent_variable_type;
template<class T> using state_t = typename T::state_type;
template<class T> using res_t   = typename T::discrete_residual_type;
template<class T> using jac_t   = typename T::discrete_jacobian_type;
template<class T> using opt_jac_ptr_t = std::optional<jac_t<T>*>;

// ---- Method presence checks for NumStates = 1,2,3,4 --------------------------
template<class T>
concept HasDRJ_1 =
  requires (const T& a) {
    { a.discreteResidualAndJacobian(
        std::declval<step_t<T> const&>(),
        std::declval<time_t<T> const&>(),
        std::declval<time_t<T> const&>(),
        std::declval<res_t<T>&>(),
        std::declval<opt_jac_ptr_t<T>>(),
        std::declval<state_t<T> const&>()
      )
    } -> std::same_as<void>;
  };

template<class T>
concept HasDRJ_2 =
  requires (const T& a) {
    { a.discreteResidualAndJacobian(
        std::declval<step_t<T> const&>(),
        std::declval<time_t<T> const&>(),
        std::declval<time_t<T> const&>(),
        std::declval<res_t<T>&>(),
        std::declval<opt_jac_ptr_t<T>>(),
        std::declval<state_t<T> const&>(),
        std::declval<state_t<T> const&>()
      )
    } -> std::same_as<void>;
  };

template<class T>
concept HasDRJ_3 =
  requires (const T& a) {
    { a.discreteResidualAndJacobian(
        std::declval<step_t<T> const&>(),
        std::declval<time_t<T> const&>(),
        std::declval<time_t<T> const&>(),
        std::declval<res_t<T>&>(),
        std::declval<opt_jac_ptr_t<T>>(),
        std::declval<state_t<T> const&>(),
        std::declval<state_t<T> const&>(),
        std::declval<state_t<T> const&>()
      )
    } -> std::same_as<void>;
  };

template<class T>
concept HasDRJ_4 =
  requires (const T& a) {
    { a.discreteResidualAndJacobian(
        std::declval<step_t<T> const&>(),
        std::declval<time_t<T> const&>(),
        std::declval<time_t<T> const&>(),
        std::declval<res_t<T>&>(),
        std::declval<opt_jac_ptr_t<T>>(),
        std::declval<state_t<T> const&>(),
        std::declval<state_t<T> const&>(),
        std::declval<state_t<T> const&>(),
        std::declval<state_t<T> const&>()
      )
    } -> std::same_as<void>;
  };

// select the right arity by NumStates
template<class T, int NumStates>
concept HasDRJ_N =
  (NumStates == 1 && HasDRJ_1<T>) ||
  (NumStates == 2 && HasDRJ_2<T>) ||
  (NumStates == 3 && HasDRJ_3<T>) ||
  (NumStates == 4 && HasDRJ_4<T>);

// Actual concepts
template <class T, int NumStates>
concept FullyDiscreteSystemWithJacobian =
  FDJ_Common<T> && HasDRJ_N<T, NumStates>;

template <class T, int NumStates>
concept RealValuedFullyDiscreteSystemWithJacobian =
     FullyDiscreteSystemWithJacobian<T, NumStates>
  && std::floating_point< scalar_trait_t<typename T::state_type> >
  && std::floating_point< scalar_trait_t<typename T::discrete_residual_type> >
  && std::floating_point< scalar_trait_t<typename T::discrete_jacobian_type> >
  && std::convertible_to<
      typename T::independent_variable_type,
      scalar_trait_t<typename T::state_type> >;

#else

template<class T, int NumStates,class enable = void>
struct FullyDiscreteSystemWithJacobian : std::false_type{};

template<class T, int NumStates>
struct FullyDiscreteSystemWithJacobian<
  T, NumStates,
  std::enable_if_t<
       ::pressio::has_independent_variable_typedef<T>::value
    && ::pressio::has_state_typedef<T>::value
    && ::pressio::has_discrete_residual_typedef<T>::value
    && ::pressio::has_discrete_jacobian_typedef<T>::value
    && std::is_copy_constructible<typename T::state_type>::value
    && std::is_copy_constructible<typename T::discrete_residual_type>::value
    && std::is_copy_constructible<typename T::discrete_jacobian_type>::value
    && ::pressio::ode::has_const_create_state_method_return_result<
      T, typename T::state_type >::value
    && ::pressio::ode::has_const_create_discrete_residual_method_return_result<
      T, typename T::discrete_residual_type>::value
    && ::pressio::ode::has_const_create_discrete_jacobian_method_return_result<
      T, typename T::discrete_jacobian_type>::value
    //
    && ::pressio::ode::has_const_discrete_residual_jacobian_method<
      T, NumStates,
      typename ::pressio::ode::StepCount::value_type,
      typename T::independent_variable_type,
      typename T::state_type,
      typename T::discrete_residual_type,
      typename T::discrete_jacobian_type
      >::value
    >
  > : std::true_type{};

template <class T, int NumStates, class = void>
struct RealValuedFullyDiscreteSystemWithJacobian : std::false_type{};

template <class T, int NumStates>
struct RealValuedFullyDiscreteSystemWithJacobian<
  T, NumStates,
  std::enable_if_t<
    FullyDiscreteSystemWithJacobian<T, NumStates>::value
    && std::is_floating_point< scalar_trait_t<typename T::state_type> >::value
    && std::is_floating_point< scalar_trait_t<typename T::discrete_residual_type> >::value
    && std::is_floating_point< scalar_trait_t<typename T::discrete_jacobian_type> >::value
    && std::is_convertible<
      typename T::independent_variable_type,
      scalar_trait_t<typename T::state_type> >::value
  >
  > : std::true_type{};

#endif






//
// ImplicitResidualJacobianPolicy
//
#ifdef PRESSIO_ENABLE_CXX20

template<class T>
concept ImplicitResidualJacobianPolicy =
  // --- required nested types
  requires {
    typename T::independent_variable_type;
    typename T::state_type;
    typename T::residual_type;
    typename T::jacobian_type;
  }
  // --- known pressio-ops data types & scalar compatibility
  && ::pressio::ops::is_known_data_type<typename T::state_type>::value
  && ::pressio::ops::is_known_data_type<typename T::residual_type>::value
  && ::pressio::ops::is_known_data_type<typename T::jacobian_type>::value
  && ::pressio::all_have_traits_and_same_scalar<
       typename T::state_type,
       typename T::residual_type,
       typename T::jacobian_type
     >::value
  && std::convertible_to<
       typename T::independent_variable_type,
       scalar_trait_t<typename T::state_type>
     >
  // --- factories on a const object
  && requires (T const& a) {
       { a.createState()    } -> std::same_as<typename T::state_type>;
       { a.createResidual() } -> std::same_as<typename T::residual_type>;
       { a.createJacobian() } -> std::same_as<typename T::jacobian_type>;
     }
  // --- call operator with exact signature; must return void and be const
  && requires (T const& a) {
       { a(
           std::declval<StepScheme const&>(),
           std::declval<typename T::state_type const&>(),
           std::declval<ImplicitStencilStatesDynamicContainer<typename T::state_type> const&>(),
           std::declval<ImplicitStencilRightHandSideDynamicContainer<typename T::residual_type>&>(),
           std::declval<::pressio::ode::StepEndAt<typename T::independent_variable_type>>(),
           std::declval<::pressio::ode::StepCount>(),
           std::declval<::pressio::ode::StepSize<typename T::independent_variable_type>>(),
           std::declval<typename T::residual_type &>(),
           std::declval<std::optional<typename T::jacobian_type*> >()
         ) } -> std::same_as<void>;
     };

#else

template<class T, class = void>
struct ImplicitResidualJacobianPolicy : std::false_type{};

template<class T>
struct ImplicitResidualJacobianPolicy<
  T,
  std::enable_if_t<
    ::pressio::has_independent_variable_typedef<T>::value
    && ::pressio::has_state_typedef<T>::value
    && ::pressio::has_residual_typedef<T>::value
    && ::pressio::has_jacobian_typedef<T>::value
    //
    && ::pressio::ops::is_known_data_type<typename T::state_type>::value
    && ::pressio::ops::is_known_data_type<typename T::residual_type>::value
    && ::pressio::ops::is_known_data_type<typename T::jacobian_type>::value
    && all_have_traits_and_same_scalar<
      typename T::state_type,
      typename T::residual_type,
      typename T::jacobian_type>::value
    && std::is_convertible<
      typename T::independent_variable_type,
      scalar_trait_t<typename T::state_type>>::value
    //
    // create methods
    //
    && ::pressio::ode::has_const_create_state_method_return_result<
      T, typename T::state_type>::value
    && std::is_same<
      typename T::residual_type,
      decltype(std::declval<T const>().createResidual())
      >::value
    && std::is_same<
      typename T::jacobian_type,
      decltype(std::declval<T const>().createJacobian())
      >::value

    && std::is_void<
      decltype
      (
       std::declval<T const>()
       (
	std::declval<StepScheme const &>(),
	std::declval<typename T::state_type const &>(),
	std::declval<ImplicitStencilStatesDynamicContainer<typename T::state_type> const & >(),
	std::declval<ImplicitStencilRightHandSideDynamicContainer<typename T::residual_type> & >(),
	std::declval< ::pressio::ode::StepEndAt<typename T::independent_variable_type> >(),
	std::declval< ::pressio::ode::StepCount >(),
	std::declval< ::pressio::ode::StepSize<typename T::independent_variable_type> >(),
	std::declval<typename T::residual_type &>(),
	std::declval< std::optional<typename T::jacobian_type*> >()
	)
       )
      >::value
    >
  > : std::true_type{};

#endif // end ifdef CXX20



#ifdef PRESSIO_ENABLE_CXX20

template<class T>
concept StepperWithoutSolver =
  // required nested types
  requires {
    typename T::independent_variable_type;
    typename T::state_type;
  }
  // canonical call must exist and return void
  && requires(
       T& stepper,
       typename T::state_type& y,
       const ::pressio::ode::StepStartAt<typename T::independent_variable_type>& t0,
       const ::pressio::ode::StepCount& k,
       const ::pressio::ode::StepSize<typename T::independent_variable_type>& dt
     ) {
       { stepper(y, t0, k, dt) } -> std::same_as<void>;
     }
  // forbid accepting const state&
  && (!requires(
        T& stepper,
        const typename T::state_type& y,
        const ::pressio::ode::StepStartAt<typename T::independent_variable_type>& t0,
        const ::pressio::ode::StepCount& k,
        const ::pressio::ode::StepSize<typename T::independent_variable_type>& dt
      ) {
        stepper(y, t0, k, dt);
      })
  // forbid accepting state&&
  && (!requires(
        T& stepper,
        typename T::state_type&& y,
        const ::pressio::ode::StepStartAt<typename T::independent_variable_type>& t0,
        const ::pressio::ode::StepCount& k,
        const ::pressio::ode::StepSize<typename T::independent_variable_type>& dt
      ) {
        stepper(std::move(y), t0, k, dt);
      });

#else