IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

 C++ Discussion :

Hésitation sur la conception de patrons de classes


Sujet :

C++

  1. #1
    Membre éclairé Avatar de Seabirds
    Homme Profil pro
    Post-doctoral fellow
    Inscrit en
    Avril 2015
    Messages
    294
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Post-doctoral fellow
    Secteur : Agroalimentaire - Agriculture

    Informations forums :
    Inscription : Avril 2015
    Messages : 294
    Par défaut Hésitation sur la conception de patrons de classes
    Bonjour à toutes et à tous !

    J'utilise depuis quelques temps déjà les templates pour mettre de la variabilité sur les types, mais je n'ai jamais été confronté au problème suivant :

    Soit un concept A (un ensemble de distribution de probas dans mon cas), proposant un service sample() dont le résultat peut être :
    - constant
    - variable dans un espace X
    - variable un espace X et un autre espace Y.

    Grand naïf, j'ai pensé faire ça :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    class A;
    template<typename X> A;
    template<typename X, typename Y> A;
    Le service commun varie dans ses arguments : sample(), sample(X), ou sample(X, Y) et l'implémentation varie selon les cas. Du coup je ne sais pas si je dois/peux donner le même nom à ces patrons de classes:
    • D'un côté si leur implémentation et leur service varie, c'est qu'ils ont pas grand chose à voir.
    • D'un autre côté ces sont tous des A, du coup j'ai eu envie de tous les nommer pareil avec l'idée que le nombre d'arguments template suffiraient à lever l'ambiguité. Le compilateur me signale aimablement que je n'y connais rien "class A redeclared with ... argument template", ce dont je lui sais gré.


    Du coup j'hésite entre
    • renommer les patrons de classe Foo, XVariableFoo, XYVariableFoo mais même si ça a l'air d'être la solution la plus facile quelque chose me dit que ça cloche
    • en apprendre plus sur toute autre approche que vous identifiriez comme étant une meilleure solution problème (peut être faut il donner à tout le monde le même nombre d'arguments template et utiliser des trucs du genre dispatch/enable_if/traits... )


    Qu'en pensez-vous ?

  2. #2
    Rédacteur/Modérateur


    Homme Profil pro
    Network game programmer
    Inscrit en
    Juin 2010
    Messages
    7 153
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 38
    Localisation : Canada

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 153
    Billets dans le blog
    4
    Par défaut
    Je pense que pour lever ton ambiguité tu dois faire une classe avec variadic template que tu spécialises pour tes 3 cas : 0, 1 et 2 templates.
    Pensez à consulter la FAQ ou les cours et tutoriels de la section C++.
    Un peu de programmation réseau ?
    Aucune aide via MP ne sera dispensée. Merci d'utiliser les forums prévus à cet effet.

  3. #3
    Membre éclairé Avatar de Seabirds
    Homme Profil pro
    Post-doctoral fellow
    Inscrit en
    Avril 2015
    Messages
    294
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Post-doctoral fellow
    Secteur : Agroalimentaire - Agriculture

    Informations forums :
    Inscription : Avril 2015
    Messages : 294
    Par défaut
    Merci pour la piste je vais aller chercher ça !

  4. #4
    Expert éminent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 644
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    Par défaut
    Salut,

    En fait, il y a plusieurs choses qui pourraient être intéressantes à faire...

    La première serait de définir un "tag" représentant chacune des variations possibles, sous une forme qui pourrait être proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    struct tagNone{
        using tag_type = tagNone;
    };
    struct tagOne{
        using tag_type = tagOne;
    };
    struct tagTwo{
        using tag_type = tagTwo;
    };
    Ce genre de tag n'apporte rien en soi à part le fait de pouvoir "tagger" les différents éléments selon leur appartenance à à l'une ou l'autre des catégories et de te faciliter énormément la vie lorsqu'il s'agit de s'assurer du type que tu manipule, par exemple, sous la forme d'un
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    template <typename T1, typename T2>
    class MaClass{
        static_assert(std::is_same(typename T1::tag_type, typename T2::tag_type>::value,
                                                  "first and second template parameter should be same type");
    },
    Ce tag te permettra alors de définir les politiques particulières propres à chacun des ensemble, par exemple sous la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
     
    template<typename TAG> struct Policy;
    template <>
    struct Policy<tagNone>{
        /* aucun paramètre requis pour tagNone */
        TypeDeRetour operator()() const{
            /* à toi de voir ce que tu fais ;) */
        }
    };
    template <>
    struct Policy<tagNone>{
        /* un paramètre requis pour tagOne */
        TypeDeRetour operator()(TypeParam) const{
            /* à toi de voir ce que tu fais ;) */
        }
    };
    template <>
    struct Policy<tagTwo>{
        /* deux paramètre requis pour tagTwo */
        TypeDeRetour operator()(T1,  T2) const{
            /* à toi de voir ce que tu fais ;) */
        }
    };
    Et, à partir de là, tu gagnes toute la liberté que tu veux, par exemple, en créant ton ensemble A sous une forme qui pourrait être proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    template <typename TAG>
    struct A{
        /* si pas tagNone, tagOne ou tagTwo, on risque une erreur de comilation ici...
         * un static_assert serait sand doute plus clair ... d'autant que tu as peut être
         * d'autres tag que ces trois là qui risquent de définir tag_type 
         */
        using tag_type = typename TAG::tag_type;
        using policy_type = Policy<tag_type>;
        template <typename ... Args>
        void foo(Args ... args){
            /* si ca ne correspond pas, l'erreur aura lieu ici à la compilation */
            policy_type()(args ...);
        }
    };
    Mais tu pourrais aussi envisager quelque chose à base de std::enable_if ou autres solutions contorsionnée Le tout étant de toujours te donner l'occasion de récupérer le type de tag sous une forme ou une autre
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  5. #5
    Membre Expert
    Avatar de Pyramidev
    Homme Profil pro
    Tech Lead
    Inscrit en
    Avril 2016
    Messages
    1 513
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Tech Lead

    Informations forums :
    Inscription : Avril 2016
    Messages : 1 513
    Par défaut
    Bonjour Seabirds.

    Voici un exemple de bout de code qui respecte la description de ton message et qui utilise l'astuce que vient de donner Bousk :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    #include <iostream>
    #include <random>
     
    template<class ...T>
    class A;
     
    template<>
    class A<>
    {
    public:
    	constexpr static int sample() noexcept
    	{
    		return 0;
    	}
    };
     
    template<class TX>
    class A<TX>
    {
    public:
    	TX sample(TX x)
    	{
    		return x*m_d(m_rd);
    	}
    private:
    	std::random_device                 m_rd;
    	std::uniform_real_distribution<TX> m_d;
    };
     
    template<class TX, class TY>
    class A<TX, TY>
    {
    public:
    	std::pair<TX, TY> sample(TX x, TY y)
    	{
    		return std::make_pair(x*m_d1(m_rd), x*m_d2(m_rd));
    	}
    private:
    	std::random_device                 m_rd;
    	std::uniform_real_distribution<TX> m_d1;
    	std::uniform_real_distribution<TY> m_d2;
    };
     
    int main()
    {
    	A<>              a1;
    	A<float>         a2;
    	A<float, double> a3;
    	const int                      s1 = a1.sample();
    	const float                    s2 = a2.sample(5.0f);
    	const std::pair<float, double> s3 = a3.sample(10.0f, 21.0);
    	std::cout << "s1 : " << s1 << '\n';
    	std::cout << "s2 : " << s2 << '\n';
    	std::cout << "s3 : (" << s3.first << " ; " << s3.second << ")\n";
    	return 0;
    }
    Cela dit, au niveau de la conception, le code que je viens d'écrire est assez étrange.
    Peux-tu nous en dire plus sur les variables membres de ton modèle de classe A, sur tes espaces X et Y et sur tes fonctions sample ?
    Peut-être que, en en sachant plus, on te conseillera de structurer le code différemment.

  6. #6
    Expert éminent

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2007
    Messages
    5 202
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Juin 2007
    Messages : 5 202
    Par défaut
    La grosse question est souvent toujours la même quand on parle de template: "Quand l'utilisateur du code devra-t-il choisir?".

    Es-tu certain que le programme utilisateur doive savoir à la compilation qu'elle type de distribution il veut utiliser.
    Es-tu certain de ne pas vouloir qu'il puisse stocker dans un même vecteur des distributions à 0, 1 ou 2 paramètres?

  7. #7
    Expert éminent

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2007
    Messages
    5 202
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Juin 2007
    Messages : 5 202
    Par défaut
    Autre piste de réflexion.
    Dans le vocabulaire mathématique, la constance est définie dans un domaine, et par rapport aux dimensions de ce domaine.

    Le Bidule que retourne tes trois A est une valeur relative à une position dans X et Y.
    Selon les cas, elle a la propriété d'être constante (ou non) par rapport à X ou Y (voire les deux).

    Finalement, c'est du ressort du Bidule lui-même de porter cette information. Et pas forcément dans son type.
    Mathématiquement, f: x->2 est une fonction de R, constante sur R. Sa nature de fonction de R n'est pas différente de f: x -> x².
    Veux-tu vraiment faire la distinction dans le type de ces deux fonctions?

  8. #8
    Membre éclairé Avatar de Seabirds
    Homme Profil pro
    Post-doctoral fellow
    Inscrit en
    Avril 2015
    Messages
    294
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Post-doctoral fellow
    Secteur : Agroalimentaire - Agriculture

    Informations forums :
    Inscription : Avril 2015
    Messages : 294
    Par défaut
    Merci à tou(te)s pour vos réponses !

    Merci Pyramidev pour le début de code, ça m'a aidé à capter la syntaxe qui était requise pour mettre en oeuvre le conseil de Bousk!
    Les précisions que tu demandes viennent plus bas avec une première version de code qui marche.

    Merci koala01, j'avais envisagé l'utilisation des politiques, mais je ne sais pas si ça aurait réglé totalement le problème car l'implémentation de la classe variait aussi selon le nombre d'argument (pas seulement le comportement de la fonction membre appellée, qu'on aurait pu spécialiser avec des politiques). Peut être qu'il y a intérêt à incorporer ce que tu proposes dans ce que j'ai pondu

    Citation Envoyé par ternel Voir le message
    Es-tu certain que le programme utilisateur doive savoir à la compilation qu'elle type de distribution il veut utiliser.
    Pour aussi certain que le débutant puisse l'être... Disons que pour l'instant j'ai du mal à voir l'intérêt de déterminer les choses pendant l'exécution. Dans le cadre d'utilisation de mon projet, le modèle simulatoire à partir duquel générer des données est déterminé dans le corps d'une fonction appelée par le main.
    Si les données simulées doivent amener à varier certaines parties du modèle, ce n'est pas en cours d'exécution que ça se passe, c'est après analyse des résultats, discussions, recherches complémentaires ... bref je ré-écris un autre main. Est-ce que ça répond à la question ?

    Citation Envoyé par ternel Voir le message
    Es-tu certain de ne pas vouloir qu'il puisse stocker dans un même vecteur des distributions à 0, 1 ou 2 paramètres?
    Wouch c'est dur comme question. On peut toujours être tenté de mettre pleins de trucs dans un même vecteur non ? Mais à court/moyen terme ce n'est pas prévu, non. Il y a une distribution temporaire construite à chaque simulation, mais elle n'a pas d'intérêt pratique, et donc est jetée à la fin de la simulation. Donc a priori on n'a pas besoin de la stocker.

    Pour être plus précis sur le concept que je cherchais à représenter, c'est une version discrète d'un kernel de transition markovien. A chaque temps t, il y a une certaine probabilité d'aller à l'état j quand on est dans l'état i. Le kernel peut changer au cours du temps, ou rester le même. Je vous mets le code que j'ai pondu jusque-là. Encore merci pour m'avoir dit ce qui manquait (le template<class...T> class TransitionKernel; )

    Le header :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
     
    #include <assert.h>
    #include "DiscreteDistribution.h"
     
    namespace coalescence {
    namespace utils {
    namespace markov{
     
    /*!
     * \brief Defines a template class to represent a discrete markovian transition kernel
     * 
     * For each initial state x_0 of an initial state space S_0, defines the associated 
     * discrete markovian probability p( x_1 = X | x_0). 
     * The arrival states of the kernel do not have to be identical (states with zero probabily can be omitted). 
     * Allows for sampling the next state knowing the present state x_0.
     *
     * \tparam State is the type of the states of the markov process, or equivalently the support of the markovian probability distribution.
     */
     
     template<class...T>
     class TransitionKernel;
     
    template<typename State, typename Distribution>
    class TransitionKernel<State, Distribution> {
     
    private:
     
    	using SelfType = TransitionKernel<State, Distribution>;
     
    	std::unordered_map<State, Distribution> m_distributions;
     
    public:
     
    	TransitionKernel(){}
     
    	//! \remark Distribution must be copy assignable
    	TransitionKernel(State const& x_0, Distribution const& dis){
    		m_distributions[x_0] = dis;
    	}
     
    	//! \remark Distribution must be move assignable
    	TransitionKernel(State const& x_0, Distribution&& dis){
    		m_distributions[x_0] = std::move(dis);
    	}
     
    	TransitionKernel(const TransitionKernel&) = default;
     
    	TransitionKernel(TransitionKernel&&) = default;
     
    	SelfType& operator=(const SelfType&) = default;
     
    	SelfType& operator=(SelfType&&) = default;
     
    	SelfType& add(State const& x_0, Distribution const& dis){
    		m_distributions[x_0] = dis;
    		return *this;
    	}
     
    	SelfType& add(State const& x_0, Distribution&& dis){
    		m_distributions[x_0] = std::move(dis);
    	}
     
    	bool has_distribution(State const& x_0) const {
    		return m_distributions.find(x_0) != m_distributions.end();
    	}
    	//! \remark sample distribution from state x_0 with random number generator g
    	template<typename Generator>
    	State operator()(State const& x_0, Generator& g) {
    		return m_distributions.at(x_0).operator()(g);
    	}
     
    };
     
    // transition kernel variable in time
    template<typename State, typename Time, typename Distribution>
    class TransitionKernel<State, Time, Distribution> {
     
    private:
     
    	using SelfType = TransitionKernel<State, Time, Distribution>;
    	using InsiderType = TransitionKernel<State, Distribution>;
    	std::unordered_map<Time, InsiderType> m_kernels;
     
    public:
     
    	TransitionKernel(){}
     
    	TransitionKernel(State const& x_0, Time const& t, Distribution const& d){
    		m_kernels[t] = InsiderType(x_0, d);
    	}
     
    	TransitionKernel(State const& x_0, Time const& t, Distribution&& d){
    		m_kernels[t] = InsiderType(x_0, std::move(d));
    	}
     
    	TransitionKernel(const TransitionKernel&) = default;
     
    	TransitionKernel(TransitionKernel&&) = default;
     
    	SelfType& operator=(const SelfType&) = default;
     
    	SelfType& operator=(SelfType&&) = default;
     
    	SelfType& add(State const& x_0, Time const& t, Distribution const& d){
    		m_kernels[t] = InsiderType(x_0, d);
    		return *this;
    	}
     
    	SelfType& add(State const& x_0, Time const& t, Distribution&& d){
    		m_kernels[t] = InsiderType(x_0, std::move(d));
    		return *this;
    	}
     
    	bool has_distribution(State const& x_0, Time const& t) const {
    		bool answer = false;
    		if(m_kernels.find(t) != m_kernels.end()){
    			if(m_kernels.at(t).has_distribution(x_0)){
    				answer = true;
    			}
    		}
    		return answer;
    	}
     
    	template<typename Generator>
    	State operator()(State const& x_0, Time const& t, Generator& g) {
    		return m_kernels.at(t).operator()(x_0, g);
    	}
    };
    } // namespace markov
    } // namespace utils
    } // namespace coalescence
    Ici le code d'utilisation :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    #include "TransitionKernel.h"
    #include <random>
    #include <assert.h>
     
    void test_state_variable_kernel(){
    	using State = int;
    	using Distribution = std::discrete_distribution<State>;
    	using Kernel = coalescence::utils::markov::TransitionKernel<State, Distribution>;
     
    	// initialization by default constructor
    	Kernel first;
     
    	// initialization by copy constructor
    	Kernel second(first);
     
    	// Also initialization by copy constructor
    	Kernel third = first;
     
    	// assignment by copy assignment operator
    	second = third;           
     
    	// assignment by move assignment operator
    	second = std::move(third);
     
    	// initialization copying distribution
    	Distribution d({1, 10, 20, 30});
    	Kernel A(1, d);
     
    	// initialization moving cell
    	Kernel B(1, std::move(d));
     
    	// Sampling the kernel
    	Kernel kernel;
     
        std::random_device rd;
        std::mt19937 gen(rd());
    	Distribution predictible_output({0, 0, 12, 0});
     
    	// Associate some State values with distributions
    	for(State x = 0; x < 10; ++x){		
    		kernel.add(x, d);
    	}
     
    	// Replace the distribution at coordinate 8
    	State x = 8;
    	kernel.add(x, predictible_output);
     
    	// Sample
    	for(int i = 0; i < 100; ++i){
    		assert(kernel.has_distribution(x));
    		State sampled_x = kernel(x, gen);
    		assert(sampled_x == 2);
    	}
    }
     
    void test_state_and_time_variable_kernel(){
    	using State = int;
    	using Time = int;
    	using Distribution = std::discrete_distribution<State>;
    	using Kernel = coalescence::utils::markov::TransitionKernel<State, Time, Distribution>;
     
    	// initialization by default constructor
    	Kernel first;
     
    	// initialization by copy constructor
    	Kernel second(first);
     
    	// Also initialization by copy constructor
    	Kernel third = first;
     
    	// assignment by copy assignment operator
    	second = third;           
     
    	// assignment by move assignment operator
    	second = std::move(third);
     
    	// initialization copying distribution
    	Distribution d({1, 10, 20, 30});
    	State x = 1;
    	Time t = 0;
    	Kernel A(x, t, d);
     
    	// initialization moving cell
    	Kernel B(x, t, std::move(d));
     
    	// Sampling the kernel
    	Kernel kernel;
     
        std::random_device rd;
        std::mt19937 gen(rd());
    	Distribution predictible_output({0, 0, 12, 0});
     
    	// Associate some State values with distributions
    	for(Time i = 0; i < 5; ++i ){
    		for(State j = 0; j < 10; ++j){		
    			kernel.add(j, i, d);
    		}
    	}
     
    	// Replace the distribution at coordinate 8
    	x = 8;
    	t = 4;
    	kernel.add(x, t, predictible_output);
     
    	// Sample
    	for(int i = 0; i < 100; ++i){
    		assert(kernel.has_distribution(x, t));
    		State sampled_x = kernel(x, t, gen);
    		assert(sampled_x == 2);
    	}
    }
     
    int main () {
     
    	test_state_variable_kernel();
    	test_state_and_time_variable_kernel();
     
    	return 0;
     
    }

  9. #9
    Membre éclairé Avatar de Seabirds
    Homme Profil pro
    Post-doctoral fellow
    Inscrit en
    Avril 2015
    Messages
    294
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Post-doctoral fellow
    Secteur : Agroalimentaire - Agriculture

    Informations forums :
    Inscription : Avril 2015
    Messages : 294
    Par défaut
    Citation Envoyé par ternel Voir le message
    Mathématiquement, f: x->2 est une fonction de R, constante sur R. Sa nature de fonction de R n'est pas différente de f: x -> x².
    Veux-tu vraiment faire la distinction dans le type de ces deux fonctions?
    Disons qu'il y a trois cadres d'utilisation pour l'utilisation du noyau de transition :
    1. quelque soit l'état initial, la distribution des probabilités de transition vers les autres états est identique. Ca c'est un cas d'intérêt pratique assez réduit (tests unitaires)
    2. les probabilités de transition varient selon l'état de départ, mais restent constantes au cours du processus markovien : ça il y a un coin du programme où c'est le cas
    3. les probabilités de transition varient selon l'état de départ, mais elles varient également au cours du processus markovien. Ca c'est le cas général qu'on utilise aussi.


    Du coup la manière de représenter ces distributions varient, parce qu'on a pas envie de stocker 1000 fois la même distribution pour tous les temps et tous les états initiaux. L'information de type est peut-être un peu synonyme de performance/mémoire ici. Bon je dis sûrement une boulette.

  10. #10
    Expert éminent

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2007
    Messages
    5 202
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Juin 2007
    Messages : 5 202
    Par défaut
    Donc, le premier cas, c'est le second où toutes les valeurs sont égales à 1/N. A part économiser un tableau (et un accès à ce tableau), on n'y gagne rien.
    Et le second, c'est le premier, avec une fonction de transition qui ne change rien.

    Deux ou trois constructeurs supplémentaires, et le troisième cas rempli parfaitement les deux autres.
    Le constructeur idéal serait, si la syntaxe le permettait:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    Kernel(distribution, function<void(distribution&)> transition = [](distribution&){})
    Comme tu es capable de construire la distribution pendant le calcul, tu dois être capable de le faire aussi pendant l'export, et il comme ce n'est probablement pas la partie la plus lente de l'opération, j'imagine qu'il n'est pas nécessaire de maintenir l'intégralité des étapes.

    D'une manière générale, ne cherche pas à optimiser la consommation (mémoire ou temporelle) tant que tu n'as pas constaté, d'une part que c'est nécessaire, d'autre part que c'est cette partie du code qui rapporterait le plus.

  11. #11
    Membre éclairé Avatar de Seabirds
    Homme Profil pro
    Post-doctoral fellow
    Inscrit en
    Avril 2015
    Messages
    294
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Post-doctoral fellow
    Secteur : Agroalimentaire - Agriculture

    Informations forums :
    Inscription : Avril 2015
    Messages : 294
    Par défaut
    Citation Envoyé par ternel Voir le message
    Donc, le premier cas, c'est le second où toutes les valeurs sont égales à 1/N.
    Je ne me suis pas exprimé assez clairement. Disons que c'est le cas où l'état à t+1 est indépendant de l'état au temps t. Mais en fait je suis débile, parce qu'une simple distribution sera utilisable, un kernel n'a ici aucun intérêt.

    [EDIT]
    Comme tu es capable de construire la distribution pendant le calcul, tu dois être capable de le faire aussi pendant l'export, et il comme ce n'est probablement pas la partie la plus lente de l'opération, j'imagine qu'il n'est pas nécessaire de maintenir l'intégralité des étapes.
    J'ai pas compris

  12. #12
    Membre Expert
    Avatar de Pyramidev
    Homme Profil pro
    Tech Lead
    Inscrit en
    Avril 2016
    Messages
    1 513
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Tech Lead

    Informations forums :
    Inscription : Avril 2016
    Messages : 1 513
    Par défaut
    Personnellement, j'aurais défini :
    • un concept TransitionKernel dont la principale contrainte est, en gros, de posséder un membre template<class Generator> state_type operator()(state_type state, Generator& g) const et
    • un concept TimeKernel dont la principale contrainte est, en gros, de posséder un membre template<class Generator> state_type operator()(Time time, state_type state, Generator& g) const.

    Ensuite, j'aurais créé des classes qui respectent les conditions du concept TransitionKernel et d'autres classes qui respectent les conditions du concept TimeKernel.

    Ainsi, on peut facilement ajouter ou modifier des implémentations.

    Par exemple, si on veut stocker en mémoire tous les états du kernel en fonction du temps, mais que ces états sont très nombreux, voici un bout de code avec :
    • un modèle de classe MatrixTransitionKernel qui stocke la matrice des probabilité des transitions entre états et
    • un modèle de classe TabTimeKernel qui stocke un tableau de couples (temps, TransitionKernel) ordonné selon le temps et dans lequel deux TransitionKernel consécutifs sont toujours différents (pour optimiser la mémoire).

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    #include <algorithm>
    #include <array>
    #include <cassert>
    #include <vector>
    #include <random>
     
    /*!
     * \tparam State      Integral type or enumeration type.
     * \tparam StateCount The valid values of State are all the values from 0 to StateCount-1.
     */
    template<class State, size_t StateCount>
    class MatrixTransitionKernel
    {
    public:
    	using                   state_type  = State;
    	static constexpr size_t state_count = StateCount;
     
    	template<class Generator>
    	State operator()(State state, Generator& g) const
    	{
    		assert(state >= 0 && state < StateCount);
    		const size_t index = static_cast<size_t>(state);
    		std::discrete_distribution<> d(&m_transition[index*StateCount], &m_transition[(index+1)*StateCount]);
    		const int result = d(g);
    		return static_cast<State>(result);
    	}
     
    	// ...
     
    private:
    	//! \brief   Matrix of transitions.
    	//! \details Each cell (i, j) contains the probability of the transition
    	//!          from the state i to the state j.
    	//!          Cells are stored line by line.
    	std::array<double, StateCount*StateCount> m_transition;
    };
     
    /*!
     * \tparam TransitionKernel A class which meets the following requirements:
     *    - TransitionKernel::state_type is an integral type or enumeration type.
     *    - TransitionKernel::state_count is a number.
     *      The valid values of state_type are all the values from 0 to state_count-1.
     *    - If tk is a TransitionKernel, state a state_type ang g a non-const lvalue
     *      of a class which meets the requirements of RandomNumberDistribution,
     *      then tk(state, g) is a valid expression convertible to state_type.
     */
    template<class Time, class TransitionKernel>
    class TabTimeKernel
    {
    public:
    	using                   state_type  = typename TransitionKernel::state_type;
    	static constexpr size_t state_count = TransitionKernel::state_count;
     
    	template<class Generator>
    	state_type operator()(Time time, state_type state, Generator& g) const
    	{
    		assert(state >= 0 && state < state_count);
    		auto it = std::partition_point(m_transitions.begin(), m_transitions.end(),
    			[time](const auto& x) { return x.first <= time; });
    		if(it == m_transitions.begin())
    			throw std::out_of_range("The time argument is too low.");
    		const TransitionKernel& tk = (--it)->second;
    		return tk(state, g);
    	}
     
    	// ...
     
    private:
    	//! \brief   Table of kernels sorted by time.
    	//! \details Between two consecutive times from this table, the kernel is constant.
    	//! \todo    Use std::deque instead of std::vector?
    	std::vector<std::pair<Time, TransitionKernel>> m_transitions;
    };
    Edit 2017-01-27-21h23 : modif de commentaire : "two times" -> "two consecutive times".
    Edit 2017-01-27-22h42 : MapTimeKernel renommé en TimeKernel dans mon message.
    Edit 2017-01-28-00h32 : TabTimeKernel::operator() : étourderie corrigée.

  13. #13
    Membre éclairé Avatar de Seabirds
    Homme Profil pro
    Post-doctoral fellow
    Inscrit en
    Avril 2015
    Messages
    294
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Post-doctoral fellow
    Secteur : Agroalimentaire - Agriculture

    Informations forums :
    Inscription : Avril 2015
    Messages : 294
    Par défaut
    Super, merci à tout le monde !

    J'ai pioché des idées un peu partout, et ça marche

    Juste pour être sûr, mais les concepts ne sont définis que dans la documentation, n'est-ce pas ?

  14. #14
    Expert éminent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 644
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    Par défaut
    Citation Envoyé par Seabirds Voir le message
    Juste pour être sûr, mais les concepts ne sont définis que dans la documentation, n'est-ce pas ?
    Oui mais... non, à vrai dire...

    Oui, parce que, pour l'instant, ils sont en cours de discussion par le comité de normalisation, non parce qu'ils existent dans boost.

    Et non parce que, avec tout ce qui se trouve dans le fichier type_traits, on a déjà tout ce qu'il faut pour vérifier la présence (ou l'absence) d'un concept particulier, à part la notation requires
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  15. #15
    Membre éclairé Avatar de Seabirds
    Homme Profil pro
    Post-doctoral fellow
    Inscrit en
    Avril 2015
    Messages
    294
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Post-doctoral fellow
    Secteur : Agroalimentaire - Agriculture

    Informations forums :
    Inscription : Avril 2015
    Messages : 294
    Par défaut
    Hum. J'avoue que ça donne très envie de s'y atteler, d'un autre côté c'est beaucoup de boulot (pour un débutant en rush perpétuel ... ).

    On verra avec le temps, pour l'instant et à moins que vous n'y voyiez un inconvénient majeur, je vais voir si je peux me contenter de quelques commentaires

    Et tout cas merci beaucoup !

  16. #16
    Expert éminent

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2007
    Messages
    5 202
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Juin 2007
    Messages : 5 202
    Par défaut
    En ce cas, voici juste quelques pistes de recherche dans cette direction:
    1. is_integral<T>::value est un booléen connu à la compilation qui indique si T est un type entier (int, long, unsigned char, etc).
    2. typename enable_if<condition, T>::type est le type T, si et seulement si condition est vraie à la compilation. Sinon, il n'est pas défini, ce qui fait que ce qui s'en sert ne compile pas.
    3. SFINAE est un sigle signifiant "Substitution Failure Is Not An Error" et disant que si parmi plusieurs possibilité template, il y en a qui ne compile pas, ce n'est pas une erreur si au moins une compile.


    is_integral est l'une des très nombreuses métafonctions qu'on trouve dans <type_traits> de la STL (ou de Boost avant C++11).

    Cela permet d'écrire:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    template <typename T>
    typename enable_if< is_integral<T>, T>::type increment(T t) { return t+1; }
     
    template <typename T>
    typename enable_if< !is_integral<T>, T>::type increment(T t) { return ++t; }
    La première fonction ne compile que si T est un entier, la seconde que si ce n'est pas le cas.
    Donc, quel que soit le type T considéré, il n'y en a qu'une qui compile.
    Attention, ce ne sont pas des spécialisations d'une même template, mais bien deux surcharges.

    Le cas le plus courant se trouve dans les spécialisations, mais les exemples sont moins clairs, je trouve.

    cpprefence.com possède une page dédiée à SFINAE qui est assez complète pour saisir son fonctionnement.

  17. #17
    Membre Expert
    Avatar de Pyramidev
    Homme Profil pro
    Tech Lead
    Inscrit en
    Avril 2016
    Messages
    1 513
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Tech Lead

    Informations forums :
    Inscription : Avril 2016
    Messages : 1 513
    Par défaut
    Citation Envoyé par Seabirds Voir le message
    Juste pour être sûr, mais les concepts ne sont définis que dans la documentation, n'est-ce pas ?
    Dans le code que j'ai écrit, le concept TransitionKernel n'était qu'un commentaire.
    Mais j'avoue avoir hésité à utiliser la fonctionnalité des concepts prévue en C++, mais toujours pas présente en C++17.
    Le code ressemblerait alors à quelque chose comme :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    #if defined(__cpp_concepts) && __cpp_concepts >= 201500
     
    template<typename T>
    concept bool TransitionKernel =
    requires(const T tk, std::random_device& rd)
    {
    	typename T::state_type;
     
    	requires std::is_integral<typename T::state_type>::value || std::is_enum<typename T::state_type>::value;
     
    	{ T::state_count } -> size_t;
     
    	// The valid values of T::state_type are all the values from 0 to T::state_count-1.
    	// If the user uses another value, the behaviour is undefined.
     
    	{ tk( static_cast<typename T::state_type>(0) , rd ) } -> typename T::state_type;
     
    	// Let rnd be a non-const lvalue of a class which meets the requirements of RandomNumberDistribution.
    	// The expression tk( static_cast<typename T::state_type>(0) , rnd )
    	// must be valid and convertible to T::state_type.
    };
     
    #endif
     
    template<class Time, class TK>
    #if defined(__cpp_concepts) && __cpp_concepts >= 201500
    	requires TransitionKernel<TK>
    #endif
    class TabTimeKernel
    {
    	// code
    };
    Le code protégé par les macros est visible avec GCC 6.3.0 si on utilise l'option -fconcepts. Ça compile bien.
    J'ai laissé en commentaires les idées du concept TransitionKernel que je n'arrivais pas à traduire en code.

  18. #18
    Membre éclairé Avatar de Seabirds
    Homme Profil pro
    Post-doctoral fellow
    Inscrit en
    Avril 2015
    Messages
    294
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Post-doctoral fellow
    Secteur : Agroalimentaire - Agriculture

    Informations forums :
    Inscription : Avril 2015
    Messages : 294
    Par défaut
    ternel : Ok pour SFINAE et enable_if , merci !

    Pyramidev : Wooof merci beaucoup mais ça c'est un peu trop haut niveau pour mon état actuel d'inconnaissances !
    Par contre j'ai plusieurs questions sur ton code précédent, dis moi si je me trompe

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    #include <algorithm>
    #include <array>
    #include <cassert>
    #include <vector>
    #include <random>
     
    /*!
     * \tparam State      Integral type or enumeration type.
     * \tparam StateCount The valid values of State are all the values from 0 to StateCount-1.
     */
    template<class State, size_t StateCount>
    class MatrixTransitionKernel
    {
    public:
    	using                   state_type  = State;
    	static constexpr size_t state_count = StateCount;
     
    	template<class Generator>
    	State operator()(State state, Generator& g) const
    	{
    		assert(state >= 0 && state < StateCount);
    		const size_t index = static_cast<size_t>(state);
    		std::discrete_distribution<> d(&m_transition[index*StateCount], &m_transition[(index+1)*StateCount]);
    		const int result = d(g);
    		return static_cast<State>(result);
    	}
     
    	// ...
     
    private:
    	//! \brief   Matrix of transitions.
    	//! \details Each cell (i, j) contains the probability of the transition
    	//!          from the state i to the state j.
    	//!          Cells are stored line by line.
    	std::array<double, StateCount*StateCount> m_transition;
    };

    size_t : On l'utilise pour définir le type d'une variable qui modélise une taille ou l'indice d'un tableau
    static_cast : juste pour rendre explicite la conversion du type integral vers le type size_t ?
    std::array<double, StateCount*StateCount> : il y a un coût à faire un std::array<std::array<double, StateCount>, StateCount> ? Je me souviens avoir lu que en gros on évitait de faire des tableaux de tableaux, que l'algo pour faire la même chose avec un seul tableau était très facile, mais je ne me souviens plus de la raison (et ne la trouve pas sur stackoverflow).

  19. #19
    Expert éminent

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2007
    Messages
    5 202
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Juin 2007
    Messages : 5 202
    Par défaut
    Le prix d'un tableau de tableau, c'est surtout particulièrement vrai si on a un tableau de pointeurs de tableaux, par exemple avec vector<vector<T>>, car dans ce cas, les différentes lignes ne sont pas côte à côte en mémoire.
    Pour array, le problème est moindre, voire inexistant, mais ca ne coute rien de le faire.

  20. #20
    Membre Expert
    Avatar de Pyramidev
    Homme Profil pro
    Tech Lead
    Inscrit en
    Avril 2016
    Messages
    1 513
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Tech Lead

    Informations forums :
    Inscription : Avril 2016
    Messages : 1 513
    Par défaut
    Citation Envoyé par Seabirds Voir le message
    static_cast : juste pour rendre explicite la conversion du type integral vers le type size_t ?
    C'est ça. C'est juste pour que la conversion soit plus explicite pour le lecteur.
    Mais ce n'est pas très important. J'aurais pu ne pas l'écrire.

    Citation Envoyé par Seabirds Voir le message
    std::array<double, StateCount*StateCount> : il y a un coût à faire un std::array<std::array<double, StateCount>, StateCount> ? Je me souviens avoir lu que en gros on évitait de faire des tableaux de tableaux, que l'algo pour faire la même chose avec un seul tableau était très facile, mais je ne me souviens plus de la raison (et ne la trouve pas sur stackoverflow).
    Un std::vector<std::vector<double>> n'est pas performant, car chaque sous-tableau std::vector<double> va faire un appel à operator new pour allouer les éléments. En plus, comme le dit ternel, les sous-tableaux ne seront pas côte à côte en mémoire.

    Par contre, un std::array<std::array<double, StateCount>, StateCount> n'a aucun de ces inconvénients. Tu peux l'utiliser sans craindre de perdre en performances.
    D'ailleurs, au niveau de la lisibilité, ça me semble être une bonne idée, car la ligne :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    std::discrete_distribution<> d(m_transition[index].begin(), m_transition[index].end());
    sera plus lisible que ma ligne :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    std::discrete_distribution<> d(&m_transition[index*StateCount], &m_transition[(index+1)*StateCount]);

+ Répondre à la discussion
Cette discussion est résolue.
Page 1 sur 2 12 DernièreDernière

Discussions similaires

  1. Réponses: 1
    Dernier message: 02/10/2016, 22h51
  2. Question sur la conception d'une classe type Logger
    Par NLS le pingouin dans le forum C++
    Réponses: 14
    Dernier message: 01/11/2010, 13h36
  3. Hésitation sur le choix de PostGreSQL
    Par brice01 dans le forum Décisions SGBD
    Réponses: 3
    Dernier message: 13/12/2004, 17h48
  4. [Débutant][Conception] Erreur avec une classe interne
    Par Devil Redneck dans le forum Général Java
    Réponses: 5
    Dernier message: 11/06/2004, 15h45
  5. Recherche Livre / Conseils sur la conception de Base
    Par Yeuma dans le forum Décisions SGBD
    Réponses: 7
    Dernier message: 02/01/2004, 14h25

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo