1 /***************************************************************************
2  *   Copyright (c) 2005-2021 by Pierre-Henri WUILLEMIN et Christophe GONZALES   *
3  *   {prenom.nom}_at_lip6.fr                                               *
4  *                                                                         *
5  *   This program is free software; you can redistribute it and/or modify  *
6  *   it under the terms of the GNU General Public License as published by  *
7  *   the Free Software Foundation; either version 2 of the License, or     *
8  *   (at your option) any later version.                                   *
9  *                                                                         *
10  *   This program is distributed in the hope that it will be useful,       *
11  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
12  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
13  *   GNU General Public License for more details.                          *
14  *                                                                         *
15  *   You should have received a copy of the GNU General Public License     *
16  *   along with this program; if not, write to the                         *
17  *   Free Software Foundation, Inc.,                                       *
18  *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
19  ***************************************************************************/
20 /**
21  * @file
22  * @brief This file contains c++ helper functions for wrappers (not included but
23  * imported in the wrapper)
24  *
25  * @author Pierre-Henri WUILLEMIN
26  */
27 #include <agrum/BN/IBayesNet.h>
28 #include <agrum/tools/core/set.h>
29 #include <agrum/tools/graphs/graphElements.h>
30 #include <agrum/tools/graphs/parts/nodeGraphPart.h>
31 #include <agrum/tools/multidim/potential.h>
32 
33 namespace PyAgrumHelper {
34 
stringFromPyObject(PyObject * o)35   std::string stringFromPyObject(PyObject* o) {
36     std::string name = "";
37     if (PyUnicode_Check(o)) {   // python3 string
38       PyObject* asbytes = PyUnicode_AsUTF8String(o);
39       name = PyBytes_AsString(asbytes);
40       Py_DECREF(asbytes);
41     } else if (PyString_Check(o)) {   // python2 string
42       name = PyString_AsString(o);
43     } else if (PyBytes_Check(o)) {   // other python3 string
44       name = PyBytes_AsString(o);
45     }
46     return name;
47   }
48 
49   // filling a Set of DiscreteVariable* from a list of string, in the context of a
50   // potential.
fillDVSetFromPyObject(const gum::Potential<double> * pot,gum::Set<const gum::DiscreteVariable * > & s,PyObject * varnames)51   void fillDVSetFromPyObject(const gum::Potential< double >*           pot,
52                              gum::Set< const gum::DiscreteVariable* >& s,
53                              PyObject*                                 varnames) {
54     gum::Set< std::string > names;
55     if (PyList_Check(varnames)) {
56       auto siz = PyList_Size(varnames);
57       for (int i = 0; i < siz; i++) {
58         std::string name = stringFromPyObject(PyList_GetItem(varnames, i));
59 
60         if (name == "")
61           GUM_ERROR(gum::InvalidArgument, "Argument is not a list of string")
62 
63         names << name;
64       }
65     } else {
66       std::string name = stringFromPyObject(varnames);
67       if (name == "") {
68         GUM_ERROR(gum::InvalidArgument, "Argument is not a list or a string")
69       } else {
70         names << name;
71       }
72     }
73 
74     for (const auto v: pot->variablesSequence())
75       if (names.contains(v->name())) s << v;
76 
77     if (s.size() == 0)
78       GUM_ERROR(gum::InvalidArgument, "No relevant dimension in the argument")
79   }
80 
81   // filling a vector of DiscreteVariable* from a list of string, in the context of
82   // a potential.
fillDVVectorFromPyObject(const gum::Potential<double> * pot,std::vector<const gum::DiscreteVariable * > & s,PyObject * varnames)83   void fillDVVectorFromPyObject(const gum::Potential< double >*              pot,
84                                 std::vector< const gum::DiscreteVariable* >& s,
85                                 PyObject* varnames) {
86     if (PyList_Check(varnames)) {
87       gum::HashTable< std::string, const gum::DiscreteVariable* > namesToVars;
88       for (gum::Idx i = 0; i < pot->nbrDim(); i++)
89         namesToVars.insert(pot->variable(i).name(), &(pot->variable(i)));
90 
91       auto siz = PyList_Size(varnames);
92       s.clear();
93 
94       for (int i = 0; i < siz; i++) {
95         std::string name = stringFromPyObject(PyList_GetItem(varnames, i));
96         if (name == "") {
97           GUM_ERROR(gum::InvalidArgument, "Argument is not a list of string")
98         }
99         if (!namesToVars.exists(name)) {
100           GUM_ERROR(gum::InvalidArgument,
101                     "Argument is not a name of a variable in this potential");
102         }
103         s.push_back(namesToVars[name]);
104       }
105     } else {
106       GUM_ERROR(gum::InvalidArgument, "Argument is not a list")
107     }
108   }
109 
110   // filling a DiscreteVariable* from a string, in the context of a potential.
fillDVFromPyObject(const gum::Potential<double> * pot,const gum::DiscreteVariable * & pvar,PyObject * varname)111   void fillDVFromPyObject(const gum::Potential< double >* pot,
112                           const gum::DiscreteVariable*&   pvar,
113                           PyObject*                       varname) {
114     const std::string name = stringFromPyObject(varname);
115     if (name == "") {
116       GUM_ERROR(gum::InvalidArgument, "Argument is not a string")
117     }
118 
119     bool isOK = false;
120     for (gum::Idx i = 0; i < pot->nbrDim(); i++) {
121       if (pot->variable(i).name() == name) {
122         pvar = &(pot->variable(i));
123         isOK = true;
124         break;
125       }
126     }
127     if (!isOK) {
128       GUM_ERROR(gum::InvalidArgument,
129                 "Argument is not a name of a variable in this potential");
130     }
131   }
132 
133   // filling a Instantiation from a dictionnary<string,int>
fillInstantiationFromPyObject(const gum::Potential<double> * pot,gum::Instantiation & inst,PyObject * dict)134   void fillInstantiationFromPyObject(const gum::Potential< double >* pot,
135                                      gum::Instantiation&             inst,
136                                      PyObject*                       dict) {
137     if (!PyDict_Check(dict)) {
138       GUM_ERROR(gum::InvalidArgument, "Argument is not a dictionary")
139     }
140 
141     gum::HashTable< std::string, const gum::DiscreteVariable* > namesToVars;
142     for (gum::Idx i = 0; i < pot->nbrDim(); i++)
143       namesToVars.insert(pot->variable(i).name(), &(pot->variable(i)));
144 
145 
146     PyObject*  key;
147     PyObject*  value;
148     Py_ssize_t pos = 0;
149     inst.clear();
150     while (PyDict_Next(dict, &pos, &key, &value)) {
151       std::string name = stringFromPyObject(key);
152       if (name == "") { GUM_ERROR(gum::InvalidArgument, "A key is not a string"); }
153       if (!namesToVars.exists(name)) {
154         // not relevant name. we just skip it
155         continue;
156       }
157       if (!(PyInt_Check(value))) {
158         GUM_ERROR(gum::InvalidArgument, "A value is not an int")
159       }
160       gum::Idx v = gum::Idx(PyInt_AsLong(value));
161       if (v >= namesToVars[name]->domainSize()) {
162         GUM_ERROR(gum::InvalidArgument,
163                   "The value " << v << " is not in the domain of " << name);
164       }
165       inst.add(*(namesToVars[name]));
166       inst.chgVal(namesToVars[name], v);
167     }
168   }
169 
nodeIdFromNameOrIndex(PyObject * n,const gum::VariableNodeMap & map)170   gum::NodeId nodeIdFromNameOrIndex(PyObject* n, const gum::VariableNodeMap& map) {
171     const std::string name = PyAgrumHelper::stringFromPyObject(n);
172     if (name != "") {
173       return map.idFromName(name);
174     } else if (PyInt_Check(n)) {
175       return gum::NodeId(PyInt_AsLong(n));
176     } else if (PyLong_Check(n)) {
177       return gum::NodeId(PyLong_AsLong(n));
178     } else {
179       GUM_ERROR(gum::InvalidArgument,
180                 "A value is neither a node name nor an node id");
181     }
182   }
183 
populateNodeSetFromPySequenceOfIntOrString(gum::NodeSet & nodeset,PyObject * seq,const gum::VariableNodeMap & map)184   void populateNodeSetFromPySequenceOfIntOrString(
185      gum::NodeSet& nodeset, PyObject* seq, const gum::VariableNodeMap& map) {
186     // if seq is just a string
187     const std::string name = PyAgrumHelper::stringFromPyObject(seq);
188     if (name != "") {
189       nodeset.insert(map.idFromName(name));
190       return;
191     }
192 
193     // if seq is just a nodeId
194     if (PyInt_Check(seq) || PyLong_Check(seq)) {
195       nodeset.insert(gum::NodeId(PyLong_AsLong(seq)));
196       return;
197     }
198 
199     // seq really is a sequence
200     PyObject* iter = PyObject_GetIter(seq);
201     if (iter != NULL) {
202       PyObject* item;
203       while ((item = PyIter_Next(iter))) {
204         nodeset.insert(nodeIdFromNameOrIndex(item, map));
205       }
206     } else {
207       GUM_ERROR(gum::InvalidArgument, "Argument <seq> is not a list nor a set")
208     }
209   }
210 
PyListFromNodeVect(const std::vector<gum::NodeId> & nodevect)211   PyObject* PyListFromNodeVect(const std::vector< gum::NodeId >& nodevect) {
212     PyObject* q = PyList_New(0);
213 
214     for (auto node: nodevect) {
215       PyList_Append(q, PyLong_FromUnsignedLong((unsigned long)node));
216     }
217 
218     return q;
219   }
220 
PyListFromNodeSet(const gum::NodeSet & nodeset)221   PyObject* PyListFromNodeSet(const gum::NodeSet& nodeset) {
222     PyObject* q = PyList_New(0);
223 
224     for (auto node: nodeset) {
225       PyList_Append(q, PyLong_FromUnsignedLong((unsigned long)node));
226     }
227 
228     return q;
229   }
230 
PySetFromNodeSet(const gum::NodeSet & nodeset)231   PyObject* PySetFromNodeSet(const gum::NodeSet& nodeset) {
232     PyObject* q = PySet_New(0);
233 
234     for (auto node: nodeset) {
235       PySet_Add(q, PyLong_FromUnsignedLong((unsigned long)node));
236     }
237 
238     return q;
239   }
240 
PySetFromNodeSet(const gum::NodeGraphPart & nodeset)241   PyObject* PySetFromNodeSet(const gum::NodeGraphPart& nodeset) {
242     PyObject* q = PySet_New(0);
243 
244     for (auto node: nodeset) {
245       PySet_Add(q, PyLong_FromUnsignedLong((unsigned long)node));
246     }
247 
248     return q;
249   }
250 
PyListFromArcVect(const std::vector<gum::Arc> & arcseq)251   PyObject* PyListFromArcVect(const std::vector< gum::Arc >& arcseq) {
252     PyObject* q = PyList_New(0);
253     for (const auto& arc: arcseq) {
254       PyList_Append(q, Py_BuildValue("(i,i)", arc.tail(), arc.head()));
255     }
256     return q;
257   }
258 
PySetFromArcSet(const gum::ArcSet & arcset)259   PyObject* PySetFromArcSet(const gum::ArcSet& arcset) {
260     PyObject* q = PySet_New(0);
261     for (const auto& arc: arcset) {
262       PySet_Add(q, Py_BuildValue("(i,i)", arc.tail(), arc.head()));
263     }
264     return q;
265   }
266 
PySetFromEdgeSet(const gum::EdgeSet & edgeset)267   PyObject* PySetFromEdgeSet(const gum::EdgeSet& edgeset) {
268     PyObject* q = PySet_New(0);
269     for (const auto& edg: edgeset) {
270       PySet_Add(q, Py_BuildValue("(i,i)", edg.first(), edg.second()));
271     }
272     return q;
273   }
274 
PyDictFromInstantiation(const gum::Instantiation & inst)275   PyObject* PyDictFromInstantiation(const gum::Instantiation& inst) {
276     PyObject* q = PyDict_New();
277     for (const auto& k: inst.variablesSequence()) {
278       PyDict_SetItemString(q,
279                            k->name().c_str(),
280                            PyLong_FromUnsignedLong((unsigned long)inst.val(*k)));
281     }
282     return q;
283   }
284 
PyDictFromPairMeanVar(std::pair<double,double> mv)285   PyObject* PyDictFromPairMeanVar(std::pair< double, double > mv) {
286     PyObject* q = PyDict_New();
287     PyDict_SetItemString(q, "mean", PyFloat_FromDouble(mv.first));
288     PyDict_SetItemString(q, "variance", PyFloat_FromDouble(mv.second));
289     return q;
290   }
291 
292   PyObject*
PySeqFromSetOfInstantiation(const gum::Set<gum::Instantiation> & soi)293      PySeqFromSetOfInstantiation(const gum::Set< gum::Instantiation >& soi) {
294     PyObject* q = PyList_New(0);
295     for (const auto& inst: soi) {
296       PyList_Append(q, PyDictFromInstantiation(inst));
297     }
298     return q;
299   }
300 }   // namespace PyAgrumHelper
301