#!/usr/bin/env python2.5
# Copyright (c) 2004-2007 Bartek Wilczynski and Norbert Dojer; All Rights Reserved.
#
# This software is distributable under the terms of the GNU
# General Public License (GPL) v2, the text of which can be found at
# http://www.gnu.org/copyleft/gpl-2.0.txt. Installing, importing or otherwise
# using this module constitutes acceptance of the terms of this License.
#
# Disclaimer
# 
# This software is provided "as-is".  There are no expressed or implied
# warranties of any kind, including, but not limited to, the warranties
# of merchantability and fittness for a given application.  In no event
# shall the authors be liable for any direct, indirect, incidental,
# special, exemplary or consequential damages (including, but not limited
# to, loss of use, data or profits, or business interruption) however
# caused and on any theory of liability, whether in contract, strict
# liability or tort (including negligence or otherwise) arising in any way
# out of the use of this software, even if advised of the possibility of
# such damage.
#

import graph,stats,math,fpconst
from itertools import chain
from BDE import BDE
from MDL import MDL
from MIT import MIT
import continuous
import util
from math import log


def eval_func(f_args):
    """Takes a tuple of a function and args, evaluates and returns result"""
    return f_args[0](*f_args[1:])

def learn_x(v):
    x,self,min_empty,min_optim,scr,verbose,n_min,limit,score=v

    selected_data = self.select_1(x)

    if min_empty:
        score_empty=scr.data_score(selected_data.subset([]))+scr.graph_score(len(parents),x,[],len(selected_data))
        score_max=score_empty-math.log(min_empty,2)
    else:
        score_max=fpconst.PosInf
    if min_optim:
        score_delta=-math.log(min_optim,2)
    else:
        score_delta=fpconst.PosInf
    (par,sc),minlist = scr.learn_1(selected_data,verbose,n_min,limit,score_max,score_delta)
    return x,par,minlist,score 



class experiment:
    """Class representing an experiment, possibly containing more than one time-point
    """
    def __init__(self,name,data,perturbed,points=[],static=False):
        """Constructor

        retains the argument data as self._data, while filling the self.data with double vectors
        (duplicates for static networks, consecutive pairs for dynamic)
        """
        self._data=data
        self.perturbed=perturbed
        self.data=[]
        self.name=name
        self.points=points
        if static:
            for i,d in enumerate(self._data):
                self.data.append(self._data[i]+d)
        else:
            # in format (data from time t - 1, data from time t)
            for i,d in enumerate(self._data[1:]):
                self.data.append(self._data[i]+d)


class dataset_1:
    """Class representing data selected for learning parents of one vertex
    """
    def __init__(self,data,vertex,parents,weights=None):
        """Constructor
        """
        self.data=data
        self.vertex=vertex
        self.parents=parents
        self.weights=weights
        
    def __len__(self):
        return len(self.data[-1])
        
    def orientation(self):
        res={}
        for i,p in enumerate(self.parents):
            sign = stats.pearsonr(self.data[i],self.data[-1])[0]
            if sign >=0:
                res[p]="+"
            else:
                res[p]="-"
        return res
        
    def subset(self,sub=[]):
        ssub = set(sub)
        sub_ind=[i for i,p in enumerate(self.parents) if p in ssub]
        ordered_sub=[self.parents[i] for i in sub_ind]
        data = [self.data[i] for i in sub_ind]
        data.append(self.data[-1])
        return dataset_1(data,self.vertex,ordered_sub,self.weights)
        
    def rm_sloops(self):
        try:
            i=self.parents.index(self.vertex)
            del self.parents[i]
            del self.data[i]
        except ValueError:
            pass
        
    def stats(self):
        def strec(key,prob,d):
            first,first_disc=d[0]
            rest=d[1:]
            if rest==[]:
                try:
                    stats_parents[key]+=prob
                except KeyError:
                    stats_parents[key]=prob
                if first_disc:
                    try:
                        stats_all[key+(first,)]+=prob
                    except KeyError:
                        stats_all[key+(first,)]=prob
                else:
                    try:
                        stats_all[key+(0,)]+=prob*(1-first)
                    except KeyError:
                        stats_all[key+(0,)]=prob*(1-first)
                    try:
                        stats_all[key+(1,)]+=prob*first
                    except KeyError:
                        stats_all[key+(1,)]=prob*first
            else:
                if first_disc:
                    strec(key+(first,),prob,rest)
                else:
                    strec(key+(0,),prob*(1-first),rest)
                    strec(key+(1,),prob*first,rest)
        d_disc=map(lambda p: p.n_disc,self.parents+[self.vertex])
        if 0 not in d_disc:
            return self.stats_disc()
        stats_all = {}
        stats_parents = {}
        for d in zip(*self.data):
            strec((),1,zip(d,d_disc))
        return stats_all,stats_parents
        
    def stats_disc(self):
        stats_all = {}
        stats_par = {}
        for d in zip(*self.data):
            d_par = tuple(d[:-1])
            d_all = tuple(d)
            try:
                stats_par[d_par]+=1
            except KeyError:
                stats_par[d_par]=1
            try:
                stats_all[d_all]+=1
            except KeyError:
                stats_all[d_all]=1
        return stats_all,stats_par
  

class gene:
    """Class corresponding to a single variable

    #TODO: rename to variable or smth... gene is inappropriate
    """
    def __init__(self,name,n_disc,index):
        self.name=name
        self.n_disc=n_disc
#        self.parents=[]
        self.values=[]
        self.index=index
        self.cpd=None
        
    def __str__(self):
        return self.name
    
    def disc_vals(self):
        """Returns discretized values of a variable

        works also for continuous variables
        """
        if self.values:
            try:
                return map(int,self.values)
            except ValueError:
                return self.values
        else:
            return [0,1] #This should take care of continuous variables... may need a change if we introduce more complex mixtures.

    def __index__(self):
        """returns the index of a variable.
        """
        return self.index

    def __add__(self,other):
        """Implements addition on indexes, so that we can write table[var+1] instad of table[var.index+1]
        
        """
        return other+self.index
    def __hash__(self):
        """returns the hash value for comparisons
        """
        return self.index

    def __eq__(self,other):
        if type(other)==int:
            return self.index==other
        else:
            return self.index==other.index    
        
class dataset:
    """
    A class representing a complete dataset.

    Implements input, output, data selection and also functions for learning networks given a scoring function
    """
    def __init__(self,name='no_name'):
        self.name=name
        self.n_series=0
        self.n_gen=0
        self.points=[]
        self.vertices= []
        self.vertice_names=[]
        self.static=False
        self.cont_weight=1.5
        self.regulators=[]
        self.parents={}
        self.prior={}
        
    def fromFile(self,f,float_transform=continuous.BiNormMeans()):
        import warnings
        warnings.warn("The old data format is deprecated! Please use the new format.")
        #number of genes in the first line
        self.n_gen=int(f.readline())
        # experiments description
        ln = f.readline().strip().split()
        self.n_series = int(ln[0])
        series = []
        for txt in ln[1:]:
            if txt.find("[")==-1:
                series.append((int(txt),[]))
            else: #perturbed genes
                series.append((int(txt[:txt.find("[")]),
                               [int(txt[1+txt.find("["):txt.find("]")])]))
        lines = []
        self.vertices=[]
        for i in range(self.n_gen):
            ln = f.readline().strip().split()
            n_disc = max(0,int(ln[1]))
            self.vertices.append(gene(ln[0],n_disc,i))
            if n_disc==0:
                if float_transform: #use float_transformn if specified
                    lines.append(float_transform.estTrans(map(float,ln[2:])))
                else:
                    lines.append(map(float,ln[2:]))
            else:
                lines.append(map(int,ln[2:]))
        self.points=[]
        i=0
        for s,p in series:
            d = []
            for k in range(s): 
                d.append(map(lambda x:x[i],lines))
                i+=1
            name="serie%d"%len(self.points)
            self.points.append(experiment(name,d,p,range(len(d))))

#        for v in self.vertices:
#            v.parents=map(lambda v: (float(max(self.cont_weight,v.n_disc)),v),self.vertices)
        for e in self.points:
            e.perturbed=map(lambda i: self.vertices[i],e.perturbed)
        return self

    def toNewFile(self,file):
        #perturbations
        for s in self.points:
            for g in s.perturbed:
                file.write("#perturbed\t%s\t%s\n"%(s.name,self.vertices[g]))
        #condition names\
        file.write("conditions")
        for s in self.points:
            for i,d in enumerate(s._data):
                file.write("\t%s:%d"%(s.name,i))
        file.write("\n")
        #expr_data
        for i,v in enumerate(self.vertices):
            file.write(str(v))
            for s in self.points:
                for j,d in enumerate(s._data):
                    if v.n_disc>0: #discrete
                        format="\t%d"
                    else: #continuous
                        format="\t%f"
                    file.write(format%d[i])
            file.write("\n")

    def fromNewFile(self,file,float_transform=continuous.BiNormMeans()):
        pert=[]
        disc={}
        cpd={'and':[],'or':[]}
        contin=[]
        self.default=None
        rec=[]
        regul=[]
        while not rec:
            ln=file.readline()
            rec=ln.strip().split()
        #################
        #THE PREAMBLE
        ################
        #print 'preamble'
        while rec[0][0]=="#": #preamble
            if rec[0]=="#perturbed": #experiment serie name followed by list of its perturbed vertices
                pert.append((rec[1],rec[2:]))
            elif rec[0]=="#regulators": #list of potential regulators of all vertices (except specified in #parents command) not specified in previous or present #regulators command
                reg=filter(lambda x:True not in map(lambda r:x in r,regul),rec[1:])
                for i,r in enumerate(reg):
                    if i!=reg.index(r):
                        reg[i]=None
                reg=filter(None,reg)
                regul.append(reg)
            elif rec[0]=="#parents": #vertice name followed by list of its potential regulators
                reg=rec[2:]
                for i in range(len(reg)):
                    if i!=reg.index(reg[i]):
                        reg[i]=None
                reg=filter(None,reg)
                self.parents[rec[1]]=reg
            elif rec[0]=="#default": #list of default possible values or 'FLOAT' for default continuous values
                if len(rec)==2 and rec[1]=="FLOAT":
                    self.default=[]
                else:
                    self.default=rec[1:]
            elif rec[0]=="#discrete": #vertice name followed by list of all its possible values
                disc[rec[1]]=rec[2:]
            elif rec[0]=="#continuous": #list of vertices with continuous values
                contin.extend(rec[1:])
            elif rec[0]=="#and": #list of vertices with cpd noisy-and
                cpd['and'].extend(rec[1:])
            elif rec[0]=="#or": #list of vertices with cpd noisy-or
                cpd['or'].extend(rec[1:])
            elif rec[0]=="#priorvert": #prior weight of all outgoing edges (except specified in #prioredge command) followed by list of related vertices
                val=float(rec[1])
                for name in rec[2:]:
                    self.prior[name]=val
            elif rec[0]=="#prioredge": #prior weight of edges preceded by edge target (common for all specified edges) and followed by list of edge sources
                target_name=rec[1]
                val=float(rec[2])
                for name in rec[3:]:
                    self.prior[(target_name,name)]=val
            else:
                pass #ignoring other stuff
            rec=[]
            while not rec:
                ln=file.readline()
                rec=ln.strip().split()
        self.regulators=regul
        
        ################
        #condition names
        ################
        #print 'conditions'
        if rec[0]!='conditions':
            self.name=rec[0]
        #dynamic or static data?
        if len(rec[1].split(":"))==1:
            self.static=True
        #else:
        #    self.static=False
        if self.static:
            conds=map(lambda x : [x],rec[1:])
            names=conds
        else:
            # todo:pbd serie not used any more?
            serie=rec[1].split(":")[0]
            # pbd: explain why conds is a singleton and not just a single standalone element
            cond=[rec[1].split(":")[1]]
            conds=[cond]
            names=[serie]
            for c in rec[2:]:
                r=c.split(":")
                if r[0]==serie:
                    cond.append(r[1])
                else:
                    serie=r[0]
                    names.append(serie)
                    cond=[r[1]]
                    conds.append(cond)
            # pbd: after that conds looks like [['1', '2', '3', ...]]
        
        ln=file.readline()
        ###################
        # EXPRESSION DATA #
        ###################
        #print 'expression'
        self.vertices=[]
        self.vertice_names=[]
        #prepare the lists for expression data
        n_cond=sum(map(len,conds))
        exp_data=map(lambda l: [ [] for el in l],conds)
        geneindex=0
        while ln: #expression data
            rec=ln.strip().split()
            if not rec:
                ln=file.readline()
                continue
            name=rec[0]
            vals=[]
            #are the values discrete or not?
            if name in contin:#did we say it's continuous?
                discrete=False
            elif name in disc.keys(): #did we say is's discrete?
                discrete=True
                vals=disc[name]
            elif self.default!=None:# did we specify a default
                if self.default==[]:
                    discrete=False
                else:
                    discrete=True
                    vals=self.default
            else: #We did not specify the type of values for this gene, try to guess it
                try:
                    i = int(rec[1])
                except ValueError: #it's not an integer
                    try:
                        i=float(rec[1])
                    except ValueError: #it's not a float -> it's an alphanumerical value
                        discrete=True
                    else:
                        discrete=False # it's a float
                else:
                    discrete=True #it's an int, let's map the strings to ints
            if discrete:
                line=rec[1:]
                if vals==[]:#we don't know what are the possible values
                    for v in line:
                        if v not in vals:
                            vals.append(v)
                    #let's sort the values
                    vals.sort()
            else: #not discrete
                line=map(float,rec[1:])
                float_params=[]
                if float_transform: #use float_transform if specified
                    float_params=float_transform.estimate(line)
                    line=float_transform.transform(line)
                
            self.vertices.append(gene(name,len(vals),geneindex))
            self.vertices[-1].values=vals
            if discrete==False:
                self.vertices[-1].floatParams=float_params
            else:
                self.vertices[-1].floatParams=None
            self.vertice_names.append(name)
            #append this line to the expression data
            for el,l in zip(line,chain(*exp_data)):
                if discrete:
                    l.append(vals.index(el))
                else:
                    l.append(el)
            ln=file.readline()
            geneindex+=1
        ################
        #POSTPROCESSING#
        ################
        #print 'postprocessing'
        #process regulatory constraints
        for v in self.vertices:
            #specify cpd
            if v.name in cpd['and']:
                v.cpd='and'
            elif v.name in cpd['or']:
                v.cpd='or'
        #process the lists of perturbed genes and make the list of experiments
        for i,(c,n) in enumerate(zip(conds,names)):
            pg = []
            for x,p in pert:
                if x==n:
                    pg.extend(map(lambda n: self.vertices[self.vertice_names.index(n)],p))# map gene names to indexes
            
            self.points.append(experiment(n,exp_data[i],pg,c,self.static))

        #set the number of genes
        self.n_gen=geneindex
        return self

    def select_1(self,vertex,parents=None):
        """
        given a vertice and a list of his parents,
        select parents values at time t
        and child value at time t+1,
        omitting the experiments where the child was perturbed.
        """
        if parents is None:
            parents = self.get_potential_parents(vertex)
        #assign weights to parents
        weights={}
        for par in parents:
            weight=float(max(self.cont_weight,par.n_disc))
            if (vertex.name,par.name) in self.prior.keys():
                weight=weight**self.prior[(vertex.name,par.name)]
            elif par.name in self.prior.keys():
                weight=weight**self.prior[par.name]
            weights[par]=weight
        parents.sort(key = lambda p: weights[p])

        res = []
        for expment in self.points:
            if vertex not in expment.perturbed:
                for sample in expment.data:
                    res.append([sample[par] for par in parents]+[sample[vertex+self.n_gen]])
        return dataset_1(zip(*res),vertex,parents,weights)

    # pbd:debug there are some inconsistencies between this version of getting potential parents and
    # the one implemented in bnfinder-waszczuk branch - see how it works on input3.txt or input4.txt 
    def get_potential_parents(self, node):
        """selects potential parents for node"""
        if self.parents.has_key(node.name):
            parents=map(lambda n: self.vertices[self.vertice_names.index(n)],self.parents[node.name])
        else:
            #maybe there are regulators specified
            if self.regulators==[]:
                parents=[v for v in self.vertices]
            else:#there is a list of regulators
                parents=[]
                for r in self.regulators:
                    if node.name in r:
                        break
                    else:
                        parents.extend(map(lambda n: self.vertices[self.vertice_names.index(n)],r))
        return parents

 
    def learn(self,score,data_factor,prior=None,distrib=False,\
            verbose=None,n_min=1,limit=None,min_empty=None,min_optim=None,fdr_weights=None):
        
        scr=score
        
        if distrib:
            try:
                from multiprocessing import Pool#,Queue
            except ImportError:
                print "Problem invoking multiprocessing module. Running on a single CPU"
                distrib=False
                #from Queue import Queue
            else:
                pool=Pool(distrib)
                mapf=pool.map


        score = 0.0
        pars ={}
        subpars ={}


        # fdr weights have higher priority than self.prior
        # if both set - ignore later one
        if self.prior and fdr_weights:
            print "Warning: fdr parameter set - ignoring priors in dataset"

        if fdr_weights:
            self.prior = {}
            for k in fdr_weights:
                self.prior[k] = log(fdr_weights[k])

        if distrib:
            from itertools import izip,repeat
            result=pool.map(learn_x,[(x,self,min_empty,min_optim,scr,verbose,n_min,limit,score) for x in self.vertices])
                            #eval_func,izip([learn_x]*len(self.vertices),self.vertices))
            #pool.close() #raises weird errors...., not necessary in normal bnf operation, since the process exits after learning
        else:
            result=map(learn_x,[(x,self,min_empty,min_optim,scr,verbose,n_min,limit,score) for x in self.vertices])

        #print result
        self.vertices=[r[0] for r in result]
        
        for x,par,minlist,score in result:
            pars[x]=par
            subpars[x]=minlist
            score+=score
        #print pars
        #move parents from a dictionary to a list
        par_list = []
        for v in self.vertices:
            try:
                par_list.append(pars[v.__index__()])
            except KeyError:
                print "ERRR",par_list,v.name,id(v),v.__index__(),[map(lambda x:(x.name,id(x)),l) for l in pars.values()]
        pars=par_list

        g = graph.graph()
        g.fromParents(self.vertices ,pars)
        for v,par in zip(self.vertices,pars):
            g.vertice_labelling[v]=v.name
            v_orientation=self.select_1(v,par).orientation()
            for p in par:
                g.edge_labelling[p,v]=v_orientation[p]
        return score,g,subpars
   
    def get_stats(self,g):
        stats={}
        for v in self.vertices:
            stats[v] = self.select_1(v,g.parents(v)).stats()
        return stats
        
    def write_txt(self,subpars,file_name):
        """Outputs suboptimal parents sets into a text file
        """

        f=open(file_name,"w")
        for v,minlist in subpars.items():
            f.write('\n%s'% v.name)
            for prob, pars in minlist:
                prob_s=util.safe_exponent(prob)
                f.write('\n %s '% prob_s)
                for p in pars:
                    f.write(' %s'% p.name)
        f.close()

    def cpd_andor(self,n_par,stats_all,stats_par,prod_in,eps=10**(-7)):
        n_all=n_par+1
        epsilon=eps*n_all*10
        v_all=0.0
        v_in=0.0
        for all,val in stats_all.items():
            v_all+=val
            if all[-1]==prod_in:
                v_in+=val
        if v_all-v_in<=eps:
            return [0.0]*n_all
        elif v_in<=eps:
            return [1.0]*n_all
        p=[1.0-eps]*n_all
        delta=epsilon+1.0
        while delta>epsilon:
            delta=0.0
            for i in range(n_all):
                p_old=p[i]
                delt=eps+1.0
                while abs(delt)>eps:
                    sumf=0.0
                    sumfprim=0.0
                    for par in stats_par.keys():
                        all=par+(prod_in,)
                        if all[i]==prod_in:
                            if all in stats_all.keys():
                                prod=1.0
                                for j in range(n_all):
                                    if all[j]==prod_in:
                                        prod*=p[j]
                                q=stats_all[all]/(1.0-prod)
                                sumf+=q-stats_par[par]
                                sumfprim+=(q/(1.0-prod))*(prod/p[i])
                            else:
                                sumf-=stats_par[par]
                    delt=sumf/sumfprim
                    p[i]-=delt
                    if p[i]>=1.0:
                        delt=1.0-eps-p[i]-delt
                        p[i]=1.0-eps
                delta+=abs(p[i]-p_old)
        return p

    def to_cpd(self,g,dirichlet=None):
        if dirichlet==None:
            dirichlet=1.0
        else:
            dirichlet=float(dirichlet)
        result={}
        for v in self.vertices:
            if True:#if v.name not in self.regulators[0]:
                n_disc=max(2,v.n_disc)
                stats_all,stats_par = self.select_1(v,g.parents(v)).stats()
                var_d={}
                result[str(v)]=var_d
                var_d["vals"]=v.values
                var_d["pars"]=map(str,g.parents(v))
                var_d["floatParams"]=str(v.floatParams)
                if v.cpd=='and':
                    var_d["type"]="and"
                elif v.cpd=='or':
                    var_d["type"]="or"
                var_cpds={}
                var_d["cpds"]=var_cpds
                if v.cpd:
                    p=self.cpd_andor(len(g.parents(v)),stats_all,stats_par,v.cpd=='or')
                    for (i,pv) in enumerate(p[:-1]):
                        var_cpds[str(g.parents(v)[i])]=pv
                    var_cpds[None]=p[-1]
                else:
                    stats_values={}
                    for par_val in stats_par.keys():
                        stats_values[par_val]=[]
                    for all_val in stats_all.keys():
                        stats_values[all_val[:-1]].append(all_val[-1])
                    for par_val in stats_par.keys():
                        d={}
                        var_cpds[par_val]=d
                        for val in stats_values[par_val]:
                            d[val]=(stats_all[par_val+(val,)]+dirichlet)/(stats_par[par_val]+dirichlet*n_disc)
                        d[None]=dirichlet/(stats_par[par_val]+dirichlet*n_disc)
                    var_cpds[None]= 1.0/n_disc
        return result

        
        
    def write_cpd(self,g,f,dirichlet=None):
        if dirichlet==None:
            dirichlet=1.0
        else:
            dirichlet=float(dirichlet)
        f.write('{\n')
        for v in self.vertices:
            n_disc=max(2,v.n_disc)
            stats_all,stats_par = self.select_1(v,g.parents(v)).stats()
            f.write('\''+str(v)+'\' : {\n')
            f.write('  \'vals\' : '+str(v.values)+' ,\n')
            f.write('  \'pars\' : '+str(map(str,g.parents(v)))+' ,\n')
            if v.cpd=='and':
                f.write('  \'type\' : \'and\' ,\n')
            elif v.cpd=='or':
                f.write('  \'type\' : \'or\' ,\n')
            f.write('  \'cpds\' : {\n')
            if v.cpd:
                p=self.cpd_andor(len(g.parents(v)),stats_all,stats_par,v.cpd=='or')
                for (i,pv) in enumerate(p[:-1]):
                    f.write('     '+str(g.parents(v)[i])+' : '+str(pv)+'\n')
                f.write('     '+str(v)+' : '+str(p[-1])+' } } ,\n')
            else:
                stats_values={}
                for par_val in stats_par.keys():
                    stats_values[par_val]=[]
                for all_val in stats_all.keys():
                    stats_values[all_val[:-1]].append(all_val[-1])
                for par_val in stats_par.keys():
                    f.write('     '+str(par_val)+' : { ')
                    for val in stats_values[par_val]:
                        f.write(str(val)+' : '+str((stats_all[par_val+(val,)]+dirichlet)/(stats_par[par_val]+dirichlet*n_disc))+' , ')
                    f.write('None : '+str(dirichlet/(stats_par[par_val]+dirichlet*n_disc))+' } ,\n')
                f.write('     None : '+str(1.0/n_disc)+' } } ,\n')
        f.write('}\n')


    def write_bif(self,g,file_name,dirichlet=None,comments=[]):
        if dirichlet==None:
            dirichlet=1.0
        else:
            dirichlet=float(dirichlet)
        f=open(file_name,"w")
        f.write('\\\\ File generated by BNfinder\n')
        import time
        f.write('\\\\ '+time.strftime('%x %X')+'\n')
        for com in comments:
            f.write('\\\\ '+com+'\n')
        f.write('\\\\ Conditional probability distributions generated with total pseudocounts number %f\n' % dirichlet)
        f.write('\n')
        f.write('network \"%s\" {}\n' % self.name)
        f.write('\n')
        for v in self.vertices:
            n_disc=max(2,v.n_disc)
            stats_all,stats_par = self.select_1(v,g.parents(v)).stats()
            f.write('variable \"%s\" {\n' % str(v))
            f.write('   type discrete[%d] { \"%s\" }\n'% (n_disc,'\" \"'.join(map(str,v.disc_vals()))))
            f.write('    }\n')
            if g.parents(v):
                f.write('probability ( \"%s\" | \"%s\" ) {\n'% (str(v),'\" \"'.join(map(str,g.parents(v)))))
                f.write('     default %s ;\n' % ' '.join([str(1.0/n_disc)]*n_disc))
            else:
                f.write('probability ( \"%s\" ) {\n'% str(v))
            stats_values={}
            for par_val in stats_par.keys():
                stats_values[par_val]=[]
            for all_val in stats_all.keys():
                stats_values[all_val[:-1]].append(all_val[-1])
            for par_val in stats_par.keys():
                if par_val:
                    f.write('     ( \"%s\" ) ' % '\" \"'.join(map(str,par_val)))
                else:
                    f.write('     table ')
                for val in v.disc_vals():
                    if val in stats_values[par_val]:
                        f.write(str((stats_all[par_val+(val,)]+dirichlet)/(stats_par[par_val]+dirichlet*n_disc))+' ')
                    else:
                        f.write(str(dirichlet/(stats_par[par_val]+dirichlet*n_disc))+' ')
                f.write(';\n')
            f.write('    }\n\n')
        f.close()

    def point2dict(self,i):
        """returns a dictionary representing one (i-th) data-point
        """
        return dict(zip(self.vertice_names+map(lambda x: x+"'",self.vertice_names),self.points[i].data[0]))
    def get_vertice_by_name(self,name):
        """Returns vertex of self corresponding to the given name
        """
        return self.vertices[self.vertice_names.index(name)]
    
    def get_probs(self,d,cpd,pars):
        """returns list of posterior probabilities of all parents being in all possible states.

        for discrete variables gives singular distributions, important for continuous vars.
        """
        res=[]
        import collections
        for p in pars:
            v=self.get_vertice_by_name(p)
            if str(d[p]) in v.values:
                dd=collections.defaultdict(float)
                dd[d[p]]=1.0
                res.append(dd) #singular distribution for discrete vars
            else: #continuous vars need care
                model=continuous.BiNormMeans()
                model.fromParams(eval(cpd[p]["floatParams"]))
                x=model.transform([d[p]])[0] # x -> probability of parent being 1
                res.append({1:x,0:1-x})
#        print res
#        a=b
        return res
                
            
    def classify(self, cpd,f,ML=False,prob=None):
        """
        Output the classification based on the regulators' values from dataset and probabilities from a cpd.

        if ML==True:
           output only the most likely value
        else: (Default)
           output posterior probabilities
        if prob:
           output only the probability of a sigle class
        """
        f.write("classes")
        for p in self.points:
            f.write("\t%s"%p.name[0])
        f.write("\n")
        dicts=map(self.point2dict,range(len(self.points)))
        for v in cpd.keys():
            if v not in self.vertice_names:
                f.write("%s\t"%v)
                c=cpd[v]
                for i,p in enumerate(self.points):
                    total_prob={}
                    for val in c["vals"]:
                        total_prob[int(val)]=0.0
                    probs=self.get_probs(dicts[i],cpd,c["pars"])
                    for possib,poster in c["cpds"].items(): #possible parent values
                        if possib!=None:
                            r=reduce(lambda x,y:x*y,[d[x] for (x,d) in zip(possib,probs)],1)
                            for val in total_prob.keys():
                                try:
                                    total_prob[val]+=r*poster[val]
                                except KeyError:
                                    pass
                                
                    ret_d=total_prob
                    #print ret_d
                    if ML: # Max likelihod
                        f.write("%s\t"%(str(sorted(zip(*reversed(zip(*(ret_d.items())))))[-1][-1]))) # pick the arg max from ret_d
                    elif prob!=None: # the probability of a given value
                        try:
                            f.write("%s\t"%(str(ret_d[prob])))
                        except KeyError:
                            f.write("%s\t"%"-0.0")
                    else: # output whole posterior
                        f.write("%s\t"%(str(ret_d)))
                    #par_vals=tuple(map(lambda x: dicts[i][x],c["pars"]))
                    #try:
                    #    ret_d=c["cpds"][par_vals]
                    #except KeyError:
                        #testing, to be removed
                        #print"*",
                        #model=continuous.BiNormMeans()
                        #print cpd[c["pars"][0]]["floatParams"]
                        #model.fromParams(eval(cpd[c["pars"][0]]["floatParams"]))
                        #x=model.transform(par_vals)[0] # x -> probability of parent being 1
                        #p=x*c["cpds"][1,][1]+(1-x)*c["cpds"][0,][1]
                        #f.write("%f\t"%(p))
                    #    f.write("%s\t"%"0.0")
                    #else:
#                         if ML: # Max likelihod
#                             f.write("%s\t"%(str(sorted(zip(*reversed(zip(*(ret_d.items())))))[-1][-1]))) # pick the arg max from ret_d
#                         elif prob!=None: # the probability of a given value
#                             try:
#                                 f.write("%s\t"%(str(ret_d[prob])))
#                             except KeyError:
#                                 f.write("%s\t"%"0.0")
#                         else: # output whole posterior
#                             f.write("%s\t"%(str(ret_d)))
                f.write("\n")
    def classify_list(self, cpd,ML=False,prob=None):
        """
        Output the classification based on the regulators' values from dataset and probabilities from a cpd.

        if ML==True:
           output only the most likely value
        else: (Default)
           output posterior probabilities
        if prob:
           output only the probability of a sigle class
        """
        result={}
        dicts=map(self.point2dict,range(len(self.points)))
        for v in cpd.keys():
            if v not in self.vertice_names:
                c=cpd[v]
                for i,p in enumerate(self.points):
                    total_prob={}
                    for val in c["vals"]:
                        total_prob[int(val)]=0.0
                    probs=self.get_probs(dicts[i],cpd,c["pars"])
                    for possib,poster in c["cpds"].items(): #possible parent values
                        if possib!=None:
                            r=reduce(lambda x,y:x*y,[d[x] for (x,d) in zip(possib,probs)],1)
                            for val in total_prob.keys():
                                try:
                                    total_prob[val]+=r*poster[val]
                                except KeyError:
                                    pass
                                
                    ret_d=total_prob
                    #print p.name[0],v
                    if ML: # Max likelihod
                        result[p.name[0],v]=sorted(zip(*reversed(zip(*(ret_d.items())))))[-1][-1] # pick the arg max from ret_d
                    elif prob!=None: # the probability of a given value
                        try:
                            result[p.name[0],v]=(ret_d[prob])
                        except KeyError:
                            result[p.name[0],v]=0.0
                    else: # output whole posterior
                        result[p.name[0],v]=ret_d
        return result
        
    
if __name__=="__main__":
    import sys
    try:
        new=open(sys.argv[2],"w")
        old=open(sys.argv[1])
        dataset().fromFile(old,None).toNewFile(new)
        new.close()
        old.close()
    except:
        print "As a quick and dirty solution for transforming old-style files into new style input files you may just run:\n python data.py old_file.txt new_file.txt"
