1 /**
2  *
3  *  Copyright 2005-2019 Pierre-Henri WUILLEMIN et Christophe GONZALES (LIP6)
4  *   {prenom.nom}_at_lip6.fr
5  *
6  *  This library is free software: you can redistribute it and/or modify
7  *  it under the terms of the GNU Lesser General Public License as published by
8  *  the Free Software Foundation, either version 3 of the License, or
9  *  (at your option) any later version.
10  *
11  *  This library is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU Lesser General Public License for more details.
15  *
16  *  You should have received a copy of the GNU Lesser General Public License
17  *  along with this library.  If not, see <http://www.gnu.org/licenses/>.
18  *
19  */
20 %ignore gum::MultiDimWithOffset;
21 %ignore gum::MultiDimImplementation;
22 %ignore gum::MultiDimInterface;
23 %ignore gum::MultiDimDecorator;
24 %ignore gum::MultiDimArray;
25 
26 /* keep tracks of variables to trick with  garbage collector */
27 %pythonappend gum::Potential<double>::Potential %{
28         self._list_vars=list()
29 %}
30 %pythonappend gum::Potential<double>::remove %{
31         self._list_vars.remove(var)
32 %}
33 
34 %pythonappend gum::Potential<double>::add %{
35         self._list_vars.append(v)
36         return self
37 %}
38 
39 
40 %define CHANGE_THEN_RETURN_SELF(methodname)
41 %pythonappend gum::Potential<double>::methodname %{
42         return self
43 %}
44 %enddef
45 
46 CHANGE_THEN_RETURN_SELF(abs)
47 CHANGE_THEN_RETURN_SELF(sq)
48 CHANGE_THEN_RETURN_SELF(log2)
49 CHANGE_THEN_RETURN_SELF(normalize)
50 CHANGE_THEN_RETURN_SELF(normalizeAsCPT)
51 CHANGE_THEN_RETURN_SELF(scale)
52 CHANGE_THEN_RETURN_SELF(inverse)
53 CHANGE_THEN_RETURN_SELF(translate)
54 
55 CHANGE_THEN_RETURN_SELF(fillWith)
56 
57 %rename ("$ignore", fullname=1) gum::Potential<double>::margSumOut(const Set<const DiscreteVariable*>& del_vars) const;
58 %rename ("$ignore", fullname=1) gum::Potential<double>::margProdOut(const Set<const DiscreteVariable*>& del_vars) const;
59 %rename ("$ignore", fullname=1) gum::Potential<double>::margMaxOut(const Set<const DiscreteVariable*>& del_vars) const;
60 %rename ("$ignore", fullname=1) gum::Potential<double>::margMinOut(const Set<const DiscreteVariable*>& del_vars) const;
61 %rename ("$ignore", fullname=1) gum::Potential<double>::margSumIn(const Set<const DiscreteVariable*>& kept_vars) const;
62 %rename ("$ignore", fullname=1) gum::Potential<double>::margProdIn(const Set<const DiscreteVariable*>& kept_vars) const;
63 %rename ("$ignore", fullname=1) gum::Potential<double>::margMaxIn(const Set<const DiscreteVariable*>& kept_vars) const;
64 %rename ("$ignore", fullname=1) gum::Potential<double>::margMinIn(const Set<const DiscreteVariable*>& kept_vars) const;
65 
66 %rename ("$ignore", fullname=1) gum::Potential<double>::reorganize(const Set<const DiscreteVariable*>& vars) const;
67 %rename ("$ignore", fullname=1) gum::Potential<double>::putFirst(const DiscreteVariable* var) const;
68 
69 
70 %extend gum::Potential<double> {
71     Potential<double> extract(PyObject* dict) {
72       gum::Instantiation inst;
73       PyAgrumHelper::fillInstantiationFromPyObject(self,inst,dict);
74       return self->extract(inst);
75     }
76 
77     Potential<double>
78     margSumOut( PyObject* varnames ) const {
79       gum::Set<const gum::DiscreteVariable*> s;
80       PyAgrumHelper::fillDVSetFromPyObject(self,s,varnames); //from helpers.h
81       return self->margSumOut(s);
82     }
83 
84     Potential<double>
85     margProdOut( PyObject* varnames ) const {
86       gum::Set<const gum::DiscreteVariable*> s;
87       PyAgrumHelper::fillDVSetFromPyObject(self,s,varnames); //from helpers.h
88       return self->margProdOut(s);
89     }
90 
91     Potential<double>
92     margMaxOut( PyObject* varnames ) const {
93       gum::Set<const gum::DiscreteVariable*> s;
94       PyAgrumHelper::fillDVSetFromPyObject(self,s,varnames); //from helpers.h
95       return self->margMaxOut(s);
96     }
97 
98     Potential<double>
99     margMinOut( PyObject* varnames ) const {
100       gum::Set<const gum::DiscreteVariable*> s;
101       PyAgrumHelper::fillDVSetFromPyObject(self,s,varnames); //from helpers.h
102       return self->margMinOut(s);
103     }
104 
105     Potential<double>
106     margSumIn( PyObject* varnames ) const {
107       gum::Set<const gum::DiscreteVariable*> s;
108       PyAgrumHelper::fillDVSetFromPyObject(self,s,varnames); //from helpers.h
109       return self->margSumIn(s);
110     }
111 
112     Potential<double>
113     margProdIn( PyObject* varnames ) const {
114       gum::Set<const gum::DiscreteVariable*> s;
115       PyAgrumHelper::fillDVSetFromPyObject(self,s,varnames); //from helpers.h
116       return self->margProdIn(s);
117     }
118 
119     Potential<double>
120     margMaxIn( PyObject* varnames ) const {
121       gum::Set<const gum::DiscreteVariable*> s;
122       PyAgrumHelper::fillDVSetFromPyObject(self,s,varnames); //from helpers.h
123       return self->margMaxIn(s);
124     }
125 
126     Potential<double>
127     margMinIn( PyObject* varnames ) const {
128       gum::Set<const gum::DiscreteVariable*> s;
129       PyAgrumHelper::fillDVSetFromPyObject(self,s,varnames); //from helpers.h
130       return self->margMinIn(s);
131     }
132 
133     // equality
134     bool __eq__(const gum::Potential<double>& b) {
135       return *self==b;
136     }
137 
138     // non equality
139     bool __ne__(const gum::Potential<double>& b) {
140       return *self!=b;
141     }
142 
143 
144   %pythoncode {
145     def __radd__(self,other):
146       return self.__add__(other)
147 
148     def __rmul__(self,other):
149       return self.__mul__(other)
150 
151     def __rsub__(self,other):
152       return (self*-1)+other
153 
154     def __rfloordiv__(self,other):
155       return Potential(self).inverse().scale(other)
156 
157     def __rtruediv__(self,other):
158       return Potential(self).inverse().scale(other)
159 
160     def __rdiv__(self,other):
161       return Potential(self).inverse().scale(other)
162 
163     def __neg__(self):
164       return -1*self
165 
166     def __abs__(self):
167       return Potential(self).abs()
168 
169     def loopIn(self):
170       """
171       Generator to iterate inside a Potential.
172 
173       Yield an gum.Instantiation that iterates over all the possible values for the gum.Potential
174 
175       Examples
176       --------
177       >>> import pyAgrum as gum
178       >>> bn=gum.fastBN("A[3]->B[3]<-C[3]")
179       >>> for i in bn.cpt("B").loopIn():
180             print(i)
181             print(bn.cpt("B").get(i))
182             bn.cpt("B").set(i,0.3)
183       """
184       i=Instantiation(self)
185       i.setFirst()
186       while not i.end():
187         yield i
188         i.inc()
189       return
190 
191     def fillWithFunction(self,s,noise=None):
192       """
193       Automatically fills the potential as a (quasi) deterministic CPT with the evaluation of the expression s.
194 
195       The expression s gives a value for the first variable using the names of the last variables.
196       The computed CPT is deterministic unless noise is used to add a 'probabilistic' noise around the exact value given by the expression.
197 
198 
199       Examples
200       --------
201       >>> import pyAgrum as gum
202       >>> bn=gum.fastBN("A[3]->B[3]<-C[3]")
203       >>> bn.cpt("B").fillWithFunction("(A+C)/2")
204 
205       Parameters
206       ----------
207       s : str
208           an expression using the name of the last variables of the Potential and giving a value to the first variable of the Potential
209       noise : list
210           an (odd) list of numerics giving a pattern of 'probabilistic noise' around the value.
211 
212       Warning
213       -------
214           The expression may have any numerical values, but will be then transformed to the closest correct value for the range of the variable.
215 
216       Returns
217       -------
218       pyAgrum.Potential
219             a reference to the modified potential
220 
221       Raises
222       ------
223       gum.InvalidArgument
224         If the first variable is Labelized or Integer, or if the len of the noise is not odd.
225       """
226       if self.variable(0).varType()==VarType_Labelized:
227         raise InvalidArgument("[pyAgrum] The variable "+self.variable(0).name()+" is a LabelizedVariable")
228       if self.variable(0).varType()==VarType_Integer:
229         raise InvalidArgument("[pyAgrum] The variable "+self.variable(0).name()+" is neither Range nor Discretized variable.")
230 
231       if noise==None:
232         mid=0
233       else:
234         if len(noise)%2==0:
235           raise InvalidArgument("[pyAgrum] len(noise) must not be even")
236         mid=int((len(noise)-1)/2)
237 
238       self.fillWith(0)
239       mi=self.variable(0).numerical(0)
240       ma=self.variable(0).numerical(self.variable(0).domainSize()-1)
241 
242       I=Instantiation(self)
243 
244       I.setFirst()
245       while not I.end():
246         vars={self.variable(i).name():self.variable(i).numerical(I.val(i)) for i in range(1,self.nbrDim())}
247         res=eval(s,None,vars)
248         if res<mi:
249           res=mi
250         if res>ma:
251           res=ma
252         pos=self.variable(0).index(str(res))
253         if mid==0:
254           I.chgVal(0,pos)
255           self.set(I,1)
256         else:
257           for i,v in enumerate(noise):
258             if 0<=pos+i-mid<self.variable(0).domainSize():
259               I.chgVal(0,pos+i-mid)
260               self.set(I,v)
261         I.incNotVar(self.variable(0))
262       self.normalizeAsCPT()
263       return self
264 
265     def variablesSequence(self):
266         """
267         Returns
268         -------
269         list
270             a list containing the sequence of variables
271         """
272         varlist = []
273         for i in range(0, self.nbrDim()):
274             varlist.append(self.variable(i))
275         return varlist
276 
277     def __prepareIndices__(self,ind):
278       """
279       From an indice (dict or tuple), returns a pair of gum.Instantiation to loop in a part of the Potential.
280       """
281       loopvars=Instantiation(self)
282       loopvars.setMutable()
283 
284       inst=Instantiation(self)
285       inst.setFirst()
286 
287       if isinstance(ind, (Number,slice)):
288         i = tuple([ind])
289       else:
290         i = ind
291 
292       vn=self.var_names
293       if isinstance(i,dict):
294           for nam in vn:
295               if nam in i:
296                   inst.chgVal(nam,i[nam])
297                   loopvars.erase(nam)
298       elif isinstance(i,tuple):
299           if len(i)>self.nbrDim():
300               raise KeyError("Too many values in '"+str(i)+"' for '"+str(self)+"'")
301           for k,v in enumerate(i):
302               if not isinstance(v,slice):
303                   nam=vn[k]
304                   inst.chgVal(nam,v)
305                   loopvars.erase(nam)
306       else:
307           raise ValueError("No subscript using '"+str(i)+"'")
308       return inst,loopvars
309 
310     def __getitem__(self, id):
311       if isinstance(id,Instantiation):
312           return self.get(id)
313 
314       inst,loopvars=self.__prepareIndices__(id)
315 
316       if loopvars.nbrDim()==0:
317           return self.get(inst)
318 
319       if loopvars.nbrDim()==self.nbrDim():
320         content=[]
321 
322         inst=Instantiation(self)
323         while not inst.end():
324             content.append(self.get(inst))
325             inst.inc()
326         tab=numpy.array(content,dtype=numpy.float64)
327         tab.shape=tuple(self.var_dims)
328         return tab
329 
330       names=[loopvars.variable(i-1).name() for i in range(loopvars.nbrDim(),0,-1)]
331       tab=numpy.zeros(tuple([loopvars.variable(i-1).domainSize() for i in range(loopvars.nbrDim(),0,-1)]))
332       while not inst.end():
333           indice=[inst.val(name) for name in names]
334           tab[tuple(indice)]=self.get(inst)
335           inst.incIn(loopvars)
336       return tab
337 
338     def __setitem__(self, id, value):
339       if isinstance(id,Instantiation):
340           self.set(id,value)
341           return
342 
343       inst,loopvars=self.__prepareIndices__(id)
344 
345       if loopvars.nbrDim()==0:
346           self.set(inst,value)
347           return
348 
349       if isinstance(value,Number):
350         while not inst.end():
351             self.set(inst,value)
352             inst.incIn(loopvars)
353       else:
354         if isinstance(value,list):
355             value=numpy.array(value)
356 
357         shape=tuple([loopvars.variable(i-1).domainSize() for i in range(loopvars.nbrDim(),0,-1)])
358         if value.shape!=shape:
359           raise IndexError("Shape of '"+str(value)+"' is not '"+str(shape)+"'")
360 
361         names = [loopvars.variable(i - 1).name() for i in range(loopvars.nbrDim(), 0, -1)]
362         while not inst.end():
363             indice = tuple([inst.val(name) for name in names])
364             self.set(inst,float(value[indice]))
365             inst.incIn(loopvars)
366 
367     def tolist(self):
368         """
369         Returns
370         -------
371         list
372             the potential as a list
373         """
374         return self.__getitem__({}).tolist()
375 
376     def toarray(self):
377         """
378         Returns
379         -------
380         array
381             the potential as an array
382         """
383         return self.__getitem__({})
384 
385     def topandas(self):
386         """
387         Returns
388         -------
389         pandas.DataFrame
390            the potential as an pandas.DataFrame
391         """
392         import pandas as pd
393         varnames = self.var_names
394         data = []
395         pname = ""
396         for inst in self.loopIn():
397             d = {k:v for k,v in reversed(inst.todict(True).items())}
398             d[pname] = self.get(inst)
399             d[pname], d[varnames[-1]] = d[varnames[-1]], d[pname]
400             data.append(d)
401         cols = varnames[:-1] + [pname]
402         return pd.DataFrame(data).set_index(cols).unstack(pname)
403 
404     def tolatex(self):
405         """
406         Render object to a LaTeX tabular.
407 
408         Requires to include `booktabs` package in the LaTeX document.
409 
410         Returns
411         -------
412         str
413          the potential as LaTeX string
414         """
415         return self.topandas().to_latex()
416 
417     def toclipboard(self,**kwargs):
418         """
419         Write a text representation of object to the system clipboard. This can be pasted into spreadsheet, for instance.
420         """
421         return self.topandas().to_clipboard()
422 
423     @property
424     def var_names(self):
425         """
426         Returns
427         -------
428         list
429             a list containing the name of each variables in the potential
430 
431         Warnings
432         --------
433             listed in the reverse order of the enumeration order of the variables.
434         """
435         return [self.variable(i-1).name() for i in range(self.nbrDim(),0,-1)]
436 
437     @property
438     def var_dims(self):
439         """
440         Returns
441         -------
442         list
443             a list containing the dimensions of each variables in the potential
444         """
445         return [self.variable(i-1).domainSize() for i in range(self.nbrDim(),0,-1)]
446   }
447 }